@tiptap/suggestion 3.26.1 → 3.27.1

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.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // src/suggestion.ts
2
2
  import { Plugin, PluginKey } from "@tiptap/pm/state";
3
- import { Decoration, DecorationSet } from "@tiptap/pm/view";
4
3
 
5
4
  // src/findSuggestionMatch.ts
6
5
  import { escapeForRegEx } from "@tiptap/core";
@@ -53,7 +52,7 @@ function findSuggestionMatch(config) {
53
52
  return null;
54
53
  }
55
54
 
56
- // src/suggestion.ts
55
+ // src/helpers.ts
57
56
  function hasInsertedWhitespace(transaction) {
58
57
  if (!transaction.docChanged) {
59
58
  return false;
@@ -67,31 +66,8 @@ function hasInsertedWhitespace(transaction) {
67
66
  return /\s/.test(inserted);
68
67
  });
69
68
  }
70
- var SuggestionPluginKey = new PluginKey("suggestion");
71
- function Suggestion({
72
- pluginKey = SuggestionPluginKey,
73
- editor,
74
- char = "@",
75
- allowSpaces = false,
76
- allowToIncludeChar = false,
77
- allowedPrefixes = [" "],
78
- startOfLine = false,
79
- decorationTag = "span",
80
- decorationClass = "suggestion",
81
- decorationContent = "",
82
- decorationEmptyClass = "is-empty",
83
- command = () => null,
84
- items = () => [],
85
- render = () => ({}),
86
- allow = () => true,
87
- findSuggestionMatch: findSuggestionMatch2 = findSuggestionMatch,
88
- shouldShow,
89
- shouldResetDismissed
90
- }) {
91
- let props;
92
- const renderer = render == null ? void 0 : render();
93
- const effectiveAllowSpaces = allowSpaces && !allowToIncludeChar;
94
- const getAnchorClientRect = () => {
69
+ function getAnchorClientRect(editor) {
70
+ return () => {
95
71
  const pos = editor.state.selection.$anchor.pos;
96
72
  const coords = editor.view.coordsAtPos(pos);
97
73
  const { top, right, bottom, left } = coords;
@@ -101,275 +77,653 @@ function Suggestion({
101
77
  return null;
102
78
  }
103
79
  };
104
- const clientRectFor = (view, decorationNode) => {
105
- if (!decorationNode) {
106
- return getAnchorClientRect;
107
- }
108
- return () => {
109
- const state = pluginKey.getState(editor.state);
110
- const decorationId = state == null ? void 0 : state.decorationId;
111
- const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
112
- return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
113
- };
80
+ }
81
+ function clientRectFor(editor, view, decorationNode, pluginKey) {
82
+ if (!decorationNode) {
83
+ return getAnchorClientRect(editor);
84
+ }
85
+ return () => {
86
+ const state = pluginKey.getState(editor.state);
87
+ const decorationId = state == null ? void 0 : state.decorationId;
88
+ const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
89
+ return (currentDecorationNode == null ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
114
90
  };
115
- const shouldKeepDismissed = ({
116
- match,
117
- dismissedRange,
91
+ }
92
+ function shouldKeepDismissed({
93
+ match,
94
+ dismissedRange,
95
+ state,
96
+ transaction,
97
+ editor,
98
+ shouldResetDismissed,
99
+ effectiveAllowSpaces
100
+ }) {
101
+ if (shouldResetDismissed == null ? void 0 : shouldResetDismissed({
102
+ editor,
118
103
  state,
119
- transaction
120
- }) => {
121
- if (shouldResetDismissed == null ? void 0 : shouldResetDismissed({
122
- editor,
123
- state,
124
- range: dismissedRange,
125
- match,
126
- transaction,
127
- allowSpaces: effectiveAllowSpaces
128
- })) {
129
- return false;
130
- }
131
- if (effectiveAllowSpaces) {
132
- return match.range.from === dismissedRange.from;
133
- }
134
- return match.range.from === dismissedRange.from && !hasInsertedWhitespace(transaction);
135
- };
136
- function dispatchExit(view, pluginKeyRef) {
137
- var _a;
138
- try {
104
+ range: dismissedRange,
105
+ match,
106
+ transaction,
107
+ allowSpaces: effectiveAllowSpaces
108
+ })) {
109
+ return false;
110
+ }
111
+ if (effectiveAllowSpaces) {
112
+ return match.range.from === dismissedRange.from;
113
+ }
114
+ return match.range.from === dismissedRange.from && !hasInsertedWhitespace(transaction);
115
+ }
116
+ function dispatchExit({
117
+ view,
118
+ pluginKeyRef
119
+ }) {
120
+ const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
121
+ view.dispatch(tr);
122
+ }
123
+
124
+ // src/plugin/props.ts
125
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
126
+ function createSuggestionProps({
127
+ pluginKey,
128
+ decorationTag,
129
+ decorationClass,
130
+ decorationContent,
131
+ decorationEmptyClass,
132
+ renderer,
133
+ dispatchExit: dispatchExit2
134
+ }) {
135
+ return {
136
+ /**
137
+ * Call the keydown hook if suggestion is active.
138
+ */
139
+ handleKeyDown(view, event) {
140
+ var _a, _b;
139
141
  const state = pluginKey.getState(view.state);
140
- const decorationNode = (state == null ? void 0 : state.decorationId) ? view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`) : null;
141
- const exitProps = {
142
- // @ts-ignore editor is available in closure
143
- editor,
144
- range: (state == null ? void 0 : state.range) || { from: 0, to: 0 },
145
- query: (state == null ? void 0 : state.query) || null,
146
- text: (state == null ? void 0 : state.text) || null,
147
- items: [],
148
- command: (commandProps) => {
149
- return command({
150
- editor,
151
- range: (state == null ? void 0 : state.range) || { from: 0, to: 0 },
152
- props: commandProps
153
- });
154
- },
155
- decorationNode,
156
- clientRect: clientRectFor(view, decorationNode)
157
- };
158
- (_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, exitProps);
159
- } catch {
142
+ if (!state.active) {
143
+ return false;
144
+ }
145
+ if (event.key === "Escape" || event.key === "Esc") {
146
+ (_a = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _a.call(renderer, { view, event, range: state.range });
147
+ dispatchExit2(view);
148
+ return true;
149
+ }
150
+ const handled = ((_b = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _b.call(renderer, { view, event, range: state.range })) || false;
151
+ return handled;
152
+ },
153
+ /**
154
+ * Setup decorator on the currently active suggestion.
155
+ */
156
+ decorations(state) {
157
+ const pluginState = pluginKey.getState(state);
158
+ const { active, range, decorationId, query } = pluginState;
159
+ if (!active) {
160
+ return null;
161
+ }
162
+ const isEmpty = !(query == null ? void 0 : query.length);
163
+ const classNames = [decorationClass];
164
+ if (isEmpty) {
165
+ classNames.push(decorationEmptyClass);
166
+ }
167
+ return DecorationSet.create(state.doc, [
168
+ Decoration.inline(range.from, range.to, {
169
+ nodeName: decorationTag,
170
+ class: classNames.join(" "),
171
+ "data-decoration-id": decorationId || void 0,
172
+ "data-decoration-content": decorationContent
173
+ })
174
+ ]);
160
175
  }
161
- const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });
162
- view.dispatch(tr);
163
- }
164
- const plugin = new Plugin({
165
- key: pluginKey,
166
- view() {
176
+ };
177
+ }
178
+
179
+ // src/plugin/state.ts
180
+ function createSuggestionState({
181
+ editor,
182
+ char,
183
+ effectiveAllowSpaces,
184
+ allowToIncludeChar,
185
+ allowedPrefixes,
186
+ startOfLine,
187
+ findSuggestionMatch: findSuggestionMatch2,
188
+ allow,
189
+ shouldShow,
190
+ shouldKeepDismissed: shouldKeepDismissed2,
191
+ pluginKey
192
+ }) {
193
+ return {
194
+ /**
195
+ * Initialize the plugin's internal state.
196
+ */
197
+ init() {
167
198
  return {
168
- update: async (view, prevState) => {
169
- var _a, _b, _c, _d, _e, _f, _g;
170
- const prev = (_a = this.key) == null ? void 0 : _a.getState(prevState);
171
- const next = (_b = this.key) == null ? void 0 : _b.getState(view.state);
172
- const moved = prev.active && next.active && prev.range.from !== next.range.from;
173
- const started = !prev.active && next.active;
174
- const stopped = prev.active && !next.active;
175
- const changed = !started && !stopped && prev.query !== next.query;
176
- const handleStart = started || moved && changed;
177
- const handleChange = changed || moved;
178
- const handleExit = stopped || moved && changed;
179
- if (!handleStart && !handleChange && !handleExit) {
180
- return;
181
- }
182
- const state = handleExit && !handleStart ? prev : next;
183
- const decorationNode = view.dom.querySelector(
184
- `[data-decoration-id="${state.decorationId}"]`
185
- );
186
- props = {
187
- editor,
188
- range: state.range,
189
- query: state.query,
190
- text: state.text,
191
- items: [],
192
- command: (commandProps) => {
193
- return command({
194
- editor,
195
- range: state.range,
196
- props: commandProps
197
- });
198
- },
199
- decorationNode,
200
- clientRect: clientRectFor(view, decorationNode)
201
- };
202
- if (handleStart) {
203
- (_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
204
- }
205
- if (handleChange) {
206
- (_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
207
- }
208
- if (handleChange || handleStart) {
209
- props.items = await items({
210
- editor,
211
- query: state.query
212
- });
213
- }
214
- if (handleExit) {
215
- (_e = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _e.call(renderer, props);
216
- }
217
- if (handleChange) {
218
- (_f = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _f.call(renderer, props);
219
- }
220
- if (handleStart) {
221
- (_g = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _g.call(renderer, props);
222
- }
223
- },
224
- destroy: () => {
225
- var _a;
226
- if (!props) {
227
- return;
228
- }
229
- (_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
230
- }
199
+ active: false,
200
+ range: { from: 0, to: 0 },
201
+ query: null,
202
+ text: null,
203
+ composing: false,
204
+ dismissedRange: null
231
205
  };
232
206
  },
233
- state: {
234
- // Initialize the plugin's internal state.
235
- init() {
236
- const state = {
237
- active: false,
238
- range: {
239
- from: 0,
240
- to: 0
241
- },
242
- query: null,
243
- text: null,
244
- composing: false,
245
- dismissedRange: null
207
+ /**
208
+ * Apply changes to the plugin state from a view transaction.
209
+ */
210
+ apply(transaction, prev, _oldState, state) {
211
+ const { isEditable } = editor;
212
+ const { composing } = editor.view;
213
+ const { selection } = transaction;
214
+ const { empty, from } = selection;
215
+ const next = { ...prev };
216
+ const meta = transaction.getMeta(pluginKey);
217
+ if (meta && meta.exit) {
218
+ next.active = false;
219
+ next.decorationId = null;
220
+ next.range = { from: 0, to: 0 };
221
+ next.query = null;
222
+ next.text = null;
223
+ next.dismissedRange = prev.active ? { ...prev.range } : prev.dismissedRange;
224
+ return next;
225
+ }
226
+ next.composing = composing;
227
+ if (transaction.docChanged && next.dismissedRange !== null) {
228
+ next.dismissedRange = {
229
+ from: transaction.mapping.map(next.dismissedRange.from),
230
+ to: transaction.mapping.map(next.dismissedRange.to)
246
231
  };
247
- return state;
248
- },
249
- // Apply changes to the plugin state from a view transaction.
250
- apply(transaction, prev, _oldState, state) {
251
- const { isEditable } = editor;
252
- const { composing } = editor.view;
253
- const { selection } = transaction;
254
- const { empty, from } = selection;
255
- const next = { ...prev };
256
- const meta = transaction.getMeta(pluginKey);
257
- if (meta && meta.exit) {
232
+ }
233
+ if (isEditable && (empty || editor.view.composing)) {
234
+ if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
258
235
  next.active = false;
259
- next.decorationId = null;
260
- next.range = { from: 0, to: 0 };
261
- next.query = null;
262
- next.text = null;
263
- next.dismissedRange = prev.active ? { ...prev.range } : prev.dismissedRange;
264
- return next;
265
236
  }
266
- next.composing = composing;
267
- if (transaction.docChanged && next.dismissedRange !== null) {
268
- next.dismissedRange = {
269
- from: transaction.mapping.map(next.dismissedRange.from),
270
- to: transaction.mapping.map(next.dismissedRange.to)
271
- };
272
- }
273
- if (isEditable && (empty || editor.view.composing)) {
274
- if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
275
- next.active = false;
276
- }
277
- const match = findSuggestionMatch2({
278
- char,
279
- allowSpaces,
280
- allowToIncludeChar,
281
- allowedPrefixes,
282
- startOfLine,
283
- $position: selection.$from
284
- });
285
- const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
286
- if (match && allow({
287
- editor,
237
+ const match = findSuggestionMatch2({
238
+ char,
239
+ allowSpaces: effectiveAllowSpaces,
240
+ allowToIncludeChar,
241
+ allowedPrefixes,
242
+ startOfLine,
243
+ $position: selection.$from
244
+ });
245
+ const decorationId = `id_${Math.floor(Math.random() * 4294967295)}`;
246
+ if (match && allow({
247
+ editor,
248
+ state,
249
+ range: match.range,
250
+ isActive: prev.active
251
+ }) && (!shouldShow || shouldShow({
252
+ editor,
253
+ range: match.range,
254
+ query: match.query,
255
+ text: match.text,
256
+ transaction
257
+ }))) {
258
+ if (next.dismissedRange !== null && !shouldKeepDismissed2({
259
+ match,
260
+ dismissedRange: next.dismissedRange,
288
261
  state,
289
- range: match.range,
290
- isActive: prev.active
291
- }) && (!shouldShow || shouldShow({
292
- editor,
293
- range: match.range,
294
- query: match.query,
295
- text: match.text,
296
262
  transaction
297
- }))) {
298
- if (next.dismissedRange !== null && !shouldKeepDismissed({
299
- match,
300
- dismissedRange: next.dismissedRange,
301
- state,
302
- transaction
303
- })) {
304
- next.dismissedRange = null;
305
- }
306
- if (next.dismissedRange === null) {
307
- next.active = true;
308
- next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
309
- next.range = match.range;
310
- next.query = match.query;
311
- next.text = match.text;
312
- } else {
313
- next.active = false;
314
- }
263
+ })) {
264
+ next.dismissedRange = null;
265
+ }
266
+ if (next.dismissedRange === null) {
267
+ next.active = true;
268
+ next.decorationId = prev.decorationId || decorationId;
269
+ next.range = match.range;
270
+ next.query = match.query;
271
+ next.text = match.text;
315
272
  } else {
316
- if (!match) {
317
- next.dismissedRange = null;
318
- }
319
273
  next.active = false;
320
274
  }
321
275
  } else {
276
+ if (!match) {
277
+ next.dismissedRange = null;
278
+ }
322
279
  next.active = false;
323
280
  }
324
- if (!next.active) {
325
- next.decorationId = null;
326
- next.range = { from: 0, to: 0 };
327
- next.query = null;
328
- next.text = null;
329
- }
330
- return next;
281
+ } else {
282
+ next.active = false;
331
283
  }
332
- },
333
- props: {
334
- // Call the keydown hook if suggestion is active.
335
- handleKeyDown(view, event) {
336
- var _a, _b;
337
- const { active, range } = plugin.getState(view.state);
338
- if (!active) {
339
- return false;
284
+ if (!next.active) {
285
+ next.decorationId = null;
286
+ next.range = { from: 0, to: 0 };
287
+ next.query = null;
288
+ next.text = null;
289
+ }
290
+ return next;
291
+ }
292
+ };
293
+ }
294
+
295
+ // src/plugin/async.ts
296
+ function createSuggestionAsyncRequestManager({
297
+ editor,
298
+ items
299
+ }) {
300
+ let abortController = null;
301
+ let debounceTimer = null;
302
+ let debounceResolve = null;
303
+ const clearDebounceTimer = () => {
304
+ if (debounceTimer !== null) {
305
+ clearTimeout(debounceTimer);
306
+ debounceTimer = null;
307
+ }
308
+ debounceResolve == null ? void 0 : debounceResolve();
309
+ debounceResolve = null;
310
+ };
311
+ const waitForDebounce = (delay) => {
312
+ return new Promise((resolve) => {
313
+ debounceResolve = resolve;
314
+ debounceTimer = setTimeout(() => {
315
+ debounceTimer = null;
316
+ const pendingResolve = debounceResolve;
317
+ debounceResolve = null;
318
+ pendingResolve == null ? void 0 : pendingResolve();
319
+ }, delay);
320
+ });
321
+ };
322
+ const abort = () => {
323
+ abortController == null ? void 0 : abortController.abort();
324
+ clearDebounceTimer();
325
+ abortController = null;
326
+ };
327
+ const fetch = async (query, debounce) => {
328
+ abort();
329
+ abortController = new AbortController();
330
+ const controller = abortController;
331
+ if (debounce > 0) {
332
+ await waitForDebounce(debounce);
333
+ }
334
+ if (abortController !== controller || controller.signal.aborted) {
335
+ return { status: "aborted" };
336
+ }
337
+ try {
338
+ const result = await items({
339
+ editor,
340
+ query,
341
+ signal: controller.signal
342
+ });
343
+ if (abortController !== controller || controller.signal.aborted) {
344
+ return { status: "aborted" };
345
+ }
346
+ return { status: "resolved", items: result };
347
+ } catch {
348
+ if (abortController !== controller || controller.signal.aborted) {
349
+ return { status: "aborted" };
350
+ }
351
+ return { status: "error" };
352
+ }
353
+ };
354
+ return {
355
+ abort,
356
+ fetch
357
+ };
358
+ }
359
+
360
+ // src/plugin/floating-ui.ts
361
+ import {
362
+ autoUpdate,
363
+ computePosition,
364
+ flip as floatingUiFlip,
365
+ offset as floatingUiOffset
366
+ } from "@floating-ui/dom";
367
+ function createSuggestionFloatingUiConfig({
368
+ placement,
369
+ offset,
370
+ flip,
371
+ floatingUi
372
+ }) {
373
+ var _a, _b, _c, _d;
374
+ const middleware = [
375
+ floatingUiOffset({
376
+ mainAxis: (_a = offset.mainAxis) != null ? _a : 4,
377
+ crossAxis: (_b = offset.crossAxis) != null ? _b : 0
378
+ })
379
+ ];
380
+ if (flip) {
381
+ middleware.push(floatingUiFlip());
382
+ }
383
+ if ((_c = floatingUi == null ? void 0 : floatingUi.middleware) == null ? void 0 : _c.length) {
384
+ middleware.push(...floatingUi.middleware);
385
+ }
386
+ return {
387
+ placement,
388
+ strategy: (_d = floatingUi == null ? void 0 : floatingUi.strategy) != null ? _d : "absolute",
389
+ middleware
390
+ };
391
+ }
392
+ function resolveContainer(container) {
393
+ if (container instanceof HTMLElement) {
394
+ return container;
395
+ }
396
+ if (typeof container === "string") {
397
+ try {
398
+ const found = document.querySelector(container);
399
+ if (found) {
400
+ return found;
401
+ }
402
+ } catch {
403
+ return document.body;
404
+ }
405
+ }
406
+ return document.body;
407
+ }
408
+ function createMount({
409
+ getReferenceRect,
410
+ contextElement,
411
+ config,
412
+ container,
413
+ dismissOnOutsideClick,
414
+ dismiss
415
+ }) {
416
+ return (element, options = {}) => {
417
+ const reference = {
418
+ getBoundingClientRect: () => {
419
+ var _a;
420
+ return (_a = getReferenceRect()) != null ? _a : new DOMRect();
421
+ },
422
+ contextElement
423
+ };
424
+ let positioned = false;
425
+ const mountedByUs = !element.isConnected;
426
+ if (mountedByUs) {
427
+ resolveContainer(container).appendChild(element);
428
+ }
429
+ if (!options.onPosition) {
430
+ element.style.visibility = "hidden";
431
+ element.style.width = "max-content";
432
+ }
433
+ const update = () => {
434
+ computePosition(reference, element, {
435
+ placement: config.placement,
436
+ strategy: config.strategy,
437
+ middleware: config.middleware
438
+ }).then(({ x, y, placement, strategy }) => {
439
+ if (options.onPosition) {
440
+ options.onPosition({ x, y, placement, strategy });
441
+ return;
340
442
  }
341
- if (event.key === "Escape" || event.key === "Esc") {
342
- const state = plugin.getState(view.state);
343
- (_a = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _a.call(renderer, { view, event, range: state.range });
344
- dispatchExit(view, pluginKey);
345
- return true;
443
+ Object.assign(element.style, {
444
+ position: strategy,
445
+ left: `${x}px`,
446
+ top: `${y}px`
447
+ });
448
+ if (!positioned) {
449
+ positioned = true;
450
+ element.style.visibility = "";
346
451
  }
347
- const handled = ((_b = renderer == null ? void 0 : renderer.onKeyDown) == null ? void 0 : _b.call(renderer, { view, event, range })) || false;
348
- return handled;
349
- },
350
- // Setup decorator on the currently active suggestion.
351
- decorations(state) {
352
- const { active, range, decorationId, query } = plugin.getState(state);
353
- if (!active) {
354
- return null;
452
+ });
453
+ };
454
+ const cleanupAutoUpdate = autoUpdate(reference, element, update, options.autoUpdate);
455
+ let onOutsidePointerDown;
456
+ if (dismissOnOutsideClick) {
457
+ onOutsidePointerDown = (event) => {
458
+ const target = event.target;
459
+ if (!(target instanceof Node) || element.contains(target) || contextElement.contains(target)) {
460
+ return;
355
461
  }
356
- const isEmpty = !(query == null ? void 0 : query.length);
357
- const classNames = [decorationClass];
358
- if (isEmpty) {
359
- classNames.push(decorationEmptyClass);
462
+ dismiss();
463
+ };
464
+ document.addEventListener("pointerdown", onOutsidePointerDown, true);
465
+ }
466
+ return () => {
467
+ cleanupAutoUpdate();
468
+ if (onOutsidePointerDown) {
469
+ document.removeEventListener("pointerdown", onOutsidePointerDown, true);
470
+ }
471
+ if (mountedByUs) {
472
+ element.remove();
473
+ }
474
+ };
475
+ };
476
+ }
477
+
478
+ // src/plugin/view.ts
479
+ function createSuggestionView({
480
+ editor,
481
+ pluginKey,
482
+ items,
483
+ renderer,
484
+ minQueryLength,
485
+ debounce,
486
+ initialItems,
487
+ placement,
488
+ offset: offsetOption,
489
+ container,
490
+ flip,
491
+ floatingUi,
492
+ dismissOnOutsideClick,
493
+ command,
494
+ clientRectFor: clientRectFor2,
495
+ dispatchExit: dispatchExit2
496
+ }) {
497
+ let props;
498
+ const asyncRequest = createSuggestionAsyncRequestManager({
499
+ editor,
500
+ items
501
+ });
502
+ const floatingUiConfig = createSuggestionFloatingUiConfig({
503
+ placement,
504
+ offset: offsetOption,
505
+ flip,
506
+ floatingUi
507
+ });
508
+ function dispatchStateUpdate(state, dispatchProps) {
509
+ var _a, _b, _c;
510
+ switch (state) {
511
+ case "started":
512
+ (_a = renderer == null ? void 0 : renderer.onStart) == null ? void 0 : _a.call(renderer, dispatchProps);
513
+ break;
514
+ case "updated":
515
+ (_b = renderer == null ? void 0 : renderer.onUpdate) == null ? void 0 : _b.call(renderer, dispatchProps);
516
+ break;
517
+ case "stopped":
518
+ (_c = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _c.call(renderer, dispatchProps);
519
+ break;
520
+ default:
521
+ break;
522
+ }
523
+ }
524
+ return {
525
+ update: async (view, prevState) => {
526
+ var _a, _b, _c, _d;
527
+ const prev = pluginKey.getState(prevState);
528
+ const next = pluginKey.getState(view.state);
529
+ if (!prev || !next) {
530
+ return;
531
+ }
532
+ let currentState = null;
533
+ const queryChanged = prev.query !== next.query;
534
+ const textChanged = prev.text !== next.text;
535
+ const rangeChanged = prev.range.from !== next.range.from || prev.range.to !== next.range.to;
536
+ const effectiveQueryChanged = queryChanged || textChanged || rangeChanged;
537
+ if (!prev.active && next.active) {
538
+ currentState = "started";
539
+ } else if (prev.active && !next.active) {
540
+ currentState = "stopped";
541
+ } else if (next.active && effectiveQueryChanged) {
542
+ currentState = "updated";
543
+ } else {
544
+ return;
545
+ }
546
+ const state = currentState === "stopped" ? prev : next;
547
+ const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
548
+ const clientRect = clientRectFor2(view, decorationNode);
549
+ const exceedsMinQueryLength = minQueryLength === 0 || (state.query ? state.query.length >= minQueryLength : false);
550
+ const willFetch = (currentState === "started" || currentState === "updated") && exceedsMinQueryLength;
551
+ props = {
552
+ editor,
553
+ range: state.range,
554
+ query: state.query || "",
555
+ text: state.text || "",
556
+ items: initialItems != null ? initialItems : [],
557
+ command: (commandProps) => {
558
+ return command({
559
+ editor,
560
+ range: state.range,
561
+ props: commandProps
562
+ });
563
+ },
564
+ decorationNode,
565
+ clientRect,
566
+ loading: willFetch,
567
+ placement,
568
+ offset: { mainAxis: (_a = offsetOption.mainAxis) != null ? _a : 4, crossAxis: (_b = offsetOption.crossAxis) != null ? _b : 0 },
569
+ container,
570
+ flip,
571
+ floatingUi: floatingUiConfig,
572
+ mount: createMount({
573
+ getReferenceRect: clientRect,
574
+ contextElement: view.dom,
575
+ config: floatingUiConfig,
576
+ container,
577
+ dismissOnOutsideClick,
578
+ dismiss: () => dispatchExit2(editor.view)
579
+ })
580
+ };
581
+ if (currentState === "started") {
582
+ (_c = renderer == null ? void 0 : renderer.onBeforeStart) == null ? void 0 : _c.call(renderer, props);
583
+ }
584
+ if (currentState === "updated") {
585
+ (_d = renderer == null ? void 0 : renderer.onBeforeUpdate) == null ? void 0 : _d.call(renderer, props);
586
+ }
587
+ if (currentState === "started") {
588
+ dispatchStateUpdate(currentState, props);
589
+ }
590
+ if (currentState === "started" || currentState === "updated") {
591
+ if (!willFetch) {
592
+ asyncRequest.abort();
593
+ props = { ...props, items: initialItems != null ? initialItems : [], loading: false };
594
+ } else {
595
+ props = { ...props, items: initialItems != null ? initialItems : [], loading: true };
596
+ currentState = "updated";
597
+ dispatchStateUpdate(currentState, props);
598
+ const result = await asyncRequest.fetch(state.query || "", debounce);
599
+ if (result.status === "aborted") {
600
+ return;
601
+ }
602
+ const currentPluginState = pluginKey.getState(view.state);
603
+ if (!(currentPluginState == null ? void 0 : currentPluginState.active)) {
604
+ asyncRequest.abort();
605
+ return;
606
+ }
607
+ props = result.status === "resolved" ? {
608
+ ...props,
609
+ items: result.items,
610
+ loading: false
611
+ } : {
612
+ ...props,
613
+ loading: false
614
+ };
360
615
  }
361
- return DecorationSet.create(state.doc, [
362
- Decoration.inline(range.from, range.to, {
363
- nodeName: decorationTag,
364
- class: classNames.join(" "),
365
- "data-decoration-id": decorationId,
366
- "data-decoration-content": decorationContent
367
- })
368
- ]);
369
616
  }
617
+ if (currentState === "stopped") {
618
+ asyncRequest.abort();
619
+ dispatchStateUpdate(currentState, props);
620
+ props = void 0;
621
+ return;
622
+ }
623
+ if (currentState === "updated") {
624
+ dispatchStateUpdate(currentState, props);
625
+ }
626
+ },
627
+ destroy: () => {
628
+ var _a;
629
+ asyncRequest.abort();
630
+ if (!props) {
631
+ return;
632
+ }
633
+ (_a = renderer == null ? void 0 : renderer.onExit) == null ? void 0 : _a.call(renderer, props);
370
634
  }
635
+ };
636
+ }
637
+
638
+ // src/suggestion.ts
639
+ var SuggestionPluginKey = new PluginKey("suggestion");
640
+ function Suggestion({
641
+ pluginKey = SuggestionPluginKey,
642
+ editor,
643
+ char = "@",
644
+ allowSpaces = false,
645
+ allowToIncludeChar = false,
646
+ allowedPrefixes = [" "],
647
+ startOfLine = false,
648
+ decorationTag = "span",
649
+ decorationClass = "suggestion",
650
+ decorationContent = "",
651
+ decorationEmptyClass = "is-empty",
652
+ command = () => null,
653
+ items = () => [],
654
+ minQueryLength = 0,
655
+ debounce = 0,
656
+ initialItems,
657
+ placement = "bottom-start",
658
+ offset: offsetOption = {},
659
+ container,
660
+ flip = true,
661
+ floatingUi,
662
+ dismissOnOutsideClick = true,
663
+ render = () => ({}),
664
+ allow = () => true,
665
+ findSuggestionMatch: findSuggestionMatch2 = findSuggestionMatch,
666
+ shouldShow,
667
+ shouldResetDismissed
668
+ }) {
669
+ const renderer = render == null ? void 0 : render();
670
+ const effectiveAllowSpaces = allowSpaces && !allowToIncludeChar;
671
+ const clientRectFor2 = (view, decorationNode) => clientRectFor(editor, view, decorationNode, pluginKey);
672
+ function shouldKeepDismissed2(props) {
673
+ return shouldKeepDismissed({
674
+ ...props,
675
+ editor,
676
+ shouldResetDismissed,
677
+ effectiveAllowSpaces
678
+ });
679
+ }
680
+ const dispatchExit2 = (view) => dispatchExit({
681
+ view,
682
+ pluginKeyRef: pluginKey
683
+ });
684
+ return new Plugin({
685
+ key: pluginKey,
686
+ view: () => createSuggestionView({
687
+ editor,
688
+ pluginKey,
689
+ items,
690
+ renderer,
691
+ minQueryLength,
692
+ debounce,
693
+ initialItems,
694
+ placement,
695
+ offset: offsetOption,
696
+ container,
697
+ flip,
698
+ floatingUi,
699
+ dismissOnOutsideClick,
700
+ command,
701
+ clientRectFor: clientRectFor2,
702
+ dispatchExit: dispatchExit2
703
+ }),
704
+ state: createSuggestionState({
705
+ editor,
706
+ char,
707
+ effectiveAllowSpaces,
708
+ allowToIncludeChar,
709
+ allowedPrefixes,
710
+ startOfLine,
711
+ findSuggestionMatch: findSuggestionMatch2,
712
+ allow,
713
+ shouldShow,
714
+ shouldKeepDismissed: shouldKeepDismissed2,
715
+ pluginKey
716
+ }),
717
+ props: createSuggestionProps({
718
+ pluginKey,
719
+ decorationTag,
720
+ decorationClass,
721
+ decorationContent,
722
+ decorationEmptyClass,
723
+ renderer,
724
+ dispatchExit: dispatchExit2
725
+ })
371
726
  });
372
- return plugin;
373
727
  }
374
728
  function exitSuggestion(view, pluginKeyRef = SuggestionPluginKey) {
375
729
  const tr = view.state.tr.setMeta(pluginKeyRef, { exit: true });