diff --git a/packages/react/src/components/Popovers/GenericPopover.tsx b/packages/react/src/components/Popovers/GenericPopover.tsx index 90c4fd3f94..34dd9751ef 100644 --- a/packages/react/src/components/Popovers/GenericPopover.tsx +++ b/packages/react/src/components/Popovers/GenericPopover.tsx @@ -8,10 +8,20 @@ import { autoUpdate, useHover, } from "@floating-ui/react"; -import { HTMLAttributes, ReactNode, useEffect, useRef } from "react"; +import { + createContext, + HTMLAttributes, + ReactNode, + useEffect, + useRef, +} from "react"; import { FloatingUIOptions } from "./FloatingUIOptions.js"; +export const GenericPopoverUpdateContext = createContext< + (() => void) | undefined +>(undefined); + export type GenericPopoverReference = | { // A DOM element to use as the reference element for the popover. @@ -81,10 +91,12 @@ export const GenericPopover = ( children: ReactNode; }, ) => { - const { refs, floatingStyles, context } = useFloating({ - whileElementsMounted: autoUpdate, - ...props.useFloatingOptions, - }); + const { refs, floatingStyles, context, update } = useFloating( + { + whileElementsMounted: autoUpdate, + ...props.useFloatingOptions, + }, + ); const { isMounted, styles } = useTransitionStyles( context, @@ -167,17 +179,21 @@ export const GenericPopover = ( // should be open. So without this fix, the popover just won't transition // out and will instead appear to hide instantly. return ( -
+ +
+ ); } return ( -
- {props.children} -
+ +
+ {props.children} +
+
); }; diff --git a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx index 08db23e7e4..ca84639661 100644 --- a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx +++ b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx @@ -1,6 +1,6 @@ import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; import { SuggestionMenu } from "@blocknote/core/extensions"; -import { autoPlacement, offset, shift, size } from "@floating-ui/react"; +import { flip, offset, shift, size } from "@floating-ui/react"; import { FC, useEffect, useMemo } from "react"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; @@ -119,14 +119,14 @@ export function GridSuggestionMenuController< offset(10), // Flips the menu placement to maximize the space available, and prevents // the menu from being cut off by the confines of the screen. - autoPlacement({ - allowedPlacements: ["bottom-start", "top-start"], + flip({ + crossAxis: false, padding: 10, }), shift(), size({ apply({ elements, availableHeight }) { - elements.floating.style.maxHeight = `${Math.max(0, availableHeight)}px`; + elements.floating.style.maxHeight = `${Math.min(600, availableHeight)}px`; }, padding: 10, }), diff --git a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx index 173b36c4ea..4db2640e83 100644 --- a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx +++ b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx @@ -1,8 +1,9 @@ import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; -import { FC, useCallback, useEffect } from "react"; +import { FC, useCallback, useContext, useEffect } from "react"; import { useBlockNoteContext } from "../../../editor/BlockNoteContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; +import { GenericPopoverUpdateContext } from "../../Popovers/GenericPopover.js"; import { useCloseSuggestionMenuNoItems } from "../hooks/useCloseSuggestionMenuNoItems.js"; import { useLoadSuggestionMenuItems } from "../hooks/useLoadSuggestionMenuItems.js"; import { useGridSuggestionMenuKeyboardNavigation } from "./hooks/useGridSuggestionMenuKeyboardNavigation.js"; @@ -49,6 +50,17 @@ export function GridSuggestionMenuWrapper(props: { getItems, ); + // If this component is used inside a `GenericPopover`, the position of the + // popover should be recalculated once all the items are fetched. This is + // because it may need to resize/shift/flip if the height of the suggestion + // menu with all items is too tall and overflows. + const update = useContext(GenericPopoverUpdateContext); + useEffect(() => { + update?.(); + }, [loadingState, update]); + + useCloseSuggestionMenuNoItems(items, usedQuery, closeMenu); + useCloseSuggestionMenuNoItems(items, usedQuery, closeMenu); const { selectedIndex } = useGridSuggestionMenuKeyboardNavigation( diff --git a/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx b/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx index 8b16805c16..67c1be9415 100644 --- a/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx +++ b/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx @@ -3,7 +3,7 @@ import { SuggestionMenu as SuggestionMenuExtension, filterSuggestionItems, } from "@blocknote/core/extensions"; -import { autoPlacement, offset, shift, size } from "@floating-ui/react"; +import { flip, offset, shift, size } from "@floating-ui/react"; import { FC, useEffect, useMemo } from "react"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; @@ -114,14 +114,14 @@ export function SuggestionMenuController< offset(10), // Flips the menu placement to maximize the space available, and prevents // the menu from being cut off by the confines of the screen. - autoPlacement({ - allowedPlacements: ["bottom-start", "top-start"], + flip({ + crossAxis: false, padding: 10, }), shift(), size({ apply({ elements, availableHeight }) { - elements.floating.style.maxHeight = `${Math.max(0, availableHeight)}px`; + elements.floating.style.maxHeight = `${Math.min(600, availableHeight)}px`; }, padding: 10, }), diff --git a/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx b/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx index 391bcb1b34..dd6fb1c158 100644 --- a/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx +++ b/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx @@ -1,8 +1,9 @@ import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; -import { FC, useCallback, useEffect } from "react"; +import { FC, useCallback, useContext, useEffect } from "react"; import { useBlockNoteContext } from "../../editor/BlockNoteContext.js"; import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; +import { GenericPopoverUpdateContext } from "../Popovers/GenericPopover.js"; import { useCloseSuggestionMenuNoItems } from "./hooks/useCloseSuggestionMenuNoItems.js"; import { useLoadSuggestionMenuItems } from "./hooks/useLoadSuggestionMenuItems.js"; import { useSuggestionMenuKeyboardNavigation } from "./hooks/useSuggestionMenuKeyboardNavigation.js"; @@ -47,6 +48,15 @@ export function SuggestionMenuWrapper(props: { getItems, ); + // If this component is used inside a `GenericPopover`, the position of the + // popover should be recalculated once all the items are fetched. This is + // because it may need to resize/shift/flip if the height of the suggestion + // menu with all items is too tall and overflows. + const update = useContext(GenericPopoverUpdateContext); + useEffect(() => { + update?.(); + }, [loadingState, update]); + useCloseSuggestionMenuNoItems(items, usedQuery, closeMenu); const { selectedIndex } = useSuggestionMenuKeyboardNavigation( diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png index 9dee39cb3b..244e4f1f5a 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png differ diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png index 9c7689e33a..8940c72206 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png differ diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png index 35c7f334b2..0e846a8c79 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png index a8092107da..8f8b8bf658 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png index 2d8d62515d..c40d5a96b5 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png index 8d118038d6..8508bb5c01 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png index 1f1fdcbe8b..03722e23a8 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png index 2f2b968c0a..ab1d485ae7 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png index 644b440fdd..888b550d03 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png differ