@tiptap/react
Advanced tools
+61
-37
@@ -336,8 +336,15 @@ "use strict"; | ||
| getInitialEditor() { | ||
| var _a; | ||
| let immediatelyRender = (_a = this.options.current.immediatelyRender) != null ? _a : true; | ||
| if (immediatelyRender && (isSSR || isNext)) { | ||
| const explicit = this.options.current.immediatelyRender; | ||
| let immediatelyRender = explicit != null ? explicit : true; | ||
| if (isSSR) { | ||
| if (immediatelyRender && isDev) { | ||
| console.warn("SSR detected. `immediatelyRender` has been set to false to avoid hydration mismatches"); | ||
| } | ||
| immediatelyRender = false; | ||
| } else if (isNext && explicit === void 0) { | ||
| immediatelyRender = false; | ||
| if (isDev) { | ||
| console.warn("SSR detected. `immediatelyRender` has been set to false to avoid hydration mismatches"); | ||
| console.warn( | ||
| "Next.js detected. `immediatelyRender` defaults to false to avoid hydration mismatches. Pass `immediatelyRender: true` explicitly if you are rendering the editor only on the client." | ||
| ); | ||
| } | ||
@@ -748,2 +755,4 @@ } | ||
| * Re-renders the React component with new props. | ||
| * Skips the render if none of the supplied props actually changed, | ||
| * to avoid unnecessary portal re-creation. | ||
| */ | ||
@@ -754,2 +763,14 @@ updateProps(props = {}) { | ||
| } | ||
| let changed = false; | ||
| const keys = Object.keys(props); | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; | ||
| if (props[key] !== this.props[key]) { | ||
| changed = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!changed) { | ||
| return; | ||
| } | ||
| this.props = { | ||
@@ -851,6 +872,16 @@ ...this.props, | ||
| /** | ||
| * Callback registered with the per-editor position-update registry. | ||
| * Stored so it can be unregistered in destroy(). | ||
| * Fires on editor updates when trackNodeViewPosition is enabled. | ||
| * Detects position shifts where update() is NOT called (e.g. typing above). | ||
| */ | ||
| this.positionCheckCallback = null; | ||
| this.handlePositionUpdate = () => { | ||
| const newPos = this.getPos(); | ||
| if (typeof newPos !== "number" || newPos === this.currentPos) { | ||
| return; | ||
| } | ||
| this.currentPos = newPos; | ||
| this.renderer.updateProps({ getPos: () => this.getPos() }); | ||
| if (typeof this.options.attrs === "function") { | ||
| this.updateElementAttributes(); | ||
| } | ||
| }; | ||
| this.cachedExtensionWithSyncedStorage = null; | ||
@@ -872,2 +903,5 @@ if (!this.node.isLeaf) { | ||
| } | ||
| if (this.options.trackNodeViewPosition) { | ||
| this.editor.on("update", this.handlePositionUpdate); | ||
| } | ||
| } | ||
@@ -914,2 +948,3 @@ /** | ||
| }; | ||
| const mountProps = props; | ||
| if (!this.component.displayName) { | ||
@@ -944,3 +979,3 @@ const capitalizeFirstChar = (string) => { | ||
| editor: this.editor, | ||
| props, | ||
| props: mountProps, | ||
| as, | ||
@@ -952,14 +987,2 @@ className: `node-${this.node.type.name} ${className}`.trim() | ||
| this.currentPos = this.getPos(); | ||
| this.positionCheckCallback = () => { | ||
| const newPos = this.getPos(); | ||
| if (typeof newPos !== "number" || newPos === this.currentPos) { | ||
| return; | ||
| } | ||
| this.currentPos = newPos; | ||
| this.renderer.updateProps({ getPos: () => this.getPos() }); | ||
| if (typeof this.options.attrs === "function") { | ||
| this.updateElementAttributes(); | ||
| } | ||
| }; | ||
| (0, import_core3.schedulePositionCheck)(this.editor, this.positionCheckCallback); | ||
| } | ||
@@ -1053,17 +1076,10 @@ /** | ||
| } | ||
| const newPos = this.getPos(); | ||
| if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) { | ||
| if (newPos === this.currentPos) { | ||
| return true; | ||
| } | ||
| this.currentPos = newPos; | ||
| rerenderComponent({ | ||
| node, | ||
| decorations, | ||
| innerDecorations, | ||
| extension: this.extensionWithSyncedStorage, | ||
| getPos: () => this.getPos() | ||
| }); | ||
| const nodeChanged = node !== this.node; | ||
| if (!nodeChanged) { | ||
| this.node = node; | ||
| this.decorations = decorations; | ||
| this.innerDecorations = innerDecorations; | ||
| return true; | ||
| } | ||
| const newPos = this.getPos(); | ||
| this.node = node; | ||
@@ -1073,3 +1089,12 @@ this.decorations = decorations; | ||
| this.currentPos = newPos; | ||
| rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }); | ||
| const extraProps = { | ||
| node, | ||
| decorations, | ||
| innerDecorations, | ||
| extension: this.extensionWithSyncedStorage | ||
| }; | ||
| if (this.options.trackNodeViewPosition) { | ||
| extraProps.getPos = () => this.getPos(); | ||
| } | ||
| rerenderComponent(extraProps); | ||
| return true; | ||
@@ -1103,5 +1128,4 @@ } | ||
| this.editor.off("selectionUpdate", this.handleSelectionUpdate); | ||
| if (this.positionCheckCallback) { | ||
| (0, import_core3.cancelPositionCheck)(this.editor, this.positionCheckCallback); | ||
| this.positionCheckCallback = null; | ||
| if (this.options.trackNodeViewPosition) { | ||
| this.editor.off("update", this.handlePositionUpdate); | ||
| } | ||
@@ -1108,0 +1132,0 @@ this.contentDOMElement = null; |
+5
-3
@@ -168,2 +168,4 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| * Re-renders the React component with new props. | ||
| * Skips the render if none of the supplied props actually changed, | ||
| * to avoid unnecessary portal re-creation. | ||
| */ | ||
@@ -263,6 +265,6 @@ updateProps(props?: Record<string, any>): void; | ||
| /** | ||
| * Callback registered with the per-editor position-update registry. | ||
| * Stored so it can be unregistered in destroy(). | ||
| * Fires on editor updates when trackNodeViewPosition is enabled. | ||
| * Detects position shifts where update() is NOT called (e.g. typing above). | ||
| */ | ||
| private positionCheckCallback; | ||
| private handlePositionUpdate; | ||
| constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>); | ||
@@ -269,0 +271,0 @@ private cachedExtensionWithSyncedStorage; |
+5
-3
@@ -168,2 +168,4 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| * Re-renders the React component with new props. | ||
| * Skips the render if none of the supplied props actually changed, | ||
| * to avoid unnecessary portal re-creation. | ||
| */ | ||
@@ -263,6 +265,6 @@ updateProps(props?: Record<string, any>): void; | ||
| /** | ||
| * Callback registered with the per-editor position-update registry. | ||
| * Stored so it can be unregistered in destroy(). | ||
| * Fires on editor updates when trackNodeViewPosition is enabled. | ||
| * Detects position shifts where update() is NOT called (e.g. typing above). | ||
| */ | ||
| private positionCheckCallback; | ||
| private handlePositionUpdate; | ||
| constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>); | ||
@@ -269,0 +271,0 @@ private cachedExtensionWithSyncedStorage; |
+62
-44
@@ -274,8 +274,15 @@ // src/Context.tsx | ||
| getInitialEditor() { | ||
| var _a; | ||
| let immediatelyRender = (_a = this.options.current.immediatelyRender) != null ? _a : true; | ||
| if (immediatelyRender && (isSSR || isNext)) { | ||
| const explicit = this.options.current.immediatelyRender; | ||
| let immediatelyRender = explicit != null ? explicit : true; | ||
| if (isSSR) { | ||
| if (immediatelyRender && isDev) { | ||
| console.warn("SSR detected. `immediatelyRender` has been set to false to avoid hydration mismatches"); | ||
| } | ||
| immediatelyRender = false; | ||
| } else if (isNext && explicit === void 0) { | ||
| immediatelyRender = false; | ||
| if (isDev) { | ||
| console.warn("SSR detected. `immediatelyRender` has been set to false to avoid hydration mismatches"); | ||
| console.warn( | ||
| "Next.js detected. `immediatelyRender` defaults to false to avoid hydration mismatches. Pass `immediatelyRender: true` explicitly if you are rendering the editor only on the client." | ||
| ); | ||
| } | ||
@@ -686,2 +693,4 @@ } | ||
| * Re-renders the React component with new props. | ||
| * Skips the render if none of the supplied props actually changed, | ||
| * to avoid unnecessary portal re-creation. | ||
| */ | ||
@@ -692,2 +701,14 @@ updateProps(props = {}) { | ||
| } | ||
| let changed = false; | ||
| const keys = Object.keys(props); | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; | ||
| if (props[key] !== this.props[key]) { | ||
| changed = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!changed) { | ||
| return; | ||
| } | ||
| this.props = { | ||
@@ -778,9 +799,3 @@ ...this.props, | ||
| // src/ReactNodeViewRenderer.tsx | ||
| import { | ||
| cancelPositionCheck, | ||
| getRenderedAttributes, | ||
| isNodeViewSelected, | ||
| NodeView, | ||
| schedulePositionCheck | ||
| } from "@tiptap/core"; | ||
| import { getRenderedAttributes, isNodeViewSelected, NodeView } from "@tiptap/core"; | ||
| import { createElement as createElement2, createRef, memo } from "react"; | ||
@@ -796,6 +811,16 @@ import { jsx as jsx7 } from "react/jsx-runtime"; | ||
| /** | ||
| * Callback registered with the per-editor position-update registry. | ||
| * Stored so it can be unregistered in destroy(). | ||
| * Fires on editor updates when trackNodeViewPosition is enabled. | ||
| * Detects position shifts where update() is NOT called (e.g. typing above). | ||
| */ | ||
| this.positionCheckCallback = null; | ||
| this.handlePositionUpdate = () => { | ||
| const newPos = this.getPos(); | ||
| if (typeof newPos !== "number" || newPos === this.currentPos) { | ||
| return; | ||
| } | ||
| this.currentPos = newPos; | ||
| this.renderer.updateProps({ getPos: () => this.getPos() }); | ||
| if (typeof this.options.attrs === "function") { | ||
| this.updateElementAttributes(); | ||
| } | ||
| }; | ||
| this.cachedExtensionWithSyncedStorage = null; | ||
@@ -817,2 +842,5 @@ if (!this.node.isLeaf) { | ||
| } | ||
| if (this.options.trackNodeViewPosition) { | ||
| this.editor.on("update", this.handlePositionUpdate); | ||
| } | ||
| } | ||
@@ -859,2 +887,3 @@ /** | ||
| }; | ||
| const mountProps = props; | ||
| if (!this.component.displayName) { | ||
@@ -889,3 +918,3 @@ const capitalizeFirstChar = (string) => { | ||
| editor: this.editor, | ||
| props, | ||
| props: mountProps, | ||
| as, | ||
@@ -897,14 +926,2 @@ className: `node-${this.node.type.name} ${className}`.trim() | ||
| this.currentPos = this.getPos(); | ||
| this.positionCheckCallback = () => { | ||
| const newPos = this.getPos(); | ||
| if (typeof newPos !== "number" || newPos === this.currentPos) { | ||
| return; | ||
| } | ||
| this.currentPos = newPos; | ||
| this.renderer.updateProps({ getPos: () => this.getPos() }); | ||
| if (typeof this.options.attrs === "function") { | ||
| this.updateElementAttributes(); | ||
| } | ||
| }; | ||
| schedulePositionCheck(this.editor, this.positionCheckCallback); | ||
| } | ||
@@ -998,17 +1015,10 @@ /** | ||
| } | ||
| const newPos = this.getPos(); | ||
| if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) { | ||
| if (newPos === this.currentPos) { | ||
| return true; | ||
| } | ||
| this.currentPos = newPos; | ||
| rerenderComponent({ | ||
| node, | ||
| decorations, | ||
| innerDecorations, | ||
| extension: this.extensionWithSyncedStorage, | ||
| getPos: () => this.getPos() | ||
| }); | ||
| const nodeChanged = node !== this.node; | ||
| if (!nodeChanged) { | ||
| this.node = node; | ||
| this.decorations = decorations; | ||
| this.innerDecorations = innerDecorations; | ||
| return true; | ||
| } | ||
| const newPos = this.getPos(); | ||
| this.node = node; | ||
@@ -1018,3 +1028,12 @@ this.decorations = decorations; | ||
| this.currentPos = newPos; | ||
| rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }); | ||
| const extraProps = { | ||
| node, | ||
| decorations, | ||
| innerDecorations, | ||
| extension: this.extensionWithSyncedStorage | ||
| }; | ||
| if (this.options.trackNodeViewPosition) { | ||
| extraProps.getPos = () => this.getPos(); | ||
| } | ||
| rerenderComponent(extraProps); | ||
| return true; | ||
@@ -1048,5 +1067,4 @@ } | ||
| this.editor.off("selectionUpdate", this.handleSelectionUpdate); | ||
| if (this.positionCheckCallback) { | ||
| cancelPositionCheck(this.editor, this.positionCheckCallback); | ||
| this.positionCheckCallback = null; | ||
| if (this.options.trackNodeViewPosition) { | ||
| this.editor.off("update", this.handlePositionUpdate); | ||
| } | ||
@@ -1053,0 +1071,0 @@ this.contentDOMElement = null; |
+7
-7
| { | ||
| "name": "@tiptap/react", | ||
| "description": "React components for tiptap", | ||
| "version": "3.23.4", | ||
| "version": "3.23.5", | ||
| "homepage": "https://tiptap.dev", | ||
@@ -51,8 +51,8 @@ "keywords": [ | ||
| "react-dom": "^19.0.0", | ||
| "@tiptap/core": "^3.23.4", | ||
| "@tiptap/pm": "^3.23.4" | ||
| "@tiptap/core": "^3.23.5", | ||
| "@tiptap/pm": "^3.23.5" | ||
| }, | ||
| "optionalDependencies": { | ||
| "@tiptap/extension-bubble-menu": "^3.23.4", | ||
| "@tiptap/extension-floating-menu": "^3.23.4" | ||
| "@tiptap/extension-bubble-menu": "^3.23.5", | ||
| "@tiptap/extension-floating-menu": "^3.23.5" | ||
| }, | ||
@@ -64,4 +64,4 @@ "peerDependencies": { | ||
| "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", | ||
| "@tiptap/core": "3.23.4", | ||
| "@tiptap/pm": "3.23.4" | ||
| "@tiptap/core": "3.23.5", | ||
| "@tiptap/pm": "3.23.5" | ||
| }, | ||
@@ -68,0 +68,0 @@ "repository": { |
@@ -8,9 +8,3 @@ import type { | ||
| } from '@tiptap/core' | ||
| import { | ||
| cancelPositionCheck, | ||
| getRenderedAttributes, | ||
| isNodeViewSelected, | ||
| NodeView, | ||
| schedulePositionCheck, | ||
| } from '@tiptap/core' | ||
| import { getRenderedAttributes, isNodeViewSelected, NodeView } from '@tiptap/core' | ||
| import type { Node, Node as ProseMirrorNode } from '@tiptap/pm/model' | ||
@@ -89,7 +83,18 @@ import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view' | ||
| /** | ||
| * Callback registered with the per-editor position-update registry. | ||
| * Stored so it can be unregistered in destroy(). | ||
| * Fires on editor updates when trackNodeViewPosition is enabled. | ||
| * Detects position shifts where update() is NOT called (e.g. typing above). | ||
| */ | ||
| private positionCheckCallback: (() => void) | null = null | ||
| private handlePositionUpdate = () => { | ||
| const newPos = this.getPos() | ||
| if (typeof newPos !== 'number' || newPos === this.currentPos) { | ||
| return | ||
| } | ||
| this.currentPos = newPos | ||
| this.renderer.updateProps({ getPos: () => this.getPos() }) | ||
| if (typeof this.options.attrs === 'function') { | ||
| this.updateElementAttributes() | ||
| } | ||
| } | ||
| constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>) { | ||
@@ -121,2 +126,6 @@ super(component, props, options) | ||
| } | ||
| if (this.options.trackNodeViewPosition) { | ||
| this.editor.on('update', this.handlePositionUpdate) | ||
| } | ||
| } | ||
@@ -154,3 +163,3 @@ | ||
| mount() { | ||
| const props = { | ||
| const props: Record<string, any> = { | ||
| editor: this.editor, | ||
@@ -168,4 +177,6 @@ node: this.node, | ||
| ref: createRef<T>(), | ||
| } satisfies ReactNodeViewProps<T> | ||
| } | ||
| const mountProps = props as ReactNodeViewProps<T> | ||
| if (!(this.component as any).displayName) { | ||
@@ -215,3 +226,3 @@ const capitalizeFirstChar = (string: string): string => { | ||
| editor: this.editor, | ||
| props, | ||
| props: mountProps, | ||
| as, | ||
@@ -224,21 +235,2 @@ className: `node-${this.node.type.name} ${className}`.trim(), | ||
| this.currentPos = this.getPos() | ||
| this.positionCheckCallback = () => { | ||
| const newPos = this.getPos() | ||
| if (typeof newPos !== 'number' || newPos === this.currentPos) { | ||
| return | ||
| } | ||
| this.currentPos = newPos | ||
| // Pass a fresh getPos reference so React's memo detects a prop change. | ||
| this.renderer.updateProps({ getPos: () => this.getPos() }) | ||
| if (typeof this.options.attrs === 'function') { | ||
| this.updateElementAttributes() | ||
| } | ||
| } | ||
| schedulePositionCheck(this.editor, this.positionCheckCallback) | ||
| } | ||
@@ -352,23 +344,20 @@ | ||
| const newPos = this.getPos() | ||
| const nodeChanged = node !== this.node | ||
| if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) { | ||
| if (newPos === this.currentPos) { | ||
| return true | ||
| } | ||
| // Position changed without a content/decoration change — trigger re-render | ||
| // so the component receives an up-to-date value from getPos(). | ||
| // Pass a fresh getPos reference so React's memo detects a prop change. | ||
| this.currentPos = newPos | ||
| rerenderComponent({ | ||
| node, | ||
| decorations, | ||
| innerDecorations, | ||
| extension: this.extensionWithSyncedStorage, | ||
| getPos: () => this.getPos(), | ||
| }) | ||
| // Node reference unchanged — only decorations may have changed. | ||
| // ProseMirror renders decorations independently on the contentDOM, | ||
| // and the getPos closure (bound in mount()) calls through to | ||
| // ProseMirror's position function at call time, so it is always | ||
| // current. Update internal refs and skip the React re-render. | ||
| // Keep currentPos unchanged here so the editor update listener can | ||
| // still detect and publish a position shift for tracked node views. | ||
| if (!nodeChanged) { | ||
| this.node = node | ||
| this.decorations = decorations | ||
| this.innerDecorations = innerDecorations | ||
| return true | ||
| } | ||
| const newPos = this.getPos() | ||
| this.node = node | ||
@@ -379,4 +368,14 @@ this.decorations = decorations | ||
| rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }) | ||
| const extraProps: Record<string, any> = { | ||
| node, | ||
| decorations, | ||
| innerDecorations, | ||
| extension: this.extensionWithSyncedStorage, | ||
| } | ||
| if (this.options.trackNodeViewPosition) { | ||
| extraProps.getPos = () => this.getPos() | ||
| } | ||
| rerenderComponent(extraProps) | ||
| return true | ||
@@ -414,5 +413,4 @@ } | ||
| if (this.positionCheckCallback) { | ||
| cancelPositionCheck(this.editor, this.positionCheckCallback) | ||
| this.positionCheckCallback = null | ||
| if (this.options.trackNodeViewPosition) { | ||
| this.editor.off('update', this.handlePositionUpdate) | ||
| } | ||
@@ -419,0 +417,0 @@ |
@@ -239,2 +239,4 @@ import type { Editor } from '@tiptap/core' | ||
| * Re-renders the React component with new props. | ||
| * Skips the render if none of the supplied props actually changed, | ||
| * to avoid unnecessary portal re-creation. | ||
| */ | ||
@@ -246,2 +248,18 @@ updateProps(props: Record<string, any> = {}): void { | ||
| // Shallow comparison — skip if every supplied key already holds the same value. | ||
| let changed = false | ||
| const keys = Object.keys(props) | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i] | ||
| if (props[key] !== this.props[key]) { | ||
| changed = true | ||
| break | ||
| } | ||
| } | ||
| if (!changed) { | ||
| return | ||
| } | ||
| this.props = { | ||
@@ -248,0 +266,0 @@ ...this.props, |
+11
-3
@@ -96,8 +96,16 @@ import { type EditorOptions, Editor } from '@tiptap/core' | ||
| private getInitialEditor() { | ||
| let immediatelyRender = this.options.current.immediatelyRender ?? true | ||
| const explicit = this.options.current.immediatelyRender | ||
| let immediatelyRender = explicit ?? true | ||
| if (immediatelyRender && (isSSR || isNext)) { | ||
| if (isSSR) { | ||
| if (immediatelyRender && isDev) { | ||
| console.warn('SSR detected. `immediatelyRender` has been set to false to avoid hydration mismatches') | ||
| } | ||
| immediatelyRender = false | ||
| } else if (isNext && explicit === undefined) { | ||
| immediatelyRender = false | ||
| if (isDev) { | ||
| console.warn('SSR detected. `immediatelyRender` has been set to false to avoid hydration mismatches') | ||
| console.warn( | ||
| 'Next.js detected. `immediatelyRender` defaults to false to avoid hydration mismatches. Pass `immediatelyRender: true` explicitly if you are rendering the editor only on the client.', | ||
| ) | ||
| } | ||
@@ -104,0 +112,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
471478
1.08%6421
1.05%