mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-07 17:43:49 +08:00
* add support for react 19 ref props (#6405) * add support for react 19 ref props * added changeset * Update packages/react/src/ReactRenderer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * use partial imports instead of importing the whole React library * fix react renderer not passing ref prop * upgrade dev dependencies for react * updated lockfile * upgrade dev dependencies * update package.json * remove optionalDependencies and move react deps to peerDependencies * enhance ReactRenderer for React 19 compatibility and improve ref handling * remove unused 'node' property from ReactNodeViewProps type definition * fix: update ref type in ReactNodeView to be generic * fix: replace FunctionComponent with NamedExoticComponent for better performance in ReactNodeView * cloned react renderer element props to avoid side effects --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: update ReactNodeViewProps to allow null ref values (#6415) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
f750c521a7
commit
e3c5c7cf6f
5
.changeset/shiny-impalas-remember.md
Normal file
5
.changeset/shiny-impalas-remember.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'@tiptap/react': minor
|
||||
---
|
||||
|
||||
Added support for React 19 ref in props
|
@ -36,8 +36,8 @@
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-import": "^15.1.0",
|
||||
"prosemirror-dev-tools": "^4.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"sass": "^1.81.0",
|
||||
"svelte": "^4.2.19",
|
||||
"tailwindcss": "^3.4.15",
|
||||
|
@ -10,7 +10,7 @@ export default props => {
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="react-component">
|
||||
<label>React Component</label>
|
||||
<label ref={props.ref}>React Component</label>
|
||||
|
||||
<div className="content">
|
||||
<button onClick={increase}>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import './MentionList.scss'
|
||||
|
||||
import React, {
|
||||
forwardRef, useEffect, useImperativeHandle,
|
||||
useEffect, useImperativeHandle,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export default forwardRef((props, ref) => {
|
||||
export default props => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
const selectItem = index => {
|
||||
@ -30,7 +30,7 @@ export default forwardRef((props, ref) => {
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
useImperativeHandle(props.ref, () => ({
|
||||
onKeyDown: ({ event }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler()
|
||||
@ -67,4 +67,4 @@ export default forwardRef((props, ref) => {
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
180
package-lock.json
generated
180
package-lock.json
generated
@ -23,8 +23,8 @@
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-commonjs": "^26.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
||||
"@typescript-eslint/parser": "^8.19.0",
|
||||
"babel-loader": "^9.2.1",
|
||||
@ -83,8 +83,8 @@
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-import": "^15.1.0",
|
||||
"prosemirror-dev-tools": "^4.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"sass": "^1.81.0",
|
||||
"svelte": "^4.2.19",
|
||||
"tailwindcss": "^3.4.15",
|
||||
@ -347,6 +347,36 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"demos/node_modules/react": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"demos/node_modules/react-dom": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"demos/node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"demos/node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
@ -5656,24 +5686,23 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
|
||||
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
||||
"version": "19.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
||||
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
|
||||
"version": "19.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
|
||||
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
@ -15319,6 +15348,18 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dev-tools/node_modules/@types/react": {
|
||||
"version": "18.3.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dev-tools/node_modules/nanoid": {
|
||||
"version": "2.1.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
|
||||
@ -15326,6 +15367,42 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prosemirror-dev-tools/node_modules/react-dock": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.6.0.tgz",
|
||||
"integrity": "sha512-jEOhv1s+pqRQ4JxgUw4XUotnprOehZ23mqchf3whxYXnvNgTQOXCxh6bpcqW8P6OybIk2bYO18r3qimZ3ypCbg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dev-tools/node_modules/react-json-tree": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.17.0.tgz",
|
||||
"integrity": "sha512-hcWjibI/fAvsKnfYk+lka5OrE1Lvb1jH5pSnFhIU5T8cCCxB85r6h/NOzDPggSSgErjmx4rl3+2EkeclIKBOhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-base16-styling": "^0.9.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dropcursor": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz",
|
||||
@ -15652,6 +15729,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@ -15675,29 +15753,12 @@
|
||||
"lodash.curry": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dock": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.6.0.tgz",
|
||||
"integrity": "sha512-jEOhv1s+pqRQ4JxgUw4XUotnprOehZ23mqchf3whxYXnvNgTQOXCxh6bpcqW8P6OybIk2bYO18r3qimZ3ypCbg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@ -15729,24 +15790,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-json-tree": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.17.0.tgz",
|
||||
"integrity": "sha512-hcWjibI/fAvsKnfYk+lka5OrE1Lvb1jH5pSnFhIU5T8cCCxB85r6h/NOzDPggSSgErjmx4rl3+2EkeclIKBOhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-base16-styling": "^0.9.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.13.0.tgz",
|
||||
@ -16722,6 +16765,7 @@
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
@ -21028,10 +21072,10 @@
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^2.12.0",
|
||||
"@tiptap/pm": "^2.12.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@ -21044,6 +21088,36 @@
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"packages/react/node_modules/react": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"packages/react/node_modules/react-dom": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"packages/react/node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/starter-kit": {
|
||||
"name": "@tiptap/starter-kit",
|
||||
"version": "2.12.0",
|
||||
|
@ -47,8 +47,8 @@
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-commonjs": "^26.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
||||
"@typescript-eslint/parser": "^8.19.0",
|
||||
"babel-loader": "^9.2.1",
|
||||
|
@ -38,10 +38,10 @@
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^2.12.0",
|
||||
"@tiptap/pm": "^2.12.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
|
@ -1,19 +1,17 @@
|
||||
import {
|
||||
DecorationWithType,
|
||||
Editor,
|
||||
getRenderedAttributes,
|
||||
NodeView,
|
||||
NodeViewProps,
|
||||
NodeViewRenderer,
|
||||
NodeViewRendererOptions,
|
||||
import type {
|
||||
DecorationWithType, Editor, NodeViewRenderer, NodeViewRendererOptions,
|
||||
} from '@tiptap/core'
|
||||
import { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
import { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
||||
import React, { ComponentType } from 'react'
|
||||
import { getRenderedAttributes, NodeView } from '@tiptap/core'
|
||||
import type { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
||||
import type { ComponentType, NamedExoticComponent } from 'react'
|
||||
import React, { createElement, createRef, memo } from 'react'
|
||||
|
||||
import { EditorWithContentComponent } from './Editor.js'
|
||||
import { ReactRenderer } from './ReactRenderer.js'
|
||||
import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView.js'
|
||||
import type { ReactNodeViewProps } from './types.js'
|
||||
import type { ReactNodeViewContextProps } from './useReactNodeView.js'
|
||||
import { ReactNodeViewContext } from './useReactNodeView.js'
|
||||
|
||||
export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
||||
/**
|
||||
@ -53,14 +51,15 @@ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
||||
}
|
||||
|
||||
export class ReactNodeView<
|
||||
Component extends ComponentType<NodeViewProps> = ComponentType<NodeViewProps>,
|
||||
T = HTMLElement,
|
||||
Component extends ComponentType<ReactNodeViewProps<T>> = ComponentType<ReactNodeViewProps<T>>,
|
||||
NodeEditor extends Editor = Editor,
|
||||
Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions,
|
||||
> extends NodeView<Component, NodeEditor, Options> {
|
||||
/**
|
||||
* The renderer instance.
|
||||
*/
|
||||
renderer!: ReactRenderer<unknown, NodeViewProps>
|
||||
renderer!: ReactRenderer<unknown, ReactNodeViewProps<T>>
|
||||
|
||||
/**
|
||||
* The element that holds the rich-text content of the node.
|
||||
@ -84,7 +83,8 @@ export class ReactNodeView<
|
||||
getPos: () => this.getPos(),
|
||||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
||||
deleteNode: () => this.deleteNode(),
|
||||
} satisfies NodeViewProps
|
||||
ref: createRef<T>(),
|
||||
} satisfies ReactNodeViewProps<T>
|
||||
|
||||
if (!(this.component as any).displayName) {
|
||||
const capitalizeFirstChar = (string: string): string => {
|
||||
@ -104,15 +104,13 @@ export class ReactNodeView<
|
||||
const Component = this.component
|
||||
// For performance reasons, we memoize the provider component
|
||||
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
||||
const ReactNodeViewProvider: React.FunctionComponent<NodeViewProps> = React.memo(
|
||||
componentProps => {
|
||||
return (
|
||||
<ReactNodeViewContext.Provider value={context}>
|
||||
{React.createElement(Component, componentProps)}
|
||||
</ReactNodeViewContext.Provider>
|
||||
)
|
||||
},
|
||||
)
|
||||
const ReactNodeViewProvider: NamedExoticComponent<ReactNodeViewProps<T>> = memo(componentProps => {
|
||||
return (
|
||||
<ReactNodeViewContext.Provider value={context}>
|
||||
{createElement(Component, componentProps)}
|
||||
</ReactNodeViewContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
ReactNodeViewProvider.displayName = 'ReactNodeView'
|
||||
|
||||
@ -320,8 +318,8 @@ export class ReactNodeView<
|
||||
/**
|
||||
* Create a React node view renderer.
|
||||
*/
|
||||
export function ReactNodeViewRenderer(
|
||||
component: ComponentType<NodeViewProps>,
|
||||
export function ReactNodeViewRenderer<T = HTMLElement>(
|
||||
component: ComponentType<ReactNodeViewProps<T>>,
|
||||
options?: Partial<ReactNodeViewRendererOptions>,
|
||||
): NodeViewRenderer {
|
||||
return props => {
|
||||
@ -332,6 +330,6 @@ export function ReactNodeViewRenderer(
|
||||
return {} as unknown as ProseMirrorNodeView
|
||||
}
|
||||
|
||||
return new ReactNodeView(component, props, options)
|
||||
return new ReactNodeView<T>(component, props, options)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,13 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import React from 'react'
|
||||
import type { Editor } from '@tiptap/core'
|
||||
import type {
|
||||
ComponentClass,
|
||||
ForwardRefExoticComponent,
|
||||
FunctionComponent,
|
||||
PropsWithoutRef,
|
||||
ReactNode,
|
||||
RefAttributes,
|
||||
} from 'react'
|
||||
import React, { version as reactVersion } from 'react'
|
||||
import { flushSync } from 'react-dom'
|
||||
|
||||
import { EditorWithContentComponent } from './Editor.js'
|
||||
@ -29,6 +37,27 @@ function isForwardRefComponent(Component: any) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're running React 19+ by detecting if function components support ref props
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isReact19Plus(): boolean {
|
||||
// React 19 is detected by checking React version if available
|
||||
// In practice, we'll use a more conservative approach and assume React 18 behavior
|
||||
// unless we can definitively detect React 19
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (reactVersion) {
|
||||
const majorVersion = parseInt(reactVersion.split('.')[0], 10)
|
||||
|
||||
return majorVersion >= 19
|
||||
}
|
||||
} catch {
|
||||
// Fallback to React 18 behavior if we can't determine version
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export interface ReactRendererOptions {
|
||||
/**
|
||||
* The editor instance.
|
||||
@ -60,9 +89,9 @@ export interface ReactRendererOptions {
|
||||
}
|
||||
|
||||
type ComponentType<R, P> =
|
||||
React.ComponentClass<P> |
|
||||
React.FunctionComponent<P> |
|
||||
React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
|
||||
| ComponentClass<P>
|
||||
| FunctionComponent<P>
|
||||
| ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<R>>
|
||||
|
||||
/**
|
||||
* The ReactRenderer class. It's responsible for rendering React components inside the editor.
|
||||
@ -86,7 +115,7 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
||||
|
||||
props: P
|
||||
|
||||
reactElement: React.ReactNode
|
||||
reactElement: ReactNode
|
||||
|
||||
ref: R | null = null
|
||||
|
||||
@ -129,14 +158,32 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
||||
const props = this.props
|
||||
const editor = this.editor as EditorWithContentComponent
|
||||
|
||||
if (isClassComponent(Component) || isForwardRefComponent(Component)) {
|
||||
// @ts-ignore This is a hack to make the ref work
|
||||
props.ref = (ref: R) => {
|
||||
this.ref = ref
|
||||
// Handle ref forwarding with React 18/19 compatibility
|
||||
const isReact19 = isReact19Plus()
|
||||
const isClassComp = isClassComponent(Component)
|
||||
const isForwardRefComp = isForwardRefComponent(Component)
|
||||
|
||||
const elementProps = { ...props }
|
||||
|
||||
if (!elementProps.ref) {
|
||||
if (isReact19) {
|
||||
// React 19: ref is a standard prop for all components
|
||||
// @ts-ignore - Setting ref prop for React 19 compatibility
|
||||
elementProps.ref = (ref: R) => {
|
||||
this.ref = ref
|
||||
}
|
||||
} else if (isClassComp || isForwardRefComp) {
|
||||
// React 18 and prior: only set ref for class components and forwardRef components
|
||||
// @ts-ignore - Setting ref prop for React 18 class/forwardRef components
|
||||
elementProps.ref = (ref: R) => {
|
||||
this.ref = ref
|
||||
}
|
||||
}
|
||||
// For function components in React 18, we can't use ref - the component won't receive it
|
||||
// This is a limitation we have to accept for React 18 function components without forwardRef
|
||||
}
|
||||
|
||||
this.reactElement = <Component {...props} />
|
||||
this.reactElement = <Component {...elementProps} />
|
||||
|
||||
editor?.contentComponent?.setRenderer(this.id, this)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ export * from './NodeViewContent.js'
|
||||
export * from './NodeViewWrapper.js'
|
||||
export * from './ReactNodeViewRenderer.js'
|
||||
export * from './ReactRenderer.js'
|
||||
export * from './types.js'
|
||||
export * from './useEditor.js'
|
||||
export * from './useEditorState.js'
|
||||
export * from './useReactNodeView.js'
|
||||
|
6
packages/react/src/types.ts
Normal file
6
packages/react/src/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { NodeViewProps as CoreNodeViewProps } from '@tiptap/core'
|
||||
import type React from 'react'
|
||||
|
||||
export type ReactNodeViewProps<T = HTMLElement> = CoreNodeViewProps & {
|
||||
ref: React.RefObject<T | null>
|
||||
}
|
Loading…
Reference in New Issue
Block a user