New:Socket for Asana Is Now Available.Learn more
Get Started

@tiptap/react

Package Overview
Dependencies
Maintainers
6
Versions
413
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tiptap/react - npm Package Compare versions

Comparing version
3.18.0
to
3.19.0
+15
-263
dist/index.cjs

@@ -51,7 +51,4 @@ "use strict";

Tiptap: () => Tiptap,
TiptapBubbleMenu: () => TiptapBubbleMenu,
TiptapContent: () => TiptapContent,
TiptapContext: () => TiptapContext,
TiptapFloatingMenu: () => TiptapFloatingMenu,
TiptapLoading: () => TiptapLoading,
TiptapWrapper: () => TiptapWrapper,

@@ -1127,199 +1124,11 @@ useCurrentEditor: () => useCurrentEditor,

// src/Tiptap.tsx
var import_react14 = require("react");
// src/menus/BubbleMenu.tsx
var import_extension_bubble_menu = require("@tiptap/extension-bubble-menu");
var import_react10 = require("@tiptap/react");
var import_react11 = __toESM(require("react"), 1);
var import_react_dom3 = require("react-dom");
var import_react10 = require("react");
var import_jsx_runtime8 = require("react/jsx-runtime");
var BubbleMenu = import_react11.default.forwardRef(
({
pluginKey = "bubbleMenu",
editor,
updateDelay,
resizeDelay,
appendTo,
shouldShow = null,
getReferencedVirtualElement,
options,
children,
...restProps
}, ref) => {
const menuEl = (0, import_react11.useRef)(document.createElement("div"));
if (typeof ref === "function") {
ref(menuEl.current);
} else if (ref) {
ref.current = menuEl.current;
}
const { editor: currentEditor } = (0, import_react10.useCurrentEditor)();
const pluginEditor = editor || currentEditor;
const bubbleMenuPluginProps = {
updateDelay,
resizeDelay,
appendTo,
pluginKey,
shouldShow,
getReferencedVirtualElement,
options
};
const bubbleMenuPluginPropsRef = (0, import_react11.useRef)(bubbleMenuPluginProps);
bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps;
const [pluginInitialized, setPluginInitialized] = (0, import_react11.useState)(false);
const skipFirstUpdateRef = (0, import_react11.useRef)(true);
(0, import_react11.useEffect)(() => {
if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) {
return;
}
if (!pluginEditor) {
console.warn("BubbleMenu component is not rendered inside of an editor component or does not have editor prop.");
return;
}
const bubbleMenuElement = menuEl.current;
bubbleMenuElement.style.visibility = "hidden";
bubbleMenuElement.style.position = "absolute";
const plugin = (0, import_extension_bubble_menu.BubbleMenuPlugin)({
...bubbleMenuPluginPropsRef.current,
editor: pluginEditor,
element: bubbleMenuElement
});
pluginEditor.registerPlugin(plugin);
const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey;
skipFirstUpdateRef.current = true;
setPluginInitialized(true);
return () => {
setPluginInitialized(false);
pluginEditor.unregisterPlugin(createdPluginKey);
window.requestAnimationFrame(() => {
if (bubbleMenuElement.parentNode) {
bubbleMenuElement.parentNode.removeChild(bubbleMenuElement);
}
});
};
}, [pluginEditor]);
(0, import_react11.useEffect)(() => {
if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {
return;
}
if (skipFirstUpdateRef.current) {
skipFirstUpdateRef.current = false;
return;
}
pluginEditor.view.dispatch(
pluginEditor.state.tr.setMeta("bubbleMenu", {
type: "updateOptions",
options: bubbleMenuPluginPropsRef.current
})
);
}, [
pluginInitialized,
pluginEditor,
updateDelay,
resizeDelay,
shouldShow,
options,
appendTo,
getReferencedVirtualElement
]);
return (0, import_react_dom3.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ...restProps, children }), menuEl.current);
var TiptapContext = (0, import_react10.createContext)({
get editor() {
throw new Error("useTiptap must be used within a <Tiptap> provider");
}
);
// src/menus/FloatingMenu.tsx
var import_extension_floating_menu = require("@tiptap/extension-floating-menu");
var import_react12 = require("@tiptap/react");
var import_react13 = __toESM(require("react"), 1);
var import_react_dom4 = require("react-dom");
var import_jsx_runtime9 = require("react/jsx-runtime");
var FloatingMenu = import_react13.default.forwardRef(
({
pluginKey = "floatingMenu",
editor,
updateDelay,
resizeDelay,
appendTo,
shouldShow = null,
options,
children,
...restProps
}, ref) => {
const menuEl = (0, import_react13.useRef)(document.createElement("div"));
if (typeof ref === "function") {
ref(menuEl.current);
} else if (ref) {
ref.current = menuEl.current;
}
const { editor: currentEditor } = (0, import_react12.useCurrentEditor)();
const pluginEditor = editor || currentEditor;
const floatingMenuPluginProps = {
updateDelay,
resizeDelay,
appendTo,
pluginKey,
shouldShow,
options
};
const floatingMenuPluginPropsRef = (0, import_react13.useRef)(floatingMenuPluginProps);
floatingMenuPluginPropsRef.current = floatingMenuPluginProps;
const [pluginInitialized, setPluginInitialized] = (0, import_react13.useState)(false);
const skipFirstUpdateRef = (0, import_react13.useRef)(true);
(0, import_react13.useEffect)(() => {
if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) {
return;
}
if (!pluginEditor) {
console.warn(
"FloatingMenu component is not rendered inside of an editor component or does not have editor prop."
);
return;
}
const floatingMenuElement = menuEl.current;
floatingMenuElement.style.visibility = "hidden";
floatingMenuElement.style.position = "absolute";
const plugin = (0, import_extension_floating_menu.FloatingMenuPlugin)({
...floatingMenuPluginPropsRef.current,
editor: pluginEditor,
element: floatingMenuElement
});
pluginEditor.registerPlugin(plugin);
const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey;
skipFirstUpdateRef.current = true;
setPluginInitialized(true);
return () => {
setPluginInitialized(false);
pluginEditor.unregisterPlugin(createdPluginKey);
window.requestAnimationFrame(() => {
if (floatingMenuElement.parentNode) {
floatingMenuElement.parentNode.removeChild(floatingMenuElement);
}
});
};
}, [pluginEditor]);
(0, import_react13.useEffect)(() => {
if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {
return;
}
if (skipFirstUpdateRef.current) {
skipFirstUpdateRef.current = false;
return;
}
pluginEditor.view.dispatch(
pluginEditor.state.tr.setMeta("floatingMenu", {
type: "updateOptions",
options: floatingMenuPluginPropsRef.current
})
);
}, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo]);
return (0, import_react_dom4.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ...restProps, children }), menuEl.current);
}
);
// src/Tiptap.tsx
var import_jsx_runtime10 = require("react/jsx-runtime");
var TiptapContext = (0, import_react14.createContext)({
editor: null,
isReady: false
});
TiptapContext.displayName = "TiptapContext";
var useTiptap = () => (0, import_react14.useContext)(TiptapContext);
var useTiptap = () => (0, import_react10.useContext)(TiptapContext);
function useTiptapState(selector, equalityFn) {

@@ -1333,25 +1142,10 @@ const { editor } = useTiptap();

}
function TiptapWrapper({ instance, children }) {
var _a;
const [isReady, setIsReady] = (0, import_react14.useState)((_a = instance == null ? void 0 : instance.isInitialized) != null ? _a : false);
(0, import_react14.useEffect)(() => {
if (!instance) {
setIsReady(false);
return;
}
if (instance.isInitialized) {
setIsReady(true);
return;
}
const handleCreate = () => {
setIsReady(true);
};
instance.on("create", handleCreate);
return () => {
instance.off("create", handleCreate);
};
}, [instance]);
const tiptapContextValue = (0, import_react14.useMemo)(() => ({ editor: instance, isReady }), [instance, isReady]);
const legacyContextValue = (0, import_react14.useMemo)(() => ({ editor: instance }), [instance]);
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TiptapContext.Provider, { value: tiptapContextValue, children }) });
function TiptapWrapper({ editor, instance, children }) {
const resolvedEditor = editor != null ? editor : instance;
if (!resolvedEditor) {
throw new Error("Tiptap: An editor instance is required. Pass a non-null `editor` prop.");
}
const tiptapContextValue = (0, import_react10.useMemo)(() => ({ editor: resolvedEditor }), [resolvedEditor]);
const legacyContextValue = (0, import_react10.useMemo)(() => ({ editor: resolvedEditor }), [resolvedEditor]);
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TiptapContext.Provider, { value: tiptapContextValue, children }) });
}

@@ -1361,29 +1155,5 @@ TiptapWrapper.displayName = "Tiptap";

const { editor } = useTiptap();
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EditorContent, { editor, ...rest });
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorContent, { editor, ...rest });
}
TiptapContent.displayName = "Tiptap.Content";
function TiptapLoading({ children }) {
const { isReady } = useTiptap();
if (isReady) {
return null;
}
return children;
}
TiptapLoading.displayName = "Tiptap.Loading";
function TiptapBubbleMenu({ children, ...rest }) {
const { editor } = useTiptap();
if (!editor) {
return null;
}
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BubbleMenu, { editor, ...rest, children });
}
TiptapBubbleMenu.displayName = "Tiptap.BubbleMenu";
function TiptapFloatingMenu({ children, ...rest }) {
const { editor } = useTiptap();
if (!editor) {
return null;
}
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FloatingMenu, { ...rest, editor, children });
}
TiptapFloatingMenu.displayName = "Tiptap.FloatingMenu";
var Tiptap = Object.assign(TiptapWrapper, {

@@ -1394,18 +1164,3 @@ /**

*/
Content: TiptapContent,
/**
* The Tiptap Loading component that renders its children only when the editor is not ready.
* @see TiptapLoading
*/
Loading: TiptapLoading,
/**
* The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
* @see TiptapBubbleMenu
*/
BubbleMenu: TiptapBubbleMenu,
/**
* The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
* @see TiptapFloatingMenu
*/
FloatingMenu: TiptapFloatingMenu
Content: TiptapContent
});

@@ -1434,7 +1189,4 @@

Tiptap,
TiptapBubbleMenu,
TiptapContent,
TiptapContext,
TiptapFloatingMenu,
TiptapLoading,
TiptapWrapper,

@@ -1441,0 +1193,0 @@ useCurrentEditor,

+45
-136

@@ -8,4 +8,2 @@ import * as react_jsx_runtime from 'react/jsx-runtime';

import { Decoration, DecorationSource } from '@tiptap/pm/view';
import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
import { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu';

@@ -307,24 +305,15 @@ /**

type Optional$1<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type BubbleMenuProps = Optional$1<Omit<Optional$1<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> & React__default.HTMLAttributes<HTMLDivElement>;
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {
editor: FloatingMenuPluginProps['editor'] | null;
options?: FloatingMenuPluginProps['options'];
} & React__default.HTMLAttributes<HTMLDivElement>;
/**
* The shape of the React context used by the `<Tiptap />` components.
*
* This object exposes the editor instance and a simple readiness flag.
* The editor instance is always available when using the default `useEditor`
* configuration. For SSR scenarios where `immediatelyRender: false` is used,
* consider using the legacy `EditorProvider` pattern instead.
*/
type TiptapContextType = {
/** The Tiptap editor instance. May be null during SSR or before initialization. */
editor: Editor | null;
/** True when the editor has finished initializing and is ready for user interaction. */
isReady: boolean;
/** The Tiptap editor instance. */
editor: Editor;
};
/**
* React context that stores the current editor instance and readiness flag.
* React context that stores the current editor instance.
*

@@ -335,5 +324,6 @@ * Use `useTiptap()` to read from this context in child components.

/**
* Hook to read the Tiptap context (`editor` + `isReady`).
* Hook to read the Tiptap context and access the editor instance.
*
* This is a small convenience wrapper around `useContext(TiptapContext)`.
* The editor is always available when used within a `<Tiptap>` provider.
*

@@ -346,5 +336,10 @@ * @returns The current `TiptapContextType` value from the provider.

*
* function Status() {
* const { isReady } = useTiptap()
* return <div>{isReady ? 'Editor ready' : 'Loading editor...'}</div>
* function Toolbar() {
* const { editor } = useTiptap()
*
* return (
* <button onClick={() => editor.chain().focus().toggleBold().run()}>
* Bold
* </button>
* )
* }

@@ -360,6 +355,2 @@ * ```

*
* Important: This hook should only be used when the editor is available.
* Use the `isReady` flag from `useTiptap()` to guard against null editor,
* or ensure your component only renders after the editor is initialized.
*
* @typeParam TSelectorResult - The type returned by the selector.

@@ -375,5 +366,2 @@ * @param selector - Function that receives the editor state snapshot and

* function WordCount() {
* const { isReady } = useTiptap()
*
* // Only use useTiptapState when the editor is ready
* const wordCount = useTiptapState(state => {

@@ -384,4 +372,2 @@ * const text = state.editor.state.doc.textContent

*
* if (!isReady) return null
*
* return <span>{wordCount} words</span>

@@ -398,5 +384,9 @@ * }

* The editor instance to provide to child components.
* Can be null during SSR or before initialization.
* Use `useEditor()` to create this instance.
*/
instance: Editor | null;
editor?: Editor;
/**
* @deprecated Use `editor` instead. Will be removed in the next major version.
*/
instance?: Editor;
children: ReactNode;

@@ -406,7 +396,4 @@ };

* Top-level provider component that makes the editor instance available via
* React context and tracks when the editor becomes ready.
* React context to all child components.
*
* The component listens to the editor's `create` event and flips the
* `isReady` flag once initialization completes.
*
* This component also provides backwards compatibility with the legacy

@@ -427,3 +414,3 @@ * `EditorContext`, so components using `useCurrentEditor()` will work

* return (
* <Tiptap instance={editor}>
* <Tiptap editor={editor}>
* <Toolbar />

@@ -436,3 +423,3 @@ * <Tiptap.Content />

*/
declare function TiptapWrapper({ instance, children }: TiptapWrapperProps): react_jsx_runtime.JSX.Element;
declare function TiptapWrapper({ editor, instance, children }: TiptapWrapperProps): react_jsx_runtime.JSX.Element;
declare namespace TiptapWrapper {

@@ -458,77 +445,7 @@ var displayName: string;

}
type TiptapLoadingProps = {
children: ReactNode;
};
/**
* Component that renders its children only when the editor is not ready.
*
* This is useful for displaying loading states or placeholders during
* editor initialization, especially with SSR.
*
* @param props - The props for the TiptapLoading component.
* @returns The children when editor is not ready, or null when ready.
*
* @example
* ```tsx
* <Tiptap instance={editor}>
* <Tiptap.Loading>
* <div className="skeleton">Loading editor...</div>
* </Tiptap.Loading>
* <Tiptap.Content />
* </Tiptap>
* ```
*/
declare function TiptapLoading({ children }: TiptapLoadingProps): ReactNode;
declare namespace TiptapLoading {
var displayName: string;
}
/**
* A wrapper around the library `BubbleMenu` that injects the editor from
* context so callers don't need to pass the `editor` prop.
*
* Returns `null` when the editor is not available (for example during SSR).
*
* @param props - Props for the underlying `BubbleMenu` (except `editor`).
* @returns A `BubbleMenu` bound to the context editor, or `null`.
*
* @example
* ```tsx
* <Tiptap.BubbleMenu tippyOptions={{ duration: 100 }}>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </Tiptap.BubbleMenu>
* ```
*/
declare function TiptapBubbleMenu({ children, ...rest }: {
children: ReactNode;
} & Omit<BubbleMenuProps, 'editor'>): react_jsx_runtime.JSX.Element | null;
declare namespace TiptapBubbleMenu {
var displayName: string;
}
/**
* A wrapper around the library `FloatingMenu` that injects the editor from
* context so callers don't need to pass the `editor` prop.
*
* Returns `null` when the editor is not available.
*
* @param props - Props for the underlying `FloatingMenu` (except `editor`).
* @returns A `FloatingMenu` bound to the context editor, or `null`.
*
* @example
* ```tsx
* <Tiptap.FloatingMenu placement="top">
* <button onClick={() => editor.chain().focus().toggleItalic().run()}>Italic</button>
* </Tiptap.FloatingMenu>
* ```
*/
declare function TiptapFloatingMenu({ children, ...rest }: {
children: ReactNode;
} & Omit<FloatingMenuProps, 'editor'>): react_jsx_runtime.JSX.Element | null;
declare namespace TiptapFloatingMenu {
var displayName: string;
}
/**
* Root `Tiptap` component. Use it as the provider for all child components.
*
* The exported object includes several helper subcomponents for common use
* cases: `Content`, `Loading`, `BubbleMenu`, and `FloatingMenu`.
* The exported object includes the `Content` subcomponent for rendering the
* editor content area.
*

@@ -539,15 +456,22 @@ * This component provides both the new `TiptapContext` (accessed via `useTiptap()`)

*
* For bubble menus and floating menus, import them separately from
* `@tiptap/react/menus` to keep floating-ui as an optional dependency.
*
* @example
* ```tsx
* const editor = useEditor({ extensions: [...] })
* import { Tiptap, useEditor } from '@tiptap/react'
* import { BubbleMenu } from '@tiptap/react/menus'
*
* return (
* <Tiptap instance={editor}>
* <Tiptap.Loading>Initializing editor...</Tiptap.Loading>
* <Tiptap.Content />
* <Tiptap.BubbleMenu>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </Tiptap.BubbleMenu>
* </Tiptap>
* )
* function App() {
* const editor = useEditor({ extensions: [...] })
*
* return (
* <Tiptap editor={editor}>
* <Tiptap.Content />
* <BubbleMenu>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </BubbleMenu>
* </Tiptap>
* )
* }
* ```

@@ -561,17 +485,2 @@ */

Content: typeof TiptapContent;
/**
* The Tiptap Loading component that renders its children only when the editor is not ready.
* @see TiptapLoading
*/
Loading: typeof TiptapLoading;
/**
* The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
* @see TiptapBubbleMenu
*/
BubbleMenu: typeof TiptapBubbleMenu;
/**
* The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
* @see TiptapFloatingMenu
*/
FloatingMenu: typeof TiptapFloatingMenu;
};

@@ -639,2 +548,2 @@

export { EditorConsumer, EditorContent, type EditorContentProps, EditorContext, type EditorContextValue, EditorProvider, type EditorProviderProps, type EditorStateSnapshot, MarkViewContent, type MarkViewContentProps, type MarkViewContextProps, NodeViewContent, type NodeViewContentProps, NodeViewWrapper, type NodeViewWrapperProps, PureEditorContent, ReactMarkView, ReactMarkViewContext, ReactMarkViewRenderer, type ReactMarkViewRendererOptions, ReactNodeView, ReactNodeViewContentProvider, ReactNodeViewContext, type ReactNodeViewContextProps, type ReactNodeViewProps, ReactNodeViewRenderer, type ReactNodeViewRendererOptions, ReactRenderer, type ReactRendererOptions, Tiptap, TiptapBubbleMenu, TiptapContent, TiptapContext, type TiptapContextType, TiptapFloatingMenu, TiptapLoading, type TiptapLoadingProps, TiptapWrapper, type TiptapWrapperProps, type UseEditorOptions, type UseEditorStateOptions, useCurrentEditor, useEditor, useEditorState, useReactNodeView, useTiptap, useTiptapState };
export { EditorConsumer, EditorContent, type EditorContentProps, EditorContext, type EditorContextValue, EditorProvider, type EditorProviderProps, type EditorStateSnapshot, MarkViewContent, type MarkViewContentProps, type MarkViewContextProps, NodeViewContent, type NodeViewContentProps, NodeViewWrapper, type NodeViewWrapperProps, PureEditorContent, ReactMarkView, ReactMarkViewContext, ReactMarkViewRenderer, type ReactMarkViewRendererOptions, ReactNodeView, ReactNodeViewContentProvider, ReactNodeViewContext, type ReactNodeViewContextProps, type ReactNodeViewProps, ReactNodeViewRenderer, type ReactNodeViewRendererOptions, ReactRenderer, type ReactRendererOptions, Tiptap, TiptapContent, TiptapContext, type TiptapContextType, TiptapWrapper, type TiptapWrapperProps, type UseEditorOptions, type UseEditorStateOptions, useCurrentEditor, useEditor, useEditorState, useReactNodeView, useTiptap, useTiptapState };

@@ -8,4 +8,2 @@ import * as react_jsx_runtime from 'react/jsx-runtime';

import { Decoration, DecorationSource } from '@tiptap/pm/view';
import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
import { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu';

@@ -307,24 +305,15 @@ /**

type Optional$1<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type BubbleMenuProps = Optional$1<Omit<Optional$1<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> & React__default.HTMLAttributes<HTMLDivElement>;
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {
editor: FloatingMenuPluginProps['editor'] | null;
options?: FloatingMenuPluginProps['options'];
} & React__default.HTMLAttributes<HTMLDivElement>;
/**
* The shape of the React context used by the `<Tiptap />` components.
*
* This object exposes the editor instance and a simple readiness flag.
* The editor instance is always available when using the default `useEditor`
* configuration. For SSR scenarios where `immediatelyRender: false` is used,
* consider using the legacy `EditorProvider` pattern instead.
*/
type TiptapContextType = {
/** The Tiptap editor instance. May be null during SSR or before initialization. */
editor: Editor | null;
/** True when the editor has finished initializing and is ready for user interaction. */
isReady: boolean;
/** The Tiptap editor instance. */
editor: Editor;
};
/**
* React context that stores the current editor instance and readiness flag.
* React context that stores the current editor instance.
*

@@ -335,5 +324,6 @@ * Use `useTiptap()` to read from this context in child components.

/**
* Hook to read the Tiptap context (`editor` + `isReady`).
* Hook to read the Tiptap context and access the editor instance.
*
* This is a small convenience wrapper around `useContext(TiptapContext)`.
* The editor is always available when used within a `<Tiptap>` provider.
*

@@ -346,5 +336,10 @@ * @returns The current `TiptapContextType` value from the provider.

*
* function Status() {
* const { isReady } = useTiptap()
* return <div>{isReady ? 'Editor ready' : 'Loading editor...'}</div>
* function Toolbar() {
* const { editor } = useTiptap()
*
* return (
* <button onClick={() => editor.chain().focus().toggleBold().run()}>
* Bold
* </button>
* )
* }

@@ -360,6 +355,2 @@ * ```

*
* Important: This hook should only be used when the editor is available.
* Use the `isReady` flag from `useTiptap()` to guard against null editor,
* or ensure your component only renders after the editor is initialized.
*
* @typeParam TSelectorResult - The type returned by the selector.

@@ -375,5 +366,2 @@ * @param selector - Function that receives the editor state snapshot and

* function WordCount() {
* const { isReady } = useTiptap()
*
* // Only use useTiptapState when the editor is ready
* const wordCount = useTiptapState(state => {

@@ -384,4 +372,2 @@ * const text = state.editor.state.doc.textContent

*
* if (!isReady) return null
*
* return <span>{wordCount} words</span>

@@ -398,5 +384,9 @@ * }

* The editor instance to provide to child components.
* Can be null during SSR or before initialization.
* Use `useEditor()` to create this instance.
*/
instance: Editor | null;
editor?: Editor;
/**
* @deprecated Use `editor` instead. Will be removed in the next major version.
*/
instance?: Editor;
children: ReactNode;

@@ -406,7 +396,4 @@ };

* Top-level provider component that makes the editor instance available via
* React context and tracks when the editor becomes ready.
* React context to all child components.
*
* The component listens to the editor's `create` event and flips the
* `isReady` flag once initialization completes.
*
* This component also provides backwards compatibility with the legacy

@@ -427,3 +414,3 @@ * `EditorContext`, so components using `useCurrentEditor()` will work

* return (
* <Tiptap instance={editor}>
* <Tiptap editor={editor}>
* <Toolbar />

@@ -436,3 +423,3 @@ * <Tiptap.Content />

*/
declare function TiptapWrapper({ instance, children }: TiptapWrapperProps): react_jsx_runtime.JSX.Element;
declare function TiptapWrapper({ editor, instance, children }: TiptapWrapperProps): react_jsx_runtime.JSX.Element;
declare namespace TiptapWrapper {

@@ -458,77 +445,7 @@ var displayName: string;

}
type TiptapLoadingProps = {
children: ReactNode;
};
/**
* Component that renders its children only when the editor is not ready.
*
* This is useful for displaying loading states or placeholders during
* editor initialization, especially with SSR.
*
* @param props - The props for the TiptapLoading component.
* @returns The children when editor is not ready, or null when ready.
*
* @example
* ```tsx
* <Tiptap instance={editor}>
* <Tiptap.Loading>
* <div className="skeleton">Loading editor...</div>
* </Tiptap.Loading>
* <Tiptap.Content />
* </Tiptap>
* ```
*/
declare function TiptapLoading({ children }: TiptapLoadingProps): ReactNode;
declare namespace TiptapLoading {
var displayName: string;
}
/**
* A wrapper around the library `BubbleMenu` that injects the editor from
* context so callers don't need to pass the `editor` prop.
*
* Returns `null` when the editor is not available (for example during SSR).
*
* @param props - Props for the underlying `BubbleMenu` (except `editor`).
* @returns A `BubbleMenu` bound to the context editor, or `null`.
*
* @example
* ```tsx
* <Tiptap.BubbleMenu tippyOptions={{ duration: 100 }}>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </Tiptap.BubbleMenu>
* ```
*/
declare function TiptapBubbleMenu({ children, ...rest }: {
children: ReactNode;
} & Omit<BubbleMenuProps, 'editor'>): react_jsx_runtime.JSX.Element | null;
declare namespace TiptapBubbleMenu {
var displayName: string;
}
/**
* A wrapper around the library `FloatingMenu` that injects the editor from
* context so callers don't need to pass the `editor` prop.
*
* Returns `null` when the editor is not available.
*
* @param props - Props for the underlying `FloatingMenu` (except `editor`).
* @returns A `FloatingMenu` bound to the context editor, or `null`.
*
* @example
* ```tsx
* <Tiptap.FloatingMenu placement="top">
* <button onClick={() => editor.chain().focus().toggleItalic().run()}>Italic</button>
* </Tiptap.FloatingMenu>
* ```
*/
declare function TiptapFloatingMenu({ children, ...rest }: {
children: ReactNode;
} & Omit<FloatingMenuProps, 'editor'>): react_jsx_runtime.JSX.Element | null;
declare namespace TiptapFloatingMenu {
var displayName: string;
}
/**
* Root `Tiptap` component. Use it as the provider for all child components.
*
* The exported object includes several helper subcomponents for common use
* cases: `Content`, `Loading`, `BubbleMenu`, and `FloatingMenu`.
* The exported object includes the `Content` subcomponent for rendering the
* editor content area.
*

@@ -539,15 +456,22 @@ * This component provides both the new `TiptapContext` (accessed via `useTiptap()`)

*
* For bubble menus and floating menus, import them separately from
* `@tiptap/react/menus` to keep floating-ui as an optional dependency.
*
* @example
* ```tsx
* const editor = useEditor({ extensions: [...] })
* import { Tiptap, useEditor } from '@tiptap/react'
* import { BubbleMenu } from '@tiptap/react/menus'
*
* return (
* <Tiptap instance={editor}>
* <Tiptap.Loading>Initializing editor...</Tiptap.Loading>
* <Tiptap.Content />
* <Tiptap.BubbleMenu>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </Tiptap.BubbleMenu>
* </Tiptap>
* )
* function App() {
* const editor = useEditor({ extensions: [...] })
*
* return (
* <Tiptap editor={editor}>
* <Tiptap.Content />
* <BubbleMenu>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </BubbleMenu>
* </Tiptap>
* )
* }
* ```

@@ -561,17 +485,2 @@ */

Content: typeof TiptapContent;
/**
* The Tiptap Loading component that renders its children only when the editor is not ready.
* @see TiptapLoading
*/
Loading: typeof TiptapLoading;
/**
* The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
* @see TiptapBubbleMenu
*/
BubbleMenu: typeof TiptapBubbleMenu;
/**
* The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
* @see TiptapFloatingMenu
*/
FloatingMenu: typeof TiptapFloatingMenu;
};

@@ -639,2 +548,2 @@

export { EditorConsumer, EditorContent, type EditorContentProps, EditorContext, type EditorContextValue, EditorProvider, type EditorProviderProps, type EditorStateSnapshot, MarkViewContent, type MarkViewContentProps, type MarkViewContextProps, NodeViewContent, type NodeViewContentProps, NodeViewWrapper, type NodeViewWrapperProps, PureEditorContent, ReactMarkView, ReactMarkViewContext, ReactMarkViewRenderer, type ReactMarkViewRendererOptions, ReactNodeView, ReactNodeViewContentProvider, ReactNodeViewContext, type ReactNodeViewContextProps, type ReactNodeViewProps, ReactNodeViewRenderer, type ReactNodeViewRendererOptions, ReactRenderer, type ReactRendererOptions, Tiptap, TiptapBubbleMenu, TiptapContent, TiptapContext, type TiptapContextType, TiptapFloatingMenu, TiptapLoading, type TiptapLoadingProps, TiptapWrapper, type TiptapWrapperProps, type UseEditorOptions, type UseEditorStateOptions, useCurrentEditor, useEditor, useEditorState, useReactNodeView, useTiptap, useTiptapState };
export { EditorConsumer, EditorContent, type EditorContentProps, EditorContext, type EditorContextValue, EditorProvider, type EditorProviderProps, type EditorStateSnapshot, MarkViewContent, type MarkViewContentProps, type MarkViewContextProps, NodeViewContent, type NodeViewContentProps, NodeViewWrapper, type NodeViewWrapperProps, PureEditorContent, ReactMarkView, ReactMarkViewContext, ReactMarkViewRenderer, type ReactMarkViewRendererOptions, ReactNodeView, ReactNodeViewContentProvider, ReactNodeViewContext, type ReactNodeViewContextProps, type ReactNodeViewProps, ReactNodeViewRenderer, type ReactNodeViewRendererOptions, ReactRenderer, type ReactRendererOptions, Tiptap, TiptapContent, TiptapContext, type TiptapContextType, TiptapWrapper, type TiptapWrapperProps, type UseEditorOptions, type UseEditorStateOptions, useCurrentEditor, useEditor, useEditorState, useReactNodeView, useTiptap, useTiptapState };

@@ -1061,196 +1061,8 @@ // src/Context.tsx

// src/Tiptap.tsx
import { createContext as createContext3, useContext as useContext3, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
// src/menus/BubbleMenu.tsx
import { BubbleMenuPlugin } from "@tiptap/extension-bubble-menu";
import { useCurrentEditor as useCurrentEditor2 } from "@tiptap/react";
import React5, { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
import { createPortal } from "react-dom";
import { createContext as createContext3, useContext as useContext3, useMemo as useMemo2 } from "react";
import { jsx as jsx8 } from "react/jsx-runtime";
var BubbleMenu = React5.forwardRef(
({
pluginKey = "bubbleMenu",
editor,
updateDelay,
resizeDelay,
appendTo,
shouldShow = null,
getReferencedVirtualElement,
options,
children,
...restProps
}, ref) => {
const menuEl = useRef2(document.createElement("div"));
if (typeof ref === "function") {
ref(menuEl.current);
} else if (ref) {
ref.current = menuEl.current;
}
const { editor: currentEditor } = useCurrentEditor2();
const pluginEditor = editor || currentEditor;
const bubbleMenuPluginProps = {
updateDelay,
resizeDelay,
appendTo,
pluginKey,
shouldShow,
getReferencedVirtualElement,
options
};
const bubbleMenuPluginPropsRef = useRef2(bubbleMenuPluginProps);
bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps;
const [pluginInitialized, setPluginInitialized] = useState3(false);
const skipFirstUpdateRef = useRef2(true);
useEffect3(() => {
if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) {
return;
}
if (!pluginEditor) {
console.warn("BubbleMenu component is not rendered inside of an editor component or does not have editor prop.");
return;
}
const bubbleMenuElement = menuEl.current;
bubbleMenuElement.style.visibility = "hidden";
bubbleMenuElement.style.position = "absolute";
const plugin = BubbleMenuPlugin({
...bubbleMenuPluginPropsRef.current,
editor: pluginEditor,
element: bubbleMenuElement
});
pluginEditor.registerPlugin(plugin);
const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey;
skipFirstUpdateRef.current = true;
setPluginInitialized(true);
return () => {
setPluginInitialized(false);
pluginEditor.unregisterPlugin(createdPluginKey);
window.requestAnimationFrame(() => {
if (bubbleMenuElement.parentNode) {
bubbleMenuElement.parentNode.removeChild(bubbleMenuElement);
}
});
};
}, [pluginEditor]);
useEffect3(() => {
if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {
return;
}
if (skipFirstUpdateRef.current) {
skipFirstUpdateRef.current = false;
return;
}
pluginEditor.view.dispatch(
pluginEditor.state.tr.setMeta("bubbleMenu", {
type: "updateOptions",
options: bubbleMenuPluginPropsRef.current
})
);
}, [
pluginInitialized,
pluginEditor,
updateDelay,
resizeDelay,
shouldShow,
options,
appendTo,
getReferencedVirtualElement
]);
return createPortal(/* @__PURE__ */ jsx8("div", { ...restProps, children }), menuEl.current);
var TiptapContext = createContext3({
get editor() {
throw new Error("useTiptap must be used within a <Tiptap> provider");
}
);
// src/menus/FloatingMenu.tsx
import { FloatingMenuPlugin } from "@tiptap/extension-floating-menu";
import { useCurrentEditor as useCurrentEditor3 } from "@tiptap/react";
import React6, { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
import { createPortal as createPortal2 } from "react-dom";
import { jsx as jsx9 } from "react/jsx-runtime";
var FloatingMenu = React6.forwardRef(
({
pluginKey = "floatingMenu",
editor,
updateDelay,
resizeDelay,
appendTo,
shouldShow = null,
options,
children,
...restProps
}, ref) => {
const menuEl = useRef3(document.createElement("div"));
if (typeof ref === "function") {
ref(menuEl.current);
} else if (ref) {
ref.current = menuEl.current;
}
const { editor: currentEditor } = useCurrentEditor3();
const pluginEditor = editor || currentEditor;
const floatingMenuPluginProps = {
updateDelay,
resizeDelay,
appendTo,
pluginKey,
shouldShow,
options
};
const floatingMenuPluginPropsRef = useRef3(floatingMenuPluginProps);
floatingMenuPluginPropsRef.current = floatingMenuPluginProps;
const [pluginInitialized, setPluginInitialized] = useState4(false);
const skipFirstUpdateRef = useRef3(true);
useEffect4(() => {
if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) {
return;
}
if (!pluginEditor) {
console.warn(
"FloatingMenu component is not rendered inside of an editor component or does not have editor prop."
);
return;
}
const floatingMenuElement = menuEl.current;
floatingMenuElement.style.visibility = "hidden";
floatingMenuElement.style.position = "absolute";
const plugin = FloatingMenuPlugin({
...floatingMenuPluginPropsRef.current,
editor: pluginEditor,
element: floatingMenuElement
});
pluginEditor.registerPlugin(plugin);
const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey;
skipFirstUpdateRef.current = true;
setPluginInitialized(true);
return () => {
setPluginInitialized(false);
pluginEditor.unregisterPlugin(createdPluginKey);
window.requestAnimationFrame(() => {
if (floatingMenuElement.parentNode) {
floatingMenuElement.parentNode.removeChild(floatingMenuElement);
}
});
};
}, [pluginEditor]);
useEffect4(() => {
if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {
return;
}
if (skipFirstUpdateRef.current) {
skipFirstUpdateRef.current = false;
return;
}
pluginEditor.view.dispatch(
pluginEditor.state.tr.setMeta("floatingMenu", {
type: "updateOptions",
options: floatingMenuPluginPropsRef.current
})
);
}, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo]);
return createPortal2(/* @__PURE__ */ jsx9("div", { ...restProps, children }), menuEl.current);
}
);
// src/Tiptap.tsx
import { jsx as jsx10 } from "react/jsx-runtime";
var TiptapContext = createContext3({
editor: null,
isReady: false
});

@@ -1267,25 +1079,10 @@ TiptapContext.displayName = "TiptapContext";

}
function TiptapWrapper({ instance, children }) {
var _a;
const [isReady, setIsReady] = useState5((_a = instance == null ? void 0 : instance.isInitialized) != null ? _a : false);
useEffect5(() => {
if (!instance) {
setIsReady(false);
return;
}
if (instance.isInitialized) {
setIsReady(true);
return;
}
const handleCreate = () => {
setIsReady(true);
};
instance.on("create", handleCreate);
return () => {
instance.off("create", handleCreate);
};
}, [instance]);
const tiptapContextValue = useMemo2(() => ({ editor: instance, isReady }), [instance, isReady]);
const legacyContextValue = useMemo2(() => ({ editor: instance }), [instance]);
return /* @__PURE__ */ jsx10(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ jsx10(TiptapContext.Provider, { value: tiptapContextValue, children }) });
function TiptapWrapper({ editor, instance, children }) {
const resolvedEditor = editor != null ? editor : instance;
if (!resolvedEditor) {
throw new Error("Tiptap: An editor instance is required. Pass a non-null `editor` prop.");
}
const tiptapContextValue = useMemo2(() => ({ editor: resolvedEditor }), [resolvedEditor]);
const legacyContextValue = useMemo2(() => ({ editor: resolvedEditor }), [resolvedEditor]);
return /* @__PURE__ */ jsx8(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ jsx8(TiptapContext.Provider, { value: tiptapContextValue, children }) });
}

@@ -1295,29 +1092,5 @@ TiptapWrapper.displayName = "Tiptap";

const { editor } = useTiptap();
return /* @__PURE__ */ jsx10(EditorContent, { editor, ...rest });
return /* @__PURE__ */ jsx8(EditorContent, { editor, ...rest });
}
TiptapContent.displayName = "Tiptap.Content";
function TiptapLoading({ children }) {
const { isReady } = useTiptap();
if (isReady) {
return null;
}
return children;
}
TiptapLoading.displayName = "Tiptap.Loading";
function TiptapBubbleMenu({ children, ...rest }) {
const { editor } = useTiptap();
if (!editor) {
return null;
}
return /* @__PURE__ */ jsx10(BubbleMenu, { editor, ...rest, children });
}
TiptapBubbleMenu.displayName = "Tiptap.BubbleMenu";
function TiptapFloatingMenu({ children, ...rest }) {
const { editor } = useTiptap();
if (!editor) {
return null;
}
return /* @__PURE__ */ jsx10(FloatingMenu, { ...rest, editor, children });
}
TiptapFloatingMenu.displayName = "Tiptap.FloatingMenu";
var Tiptap = Object.assign(TiptapWrapper, {

@@ -1328,18 +1101,3 @@ /**

*/
Content: TiptapContent,
/**
* The Tiptap Loading component that renders its children only when the editor is not ready.
* @see TiptapLoading
*/
Loading: TiptapLoading,
/**
* The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
* @see TiptapBubbleMenu
*/
BubbleMenu: TiptapBubbleMenu,
/**
* The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
* @see TiptapFloatingMenu
*/
FloatingMenu: TiptapFloatingMenu
Content: TiptapContent
});

@@ -1367,7 +1125,4 @@

Tiptap,
TiptapBubbleMenu,
TiptapContent,
TiptapContext,
TiptapFloatingMenu,
TiptapLoading,
TiptapWrapper,

@@ -1374,0 +1129,0 @@ useCurrentEditor,

{
"name": "@tiptap/react",
"description": "React components for tiptap",
"version": "3.18.0",
"version": "3.19.0",
"homepage": "https://tiptap.dev",

@@ -51,8 +51,8 @@ "keywords": [

"react-dom": "^19.0.0",
"@tiptap/core": "^3.18.0",
"@tiptap/pm": "^3.18.0"
"@tiptap/core": "^3.19.0",
"@tiptap/pm": "^3.19.0"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.18.0",
"@tiptap/extension-floating-menu": "^3.18.0"
"@tiptap/extension-bubble-menu": "^3.19.0",
"@tiptap/extension-floating-menu": "^3.19.0"
},

@@ -64,4 +64,4 @@ "peerDependencies": {

"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@tiptap/core": "^3.18.0",
"@tiptap/pm": "^3.18.0"
"@tiptap/core": "^3.19.0",
"@tiptap/pm": "^3.19.0"
},

@@ -68,0 +68,0 @@ "repository": {

import type { ReactNode } from 'react'
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
import { createContext, useContext, useMemo } from 'react'

@@ -7,4 +7,2 @@ import { EditorContext } from './Context.js'

import { EditorContent, useEditorState } from './index.js'
import { type BubbleMenuProps, BubbleMenu } from './menus/BubbleMenu.js'
import { type FloatingMenuProps, FloatingMenu } from './menus/FloatingMenu.js'

@@ -14,14 +12,13 @@ /**

*
* This object exposes the editor instance and a simple readiness flag.
* The editor instance is always available when using the default `useEditor`
* configuration. For SSR scenarios where `immediatelyRender: false` is used,
* consider using the legacy `EditorProvider` pattern instead.
*/
export type TiptapContextType = {
/** The Tiptap editor instance. May be null during SSR or before initialization. */
editor: Editor | null
/** True when the editor has finished initializing and is ready for user interaction. */
isReady: boolean
/** The Tiptap editor instance. */
editor: Editor
}
/**
* React context that stores the current editor instance and readiness flag.
* React context that stores the current editor instance.
*

@@ -31,4 +28,5 @@ * Use `useTiptap()` to read from this context in child components.

export const TiptapContext = createContext<TiptapContextType>({
editor: null,
isReady: false,
get editor(): Editor {
throw new Error('useTiptap must be used within a <Tiptap> provider')
},
})

@@ -39,5 +37,6 @@

/**
* Hook to read the Tiptap context (`editor` + `isReady`).
* Hook to read the Tiptap context and access the editor instance.
*
* This is a small convenience wrapper around `useContext(TiptapContext)`.
* The editor is always available when used within a `<Tiptap>` provider.
*

@@ -50,5 +49,10 @@ * @returns The current `TiptapContextType` value from the provider.

*
* function Status() {
* const { isReady } = useTiptap()
* return <div>{isReady ? 'Editor ready' : 'Loading editor...'}</div>
* function Toolbar() {
* const { editor } = useTiptap()
*
* return (
* <button onClick={() => editor.chain().focus().toggleBold().run()}>
* Bold
* </button>
* )
* }

@@ -65,6 +69,2 @@ * ```

*
* Important: This hook should only be used when the editor is available.
* Use the `isReady` flag from `useTiptap()` to guard against null editor,
* or ensure your component only renders after the editor is initialized.
*
* @typeParam TSelectorResult - The type returned by the selector.

@@ -80,5 +80,2 @@ * @param selector - Function that receives the editor state snapshot and

* function WordCount() {
* const { isReady } = useTiptap()
*
* // Only use useTiptapState when the editor is ready
* const wordCount = useTiptapState(state => {

@@ -89,4 +86,2 @@ * const text = state.editor.state.doc.textContent

*
* if (!isReady) return null
*
* return <span>{wordCount} words</span>

@@ -101,4 +96,5 @@ * }

const { editor } = useTiptap()
return useEditorState({
editor: editor as Editor,
editor,
selector,

@@ -115,5 +111,11 @@ equalityFn,

* The editor instance to provide to child components.
* Can be null during SSR or before initialization.
* Use `useEditor()` to create this instance.
*/
instance: Editor | null
editor?: Editor
/**
* @deprecated Use `editor` instead. Will be removed in the next major version.
*/
instance?: Editor
children: ReactNode

@@ -124,7 +126,4 @@ }

* Top-level provider component that makes the editor instance available via
* React context and tracks when the editor becomes ready.
* React context to all child components.
*
* The component listens to the editor's `create` event and flips the
* `isReady` flag once initialization completes.
*
* This component also provides backwards compatibility with the legacy

@@ -145,3 +144,3 @@ * `EditorContext`, so components using `useCurrentEditor()` will work

* return (
* <Tiptap instance={editor}>
* <Tiptap editor={editor}>
* <Toolbar />

@@ -154,34 +153,14 @@ * <Tiptap.Content />

*/
export function TiptapWrapper({ instance, children }: TiptapWrapperProps) {
const [isReady, setIsReady] = useState(instance?.isInitialized ?? false)
export function TiptapWrapper({ editor, instance, children }: TiptapWrapperProps) {
const resolvedEditor = editor ?? instance
useEffect(() => {
if (!instance) {
setIsReady(false)
return
}
if (!resolvedEditor) {
throw new Error('Tiptap: An editor instance is required. Pass a non-null `editor` prop.')
}
// If the editor is already initialized, set isReady to true
if (instance.isInitialized) {
setIsReady(true)
return
}
const tiptapContextValue = useMemo<TiptapContextType>(() => ({ editor: resolvedEditor }), [resolvedEditor])
const handleCreate = () => {
setIsReady(true)
}
instance.on('create', handleCreate)
return () => {
instance.off('create', handleCreate)
}
}, [instance])
// Memoize context values to prevent unnecessary re-renders
const tiptapContextValue = useMemo<TiptapContextType>(() => ({ editor: instance, isReady }), [instance, isReady])
// Provide backwards compatibility with the legacy EditorContext
// so components using useCurrentEditor() work inside <Tiptap>
const legacyContextValue = useMemo(() => ({ editor: instance }), [instance])
const legacyContextValue = useMemo(() => ({ editor: resolvedEditor }), [resolvedEditor])

@@ -218,106 +197,7 @@ return (

export type TiptapLoadingProps = {
children: ReactNode
}
/**
* Component that renders its children only when the editor is not ready.
*
* This is useful for displaying loading states or placeholders during
* editor initialization, especially with SSR.
*
* @param props - The props for the TiptapLoading component.
* @returns The children when editor is not ready, or null when ready.
*
* @example
* ```tsx
* <Tiptap instance={editor}>
* <Tiptap.Loading>
* <div className="skeleton">Loading editor...</div>
* </Tiptap.Loading>
* <Tiptap.Content />
* </Tiptap>
* ```
*/
export function TiptapLoading({ children }: TiptapLoadingProps) {
const { isReady } = useTiptap()
if (isReady) {
return null
}
return children
}
TiptapLoading.displayName = 'Tiptap.Loading'
/**
* A wrapper around the library `BubbleMenu` that injects the editor from
* context so callers don't need to pass the `editor` prop.
*
* Returns `null` when the editor is not available (for example during SSR).
*
* @param props - Props for the underlying `BubbleMenu` (except `editor`).
* @returns A `BubbleMenu` bound to the context editor, or `null`.
*
* @example
* ```tsx
* <Tiptap.BubbleMenu tippyOptions={{ duration: 100 }}>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </Tiptap.BubbleMenu>
* ```
*/
export function TiptapBubbleMenu({ children, ...rest }: { children: ReactNode } & Omit<BubbleMenuProps, 'editor'>) {
const { editor } = useTiptap()
if (!editor) {
return null
}
return (
<BubbleMenu editor={editor} {...rest}>
{children}
</BubbleMenu>
)
}
TiptapBubbleMenu.displayName = 'Tiptap.BubbleMenu'
/**
* A wrapper around the library `FloatingMenu` that injects the editor from
* context so callers don't need to pass the `editor` prop.
*
* Returns `null` when the editor is not available.
*
* @param props - Props for the underlying `FloatingMenu` (except `editor`).
* @returns A `FloatingMenu` bound to the context editor, or `null`.
*
* @example
* ```tsx
* <Tiptap.FloatingMenu placement="top">
* <button onClick={() => editor.chain().focus().toggleItalic().run()}>Italic</button>
* </Tiptap.FloatingMenu>
* ```
*/
export function TiptapFloatingMenu({ children, ...rest }: { children: ReactNode } & Omit<FloatingMenuProps, 'editor'>) {
const { editor } = useTiptap()
if (!editor) {
return null
}
return (
<FloatingMenu {...rest} editor={editor}>
{children}
</FloatingMenu>
)
}
TiptapFloatingMenu.displayName = 'Tiptap.FloatingMenu'
/**
* Root `Tiptap` component. Use it as the provider for all child components.
*
* The exported object includes several helper subcomponents for common use
* cases: `Content`, `Loading`, `BubbleMenu`, and `FloatingMenu`.
* The exported object includes the `Content` subcomponent for rendering the
* editor content area.
*

@@ -328,15 +208,22 @@ * This component provides both the new `TiptapContext` (accessed via `useTiptap()`)

*
* For bubble menus and floating menus, import them separately from
* `@tiptap/react/menus` to keep floating-ui as an optional dependency.
*
* @example
* ```tsx
* const editor = useEditor({ extensions: [...] })
* import { Tiptap, useEditor } from '@tiptap/react'
* import { BubbleMenu } from '@tiptap/react/menus'
*
* return (
* <Tiptap instance={editor}>
* <Tiptap.Loading>Initializing editor...</Tiptap.Loading>
* <Tiptap.Content />
* <Tiptap.BubbleMenu>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </Tiptap.BubbleMenu>
* </Tiptap>
* )
* function App() {
* const editor = useEditor({ extensions: [...] })
*
* return (
* <Tiptap editor={editor}>
* <Tiptap.Content />
* <BubbleMenu>
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
* </BubbleMenu>
* </Tiptap>
* )
* }
* ```

@@ -350,22 +237,4 @@ */

Content: TiptapContent,
/**
* The Tiptap Loading component that renders its children only when the editor is not ready.
* @see TiptapLoading
*/
Loading: TiptapLoading,
/**
* The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
* @see TiptapBubbleMenu
*/
BubbleMenu: TiptapBubbleMenu,
/**
* The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
* @see TiptapFloatingMenu
*/
FloatingMenu: TiptapFloatingMenu,
})
export default Tiptap

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display