@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/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/
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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 });
|