@portabletext/plugin-typeahead-picker 6.0.44 → 6.0.46

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
@@ -2,1296 +2,1014 @@ import { keyGenerator, useEditor } from "@portabletext/editor";
2
2
  import { c } from "react/compiler-runtime";
3
3
  import { useActor } from "@xstate/react";
4
4
  import { defineBehavior, effect, forward, raise } from "@portabletext/editor/behaviors";
5
- import { getFocusSpan, getNextSpan, isSelectionCollapsed, isPointAfterSelection, isPointBeforeSelection, getMarkState, getPreviousSpan } from "@portabletext/editor/selectors";
5
+ import { getFocusSpan, getMarkState, getNextSpan, getPreviousSpan, isPointAfterSelection, isPointBeforeSelection, isSelectionCollapsed } from "@portabletext/editor/selectors";
6
6
  import { isEqualPaths, isEqualSelectionPoints } from "@portabletext/editor/utils";
7
7
  import { createKeyboardShortcut } from "@portabletext/keyboard-shortcuts";
8
- import { defineInputRuleBehavior, defineInputRule } from "@portabletext/plugin-input-rule";
9
- import { setup, assign, sendTo, fromPromise, fromCallback } from "xstate";
8
+ import { defineInputRule, defineInputRuleBehavior } from "@portabletext/plugin-input-rule";
9
+ import { assign, fromCallback, fromPromise, sendTo, setup } from "xstate";
10
+ /** @public */
10
11
  function defineTypeaheadPicker(config) {
11
- return {
12
- ...config,
13
- _id: keyGenerator()
14
- };
12
+ return {
13
+ ...config,
14
+ _id: keyGenerator()
15
+ };
15
16
  }
17
+ /**
18
+ * Extract keyword from pattern text using the trigger pattern.
19
+ * Removes the trigger match from the start and delimiter from the end.
20
+ *
21
+ * @param patternText - The full pattern text (e.g., `:joy:` or `:joy`)
22
+ * @param triggerPattern - Pattern matching the trigger (e.g., /:/)
23
+ * @param delimiter - Optional delimiter character (e.g., `:`)
24
+ * @param completePattern - Optional complete pattern to detect if this is a complete match
25
+ */
16
26
  function extractKeyword(patternText, triggerPattern, delimiter, completePattern) {
17
- const triggerMatch = patternText.match(triggerPattern);
18
- if (!triggerMatch || triggerMatch.index !== 0)
19
- return patternText;
20
- let keyword = patternText.slice(triggerMatch[0].length);
21
- return delimiter && keyword.endsWith(delimiter) && (completePattern && (() => {
22
- const completeMatch = patternText.match(completePattern);
23
- return completeMatch && completeMatch.index === 0 && completeMatch[0] === patternText;
24
- })() || keyword.length > delimiter.length) && (keyword = keyword.slice(0, -delimiter.length)), keyword;
27
+ let triggerMatch = patternText.match(triggerPattern);
28
+ if (!triggerMatch || triggerMatch.index !== 0) return patternText;
29
+ let keyword = patternText.slice(triggerMatch[0].length);
30
+ return delimiter && keyword.endsWith(delimiter) && (completePattern && (() => {
31
+ let completeMatch = patternText.match(completePattern);
32
+ return completeMatch && completeMatch.index === 0 && completeMatch[0] === patternText;
33
+ })() || keyword.length > delimiter.length) && (keyword = keyword.slice(0, -delimiter.length)), keyword;
25
34
  }
26
35
  function escapeRegExp(str) {
27
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28
37
  }
38
+ /**
39
+ * Build the trigger pattern from the definition.
40
+ */
29
41
  function buildTriggerPattern(definition) {
30
- return new RegExp(definition.trigger.source);
42
+ return new RegExp(definition.trigger.source);
31
43
  }
44
+ /**
45
+ * Build the partial pattern (trigger + keyword) from the definition.
46
+ */
32
47
  function buildPartialPattern(definition) {
33
- return new RegExp(definition.trigger.source + definition.keyword.source);
48
+ return new RegExp(definition.trigger.source + definition.keyword.source);
34
49
  }
50
+ /**
51
+ * Build the complete pattern (trigger + keyword + delimiter) from the definition.
52
+ */
35
53
  function buildCompletePattern(definition) {
36
- if (!definition.delimiter)
37
- return;
38
- const escapedDelimiter = escapeRegExp(definition.delimiter);
39
- return new RegExp(definition.trigger.source + definition.keyword.source + escapedDelimiter);
54
+ if (!definition.delimiter) return;
55
+ let escapedDelimiter = escapeRegExp(definition.delimiter);
56
+ return new RegExp(definition.trigger.source + definition.keyword.source + escapedDelimiter);
40
57
  }
41
- const arrowUpShortcut = createKeyboardShortcut({
42
- default: [{
43
- key: "ArrowUp"
44
- }]
45
- }), arrowDownShortcut = createKeyboardShortcut({
46
- default: [{
47
- key: "ArrowDown"
48
- }]
49
- }), enterShortcut = createKeyboardShortcut({
50
- default: [{
51
- key: "Enter"
52
- }]
53
- }), tabShortcut = createKeyboardShortcut({
54
- default: [{
55
- key: "Tab"
56
- }]
57
- }), escapeShortcut = createKeyboardShortcut({
58
- default: [{
59
- key: "Escape"
60
- }]
61
- }), getTriggerState = (snapshot) => {
62
- const focusSpan = getFocusSpan(snapshot), markState = getMarkState(snapshot);
63
- if (!focusSpan || !markState || !snapshot.context.selection)
64
- return;
65
- const focusSpanTextBefore = focusSpan.node.text.slice(0, snapshot.context.selection.focus.offset), focusSpanTextAfter = focusSpan.node.text.slice(snapshot.context.selection.focus.offset), previousSpan = getPreviousSpan(snapshot), nextSpan = getNextSpan(snapshot);
66
- return {
67
- focusSpan,
68
- markState,
69
- focusSpanTextBefore,
70
- focusSpanTextAfter,
71
- previousSpan,
72
- nextSpan
73
- };
58
+ const arrowUpShortcut = createKeyboardShortcut({ default: [{ key: "ArrowUp" }] }), arrowDownShortcut = createKeyboardShortcut({ default: [{ key: "ArrowDown" }] }), enterShortcut = createKeyboardShortcut({ default: [{ key: "Enter" }] }), tabShortcut = createKeyboardShortcut({ default: [{ key: "Tab" }] }), escapeShortcut = createKeyboardShortcut({ default: [{ key: "Escape" }] }), getTriggerState = (snapshot) => {
59
+ let focusSpan = getFocusSpan(snapshot), markState = getMarkState(snapshot);
60
+ if (!(!focusSpan || !markState || !snapshot.context.selection)) return {
61
+ focusSpan,
62
+ markState,
63
+ focusSpanTextBefore: focusSpan.node.text.slice(0, snapshot.context.selection.focus.offset),
64
+ focusSpanTextAfter: focusSpan.node.text.slice(snapshot.context.selection.focus.offset),
65
+ previousSpan: getPreviousSpan(snapshot),
66
+ nextSpan: getNextSpan(snapshot)
67
+ };
74
68
  };
75
- function createTriggerActions({
76
- snapshot,
77
- payload,
78
- keywordState,
79
- pickerId
80
- }) {
81
- if (payload.markState.state === "unchanged") {
82
- const textBeforeMatch = payload.focusSpanTextBefore.slice(0, payload.lastMatch.targetOffsets.anchor.offset), focusSpan2 = {
83
- node: {
84
- _key: payload.focusSpan.node._key,
85
- _type: payload.focusSpan.node._type,
86
- text: `${textBeforeMatch}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,
87
- marks: payload.markState.marks
88
- },
89
- path: payload.focusSpan.path,
90
- textBefore: textBeforeMatch,
91
- textAfter: payload.focusSpanTextAfter
92
- };
93
- return keywordState === "complete" ? [raise(createKeywordFoundEvent({
94
- focusSpan: focusSpan2,
95
- extractedKeyword: payload.extractedKeyword,
96
- pickerId
97
- }))] : [raise(createTriggerFoundEvent({
98
- focusSpan: focusSpan2,
99
- extractedKeyword: payload.extractedKeyword,
100
- pickerId
101
- }))];
102
- }
103
- const newSpan = {
104
- _key: snapshot.context.keyGenerator(),
105
- _type: payload.focusSpan.node._type,
106
- text: payload.lastMatch.text,
107
- marks: payload.markState.marks
108
- };
109
- let focusSpan = {
110
- node: {
111
- _key: newSpan._key,
112
- _type: newSpan._type,
113
- text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,
114
- marks: payload.markState.marks
115
- },
116
- path: [...payload.focusSpan.path.slice(0, -2), "children", {
117
- _key: newSpan._key
118
- }],
119
- textBefore: "",
120
- textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter
121
- };
122
- return payload.previousSpan && payload.focusSpanTextBefore.length === 0 && JSON.stringify(payload.previousSpan.node.marks ?? []) === JSON.stringify(payload.markState.marks) && (focusSpan = {
123
- node: {
124
- _key: payload.previousSpan.node._key,
125
- _type: newSpan._type,
126
- text: `${payload.previousSpan.node.text}${newSpan.text}`,
127
- marks: newSpan.marks
128
- },
129
- path: payload.previousSpan.path,
130
- textBefore: payload.previousSpan.node.text,
131
- textAfter: ""
132
- }), [raise({
133
- type: "select",
134
- at: payload.lastMatch.targetOffsets
135
- }), raise({
136
- type: "delete",
137
- at: payload.lastMatch.targetOffsets
138
- }), raise({
139
- type: "insert.child",
140
- child: newSpan
141
- }), ...keywordState === "complete" ? [raise(createKeywordFoundEvent({
142
- focusSpan,
143
- extractedKeyword: payload.extractedKeyword,
144
- pickerId
145
- }))] : [raise(createTriggerFoundEvent({
146
- focusSpan,
147
- extractedKeyword: payload.extractedKeyword,
148
- pickerId
149
- }))]];
69
+ function createTriggerActions({ snapshot, payload, keywordState, pickerId }) {
70
+ if (payload.markState.state === "unchanged") {
71
+ let textBeforeMatch = payload.focusSpanTextBefore.slice(0, payload.lastMatch.targetOffsets.anchor.offset), focusSpan = {
72
+ node: {
73
+ _key: payload.focusSpan.node._key,
74
+ _type: payload.focusSpan.node._type,
75
+ text: `${textBeforeMatch}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,
76
+ marks: payload.markState.marks
77
+ },
78
+ path: payload.focusSpan.path,
79
+ textBefore: textBeforeMatch,
80
+ textAfter: payload.focusSpanTextAfter
81
+ };
82
+ return keywordState === "complete" ? [raise(createKeywordFoundEvent({
83
+ focusSpan,
84
+ extractedKeyword: payload.extractedKeyword,
85
+ pickerId
86
+ }))] : [raise(createTriggerFoundEvent({
87
+ focusSpan,
88
+ extractedKeyword: payload.extractedKeyword,
89
+ pickerId
90
+ }))];
91
+ }
92
+ let newSpan = {
93
+ _key: snapshot.context.keyGenerator(),
94
+ _type: payload.focusSpan.node._type,
95
+ text: payload.lastMatch.text,
96
+ marks: payload.markState.marks
97
+ }, focusSpan = {
98
+ node: {
99
+ _key: newSpan._key,
100
+ _type: newSpan._type,
101
+ text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,
102
+ marks: payload.markState.marks
103
+ },
104
+ path: [
105
+ ...payload.focusSpan.path.slice(0, -2),
106
+ "children",
107
+ { _key: newSpan._key }
108
+ ],
109
+ textBefore: "",
110
+ textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter
111
+ };
112
+ return payload.previousSpan && payload.focusSpanTextBefore.length === 0 && JSON.stringify(payload.previousSpan.node.marks ?? []) === JSON.stringify(payload.markState.marks) && (focusSpan = {
113
+ node: {
114
+ _key: payload.previousSpan.node._key,
115
+ _type: newSpan._type,
116
+ text: `${payload.previousSpan.node.text}${newSpan.text}`,
117
+ marks: newSpan.marks
118
+ },
119
+ path: payload.previousSpan.path,
120
+ textBefore: payload.previousSpan.node.text,
121
+ textAfter: ""
122
+ }), [
123
+ raise({
124
+ type: "select",
125
+ at: payload.lastMatch.targetOffsets
126
+ }),
127
+ raise({
128
+ type: "delete",
129
+ at: payload.lastMatch.targetOffsets
130
+ }),
131
+ raise({
132
+ type: "insert.child",
133
+ child: newSpan
134
+ }),
135
+ ...keywordState === "complete" ? [raise(createKeywordFoundEvent({
136
+ focusSpan,
137
+ extractedKeyword: payload.extractedKeyword,
138
+ pickerId
139
+ }))] : [raise(createTriggerFoundEvent({
140
+ focusSpan,
141
+ extractedKeyword: payload.extractedKeyword,
142
+ pickerId
143
+ }))]
144
+ ];
150
145
  }
151
146
  function createTriggerFoundEvent(payload) {
152
- return {
153
- type: "custom.typeahead trigger found",
154
- ...payload
155
- };
147
+ return {
148
+ type: "custom.typeahead trigger found",
149
+ ...payload
150
+ };
156
151
  }
157
152
  function createKeywordFoundEvent(payload) {
158
- return {
159
- type: "custom.typeahead keyword found",
160
- ...payload
161
- };
153
+ return {
154
+ type: "custom.typeahead keyword found",
155
+ ...payload
156
+ };
162
157
  }
158
+ /**
159
+ * Extract the pattern text (trigger + keyword) from focus span data.
160
+ */
163
161
  function extractPatternTextFromFocusSpan(focusSpan) {
164
- return focusSpan.textBefore.length > 0 && focusSpan.textAfter.length > 0 ? focusSpan.node.text.slice(focusSpan.textBefore.length, -focusSpan.textAfter.length) : focusSpan.textBefore.length > 0 ? focusSpan.node.text.slice(focusSpan.textBefore.length) : focusSpan.textAfter.length > 0 ? focusSpan.node.text.slice(0, -focusSpan.textAfter.length) : focusSpan.node.text;
162
+ return focusSpan.textBefore.length > 0 && focusSpan.textAfter.length > 0 ? focusSpan.node.text.slice(focusSpan.textBefore.length, -focusSpan.textAfter.length) : focusSpan.textBefore.length > 0 ? focusSpan.node.text.slice(focusSpan.textBefore.length) : focusSpan.textAfter.length > 0 ? focusSpan.node.text.slice(0, -focusSpan.textAfter.length) : focusSpan.node.text;
165
163
  }
166
164
  function createInputRules(definition) {
167
- const rules = [], triggerPattern = buildTriggerPattern(definition), partialPattern = buildPartialPattern(definition), completePattern = buildCompletePattern(definition);
168
- if (completePattern) {
169
- const completeRule = defineInputRule({
170
- on: completePattern,
171
- guard: ({
172
- snapshot,
173
- event
174
- }) => {
175
- const lastMatch = event.matches.at(-1);
176
- if (lastMatch === void 0)
177
- return !1;
178
- if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {
179
- const insertedMatch = event.textInserted.match(completePattern);
180
- if (insertedMatch === null || insertedMatch.index !== 0)
181
- return !1;
182
- const triggerState2 = getTriggerState(snapshot);
183
- return triggerState2 ? {
184
- ...triggerState2,
185
- lastMatch: {
186
- ...lastMatch,
187
- text: event.textInserted,
188
- targetOffsets: {
189
- ...lastMatch.targetOffsets,
190
- anchor: {
191
- ...lastMatch.targetOffsets.anchor,
192
- offset: event.textBefore.length
193
- }
194
- }
195
- },
196
- extractedKeyword: extractKeyword(event.textInserted, triggerPattern, definition.delimiter, completePattern)
197
- } : !1;
198
- }
199
- const triggerState = getTriggerState(snapshot);
200
- return triggerState ? {
201
- ...triggerState,
202
- lastMatch,
203
- extractedKeyword: extractKeyword(lastMatch.text, triggerPattern, definition.delimiter, completePattern)
204
- } : !1;
205
- },
206
- actions: [({
207
- snapshot
208
- }, payload) => createTriggerActions({
209
- snapshot,
210
- payload,
211
- keywordState: "complete",
212
- pickerId: definition._id
213
- })]
214
- });
215
- rules.push(completeRule);
216
- }
217
- const partialRule = defineInputRule({
218
- on: partialPattern,
219
- guard: ({
220
- snapshot,
221
- event
222
- }) => {
223
- const lastMatch = event.matches.at(-1);
224
- if (lastMatch === void 0)
225
- return !1;
226
- if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {
227
- const insertedMatch = event.textInserted.match(partialPattern);
228
- if (insertedMatch === null || insertedMatch.index !== 0)
229
- return !1;
230
- if (completePattern) {
231
- const completeMatch = event.textInserted.match(completePattern);
232
- if (completeMatch !== null && completeMatch.index === 0 && completeMatch[0] === event.textInserted)
233
- return !1;
234
- }
235
- const triggerState2 = getTriggerState(snapshot);
236
- return triggerState2 ? {
237
- ...triggerState2,
238
- lastMatch: {
239
- ...lastMatch,
240
- text: event.textInserted,
241
- targetOffsets: {
242
- ...lastMatch.targetOffsets,
243
- anchor: {
244
- ...lastMatch.targetOffsets.anchor,
245
- offset: event.textBefore.length
246
- }
247
- }
248
- },
249
- extractedKeyword: extractKeyword(event.textInserted, triggerPattern, definition.delimiter)
250
- } : !1;
251
- }
252
- const triggerState = getTriggerState(snapshot);
253
- return triggerState ? {
254
- ...triggerState,
255
- lastMatch,
256
- extractedKeyword: extractKeyword(lastMatch.text, triggerPattern, definition.delimiter)
257
- } : !1;
258
- },
259
- actions: [({
260
- snapshot
261
- }, payload) => createTriggerActions({
262
- snapshot,
263
- payload,
264
- keywordState: "partial",
265
- pickerId: definition._id
266
- })]
267
- });
268
- rules.push(partialRule);
269
- const triggerRule = defineInputRule({
270
- on: triggerPattern,
271
- guard: ({
272
- snapshot,
273
- event
274
- }) => {
275
- const lastMatch = event.matches.at(-1);
276
- if (lastMatch === void 0 || event.textInserted !== lastMatch.text)
277
- return !1;
278
- const triggerState = getTriggerState(snapshot);
279
- return triggerState ? {
280
- ...triggerState,
281
- lastMatch,
282
- extractedKeyword: ""
283
- } : !1;
284
- },
285
- actions: [({
286
- snapshot
287
- }, payload) => createTriggerActions({
288
- snapshot,
289
- payload,
290
- keywordState: "partial",
291
- pickerId: definition._id
292
- })]
293
- });
294
- return rules.push(triggerRule), rules;
165
+ let rules = [], triggerPattern = buildTriggerPattern(definition), partialPattern = buildPartialPattern(definition), completePattern = buildCompletePattern(definition);
166
+ if (completePattern) {
167
+ let completeRule = defineInputRule({
168
+ on: completePattern,
169
+ guard: ({ snapshot, event }) => {
170
+ let lastMatch = event.matches.at(-1);
171
+ if (lastMatch === void 0) return !1;
172
+ if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {
173
+ let insertedMatch = event.textInserted.match(completePattern);
174
+ if (insertedMatch === null || insertedMatch.index !== 0) return !1;
175
+ let triggerState = getTriggerState(snapshot);
176
+ return triggerState ? {
177
+ ...triggerState,
178
+ lastMatch: {
179
+ ...lastMatch,
180
+ text: event.textInserted,
181
+ targetOffsets: {
182
+ ...lastMatch.targetOffsets,
183
+ anchor: {
184
+ ...lastMatch.targetOffsets.anchor,
185
+ offset: event.textBefore.length
186
+ }
187
+ }
188
+ },
189
+ extractedKeyword: extractKeyword(event.textInserted, triggerPattern, definition.delimiter, completePattern)
190
+ } : !1;
191
+ }
192
+ let triggerState = getTriggerState(snapshot);
193
+ return triggerState ? {
194
+ ...triggerState,
195
+ lastMatch,
196
+ extractedKeyword: extractKeyword(lastMatch.text, triggerPattern, definition.delimiter, completePattern)
197
+ } : !1;
198
+ },
199
+ actions: [({ snapshot }, payload) => createTriggerActions({
200
+ snapshot,
201
+ payload,
202
+ keywordState: "complete",
203
+ pickerId: definition._id
204
+ })]
205
+ });
206
+ rules.push(completeRule);
207
+ }
208
+ let partialRule = defineInputRule({
209
+ on: partialPattern,
210
+ guard: ({ snapshot, event }) => {
211
+ let lastMatch = event.matches.at(-1);
212
+ if (lastMatch === void 0) return !1;
213
+ if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {
214
+ let insertedMatch = event.textInserted.match(partialPattern);
215
+ if (insertedMatch === null || insertedMatch.index !== 0) return !1;
216
+ if (completePattern) {
217
+ let completeMatch = event.textInserted.match(completePattern);
218
+ if (completeMatch !== null && completeMatch.index === 0 && completeMatch[0] === event.textInserted) return !1;
219
+ }
220
+ let triggerState = getTriggerState(snapshot);
221
+ return triggerState ? {
222
+ ...triggerState,
223
+ lastMatch: {
224
+ ...lastMatch,
225
+ text: event.textInserted,
226
+ targetOffsets: {
227
+ ...lastMatch.targetOffsets,
228
+ anchor: {
229
+ ...lastMatch.targetOffsets.anchor,
230
+ offset: event.textBefore.length
231
+ }
232
+ }
233
+ },
234
+ extractedKeyword: extractKeyword(event.textInserted, triggerPattern, definition.delimiter)
235
+ } : !1;
236
+ }
237
+ let triggerState = getTriggerState(snapshot);
238
+ return triggerState ? {
239
+ ...triggerState,
240
+ lastMatch,
241
+ extractedKeyword: extractKeyword(lastMatch.text, triggerPattern, definition.delimiter)
242
+ } : !1;
243
+ },
244
+ actions: [({ snapshot }, payload) => createTriggerActions({
245
+ snapshot,
246
+ payload,
247
+ keywordState: "partial",
248
+ pickerId: definition._id
249
+ })]
250
+ });
251
+ rules.push(partialRule);
252
+ let triggerRule = defineInputRule({
253
+ on: triggerPattern,
254
+ guard: ({ snapshot, event }) => {
255
+ let lastMatch = event.matches.at(-1);
256
+ if (lastMatch === void 0 || event.textInserted !== lastMatch.text) return !1;
257
+ let triggerState = getTriggerState(snapshot);
258
+ return triggerState ? {
259
+ ...triggerState,
260
+ lastMatch,
261
+ extractedKeyword: ""
262
+ } : !1;
263
+ },
264
+ actions: [({ snapshot }, payload) => createTriggerActions({
265
+ snapshot,
266
+ payload,
267
+ keywordState: "partial",
268
+ pickerId: definition._id
269
+ })]
270
+ });
271
+ return rules.push(triggerRule), rules;
295
272
  }
296
273
  function createTriggerGuard(definition) {
297
- return ({
298
- event,
299
- snapshot,
300
- dom
301
- }) => event.pickerId !== definition._id ? !1 : definition.guard ? definition.guard({
302
- snapshot,
303
- dom,
304
- event: {
305
- type: "custom.typeahead trigger found"
306
- }
307
- }) : !0;
274
+ return ({ event, snapshot, dom }) => event.pickerId === definition._id ? !definition.guard || definition.guard({
275
+ snapshot,
276
+ dom,
277
+ event: { type: "custom.typeahead trigger found" }
278
+ }) : !1;
308
279
  }
309
- const triggerListenerCallback = () => ({
310
- sendBack,
311
- input
312
- }) => {
313
- const rules = createInputRules(input.definition), triggerGuard = createTriggerGuard(input.definition), unregisterBehaviors = [input.editor.registerBehavior({
314
- behavior: defineInputRuleBehavior({
315
- rules
316
- })
317
- }), input.editor.registerBehavior({
318
- behavior: defineBehavior({
319
- on: "custom.typeahead keyword found",
320
- guard: triggerGuard,
321
- actions: [({
322
- event
323
- }) => [effect(() => {
324
- sendBack(event);
325
- })]]
326
- })
327
- }), input.editor.registerBehavior({
328
- behavior: defineBehavior({
329
- on: "custom.typeahead trigger found",
330
- guard: triggerGuard,
331
- actions: [({
332
- event
333
- }) => [effect(() => {
334
- sendBack(event);
335
- })]]
336
- })
337
- })];
338
- return () => {
339
- for (const unregister of unregisterBehaviors)
340
- unregister();
341
- };
342
- }, escapeListenerCallback = () => ({
343
- sendBack,
344
- input,
345
- receive
346
- }) => {
347
- let context = input.context;
348
- return receive((event) => {
349
- context = event.context;
350
- }), input.context.editor.registerBehavior({
351
- behavior: defineBehavior({
352
- on: "keyboard.keydown",
353
- guard: ({
354
- event
355
- }) => escapeShortcut.guard(event.originEvent),
356
- actions: [({
357
- snapshot,
358
- dom
359
- }) => {
360
- if (!context.focusSpan || !context.definition.onDismiss)
361
- return [effect(() => sendBack({
362
- type: "close"
363
- }))];
364
- const patternSelection = {
365
- anchor: {
366
- path: context.focusSpan.path,
367
- offset: context.focusSpan.textBefore.length
368
- },
369
- focus: {
370
- path: context.focusSpan.path,
371
- offset: context.focusSpan.node.text.length - context.focusSpan.textAfter.length
372
- }
373
- };
374
- return [...context.definition.onDismiss.flatMap((actionSet) => actionSet({
375
- snapshot,
376
- dom,
377
- event: {
378
- type: "custom.typeahead dismiss",
379
- patternSelection
380
- }
381
- }, !0)), effect(() => sendBack({
382
- type: "close"
383
- }))];
384
- }]
385
- })
386
- });
387
- }, arrowListenerCallback = () => ({
388
- sendBack,
389
- input
390
- }) => {
391
- const unregisterBehaviors = [input.editor.registerBehavior({
392
- behavior: defineBehavior({
393
- on: "keyboard.keydown",
394
- guard: ({
395
- event
396
- }) => arrowDownShortcut.guard(event.originEvent),
397
- actions: [() => [effect(() => {
398
- sendBack({
399
- type: "navigate down"
400
- });
401
- })]]
402
- })
403
- }), input.editor.registerBehavior({
404
- behavior: defineBehavior({
405
- on: "keyboard.keydown",
406
- guard: ({
407
- event
408
- }) => arrowUpShortcut.guard(event.originEvent),
409
- actions: [() => [effect(() => {
410
- sendBack({
411
- type: "navigate up"
412
- });
413
- })]]
414
- })
415
- })];
416
- return () => {
417
- for (const unregister of unregisterBehaviors)
418
- unregister();
419
- };
420
- }, selectionListenerCallback = () => ({
421
- sendBack,
422
- input
423
- }) => input.editor.on("selection", () => {
424
- sendBack({
425
- type: "selection changed"
426
- });
427
- }).unsubscribe, dismissListenerCallback = () => ({
428
- sendBack,
429
- input,
430
- receive
431
- }) => {
432
- let context = input.context;
433
- return receive((event) => {
434
- context = event.context;
435
- }), input.context.editor.registerBehavior({
436
- behavior: defineBehavior({
437
- on: "custom.typeahead dismiss",
438
- guard: ({
439
- event
440
- }) => event.pickerId === context.definition._id,
441
- actions: [({
442
- snapshot,
443
- dom
444
- }) => {
445
- if (!context.focusSpan || !context.definition.onDismiss)
446
- return [effect(() => sendBack({
447
- type: "close"
448
- }))];
449
- const patternSelection = {
450
- anchor: {
451
- path: context.focusSpan.path,
452
- offset: context.focusSpan.textBefore.length
453
- },
454
- focus: {
455
- path: context.focusSpan.path,
456
- offset: context.focusSpan.node.text.length - context.focusSpan.textAfter.length
457
- }
458
- };
459
- return [...context.definition.onDismiss.flatMap((actionSet) => actionSet({
460
- snapshot,
461
- dom,
462
- event: {
463
- type: "custom.typeahead dismiss",
464
- patternSelection
465
- }
466
- }, !0)), effect(() => sendBack({
467
- type: "close"
468
- }))];
469
- }]
470
- })
471
- });
472
- }, submitListenerCallback = () => ({
473
- sendBack,
474
- input,
475
- receive
476
- }) => {
477
- let context = input.context;
478
- receive((event) => {
479
- context = event.context;
480
- });
481
- const unregisterBehaviors = [input.context.editor.registerBehavior({
482
- behavior: defineBehavior({
483
- on: "keyboard.keydown",
484
- guard: ({
485
- event
486
- }) => {
487
- if (!enterShortcut.guard(event.originEvent) && !tabShortcut.guard(event.originEvent))
488
- return !1;
489
- const focusSpan = context.focusSpan, match = context.matches[context.selectedIndex];
490
- return match && focusSpan ? {
491
- focusSpan,
492
- match
493
- } : !1;
494
- },
495
- actions: [() => [effect(() => {
496
- sendBack({
497
- type: "select"
498
- });
499
- })]]
500
- })
501
- }), input.context.editor.registerBehavior({
502
- behavior: defineBehavior({
503
- on: "keyboard.keydown",
504
- guard: ({
505
- event
506
- }) => (enterShortcut.guard(event.originEvent) || tabShortcut.guard(event.originEvent)) && context.patternText.length === 1,
507
- actions: [({
508
- event
509
- }) => [forward(event), effect(() => {
510
- sendBack({
511
- type: "close"
512
- });
513
- })]]
514
- })
515
- }), input.context.editor.registerBehavior({
516
- behavior: defineBehavior({
517
- on: "keyboard.keydown",
518
- guard: ({
519
- event
520
- }) => (enterShortcut.guard(event.originEvent) || tabShortcut.guard(event.originEvent)) && context.patternText.length > 1 && context.matches.length === 0,
521
- actions: [({
522
- snapshot,
523
- dom
524
- }) => {
525
- if (!context.focusSpan || !context.definition.onDismiss)
526
- return [effect(() => sendBack({
527
- type: "close"
528
- }))];
529
- const patternSelection = {
530
- anchor: {
531
- path: context.focusSpan.path,
532
- offset: context.focusSpan.textBefore.length
533
- },
534
- focus: {
535
- path: context.focusSpan.path,
536
- offset: context.focusSpan.node.text.length - context.focusSpan.textAfter.length
537
- }
538
- };
539
- return [...context.definition.onDismiss.flatMap((actionSet) => actionSet({
540
- snapshot,
541
- dom,
542
- event: {
543
- type: "custom.typeahead dismiss",
544
- patternSelection
545
- }
546
- }, !0)), effect(() => sendBack({
547
- type: "close"
548
- }))];
549
- }]
550
- })
551
- })];
552
- return () => {
553
- for (const unregister of unregisterBehaviors)
554
- unregister();
555
- };
556
- }, textInsertionListenerCallback = () => ({
557
- sendBack,
558
- input,
559
- receive
560
- }) => {
561
- let context = input.context;
562
- return receive((event) => {
563
- context = event.context;
564
- }), input.context.editor.registerBehavior({
565
- behavior: defineBehavior({
566
- on: "insert.text",
567
- guard: ({
568
- snapshot
569
- }) => {
570
- if (!context.focusSpan || !snapshot.context.selection)
571
- return !1;
572
- const keywordAnchor = {
573
- path: context.focusSpan.path,
574
- offset: context.focusSpan.textBefore.length
575
- };
576
- return isEqualSelectionPoints(snapshot.context.selection.focus, keywordAnchor);
577
- },
578
- actions: [({
579
- event
580
- }) => [forward(event), effect(() => {
581
- sendBack({
582
- type: "close"
583
- });
584
- })]]
585
- })
586
- });
587
- }, selectMatchListenerCallback = () => ({
588
- sendBack,
589
- input
590
- }) => input.context.editor.registerBehavior({
591
- behavior: defineBehavior({
592
- on: "custom.typeahead select match",
593
- guard: ({
594
- event
595
- }) => event.pickerId === input.context.definition._id,
596
- actions: [({
597
- event,
598
- snapshot,
599
- dom
600
- }) => {
601
- const patternSelection = {
602
- anchor: {
603
- path: event.focusSpan.path,
604
- offset: event.focusSpan.textBefore.length
605
- },
606
- focus: {
607
- path: event.focusSpan.path,
608
- offset: event.focusSpan.node.text.length - event.focusSpan.textAfter.length
609
- }
610
- }, selectActions = input.context.definition.onSelect.flatMap((actionSet) => actionSet({
611
- snapshot,
612
- dom,
613
- event: {
614
- type: "custom.typeahead select",
615
- match: event.match,
616
- keyword: event.keyword,
617
- patternSelection
618
- }
619
- }, !0));
620
- return [effect(() => {
621
- sendBack({
622
- type: "close"
623
- });
624
- }), ...selectActions];
625
- }]
626
- })
627
- }), typeaheadPickerMachine = setup({
628
- types: {
629
- context: {},
630
- input: {},
631
- events: {}
632
- },
633
- delays: {
634
- DEBOUNCE: ({
635
- context
636
- }) => context.definition.debounceMs ?? 0
637
- },
638
- actors: {
639
- "trigger listener": fromCallback(triggerListenerCallback()),
640
- "escape listener": fromCallback(escapeListenerCallback()),
641
- "arrow listener": fromCallback(arrowListenerCallback()),
642
- "selection listener": fromCallback(selectionListenerCallback()),
643
- "submit listener": fromCallback(submitListenerCallback()),
644
- "text insertion listener": fromCallback(textInsertionListenerCallback()),
645
- "select match listener": fromCallback(selectMatchListenerCallback()),
646
- "dismiss listener": fromCallback(dismissListenerCallback()),
647
- "get matches": fromPromise(async ({
648
- input
649
- }) => {
650
- const result = input.getMatches({
651
- keyword: input.keyword
652
- }), matches = await Promise.resolve(result);
653
- return {
654
- keyword: input.keyword,
655
- matches
656
- };
657
- })
658
- },
659
- actions: {
660
- "handle trigger found": assign(({
661
- context,
662
- event
663
- }) => {
664
- if (event.type !== "custom.typeahead trigger found" && event.type !== "custom.typeahead keyword found")
665
- return {};
666
- const focusSpan = event.focusSpan, patternText = extractPatternTextFromFocusSpan(focusSpan), keyword = event.extractedKeyword;
667
- if (context.definition.mode === "async" || context.definition.debounceMs)
668
- return {
669
- focusSpan,
670
- patternText,
671
- keyword,
672
- isLoading: !0,
673
- selectedIndex: 0
674
- };
675
- const matches = context.definition.getMatches({
676
- keyword
677
- });
678
- return {
679
- focusSpan,
680
- patternText,
681
- keyword,
682
- matches,
683
- requestedKeyword: keyword,
684
- isLoading: !1,
685
- selectedIndex: 0
686
- };
687
- }),
688
- "handle selection changed": assign(({
689
- context
690
- }) => {
691
- if (!context.focusSpan)
692
- return {
693
- focusSpan: void 0
694
- };
695
- const snapshot = context.editor.getSnapshot(), currentFocusSpan = getFocusSpan(snapshot);
696
- if (!snapshot.context.selection || !currentFocusSpan)
697
- return {
698
- focusSpan: void 0
699
- };
700
- const nextSpan = getNextSpan({
701
- ...snapshot,
702
- context: {
703
- ...snapshot.context,
704
- selection: {
705
- anchor: {
706
- path: context.focusSpan.path,
707
- offset: 0
708
- },
709
- focus: {
710
- path: context.focusSpan.path,
711
- offset: 0
712
- }
713
- }
714
- }
715
- });
716
- if (!isEqualPaths(currentFocusSpan.path, context.focusSpan.path))
717
- return nextSpan && context.focusSpan.textAfter.length === 0 && snapshot.context.selection.focus.offset === 0 && isSelectionCollapsed(snapshot) ? {} : {
718
- focusSpan: void 0
719
- };
720
- if (!currentFocusSpan.node.text.startsWith(context.focusSpan.textBefore))
721
- return {
722
- focusSpan: void 0
723
- };
724
- if (!currentFocusSpan.node.text.endsWith(context.focusSpan.textAfter))
725
- return {
726
- focusSpan: void 0
727
- };
728
- const keywordAnchor = {
729
- path: currentFocusSpan.path,
730
- offset: context.focusSpan.textBefore.length
731
- }, keywordFocus = {
732
- path: currentFocusSpan.path,
733
- offset: currentFocusSpan.node.text.length - context.focusSpan.textAfter.length
734
- }, selectionIsBeforeKeyword = isPointAfterSelection(keywordAnchor)(snapshot), selectionIsAfterKeyword = isPointBeforeSelection(keywordFocus)(snapshot);
735
- if (selectionIsBeforeKeyword || selectionIsAfterKeyword)
736
- return {
737
- focusSpan: void 0
738
- };
739
- const focusSpan = {
740
- node: currentFocusSpan.node,
741
- path: currentFocusSpan.path,
742
- textBefore: context.focusSpan.textBefore,
743
- textAfter: context.focusSpan.textAfter
744
- }, patternText = extractPatternTextFromFocusSpan(focusSpan), keyword = extractKeyword(patternText, context.triggerPattern, context.definition.delimiter, context.completePattern);
745
- if (context.definition.mode === "async" || context.definition.debounceMs)
746
- return {
747
- focusSpan,
748
- patternText,
749
- keyword,
750
- selectedIndex: patternText !== context.patternText ? 0 : context.selectedIndex,
751
- isLoading: context.isLoading || context.requestedKeyword !== keyword
752
- };
753
- const matches = context.definition.getMatches({
754
- keyword
755
- });
756
- return {
757
- focusSpan,
758
- patternText,
759
- keyword,
760
- matches,
761
- requestedKeyword: keyword,
762
- selectedIndex: patternText !== context.patternText ? 0 : context.selectedIndex,
763
- isLoading: !1
764
- };
765
- }),
766
- "handle async load complete": assign(({
767
- context,
768
- event
769
- }) => {
770
- const output = event.output;
771
- return output.keyword !== context.keyword ? {
772
- isLoading: context.keyword !== context.requestedKeyword
773
- } : {
774
- matches: output.matches,
775
- isLoading: context.keyword !== context.requestedKeyword
776
- };
777
- }),
778
- reset: assign({
779
- patternText: "",
780
- keyword: "",
781
- matches: [],
782
- selectedIndex: 0,
783
- isLoading: !1,
784
- requestedKeyword: "",
785
- focusSpan: void 0,
786
- error: void 0
787
- }),
788
- navigate: assign(({
789
- context,
790
- event
791
- }) => context.matches.length === 0 ? {
792
- selectedIndex: 0
793
- } : event.type === "navigate to" ? {
794
- selectedIndex: event.index
795
- } : event.type === "navigate up" ? {
796
- selectedIndex: (context.selectedIndex - 1 + context.matches.length) % context.matches.length
797
- } : {
798
- selectedIndex: (context.selectedIndex + 1) % context.matches.length
799
- }),
800
- "select match": ({
801
- context
802
- }, params) => {
803
- if (!context.focusSpan)
804
- return;
805
- const match = params.exact ? getFirstExactMatch(context.matches) : context.matches[context.selectedIndex];
806
- match && context.editor.send({
807
- type: "custom.typeahead select match",
808
- match,
809
- focusSpan: context.focusSpan,
810
- keyword: context.keyword,
811
- pickerId: context.definition._id
812
- });
813
- },
814
- "update submit listener context": sendTo("submit listener", ({
815
- context
816
- }) => ({
817
- type: "context changed",
818
- context
819
- })),
820
- "update text insertion listener context": sendTo("text insertion listener", ({
821
- context
822
- }) => ({
823
- type: "context changed",
824
- context
825
- })),
826
- "update escape listener context": sendTo("escape listener", ({
827
- context
828
- }) => ({
829
- type: "context changed",
830
- context
831
- })),
832
- "update request dismiss listener context": sendTo("dismiss listener", ({
833
- context
834
- }) => ({
835
- type: "context changed",
836
- context
837
- })),
838
- "handle error": assign({
839
- isLoading: !1,
840
- error: ({
841
- event
842
- }) => event.error
843
- })
844
- },
845
- guards: {
846
- "no focus span": ({
847
- context
848
- }) => !context.focusSpan,
849
- "invalid pattern": ({
850
- context
851
- }) => {
852
- if (!context.patternText)
853
- return !0;
854
- const triggerMatch = context.patternText.match(context.triggerPattern);
855
- if (triggerMatch && triggerMatch.index === 0 && triggerMatch[0] === context.patternText)
856
- return !1;
857
- const partialMatch = context.patternText.match(context.partialPattern);
858
- if (partialMatch && partialMatch.index === 0 && partialMatch[0] === context.patternText)
859
- return !1;
860
- if (context.completePattern) {
861
- const completeMatch = context.patternText.match(context.completePattern);
862
- if (completeMatch && completeMatch.index === 0 && completeMatch[0] === context.patternText)
863
- return !1;
864
- }
865
- return !0;
866
- },
867
- "no debounce": ({
868
- context
869
- }) => !context.definition.debounceMs || context.definition.debounceMs === 0,
870
- "is complete keyword": ({
871
- context
872
- }) => {
873
- if (!context.completePattern || !context.focusSpan)
874
- return !1;
875
- const fullKeywordText = context.focusSpan.node.text.slice(context.focusSpan.textBefore.length, context.focusSpan.textAfter.length > 0 ? -context.focusSpan.textAfter.length : void 0), completeMatch = fullKeywordText.match(context.completePattern);
876
- return !completeMatch || completeMatch.index !== 0 || completeMatch[0] !== fullKeywordText ? !1 : hasAtLeastOneExactMatch(context.matches);
877
- },
878
- "has matches": ({
879
- context
880
- }) => context.matches.length > 0,
881
- "no matches": ({
882
- context
883
- }) => context.matches.length === 0,
884
- "is loading": ({
885
- context
886
- }) => context.isLoading
887
- }
280
+ const triggerListenerCallback = () => ({ sendBack, input }) => {
281
+ let rules = createInputRules(input.definition), triggerGuard = createTriggerGuard(input.definition), unregisterBehaviors = [
282
+ input.editor.registerBehavior({ behavior: defineInputRuleBehavior({ rules }) }),
283
+ input.editor.registerBehavior({ behavior: defineBehavior({
284
+ on: "custom.typeahead keyword found",
285
+ guard: triggerGuard,
286
+ actions: [({ event }) => [effect(() => {
287
+ sendBack(event);
288
+ })]]
289
+ }) }),
290
+ input.editor.registerBehavior({ behavior: defineBehavior({
291
+ on: "custom.typeahead trigger found",
292
+ guard: triggerGuard,
293
+ actions: [({ event }) => [effect(() => {
294
+ sendBack(event);
295
+ })]]
296
+ }) })
297
+ ];
298
+ return () => {
299
+ for (let unregister of unregisterBehaviors) unregister();
300
+ };
301
+ }, escapeListenerCallback = () => ({ sendBack, input, receive }) => {
302
+ let context = input.context;
303
+ return receive((event) => {
304
+ context = event.context;
305
+ }), input.context.editor.registerBehavior({ behavior: defineBehavior({
306
+ on: "keyboard.keydown",
307
+ guard: ({ event }) => escapeShortcut.guard(event.originEvent),
308
+ actions: [({ snapshot, dom }) => {
309
+ if (!context.focusSpan || !context.definition.onDismiss) return [effect(() => sendBack({ type: "close" }))];
310
+ let patternSelection = {
311
+ anchor: {
312
+ path: context.focusSpan.path,
313
+ offset: context.focusSpan.textBefore.length
314
+ },
315
+ focus: {
316
+ path: context.focusSpan.path,
317
+ offset: context.focusSpan.node.text.length - context.focusSpan.textAfter.length
318
+ }
319
+ };
320
+ return [...context.definition.onDismiss.flatMap((actionSet) => actionSet({
321
+ snapshot,
322
+ dom,
323
+ event: {
324
+ type: "custom.typeahead dismiss",
325
+ patternSelection
326
+ }
327
+ }, !0)), effect(() => sendBack({ type: "close" }))];
328
+ }]
329
+ }) });
330
+ }, arrowListenerCallback = () => ({ sendBack, input }) => {
331
+ let unregisterBehaviors = [input.editor.registerBehavior({ behavior: defineBehavior({
332
+ on: "keyboard.keydown",
333
+ guard: ({ event }) => arrowDownShortcut.guard(event.originEvent),
334
+ actions: [() => [effect(() => {
335
+ sendBack({ type: "navigate down" });
336
+ })]]
337
+ }) }), input.editor.registerBehavior({ behavior: defineBehavior({
338
+ on: "keyboard.keydown",
339
+ guard: ({ event }) => arrowUpShortcut.guard(event.originEvent),
340
+ actions: [() => [effect(() => {
341
+ sendBack({ type: "navigate up" });
342
+ })]]
343
+ }) })];
344
+ return () => {
345
+ for (let unregister of unregisterBehaviors) unregister();
346
+ };
347
+ }, selectionListenerCallback = () => ({ sendBack, input }) => input.editor.on("selection", () => {
348
+ sendBack({ type: "selection changed" });
349
+ }).unsubscribe, dismissListenerCallback = () => ({ sendBack, input, receive }) => {
350
+ let context = input.context;
351
+ return receive((event) => {
352
+ context = event.context;
353
+ }), input.context.editor.registerBehavior({ behavior: defineBehavior({
354
+ on: "custom.typeahead dismiss",
355
+ guard: ({ event }) => event.pickerId === context.definition._id,
356
+ actions: [({ snapshot, dom }) => {
357
+ if (!context.focusSpan || !context.definition.onDismiss) return [effect(() => sendBack({ type: "close" }))];
358
+ let patternSelection = {
359
+ anchor: {
360
+ path: context.focusSpan.path,
361
+ offset: context.focusSpan.textBefore.length
362
+ },
363
+ focus: {
364
+ path: context.focusSpan.path,
365
+ offset: context.focusSpan.node.text.length - context.focusSpan.textAfter.length
366
+ }
367
+ };
368
+ return [...context.definition.onDismiss.flatMap((actionSet) => actionSet({
369
+ snapshot,
370
+ dom,
371
+ event: {
372
+ type: "custom.typeahead dismiss",
373
+ patternSelection
374
+ }
375
+ }, !0)), effect(() => sendBack({ type: "close" }))];
376
+ }]
377
+ }) });
378
+ }, submitListenerCallback = () => ({ sendBack, input, receive }) => {
379
+ let context = input.context;
380
+ receive((event) => {
381
+ context = event.context;
382
+ });
383
+ let unregisterBehaviors = [
384
+ input.context.editor.registerBehavior({ behavior: defineBehavior({
385
+ on: "keyboard.keydown",
386
+ guard: ({ event }) => {
387
+ if (!enterShortcut.guard(event.originEvent) && !tabShortcut.guard(event.originEvent)) return !1;
388
+ let focusSpan = context.focusSpan, match = context.matches[context.selectedIndex];
389
+ return match && focusSpan ? {
390
+ focusSpan,
391
+ match
392
+ } : !1;
393
+ },
394
+ actions: [() => [effect(() => {
395
+ sendBack({ type: "select" });
396
+ })]]
397
+ }) }),
398
+ input.context.editor.registerBehavior({ behavior: defineBehavior({
399
+ on: "keyboard.keydown",
400
+ guard: ({ event }) => (enterShortcut.guard(event.originEvent) || tabShortcut.guard(event.originEvent)) && context.patternText.length === 1,
401
+ actions: [({ event }) => [forward(event), effect(() => {
402
+ sendBack({ type: "close" });
403
+ })]]
404
+ }) }),
405
+ input.context.editor.registerBehavior({ behavior: defineBehavior({
406
+ on: "keyboard.keydown",
407
+ guard: ({ event }) => (enterShortcut.guard(event.originEvent) || tabShortcut.guard(event.originEvent)) && context.patternText.length > 1 && context.matches.length === 0,
408
+ actions: [({ snapshot, dom }) => {
409
+ if (!context.focusSpan || !context.definition.onDismiss) return [effect(() => sendBack({ type: "close" }))];
410
+ let patternSelection = {
411
+ anchor: {
412
+ path: context.focusSpan.path,
413
+ offset: context.focusSpan.textBefore.length
414
+ },
415
+ focus: {
416
+ path: context.focusSpan.path,
417
+ offset: context.focusSpan.node.text.length - context.focusSpan.textAfter.length
418
+ }
419
+ };
420
+ return [...context.definition.onDismiss.flatMap((actionSet) => actionSet({
421
+ snapshot,
422
+ dom,
423
+ event: {
424
+ type: "custom.typeahead dismiss",
425
+ patternSelection
426
+ }
427
+ }, !0)), effect(() => sendBack({ type: "close" }))];
428
+ }]
429
+ }) })
430
+ ];
431
+ return () => {
432
+ for (let unregister of unregisterBehaviors) unregister();
433
+ };
434
+ }, textInsertionListenerCallback = () => ({ sendBack, input, receive }) => {
435
+ let context = input.context;
436
+ return receive((event) => {
437
+ context = event.context;
438
+ }), input.context.editor.registerBehavior({ behavior: defineBehavior({
439
+ on: "insert.text",
440
+ guard: ({ snapshot }) => {
441
+ if (!context.focusSpan || !snapshot.context.selection) return !1;
442
+ let keywordAnchor = {
443
+ path: context.focusSpan.path,
444
+ offset: context.focusSpan.textBefore.length
445
+ };
446
+ return isEqualSelectionPoints(snapshot.context.selection.focus, keywordAnchor);
447
+ },
448
+ actions: [({ event }) => [forward(event), effect(() => {
449
+ sendBack({ type: "close" });
450
+ })]]
451
+ }) });
452
+ }, selectMatchListenerCallback = () => ({ sendBack, input }) => input.context.editor.registerBehavior({ behavior: defineBehavior({
453
+ on: "custom.typeahead select match",
454
+ guard: ({ event }) => event.pickerId === input.context.definition._id,
455
+ actions: [({ event, snapshot, dom }) => {
456
+ let patternSelection = {
457
+ anchor: {
458
+ path: event.focusSpan.path,
459
+ offset: event.focusSpan.textBefore.length
460
+ },
461
+ focus: {
462
+ path: event.focusSpan.path,
463
+ offset: event.focusSpan.node.text.length - event.focusSpan.textAfter.length
464
+ }
465
+ }, selectActions = input.context.definition.onSelect.flatMap((actionSet) => actionSet({
466
+ snapshot,
467
+ dom,
468
+ event: {
469
+ type: "custom.typeahead select",
470
+ match: event.match,
471
+ keyword: event.keyword,
472
+ patternSelection
473
+ }
474
+ }, !0));
475
+ return [effect(() => {
476
+ sendBack({ type: "close" });
477
+ }), ...selectActions];
478
+ }]
479
+ }) }), typeaheadPickerMachine = setup({
480
+ types: {
481
+ context: {},
482
+ input: {},
483
+ events: {}
484
+ },
485
+ delays: { DEBOUNCE: ({ context }) => context.definition.debounceMs ?? 0 },
486
+ actors: {
487
+ "trigger listener": fromCallback(triggerListenerCallback()),
488
+ "escape listener": fromCallback(escapeListenerCallback()),
489
+ "arrow listener": fromCallback(arrowListenerCallback()),
490
+ "selection listener": fromCallback(selectionListenerCallback()),
491
+ "submit listener": fromCallback(submitListenerCallback()),
492
+ "text insertion listener": fromCallback(textInsertionListenerCallback()),
493
+ "select match listener": fromCallback(selectMatchListenerCallback()),
494
+ "dismiss listener": fromCallback(dismissListenerCallback()),
495
+ "get matches": fromPromise(async ({ input }) => {
496
+ let result = input.getMatches({ keyword: input.keyword }), matches = await Promise.resolve(result);
497
+ return {
498
+ keyword: input.keyword,
499
+ matches
500
+ };
501
+ })
502
+ },
503
+ actions: {
504
+ "handle trigger found": assign(({ context, event }) => {
505
+ if (event.type !== "custom.typeahead trigger found" && event.type !== "custom.typeahead keyword found") return {};
506
+ let focusSpan = event.focusSpan, patternText = extractPatternTextFromFocusSpan(focusSpan), keyword = event.extractedKeyword;
507
+ return context.definition.mode === "async" || context.definition.debounceMs ? {
508
+ focusSpan,
509
+ patternText,
510
+ keyword,
511
+ isLoading: !0,
512
+ selectedIndex: 0
513
+ } : {
514
+ focusSpan,
515
+ patternText,
516
+ keyword,
517
+ matches: context.definition.getMatches({ keyword }),
518
+ requestedKeyword: keyword,
519
+ isLoading: !1,
520
+ selectedIndex: 0
521
+ };
522
+ }),
523
+ "handle selection changed": assign(({ context }) => {
524
+ if (!context.focusSpan) return { focusSpan: void 0 };
525
+ let snapshot = context.editor.getSnapshot(), currentFocusSpan = getFocusSpan(snapshot);
526
+ if (!snapshot.context.selection || !currentFocusSpan) return { focusSpan: void 0 };
527
+ let nextSpan = getNextSpan({
528
+ ...snapshot,
529
+ context: {
530
+ ...snapshot.context,
531
+ selection: {
532
+ anchor: {
533
+ path: context.focusSpan.path,
534
+ offset: 0
535
+ },
536
+ focus: {
537
+ path: context.focusSpan.path,
538
+ offset: 0
539
+ }
540
+ }
541
+ }
542
+ });
543
+ if (!isEqualPaths(currentFocusSpan.path, context.focusSpan.path)) return nextSpan && context.focusSpan.textAfter.length === 0 && snapshot.context.selection.focus.offset === 0 && isSelectionCollapsed(snapshot) ? {} : { focusSpan: void 0 };
544
+ if (!currentFocusSpan.node.text.startsWith(context.focusSpan.textBefore) || !currentFocusSpan.node.text.endsWith(context.focusSpan.textAfter)) return { focusSpan: void 0 };
545
+ let keywordAnchor = {
546
+ path: currentFocusSpan.path,
547
+ offset: context.focusSpan.textBefore.length
548
+ }, keywordFocus = {
549
+ path: currentFocusSpan.path,
550
+ offset: currentFocusSpan.node.text.length - context.focusSpan.textAfter.length
551
+ }, selectionIsBeforeKeyword = isPointAfterSelection(keywordAnchor)(snapshot), selectionIsAfterKeyword = isPointBeforeSelection(keywordFocus)(snapshot);
552
+ if (selectionIsBeforeKeyword || selectionIsAfterKeyword) return { focusSpan: void 0 };
553
+ let focusSpan = {
554
+ node: currentFocusSpan.node,
555
+ path: currentFocusSpan.path,
556
+ textBefore: context.focusSpan.textBefore,
557
+ textAfter: context.focusSpan.textAfter
558
+ }, patternText = extractPatternTextFromFocusSpan(focusSpan), keyword = extractKeyword(patternText, context.triggerPattern, context.definition.delimiter, context.completePattern);
559
+ return context.definition.mode === "async" || context.definition.debounceMs ? {
560
+ focusSpan,
561
+ patternText,
562
+ keyword,
563
+ selectedIndex: patternText === context.patternText ? context.selectedIndex : 0,
564
+ isLoading: context.isLoading || context.requestedKeyword !== keyword
565
+ } : {
566
+ focusSpan,
567
+ patternText,
568
+ keyword,
569
+ matches: context.definition.getMatches({ keyword }),
570
+ requestedKeyword: keyword,
571
+ selectedIndex: patternText === context.patternText ? context.selectedIndex : 0,
572
+ isLoading: !1
573
+ };
574
+ }),
575
+ "handle async load complete": assign(({ context, event }) => {
576
+ let output = event.output;
577
+ return output.keyword === context.keyword ? {
578
+ matches: output.matches,
579
+ isLoading: context.keyword !== context.requestedKeyword
580
+ } : { isLoading: context.keyword !== context.requestedKeyword };
581
+ }),
582
+ reset: assign({
583
+ patternText: "",
584
+ keyword: "",
585
+ matches: [],
586
+ selectedIndex: 0,
587
+ isLoading: !1,
588
+ requestedKeyword: "",
589
+ focusSpan: void 0,
590
+ error: void 0
591
+ }),
592
+ navigate: assign(({ context, event }) => context.matches.length === 0 ? { selectedIndex: 0 } : event.type === "navigate to" ? { selectedIndex: event.index } : event.type === "navigate up" ? { selectedIndex: (context.selectedIndex - 1 + context.matches.length) % context.matches.length } : { selectedIndex: (context.selectedIndex + 1) % context.matches.length }),
593
+ "select match": ({ context }, params) => {
594
+ if (!context.focusSpan) return;
595
+ let match = params.exact ? getFirstExactMatch(context.matches) : context.matches[context.selectedIndex];
596
+ match && context.editor.send({
597
+ type: "custom.typeahead select match",
598
+ match,
599
+ focusSpan: context.focusSpan,
600
+ keyword: context.keyword,
601
+ pickerId: context.definition._id
602
+ });
603
+ },
604
+ "update submit listener context": sendTo("submit listener", ({ context }) => ({
605
+ type: "context changed",
606
+ context
607
+ })),
608
+ "update text insertion listener context": sendTo("text insertion listener", ({ context }) => ({
609
+ type: "context changed",
610
+ context
611
+ })),
612
+ "update escape listener context": sendTo("escape listener", ({ context }) => ({
613
+ type: "context changed",
614
+ context
615
+ })),
616
+ "update request dismiss listener context": sendTo("dismiss listener", ({ context }) => ({
617
+ type: "context changed",
618
+ context
619
+ })),
620
+ "handle error": assign({
621
+ isLoading: !1,
622
+ error: ({ event }) => event.error
623
+ })
624
+ },
625
+ guards: {
626
+ "no focus span": ({ context }) => !context.focusSpan,
627
+ "invalid pattern": ({ context }) => {
628
+ if (!context.patternText) return !0;
629
+ let triggerMatch = context.patternText.match(context.triggerPattern);
630
+ if (triggerMatch && triggerMatch.index === 0 && triggerMatch[0] === context.patternText) return !1;
631
+ let partialMatch = context.patternText.match(context.partialPattern);
632
+ if (partialMatch && partialMatch.index === 0 && partialMatch[0] === context.patternText) return !1;
633
+ if (context.completePattern) {
634
+ let completeMatch = context.patternText.match(context.completePattern);
635
+ if (completeMatch && completeMatch.index === 0 && completeMatch[0] === context.patternText) return !1;
636
+ }
637
+ return !0;
638
+ },
639
+ "no debounce": ({ context }) => !context.definition.debounceMs || context.definition.debounceMs === 0,
640
+ "is complete keyword": ({ context }) => {
641
+ if (!context.completePattern || !context.focusSpan) return !1;
642
+ let fullKeywordText = context.focusSpan.node.text.slice(context.focusSpan.textBefore.length, context.focusSpan.textAfter.length > 0 ? -context.focusSpan.textAfter.length : void 0), completeMatch = fullKeywordText.match(context.completePattern);
643
+ return !completeMatch || completeMatch.index !== 0 || completeMatch[0] !== fullKeywordText ? !1 : hasAtLeastOneExactMatch(context.matches);
644
+ },
645
+ "has matches": ({ context }) => context.matches.length > 0,
646
+ "no matches": ({ context }) => context.matches.length === 0,
647
+ "is loading": ({ context }) => context.isLoading
648
+ }
888
649
  }).createMachine({
889
- id: "typeahead picker",
890
- context: ({
891
- input
892
- }) => ({
893
- editor: input.editor,
894
- definition: input.definition,
895
- triggerPattern: buildTriggerPattern(input.definition),
896
- partialPattern: buildPartialPattern(input.definition),
897
- completePattern: buildCompletePattern(input.definition),
898
- matches: [],
899
- selectedIndex: 0,
900
- focusSpan: void 0,
901
- patternText: "",
902
- keyword: "",
903
- requestedKeyword: "",
904
- error: void 0,
905
- isLoading: !1
906
- }),
907
- initial: "idle",
908
- states: {
909
- idle: {
910
- entry: ["reset"],
911
- invoke: {
912
- src: "trigger listener",
913
- input: ({
914
- context
915
- }) => ({
916
- editor: context.editor,
917
- definition: context.definition
918
- })
919
- },
920
- on: {
921
- "custom.typeahead trigger found": {
922
- target: "active",
923
- actions: ["handle trigger found"]
924
- },
925
- "custom.typeahead keyword found": {
926
- target: "checking complete",
927
- actions: ["handle trigger found"]
928
- }
929
- }
930
- },
931
- "checking complete": {
932
- invoke: [{
933
- src: "select match listener",
934
- input: ({
935
- context
936
- }) => ({
937
- context
938
- })
939
- }, {
940
- src: "get matches",
941
- input: ({
942
- context
943
- }) => ({
944
- keyword: context.keyword,
945
- getMatches: context.definition.getMatches
946
- }),
947
- onDone: [{
948
- guard: ({
949
- event
950
- }) => hasAtLeastOneExactMatch(event.output.matches),
951
- target: "idle",
952
- actions: [assign({
953
- matches: ({
954
- event
955
- }) => event.output.matches
956
- }), {
957
- type: "select match",
958
- params: {
959
- exact: !0
960
- }
961
- }]
962
- }, {
963
- target: "active",
964
- actions: [assign({
965
- matches: ({
966
- event
967
- }) => event.output.matches
968
- })]
969
- }],
970
- onError: {
971
- target: "active.no matches",
972
- actions: ["handle error"]
973
- }
974
- }]
975
- },
976
- active: {
977
- invoke: [{
978
- src: "select match listener",
979
- input: ({
980
- context
981
- }) => ({
982
- context
983
- })
984
- }, {
985
- src: "escape listener",
986
- id: "escape listener",
987
- input: ({
988
- context
989
- }) => ({
990
- context
991
- })
992
- }, {
993
- src: "selection listener",
994
- input: ({
995
- context
996
- }) => ({
997
- editor: context.editor
998
- })
999
- }, {
1000
- src: "submit listener",
1001
- id: "submit listener",
1002
- input: ({
1003
- context
1004
- }) => ({
1005
- context
1006
- })
1007
- }, {
1008
- src: "text insertion listener",
1009
- id: "text insertion listener",
1010
- input: ({
1011
- context
1012
- }) => ({
1013
- context
1014
- })
1015
- }, {
1016
- src: "dismiss listener",
1017
- id: "dismiss listener",
1018
- input: ({
1019
- context
1020
- }) => ({
1021
- context
1022
- })
1023
- }],
1024
- on: {
1025
- close: {
1026
- target: "idle"
1027
- },
1028
- "selection changed": {
1029
- actions: ["handle selection changed", "update submit listener context", "update text insertion listener context", "update escape listener context", "update request dismiss listener context"]
1030
- }
1031
- },
1032
- always: [{
1033
- guard: "no focus span",
1034
- target: "idle"
1035
- }, {
1036
- guard: "invalid pattern",
1037
- target: "idle"
1038
- }, {
1039
- guard: "is complete keyword",
1040
- actions: [{
1041
- type: "select match",
1042
- params: {
1043
- exact: !1
1044
- }
1045
- }],
1046
- target: "idle"
1047
- }],
1048
- initial: "evaluating",
1049
- states: {
1050
- evaluating: {
1051
- always: [{
1052
- guard: "is loading",
1053
- target: "loading"
1054
- }, {
1055
- guard: "has matches",
1056
- target: "showing matches"
1057
- }, {
1058
- target: "no matches"
1059
- }]
1060
- },
1061
- loading: {
1062
- entry: [assign({
1063
- requestedKeyword: ({
1064
- context
1065
- }) => context.keyword
1066
- })],
1067
- initial: "debouncing",
1068
- states: {
1069
- debouncing: {
1070
- always: [{
1071
- guard: "no debounce",
1072
- target: "fetching"
1073
- }],
1074
- after: {
1075
- DEBOUNCE: "fetching"
1076
- }
1077
- },
1078
- fetching: {
1079
- invoke: {
1080
- src: "get matches",
1081
- input: ({
1082
- context
1083
- }) => ({
1084
- keyword: context.keyword,
1085
- getMatches: context.definition.getMatches
1086
- }),
1087
- onDone: {
1088
- target: "#typeahead picker.active.evaluating",
1089
- actions: [assign(({
1090
- context,
1091
- event
1092
- }) => event.output.keyword !== context.keyword ? {
1093
- isLoading: context.patternText !== context.requestedKeyword
1094
- } : {
1095
- matches: event.output.matches,
1096
- isLoading: context.keyword !== context.requestedKeyword
1097
- })]
1098
- },
1099
- onError: {
1100
- target: "#typeahead picker.active.no matches",
1101
- actions: ["handle error"]
1102
- }
1103
- }
1104
- }
1105
- }
1106
- },
1107
- "no matches": {
1108
- entry: [assign({
1109
- selectedIndex: 0
1110
- })],
1111
- always: [{
1112
- guard: "has matches",
1113
- target: "showing matches"
1114
- }],
1115
- initial: "idle",
1116
- states: {
1117
- idle: {
1118
- always: [{
1119
- guard: "is loading",
1120
- target: "loading"
1121
- }]
1122
- },
1123
- loading: {
1124
- entry: [assign({
1125
- requestedKeyword: ({
1126
- context
1127
- }) => context.keyword
1128
- })],
1129
- initial: "debouncing",
1130
- states: {
1131
- debouncing: {
1132
- always: [{
1133
- guard: "no debounce",
1134
- target: "fetching"
1135
- }],
1136
- after: {
1137
- DEBOUNCE: "fetching"
1138
- }
1139
- },
1140
- fetching: {
1141
- invoke: {
1142
- src: "get matches",
1143
- input: ({
1144
- context
1145
- }) => ({
1146
- keyword: context.keyword,
1147
- getMatches: context.definition.getMatches
1148
- }),
1149
- onDone: {
1150
- target: "#typeahead picker.active.no matches.idle",
1151
- actions: ["handle async load complete"]
1152
- },
1153
- onError: {
1154
- target: "#typeahead picker.active.no matches.idle",
1155
- actions: ["handle error"]
1156
- }
1157
- }
1158
- }
1159
- }
1160
- }
1161
- }
1162
- },
1163
- "showing matches": {
1164
- entry: ["update submit listener context", "update text insertion listener context"],
1165
- invoke: {
1166
- src: "arrow listener",
1167
- input: ({
1168
- context
1169
- }) => ({
1170
- editor: context.editor
1171
- })
1172
- },
1173
- always: [{
1174
- guard: "no matches",
1175
- target: "no matches"
1176
- }],
1177
- on: {
1178
- "navigate down": {
1179
- actions: ["navigate", "update submit listener context", "update text insertion listener context"]
1180
- },
1181
- "navigate up": {
1182
- actions: ["navigate", "update submit listener context", "update text insertion listener context"]
1183
- },
1184
- "navigate to": {
1185
- actions: ["navigate", "update submit listener context", "update text insertion listener context"]
1186
- },
1187
- select: {
1188
- target: "#typeahead picker.idle",
1189
- actions: [{
1190
- type: "select match",
1191
- params: {
1192
- exact: !1
1193
- }
1194
- }]
1195
- }
1196
- },
1197
- initial: "idle",
1198
- states: {
1199
- idle: {
1200
- always: [{
1201
- guard: "is loading",
1202
- target: "loading"
1203
- }]
1204
- },
1205
- loading: {
1206
- entry: [assign({
1207
- requestedKeyword: ({
1208
- context
1209
- }) => context.keyword
1210
- })],
1211
- initial: "debouncing",
1212
- states: {
1213
- debouncing: {
1214
- always: [{
1215
- guard: "no debounce",
1216
- target: "fetching"
1217
- }],
1218
- after: {
1219
- DEBOUNCE: "fetching"
1220
- }
1221
- },
1222
- fetching: {
1223
- invoke: {
1224
- src: "get matches",
1225
- input: ({
1226
- context
1227
- }) => ({
1228
- keyword: context.keyword,
1229
- getMatches: context.definition.getMatches
1230
- }),
1231
- onDone: {
1232
- target: "#typeahead picker.active.showing matches.idle",
1233
- actions: ["handle async load complete"]
1234
- },
1235
- onError: {
1236
- target: "#typeahead picker.active.showing matches.idle",
1237
- actions: ["handle error"]
1238
- }
1239
- }
1240
- }
1241
- }
1242
- }
1243
- }
1244
- }
1245
- }
1246
- }
1247
- }
650
+ id: "typeahead picker",
651
+ context: ({ input }) => ({
652
+ editor: input.editor,
653
+ definition: input.definition,
654
+ triggerPattern: buildTriggerPattern(input.definition),
655
+ partialPattern: buildPartialPattern(input.definition),
656
+ completePattern: buildCompletePattern(input.definition),
657
+ matches: [],
658
+ selectedIndex: 0,
659
+ focusSpan: void 0,
660
+ patternText: "",
661
+ keyword: "",
662
+ requestedKeyword: "",
663
+ error: void 0,
664
+ isLoading: !1
665
+ }),
666
+ initial: "idle",
667
+ states: {
668
+ idle: {
669
+ entry: ["reset"],
670
+ invoke: {
671
+ src: "trigger listener",
672
+ input: ({ context }) => ({
673
+ editor: context.editor,
674
+ definition: context.definition
675
+ })
676
+ },
677
+ on: {
678
+ "custom.typeahead trigger found": {
679
+ target: "active",
680
+ actions: ["handle trigger found"]
681
+ },
682
+ "custom.typeahead keyword found": {
683
+ target: "checking complete",
684
+ actions: ["handle trigger found"]
685
+ }
686
+ }
687
+ },
688
+ "checking complete": { invoke: [{
689
+ src: "select match listener",
690
+ input: ({ context }) => ({ context })
691
+ }, {
692
+ src: "get matches",
693
+ input: ({ context }) => ({
694
+ keyword: context.keyword,
695
+ getMatches: context.definition.getMatches
696
+ }),
697
+ onDone: [{
698
+ guard: ({ event }) => hasAtLeastOneExactMatch(event.output.matches),
699
+ target: "idle",
700
+ actions: [assign({ matches: ({ event }) => event.output.matches }), {
701
+ type: "select match",
702
+ params: { exact: !0 }
703
+ }]
704
+ }, {
705
+ target: "active",
706
+ actions: [assign({ matches: ({ event }) => event.output.matches })]
707
+ }],
708
+ onError: {
709
+ target: "active.no matches",
710
+ actions: ["handle error"]
711
+ }
712
+ }] },
713
+ active: {
714
+ invoke: [
715
+ {
716
+ src: "select match listener",
717
+ input: ({ context }) => ({ context })
718
+ },
719
+ {
720
+ src: "escape listener",
721
+ id: "escape listener",
722
+ input: ({ context }) => ({ context })
723
+ },
724
+ {
725
+ src: "selection listener",
726
+ input: ({ context }) => ({ editor: context.editor })
727
+ },
728
+ {
729
+ src: "submit listener",
730
+ id: "submit listener",
731
+ input: ({ context }) => ({ context })
732
+ },
733
+ {
734
+ src: "text insertion listener",
735
+ id: "text insertion listener",
736
+ input: ({ context }) => ({ context })
737
+ },
738
+ {
739
+ src: "dismiss listener",
740
+ id: "dismiss listener",
741
+ input: ({ context }) => ({ context })
742
+ }
743
+ ],
744
+ on: {
745
+ close: { target: "idle" },
746
+ "selection changed": { actions: [
747
+ "handle selection changed",
748
+ "update submit listener context",
749
+ "update text insertion listener context",
750
+ "update escape listener context",
751
+ "update request dismiss listener context"
752
+ ] }
753
+ },
754
+ always: [
755
+ {
756
+ guard: "no focus span",
757
+ target: "idle"
758
+ },
759
+ {
760
+ guard: "invalid pattern",
761
+ target: "idle"
762
+ },
763
+ {
764
+ guard: "is complete keyword",
765
+ actions: [{
766
+ type: "select match",
767
+ params: { exact: !1 }
768
+ }],
769
+ target: "idle"
770
+ }
771
+ ],
772
+ initial: "evaluating",
773
+ states: {
774
+ evaluating: { always: [
775
+ {
776
+ guard: "is loading",
777
+ target: "loading"
778
+ },
779
+ {
780
+ guard: "has matches",
781
+ target: "showing matches"
782
+ },
783
+ { target: "no matches" }
784
+ ] },
785
+ loading: {
786
+ entry: [assign({ requestedKeyword: ({ context }) => context.keyword })],
787
+ initial: "debouncing",
788
+ states: {
789
+ debouncing: {
790
+ always: [{
791
+ guard: "no debounce",
792
+ target: "fetching"
793
+ }],
794
+ after: { DEBOUNCE: "fetching" }
795
+ },
796
+ fetching: { invoke: {
797
+ src: "get matches",
798
+ input: ({ context }) => ({
799
+ keyword: context.keyword,
800
+ getMatches: context.definition.getMatches
801
+ }),
802
+ onDone: {
803
+ target: "#typeahead picker.active.evaluating",
804
+ actions: [assign(({ context, event }) => event.output.keyword === context.keyword ? {
805
+ matches: event.output.matches,
806
+ isLoading: context.keyword !== context.requestedKeyword
807
+ } : { isLoading: context.patternText !== context.requestedKeyword })]
808
+ },
809
+ onError: {
810
+ target: "#typeahead picker.active.no matches",
811
+ actions: ["handle error"]
812
+ }
813
+ } }
814
+ }
815
+ },
816
+ "no matches": {
817
+ entry: [assign({ selectedIndex: 0 })],
818
+ always: [{
819
+ guard: "has matches",
820
+ target: "showing matches"
821
+ }],
822
+ initial: "idle",
823
+ states: {
824
+ idle: { always: [{
825
+ guard: "is loading",
826
+ target: "loading"
827
+ }] },
828
+ loading: {
829
+ entry: [assign({ requestedKeyword: ({ context }) => context.keyword })],
830
+ initial: "debouncing",
831
+ states: {
832
+ debouncing: {
833
+ always: [{
834
+ guard: "no debounce",
835
+ target: "fetching"
836
+ }],
837
+ after: { DEBOUNCE: "fetching" }
838
+ },
839
+ fetching: { invoke: {
840
+ src: "get matches",
841
+ input: ({ context }) => ({
842
+ keyword: context.keyword,
843
+ getMatches: context.definition.getMatches
844
+ }),
845
+ onDone: {
846
+ target: "#typeahead picker.active.no matches.idle",
847
+ actions: ["handle async load complete"]
848
+ },
849
+ onError: {
850
+ target: "#typeahead picker.active.no matches.idle",
851
+ actions: ["handle error"]
852
+ }
853
+ } }
854
+ }
855
+ }
856
+ }
857
+ },
858
+ "showing matches": {
859
+ entry: ["update submit listener context", "update text insertion listener context"],
860
+ invoke: {
861
+ src: "arrow listener",
862
+ input: ({ context }) => ({ editor: context.editor })
863
+ },
864
+ always: [{
865
+ guard: "no matches",
866
+ target: "no matches"
867
+ }],
868
+ on: {
869
+ "navigate down": { actions: [
870
+ "navigate",
871
+ "update submit listener context",
872
+ "update text insertion listener context"
873
+ ] },
874
+ "navigate up": { actions: [
875
+ "navigate",
876
+ "update submit listener context",
877
+ "update text insertion listener context"
878
+ ] },
879
+ "navigate to": { actions: [
880
+ "navigate",
881
+ "update submit listener context",
882
+ "update text insertion listener context"
883
+ ] },
884
+ select: {
885
+ target: "#typeahead picker.idle",
886
+ actions: [{
887
+ type: "select match",
888
+ params: { exact: !1 }
889
+ }]
890
+ }
891
+ },
892
+ initial: "idle",
893
+ states: {
894
+ idle: { always: [{
895
+ guard: "is loading",
896
+ target: "loading"
897
+ }] },
898
+ loading: {
899
+ entry: [assign({ requestedKeyword: ({ context }) => context.keyword })],
900
+ initial: "debouncing",
901
+ states: {
902
+ debouncing: {
903
+ always: [{
904
+ guard: "no debounce",
905
+ target: "fetching"
906
+ }],
907
+ after: { DEBOUNCE: "fetching" }
908
+ },
909
+ fetching: { invoke: {
910
+ src: "get matches",
911
+ input: ({ context }) => ({
912
+ keyword: context.keyword,
913
+ getMatches: context.definition.getMatches
914
+ }),
915
+ onDone: {
916
+ target: "#typeahead picker.active.showing matches.idle",
917
+ actions: ["handle async load complete"]
918
+ },
919
+ onError: {
920
+ target: "#typeahead picker.active.showing matches.idle",
921
+ actions: ["handle error"]
922
+ }
923
+ } }
924
+ }
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+ }
1248
931
  });
932
+ /**
933
+ * Check if matches contain at least one exact match.
934
+ */
1249
935
  function hasAtLeastOneExactMatch(matches) {
1250
- return matches.some((match) => match?.type === "exact");
936
+ return matches.some((match) => match?.type === "exact");
1251
937
  }
938
+ /**
939
+ * Get the first exact match from matches.
940
+ */
1252
941
  function getFirstExactMatch(matches) {
1253
- return matches.find((match) => match?.type === "exact");
942
+ return matches.find((match) => match?.type === "exact");
1254
943
  }
944
+ /**
945
+ * React hook that activates a typeahead picker and returns its current state.
946
+ *
947
+ * Call inside a component rendered within an `EditorProvider`.
948
+ * The picker automatically monitors the editor for trigger patterns.
949
+ *
950
+ * @example
951
+ * ```tsx
952
+ * function MentionPickerUI() {
953
+ * const picker = useTypeaheadPicker(mentionPickerDefinition)
954
+ *
955
+ * if (picker.snapshot.matches('idle')) return null
956
+ * if (picker.snapshot.matches({active: 'loading'})) return <Spinner />
957
+ * if (picker.snapshot.matches({active: 'no matches'})) return <NoResults />
958
+ *
959
+ * const {matches, selectedIndex} = picker.snapshot.context
960
+ *
961
+ * return (
962
+ * <ul>
963
+ * {matches.map((match, index) => (
964
+ * <li
965
+ * key={match.key}
966
+ * aria-selected={index === selectedIndex}
967
+ * onMouseEnter={() => picker.send({type: 'navigate to', index})}
968
+ * onClick={() => picker.send({type: 'select'})}
969
+ * >
970
+ * {match.name}
971
+ * </li>
972
+ * ))}
973
+ * </ul>
974
+ * )
975
+ * }
976
+ * ```
977
+ *
978
+ * @public
979
+ */
1255
980
  function useTypeaheadPicker(definition) {
1256
- const $ = c(20), editor = useEditor(), t0 = definition;
1257
- let t1;
1258
- $[0] !== editor || $[1] !== t0 ? (t1 = {
1259
- input: {
1260
- editor,
1261
- definition: t0
1262
- }
1263
- }, $[0] = editor, $[1] = t0, $[2] = t1) : t1 = $[2];
1264
- const [actorSnapshot, send] = useActor(typeaheadPickerMachine, t1);
1265
- let t2;
1266
- $[3] !== actorSnapshot ? (t2 = (state) => actorSnapshot.matches(state), $[3] = actorSnapshot, $[4] = t2) : t2 = $[4];
1267
- const t3 = actorSnapshot.context.matches;
1268
- let t4;
1269
- $[5] !== actorSnapshot.context.error || $[6] !== actorSnapshot.context.keyword || $[7] !== actorSnapshot.context.selectedIndex || $[8] !== t3 ? (t4 = {
1270
- keyword: actorSnapshot.context.keyword,
1271
- matches: t3,
1272
- selectedIndex: actorSnapshot.context.selectedIndex,
1273
- error: actorSnapshot.context.error
1274
- }, $[5] = actorSnapshot.context.error, $[6] = actorSnapshot.context.keyword, $[7] = actorSnapshot.context.selectedIndex, $[8] = t3, $[9] = t4) : t4 = $[9];
1275
- let t5;
1276
- $[10] !== t2 || $[11] !== t4 ? (t5 = {
1277
- matches: t2,
1278
- context: t4
1279
- }, $[10] = t2, $[11] = t4, $[12] = t5) : t5 = $[12];
1280
- let t6;
1281
- $[13] !== definition || $[14] !== editor || $[15] !== send ? (t6 = (event) => {
1282
- event.type === "dismiss" ? editor.send({
1283
- type: "custom.typeahead dismiss",
1284
- pickerId: definition._id
1285
- }) : send(event);
1286
- }, $[13] = definition, $[14] = editor, $[15] = send, $[16] = t6) : t6 = $[16];
1287
- let t7;
1288
- return $[17] !== t5 || $[18] !== t6 ? (t7 = {
1289
- snapshot: t5,
1290
- send: t6
1291
- }, $[17] = t5, $[18] = t6, $[19] = t7) : t7 = $[19], t7;
981
+ let $ = c(20), editor = useEditor(), t0 = definition, t1;
982
+ $[0] !== editor || $[1] !== t0 ? (t1 = { input: {
983
+ editor,
984
+ definition: t0
985
+ } }, $[0] = editor, $[1] = t0, $[2] = t1) : t1 = $[2];
986
+ let [actorSnapshot, send] = useActor(typeaheadPickerMachine, t1), t2;
987
+ $[3] === actorSnapshot ? t2 = $[4] : (t2 = (state) => actorSnapshot.matches(state), $[3] = actorSnapshot, $[4] = t2);
988
+ let t3 = actorSnapshot.context.matches, t4;
989
+ $[5] !== actorSnapshot.context.error || $[6] !== actorSnapshot.context.keyword || $[7] !== actorSnapshot.context.selectedIndex || $[8] !== t3 ? (t4 = {
990
+ keyword: actorSnapshot.context.keyword,
991
+ matches: t3,
992
+ selectedIndex: actorSnapshot.context.selectedIndex,
993
+ error: actorSnapshot.context.error
994
+ }, $[5] = actorSnapshot.context.error, $[6] = actorSnapshot.context.keyword, $[7] = actorSnapshot.context.selectedIndex, $[8] = t3, $[9] = t4) : t4 = $[9];
995
+ let t5;
996
+ $[10] !== t2 || $[11] !== t4 ? (t5 = {
997
+ matches: t2,
998
+ context: t4
999
+ }, $[10] = t2, $[11] = t4, $[12] = t5) : t5 = $[12];
1000
+ let t6;
1001
+ $[13] !== definition || $[14] !== editor || $[15] !== send ? (t6 = (event) => {
1002
+ event.type === "dismiss" ? editor.send({
1003
+ type: "custom.typeahead dismiss",
1004
+ pickerId: definition._id
1005
+ }) : send(event);
1006
+ }, $[13] = definition, $[14] = editor, $[15] = send, $[16] = t6) : t6 = $[16];
1007
+ let t7;
1008
+ return $[17] !== t5 || $[18] !== t6 ? (t7 = {
1009
+ snapshot: t5,
1010
+ send: t6
1011
+ }, $[17] = t5, $[18] = t6, $[19] = t7) : t7 = $[19], t7;
1292
1012
  }
1293
- export {
1294
- defineTypeaheadPicker,
1295
- useTypeaheadPicker
1296
- };
1297
- //# sourceMappingURL=index.js.map
1013
+ export { defineTypeaheadPicker, useTypeaheadPicker };
1014
+
1015
+ //# sourceMappingURL=index.js.map