@blocknote/react
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 67.5 kB
Source Map (JSON)
{"version":3,"file":"confirmDiscardUnsavedComment-CwhAQZ3p.cjs","names":[],"sources":["../src/editor/BlockNoteContext.ts","../src/hooks/useBlockNoteEditor.ts","../src/hooks/useStore.ts","../src/hooks/useExtension.ts","../src/util/useIsomorphicLayoutEffect.ts","../src/hooks/useEditorState.ts","../src/hooks/useEditorDomElement.ts","../src/editor/BlockNoteViewContext.ts","../src/editor/PortalElementOverride.tsx","../src/components/Popovers/GenericPopover.tsx","../src/editor/ComponentsContext.tsx","../src/i18n/dictionary.ts","../src/components/Popovers/PositionPopover.tsx","../src/hooks/useCreateBlockNote.tsx","../src/components/Comments/CommentEditor.tsx","../src/components/Comments/defaultCommentEditorSchema.ts","../src/components/Comments/confirmDiscardUnsavedComment.ts"],"sourcesContent":["import {\n BlockNoteEditor,\n BlockNoteSchema,\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\nimport { createContext, useContext, useState } from \"react\";\n\nexport type BlockNoteContextValue<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n> = {\n setContentEditableProps?: ReturnType<typeof useState<Record<string, any>>>[1]; // copy type of setXXX from useState\n editor?: BlockNoteEditor<BSchema, ISchema, SSchema>;\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const BlockNoteContext = createContext<\n BlockNoteContextValue | undefined\n>(undefined);\n\n/**\n * Get the BlockNoteContext instance from the nearest BlockNoteContext provider\n * @param _schema: optional, pass in the schema to return type-safe Context if you're using a custom schema\n */\nexport function useBlockNoteContext<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n>(\n _schema?: BlockNoteSchema<BSchema, ISchema, SSchema>,\n): BlockNoteContextValue<BSchema, ISchema, SSchema> | undefined {\n const context = useContext(BlockNoteContext) as any;\n\n return context;\n}\n","import {\n BlockNoteEditor,\n BlockNoteSchema,\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"@blocknote/core\";\n\nimport { useBlockNoteContext } from \"../editor/BlockNoteContext.js\";\n\n/**\n * Get the BlockNoteEditor instance from the nearest BlockNoteContext provider\n * @param _schema: optional, pass in the schema to return type-safe BlockNoteEditor if you're using a custom schema\n */\nexport function useBlockNoteEditor<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n>(\n _schema?: BlockNoteSchema<BSchema, ISchema, SSchema>,\n): BlockNoteEditor<BSchema, ISchema, SSchema> {\n const context = useBlockNoteContext(_schema);\n\n if (!context?.editor) {\n throw new Error(\n \"useBlockNoteEditor was called outside of a BlockNoteContext provider or BlockNoteView component\",\n );\n }\n\n return context.editor;\n}\n","// Vendored from https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts (MIT)\n//\n// See `packages/core/src/util/Store.ts` for why the store itself is vendored. This is the\n// matching React binding, kept behaviourally identical to `@tanstack/react-store@0.7.7` —\n// in particular the `shallow` default comparator, which `useCommentUsers` and\n// `useVersionUsers` rely on to avoid re-rendering when an unrelated user resolves.\n\nimport type { Store } from \"@blocknote/core\";\nimport { useSyncExternalStoreWithSelector } from \"use-sync-external-store/shim/with-selector\";\n\n/**\n * Subscribe to a {@link Store}, optionally selecting a slice of its state.\n *\n * The component re-renders only when the selected value changes under a\n * {@link shallow} comparison, so selectors are free to build a fresh object,\n * `Map` or `Set` on each call.\n */\nexport function useStore<TState, TSelected = NoInfer<TState>>(\n store: Store<TState>,\n selector: (state: TState) => TSelected = (d) => d as unknown as TSelected,\n): TSelected {\n return useSyncExternalStoreWithSelector(\n store.subscribe,\n () => store.state,\n () => store.state,\n selector,\n shallow,\n );\n}\n\n/**\n * Compares two values one level deep, with special handling for `Map`, `Set` and `Date`.\n */\nexport function shallow<T>(objA: T, objB: T): boolean {\n if (Object.is(objA, objB)) {\n return true;\n }\n\n if (\n typeof objA !== \"object\" ||\n objA === null ||\n typeof objB !== \"object\" ||\n objB === null\n ) {\n return false;\n }\n\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) {\n return false;\n }\n for (const [k, v] of objA) {\n if (!objB.has(k) || !Object.is(v, objB.get(k))) {\n return false;\n }\n }\n return true;\n }\n\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) {\n return false;\n }\n for (const v of objA) {\n if (!objB.has(v)) {\n return false;\n }\n }\n return true;\n }\n\n if (objA instanceof Date && objB instanceof Date) {\n return objA.getTime() === objB.getTime();\n }\n\n const keysA = getOwnKeys(objA);\n if (keysA.length !== getOwnKeys(objB).length) {\n return false;\n }\n\n return keysA.every(\n (key) =>\n Object.prototype.hasOwnProperty.call(objB, key) &&\n Object.is(objA[key as keyof T], objB[key as keyof T]),\n );\n}\n\nfunction getOwnKeys<T extends object>(obj: T): Array<string | symbol> {\n return (Object.keys(obj) as Array<string | symbol>).concat(\n Object.getOwnPropertySymbols(obj),\n );\n}\n","import {\n BlockNoteEditor,\n Extension,\n ExtensionFactory,\n Store,\n} from \"@blocknote/core\";\nimport { useBlockNoteEditor } from \"./useBlockNoteEditor.js\";\nimport { useStore } from \"./useStore.js\";\n\n/**\n * Use an extension instance\n */\nexport function useExtension<\n const T extends ExtensionFactory | Extension | string,\n>(\n plugin: T,\n ctx?: { editor?: BlockNoteEditor<any, any, any> },\n): T extends ExtensionFactory\n ? NonNullable<ReturnType<ReturnType<T>>>\n : T extends string\n ? Extension\n : T extends Extension\n ? T\n : never {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const editor = ctx?.editor ?? useBlockNoteEditor();\n\n const instance = editor.getExtension(plugin as any);\n\n if (!instance) {\n throw new Error(\"Extension not found\", { cause: { plugin } });\n }\n\n return instance;\n}\n\ntype ExtractStore<T> = T extends Store<infer U> ? U : never;\n\n/**\n * Use the state of an extension\n */\nexport function useExtensionState<\n T extends ExtensionFactory | Extension,\n TExtension = T extends ExtensionFactory ? ReturnType<ReturnType<T>> : T,\n TStore = TExtension extends { store: Store<any> }\n ? TExtension[\"store\"]\n : never,\n TSelected = NoInfer<ExtractStore<TStore>>,\n>(\n plugin: T | string,\n ctx?: {\n editor?: BlockNoteEditor<any, any, any>;\n selector?: (state: NoInfer<ExtractStore<TStore>>) => TSelected;\n },\n): TSelected {\n const extension = useExtension(\n plugin as ExtensionFactory | Extension | string,\n ctx,\n );\n const { store } = extension;\n if (!store) {\n throw new Error(\"Store not found on plugin\", { cause: { plugin } });\n }\n return useStore<ExtractStore<TStore>, TSelected>(store, ctx?.selector as any);\n}\n","import { useEffect, useLayoutEffect } from \"react\";\n\n/**\n * `useLayoutEffect` in the browser, `useEffect` under SSR — where\n * `useLayoutEffect` cannot run and React warns.\n *\n * Used for latest-ref updates: the ref must be current before any layout\n * effect can trigger an editor event, or a subscription could still invoke\n * the previous render's callback.\n */\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import type { BlockNoteEditor } from \"@blocknote/core\";\nimport deepEqual from \"fast-deep-equal/es6/react.js\";\nimport { useDebugValue, useState } from \"react\";\nimport { useSyncExternalStoreWithSelector } from \"use-sync-external-store/shim/with-selector\";\nimport { useBlockNoteContext } from \"../editor/BlockNoteContext.js\";\nimport { useIsomorphicLayoutEffect } from \"../util/useIsomorphicLayoutEffect.js\";\n\nexport type EditorStateSnapshot<\n TEditor extends BlockNoteEditor<any, any, any> | null = BlockNoteEditor<\n any,\n any,\n any\n > | null,\n> = {\n editor: TEditor;\n transactionNumber: number;\n};\n\nexport type UseEditorStateOptions<\n TSelectorResult,\n TEditor extends BlockNoteEditor<any, any, any> | null = BlockNoteEditor<\n any,\n any,\n any\n > | null,\n> = {\n /**\n * The editor instance. If not provided, will use the editor from BlockNoteContext.\n */\n editor?: TEditor;\n\n /**\n * A selector function to determine the value to compare for re-rendering.\n */\n selector: (context: EditorStateSnapshot<TEditor>) => TSelectorResult;\n\n /**\n * A custom equality function to determine if the editor should re-render.\n * @default `deepEqual` from `fast-deep-equal`\n */\n equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean;\n\n /**\n * The event to subscribe to.\n * @default \"all\"\n */\n on?: \"all\" | \"mount\" | \"selection\" | \"change\";\n};\n\n/**\n * To synchronize the editor instance with the component state,\n * we need to create a separate instance that is not affected by the component re-renders.\n */\nclass EditorStateManager<\n TEditor extends BlockNoteEditor<any, any, any> | null = BlockNoteEditor<\n any,\n any,\n any\n > | null,\n> {\n private transactionNumber = 0;\n\n private lastTransactionNumber = 0;\n\n private lastSnapshot: EditorStateSnapshot<TEditor>;\n\n private editor: TEditor;\n\n private subscribers = new Set<() => void>();\n\n constructor(initialEditor: TEditor) {\n this.editor = initialEditor;\n this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 };\n\n this.getSnapshot = this.getSnapshot.bind(this);\n this.getServerSnapshot = this.getServerSnapshot.bind(this);\n this.watch = this.watch.bind(this);\n this.subscribe = this.subscribe.bind(this);\n }\n\n /**\n * Get the current editor instance.\n */\n getSnapshot(): EditorStateSnapshot<TEditor> {\n if (this.transactionNumber === this.lastTransactionNumber) {\n return this.lastSnapshot;\n }\n this.lastTransactionNumber = this.transactionNumber;\n this.lastSnapshot = {\n editor: this.editor,\n transactionNumber: this.transactionNumber,\n };\n return this.lastSnapshot;\n }\n\n /**\n * Always disable the editor on the server-side.\n */\n getServerSnapshot(): EditorStateSnapshot<null> {\n return { editor: null, transactionNumber: 0 };\n }\n\n /**\n * Subscribe to the editor instance's changes.\n */\n subscribe(callback: () => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Watch the editor instance for changes.\n */\n watch(\n nextEditor: BlockNoteEditor<any, any, any> | null,\n on: \"all\" | \"mount\" | \"selection\" | \"change\",\n ): undefined | (() => void) {\n this.editor = nextEditor as TEditor;\n\n if (this.editor) {\n /**\n * This will force a re-render when the editor state changes.\n * This is to support things like `editor.can().toggleBold()` in components that `useEditor`.\n * This could be more efficient, but it's a good trade-off for now.\n */\n const fn = () => {\n this.transactionNumber += 1;\n this.subscribers.forEach((callback) => callback());\n };\n\n const currentTiptapEditor = this.editor._tiptapEditor;\n\n const EVENT_TYPES = {\n all: [\"transaction\", \"create\", \"mount\", \"unmount\"],\n // Listen for \"create\" as \"mount\" may fire before the hook is run.\n mount: [\"create\", \"mount\", \"unmount\"],\n selection: [\"selectionUpdate\"],\n change: [\"update\"],\n } as const;\n\n for (const eventType of EVENT_TYPES[on]) {\n currentTiptapEditor.on(eventType, fn);\n }\n\n return () => {\n for (const eventType of EVENT_TYPES[on]) {\n currentTiptapEditor.off(eventType, fn);\n }\n };\n }\n\n return undefined;\n }\n}\n\n/**\n * This hook allows you to watch for changes on the editor instance.\n * It will allow you to select a part of the editor state and re-render the component when it changes.\n * @example\n * ```tsx\n * const { currentSelection } = useEditorState({\n * selector: snapshot => ({ currentSelection: snapshot.editor?._tiptapEditor.state.selection }),\n * })\n * ```\n */\nexport function useEditorState<TSelectorResult>(\n options: UseEditorStateOptions<\n TSelectorResult,\n BlockNoteEditor<any, any, any>\n >,\n): TSelectorResult;\n/**\n * This hook allows you to watch for changes on the editor instance.\n * It will allow you to select a part of the editor state and re-render the component when it changes.\n * @example\n * ```tsx\n * const { currentSelection } = useEditorState({\n * selector: snapshot => ({ currentSelection: snapshot.editor?._tiptapEditor.state.selection }),\n * })\n * ```\n */\nexport function useEditorState<TSelectorResult>(\n options: UseEditorStateOptions<\n TSelectorResult,\n BlockNoteEditor<any, any, any> | null\n >,\n): TSelectorResult | null;\n\n/**\n * This hook allows you to watch for changes on the editor instance.\n * It will allow you to select a part of the editor state and re-render the component when it changes.\n * @example\n * ```tsx\n * const { currentSelection } = useEditorState({\n * selector: snapshot => ({ currentSelection: snapshot.editor?._tiptapEditor.state.selection }),\n * })\n * ```\n */\nexport function useEditorState<TSelectorResult>(\n options:\n | UseEditorStateOptions<TSelectorResult, BlockNoteEditor<any, any, any>>\n | UseEditorStateOptions<\n TSelectorResult,\n BlockNoteEditor<any, any, any> | null\n >,\n): TSelectorResult | null {\n const editorContext = useBlockNoteContext();\n const editor = options.editor || editorContext?.editor || null;\n const on = options.on || \"all\";\n\n const [editorStateManager] = useState(() => new EditorStateManager(editor));\n\n // Using the `useSyncExternalStore` hook to sync the editor instance with the component state\n const selectedState = useSyncExternalStoreWithSelector(\n // eslint-disable-next-line @typescript-eslint/unbound-method -- methods are bound in EditorStateManager constructor\n editorStateManager.subscribe,\n // eslint-disable-next-line @typescript-eslint/unbound-method -- methods are bound in EditorStateManager constructor\n editorStateManager.getSnapshot,\n // eslint-disable-next-line @typescript-eslint/unbound-method -- methods are bound in EditorStateManager constructor\n editorStateManager.getServerSnapshot,\n options.selector as UseEditorStateOptions<\n TSelectorResult,\n BlockNoteEditor<any, any, any> | null\n >[\"selector\"],\n options.equalityFn ?? deepEqual,\n );\n\n useIsomorphicLayoutEffect(() => {\n return editorStateManager.watch(editor, on);\n }, [editor, editorStateManager, on]);\n\n useDebugValue(selectedState);\n\n return selectedState;\n}\n","import { BlockNoteEditor } from \"@blocknote/core\";\n\nimport { useBlockNoteContext } from \"../editor/BlockNoteContext.js\";\nimport { useEditorState } from \"./useEditorState.js\";\n\n// Returns the editor's DOM element reactively.\nexport function useEditorDOMElement(editor?: BlockNoteEditor<any, any, any>) {\n const editorContext = useBlockNoteContext();\n if (!editor) {\n editor = editorContext?.editor;\n }\n\n if (!editor) {\n throw new Error(\n \"'editor' is required in `useEditorDOMElement`, either from BlockNoteContext or as a function argument\",\n );\n }\n\n return useEditorState({\n editor,\n selector: (ctx) => ctx.editor.domElement,\n equalityFn: (a, b) => a === b,\n on: \"mount\",\n });\n}\n","import { createContext, useContext } from \"react\";\nimport { BlockNoteDefaultUIProps } from \"./BlockNoteDefaultUI.js\";\n\nexport type BlockNoteViewContextValue = {\n editorProps: {\n autoFocus?: boolean;\n contentEditableProps?: Record<string, any>;\n editable?: boolean;\n };\n defaultUIProps: BlockNoteDefaultUIProps;\n /**\n * Makes `element` a themed BlockNote root: the classes and color-scheme\n * attribute the stylesheet keys off, plus whatever the UI library adds (its\n * own color-scheme attribute, theme CSS variables).\n *\n * Applied imperatively because it is used for the portal roots BlockNote\n * mounts outside React's DOM tree, which cannot be themed with props (see\n * `PortalElementOverride`). The editor container is themed by rendering the\n * same values as props instead.\n */\n applyThemedRoot: (element: HTMLElement) => void;\n};\n\nexport const BlockNoteViewContext = createContext<\n BlockNoteViewContextValue | undefined\n>(undefined);\n\nexport function useBlockNoteViewContext():\n | BlockNoteViewContextValue\n | undefined {\n const context = useContext(BlockNoteViewContext) as any;\n\n return context;\n}\n","import {\n createContext,\n ReactNode,\n useCallback,\n useContext,\n useEffect,\n useInsertionEffect,\n useState,\n} from \"react\";\n\nimport { useBlockNoteEditor } from \"../hooks/useBlockNoteEditor.js\";\nimport { useEditorDOMElement } from \"../hooks/useEditorDomElement.js\";\nimport { useBlockNoteViewContext } from \"./BlockNoteViewContext.js\";\n\n// Runs in the commit's mutation phase, before any layout effect in the tree.\nconst useIsomorphicInsertionEffect =\n typeof window !== \"undefined\" ? useInsertionEffect : useEffect;\n\n// Set by `PortalElementOverride` (a root to escape to) and by\n// `PortalElementAnchor` (a UI element's own wrapper); the default comes from\n// the editor itself, see `usePortalElement`.\nconst PortalElementContext = createContext<HTMLElement | null>(null);\n\n/**\n * The element the floating UI below should portal into: the nearest\n * {@link PortalElementAnchor} (the wrapper of the toolbar, side menu, … that\n * opens it), else the nearest {@link PortalElementOverride}'s element, else by\n * default the element wrapping the editor element. In the default layout that\n * is the editor's `bn-container`; with `renderEditor={false}` it is whatever\n * `BlockNoteViewEditor` was rendered into, so the floating UI clips and scrolls\n * with the editor rather than escaping into the layout around it. All of these\n * sit inside a themed `.bn-root`, so portalled UI keeps the editor's styling\n * and color scheme wherever in the DOM it lands.\n *\n * `null` until the editor has mounted, and on the server. Consumers render\n * nothing until it exists.\n */\nexport function usePortalElement(): HTMLElement | null {\n const override = useContext(PortalElementContext);\n const editorDOMElement = useEditorDOMElement();\n\n if (override) {\n return override;\n }\n\n return editorDOMElement?.parentElement ?? null;\n}\n\n/**\n * Resets the portal element for the subtree to the editor's own default (see\n * {@link usePortalElement}). `BlockNoteViewContainer` wraps its content in it:\n * a `BlockNoteView` nested inside another view's floating UI (the comments\n * composer, an editor in a custom block's popover) would otherwise inherit the\n * outer view's anchor or override, an element registered with the outer\n * editor, so the nested editor's own menus and popovers would count as\n * outside it for `isWithinEditor` and the focus tracking built on it.\n */\nexport function PortalElementReset(props: { children?: ReactNode }) {\n return (\n <PortalElementContext.Provider value={null}>\n {props.children}\n </PortalElementContext.Provider>\n );\n}\n\n/**\n * Redirects the floating UI below it into `target`, for UI that must escape\n * the editor container — an ancestor's `overflow` clipping it, or a stacking\n * context painting it behind the page (see\n * `MobileFormattingToolbarController`).\n *\n * The portal element is a themed `.bn-root` mounted inside `target`, so\n * portalled UI stays styled wherever it goes. It is created up front rather\n * than rendered, so consumers have it on their first render, and attached in\n * an insertion effect, so it is in the document before any layout effect of\n * the children runs: a UI library that portals eagerly (Ariakit renders its\n * popovers hidden from the start) picks its mount point in a layout effect,\n * and given a still-detached element it re-parents it to `document.body`,\n * outside the editor's registered UI. It is also registered with the editor,\n * so focus inside it still counts as focus within the editor.\n *\n * `undefined` means no redirect: the ambient portal element stays in effect.\n */\nexport function PortalElementOverride(props: {\n target?: HTMLElement;\n children?: ReactNode;\n}) {\n const { target, children } = props;\n\n const editor = useBlockNoteEditor();\n const applyThemedRoot = useBlockNoteViewContext()?.applyThemedRoot;\n\n const [portalElement] = useState(() =>\n typeof document === \"undefined\" ? null : document.createElement(\"div\"),\n );\n\n // An insertion effect, not a layout effect: a parent's layout effect runs\n // after its children's, and Ariakit picks the mount point of its eagerly\n // rendered popovers in a layout effect, re-parenting a still-detached\n // element to `document.body`, outside the editor's registered UI.\n // How-to-test: as a layout effect, Ariakit re-parents the mobile toolbar's anchor to document.body, so focus in the link form counts as outside the editor and the toolbar unmounts (covered by portalElements: \"has an override root in the document before its children's layout effects run\", and skinFocus, android, ariakit: \"the link button hands focus to the URL input\").\n useIsomorphicInsertionEffect(() => {\n if (!portalElement || !target) {\n return;\n }\n\n target.appendChild(portalElement);\n return () => portalElement.remove();\n }, [portalElement, target]);\n\n // React does not render this element, so the same theming the editor\n // container gets from its props is applied here by hand. Same phase as the\n // attach above, so children measure themed styles from their first layout\n // effect on.\n useIsomorphicInsertionEffect(() => {\n if (!portalElement || !target) {\n return;\n }\n\n applyThemedRoot?.(portalElement);\n }, [portalElement, target, applyThemedRoot]);\n\n // Floating UI portalled out of the editor's DOM tree is still the editor's\n // UI: registering the element keeps `editor.isWithinEditor` (and the focus\n // tracking built on it) true for what renders inside. Registered in the\n // same phase too, so nothing a child focuses from its own effects is ever\n // judged before the root counts as editor UI.\n useIsomorphicInsertionEffect(() => {\n if (!portalElement || !target) {\n return;\n }\n\n editor.registerPortalElement(portalElement);\n return () => editor.unregisterPortalElement(portalElement);\n }, [editor, portalElement, target]);\n\n if (target === undefined) {\n return children;\n }\n\n return (\n <PortalElementContext.Provider value={portalElement}>\n {children}\n </PortalElementContext.Provider>\n );\n}\n\n/**\n * An anchor for the floating UI a UI element opens (its menus, popovers and\n * forms): a zero-size, absolutely positioned element next to that UI element,\n * inside the wrapper that positions it. What portals into it stays a DOM\n * descendant of that wrapper, so it shares the wrapper's stacking context and\n * visibility (it hides when the UI element hides) while taking no part in its\n * layout.\n *\n * The anchor exists from the first render (created up front and attached to\n * the rendered holder on commit, before any effect runs), so consumers never\n * see a `null` and nothing re-renders to pick it up. The holder is rendered\n * by React so that it, and with it the anchor, is re-attached whenever the\n * wrapper's content is re-rendered.\n */\nfunction usePortalElementAnchor(): {\n anchor: HTMLElement | null;\n holder: ReactNode;\n} {\n const [anchor] = useState(() => {\n if (typeof document === \"undefined\") {\n return null;\n }\n const element = document.createElement(\"span\");\n element.className = \"bn-portal-anchor\";\n return element;\n });\n\n const holderRef = useCallback(\n (holder: HTMLElement | null) => {\n if (holder && anchor && anchor.parentElement !== holder) {\n holder.appendChild(anchor);\n }\n },\n [anchor],\n );\n\n const holder = (\n <span\n ref={holderRef}\n className={PORTAL_ELEMENT_ANCHOR_HOLDER_CLASS}\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"visible\" }}\n />\n );\n\n return { anchor, holder };\n}\n\nconst PORTAL_ELEMENT_ANCHOR_HOLDER_CLASS = \"bn-portal-anchor-holder\";\n\n/**\n * Whether `element` has rendered children other than a\n * {@link PortalElementAnchor}'s holder. The holder means a wrapper that renders\n * an anchor is never empty, so \"the UI element rendered nothing\" has to be\n * checked with this instead of the wrapper's `innerHTML`.\n */\nexport function hasChildrenBesidesPortalElementAnchor(\n element: HTMLElement,\n): boolean {\n return Array.from(element.childNodes).some(\n (node) =>\n !(\n node instanceof Element &&\n node.classList.contains(PORTAL_ELEMENT_ANCHOR_HOLDER_CLASS)\n ),\n );\n}\n\n/**\n * Renders a portal anchor inside a UI element's wrapper and makes it the\n * portal element for everything below (see {@link usePortalElementAnchor}): the\n * menus and popovers a toolbar, side menu or table handle opens render inside\n * the wrapper that positions and hides that UI element. Nested menus resolve\n * to the same anchor, never to their parent dropdown, which may clip.\n *\n * The anchor is a sibling of the UI element, not a descendant, so it is never\n * inside a scrolling part of it (iOS WebKit clips positioned descendants of\n * scroll containers); and a `portalElements` override that relocates the\n * wrapper takes the anchor, and so the popups, along with it.\n *\n * Pass a function as `children` to receive the portal element for props that\n * need it explicitly.\n */\nexport function PortalElementAnchor(props: {\n children?: ReactNode | ((portalElement: HTMLElement | null) => ReactNode);\n}) {\n const { anchor, holder } = usePortalElementAnchor();\n const ambient = usePortalElement();\n const portalElement = anchor ?? ambient;\n\n const children =\n typeof props.children === \"function\"\n ? props.children(portalElement)\n : props.children;\n\n return (\n <>\n {holder}\n <PortalElementContext.Provider value={portalElement}>\n {children}\n </PortalElementContext.Provider>\n </>\n );\n}\n","import {\n autoUpdate,\n FloatingFocusManager,\n FloatingPortal,\n hide,\n useDismiss,\n useFloating,\n UseFloatingOptions,\n useHover,\n useInteractions,\n useMergeRefs,\n useTransitionStatus,\n useTransitionStyles,\n} from \"@floating-ui/react\";\nimport { HTMLAttributes, ReactNode, useEffect, useRef } from \"react\";\n\nimport {\n hasChildrenBesidesPortalElementAnchor,\n PortalElementAnchor,\n usePortalElement,\n} from \"../../editor/PortalElementOverride.js\";\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { FloatingUIOptions } from \"./FloatingUIOptions.js\";\n\nexport type GenericPopoverReference =\n | {\n // A DOM element to use as the reference element for the popover.\n element: Element;\n // To update the popover position, `element.getReferenceBoundingRect`\n // is called. This flag caches the last result of the call while the\n // element is mounted to the DOM, so it doesn't update while the\n // popover is closing and transitioning out. Useful for if the\n // reference element unmounts, as `element.getReferenceBoundingRect`\n // would return a `DOMRect` with x, y, width, and height of 0.\n // Defaults to `true`.\n cacheMountedBoundingClientRect?: boolean;\n }\n | {\n element: undefined;\n // When no reference element is provided, this can be provided as an\n // alternative \"virtual\" element to position the popover around.\n getBoundingClientRect: () => DOMRect;\n // Optional per-line client rects, required by floating-ui's `inline()`\n // middleware. Virtual elements have no default `getClientRects`, so it\n // must be provided explicitly when `inline()` is used.\n getClientRects?: () => DOMRectList;\n }\n | {\n element: Element;\n cacheMountedBoundingClientRect?: boolean;\n // If both `element` and `getBoundingClientRect` are provided, uses\n // `getBoundingClientRect` to position the popover, but still treats\n // `element` as the reference element for all other purposes. When\n // `cacheMountedBoundingClientRect` is `true` or unspecified, this\n // function is not called while the reference element is not mounted.\n getBoundingClientRect: () => DOMRect;\n // See above.\n getClientRects?: () => DOMRectList;\n };\n\n// Returns a modified version of `getBoundingClientRect`, if\n// `reference.element` is passed and `reference.cacheMountedBoundingClientRect`\n// is `true` or `undefined`. In the modified version, each new result is cached\n// and returned while `reference.element` is connected to the DOM. If it is no\n// longer connected, the cache is no longer updated and the last cached result\n// is used.\n//\n// In all other cases, just returns `reference.getBoundingClientRect`, or\n// `reference.element.getBoundingClientRect` if it's not defined.\nexport function getMountedBoundingClientRectCache(\n reference: GenericPopoverReference,\n) {\n let lastBoundingClientRect = new DOMRect();\n const getBoundingClientRect =\n \"getBoundingClientRect\" in reference\n ? () => reference.getBoundingClientRect()\n : () => reference.element.getBoundingClientRect();\n\n return () => {\n if (\n reference.element &&\n (reference.cacheMountedBoundingClientRect ?? true)\n ) {\n if (reference.element.isConnected) {\n lastBoundingClientRect = getBoundingClientRect();\n }\n\n return lastBoundingClientRect;\n }\n\n return getBoundingClientRect();\n };\n}\n\n/**\n * Merges two `whileElementsMounted` handlers into one. Both run when elements\n * mount, and both cleanup functions are called on unmount.\n */\nfunction mergeWhileElementsMounted(\n a: UseFloatingOptions[\"whileElementsMounted\"],\n b: UseFloatingOptions[\"whileElementsMounted\"],\n): UseFloatingOptions[\"whileElementsMounted\"] {\n if (!a) {\n return b;\n }\n if (!b) {\n return a;\n }\n\n return (reference, floating, update) => {\n const cleanupA = a(reference, floating, update);\n const cleanupB = b(reference, floating, update);\n return () => {\n cleanupA?.();\n cleanupB?.();\n };\n };\n}\n\nexport const GenericPopover = (\n props: FloatingUIOptions & {\n reference?: GenericPopoverReference;\n children: ReactNode;\n },\n) => {\n const editor = useBlockNoteEditor();\n // The ambient portal element — always a resolved, themed, registered root, as\n // `EditorPortalContext` is only ever provided by `PortalElementOverride` (the default from\n // `BlockNoteView`, or a controller's / the mobile toolbar's override).\n // `null` during SSR and for the frame before resolution — handled after the\n // hooks below.\n const portalElement = usePortalElement();\n const {\n whileElementsMounted: _whileElementsMounted,\n middleware,\n ...restFloatingOptions\n } = props.useFloatingOptions ?? {};\n\n const { refs, floatingStyles, context, middlewareData } =\n useFloating<HTMLDivElement>({\n whileElementsMounted: mergeWhileElementsMounted(\n autoUpdate,\n props.useFloatingOptions?.whileElementsMounted,\n ),\n middleware: [...(middleware ?? []), hide()],\n ...restFloatingOptions,\n });\n\n const { isMounted, styles } = useTransitionStyles(\n context,\n props.useTransitionStylesProps,\n );\n const { status } = useTransitionStatus(\n context,\n props.useTransitionStatusProps,\n );\n\n const dismiss = useDismiss(context, props.useDismissProps);\n const hover = useHover(context, { enabled: false, ...props.useHoverProps });\n // Also returns `getReferenceProps` but unused as the reference element may\n // not even be managed by React, so we may be unable to set them. Seems like\n // `refs.setReferences` attaches most of the same listeners anyway, but\n // possible both are needed.\n const { getFloatingProps } = useInteractions([dismiss, hover]);\n\n const innerHTML = useRef<string>(\"\");\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useMergeRefs([ref, refs.setFloating]);\n\n useEffect(() => {\n if (props.reference) {\n const element =\n \"element\" in props.reference ? props.reference.element : undefined;\n\n if (\n element !== undefined &&\n (props.focusManagerProps?.disabled || !editor.isWithinEditor(element))\n ) {\n // Only set domReference when FloatingFocusManager is disabled.\n // When FloatingFocusManager is active (disabled !== false) and the\n // reference is inside the ProseMirror editor, setting domReference\n // causes floating-ui to call insertAdjacentElement on the reference,\n // inserting a focus-return <span> into the PM contenteditable. This\n // triggers PM's MutationObserver and resets the editor selection.\n // (issue #2525)\n refs.setReference(element);\n }\n\n // Forward `getClientRects` when provided, so floating-ui's `inline()`\n // middleware can read per-line rects off a virtual reference (it calls\n // `getClientRects()`, which virtual elements lack by default).\n const getClientRects =\n \"getClientRects\" in props.reference\n ? props.reference.getClientRects\n : undefined;\n\n refs.setPositionReference({\n getBoundingClientRect: getMountedBoundingClientRectCache(\n props.reference,\n ),\n ...(getClientRects ? { getClientRects } : {}),\n contextElement: element,\n });\n }\n }, [props.reference, refs, props.focusManagerProps?.disabled, editor]);\n\n // Stores the last rendered `innerHTML` of the popover while it was open. The\n // `innerHTML` is used while the popover is closing, as the React children\n // may rerender during this time, causing unwanted behaviour.\n useEffect(\n () => {\n if (status === \"initial\" || status === \"open\") {\n // Only store while the children have rendered something. In the\n // render where a controller flips `open` to `false`, its children are\n // typically already gone while `status` is still \"open\", and that\n // empty state must not replace the snapshot the closing popover is\n // about to show. The wrapper is never truly empty though: it always\n // contains the `PortalElementAnchor` holder.\n if (ref.current && hasChildrenBesidesPortalElementAnchor(ref.current)) {\n innerHTML.current = ref.current.innerHTML;\n }\n }\n },\n // `props.children` is added to the deps, since it's ultimately the HTML of\n // the children that we're storing.\n [status, props.reference, props.children],\n );\n\n if (!isMounted || !portalElement) {\n return false;\n }\n\n const mergedProps: HTMLAttributes<HTMLDivElement> = {\n ...props.elementProps,\n style: {\n display: \"flex\",\n ...props.elementProps?.style,\n zIndex: `calc(var(--bn-ui-base-z-index, 0) + ${props.elementProps?.style?.zIndex || 0})`,\n ...floatingStyles,\n ...styles,\n ...(middlewareData.hide?.referenceHidden\n ? { visibility: \"hidden\" as const }\n : {}),\n },\n ...getFloatingProps(),\n };\n\n if (status === \"close\") {\n // While the popover is closing, shows its last rendered `innerHTML` while\n // it was open, instead of the React children. This is because they may\n // rerender during this time, causing unwanted behaviour.\n //\n // When we use the `GenericPopover` for BlockNote's internal UI elements\n // this isn't a huge deal, as we only pass child components if the popover\n // should be open. So without this fix, the popover just won't transition\n // out and will instead appear to hide instantly.\n return (\n <FloatingPortal root={portalElement}>\n <div\n ref={mergedRefs}\n {...mergedProps}\n dangerouslySetInnerHTML={{ __html: innerHTML.current }}\n />\n </FloatingPortal>\n );\n }\n\n // The children render inside a `PortalElementAnchor`: the menus and popovers\n // they open portal into this wrapper instead of the editor container, so\n // they share its stacking context and visibility (they paint above what the\n // wrapper paints above, and hide when it hides) and move with it when\n // `portalElements` relocates it. Rendering them inline instead would clip\n // them to the toolbar, or, on iOS, to a scrolling one. See\n // `PortalElementAnchor` for the details; behaviour is pinned by\n // `tests/src/end-to-end/portals/floatingComponentMenus.test.tsx`.\n if (!props.focusManagerProps?.disabled) {\n return (\n <FloatingPortal root={portalElement}>\n <FloatingFocusManager {...props.focusManagerProps} context={context}>\n <div ref={mergedRefs} {...mergedProps}>\n <PortalElementAnchor>{props.children}</PortalElementAnchor>\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n );\n }\n\n return (\n <FloatingPortal root={portalElement}>\n <div ref={mergedRefs} {...mergedProps}>\n <PortalElementAnchor>{props.children}</PortalElementAnchor>\n </div>\n </FloatingPortal>\n );\n};\n","import {\n ReactElement,\n ChangeEvent,\n ComponentType,\n createContext,\n CSSProperties,\n ForwardedRef,\n HTMLInputAutoCompleteAttribute,\n KeyboardEvent,\n MouseEvent,\n ReactNode,\n useContext,\n} from \"react\";\n\nimport { BlockNoteEditor, User } from \"@blocknote/core\";\nimport { DefaultReactGridSuggestionItem } from \"../components/SuggestionMenu/GridSuggestionMenu/types.js\";\nimport { DefaultReactSuggestionItem } from \"../components/SuggestionMenu/types.js\";\n\ntype ToolbarRootType = {\n className?: string;\n children?: ReactNode;\n onMouseEnter?: () => void;\n onMouseLeave?: () => void;\n variant?: \"default\" | \"action-toolbar\";\n};\n\ntype ToolbarButtonType = {\n className?: string;\n mainTooltip?: string;\n secondaryTooltip?: string;\n icon?: ReactNode;\n onClick?: (e: MouseEvent) => void;\n isSelected?: boolean;\n isDisabled?: boolean;\n variant?: \"default\" | \"compact\";\n} & (\n | { children: ReactNode; label?: string }\n | { children?: undefined; label: string }\n);\n\ntype ToolbarSelectType = {\n className?: string;\n items: {\n text: string;\n icon: ReactNode;\n onClick: () => void;\n isSelected: boolean;\n isDisabled?: boolean;\n }[];\n isDisabled?: boolean;\n portalElement: HTMLElement | null;\n /**\n * When true, the UI library must not move focus into the surface when it\n * opens (the mobile toolbar: a focus move blurs the editor and closes the\n * on-screen keyboard). An input inside that asks for focus itself still\n * gets it.\n */\n preventFocusOnOpen?: boolean;\n};\n\ntype MenuButtonType = {\n className?: string;\n onClick?: (e: MouseEvent) => void;\n icon?: ReactNode;\n onDragStart?: (e: React.DragEvent) => void;\n onDragEnd?: (e: React.DragEvent) => void;\n draggable?: boolean;\n} & (\n | { children: ReactNode; label?: string }\n | { children?: undefined; label: string }\n);\n\nexport type ComponentProps = {\n FormattingToolbar: {\n Root: ToolbarRootType;\n Button: ToolbarButtonType;\n Select: ToolbarSelectType;\n };\n FilePanel: {\n Root: {\n className?: string;\n tabs: {\n name: string;\n tabPanel: ReactNode;\n }[];\n openTab: string;\n setOpenTab: (name: string) => void;\n defaultOpenTab: string;\n loading: boolean;\n };\n Button: {\n className?: string;\n /**\n * Explicit, because the skins' underlying buttons disagree on the\n * default (Mantine's is `type=\"button\"`, shadcn's was `\"submit\"`) and\n * a submit button inside a `Form.Root` must reliably submit on every\n * skin. `\"submit\"` buttons need no `onClick` - the form's `onSubmit`\n * is the single commit path, so clicking cannot fire twice.\n */\n type: \"button\" | \"submit\";\n onClick?: () => void;\n } & (\n | { children: ReactNode; label?: string }\n | { children?: undefined; label: string }\n );\n FileInput: {\n className?: string;\n accept: string;\n value: File | null;\n placeholder: string;\n onChange: (payload: File | null) => void;\n };\n TabPanel: {\n className?: string;\n children?: ReactNode;\n };\n TextInput: {\n className?: string;\n value: string;\n placeholder: string;\n onChange: (event: ChangeEvent<HTMLInputElement>) => void;\n onKeyDown?: (event: KeyboardEvent) => void;\n };\n };\n LinkToolbar: {\n Root: ToolbarRootType;\n Button: ToolbarButtonType;\n Select: ToolbarSelectType;\n };\n SideMenu: {\n Root: {\n className?: string;\n children?: ReactNode;\n };\n Button: {\n className?: string;\n onClick?: (e: MouseEvent) => void;\n icon?: ReactNode;\n onDragStart?: (e: React.DragEvent) => void;\n onDragEnd?: (e: React.DragEvent) => void;\n draggable?: boolean;\n } & (\n | { children: ReactNode; label?: string }\n | { children?: undefined; label: string }\n );\n };\n SuggestionMenu: {\n Root: {\n id: string;\n className?: string;\n children?: ReactNode;\n };\n EmptyItem: {\n className?: string;\n children?: ReactNode;\n };\n Item: {\n className?: string;\n id: string;\n isSelected: boolean;\n onClick: () => void;\n item: Omit<DefaultReactSuggestionItem, \"onItemClick\">;\n };\n Label: {\n className?: string;\n children?: ReactNode;\n };\n Loader: {\n className?: string;\n };\n };\n GridSuggestionMenu: {\n Root: {\n id: string;\n columns: number;\n className?: string;\n children?: ReactNode;\n };\n EmptyItem: {\n columns: number;\n className?: string;\n children?: ReactNode;\n };\n Item: {\n className?: string;\n id: string;\n isSelected: boolean;\n onClick: () => void;\n item: DefaultReactGridSuggestionItem;\n };\n // Label: {\n // className?: string;\n // children?: ReactNode;\n // };\n Loader: {\n columns: number;\n className?: string;\n children?: ReactNode;\n };\n };\n TableHandle: {\n Root: {\n className?: string;\n draggable: boolean;\n onDragStart: (e: React.DragEvent) => void;\n onDragEnd: () => void;\n style?: CSSProperties;\n } & (\n | { children: ReactNode; label?: string }\n | { children?: undefined; label: string }\n );\n ExtendButton: {\n className?: string;\n onClick: (e: React.MouseEvent) => void;\n onMouseDown: (e: React.MouseEvent) => void;\n children: ReactNode;\n };\n };\n Comments: {\n Card: {\n className?: string;\n headerText?: string;\n selected?: boolean;\n onFocus?: (event: React.FocusEvent) => void;\n onBlur?: (event: React.FocusEvent) => void;\n tabIndex?: number;\n children?: ReactNode;\n };\n CardSection: {\n className?: string;\n children?: ReactNode;\n };\n ExpandSectionsPrompt: {\n className?: string;\n children?: ReactNode;\n };\n Editor: {\n className?: string;\n autoFocus?: boolean;\n editable: boolean;\n editor: BlockNoteEditor<any, any, any>;\n onFocus?: () => void;\n onBlur?: () => void;\n };\n Comment: {\n className?: string;\n children?: ReactNode;\n authorInfo: \"loading\" | User;\n timeString: string;\n edited: boolean;\n actions?: ReactNode;\n showActions?: boolean | \"hover\";\n emojiPickerOpen?: boolean;\n };\n };\n Versioning: {\n /**\n * The scrollable container for the version-history sidebar (header +\n * snapshot rows).\n */\n Sidebar: {\n className?: string;\n children?: ReactNode;\n };\n /**\n * A single row in the version-history sidebar — the live \"current version\"\n * entry or a stored snapshot.\n */\n Snapshot: {\n className?: string;\n /** Whether this row is the version currently shown in the editor. */\n selected?: boolean;\n /** Whether this row is the baseline the current diff is compared against. */\n comparing?: boolean;\n onClick?: () => void;\n /** Row actions (e.g. the \"...\" menu), revealed on hover. */\n actions?: ReactNode;\n children?: ReactNode;\n };\n };\n AttributionTooltip: {\n /**\n * The attribution tooltip shown when hovering a suggestion mark. Positioned\n * by floating-ui and portaled by the controller — this only styles the box.\n */\n Root: {\n className?: string;\n /**\n * App-supplied class from `getAttributionMarkClassName` (override path).\n * When set, `Root` applies it and ignores `backgroundColor`.\n */\n markClassName?: string;\n /**\n * Per-user author color (default path). Applied inline when there's no\n * `markClassName`, since the tooltip is portaled away from the mark and\n * can't inherit the mark's color.\n */\n backgroundColor?: string;\n children?: ReactNode;\n };\n };\n // TODO: We should try to make everything as generic as we can\n Generic: {\n Badge: {\n Root: {\n className?: string;\n text: string;\n icon?: ReactNode;\n isSelected?: boolean;\n mainTooltip?: string;\n secondaryTooltip?: string;\n onClick?: (event: React.MouseEvent) => void;\n onMouseEnter?: () => void;\n };\n Group: {\n className?: string;\n children: ReactNode;\n };\n };\n Form: {\n Root: {\n children?: ReactNode;\n /**\n * Called on the form's `submit` event. Implementations must render a\n * real `<form>` and `preventDefault`: native submission is the only\n * path that works for every input source — mobile IMEs commit\n * through it without dispatching any key event.\n */\n onSubmit?: () => void;\n /**\n * The form's submit control, rendered inside the `<form>`. Usually\n * `ScreenReaderOnlySubmit`; a visible `type=\"submit\"` button to make\n * it the form's one affordance (the embed tab); or `\"none\"` — then\n * the form must have exactly one field, or Enter submits nothing.\n */\n submitButton: ReactElement | \"none\";\n };\n TextInput: {\n className?: string;\n name: string;\n label?: string;\n variant?: \"default\" | \"large\";\n icon: ReactNode;\n rightSection?: ReactNode;\n autoFocus?: boolean;\n placeholder?: string;\n disabled?: boolean;\n value: string;\n onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;\n onChange: (event: ChangeEvent<HTMLInputElement>) => void;\n autoComplete?: HTMLInputAutoCompleteAttribute;\n \"aria-activedescendant\"?: string;\n ref?: ForwardedRef<HTMLInputElement>;\n };\n };\n Menu: {\n Root: {\n sub?: boolean;\n onOpenChange?: (open: boolean) => void;\n position?:\n | \"top\"\n | \"right\"\n | \"bottom\"\n | \"left\"\n | `${\"top\" | \"right\" | \"bottom\" | \"left\"}-${\"start\" | \"end\"}`;\n portalElement: HTMLElement | null;\n /** See `ToolbarSelect.preventFocusOnOpen`. */\n preventFocusOnOpen?: boolean;\n children?: ReactNode;\n };\n Divider: {\n className?: string;\n };\n Dropdown: {\n className?: string;\n children?: ReactNode;\n sub?: boolean;\n };\n Item: {\n className?: string;\n children?: ReactNode;\n\n subTrigger?: boolean;\n icon?: ReactNode;\n checked?: boolean;\n onClick?: () => void;\n };\n Label: {\n className?: string;\n children?: ReactNode;\n };\n Trigger: {\n children?: ReactNode;\n sub?: boolean;\n };\n Button: MenuButtonType;\n };\n Popover: {\n Root: {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n position?:\n | \"top\"\n | \"right\"\n | \"bottom\"\n | \"left\"\n | `${\"top\" | \"right\" | \"bottom\" | \"left\"}-${\"start\" | \"end\"}`;\n portalElement: HTMLElement | null;\n children?: ReactNode;\n };\n Content: {\n className?: string;\n variant: \"form-popover\" | \"panel-popover\";\n children?: ReactNode;\n };\n Trigger: {\n children?: ReactNode;\n };\n };\n Toolbar: {\n Root: ToolbarRootType;\n Button: ToolbarButtonType;\n Select: ToolbarSelectType;\n };\n };\n};\n\nexport type Components = {\n [Components in keyof Omit<ComponentProps, \"Generic\">]: {\n [Component in keyof ComponentProps[Components]]: ComponentType<\n ComponentProps[Components][Component]\n >;\n };\n} & {\n // only needed as Generic Root/etc elements are 1 level of nesting deeper\n Generic: {\n [GenericComponents in keyof ComponentProps[\"Generic\"]]: {\n [Component in keyof ComponentProps[\"Generic\"][GenericComponents]]: ComponentType<\n ComponentProps[\"Generic\"][GenericComponents][Component]\n >;\n };\n };\n};\n\nexport const ComponentsContext = createContext<Components | undefined>(\n undefined,\n);\n\nexport function useComponentsContext(): Components | undefined {\n return useContext(ComponentsContext)!;\n}\n","import { Dictionary } from \"@blocknote/core\";\nimport { useBlockNoteContext } from \"../editor/BlockNoteContext.js\";\n\nexport function useDictionary(): Dictionary {\n const ctx = useBlockNoteContext();\n return ctx!.editor!.dictionary;\n}\n","import { posToDOMRect } from \"@tiptap/core\";\nimport { ReactNode, useMemo } from \"react\";\n\nimport { useBlockNoteEditor } from \"../../hooks/useBlockNoteEditor.js\";\nimport { useEditorDOMElement } from \"../../hooks/useEditorDomElement.js\";\nimport { FloatingUIOptions } from \"./FloatingUIOptions.js\";\nimport { GenericPopover, GenericPopoverReference } from \"./GenericPopover.js\";\n\nexport const PositionPopover = (\n props: FloatingUIOptions & {\n position: { from: number; to?: number } | undefined;\n children: ReactNode;\n },\n) => {\n const { position, children, ...floatingUIOptions } = props;\n const { from, to } = position || {};\n\n const editor = useBlockNoteEditor<any, any, any>();\n const editorDOMElement = useEditorDOMElement();\n\n const reference = useMemo<GenericPopoverReference | undefined>(() => {\n if (from === undefined || to === undefined) {\n return undefined;\n }\n\n return {\n // Use first child as the editor DOM element may itself be scrollable.\n // For FloatingUI to auto-update the position during scrolling, the\n // `contextElement` must be a descendant of the scroll container.\n element: editorDOMElement?.firstElementChild || undefined,\n getBoundingClientRect: () =>\n posToDOMRect(editor.prosemirrorView, from, to ?? from),\n };\n }, [editor, editorDOMElement, from, to]);\n\n return (\n <GenericPopover reference={reference} {...floatingUIOptions}>\n {position !== undefined && children}\n </GenericPopover>\n );\n};\n","import {\n BlockNoteEditor,\n BlockNoteEditorOptions,\n CustomBlockNoteSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n} from \"@blocknote/core\";\nimport { DependencyList, useMemo } from \"react\";\n\n/**\n * Hook to instantiate a BlockNote Editor instance in React\n */\nexport const useCreateBlockNote = <\n Options extends Partial<BlockNoteEditorOptions<any, any, any>> | undefined,\n>(\n options: Options = {} as Options,\n deps: DependencyList = [],\n): Options extends {\n schema: CustomBlockNoteSchema<infer BSchema, infer ISchema, infer SSchema>;\n}\n ? BlockNoteEditor<BSchema, ISchema, SSchema>\n : BlockNoteEditor<\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema\n > => {\n return useMemo(() => {\n const editor = BlockNoteEditor.create(options) as any;\n if (window) {\n // for testing / dev purposes\n (window as any).ProseMirror = editor._tiptapEditor;\n }\n return editor;\n }, deps); //eslint-disable-line react-hooks/exhaustive-deps\n};\n","import { BlockNoteEditor } from \"@blocknote/core\";\nimport { ReactNode, useCallback, useEffect, useState } from \"react\";\nimport { useComponentsContext } from \"../../editor/ComponentsContext.js\";\nimport { useEditorState } from \"../../hooks/useEditorState.js\";\n\n/**\n * The CommentEditor component displays an editor for creating or editing a comment.\n * Currently, we also use the non-editable version for displaying a comment.\n *\n * It's used:\n * - to create a new comment (FloatingComposer.tsx)\n * - As the last item in a Thread, to compose a reply (Thread.tsx)\n * - To edit or display an existing comment (Comment.tsx)\n *\n */\nexport const CommentEditor = (props: {\n autoFocus?: boolean;\n editable: boolean;\n actions?: (args: { isFocused: boolean; isEmpty: boolean }) => ReactNode;\n editor: BlockNoteEditor<any, any, any>;\n}) => {\n const [isFocused, setIsFocused] = useState(false);\n const isEmpty = useEditorState({\n editor: props.editor,\n selector: ({ editor }) => editor.isEmpty,\n });\n\n const components = useComponentsContext()!;\n\n const onFocus = useCallback(() => {\n setIsFocused(true);\n }, []);\n\n const onBlur = useCallback(() => {\n setIsFocused(false);\n }, []);\n\n // When we click the edit button on a comment, we also want to focus the\n // comment editor\n useEffect(() => {\n if (props.editable && props.autoFocus) {\n props.editor.focus();\n }\n }, [props.autoFocus, props.editable, props.editor]);\n\n return (\n <>\n <components.Comments.Editor\n autoFocus={props.autoFocus}\n className=\"bn-comment-editor\"\n editor={props.editor}\n onFocus={onFocus}\n onBlur={onBlur}\n editable={props.editable}\n />\n {props.actions && (\n <div className={\"bn-comment-actions-wrapper\"}>\n {props.actions({ isFocused, isEmpty })}\n </div>\n )}\n </>\n );\n};\n","import { BlockNoteSchema, defaultStyleSpecs } from \"@blocknote/core\";\nimport { createParagraphBlockSpec } from \"@blocknote/core\";\n\n// this is quite convoluted. we'll clean this up when we make\n// it easier to extend / customize the default blocks\n\n// remove textColor, backgroundColor from styleSpecs\nconst {\n textColor: _textColor,\n backgroundColor: _backgroundColor,\n ...styleSpecs\n} = defaultStyleSpecs;\n\n// the schema to use for comments\nexport const defaultCommentEditorSchema = BlockNoteSchema.create({\n blockSpecs: {\n paragraph: createParagraphBlockSpec(),\n },\n styleSpecs,\n});\n","/**\n * Decides whether comment editor content may be discarded, prompting the user\n * for confirmation when there is unsaved content and confirmation is enabled.\n *\n * Used by the comment composers (new comment, reply and edit) when they're\n * dismissed (e.g. by clicking outside or pressing Escape), so the user doesn't\n * silently lose text they've typed.\n *\n * @returns `true` when it's safe to discard (nothing unsaved, confirmation\n * disabled, or the user accepted the prompt), and `false` when the user\n * cancelled and the editor should stay open.\n */\nexport function confirmDiscardUnsavedComment(opts: {\n /**\n * Whether the editor(s) being dismissed currently hold unsaved content.\n */\n hasUnsavedContent: boolean;\n /**\n * Whether the confirmation prompt is enabled (see the `confirmBeforeDiscard`\n * option on the comments extension).\n */\n confirmBeforeDiscard: boolean;\n /**\n * The message shown in the confirmation prompt.\n */\n message: string;\n /**\n * The confirm implementation. Defaults to `window.confirm`; injectable for\n * testing.\n */\n confirm?: (message: string) => boolean;\n}): boolean {\n if (!opts.hasUnsavedContent || !opts.confirmBeforeDiscard) {\n return true;\n }\n\n const confirm = opts.confirm ?? ((message) => window.confirm(message));\n return confirm(opts.message);\n}\n"],"mappings":"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"}