@tiptap/react 3.18.0 → 3.19.0
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/dist/index.cjs +15 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -136
- package/dist/index.d.ts +45 -136
- package/dist/index.js +14 -259
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Tiptap.tsx +58 -189
package/src/Tiptap.tsx
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import { createContext, useContext,
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react'
|
|
3
3
|
|
|
4
4
|
import { EditorContext } from './Context.js'
|
|
5
5
|
import type { Editor, EditorContentProps, EditorStateSnapshot } from './index.js'
|
|
6
6
|
import { EditorContent, useEditorState } from './index.js'
|
|
7
|
-
import { type BubbleMenuProps, BubbleMenu } from './menus/BubbleMenu.js'
|
|
8
|
-
import { type FloatingMenuProps, FloatingMenu } from './menus/FloatingMenu.js'
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* The shape of the React context used by the `<Tiptap />` components.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
11
|
+
* The editor instance is always available when using the default `useEditor`
|
|
12
|
+
* configuration. For SSR scenarios where `immediatelyRender: false` is used,
|
|
13
|
+
* consider using the legacy `EditorProvider` pattern instead.
|
|
14
14
|
*/
|
|
15
15
|
export type TiptapContextType = {
|
|
16
|
-
/** The Tiptap editor instance.
|
|
17
|
-
editor: Editor
|
|
18
|
-
|
|
19
|
-
/** True when the editor has finished initializing and is ready for user interaction. */
|
|
20
|
-
isReady: boolean
|
|
16
|
+
/** The Tiptap editor instance. */
|
|
17
|
+
editor: Editor
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
|
-
* React context that stores the current editor instance
|
|
21
|
+
* React context that stores the current editor instance.
|
|
25
22
|
*
|
|
26
23
|
* Use `useTiptap()` to read from this context in child components.
|
|
27
24
|
*/
|
|
28
25
|
export const TiptapContext = createContext<TiptapContextType>({
|
|
29
|
-
editor:
|
|
30
|
-
|
|
26
|
+
get editor(): Editor {
|
|
27
|
+
throw new Error('useTiptap must be used within a <Tiptap> provider')
|
|
28
|
+
},
|
|
31
29
|
})
|
|
32
30
|
|
|
33
31
|
TiptapContext.displayName = 'TiptapContext'
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
|
-
* Hook to read the Tiptap context
|
|
34
|
+
* Hook to read the Tiptap context and access the editor instance.
|
|
37
35
|
*
|
|
38
36
|
* This is a small convenience wrapper around `useContext(TiptapContext)`.
|
|
37
|
+
* The editor is always available when used within a `<Tiptap>` provider.
|
|
39
38
|
*
|
|
40
39
|
* @returns The current `TiptapContextType` value from the provider.
|
|
41
40
|
*
|
|
@@ -43,9 +42,14 @@ TiptapContext.displayName = 'TiptapContext'
|
|
|
43
42
|
* ```tsx
|
|
44
43
|
* import { useTiptap } from '@tiptap/react'
|
|
45
44
|
*
|
|
46
|
-
* function
|
|
47
|
-
* const {
|
|
48
|
-
*
|
|
45
|
+
* function Toolbar() {
|
|
46
|
+
* const { editor } = useTiptap()
|
|
47
|
+
*
|
|
48
|
+
* return (
|
|
49
|
+
* <button onClick={() => editor.chain().focus().toggleBold().run()}>
|
|
50
|
+
* Bold
|
|
51
|
+
* </button>
|
|
52
|
+
* )
|
|
49
53
|
* }
|
|
50
54
|
* ```
|
|
51
55
|
*/
|
|
@@ -57,10 +61,6 @@ export const useTiptap = () => useContext(TiptapContext)
|
|
|
57
61
|
* This is a thin wrapper around `useEditorState` that reads the `editor`
|
|
58
62
|
* instance from `useTiptap()` so callers don't have to pass it manually.
|
|
59
63
|
*
|
|
60
|
-
* Important: This hook should only be used when the editor is available.
|
|
61
|
-
* Use the `isReady` flag from `useTiptap()` to guard against null editor,
|
|
62
|
-
* or ensure your component only renders after the editor is initialized.
|
|
63
|
-
*
|
|
64
64
|
* @typeParam TSelectorResult - The type returned by the selector.
|
|
65
65
|
* @param selector - Function that receives the editor state snapshot and
|
|
66
66
|
* returns the piece of state you want to subscribe to.
|
|
@@ -71,16 +71,11 @@ export const useTiptap = () => useContext(TiptapContext)
|
|
|
71
71
|
* @example
|
|
72
72
|
* ```tsx
|
|
73
73
|
* function WordCount() {
|
|
74
|
-
* const { isReady } = useTiptap()
|
|
75
|
-
*
|
|
76
|
-
* // Only use useTiptapState when the editor is ready
|
|
77
74
|
* const wordCount = useTiptapState(state => {
|
|
78
75
|
* const text = state.editor.state.doc.textContent
|
|
79
76
|
* return text.split(/\s+/).filter(Boolean).length
|
|
80
77
|
* })
|
|
81
78
|
*
|
|
82
|
-
* if (!isReady) return null
|
|
83
|
-
*
|
|
84
79
|
* return <span>{wordCount} words</span>
|
|
85
80
|
* }
|
|
86
81
|
* ```
|
|
@@ -90,8 +85,9 @@ export function useTiptapState<TSelectorResult>(
|
|
|
90
85
|
equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean,
|
|
91
86
|
) {
|
|
92
87
|
const { editor } = useTiptap()
|
|
88
|
+
|
|
93
89
|
return useEditorState({
|
|
94
|
-
editor
|
|
90
|
+
editor,
|
|
95
91
|
selector,
|
|
96
92
|
equalityFn,
|
|
97
93
|
})
|
|
@@ -103,18 +99,21 @@ export function useTiptapState<TSelectorResult>(
|
|
|
103
99
|
export type TiptapWrapperProps = {
|
|
104
100
|
/**
|
|
105
101
|
* The editor instance to provide to child components.
|
|
106
|
-
*
|
|
102
|
+
* Use `useEditor()` to create this instance.
|
|
103
|
+
*/
|
|
104
|
+
editor?: Editor
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @deprecated Use `editor` instead. Will be removed in the next major version.
|
|
107
108
|
*/
|
|
108
|
-
instance
|
|
109
|
+
instance?: Editor
|
|
110
|
+
|
|
109
111
|
children: ReactNode
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
/**
|
|
113
115
|
* Top-level provider component that makes the editor instance available via
|
|
114
|
-
* React context
|
|
115
|
-
*
|
|
116
|
-
* The component listens to the editor's `create` event and flips the
|
|
117
|
-
* `isReady` flag once initialization completes.
|
|
116
|
+
* React context to all child components.
|
|
118
117
|
*
|
|
119
118
|
* This component also provides backwards compatibility with the legacy
|
|
120
119
|
* `EditorContext`, so components using `useCurrentEditor()` will work
|
|
@@ -131,7 +130,7 @@ export type TiptapWrapperProps = {
|
|
|
131
130
|
* const editor = useEditor({ extensions: [...] })
|
|
132
131
|
*
|
|
133
132
|
* return (
|
|
134
|
-
* <Tiptap
|
|
133
|
+
* <Tiptap editor={editor}>
|
|
135
134
|
* <Toolbar />
|
|
136
135
|
* <Tiptap.Content />
|
|
137
136
|
* </Tiptap>
|
|
@@ -139,38 +138,18 @@ export type TiptapWrapperProps = {
|
|
|
139
138
|
* }
|
|
140
139
|
* ```
|
|
141
140
|
*/
|
|
142
|
-
export function TiptapWrapper({ instance, children }: TiptapWrapperProps) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
if (!instance) {
|
|
147
|
-
setIsReady(false)
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// If the editor is already initialized, set isReady to true
|
|
152
|
-
if (instance.isInitialized) {
|
|
153
|
-
setIsReady(true)
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const handleCreate = () => {
|
|
158
|
-
setIsReady(true)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
instance.on('create', handleCreate)
|
|
141
|
+
export function TiptapWrapper({ editor, instance, children }: TiptapWrapperProps) {
|
|
142
|
+
const resolvedEditor = editor ?? instance
|
|
162
143
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}, [instance])
|
|
144
|
+
if (!resolvedEditor) {
|
|
145
|
+
throw new Error('Tiptap: An editor instance is required. Pass a non-null `editor` prop.')
|
|
146
|
+
}
|
|
167
147
|
|
|
168
|
-
|
|
169
|
-
const tiptapContextValue = useMemo<TiptapContextType>(() => ({ editor: instance, isReady }), [instance, isReady])
|
|
148
|
+
const tiptapContextValue = useMemo<TiptapContextType>(() => ({ editor: resolvedEditor }), [resolvedEditor])
|
|
170
149
|
|
|
171
150
|
// Provide backwards compatibility with the legacy EditorContext
|
|
172
151
|
// so components using useCurrentEditor() work inside <Tiptap>
|
|
173
|
-
const legacyContextValue = useMemo(() => ({ editor:
|
|
152
|
+
const legacyContextValue = useMemo(() => ({ editor: resolvedEditor }), [resolvedEditor])
|
|
174
153
|
|
|
175
154
|
return (
|
|
176
155
|
<EditorContext.Provider value={legacyContextValue}>
|
|
@@ -202,128 +181,36 @@ export function TiptapContent({ ...rest }: Omit<EditorContentProps, 'editor' | '
|
|
|
202
181
|
|
|
203
182
|
TiptapContent.displayName = 'Tiptap.Content'
|
|
204
183
|
|
|
205
|
-
export type TiptapLoadingProps = {
|
|
206
|
-
children: ReactNode
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Component that renders its children only when the editor is not ready.
|
|
211
|
-
*
|
|
212
|
-
* This is useful for displaying loading states or placeholders during
|
|
213
|
-
* editor initialization, especially with SSR.
|
|
214
|
-
*
|
|
215
|
-
* @param props - The props for the TiptapLoading component.
|
|
216
|
-
* @returns The children when editor is not ready, or null when ready.
|
|
217
|
-
*
|
|
218
|
-
* @example
|
|
219
|
-
* ```tsx
|
|
220
|
-
* <Tiptap instance={editor}>
|
|
221
|
-
* <Tiptap.Loading>
|
|
222
|
-
* <div className="skeleton">Loading editor...</div>
|
|
223
|
-
* </Tiptap.Loading>
|
|
224
|
-
* <Tiptap.Content />
|
|
225
|
-
* </Tiptap>
|
|
226
|
-
* ```
|
|
227
|
-
*/
|
|
228
|
-
export function TiptapLoading({ children }: TiptapLoadingProps) {
|
|
229
|
-
const { isReady } = useTiptap()
|
|
230
|
-
|
|
231
|
-
if (isReady) {
|
|
232
|
-
return null
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return children
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
TiptapLoading.displayName = 'Tiptap.Loading'
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* A wrapper around the library `BubbleMenu` that injects the editor from
|
|
242
|
-
* context so callers don't need to pass the `editor` prop.
|
|
243
|
-
*
|
|
244
|
-
* Returns `null` when the editor is not available (for example during SSR).
|
|
245
|
-
*
|
|
246
|
-
* @param props - Props for the underlying `BubbleMenu` (except `editor`).
|
|
247
|
-
* @returns A `BubbleMenu` bound to the context editor, or `null`.
|
|
248
|
-
*
|
|
249
|
-
* @example
|
|
250
|
-
* ```tsx
|
|
251
|
-
* <Tiptap.BubbleMenu tippyOptions={{ duration: 100 }}>
|
|
252
|
-
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
|
|
253
|
-
* </Tiptap.BubbleMenu>
|
|
254
|
-
* ```
|
|
255
|
-
*/
|
|
256
|
-
export function TiptapBubbleMenu({ children, ...rest }: { children: ReactNode } & Omit<BubbleMenuProps, 'editor'>) {
|
|
257
|
-
const { editor } = useTiptap()
|
|
258
|
-
|
|
259
|
-
if (!editor) {
|
|
260
|
-
return null
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<BubbleMenu editor={editor} {...rest}>
|
|
265
|
-
{children}
|
|
266
|
-
</BubbleMenu>
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
TiptapBubbleMenu.displayName = 'Tiptap.BubbleMenu'
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* A wrapper around the library `FloatingMenu` that injects the editor from
|
|
274
|
-
* context so callers don't need to pass the `editor` prop.
|
|
275
|
-
*
|
|
276
|
-
* Returns `null` when the editor is not available.
|
|
277
|
-
*
|
|
278
|
-
* @param props - Props for the underlying `FloatingMenu` (except `editor`).
|
|
279
|
-
* @returns A `FloatingMenu` bound to the context editor, or `null`.
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* ```tsx
|
|
283
|
-
* <Tiptap.FloatingMenu placement="top">
|
|
284
|
-
* <button onClick={() => editor.chain().focus().toggleItalic().run()}>Italic</button>
|
|
285
|
-
* </Tiptap.FloatingMenu>
|
|
286
|
-
* ```
|
|
287
|
-
*/
|
|
288
|
-
export function TiptapFloatingMenu({ children, ...rest }: { children: ReactNode } & Omit<FloatingMenuProps, 'editor'>) {
|
|
289
|
-
const { editor } = useTiptap()
|
|
290
|
-
|
|
291
|
-
if (!editor) {
|
|
292
|
-
return null
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<FloatingMenu {...rest} editor={editor}>
|
|
297
|
-
{children}
|
|
298
|
-
</FloatingMenu>
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
TiptapFloatingMenu.displayName = 'Tiptap.FloatingMenu'
|
|
303
|
-
|
|
304
184
|
/**
|
|
305
185
|
* Root `Tiptap` component. Use it as the provider for all child components.
|
|
306
186
|
*
|
|
307
|
-
* The exported object includes
|
|
308
|
-
*
|
|
187
|
+
* The exported object includes the `Content` subcomponent for rendering the
|
|
188
|
+
* editor content area.
|
|
309
189
|
*
|
|
310
190
|
* This component provides both the new `TiptapContext` (accessed via `useTiptap()`)
|
|
311
191
|
* and the legacy `EditorContext` (accessed via `useCurrentEditor()`) for
|
|
312
192
|
* backwards compatibility.
|
|
313
193
|
*
|
|
194
|
+
* For bubble menus and floating menus, import them separately from
|
|
195
|
+
* `@tiptap/react/menus` to keep floating-ui as an optional dependency.
|
|
196
|
+
*
|
|
314
197
|
* @example
|
|
315
198
|
* ```tsx
|
|
316
|
-
*
|
|
199
|
+
* import { Tiptap, useEditor } from '@tiptap/react'
|
|
200
|
+
* import { BubbleMenu } from '@tiptap/react/menus'
|
|
317
201
|
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* <Tiptap
|
|
323
|
-
* <
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
202
|
+
* function App() {
|
|
203
|
+
* const editor = useEditor({ extensions: [...] })
|
|
204
|
+
*
|
|
205
|
+
* return (
|
|
206
|
+
* <Tiptap editor={editor}>
|
|
207
|
+
* <Tiptap.Content />
|
|
208
|
+
* <BubbleMenu>
|
|
209
|
+
* <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button>
|
|
210
|
+
* </BubbleMenu>
|
|
211
|
+
* </Tiptap>
|
|
212
|
+
* )
|
|
213
|
+
* }
|
|
327
214
|
* ```
|
|
328
215
|
*/
|
|
329
216
|
export const Tiptap = Object.assign(TiptapWrapper, {
|
|
@@ -332,24 +219,6 @@ export const Tiptap = Object.assign(TiptapWrapper, {
|
|
|
332
219
|
* @see TiptapContent
|
|
333
220
|
*/
|
|
334
221
|
Content: TiptapContent,
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* The Tiptap Loading component that renders its children only when the editor is not ready.
|
|
338
|
-
* @see TiptapLoading
|
|
339
|
-
*/
|
|
340
|
-
Loading: TiptapLoading,
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
|
|
344
|
-
* @see TiptapBubbleMenu
|
|
345
|
-
*/
|
|
346
|
-
BubbleMenu: TiptapBubbleMenu,
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
|
|
350
|
-
* @see TiptapFloatingMenu
|
|
351
|
-
*/
|
|
352
|
-
FloatingMenu: TiptapFloatingMenu,
|
|
353
222
|
})
|
|
354
223
|
|
|
355
224
|
export default Tiptap
|