@portabletext/plugin-input-rule 6.0.13 → 6.0.14

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.js CHANGED
@@ -1,443 +1,377 @@
1
1
  import { c } from "react/compiler-runtime";
2
2
  import { useEditor } from "@portabletext/editor";
3
- import { defineBehavior, raise, effect, forward } from "@portabletext/editor/behaviors";
4
- import { getNextInlineObjects, getPreviousInlineObjects, getBlockOffsets, getFocusBlock, getBlockTextBefore, getMarkState } from "@portabletext/editor/selectors";
5
- import { blockOffsetToSpanSelectionPoint, childSelectionPointToBlockOffset, isKeyedSegment, isEqualSelections, isSelectionCollapsed } from "@portabletext/editor/utils";
3
+ import { defineBehavior, effect, forward, raise } from "@portabletext/editor/behaviors";
4
+ import { getBlockOffsets, getBlockTextBefore, getFocusBlock, getMarkState, getNextInlineObjects, getPreviousInlineObjects } from "@portabletext/editor/selectors";
5
+ import { blockOffsetToSpanSelectionPoint, childSelectionPointToBlockOffset, isEqualSelections, isKeyedSegment, isSelectionCollapsed } from "@portabletext/editor/utils";
6
6
  import { useActorRef } from "@xstate/react";
7
- import { setup, fromCallback } from "xstate";
7
+ import { fromCallback, setup } from "xstate";
8
+ /**
9
+ * @public
10
+ */
8
11
  function defineInputRule(config) {
9
- return config;
12
+ return config;
10
13
  }
11
- function getInputRuleMatchLocation({
12
- match,
13
- adjustIndexBy,
14
- snapshot,
15
- focusBlock,
16
- originalTextBefore,
17
- allowedInlineObjectRanges
18
- }) {
19
- const [text, start, end] = match, adjustedIndex = start + adjustIndexBy, targetOffsets = {
20
- anchor: {
21
- path: focusBlock.path,
22
- offset: adjustedIndex
23
- },
24
- focus: {
25
- path: focusBlock.path,
26
- offset: adjustedIndex + end - start
27
- },
28
- backward: !1
29
- }, normalizedOffsets = {
30
- anchor: {
31
- path: focusBlock.path,
32
- offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
33
- },
34
- focus: {
35
- path: focusBlock.path,
36
- offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
37
- }
38
- }, anchorBackwards = blockOffsetToSpanSelectionPoint({
39
- snapshot,
40
- blockOffset: normalizedOffsets.anchor,
41
- direction: "backward"
42
- }), focusForwards = blockOffsetToSpanSelectionPoint({
43
- snapshot,
44
- blockOffset: normalizedOffsets.focus,
45
- direction: "forward"
46
- });
47
- if (!anchorBackwards || !focusForwards)
48
- return;
49
- const selection = {
50
- anchor: anchorBackwards,
51
- focus: focusForwards
52
- }, inlineObjectsAfterMatch = getNextInlineObjects({
53
- ...snapshot,
54
- context: {
55
- ...snapshot.context,
56
- selection: {
57
- anchor: selection.anchor,
58
- focus: selection.anchor
59
- }
60
- }
61
- }), inlineObjectsBefore = getPreviousInlineObjects(snapshot), inlineObjectsInMatch = inlineObjectsAfterMatch.filter((inlineObjectAfter) => inlineObjectsBefore.some((inlineObjectBefore) => inlineObjectAfter.node._key === inlineObjectBefore.node._key));
62
- for (const inlineObject of inlineObjectsInMatch) {
63
- const inlineObjectOffset = childSelectionPointToBlockOffset({
64
- snapshot,
65
- selectionPoint: {
66
- path: inlineObject.path,
67
- offset: 0
68
- }
69
- });
70
- if (!inlineObjectOffset || !allowedInlineObjectRanges.some((range) => inlineObjectOffset.offset >= range.start + adjustIndexBy && inlineObjectOffset.offset <= range.end + adjustIndexBy))
71
- return;
72
- }
73
- return {
74
- text,
75
- selection,
76
- targetOffsets
77
- };
14
+ function getInputRuleMatchLocation({ match, adjustIndexBy, snapshot, focusBlock, originalTextBefore, allowedInlineObjectRanges }) {
15
+ let [text, start, end] = match, adjustedIndex = start + adjustIndexBy, targetOffsets = {
16
+ anchor: {
17
+ path: focusBlock.path,
18
+ offset: adjustedIndex
19
+ },
20
+ focus: {
21
+ path: focusBlock.path,
22
+ offset: adjustedIndex + end - start
23
+ },
24
+ backward: !1
25
+ }, normalizedOffsets = {
26
+ anchor: {
27
+ path: focusBlock.path,
28
+ offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
29
+ },
30
+ focus: {
31
+ path: focusBlock.path,
32
+ offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
33
+ },
34
+ backward: !1
35
+ }, anchorBackwards = blockOffsetToSpanSelectionPoint({
36
+ snapshot,
37
+ blockOffset: normalizedOffsets.anchor,
38
+ direction: "backward"
39
+ }), focusForwards = blockOffsetToSpanSelectionPoint({
40
+ snapshot,
41
+ blockOffset: normalizedOffsets.focus,
42
+ direction: "forward"
43
+ });
44
+ if (!anchorBackwards || !focusForwards) return;
45
+ let selection = {
46
+ anchor: anchorBackwards,
47
+ focus: focusForwards
48
+ }, inlineObjectsAfterMatch = getNextInlineObjects({
49
+ ...snapshot,
50
+ context: {
51
+ ...snapshot.context,
52
+ selection: {
53
+ anchor: selection.anchor,
54
+ focus: selection.anchor
55
+ }
56
+ }
57
+ }), inlineObjectsBefore = getPreviousInlineObjects(snapshot), inlineObjectsInMatch = inlineObjectsAfterMatch.filter((inlineObjectAfter) => inlineObjectsBefore.some((inlineObjectBefore) => inlineObjectAfter.node._key === inlineObjectBefore.node._key));
58
+ for (let inlineObject of inlineObjectsInMatch) {
59
+ let inlineObjectOffset = childSelectionPointToBlockOffset({
60
+ snapshot,
61
+ selectionPoint: {
62
+ path: inlineObject.path,
63
+ offset: 0
64
+ }
65
+ });
66
+ if (!inlineObjectOffset || !allowedInlineObjectRanges.some((range) => inlineObjectOffset.offset >= range.start + adjustIndexBy && inlineObjectOffset.offset <= range.end + adjustIndexBy)) return;
67
+ }
68
+ return {
69
+ text,
70
+ selection,
71
+ targetOffsets
72
+ };
78
73
  }
74
+ /**
75
+ * @public
76
+ */
79
77
  function defineInputRuleBehavior(config) {
80
- return defineBehavior({
81
- on: "insert.text",
82
- guard: ({
83
- snapshot,
84
- event,
85
- dom
86
- }) => {
87
- if (!snapshot.context.selection || !isSelectionCollapsed(snapshot.context.selection))
88
- return !1;
89
- const focusBlock = getFocusBlock(snapshot);
90
- if (!focusBlock)
91
- return !1;
92
- const originalTextBefore = getBlockTextBefore(snapshot);
93
- let textBefore = originalTextBefore;
94
- const originalNewText = textBefore + event.text;
95
- let newText = originalNewText;
96
- const foundMatches = [], foundActions = [];
97
- for (const rule of config.rules) {
98
- const safeUserFlags = rule.on.flags.replace(/[^imsu]/g, ""), matcher = new RegExp(rule.on.source, `gd${safeUserFlags}`);
99
- for (; ; ) {
100
- const ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
101
- if (regExpMatch.indices === void 0)
102
- return [];
103
- const match = regExpMatch.indices.at(0);
104
- if (!match)
105
- return [];
106
- const allowedInlineObjectRanges = (rule.inlineObjects?.allow ?? []).flatMap((groupName) => {
107
- const span = regExpMatch.indices?.groups?.[groupName];
108
- return span ? [{
109
- start: span[0],
110
- end: span[1]
111
- }] : [];
112
- }), matchLocation = getInputRuleMatchLocation({
113
- match: [regExpMatch.at(0) ?? "", ...match],
114
- adjustIndexBy: originalNewText.length - newText.length,
115
- snapshot,
116
- focusBlock,
117
- originalTextBefore,
118
- allowedInlineObjectRanges
119
- });
120
- if (!matchLocation)
121
- return [];
122
- if (matchLocation.targetOffsets.focus.offset <= originalTextBefore.length)
123
- return [];
124
- if (foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset))
125
- return [];
126
- const groups = {};
127
- for (const [groupName, span] of Object.entries(regExpMatch.indices.groups ?? {})) {
128
- if (!span)
129
- continue;
130
- const groupLocation = getInputRuleMatchLocation({
131
- match: [regExpMatch.groups?.[groupName] ?? "", span[0], span[1]],
132
- adjustIndexBy: originalNewText.length - newText.length,
133
- snapshot,
134
- focusBlock,
135
- originalTextBefore,
136
- allowedInlineObjectRanges
137
- });
138
- groupLocation && (groups[groupName] = groupLocation);
139
- }
140
- return [{
141
- text: matchLocation.text,
142
- selection: matchLocation.selection,
143
- targetOffsets: matchLocation.targetOffsets,
144
- groups
145
- }];
146
- });
147
- if (ruleMatches.length > 0) {
148
- const guardResult = rule.guard?.({
149
- snapshot,
150
- event: {
151
- type: "custom.input rule",
152
- matches: ruleMatches,
153
- focusBlock,
154
- textBefore: originalTextBefore,
155
- textInserted: event.text
156
- },
157
- dom
158
- }) ?? !0;
159
- if (!guardResult)
160
- break;
161
- const actionSets = rule.actions.map((action) => action({
162
- snapshot,
163
- event: {
164
- type: "custom.input rule",
165
- matches: ruleMatches,
166
- focusBlock,
167
- textBefore: originalTextBefore,
168
- textInserted: event.text
169
- },
170
- dom
171
- }, guardResult));
172
- for (const actionSet of actionSets)
173
- for (const action of actionSet)
174
- foundActions.push(action);
175
- const matches = ruleMatches.flatMap((match) => {
176
- const groupLocations = Object.values(match.groups).filter((location) => location !== void 0);
177
- return groupLocations.length === 0 ? [match] : groupLocations;
178
- });
179
- for (const match of matches)
180
- foundMatches.push(match), textBefore = newText.slice(0, match.targetOffsets.focus.offset ?? 0), newText = originalNewText.slice(match.targetOffsets.focus.offset ?? 0);
181
- } else
182
- break;
183
- }
184
- }
185
- return foundActions.length === 0 ? !1 : {
186
- actions: foundActions
187
- };
188
- },
189
- actions: [({
190
- event
191
- }) => [forward(event)], (_, {
192
- actions
193
- }) => actions, ({
194
- snapshot
195
- }) => [effect(() => {
196
- const blockOffsets = getBlockOffsets(snapshot);
197
- config.onApply?.({
198
- endOffsets: blockOffsets,
199
- endSelection: snapshot.context.selection
200
- });
201
- })]]
202
- });
78
+ return defineBehavior({
79
+ on: "insert.text",
80
+ guard: ({ snapshot, event, dom }) => {
81
+ if (!snapshot.context.selection || !isSelectionCollapsed(snapshot.context.selection)) return !1;
82
+ let focusBlock = getFocusBlock(snapshot);
83
+ if (!focusBlock) return !1;
84
+ let originalTextBefore = getBlockTextBefore(snapshot), textBefore = originalTextBefore, originalNewText = textBefore + event.text, newText = originalNewText, foundMatches = [], foundActions = [];
85
+ for (let rule of config.rules) {
86
+ let safeUserFlags = rule.on.flags.replace(/[^imsu]/g, ""), matcher = new RegExp(rule.on.source, `gd${safeUserFlags}`);
87
+ for (;;) {
88
+ let ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
89
+ if (regExpMatch.indices === void 0) return [];
90
+ let match = regExpMatch.indices.at(0);
91
+ if (!match) return [];
92
+ let allowedInlineObjectRanges = (rule.inlineObjects?.allow ?? []).flatMap((groupName) => {
93
+ let span = regExpMatch.indices?.groups?.[groupName];
94
+ return span ? [{
95
+ start: span[0],
96
+ end: span[1]
97
+ }] : [];
98
+ }), matchLocation = getInputRuleMatchLocation({
99
+ match: [regExpMatch.at(0) ?? "", ...match],
100
+ adjustIndexBy: originalNewText.length - newText.length,
101
+ snapshot,
102
+ focusBlock,
103
+ originalTextBefore,
104
+ allowedInlineObjectRanges
105
+ });
106
+ if (!matchLocation || matchLocation.targetOffsets.focus.offset <= originalTextBefore.length || foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset)) return [];
107
+ let groups = {};
108
+ for (let [groupName, span] of Object.entries(regExpMatch.indices.groups ?? {})) {
109
+ if (!span) continue;
110
+ let groupLocation = getInputRuleMatchLocation({
111
+ match: [
112
+ regExpMatch.groups?.[groupName] ?? "",
113
+ span[0],
114
+ span[1]
115
+ ],
116
+ adjustIndexBy: originalNewText.length - newText.length,
117
+ snapshot,
118
+ focusBlock,
119
+ originalTextBefore,
120
+ allowedInlineObjectRanges
121
+ });
122
+ groupLocation && (groups[groupName] = groupLocation);
123
+ }
124
+ return [{
125
+ text: matchLocation.text,
126
+ selection: matchLocation.selection,
127
+ targetOffsets: matchLocation.targetOffsets,
128
+ groups
129
+ }];
130
+ });
131
+ if (ruleMatches.length > 0) {
132
+ let guardResult = rule.guard?.({
133
+ snapshot,
134
+ event: {
135
+ type: "custom.input rule",
136
+ matches: ruleMatches,
137
+ focusBlock,
138
+ textBefore: originalTextBefore,
139
+ textInserted: event.text
140
+ },
141
+ dom
142
+ }) ?? !0;
143
+ if (!guardResult) break;
144
+ let actionSets = rule.actions.map((action) => action({
145
+ snapshot,
146
+ event: {
147
+ type: "custom.input rule",
148
+ matches: ruleMatches,
149
+ focusBlock,
150
+ textBefore: originalTextBefore,
151
+ textInserted: event.text
152
+ },
153
+ dom
154
+ }, guardResult));
155
+ for (let actionSet of actionSets) for (let action of actionSet) foundActions.push(action);
156
+ let matches = ruleMatches.flatMap((match) => {
157
+ let groupLocations = Object.values(match.groups).filter((location) => location !== void 0);
158
+ return groupLocations.length === 0 ? [match] : groupLocations;
159
+ });
160
+ for (let match of matches) foundMatches.push(match), textBefore = newText.slice(0, match.targetOffsets.focus.offset ?? 0), newText = originalNewText.slice(match.targetOffsets.focus.offset ?? 0);
161
+ } else break;
162
+ }
163
+ }
164
+ return foundActions.length !== 0 && { actions: foundActions };
165
+ },
166
+ actions: [
167
+ ({ event }) => [forward(event)],
168
+ (_, { actions }) => actions,
169
+ ({ snapshot }) => [effect(() => {
170
+ let blockOffsets = getBlockOffsets(snapshot);
171
+ config.onApply?.({
172
+ endOffsets: blockOffsets,
173
+ endSelection: snapshot.context.selection
174
+ });
175
+ })]
176
+ ]
177
+ });
203
178
  }
179
+ /**
180
+ * Turn an array of `InputRule`s into a Behavior that can be used to apply the
181
+ * rules to the editor.
182
+ *
183
+ * The plugin handles undo/redo out of the box including smart undo with
184
+ * Backspace.
185
+ *
186
+ * @example
187
+ * ```tsx
188
+ * <InputRulePlugin rules={smartQuotesRules} />
189
+ * ```
190
+ *
191
+ * @public
192
+ */
204
193
  function InputRulePlugin(props) {
205
- const $ = c(3), editor = useEditor();
206
- let t0;
207
- return $[0] !== editor || $[1] !== props.rules ? (t0 = {
208
- input: {
209
- editor,
210
- rules: props.rules
211
- }
212
- }, $[0] = editor, $[1] = props.rules, $[2] = t0) : t0 = $[2], useActorRef(inputRuleMachine, t0), null;
194
+ let $ = c(3), editor = useEditor(), t0;
195
+ return $[0] !== editor || $[1] !== props.rules ? (t0 = { input: {
196
+ editor,
197
+ rules: props.rules
198
+ } }, $[0] = editor, $[1] = props.rules, $[2] = t0) : t0 = $[2], useActorRef(inputRuleMachine, t0), null;
213
199
  }
214
- const inputRuleListenerCallback = ({
215
- input,
216
- sendBack
217
- }) => {
218
- const unregister = input.editor.registerBehavior({
219
- behavior: defineInputRuleBehavior({
220
- rules: input.rules,
221
- onApply: ({
222
- endOffsets,
223
- endSelection
224
- }) => {
225
- sendBack({
226
- type: "input rule raised",
227
- endOffsets,
228
- endSelection
229
- });
230
- }
231
- })
232
- });
233
- return () => {
234
- unregister();
235
- };
236
- }, deleteBackwardListenerCallback = ({
237
- input,
238
- sendBack
239
- }) => input.editor.registerBehavior({
240
- behavior: defineBehavior({
241
- on: "delete.backward",
242
- actions: [() => [raise({
243
- type: "history.undo"
244
- }), effect(() => {
245
- sendBack({
246
- type: "history.undo raised"
247
- });
248
- })]]
249
- })
250
- }), selectionListenerCallback = ({
251
- sendBack,
252
- input
253
- }) => {
254
- const subscription = input.editor.on("selection", (event) => {
255
- const blockOffsets = getBlockOffsets({
256
- ...input.editor.getSnapshot(),
257
- context: {
258
- ...input.editor.getSnapshot().context,
259
- selection: event.selection
260
- }
261
- });
262
- sendBack({
263
- type: "selection changed",
264
- blockOffsets,
265
- selection: event.selection
266
- });
267
- });
268
- return () => subscription.unsubscribe();
200
+ const inputRuleListenerCallback = ({ input, sendBack }) => {
201
+ let unregister = input.editor.registerBehavior({ behavior: defineInputRuleBehavior({
202
+ rules: input.rules,
203
+ onApply: ({ endOffsets, endSelection }) => {
204
+ sendBack({
205
+ type: "input rule raised",
206
+ endOffsets,
207
+ endSelection
208
+ });
209
+ }
210
+ }) });
211
+ return () => {
212
+ unregister();
213
+ };
214
+ }, deleteBackwardListenerCallback = ({ input, sendBack }) => input.editor.registerBehavior({ behavior: defineBehavior({
215
+ on: "delete.backward",
216
+ actions: [() => [raise({ type: "history.undo" }), effect(() => {
217
+ sendBack({ type: "history.undo raised" });
218
+ })]]
219
+ }) }), selectionListenerCallback = ({ sendBack, input }) => {
220
+ let subscription = input.editor.on("selection", (event) => {
221
+ sendBack({
222
+ type: "selection changed",
223
+ blockOffsets: getBlockOffsets({
224
+ ...input.editor.getSnapshot(),
225
+ context: {
226
+ ...input.editor.getSnapshot().context,
227
+ selection: event.selection
228
+ }
229
+ }),
230
+ selection: event.selection
231
+ });
232
+ });
233
+ return () => subscription.unsubscribe();
269
234
  }, inputRuleSetup = setup({
270
- types: {
271
- context: {},
272
- input: {},
273
- events: {}
274
- },
275
- actors: {
276
- "delete.backward listener": fromCallback(deleteBackwardListenerCallback),
277
- "input rule listener": fromCallback(inputRuleListenerCallback),
278
- "selection listener": fromCallback(selectionListenerCallback)
279
- },
280
- guards: {
281
- "selection changed": ({
282
- context,
283
- event
284
- }) => {
285
- if (event.type !== "selection changed")
286
- return !1;
287
- if (event.blockOffsets && context.endOffsets) {
288
- const contextStartBlock = context.endOffsets.start.path.at(-1), eventStartBlock = event.blockOffsets.start.path.at(-1), contextEndBlock = context.endOffsets.end.path.at(-1), eventEndBlock = event.blockOffsets.end.path.at(-1);
289
- if (!isKeyedSegment(contextStartBlock) || !isKeyedSegment(eventStartBlock) || !isKeyedSegment(contextEndBlock) || !isKeyedSegment(eventEndBlock))
290
- return !1;
291
- const startChanged = contextStartBlock._key !== eventStartBlock._key || context.endOffsets.start.offset !== event.blockOffsets.start.offset, endChanged = contextEndBlock._key !== eventEndBlock._key || context.endOffsets.end.offset !== event.blockOffsets.end.offset;
292
- return startChanged || endChanged;
293
- }
294
- return !isEqualSelections(context.endSelection, event.selection);
295
- }
296
- }
235
+ types: {
236
+ context: {},
237
+ input: {},
238
+ events: {}
239
+ },
240
+ actors: {
241
+ "delete.backward listener": fromCallback(deleteBackwardListenerCallback),
242
+ "input rule listener": fromCallback(inputRuleListenerCallback),
243
+ "selection listener": fromCallback(selectionListenerCallback)
244
+ },
245
+ guards: { "selection changed": ({ context, event }) => {
246
+ if (event.type !== "selection changed") return !1;
247
+ if (event.blockOffsets && context.endOffsets) {
248
+ let contextStartBlock = context.endOffsets.start.path.at(-1), eventStartBlock = event.blockOffsets.start.path.at(-1), contextEndBlock = context.endOffsets.end.path.at(-1), eventEndBlock = event.blockOffsets.end.path.at(-1);
249
+ if (!isKeyedSegment(contextStartBlock) || !isKeyedSegment(eventStartBlock) || !isKeyedSegment(contextEndBlock) || !isKeyedSegment(eventEndBlock)) return !1;
250
+ let startChanged = contextStartBlock._key !== eventStartBlock._key || context.endOffsets.start.offset !== event.blockOffsets.start.offset, endChanged = contextEndBlock._key !== eventEndBlock._key || context.endOffsets.end.offset !== event.blockOffsets.end.offset;
251
+ return startChanged || endChanged;
252
+ }
253
+ return !isEqualSelections(context.endSelection, event.selection);
254
+ } }
297
255
  }), assignEndState = inputRuleSetup.assign({
298
- endOffsets: ({
299
- context,
300
- event
301
- }) => event.type === "input rule raised" ? event.endOffsets : context.endOffsets,
302
- endSelection: ({
303
- context,
304
- event
305
- }) => event.type === "input rule raised" ? event.endSelection : context.endSelection
256
+ endOffsets: ({ context, event }) => event.type === "input rule raised" ? event.endOffsets : context.endOffsets,
257
+ endSelection: ({ context, event }) => event.type === "input rule raised" ? event.endSelection : context.endSelection
306
258
  }), inputRuleMachine = inputRuleSetup.createMachine({
307
- id: "input rule",
308
- context: ({
309
- input
310
- }) => ({
311
- editor: input.editor,
312
- rules: input.rules,
313
- endOffsets: void 0,
314
- endSelection: null
315
- }),
316
- initial: "idle",
317
- invoke: {
318
- src: "input rule listener",
319
- input: ({
320
- context
321
- }) => ({
322
- editor: context.editor,
323
- rules: context.rules
324
- })
325
- },
326
- on: {
327
- "input rule raised": {
328
- target: ".input rule applied",
329
- actions: assignEndState
330
- }
331
- },
332
- states: {
333
- idle: {},
334
- "input rule applied": {
335
- invoke: [{
336
- src: "delete.backward listener",
337
- input: ({
338
- context
339
- }) => ({
340
- editor: context.editor
341
- })
342
- }, {
343
- src: "selection listener",
344
- input: ({
345
- context
346
- }) => ({
347
- editor: context.editor
348
- })
349
- }],
350
- on: {
351
- "selection changed": {
352
- target: "idle",
353
- guard: "selection changed"
354
- },
355
- "history.undo raised": {
356
- target: "idle"
357
- }
358
- }
359
- }
360
- }
259
+ id: "input rule",
260
+ context: ({ input }) => ({
261
+ editor: input.editor,
262
+ rules: input.rules,
263
+ endOffsets: void 0,
264
+ endSelection: null
265
+ }),
266
+ initial: "idle",
267
+ invoke: {
268
+ src: "input rule listener",
269
+ input: ({ context }) => ({
270
+ editor: context.editor,
271
+ rules: context.rules
272
+ })
273
+ },
274
+ on: { "input rule raised": {
275
+ target: ".input rule applied",
276
+ actions: assignEndState
277
+ } },
278
+ states: {
279
+ idle: {},
280
+ "input rule applied": {
281
+ invoke: [{
282
+ src: "delete.backward listener",
283
+ input: ({ context }) => ({ editor: context.editor })
284
+ }, {
285
+ src: "selection listener",
286
+ input: ({ context }) => ({ editor: context.editor })
287
+ }],
288
+ on: {
289
+ "selection changed": {
290
+ target: "idle",
291
+ guard: "selection changed"
292
+ },
293
+ "history.undo raised": { target: "idle" }
294
+ }
295
+ }
296
+ }
361
297
  });
298
+ /**
299
+ * Define an `InputRule` specifically designed to transform matched text into
300
+ * some other text.
301
+ *
302
+ * @example
303
+ * ```tsx
304
+ * const transformRule = defineTextTransformRule({
305
+ * on: /--/,
306
+ * transform: () => '—',
307
+ * })
308
+ * ```
309
+ *
310
+ * @public
311
+ */
362
312
  function defineTextTransformRule(config) {
363
- const transformRecord = typeof config.transform == "function" ? void 0 : config.transform;
364
- if (transformRecord) {
365
- const probeFlags = config.on.flags.replace(/[gyd]/g, ""), namedGroups = Object.keys(new RegExp(`${config.on.source}|`, probeFlags).exec("")?.groups ?? {});
366
- for (const groupName of Object.keys(transformRecord))
367
- if (!namedGroups.includes(groupName))
368
- throw new Error(`defineTextTransformRule: \`transform\` targets the group "${groupName}", but \`on\` (${config.on}) has no such named capture group` + (namedGroups.length > 0 ? `. Named groups: ${namedGroups.map((name) => `"${name}"`).join(", ")}` : ". The pattern has no named capture groups"));
369
- }
370
- return {
371
- on: config.on,
372
- guard: config.guard ?? (() => !0),
373
- actions: [({
374
- snapshot,
375
- event
376
- }, guardResponse) => {
377
- const targets = event.matches.flatMap((match) => transformRecord ? Object.entries(transformRecord).flatMap(([groupName, groupTransform]) => {
378
- const location = match.groups[groupName];
379
- return location ? [{
380
- location,
381
- transform: groupTransform
382
- }] : [];
383
- }) : [{
384
- location: match,
385
- transform: config.transform
386
- }]).sort((a, b) => a.location.targetOffsets.anchor.offset - b.location.targetOffsets.anchor.offset), newText = event.textBefore + event.textInserted;
387
- let textLengthDelta = 0;
388
- const actions = [];
389
- for (const {
390
- location,
391
- transform
392
- } of targets.reverse()) {
393
- const text = transform({
394
- location
395
- }, guardResponse);
396
- textLengthDelta = textLengthDelta - (text.length - (location.targetOffsets.focus.offset - location.targetOffsets.anchor.offset)), actions.push(raise({
397
- type: "select",
398
- at: location.targetOffsets
399
- })), actions.push(raise({
400
- type: "delete",
401
- at: location.targetOffsets
402
- })), actions.push(raise({
403
- type: "insert.child",
404
- child: {
405
- _type: snapshot.context.schema.span.name,
406
- text,
407
- marks: getMarkState({
408
- ...snapshot,
409
- context: {
410
- ...snapshot.context,
411
- selection: {
412
- anchor: location.selection.anchor,
413
- focus: {
414
- path: location.selection.focus.path,
415
- offset: Math.min(location.selection.focus.offset, event.textBefore.length)
416
- }
417
- }
418
- }
419
- })?.marks ?? []
420
- }
421
- }));
422
- }
423
- const endCaretPosition = {
424
- path: event.focusBlock.path,
425
- offset: newText.length - textLengthDelta
426
- };
427
- return [...actions, raise({
428
- type: "select",
429
- at: {
430
- anchor: endCaretPosition,
431
- focus: endCaretPosition
432
- }
433
- })];
434
- }]
435
- };
313
+ let transformRecord = typeof config.transform == "function" ? void 0 : config.transform;
314
+ if (transformRecord) {
315
+ let probeFlags = config.on.flags.replace(/[gyd]/g, ""), namedGroups = Object.keys(RegExp(`${config.on.source}|`, probeFlags).exec("")?.groups ?? {});
316
+ for (let groupName of Object.keys(transformRecord)) if (!namedGroups.includes(groupName)) throw Error(`defineTextTransformRule: \`transform\` targets the group "${groupName}", but \`on\` (${config.on}) has no such named capture group` + (namedGroups.length > 0 ? `. Named groups: ${namedGroups.map((name) => `"${name}"`).join(", ")}` : ". The pattern has no named capture groups"));
317
+ }
318
+ return {
319
+ on: config.on,
320
+ guard: config.guard ?? (() => !0),
321
+ actions: [({ snapshot, event }, guardResponse) => {
322
+ let targets = event.matches.flatMap((match) => transformRecord ? Object.entries(transformRecord).flatMap(([groupName, groupTransform]) => {
323
+ let location = match.groups[groupName];
324
+ return location ? [{
325
+ location,
326
+ transform: groupTransform
327
+ }] : [];
328
+ }) : [{
329
+ location: match,
330
+ transform: config.transform
331
+ }]).sort((a, b) => a.location.targetOffsets.anchor.offset - b.location.targetOffsets.anchor.offset), newText = event.textBefore + event.textInserted, textLengthDelta = 0, actions = [];
332
+ for (let { location, transform } of targets.reverse()) {
333
+ let text = transform({ location }, guardResponse);
334
+ textLengthDelta -= text.length - (location.targetOffsets.focus.offset - location.targetOffsets.anchor.offset), actions.push(raise({
335
+ type: "select",
336
+ at: location.targetOffsets
337
+ })), actions.push(raise({
338
+ type: "delete",
339
+ at: location.targetOffsets
340
+ })), actions.push(raise({
341
+ type: "insert.child",
342
+ child: {
343
+ _type: snapshot.context.schema.span.name,
344
+ text,
345
+ marks: getMarkState({
346
+ ...snapshot,
347
+ context: {
348
+ ...snapshot.context,
349
+ selection: {
350
+ anchor: location.selection.anchor,
351
+ focus: {
352
+ path: location.selection.focus.path,
353
+ offset: Math.min(location.selection.focus.offset, event.textBefore.length)
354
+ }
355
+ }
356
+ }
357
+ })?.marks ?? []
358
+ }
359
+ }));
360
+ }
361
+ let endCaretPosition = {
362
+ path: event.focusBlock.path,
363
+ offset: newText.length - textLengthDelta
364
+ };
365
+ return [...actions, raise({
366
+ type: "select",
367
+ at: {
368
+ anchor: endCaretPosition,
369
+ focus: endCaretPosition
370
+ }
371
+ })];
372
+ }]
373
+ };
436
374
  }
437
- export {
438
- InputRulePlugin,
439
- defineInputRule,
440
- defineInputRuleBehavior,
441
- defineTextTransformRule
442
- };
443
- //# sourceMappingURL=index.js.map
375
+ export { InputRulePlugin, defineInputRule, defineInputRuleBehavior, defineTextTransformRule };
376
+
377
+ //# sourceMappingURL=index.js.map