@portabletext/plugin-emoji-picker 1.0.4 β†’ 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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
- }, focusSpan = {
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 [behaviors.raise({
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
- snapshot
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
- }).unsubscribe, emojiPickerMachine = xstate.setup({
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 (!focusSpan || JSON.stringify(focusSpan.path) !== JSON.stringify(context.focusSpan.path) || !focusSpan.node.text.startsWith(context.focusSpan.textBefore) || !focusSpan.node.text.endsWith(context.focusSpan.textAfter))
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: [{
@@ -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, getNextSpan } from "@portabletext/editor/selectors";
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
- }, focusSpan = {
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 [raise({
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
- snapshot
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
- }).unsubscribe, emojiPickerMachine = setup({
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 (!focusSpan || JSON.stringify(focusSpan.path) !== JSON.stringify(context.focusSpan.path) || !focusSpan.node.text.startsWith(context.focusSpan.textBefore) || !focusSpan.node.text.endsWith(context.focusSpan.textAfter))
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.4",
3
+ "version": "1.0.6",
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.1"
44
+ "@portabletext/plugin-input-rule": "~0.4.3"
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.16.0",
60
+ "@portabletext/editor": "2.17.1",
61
61
  "@portabletext/schema": "1.2.0",
62
62
  "racejar": "1.3.2"
63
63
  },
64
64
  "peerDependencies": {
65
- "@portabletext/editor": "^2.16.0",
65
+ "@portabletext/editor": "^2.17.1",
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 {defineBehavior, effect, raise} from '@portabletext/editor/behaviors'
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
- const focusSpan = {
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
- const snapshot = input.editor.getSnapshot()
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
  ],
@@ -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 <text>
129
- And "strong" around <decorated>
142
+ Given the text "foo bar baz"
143
+ And "strong" around "bar"
130
144
  When the caret is put <position>
131
- And <keyword> is typed
145
+ And ":joy:" is typed
132
146
  Then the text is <final text>
133
147
 
134
148
  Examples:
135
- | text | decorated | position | keyword | final text |
136
- | "foo bar baz" | "bar" | after "foo " | ":joy:" | "foo πŸ˜‚,bar, baz" |
137
- # | "foo bar baz" | "bar" | before "bar" | ":joy:" | "foo πŸ˜‚,bar, baz" |
138
- | "foo bar baz" | "bar" | after "bar" | ":joy:" | "foo ,barπŸ˜‚, baz" |
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 <text>
155
- And a "link" "l1" around <annotated>
182
+ Given the text "foo bar baz"
183
+ And a "link" "l1" around "bar"
156
184
  When the caret is put <position>
157
- And <inserted text> is inserted
158
- Then the keyword is <keyword>
185
+ And ":joy:" is typed
186
+ Then the text is <final text>
159
187
 
160
188
  Examples:
161
- | text | annotated | position | inserted text | keyword |
162
- | "foo bar baz" | "bar" | after "foo " | ":j" | "j" |
163
- # | "foo bar baz" | "bar" | before "bar" | ":j" | "j" |
164
- | "foo bar baz" | "bar" | after "bar" | ":j" | "j" |
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
- # | "foo bar baz" | "bar" | before " baz" | ":j" | "j" |
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
- And "{Enter}" is pressed
173
- Then the text is <final text>
200
+ Then the keyword is ""
174
201
 
175
202
  Examples:
176
- | text | inserted text | button | new text | final text |
177
- | "" | ":j" | "{ArrowLeft}{ArrowLeft}" | "f" | "f\|:j" |
178
- | "fo" | ":j" | "{ArrowLeft}{ArrowLeft}" | "o" | "foo\|:j" |
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>