@portabletext/plugin-input-rule 0.1.0 → 0.1.1

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,9 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var compilerRuntime = require("react/compiler-runtime"), editor = require("@portabletext/editor"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), utils = require("@portabletext/editor/utils"), react = require("@xstate/react"), xstate = require("xstate");
3
+ var compilerRuntime = require("react/compiler-runtime"), editor = require("@portabletext/editor"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), react = require("@xstate/react"), xstate = require("xstate"), utils = require("@portabletext/editor/utils");
4
4
  function defineInputRule(config) {
5
5
  return config;
6
6
  }
7
+ function getInputRuleMatchLocation({
8
+ match,
9
+ adjustIndexBy,
10
+ snapshot,
11
+ focusTextBlock,
12
+ originalTextBefore
13
+ }) {
14
+ const [start, end] = match, adjustedIndex = start + adjustIndexBy, targetOffsets = {
15
+ anchor: {
16
+ path: focusTextBlock.path,
17
+ offset: adjustedIndex
18
+ },
19
+ focus: {
20
+ path: focusTextBlock.path,
21
+ offset: adjustedIndex + end - start
22
+ },
23
+ backward: !1
24
+ }, normalizedOffsets = {
25
+ anchor: {
26
+ path: focusTextBlock.path,
27
+ offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
28
+ },
29
+ focus: {
30
+ path: focusTextBlock.path,
31
+ offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
32
+ },
33
+ backward: !1
34
+ }, selection = utils.blockOffsetsToSelection({
35
+ context: snapshot.context,
36
+ offsets: normalizedOffsets,
37
+ backward: !1
38
+ });
39
+ if (selection)
40
+ return {
41
+ selection,
42
+ targetOffsets
43
+ };
44
+ }
7
45
  function createInputRuleBehavior(config) {
8
46
  return behaviors.defineBehavior({
9
47
  on: "insert.text",
@@ -23,152 +61,37 @@ function createInputRuleBehavior(config) {
23
61
  for (const rule of config.rules) {
24
62
  const matcher = new RegExp(rule.on.source, "gd");
25
63
  for (; ; ) {
26
- const matchesInTextBefore = [...textBefore.matchAll(matcher)].flatMap((regExpMatch) => {
64
+ const ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
27
65
  if (regExpMatch.indices === void 0)
28
66
  return [];
29
- const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
30
- if (index === void 0)
67
+ const match = regExpMatch.indices.at(0);
68
+ if (!match)
31
69
  return [];
32
- const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
33
- if (firstMatchStart === void 0 || firstMatchEnd === void 0)
34
- return [];
35
- const match = {
36
- index: firstMatchStart,
37
- length: firstMatchEnd - firstMatchStart
38
- }, adjustedIndex = match.index + originalNewText.length - newText.length, targetOffsets = {
39
- anchor: {
40
- path: focusTextBlock.path,
41
- offset: adjustedIndex
42
- },
43
- focus: {
44
- path: focusTextBlock.path,
45
- offset: adjustedIndex + match.length
46
- },
47
- backward: !1
48
- }, selection = utils.blockOffsetsToSelection({
49
- context: snapshot.context,
50
- offsets: targetOffsets,
51
- backward: !1
70
+ const matchLocation = getInputRuleMatchLocation({
71
+ match,
72
+ adjustIndexBy: originalNewText.length - newText.length,
73
+ snapshot,
74
+ focusTextBlock,
75
+ originalTextBefore
52
76
  });
53
- if (!selection)
77
+ if (!matchLocation)
54
78
  return [];
55
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).map(([start, end]) => ({
56
- index: start,
57
- length: end - start
58
- })) : [];
59
- return [{
60
- selection,
61
- targetOffsets,
62
- groupMatches: groupMatches.flatMap((groupMatch) => {
63
- const adjustedIndex2 = groupMatch.index + originalNewText.length - newText.length, targetOffsets2 = {
64
- anchor: {
65
- path: focusTextBlock.path,
66
- offset: adjustedIndex2
67
- },
68
- focus: {
69
- path: focusTextBlock.path,
70
- offset: adjustedIndex2 + groupMatch.length
71
- },
72
- backward: !1
73
- }, normalizedOffsets = {
74
- anchor: {
75
- path: focusTextBlock.path,
76
- offset: Math.min(targetOffsets2.anchor.offset, originalTextBefore.length)
77
- },
78
- focus: {
79
- path: focusTextBlock.path,
80
- offset: Math.min(targetOffsets2.focus.offset, originalTextBefore.length)
81
- },
82
- backward: !1
83
- }, selection2 = utils.blockOffsetsToSelection({
84
- context: snapshot.context,
85
- offsets: normalizedOffsets,
86
- backward: !1
87
- });
88
- return selection2 ? {
89
- selection: selection2,
90
- targetOffsets: targetOffsets2
91
- } : [];
92
- })
93
- }];
94
- }), ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
95
- if (regExpMatch.indices === void 0)
96
- return [];
97
- const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
98
- if (index === void 0)
79
+ if (matchLocation.targetOffsets.focus.offset <= originalTextBefore.length)
99
80
  return [];
100
- const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
101
- if (firstMatchStart === void 0 || firstMatchEnd === void 0)
81
+ if (foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset))
102
82
  return [];
103
- const match = {
104
- index: firstMatchStart,
105
- length: firstMatchEnd - firstMatchStart
106
- }, adjustedIndex = match.index + originalNewText.length - newText.length, targetOffsets = {
107
- anchor: {
108
- path: focusTextBlock.path,
109
- offset: adjustedIndex
110
- },
111
- focus: {
112
- path: focusTextBlock.path,
113
- offset: adjustedIndex + match.length
114
- },
115
- backward: !1
116
- }, normalizedOffsets = {
117
- anchor: {
118
- path: focusTextBlock.path,
119
- offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
120
- },
121
- focus: {
122
- path: focusTextBlock.path,
123
- offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
124
- },
125
- backward: !1
126
- }, selection = utils.blockOffsetsToSelection({
127
- context: snapshot.context,
128
- offsets: normalizedOffsets,
129
- backward: !1
130
- });
131
- if (!selection)
132
- return [];
133
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).map(([start, end]) => ({
134
- index: start,
135
- length: end - start
136
- })) : [], ruleMatch = {
137
- selection,
138
- targetOffsets,
139
- groupMatches: groupMatches.flatMap((groupMatch) => {
140
- const adjustedIndex2 = groupMatch.index + originalNewText.length - newText.length, targetOffsets2 = {
141
- anchor: {
142
- path: focusTextBlock.path,
143
- offset: adjustedIndex2
144
- },
145
- focus: {
146
- path: focusTextBlock.path,
147
- offset: adjustedIndex2 + groupMatch.length
148
- },
149
- backward: !1
150
- }, normalizedOffsets2 = {
151
- anchor: {
152
- path: focusTextBlock.path,
153
- offset: Math.min(targetOffsets2.anchor.offset, originalTextBefore.length)
154
- },
155
- focus: {
156
- path: focusTextBlock.path,
157
- offset: Math.min(targetOffsets2.focus.offset, originalTextBefore.length)
158
- },
159
- backward: !1
160
- }, selection2 = utils.blockOffsetsToSelection({
161
- context: snapshot.context,
162
- offsets: normalizedOffsets2,
163
- backward: !1
164
- });
165
- return selection2 ? [{
166
- targetOffsets: targetOffsets2,
167
- selection: selection2
168
- }] : [];
169
- })
170
- };
171
- return foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === adjustedIndex) ? [] : matchesInTextBefore.some((matchInTextBefore) => matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex) ? [] : [ruleMatch];
83
+ const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1) : [];
84
+ return [{
85
+ selection: matchLocation.selection,
86
+ targetOffsets: matchLocation.targetOffsets,
87
+ groupMatches: groupMatches.flatMap((match2) => getInputRuleMatchLocation({
88
+ match: match2,
89
+ adjustIndexBy: originalNewText.length - newText.length,
90
+ snapshot,
91
+ focusTextBlock,
92
+ originalTextBefore
93
+ }) || [])
94
+ }];
172
95
  });
173
96
  if (ruleMatches.length > 0) {
174
97
  const guardResult = rule.guard?.({
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/input-rule.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n PortableTextTextBlock,\n} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\n\ntype InputRuleMatchLocation = {\n /**\n * Estimated selection of where in the original text the match is located.\n * The selection is estimated since the match is found in the text after\n * insertion.\n */\n selection: NonNullable<EditorSelection>\n /**\n * Block offsets of the match in the text after the insertion\n */\n targetOffsets: {\n anchor: BlockOffset\n focus: BlockOffset\n backward: boolean\n }\n}\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {blockOffsetsToSelection} from '@portabletext/editor/utils'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text before the insertion\n const matchesInTextBefore: Array<InputRuleMatch> = [\n ...textBefore.matchAll(matcher),\n ].flatMap((regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]\n\n if (index === undefined) {\n return []\n }\n\n const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(\n 0,\n ) ?? [undefined, undefined]\n\n if (firstMatchStart === undefined || firstMatchEnd === undefined) {\n return []\n }\n\n const match = {\n index: firstMatchStart,\n length: firstMatchEnd - firstMatchStart,\n }\n const adjustedIndex =\n match.index + originalNewText.length - newText.length\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + match.length,\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: targetOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1).map(([start, end]) => ({\n index: start,\n length: end - start,\n }))\n : []\n const ruleMatch = {\n selection,\n targetOffsets,\n groupMatches: groupMatches.flatMap((groupMatch) => {\n const adjustedIndex =\n groupMatch.index + originalNewText.length - newText.length\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + groupMatch.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n return {\n selection,\n targetOffsets,\n }\n }),\n }\n\n return [ruleMatch]\n })\n const matchesInNewText = [...newText.matchAll(matcher)]\n // Find matches in the text after the insertion\n const ruleMatches = matchesInNewText.flatMap((regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]\n\n if (index === undefined) {\n return []\n }\n\n const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(\n 0,\n ) ?? [undefined, undefined]\n\n if (firstMatchStart === undefined || firstMatchEnd === undefined) {\n return []\n }\n\n const match = {\n index: firstMatchStart,\n length: firstMatchEnd - firstMatchStart,\n }\n const adjustedIndex =\n match.index + originalNewText.length - newText.length\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + match.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1).map(([start, end]) => ({\n index: start,\n length: end - start,\n }))\n : []\n\n const ruleMatch = {\n selection,\n targetOffsets,\n groupMatches: groupMatches.flatMap((groupMatch) => {\n const adjustedIndex =\n groupMatch.index + originalNewText.length - newText.length\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + groupMatch.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n return [\n {\n targetOffsets,\n selection,\n },\n ]\n }),\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset === adjustedIndex,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const existsInTextBefore = matchesInTextBefore.some(\n (matchInTextBefore) =>\n matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex,\n )\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n return [ruleMatch]\n })\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\n\n/**\n * @alpha\n */\nexport type TextTransformRule = {\n on: RegExp\n guard?: InputRuleGuard\n transform: () => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const matches = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const textLengthDelta = matches.reduce((length, match) => {\n return (\n length -\n (config.transform().length -\n (match.targetOffsets.focus.offset -\n match.targetOffsets.anchor.offset))\n )\n }, 0)\n\n const newText = event.textBefore + event.textInserted\n const endCaretPosition = {\n path: event.focusTextBlock.path,\n offset: newText.length - textLengthDelta,\n }\n\n const actions = matches.reverse().flatMap((match) => [\n raise({type: 'select', at: match.targetOffsets}),\n raise({type: 'delete', at: match.targetOffsets}),\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text: config.transform(),\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: match.selection.anchor,\n focus: {\n path: match.selection.focus.path,\n offset: Math.min(\n match.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n ])\n\n return [\n ...actions,\n raise({\n type: 'select',\n at: {\n anchor: endCaretPosition,\n focus: endCaretPosition,\n },\n }),\n ]\n },\n ],\n }\n}\n"],"names":["defineInputRule","config","createInputRuleBehavior","defineBehavior","on","guard","snapshot","event","dom","focusTextBlock","getFocusTextBlock","originalTextBefore","getBlockTextBefore","textBefore","originalNewText","text","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","matchesInTextBefore","matchAll","flatMap","regExpMatch","indices","undefined","index","at","firstMatchStart","firstMatchEnd","match","length","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","selection","blockOffsetsToSelection","context","offsets","groupMatches","slice","map","start","end","groupMatch","normalizedOffsets","Math","min","ruleMatches","ruleMatch","some","foundMatch","matchInTextBefore","guardResult","type","matches","textInserted","actionSets","actions","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","_key","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","textLengthDelta","reduce","transform","endCaretPosition","reverse","child","_type","schema","span","name","marks","getMarkState"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/input-rule.ts","../src/input-rule-match-location.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {BlockPath, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {blockOffsetsToSelection} from '@portabletext/editor/utils'\n\nexport type InputRuleMatchLocation = {\n /**\n * Estimated selection of where in the original text the match is located.\n * The selection is estimated since the match is found in the text after\n * insertion.\n */\n selection: NonNullable<EditorSelection>\n /**\n * Block offsets of the match in the text after the insertion\n */\n targetOffsets: {\n anchor: BlockOffset\n focus: BlockOffset\n backward: boolean\n }\n}\n\nexport function getInputRuleMatchLocation({\n match,\n adjustIndexBy,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n}: {\n match: [number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\n path: BlockPath\n }\n originalTextBefore: string\n}): InputRuleMatchLocation | undefined {\n const [start, end] = match\n const adjustedIndex = start + adjustIndexBy\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return undefined\n }\n\n return {\n selection,\n targetOffsets,\n }\n}\n","import {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match,\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1)\n : []\n\n const ruleMatch = {\n selection: matchLocation.selection,\n targetOffsets: matchLocation.targetOffsets,\n groupMatches: groupMatches.flatMap((match) => {\n const groupMatchLocation = getInputRuleMatchLocation({\n match,\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\n )\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\n\n/**\n * @alpha\n */\nexport type TextTransformRule = {\n on: RegExp\n guard?: InputRuleGuard\n transform: () => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const matches = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const textLengthDelta = matches.reduce((length, match) => {\n return (\n length -\n (config.transform().length -\n (match.targetOffsets.focus.offset -\n match.targetOffsets.anchor.offset))\n )\n }, 0)\n\n const newText = event.textBefore + event.textInserted\n const endCaretPosition = {\n path: event.focusTextBlock.path,\n offset: newText.length - textLengthDelta,\n }\n\n const actions = matches.reverse().flatMap((match) => [\n raise({type: 'select', at: match.targetOffsets}),\n raise({type: 'delete', at: match.targetOffsets}),\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text: config.transform(),\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: match.selection.anchor,\n focus: {\n path: match.selection.focus.path,\n offset: Math.min(\n match.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n ])\n\n return [\n ...actions,\n raise({\n type: 'select',\n at: {\n anchor: endCaretPosition,\n focus: endCaretPosition,\n },\n }),\n ]\n },\n ],\n }\n}\n"],"names":["defineInputRule","config","getInputRuleMatchLocation","match","adjustIndexBy","snapshot","focusTextBlock","originalTextBefore","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","selection","blockOffsetsToSelection","context","offsets","createInputRuleBehavior","defineBehavior","on","guard","event","dom","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","text","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","some","foundMatch","groupMatches","slice","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","_key","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","textLengthDelta","reduce","transform","endCaretPosition","reverse","child","_type","schema","span","name","marks","getMarkState"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,12 +2,50 @@ import { c } from "react/compiler-runtime";
2
2
  import { useEditor } from "@portabletext/editor";
3
3
  import { defineBehavior, effect, forward, raise } from "@portabletext/editor/behaviors";
4
4
  import { getBlockOffsets, getFocusTextBlock, getBlockTextBefore, getMarkState } from "@portabletext/editor/selectors";
5
- import { blockOffsetsToSelection } from "@portabletext/editor/utils";
6
5
  import { useActorRef } from "@xstate/react";
7
6
  import { setup, fromCallback } from "xstate";
7
+ import { blockOffsetsToSelection } from "@portabletext/editor/utils";
8
8
  function defineInputRule(config) {
9
9
  return config;
10
10
  }
11
+ function getInputRuleMatchLocation({
12
+ match,
13
+ adjustIndexBy,
14
+ snapshot,
15
+ focusTextBlock,
16
+ originalTextBefore
17
+ }) {
18
+ const [start, end] = match, adjustedIndex = start + adjustIndexBy, targetOffsets = {
19
+ anchor: {
20
+ path: focusTextBlock.path,
21
+ offset: adjustedIndex
22
+ },
23
+ focus: {
24
+ path: focusTextBlock.path,
25
+ offset: adjustedIndex + end - start
26
+ },
27
+ backward: !1
28
+ }, normalizedOffsets = {
29
+ anchor: {
30
+ path: focusTextBlock.path,
31
+ offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
32
+ },
33
+ focus: {
34
+ path: focusTextBlock.path,
35
+ offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
36
+ },
37
+ backward: !1
38
+ }, selection = blockOffsetsToSelection({
39
+ context: snapshot.context,
40
+ offsets: normalizedOffsets,
41
+ backward: !1
42
+ });
43
+ if (selection)
44
+ return {
45
+ selection,
46
+ targetOffsets
47
+ };
48
+ }
11
49
  function createInputRuleBehavior(config) {
12
50
  return defineBehavior({
13
51
  on: "insert.text",
@@ -27,152 +65,37 @@ function createInputRuleBehavior(config) {
27
65
  for (const rule of config.rules) {
28
66
  const matcher = new RegExp(rule.on.source, "gd");
29
67
  for (; ; ) {
30
- const matchesInTextBefore = [...textBefore.matchAll(matcher)].flatMap((regExpMatch) => {
68
+ const ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
31
69
  if (regExpMatch.indices === void 0)
32
70
  return [];
33
- const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
34
- if (index === void 0)
35
- return [];
36
- const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
37
- if (firstMatchStart === void 0 || firstMatchEnd === void 0)
71
+ const match = regExpMatch.indices.at(0);
72
+ if (!match)
38
73
  return [];
39
- const match = {
40
- index: firstMatchStart,
41
- length: firstMatchEnd - firstMatchStart
42
- }, adjustedIndex = match.index + originalNewText.length - newText.length, targetOffsets = {
43
- anchor: {
44
- path: focusTextBlock.path,
45
- offset: adjustedIndex
46
- },
47
- focus: {
48
- path: focusTextBlock.path,
49
- offset: adjustedIndex + match.length
50
- },
51
- backward: !1
52
- }, selection = blockOffsetsToSelection({
53
- context: snapshot.context,
54
- offsets: targetOffsets,
55
- backward: !1
74
+ const matchLocation = getInputRuleMatchLocation({
75
+ match,
76
+ adjustIndexBy: originalNewText.length - newText.length,
77
+ snapshot,
78
+ focusTextBlock,
79
+ originalTextBefore
56
80
  });
57
- if (!selection)
81
+ if (!matchLocation)
58
82
  return [];
59
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).map(([start, end]) => ({
60
- index: start,
61
- length: end - start
62
- })) : [];
63
- return [{
64
- selection,
65
- targetOffsets,
66
- groupMatches: groupMatches.flatMap((groupMatch) => {
67
- const adjustedIndex2 = groupMatch.index + originalNewText.length - newText.length, targetOffsets2 = {
68
- anchor: {
69
- path: focusTextBlock.path,
70
- offset: adjustedIndex2
71
- },
72
- focus: {
73
- path: focusTextBlock.path,
74
- offset: adjustedIndex2 + groupMatch.length
75
- },
76
- backward: !1
77
- }, normalizedOffsets = {
78
- anchor: {
79
- path: focusTextBlock.path,
80
- offset: Math.min(targetOffsets2.anchor.offset, originalTextBefore.length)
81
- },
82
- focus: {
83
- path: focusTextBlock.path,
84
- offset: Math.min(targetOffsets2.focus.offset, originalTextBefore.length)
85
- },
86
- backward: !1
87
- }, selection2 = blockOffsetsToSelection({
88
- context: snapshot.context,
89
- offsets: normalizedOffsets,
90
- backward: !1
91
- });
92
- return selection2 ? {
93
- selection: selection2,
94
- targetOffsets: targetOffsets2
95
- } : [];
96
- })
97
- }];
98
- }), ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
99
- if (regExpMatch.indices === void 0)
100
- return [];
101
- const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
102
- if (index === void 0)
83
+ if (matchLocation.targetOffsets.focus.offset <= originalTextBefore.length)
103
84
  return [];
104
- const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
105
- if (firstMatchStart === void 0 || firstMatchEnd === void 0)
106
- return [];
107
- const match = {
108
- index: firstMatchStart,
109
- length: firstMatchEnd - firstMatchStart
110
- }, adjustedIndex = match.index + originalNewText.length - newText.length, targetOffsets = {
111
- anchor: {
112
- path: focusTextBlock.path,
113
- offset: adjustedIndex
114
- },
115
- focus: {
116
- path: focusTextBlock.path,
117
- offset: adjustedIndex + match.length
118
- },
119
- backward: !1
120
- }, normalizedOffsets = {
121
- anchor: {
122
- path: focusTextBlock.path,
123
- offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
124
- },
125
- focus: {
126
- path: focusTextBlock.path,
127
- offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
128
- },
129
- backward: !1
130
- }, selection = blockOffsetsToSelection({
131
- context: snapshot.context,
132
- offsets: normalizedOffsets,
133
- backward: !1
134
- });
135
- if (!selection)
85
+ if (foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset))
136
86
  return [];
137
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).map(([start, end]) => ({
138
- index: start,
139
- length: end - start
140
- })) : [], ruleMatch = {
141
- selection,
142
- targetOffsets,
143
- groupMatches: groupMatches.flatMap((groupMatch) => {
144
- const adjustedIndex2 = groupMatch.index + originalNewText.length - newText.length, targetOffsets2 = {
145
- anchor: {
146
- path: focusTextBlock.path,
147
- offset: adjustedIndex2
148
- },
149
- focus: {
150
- path: focusTextBlock.path,
151
- offset: adjustedIndex2 + groupMatch.length
152
- },
153
- backward: !1
154
- }, normalizedOffsets2 = {
155
- anchor: {
156
- path: focusTextBlock.path,
157
- offset: Math.min(targetOffsets2.anchor.offset, originalTextBefore.length)
158
- },
159
- focus: {
160
- path: focusTextBlock.path,
161
- offset: Math.min(targetOffsets2.focus.offset, originalTextBefore.length)
162
- },
163
- backward: !1
164
- }, selection2 = blockOffsetsToSelection({
165
- context: snapshot.context,
166
- offsets: normalizedOffsets2,
167
- backward: !1
168
- });
169
- return selection2 ? [{
170
- targetOffsets: targetOffsets2,
171
- selection: selection2
172
- }] : [];
173
- })
174
- };
175
- return foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === adjustedIndex) ? [] : matchesInTextBefore.some((matchInTextBefore) => matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex) ? [] : [ruleMatch];
87
+ const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1) : [];
88
+ return [{
89
+ selection: matchLocation.selection,
90
+ targetOffsets: matchLocation.targetOffsets,
91
+ groupMatches: groupMatches.flatMap((match2) => getInputRuleMatchLocation({
92
+ match: match2,
93
+ adjustIndexBy: originalNewText.length - newText.length,
94
+ snapshot,
95
+ focusTextBlock,
96
+ originalTextBefore
97
+ }) || [])
98
+ }];
176
99
  });
177
100
  if (ruleMatches.length > 0) {
178
101
  const guardResult = rule.guard?.({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/input-rule.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n PortableTextTextBlock,\n} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\n\ntype InputRuleMatchLocation = {\n /**\n * Estimated selection of where in the original text the match is located.\n * The selection is estimated since the match is found in the text after\n * insertion.\n */\n selection: NonNullable<EditorSelection>\n /**\n * Block offsets of the match in the text after the insertion\n */\n targetOffsets: {\n anchor: BlockOffset\n focus: BlockOffset\n backward: boolean\n }\n}\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {blockOffsetsToSelection} from '@portabletext/editor/utils'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text before the insertion\n const matchesInTextBefore: Array<InputRuleMatch> = [\n ...textBefore.matchAll(matcher),\n ].flatMap((regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]\n\n if (index === undefined) {\n return []\n }\n\n const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(\n 0,\n ) ?? [undefined, undefined]\n\n if (firstMatchStart === undefined || firstMatchEnd === undefined) {\n return []\n }\n\n const match = {\n index: firstMatchStart,\n length: firstMatchEnd - firstMatchStart,\n }\n const adjustedIndex =\n match.index + originalNewText.length - newText.length\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + match.length,\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: targetOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1).map(([start, end]) => ({\n index: start,\n length: end - start,\n }))\n : []\n const ruleMatch = {\n selection,\n targetOffsets,\n groupMatches: groupMatches.flatMap((groupMatch) => {\n const adjustedIndex =\n groupMatch.index + originalNewText.length - newText.length\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + groupMatch.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n return {\n selection,\n targetOffsets,\n }\n }),\n }\n\n return [ruleMatch]\n })\n const matchesInNewText = [...newText.matchAll(matcher)]\n // Find matches in the text after the insertion\n const ruleMatches = matchesInNewText.flatMap((regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]\n\n if (index === undefined) {\n return []\n }\n\n const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(\n 0,\n ) ?? [undefined, undefined]\n\n if (firstMatchStart === undefined || firstMatchEnd === undefined) {\n return []\n }\n\n const match = {\n index: firstMatchStart,\n length: firstMatchEnd - firstMatchStart,\n }\n const adjustedIndex =\n match.index + originalNewText.length - newText.length\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + match.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1).map(([start, end]) => ({\n index: start,\n length: end - start,\n }))\n : []\n\n const ruleMatch = {\n selection,\n targetOffsets,\n groupMatches: groupMatches.flatMap((groupMatch) => {\n const adjustedIndex =\n groupMatch.index + originalNewText.length - newText.length\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + groupMatch.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n return [\n {\n targetOffsets,\n selection,\n },\n ]\n }),\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset === adjustedIndex,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const existsInTextBefore = matchesInTextBefore.some(\n (matchInTextBefore) =>\n matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex,\n )\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n return [ruleMatch]\n })\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\n\n/**\n * @alpha\n */\nexport type TextTransformRule = {\n on: RegExp\n guard?: InputRuleGuard\n transform: () => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const matches = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const textLengthDelta = matches.reduce((length, match) => {\n return (\n length -\n (config.transform().length -\n (match.targetOffsets.focus.offset -\n match.targetOffsets.anchor.offset))\n )\n }, 0)\n\n const newText = event.textBefore + event.textInserted\n const endCaretPosition = {\n path: event.focusTextBlock.path,\n offset: newText.length - textLengthDelta,\n }\n\n const actions = matches.reverse().flatMap((match) => [\n raise({type: 'select', at: match.targetOffsets}),\n raise({type: 'delete', at: match.targetOffsets}),\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text: config.transform(),\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: match.selection.anchor,\n focus: {\n path: match.selection.focus.path,\n offset: Math.min(\n match.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n ])\n\n return [\n ...actions,\n raise({\n type: 'select',\n at: {\n anchor: endCaretPosition,\n focus: endCaretPosition,\n },\n }),\n ]\n },\n ],\n }\n}\n"],"names":["defineInputRule","config","createInputRuleBehavior","defineBehavior","on","guard","snapshot","event","dom","focusTextBlock","getFocusTextBlock","originalTextBefore","getBlockTextBefore","textBefore","originalNewText","text","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","matchesInTextBefore","matchAll","flatMap","regExpMatch","indices","undefined","index","at","firstMatchStart","firstMatchEnd","match","length","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","selection","blockOffsetsToSelection","context","offsets","groupMatches","slice","map","start","end","groupMatch","normalizedOffsets","Math","min","ruleMatches","ruleMatch","some","foundMatch","matchInTextBefore","guardResult","type","matches","textInserted","actionSets","actions","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","_key","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","textLengthDelta","reduce","transform","endCaretPosition","reverse","child","_type","schema","span","name","marks","getMarkState"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/input-rule.ts","../src/input-rule-match-location.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {BlockPath, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {blockOffsetsToSelection} from '@portabletext/editor/utils'\n\nexport type InputRuleMatchLocation = {\n /**\n * Estimated selection of where in the original text the match is located.\n * The selection is estimated since the match is found in the text after\n * insertion.\n */\n selection: NonNullable<EditorSelection>\n /**\n * Block offsets of the match in the text after the insertion\n */\n targetOffsets: {\n anchor: BlockOffset\n focus: BlockOffset\n backward: boolean\n }\n}\n\nexport function getInputRuleMatchLocation({\n match,\n adjustIndexBy,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n}: {\n match: [number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\n path: BlockPath\n }\n originalTextBefore: string\n}): InputRuleMatchLocation | undefined {\n const [start, end] = match\n const adjustedIndex = start + adjustIndexBy\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return undefined\n }\n\n return {\n selection,\n targetOffsets,\n }\n}\n","import {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match,\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1)\n : []\n\n const ruleMatch = {\n selection: matchLocation.selection,\n targetOffsets: matchLocation.targetOffsets,\n groupMatches: groupMatches.flatMap((match) => {\n const groupMatchLocation = getInputRuleMatchLocation({\n match,\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\n )\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\n\n/**\n * @alpha\n */\nexport type TextTransformRule = {\n on: RegExp\n guard?: InputRuleGuard\n transform: () => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const matches = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const textLengthDelta = matches.reduce((length, match) => {\n return (\n length -\n (config.transform().length -\n (match.targetOffsets.focus.offset -\n match.targetOffsets.anchor.offset))\n )\n }, 0)\n\n const newText = event.textBefore + event.textInserted\n const endCaretPosition = {\n path: event.focusTextBlock.path,\n offset: newText.length - textLengthDelta,\n }\n\n const actions = matches.reverse().flatMap((match) => [\n raise({type: 'select', at: match.targetOffsets}),\n raise({type: 'delete', at: match.targetOffsets}),\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text: config.transform(),\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: match.selection.anchor,\n focus: {\n path: match.selection.focus.path,\n offset: Math.min(\n match.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n ])\n\n return [\n ...actions,\n raise({\n type: 'select',\n at: {\n anchor: endCaretPosition,\n focus: endCaretPosition,\n },\n }),\n ]\n },\n ],\n }\n}\n"],"names":["defineInputRule","config","getInputRuleMatchLocation","match","adjustIndexBy","snapshot","focusTextBlock","originalTextBefore","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","selection","blockOffsetsToSelection","context","offsets","createInputRuleBehavior","defineBehavior","on","guard","event","dom","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","text","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","some","foundMatch","groupMatches","slice","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","_key","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","textLengthDelta","reduce","transform","endCaretPosition","reverse","child","_type","schema","span","name","marks","getMarkState"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/plugin-input-rule",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Easily configure input rules in the Portable Text Editor",
5
5
  "keywords": [
6
6
  "portabletext",
@@ -54,8 +54,8 @@
54
54
  "typescript-eslint": "^8.41.0",
55
55
  "vitest": "^3.2.4",
56
56
  "@portabletext/editor": "2.13.5",
57
- "racejar": "1.3.1",
58
- "@portabletext/schema": "1.2.0"
57
+ "@portabletext/schema": "1.2.0",
58
+ "racejar": "1.3.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@portabletext/editor": "^2.13.5",
@@ -0,0 +1,80 @@
1
+ import type {
2
+ BlockOffset,
3
+ BlockPath,
4
+ EditorSelection,
5
+ EditorSnapshot,
6
+ } from '@portabletext/editor'
7
+ import {blockOffsetsToSelection} from '@portabletext/editor/utils'
8
+
9
+ export type InputRuleMatchLocation = {
10
+ /**
11
+ * Estimated selection of where in the original text the match is located.
12
+ * The selection is estimated since the match is found in the text after
13
+ * insertion.
14
+ */
15
+ selection: NonNullable<EditorSelection>
16
+ /**
17
+ * Block offsets of the match in the text after the insertion
18
+ */
19
+ targetOffsets: {
20
+ anchor: BlockOffset
21
+ focus: BlockOffset
22
+ backward: boolean
23
+ }
24
+ }
25
+
26
+ export function getInputRuleMatchLocation({
27
+ match,
28
+ adjustIndexBy,
29
+ snapshot,
30
+ focusTextBlock,
31
+ originalTextBefore,
32
+ }: {
33
+ match: [number, number]
34
+ adjustIndexBy: number
35
+ snapshot: EditorSnapshot
36
+ focusTextBlock: {
37
+ path: BlockPath
38
+ }
39
+ originalTextBefore: string
40
+ }): InputRuleMatchLocation | undefined {
41
+ const [start, end] = match
42
+ const adjustedIndex = start + adjustIndexBy
43
+
44
+ const targetOffsets = {
45
+ anchor: {
46
+ path: focusTextBlock.path,
47
+ offset: adjustedIndex,
48
+ },
49
+ focus: {
50
+ path: focusTextBlock.path,
51
+ offset: adjustedIndex + end - start,
52
+ },
53
+ backward: false,
54
+ }
55
+ const normalizedOffsets = {
56
+ anchor: {
57
+ path: focusTextBlock.path,
58
+ offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),
59
+ },
60
+ focus: {
61
+ path: focusTextBlock.path,
62
+ offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),
63
+ },
64
+ backward: false,
65
+ }
66
+ const selection = blockOffsetsToSelection({
67
+ context: snapshot.context,
68
+ offsets: normalizedOffsets,
69
+ backward: false,
70
+ })
71
+
72
+ if (!selection) {
73
+ return undefined
74
+ }
75
+
76
+ return {
77
+ selection,
78
+ targetOffsets,
79
+ }
80
+ }
package/src/input-rule.ts CHANGED
@@ -1,30 +1,9 @@
1
- import type {
2
- BlockOffset,
3
- BlockPath,
4
- EditorSelection,
5
- PortableTextTextBlock,
6
- } from '@portabletext/editor'
1
+ import type {BlockPath, PortableTextTextBlock} from '@portabletext/editor'
7
2
  import type {
8
3
  BehaviorActionSet,
9
4
  BehaviorGuard,
10
5
  } from '@portabletext/editor/behaviors'
11
-
12
- type InputRuleMatchLocation = {
13
- /**
14
- * Estimated selection of where in the original text the match is located.
15
- * The selection is estimated since the match is found in the text after
16
- * insertion.
17
- */
18
- selection: NonNullable<EditorSelection>
19
- /**
20
- * Block offsets of the match in the text after the insertion
21
- */
22
- targetOffsets: {
23
- anchor: BlockOffset
24
- focus: BlockOffset
25
- backward: boolean
26
- }
27
- }
6
+ import type {InputRuleMatchLocation} from './input-rule-match-location'
28
7
 
29
8
  /**
30
9
  * Match found in the text after the insertion
@@ -11,7 +11,6 @@ import {
11
11
  getBlockTextBefore,
12
12
  getFocusTextBlock,
13
13
  } from '@portabletext/editor/selectors'
14
- import {blockOffsetsToSelection} from '@portabletext/editor/utils'
15
14
  import {useActorRef} from '@xstate/react'
16
15
  import {
17
16
  fromCallback,
@@ -20,6 +19,7 @@ import {
20
19
  type CallbackLogicFunction,
21
20
  } from 'xstate'
22
21
  import type {InputRule, InputRuleMatch} from './input-rule'
22
+ import {getInputRuleMatchLocation} from './input-rule-match-location'
23
23
 
24
24
  function createInputRuleBehavior(config: {
25
25
  rules: Array<InputRule>
@@ -50,265 +50,79 @@ function createInputRuleBehavior(config: {
50
50
  const matcher = new RegExp(rule.on.source, 'gd')
51
51
 
52
52
  while (true) {
53
- // Find matches in the text before the insertion
54
- const matchesInTextBefore: Array<InputRuleMatch> = [
55
- ...textBefore.matchAll(matcher),
56
- ].flatMap((regExpMatch) => {
57
- if (regExpMatch.indices === undefined) {
58
- return []
59
- }
60
-
61
- const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]
62
-
63
- if (index === undefined) {
64
- return []
65
- }
66
-
67
- const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(
68
- 0,
69
- ) ?? [undefined, undefined]
70
-
71
- if (firstMatchStart === undefined || firstMatchEnd === undefined) {
72
- return []
73
- }
74
-
75
- const match = {
76
- index: firstMatchStart,
77
- length: firstMatchEnd - firstMatchStart,
78
- }
79
- const adjustedIndex =
80
- match.index + originalNewText.length - newText.length
81
- const targetOffsets = {
82
- anchor: {
83
- path: focusTextBlock.path,
84
- offset: adjustedIndex,
85
- },
86
- focus: {
87
- path: focusTextBlock.path,
88
- offset: adjustedIndex + match.length,
89
- },
90
- backward: false,
91
- }
92
- const selection = blockOffsetsToSelection({
93
- context: snapshot.context,
94
- offsets: targetOffsets,
95
- backward: false,
96
- })
97
-
98
- if (!selection) {
99
- return []
100
- }
101
-
102
- const groupMatches =
103
- regExpMatch.indices.length > 1
104
- ? regExpMatch.indices.slice(1).map(([start, end]) => ({
105
- index: start,
106
- length: end - start,
107
- }))
108
- : []
109
- const ruleMatch = {
110
- selection,
111
- targetOffsets,
112
- groupMatches: groupMatches.flatMap((groupMatch) => {
113
- const adjustedIndex =
114
- groupMatch.index + originalNewText.length - newText.length
115
-
116
- const targetOffsets = {
117
- anchor: {
118
- path: focusTextBlock.path,
119
- offset: adjustedIndex,
120
- },
121
- focus: {
122
- path: focusTextBlock.path,
123
- offset: adjustedIndex + groupMatch.length,
124
- },
125
- backward: false,
126
- }
127
- const normalizedOffsets = {
128
- anchor: {
129
- path: focusTextBlock.path,
130
- offset: Math.min(
131
- targetOffsets.anchor.offset,
132
- originalTextBefore.length,
133
- ),
134
- },
135
- focus: {
136
- path: focusTextBlock.path,
137
- offset: Math.min(
138
- targetOffsets.focus.offset,
139
- originalTextBefore.length,
140
- ),
141
- },
142
- backward: false,
143
- }
144
- const selection = blockOffsetsToSelection({
145
- context: snapshot.context,
146
- offsets: normalizedOffsets,
147
- backward: false,
148
- })
149
-
150
- if (!selection) {
151
- return []
152
- }
153
-
154
- return {
155
- selection,
156
- targetOffsets,
157
- }
158
- }),
159
- }
160
-
161
- return [ruleMatch]
162
- })
163
- const matchesInNewText = [...newText.matchAll(matcher)]
164
53
  // Find matches in the text after the insertion
165
- const ruleMatches = matchesInNewText.flatMap((regExpMatch) => {
166
- if (regExpMatch.indices === undefined) {
167
- return []
168
- }
54
+ const ruleMatches = [...newText.matchAll(matcher)].flatMap(
55
+ (regExpMatch) => {
56
+ if (regExpMatch.indices === undefined) {
57
+ return []
58
+ }
169
59
 
170
- const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]
60
+ const match = regExpMatch.indices.at(0)
171
61
 
172
- if (index === undefined) {
173
- return []
174
- }
62
+ if (!match) {
63
+ return []
64
+ }
65
+
66
+ const matchLocation = getInputRuleMatchLocation({
67
+ match,
68
+ adjustIndexBy: originalNewText.length - newText.length,
69
+ snapshot,
70
+ focusTextBlock,
71
+ originalTextBefore,
72
+ })
175
73
 
176
- const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(
177
- 0,
178
- ) ?? [undefined, undefined]
74
+ if (!matchLocation) {
75
+ return []
76
+ }
179
77
 
180
- if (firstMatchStart === undefined || firstMatchEnd === undefined) {
181
- return []
182
- }
78
+ const existsInTextBefore =
79
+ matchLocation.targetOffsets.focus.offset <=
80
+ originalTextBefore.length
183
81
 
184
- const match = {
185
- index: firstMatchStart,
186
- length: firstMatchEnd - firstMatchStart,
187
- }
188
- const adjustedIndex =
189
- match.index + originalNewText.length - newText.length
190
- const targetOffsets = {
191
- anchor: {
192
- path: focusTextBlock.path,
193
- offset: adjustedIndex,
194
- },
195
- focus: {
196
- path: focusTextBlock.path,
197
- offset: adjustedIndex + match.length,
198
- },
199
- backward: false,
200
- }
201
- const normalizedOffsets = {
202
- anchor: {
203
- path: focusTextBlock.path,
204
- offset: Math.min(
205
- targetOffsets.anchor.offset,
206
- originalTextBefore.length,
207
- ),
208
- },
209
- focus: {
210
- path: focusTextBlock.path,
211
- offset: Math.min(
212
- targetOffsets.focus.offset,
213
- originalTextBefore.length,
214
- ),
215
- },
216
- backward: false,
217
- }
218
- const selection = blockOffsetsToSelection({
219
- context: snapshot.context,
220
- offsets: normalizedOffsets,
221
- backward: false,
222
- })
223
-
224
- if (!selection) {
225
- return []
226
- }
82
+ // Ignore if this match occurs in the text before the insertion
83
+ if (existsInTextBefore) {
84
+ return []
85
+ }
227
86
 
228
- const groupMatches =
229
- regExpMatch.indices.length > 1
230
- ? regExpMatch.indices.slice(1).map(([start, end]) => ({
231
- index: start,
232
- length: end - start,
233
- }))
234
- : []
235
-
236
- const ruleMatch = {
237
- selection,
238
- targetOffsets,
239
- groupMatches: groupMatches.flatMap((groupMatch) => {
240
- const adjustedIndex =
241
- groupMatch.index + originalNewText.length - newText.length
242
-
243
- const targetOffsets = {
244
- anchor: {
245
- path: focusTextBlock.path,
246
- offset: adjustedIndex,
247
- },
248
- focus: {
249
- path: focusTextBlock.path,
250
- offset: adjustedIndex + groupMatch.length,
251
- },
252
- backward: false,
253
- }
254
- const normalizedOffsets = {
255
- anchor: {
256
- path: focusTextBlock.path,
257
- offset: Math.min(
258
- targetOffsets.anchor.offset,
259
- originalTextBefore.length,
260
- ),
261
- },
262
- focus: {
263
- path: focusTextBlock.path,
264
- offset: Math.min(
265
- targetOffsets.focus.offset,
266
- originalTextBefore.length,
267
- ),
268
- },
269
- backward: false,
270
- }
271
- const selection = blockOffsetsToSelection({
272
- context: snapshot.context,
273
- offsets: normalizedOffsets,
274
- backward: false,
275
- })
276
-
277
- if (!selection) {
278
- return []
279
- }
280
-
281
- return [
282
- {
283
- targetOffsets,
284
- selection,
285
- },
286
- ]
287
- }),
288
- }
87
+ const alreadyFound = foundMatches.some(
88
+ (foundMatch) =>
89
+ foundMatch.targetOffsets.anchor.offset ===
90
+ matchLocation.targetOffsets.anchor.offset,
91
+ )
289
92
 
290
- const alreadyFound = foundMatches.some(
291
- (foundMatch) =>
292
- foundMatch.targetOffsets.anchor.offset === adjustedIndex,
293
- )
93
+ // Ignore if this match has already been found
94
+ if (alreadyFound) {
95
+ return []
96
+ }
294
97
 
295
- // Ignore if this match has already been found
296
- if (alreadyFound) {
297
- return []
298
- }
98
+ const groupMatches =
99
+ regExpMatch.indices.length > 1
100
+ ? regExpMatch.indices.slice(1)
101
+ : []
102
+
103
+ const ruleMatch = {
104
+ selection: matchLocation.selection,
105
+ targetOffsets: matchLocation.targetOffsets,
106
+ groupMatches: groupMatches.flatMap((match) => {
107
+ const groupMatchLocation = getInputRuleMatchLocation({
108
+ match,
109
+ adjustIndexBy: originalNewText.length - newText.length,
110
+ snapshot,
111
+ focusTextBlock,
112
+ originalTextBefore,
113
+ })
299
114
 
300
- const existsInTextBefore = matchesInTextBefore.some(
301
- (matchInTextBefore) =>
302
- matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex,
303
- )
115
+ if (!groupMatchLocation) {
116
+ return []
117
+ }
304
118
 
305
- // Ignore if this match occurs in the text before the insertion
306
- if (existsInTextBefore) {
307
- return []
308
- }
119
+ return groupMatchLocation
120
+ }),
121
+ }
309
122
 
310
- return [ruleMatch]
311
- })
123
+ return [ruleMatch]
124
+ },
125
+ )
312
126
 
313
127
  if (ruleMatches.length > 0) {
314
128
  const guardResult =
@@ -354,6 +168,7 @@ function createInputRuleBehavior(config: {
354
168
  const matches = ruleMatches.flatMap((match) =>
355
169
  match.groupMatches.length === 0 ? [match] : match.groupMatches,
356
170
  )
171
+
357
172
  for (const match of matches) {
358
173
  // Remember each match and adjust `textBefore` and `newText` so
359
174
  // no subsequent matches can overlap with this one