@tiptap/suggestion 3.26.1 → 3.27.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 +619 -270
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +193 -2
- package/dist/index.d.ts +193 -2
- package/dist/index.js +624 -270
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
- package/src/__tests__/suggestion.test.ts +837 -0
- package/src/helpers.ts +129 -0
- package/src/plugin/async.ts +89 -0
- package/src/plugin/floating-ui.ts +204 -0
- package/src/plugin/props.ts +94 -0
- package/src/plugin/state.ts +182 -0
- package/src/plugin/view.ts +236 -0
- package/src/suggestion.ts +97 -606
- package/src/types.ts +439 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import type { AutoUpdateOptions, Middleware } from '@floating-ui/dom'
|
|
2
|
+
import type { Editor, Range } from '@tiptap/core'
|
|
3
|
+
import type { EditorState, PluginKey, Transaction } from '@tiptap/pm/state'
|
|
4
|
+
import type { EditorView } from '@tiptap/pm/view'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
findSuggestionMatch as defaultFindSuggestionMatch,
|
|
8
|
+
SuggestionMatch,
|
|
9
|
+
} from './findSuggestionMatch.js'
|
|
10
|
+
|
|
11
|
+
export type SuggestionPlacement =
|
|
12
|
+
| 'top'
|
|
13
|
+
| 'top-start'
|
|
14
|
+
| 'top-end'
|
|
15
|
+
| 'bottom'
|
|
16
|
+
| 'bottom-start'
|
|
17
|
+
| 'bottom-end'
|
|
18
|
+
|
|
19
|
+
export type SuggestionFloatingUiOptions = {
|
|
20
|
+
strategy?: 'absolute' | 'fixed'
|
|
21
|
+
middleware?: Middleware[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SuggestionFloatingUiConfig = {
|
|
25
|
+
placement: SuggestionPlacement
|
|
26
|
+
strategy: 'absolute' | 'fixed'
|
|
27
|
+
middleware: Middleware[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The computed position handed to a custom `onPosition` callback when using
|
|
32
|
+
* managed positioning via {@link SuggestionProps.mount}.
|
|
33
|
+
*/
|
|
34
|
+
export type SuggestionPositionData = {
|
|
35
|
+
x: number
|
|
36
|
+
y: number
|
|
37
|
+
placement: SuggestionPlacement
|
|
38
|
+
strategy: 'absolute' | 'fixed'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Options for managed mounting + positioning via {@link SuggestionProps.mount}.
|
|
43
|
+
*/
|
|
44
|
+
export type SuggestionMountOptions = {
|
|
45
|
+
/**
|
|
46
|
+
* Override how the computed position is applied to the element.
|
|
47
|
+
* When provided, the plugin stops writing `style.left`/`style.top` itself and
|
|
48
|
+
* hands you the computed coordinates so you can apply them however you want
|
|
49
|
+
* (custom transforms, animation, writing to a framework ref, etc.).
|
|
50
|
+
*/
|
|
51
|
+
onPosition?: (data: SuggestionPositionData) => void
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Options forwarded to Floating UI's `autoUpdate`. Use this to opt into
|
|
55
|
+
* `animationFrame` polling for anchors that move inside transformed or
|
|
56
|
+
* animated containers, or to disable specific observers.
|
|
57
|
+
* @see https://floating-ui.com/docs/autoUpdate
|
|
58
|
+
*/
|
|
59
|
+
autoUpdate?: AutoUpdateOptions
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Mounts a floating element and takes over its positioning. The plugin appends
|
|
64
|
+
* the element into the configured `container` (default `document.body`), keeps
|
|
65
|
+
* it anchored to the suggestion's cursor rect, and automatically repositions it
|
|
66
|
+
* on scroll, resize, and layout shifts via Floating UI's `autoUpdate` — no
|
|
67
|
+
* manual listeners required.
|
|
68
|
+
*
|
|
69
|
+
* Returns an `unmount` function that tears down the listeners and removes the
|
|
70
|
+
* element (when the plugin mounted it). Call it from `onExit`.
|
|
71
|
+
*/
|
|
72
|
+
export type SuggestionMount = (element: HTMLElement, options?: SuggestionMountOptions) => () => void
|
|
73
|
+
|
|
74
|
+
export type PluginState = {
|
|
75
|
+
active: boolean
|
|
76
|
+
range: Range
|
|
77
|
+
query: string | null
|
|
78
|
+
text: string | null
|
|
79
|
+
decorationId?: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SuggestionOptions<I = any, TSelected = any> {
|
|
83
|
+
/**
|
|
84
|
+
* The plugin key for the suggestion plugin.
|
|
85
|
+
* @default 'suggestion'
|
|
86
|
+
* @example 'mention'
|
|
87
|
+
*/
|
|
88
|
+
pluginKey?: PluginKey
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
92
|
+
* This is useful to prevent suggestions from opening for remote users in collaborative environments.
|
|
93
|
+
* @param props The props object.
|
|
94
|
+
* @param props.editor The editor instance.
|
|
95
|
+
* @param props.range The range of the suggestion.
|
|
96
|
+
* @param props.query The current suggestion query.
|
|
97
|
+
* @param props.text The current suggestion text.
|
|
98
|
+
* @param props.transaction The current transaction.
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
* @example ({ transaction }) => isChangeOrigin(transaction)
|
|
101
|
+
*/
|
|
102
|
+
shouldShow?: (props: {
|
|
103
|
+
editor: Editor
|
|
104
|
+
range: Range
|
|
105
|
+
query: string
|
|
106
|
+
text: string
|
|
107
|
+
transaction: Transaction
|
|
108
|
+
}) => boolean
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Controls when a dismissed suggestion becomes active again.
|
|
112
|
+
* Return `true` to clear the dismissed context for the current transaction.
|
|
113
|
+
*/
|
|
114
|
+
shouldResetDismissed?: (props: {
|
|
115
|
+
editor: Editor
|
|
116
|
+
state: EditorState
|
|
117
|
+
range: Range
|
|
118
|
+
match: Exclude<SuggestionMatch, null>
|
|
119
|
+
transaction: Transaction
|
|
120
|
+
allowSpaces: boolean
|
|
121
|
+
}) => boolean
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The editor instance.
|
|
125
|
+
* @default null
|
|
126
|
+
*/
|
|
127
|
+
editor: Editor
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The character that triggers the suggestion.
|
|
131
|
+
* @default '@'
|
|
132
|
+
* @example '#'
|
|
133
|
+
*/
|
|
134
|
+
char?: string
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Allow spaces in the suggestion query. Not compatible with `allowToIncludeChar`. Will be disabled if `allowToIncludeChar` is set to `true`.
|
|
138
|
+
* @default false
|
|
139
|
+
* @example true
|
|
140
|
+
*/
|
|
141
|
+
allowSpaces?: boolean
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Allow the character to be included in the suggestion query. Not compatible with `allowSpaces`.
|
|
145
|
+
* @default false
|
|
146
|
+
*/
|
|
147
|
+
allowToIncludeChar?: boolean
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Allow prefixes in the suggestion query.
|
|
151
|
+
* @default [' ']
|
|
152
|
+
* @example [' ', '@']
|
|
153
|
+
*/
|
|
154
|
+
allowedPrefixes?: string[] | null
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Only match suggestions at the start of the line.
|
|
158
|
+
* @default false
|
|
159
|
+
* @example true
|
|
160
|
+
*/
|
|
161
|
+
startOfLine?: boolean
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The tag name of the decoration node.
|
|
165
|
+
* @default 'span'
|
|
166
|
+
* @example 'div'
|
|
167
|
+
*/
|
|
168
|
+
decorationTag?: string
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The class name of the decoration node.
|
|
172
|
+
* @default 'suggestion'
|
|
173
|
+
* @example 'mention'
|
|
174
|
+
*/
|
|
175
|
+
decorationClass?: string
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates a decoration with the provided content.
|
|
179
|
+
* @param decorationContent - The content to display in the decoration
|
|
180
|
+
* @default "" - Creates an empty decoration if no content provided
|
|
181
|
+
*/
|
|
182
|
+
decorationContent?: string
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The class name of the decoration node when it is empty.
|
|
186
|
+
* @default 'is-empty'
|
|
187
|
+
* @example 'is-empty'
|
|
188
|
+
*/
|
|
189
|
+
decorationEmptyClass?: string
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A function that is called when a suggestion is selected.
|
|
193
|
+
* @param props The props object.
|
|
194
|
+
* @param props.editor The editor instance.
|
|
195
|
+
* @param props.range The range of the suggestion.
|
|
196
|
+
* @param props.props The props of the selected suggestion.
|
|
197
|
+
* @returns void
|
|
198
|
+
* @example ({ editor, range, props }) => { props.command(props.props) }
|
|
199
|
+
*/
|
|
200
|
+
command?: (props: { editor: Editor; range: Range; props: TSelected }) => void
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Minimum query length before `items()` is called.
|
|
204
|
+
* When the query is shorter, empty items are passed to the renderer.
|
|
205
|
+
* @default 0 (no filter, same as before)
|
|
206
|
+
* @example 2
|
|
207
|
+
*/
|
|
208
|
+
minQueryLength?: number
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Debounce in milliseconds. When set, `items()` will only be called
|
|
212
|
+
* after the user stops typing for this duration.
|
|
213
|
+
* @default 0 (no debounce, same as before)
|
|
214
|
+
* @example 300
|
|
215
|
+
*/
|
|
216
|
+
debounce?: number
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Items shown immediately when the suggestion popup opens,
|
|
220
|
+
* before the async `items()` call resolves.
|
|
221
|
+
* Useful for showing recent or popular items while loading.
|
|
222
|
+
* @default undefined (no pre-populated items)
|
|
223
|
+
*/
|
|
224
|
+
initialItems?: I[]
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Placement of the popup relative to the cursor.
|
|
228
|
+
* Consumers can read this from `SuggestionProps` to configure their positioning library.
|
|
229
|
+
* @default 'bottom-start'
|
|
230
|
+
*/
|
|
231
|
+
placement?: SuggestionPlacement
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Offset of the popup in pixels.
|
|
235
|
+
* Consumers can read this from `SuggestionProps` to configure their positioning library.
|
|
236
|
+
* @default { mainAxis: 4, crossAxis: 0 }
|
|
237
|
+
*/
|
|
238
|
+
offset?: { mainAxis?: number; crossAxis?: number }
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* CSS selector or element that defines the containment context for the popup.
|
|
242
|
+
* Consumers can read this from `SuggestionProps` when rendering inside modals or dialogs.
|
|
243
|
+
* @default undefined (no containment)
|
|
244
|
+
*/
|
|
245
|
+
container?: string | HTMLElement
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Whether the popup should automatically flip when there isn't enough space.
|
|
249
|
+
* Consumers can read this from `SuggestionProps` to configure their positioning library.
|
|
250
|
+
* @default true
|
|
251
|
+
*/
|
|
252
|
+
flip?: boolean
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Additional Floating UI options and middleware passed through to the renderer.
|
|
256
|
+
* The plugin keeps ownership of the anchor and placement, but consumers can
|
|
257
|
+
* append custom middleware here.
|
|
258
|
+
*/
|
|
259
|
+
floatingUi?: SuggestionFloatingUiOptions
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Dismiss the suggestion when the user interacts outside both the popup and
|
|
263
|
+
* the editor. Only applies when using managed mounting via
|
|
264
|
+
* {@link SuggestionProps.mount} (the plugin needs to know the popup element).
|
|
265
|
+
* @default true
|
|
266
|
+
*/
|
|
267
|
+
dismissOnOutsideClick?: boolean
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* A function that returns the suggestion items in form of an array.
|
|
271
|
+
* @param props The props object.
|
|
272
|
+
* @param props.editor The editor instance.
|
|
273
|
+
* @param props.query The current suggestion query.
|
|
274
|
+
* @returns An array of suggestion items.
|
|
275
|
+
* @example ({ editor, query }) => [{ id: 1, label: 'John Doe' }]
|
|
276
|
+
*/
|
|
277
|
+
items?: (props: { query: string; editor: Editor; signal: AbortSignal }) => I[] | Promise<I[]>
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The render function for the suggestion.
|
|
281
|
+
* @returns An object with render functions.
|
|
282
|
+
*/
|
|
283
|
+
render?: () => {
|
|
284
|
+
onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void
|
|
285
|
+
onStart?: (props: SuggestionProps<I, TSelected>) => void
|
|
286
|
+
onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void
|
|
287
|
+
onUpdate?: (props: SuggestionProps<I, TSelected>) => void
|
|
288
|
+
onExit?: (props: SuggestionProps<I, TSelected>) => void
|
|
289
|
+
onKeyDown?: (props: SuggestionKeyDownProps) => boolean
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
294
|
+
* @param props The props object.
|
|
295
|
+
* @returns {boolean}
|
|
296
|
+
*/
|
|
297
|
+
allow?: (props: {
|
|
298
|
+
editor: Editor
|
|
299
|
+
state: EditorState
|
|
300
|
+
range: Range
|
|
301
|
+
isActive?: boolean
|
|
302
|
+
}) => boolean
|
|
303
|
+
findSuggestionMatch?: typeof defaultFindSuggestionMatch
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The props passed to the suggestion's render functions (onStart, onUpdate, onExit).
|
|
308
|
+
*/
|
|
309
|
+
export interface SuggestionProps<I = any, TSelected = any> {
|
|
310
|
+
/**
|
|
311
|
+
* The editor instance.
|
|
312
|
+
*/
|
|
313
|
+
editor: Editor
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The range of the suggestion.
|
|
317
|
+
*/
|
|
318
|
+
range: Range
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The current suggestion query.
|
|
322
|
+
*/
|
|
323
|
+
query: string
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The current suggestion text.
|
|
327
|
+
*/
|
|
328
|
+
text: string
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The suggestion items array.
|
|
332
|
+
*/
|
|
333
|
+
items: I[]
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* A function that is called when a suggestion is selected.
|
|
337
|
+
* @param props The props object.
|
|
338
|
+
* @returns void
|
|
339
|
+
*/
|
|
340
|
+
command: (props: TSelected) => void
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The decoration node HTML element
|
|
344
|
+
* @default null
|
|
345
|
+
*/
|
|
346
|
+
decorationNode: Element | null
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* The function that returns the client rect
|
|
350
|
+
* @default null
|
|
351
|
+
* @example () => new DOMRect(0, 0, 0, 0)
|
|
352
|
+
*/
|
|
353
|
+
clientRect?: (() => DOMRect | null) | null
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Placement of the popup relative to the cursor.
|
|
357
|
+
* @default 'bottom-start'
|
|
358
|
+
*/
|
|
359
|
+
placement: SuggestionPlacement
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Offset of the popup in pixels.
|
|
363
|
+
* @default { mainAxis: 4, crossAxis: 0 }
|
|
364
|
+
*/
|
|
365
|
+
offset: { mainAxis: number; crossAxis: number }
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* CSS selector or element that defines the containment context for the popup.
|
|
369
|
+
* @default undefined
|
|
370
|
+
*/
|
|
371
|
+
container?: string | HTMLElement
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Whether the popup should automatically flip when there isn't enough space.
|
|
375
|
+
* @default true
|
|
376
|
+
*/
|
|
377
|
+
flip: boolean
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Resolved Floating UI config for direct use with `computePosition()`.
|
|
381
|
+
* This is the escape hatch: reach for it only when you mount the element
|
|
382
|
+
* yourself and want to run the positioning loop manually instead of using
|
|
383
|
+
* {@link SuggestionProps.mount}.
|
|
384
|
+
*/
|
|
385
|
+
floatingUi: SuggestionFloatingUiConfig
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Mounts your floating element and takes over positioning — the recommended,
|
|
389
|
+
* default way to render a suggestion popup.
|
|
390
|
+
*
|
|
391
|
+
* Pass the element you rendered (e.g. from `ReactRenderer`/`VueRenderer`). The
|
|
392
|
+
* plugin appends it into the configured `container` (default `document.body`),
|
|
393
|
+
* keeps it anchored to the cursor, and repositions it on scroll, resize, and
|
|
394
|
+
* layout shifts — no manual listeners required. Returns an `unmount` function
|
|
395
|
+
* that tears everything down; call it in `onExit`.
|
|
396
|
+
*
|
|
397
|
+
* Escape hatch: mount the element yourself and skip this, then run your own
|
|
398
|
+
* positioning loop with {@link SuggestionProps.floatingUi} +
|
|
399
|
+
* {@link SuggestionProps.clientRect}.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* onStart: props => {
|
|
404
|
+
* component = new ReactRenderer(DropdownList, { props, editor: props.editor })
|
|
405
|
+
* unmount = props.mount(component.element)
|
|
406
|
+
* },
|
|
407
|
+
* onExit: () => unmount?.(),
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
mount: SuggestionMount
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Whether the items are currently being loaded.
|
|
414
|
+
* `true` before the async `items()` call resolves.
|
|
415
|
+
* Useful for showing a loading spinner or skeleton.
|
|
416
|
+
* @default false
|
|
417
|
+
*/
|
|
418
|
+
loading: boolean
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The props passed to the suggestion's onKeyDown render function
|
|
423
|
+
*/
|
|
424
|
+
export interface SuggestionKeyDownProps {
|
|
425
|
+
view: EditorView
|
|
426
|
+
event: KeyboardEvent
|
|
427
|
+
range: Range
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** @internal Internal state shape for the suggestion plugin. */
|
|
431
|
+
export interface SuggestionPluginState {
|
|
432
|
+
active: boolean
|
|
433
|
+
range: Range
|
|
434
|
+
query: null | string
|
|
435
|
+
text: null | string
|
|
436
|
+
composing: boolean
|
|
437
|
+
decorationId?: string | null
|
|
438
|
+
dismissedRange: Range | null
|
|
439
|
+
}
|