@portabletext/plugin-emoji-picker 1.0.4 β 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 +89 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +91 -14
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/emoji-picker-machine.tsx +137 -7
- package/src/emoji-picker.feature +51 -23
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
3
|
-
var compilerRuntime = require("react/compiler-runtime"), editor = require("@portabletext/editor"), react = require("@xstate/react"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), keyboardShortcuts = require("@portabletext/keyboard-shortcuts"), pluginInputRule = require("@portabletext/plugin-input-rule"), xstate = require("xstate");
|
|
3
|
+
var compilerRuntime = require("react/compiler-runtime"), editor = require("@portabletext/editor"), react = require("@xstate/react"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), utils = require("@portabletext/editor/utils"), keyboardShortcuts = require("@portabletext/keyboard-shortcuts"), pluginInputRule = require("@portabletext/plugin-input-rule"), xstate = require("xstate");
|
|
4
4
|
function createMatchEmojis(config) {
|
|
5
5
|
return ({
|
|
6
6
|
keyword
|
|
@@ -60,12 +60,13 @@ const arrowUpShortcut = keyboardShortcuts.createKeyboardShortcut({
|
|
|
60
60
|
const focusSpan = selectors.getFocusSpan(snapshot), markState = selectors.getMarkState(snapshot);
|
|
61
61
|
if (!focusSpan || !markState || !snapshot.context.selection)
|
|
62
62
|
return;
|
|
63
|
-
const focusSpanTextBefore = focusSpan.node.text.slice(0, snapshot.context.selection.focus.offset), focusSpanTextAfter = focusSpan.node.text.slice(snapshot.context.selection.focus.offset), nextSpan = selectors.getNextSpan(snapshot);
|
|
63
|
+
const focusSpanTextBefore = focusSpan.node.text.slice(0, snapshot.context.selection.focus.offset), focusSpanTextAfter = focusSpan.node.text.slice(snapshot.context.selection.focus.offset), previousSpan = selectors.getPreviousSpan(snapshot), nextSpan = selectors.getNextSpan(snapshot);
|
|
64
64
|
return {
|
|
65
65
|
focusSpan,
|
|
66
66
|
markState,
|
|
67
67
|
focusSpanTextBefore,
|
|
68
68
|
focusSpanTextAfter,
|
|
69
|
+
previousSpan,
|
|
69
70
|
nextSpan
|
|
70
71
|
};
|
|
71
72
|
};
|
|
@@ -97,11 +98,12 @@ function createTriggerActions({
|
|
|
97
98
|
_type: payload.focusSpan.node._type,
|
|
98
99
|
text: payload.lastMatch.text,
|
|
99
100
|
marks: payload.markState.marks
|
|
100
|
-
}
|
|
101
|
+
};
|
|
102
|
+
let focusSpan = {
|
|
101
103
|
node: {
|
|
102
104
|
_key: newSpan._key,
|
|
103
105
|
_type: newSpan._type,
|
|
104
|
-
text: `${newSpan.text}${payload.nextSpan?.node.text ??
|
|
106
|
+
text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,
|
|
105
107
|
marks: payload.markState.marks
|
|
106
108
|
},
|
|
107
109
|
path: [{
|
|
@@ -110,9 +112,19 @@ function createTriggerActions({
|
|
|
110
112
|
_key: newSpan._key
|
|
111
113
|
}],
|
|
112
114
|
textBefore: "",
|
|
113
|
-
textAfter: payload.nextSpan?.node.text ??
|
|
115
|
+
textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter
|
|
114
116
|
};
|
|
115
|
-
return [
|
|
117
|
+
return payload.previousSpan && payload.focusSpanTextBefore.length === 0 && JSON.stringify(payload.previousSpan.node.marks ?? []) === JSON.stringify(payload.markState.marks) && (focusSpan = {
|
|
118
|
+
node: {
|
|
119
|
+
_key: payload.previousSpan.node._key,
|
|
120
|
+
_type: newSpan._type,
|
|
121
|
+
text: `${payload.previousSpan.node.text}${newSpan.text}`,
|
|
122
|
+
marks: newSpan.marks
|
|
123
|
+
},
|
|
124
|
+
path: payload.previousSpan.path,
|
|
125
|
+
textBefore: payload.previousSpan.node.text,
|
|
126
|
+
textAfter: ""
|
|
127
|
+
}), [behaviors.raise({
|
|
116
128
|
type: "select",
|
|
117
129
|
at: payload.lastMatch.targetOffsets
|
|
118
130
|
}), behaviors.raise({
|
|
@@ -368,12 +380,41 @@ const triggerListenerCallback = ({
|
|
|
368
380
|
sendBack,
|
|
369
381
|
input
|
|
370
382
|
}) => input.editor.on("selection", () => {
|
|
371
|
-
const snapshot = input.editor.getSnapshot();
|
|
372
383
|
sendBack({
|
|
373
|
-
type: "selection changed"
|
|
374
|
-
|
|
384
|
+
type: "selection changed"
|
|
385
|
+
});
|
|
386
|
+
}).unsubscribe, textInsertionListenerCallback = ({
|
|
387
|
+
sendBack,
|
|
388
|
+
input,
|
|
389
|
+
receive
|
|
390
|
+
}) => {
|
|
391
|
+
let context = input.context;
|
|
392
|
+
return receive((event) => {
|
|
393
|
+
context = event.context;
|
|
394
|
+
}), input.context.editor.registerBehavior({
|
|
395
|
+
behavior: behaviors.defineBehavior({
|
|
396
|
+
on: "insert.text",
|
|
397
|
+
guard: ({
|
|
398
|
+
snapshot
|
|
399
|
+
}) => {
|
|
400
|
+
if (!context.focusSpan || !snapshot.context.selection)
|
|
401
|
+
return !1;
|
|
402
|
+
const keywordAnchor = {
|
|
403
|
+
path: context.focusSpan.path,
|
|
404
|
+
offset: context.focusSpan.textBefore.length
|
|
405
|
+
};
|
|
406
|
+
return utils.isEqualSelectionPoints(snapshot.context.selection.focus, keywordAnchor);
|
|
407
|
+
},
|
|
408
|
+
actions: [({
|
|
409
|
+
event
|
|
410
|
+
}) => [behaviors.forward(event), behaviors.effect(() => {
|
|
411
|
+
sendBack({
|
|
412
|
+
type: "dismiss"
|
|
413
|
+
});
|
|
414
|
+
})]]
|
|
415
|
+
})
|
|
375
416
|
});
|
|
376
|
-
}
|
|
417
|
+
}, emojiPickerMachine = xstate.setup({
|
|
377
418
|
types: {
|
|
378
419
|
context: {},
|
|
379
420
|
input: {},
|
|
@@ -385,7 +426,8 @@ const triggerListenerCallback = ({
|
|
|
385
426
|
"arrow listener": xstate.fromCallback(arrowListenerCallback),
|
|
386
427
|
"trigger listener": xstate.fromCallback(triggerListenerCallback),
|
|
387
428
|
"escape listener": xstate.fromCallback(escapeListenerCallback),
|
|
388
|
-
"selection listener": xstate.fromCallback(selectionListenerCallback)
|
|
429
|
+
"selection listener": xstate.fromCallback(selectionListenerCallback),
|
|
430
|
+
"text insertion listener": xstate.fromCallback(textInsertionListenerCallback)
|
|
389
431
|
},
|
|
390
432
|
actions: {
|
|
391
433
|
"set focus span": xstate.assign({
|
|
@@ -401,7 +443,27 @@ const triggerListenerCallback = ({
|
|
|
401
443
|
if (!context.focusSpan)
|
|
402
444
|
return;
|
|
403
445
|
const snapshot = context.editor.getSnapshot(), focusSpan = selectors.getFocusSpan(snapshot);
|
|
404
|
-
if (!
|
|
446
|
+
if (!snapshot.context.selection || !focusSpan)
|
|
447
|
+
return;
|
|
448
|
+
const nextSpan = selectors.getNextSpan({
|
|
449
|
+
...snapshot,
|
|
450
|
+
context: {
|
|
451
|
+
...snapshot.context,
|
|
452
|
+
selection: {
|
|
453
|
+
anchor: {
|
|
454
|
+
path: context.focusSpan.path,
|
|
455
|
+
offset: 0
|
|
456
|
+
},
|
|
457
|
+
focus: {
|
|
458
|
+
path: context.focusSpan.path,
|
|
459
|
+
offset: 0
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
if (JSON.stringify(focusSpan.path) !== JSON.stringify(context.focusSpan.path))
|
|
465
|
+
return nextSpan && context.focusSpan.textAfter.length === 0 && snapshot.context.selection.focus.offset === 0 && utils.isSelectionCollapsed(snapshot.context.selection) ? context.focusSpan : void 0;
|
|
466
|
+
if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore) || !focusSpan.node.text.endsWith(context.focusSpan.textAfter))
|
|
405
467
|
return;
|
|
406
468
|
const keywordAnchor = {
|
|
407
469
|
path: focusSpan.path,
|
|
@@ -458,6 +520,12 @@ const triggerListenerCallback = ({
|
|
|
458
520
|
type: "context changed",
|
|
459
521
|
context
|
|
460
522
|
})),
|
|
523
|
+
"update text insertion listener context": xstate.sendTo("text insertion listener", ({
|
|
524
|
+
context
|
|
525
|
+
}) => ({
|
|
526
|
+
type: "context changed",
|
|
527
|
+
context
|
|
528
|
+
})),
|
|
461
529
|
"insert selected match": ({
|
|
462
530
|
context
|
|
463
531
|
}) => {
|
|
@@ -564,13 +632,21 @@ const triggerListenerCallback = ({
|
|
|
564
632
|
}) => ({
|
|
565
633
|
editor: context.editor
|
|
566
634
|
})
|
|
635
|
+
}, {
|
|
636
|
+
src: "text insertion listener",
|
|
637
|
+
id: "text insertion listener",
|
|
638
|
+
input: ({
|
|
639
|
+
context
|
|
640
|
+
}) => ({
|
|
641
|
+
context
|
|
642
|
+
})
|
|
567
643
|
}],
|
|
568
644
|
on: {
|
|
569
645
|
dismiss: {
|
|
570
646
|
target: "idle"
|
|
571
647
|
},
|
|
572
648
|
"selection changed": [{
|
|
573
|
-
actions: ["update focus span", "update keyword", "update matches", "reset selected index", "update submit listener context"]
|
|
649
|
+
actions: ["update focus span", "update keyword", "update matches", "reset selected index", "update submit listener context", "update text insertion listener context"]
|
|
574
650
|
}]
|
|
575
651
|
},
|
|
576
652
|
always: [{
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/create-match-emojis.ts","../src/emoji-picker-machine.tsx","../src/use-emoji-picker.ts"],"sourcesContent":["import type {MatchEmojis} from './match-emojis'\n\n/**\n * Proposed, but not required type, to represent an emoji match.\n *\n * @example\n * ```tsx\n * {\n * type: 'exact',\n * key: 'π-joy',\n * emoji: 'π',\n * keyword: 'joy',\n * }\n * ```\n * @example\n * ```tsx\n * {\n * type: 'partial',\n * key: 'πΉ-joy-_cat',\n * emoji: 'πΉ',\n * keyword: 'joy',\n * startSlice: '',\n * endSlice: '_cat',\n * }\n * ```\n *\n * @beta\n */\nexport type EmojiMatch =\n | {\n type: 'exact'\n key: string\n emoji: string\n keyword: string\n }\n | {\n type: 'partial'\n key: string\n emoji: string\n keyword: string\n startSlice: string\n endSlice: string\n }\n\n/**\n * Proposed, but not required, function to create a `MatchEmojis` function.\n *\n * @example\n * ```ts\n * const matchEmojis = createMatchEmojis({\n * emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * },\n * })\n * ```\n *\n * @beta\n */\nexport function createMatchEmojis(config: {\n emojis: Record<string, ReadonlyArray<string>>\n}): MatchEmojis<EmojiMatch> {\n return ({keyword}: {keyword: string}) => {\n const foundEmojis: Array<EmojiMatch> = []\n\n if (keyword.length < 1) {\n return foundEmojis\n }\n\n for (const emoji in config.emojis) {\n const emojiKeywords = config.emojis[emoji] ?? []\n\n for (const emojiKeyword of emojiKeywords) {\n const keywordIndex = emojiKeyword.indexOf(keyword)\n\n if (keywordIndex === -1) {\n continue\n }\n\n if (emojiKeyword === keyword) {\n foundEmojis.push({\n type: 'exact',\n key: `${emoji}-${keyword}`,\n emoji,\n keyword,\n })\n } else {\n const start = emojiKeyword.slice(0, keywordIndex)\n const end = emojiKeyword.slice(keywordIndex + keyword.length)\n\n foundEmojis.push({\n type: 'partial',\n key: `${emoji}-${start}${keyword}${end}`,\n emoji,\n keyword,\n startSlice: start,\n endSlice: end,\n })\n }\n }\n }\n\n return foundEmojis\n }\n}\n","import type {\n ChildPath,\n Editor,\n EditorSelector,\n EditorSnapshot,\n PortableTextSpan,\n} from '@portabletext/editor'\nimport {defineBehavior, effect, raise} from '@portabletext/editor/behaviors'\nimport {\n getFocusSpan,\n getMarkState,\n getNextSpan,\n isPointAfterSelection,\n isPointBeforeSelection,\n type MarkState,\n} from '@portabletext/editor/selectors'\nimport {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'\nimport {\n defineInputRule,\n defineInputRuleBehavior,\n type InputRuleMatch,\n} from '@portabletext/plugin-input-rule'\nimport {\n assertEvent,\n assign,\n fromCallback,\n not,\n sendTo,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/*******************\n * Keyboard shortcuts\n *******************/\nconst arrowUpShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowUp'}],\n})\nconst arrowDownShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowDown'}],\n})\nconst enterShortcut = createKeyboardShortcut({\n default: [{key: 'Enter'}],\n})\nconst tabShortcut = createKeyboardShortcut({\n default: [{key: 'Tab'}],\n})\nconst escapeShortcut = createKeyboardShortcut({\n default: [{key: 'Escape'}],\n})\n\nconst getTriggerState: EditorSelector<\n | {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n }\n markState: MarkState\n focusSpanTextBefore: string\n focusSpanTextAfter: string\n nextSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n }\n | undefined\n }\n | undefined\n> = (snapshot) => {\n const focusSpan = getFocusSpan(snapshot)\n const markState = getMarkState(snapshot)\n\n if (!focusSpan || !markState || !snapshot.context.selection) {\n return undefined\n }\n\n const focusSpanTextBefore = focusSpan.node.text.slice(\n 0,\n snapshot.context.selection.focus.offset,\n )\n const focusSpanTextAfter = focusSpan.node.text.slice(\n snapshot.context.selection.focus.offset,\n )\n const nextSpan = getNextSpan(snapshot)\n\n return {\n focusSpan,\n markState,\n focusSpanTextBefore,\n focusSpanTextAfter,\n nextSpan,\n }\n}\n\nfunction createTriggerActions({\n snapshot,\n payload,\n keywordState,\n}: {\n snapshot: EditorSnapshot\n payload: ReturnType<typeof getTriggerState> & {lastMatch: InputRuleMatch}\n keywordState: 'partial' | 'complete'\n}) {\n if (payload.markState.state === 'unchanged') {\n const focusSpan = {\n node: {\n _key: payload.focusSpan.node._key,\n _type: payload.focusSpan.node._type,\n text: `${payload.focusSpanTextBefore}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,\n marks: payload.markState.marks,\n },\n path: payload.focusSpan.path,\n textBefore: payload.focusSpanTextBefore,\n textAfter: payload.focusSpanTextAfter,\n }\n\n if (keywordState === 'complete') {\n return [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n return [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n const newSpan = {\n _key: snapshot.context.keyGenerator(),\n _type: payload.focusSpan.node._type,\n text: payload.lastMatch.text,\n marks: payload.markState.marks,\n }\n const focusSpan = {\n node: {\n _key: newSpan._key,\n _type: newSpan._type,\n text: `${newSpan.text}${payload.nextSpan?.node.text ?? ''}`,\n marks: payload.markState.marks,\n },\n path: [\n {_key: payload.focusSpan.path[0]._key},\n 'children',\n {_key: newSpan._key},\n ] satisfies ChildPath,\n textBefore: '',\n textAfter: payload.nextSpan?.node.text ?? '',\n }\n\n return [\n raise({type: 'select', at: payload.lastMatch.targetOffsets}),\n raise({type: 'delete', at: payload.lastMatch.targetOffsets}),\n raise({type: 'insert.child', child: newSpan}),\n ...(keywordState === 'complete'\n ? [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n : [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]),\n ]\n}\n\n/*******************\n * Input Rules\n *******************/\n\n/**\n * Listen for a single colon insertion\n */\nconst triggerRule = defineInputRule({\n on: /:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n lastMatch,\n ...triggerState,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\ntype TriggerFoundEvent = ReturnType<typeof createTriggerFoundEvent>\n\nfunction createTriggerFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.trigger found',\n ...payload,\n } as const\n}\n\n/**\n * Listen for a partial keyword like \":joy\"\n */\nconst partialKeywordRule = defineInputRule({\n on: /:[\\S]+/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\n/**\n * Listen for a complete keyword like \":joy:\"\n */\nconst keywordRule = defineInputRule({\n on: /:[\\S]+:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'complete'}),\n ],\n})\n\ntype KeywordFoundEvent = ReturnType<typeof createKeywordFoundEvent>\n\nfunction createKeywordFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.keyword found',\n ...payload,\n } as const\n}\n\ntype EmojiPickerContext = {\n editor: Editor\n matches: ReadonlyArray<BaseEmojiMatch>\n matchEmojis: MatchEmojis<BaseEmojiMatch>\n selectedIndex: number\n focusSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n | undefined\n incompleteKeywordRegex: RegExp\n keyword: string\n}\n\ntype EmojiPickerEvent =\n | TriggerFoundEvent\n | KeywordFoundEvent\n | {\n type: 'selection changed'\n snapshot: EditorSnapshot\n }\n | {\n type: 'dismiss'\n }\n | {\n type: 'navigate down'\n }\n | {\n type: 'navigate up'\n }\n | {\n type: 'navigate to'\n index: number\n }\n | {\n type: 'insert selected match'\n }\n\nconst triggerListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineInputRuleBehavior({\n rules: [keywordRule, partialKeywordRule, triggerRule],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<KeywordFoundEvent, KeywordFoundEvent['type']>({\n on: 'custom.keyword found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<TriggerFoundEvent, TriggerFoundEvent['type']>({\n on: 'custom.trigger found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst escapeListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => escapeShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst arrowListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowDownShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate down'})\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowUpShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate up'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst emojiInsertListener: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input}) => {\n return input.context.editor.registerBehavior({\n behavior: defineBehavior<{\n emoji: string\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n }>({\n on: 'custom.insert emoji',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n raise({\n type: 'delete',\n at: {\n anchor: {\n path: event.focusSpan.path,\n offset: event.focusSpan.textBefore.length,\n },\n focus: {\n path: event.focusSpan.path,\n offset:\n event.focusSpan.node.text.length -\n event.focusSpan.textAfter.length,\n },\n },\n }),\n raise({\n type: 'insert.text',\n text: event.emoji,\n }),\n ],\n ],\n }),\n })\n}\n\nconst submitListenerCallback: CallbackLogicFunction<\n {type: 'context changed'; context: EmojiPickerContext},\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input, receive}) => {\n let context = input.context\n\n receive((event) => {\n context = event.context\n })\n\n const unregisterBehaviors = [\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => {\n if (\n !enterShortcut.guard(event.originEvent) &&\n !tabShortcut.guard(event.originEvent)\n ) {\n return false\n }\n\n const focusSpan = context.focusSpan\n const match = context.matches[context.selectedIndex]\n\n return match && focusSpan ? {focusSpan, emoji: match.emoji} : false\n },\n actions: [\n (_, {focusSpan, emoji}) => [\n raise({\n type: 'custom.insert emoji',\n emoji,\n focusSpan,\n }),\n ],\n ],\n }),\n }),\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) =>\n enterShortcut.guard(event.originEvent) ||\n tabShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const subscription = input.editor.on('selection', () => {\n const snapshot = input.editor.getSnapshot()\n sendBack({type: 'selection changed', snapshot})\n })\n\n return subscription.unsubscribe\n}\n\nexport const emojiPickerMachine = setup({\n types: {\n context: {} as EmojiPickerContext,\n input: {} as {\n editor: Editor\n matchEmojis: MatchEmojis\n },\n events: {} as EmojiPickerEvent,\n },\n actors: {\n 'emoji insert listener': fromCallback(emojiInsertListener),\n 'submit listener': fromCallback(submitListenerCallback),\n 'arrow listener': fromCallback(arrowListenerCallback),\n 'trigger listener': fromCallback(triggerListenerCallback),\n 'escape listener': fromCallback(escapeListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n actions: {\n 'set focus span': assign({\n focusSpan: ({context, event}) => {\n if (\n event.type !== 'custom.trigger found' &&\n event.type !== 'custom.keyword found'\n ) {\n return context.focusSpan\n }\n\n return event.focusSpan\n },\n }),\n 'update focus span': assign({\n focusSpan: ({context}) => {\n if (!context.focusSpan) {\n return undefined\n }\n\n const snapshot = context.editor.getSnapshot()\n const focusSpan = getFocusSpan(snapshot)\n\n if (!focusSpan) {\n return undefined\n }\n\n if (\n JSON.stringify(focusSpan.path) !==\n JSON.stringify(context.focusSpan.path)\n ) {\n return undefined\n }\n\n if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore)) {\n return undefined\n }\n\n if (!focusSpan.node.text.endsWith(context.focusSpan.textAfter)) {\n return undefined\n }\n\n const keywordAnchor = {\n path: focusSpan.path,\n offset: context.focusSpan.textBefore.length,\n }\n const keywordFocus = {\n path: focusSpan.path,\n offset:\n focusSpan.node.text.length - context.focusSpan.textAfter.length,\n }\n\n const selectionIsBeforeKeyword =\n isPointAfterSelection(keywordAnchor)(snapshot)\n\n const selectionIsAfterKeyword =\n isPointBeforeSelection(keywordFocus)(snapshot)\n\n if (selectionIsBeforeKeyword || selectionIsAfterKeyword) {\n return undefined\n }\n\n return {\n node: focusSpan.node,\n path: focusSpan.path,\n textBefore: context.focusSpan.textBefore,\n textAfter: context.focusSpan.textAfter,\n }\n },\n }),\n 'update keyword': assign({\n keyword: ({context}) => {\n if (!context.focusSpan) {\n return ''\n }\n\n if (\n context.focusSpan.textBefore.length > 0 &&\n context.focusSpan.textAfter.length > 0\n ) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n -context.focusSpan.textAfter.length,\n )\n }\n\n if (context.focusSpan.textBefore.length > 0) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n )\n }\n\n if (context.focusSpan.textAfter.length > 0) {\n return context.focusSpan.node.text.slice(\n 0,\n -context.focusSpan.textAfter.length,\n )\n }\n\n return context.focusSpan.node.text\n },\n }),\n 'update matches': assign({\n matches: ({context}) => {\n // Strip leading colon\n let rawKeyword = context.keyword.startsWith(':')\n ? context.keyword.slice(1)\n : context.keyword\n // Strip trailing colon\n rawKeyword =\n rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n\n if (rawKeyword === undefined) {\n return []\n }\n\n return context.matchEmojis({keyword: rawKeyword})\n },\n }),\n 'reset selected index': assign({\n selectedIndex: 0,\n }),\n 'increment selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === context.matches.length - 1) {\n return 0\n }\n return context.selectedIndex + 1\n },\n }),\n 'decrement selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === 0) {\n return context.matches.length - 1\n }\n return context.selectedIndex - 1\n },\n }),\n 'set selected index': assign({\n selectedIndex: ({event}) => {\n assertEvent(event, 'navigate to')\n\n return event.index\n },\n }),\n 'update submit listener context': sendTo(\n 'submit listener',\n ({context}) => ({\n type: 'context changed',\n context,\n }),\n ),\n 'insert selected match': ({context}) => {\n const match = context.matches[context.selectedIndex]\n\n if (!match || !context.focusSpan) {\n return\n }\n\n context.editor.send({\n type: 'custom.insert emoji',\n emoji: match.emoji,\n focusSpan: context.focusSpan,\n })\n },\n 'reset': assign({\n focusSpan: undefined,\n keyword: '',\n matches: [],\n selectedIndex: 0,\n }),\n },\n guards: {\n 'no focus span': ({context}) => {\n return !context.focusSpan\n },\n 'has matches': ({context}) => {\n return context.matches.length > 0\n },\n 'no matches': not('has matches'),\n 'keyword is malformed': ({context}) => {\n return !context.incompleteKeywordRegex.test(context.keyword)\n },\n 'keyword is direct match': ({context}) => {\n const fullKeywordRegex = /^:[\\S]+:$/\n\n if (!fullKeywordRegex.test(context.keyword)) {\n return false\n }\n\n const match = context.matches.at(context.selectedIndex)\n\n if (!match || match.type !== 'exact') {\n return false\n }\n\n return true\n },\n },\n}).createMachine({\n id: 'emoji picker',\n context: ({input}) => ({\n editor: input.editor,\n keyword: '',\n focusSpan: undefined,\n matchEmojis: input.matchEmojis,\n incompleteKeywordRegex: /^:[\\S]*$/,\n matches: [],\n selectedIndex: 0,\n }),\n initial: 'idle',\n invoke: [\n {\n src: 'emoji insert listener',\n id: 'emoji insert listener',\n input: ({context}) => ({context}),\n },\n ],\n states: {\n idle: {\n entry: ['reset'],\n invoke: {\n src: 'trigger listener',\n input: ({context}) => ({editor: context.editor}),\n },\n on: {\n 'custom.trigger found': {\n target: 'searching',\n actions: ['set focus span', 'update keyword'],\n },\n 'custom.keyword found': {\n actions: [\n 'set focus span',\n 'update keyword',\n 'update matches',\n 'insert selected match',\n ],\n target: 'idle',\n reenter: true,\n },\n },\n },\n searching: {\n invoke: [\n {\n src: 'submit listener',\n id: 'submit listener',\n input: ({context}) => ({context}),\n },\n {\n src: 'escape listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'dismiss': {\n target: 'idle',\n },\n 'selection changed': [\n {\n actions: [\n 'update focus span',\n 'update keyword',\n 'update matches',\n 'reset selected index',\n 'update submit listener context',\n ],\n },\n ],\n },\n always: [\n {\n guard: 'no focus span',\n target: 'idle',\n },\n {\n guard: 'keyword is malformed',\n target: 'idle',\n },\n {\n guard: 'keyword is direct match',\n actions: ['insert selected match'],\n target: 'idle',\n },\n ],\n initial: 'no matches showing',\n states: {\n 'no matches showing': {\n entry: ['reset selected index'],\n always: {\n guard: 'has matches',\n target: 'showing matches',\n },\n },\n 'showing matches': {\n invoke: {\n src: 'arrow listener',\n input: ({context}) => ({editor: context.editor}),\n },\n always: [\n {\n guard: 'no matches',\n target: 'no matches showing',\n },\n ],\n on: {\n 'navigate down': {\n actions: [\n 'increment selected index',\n 'update submit listener context',\n ],\n },\n 'navigate up': {\n actions: [\n 'decrement selected index',\n 'update submit listener context',\n ],\n },\n 'navigate to': {\n actions: ['set selected index', 'update submit listener context'],\n },\n 'insert selected match': {\n actions: ['insert selected match'],\n },\n },\n },\n },\n },\n },\n})\n","import {useEditor} from '@portabletext/editor'\nimport {useActorRef, useSelector} from '@xstate/react'\nimport {useCallback} from 'react'\nimport {emojiPickerMachine} from './emoji-picker-machine'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/**\n * @beta\n */\nexport type EmojiPicker<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> = {\n /**\n * The matched keyword.\n *\n * Can be used to display the keyword in the UI or conditionally render the\n * list of matches.\n *\n * @example\n * ```tsx\n * if (keyword.length < 1) {\n * return null\n * }\n * ```\n */\n keyword: string\n\n /**\n * Emoji matches found for the current keyword.\n *\n * Can be used to display the matches in a list.\n */\n matches: ReadonlyArray<TEmojiMatch>\n\n /**\n * The index of the selected match.\n *\n * Can be used to highlight the selected match in the list.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * />\n * ```\n */\n selectedIndex: number\n\n /**\n * Navigate to a specific match by index.\n *\n * Can be used to control the `selectedIndex`. For example, using\n * `onMouseEnter`.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * />\n * ```\n */\n onNavigateTo: (index: number) => void\n\n /**\n * Select the current match.\n *\n * Can be used to insert the currently selected match.\n *\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * onSelect={() => {onSelect()}}\n * />\n * ```\n *\n * Note: The currently selected match is automatically inserted on Enter or\n * Tab.\n */\n onSelect: () => void\n\n /**\n * Dismiss the emoji picker. Can be used to let the user dismiss the picker\n * by clicking a button.\n *\n * @example\n * ```tsx\n * {matches.length === 0 ? (\n * <Button onPress={onDismiss}>Dismiss</Button>\n * ) : <EmojiListBox {...props} />}\n * ```\n *\n * Note: The emoji picker is automatically dismissed on Escape.\n */\n onDismiss: () => void\n}\n\n/**\n * @beta\n */\nexport type EmojiPickerProps<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n> = {\n matchEmojis: MatchEmojis<TEmojiMatch>\n}\n\n/**\n * Handles the state and logic needed to create an emoji picker.\n *\n * The `matchEmojis` function is generic and can return any shape of emoji\n * match required for the emoji picker.\n *\n * However, the default implementation of `matchEmojis` returns an array of\n * `EmojiMatch` objects and can be created using the `createMatchEmojis`\n * function.\n *\n * @example\n *\n * ```tsx\n * const matchEmojis = createMatchEmojis({emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * }})\n *\n * const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} =\n * useEmojiPicker({matchEmojis})\n * ```\n *\n * Note: This hook is not concerned with the UI, how the emoji picker is\n * rendered or positioned in the document.\n *\n * @beta\n */\nexport function useEmojiPicker<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n>(props: EmojiPickerProps<TEmojiMatch>): EmojiPicker<TEmojiMatch> {\n const editor = useEditor()\n const emojiPickerActor = useActorRef(emojiPickerMachine, {\n input: {editor, matchEmojis: props.matchEmojis},\n })\n const keyword = useSelector(emojiPickerActor, (snapshot) => {\n const rawKeyword = snapshot.context.keyword.startsWith(':')\n ? snapshot.context.keyword.slice(1)\n : snapshot.context.keyword\n return rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n })\n const matches = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.matches as ReadonlyArray<TEmojiMatch>,\n )\n const selectedIndex = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.selectedIndex,\n )\n\n const onDismiss = useCallback(() => {\n emojiPickerActor.send({type: 'dismiss'})\n }, [emojiPickerActor])\n const onNavigateTo = useCallback(\n (index: number) => {\n emojiPickerActor.send({type: 'navigate to', index})\n },\n [emojiPickerActor],\n )\n const onSelect = useCallback(() => {\n emojiPickerActor.send({type: 'insert selected match'})\n editor.send({type: 'focus'})\n }, [emojiPickerActor, editor])\n\n return {\n keyword,\n matches,\n selectedIndex,\n onDismiss,\n onNavigateTo,\n onSelect,\n }\n}\n"],"names":["createMatchEmojis","config","keyword","foundEmojis","length","emoji","emojis","emojiKeywords","emojiKeyword","keywordIndex","indexOf","push","type","key","start","slice","end","startSlice","endSlice","arrowUpShortcut","createKeyboardShortcut","default","arrowDownShortcut","enterShortcut","tabShortcut","escapeShortcut","getTriggerState","snapshot","focusSpan","getFocusSpan","markState","getMarkState","context","selection","focusSpanTextBefore","node","text","focus","offset","focusSpanTextAfter","nextSpan","getNextSpan","createTriggerActions","payload","keywordState","state","_key","_type","lastMatch","marks","path","textBefore","textAfter","raise","createKeywordFoundEvent","createTriggerFoundEvent","newSpan","keyGenerator","at","targetOffsets","child","triggerRule","defineInputRule","on","guard","event","matches","undefined","triggerState","actions","partialKeywordRule","anchor","keywordRule","triggerListenerCallback","sendBack","input","unregisterBehaviors","editor","registerBehavior","behavior","defineInputRuleBehavior","rules","defineBehavior","effect","unregister","escapeListenerCallback","originEvent","arrowListenerCallback","emojiInsertListener","submitListenerCallback","receive","match","selectedIndex","_","selectionListenerCallback","getSnapshot","unsubscribe","emojiPickerMachine","setup","types","events","actors","fromCallback","assign","JSON","stringify","startsWith","endsWith","keywordAnchor","keywordFocus","selectionIsBeforeKeyword","isPointAfterSelection","selectionIsAfterKeyword","isPointBeforeSelection","rawKeyword","matchEmojis","assertEvent","index","sendTo","insert selected match","send","guards","no focus span","has matches","not","keyword is malformed","incompleteKeywordRegex","test","keyword is direct match","createMachine","id","initial","invoke","src","states","idle","entry","target","reenter","searching","always","useEmojiPicker","props","$","_c","useEditor","t0","emojiPickerActor","useActorRef","useSelector","_temp","_temp2","_temp3","t1","onDismiss","t2","onNavigateTo","t3","onSelect","t4","snapshot_1","snapshot_0"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/create-match-emojis.ts","../src/emoji-picker-machine.tsx","../src/use-emoji-picker.ts"],"sourcesContent":["import type {MatchEmojis} from './match-emojis'\n\n/**\n * Proposed, but not required type, to represent an emoji match.\n *\n * @example\n * ```tsx\n * {\n * type: 'exact',\n * key: 'π-joy',\n * emoji: 'π',\n * keyword: 'joy',\n * }\n * ```\n * @example\n * ```tsx\n * {\n * type: 'partial',\n * key: 'πΉ-joy-_cat',\n * emoji: 'πΉ',\n * keyword: 'joy',\n * startSlice: '',\n * endSlice: '_cat',\n * }\n * ```\n *\n * @beta\n */\nexport type EmojiMatch =\n | {\n type: 'exact'\n key: string\n emoji: string\n keyword: string\n }\n | {\n type: 'partial'\n key: string\n emoji: string\n keyword: string\n startSlice: string\n endSlice: string\n }\n\n/**\n * Proposed, but not required, function to create a `MatchEmojis` function.\n *\n * @example\n * ```ts\n * const matchEmojis = createMatchEmojis({\n * emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * },\n * })\n * ```\n *\n * @beta\n */\nexport function createMatchEmojis(config: {\n emojis: Record<string, ReadonlyArray<string>>\n}): MatchEmojis<EmojiMatch> {\n return ({keyword}: {keyword: string}) => {\n const foundEmojis: Array<EmojiMatch> = []\n\n if (keyword.length < 1) {\n return foundEmojis\n }\n\n for (const emoji in config.emojis) {\n const emojiKeywords = config.emojis[emoji] ?? []\n\n for (const emojiKeyword of emojiKeywords) {\n const keywordIndex = emojiKeyword.indexOf(keyword)\n\n if (keywordIndex === -1) {\n continue\n }\n\n if (emojiKeyword === keyword) {\n foundEmojis.push({\n type: 'exact',\n key: `${emoji}-${keyword}`,\n emoji,\n keyword,\n })\n } else {\n const start = emojiKeyword.slice(0, keywordIndex)\n const end = emojiKeyword.slice(keywordIndex + keyword.length)\n\n foundEmojis.push({\n type: 'partial',\n key: `${emoji}-${start}${keyword}${end}`,\n emoji,\n keyword,\n startSlice: start,\n endSlice: end,\n })\n }\n }\n }\n\n return foundEmojis\n }\n}\n","import type {\n ChildPath,\n Editor,\n EditorSelector,\n EditorSnapshot,\n PortableTextSpan,\n} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n} from '@portabletext/editor/behaviors'\nimport {\n getFocusSpan,\n getMarkState,\n getNextSpan,\n getPreviousSpan,\n isPointAfterSelection,\n isPointBeforeSelection,\n type MarkState,\n} from '@portabletext/editor/selectors'\nimport {\n isEqualSelectionPoints,\n isSelectionCollapsed,\n} from '@portabletext/editor/utils'\nimport {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'\nimport {\n defineInputRule,\n defineInputRuleBehavior,\n type InputRuleMatch,\n} from '@portabletext/plugin-input-rule'\nimport {\n assertEvent,\n assign,\n fromCallback,\n not,\n sendTo,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/*******************\n * Keyboard shortcuts\n *******************/\nconst arrowUpShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowUp'}],\n})\nconst arrowDownShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowDown'}],\n})\nconst enterShortcut = createKeyboardShortcut({\n default: [{key: 'Enter'}],\n})\nconst tabShortcut = createKeyboardShortcut({\n default: [{key: 'Tab'}],\n})\nconst escapeShortcut = createKeyboardShortcut({\n default: [{key: 'Escape'}],\n})\n\nconst getTriggerState: EditorSelector<\n | {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n }\n markState: MarkState\n focusSpanTextBefore: string\n focusSpanTextAfter: string\n previousSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n }\n | undefined\n nextSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n }\n | undefined\n }\n | undefined\n> = (snapshot) => {\n const focusSpan = getFocusSpan(snapshot)\n const markState = getMarkState(snapshot)\n\n if (!focusSpan || !markState || !snapshot.context.selection) {\n return undefined\n }\n\n const focusSpanTextBefore = focusSpan.node.text.slice(\n 0,\n snapshot.context.selection.focus.offset,\n )\n const focusSpanTextAfter = focusSpan.node.text.slice(\n snapshot.context.selection.focus.offset,\n )\n const previousSpan = getPreviousSpan(snapshot)\n const nextSpan = getNextSpan(snapshot)\n\n return {\n focusSpan,\n markState,\n focusSpanTextBefore,\n focusSpanTextAfter,\n previousSpan,\n nextSpan,\n }\n}\n\nfunction createTriggerActions({\n snapshot,\n payload,\n keywordState,\n}: {\n snapshot: EditorSnapshot\n payload: ReturnType<typeof getTriggerState> & {lastMatch: InputRuleMatch}\n keywordState: 'partial' | 'complete'\n}) {\n if (payload.markState.state === 'unchanged') {\n const focusSpan = {\n node: {\n _key: payload.focusSpan.node._key,\n _type: payload.focusSpan.node._type,\n text: `${payload.focusSpanTextBefore}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,\n marks: payload.markState.marks,\n },\n path: payload.focusSpan.path,\n textBefore: payload.focusSpanTextBefore,\n textAfter: payload.focusSpanTextAfter,\n }\n\n if (keywordState === 'complete') {\n return [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n return [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n const newSpan = {\n _key: snapshot.context.keyGenerator(),\n _type: payload.focusSpan.node._type,\n text: payload.lastMatch.text,\n marks: payload.markState.marks,\n }\n\n let focusSpan = {\n node: {\n _key: newSpan._key,\n _type: newSpan._type,\n text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,\n marks: payload.markState.marks,\n },\n path: [\n {_key: payload.focusSpan.path[0]._key},\n 'children',\n {_key: newSpan._key},\n ] satisfies ChildPath,\n textBefore: '',\n textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter,\n }\n\n if (\n payload.previousSpan &&\n payload.focusSpanTextBefore.length === 0 &&\n JSON.stringify(payload.previousSpan.node.marks ?? []) ===\n JSON.stringify(payload.markState.marks)\n ) {\n // The text will be inserted into the previous span, so we'll treat that\n // as the focus span\n\n focusSpan = {\n node: {\n _key: payload.previousSpan.node._key,\n _type: newSpan._type,\n text: `${payload.previousSpan.node.text}${newSpan.text}`,\n marks: newSpan.marks,\n },\n path: payload.previousSpan.path,\n textBefore: payload.previousSpan.node.text,\n textAfter: '',\n }\n }\n\n return [\n raise({type: 'select', at: payload.lastMatch.targetOffsets}),\n raise({type: 'delete', at: payload.lastMatch.targetOffsets}),\n raise({type: 'insert.child', child: newSpan}),\n ...(keywordState === 'complete'\n ? [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n : [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]),\n ]\n}\n\n/*******************\n * Input Rules\n *******************/\n\n/**\n * Listen for a single colon insertion\n */\nconst triggerRule = defineInputRule({\n on: /:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n lastMatch,\n ...triggerState,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\ntype TriggerFoundEvent = ReturnType<typeof createTriggerFoundEvent>\n\nfunction createTriggerFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.trigger found',\n ...payload,\n } as const\n}\n\n/**\n * Listen for a partial keyword like \":joy\"\n */\nconst partialKeywordRule = defineInputRule({\n on: /:[\\S]+/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\n/**\n * Listen for a complete keyword like \":joy:\"\n */\nconst keywordRule = defineInputRule({\n on: /:[\\S]+:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'complete'}),\n ],\n})\n\ntype KeywordFoundEvent = ReturnType<typeof createKeywordFoundEvent>\n\nfunction createKeywordFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.keyword found',\n ...payload,\n } as const\n}\n\ntype EmojiPickerContext = {\n editor: Editor\n matches: ReadonlyArray<BaseEmojiMatch>\n matchEmojis: MatchEmojis<BaseEmojiMatch>\n selectedIndex: number\n focusSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n | undefined\n incompleteKeywordRegex: RegExp\n keyword: string\n}\n\ntype EmojiPickerEvent =\n | TriggerFoundEvent\n | KeywordFoundEvent\n | {\n type: 'selection changed'\n }\n | {\n type: 'dismiss'\n }\n | {\n type: 'navigate down'\n }\n | {\n type: 'navigate up'\n }\n | {\n type: 'navigate to'\n index: number\n }\n | {\n type: 'insert selected match'\n }\n\nconst triggerListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineInputRuleBehavior({\n rules: [keywordRule, partialKeywordRule, triggerRule],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<KeywordFoundEvent, KeywordFoundEvent['type']>({\n on: 'custom.keyword found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<TriggerFoundEvent, TriggerFoundEvent['type']>({\n on: 'custom.trigger found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst escapeListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => escapeShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst arrowListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowDownShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate down'})\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowUpShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate up'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst emojiInsertListener: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input}) => {\n return input.context.editor.registerBehavior({\n behavior: defineBehavior<{\n emoji: string\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n }>({\n on: 'custom.insert emoji',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n raise({\n type: 'delete',\n at: {\n anchor: {\n path: event.focusSpan.path,\n offset: event.focusSpan.textBefore.length,\n },\n focus: {\n path: event.focusSpan.path,\n offset:\n event.focusSpan.node.text.length -\n event.focusSpan.textAfter.length,\n },\n },\n }),\n raise({\n type: 'insert.text',\n text: event.emoji,\n }),\n ],\n ],\n }),\n })\n}\n\nconst submitListenerCallback: CallbackLogicFunction<\n {type: 'context changed'; context: EmojiPickerContext},\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input, receive}) => {\n let context = input.context\n\n receive((event) => {\n context = event.context\n })\n\n const unregisterBehaviors = [\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => {\n if (\n !enterShortcut.guard(event.originEvent) &&\n !tabShortcut.guard(event.originEvent)\n ) {\n return false\n }\n\n const focusSpan = context.focusSpan\n const match = context.matches[context.selectedIndex]\n\n return match && focusSpan ? {focusSpan, emoji: match.emoji} : false\n },\n actions: [\n (_, {focusSpan, emoji}) => [\n raise({\n type: 'custom.insert emoji',\n emoji,\n focusSpan,\n }),\n ],\n ],\n }),\n }),\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) =>\n enterShortcut.guard(event.originEvent) ||\n tabShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const subscription = input.editor.on('selection', () => {\n sendBack({type: 'selection changed'})\n })\n\n return subscription.unsubscribe\n}\n\nconst textInsertionListenerCallback: CallbackLogicFunction<\n {type: 'context changed'; context: EmojiPickerContext},\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input, receive}) => {\n let context = input.context\n\n receive((event) => {\n context = event.context\n })\n\n return input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'insert.text',\n guard: ({snapshot}) => {\n if (!context.focusSpan) {\n return false\n }\n\n if (!snapshot.context.selection) {\n return false\n }\n\n const keywordAnchor = {\n path: context.focusSpan.path,\n offset: context.focusSpan.textBefore.length,\n }\n\n return isEqualSelectionPoints(\n snapshot.context.selection.focus,\n keywordAnchor,\n )\n },\n actions: [\n ({event}) => [\n forward(event),\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n })\n}\n\nexport const emojiPickerMachine = setup({\n types: {\n context: {} as EmojiPickerContext,\n input: {} as {\n editor: Editor\n matchEmojis: MatchEmojis\n },\n events: {} as EmojiPickerEvent,\n },\n actors: {\n 'emoji insert listener': fromCallback(emojiInsertListener),\n 'submit listener': fromCallback(submitListenerCallback),\n 'arrow listener': fromCallback(arrowListenerCallback),\n 'trigger listener': fromCallback(triggerListenerCallback),\n 'escape listener': fromCallback(escapeListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n 'text insertion listener': fromCallback(textInsertionListenerCallback),\n },\n actions: {\n 'set focus span': assign({\n focusSpan: ({context, event}) => {\n if (\n event.type !== 'custom.trigger found' &&\n event.type !== 'custom.keyword found'\n ) {\n return context.focusSpan\n }\n\n return event.focusSpan\n },\n }),\n 'update focus span': assign({\n focusSpan: ({context}) => {\n if (!context.focusSpan) {\n return undefined\n }\n\n const snapshot = context.editor.getSnapshot()\n const focusSpan = getFocusSpan(snapshot)\n\n if (!snapshot.context.selection) {\n return undefined\n }\n\n if (!focusSpan) {\n return undefined\n }\n\n const nextSpan = getNextSpan({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: {\n path: context.focusSpan.path,\n offset: 0,\n },\n focus: {\n path: context.focusSpan.path,\n offset: 0,\n },\n },\n },\n })\n\n if (\n JSON.stringify(focusSpan.path) !==\n JSON.stringify(context.focusSpan.path)\n ) {\n if (\n nextSpan &&\n context.focusSpan.textAfter.length === 0 &&\n snapshot.context.selection.focus.offset === 0 &&\n isSelectionCollapsed(snapshot.context.selection)\n ) {\n // This is an edge case where the caret is moved from the end of\n // the focus span to the start of the next span.\n return context.focusSpan\n }\n\n return undefined\n }\n\n if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore)) {\n return undefined\n }\n\n if (!focusSpan.node.text.endsWith(context.focusSpan.textAfter)) {\n return undefined\n }\n\n const keywordAnchor = {\n path: focusSpan.path,\n offset: context.focusSpan.textBefore.length,\n }\n const keywordFocus = {\n path: focusSpan.path,\n offset:\n focusSpan.node.text.length - context.focusSpan.textAfter.length,\n }\n\n const selectionIsBeforeKeyword =\n isPointAfterSelection(keywordAnchor)(snapshot)\n\n const selectionIsAfterKeyword =\n isPointBeforeSelection(keywordFocus)(snapshot)\n\n if (selectionIsBeforeKeyword || selectionIsAfterKeyword) {\n return undefined\n }\n\n return {\n node: focusSpan.node,\n path: focusSpan.path,\n textBefore: context.focusSpan.textBefore,\n textAfter: context.focusSpan.textAfter,\n }\n },\n }),\n 'update keyword': assign({\n keyword: ({context}) => {\n if (!context.focusSpan) {\n return ''\n }\n\n if (\n context.focusSpan.textBefore.length > 0 &&\n context.focusSpan.textAfter.length > 0\n ) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n -context.focusSpan.textAfter.length,\n )\n }\n\n if (context.focusSpan.textBefore.length > 0) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n )\n }\n\n if (context.focusSpan.textAfter.length > 0) {\n return context.focusSpan.node.text.slice(\n 0,\n -context.focusSpan.textAfter.length,\n )\n }\n\n return context.focusSpan.node.text\n },\n }),\n 'update matches': assign({\n matches: ({context}) => {\n // Strip leading colon\n let rawKeyword = context.keyword.startsWith(':')\n ? context.keyword.slice(1)\n : context.keyword\n // Strip trailing colon\n rawKeyword =\n rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n\n if (rawKeyword === undefined) {\n return []\n }\n\n return context.matchEmojis({keyword: rawKeyword})\n },\n }),\n 'reset selected index': assign({\n selectedIndex: 0,\n }),\n 'increment selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === context.matches.length - 1) {\n return 0\n }\n return context.selectedIndex + 1\n },\n }),\n 'decrement selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === 0) {\n return context.matches.length - 1\n }\n return context.selectedIndex - 1\n },\n }),\n 'set selected index': assign({\n selectedIndex: ({event}) => {\n assertEvent(event, 'navigate to')\n\n return event.index\n },\n }),\n 'update submit listener context': sendTo(\n 'submit listener',\n ({context}) => ({\n type: 'context changed',\n context,\n }),\n ),\n 'update text insertion listener context': sendTo(\n 'text insertion listener',\n ({context}) => ({\n type: 'context changed',\n context,\n }),\n ),\n 'insert selected match': ({context}) => {\n const match = context.matches[context.selectedIndex]\n\n if (!match || !context.focusSpan) {\n return\n }\n\n context.editor.send({\n type: 'custom.insert emoji',\n emoji: match.emoji,\n focusSpan: context.focusSpan,\n })\n },\n 'reset': assign({\n focusSpan: undefined,\n keyword: '',\n matches: [],\n selectedIndex: 0,\n }),\n },\n guards: {\n 'no focus span': ({context}) => {\n return !context.focusSpan\n },\n 'has matches': ({context}) => {\n return context.matches.length > 0\n },\n 'no matches': not('has matches'),\n 'keyword is malformed': ({context}) => {\n return !context.incompleteKeywordRegex.test(context.keyword)\n },\n 'keyword is direct match': ({context}) => {\n const fullKeywordRegex = /^:[\\S]+:$/\n\n if (!fullKeywordRegex.test(context.keyword)) {\n return false\n }\n\n const match = context.matches.at(context.selectedIndex)\n\n if (!match || match.type !== 'exact') {\n return false\n }\n\n return true\n },\n },\n}).createMachine({\n id: 'emoji picker',\n context: ({input}) => ({\n editor: input.editor,\n keyword: '',\n focusSpan: undefined,\n matchEmojis: input.matchEmojis,\n incompleteKeywordRegex: /^:[\\S]*$/,\n matches: [],\n selectedIndex: 0,\n }),\n initial: 'idle',\n invoke: [\n {\n src: 'emoji insert listener',\n id: 'emoji insert listener',\n input: ({context}) => ({context}),\n },\n ],\n states: {\n idle: {\n entry: ['reset'],\n invoke: {\n src: 'trigger listener',\n input: ({context}) => ({editor: context.editor}),\n },\n on: {\n 'custom.trigger found': {\n target: 'searching',\n actions: ['set focus span', 'update keyword'],\n },\n 'custom.keyword found': {\n actions: [\n 'set focus span',\n 'update keyword',\n 'update matches',\n 'insert selected match',\n ],\n target: 'idle',\n reenter: true,\n },\n },\n },\n searching: {\n invoke: [\n {\n src: 'submit listener',\n id: 'submit listener',\n input: ({context}) => ({context}),\n },\n {\n src: 'escape listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'text insertion listener',\n id: 'text insertion listener',\n input: ({context}) => ({context}),\n },\n ],\n on: {\n 'dismiss': {\n target: 'idle',\n },\n 'selection changed': [\n {\n actions: [\n 'update focus span',\n 'update keyword',\n 'update matches',\n 'reset selected index',\n 'update submit listener context',\n 'update text insertion listener context',\n ],\n },\n ],\n },\n always: [\n {\n guard: 'no focus span',\n target: 'idle',\n },\n {\n guard: 'keyword is malformed',\n target: 'idle',\n },\n {\n guard: 'keyword is direct match',\n actions: ['insert selected match'],\n target: 'idle',\n },\n ],\n initial: 'no matches showing',\n states: {\n 'no matches showing': {\n entry: ['reset selected index'],\n always: {\n guard: 'has matches',\n target: 'showing matches',\n },\n },\n 'showing matches': {\n invoke: {\n src: 'arrow listener',\n input: ({context}) => ({editor: context.editor}),\n },\n always: [\n {\n guard: 'no matches',\n target: 'no matches showing',\n },\n ],\n on: {\n 'navigate down': {\n actions: [\n 'increment selected index',\n 'update submit listener context',\n ],\n },\n 'navigate up': {\n actions: [\n 'decrement selected index',\n 'update submit listener context',\n ],\n },\n 'navigate to': {\n actions: ['set selected index', 'update submit listener context'],\n },\n 'insert selected match': {\n actions: ['insert selected match'],\n },\n },\n },\n },\n },\n },\n})\n","import {useEditor} from '@portabletext/editor'\nimport {useActorRef, useSelector} from '@xstate/react'\nimport {useCallback} from 'react'\nimport {emojiPickerMachine} from './emoji-picker-machine'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/**\n * @beta\n */\nexport type EmojiPicker<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> = {\n /**\n * The matched keyword.\n *\n * Can be used to display the keyword in the UI or conditionally render the\n * list of matches.\n *\n * @example\n * ```tsx\n * if (keyword.length < 1) {\n * return null\n * }\n * ```\n */\n keyword: string\n\n /**\n * Emoji matches found for the current keyword.\n *\n * Can be used to display the matches in a list.\n */\n matches: ReadonlyArray<TEmojiMatch>\n\n /**\n * The index of the selected match.\n *\n * Can be used to highlight the selected match in the list.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * />\n * ```\n */\n selectedIndex: number\n\n /**\n * Navigate to a specific match by index.\n *\n * Can be used to control the `selectedIndex`. For example, using\n * `onMouseEnter`.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * />\n * ```\n */\n onNavigateTo: (index: number) => void\n\n /**\n * Select the current match.\n *\n * Can be used to insert the currently selected match.\n *\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * onSelect={() => {onSelect()}}\n * />\n * ```\n *\n * Note: The currently selected match is automatically inserted on Enter or\n * Tab.\n */\n onSelect: () => void\n\n /**\n * Dismiss the emoji picker. Can be used to let the user dismiss the picker\n * by clicking a button.\n *\n * @example\n * ```tsx\n * {matches.length === 0 ? (\n * <Button onPress={onDismiss}>Dismiss</Button>\n * ) : <EmojiListBox {...props} />}\n * ```\n *\n * Note: The emoji picker is automatically dismissed on Escape.\n */\n onDismiss: () => void\n}\n\n/**\n * @beta\n */\nexport type EmojiPickerProps<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n> = {\n matchEmojis: MatchEmojis<TEmojiMatch>\n}\n\n/**\n * Handles the state and logic needed to create an emoji picker.\n *\n * The `matchEmojis` function is generic and can return any shape of emoji\n * match required for the emoji picker.\n *\n * However, the default implementation of `matchEmojis` returns an array of\n * `EmojiMatch` objects and can be created using the `createMatchEmojis`\n * function.\n *\n * @example\n *\n * ```tsx\n * const matchEmojis = createMatchEmojis({emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * }})\n *\n * const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} =\n * useEmojiPicker({matchEmojis})\n * ```\n *\n * Note: This hook is not concerned with the UI, how the emoji picker is\n * rendered or positioned in the document.\n *\n * @beta\n */\nexport function useEmojiPicker<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n>(props: EmojiPickerProps<TEmojiMatch>): EmojiPicker<TEmojiMatch> {\n const editor = useEditor()\n const emojiPickerActor = useActorRef(emojiPickerMachine, {\n input: {editor, matchEmojis: props.matchEmojis},\n })\n const keyword = useSelector(emojiPickerActor, (snapshot) => {\n const rawKeyword = snapshot.context.keyword.startsWith(':')\n ? snapshot.context.keyword.slice(1)\n : snapshot.context.keyword\n return rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n })\n const matches = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.matches as ReadonlyArray<TEmojiMatch>,\n )\n const selectedIndex = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.selectedIndex,\n )\n\n const onDismiss = useCallback(() => {\n emojiPickerActor.send({type: 'dismiss'})\n }, [emojiPickerActor])\n const onNavigateTo = useCallback(\n (index: number) => {\n emojiPickerActor.send({type: 'navigate to', index})\n },\n [emojiPickerActor],\n )\n const onSelect = useCallback(() => {\n emojiPickerActor.send({type: 'insert selected match'})\n editor.send({type: 'focus'})\n }, [emojiPickerActor, editor])\n\n return {\n keyword,\n matches,\n selectedIndex,\n onDismiss,\n onNavigateTo,\n onSelect,\n }\n}\n"],"names":["createMatchEmojis","config","keyword","foundEmojis","length","emoji","emojis","emojiKeywords","emojiKeyword","keywordIndex","indexOf","push","type","key","start","slice","end","startSlice","endSlice","arrowUpShortcut","createKeyboardShortcut","default","arrowDownShortcut","enterShortcut","tabShortcut","escapeShortcut","getTriggerState","snapshot","focusSpan","getFocusSpan","markState","getMarkState","context","selection","focusSpanTextBefore","node","text","focus","offset","focusSpanTextAfter","previousSpan","getPreviousSpan","nextSpan","getNextSpan","createTriggerActions","payload","keywordState","state","_key","_type","lastMatch","marks","path","textBefore","textAfter","raise","createKeywordFoundEvent","createTriggerFoundEvent","newSpan","keyGenerator","JSON","stringify","at","targetOffsets","child","triggerRule","defineInputRule","on","guard","event","matches","undefined","triggerState","actions","partialKeywordRule","anchor","keywordRule","triggerListenerCallback","sendBack","input","unregisterBehaviors","editor","registerBehavior","behavior","defineInputRuleBehavior","rules","defineBehavior","effect","unregister","escapeListenerCallback","originEvent","arrowListenerCallback","emojiInsertListener","submitListenerCallback","receive","match","selectedIndex","_","selectionListenerCallback","unsubscribe","textInsertionListenerCallback","keywordAnchor","isEqualSelectionPoints","forward","emojiPickerMachine","setup","types","events","actors","fromCallback","assign","getSnapshot","isSelectionCollapsed","startsWith","endsWith","keywordFocus","selectionIsBeforeKeyword","isPointAfterSelection","selectionIsAfterKeyword","isPointBeforeSelection","rawKeyword","matchEmojis","assertEvent","index","sendTo","insert selected match","send","guards","no focus span","has matches","not","keyword is malformed","incompleteKeywordRegex","test","keyword is direct match","createMachine","id","initial","invoke","src","states","idle","entry","target","reenter","searching","always","useEmojiPicker","props","$","_c","useEditor","t0","emojiPickerActor","useActorRef","useSelector","_temp","_temp2","_temp3","t1","onDismiss","t2","onNavigateTo","t3","onSelect","t4","snapshot_1","snapshot_0"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { c } from "react/compiler-runtime";
|
|
2
2
|
import { useEditor } from "@portabletext/editor";
|
|
3
3
|
import { useActorRef, useSelector } from "@xstate/react";
|
|
4
|
-
import { raise, defineBehavior, effect } from "@portabletext/editor/behaviors";
|
|
5
|
-
import { getFocusSpan, isPointAfterSelection, isPointBeforeSelection, getMarkState,
|
|
4
|
+
import { raise, defineBehavior, forward, effect } from "@portabletext/editor/behaviors";
|
|
5
|
+
import { getFocusSpan, getNextSpan, isPointAfterSelection, isPointBeforeSelection, getMarkState, getPreviousSpan } from "@portabletext/editor/selectors";
|
|
6
|
+
import { isSelectionCollapsed, isEqualSelectionPoints } from "@portabletext/editor/utils";
|
|
6
7
|
import { createKeyboardShortcut } from "@portabletext/keyboard-shortcuts";
|
|
7
8
|
import { defineInputRule, defineInputRuleBehavior } from "@portabletext/plugin-input-rule";
|
|
8
9
|
import { setup, not, assign, sendTo, fromCallback, assertEvent } from "xstate";
|
|
@@ -65,12 +66,13 @@ const arrowUpShortcut = createKeyboardShortcut({
|
|
|
65
66
|
const focusSpan = getFocusSpan(snapshot), markState = getMarkState(snapshot);
|
|
66
67
|
if (!focusSpan || !markState || !snapshot.context.selection)
|
|
67
68
|
return;
|
|
68
|
-
const focusSpanTextBefore = focusSpan.node.text.slice(0, snapshot.context.selection.focus.offset), focusSpanTextAfter = focusSpan.node.text.slice(snapshot.context.selection.focus.offset), nextSpan = getNextSpan(snapshot);
|
|
69
|
+
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);
|
|
69
70
|
return {
|
|
70
71
|
focusSpan,
|
|
71
72
|
markState,
|
|
72
73
|
focusSpanTextBefore,
|
|
73
74
|
focusSpanTextAfter,
|
|
75
|
+
previousSpan,
|
|
74
76
|
nextSpan
|
|
75
77
|
};
|
|
76
78
|
};
|
|
@@ -102,11 +104,12 @@ function createTriggerActions({
|
|
|
102
104
|
_type: payload.focusSpan.node._type,
|
|
103
105
|
text: payload.lastMatch.text,
|
|
104
106
|
marks: payload.markState.marks
|
|
105
|
-
}
|
|
107
|
+
};
|
|
108
|
+
let focusSpan = {
|
|
106
109
|
node: {
|
|
107
110
|
_key: newSpan._key,
|
|
108
111
|
_type: newSpan._type,
|
|
109
|
-
text: `${newSpan.text}${payload.nextSpan?.node.text ??
|
|
112
|
+
text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,
|
|
110
113
|
marks: payload.markState.marks
|
|
111
114
|
},
|
|
112
115
|
path: [{
|
|
@@ -115,9 +118,19 @@ function createTriggerActions({
|
|
|
115
118
|
_key: newSpan._key
|
|
116
119
|
}],
|
|
117
120
|
textBefore: "",
|
|
118
|
-
textAfter: payload.nextSpan?.node.text ??
|
|
121
|
+
textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter
|
|
119
122
|
};
|
|
120
|
-
return [
|
|
123
|
+
return payload.previousSpan && payload.focusSpanTextBefore.length === 0 && JSON.stringify(payload.previousSpan.node.marks ?? []) === JSON.stringify(payload.markState.marks) && (focusSpan = {
|
|
124
|
+
node: {
|
|
125
|
+
_key: payload.previousSpan.node._key,
|
|
126
|
+
_type: newSpan._type,
|
|
127
|
+
text: `${payload.previousSpan.node.text}${newSpan.text}`,
|
|
128
|
+
marks: newSpan.marks
|
|
129
|
+
},
|
|
130
|
+
path: payload.previousSpan.path,
|
|
131
|
+
textBefore: payload.previousSpan.node.text,
|
|
132
|
+
textAfter: ""
|
|
133
|
+
}), [raise({
|
|
121
134
|
type: "select",
|
|
122
135
|
at: payload.lastMatch.targetOffsets
|
|
123
136
|
}), raise({
|
|
@@ -373,12 +386,41 @@ const triggerListenerCallback = ({
|
|
|
373
386
|
sendBack,
|
|
374
387
|
input
|
|
375
388
|
}) => input.editor.on("selection", () => {
|
|
376
|
-
const snapshot = input.editor.getSnapshot();
|
|
377
389
|
sendBack({
|
|
378
|
-
type: "selection changed"
|
|
379
|
-
|
|
390
|
+
type: "selection changed"
|
|
391
|
+
});
|
|
392
|
+
}).unsubscribe, textInsertionListenerCallback = ({
|
|
393
|
+
sendBack,
|
|
394
|
+
input,
|
|
395
|
+
receive
|
|
396
|
+
}) => {
|
|
397
|
+
let context = input.context;
|
|
398
|
+
return receive((event) => {
|
|
399
|
+
context = event.context;
|
|
400
|
+
}), input.context.editor.registerBehavior({
|
|
401
|
+
behavior: defineBehavior({
|
|
402
|
+
on: "insert.text",
|
|
403
|
+
guard: ({
|
|
404
|
+
snapshot
|
|
405
|
+
}) => {
|
|
406
|
+
if (!context.focusSpan || !snapshot.context.selection)
|
|
407
|
+
return !1;
|
|
408
|
+
const keywordAnchor = {
|
|
409
|
+
path: context.focusSpan.path,
|
|
410
|
+
offset: context.focusSpan.textBefore.length
|
|
411
|
+
};
|
|
412
|
+
return isEqualSelectionPoints(snapshot.context.selection.focus, keywordAnchor);
|
|
413
|
+
},
|
|
414
|
+
actions: [({
|
|
415
|
+
event
|
|
416
|
+
}) => [forward(event), effect(() => {
|
|
417
|
+
sendBack({
|
|
418
|
+
type: "dismiss"
|
|
419
|
+
});
|
|
420
|
+
})]]
|
|
421
|
+
})
|
|
380
422
|
});
|
|
381
|
-
}
|
|
423
|
+
}, emojiPickerMachine = setup({
|
|
382
424
|
types: {
|
|
383
425
|
context: {},
|
|
384
426
|
input: {},
|
|
@@ -390,7 +432,8 @@ const triggerListenerCallback = ({
|
|
|
390
432
|
"arrow listener": fromCallback(arrowListenerCallback),
|
|
391
433
|
"trigger listener": fromCallback(triggerListenerCallback),
|
|
392
434
|
"escape listener": fromCallback(escapeListenerCallback),
|
|
393
|
-
"selection listener": fromCallback(selectionListenerCallback)
|
|
435
|
+
"selection listener": fromCallback(selectionListenerCallback),
|
|
436
|
+
"text insertion listener": fromCallback(textInsertionListenerCallback)
|
|
394
437
|
},
|
|
395
438
|
actions: {
|
|
396
439
|
"set focus span": assign({
|
|
@@ -406,7 +449,27 @@ const triggerListenerCallback = ({
|
|
|
406
449
|
if (!context.focusSpan)
|
|
407
450
|
return;
|
|
408
451
|
const snapshot = context.editor.getSnapshot(), focusSpan = getFocusSpan(snapshot);
|
|
409
|
-
if (!
|
|
452
|
+
if (!snapshot.context.selection || !focusSpan)
|
|
453
|
+
return;
|
|
454
|
+
const nextSpan = getNextSpan({
|
|
455
|
+
...snapshot,
|
|
456
|
+
context: {
|
|
457
|
+
...snapshot.context,
|
|
458
|
+
selection: {
|
|
459
|
+
anchor: {
|
|
460
|
+
path: context.focusSpan.path,
|
|
461
|
+
offset: 0
|
|
462
|
+
},
|
|
463
|
+
focus: {
|
|
464
|
+
path: context.focusSpan.path,
|
|
465
|
+
offset: 0
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
if (JSON.stringify(focusSpan.path) !== JSON.stringify(context.focusSpan.path))
|
|
471
|
+
return nextSpan && context.focusSpan.textAfter.length === 0 && snapshot.context.selection.focus.offset === 0 && isSelectionCollapsed(snapshot.context.selection) ? context.focusSpan : void 0;
|
|
472
|
+
if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore) || !focusSpan.node.text.endsWith(context.focusSpan.textAfter))
|
|
410
473
|
return;
|
|
411
474
|
const keywordAnchor = {
|
|
412
475
|
path: focusSpan.path,
|
|
@@ -463,6 +526,12 @@ const triggerListenerCallback = ({
|
|
|
463
526
|
type: "context changed",
|
|
464
527
|
context
|
|
465
528
|
})),
|
|
529
|
+
"update text insertion listener context": sendTo("text insertion listener", ({
|
|
530
|
+
context
|
|
531
|
+
}) => ({
|
|
532
|
+
type: "context changed",
|
|
533
|
+
context
|
|
534
|
+
})),
|
|
466
535
|
"insert selected match": ({
|
|
467
536
|
context
|
|
468
537
|
}) => {
|
|
@@ -569,13 +638,21 @@ const triggerListenerCallback = ({
|
|
|
569
638
|
}) => ({
|
|
570
639
|
editor: context.editor
|
|
571
640
|
})
|
|
641
|
+
}, {
|
|
642
|
+
src: "text insertion listener",
|
|
643
|
+
id: "text insertion listener",
|
|
644
|
+
input: ({
|
|
645
|
+
context
|
|
646
|
+
}) => ({
|
|
647
|
+
context
|
|
648
|
+
})
|
|
572
649
|
}],
|
|
573
650
|
on: {
|
|
574
651
|
dismiss: {
|
|
575
652
|
target: "idle"
|
|
576
653
|
},
|
|
577
654
|
"selection changed": [{
|
|
578
|
-
actions: ["update focus span", "update keyword", "update matches", "reset selected index", "update submit listener context"]
|
|
655
|
+
actions: ["update focus span", "update keyword", "update matches", "reset selected index", "update submit listener context", "update text insertion listener context"]
|
|
579
656
|
}]
|
|
580
657
|
},
|
|
581
658
|
always: [{
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/create-match-emojis.ts","../src/emoji-picker-machine.tsx","../src/use-emoji-picker.ts"],"sourcesContent":["import type {MatchEmojis} from './match-emojis'\n\n/**\n * Proposed, but not required type, to represent an emoji match.\n *\n * @example\n * ```tsx\n * {\n * type: 'exact',\n * key: 'π-joy',\n * emoji: 'π',\n * keyword: 'joy',\n * }\n * ```\n * @example\n * ```tsx\n * {\n * type: 'partial',\n * key: 'πΉ-joy-_cat',\n * emoji: 'πΉ',\n * keyword: 'joy',\n * startSlice: '',\n * endSlice: '_cat',\n * }\n * ```\n *\n * @beta\n */\nexport type EmojiMatch =\n | {\n type: 'exact'\n key: string\n emoji: string\n keyword: string\n }\n | {\n type: 'partial'\n key: string\n emoji: string\n keyword: string\n startSlice: string\n endSlice: string\n }\n\n/**\n * Proposed, but not required, function to create a `MatchEmojis` function.\n *\n * @example\n * ```ts\n * const matchEmojis = createMatchEmojis({\n * emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * },\n * })\n * ```\n *\n * @beta\n */\nexport function createMatchEmojis(config: {\n emojis: Record<string, ReadonlyArray<string>>\n}): MatchEmojis<EmojiMatch> {\n return ({keyword}: {keyword: string}) => {\n const foundEmojis: Array<EmojiMatch> = []\n\n if (keyword.length < 1) {\n return foundEmojis\n }\n\n for (const emoji in config.emojis) {\n const emojiKeywords = config.emojis[emoji] ?? []\n\n for (const emojiKeyword of emojiKeywords) {\n const keywordIndex = emojiKeyword.indexOf(keyword)\n\n if (keywordIndex === -1) {\n continue\n }\n\n if (emojiKeyword === keyword) {\n foundEmojis.push({\n type: 'exact',\n key: `${emoji}-${keyword}`,\n emoji,\n keyword,\n })\n } else {\n const start = emojiKeyword.slice(0, keywordIndex)\n const end = emojiKeyword.slice(keywordIndex + keyword.length)\n\n foundEmojis.push({\n type: 'partial',\n key: `${emoji}-${start}${keyword}${end}`,\n emoji,\n keyword,\n startSlice: start,\n endSlice: end,\n })\n }\n }\n }\n\n return foundEmojis\n }\n}\n","import type {\n ChildPath,\n Editor,\n EditorSelector,\n EditorSnapshot,\n PortableTextSpan,\n} from '@portabletext/editor'\nimport {defineBehavior, effect, raise} from '@portabletext/editor/behaviors'\nimport {\n getFocusSpan,\n getMarkState,\n getNextSpan,\n isPointAfterSelection,\n isPointBeforeSelection,\n type MarkState,\n} from '@portabletext/editor/selectors'\nimport {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'\nimport {\n defineInputRule,\n defineInputRuleBehavior,\n type InputRuleMatch,\n} from '@portabletext/plugin-input-rule'\nimport {\n assertEvent,\n assign,\n fromCallback,\n not,\n sendTo,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/*******************\n * Keyboard shortcuts\n *******************/\nconst arrowUpShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowUp'}],\n})\nconst arrowDownShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowDown'}],\n})\nconst enterShortcut = createKeyboardShortcut({\n default: [{key: 'Enter'}],\n})\nconst tabShortcut = createKeyboardShortcut({\n default: [{key: 'Tab'}],\n})\nconst escapeShortcut = createKeyboardShortcut({\n default: [{key: 'Escape'}],\n})\n\nconst getTriggerState: EditorSelector<\n | {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n }\n markState: MarkState\n focusSpanTextBefore: string\n focusSpanTextAfter: string\n nextSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n }\n | undefined\n }\n | undefined\n> = (snapshot) => {\n const focusSpan = getFocusSpan(snapshot)\n const markState = getMarkState(snapshot)\n\n if (!focusSpan || !markState || !snapshot.context.selection) {\n return undefined\n }\n\n const focusSpanTextBefore = focusSpan.node.text.slice(\n 0,\n snapshot.context.selection.focus.offset,\n )\n const focusSpanTextAfter = focusSpan.node.text.slice(\n snapshot.context.selection.focus.offset,\n )\n const nextSpan = getNextSpan(snapshot)\n\n return {\n focusSpan,\n markState,\n focusSpanTextBefore,\n focusSpanTextAfter,\n nextSpan,\n }\n}\n\nfunction createTriggerActions({\n snapshot,\n payload,\n keywordState,\n}: {\n snapshot: EditorSnapshot\n payload: ReturnType<typeof getTriggerState> & {lastMatch: InputRuleMatch}\n keywordState: 'partial' | 'complete'\n}) {\n if (payload.markState.state === 'unchanged') {\n const focusSpan = {\n node: {\n _key: payload.focusSpan.node._key,\n _type: payload.focusSpan.node._type,\n text: `${payload.focusSpanTextBefore}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,\n marks: payload.markState.marks,\n },\n path: payload.focusSpan.path,\n textBefore: payload.focusSpanTextBefore,\n textAfter: payload.focusSpanTextAfter,\n }\n\n if (keywordState === 'complete') {\n return [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n return [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n const newSpan = {\n _key: snapshot.context.keyGenerator(),\n _type: payload.focusSpan.node._type,\n text: payload.lastMatch.text,\n marks: payload.markState.marks,\n }\n const focusSpan = {\n node: {\n _key: newSpan._key,\n _type: newSpan._type,\n text: `${newSpan.text}${payload.nextSpan?.node.text ?? ''}`,\n marks: payload.markState.marks,\n },\n path: [\n {_key: payload.focusSpan.path[0]._key},\n 'children',\n {_key: newSpan._key},\n ] satisfies ChildPath,\n textBefore: '',\n textAfter: payload.nextSpan?.node.text ?? '',\n }\n\n return [\n raise({type: 'select', at: payload.lastMatch.targetOffsets}),\n raise({type: 'delete', at: payload.lastMatch.targetOffsets}),\n raise({type: 'insert.child', child: newSpan}),\n ...(keywordState === 'complete'\n ? [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n : [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]),\n ]\n}\n\n/*******************\n * Input Rules\n *******************/\n\n/**\n * Listen for a single colon insertion\n */\nconst triggerRule = defineInputRule({\n on: /:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n lastMatch,\n ...triggerState,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\ntype TriggerFoundEvent = ReturnType<typeof createTriggerFoundEvent>\n\nfunction createTriggerFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.trigger found',\n ...payload,\n } as const\n}\n\n/**\n * Listen for a partial keyword like \":joy\"\n */\nconst partialKeywordRule = defineInputRule({\n on: /:[\\S]+/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\n/**\n * Listen for a complete keyword like \":joy:\"\n */\nconst keywordRule = defineInputRule({\n on: /:[\\S]+:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'complete'}),\n ],\n})\n\ntype KeywordFoundEvent = ReturnType<typeof createKeywordFoundEvent>\n\nfunction createKeywordFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.keyword found',\n ...payload,\n } as const\n}\n\ntype EmojiPickerContext = {\n editor: Editor\n matches: ReadonlyArray<BaseEmojiMatch>\n matchEmojis: MatchEmojis<BaseEmojiMatch>\n selectedIndex: number\n focusSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n | undefined\n incompleteKeywordRegex: RegExp\n keyword: string\n}\n\ntype EmojiPickerEvent =\n | TriggerFoundEvent\n | KeywordFoundEvent\n | {\n type: 'selection changed'\n snapshot: EditorSnapshot\n }\n | {\n type: 'dismiss'\n }\n | {\n type: 'navigate down'\n }\n | {\n type: 'navigate up'\n }\n | {\n type: 'navigate to'\n index: number\n }\n | {\n type: 'insert selected match'\n }\n\nconst triggerListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineInputRuleBehavior({\n rules: [keywordRule, partialKeywordRule, triggerRule],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<KeywordFoundEvent, KeywordFoundEvent['type']>({\n on: 'custom.keyword found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<TriggerFoundEvent, TriggerFoundEvent['type']>({\n on: 'custom.trigger found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst escapeListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => escapeShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst arrowListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowDownShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate down'})\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowUpShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate up'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst emojiInsertListener: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input}) => {\n return input.context.editor.registerBehavior({\n behavior: defineBehavior<{\n emoji: string\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n }>({\n on: 'custom.insert emoji',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n raise({\n type: 'delete',\n at: {\n anchor: {\n path: event.focusSpan.path,\n offset: event.focusSpan.textBefore.length,\n },\n focus: {\n path: event.focusSpan.path,\n offset:\n event.focusSpan.node.text.length -\n event.focusSpan.textAfter.length,\n },\n },\n }),\n raise({\n type: 'insert.text',\n text: event.emoji,\n }),\n ],\n ],\n }),\n })\n}\n\nconst submitListenerCallback: CallbackLogicFunction<\n {type: 'context changed'; context: EmojiPickerContext},\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input, receive}) => {\n let context = input.context\n\n receive((event) => {\n context = event.context\n })\n\n const unregisterBehaviors = [\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => {\n if (\n !enterShortcut.guard(event.originEvent) &&\n !tabShortcut.guard(event.originEvent)\n ) {\n return false\n }\n\n const focusSpan = context.focusSpan\n const match = context.matches[context.selectedIndex]\n\n return match && focusSpan ? {focusSpan, emoji: match.emoji} : false\n },\n actions: [\n (_, {focusSpan, emoji}) => [\n raise({\n type: 'custom.insert emoji',\n emoji,\n focusSpan,\n }),\n ],\n ],\n }),\n }),\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) =>\n enterShortcut.guard(event.originEvent) ||\n tabShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const subscription = input.editor.on('selection', () => {\n const snapshot = input.editor.getSnapshot()\n sendBack({type: 'selection changed', snapshot})\n })\n\n return subscription.unsubscribe\n}\n\nexport const emojiPickerMachine = setup({\n types: {\n context: {} as EmojiPickerContext,\n input: {} as {\n editor: Editor\n matchEmojis: MatchEmojis\n },\n events: {} as EmojiPickerEvent,\n },\n actors: {\n 'emoji insert listener': fromCallback(emojiInsertListener),\n 'submit listener': fromCallback(submitListenerCallback),\n 'arrow listener': fromCallback(arrowListenerCallback),\n 'trigger listener': fromCallback(triggerListenerCallback),\n 'escape listener': fromCallback(escapeListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n actions: {\n 'set focus span': assign({\n focusSpan: ({context, event}) => {\n if (\n event.type !== 'custom.trigger found' &&\n event.type !== 'custom.keyword found'\n ) {\n return context.focusSpan\n }\n\n return event.focusSpan\n },\n }),\n 'update focus span': assign({\n focusSpan: ({context}) => {\n if (!context.focusSpan) {\n return undefined\n }\n\n const snapshot = context.editor.getSnapshot()\n const focusSpan = getFocusSpan(snapshot)\n\n if (!focusSpan) {\n return undefined\n }\n\n if (\n JSON.stringify(focusSpan.path) !==\n JSON.stringify(context.focusSpan.path)\n ) {\n return undefined\n }\n\n if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore)) {\n return undefined\n }\n\n if (!focusSpan.node.text.endsWith(context.focusSpan.textAfter)) {\n return undefined\n }\n\n const keywordAnchor = {\n path: focusSpan.path,\n offset: context.focusSpan.textBefore.length,\n }\n const keywordFocus = {\n path: focusSpan.path,\n offset:\n focusSpan.node.text.length - context.focusSpan.textAfter.length,\n }\n\n const selectionIsBeforeKeyword =\n isPointAfterSelection(keywordAnchor)(snapshot)\n\n const selectionIsAfterKeyword =\n isPointBeforeSelection(keywordFocus)(snapshot)\n\n if (selectionIsBeforeKeyword || selectionIsAfterKeyword) {\n return undefined\n }\n\n return {\n node: focusSpan.node,\n path: focusSpan.path,\n textBefore: context.focusSpan.textBefore,\n textAfter: context.focusSpan.textAfter,\n }\n },\n }),\n 'update keyword': assign({\n keyword: ({context}) => {\n if (!context.focusSpan) {\n return ''\n }\n\n if (\n context.focusSpan.textBefore.length > 0 &&\n context.focusSpan.textAfter.length > 0\n ) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n -context.focusSpan.textAfter.length,\n )\n }\n\n if (context.focusSpan.textBefore.length > 0) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n )\n }\n\n if (context.focusSpan.textAfter.length > 0) {\n return context.focusSpan.node.text.slice(\n 0,\n -context.focusSpan.textAfter.length,\n )\n }\n\n return context.focusSpan.node.text\n },\n }),\n 'update matches': assign({\n matches: ({context}) => {\n // Strip leading colon\n let rawKeyword = context.keyword.startsWith(':')\n ? context.keyword.slice(1)\n : context.keyword\n // Strip trailing colon\n rawKeyword =\n rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n\n if (rawKeyword === undefined) {\n return []\n }\n\n return context.matchEmojis({keyword: rawKeyword})\n },\n }),\n 'reset selected index': assign({\n selectedIndex: 0,\n }),\n 'increment selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === context.matches.length - 1) {\n return 0\n }\n return context.selectedIndex + 1\n },\n }),\n 'decrement selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === 0) {\n return context.matches.length - 1\n }\n return context.selectedIndex - 1\n },\n }),\n 'set selected index': assign({\n selectedIndex: ({event}) => {\n assertEvent(event, 'navigate to')\n\n return event.index\n },\n }),\n 'update submit listener context': sendTo(\n 'submit listener',\n ({context}) => ({\n type: 'context changed',\n context,\n }),\n ),\n 'insert selected match': ({context}) => {\n const match = context.matches[context.selectedIndex]\n\n if (!match || !context.focusSpan) {\n return\n }\n\n context.editor.send({\n type: 'custom.insert emoji',\n emoji: match.emoji,\n focusSpan: context.focusSpan,\n })\n },\n 'reset': assign({\n focusSpan: undefined,\n keyword: '',\n matches: [],\n selectedIndex: 0,\n }),\n },\n guards: {\n 'no focus span': ({context}) => {\n return !context.focusSpan\n },\n 'has matches': ({context}) => {\n return context.matches.length > 0\n },\n 'no matches': not('has matches'),\n 'keyword is malformed': ({context}) => {\n return !context.incompleteKeywordRegex.test(context.keyword)\n },\n 'keyword is direct match': ({context}) => {\n const fullKeywordRegex = /^:[\\S]+:$/\n\n if (!fullKeywordRegex.test(context.keyword)) {\n return false\n }\n\n const match = context.matches.at(context.selectedIndex)\n\n if (!match || match.type !== 'exact') {\n return false\n }\n\n return true\n },\n },\n}).createMachine({\n id: 'emoji picker',\n context: ({input}) => ({\n editor: input.editor,\n keyword: '',\n focusSpan: undefined,\n matchEmojis: input.matchEmojis,\n incompleteKeywordRegex: /^:[\\S]*$/,\n matches: [],\n selectedIndex: 0,\n }),\n initial: 'idle',\n invoke: [\n {\n src: 'emoji insert listener',\n id: 'emoji insert listener',\n input: ({context}) => ({context}),\n },\n ],\n states: {\n idle: {\n entry: ['reset'],\n invoke: {\n src: 'trigger listener',\n input: ({context}) => ({editor: context.editor}),\n },\n on: {\n 'custom.trigger found': {\n target: 'searching',\n actions: ['set focus span', 'update keyword'],\n },\n 'custom.keyword found': {\n actions: [\n 'set focus span',\n 'update keyword',\n 'update matches',\n 'insert selected match',\n ],\n target: 'idle',\n reenter: true,\n },\n },\n },\n searching: {\n invoke: [\n {\n src: 'submit listener',\n id: 'submit listener',\n input: ({context}) => ({context}),\n },\n {\n src: 'escape listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'dismiss': {\n target: 'idle',\n },\n 'selection changed': [\n {\n actions: [\n 'update focus span',\n 'update keyword',\n 'update matches',\n 'reset selected index',\n 'update submit listener context',\n ],\n },\n ],\n },\n always: [\n {\n guard: 'no focus span',\n target: 'idle',\n },\n {\n guard: 'keyword is malformed',\n target: 'idle',\n },\n {\n guard: 'keyword is direct match',\n actions: ['insert selected match'],\n target: 'idle',\n },\n ],\n initial: 'no matches showing',\n states: {\n 'no matches showing': {\n entry: ['reset selected index'],\n always: {\n guard: 'has matches',\n target: 'showing matches',\n },\n },\n 'showing matches': {\n invoke: {\n src: 'arrow listener',\n input: ({context}) => ({editor: context.editor}),\n },\n always: [\n {\n guard: 'no matches',\n target: 'no matches showing',\n },\n ],\n on: {\n 'navigate down': {\n actions: [\n 'increment selected index',\n 'update submit listener context',\n ],\n },\n 'navigate up': {\n actions: [\n 'decrement selected index',\n 'update submit listener context',\n ],\n },\n 'navigate to': {\n actions: ['set selected index', 'update submit listener context'],\n },\n 'insert selected match': {\n actions: ['insert selected match'],\n },\n },\n },\n },\n },\n },\n})\n","import {useEditor} from '@portabletext/editor'\nimport {useActorRef, useSelector} from '@xstate/react'\nimport {useCallback} from 'react'\nimport {emojiPickerMachine} from './emoji-picker-machine'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/**\n * @beta\n */\nexport type EmojiPicker<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> = {\n /**\n * The matched keyword.\n *\n * Can be used to display the keyword in the UI or conditionally render the\n * list of matches.\n *\n * @example\n * ```tsx\n * if (keyword.length < 1) {\n * return null\n * }\n * ```\n */\n keyword: string\n\n /**\n * Emoji matches found for the current keyword.\n *\n * Can be used to display the matches in a list.\n */\n matches: ReadonlyArray<TEmojiMatch>\n\n /**\n * The index of the selected match.\n *\n * Can be used to highlight the selected match in the list.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * />\n * ```\n */\n selectedIndex: number\n\n /**\n * Navigate to a specific match by index.\n *\n * Can be used to control the `selectedIndex`. For example, using\n * `onMouseEnter`.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * />\n * ```\n */\n onNavigateTo: (index: number) => void\n\n /**\n * Select the current match.\n *\n * Can be used to insert the currently selected match.\n *\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * onSelect={() => {onSelect()}}\n * />\n * ```\n *\n * Note: The currently selected match is automatically inserted on Enter or\n * Tab.\n */\n onSelect: () => void\n\n /**\n * Dismiss the emoji picker. Can be used to let the user dismiss the picker\n * by clicking a button.\n *\n * @example\n * ```tsx\n * {matches.length === 0 ? (\n * <Button onPress={onDismiss}>Dismiss</Button>\n * ) : <EmojiListBox {...props} />}\n * ```\n *\n * Note: The emoji picker is automatically dismissed on Escape.\n */\n onDismiss: () => void\n}\n\n/**\n * @beta\n */\nexport type EmojiPickerProps<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n> = {\n matchEmojis: MatchEmojis<TEmojiMatch>\n}\n\n/**\n * Handles the state and logic needed to create an emoji picker.\n *\n * The `matchEmojis` function is generic and can return any shape of emoji\n * match required for the emoji picker.\n *\n * However, the default implementation of `matchEmojis` returns an array of\n * `EmojiMatch` objects and can be created using the `createMatchEmojis`\n * function.\n *\n * @example\n *\n * ```tsx\n * const matchEmojis = createMatchEmojis({emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * }})\n *\n * const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} =\n * useEmojiPicker({matchEmojis})\n * ```\n *\n * Note: This hook is not concerned with the UI, how the emoji picker is\n * rendered or positioned in the document.\n *\n * @beta\n */\nexport function useEmojiPicker<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n>(props: EmojiPickerProps<TEmojiMatch>): EmojiPicker<TEmojiMatch> {\n const editor = useEditor()\n const emojiPickerActor = useActorRef(emojiPickerMachine, {\n input: {editor, matchEmojis: props.matchEmojis},\n })\n const keyword = useSelector(emojiPickerActor, (snapshot) => {\n const rawKeyword = snapshot.context.keyword.startsWith(':')\n ? snapshot.context.keyword.slice(1)\n : snapshot.context.keyword\n return rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n })\n const matches = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.matches as ReadonlyArray<TEmojiMatch>,\n )\n const selectedIndex = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.selectedIndex,\n )\n\n const onDismiss = useCallback(() => {\n emojiPickerActor.send({type: 'dismiss'})\n }, [emojiPickerActor])\n const onNavigateTo = useCallback(\n (index: number) => {\n emojiPickerActor.send({type: 'navigate to', index})\n },\n [emojiPickerActor],\n )\n const onSelect = useCallback(() => {\n emojiPickerActor.send({type: 'insert selected match'})\n editor.send({type: 'focus'})\n }, [emojiPickerActor, editor])\n\n return {\n keyword,\n matches,\n selectedIndex,\n onDismiss,\n onNavigateTo,\n onSelect,\n }\n}\n"],"names":["createMatchEmojis","config","keyword","foundEmojis","length","emoji","emojis","emojiKeywords","emojiKeyword","keywordIndex","indexOf","push","type","key","start","slice","end","startSlice","endSlice","arrowUpShortcut","createKeyboardShortcut","default","arrowDownShortcut","enterShortcut","tabShortcut","escapeShortcut","getTriggerState","snapshot","focusSpan","getFocusSpan","markState","getMarkState","context","selection","focusSpanTextBefore","node","text","focus","offset","focusSpanTextAfter","nextSpan","getNextSpan","createTriggerActions","payload","keywordState","state","_key","_type","lastMatch","marks","path","textBefore","textAfter","raise","createKeywordFoundEvent","createTriggerFoundEvent","newSpan","keyGenerator","at","targetOffsets","child","triggerRule","defineInputRule","on","guard","event","matches","undefined","triggerState","actions","partialKeywordRule","anchor","keywordRule","triggerListenerCallback","sendBack","input","unregisterBehaviors","editor","registerBehavior","behavior","defineInputRuleBehavior","rules","defineBehavior","effect","unregister","escapeListenerCallback","originEvent","arrowListenerCallback","emojiInsertListener","submitListenerCallback","receive","match","selectedIndex","_","selectionListenerCallback","getSnapshot","unsubscribe","emojiPickerMachine","setup","types","events","actors","fromCallback","assign","JSON","stringify","startsWith","endsWith","keywordAnchor","keywordFocus","selectionIsBeforeKeyword","isPointAfterSelection","selectionIsAfterKeyword","isPointBeforeSelection","rawKeyword","matchEmojis","assertEvent","index","sendTo","insert selected match","send","guards","no focus span","has matches","not","keyword is malformed","incompleteKeywordRegex","test","keyword is direct match","createMachine","id","initial","invoke","src","states","idle","entry","target","reenter","searching","always","useEmojiPicker","props","$","_c","useEditor","t0","emojiPickerActor","useActorRef","useSelector","_temp","_temp2","_temp3","t1","onDismiss","t2","onNavigateTo","t3","onSelect","t4","snapshot_1","snapshot_0"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/create-match-emojis.ts","../src/emoji-picker-machine.tsx","../src/use-emoji-picker.ts"],"sourcesContent":["import type {MatchEmojis} from './match-emojis'\n\n/**\n * Proposed, but not required type, to represent an emoji match.\n *\n * @example\n * ```tsx\n * {\n * type: 'exact',\n * key: 'π-joy',\n * emoji: 'π',\n * keyword: 'joy',\n * }\n * ```\n * @example\n * ```tsx\n * {\n * type: 'partial',\n * key: 'πΉ-joy-_cat',\n * emoji: 'πΉ',\n * keyword: 'joy',\n * startSlice: '',\n * endSlice: '_cat',\n * }\n * ```\n *\n * @beta\n */\nexport type EmojiMatch =\n | {\n type: 'exact'\n key: string\n emoji: string\n keyword: string\n }\n | {\n type: 'partial'\n key: string\n emoji: string\n keyword: string\n startSlice: string\n endSlice: string\n }\n\n/**\n * Proposed, but not required, function to create a `MatchEmojis` function.\n *\n * @example\n * ```ts\n * const matchEmojis = createMatchEmojis({\n * emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * },\n * })\n * ```\n *\n * @beta\n */\nexport function createMatchEmojis(config: {\n emojis: Record<string, ReadonlyArray<string>>\n}): MatchEmojis<EmojiMatch> {\n return ({keyword}: {keyword: string}) => {\n const foundEmojis: Array<EmojiMatch> = []\n\n if (keyword.length < 1) {\n return foundEmojis\n }\n\n for (const emoji in config.emojis) {\n const emojiKeywords = config.emojis[emoji] ?? []\n\n for (const emojiKeyword of emojiKeywords) {\n const keywordIndex = emojiKeyword.indexOf(keyword)\n\n if (keywordIndex === -1) {\n continue\n }\n\n if (emojiKeyword === keyword) {\n foundEmojis.push({\n type: 'exact',\n key: `${emoji}-${keyword}`,\n emoji,\n keyword,\n })\n } else {\n const start = emojiKeyword.slice(0, keywordIndex)\n const end = emojiKeyword.slice(keywordIndex + keyword.length)\n\n foundEmojis.push({\n type: 'partial',\n key: `${emoji}-${start}${keyword}${end}`,\n emoji,\n keyword,\n startSlice: start,\n endSlice: end,\n })\n }\n }\n }\n\n return foundEmojis\n }\n}\n","import type {\n ChildPath,\n Editor,\n EditorSelector,\n EditorSnapshot,\n PortableTextSpan,\n} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n} from '@portabletext/editor/behaviors'\nimport {\n getFocusSpan,\n getMarkState,\n getNextSpan,\n getPreviousSpan,\n isPointAfterSelection,\n isPointBeforeSelection,\n type MarkState,\n} from '@portabletext/editor/selectors'\nimport {\n isEqualSelectionPoints,\n isSelectionCollapsed,\n} from '@portabletext/editor/utils'\nimport {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'\nimport {\n defineInputRule,\n defineInputRuleBehavior,\n type InputRuleMatch,\n} from '@portabletext/plugin-input-rule'\nimport {\n assertEvent,\n assign,\n fromCallback,\n not,\n sendTo,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/*******************\n * Keyboard shortcuts\n *******************/\nconst arrowUpShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowUp'}],\n})\nconst arrowDownShortcut = createKeyboardShortcut({\n default: [{key: 'ArrowDown'}],\n})\nconst enterShortcut = createKeyboardShortcut({\n default: [{key: 'Enter'}],\n})\nconst tabShortcut = createKeyboardShortcut({\n default: [{key: 'Tab'}],\n})\nconst escapeShortcut = createKeyboardShortcut({\n default: [{key: 'Escape'}],\n})\n\nconst getTriggerState: EditorSelector<\n | {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n }\n markState: MarkState\n focusSpanTextBefore: string\n focusSpanTextAfter: string\n previousSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n }\n | undefined\n nextSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n }\n | undefined\n }\n | undefined\n> = (snapshot) => {\n const focusSpan = getFocusSpan(snapshot)\n const markState = getMarkState(snapshot)\n\n if (!focusSpan || !markState || !snapshot.context.selection) {\n return undefined\n }\n\n const focusSpanTextBefore = focusSpan.node.text.slice(\n 0,\n snapshot.context.selection.focus.offset,\n )\n const focusSpanTextAfter = focusSpan.node.text.slice(\n snapshot.context.selection.focus.offset,\n )\n const previousSpan = getPreviousSpan(snapshot)\n const nextSpan = getNextSpan(snapshot)\n\n return {\n focusSpan,\n markState,\n focusSpanTextBefore,\n focusSpanTextAfter,\n previousSpan,\n nextSpan,\n }\n}\n\nfunction createTriggerActions({\n snapshot,\n payload,\n keywordState,\n}: {\n snapshot: EditorSnapshot\n payload: ReturnType<typeof getTriggerState> & {lastMatch: InputRuleMatch}\n keywordState: 'partial' | 'complete'\n}) {\n if (payload.markState.state === 'unchanged') {\n const focusSpan = {\n node: {\n _key: payload.focusSpan.node._key,\n _type: payload.focusSpan.node._type,\n text: `${payload.focusSpanTextBefore}${payload.lastMatch.text}${payload.focusSpanTextAfter}`,\n marks: payload.markState.marks,\n },\n path: payload.focusSpan.path,\n textBefore: payload.focusSpanTextBefore,\n textAfter: payload.focusSpanTextAfter,\n }\n\n if (keywordState === 'complete') {\n return [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n return [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]\n }\n\n const newSpan = {\n _key: snapshot.context.keyGenerator(),\n _type: payload.focusSpan.node._type,\n text: payload.lastMatch.text,\n marks: payload.markState.marks,\n }\n\n let focusSpan = {\n node: {\n _key: newSpan._key,\n _type: newSpan._type,\n text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,\n marks: payload.markState.marks,\n },\n path: [\n {_key: payload.focusSpan.path[0]._key},\n 'children',\n {_key: newSpan._key},\n ] satisfies ChildPath,\n textBefore: '',\n textAfter: payload.nextSpan?.node.text ?? payload.focusSpanTextAfter,\n }\n\n if (\n payload.previousSpan &&\n payload.focusSpanTextBefore.length === 0 &&\n JSON.stringify(payload.previousSpan.node.marks ?? []) ===\n JSON.stringify(payload.markState.marks)\n ) {\n // The text will be inserted into the previous span, so we'll treat that\n // as the focus span\n\n focusSpan = {\n node: {\n _key: payload.previousSpan.node._key,\n _type: newSpan._type,\n text: `${payload.previousSpan.node.text}${newSpan.text}`,\n marks: newSpan.marks,\n },\n path: payload.previousSpan.path,\n textBefore: payload.previousSpan.node.text,\n textAfter: '',\n }\n }\n\n return [\n raise({type: 'select', at: payload.lastMatch.targetOffsets}),\n raise({type: 'delete', at: payload.lastMatch.targetOffsets}),\n raise({type: 'insert.child', child: newSpan}),\n ...(keywordState === 'complete'\n ? [\n raise(\n createKeywordFoundEvent({\n focusSpan,\n }),\n ),\n ]\n : [\n raise(\n createTriggerFoundEvent({\n focusSpan,\n }),\n ),\n ]),\n ]\n}\n\n/*******************\n * Input Rules\n *******************/\n\n/**\n * Listen for a single colon insertion\n */\nconst triggerRule = defineInputRule({\n on: /:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n lastMatch,\n ...triggerState,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\ntype TriggerFoundEvent = ReturnType<typeof createTriggerFoundEvent>\n\nfunction createTriggerFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.trigger found',\n ...payload,\n } as const\n}\n\n/**\n * Listen for a partial keyword like \":joy\"\n */\nconst partialKeywordRule = defineInputRule({\n on: /:[\\S]+/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'partial'}),\n ],\n})\n\n/**\n * Listen for a complete keyword like \":joy:\"\n */\nconst keywordRule = defineInputRule({\n on: /:[\\S]+:/,\n guard: ({snapshot, event}) => {\n const lastMatch = event.matches.at(-1)\n\n if (lastMatch === undefined) {\n return false\n }\n\n if (lastMatch.targetOffsets.anchor.offset < event.textBefore.length) {\n return false\n }\n\n const triggerState = getTriggerState(snapshot)\n\n if (!triggerState) {\n return false\n }\n\n return {\n ...triggerState,\n lastMatch,\n }\n },\n actions: [\n ({snapshot}, payload) =>\n createTriggerActions({snapshot, payload, keywordState: 'complete'}),\n ],\n})\n\ntype KeywordFoundEvent = ReturnType<typeof createKeywordFoundEvent>\n\nfunction createKeywordFoundEvent(payload: {\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n}) {\n return {\n type: 'custom.keyword found',\n ...payload,\n } as const\n}\n\ntype EmojiPickerContext = {\n editor: Editor\n matches: ReadonlyArray<BaseEmojiMatch>\n matchEmojis: MatchEmojis<BaseEmojiMatch>\n selectedIndex: number\n focusSpan:\n | {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n | undefined\n incompleteKeywordRegex: RegExp\n keyword: string\n}\n\ntype EmojiPickerEvent =\n | TriggerFoundEvent\n | KeywordFoundEvent\n | {\n type: 'selection changed'\n }\n | {\n type: 'dismiss'\n }\n | {\n type: 'navigate down'\n }\n | {\n type: 'navigate up'\n }\n | {\n type: 'navigate to'\n index: number\n }\n | {\n type: 'insert selected match'\n }\n\nconst triggerListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineInputRuleBehavior({\n rules: [keywordRule, partialKeywordRule, triggerRule],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<KeywordFoundEvent, KeywordFoundEvent['type']>({\n on: 'custom.keyword found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior<TriggerFoundEvent, TriggerFoundEvent['type']>({\n on: 'custom.trigger found',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack(event)\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst escapeListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => escapeShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst arrowListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregisterBehaviors = [\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowDownShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate down'})\n }),\n ],\n ],\n }),\n }),\n input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => arrowUpShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'navigate up'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst emojiInsertListener: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input}) => {\n return input.context.editor.registerBehavior({\n behavior: defineBehavior<{\n emoji: string\n focusSpan: {\n node: PortableTextSpan\n path: ChildPath\n textBefore: string\n textAfter: string\n }\n }>({\n on: 'custom.insert emoji',\n actions: [\n ({event}) => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n raise({\n type: 'delete',\n at: {\n anchor: {\n path: event.focusSpan.path,\n offset: event.focusSpan.textBefore.length,\n },\n focus: {\n path: event.focusSpan.path,\n offset:\n event.focusSpan.node.text.length -\n event.focusSpan.textAfter.length,\n },\n },\n }),\n raise({\n type: 'insert.text',\n text: event.emoji,\n }),\n ],\n ],\n }),\n })\n}\n\nconst submitListenerCallback: CallbackLogicFunction<\n {type: 'context changed'; context: EmojiPickerContext},\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input, receive}) => {\n let context = input.context\n\n receive((event) => {\n context = event.context\n })\n\n const unregisterBehaviors = [\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) => {\n if (\n !enterShortcut.guard(event.originEvent) &&\n !tabShortcut.guard(event.originEvent)\n ) {\n return false\n }\n\n const focusSpan = context.focusSpan\n const match = context.matches[context.selectedIndex]\n\n return match && focusSpan ? {focusSpan, emoji: match.emoji} : false\n },\n actions: [\n (_, {focusSpan, emoji}) => [\n raise({\n type: 'custom.insert emoji',\n emoji,\n focusSpan,\n }),\n ],\n ],\n }),\n }),\n input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'keyboard.keydown',\n guard: ({event}) =>\n enterShortcut.guard(event.originEvent) ||\n tabShortcut.guard(event.originEvent),\n actions: [\n () => [\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n }),\n ]\n\n return () => {\n for (const unregister of unregisterBehaviors) {\n unregister()\n }\n }\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n EmojiPickerEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const subscription = input.editor.on('selection', () => {\n sendBack({type: 'selection changed'})\n })\n\n return subscription.unsubscribe\n}\n\nconst textInsertionListenerCallback: CallbackLogicFunction<\n {type: 'context changed'; context: EmojiPickerContext},\n EmojiPickerEvent,\n {context: EmojiPickerContext}\n> = ({sendBack, input, receive}) => {\n let context = input.context\n\n receive((event) => {\n context = event.context\n })\n\n return input.context.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'insert.text',\n guard: ({snapshot}) => {\n if (!context.focusSpan) {\n return false\n }\n\n if (!snapshot.context.selection) {\n return false\n }\n\n const keywordAnchor = {\n path: context.focusSpan.path,\n offset: context.focusSpan.textBefore.length,\n }\n\n return isEqualSelectionPoints(\n snapshot.context.selection.focus,\n keywordAnchor,\n )\n },\n actions: [\n ({event}) => [\n forward(event),\n effect(() => {\n sendBack({type: 'dismiss'})\n }),\n ],\n ],\n }),\n })\n}\n\nexport const emojiPickerMachine = setup({\n types: {\n context: {} as EmojiPickerContext,\n input: {} as {\n editor: Editor\n matchEmojis: MatchEmojis\n },\n events: {} as EmojiPickerEvent,\n },\n actors: {\n 'emoji insert listener': fromCallback(emojiInsertListener),\n 'submit listener': fromCallback(submitListenerCallback),\n 'arrow listener': fromCallback(arrowListenerCallback),\n 'trigger listener': fromCallback(triggerListenerCallback),\n 'escape listener': fromCallback(escapeListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n 'text insertion listener': fromCallback(textInsertionListenerCallback),\n },\n actions: {\n 'set focus span': assign({\n focusSpan: ({context, event}) => {\n if (\n event.type !== 'custom.trigger found' &&\n event.type !== 'custom.keyword found'\n ) {\n return context.focusSpan\n }\n\n return event.focusSpan\n },\n }),\n 'update focus span': assign({\n focusSpan: ({context}) => {\n if (!context.focusSpan) {\n return undefined\n }\n\n const snapshot = context.editor.getSnapshot()\n const focusSpan = getFocusSpan(snapshot)\n\n if (!snapshot.context.selection) {\n return undefined\n }\n\n if (!focusSpan) {\n return undefined\n }\n\n const nextSpan = getNextSpan({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: {\n path: context.focusSpan.path,\n offset: 0,\n },\n focus: {\n path: context.focusSpan.path,\n offset: 0,\n },\n },\n },\n })\n\n if (\n JSON.stringify(focusSpan.path) !==\n JSON.stringify(context.focusSpan.path)\n ) {\n if (\n nextSpan &&\n context.focusSpan.textAfter.length === 0 &&\n snapshot.context.selection.focus.offset === 0 &&\n isSelectionCollapsed(snapshot.context.selection)\n ) {\n // This is an edge case where the caret is moved from the end of\n // the focus span to the start of the next span.\n return context.focusSpan\n }\n\n return undefined\n }\n\n if (!focusSpan.node.text.startsWith(context.focusSpan.textBefore)) {\n return undefined\n }\n\n if (!focusSpan.node.text.endsWith(context.focusSpan.textAfter)) {\n return undefined\n }\n\n const keywordAnchor = {\n path: focusSpan.path,\n offset: context.focusSpan.textBefore.length,\n }\n const keywordFocus = {\n path: focusSpan.path,\n offset:\n focusSpan.node.text.length - context.focusSpan.textAfter.length,\n }\n\n const selectionIsBeforeKeyword =\n isPointAfterSelection(keywordAnchor)(snapshot)\n\n const selectionIsAfterKeyword =\n isPointBeforeSelection(keywordFocus)(snapshot)\n\n if (selectionIsBeforeKeyword || selectionIsAfterKeyword) {\n return undefined\n }\n\n return {\n node: focusSpan.node,\n path: focusSpan.path,\n textBefore: context.focusSpan.textBefore,\n textAfter: context.focusSpan.textAfter,\n }\n },\n }),\n 'update keyword': assign({\n keyword: ({context}) => {\n if (!context.focusSpan) {\n return ''\n }\n\n if (\n context.focusSpan.textBefore.length > 0 &&\n context.focusSpan.textAfter.length > 0\n ) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n -context.focusSpan.textAfter.length,\n )\n }\n\n if (context.focusSpan.textBefore.length > 0) {\n return context.focusSpan.node.text.slice(\n context.focusSpan.textBefore.length,\n )\n }\n\n if (context.focusSpan.textAfter.length > 0) {\n return context.focusSpan.node.text.slice(\n 0,\n -context.focusSpan.textAfter.length,\n )\n }\n\n return context.focusSpan.node.text\n },\n }),\n 'update matches': assign({\n matches: ({context}) => {\n // Strip leading colon\n let rawKeyword = context.keyword.startsWith(':')\n ? context.keyword.slice(1)\n : context.keyword\n // Strip trailing colon\n rawKeyword =\n rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n\n if (rawKeyword === undefined) {\n return []\n }\n\n return context.matchEmojis({keyword: rawKeyword})\n },\n }),\n 'reset selected index': assign({\n selectedIndex: 0,\n }),\n 'increment selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === context.matches.length - 1) {\n return 0\n }\n return context.selectedIndex + 1\n },\n }),\n 'decrement selected index': assign({\n selectedIndex: ({context}) => {\n if (context.selectedIndex === 0) {\n return context.matches.length - 1\n }\n return context.selectedIndex - 1\n },\n }),\n 'set selected index': assign({\n selectedIndex: ({event}) => {\n assertEvent(event, 'navigate to')\n\n return event.index\n },\n }),\n 'update submit listener context': sendTo(\n 'submit listener',\n ({context}) => ({\n type: 'context changed',\n context,\n }),\n ),\n 'update text insertion listener context': sendTo(\n 'text insertion listener',\n ({context}) => ({\n type: 'context changed',\n context,\n }),\n ),\n 'insert selected match': ({context}) => {\n const match = context.matches[context.selectedIndex]\n\n if (!match || !context.focusSpan) {\n return\n }\n\n context.editor.send({\n type: 'custom.insert emoji',\n emoji: match.emoji,\n focusSpan: context.focusSpan,\n })\n },\n 'reset': assign({\n focusSpan: undefined,\n keyword: '',\n matches: [],\n selectedIndex: 0,\n }),\n },\n guards: {\n 'no focus span': ({context}) => {\n return !context.focusSpan\n },\n 'has matches': ({context}) => {\n return context.matches.length > 0\n },\n 'no matches': not('has matches'),\n 'keyword is malformed': ({context}) => {\n return !context.incompleteKeywordRegex.test(context.keyword)\n },\n 'keyword is direct match': ({context}) => {\n const fullKeywordRegex = /^:[\\S]+:$/\n\n if (!fullKeywordRegex.test(context.keyword)) {\n return false\n }\n\n const match = context.matches.at(context.selectedIndex)\n\n if (!match || match.type !== 'exact') {\n return false\n }\n\n return true\n },\n },\n}).createMachine({\n id: 'emoji picker',\n context: ({input}) => ({\n editor: input.editor,\n keyword: '',\n focusSpan: undefined,\n matchEmojis: input.matchEmojis,\n incompleteKeywordRegex: /^:[\\S]*$/,\n matches: [],\n selectedIndex: 0,\n }),\n initial: 'idle',\n invoke: [\n {\n src: 'emoji insert listener',\n id: 'emoji insert listener',\n input: ({context}) => ({context}),\n },\n ],\n states: {\n idle: {\n entry: ['reset'],\n invoke: {\n src: 'trigger listener',\n input: ({context}) => ({editor: context.editor}),\n },\n on: {\n 'custom.trigger found': {\n target: 'searching',\n actions: ['set focus span', 'update keyword'],\n },\n 'custom.keyword found': {\n actions: [\n 'set focus span',\n 'update keyword',\n 'update matches',\n 'insert selected match',\n ],\n target: 'idle',\n reenter: true,\n },\n },\n },\n searching: {\n invoke: [\n {\n src: 'submit listener',\n id: 'submit listener',\n input: ({context}) => ({context}),\n },\n {\n src: 'escape listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'text insertion listener',\n id: 'text insertion listener',\n input: ({context}) => ({context}),\n },\n ],\n on: {\n 'dismiss': {\n target: 'idle',\n },\n 'selection changed': [\n {\n actions: [\n 'update focus span',\n 'update keyword',\n 'update matches',\n 'reset selected index',\n 'update submit listener context',\n 'update text insertion listener context',\n ],\n },\n ],\n },\n always: [\n {\n guard: 'no focus span',\n target: 'idle',\n },\n {\n guard: 'keyword is malformed',\n target: 'idle',\n },\n {\n guard: 'keyword is direct match',\n actions: ['insert selected match'],\n target: 'idle',\n },\n ],\n initial: 'no matches showing',\n states: {\n 'no matches showing': {\n entry: ['reset selected index'],\n always: {\n guard: 'has matches',\n target: 'showing matches',\n },\n },\n 'showing matches': {\n invoke: {\n src: 'arrow listener',\n input: ({context}) => ({editor: context.editor}),\n },\n always: [\n {\n guard: 'no matches',\n target: 'no matches showing',\n },\n ],\n on: {\n 'navigate down': {\n actions: [\n 'increment selected index',\n 'update submit listener context',\n ],\n },\n 'navigate up': {\n actions: [\n 'decrement selected index',\n 'update submit listener context',\n ],\n },\n 'navigate to': {\n actions: ['set selected index', 'update submit listener context'],\n },\n 'insert selected match': {\n actions: ['insert selected match'],\n },\n },\n },\n },\n },\n },\n})\n","import {useEditor} from '@portabletext/editor'\nimport {useActorRef, useSelector} from '@xstate/react'\nimport {useCallback} from 'react'\nimport {emojiPickerMachine} from './emoji-picker-machine'\nimport type {BaseEmojiMatch, MatchEmojis} from './match-emojis'\n\n/**\n * @beta\n */\nexport type EmojiPicker<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> = {\n /**\n * The matched keyword.\n *\n * Can be used to display the keyword in the UI or conditionally render the\n * list of matches.\n *\n * @example\n * ```tsx\n * if (keyword.length < 1) {\n * return null\n * }\n * ```\n */\n keyword: string\n\n /**\n * Emoji matches found for the current keyword.\n *\n * Can be used to display the matches in a list.\n */\n matches: ReadonlyArray<TEmojiMatch>\n\n /**\n * The index of the selected match.\n *\n * Can be used to highlight the selected match in the list.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * />\n * ```\n */\n selectedIndex: number\n\n /**\n * Navigate to a specific match by index.\n *\n * Can be used to control the `selectedIndex`. For example, using\n * `onMouseEnter`.\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * />\n * ```\n */\n onNavigateTo: (index: number) => void\n\n /**\n * Select the current match.\n *\n * Can be used to insert the currently selected match.\n *\n *\n * @example\n * ```tsx\n * <EmojiListItem\n * key={match.key}\n * match={match}\n * selected={selectedIndex === index}\n * onMouseEnter={() => {onNavigateTo(index)}}\n * onSelect={() => {onSelect()}}\n * />\n * ```\n *\n * Note: The currently selected match is automatically inserted on Enter or\n * Tab.\n */\n onSelect: () => void\n\n /**\n * Dismiss the emoji picker. Can be used to let the user dismiss the picker\n * by clicking a button.\n *\n * @example\n * ```tsx\n * {matches.length === 0 ? (\n * <Button onPress={onDismiss}>Dismiss</Button>\n * ) : <EmojiListBox {...props} />}\n * ```\n *\n * Note: The emoji picker is automatically dismissed on Escape.\n */\n onDismiss: () => void\n}\n\n/**\n * @beta\n */\nexport type EmojiPickerProps<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n> = {\n matchEmojis: MatchEmojis<TEmojiMatch>\n}\n\n/**\n * Handles the state and logic needed to create an emoji picker.\n *\n * The `matchEmojis` function is generic and can return any shape of emoji\n * match required for the emoji picker.\n *\n * However, the default implementation of `matchEmojis` returns an array of\n * `EmojiMatch` objects and can be created using the `createMatchEmojis`\n * function.\n *\n * @example\n *\n * ```tsx\n * const matchEmojis = createMatchEmojis({emojis: {\n * 'π': ['joy'],\n * 'πΉ': ['joy_cat'],\n * }})\n *\n * const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} =\n * useEmojiPicker({matchEmojis})\n * ```\n *\n * Note: This hook is not concerned with the UI, how the emoji picker is\n * rendered or positioned in the document.\n *\n * @beta\n */\nexport function useEmojiPicker<\n TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,\n>(props: EmojiPickerProps<TEmojiMatch>): EmojiPicker<TEmojiMatch> {\n const editor = useEditor()\n const emojiPickerActor = useActorRef(emojiPickerMachine, {\n input: {editor, matchEmojis: props.matchEmojis},\n })\n const keyword = useSelector(emojiPickerActor, (snapshot) => {\n const rawKeyword = snapshot.context.keyword.startsWith(':')\n ? snapshot.context.keyword.slice(1)\n : snapshot.context.keyword\n return rawKeyword.length > 1 && rawKeyword.endsWith(':')\n ? rawKeyword.slice(0, -1)\n : rawKeyword\n })\n const matches = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.matches as ReadonlyArray<TEmojiMatch>,\n )\n const selectedIndex = useSelector(\n emojiPickerActor,\n (snapshot) => snapshot.context.selectedIndex,\n )\n\n const onDismiss = useCallback(() => {\n emojiPickerActor.send({type: 'dismiss'})\n }, [emojiPickerActor])\n const onNavigateTo = useCallback(\n (index: number) => {\n emojiPickerActor.send({type: 'navigate to', index})\n },\n [emojiPickerActor],\n )\n const onSelect = useCallback(() => {\n emojiPickerActor.send({type: 'insert selected match'})\n editor.send({type: 'focus'})\n }, [emojiPickerActor, editor])\n\n return {\n keyword,\n matches,\n selectedIndex,\n onDismiss,\n onNavigateTo,\n onSelect,\n }\n}\n"],"names":["createMatchEmojis","config","keyword","foundEmojis","length","emoji","emojis","emojiKeywords","emojiKeyword","keywordIndex","indexOf","push","type","key","start","slice","end","startSlice","endSlice","arrowUpShortcut","createKeyboardShortcut","default","arrowDownShortcut","enterShortcut","tabShortcut","escapeShortcut","getTriggerState","snapshot","focusSpan","getFocusSpan","markState","getMarkState","context","selection","focusSpanTextBefore","node","text","focus","offset","focusSpanTextAfter","previousSpan","getPreviousSpan","nextSpan","getNextSpan","createTriggerActions","payload","keywordState","state","_key","_type","lastMatch","marks","path","textBefore","textAfter","raise","createKeywordFoundEvent","createTriggerFoundEvent","newSpan","keyGenerator","JSON","stringify","at","targetOffsets","child","triggerRule","defineInputRule","on","guard","event","matches","undefined","triggerState","actions","partialKeywordRule","anchor","keywordRule","triggerListenerCallback","sendBack","input","unregisterBehaviors","editor","registerBehavior","behavior","defineInputRuleBehavior","rules","defineBehavior","effect","unregister","escapeListenerCallback","originEvent","arrowListenerCallback","emojiInsertListener","submitListenerCallback","receive","match","selectedIndex","_","selectionListenerCallback","unsubscribe","textInsertionListenerCallback","keywordAnchor","isEqualSelectionPoints","forward","emojiPickerMachine","setup","types","events","actors","fromCallback","assign","getSnapshot","isSelectionCollapsed","startsWith","endsWith","keywordFocus","selectionIsBeforeKeyword","isPointAfterSelection","selectionIsAfterKeyword","isPointBeforeSelection","rawKeyword","matchEmojis","assertEvent","index","sendTo","insert selected match","send","guards","no focus span","has matches","not","keyword is malformed","incompleteKeywordRegex","test","keyword is direct match","createMachine","id","initial","invoke","src","states","idle","entry","target","reenter","searching","always","useEmojiPicker","props","$","_c","useEditor","t0","emojiPickerActor","useActorRef","useSelector","_temp","_temp2","_temp3","t1","onDismiss","t2","onNavigateTo","t3","onSelect","t4","snapshot_1","snapshot_0"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/plugin-emoji-picker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Easily configure an Emoji Picker for the Portable Text Editor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"portabletext",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"react-compiler-runtime": "1.0.0",
|
|
42
42
|
"xstate": "^5.23.0",
|
|
43
43
|
"@portabletext/keyboard-shortcuts": "^1.1.1",
|
|
44
|
-
"@portabletext/plugin-input-rule": "~0.4.
|
|
44
|
+
"@portabletext/plugin-input-rule": "~0.4.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@sanity/pkg-utils": "^8.1.4",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"typescript": "5.9.3",
|
|
58
58
|
"typescript-eslint": "^8.46.1",
|
|
59
59
|
"vitest": "^4.0.4",
|
|
60
|
-
"@portabletext/editor": "2.
|
|
60
|
+
"@portabletext/editor": "2.17.0",
|
|
61
61
|
"@portabletext/schema": "1.2.0",
|
|
62
62
|
"racejar": "1.3.2"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@portabletext/editor": "^2.
|
|
65
|
+
"@portabletext/editor": "^2.17.0",
|
|
66
66
|
"react": "^19.1.1"
|
|
67
67
|
},
|
|
68
68
|
"publishConfig": {
|
|
@@ -5,15 +5,25 @@ import type {
|
|
|
5
5
|
EditorSnapshot,
|
|
6
6
|
PortableTextSpan,
|
|
7
7
|
} from '@portabletext/editor'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
defineBehavior,
|
|
10
|
+
effect,
|
|
11
|
+
forward,
|
|
12
|
+
raise,
|
|
13
|
+
} from '@portabletext/editor/behaviors'
|
|
9
14
|
import {
|
|
10
15
|
getFocusSpan,
|
|
11
16
|
getMarkState,
|
|
12
17
|
getNextSpan,
|
|
18
|
+
getPreviousSpan,
|
|
13
19
|
isPointAfterSelection,
|
|
14
20
|
isPointBeforeSelection,
|
|
15
21
|
type MarkState,
|
|
16
22
|
} from '@portabletext/editor/selectors'
|
|
23
|
+
import {
|
|
24
|
+
isEqualSelectionPoints,
|
|
25
|
+
isSelectionCollapsed,
|
|
26
|
+
} from '@portabletext/editor/utils'
|
|
17
27
|
import {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts'
|
|
18
28
|
import {
|
|
19
29
|
defineInputRule,
|
|
@@ -60,6 +70,12 @@ const getTriggerState: EditorSelector<
|
|
|
60
70
|
markState: MarkState
|
|
61
71
|
focusSpanTextBefore: string
|
|
62
72
|
focusSpanTextAfter: string
|
|
73
|
+
previousSpan:
|
|
74
|
+
| {
|
|
75
|
+
node: PortableTextSpan
|
|
76
|
+
path: ChildPath
|
|
77
|
+
}
|
|
78
|
+
| undefined
|
|
63
79
|
nextSpan:
|
|
64
80
|
| {
|
|
65
81
|
node: PortableTextSpan
|
|
@@ -83,6 +99,7 @@ const getTriggerState: EditorSelector<
|
|
|
83
99
|
const focusSpanTextAfter = focusSpan.node.text.slice(
|
|
84
100
|
snapshot.context.selection.focus.offset,
|
|
85
101
|
)
|
|
102
|
+
const previousSpan = getPreviousSpan(snapshot)
|
|
86
103
|
const nextSpan = getNextSpan(snapshot)
|
|
87
104
|
|
|
88
105
|
return {
|
|
@@ -90,6 +107,7 @@ const getTriggerState: EditorSelector<
|
|
|
90
107
|
markState,
|
|
91
108
|
focusSpanTextBefore,
|
|
92
109
|
focusSpanTextAfter,
|
|
110
|
+
previousSpan,
|
|
93
111
|
nextSpan,
|
|
94
112
|
}
|
|
95
113
|
}
|
|
@@ -141,11 +159,12 @@ function createTriggerActions({
|
|
|
141
159
|
text: payload.lastMatch.text,
|
|
142
160
|
marks: payload.markState.marks,
|
|
143
161
|
}
|
|
144
|
-
|
|
162
|
+
|
|
163
|
+
let focusSpan = {
|
|
145
164
|
node: {
|
|
146
165
|
_key: newSpan._key,
|
|
147
166
|
_type: newSpan._type,
|
|
148
|
-
text: `${newSpan.text}${payload.nextSpan?.node.text ??
|
|
167
|
+
text: `${newSpan.text}${payload.nextSpan?.node.text ?? payload.focusSpanTextAfter}`,
|
|
149
168
|
marks: payload.markState.marks,
|
|
150
169
|
},
|
|
151
170
|
path: [
|
|
@@ -154,7 +173,29 @@ function createTriggerActions({
|
|
|
154
173
|
{_key: newSpan._key},
|
|
155
174
|
] satisfies ChildPath,
|
|
156
175
|
textBefore: '',
|
|
157
|
-
textAfter: payload.nextSpan?.node.text ??
|
|
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
|
+
}
|
|
158
199
|
}
|
|
159
200
|
|
|
160
201
|
return [
|
|
@@ -332,7 +373,6 @@ type EmojiPickerEvent =
|
|
|
332
373
|
| KeywordFoundEvent
|
|
333
374
|
| {
|
|
334
375
|
type: 'selection changed'
|
|
335
|
-
snapshot: EditorSnapshot
|
|
336
376
|
}
|
|
337
377
|
| {
|
|
338
378
|
type: 'dismiss'
|
|
@@ -571,13 +611,57 @@ const selectionListenerCallback: CallbackLogicFunction<
|
|
|
571
611
|
{editor: Editor}
|
|
572
612
|
> = ({sendBack, input}) => {
|
|
573
613
|
const subscription = input.editor.on('selection', () => {
|
|
574
|
-
|
|
575
|
-
sendBack({type: 'selection changed', snapshot})
|
|
614
|
+
sendBack({type: 'selection changed'})
|
|
576
615
|
})
|
|
577
616
|
|
|
578
617
|
return subscription.unsubscribe
|
|
579
618
|
}
|
|
580
619
|
|
|
620
|
+
const textInsertionListenerCallback: CallbackLogicFunction<
|
|
621
|
+
{type: 'context changed'; context: EmojiPickerContext},
|
|
622
|
+
EmojiPickerEvent,
|
|
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
|
+
}),
|
|
659
|
+
],
|
|
660
|
+
],
|
|
661
|
+
}),
|
|
662
|
+
})
|
|
663
|
+
}
|
|
664
|
+
|
|
581
665
|
export const emojiPickerMachine = setup({
|
|
582
666
|
types: {
|
|
583
667
|
context: {} as EmojiPickerContext,
|
|
@@ -594,6 +678,7 @@ export const emojiPickerMachine = setup({
|
|
|
594
678
|
'trigger listener': fromCallback(triggerListenerCallback),
|
|
595
679
|
'escape listener': fromCallback(escapeListenerCallback),
|
|
596
680
|
'selection listener': fromCallback(selectionListenerCallback),
|
|
681
|
+
'text insertion listener': fromCallback(textInsertionListenerCallback),
|
|
597
682
|
},
|
|
598
683
|
actions: {
|
|
599
684
|
'set focus span': assign({
|
|
@@ -617,14 +702,46 @@ export const emojiPickerMachine = setup({
|
|
|
617
702
|
const snapshot = context.editor.getSnapshot()
|
|
618
703
|
const focusSpan = getFocusSpan(snapshot)
|
|
619
704
|
|
|
705
|
+
if (!snapshot.context.selection) {
|
|
706
|
+
return undefined
|
|
707
|
+
}
|
|
708
|
+
|
|
620
709
|
if (!focusSpan) {
|
|
621
710
|
return undefined
|
|
622
711
|
}
|
|
623
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
|
+
|
|
624
730
|
if (
|
|
625
731
|
JSON.stringify(focusSpan.path) !==
|
|
626
732
|
JSON.stringify(context.focusSpan.path)
|
|
627
733
|
) {
|
|
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
|
+
|
|
628
745
|
return undefined
|
|
629
746
|
}
|
|
630
747
|
|
|
@@ -748,6 +865,13 @@ export const emojiPickerMachine = setup({
|
|
|
748
865
|
context,
|
|
749
866
|
}),
|
|
750
867
|
),
|
|
868
|
+
'update text insertion listener context': sendTo(
|
|
869
|
+
'text insertion listener',
|
|
870
|
+
({context}) => ({
|
|
871
|
+
type: 'context changed',
|
|
872
|
+
context,
|
|
873
|
+
}),
|
|
874
|
+
),
|
|
751
875
|
'insert selected match': ({context}) => {
|
|
752
876
|
const match = context.matches[context.selectedIndex]
|
|
753
877
|
|
|
@@ -853,6 +977,11 @@ export const emojiPickerMachine = setup({
|
|
|
853
977
|
src: 'selection listener',
|
|
854
978
|
input: ({context}) => ({editor: context.editor}),
|
|
855
979
|
},
|
|
980
|
+
{
|
|
981
|
+
src: 'text insertion listener',
|
|
982
|
+
id: 'text insertion listener',
|
|
983
|
+
input: ({context}) => ({context}),
|
|
984
|
+
},
|
|
856
985
|
],
|
|
857
986
|
on: {
|
|
858
987
|
'dismiss': {
|
|
@@ -866,6 +995,7 @@ export const emojiPickerMachine = setup({
|
|
|
866
995
|
'update matches',
|
|
867
996
|
'reset selected index',
|
|
868
997
|
'update submit listener context',
|
|
998
|
+
'update text insertion listener context',
|
|
869
999
|
],
|
|
870
1000
|
},
|
|
871
1001
|
],
|
package/src/emoji-picker.feature
CHANGED
|
@@ -124,20 +124,34 @@ Feature: Emoji Picker
|
|
|
124
124
|
| "foo bar baz" | "bar" | before "ar baz" | ":joy:" | "foo ,bπar, baz" |
|
|
125
125
|
| "foo bar baz" | "bar" | before "r baz" | ":joy:" | "foo ,baπr, baz" |
|
|
126
126
|
|
|
127
|
+
Scenario Outline: Triggering at the edge of decorator
|
|
128
|
+
Given the text "foo bar baz"
|
|
129
|
+
And "strong" around "bar"
|
|
130
|
+
When the caret is put <position>
|
|
131
|
+
And ":j" is typed
|
|
132
|
+
Then the keyword is "j"
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
| position |
|
|
136
|
+
| after "foo " |
|
|
137
|
+
| before "bar" |
|
|
138
|
+
| after "bar" |
|
|
139
|
+
| before " baz" |
|
|
140
|
+
|
|
127
141
|
Scenario Outline: Matching at the edge of decorator
|
|
128
|
-
Given the text
|
|
129
|
-
And "strong" around
|
|
142
|
+
Given the text "foo bar baz"
|
|
143
|
+
And "strong" around "bar"
|
|
130
144
|
When the caret is put <position>
|
|
131
|
-
And
|
|
145
|
+
And ":joy:" is typed
|
|
132
146
|
Then the text is <final text>
|
|
133
147
|
|
|
134
148
|
Examples:
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
|
149
|
+
| position | final text |
|
|
150
|
+
| after "foo " | "foo π,bar, baz" |
|
|
151
|
+
| before "bar" | "foo π,bar, baz" |
|
|
152
|
+
| after "bar" | "foo ,barπ, baz" |
|
|
153
|
+
| before " baz" | "foo ,barπ, baz" |
|
|
139
154
|
|
|
140
|
-
# | "foo bar baz" | "bar" | before " baz" | ":joy:" | "foo ,barπ, baz" |
|
|
141
155
|
Scenario Outline: Matching inside annotation
|
|
142
156
|
Given the text <text>
|
|
143
157
|
And a "link" "l1" around <annotated>
|
|
@@ -150,32 +164,46 @@ Feature: Emoji Picker
|
|
|
150
164
|
| "foo bar baz" | "bar" | before "ar baz" | ":joy:" | "foo ,bπar, baz" |
|
|
151
165
|
| "foo bar baz" | "bar" | before "r baz" | ":joy:" | "foo ,baπr, baz" |
|
|
152
166
|
|
|
167
|
+
Scenario Outline: Triggering at the edge of an annotation
|
|
168
|
+
Given the text "foo bar baz"
|
|
169
|
+
And a "link" "l1" around "bar"
|
|
170
|
+
When the caret is put <position>
|
|
171
|
+
And ":j" is typed
|
|
172
|
+
Then the keyword is "j"
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
| position |
|
|
176
|
+
| after "foo " |
|
|
177
|
+
| before "bar" |
|
|
178
|
+
| after "bar" |
|
|
179
|
+
| before " baz" |
|
|
180
|
+
|
|
153
181
|
Scenario Outline: Matching at the edge of an annotation
|
|
154
|
-
Given the text
|
|
155
|
-
And a "link" "l1" around
|
|
182
|
+
Given the text "foo bar baz"
|
|
183
|
+
And a "link" "l1" around "bar"
|
|
156
184
|
When the caret is put <position>
|
|
157
|
-
And
|
|
158
|
-
Then the
|
|
185
|
+
And ":joy:" is typed
|
|
186
|
+
Then the text is <final text>
|
|
159
187
|
|
|
160
188
|
Examples:
|
|
161
|
-
|
|
|
162
|
-
| "foo
|
|
163
|
-
|
|
164
|
-
| "
|
|
189
|
+
| position | final text |
|
|
190
|
+
| after "foo " | "foo π,bar, baz" |
|
|
191
|
+
| before "bar" | "foo π,bar, baz" |
|
|
192
|
+
| after "bar" | "foo ,bar,π baz" |
|
|
193
|
+
| before " baz" | "foo ,bar,π baz" |
|
|
165
194
|
|
|
166
|
-
|
|
167
|
-
Scenario Outline: Typing before the colon
|
|
195
|
+
Scenario Outline: Typing before the colon dismisses the emoji picker
|
|
168
196
|
Given the text <text>
|
|
169
197
|
When <inserted text> is typed
|
|
170
198
|
And <button> is pressed
|
|
171
199
|
And <new text> is typed
|
|
172
|
-
|
|
173
|
-
Then the text is <final text>
|
|
200
|
+
Then the keyword is ""
|
|
174
201
|
|
|
175
202
|
Examples:
|
|
176
|
-
| text | inserted text | button | new text |
|
|
177
|
-
| "" | ":j" | "{ArrowLeft}{ArrowLeft}" | "f" |
|
|
178
|
-
| "fo" | ":j" | "{ArrowLeft}{ArrowLeft}" | "o" |
|
|
203
|
+
| text | inserted text | button | new text |
|
|
204
|
+
| "" | ":j" | "{ArrowLeft}{ArrowLeft}" | "f" |
|
|
205
|
+
| "fo" | ":j" | "{ArrowLeft}{ArrowLeft}" | "o" |
|
|
206
|
+
| "" | ":j" | "{ArrowLeft}{ArrowLeft}" | ":" |
|
|
179
207
|
|
|
180
208
|
Scenario Outline: Navigating away from the keyword
|
|
181
209
|
Given the text <text>
|