diff --git a/examples/07-collaboration/05-comments/src/SettingsSelect.tsx b/examples/07-collaboration/05-comments/src/SettingsSelect.tsx
index 0dfc79dc3f..683d19de84 100644
--- a/examples/07-collaboration/05-comments/src/SettingsSelect.tsx
+++ b/examples/07-collaboration/05-comments/src/SettingsSelect.tsx
@@ -1,4 +1,8 @@
-import { ComponentProps, useComponentsContext } from "@blocknote/react";
+import {
+ ComponentProps,
+ useComponentsContext,
+ useEditorPortalElement,
+} from "@blocknote/react";
// This component is used to display a selection dropdown with a label. By using
// the useComponentsContext hook, we can create it out of existing components
@@ -9,6 +13,10 @@ export const SettingsSelect = (props: {
items: ComponentProps["FormattingToolbar"]["Select"]["items"];
}) => {
const Components = useComponentsContext()!;
+ // The select's dropdown portals into the editor's portal element, which keeps
+ // it themed and clear of any overflow clipping. The prop is required, so it
+ // can't be left out by accident.
+ const editorPortalElement = useEditorPortalElement();
return (
@@ -17,6 +25,7 @@ export const SettingsSelect = (props: {
diff --git a/examples/07-collaboration/06-comments-with-sidebar/src/SettingsSelect.tsx b/examples/07-collaboration/06-comments-with-sidebar/src/SettingsSelect.tsx
index 0dfc79dc3f..683d19de84 100644
--- a/examples/07-collaboration/06-comments-with-sidebar/src/SettingsSelect.tsx
+++ b/examples/07-collaboration/06-comments-with-sidebar/src/SettingsSelect.tsx
@@ -1,4 +1,8 @@
-import { ComponentProps, useComponentsContext } from "@blocknote/react";
+import {
+ ComponentProps,
+ useComponentsContext,
+ useEditorPortalElement,
+} from "@blocknote/react";
// This component is used to display a selection dropdown with a label. By using
// the useComponentsContext hook, we can create it out of existing components
@@ -9,6 +13,10 @@ export const SettingsSelect = (props: {
items: ComponentProps["FormattingToolbar"]["Select"]["items"];
}) => {
const Components = useComponentsContext()!;
+ // The select's dropdown portals into the editor's portal element, which keeps
+ // it themed and clear of any overflow clipping. The prop is required, so it
+ // can't be left out by accident.
+ const editorPortalElement = useEditorPortalElement();
return (
@@ -17,6 +25,7 @@ export const SettingsSelect = (props: {
diff --git a/examples/07-collaboration/11-versioning-yjs13/src/SettingsSelect.tsx b/examples/07-collaboration/11-versioning-yjs13/src/SettingsSelect.tsx
index 0dfc79dc3f..683d19de84 100644
--- a/examples/07-collaboration/11-versioning-yjs13/src/SettingsSelect.tsx
+++ b/examples/07-collaboration/11-versioning-yjs13/src/SettingsSelect.tsx
@@ -1,4 +1,8 @@
-import { ComponentProps, useComponentsContext } from "@blocknote/react";
+import {
+ ComponentProps,
+ useComponentsContext,
+ useEditorPortalElement,
+} from "@blocknote/react";
// This component is used to display a selection dropdown with a label. By using
// the useComponentsContext hook, we can create it out of existing components
@@ -9,6 +13,10 @@ export const SettingsSelect = (props: {
items: ComponentProps["FormattingToolbar"]["Select"]["items"];
}) => {
const Components = useComponentsContext()!;
+ // The select's dropdown portals into the editor's portal element, which keeps
+ // it themed and clear of any overflow clipping. The prop is required, so it
+ // can't be left out by accident.
+ const editorPortalElement = useEditorPortalElement();
return (
@@ -17,6 +25,7 @@ export const SettingsSelect = (props: {
diff --git a/packages/ariakit/src/menu/Menu.tsx b/packages/ariakit/src/menu/Menu.tsx
index 177dc37f73..f1a5c527c0 100644
--- a/packages/ariakit/src/menu/Menu.tsx
+++ b/packages/ariakit/src/menu/Menu.tsx
@@ -15,9 +15,7 @@ import { createContext, forwardRef, useContext } from "react";
// Threads the `portalRoot` override from `Menu` (the provider) down to
// `MenuDropdown`, where ariakit's `portalElement` prop actually lives.
-const PortalRootContext = createContext(
- undefined,
-);
+const PortalRootContext = createContext(null);
export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
const {
@@ -25,6 +23,9 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
onOpenChange,
position,
portalRoot,
+ // ariakit's `virtualFocus` keeps DOM focus on the editor (roving via
+ // `aria-activedescendant`), so there is no focus to suppress here.
+ preventFocusOnOpen: _preventFocusOnOpen,
sub: _sub, // unused
...rest
} = props;
@@ -63,7 +64,10 @@ export const MenuDropdown = forwardRef<
{children}
diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx
index df8e01128b..93d2d8141b 100644
--- a/packages/ariakit/src/popover/Popover.tsx
+++ b/packages/ariakit/src/popover/Popover.tsx
@@ -8,9 +8,7 @@ import { assertEmpty, mergeCSSClasses } from "@blocknote/core";
import { ComponentProps } from "@blocknote/react";
import { createContext, forwardRef, useContext } from "react";
-const PortalRootContext = createContext(
- undefined,
-);
+const PortalRootContext = createContext(null);
export const PopoverTrigger = forwardRef<
HTMLButtonElement,
@@ -40,7 +38,10 @@ export const PopoverContent = forwardRef<
className || "",
variant === "panel-popover" ? "bn-ak-panel-popover" : "",
)}
- portalElement={portalRoot ?? undefined}
+ // Ariakit falls back to a body-appended div for a missing element, so
+ // don't portal at all until there is one (editor not mounted yet).
+ portal={portalRoot !== null}
+ portalElement={portalRoot}
ref={ref}
>
{children}
@@ -51,7 +52,15 @@ export const PopoverContent = forwardRef<
export const Popover = (
props: ComponentProps["Generic"]["Popover"]["Root"],
) => {
- const { children, open, onOpenChange, position, portalRoot, ...rest } = props;
+ const {
+ children,
+ open,
+ onOpenChange,
+ position,
+ portalRoot,
+ preventFocusOnOpen: _preventFocusOnOpen, // unused; see Menu.tsx
+ ...rest
+ } = props;
assertEmpty(rest);
diff --git a/packages/ariakit/src/toolbar/ToolbarSelect.tsx b/packages/ariakit/src/toolbar/ToolbarSelect.tsx
index 26d817976e..4eca7d3a5f 100644
--- a/packages/ariakit/src/toolbar/ToolbarSelect.tsx
+++ b/packages/ariakit/src/toolbar/ToolbarSelect.tsx
@@ -16,7 +16,14 @@ export const ToolbarSelect = forwardRef<
HTMLDivElement,
ComponentProps["FormattingToolbar"]["Select"]
>((props, ref) => {
- const { className, items, isDisabled, portalRoot, ...rest } = props;
+ const {
+ className,
+ items,
+ isDisabled,
+ portalRoot,
+ preventFocusOnOpen: _preventFocusOnOpen, // unused; see Menu.tsx
+ ...rest
+ } = props;
assertEmpty(rest);
@@ -40,7 +47,10 @@ export const ToolbarSelect = forwardRef<
className={mergeCSSClasses("bn-ak-popover", className || "")}
ref={ref}
gutter={4}
- portalElement={portalRoot ?? undefined}
+ // Ariakit falls back to a body-appended div for a missing element,
+ // so don't portal at all until there is one (editor not mounted yet).
+ portal={portalRoot !== null}
+ portalElement={portalRoot}
>
{items.map((option) => (
(undefined);
export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
- const { children, onOpenChange, position, portalRoot, sub, ...rest } = props;
+ const {
+ children,
+ onOpenChange,
+ position,
+ portalRoot,
+ preventFocusOnOpen,
+ sub,
+ ...rest
+ } = props;
assertEmpty(rest);
@@ -38,9 +46,9 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
{
- const { open, onOpenChange, position, portalRoot, children, ...rest } = props;
+ const {
+ open,
+ onOpenChange,
+ position,
+ portalRoot,
+ preventFocusOnOpen,
+ children,
+ ...rest
+ } = props;
assertEmpty(rest);
@@ -20,9 +28,9 @@ export const Popover = (
middlewares={{ size: { padding: 20 } }}
withinPortal={!!portalRoot}
portalProps={portalRoot ? { target: portalRoot } : undefined}
- // Do not move focus to the dropdown on mobile, as it blurs the editor's
- // contentEditable and dismisses the on-screen keyboard.
- trapFocus={portalRoot ? false : undefined}
+ // Do not move focus to the dropdown when requested (mobile), as it blurs
+ // the editor's contentEditable and dismisses the on-screen keyboard.
+ trapFocus={preventFocusOnOpen ? false : undefined}
opened={open}
onChange={onOpenChange}
position={position}
diff --git a/packages/mantine/src/toolbar/ToolbarSelect.tsx b/packages/mantine/src/toolbar/ToolbarSelect.tsx
index 16f7023c16..567d723270 100644
--- a/packages/mantine/src/toolbar/ToolbarSelect.tsx
+++ b/packages/mantine/src/toolbar/ToolbarSelect.tsx
@@ -14,7 +14,14 @@ export const ToolbarSelect = forwardRef<
HTMLDivElement,
ComponentProps["FormattingToolbar"]["Select"]
>((props, ref) => {
- const { className, items, isDisabled, portalRoot, ...rest } = props;
+ const {
+ className,
+ items,
+ isDisabled,
+ portalRoot,
+ preventFocusOnOpen,
+ ...rest
+ } = props;
assertEmpty(rest);
@@ -32,9 +39,9 @@ export const ToolbarSelect = forwardRef<
exitDuration: 0,
}}
disabled={isDisabled}
- // Do not move focus to the dropdown on mobile, as it blurs the editor's
- // contentEditable and dismisses the on-screen keyboard.
- trapFocus={portalRoot ? false : undefined}
+ // Do not move focus to the dropdown when requested (mobile), as it blurs
+ // the editor's contentEditable and dismisses the on-screen keyboard.
+ trapFocus={preventFocusOnOpen ? false : undefined}
middlewares={{
flip: true,
shift: true,
diff --git a/packages/react/src/components/Comments/Comment.tsx b/packages/react/src/components/Comments/Comment.tsx
index d4a6df40d6..519f3ce431 100644
--- a/packages/react/src/components/Comments/Comment.tsx
+++ b/packages/react/src/components/Comments/Comment.tsx
@@ -18,6 +18,7 @@ import {
Components,
useComponentsContext,
} from "../../editor/ComponentsContext.js";
+import { useEditorPortalElement } from "../../editor/EditorPortalProvider.js";
import { useCreateBlockNote } from "../../hooks/useCreateBlockNote.js";
import { useExtension } from "../../hooks/useExtension.js";
import { useDictionary } from "../../i18n/dictionary.js";
@@ -161,6 +162,7 @@ export const Comment = ({
});
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const [isEditing, setEditing] = useState(false);
const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);
@@ -289,7 +291,10 @@ export const Comment = ({
))}
{(canDeleteComment || canEditComment) && (
-
+
{
const dict = useDictionary();
const uiMode = useUIMode();
const editorPortalElement = useEditorPortalElement();
- // Only portal (and suppress dropdown focus) in the mobile toolbar; desktop
- // renders inline with default focus behavior.
- const portalRoot =
- uiMode === "mobile" ? (editorPortalElement ?? undefined) : undefined;
const editor = useBlockNoteEditor<
BlockSchema,
InlineContentSchema,
@@ -145,14 +141,14 @@ export const ColorStyleButton = () => {
return (
{
const dict = useDictionary();
const uiMode = useUIMode();
const editorPortalElement = useEditorPortalElement();
- // Only portal (and suppress dropdown focus) in the mobile toolbar; desktop
- // renders inline with default focus behavior.
- const portalRoot =
- uiMode === "mobile" ? (editorPortalElement ?? undefined) : undefined;
const formattingToolbar = useExtension(FormattingToolbarExtension);
// eslint-disable-next-line @typescript-eslint/unbound-method -- showSelection is a plain object method, not a class method
@@ -136,13 +132,14 @@ export const CreateLinkButton = () => {
{/* TODO: hide tooltip on click */}
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx
index 7c59fbaeff..7058398840 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx
@@ -20,10 +20,6 @@ export const FileCaptionButton = () => {
const Components = useComponentsContext()!;
const uiMode = useUIMode();
const editorPortalElement = useEditorPortalElement();
- // Only portal (and suppress dropdown focus) in the mobile toolbar; desktop
- // renders inline with default focus behavior.
- const portalRoot =
- uiMode === "mobile" ? (editorPortalElement ?? undefined) : undefined;
const editor = useBlockNoteEditor<
BlockSchema,
@@ -112,13 +108,14 @@ export const FileCaptionButton = () => {
{
const Components = useComponentsContext()!;
const uiMode = useUIMode();
const editorPortalElement = useEditorPortalElement();
- // Only portal (and suppress dropdown focus) in the mobile toolbar; desktop
- // renders inline with default focus behavior.
- const portalRoot =
- uiMode === "mobile" ? (editorPortalElement ?? undefined) : undefined;
const editor = useBlockNoteEditor<
BlockSchema,
@@ -112,13 +108,14 @@ export const FileRenameButton = () => {
{
const Components = useComponentsContext()!;
const uiMode = useUIMode();
const editorPortalElement = useEditorPortalElement();
- // Only portal (and suppress dropdown focus) in the mobile toolbar; desktop
- // renders inline with default focus behavior.
- const portalRoot =
- uiMode === "mobile" ? (editorPortalElement ?? undefined) : undefined;
const editor = useBlockNoteEditor<
BlockSchema,
@@ -73,7 +69,14 @@ export const FileReplaceButton = () => {
editor.focus();
}
}}
- portalRoot={portalRoot}
+ // Portal the popover into the editor's themed portal target so it
+ // inherits styling and escapes any scroll-container overflow clipping.
+ // On mobile that target is the toolbar's body-level container (see
+ // `MobileFormattingToolbarController`), and `preventFocusOnOpen` stops
+ // focus moving into the popover, which would blur the editor and dismiss
+ // the on-screen keyboard.
+ portalRoot={editorPortalElement}
+ preventFocusOnOpen={uiMode === "mobile"}
>
{
const Components = useComponentsContext()!;
const uiMode = useUIMode();
const editorPortalElement = useEditorPortalElement();
- // Only portal (and suppress dropdown focus) in the mobile toolbar; desktop
- // renders inline with default focus behavior.
- const portalRoot =
- uiMode === "mobile" ? (editorPortalElement ?? undefined) : undefined;
const editor = useBlockNoteEditor<
BlockSchema,
@@ -220,7 +216,11 @@ export const BlockTypeSelect = (props: { items?: BlockTypeSelectItem[] }) => {
);
};
diff --git a/packages/react/src/components/LinkToolbar/DefaultButtons/EditLinkButton.tsx b/packages/react/src/components/LinkToolbar/DefaultButtons/EditLinkButton.tsx
index 52cef731f4..04f3233080 100644
--- a/packages/react/src/components/LinkToolbar/DefaultButtons/EditLinkButton.tsx
+++ b/packages/react/src/components/LinkToolbar/DefaultButtons/EditLinkButton.tsx
@@ -1,4 +1,5 @@
import { useComponentsContext } from "../../../editor/ComponentsContext.js";
+import { useEditorPortalElement } from "../../../editor/EditorPortalProvider.js";
import { useDictionary } from "../../../i18n/dictionary.js";
import { EditLinkMenuItems } from "../EditLinkMenuItems.js";
import { LinkToolbarProps } from "../LinkToolbarProps.js";
@@ -10,11 +11,13 @@ export const EditLinkButton = (
>,
) => {
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const dict = useDictionary();
return (
{
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const dict = useDictionary();
const sideMenu = useExtension(SideMenuExtension);
@@ -39,6 +41,7 @@ export const DragHandleButton = (
}
}}
position={"left"}
+ portalRoot={editorPortalElement}
>
{
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const editor = useBlockNoteEditor();
@@ -30,7 +32,11 @@ export const BlockColorsItem = (props: { children: ReactNode }) => {
}
return (
-
+
{
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const editor = useBlockNoteEditor();
@@ -45,6 +47,7 @@ export const TableCellButton = (
}
}}
position={"right"}
+ portalRoot={editorPortalElement}
>
diff --git a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx
index 36e1ecca2f..df96f2a6d6 100644
--- a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx
+++ b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx
@@ -3,6 +3,7 @@ import { TableHandlesExtension } from "@blocknote/core/extensions";
import { ReactNode } from "react";
import { useComponentsContext } from "../../../../editor/ComponentsContext.js";
+import { useEditorPortalElement } from "../../../../editor/EditorPortalProvider.js";
import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js";
import { useExtensionState } from "../../../../hooks/useExtension.js";
import { useDictionary } from "../../../../i18n/dictionary.js";
@@ -10,6 +11,7 @@ import { ColorPicker } from "../../../ColorPicker/ColorPicker.js";
export const ColorPickerButton = (props: { children?: ReactNode }) => {
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const dict = useDictionary();
const editor = useBlockNoteEditor();
@@ -74,7 +76,11 @@ export const ColorPickerButton = (props: { children?: ReactNode }) => {
}
return (
-
+
{
const editor = useBlockNoteEditor();
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const [isDragging, setIsDragging] = useState(false);
@@ -66,6 +68,7 @@ export const TableHandle = (
}
}}
position={"right"}
+ portalRoot={editorPortalElement}
>
{
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const dict = useDictionary();
const editor = useBlockNoteEditor<
{ table: DefaultBlockSchema["table"] },
@@ -104,7 +106,11 @@ export const ColorPickerButton = <
const firstCell = mapTableCell(currentCells[0].cell);
return (
-
+
{
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const { canPreviewCurrent, previewCurrentVersion, exitPreview } =
useExtension(VersioningExtension);
const selected = useExtensionState(VersioningExtension, {
@@ -72,7 +74,10 @@ export const CurrentSnapshot = ({
variant="action-toolbar"
className="bn-action-toolbar"
>
-
+
{
const Components = useComponentsContext()!;
+ const editorPortalElement = useEditorPortalElement();
const {
canRestore,
restore,
@@ -110,7 +112,10 @@ export const Snapshot = ({
variant="action-toolbar"
className="bn-action-toolbar"
>
-
+
{mainTooltip}
diff --git a/packages/shadcn/src/menu/Menu.tsx b/packages/shadcn/src/menu/Menu.tsx
index abe101b9fa..fd64f69740 100644
--- a/packages/shadcn/src/menu/Menu.tsx
+++ b/packages/shadcn/src/menu/Menu.tsx
@@ -1,13 +1,11 @@
import { assertEmpty } from "@blocknote/core";
-import { ComponentProps, useEditorPortalElement } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
import { ChevronRight } from "lucide-react";
import { createContext, forwardRef, ReactElement, useContext } from "react";
import { cn } from "../lib/utils.js";
import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js";
-const PortalRootContext = createContext(
- undefined,
-);
+const PortalRootContext = createContext(null);
export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
const {
@@ -15,6 +13,9 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
onOpenChange,
position: _position, // Unused
portalRoot,
+ // base-ui manages menu focus itself; unlike Mantine there is no focus to
+ // suppress, so this is intentionally unused.
+ preventFocusOnOpen: _preventFocusOnOpen,
sub,
...rest
} = props;
@@ -81,11 +82,11 @@ export const MenuDropdown = forwardRef<
const ShadCNComponents = useShadCNComponentsContext()!;
- const portalRoot = useContext(PortalRootContext);
- // Default to the ambient portal target (a themed `.bn-root`) so the menu
- // inherits light/dark mode instead of the document body's.
- const editorPortalElement = useEditorPortalElement();
- const container = portalRoot ?? editorPortalElement ?? undefined;
+ // The `portalRoot` supplied at the call site is a themed `.bn-root`, so the
+ // menu inherits light/dark mode instead of the document body's.
+ // `null` (editor not mounted yet) makes Base UI wait for a container
+ // instead of falling back to the body; nothing is open at that point.
+ const container = useContext(PortalRootContext);
if (sub) {
return (
diff --git a/packages/shadcn/src/popover/popover.tsx b/packages/shadcn/src/popover/popover.tsx
index 793530ce3b..886a7af479 100644
--- a/packages/shadcn/src/popover/popover.tsx
+++ b/packages/shadcn/src/popover/popover.tsx
@@ -1,13 +1,11 @@
import { assertEmpty } from "@blocknote/core";
-import { ComponentProps, useEditorPortalElement } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
import { createContext, forwardRef, ReactElement, useContext } from "react";
import { cn } from "../lib/utils.js";
import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js";
-const PortalRootContext = createContext(
- undefined,
-);
+const PortalRootContext = createContext(null);
export const Popover = (
props: ComponentProps["Generic"]["Popover"]["Root"],
@@ -18,6 +16,9 @@ export const Popover = (
onOpenChange,
position: _position, // unused
portalRoot,
+ // base-ui manages popover focus itself; unlike Mantine there is no focus to
+ // suppress, so this is intentionally unused.
+ preventFocusOnOpen: _preventFocusOnOpen,
...rest
} = props;
@@ -61,16 +62,17 @@ export const PopoverContent = forwardRef<
const ShadCNComponents = useShadCNComponentsContext()!;
- const portalRoot = useContext(PortalRootContext);
- // Default to the ambient portal target (a themed `.bn-root`) so popovers
- // inherit light/dark mode instead of the document body's, and escape the
- // mobile formatting toolbar's horizontal scroll clip.
- const editorPortalElement = useEditorPortalElement();
+ // The `portalRoot` supplied at the call site is a themed `.bn-root`, so
+ // popovers inherit light/dark mode instead of the document body's, and escape
+ // the mobile formatting toolbar's horizontal scroll clip.
+ // `null` (editor not mounted yet) makes Base UI wait for a container
+ // instead of falling back to the body; nothing is open at that point.
+ const container = useContext(PortalRootContext);
return (
(
// Portal the tooltip into the ambient portal target (a themed `.bn-root`)
// so it inherits the editor's light/dark color scheme instead of the
// document body's.
+ // NOTE: Only ShadCN Badge / Tooltip depend on useEditorPortalElement.
+ // Alternative would be to pass a portalElement to these components, but they
+ // would be ignored by ariakit / mantine. For now keep these two exceptions
+ // (ideally skin components don't have a dependency on the editor's context)
+
const editorPortalElement = useEditorPortalElement();
const trigger =
@@ -112,7 +117,7 @@ export const ToolbarButton = forwardRef(
{mainTooltip}
@@ -127,16 +132,21 @@ export const ToolbarSelect = forwardRef<
HTMLDivElement,
ComponentProps["FormattingToolbar"]["Select"]
>((props, ref) => {
- const { className, items, isDisabled, portalRoot, ...rest } = props;
+ const {
+ className,
+ items,
+ isDisabled,
+ portalRoot,
+ // base-ui manages select focus itself; unlike Mantine there is no focus to
+ // suppress, so this is intentionally unused.
+ preventFocusOnOpen: _preventFocusOnOpen,
+ ...rest
+ } = props;
assertEmpty(rest);
const ShadCNComponents = useShadCNComponentsContext()!;
- // Default to the ambient portal target (a themed `.bn-root`) so the dropdown
- // inherits light/dark mode instead of the body's.
- const editorPortalElement = useEditorPortalElement();
-
// TODO?
const SelectItemContent = (props: any) => (
@@ -164,7 +174,7 @@ export const ToolbarSelect = forwardRef<