@portabletext/plugin-emoji-picker 1.0.3 → 1.0.5
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 +241 -270
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +245 -258
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/emoji-picker-machine.tsx +444 -377
- package/src/emoji-picker.feature +81 -24
- package/src/emoji-picker.test.tsx +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
ChildPath,
|
|
3
3
|
Editor,
|
|
4
|
-
|
|
4
|
+
EditorSelector,
|
|
5
5
|
EditorSnapshot,
|
|
6
|
+
PortableTextSpan,
|
|
6
7
|
} from '@portabletext/editor'
|
|
7
8
|
import {
|
|
8
9
|
defineBehavior,
|
|
@@ -10,19 +11,30 @@ import {
|
|
|
10
11
|
forward,
|
|
11
12
|
raise,
|
|
12
13
|
} from '@portabletext/editor/behaviors'
|
|
13
|
-
import
|
|
14
|
-
|
|
14
|
+
import {
|
|
15
|
+
getFocusSpan,
|
|
16
|
+
getMarkState,
|
|
17
|
+
getNextSpan,
|
|
18
|
+
getPreviousSpan,
|
|
19
|
+
isPointAfterSelection,
|
|
20
|
+
isPointBeforeSelection,
|
|
21
|
+
type MarkState,
|
|
22
|
+
} from '@portabletext/editor/selectors'
|
|
23
|
+
import {
|
|
24
|
+
isEqualSelectionPoints,
|
|
25
|
+
isSelectionCollapsed,
|
|
26
|
+
} from '@portabletext/editor/utils'
|
|
15
27
|
import {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'
|
|
16
28
|
import {
|
|
17
29
|
defineInputRule,
|
|
18
30
|
defineInputRuleBehavior,
|
|
31
|
+
type InputRuleMatch,
|
|
19
32
|
} from '@portabletext/plugin-input-rule'
|
|
20
33
|
import {
|
|
21
34
|
assertEvent,
|
|
22
35
|
assign,
|
|
23
36
|
fromCallback,
|
|
24
37
|
not,
|
|
25
|
-
or,
|
|
26
38
|
sendTo,
|
|
27
39
|
setup,
|
|
28
40
|
type AnyEventObject,
|
|
@@ -49,6 +61,165 @@ const escapeShortcut = createKeyboardShortcut({
|
|
|
49
61
|
default: [{key: 'Escape'}],
|
|
50
62
|
})
|
|
51
63
|
|
|
64
|
+
const getTriggerState: EditorSelector<
|
|
65
|
+
| {
|
|
66
|
+
focusSpan: {
|
|
67
|
+
node: PortableTextSpan
|
|
68
|
+
path: ChildPath
|
|
69
|
+
}
|
|
70
|
+
markState: MarkState
|
|
71
|
+
focusSpanTextBefore: string
|
|
72
|
+
focusSpanTextAfter: string
|
|
73
|
+
previousSpan:
|
|
74
|
+
| {
|
|
75
|
+
node: PortableTextSpan
|
|
76
|
+
path: ChildPath
|
|
77
|
+
}
|
|
78
|
+
| undefined
|
|
79
|
+
nextSpan:
|
|
80
|
+
| {
|
|
81
|
+
node: PortableTextSpan
|
|
82
|
+
path: ChildPath
|
|
83
|
+
}
|
|
84
|
+
| undefined
|
|
85
|
+
}
|
|
86
|
+
| undefined
|
|
87
|
+
> = (snapshot) => {
|
|
88
|
+
const focusSpan = getFocusSpan(snapshot)
|
|
89
|
+
const markState = getMarkState(snapshot)
|
|
90
|
+
|
|
91
|
+
if (!focusSpan || !markState || !snapshot.context.selection) {
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const focusSpanTextBefore = focusSpan.node.text.slice(
|
|
96
|
+
0,
|
|
97
|
+
snapshot.context.selection.focus.offset,
|
|
98
|
+
)
|
|
99
|
+
const focusSpanTextAfter = focusSpan.node.text.slice(
|
|
100
|
+
snapshot.context.selection.focus.offset,
|
|
101
|
+
)
|
|
102
|
+
const previousSpan = getPreviousSpan(snapshot)
|
|
103
|
+
const nextSpan = getNextSpan(snapshot)
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
focusSpan,
|
|
107
|
+
markState,
|
|
108
|
+
focusSpanTextBefore,
|
|
109
|
+
focusSpanTextAfter,
|
|
110
|
+
previousSpan,
|
|
111
|
+
nextSpan,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createTriggerActions({
|
|
116
|
+
snapshot,
|
|
117
|
+
payload,
|
|
118
|
+
keywordState,
|
|
119
|
+
}: {
|
|
120
|
+
snapshot: EditorSnapshot
|
|
121
|
+
payload: ReturnType<typeof getTriggerState> & {lastMatch: InputRuleMatch}
|
|
122
|
+
keywordState: 'partial' | 'complete'
|
|
123
|
+
}) {
|
|
124
|
+
if (payload.markState.state === 'unchanged') {
|
|
125
|
+
const focusSpan = {
|
|
126
|
+
node: {
|
|
127
|
+
_key: payload.focusSpan.node._key,
|
|
128
|
+
_type: payload.focusSpan.node._type,
|
|
129
|
+
text: `${payload.focusSpanTextBefore}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,
|
|
130
|
+
marks: payload.markState.marks,
|
|
131
|
+
},
|
|
132
|
+
path: payload.focusSpan.path,
|
|
133
|
+
textBefore: payload.focusSpanTextBefore,
|
|
134
|
+
textAfter: payload.focusSpanTextAfter,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (keywordState === 'complete') {
|
|
138
|
+
return [
|
|
139
|
+
raise(
|
|
140
|
+
createKeywordFoundEvent({
|
|
141
|
+
focusSpan,
|
|
142
|
+
}),
|
|
143
|
+
),
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return [
|
|
148
|
+
raise(
|
|
149
|
+
createTriggerFoundEvent({
|
|
150
|
+
focusSpan,
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const newSpan = {
|
|
157
|
+
_key: snapshot.context.keyGenerator(),
|
|
158
|
+
_type: payload.focusSpan.node._type,
|
|
159
|
+
text: payload.lastMatch.text,
|
|
160
|
+
marks: payload.markState.marks,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let focusSpan = {
|
|
164
|
+
node: {
|
|
165
|
+
_key: newSpan._key,
|
|
166
|
+
_type: newSpan._type,
|
|
167
|
+
text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,
|
|
168
|
+
marks: payload.markState.marks,
|
|
169
|
+
},
|
|
170
|
+
path: [
|
|
171
|
+
{_key: payload.focusSpan.path[0]._key},
|
|
172
|
+
'children',
|
|
173
|
+
{_key: newSpan._key},
|
|
174
|
+
] satisfies ChildPath,
|
|
175
|
+
textBefore: '',
|
|
176
|
+
textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
payload.previousSpan &&
|
|
181
|
+
payload.focusSpanTextBefore.length === 0 &&
|
|
182
|
+
JSON.stringify(payload.previousSpan.node.marks ?? []) ===
|
|
183
|
+
JSON.stringify(payload.markState.marks)
|
|
184
|
+
) {
|
|
185
|
+
// The text will be inserted into the previous span, so we'll treat that
|
|
186
|
+
// as the focus span
|
|
187
|
+
|
|
188
|
+
focusSpan = {
|
|
189
|
+
node: {
|
|
190
|
+
_key: payload.previousSpan.node._key,
|
|
191
|
+
_type: newSpan._type,
|
|
192
|
+
text: `${payload.previousSpan.node.text}${newSpan.text}`,
|
|
193
|
+
marks: newSpan.marks,
|
|
194
|
+
},
|
|
195
|
+
path: payload.previousSpan.path,
|
|
196
|
+
textBefore: payload.previousSpan.node.text,
|
|
197
|
+
textAfter: '',
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return [
|
|
202
|
+
raise({type: 'select', at: payload.lastMatch.targetOffsets}),
|
|
203
|
+
raise({type: 'delete', at: payload.lastMatch.targetOffsets}),
|
|
204
|
+
raise({type: 'insert.child', child: newSpan}),
|
|
205
|
+
...(keywordState === 'complete'
|
|
206
|
+
? [
|
|
207
|
+
raise(
|
|
208
|
+
createKeywordFoundEvent({
|
|
209
|
+
focusSpan,
|
|
210
|
+
}),
|
|
211
|
+
),
|
|
212
|
+
]
|
|
213
|
+
: [
|
|
214
|
+
raise(
|
|
215
|
+
createTriggerFoundEvent({
|
|
216
|
+
focusSpan,
|
|
217
|
+
}),
|
|
218
|
+
),
|
|
219
|
+
]),
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
|
|
52
223
|
/*******************
|
|
53
224
|
* Input Rules
|
|
54
225
|
*******************/
|
|
@@ -58,34 +229,39 @@ const escapeShortcut = createKeyboardShortcut({
|
|
|
58
229
|
*/
|
|
59
230
|
const triggerRule = defineInputRule({
|
|
60
231
|
on: /:/,
|
|
61
|
-
guard: ({event}) => {
|
|
232
|
+
guard: ({snapshot, event}) => {
|
|
62
233
|
const lastMatch = event.matches.at(-1)
|
|
63
234
|
|
|
64
235
|
if (lastMatch === undefined) {
|
|
65
236
|
return false
|
|
66
237
|
}
|
|
67
238
|
|
|
239
|
+
const triggerState = getTriggerState(snapshot)
|
|
240
|
+
|
|
241
|
+
if (!triggerState) {
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
|
|
68
245
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
point: lastMatch.selection.anchor,
|
|
72
|
-
blockOffset: lastMatch.targetOffsets.anchor,
|
|
73
|
-
},
|
|
74
|
-
keywordFocus: lastMatch.targetOffsets.focus,
|
|
246
|
+
lastMatch,
|
|
247
|
+
...triggerState,
|
|
75
248
|
}
|
|
76
249
|
},
|
|
77
|
-
actions: [
|
|
250
|
+
actions: [
|
|
251
|
+
({snapshot}, payload) =>
|
|
252
|
+
createTriggerActions({snapshot, payload, keywordState: 'partial'}),
|
|
253
|
+
],
|
|
78
254
|
})
|
|
79
255
|
|
|
80
256
|
type TriggerFoundEvent = ReturnType<typeof createTriggerFoundEvent>
|
|
81
257
|
|
|
82
258
|
function createTriggerFoundEvent(payload: {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
259
|
+
focusSpan: {
|
|
260
|
+
node: PortableTextSpan
|
|
261
|
+
path: ChildPath
|
|
262
|
+
textBefore: string
|
|
263
|
+
textAfter: string
|
|
87
264
|
}
|
|
88
|
-
keywordFocus: BlockOffset
|
|
89
265
|
}) {
|
|
90
266
|
return {
|
|
91
267
|
type: 'custom.trigger found',
|
|
@@ -98,76 +274,76 @@ function createTriggerFoundEvent(payload: {
|
|
|
98
274
|
*/
|
|
99
275
|
const partialKeywordRule = defineInputRule({
|
|
100
276
|
on: /:[\S]+/,
|
|
101
|
-
guard: ({event}) => {
|
|
277
|
+
guard: ({snapshot, event}) => {
|
|
102
278
|
const lastMatch = event.matches.at(-1)
|
|
103
279
|
|
|
104
280
|
if (lastMatch === undefined) {
|
|
105
281
|
return false
|
|
106
282
|
}
|
|
107
283
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
point: lastMatch.selection.anchor,
|
|
111
|
-
blockOffset: lastMatch.targetOffsets.anchor,
|
|
284
|
+
if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {
|
|
285
|
+
return false
|
|
112
286
|
}
|
|
113
|
-
const keywordFocus = lastMatch.targetOffsets.focus
|
|
114
287
|
|
|
115
|
-
|
|
116
|
-
},
|
|
117
|
-
actions: [(_, payload) => [raise(createPartialKeywordFoundEvent(payload))]],
|
|
118
|
-
})
|
|
288
|
+
const triggerState = getTriggerState(snapshot)
|
|
119
289
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
290
|
+
if (!triggerState) {
|
|
291
|
+
return false
|
|
292
|
+
}
|
|
123
293
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
...payload,
|
|
135
|
-
} as const
|
|
136
|
-
}
|
|
294
|
+
return {
|
|
295
|
+
...triggerState,
|
|
296
|
+
lastMatch,
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
actions: [
|
|
300
|
+
({snapshot}, payload) =>
|
|
301
|
+
createTriggerActions({snapshot, payload, keywordState: 'partial'}),
|
|
302
|
+
],
|
|
303
|
+
})
|
|
137
304
|
|
|
138
305
|
/**
|
|
139
306
|
* Listen for a complete keyword like ":joy:"
|
|
140
307
|
*/
|
|
141
308
|
const keywordRule = defineInputRule({
|
|
142
309
|
on: /:[\S]+:/,
|
|
143
|
-
guard: ({event}) => {
|
|
310
|
+
guard: ({snapshot, event}) => {
|
|
144
311
|
const lastMatch = event.matches.at(-1)
|
|
145
312
|
|
|
146
313
|
if (lastMatch === undefined) {
|
|
147
314
|
return false
|
|
148
315
|
}
|
|
149
316
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
317
|
+
if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {
|
|
318
|
+
return false
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const triggerState = getTriggerState(snapshot)
|
|
322
|
+
|
|
323
|
+
if (!triggerState) {
|
|
324
|
+
return false
|
|
154
325
|
}
|
|
155
|
-
const keywordFocus = lastMatch.targetOffsets.focus
|
|
156
326
|
|
|
157
|
-
return {
|
|
327
|
+
return {
|
|
328
|
+
...triggerState,
|
|
329
|
+
lastMatch,
|
|
330
|
+
}
|
|
158
331
|
},
|
|
159
|
-
actions: [
|
|
332
|
+
actions: [
|
|
333
|
+
({snapshot}, payload) =>
|
|
334
|
+
createTriggerActions({snapshot, payload, keywordState: 'complete'}),
|
|
335
|
+
],
|
|
160
336
|
})
|
|
161
337
|
|
|
162
338
|
type KeywordFoundEvent = ReturnType<typeof createKeywordFoundEvent>
|
|
163
339
|
|
|
164
340
|
function createKeywordFoundEvent(payload: {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
341
|
+
focusSpan: {
|
|
342
|
+
node: PortableTextSpan
|
|
343
|
+
path: ChildPath
|
|
344
|
+
textBefore: string
|
|
345
|
+
textAfter: string
|
|
169
346
|
}
|
|
170
|
-
keywordFocus: BlockOffset
|
|
171
347
|
}) {
|
|
172
348
|
return {
|
|
173
349
|
type: 'custom.keyword found',
|
|
@@ -180,37 +356,23 @@ type EmojiPickerContext = {
|
|
|
180
356
|
matches: ReadonlyArray<BaseEmojiMatch>
|
|
181
357
|
matchEmojis: MatchEmojis<BaseEmojiMatch>
|
|
182
358
|
selectedIndex: number
|
|
183
|
-
|
|
359
|
+
focusSpan:
|
|
184
360
|
| {
|
|
185
|
-
|
|
186
|
-
|
|
361
|
+
node: PortableTextSpan
|
|
362
|
+
path: ChildPath
|
|
363
|
+
textBefore: string
|
|
364
|
+
textAfter: string
|
|
187
365
|
}
|
|
188
366
|
| undefined
|
|
189
|
-
keywordFocus: BlockOffset | undefined
|
|
190
367
|
incompleteKeywordRegex: RegExp
|
|
191
368
|
keyword: string
|
|
192
369
|
}
|
|
193
370
|
|
|
194
371
|
type EmojiPickerEvent =
|
|
195
372
|
| TriggerFoundEvent
|
|
196
|
-
| PartialKeywordFoundEvent
|
|
197
373
|
| KeywordFoundEvent
|
|
198
374
|
| {
|
|
199
375
|
type: 'selection changed'
|
|
200
|
-
snapshot: EditorSnapshot
|
|
201
|
-
}
|
|
202
|
-
| {
|
|
203
|
-
type: 'insert.text'
|
|
204
|
-
focus: EditorSelectionPoint
|
|
205
|
-
text: string
|
|
206
|
-
}
|
|
207
|
-
| {
|
|
208
|
-
type: 'delete.backward'
|
|
209
|
-
focus: EditorSelectionPoint
|
|
210
|
-
}
|
|
211
|
-
| {
|
|
212
|
-
type: 'delete.forward'
|
|
213
|
-
focus: EditorSelectionPoint
|
|
214
376
|
}
|
|
215
377
|
| {
|
|
216
378
|
type: 'dismiss'
|
|
@@ -252,21 +414,6 @@ const triggerListenerCallback: CallbackLogicFunction<
|
|
|
252
414
|
],
|
|
253
415
|
}),
|
|
254
416
|
}),
|
|
255
|
-
input.editor.registerBehavior({
|
|
256
|
-
behavior: defineBehavior<
|
|
257
|
-
PartialKeywordFoundEvent,
|
|
258
|
-
PartialKeywordFoundEvent['type']
|
|
259
|
-
>({
|
|
260
|
-
on: 'custom.partial keyword found',
|
|
261
|
-
actions: [
|
|
262
|
-
({event}) => [
|
|
263
|
-
effect(() => {
|
|
264
|
-
sendBack(event)
|
|
265
|
-
}),
|
|
266
|
-
],
|
|
267
|
-
],
|
|
268
|
-
}),
|
|
269
|
-
}),
|
|
270
417
|
input.editor.registerBehavior({
|
|
271
418
|
behavior: defineBehavior<TriggerFoundEvent, TriggerFoundEvent['type']>({
|
|
272
419
|
on: 'custom.trigger found',
|
|
@@ -357,8 +504,12 @@ const emojiInsertListener: CallbackLogicFunction<
|
|
|
357
504
|
return input.context.editor.registerBehavior({
|
|
358
505
|
behavior: defineBehavior<{
|
|
359
506
|
emoji: string
|
|
360
|
-
|
|
361
|
-
|
|
507
|
+
focusSpan: {
|
|
508
|
+
node: PortableTextSpan
|
|
509
|
+
path: ChildPath
|
|
510
|
+
textBefore: string
|
|
511
|
+
textAfter: string
|
|
512
|
+
}
|
|
362
513
|
}>({
|
|
363
514
|
on: 'custom.insert emoji',
|
|
364
515
|
actions: [
|
|
@@ -367,8 +518,19 @@ const emojiInsertListener: CallbackLogicFunction<
|
|
|
367
518
|
sendBack({type: 'dismiss'})
|
|
368
519
|
}),
|
|
369
520
|
raise({
|
|
370
|
-
type: 'delete
|
|
371
|
-
at: {
|
|
521
|
+
type: 'delete',
|
|
522
|
+
at: {
|
|
523
|
+
anchor: {
|
|
524
|
+
path: event.focusSpan.path,
|
|
525
|
+
offset: event.focusSpan.textBefore.length,
|
|
526
|
+
},
|
|
527
|
+
focus: {
|
|
528
|
+
path: event.focusSpan.path,
|
|
529
|
+
offset:
|
|
530
|
+
event.focusSpan.node.text.length -
|
|
531
|
+
event.focusSpan.textAfter.length,
|
|
532
|
+
},
|
|
533
|
+
},
|
|
372
534
|
}),
|
|
373
535
|
raise({
|
|
374
536
|
type: 'insert.text',
|
|
@@ -403,21 +565,17 @@ const submitListenerCallback: CallbackLogicFunction<
|
|
|
403
565
|
return false
|
|
404
566
|
}
|
|
405
567
|
|
|
406
|
-
const
|
|
407
|
-
const focus = context.keywordFocus
|
|
568
|
+
const focusSpan = context.focusSpan
|
|
408
569
|
const match = context.matches[context.selectedIndex]
|
|
409
570
|
|
|
410
|
-
return match &&
|
|
411
|
-
? {anchor, focus, emoji: match.emoji}
|
|
412
|
-
: false
|
|
571
|
+
return match && focusSpan ? {focusSpan, emoji: match.emoji} : false
|
|
413
572
|
},
|
|
414
573
|
actions: [
|
|
415
|
-
(_, {
|
|
574
|
+
(_, {focusSpan, emoji}) => [
|
|
416
575
|
raise({
|
|
417
576
|
type: 'custom.insert emoji',
|
|
418
577
|
emoji,
|
|
419
|
-
|
|
420
|
-
focus,
|
|
578
|
+
focusSpan,
|
|
421
579
|
}),
|
|
422
580
|
],
|
|
423
581
|
],
|
|
@@ -438,31 +596,6 @@ const submitListenerCallback: CallbackLogicFunction<
|
|
|
438
596
|
],
|
|
439
597
|
}),
|
|
440
598
|
}),
|
|
441
|
-
input.context.editor.registerBehavior({
|
|
442
|
-
behavior: defineInputRuleBehavior({
|
|
443
|
-
rules: [keywordRule],
|
|
444
|
-
}),
|
|
445
|
-
}),
|
|
446
|
-
input.context.editor.registerBehavior({
|
|
447
|
-
behavior: defineBehavior<
|
|
448
|
-
KeywordFoundEvent,
|
|
449
|
-
KeywordFoundEvent['type'],
|
|
450
|
-
{
|
|
451
|
-
anchor: BlockOffset
|
|
452
|
-
focus: BlockOffset
|
|
453
|
-
emoji: string
|
|
454
|
-
}
|
|
455
|
-
>({
|
|
456
|
-
on: 'custom.keyword found',
|
|
457
|
-
actions: [
|
|
458
|
-
({event}) => [
|
|
459
|
-
effect(() => {
|
|
460
|
-
sendBack(event)
|
|
461
|
-
}),
|
|
462
|
-
],
|
|
463
|
-
],
|
|
464
|
-
}),
|
|
465
|
-
}),
|
|
466
599
|
]
|
|
467
600
|
|
|
468
601
|
return () => {
|
|
@@ -478,86 +611,55 @@ const selectionListenerCallback: CallbackLogicFunction<
|
|
|
478
611
|
{editor: Editor}
|
|
479
612
|
> = ({sendBack, input}) => {
|
|
480
613
|
const subscription = input.editor.on('selection', () => {
|
|
481
|
-
|
|
482
|
-
sendBack({type: 'selection changed', snapshot})
|
|
614
|
+
sendBack({type: 'selection changed'})
|
|
483
615
|
})
|
|
484
616
|
|
|
485
617
|
return subscription.unsubscribe
|
|
486
618
|
}
|
|
487
619
|
|
|
488
|
-
const
|
|
489
|
-
|
|
620
|
+
const textInsertionListenerCallback: CallbackLogicFunction<
|
|
621
|
+
{type: 'context changed'; context: EmojiPickerContext},
|
|
490
622
|
EmojiPickerEvent,
|
|
491
|
-
{
|
|
492
|
-
> = ({sendBack, input}) => {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
})
|
|
528
|
-
}),
|
|
529
|
-
forward(event),
|
|
530
|
-
],
|
|
531
|
-
],
|
|
532
|
-
}),
|
|
533
|
-
}),
|
|
534
|
-
input.editor.registerBehavior({
|
|
535
|
-
behavior: defineBehavior({
|
|
536
|
-
on: 'delete.forward',
|
|
537
|
-
guard: ({snapshot, event}) =>
|
|
538
|
-
event.unit === 'character' && snapshot.context.selection
|
|
539
|
-
? {focus: snapshot.context.selection.focus}
|
|
540
|
-
: false,
|
|
541
|
-
actions: [
|
|
542
|
-
({event}, {focus}) => [
|
|
543
|
-
effect(() => {
|
|
544
|
-
sendBack({
|
|
545
|
-
type: 'delete.forward',
|
|
546
|
-
focus,
|
|
547
|
-
})
|
|
548
|
-
}),
|
|
549
|
-
forward(event),
|
|
550
|
-
],
|
|
623
|
+
{context: EmojiPickerContext}
|
|
624
|
+
> = ({sendBack, input, receive}) => {
|
|
625
|
+
let context = input.context
|
|
626
|
+
|
|
627
|
+
receive((event) => {
|
|
628
|
+
context = event.context
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
return input.context.editor.registerBehavior({
|
|
632
|
+
behavior: defineBehavior({
|
|
633
|
+
on: 'insert.text',
|
|
634
|
+
guard: ({snapshot}) => {
|
|
635
|
+
if (!context.focusSpan) {
|
|
636
|
+
return false
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (!snapshot.context.selection) {
|
|
640
|
+
return false
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const keywordAnchor = {
|
|
644
|
+
path: context.focusSpan.path,
|
|
645
|
+
offset: context.focusSpan.textBefore.length,
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return isEqualSelectionPoints(
|
|
649
|
+
snapshot.context.selection.focus,
|
|
650
|
+
keywordAnchor,
|
|
651
|
+
)
|
|
652
|
+
},
|
|
653
|
+
actions: [
|
|
654
|
+
({event}) => [
|
|
655
|
+
forward(event),
|
|
656
|
+
effect(() => {
|
|
657
|
+
sendBack({type: 'dismiss'})
|
|
658
|
+
}),
|
|
551
659
|
],
|
|
552
|
-
|
|
660
|
+
],
|
|
553
661
|
}),
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return () => {
|
|
557
|
-
for (const unregister of unregisterBehaviors) {
|
|
558
|
-
unregister()
|
|
559
|
-
}
|
|
560
|
-
}
|
|
662
|
+
})
|
|
561
663
|
}
|
|
562
664
|
|
|
563
665
|
export const emojiPickerMachine = setup({
|
|
@@ -576,96 +678,139 @@ export const emojiPickerMachine = setup({
|
|
|
576
678
|
'trigger listener': fromCallback(triggerListenerCallback),
|
|
577
679
|
'escape listener': fromCallback(escapeListenerCallback),
|
|
578
680
|
'selection listener': fromCallback(selectionListenerCallback),
|
|
579
|
-
'text
|
|
681
|
+
'text insertion listener': fromCallback(textInsertionListenerCallback),
|
|
580
682
|
},
|
|
581
683
|
actions: {
|
|
582
|
-
'
|
|
583
|
-
|
|
684
|
+
'set focus span': assign({
|
|
685
|
+
focusSpan: ({context, event}) => {
|
|
584
686
|
if (
|
|
585
687
|
event.type !== 'custom.trigger found' &&
|
|
586
|
-
event.type !== 'custom.partial keyword found' &&
|
|
587
688
|
event.type !== 'custom.keyword found'
|
|
588
689
|
) {
|
|
589
|
-
return context.
|
|
690
|
+
return context.focusSpan
|
|
590
691
|
}
|
|
591
692
|
|
|
592
|
-
return event.
|
|
693
|
+
return event.focusSpan
|
|
593
694
|
},
|
|
594
695
|
}),
|
|
595
|
-
'
|
|
596
|
-
|
|
597
|
-
if (
|
|
598
|
-
|
|
599
|
-
event.type !== 'custom.partial keyword found' &&
|
|
600
|
-
event.type !== 'custom.keyword found'
|
|
601
|
-
) {
|
|
602
|
-
return context.keywordAnchor
|
|
696
|
+
'update focus span': assign({
|
|
697
|
+
focusSpan: ({context}) => {
|
|
698
|
+
if (!context.focusSpan) {
|
|
699
|
+
return undefined
|
|
603
700
|
}
|
|
604
701
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
702
|
+
const snapshot = context.editor.getSnapshot()
|
|
703
|
+
const focusSpan = getFocusSpan(snapshot)
|
|
704
|
+
|
|
705
|
+
if (!snapshot.context.selection) {
|
|
706
|
+
return undefined
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (!focusSpan) {
|
|
710
|
+
return undefined
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const nextSpan = getNextSpan({
|
|
714
|
+
...snapshot,
|
|
715
|
+
context: {
|
|
716
|
+
...snapshot.context,
|
|
717
|
+
selection: {
|
|
718
|
+
anchor: {
|
|
719
|
+
path: context.focusSpan.path,
|
|
720
|
+
offset: 0,
|
|
721
|
+
},
|
|
722
|
+
focus: {
|
|
723
|
+
path: context.focusSpan.path,
|
|
724
|
+
offset: 0,
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
})
|
|
729
|
+
|
|
610
730
|
if (
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
event.type !== 'custom.keyword found'
|
|
731
|
+
JSON.stringify(focusSpan.path) !==
|
|
732
|
+
JSON.stringify(context.focusSpan.path)
|
|
614
733
|
) {
|
|
615
|
-
|
|
734
|
+
if (
|
|
735
|
+
nextSpan &&
|
|
736
|
+
context.focusSpan.textAfter.length === 0 &&
|
|
737
|
+
snapshot.context.selection.focus.offset === 0 &&
|
|
738
|
+
isSelectionCollapsed(snapshot.context.selection)
|
|
739
|
+
) {
|
|
740
|
+
// This is an edge case where the caret is moved from the end of
|
|
741
|
+
// the focus span to the start of the next span.
|
|
742
|
+
return context.focusSpan
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return undefined
|
|
616
746
|
}
|
|
617
747
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
'update keyword focus': assign({
|
|
622
|
-
keywordFocus: ({context, event}) => {
|
|
623
|
-
assertEvent(event, ['insert.text', 'delete.backward', 'delete.forward'])
|
|
748
|
+
if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore)) {
|
|
749
|
+
return undefined
|
|
750
|
+
}
|
|
624
751
|
|
|
625
|
-
if (!context.
|
|
626
|
-
return
|
|
752
|
+
if (!focusSpan.node.text.endsWith(context.focusSpan.textAfter)) {
|
|
753
|
+
return undefined
|
|
627
754
|
}
|
|
628
755
|
|
|
629
|
-
|
|
630
|
-
path:
|
|
756
|
+
const keywordAnchor = {
|
|
757
|
+
path: focusSpan.path,
|
|
758
|
+
offset: context.focusSpan.textBefore.length,
|
|
759
|
+
}
|
|
760
|
+
const keywordFocus = {
|
|
761
|
+
path: focusSpan.path,
|
|
631
762
|
offset:
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
763
|
+
focusSpan.node.text.length - context.focusSpan.textAfter.length,
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const selectionIsBeforeKeyword =
|
|
767
|
+
isPointAfterSelection(keywordAnchor)(snapshot)
|
|
768
|
+
|
|
769
|
+
const selectionIsAfterKeyword =
|
|
770
|
+
isPointBeforeSelection(keywordFocus)(snapshot)
|
|
771
|
+
|
|
772
|
+
if (selectionIsBeforeKeyword || selectionIsAfterKeyword) {
|
|
773
|
+
return undefined
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return {
|
|
777
|
+
node: focusSpan.node,
|
|
778
|
+
path: focusSpan.path,
|
|
779
|
+
textBefore: context.focusSpan.textBefore,
|
|
780
|
+
textAfter: context.focusSpan.textAfter,
|
|
638
781
|
}
|
|
639
782
|
},
|
|
640
783
|
}),
|
|
641
784
|
'update keyword': assign({
|
|
642
|
-
keyword: ({context
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (!context.keywordAnchor || !context.keywordFocus) {
|
|
785
|
+
keyword: ({context}) => {
|
|
786
|
+
if (!context.focusSpan) {
|
|
646
787
|
return ''
|
|
647
788
|
}
|
|
648
789
|
|
|
649
|
-
|
|
650
|
-
context
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
790
|
+
if (
|
|
791
|
+
context.focusSpan.textBefore.length > 0 &&
|
|
792
|
+
context.focusSpan.textAfter.length > 0
|
|
793
|
+
) {
|
|
794
|
+
return context.focusSpan.node.text.slice(
|
|
795
|
+
context.focusSpan.textBefore.length,
|
|
796
|
+
-context.focusSpan.textAfter.length,
|
|
797
|
+
)
|
|
798
|
+
}
|
|
654
799
|
|
|
655
|
-
if (
|
|
656
|
-
return
|
|
800
|
+
if (context.focusSpan.textBefore.length > 0) {
|
|
801
|
+
return context.focusSpan.node.text.slice(
|
|
802
|
+
context.focusSpan.textBefore.length,
|
|
803
|
+
)
|
|
657
804
|
}
|
|
658
805
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
},
|
|
668
|
-
})
|
|
806
|
+
if (context.focusSpan.textAfter.length > 0) {
|
|
807
|
+
return context.focusSpan.node.text.slice(
|
|
808
|
+
0,
|
|
809
|
+
-context.focusSpan.textAfter.length,
|
|
810
|
+
)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return context.focusSpan.node.text
|
|
669
814
|
},
|
|
670
815
|
}),
|
|
671
816
|
'update matches': assign({
|
|
@@ -713,132 +858,75 @@ export const emojiPickerMachine = setup({
|
|
|
713
858
|
return event.index
|
|
714
859
|
},
|
|
715
860
|
}),
|
|
716
|
-
'update
|
|
717
|
-
'
|
|
861
|
+
'update submit listener context': sendTo(
|
|
862
|
+
'submit listener',
|
|
718
863
|
({context}) => ({
|
|
719
864
|
type: 'context changed',
|
|
720
865
|
context,
|
|
721
866
|
}),
|
|
722
867
|
),
|
|
723
|
-
'update
|
|
724
|
-
'
|
|
868
|
+
'update text insertion listener context': sendTo(
|
|
869
|
+
'text insertion listener',
|
|
725
870
|
({context}) => ({
|
|
726
871
|
type: 'context changed',
|
|
727
872
|
context,
|
|
728
873
|
}),
|
|
729
874
|
),
|
|
730
|
-
'insert selected match': ({context
|
|
875
|
+
'insert selected match': ({context}) => {
|
|
731
876
|
const match = context.matches[context.selectedIndex]
|
|
732
877
|
|
|
733
|
-
if (!match || !context.
|
|
734
|
-
return
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (event.type === 'custom.keyword found' && match.type !== 'exact') {
|
|
878
|
+
if (!match || !context.focusSpan) {
|
|
738
879
|
return
|
|
739
880
|
}
|
|
740
881
|
|
|
741
882
|
context.editor.send({
|
|
742
883
|
type: 'custom.insert emoji',
|
|
743
884
|
emoji: match.emoji,
|
|
744
|
-
|
|
745
|
-
focus: context.keywordFocus,
|
|
885
|
+
focusSpan: context.focusSpan,
|
|
746
886
|
})
|
|
747
887
|
},
|
|
748
888
|
'reset': assign({
|
|
749
|
-
|
|
750
|
-
keywordFocus: undefined,
|
|
889
|
+
focusSpan: undefined,
|
|
751
890
|
keyword: '',
|
|
752
891
|
matches: [],
|
|
753
892
|
selectedIndex: 0,
|
|
754
893
|
}),
|
|
755
894
|
},
|
|
756
895
|
guards: {
|
|
896
|
+
'no focus span': ({context}) => {
|
|
897
|
+
return !context.focusSpan
|
|
898
|
+
},
|
|
757
899
|
'has matches': ({context}) => {
|
|
758
900
|
return context.matches.length > 0
|
|
759
901
|
},
|
|
760
902
|
'no matches': not('has matches'),
|
|
761
|
-
'keyword is
|
|
762
|
-
return context.incompleteKeywordRegex.test(context.keyword)
|
|
903
|
+
'keyword is malformed': ({context}) => {
|
|
904
|
+
return !context.incompleteKeywordRegex.test(context.keyword)
|
|
763
905
|
},
|
|
764
|
-
'keyword is
|
|
765
|
-
|
|
766
|
-
assertEvent(event, 'selection changed')
|
|
906
|
+
'keyword is direct match': ({context}) => {
|
|
907
|
+
const fullKeywordRegex = /^:[\S]+:$/
|
|
767
908
|
|
|
768
|
-
if (!context.
|
|
769
|
-
return true
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
return selectors.isPointAfterSelection(context.keywordAnchor.point)(
|
|
773
|
-
event.snapshot,
|
|
774
|
-
)
|
|
775
|
-
},
|
|
776
|
-
'selection is after keyword': ({context, event}) => {
|
|
777
|
-
assertEvent(event, 'selection changed')
|
|
778
|
-
|
|
779
|
-
if (context.keywordFocus === undefined) {
|
|
780
|
-
return true
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const keywordFocusPoint = utils.blockOffsetToSpanSelectionPoint({
|
|
784
|
-
context: event.snapshot.context,
|
|
785
|
-
blockOffset: context.keywordFocus,
|
|
786
|
-
direction: 'forward',
|
|
787
|
-
})
|
|
788
|
-
|
|
789
|
-
if (!keywordFocusPoint) {
|
|
790
|
-
return true
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
return selectors.isPointBeforeSelection(keywordFocusPoint)(event.snapshot)
|
|
794
|
-
},
|
|
795
|
-
'selection is expanded': ({event}) => {
|
|
796
|
-
assertEvent(event, 'selection changed')
|
|
797
|
-
|
|
798
|
-
return selectors.isSelectionExpanded(event.snapshot)
|
|
799
|
-
},
|
|
800
|
-
'selection moved unexpectedly': or([
|
|
801
|
-
'selection is before keyword',
|
|
802
|
-
'selection is after keyword',
|
|
803
|
-
'selection is expanded',
|
|
804
|
-
]),
|
|
805
|
-
'unexpected text insertion': ({context, event}) => {
|
|
806
|
-
if (event.type !== 'insert.text') {
|
|
909
|
+
if (!fullKeywordRegex.test(context.keyword)) {
|
|
807
910
|
return false
|
|
808
911
|
}
|
|
809
912
|
|
|
810
|
-
|
|
913
|
+
const match = context.matches.at(context.selectedIndex)
|
|
914
|
+
|
|
915
|
+
if (!match || match.type !== 'exact') {
|
|
811
916
|
return false
|
|
812
917
|
}
|
|
813
918
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const textInsertedBeforeKeyword =
|
|
817
|
-
selectors.isPointBeforeSelection(event.focus)({
|
|
818
|
-
...snapshot,
|
|
819
|
-
context: {
|
|
820
|
-
...snapshot.context,
|
|
821
|
-
selection: {
|
|
822
|
-
anchor: context.keywordAnchor.point,
|
|
823
|
-
focus: context.keywordAnchor.point,
|
|
824
|
-
},
|
|
825
|
-
},
|
|
826
|
-
}) ||
|
|
827
|
-
utils.isEqualSelectionPoints(event.focus, context.keywordAnchor.point)
|
|
828
|
-
|
|
829
|
-
return textInsertedBeforeKeyword
|
|
919
|
+
return true
|
|
830
920
|
},
|
|
831
921
|
},
|
|
832
922
|
}).createMachine({
|
|
833
|
-
/** @xstate-layout N4IgpgJg5mDOIC5RgLYHsBWBLABABywGMBrMAJwDosIAbMAYkLRrQDsctXZyAXSAbQAMAXUSg8aWFh5Y2YkAA9EARmUB2AJwUAzADYNADgCs65YO1q1ygDQgAnojUG1O5c+W7tAJmdfdRgF8A21RMXAIScgpuAEMyQgALTih6Tm4yHgo+BR4hUSQQCSkZOQKlBABaZW0DHSMAFksNQUF6oyNzL1sHBC9vCnrGtS8GtQaNNt0gkPRsfCJSSlj4pNYUiDA6PgoAMzQyAHc4iDz5IulZVnlyr3MKE30LA31DZoNuxD6vAaHdP29vOo1NNwLNwgsostEsl6BstmAKAAjGIkI5kE4iM6SC6lUDlerabT3arDfS3eoaPQfXr9QaWEaNcaTEGhOYRRbRMBxaFrWFYWAofmwU4Fc4lK5lFTqWqDAwUjReeoGbwaNTU1TPCh-QxNNS6XQGHwssHzSJLLkrGHcOiEcU4RIxNYCTGi7Hi66IfxE1oeZX1EbeZXUhUUZSKjxOOV6bTmY1hU0cqGrFLWsC2y72hKOmAnZT5cRuy4ehDVXQUVXDIyWGpmAzveyfLRKwQaVRVwR1ixeONsiHm7nJ+gigvFIuSkvKVuhgwaEwKmMKwbqkaCAaverqVuvKbBUHx9mQi08qAUVhoHAoGI8RJwHCwBJoA4w4eFQu4xSfaoDA3KOmCVSTn06r-i4-hGH0ZiEoI4H1D24JmpyA7JNED5PmsF5XjesD0KwMQAG5YFAV5gDgECPqwL5imOeKIIM9ShsoRh1i2erPB41L6C40GqISUb6n8cEJoeSFrChj7JBh14JHAOH4YRxE4AArnglFvhKNEIGoq4+E0yraPULa6PUHGqhQ3HVDUBL8dogkHv2lqife4noZeUkybhBFEXwOA8Ggqmju+5RGPqFBqP6ujDD4v4tjYDYIMqLjVKo5gdM4TGBLurLwYmR7JmJaFQJJWGpFwvB3psaZ8BARUJP5OLqR+CD1Loq5ymYfyeP4spGOqv70fpv7NBSBKtBlMz7n2iEOSeTkFTVMl1e645eB49wGP+K0UpBbjaMBzQUF4IzBYq7TNa2QS7meGzwAUWVCWQWIBQ15RVJq2ijJoLRtB03jUhUVYUHKtz-gqeoxkYGi2ZN1B0I99XFqobTlh4-5-Ot4H1j0ZirbcENhR4RmKrBmUmnZU3HnDS0aVUjHlh2cqtkqfTBdSSr0RMGieBS7htASUMIUmyFnvNsB3qhySU9RjUVBFdN1ltTPvbowZOGZEWHe9xgTHo-M5SJM3iy5mHSTdI7w+OlnlgY6heJzbgUv4ytxaqtSCBFg1eJFM4XQEQA */
|
|
834
923
|
id: 'emoji picker',
|
|
835
924
|
context: ({input}) => ({
|
|
836
925
|
editor: input.editor,
|
|
837
926
|
keyword: '',
|
|
838
|
-
|
|
839
|
-
keywordFocus: undefined,
|
|
927
|
+
focusSpan: undefined,
|
|
840
928
|
matchEmojis: input.matchEmojis,
|
|
841
|
-
incompleteKeywordRegex:
|
|
929
|
+
incompleteKeywordRegex: /^:[\S]*$/,
|
|
842
930
|
matches: [],
|
|
843
931
|
selectedIndex: 0,
|
|
844
932
|
}),
|
|
@@ -860,17 +948,12 @@ export const emojiPickerMachine = setup({
|
|
|
860
948
|
on: {
|
|
861
949
|
'custom.trigger found': {
|
|
862
950
|
target: 'searching',
|
|
863
|
-
actions: ['set
|
|
864
|
-
},
|
|
865
|
-
'custom.partial keyword found': {
|
|
866
|
-
target: 'searching',
|
|
867
|
-
actions: ['set keyword anchor', 'set keyword focus', 'init keyword'],
|
|
951
|
+
actions: ['set focus span', 'update keyword'],
|
|
868
952
|
},
|
|
869
953
|
'custom.keyword found': {
|
|
870
954
|
actions: [
|
|
871
|
-
'set
|
|
872
|
-
'
|
|
873
|
-
'init keyword',
|
|
955
|
+
'set focus span',
|
|
956
|
+
'update keyword',
|
|
874
957
|
'update matches',
|
|
875
958
|
'insert selected match',
|
|
876
959
|
],
|
|
@@ -895,58 +978,42 @@ export const emojiPickerMachine = setup({
|
|
|
895
978
|
input: ({context}) => ({editor: context.editor}),
|
|
896
979
|
},
|
|
897
980
|
{
|
|
898
|
-
src: 'text
|
|
899
|
-
|
|
981
|
+
src: 'text insertion listener',
|
|
982
|
+
id: 'text insertion listener',
|
|
983
|
+
input: ({context}) => ({context}),
|
|
900
984
|
},
|
|
901
985
|
],
|
|
902
986
|
on: {
|
|
903
|
-
'custom.keyword found': {
|
|
904
|
-
actions: [
|
|
905
|
-
'set keyword anchor',
|
|
906
|
-
'set keyword focus',
|
|
907
|
-
'init keyword',
|
|
908
|
-
'update matches',
|
|
909
|
-
'insert selected match',
|
|
910
|
-
],
|
|
911
|
-
},
|
|
912
|
-
'insert.text': [
|
|
913
|
-
{
|
|
914
|
-
guard: 'unexpected text insertion',
|
|
915
|
-
target: 'idle',
|
|
916
|
-
},
|
|
917
|
-
{
|
|
918
|
-
actions: ['update keyword focus'],
|
|
919
|
-
},
|
|
920
|
-
],
|
|
921
|
-
'delete.forward': {
|
|
922
|
-
actions: ['update keyword focus'],
|
|
923
|
-
},
|
|
924
|
-
'delete.backward': {
|
|
925
|
-
actions: ['update keyword focus'],
|
|
926
|
-
},
|
|
927
987
|
'dismiss': {
|
|
928
988
|
target: 'idle',
|
|
929
989
|
},
|
|
930
990
|
'selection changed': [
|
|
931
|
-
{
|
|
932
|
-
guard: 'selection moved unexpectedly',
|
|
933
|
-
target: 'idle',
|
|
934
|
-
},
|
|
935
991
|
{
|
|
936
992
|
actions: [
|
|
993
|
+
'update focus span',
|
|
937
994
|
'update keyword',
|
|
938
995
|
'update matches',
|
|
939
996
|
'reset selected index',
|
|
940
997
|
'update submit listener context',
|
|
998
|
+
'update text insertion listener context',
|
|
941
999
|
],
|
|
942
1000
|
},
|
|
943
1001
|
],
|
|
944
1002
|
},
|
|
945
1003
|
always: [
|
|
1004
|
+
{
|
|
1005
|
+
guard: 'no focus span',
|
|
1006
|
+
target: 'idle',
|
|
1007
|
+
},
|
|
946
1008
|
{
|
|
947
1009
|
guard: 'keyword is malformed',
|
|
948
1010
|
target: 'idle',
|
|
949
1011
|
},
|
|
1012
|
+
{
|
|
1013
|
+
guard: 'keyword is direct match',
|
|
1014
|
+
actions: ['insert selected match'],
|
|
1015
|
+
target: 'idle',
|
|
1016
|
+
},
|
|
950
1017
|
],
|
|
951
1018
|
initial: 'no matches showing',
|
|
952
1019
|
states: {
|