@portabletext/plugin-input-rule 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 - 2025 Sanity.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # `@portabletext/plugin-input-rule`
2
+
3
+ > Easily configure input rules in the Portable Text Editor
4
+
5
+ This plugin is currently a work in progress. Docs to come with the APIs have settled.
package/dist/index.cjs ADDED
@@ -0,0 +1,433 @@
1
+ "use strict";
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");
4
+ function defineInputRule(config) {
5
+ return config;
6
+ }
7
+ function createInputRuleBehavior(config) {
8
+ return behaviors.defineBehavior({
9
+ on: "insert.text",
10
+ guard: ({
11
+ snapshot,
12
+ event,
13
+ dom
14
+ }) => {
15
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot);
16
+ if (!focusTextBlock)
17
+ return !1;
18
+ const originalTextBefore = selectors.getBlockTextBefore(snapshot);
19
+ let textBefore = originalTextBefore;
20
+ const originalNewText = textBefore + event.text;
21
+ let newText = originalNewText;
22
+ const foundMatches = [], foundActions = [];
23
+ for (const rule of config.rules) {
24
+ const matcher = new RegExp(rule.on.source, "gd");
25
+ for (; ; ) {
26
+ const matchesInTextBefore = [...textBefore.matchAll(matcher)].flatMap((regExpMatch) => {
27
+ if (regExpMatch.indices === void 0)
28
+ return [];
29
+ const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
30
+ if (index === void 0)
31
+ 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
52
+ });
53
+ if (!selection)
54
+ 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)
99
+ return [];
100
+ const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
101
+ if (firstMatchStart === void 0 || firstMatchEnd === void 0)
102
+ 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];
172
+ });
173
+ if (ruleMatches.length > 0) {
174
+ const guardResult = rule.guard?.({
175
+ snapshot,
176
+ event: {
177
+ type: "custom.input rule",
178
+ matches: ruleMatches,
179
+ focusTextBlock,
180
+ textBefore: originalTextBefore,
181
+ textInserted: event.text
182
+ },
183
+ dom
184
+ }) ?? !0;
185
+ if (!guardResult)
186
+ break;
187
+ const actionSets = rule.actions.map((action) => action({
188
+ snapshot,
189
+ event: {
190
+ type: "custom.input rule",
191
+ matches: ruleMatches,
192
+ focusTextBlock,
193
+ textBefore: originalTextBefore,
194
+ textInserted: event.text
195
+ },
196
+ dom
197
+ }, guardResult));
198
+ for (const actionSet of actionSets)
199
+ for (const action of actionSet)
200
+ foundActions.push(action);
201
+ const matches = ruleMatches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches);
202
+ for (const match of matches)
203
+ foundMatches.push(match), textBefore = newText.slice(0, match.targetOffsets.focus.offset ?? 0), newText = originalNewText.slice(match.targetOffsets.focus.offset ?? 0);
204
+ } else
205
+ break;
206
+ }
207
+ }
208
+ return foundActions.length === 0 ? !1 : {
209
+ actions: foundActions
210
+ };
211
+ },
212
+ actions: [({
213
+ event
214
+ }) => [behaviors.forward(event)], (_, {
215
+ actions
216
+ }) => actions, ({
217
+ snapshot
218
+ }) => [behaviors.effect(() => {
219
+ const blockOffsets = selectors.getBlockOffsets(snapshot);
220
+ config.onApply({
221
+ endOffsets: blockOffsets
222
+ });
223
+ })]]
224
+ });
225
+ }
226
+ function InputRulePlugin(props) {
227
+ const $ = compilerRuntime.c(3), editor$1 = editor.useEditor();
228
+ let t0;
229
+ return $[0] !== editor$1 || $[1] !== props.rules ? (t0 = {
230
+ input: {
231
+ editor: editor$1,
232
+ rules: props.rules
233
+ }
234
+ }, $[0] = editor$1, $[1] = props.rules, $[2] = t0) : t0 = $[2], react.useActorRef(inputRuleMachine, t0), null;
235
+ }
236
+ const inputRuleListenerCallback = ({
237
+ input,
238
+ sendBack
239
+ }) => {
240
+ const unregister = input.editor.registerBehavior({
241
+ behavior: createInputRuleBehavior({
242
+ rules: input.rules,
243
+ onApply: ({
244
+ endOffsets
245
+ }) => {
246
+ sendBack({
247
+ type: "input rule raised",
248
+ endOffsets
249
+ });
250
+ }
251
+ })
252
+ });
253
+ return () => {
254
+ unregister();
255
+ };
256
+ }, deleteBackwardListenerCallback = ({
257
+ input,
258
+ sendBack
259
+ }) => input.editor.registerBehavior({
260
+ behavior: behaviors.defineBehavior({
261
+ on: "delete.backward",
262
+ actions: [() => [behaviors.raise({
263
+ type: "history.undo"
264
+ }), behaviors.effect(() => {
265
+ sendBack({
266
+ type: "history.undo raised"
267
+ });
268
+ })]]
269
+ })
270
+ }), selectionListenerCallback = ({
271
+ sendBack,
272
+ input
273
+ }) => input.editor.registerBehavior({
274
+ behavior: behaviors.defineBehavior({
275
+ on: "select",
276
+ guard: ({
277
+ snapshot,
278
+ event
279
+ }) => ({
280
+ blockOffsets: selectors.getBlockOffsets({
281
+ ...snapshot,
282
+ context: {
283
+ ...snapshot.context,
284
+ selection: event.at
285
+ }
286
+ })
287
+ }),
288
+ actions: [({
289
+ event
290
+ }, {
291
+ blockOffsets
292
+ }) => [behaviors.effect(() => {
293
+ sendBack({
294
+ type: "selection changed",
295
+ blockOffsets
296
+ });
297
+ }), behaviors.forward(event)]]
298
+ })
299
+ }), inputRuleSetup = xstate.setup({
300
+ types: {
301
+ context: {},
302
+ input: {},
303
+ events: {}
304
+ },
305
+ actors: {
306
+ "delete.backward listener": xstate.fromCallback(deleteBackwardListenerCallback),
307
+ "input rule listener": xstate.fromCallback(inputRuleListenerCallback),
308
+ "selection listener": xstate.fromCallback(selectionListenerCallback)
309
+ },
310
+ guards: {
311
+ "block offset changed": ({
312
+ context,
313
+ event
314
+ }) => {
315
+ if (event.type !== "selection changed")
316
+ return !1;
317
+ if (!event.blockOffsets || !context.endOffsets)
318
+ return !0;
319
+ const startChanged = context.endOffsets.start.path[0]._key !== event.blockOffsets.start.path[0]._key || context.endOffsets.start.offset !== event.blockOffsets.start.offset, endChanged = context.endOffsets.end.path[0]._key !== event.blockOffsets.end.path[0]._key || context.endOffsets.end.offset !== event.blockOffsets.end.offset;
320
+ return startChanged || endChanged;
321
+ }
322
+ }
323
+ }), assignEndOffsets = inputRuleSetup.assign({
324
+ endOffsets: ({
325
+ context,
326
+ event
327
+ }) => event.type === "input rule raised" ? event.endOffsets : context.endOffsets
328
+ }), inputRuleMachine = inputRuleSetup.createMachine({
329
+ id: "input rule",
330
+ context: ({
331
+ input
332
+ }) => ({
333
+ editor: input.editor,
334
+ rules: input.rules,
335
+ endOffsets: void 0
336
+ }),
337
+ initial: "idle",
338
+ invoke: {
339
+ src: "input rule listener",
340
+ input: ({
341
+ context
342
+ }) => ({
343
+ editor: context.editor,
344
+ rules: context.rules
345
+ })
346
+ },
347
+ on: {
348
+ "input rule raised": {
349
+ target: ".input rule applied",
350
+ actions: assignEndOffsets
351
+ }
352
+ },
353
+ states: {
354
+ idle: {},
355
+ "input rule applied": {
356
+ invoke: [{
357
+ src: "delete.backward listener",
358
+ input: ({
359
+ context
360
+ }) => ({
361
+ editor: context.editor
362
+ })
363
+ }, {
364
+ src: "selection listener",
365
+ input: ({
366
+ context
367
+ }) => ({
368
+ editor: context.editor
369
+ })
370
+ }],
371
+ on: {
372
+ "selection changed": {
373
+ target: "idle",
374
+ guard: "block offset changed"
375
+ },
376
+ "history.undo raised": {
377
+ target: "idle"
378
+ }
379
+ }
380
+ }
381
+ }
382
+ });
383
+ function defineTextTransformRule(config) {
384
+ return {
385
+ on: config.on,
386
+ guard: config.guard ?? (() => !0),
387
+ actions: [({
388
+ snapshot,
389
+ event
390
+ }) => {
391
+ const matches = event.matches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches), textLengthDelta = matches.reduce((length, match) => length - (config.transform().length - (match.targetOffsets.focus.offset - match.targetOffsets.anchor.offset)), 0), newText = event.textBefore + event.textInserted, endCaretPosition = {
392
+ path: event.focusTextBlock.path,
393
+ offset: newText.length - textLengthDelta
394
+ };
395
+ return [...matches.reverse().flatMap((match) => [behaviors.raise({
396
+ type: "select",
397
+ at: match.targetOffsets
398
+ }), behaviors.raise({
399
+ type: "delete",
400
+ at: match.targetOffsets
401
+ }), behaviors.raise({
402
+ type: "insert.child",
403
+ child: {
404
+ _type: snapshot.context.schema.span.name,
405
+ text: config.transform(),
406
+ marks: selectors.getMarkState({
407
+ ...snapshot,
408
+ context: {
409
+ ...snapshot.context,
410
+ selection: {
411
+ anchor: match.selection.anchor,
412
+ focus: {
413
+ path: match.selection.focus.path,
414
+ offset: Math.min(match.selection.focus.offset, event.textBefore.length)
415
+ }
416
+ }
417
+ }
418
+ })?.marks ?? []
419
+ }
420
+ })]), behaviors.raise({
421
+ type: "select",
422
+ at: {
423
+ anchor: endCaretPosition,
424
+ focus: endCaretPosition
425
+ }
426
+ })];
427
+ }]
428
+ };
429
+ }
430
+ exports.InputRulePlugin = InputRulePlugin;
431
+ exports.defineInputRule = defineInputRule;
432
+ exports.defineTextTransformRule = defineTextTransformRule;
433
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,129 @@
1
+ import type {
2
+ BlockOffset,
3
+ BlockPath,
4
+ EditorSelection,
5
+ PortableTextTextBlock,
6
+ } from '@portabletext/editor'
7
+ import type {
8
+ BehaviorActionSet,
9
+ BehaviorGuard,
10
+ } from '@portabletext/editor/behaviors'
11
+
12
+ /**
13
+ * @alpha
14
+ */
15
+ export declare function defineInputRule(config: InputRule): InputRule
16
+
17
+ /**
18
+ * Define an `InputRule` specifically designed to transform matched text into
19
+ * some other text.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const transformRule = defineTextTransformRule({
24
+ * on: /--/,
25
+ * transform: () => '—',
26
+ * })
27
+ * ```
28
+ *
29
+ * @alpha
30
+ */
31
+ export declare function defineTextTransformRule(
32
+ config: TextTransformRule,
33
+ ): InputRule
34
+
35
+ /**
36
+ * @alpha
37
+ */
38
+ export declare type InputRule = {
39
+ on: RegExp
40
+ guard?: InputRuleGuard
41
+ actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>
42
+ }
43
+
44
+ /**
45
+ * @alpha
46
+ */
47
+ export declare type InputRuleEvent = {
48
+ type: 'custom.input rule'
49
+ /**
50
+ * Matches found by the input rule
51
+ */
52
+ matches: Array<InputRuleMatch>
53
+ /**
54
+ * The text before the insertion
55
+ */
56
+ textBefore: string
57
+ /**
58
+ * The text is destined to be inserted
59
+ */
60
+ textInserted: string
61
+ /**
62
+ * The text block where the insertion takes place
63
+ */
64
+ focusTextBlock: {
65
+ path: BlockPath
66
+ node: PortableTextTextBlock
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @alpha
72
+ */
73
+ export declare type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>
74
+
75
+ /**
76
+ * Match found in the text after the insertion
77
+ * @alpha
78
+ */
79
+ export declare type InputRuleMatch = InputRuleMatchLocation & {
80
+ groupMatches: Array<InputRuleMatchLocation>
81
+ }
82
+
83
+ declare type InputRuleMatchLocation = {
84
+ /**
85
+ * Estimated selection of where in the original text the match is located.
86
+ * The selection is estimated since the match is found in the text after
87
+ * insertion.
88
+ */
89
+ selection: NonNullable<EditorSelection>
90
+ /**
91
+ * Block offsets of the match in the text after the insertion
92
+ */
93
+ targetOffsets: {
94
+ anchor: BlockOffset
95
+ focus: BlockOffset
96
+ backward: boolean
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Turn an array of `InputRule`s into a Behavior that can be used to apply the
102
+ * rules to the editor.
103
+ *
104
+ * The plugin handles undo/redo out of the box including smart undo with
105
+ * Backspace.
106
+ *
107
+ * @example
108
+ * ```tsx
109
+ * <InputRulePlugin rules={smartQuotesRules} />
110
+ * ```
111
+ *
112
+ * @alpha
113
+ */
114
+ export declare function InputRulePlugin(props: InputRulePluginProps): null
115
+
116
+ declare type InputRulePluginProps = {
117
+ rules: Array<InputRule>
118
+ }
119
+
120
+ /**
121
+ * @alpha
122
+ */
123
+ export declare type TextTransformRule = {
124
+ on: RegExp
125
+ guard?: InputRuleGuard
126
+ transform: () => string
127
+ }
128
+
129
+ export {}