@tiptap/react 3.0.0-next.3 → 3.0.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +5 -1
- package/dist/index.cjs +202 -214
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -44
- package/dist/index.d.ts +55 -44
- package/dist/index.js +169 -200
- package/dist/index.js.map +1 -1
- package/dist/menus/index.cjs +142 -0
- package/dist/menus/index.cjs.map +1 -0
- package/dist/menus/index.d.cts +19 -0
- package/dist/menus/index.d.ts +19 -0
- package/dist/menus/index.js +104 -0
- package/dist/menus/index.js.map +1 -0
- package/package.json +30 -16
- package/src/Context.tsx +9 -13
- package/src/Editor.ts +5 -5
- package/src/EditorContent.tsx +10 -19
- package/src/NodeViewContent.tsx +12 -9
- package/src/NodeViewWrapper.tsx +2 -2
- package/src/ReactMarkViewRenderer.tsx +108 -0
- package/src/ReactNodeViewRenderer.tsx +23 -36
- package/src/ReactRenderer.tsx +16 -25
- package/src/index.ts +1 -2
- package/src/{BubbleMenu.tsx → menus/BubbleMenu.tsx} +9 -35
- package/src/menus/FloatingMenu.tsx +67 -0
- package/src/menus/index.ts +2 -0
- package/src/useEditor.ts +62 -26
- package/src/useEditorState.ts +14 -19
- package/src/useReactNodeView.ts +20 -5
- package/src/FloatingMenu.tsx +0 -83
package/src/ReactRenderer.tsx
CHANGED
|
@@ -10,11 +10,7 @@ import { EditorWithContentComponent } from './Editor.js'
|
|
|
10
10
|
* @returns {boolean}
|
|
11
11
|
*/
|
|
12
12
|
function isClassComponent(Component: any) {
|
|
13
|
-
return !!(
|
|
14
|
-
typeof Component === 'function'
|
|
15
|
-
&& Component.prototype
|
|
16
|
-
&& Component.prototype.isReactComponent
|
|
17
|
-
)
|
|
13
|
+
return !!(typeof Component === 'function' && Component.prototype && Component.prototype.isReactComponent)
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
/**
|
|
@@ -23,10 +19,7 @@ function isClassComponent(Component: any) {
|
|
|
23
19
|
* @returns {boolean}
|
|
24
20
|
*/
|
|
25
21
|
function isForwardRefComponent(Component: any) {
|
|
26
|
-
return !!(
|
|
27
|
-
typeof Component === 'object'
|
|
28
|
-
&& Component.$$typeof?.toString() === 'Symbol(react.forward_ref)'
|
|
29
|
-
)
|
|
22
|
+
return !!(typeof Component === 'object' && Component.$$typeof?.toString() === 'Symbol(react.forward_ref)')
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
export interface ReactRendererOptions {
|
|
@@ -34,21 +27,21 @@ export interface ReactRendererOptions {
|
|
|
34
27
|
* The editor instance.
|
|
35
28
|
* @type {Editor}
|
|
36
29
|
*/
|
|
37
|
-
editor: Editor
|
|
30
|
+
editor: Editor
|
|
38
31
|
|
|
39
32
|
/**
|
|
40
33
|
* The props for the component.
|
|
41
34
|
* @type {Record<string, any>}
|
|
42
35
|
* @default {}
|
|
43
36
|
*/
|
|
44
|
-
props?: Record<string, any
|
|
37
|
+
props?: Record<string, any>
|
|
45
38
|
|
|
46
39
|
/**
|
|
47
40
|
* The tag name of the element.
|
|
48
41
|
* @type {string}
|
|
49
42
|
* @default 'div'
|
|
50
43
|
*/
|
|
51
|
-
as?: string
|
|
44
|
+
as?: string
|
|
52
45
|
|
|
53
46
|
/**
|
|
54
47
|
* The class name of the element.
|
|
@@ -56,13 +49,13 @@ export interface ReactRendererOptions {
|
|
|
56
49
|
* @default ''
|
|
57
50
|
* @example 'foo bar'
|
|
58
51
|
*/
|
|
59
|
-
className?: string
|
|
52
|
+
className?: string
|
|
60
53
|
}
|
|
61
54
|
|
|
62
55
|
type ComponentType<R, P> =
|
|
63
|
-
React.ComponentClass<P>
|
|
64
|
-
React.FunctionComponent<P>
|
|
65
|
-
React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R
|
|
56
|
+
| React.ComponentClass<P>
|
|
57
|
+
| React.FunctionComponent<P>
|
|
58
|
+
| React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>
|
|
66
59
|
|
|
67
60
|
/**
|
|
68
61
|
* The ReactRenderer class. It's responsible for rendering React components inside the editor.
|
|
@@ -74,8 +67,8 @@ type ComponentType<R, P> =
|
|
|
74
67
|
* },
|
|
75
68
|
* as: 'span',
|
|
76
69
|
* })
|
|
77
|
-
*/
|
|
78
|
-
export class ReactRenderer<R = unknown, P extends Record<string, any> =
|
|
70
|
+
*/
|
|
71
|
+
export class ReactRenderer<R = unknown, P extends Record<string, any> = object> {
|
|
79
72
|
id: string
|
|
80
73
|
|
|
81
74
|
editor: Editor
|
|
@@ -93,13 +86,11 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = {}> {
|
|
|
93
86
|
/**
|
|
94
87
|
* Immediately creates element and renders the provided React component.
|
|
95
88
|
*/
|
|
96
|
-
constructor(
|
|
97
|
-
|
|
98
|
-
props = {},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}: ReactRendererOptions) {
|
|
102
|
-
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
|
89
|
+
constructor(
|
|
90
|
+
component: ComponentType<R, P>,
|
|
91
|
+
{ editor, props = {}, as = 'div', className = '' }: ReactRendererOptions,
|
|
92
|
+
) {
|
|
93
|
+
this.id = Math.floor(Math.random() * 0xffffffff).toString()
|
|
103
94
|
this.component = component
|
|
104
95
|
this.editor = editor as EditorWithContentComponent
|
|
105
96
|
this.props = props as P
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export * from './BubbleMenu.js'
|
|
2
1
|
export * from './Context.js'
|
|
3
2
|
export * from './EditorContent.js'
|
|
4
|
-
export * from './FloatingMenu.js'
|
|
5
3
|
export * from './NodeViewContent.js'
|
|
6
4
|
export * from './NodeViewWrapper.js'
|
|
5
|
+
export * from './ReactMarkViewRenderer.js'
|
|
7
6
|
export * from './ReactNodeViewRenderer.js'
|
|
8
7
|
export * from './ReactRenderer.js'
|
|
9
8
|
export * from './useEditor.js'
|
|
@@ -1,33 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type BubbleMenuPluginProps, BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'
|
|
2
|
+
import { useCurrentEditor } from '@tiptap/react'
|
|
2
3
|
import React, { useEffect, useRef } from 'react'
|
|
3
4
|
import { createPortal } from 'react-dom'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
|
6
7
|
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
export type BubbleMenuProps = Omit<
|
|
10
|
-
Optional<BubbleMenuPluginProps, 'pluginKey'>,
|
|
11
|
-
'element' | 'editor'
|
|
12
|
-
> & {
|
|
13
|
-
editor: BubbleMenuPluginProps['editor'] | null;
|
|
14
|
-
updateDelay?: number;
|
|
15
|
-
resizeDelay?: number;
|
|
16
|
-
options?: BubbleMenuPluginProps['options'];
|
|
17
|
-
} & React.HTMLAttributes<HTMLDivElement>;
|
|
8
|
+
export type BubbleMenuProps = Optional<Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> &
|
|
9
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
18
10
|
|
|
19
11
|
export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(
|
|
20
12
|
(
|
|
21
|
-
{
|
|
22
|
-
pluginKey = 'bubbleMenu',
|
|
23
|
-
editor,
|
|
24
|
-
updateDelay,
|
|
25
|
-
resizeDelay,
|
|
26
|
-
shouldShow = null,
|
|
27
|
-
options,
|
|
28
|
-
children,
|
|
29
|
-
...restProps
|
|
30
|
-
},
|
|
13
|
+
{ pluginKey = 'bubbleMenu', editor, updateDelay, resizeDelay, shouldShow = null, options, children, ...restProps },
|
|
31
14
|
ref,
|
|
32
15
|
) => {
|
|
33
16
|
const menuEl = useRef(document.createElement('div'))
|
|
@@ -46,16 +29,14 @@ export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(
|
|
|
46
29
|
bubbleMenuElement.style.visibility = 'hidden'
|
|
47
30
|
bubbleMenuElement.style.position = 'absolute'
|
|
48
31
|
|
|
49
|
-
if (editor?.isDestroyed || currentEditor?.isDestroyed) {
|
|
32
|
+
if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {
|
|
50
33
|
return
|
|
51
34
|
}
|
|
52
35
|
|
|
53
36
|
const attachToEditor = editor || currentEditor
|
|
54
37
|
|
|
55
38
|
if (!attachToEditor) {
|
|
56
|
-
console.warn(
|
|
57
|
-
'BubbleMenu component is not rendered inside of an editor component or does not have editor prop.',
|
|
58
|
-
)
|
|
39
|
+
console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.')
|
|
59
40
|
return
|
|
60
41
|
}
|
|
61
42
|
|
|
@@ -82,13 +63,6 @@ export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(
|
|
|
82
63
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83
64
|
}, [editor, currentEditor])
|
|
84
65
|
|
|
85
|
-
return createPortal(
|
|
86
|
-
<div
|
|
87
|
-
{...restProps}
|
|
88
|
-
>
|
|
89
|
-
{children}
|
|
90
|
-
</div>,
|
|
91
|
-
menuEl.current,
|
|
92
|
-
)
|
|
66
|
+
return createPortal(<div {...restProps}>{children}</div>, menuEl.current)
|
|
93
67
|
},
|
|
94
68
|
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
|
|
2
|
+
import { useCurrentEditor } from '@tiptap/react'
|
|
3
|
+
import React, { useEffect, useRef } from 'react'
|
|
4
|
+
import { createPortal } from 'react-dom'
|
|
5
|
+
|
|
6
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
|
7
|
+
|
|
8
|
+
export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {
|
|
9
|
+
editor: FloatingMenuPluginProps['editor'] | null
|
|
10
|
+
options?: FloatingMenuPluginProps['options']
|
|
11
|
+
} & React.HTMLAttributes<HTMLDivElement>
|
|
12
|
+
|
|
13
|
+
export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(
|
|
14
|
+
({ pluginKey = 'floatingMenu', editor, shouldShow = null, options, children, ...restProps }, ref) => {
|
|
15
|
+
const menuEl = useRef(document.createElement('div'))
|
|
16
|
+
|
|
17
|
+
if (typeof ref === 'function') {
|
|
18
|
+
ref(menuEl.current)
|
|
19
|
+
} else if (ref) {
|
|
20
|
+
ref.current = menuEl.current
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { editor: currentEditor } = useCurrentEditor()
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const floatingMenuElement = menuEl.current
|
|
27
|
+
|
|
28
|
+
floatingMenuElement.style.visibility = 'hidden'
|
|
29
|
+
floatingMenuElement.style.position = 'absolute'
|
|
30
|
+
|
|
31
|
+
if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const attachToEditor = editor || currentEditor
|
|
36
|
+
|
|
37
|
+
if (!attachToEditor) {
|
|
38
|
+
console.warn(
|
|
39
|
+
'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',
|
|
40
|
+
)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const plugin = FloatingMenuPlugin({
|
|
45
|
+
editor: attachToEditor,
|
|
46
|
+
element: floatingMenuElement,
|
|
47
|
+
pluginKey,
|
|
48
|
+
shouldShow,
|
|
49
|
+
options,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
attachToEditor.registerPlugin(plugin)
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
attachToEditor.unregisterPlugin(pluginKey)
|
|
56
|
+
window.requestAnimationFrame(() => {
|
|
57
|
+
if (floatingMenuElement.parentNode) {
|
|
58
|
+
floatingMenuElement.parentNode.removeChild(floatingMenuElement)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
63
|
+
}, [editor, currentEditor])
|
|
64
|
+
|
|
65
|
+
return createPortal(<div {...restProps}>{children}</div>, menuEl.current)
|
|
66
|
+
},
|
|
67
|
+
)
|
package/src/useEditor.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { type EditorOptions, Editor } from '@tiptap/core'
|
|
2
|
-
import {
|
|
3
|
-
DependencyList,
|
|
4
|
-
MutableRefObject,
|
|
5
|
-
useDebugValue,
|
|
6
|
-
useEffect,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
} from 'react'
|
|
2
|
+
import { DependencyList, MutableRefObject, useDebugValue, useEffect, useRef, useState } from 'react'
|
|
10
3
|
import { useSyncExternalStore } from 'use-sync-external-store/shim'
|
|
11
4
|
|
|
12
5
|
import { useEditorState } from './useEditorState.js'
|
|
13
6
|
|
|
7
|
+
// @ts-ignore
|
|
14
8
|
const isDev = process.env.NODE_ENV !== 'production'
|
|
15
9
|
const isSSR = typeof window === 'undefined'
|
|
16
10
|
const isNext = isSSR || Boolean(typeof window !== 'undefined' && (window as any).next)
|
|
@@ -25,14 +19,14 @@ export type UseEditorOptions = Partial<EditorOptions> & {
|
|
|
25
19
|
* If server-side rendering, set this to `false`.
|
|
26
20
|
* @default true
|
|
27
21
|
*/
|
|
28
|
-
immediatelyRender?: boolean
|
|
22
|
+
immediatelyRender?: boolean
|
|
29
23
|
/**
|
|
30
24
|
* Whether to re-render the editor on each transaction.
|
|
31
25
|
* This is legacy behavior that will be removed in future versions.
|
|
32
26
|
* @default false
|
|
33
27
|
*/
|
|
34
|
-
shouldRerenderOnTransaction?: boolean
|
|
35
|
-
}
|
|
28
|
+
shouldRerenderOnTransaction?: boolean
|
|
29
|
+
}
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* This class handles the creation, destruction, and re-creation of the editor instance.
|
|
@@ -150,6 +144,7 @@ class EditorInstanceManager {
|
|
|
150
144
|
onContentError: (...args) => this.options.current.onContentError?.(...args),
|
|
151
145
|
onDrop: (...args) => this.options.current.onDrop?.(...args),
|
|
152
146
|
onPaste: (...args) => this.options.current.onPaste?.(...args),
|
|
147
|
+
onDelete: (...args) => this.options.current.onDelete?.(...args),
|
|
153
148
|
}
|
|
154
149
|
const editor = new Editor(optionsToApply)
|
|
155
150
|
|
|
@@ -183,6 +178,47 @@ class EditorInstanceManager {
|
|
|
183
178
|
}
|
|
184
179
|
}
|
|
185
180
|
|
|
181
|
+
static compareOptions(a: UseEditorOptions, b: UseEditorOptions) {
|
|
182
|
+
return (Object.keys(a) as (keyof UseEditorOptions)[]).every(key => {
|
|
183
|
+
if (
|
|
184
|
+
[
|
|
185
|
+
'onCreate',
|
|
186
|
+
'onBeforeCreate',
|
|
187
|
+
'onDestroy',
|
|
188
|
+
'onUpdate',
|
|
189
|
+
'onTransaction',
|
|
190
|
+
'onFocus',
|
|
191
|
+
'onBlur',
|
|
192
|
+
'onSelectionUpdate',
|
|
193
|
+
'onContentError',
|
|
194
|
+
'onDrop',
|
|
195
|
+
'onPaste',
|
|
196
|
+
].includes(key)
|
|
197
|
+
) {
|
|
198
|
+
// we don't want to compare callbacks, they are always different and only registered once
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// We often encourage putting extensions inlined in the options object, so we will do a slightly deeper comparison here
|
|
203
|
+
if (key === 'extensions' && a.extensions && b.extensions) {
|
|
204
|
+
if (a.extensions.length !== b.extensions.length) {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
return a.extensions.every((extension, index) => {
|
|
208
|
+
if (extension !== b.extensions?.[index]) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
return true
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
if (a[key] !== b[key]) {
|
|
215
|
+
// if any of the options have changed, we should update the editor options
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
return true
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
186
222
|
/**
|
|
187
223
|
* On each render, we will create, update, or destroy the editor instance.
|
|
188
224
|
* @param deps The dependencies to watch for changes
|
|
@@ -196,12 +232,15 @@ class EditorInstanceManager {
|
|
|
196
232
|
clearTimeout(this.scheduledDestructionTimeout)
|
|
197
233
|
|
|
198
234
|
if (this.editor && !this.editor.isDestroyed && deps.length === 0) {
|
|
199
|
-
// if the editor does exist & deps are empty, we don't need to re-initialize the editor
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
235
|
+
// if the editor does exist & deps are empty, we don't need to re-initialize the editor generally
|
|
236
|
+
if (!EditorInstanceManager.compareOptions(this.options.current, this.editor.options)) {
|
|
237
|
+
// But, the options are different, so we need to update the editor options
|
|
238
|
+
// Still, this is faster than re-creating the editor
|
|
239
|
+
this.editor.setOptions({
|
|
240
|
+
...this.options.current,
|
|
241
|
+
editable: this.editor.isEditable,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
205
244
|
} else {
|
|
206
245
|
// When the editor:
|
|
207
246
|
// - does not yet exist
|
|
@@ -229,8 +268,8 @@ class EditorInstanceManager {
|
|
|
229
268
|
this.previousDeps = deps
|
|
230
269
|
return
|
|
231
270
|
}
|
|
232
|
-
const depsAreEqual =
|
|
233
|
-
&& this.previousDeps.every((dep, index) => dep === deps[index])
|
|
271
|
+
const depsAreEqual =
|
|
272
|
+
this.previousDeps.length === deps.length && this.previousDeps.every((dep, index) => dep === deps[index])
|
|
234
273
|
|
|
235
274
|
if (depsAreEqual) {
|
|
236
275
|
// deps exist and are equal, no need to recreate
|
|
@@ -289,8 +328,8 @@ class EditorInstanceManager {
|
|
|
289
328
|
*/
|
|
290
329
|
export function useEditor(
|
|
291
330
|
options: UseEditorOptions & { immediatelyRender: false },
|
|
292
|
-
deps?: DependencyList
|
|
293
|
-
): Editor | null
|
|
331
|
+
deps?: DependencyList,
|
|
332
|
+
): Editor | null
|
|
294
333
|
|
|
295
334
|
/**
|
|
296
335
|
* This hook allows you to create an editor instance.
|
|
@@ -299,12 +338,9 @@ export function useEditor(
|
|
|
299
338
|
* @returns The editor instance
|
|
300
339
|
* @example const editor = useEditor({ extensions: [...] })
|
|
301
340
|
*/
|
|
302
|
-
export function useEditor(options: UseEditorOptions, deps?: DependencyList): Editor
|
|
341
|
+
export function useEditor(options: UseEditorOptions, deps?: DependencyList): Editor
|
|
303
342
|
|
|
304
|
-
export function useEditor(
|
|
305
|
-
options: UseEditorOptions = {},
|
|
306
|
-
deps: DependencyList = [],
|
|
307
|
-
): Editor | null {
|
|
343
|
+
export function useEditor(options: UseEditorOptions = {}, deps: DependencyList = []): Editor | null {
|
|
308
344
|
const mostRecentOptions = useRef(options)
|
|
309
345
|
|
|
310
346
|
mostRecentOptions.current = options
|
package/src/useEditorState.ts
CHANGED
|
@@ -1,35 +1,30 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/core'
|
|
2
2
|
import deepEqual from 'fast-deep-equal/es6/react'
|
|
3
|
-
import {
|
|
4
|
-
useDebugValue, useEffect, useLayoutEffect, useState,
|
|
5
|
-
} from 'react'
|
|
3
|
+
import { useDebugValue, useEffect, useLayoutEffect, useState } from 'react'
|
|
6
4
|
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'
|
|
7
5
|
|
|
8
6
|
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
|
9
7
|
|
|
10
8
|
export type EditorStateSnapshot<TEditor extends Editor | null = Editor | null> = {
|
|
11
|
-
editor: TEditor
|
|
12
|
-
transactionNumber: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type UseEditorStateOptions<
|
|
16
|
-
TSelectorResult,
|
|
17
|
-
TEditor extends Editor | null = Editor | null,
|
|
18
|
-
> = {
|
|
9
|
+
editor: TEditor
|
|
10
|
+
transactionNumber: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type UseEditorStateOptions<TSelectorResult, TEditor extends Editor | null = Editor | null> = {
|
|
19
14
|
/**
|
|
20
15
|
* The editor instance.
|
|
21
16
|
*/
|
|
22
|
-
editor: TEditor
|
|
17
|
+
editor: TEditor
|
|
23
18
|
/**
|
|
24
19
|
* A selector function to determine the value to compare for re-rendering.
|
|
25
20
|
*/
|
|
26
|
-
selector: (context: EditorStateSnapshot<TEditor>) => TSelectorResult
|
|
21
|
+
selector: (context: EditorStateSnapshot<TEditor>) => TSelectorResult
|
|
27
22
|
/**
|
|
28
23
|
* A custom equality function to determine if the editor should re-render.
|
|
29
24
|
* @default `deepEqual` from `fast-deep-equal`
|
|
30
25
|
*/
|
|
31
|
-
equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean
|
|
32
|
-
}
|
|
26
|
+
equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean
|
|
27
|
+
}
|
|
33
28
|
|
|
34
29
|
/**
|
|
35
30
|
* To synchronize the editor instance with the component state,
|
|
@@ -126,8 +121,8 @@ class EditorStateManager<TEditor extends Editor | null = Editor | null> {
|
|
|
126
121
|
* })
|
|
127
122
|
*/
|
|
128
123
|
export function useEditorState<TSelectorResult>(
|
|
129
|
-
options: UseEditorStateOptions<TSelectorResult, Editor
|
|
130
|
-
): TSelectorResult
|
|
124
|
+
options: UseEditorStateOptions<TSelectorResult, Editor>,
|
|
125
|
+
): TSelectorResult
|
|
131
126
|
/**
|
|
132
127
|
* This hook allows you to watch for changes on the editor instance.
|
|
133
128
|
* It will allow you to select a part of the editor state and re-render the component when it changes.
|
|
@@ -140,8 +135,8 @@ export function useEditorState<TSelectorResult>(
|
|
|
140
135
|
* })
|
|
141
136
|
*/
|
|
142
137
|
export function useEditorState<TSelectorResult>(
|
|
143
|
-
options: UseEditorStateOptions<TSelectorResult, Editor | null
|
|
144
|
-
): TSelectorResult | null
|
|
138
|
+
options: UseEditorStateOptions<TSelectorResult, Editor | null>,
|
|
139
|
+
): TSelectorResult | null
|
|
145
140
|
|
|
146
141
|
/**
|
|
147
142
|
* This hook allows you to watch for changes on the editor instance.
|
package/src/useReactNodeView.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
1
|
+
import { createContext, createElement, ReactNode, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
export interface ReactNodeViewContextProps {
|
|
4
|
-
onDragStart
|
|
5
|
-
nodeViewContentRef
|
|
4
|
+
onDragStart?: (event: DragEvent) => void
|
|
5
|
+
nodeViewContentRef?: (element: HTMLElement | null) => void
|
|
6
|
+
/**
|
|
7
|
+
* This allows you to add children into the NodeViewContent component.
|
|
8
|
+
* This is useful when statically rendering the content of a node view.
|
|
9
|
+
*/
|
|
10
|
+
nodeViewContentChildren?: ReactNode
|
|
6
11
|
}
|
|
7
12
|
|
|
8
|
-
export const ReactNodeViewContext = createContext<
|
|
9
|
-
onDragStart:
|
|
13
|
+
export const ReactNodeViewContext = createContext<ReactNodeViewContextProps>({
|
|
14
|
+
onDragStart: () => {
|
|
15
|
+
// no-op
|
|
16
|
+
},
|
|
17
|
+
nodeViewContentChildren: undefined,
|
|
18
|
+
nodeViewContentRef: () => {
|
|
19
|
+
// no-op
|
|
20
|
+
},
|
|
10
21
|
})
|
|
11
22
|
|
|
23
|
+
export const ReactNodeViewContentProvider = ({ children, content }: { children: ReactNode; content: ReactNode }) => {
|
|
24
|
+
return createElement(ReactNodeViewContext.Provider, { value: { nodeViewContentChildren: content } }, children)
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
export const useReactNodeView = () => useContext(ReactNodeViewContext)
|
package/src/FloatingMenu.tsx
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
|
|
2
|
-
import React, { useEffect, useRef } from 'react'
|
|
3
|
-
import { createPortal } from 'react-dom'
|
|
4
|
-
|
|
5
|
-
import { useCurrentEditor } from './Context.js'
|
|
6
|
-
|
|
7
|
-
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
8
|
-
|
|
9
|
-
export type FloatingMenuProps = Omit<
|
|
10
|
-
Optional<FloatingMenuPluginProps, 'pluginKey'>,
|
|
11
|
-
'element' | 'editor'
|
|
12
|
-
> & {
|
|
13
|
-
editor: FloatingMenuPluginProps['editor'] | null;
|
|
14
|
-
options?: FloatingMenuPluginProps['options'];
|
|
15
|
-
} & React.HTMLAttributes<HTMLDivElement>;
|
|
16
|
-
|
|
17
|
-
export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(({
|
|
18
|
-
pluginKey = 'floatingMenu',
|
|
19
|
-
editor,
|
|
20
|
-
shouldShow = null,
|
|
21
|
-
options,
|
|
22
|
-
children,
|
|
23
|
-
...restProps
|
|
24
|
-
}, ref) => {
|
|
25
|
-
const menuEl = useRef(document.createElement('div'))
|
|
26
|
-
|
|
27
|
-
if (typeof ref === 'function') {
|
|
28
|
-
ref(menuEl.current)
|
|
29
|
-
} else if (ref) {
|
|
30
|
-
ref.current = menuEl.current
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { editor: currentEditor } = useCurrentEditor()
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
const floatingMenuElement = menuEl.current
|
|
37
|
-
|
|
38
|
-
floatingMenuElement.style.visibility = 'hidden'
|
|
39
|
-
floatingMenuElement.style.position = 'absolute'
|
|
40
|
-
|
|
41
|
-
if (editor?.isDestroyed || currentEditor?.isDestroyed) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const attachToEditor = editor || currentEditor
|
|
46
|
-
|
|
47
|
-
if (!attachToEditor) {
|
|
48
|
-
console.warn(
|
|
49
|
-
'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',
|
|
50
|
-
)
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const plugin = FloatingMenuPlugin({
|
|
55
|
-
editor: attachToEditor,
|
|
56
|
-
element: floatingMenuElement,
|
|
57
|
-
pluginKey,
|
|
58
|
-
shouldShow,
|
|
59
|
-
options,
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
attachToEditor.registerPlugin(plugin)
|
|
63
|
-
|
|
64
|
-
return () => {
|
|
65
|
-
attachToEditor.unregisterPlugin(pluginKey)
|
|
66
|
-
window.requestAnimationFrame(() => {
|
|
67
|
-
if (floatingMenuElement.parentNode) {
|
|
68
|
-
floatingMenuElement.parentNode.removeChild(floatingMenuElement)
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
|
-
}, [editor, currentEditor])
|
|
74
|
-
|
|
75
|
-
return createPortal(
|
|
76
|
-
<div
|
|
77
|
-
{...restProps}
|
|
78
|
-
>
|
|
79
|
-
{children}
|
|
80
|
-
</div>,
|
|
81
|
-
menuEl.current,
|
|
82
|
-
)
|
|
83
|
-
})
|