diff --git a/packages/core/src/util/browser.ts b/packages/core/src/util/browser.ts index 9ecdf3250d..ef626cee8c 100644 --- a/packages/core/src/util/browser.ts +++ b/packages/core/src/util/browser.ts @@ -12,7 +12,7 @@ export function formatKeyboardShortcut(shortcut: string, ctrlText = "Ctrl") { } } -export function mergeCSSClasses(...classes: string[]) { +export function mergeCSSClasses(...classes: (string | undefined)[]) { return classes.filter((c) => c).join(" "); } diff --git a/packages/react/src/editor/BlockNoteContext.ts b/packages/react/src/editor/BlockNoteContext.ts index 1a14ab4805..daabf51e26 100644 --- a/packages/react/src/editor/BlockNoteContext.ts +++ b/packages/react/src/editor/BlockNoteContext.ts @@ -10,7 +10,7 @@ import { } from "@blocknote/core"; import { createContext, useContext, useState } from "react"; -type BlockNoteContextValue< +export type BlockNoteContextValue< BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index a18b10e49d..b719e6b59f 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -5,7 +5,6 @@ import { StyleSchema, mergeCSSClasses, } from "@blocknote/core"; - import React, { HTMLAttributes, ReactNode, @@ -15,14 +14,23 @@ import React, { useMemo, useState, } from "react"; +import { useBlockNoteEditor } from "../hooks/useBlockNoteEditor.js"; import { useEditorChange } from "../hooks/useEditorChange.js"; import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange.js"; import { usePrefersColorScheme } from "../hooks/usePrefersColorScheme.js"; -import { BlockNoteContext, useBlockNoteContext } from "./BlockNoteContext.js"; +import { + BlockNoteContext, + BlockNoteContextValue, + useBlockNoteContext, +} from "./BlockNoteContext.js"; import { BlockNoteDefaultUI, BlockNoteDefaultUIProps, } from "./BlockNoteDefaultUI.js"; +import { + BlockNoteViewContext, + useBlockNoteViewContext, +} from "./BlockNoteViewContext.js"; import { Portals, getContentComponent } from "./EditorContent.js"; import { ElementRenderer } from "./ElementRenderer.js"; import "./styles.css"; @@ -40,8 +48,18 @@ export type BlockNoteViewProps< theme?: "light" | "dark"; + /** + * Whether to render the editor element itself. + * When `false`, you're responsible for rendering the editor yourself using the `BlockNoteViewEditor` component. + * + * @default true + */ + renderEditor?: boolean; + /** * Locks the editor from being editable by the user if set to `false`. + * + * @default true */ editable?: boolean; /** @@ -86,6 +104,8 @@ function BlockNoteViewComponent< sideMenu, filePanel, tableHandles, + autoFocus, + renderEditor = !editor.headless, ...rest } = props; @@ -109,33 +129,16 @@ function BlockNoteViewComponent< editor.isEditable = editable !== false; }, [editable, editor]); - const renderChildren = useMemo(() => { - return ( - <> - {children} - - - ); - }, [ - children, - formattingToolbar, - linkToolbar, - slashMenu, - emojiPicker, - sideMenu, - filePanel, - tableHandles, - ]); + const setElementRenderer = useCallback( + (ref: (typeof editor)["elementRenderer"]) => { + editor.elementRenderer = ref; + }, + [editor] + ); - const context = useMemo(() => { + // The BlockNoteContext makes sure the editor and some helper methods + // are always available to nesteed compoenents + const blockNoteContext: BlockNoteContextValue = useMemo(() => { return { ...existingContext, editor, @@ -143,49 +146,41 @@ function BlockNoteViewComponent< }; }, [existingContext, editor]); - const setElementRenderer = useCallback( - (ref: (typeof editor)["elementRenderer"]) => { - editor.elementRenderer = ref; - }, - [editor] - ); - - const portalManager = useMemo(() => { - return getContentComponent(); - }, []); + // We set defaultUIProps and editorProps on a different context, the BlockNoteViewContext. + // This BlockNoteViewContext is used to render the editor and the default UI. + const defaultUIProps = { + formattingToolbar, + linkToolbar, + slashMenu, + emojiPicker, + sideMenu, + filePanel, + tableHandles, + }; - const mount = useCallback( - (element: HTMLElement | null) => { - editor.mount(element, portalManager); - }, - [editor, portalManager] - ); + const editorProps = { + autoFocus, + className, + editorColorScheme, + contentEditableProps, + ref, + ...rest, + }; return ( - - - {!editor.headless && ( - <> - -
-
- {renderChildren} -
- - )} + + + + {renderEditor ? ( + {children} + ) : ( + children + )} + ); } @@ -200,3 +195,76 @@ export const BlockNoteViewRaw = React.forwardRef(BlockNoteViewComponent) as < ref?: React.ForwardedRef; } ) => ReturnType>; + +/** + * Renders the editor itself and the default UI elements + */ +export const BlockNoteViewEditor = (props: { children: ReactNode }) => { + const ctx = useBlockNoteViewContext()!; + const editor = useBlockNoteEditor(); + + const portalManager = useMemo(() => { + return getContentComponent(); + }, []); + + const mount = useCallback( + (element: HTMLElement | null) => { + editor.mount(element, portalManager); + }, + [editor, portalManager] + ); + + return ( + <> + + + {/* Renders the UI elements such as formatting toolbar, etc, unless they have been explicitly disabled in defaultUIProps */} + + {/* Manually passed in children, such as customized UI elements / controllers */} + {props.children} + + + ); +}; + +/** + * Renders the container div + contentEditable div. + */ +const EditorElement = ( + props: { + className?: string; + editorColorScheme?: string; + autoFocus?: boolean; + mount: (element: HTMLElement | null) => void; + contentEditableProps?: Record; + children: ReactNode; + } & HTMLAttributes +) => { + const { + className, + editorColorScheme, + autoFocus, + mount, + children, + contentEditableProps, + ...rest + } = props; + return ( + // The container wraps the contentEditable div and UI Elements such as sidebar, formatting toolbar, etc. +
+ {/* The actual contentEditable that Prosemirror mounts to */} +
+ {/* The UI elements such as sidebar, formatting toolbar, etc. */} + {children} +
+ ); +}; diff --git a/packages/react/src/editor/BlockNoteViewContext.ts b/packages/react/src/editor/BlockNoteViewContext.ts new file mode 100644 index 0000000000..f285ddace4 --- /dev/null +++ b/packages/react/src/editor/BlockNoteViewContext.ts @@ -0,0 +1,19 @@ +import { createContext, useContext } from "react"; +import { BlockNoteDefaultUIProps } from "./BlockNoteDefaultUI.js"; + +export type BlockNoteViewContextValue = { + editorProps: any; + defaultUIProps: BlockNoteDefaultUIProps; +}; + +export const BlockNoteViewContext = createContext< + BlockNoteViewContextValue | undefined +>(undefined); + +export function useBlockNoteViewContext(): + | BlockNoteViewContextValue + | undefined { + const context = useContext(BlockNoteViewContext) as any; + + return context; +}