diff --git a/docs/pages/docs/advanced/component-libraries.mdx b/docs/pages/docs/advanced/component-libraries.mdx deleted file mode 100644 index 9b11e720ce..0000000000 --- a/docs/pages/docs/advanced/component-libraries.mdx +++ /dev/null @@ -1,95 +0,0 @@ ---- -title: Using Other Component Libraries -description: While BlockNote's default UI uses the Mantine component library, you can configure it to use other libraries instead. -imageTitle: Use with Other Component Libraries -path: /docs/component-libraries ---- - -import { Callout } from "nextra/components"; -import { Example } from "@/components/example"; - -# Using Other Component Libraries - -While BlockNote's default UI uses the [Mantine](https://mantine.dev/) component library, you can configure it to use other libraries instead. - -## Using Ariakit/ShadCN - -BlockNote has plug & play support for [Ariakit](https://ariakit.org/) and [ShadCN](https://ui.shadcn.com/). You can switch to them just by importing `BlockNoteView` from either `@blocknote/ariakit` or `@blocknote/shadcn` instead of `@blocknote/mantine`, as well as the corresponding CSS file. - - - - - -## ShadCN Customization - -If you want BlockNote to use customized ShadCN components instead of the default ones, you can pass them using the `shadCNComponents` prop of `BlockNoteView`: - -```tsx -return ( - -); -``` - -You can pass components from the following ShadCN modules: - -- Badge -- Button -- Card -- DropdownMenu -- Form -- Input -- Label -- Popover -- Select -- Tabs -- Toggle -- Tooltip - - - To ensure compatibility, your ShadCN components should not use Portals, as styling and CSS variables are scoped to only the editor. - - -## Using Your Own Components - -If you want to use a different component library to Mantine/Ariakit/ShadCN, you will have to provide your own `BlockNoteView` implementation using the `BlockNoteViewRaw` component and a `ComponentsContext`: - -```tsx -import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; -import { - BlockNoteViewRaw, - Components, - ComponentsContext, -} from "@blocknote/react"; -import { ComponentProps } from "react"; - -export const components: Components = { - ... -}; - -export const BlockNoteView = < - BSchema extends BlockSchema, - ISchema extends InlineContentSchema, - SSchema extends StyleSchema ->( - props: ComponentProps> -) => { - return ( - - - - ); -}; -``` - -The components you want BlockNote to use should be added to `components`. To see exactly how this object is structured, which components you need to provide, and what props each component should take, see [this source file](https://github.com/TypeCellOS/BlockNote/tree/main/packages/react/src/editor/ComponentsContext.tsx). - diff --git a/docs/pages/docs/styling-theming/overriding-css.mdx b/docs/pages/docs/styling-theming/overriding-css.mdx index dffa489a17..e1322417e0 100644 --- a/docs/pages/docs/styling-theming/overriding-css.mdx +++ b/docs/pages/docs/styling-theming/overriding-css.mdx @@ -37,7 +37,7 @@ Within the editor's DOM structure, you'll find many elements have classes with t `.bn-drag-handle-menu`: Element for the drag handle menu. -`.bn-slash-menu`: Element for the slash menu. +`.bn-suggestion-menu`: Element for suggestion menu. ## BlockNote CSS Attributes @@ -52,5 +52,3 @@ For example, `[data-content-type="heading"][data-level="2"]` will select all hea ## Mantine Classes Because BlockNote uses [Mantine](https://mantine.dev/) for its UI, you can also write CSS rules using any of the default Mantine component classes. - - diff --git a/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx b/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx index 77cf56f538..c387f67f1e 100644 --- a/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx +++ b/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx @@ -13,6 +13,7 @@ export function RemoveBlockButton(props: SideMenuProps) { return ( { return ( } onClick={() => { diff --git a/packages/ariakit/src/input/Form.tsx b/packages/ariakit/src/input/Form.tsx index e12de4f09f..40150cf20c 100644 --- a/packages/ariakit/src/input/Form.tsx +++ b/packages/ariakit/src/input/Form.tsx @@ -1,9 +1,12 @@ import * as Ariakit from "@ariakit/react"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; export const Form = (props: ComponentProps["Generic"]["Form"]["Root"]) => { - const { children } = props; + const { children, ...rest } = props; + + assertEmpty(rest); return {children}; }; diff --git a/packages/ariakit/src/input/TextInput.tsx b/packages/ariakit/src/input/TextInput.tsx index 4bcb781049..39cfa7aac4 100644 --- a/packages/ariakit/src/input/TextInput.tsx +++ b/packages/ariakit/src/input/TextInput.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -19,8 +19,11 @@ export const TextInput = forwardRef< onKeyDown, onChange, onSubmit, + ...rest } = props; + assertEmpty(rest); + return ( <> {props.label && ( diff --git a/packages/ariakit/src/menu/Menu.tsx b/packages/ariakit/src/menu/Menu.tsx index cf2f9dcb83..c111bcdf78 100644 --- a/packages/ariakit/src/menu/Menu.tsx +++ b/packages/ariakit/src/menu/Menu.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -9,9 +9,12 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { children, onOpenChange, position, - // sub + sub, // unused + ...rest } = props; + assertEmpty(rest); + return ( ((props, ref) => { - const { className, children } = props; + const { + className, + children, + sub, // unused + ...rest + } = props; + + assertEmpty(rest); return ( ((props, ref) => { - const { className, children, icon, checked, subTrigger, onClick } = props; + const { className, children, icon, checked, subTrigger, onClick, ...rest } = + props; + + assertEmpty(rest); if (subTrigger) { return ( @@ -73,7 +86,9 @@ export const MenuLabel = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Menu"]["Label"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return ( { - const { children, sub } = props; + const { children, sub, ...rest } = props; + + assertEmpty(rest); if (sub) { return children; @@ -100,7 +117,9 @@ export const MenuDivider = forwardRef< HTMLHRElement, ComponentProps["Generic"]["Menu"]["Divider"] >((props, ref) => { - const { className } = props; + const { className, ...rest } = props; + + assertEmpty(rest); return ( ((props, ref) => { - const { className, children, onClick } = props; + const { className, children, onClick, label, ...rest } = props; + + assertEmpty(rest); return ( {children} diff --git a/packages/ariakit/src/panel/PanelFileInput.tsx b/packages/ariakit/src/panel/PanelFileInput.tsx index 36df131840..ca9b622222 100644 --- a/packages/ariakit/src/panel/PanelFileInput.tsx +++ b/packages/ariakit/src/panel/PanelFileInput.tsx @@ -1,4 +1,5 @@ import * as Ariakit from "@ariakit/react"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,9 @@ export const PanelFileInput = forwardRef< HTMLInputElement, ComponentProps["ImagePanel"]["FileInput"] >((props, ref) => { - const { className, value, placeholder, onChange } = props; + const { className, value, placeholder, onChange, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/ariakit/src/panel/PanelTab.tsx b/packages/ariakit/src/panel/PanelTab.tsx index 58ed2aba46..224d299cea 100644 --- a/packages/ariakit/src/panel/PanelTab.tsx +++ b/packages/ariakit/src/panel/PanelTab.tsx @@ -1,3 +1,4 @@ +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -5,7 +6,9 @@ export const PanelTab = forwardRef< HTMLDivElement, ComponentProps["ImagePanel"]["TabPanel"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return (
diff --git a/packages/ariakit/src/panel/PanelTextInput.tsx b/packages/ariakit/src/panel/PanelTextInput.tsx index 3e7f9c2ac4..91f13050b7 100644 --- a/packages/ariakit/src/panel/PanelTextInput.tsx +++ b/packages/ariakit/src/panel/PanelTextInput.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -8,7 +8,9 @@ export const PanelTextInput = forwardRef< HTMLInputElement, ComponentProps["ImagePanel"]["TextInput"] >((props, ref) => { - const { className, value, placeholder, onKeyDown, onChange } = props; + const { className, value, placeholder, onKeyDown, onChange, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx index 71adb6f07f..2375c730a1 100644 --- a/packages/ariakit/src/popover/Popover.tsx +++ b/packages/ariakit/src/popover/Popover.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -8,7 +8,9 @@ export const PopoverTrigger = forwardRef< HTMLButtonElement, ComponentProps["Generic"]["Popover"]["Trigger"] >((props, ref) => { - const { children } = props; + const { children, ...rest } = props; + + assertEmpty(rest); return ; }); @@ -17,7 +19,9 @@ export const PopoverContent = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Popover"]["Content"] >((props, ref) => { - const { className, children } = props; + const { className, children, variant, ...rest } = props; + + assertEmpty(rest); return ( { - const { children, opened, position } = props; + const { children, opened, position, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/ariakit/src/sideMenu/SideMenu.tsx b/packages/ariakit/src/sideMenu/SideMenu.tsx index 742112d71c..0a62b80310 100644 --- a/packages/ariakit/src/sideMenu/SideMenu.tsx +++ b/packages/ariakit/src/sideMenu/SideMenu.tsx @@ -1,4 +1,5 @@ import * as Ariakit from "@ariakit/react"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,9 @@ export const SideMenu = forwardRef< HTMLDivElement, ComponentProps["SideMenu"]["Root"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/ariakit/src/sideMenu/SideMenuButton.tsx b/packages/ariakit/src/sideMenu/SideMenuButton.tsx index 60ed1b7675..a8eeed8512 100644 --- a/packages/ariakit/src/sideMenu/SideMenuButton.tsx +++ b/packages/ariakit/src/sideMenu/SideMenuButton.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -8,16 +8,35 @@ export const SideMenuButton = forwardRef< HTMLButtonElement, ComponentProps["SideMenu"]["Button"] >((props, ref) => { - const { className, children, icon, onClick } = props; + const { + className, + children, + icon, + onClick, + label, + onDragEnd, + onDragStart, + draggable, + ...rest + } = props; + + // false, because rest props can be added by ariakit when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); return ( + onClick={onClick} + {...rest}> {icon} {children} diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index 78b42d104c..54825e8bbe 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -17,13 +17,11 @@ .bn-toolbar .bn-ak-button[data-selected] { padding-top: 0.125rem; - box-shadow: inset 0 0 0 1px var(--border), - inset 0 2px 0 var(--border); + box-shadow: inset 0 0 0 1px var(--border), inset 0 2px 0 var(--border); } .bn-toolbar .bn-ak-button[data-selected]:where(.dark, .dark *) { - box-shadow: inset 0 0 0 1px var(--border), - inset 0 1px 1px 1px var(--shadow); + box-shadow: inset 0 0 0 1px var(--border), inset 0 1px 1px 1px var(--shadow); } .bn-toolbar .bn-ak-popover { @@ -45,7 +43,8 @@ outline-style: none; } -.bn-ak-menu-item[data-hovered] { +.bn-ak-menu-item[aria-selected="true"], +.bn-ak-menu-item:hover { background-color: hsl(204 100% 40%); color: hsl(204 20% 100%); } @@ -58,7 +57,8 @@ overflow: visible; } -.bn-suggestion-menu, .bn-color-picker-dropdown { +.bn-suggestion-menu, +.bn-color-picker-dropdown { overflow: scroll; } @@ -78,10 +78,8 @@ --border: rgb(0 0 0/13%); --highlight: rgb(255 255 255/20%); --shadow: rgb(0 0 0/10%); - box-shadow: inset 0 0 0 1px var(--border), - inset 0 2px 0 var(--highlight), - inset 0 -1px 0 var(--shadow), - 0 1px 1px var(--shadow); + box-shadow: inset 0 0 0 1px var(--border), inset 0 2px 0 var(--highlight), + inset 0 -1px 0 var(--shadow), 0 1px 1px var(--shadow); font-size: 0.7rem; border-radius: 4px; padding-inline: 4px; @@ -111,7 +109,8 @@ width: fit-content; } -.bn-side-menu, .bn-table-handle { +.bn-side-menu, +.bn-table-handle { color: gray; } @@ -119,12 +118,14 @@ color: hsl(204 20% 100%); } -.bn-ak-tab, .bn-file-input { +.bn-ak-tab, +.bn-file-input { background-color: transparent; color: black; } -.bn-ak-tab:where(.dark, .dark *), .bn-file-input:where(.dark, .dark *) { +.bn-ak-tab:where(.dark, .dark *), +.bn-file-input:where(.dark, .dark *) { color: white; } @@ -132,4 +133,4 @@ align-items: center; display: flex; flex-direction: column; -} \ No newline at end of file +} diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx index 4229250989..31ada1bd77 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -8,11 +8,15 @@ export const SuggestionMenu = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["Root"] >((props, ref) => { - const { className, children } = props; + const { className, children, id, ...rest } = props; + + assertEmpty(rest); return ( {children} diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx index 9729f0685b..82eee48bef 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx @@ -1,4 +1,4 @@ -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -6,7 +6,9 @@ export const SuggestionMenuEmptyItem = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["EmptyItem"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return (
((props, ref) => { - const { - className, - title, - subtext, - // group, - icon, - badge, - // aliases, - // onItemClick, - isSelected, - setSelected, - onClick, - } = props; + const { className, item, isSelected, onClick, id, ...rest } = props; - const handleMouseLeave = useCallback(() => { - setSelected?.(false); - }, [setSelected]); - - const handleMouseEnter = useCallback(() => { - setSelected?.(true); - }, [setSelected]); + assertEmpty(rest); return (
- {icon && ( + role="option" + aria-selected={isSelected || undefined}> + {item.icon && (
- {icon} + {item.icon}
)}
-
{title}
-
{subtext}
+
{item.title}
+
+ {item.subtext} +
- {badge && ( + {item.badge && (
-
{badge}
+
{item.badge}
)}
diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx index 63e77d2cab..b4b563640d 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx @@ -1,4 +1,4 @@ -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -6,7 +6,9 @@ export const SuggestionMenuLabel = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["Label"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return (
((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return (
diff --git a/packages/ariakit/src/tableHandle/TableHandle.tsx b/packages/ariakit/src/tableHandle/TableHandle.tsx index 4c798d6637..7a1293f04f 100644 --- a/packages/ariakit/src/tableHandle/TableHandle.tsx +++ b/packages/ariakit/src/tableHandle/TableHandle.tsx @@ -1,6 +1,6 @@ import * as Ariakit from "@ariakit/react"; -import { mergeCSSClasses } from "@blocknote/core"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -15,9 +15,14 @@ export const TableHandle = forwardRef< onDragStart, onDragEnd, style, + label, ...rest } = props; + // false, because rest props can be added by ariakit when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + return ( ( (props, ref) => { - const { className, children, onMouseEnter, onMouseLeave } = props; + const { className, children, onMouseEnter, onMouseLeave, ...rest } = props; + + assertEmpty(rest); return ( ( isSelected, isDisabled, onClick, + label, ...rest } = props; + // false, because rest props can be added by ariakit when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + return ( ( } }} onClick={onClick} + aria-pressed={isSelected} data-selected={isSelected ? "true" : undefined} data-test={ props.mainTooltip.slice(0, 1).toLowerCase() + @@ -58,7 +65,7 @@ export const ToolbarButton = forwardRef( /> {mainTooltip} - {secondaryTooltip} + {secondaryTooltip && {secondaryTooltip}} ); diff --git a/packages/ariakit/src/toolbar/ToolbarSelect.tsx b/packages/ariakit/src/toolbar/ToolbarSelect.tsx index 930a281380..14edee97b0 100644 --- a/packages/ariakit/src/toolbar/ToolbarSelect.tsx +++ b/packages/ariakit/src/toolbar/ToolbarSelect.tsx @@ -1,14 +1,16 @@ import * as Ariakit from "@ariakit/react"; +import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { mergeCSSClasses } from "@blocknote/core"; import { forwardRef } from "react"; export const ToolbarSelect = forwardRef< HTMLDivElement, ComponentProps["FormattingToolbar"]["Select"] >((props, ref) => { - const { className, items, isDisabled } = props; + const { className, items, isDisabled, ...rest } = props; + + assertEmpty(rest); const selectedItem = props.items.filter((p) => p.isSelected)[0]; diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 3365474764..7e2396040d 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -781,8 +781,6 @@ export class BlockNoteEditor< * @param styles The styles to add. */ public addStyles(styles: Styles) { - this._tiptapEditor.view.focus(); - for (const [style, value] of Object.entries(styles)) { const config = this.schema.styleSchema[style]; if (!config) { @@ -803,8 +801,6 @@ export class BlockNoteEditor< * @param styles The styles to remove. */ public removeStyles(styles: Styles) { - this._tiptapEditor.view.focus(); - for (const style of Object.keys(styles)) { this._tiptapEditor.commands.unsetMark(style); } @@ -815,8 +811,6 @@ export class BlockNoteEditor< * @param styles The styles to toggle. */ public toggleStyles(styles: Styles) { - this._tiptapEditor.view.focus(); - for (const [style, value] of Object.entries(styles)) { const config = this.schema.styleSchema[style]; if (!config) { diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 3ce839ba95..a828fd0d49 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -1,4 +1,4 @@ -import { Extensions, extensions } from "@tiptap/core"; +import { Extension, Extensions, extensions } from "@tiptap/core"; import type { BlockNoteEditor } from "./BlockNoteEditor"; @@ -85,10 +85,26 @@ export const getBlockNoteExtensions = < BackgroundColorExtension, TextAlignmentExtension, + // make sure escape blurs editor, so that we can tab to other elements in the host page (accessibility) + Extension.create({ + name: "OverrideEscape", + addKeyboardShortcuts() { + return { + Escape: () => { + if (opts.editor.suggestionMenus.shown) { + // escape is handled by suggestionmenu + return false; + } + return this.editor.commands.blur(); + }, + }; + }, + }), + // nodes Doc, BlockContainer.configure({ - editor: opts.editor as any, + editor: opts.editor, domAttributes: opts.domAttributes, }), BlockGroup.configure({ diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index 8aee5c7f75..6857c6f0e7 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -126,12 +126,15 @@ export class BlockNoteTipTapEditor extends TiptapEditor { private createViewAlternative() { // Without queueMicrotask, custom IC / styles will give a React FlushSync error queueMicrotask(() => { - this.view = new EditorView(this.options.element, { - ...this.options.editorProps, - // @ts-ignore - dispatchTransaction: this.dispatchTransaction.bind(this), - state: this.state, - }); + this.view = new EditorView( + { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one + { + ...this.options.editorProps, + // @ts-ignore + dispatchTransaction: this.dispatchTransaction.bind(this), + state: this.state, + } + ); // `editor.view` is not yet available at this time. // Therefore we will add all plugins and node views directly afterwards. diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts index b1b329081a..3e60b762e6 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts @@ -1,5 +1,5 @@ import { isNodeSelection, posToDOMRect } from "@tiptap/core"; -import { EditorState, Plugin, PluginKey } from "prosemirror-state"; +import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; @@ -9,7 +9,7 @@ import { EventEmitter } from "../../util/EventEmitter"; export type FormattingToolbarState = UiElementPosition; -export class FormattingToolbarView { +export class FormattingToolbarView implements PluginView { public state?: FormattingToolbarState; public emitUpdate: () => void; @@ -48,9 +48,6 @@ export class FormattingToolbarView { pmView.dom.addEventListener("dragstart", this.dragHandler); pmView.dom.addEventListener("dragover", this.dragHandler); - pmView.dom.addEventListener("focus", this.focusHandler); - pmView.dom.addEventListener("blur", this.blurHandler); - document.addEventListener("scroll", this.scrollHandler); } @@ -71,42 +68,6 @@ export class FormattingToolbarView { } }; - focusHandler = () => { - // we use `setTimeout` to make sure `selection` is already updated - setTimeout(() => this.update(this.pmView)); - }; - - blurHandler = (event: FocusEvent) => { - if (this.preventHide) { - this.preventHide = false; - - return; - } - - const editorWrapper = this.pmView.dom.parentElement!; - - // Checks if the focus is moving to an element outside the editor. If it is, - // the toolbar is hidden. - if ( - // An element is clicked. - event && - event.relatedTarget && - // Element is inside the editor. - (editorWrapper === (event.relatedTarget as Node) || - editorWrapper.contains(event.relatedTarget as Node) || - (event.relatedTarget as HTMLElement).matches( - ".bn-ui-container, .bn-ui-container *" - )) - ) { - return; - } - - if (this.state?.show) { - this.state.show = false; - this.emitUpdate(); - } - }; - scrollHandler = () => { if (this.state?.show) { this.state.referencePos = this.getSelectionBoundingBox(); @@ -177,12 +138,16 @@ export class FormattingToolbarView { this.pmView.dom.removeEventListener("dragstart", this.dragHandler); this.pmView.dom.removeEventListener("dragover", this.dragHandler); - this.pmView.dom.removeEventListener("focus", this.focusHandler); - this.pmView.dom.removeEventListener("blur", this.blurHandler); - document.removeEventListener("scroll", this.scrollHandler); } + closeMenu = () => { + if (this.state?.show) { + this.state.show = false; + this.emitUpdate(); + } + }; + getSelectionBoundingBox() { const { state } = this.pmView; const { selection } = state; @@ -222,10 +187,25 @@ export class FormattingToolbarProsemirrorPlugin extends EventEmitter { }); return this.view; }, + props: { + handleKeyDown: (_view, event: KeyboardEvent) => { + if (event.key === "Escape" && this.shown) { + this.view!.closeMenu(); + return true; + } + return false; + }, + }, }); } + public get shown() { + return this.view?.state?.show || false; + } + public onUpdate(callback: (state: FormattingToolbarState) => void) { return this.on("update", callback); } + + public closeMenu = () => this.view!.closeMenu(); } diff --git a/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts b/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts index 516f7f2852..d83c7879f3 100644 --- a/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts +++ b/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts @@ -1,15 +1,15 @@ -import { EditorState, Plugin, PluginKey } from "prosemirror-state"; +import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; +import { DefaultBlockSchema } from "../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; import type { BlockFromConfig, InlineContentSchema, StyleSchema, } from "../../schema"; -import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; import { EventEmitter } from "../../util/EventEmitter"; -import { DefaultBlockSchema } from "../../blocks/defaultBlocks"; export type ImagePanelState< I extends InlineContentSchema, @@ -22,7 +22,8 @@ export type ImagePanelState< export class ImagePanelView< I extends InlineContentSchema, S extends StyleSchema -> { +> implements PluginView +{ public state?: ImagePanelState; public emitUpdate: () => void; @@ -45,8 +46,6 @@ export class ImagePanelView< pmView.dom.addEventListener("dragstart", this.dragstartHandler); - pmView.dom.addEventListener("blur", this.blurHandler); - document.addEventListener("scroll", this.scrollHandler); } @@ -65,28 +64,6 @@ export class ImagePanelView< } }; - blurHandler = (event: FocusEvent) => { - const editorWrapper = this.pmView.dom.parentElement!; - - // Checks if the focus is moving to an element outside the editor. If it is, - // the panel is hidden. - if ( - // An element is clicked. - event && - event.relatedTarget && - // Element is inside the editor. - (editorWrapper === (event.relatedTarget as Node) || - editorWrapper.contains(event.relatedTarget as Node)) - ) { - return; - } - - if (this.state?.show) { - this.state.show = false; - this.emitUpdate(); - } - }; - scrollHandler = () => { if (this.state?.show) { const blockElement = document.querySelector( @@ -131,13 +108,18 @@ export class ImagePanelView< } } + closeMenu = () => { + if (this.state?.show) { + this.state.show = false; + this.emitUpdate(); + } + }; + destroy() { this.pmView.dom.removeEventListener("mousedown", this.mouseDownHandler); this.pmView.dom.removeEventListener("dragstart", this.dragstartHandler); - this.pmView.dom.removeEventListener("blur", this.blurHandler); - document.removeEventListener("scroll", this.scrollHandler); } } @@ -170,6 +152,15 @@ export class ImagePanelProsemirrorPlugin< ); return this.view; }, + props: { + handleKeyDown: (_view, event: KeyboardEvent) => { + if (event.key === "Escape" && this.shown) { + this.view!.closeMenu(); + return true; + } + return false; + }, + }, state: { init: () => { return { @@ -189,7 +180,13 @@ export class ImagePanelProsemirrorPlugin< }); } + public get shown() { + return this.view?.state?.show || false; + } + public onUpdate(callback: (state: ImagePanelState) => void) { return this.on("update", callback); } + + public closeMenu = () => this.view!.closeMenu(); } diff --git a/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts b/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts index 7fd9cde3a4..a50e0d78a5 100644 --- a/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +++ b/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts @@ -1,11 +1,11 @@ import { getMarkRange, posToDOMRect, Range } from "@tiptap/core"; import { EditorView } from "@tiptap/pm/view"; import { Mark } from "prosemirror-model"; -import { Plugin, PluginKey } from "prosemirror-state"; +import { Plugin, PluginKey, PluginView } from "prosemirror-state"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; -import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema"; import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema"; import { EventEmitter } from "../../util/EventEmitter"; export type LinkToolbarState = UiElementPosition & { @@ -15,7 +15,7 @@ export type LinkToolbarState = UiElementPosition & { text: string; }; -class LinkToolbarView { +class LinkToolbarView implements PluginView { public state?: LinkToolbarState; public emitUpdate: () => void; @@ -258,6 +258,13 @@ class LinkToolbarView { } } + closeMenu = () => { + if (this.state?.show) { + this.state.show = false; + this.emitUpdate(); + } + }; + destroy() { this.pmView.dom.removeEventListener("mouseover", this.mouseOverHandler); document.removeEventListener("scroll", this.scrollHandler); @@ -285,6 +292,15 @@ export class LinkToolbarProsemirrorPlugin< }); return this.view; }, + props: { + handleKeyDown: (_view, event: KeyboardEvent) => { + if (event.key === "Escape" && this.shown) { + this.view!.closeMenu(); + return true; + } + return false; + }, + }, }); } @@ -327,4 +343,10 @@ export class LinkToolbarProsemirrorPlugin< public stopHideTimer = () => { this.view!.stopMenuUpdateTimer(); }; + + public get shown() { + return this.view?.state?.show || false; + } + + public closeMenu = () => this.view!.closeMenu(); } diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index 9a262c4801..58cf687c58 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -373,11 +373,12 @@ export class SideMenuView< }; onKeyDown = (_event: KeyboardEvent) => { - if (this.state?.show) { + if (this.state?.show && this.editor.isFocused()) { + // Typing in editor should hide side menu this.state.show = false; this.emitUpdate(this.state); + this.menuFrozen = false; } - this.menuFrozen = false; }; onMouseDown = (_event: MouseEvent) => { diff --git a/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts b/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts index d24ab7279c..b2a9056d0d 100644 --- a/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts +++ b/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts @@ -3,8 +3,8 @@ import { EditorState, Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; -import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema"; import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema"; import { EventEmitter } from "../../util/EventEmitter"; const findBlock = findParentNode((node) => node.type.name === "blockContainer"); @@ -18,7 +18,7 @@ class SuggestionMenuView< I extends InlineContentSchema, S extends StyleSchema > { - private state?: SuggestionMenuState; + public state?: SuggestionMenuState; public emitUpdate: (triggerCharacter: string) => void; pluginState: SuggestionPluginState; @@ -339,6 +339,10 @@ export class SuggestionMenuProseMirrorPlugin< closeMenu = () => this.view!.closeMenu(); clearQuery = () => this.view!.clearQuery(); + + public get shown() { + return this.view?.state?.show || false; + } } export function createSuggestionMenu< diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index 3efd049612..bb324bc89a 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -69,6 +69,10 @@ export const en = { add_button: "Add Image", }, // from react package: + side_menu: { + add_block_label: "Add block", + drag_handle_label: "Open block menu", + }, drag_handle: { delete_menuitem: "Delete", colors_menuitem: "Colors", diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index be0cf48363..d61bf2530f 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -71,6 +71,10 @@ export const nl: Dictionary = { add_button: "Afbeelding toevoegen", }, // from react package: + side_menu: { + add_block_label: "Nieuw blok", + drag_handle_label: "Open blok menu", + }, drag_handle: { delete_menuitem: "Verwijder", colors_menuitem: "Kleuren", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1a70105d11..34cbae5a64 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -27,5 +27,5 @@ export * from "./api/nodeConversions/nodeConversions"; export * from "./api/testUtil/partialBlockTestUtil"; export * from "./extensions/UniqueID/UniqueID"; export * from "./i18n/dictionary"; -export { UnreachableCaseError } from "./util/typescript"; +export { UnreachableCaseError, assertEmpty } from "./util/typescript"; export { locales }; diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 73015220b8..530315ea07 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -62,7 +62,7 @@ declare module "@tiptap/core" { */ export const BlockContainer = Node.create<{ domAttributes?: BlockNoteDOMAttributes; - editor: BlockNoteEditor; + editor: BlockNoteEditor; }>({ name: "blockContainer", group: "blockContainer", @@ -686,10 +686,26 @@ export const BlockContainer = Node.create<{ // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the // editor since the browser will try to use tab for keyboard navigation. Tab: () => { + if ( + this.options.editor.formattingToolbar?.shown || + this.options.editor.linkToolbar?.shown || + this.options.editor.imagePanel?.shown + ) { + // don't handle tabs if a toolbar is shown, so we can tab into / out of it + return false; + } this.editor.commands.sinkListItem("blockContainer"); return true; }, "Shift-Tab": () => { + if ( + this.options.editor.formattingToolbar?.shown || + this.options.editor.linkToolbar?.shown || + this.options.editor.imagePanel?.shown + ) { + // don't handle tabs if a toolbar is shown, so we can tab into / out of it + return false; + } this.editor.commands.liftListItem("blockContainer"); return true; }, diff --git a/packages/core/src/util/typescript.ts b/packages/core/src/util/typescript.ts index edda7588e9..feb2f59134 100644 --- a/packages/core/src/util/typescript.ts +++ b/packages/core/src/util/typescript.ts @@ -4,5 +4,13 @@ export class UnreachableCaseError extends Error { } } +export function assertEmpty(obj: Record, throwError = true) { + const { "data-test": dataTest, ...rest } = obj; // exclude data-test + + if (Object.keys(rest).length > 0 && throwError) { + throw new Error("Object must be empty " + JSON.stringify(obj)); + } +} + // TODO: change for built-in version of typescript 5.4 after upgrade export type NoInfer = [T][T extends any ? 0 : never]; diff --git a/packages/mantine/src/form/TextInput.tsx b/packages/mantine/src/form/TextInput.tsx index db4a76a1c6..917ac5953a 100644 --- a/packages/mantine/src/form/TextInput.tsx +++ b/packages/mantine/src/form/TextInput.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -18,8 +19,11 @@ export const TextInput = forwardRef< onKeyDown, onChange, onSubmit, + ...rest } = props; + assertEmpty(rest); + return ( void; - onTriggerMouseLeave: () => void; onMenuMouseOver: () => void; onMenuMouseLeave: () => void; } @@ -67,15 +66,19 @@ const SubMenuContext = createContext< // // const SubMenu = forwardRef< - HTMLDivElement, + HTMLButtonElement, ComponentProps["Generic"]["Menu"]["Root"] >((props, ref) => { const { children, onOpenChange, position, - // sub + sub, // not used + ...rest } = props; + + assertEmpty(rest); + const [opened, setOpened] = useState(false); const menuCloseTimer = useRef | undefined>(); @@ -93,22 +96,18 @@ const SubMenu = forwardRef< if (menuCloseTimer.current) { clearTimeout(menuCloseTimer.current); } - setOpened(true); }, []); return ( { - const { children, onOpenChange, position, sub } = props; + const { children, onOpenChange, position, sub, ...rest } = props; + + assertEmpty(rest); if (sub) { return ; @@ -146,13 +147,15 @@ export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { }; export const MenuItem = forwardRef< - HTMLDivElement, + HTMLButtonElement & HTMLDivElement, ComponentProps["Generic"]["Menu"]["Item"] >((props, ref) => { const { className, children, icon, checked, subTrigger, onClick, ...rest } = props; - const ctx = useContext(SubMenuContext); + // false, because rest props can be added by mantine when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); if (subTrigger) { return ( @@ -163,14 +166,10 @@ export const MenuItem = forwardRef< ); } - const onMouseLeave = subTrigger ? ctx!.onTriggerMouseLeave : undefined; - const onMouseOver = subTrigger ? ctx!.onTriggerMouseOver : undefined; - return ( ) : null } - onMouseOver={onMouseOver} - onMouseLeave={onMouseLeave} onClick={onClick} {...rest}> {children} @@ -193,9 +190,12 @@ export const MenuTrigger = ( ) => { const { children, - // sub + sub, // unused + ...rest } = props; + assertEmpty(rest); + return {children}; }; @@ -206,9 +206,12 @@ export const MenuDropdown = forwardRef< const { className, children, - // sub + sub, //unused + ...rest } = props; + assertEmpty(rest); + const ctx = useContext(SubMenuContext); return ( @@ -226,7 +229,9 @@ export const MenuDivider = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Menu"]["Divider"] >((props, ref) => { - const { className } = props; + const { className, ...rest } = props; + + assertEmpty(rest); return ; }); @@ -235,7 +240,9 @@ export const MenuLabel = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Menu"]["Label"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/mantine/src/panel/Panel.tsx b/packages/mantine/src/panel/Panel.tsx index 78a29f6231..6f15c3dba6 100644 --- a/packages/mantine/src/panel/Panel.tsx +++ b/packages/mantine/src/panel/Panel.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -14,9 +15,11 @@ export const Panel = forwardRef< openTab, setOpenTab, loading, - // setLoading, + ...rest } = props; + assertEmpty(rest); + return ( ((props, ref) => { - const { className, children, onClick, ...rest } = props; + const { className, children, onClick, label, ...rest } = props; + + assertEmpty(rest); return ( ((props, ref) => { const { className, value, placeholder, onChange, ...rest } = props; + assertEmpty(rest); + return ( ((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return (
diff --git a/packages/mantine/src/panel/PanelTextInput.tsx b/packages/mantine/src/panel/PanelTextInput.tsx index 59f6f9b884..e68efaf18e 100644 --- a/packages/mantine/src/panel/PanelTextInput.tsx +++ b/packages/mantine/src/panel/PanelTextInput.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,9 @@ export const PanelTextInput = forwardRef< HTMLInputElement, ComponentProps["ImagePanel"]["TextInput"] >((props, ref) => { - const { className, value, placeholder, onKeyDown, onChange } = props; + const { className, value, placeholder, onKeyDown, onChange, ...rest } = props; + + assertEmpty(rest); return ( { - const { children, opened, position } = props; + const { children, opened, position, ...rest } = props; + + assertEmpty(rest); return ( { - const { children } = props; + const { children, ...rest } = props; + + assertEmpty(rest); return {children}; }; @@ -31,7 +36,14 @@ export const PopoverContent = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Popover"]["Content"] >((props, ref) => { - const { className, children } = props; + const { + className, + children, + variant, // unused + ...rest + } = props; + + assertEmpty(rest); return ( diff --git a/packages/mantine/src/sideMenu/SideMenu.tsx b/packages/mantine/src/sideMenu/SideMenu.tsx index 7a233730d8..91313ad206 100644 --- a/packages/mantine/src/sideMenu/SideMenu.tsx +++ b/packages/mantine/src/sideMenu/SideMenu.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,9 @@ export const SideMenu = forwardRef< HTMLDivElement, ComponentProps["SideMenu"]["Root"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/mantine/src/sideMenu/SideMenuButton.tsx b/packages/mantine/src/sideMenu/SideMenuButton.tsx index 329f0baf4f..cb1f21a1a6 100644 --- a/packages/mantine/src/sideMenu/SideMenuButton.tsx +++ b/packages/mantine/src/sideMenu/SideMenuButton.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,21 @@ export const SideMenuButton = forwardRef< HTMLButtonElement, ComponentProps["SideMenu"]["Button"] >((props, ref) => { - const { className, children, icon, onClick, ...rest } = props; + const { + className, + children, + icon, + onClick, + onDragEnd, + onDragStart, + draggable, + label, + ...rest + } = props; + + // false, because rest props can be added by mantine when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); if (icon) { return ( @@ -16,6 +31,10 @@ export const SideMenuButton = forwardRef< className={className} ref={ref} onClick={onClick} + onDragEnd={onDragEnd} + onDragStart={onDragStart} + draggable={draggable} + aria-label={label} {...rest}> {icon} @@ -23,7 +42,15 @@ export const SideMenuButton = forwardRef< } return ( - + {children} ); diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css index dd22c7f316..962b836a7c 100644 --- a/packages/mantine/src/style.css +++ b/packages/mantine/src/style.css @@ -5,227 +5,228 @@ /* Mantine Badge component base styles */ .bn-container .mantine-Badge-root { - background-color: var(--bn-colors-tooltip-background); - color: var(--bn-colors-tooltip-text); + background-color: var(--bn-colors-tooltip-background); + color: var(--bn-colors-tooltip-text); } /* Mantine FileInput component base styles */ .bn-container .mantine-FileInput-input { - align-items: center; - background-color: var(--bn-colors-menu-background); - border: none; - border-radius: 4px; - color: var(--bn-colors-menu-text); - display: flex; - flex-direction: row; - justify-content: center; + align-items: center; + background-color: var(--bn-colors-menu-background); + border: none; + border-radius: 4px; + color: var(--bn-colors-menu-text); + display: flex; + flex-direction: row; + justify-content: center; } .bn-container .mantine-FileInput-input:hover { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } .bn-container .mantine-FileInput-wrapper { - border: solid var(--bn-colors-border) 1px; - border-radius: 4px; + border: solid var(--bn-colors-border) 1px; + border-radius: 4px; } .bn-container .mantine-InputPlaceholder-placeholder { - color: var(--bn-colors-menu-text); - font-weight: 600; + color: var(--bn-colors-menu-text); + font-weight: 600; } /* Mantine Menu component base styles */ .bn-container .mantine-Menu-dropdown { - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); - box-sizing: border-box; - color: var(--bn-colors-menu-text); - padding: 2px; - overflow: auto; + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + box-sizing: border-box; + color: var(--bn-colors-menu-text); + padding: 2px; + overflow: auto; } .bn-container .mantine-Menu-label { - background-color: var(--bn-colors-menu-background); - color: var(--bn-colors-menu-text); + background-color: var(--bn-colors-menu-background); + color: var(--bn-colors-menu-text); } .bn-container .mantine-Menu-item { - background-color: var(--bn-colors-menu-background); - border: none; - border-radius: var(--bn-border-radius-small); - color: var(--bn-colors-menu-text); + background-color: var(--bn-colors-menu-background); + border: none; + border-radius: var(--bn-border-radius-small); + color: var(--bn-colors-menu-text); } -.bn-container .mantine-Menu-item[data-hovered] { - background-color: var(--bn-colors-hovered-background); - border: none; - color: var(--bn-colors-hovered-text); +.bn-container .mantine-Menu-item[aria-selected="true"], +.bn-container .mantine-Menu-item:hover { + background-color: var(--bn-colors-hovered-background); + border: none; + color: var(--bn-colors-hovered-text); } /* Mantine Popover component base styles */ .bn-container .mantine-Popover-dropdown { - background-color: transparent; - border: none; - border-radius: 0; - box-shadow: none; - padding: 0; + background-color: transparent; + border: none; + border-radius: 0; + box-shadow: none; + padding: 0; } /* Mantine Tabs component base styles */ .bn-container .mantine-Tabs-root { - width: 100%; - background-color: var(--bn-colors-menu-background); + width: 100%; + background-color: var(--bn-colors-menu-background); } .bn-container .mantine-Tabs-list:before { - border-color: var(--bn-colors-hovered-background); + border-color: var(--bn-colors-hovered-background); } .bn-container .mantine-Tabs-tab { - color: var(--bn-colors-menu-text); - border-color: var(--bn-colors-hovered-background); + color: var(--bn-colors-menu-text); + border-color: var(--bn-colors-hovered-background); } .bn-container .mantine-Tabs-tab:hover { - background-color: var(--bn-colors-hovered-background); - border-color: var(--bn-colors-hovered-background); - color: var(--bn-colors-hovered-text); + background-color: var(--bn-colors-hovered-background); + border-color: var(--bn-colors-hovered-background); + color: var(--bn-colors-hovered-text); } .bn-container .mantine-Tabs-tab[data-active], .bn-container .mantine-Tabs-tab[data-active]:hover { - border-color: var(--bn-colors-menu-text); - color: var(--bn-colors-menu-text); + border-color: var(--bn-colors-menu-text); + color: var(--bn-colors-menu-text); } .bn-container .mantine-Tabs-panel { - padding: 8px; + padding: 8px; } /* Mantine TextInput component base styles */ .bn-container .mantine-TextInput-input { - background-color: var(--bn-colors-menu-background); - border: solid var(--bn-colors-border) 1px; - border-radius: 4px; - color: var(--bn-colors-menu-text); - height: 32px; + background-color: var(--bn-colors-menu-background); + border: solid var(--bn-colors-border) 1px; + border-radius: 4px; + color: var(--bn-colors-menu-text); + height: 32px; } /* Mantine Tooltip component base styles */ .bn-container .mantine-Tooltip-tooltip { - background-color: transparent; - border: none; - border-radius: 0; - box-shadow: none; - padding: 0; + background-color: transparent; + border: none; + border-radius: 0; + box-shadow: none; + padding: 0; } /* UI element styling */ /* Select styling */ .bn-select { - overflow: auto; + overflow: auto; } /* Toolbar styling */ .bn-toolbar { - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); - flex-wrap: nowrap; - gap: 2px; - padding: 2px; - width: fit-content; + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + flex-wrap: nowrap; + gap: 2px; + padding: 2px; + width: fit-content; } .bn-toolbar:empty { - display: none; + display: none; } .bn-toolbar .mantine-Button-root, .bn-toolbar .mantine-ActionIcon-root { - background-color: var(--bn-colors-menu-background); - border: none; - border-radius: var(--bn-border-radius-small); - color: var(--bn-colors-menu-text); + background-color: var(--bn-colors-menu-background); + border: none; + border-radius: var(--bn-border-radius-small); + color: var(--bn-colors-menu-text); } .bn-toolbar .mantine-Button-root:hover, .bn-toolbar .mantine-ActionIcon-root:hover { - background-color: var(--bn-colors-hovered-background); - border: none; - color: var(--bn-colors-hovered-text); + background-color: var(--bn-colors-hovered-background); + border: none; + color: var(--bn-colors-hovered-text); } .bn-toolbar .mantine-Button-root[data-selected], .bn-toolbar .mantine-ActionIcon-root[data-selected] { - background-color: var(--bn-colors-selected-background); - border: none; - color: var(--bn-colors-selected-text); + background-color: var(--bn-colors-selected-background); + border: none; + color: var(--bn-colors-selected-text); } .bn-toolbar .mantine-Button-root[data-disabled], -.bn-toolbar .mantine-ActionIcon-root[data-disabled] { - background-color: var(--bn-colors-disabled-background); - border: none; - color: var(--bn-colors-disabled-text); +.bn-toolbar .mantine-ActionIcon-root[data-disabled] { + background-color: var(--bn-colors-disabled-background); + border: none; + color: var(--bn-colors-disabled-text); } .bn-toolbar .mantine-Menu-item { - font-size: 12px; - height: 30px; + font-size: 12px; + height: 30px; } .bn-toolbar .mantine-Menu-item:hover { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } .bn-container .bn-form-popover { - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); - color: var(--bn-colors-menu-text); - gap: 4px; - min-width: 145px; - padding: 2px; + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + color: var(--bn-colors-menu-text); + gap: 4px; + min-width: 145px; + padding: 2px; } .bn-form-popover .mantine-TextInput-root, .bn-form-popover .mantine-FileInput-root { - width: 300px; + width: 300px; } .bn-form-popover .mantine-TextInput-wrapper, .bn-form-popover .mantine-FileInput-wrapper { - padding: 0; - border-radius: 4px; + padding: 0; + border-radius: 4px; } .bn-form-popover .mantine-TextInput-wrapper:hover { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } .bn-form-popover .mantine-TextInput-input, .bn-form-popover .mantine-FileInput-input { - border: none; - font-size: 12px; + border: none; + font-size: 12px; } .bn-form-popover .mantine-FileInput-input:hover { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } .bn-form-popover .mantine-FileInput-section[data-position="left"] { - color: var(--bn-colors-menu-text); + color: var(--bn-colors-menu-text); } .bn-form-popover .mantine-FileInput-placeholder { - color: var(--bn-colors-menu-text); + color: var(--bn-colors-menu-text); } /* Suggestion Menu styling*/ @@ -235,254 +236,255 @@ own. */ /* https://github.com/mantinedev/mantine/blob/e3e3bb834de1f2f75a27dbc757dc0a2fc6a6cba8/packages/%40mantine/core/src/components/Menu/Menu.module.css */ .bn-suggestion-menu { - max-height: 100%; - position: relative; - box-shadow: var(--mantine-shadow-md); - border: calc(0.0625rem * var(--mantine-scale)) solid + max-height: 100%; + position: relative; + box-shadow: var(--mantine-shadow-md); + border: calc(0.0625rem * var(--mantine-scale)) solid var(--mantine-color-gray-2); - border-radius: var(--mantine-radius-default); - padding: 4px; + border-radius: var(--mantine-radius-default); + padding: 4px; } .bn-suggestion-menu-label { - color: var(--mantine-color-dimmed); - font-weight: 500; - font-size: var(--mantine-font-size-xs); - padding: calc(var(--mantine-spacing-xs) / 2) var(--mantine-spacing-sm); - cursor: default; + color: var(--mantine-color-dimmed); + font-weight: 500; + font-size: var(--mantine-font-size-xs); + padding: calc(var(--mantine-spacing-xs) / 2) var(--mantine-spacing-sm); + cursor: default; } .bn-suggestion-menu-item { - font-size: var(--mantine-font-size-sm); - width: 100%; - padding: calc(var(--mantine-spacing-xs) / 1.5) var(--mantine-spacing-sm); - border-radius: var(--popover-radius, var(--mantine-radius-default)); - color: var(--menu-item-color, var(--mantine-color-text)); - display: flex; - align-items: center; - user-select: none; - - &:where([data-disabled], :disabled) { - color: var(--mantine-color-dimmed); - opacity: 0.6; - pointer-events: none; - } - - &:where([data-hovered]) { - } + font-size: var(--mantine-font-size-sm); + width: 100%; + padding: calc(var(--mantine-spacing-xs) / 1.5) var(--mantine-spacing-sm); + border-radius: var(--popover-radius, var(--mantine-radius-default)); + color: var(--menu-item-color, var(--mantine-color-text)); + display: flex; + align-items: center; + user-select: none; + + &:where([data-disabled], :disabled) { + color: var(--mantine-color-dimmed); + opacity: 0.6; + pointer-events: none; + } } /* Additional Suggestion Menu styling*/ .bn-mt-suggestion-menu-item-body { - flex: 1; + flex: 1; } .bn-mt-suggestion-menu-item-section { - display: flex; - justify-content: center; - align-items: center; + display: flex; + justify-content: center; + align-items: center; - &:where([data-position="left"]) { - margin-inline-end: var(--mantine-spacing-xs); - } + &:where([data-position="left"]) { + margin-inline-end: var(--mantine-spacing-xs); + } - &:where([data-position="right"]) { - margin-inline-start: var(--mantine-spacing-xs); - } + &:where([data-position="right"]) { + margin-inline-start: var(--mantine-spacing-xs); + } } .bn-suggestion-menu { - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); - box-sizing: border-box; - color: var(--bn-colors-menu-text); - overflow-y: auto; - padding: 2px; + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + box-sizing: border-box; + color: var(--bn-colors-menu-text); + overflow-y: auto; + padding: 2px; } .bn-suggestion-menu-item { - cursor: pointer; - height: 52px; + cursor: pointer; + height: 52px; } -.bn-suggestion-menu-item[data-hovered] { - background-color: var(--bn-colors-hovered-background); +.bn-suggestion-menu-item[aria-selected="true"], +.bn-suggestion-menu-item:hover { + background-color: var(--bn-colors-hovered-background); } .bn-mt-suggestion-menu-item-section { - color: var(--bn-colors-tooltip-text); + color: var(--bn-colors-tooltip-text); } .bn-mt-suggestion-menu-item-section[data-position="left"] { - background-color: var(--bn-colors-tooltip-background); - border-radius: var(--bn-border-radius-small); - padding: 8px; + background-color: var(--bn-colors-tooltip-background); + border-radius: var(--bn-border-radius-small); + padding: 8px; } .bn-mt-suggestion-menu-item-body { - align-items: stretch; - color: var(--bn-colors-menu-text); - display: flex; - flex: 1; - flex-direction: column; - justify-content: flex-start; - padding-right: 16px; + align-items: stretch; + color: var(--bn-colors-menu-text); + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + padding-right: 16px; } .bn-mt-suggestion-menu-item-title { - line-height: 20px; - font-weight: 500; - font-size: 14px; - margin: 0; - padding: 0; + line-height: 20px; + font-weight: 500; + font-size: 14px; + margin: 0; + padding: 0; } .bn-mt-suggestion-menu-item-subtitle { - line-height: 16px; - font-size: 10px; - margin: 0; - padding: 0; + line-height: 16px; + font-size: 10px; + margin: 0; + padding: 0; } .bn-suggestion-menu-label { - color: var(--bn-colors-hovered-text); + color: var(--bn-colors-hovered-text); } .bn-suggestion-menu-loader { - height: 20px; - width: 100%; + height: 20px; + width: 100%; } .bn-suggestion-menu-loader span { - background-color: var(--bn-colors-side-menu); + background-color: var(--bn-colors-side-menu); } /* Side Menu styling */ .bn-side-menu { - background-color: transparent; - overflow: visible; + background-color: transparent; + overflow: visible; } -.bn-side-menu .mantine-Menu-item, .bn-table-handle-menu .mantine-Menu-item { - font-size: 12px; - height: 30px; +.bn-side-menu .mantine-Menu-item, +.bn-table-handle-menu .mantine-Menu-item { + font-size: 12px; + height: 30px; } .bn-side-menu .mantine-UnstyledButton-root:not(.mantine-Menu-item) { - background-color: transparent; + background-color: transparent; } .bn-side-menu .mantine-UnstyledButton-root:hover { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } .bn-side-menu .mantine-UnstyledButton-root:not(.mantine-Menu-item) svg { - background-color: transparent; - color: var(--bn-colors-side-menu); - height: 22px; - width: 22px; + background-color: transparent; + color: var(--bn-colors-side-menu); + height: 22px; + width: 22px; } .bn-side-menu > [draggable="true"] { - display: flex; + display: flex; } .bn-side-menu .mantine-Menu-dropdown { - min-width: 100px; - padding: 2px; - position: absolute; + min-width: 100px; + padding: 2px; + position: absolute; } /* Image Panel styling*/ .bn-panel { - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); - padding: 2px; - width: 500px; + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + padding: 2px; + width: 500px; } .bn-panel .bn-tab-panel { - align-items: center; - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; + align-items: center; + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; } .bn-panel .mantine-TextInput-root, .bn-panel .mantine-FileInput-root { - width: 100%; + width: 100%; } .bn-panel .mantine-Button-root { - background-color: var(--bn-colors-menu-background); - border: solid var(--bn-colors-border) 1px; - border-radius: var(--bn-border-radius-small); - color: var(--bn-colors-menu-text); - height: 32px; - width: 60%; + background-color: var(--bn-colors-menu-background); + border: solid var(--bn-colors-border) 1px; + border-radius: var(--bn-border-radius-small); + color: var(--bn-colors-menu-text); + height: 32px; + width: 60%; } .bn-panel .mantine-Button-root:hover { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } .bn-panel.mantine-Text-root { - text-align: center; + text-align: center; } /* Table Handle styling */ .bn-table-handle { - align-items: center; - background-color: var(--bn-colors-menu-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-small); - box-shadow: var(--bn-shadow-light); - color: var(--bn-colors-side-menu); - cursor: pointer; - display: flex; - justify-content: center; - overflow: visible; - padding: 0; + align-items: center; + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-small); + box-shadow: var(--bn-shadow-light); + color: var(--bn-colors-side-menu); + cursor: pointer; + display: flex; + justify-content: center; + overflow: visible; + padding: 0; } .bn-table-handle svg { - margin-inline: -4px; + margin-inline: -4px; } .bn-table-handle:hover, .bn-table-handle-dragging { - background-color: var(--bn-colors-hovered-background); + background-color: var(--bn-colors-hovered-background); } /* Drag Handle & Table Handle Menu styling */ .bn-container .bn-drag-handle-menu { - overflow: visible; + overflow: visible; } /* Tooltip styling */ .bn-tooltip { - background-color: var(--bn-colors-tooltip-background); - border: var(--bn-border); - border-radius: var(--bn-border-radius-medium); - box-shadow: var(--bn-shadow-medium); - color: var(--bn-colors-tooltip-text); - padding: 4px 10px; - text-align: center; + background-color: var(--bn-colors-tooltip-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + color: var(--bn-colors-tooltip-text); + padding: 4px 10px; + text-align: center; } /* Additional menu styles */ .bn-tick-space { - padding: 0; - width: 20px; + padding: 0; + width: 20px; } -.bn-mt-sub-menu-item > .mantine-Menu-itemLabel > div:not(.mantine-Menu-dropdown) { - align-items: center; - display: flex; - justify-content: space-between; +.bn-mt-sub-menu-item + > .mantine-Menu-itemLabel + > div:not(.mantine-Menu-dropdown) { + align-items: center; + display: flex; + justify-content: space-between; } diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx index 4c0e5a6db7..8d9a2ea257 100644 --- a/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx +++ b/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,10 +8,17 @@ export const SuggestionMenu = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["Root"] >((props, ref) => { - const { className, children } = props; + const { className, children, id, ...rest } = props; + + assertEmpty(rest); return ( - + {children} ); diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx index 3e88792d86..990a285590 100644 --- a/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx +++ b/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,9 @@ export const SuggestionMenuEmptyItem = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["EmptyItem"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx index bc622bb073..bf67bf71fc 100644 --- a/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx +++ b/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx @@ -1,65 +1,45 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef, useCallback } from "react"; +import { forwardRef } from "react"; export const SuggestionMenuItem = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["Item"] >((props, ref) => { - const { - className, - title, - subtext, - // group, - icon, - badge, - // aliases, - // onItemClick, - isSelected, - setSelected, - onClick, - } = props; + const { className, isSelected, onClick, item, id, ...rest } = props; - const handleMouseLeave = useCallback(() => { - setSelected?.(false); - }, [setSelected]); - - const handleMouseEnter = useCallback(() => { - setSelected?.(true); - }, [setSelected]); + assertEmpty(rest); return ( - {icon && ( + id={id} + role="option" + aria-selected={isSelected || undefined}> + {item.icon && ( - {icon} + {item.icon} )} - {title} + {item.title} - {subtext} + {item.subtext} - {badge && ( + {item.badge && ( - {badge} + {item.badge} )} diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx index 9c7178cc9b..a913f3f5eb 100644 --- a/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx +++ b/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -7,7 +8,9 @@ export const SuggestionMenuLabel = forwardRef< HTMLDivElement, ComponentProps["SuggestionMenu"]["Label"] >((props, ref) => { - const { className, children } = props; + const { className, children, ...rest } = props; + + assertEmpty(rest); return ( diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx index 252a2682db..1d3e7fd99d 100644 --- a/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx +++ b/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx @@ -1,5 +1,6 @@ import * as Mantine from "@mantine/core"; +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -9,8 +10,11 @@ export const SuggestionMenuLoader = forwardRef< >((props, ref) => { const { className, - // children + children, // unused, using "dots" instead + ...rest } = props; + assertEmpty(rest); + return ; }); diff --git a/packages/mantine/src/tableHandle/TableHandle.tsx b/packages/mantine/src/tableHandle/TableHandle.tsx index 08ed50271e..2e172542a7 100644 --- a/packages/mantine/src/tableHandle/TableHandle.tsx +++ b/packages/mantine/src/tableHandle/TableHandle.tsx @@ -1,3 +1,4 @@ +import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { forwardRef } from "react"; @@ -12,13 +13,19 @@ export const TableHandle = forwardRef< onDragStart, onDragEnd, style, + label, ...rest } = props; + // false, because rest props can be added by mantine when button is used as a trigger + // assertEmpty in this case is only used at typescript level, not runtime level + assertEmpty(rest, false); + return (