@tiptap/suggestion 3.26.0 → 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/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
+ }