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