@portabletext/plugin-input-rule 5.0.29 → 6.0.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/README.md CHANGED
@@ -71,6 +71,34 @@ export function MyMarkdownPlugin() {
71
71
 
72
72
  > **Tip:** The [`@portabletext/plugin-markdown-shortcuts`](../plugin-markdown-shortcuts/) package is already built using Input Rules and provides common markdown shortcuts out of the box.
73
73
 
74
+ ## Working with capture groups
75
+
76
+ When a rule needs the location of a _part_ of its match, capture that part in a **named group** and read it back by name from `match.groups`:
77
+
78
+ ```tsx
79
+ const mentionRule = defineInputRule({
80
+ on: /@(?<handle>\w+)!$/,
81
+ guard: ({event}) => {
82
+ const match = event.matches.at(0)
83
+ const handle = match?.groups['handle']
84
+
85
+ if (!handle) {
86
+ return false
87
+ }
88
+
89
+ return {handle}
90
+ },
91
+ actions: [
92
+ (_, {handle}) => [
93
+ // `handle.text` is the captured text, `handle.targetOffsets` its
94
+ // location — same shape as the match itself
95
+ ],
96
+ ],
97
+ })
98
+ ```
99
+
100
+ `match.groups` mirrors the platform's `RegExpMatchArray.groups`: entries exist only for named groups that participated in the match, so always handle `undefined` (an optional group may not have matched). A capture group must be named (`(?<name>...)`) for its location to be handed back — unnamed groups remain useful for regex mechanics like alternation (`/^(-|\*) /`) but get no location. In codebases with `noPropertyAccessFromIndexSignature` enabled, access entries with brackets: `match.groups['handle']`.
101
+
74
102
  ## Text transformation rules
75
103
 
76
104
  Text transformations are so common that the plugin provides a high-level `defineTextTransformRule` helper to configure them without any boilerplate:
@@ -93,6 +121,18 @@ export function MyTypographyPlugin() {
93
121
 
94
122
  In fact, the production-ready [`@portabletext/plugin-typography`](../plugin-typography/) is built on top of Input Rules and comes packed with common text transformations like this.
95
123
 
124
+ A transform that should replace only _part_ of its match uses the record form of `transform`: keys name the capture groups to replace, each with its own transform:
125
+
126
+ ```tsx
127
+ const multiplicationRule = defineTextTransformRule({
128
+ on: /\d+\s?(?<operator>[*x])\s?\d+/,
129
+ // Only the operator's span is replaced; the digits around it stay
130
+ transform: {operator: () => '×'},
131
+ })
132
+ ```
133
+
134
+ Unlike ProseMirror's and TipTap's input rules, which implicitly replace the first capture group when one exists, replacement targets are always declared: a function transform replaces the whole match, regardless of any capture groups, and a record transform replaces exactly the groups its keys name. `defineTextTransformRule` throws at definition time when a key names a group the pattern doesn't have, and a match in which none of the keys participated is skipped. The surrounding context (like the digits above) must stay _inside_ the match rather than in lookarounds, a rule only fires when its match involves the just-inserted text, so a trailing lookahead would leave the match entirely in already-typed text and the rule would never trigger.
135
+
96
136
  ## Advanced examples
97
137
 
98
138
  Input Rules can handle more complex transformations. Here are two advanced examples:
@@ -106,7 +146,7 @@ This example shows how to convert markdown-style link syntax `[text](url)` into
106
146
 
107
147
  ```tsx
108
148
  const markdownLinkRule = defineInputRule({
109
- on: /\[(.+)]\((.+)\)/,
149
+ on: /\[(?<text>.+)]\((?<href>.+)\)/,
110
150
  actions: [
111
151
  ({snapshot, event}) => {
112
152
  const newText = event.textBefore + event.textInserted
@@ -114,8 +154,8 @@ const markdownLinkRule = defineInputRule({
114
154
  const actions: Array<BehaviorAction> = []
115
155
 
116
156
  for (const match of event.matches.reverse()) {
117
- const textMatch = match.groupMatches.at(0)
118
- const hrefMatch = match.groupMatches.at(1)
157
+ const textMatch = match.groups['text']
158
+ const hrefMatch = match.groups['href']
119
159
 
120
160
  if (textMatch === undefined || hrefMatch === undefined) {
121
161
  continue
@@ -193,7 +233,7 @@ This example demonstrates how to convert text patterns like `{AAPL}` into custom
193
233
 
194
234
  ```tsx
195
235
  const stockTickerRule = defineInputRule({
196
- on: /\{(.+)\}/,
236
+ on: /\{(?<symbol>.+)\}/,
197
237
  guard: ({snapshot, event}) => {
198
238
  const match = event.matches.at(0)
199
239
 
@@ -201,7 +241,7 @@ const stockTickerRule = defineInputRule({
201
241
  return false
202
242
  }
203
243
 
204
- const symbolMatch = match.groupMatches.at(0)
244
+ const symbolMatch = match.groups['symbol']
205
245
 
206
246
  if (symbolMatch === undefined) {
207
247
  return false
@@ -252,3 +292,27 @@ const stockTickerRule = defineInputRule({
252
292
  ],
253
293
  })
254
294
  ```
295
+
296
+ ## Matches spanning inline objects
297
+
298
+ Inline objects don't contribute to the text your RegExp matches against, so a pattern can match "across" one without knowing it. By default, such a match is dropped before your `guard` runs: for rules like the stock ticker above, whose actions `delete` the matched range and replace it, firing would destroy the inline object sitting inside the range.
299
+
300
+ Rules whose actions leave part of the matched range in place can grant leniency per named capture group:
301
+
302
+ ```tsx
303
+ const strongPairRule = defineInputRule({
304
+ on: /\*\*(?<content>[^*\n]+?)\*\*$/,
305
+ // The actions decorate the content and delete only the `**` markers, so
306
+ // an inline object inside the content is harmless and the match should
307
+ // fire. An inline object anywhere else in the match, between the marker
308
+ // characters, still drops the match.
309
+ inlineObjects: {allow: ['content']},
310
+ // ...
311
+ })
312
+ ```
313
+
314
+ The match survives when every inline object inside it sits within a listed group's matched span (inclusive of its edges). Unlisted groups and the text between groups, the rule's syntax markers, stay protected. This also expresses "this group's text becomes data": a markdown link rule can allow objects in its `text` group while leaving its `href` group protected, an inline object inside the href would make the captured text a lie.
315
+
316
+ The [`@portabletext/plugin-character-pair-decorator`](../plugin-character-pair-decorator/) package is built exactly this way: `inlineObjects: {allow: ['content']}` lets `**bo`⟨inline object⟩`ld**` decorate across the object, while a match with an inline object between the marker characters stays literal.
317
+
318
+ To allow inline objects anywhere in the match, capture the whole pattern in a named group and list it.
package/dist/index.d.ts CHANGED
@@ -8,14 +8,14 @@ import {EditorSelection} from '@portabletext/editor'
8
8
  import type {PortableTextBlock} from '@portabletext/editor'
9
9
 
10
10
  /**
11
- * @alpha
11
+ * @public
12
12
  */
13
13
  export declare function defineInputRule<TGuardResponse = true>(
14
14
  config: InputRule<TGuardResponse>,
15
15
  ): InputRule<TGuardResponse>
16
16
 
17
17
  /**
18
- * @alpha
18
+ * @public
19
19
  */
20
20
  export declare function defineInputRuleBehavior(config: {
21
21
  rules: Array<InputRule<any>>
@@ -142,23 +142,40 @@ export declare function defineInputRuleBehavior(config: {
142
142
  * })
143
143
  * ```
144
144
  *
145
- * @alpha
145
+ * @public
146
146
  */
147
147
  export declare function defineTextTransformRule<TGuardResponse = true>(
148
148
  config: TextTransformRule<TGuardResponse>,
149
149
  ): InputRule<TGuardResponse>
150
150
 
151
151
  /**
152
- * @alpha
152
+ * @public
153
153
  */
154
154
  export declare type InputRule<TGuardResponse = true> = {
155
155
  on: RegExp
156
+ /**
157
+ * Named capture groups inside which an inline object may sit without
158
+ * dropping the match.
159
+ *
160
+ * Inline objects contribute nothing to the text the RegExp matches
161
+ * against, so a match can span one invisibly. By default any such match
162
+ * is dropped: rules commonly delete the matched range, and deleting
163
+ * across an inline object destroys it. Listing a group allows inline
164
+ * objects within that group's matched span (inclusive of its edges,
165
+ * so an object adjacent to the span does not drop the match); everything
166
+ * else in the match, unlisted groups and the text between groups, stays
167
+ * protected. To allow inline objects anywhere in the match, capture the
168
+ * whole pattern in a named group and list it.
169
+ */
170
+ inlineObjects?: {
171
+ allow: Array<string>
172
+ }
156
173
  guard?: InputRuleGuard<TGuardResponse>
157
174
  actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>
158
175
  }
159
176
 
160
177
  /**
161
- * @alpha
178
+ * @public
162
179
  */
163
180
  export declare type InputRuleEvent = {
164
181
  type: 'custom.input rule'
@@ -184,7 +201,7 @@ export declare type InputRuleEvent = {
184
201
  }
185
202
 
186
203
  /**
187
- * @alpha
204
+ * @public
188
205
  */
189
206
  export declare type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<
190
207
  InputRuleEvent,
@@ -193,13 +210,23 @@ export declare type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<
193
210
 
194
211
  /**
195
212
  * Match found in the text after the insertion
196
- * @alpha
213
+ * @public
197
214
  */
198
215
  export declare type InputRuleMatch = InputRuleMatchLocation & {
199
- groupMatches: Array<InputRuleMatchLocation>
216
+ /**
217
+ * Locations of the match's named capture groups, keyed by group name.
218
+ * Only groups that participated in the match are present, and a capture
219
+ * group must be named (`(?<name>...)`) for its location to be exposed;
220
+ * unnamed groups remain usable for regex mechanics (alternation) but get
221
+ * no location.
222
+ */
223
+ groups: Record<string, InputRuleMatchLocation | undefined>
200
224
  }
201
225
 
202
- declare type InputRuleMatchLocation = {
226
+ /**
227
+ * @public
228
+ */
229
+ export declare type InputRuleMatchLocation = {
203
230
  /**
204
231
  * The matched text
205
232
  */
@@ -232,7 +259,7 @@ declare type InputRuleMatchLocation = {
232
259
  * <InputRulePlugin rules={smartQuotesRules} />
233
260
  * ```
234
261
  *
235
- * @alpha
262
+ * @public
236
263
  */
237
264
  export declare function InputRulePlugin(props: InputRulePluginProps): null
238
265
 
@@ -241,19 +268,43 @@ declare type InputRulePluginProps = {
241
268
  }
242
269
 
243
270
  /**
244
- * @alpha
271
+ * @public
272
+ */
273
+ export declare type TextTransform<TGuardResponse = true> = (
274
+ {
275
+ location,
276
+ }: {
277
+ location: InputRuleMatchLocation
278
+ },
279
+ guardResponse: TGuardResponse,
280
+ ) => string
281
+
282
+ /**
283
+ * @public
245
284
  */
246
285
  export declare type TextTransformRule<TGuardResponse = true> = {
247
286
  on: RegExp
248
287
  guard?: InputRuleGuard<TGuardResponse>
249
- transform: (
250
- {
251
- location,
252
- }: {
253
- location: InputRuleMatchLocation
254
- },
255
- guardResponse: TGuardResponse,
256
- ) => string
288
+ /**
289
+ * What to replace, and with what.
290
+ *
291
+ * A function replaces the whole match, always, regardless of any capture
292
+ * groups in the pattern. A record replaces only the spans of the named
293
+ * capture groups given as keys, each with its own transform,
294
+ * `/\d+\s?(?<operator>[*x])\s?\d+/` with
295
+ * `transform: {operator: () => '×'}` turns `2x3` into `2×3` rather than
296
+ * `×`. Use the record form when the pattern needs surrounding context to
297
+ * decide *when* to fire but only part of the match should change; the
298
+ * context must sit inside the match rather than in lookarounds, a rule
299
+ * only fires when its match involves the just-inserted text.
300
+ *
301
+ * Every key must exist as a named capture group in `on`;
302
+ * `defineTextTransformRule` throws otherwise. A match in which none of
303
+ * the keys participated has nothing to replace and is skipped.
304
+ */
305
+ transform:
306
+ | TextTransform<TGuardResponse>
307
+ | Record<string, TextTransform<TGuardResponse>>
257
308
  }
258
309
 
259
310
  export {}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { c } from "react/compiler-runtime";
2
2
  import { useEditor } from "@portabletext/editor";
3
3
  import { defineBehavior, raise, effect, forward } from "@portabletext/editor/behaviors";
4
4
  import { getNextInlineObjects, getPreviousInlineObjects, getBlockOffsets, getFocusBlock, getBlockTextBefore, getMarkState } from "@portabletext/editor/selectors";
5
- import { blockOffsetToSpanSelectionPoint, isKeyedSegment, isEqualSelections, isSelectionCollapsed } from "@portabletext/editor/utils";
5
+ import { blockOffsetToSpanSelectionPoint, childSelectionPointToBlockOffset, isKeyedSegment, isEqualSelections, isSelectionCollapsed } from "@portabletext/editor/utils";
6
6
  import { useActorRef } from "@xstate/react";
7
7
  import { setup, fromCallback } from "xstate";
8
8
  function defineInputRule(config) {
@@ -13,7 +13,8 @@ function getInputRuleMatchLocation({
13
13
  adjustIndexBy,
14
14
  snapshot,
15
15
  focusBlock,
16
- originalTextBefore
16
+ originalTextBefore,
17
+ allowedInlineObjectRanges
17
18
  }) {
18
19
  const [text, start, end] = match, adjustedIndex = start + adjustIndexBy, targetOffsets = {
19
20
  anchor: {
@@ -57,13 +58,23 @@ function getInputRuleMatchLocation({
57
58
  focus: selection.anchor
58
59
  }
59
60
  }
60
- }), inlineObjectsBefore = getPreviousInlineObjects(snapshot);
61
- if (!inlineObjectsAfterMatch.some((inlineObjectAfter) => inlineObjectsBefore.some((inlineObjectBefore) => inlineObjectAfter.node._key === inlineObjectBefore.node._key)))
62
- return {
63
- text,
64
- selection,
65
- targetOffsets
66
- };
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
+ };
67
78
  }
68
79
  function defineInputRuleBehavior(config) {
69
80
  return defineBehavior({
@@ -92,12 +103,19 @@ function defineInputRuleBehavior(config) {
92
103
  const match = regExpMatch.indices.at(0);
93
104
  if (!match)
94
105
  return [];
95
- const matchLocation = getInputRuleMatchLocation({
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({
96
113
  match: [regExpMatch.at(0) ?? "", ...match],
97
114
  adjustIndexBy: originalNewText.length - newText.length,
98
115
  snapshot,
99
116
  focusBlock,
100
- originalTextBefore
117
+ originalTextBefore,
118
+ allowedInlineObjectRanges
101
119
  });
102
120
  if (!matchLocation)
103
121
  return [];
@@ -105,21 +123,25 @@ function defineInputRuleBehavior(config) {
105
123
  return [];
106
124
  if (foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset))
107
125
  return [];
108
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).filter((indices) => indices !== void 0) : [];
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
+ }
109
140
  return [{
110
141
  text: matchLocation.text,
111
142
  selection: matchLocation.selection,
112
143
  targetOffsets: matchLocation.targetOffsets,
113
- groupMatches: groupMatches.flatMap((match2, index) => {
114
- const text = regExpMatch.at(index + 1) ?? "";
115
- return getInputRuleMatchLocation({
116
- match: [text, ...match2],
117
- adjustIndexBy: originalNewText.length - newText.length,
118
- snapshot,
119
- focusBlock,
120
- originalTextBefore
121
- }) || [];
122
- })
144
+ groups
123
145
  }];
124
146
  });
125
147
  if (ruleMatches.length > 0) {
@@ -150,7 +172,10 @@ function defineInputRuleBehavior(config) {
150
172
  for (const actionSet of actionSets)
151
173
  for (const action of actionSet)
152
174
  foundActions.push(action);
153
- const matches = ruleMatches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches);
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
+ });
154
179
  for (const match of matches)
155
180
  foundMatches.push(match), textBefore = newText.slice(0, match.targetOffsets.focus.offset ?? 0), newText = originalNewText.slice(match.targetOffsets.focus.offset ?? 0);
156
181
  } else
@@ -335,6 +360,13 @@ const inputRuleListenerCallback = ({
335
360
  }
336
361
  });
337
362
  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
+ }
338
370
  return {
339
371
  on: config.on,
340
372
  guard: config.guard ?? (() => !0),
@@ -342,11 +374,23 @@ function defineTextTransformRule(config) {
342
374
  snapshot,
343
375
  event
344
376
  }, guardResponse) => {
345
- const locations = event.matches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches), newText = event.textBefore + event.textInserted;
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;
346
387
  let textLengthDelta = 0;
347
388
  const actions = [];
348
- for (const location of locations.reverse()) {
349
- const text = config.transform({
389
+ for (const {
390
+ location,
391
+ transform
392
+ } of targets.reverse()) {
393
+ const text = transform({
350
394
  location
351
395
  }, guardResponse);
352
396
  textLengthDelta = textLengthDelta - (text.length - (location.targetOffsets.focus.offset - location.targetOffsets.anchor.offset)), actions.push(raise({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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, PortableTextBlock} 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 block where the insertion takes place\n */\n focusBlock: {\n path: BlockPath\n node: PortableTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<\n InputRuleEvent,\n TGuardResponse\n>\n\n/**\n * @alpha\n */\nexport type InputRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule<TGuardResponse = true>(\n config: InputRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {\n getNextInlineObjects,\n getPreviousInlineObjects,\n} from '@portabletext/editor/selectors'\nimport {blockOffsetToSpanSelectionPoint} from '@portabletext/editor/utils'\n\nexport type InputRuleMatchLocation = {\n /**\n * The matched text\n */\n text: string\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 focusBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusBlock: {\n path: BlockPath\n }\n originalTextBefore: string\n}): InputRuleMatchLocation | undefined {\n const [text, start, end] = match\n const adjustedIndex = start + adjustIndexBy\n\n const targetOffsets = {\n anchor: {\n path: focusBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n snapshot,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n snapshot,\n blockOffset: normalizedOffsets.focus,\n direction: 'forward',\n })\n\n if (!anchorBackwards || !focusForwards) {\n return undefined\n }\n\n const selection = {\n anchor: anchorBackwards,\n focus: focusForwards,\n }\n\n const inlineObjectsAfterMatch = getNextInlineObjects({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: selection.anchor,\n focus: selection.anchor,\n },\n },\n })\n const inlineObjectsBefore = getPreviousInlineObjects(snapshot)\n\n if (\n inlineObjectsAfterMatch.some((inlineObjectAfter) =>\n inlineObjectsBefore.some(\n (inlineObjectBefore) =>\n inlineObjectAfter.node._key === inlineObjectBefore.node._key,\n ),\n )\n ) {\n return undefined\n }\n\n return {\n text,\n selection,\n targetOffsets,\n }\n}\n","import {\n useEditor,\n type BlockOffset,\n type Editor,\n type EditorSelection,\n} 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 getFocusBlock,\n} from '@portabletext/editor/selectors'\nimport {\n isEqualSelections,\n isKeyedSegment,\n isSelectionCollapsed,\n} 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'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @alpha\n */\nexport function defineInputRuleBehavior(config: {\n rules: Array<InputRule<any>>\n onApply?: ({\n endOffsets,\n endSelection,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n endSelection: EditorSelection\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n if (\n !snapshot.context.selection ||\n !isSelectionCollapsed(snapshot.context.selection)\n ) {\n return false\n }\n\n const focusBlock = getFocusBlock(snapshot)\n\n if (!focusBlock) {\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 // Preserve a known-safe subset of flags from `rule.on`: `i` for\n // case-insensitive matches, `m` for multi-line anchors, `s` for\n // dot-matches-newline, `u` for Unicode property escapes. The plugin\n // sets `g` (to drive `matchAll`) and `d` (to read match indices)\n // itself, so user-set `g`/`d` are dropped to avoid `Invalid flags`\n // and keep the loop's contract. `y` (sticky) is also dropped: the\n // plugin's `matchAll` loop slices and re-feeds `newText` after each\n // match, which is incompatible with sticky's `lastIndex`-anchored\n // semantics.\n const safeUserFlags = rule.on.flags.replace(/[^imsu]/g, '')\n const matcher = new RegExp(rule.on.source, `gd${safeUserFlags}`)\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: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusBlock,\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\n .slice(1)\n .filter((indices) => indices !== undefined)\n : []\n\n const ruleMatch = {\n text: matchLocation.text,\n selection: matchLocation.selection,\n targetOffsets: matchLocation.targetOffsets,\n groupMatches: groupMatches.flatMap((match, index) => {\n const text = regExpMatch.at(index + 1) ?? ''\n const groupMatchLocation = getInputRuleMatchLocation({\n match: [text, ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusBlock,\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 focusBlock,\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 focusBlock,\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?.({\n endOffsets: blockOffsets,\n endSelection: snapshot.context.selection,\n })\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule<any>>\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 endSelection: EditorSelection\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n selection: EditorSelection\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: defineInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets, endSelection}) => {\n sendBack({type: 'input rule raised', endOffsets, endSelection})\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 // Listen for the emitted 'selection' event which fires after ANY cursor\n // movement (typing, clicking, pasting, etc.) - not just explicit 'select'\n // behavior events.\n const subscription = input.editor.on('selection', (event) => {\n const blockOffsets = getBlockOffsets({\n ...input.editor.getSnapshot(),\n context: {\n ...input.editor.getSnapshot().context,\n selection: event.selection,\n },\n })\n\n sendBack({\n type: 'selection changed',\n blockOffsets,\n selection: event.selection,\n })\n })\n\n return () => subscription.unsubscribe()\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 endSelection: EditorSelection\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 'selection changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n // When block offsets are available for both the end state and the\n // current selection, compare them. Block offsets normalize away\n // span-level differences (e.g. cursor at the same position but in a\n // different span after normalization).\n if (event.blockOffsets && context.endOffsets) {\n const contextStartBlock = context.endOffsets.start.path.at(-1)\n const eventStartBlock = event.blockOffsets.start.path.at(-1)\n const contextEndBlock = context.endOffsets.end.path.at(-1)\n const eventEndBlock = event.blockOffsets.end.path.at(-1)\n\n if (\n !isKeyedSegment(contextStartBlock) ||\n !isKeyedSegment(eventStartBlock) ||\n !isKeyedSegment(contextEndBlock) ||\n !isKeyedSegment(eventEndBlock)\n ) {\n return false\n }\n\n const startChanged =\n contextStartBlock._key !== eventStartBlock._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n contextEndBlock._key !== eventEndBlock._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n }\n\n // Block offsets can't be computed when the cursor is on an inline\n // object (e.g. after a stock ticker rule inserts one). Fall back to\n // comparing the raw selections.\n return !isEqualSelections(context.endSelection, event.selection)\n },\n },\n})\n\nconst assignEndState = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n endSelection: ({context, event}) =>\n event.type === 'input rule raised'\n ? event.endSelection\n : context.endSelection,\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 endSelection: null,\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: assignEndState,\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: 'selection changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise, type BehaviorAction} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @alpha\n */\nexport type TextTransformRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n transform: (\n {location}: {location: InputRuleMatchLocation},\n guardResponse: TGuardResponse,\n ) => 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<TGuardResponse = true>(\n config: TextTransformRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return {\n on: config.on,\n guard: config.guard ?? (() => true as TGuardResponse),\n actions: [\n ({snapshot, event}, guardResponse) => {\n const locations = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const newText = event.textBefore + event.textInserted\n\n let textLengthDelta = 0\n const actions: Array<BehaviorAction> = []\n\n for (const location of locations.reverse()) {\n const text = config.transform({location}, guardResponse)\n\n textLengthDelta =\n textLengthDelta -\n (text.length -\n (location.targetOffsets.focus.offset -\n location.targetOffsets.anchor.offset))\n\n actions.push(raise({type: 'select', at: location.targetOffsets}))\n actions.push(raise({type: 'delete', at: location.targetOffsets}))\n actions.push(\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text,\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: location.selection.anchor,\n focus: {\n path: location.selection.focus.path,\n offset: Math.min(\n location.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n )\n }\n\n const endCaretPosition = {\n path: event.focusBlock.path,\n offset: newText.length - textLengthDelta,\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","focusBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","context","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","defineInputRuleBehavior","defineBehavior","on","guard","event","dom","isSelectionCollapsed","getFocusBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","safeUserFlags","flags","replace","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","foundMatch","groupMatches","slice","filter","index","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","endSelection","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","subscription","getSnapshot","unsubscribe","inputRuleSetup","setup","types","events","actors","fromCallback","guards","selection changed","contextStartBlock","eventStartBlock","contextEndBlock","eventEndBlock","isKeyedSegment","startChanged","endChanged","isEqualSelections","assignEndState","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","guardResponse","locations","textLengthDelta","location","reverse","transform","child","_type","schema","span","name","marks","getMarkState","endCaretPosition"],"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, PortableTextBlock} 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 * @public\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n /**\n * Locations of the match's named capture groups, keyed by group name.\n * Only groups that participated in the match are present, and a capture\n * group must be named (`(?<name>...)`) for its location to be exposed;\n * unnamed groups remain usable for regex mechanics (alternation) but get\n * no location.\n */\n groups: Record<string, InputRuleMatchLocation | undefined>\n}\n\n/**\n * @public\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 block where the insertion takes place\n */\n focusBlock: {\n path: BlockPath\n node: PortableTextBlock\n }\n}\n\n/**\n * @public\n */\nexport type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<\n InputRuleEvent,\n TGuardResponse\n>\n\n/**\n * @public\n */\nexport type InputRule<TGuardResponse = true> = {\n on: RegExp\n /**\n * Named capture groups inside which an inline object may sit without\n * dropping the match.\n *\n * Inline objects contribute nothing to the text the RegExp matches\n * against, so a match can span one invisibly. By default any such match\n * is dropped: rules commonly delete the matched range, and deleting\n * across an inline object destroys it. Listing a group allows inline\n * objects within that group's matched span (inclusive of its edges,\n * so an object adjacent to the span does not drop the match); everything\n * else in the match, unlisted groups and the text between groups, stays\n * protected. To allow inline objects anywhere in the match, capture the\n * whole pattern in a named group and list it.\n */\n inlineObjects?: {allow: Array<string>}\n guard?: InputRuleGuard<TGuardResponse>\n actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>\n}\n\n/**\n * @public\n */\nexport function defineInputRule<TGuardResponse = true>(\n config: InputRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {\n getNextInlineObjects,\n getPreviousInlineObjects,\n} from '@portabletext/editor/selectors'\nimport {\n blockOffsetToSpanSelectionPoint,\n childSelectionPointToBlockOffset,\n} from '@portabletext/editor/utils'\n\n/**\n * @public\n */\nexport type InputRuleMatchLocation = {\n /**\n * The matched text\n */\n text: string\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 focusBlock,\n originalTextBefore,\n allowedInlineObjectRanges,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusBlock: {\n path: BlockPath\n }\n originalTextBefore: string\n /**\n * Index ranges (in the same text space as `match`) inside which an inline\n * object may sit without invalidating the match. Empty means any inline\n * object inside the match invalidates it.\n */\n allowedInlineObjectRanges: Array<{start: number; end: number}>\n}): InputRuleMatchLocation | undefined {\n const [text, start, end] = match\n const adjustedIndex = start + adjustIndexBy\n\n const targetOffsets = {\n anchor: {\n path: focusBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n snapshot,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n snapshot,\n blockOffset: normalizedOffsets.focus,\n direction: 'forward',\n })\n\n if (!anchorBackwards || !focusForwards) {\n return undefined\n }\n\n const selection = {\n anchor: anchorBackwards,\n focus: focusForwards,\n }\n\n const inlineObjectsAfterMatch = getNextInlineObjects({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: selection.anchor,\n focus: selection.anchor,\n },\n },\n })\n const inlineObjectsBefore = getPreviousInlineObjects(snapshot)\n\n const inlineObjectsInMatch = inlineObjectsAfterMatch.filter(\n (inlineObjectAfter) =>\n inlineObjectsBefore.some(\n (inlineObjectBefore) =>\n inlineObjectAfter.node._key === inlineObjectBefore.node._key,\n ),\n )\n\n for (const inlineObject of inlineObjectsInMatch) {\n const inlineObjectOffset = childSelectionPointToBlockOffset({\n snapshot,\n selectionPoint: {path: inlineObject.path, offset: 0},\n })\n\n if (!inlineObjectOffset) {\n return undefined\n }\n\n const allowed = allowedInlineObjectRanges.some(\n (range) =>\n inlineObjectOffset.offset >= range.start + adjustIndexBy &&\n inlineObjectOffset.offset <= range.end + adjustIndexBy,\n )\n\n if (!allowed) {\n return undefined\n }\n }\n\n return {\n text,\n selection,\n targetOffsets,\n }\n}\n","import {\n useEditor,\n type BlockOffset,\n type Editor,\n type EditorSelection,\n} 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 getFocusBlock,\n} from '@portabletext/editor/selectors'\nimport {\n isEqualSelections,\n isKeyedSegment,\n isSelectionCollapsed,\n} 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} from './input-rule'\nimport {\n getInputRuleMatchLocation,\n type InputRuleMatchLocation,\n} from './input-rule-match-location'\n\n/**\n * @public\n */\nexport function defineInputRuleBehavior(config: {\n rules: Array<InputRule<any>>\n onApply?: ({\n endOffsets,\n endSelection,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n endSelection: EditorSelection\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n if (\n !snapshot.context.selection ||\n !isSelectionCollapsed(snapshot.context.selection)\n ) {\n return false\n }\n\n const focusBlock = getFocusBlock(snapshot)\n\n if (!focusBlock) {\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<InputRuleMatchLocation> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n // Preserve a known-safe subset of flags from `rule.on`: `i` for\n // case-insensitive matches, `m` for multi-line anchors, `s` for\n // dot-matches-newline, `u` for Unicode property escapes. The plugin\n // sets `g` (to drive `matchAll`) and `d` (to read match indices)\n // itself, so user-set `g`/`d` are dropped to avoid `Invalid flags`\n // and keep the loop's contract. `y` (sticky) is also dropped: the\n // plugin's `matchAll` loop slices and re-feeds `newText` after each\n // match, which is incompatible with sticky's `lastIndex`-anchored\n // semantics.\n const safeUserFlags = rule.on.flags.replace(/[^imsu]/g, '')\n const matcher = new RegExp(rule.on.source, `gd${safeUserFlags}`)\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 // Resolve the rule's allowed groups to their matched spans;\n // a listed group that didn't participate in this match grants\n // no leniency.\n const allowedInlineObjectRanges = (\n rule.inlineObjects?.allow ?? []\n ).flatMap((groupName) => {\n const span = regExpMatch.indices?.groups?.[groupName]\n\n return span ? [{start: span[0], end: span[1]}] : []\n })\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusBlock,\n originalTextBefore,\n allowedInlineObjectRanges,\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 groups: Record<string, InputRuleMatchLocation | undefined> =\n {}\n\n for (const [groupName, span] of Object.entries(\n regExpMatch.indices.groups ?? {},\n )) {\n if (!span) {\n continue\n }\n\n const groupLocation = getInputRuleMatchLocation({\n match: [\n regExpMatch.groups?.[groupName] ?? '',\n span[0],\n span[1],\n ],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusBlock,\n originalTextBefore,\n allowedInlineObjectRanges,\n })\n\n if (groupLocation) {\n groups[groupName] = groupLocation\n }\n }\n\n const ruleMatch = {\n text: matchLocation.text,\n selection: matchLocation.selection,\n targetOffsets: matchLocation.targetOffsets,\n groups,\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 focusBlock,\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 focusBlock,\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 const groupLocations = Object.values(match.groups).filter(\n (location) => location !== undefined,\n )\n\n return groupLocations.length === 0 ? [match] : groupLocations\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?.({\n endOffsets: blockOffsets,\n endSelection: snapshot.context.selection,\n })\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule<any>>\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 * @public\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 endSelection: EditorSelection\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n selection: EditorSelection\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: defineInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets, endSelection}) => {\n sendBack({type: 'input rule raised', endOffsets, endSelection})\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 // Listen for the emitted 'selection' event which fires after ANY cursor\n // movement (typing, clicking, pasting, etc.) - not just explicit 'select'\n // behavior events.\n const subscription = input.editor.on('selection', (event) => {\n const blockOffsets = getBlockOffsets({\n ...input.editor.getSnapshot(),\n context: {\n ...input.editor.getSnapshot().context,\n selection: event.selection,\n },\n })\n\n sendBack({\n type: 'selection changed',\n blockOffsets,\n selection: event.selection,\n })\n })\n\n return () => subscription.unsubscribe()\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 endSelection: EditorSelection\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 'selection changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n // When block offsets are available for both the end state and the\n // current selection, compare them. Block offsets normalize away\n // span-level differences (e.g. cursor at the same position but in a\n // different span after normalization).\n if (event.blockOffsets && context.endOffsets) {\n const contextStartBlock = context.endOffsets.start.path.at(-1)\n const eventStartBlock = event.blockOffsets.start.path.at(-1)\n const contextEndBlock = context.endOffsets.end.path.at(-1)\n const eventEndBlock = event.blockOffsets.end.path.at(-1)\n\n if (\n !isKeyedSegment(contextStartBlock) ||\n !isKeyedSegment(eventStartBlock) ||\n !isKeyedSegment(contextEndBlock) ||\n !isKeyedSegment(eventEndBlock)\n ) {\n return false\n }\n\n const startChanged =\n contextStartBlock._key !== eventStartBlock._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n contextEndBlock._key !== eventEndBlock._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n }\n\n // Block offsets can't be computed when the cursor is on an inline\n // object (e.g. after a stock ticker rule inserts one). Fall back to\n // comparing the raw selections.\n return !isEqualSelections(context.endSelection, event.selection)\n },\n },\n})\n\nconst assignEndState = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n endSelection: ({context, event}) =>\n event.type === 'input rule raised'\n ? event.endSelection\n : context.endSelection,\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 endSelection: null,\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: assignEndState,\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: 'selection changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise, type BehaviorAction} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @public\n */\nexport type TextTransform<TGuardResponse = true> = (\n {location}: {location: InputRuleMatchLocation},\n guardResponse: TGuardResponse,\n) => string\n\n/**\n * @public\n */\nexport type TextTransformRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n /**\n * What to replace, and with what.\n *\n * A function replaces the whole match, always, regardless of any capture\n * groups in the pattern. A record replaces only the spans of the named\n * capture groups given as keys, each with its own transform,\n * `/\\d+\\s?(?<operator>[*x])\\s?\\d+/` with\n * `transform: {operator: () => '×'}` turns `2x3` into `2×3` rather than\n * `×`. Use the record form when the pattern needs surrounding context to\n * decide *when* to fire but only part of the match should change; the\n * context must sit inside the match rather than in lookarounds, a rule\n * only fires when its match involves the just-inserted text.\n *\n * Every key must exist as a named capture group in `on`;\n * `defineTextTransformRule` throws otherwise. A match in which none of\n * the keys participated has nothing to replace and is skipped.\n */\n transform:\n | TextTransform<TGuardResponse>\n | Record<string, TextTransform<TGuardResponse>>\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 * @public\n */\nexport function defineTextTransformRule<TGuardResponse = true>(\n config: TextTransformRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n const transformRecord =\n typeof config.transform === 'function' ? undefined : config.transform\n\n if (transformRecord) {\n // The appended `|` adds an empty alternative that matches the empty\n // string, so `exec('')` always produces a match whose `groups` object\n // carries a key for every named capture group in the pattern. `g`/`y`/\n // `d` are dropped (irrelevant for the probe, and sticky would anchor\n // it); the remaining flags are kept because recompiling without them\n // can be a syntax error (`\\u{...}` requires `u`).\n const probeFlags = config.on.flags.replace(/[gyd]/g, '')\n const namedGroups = Object.keys(\n new RegExp(`${config.on.source}|`, probeFlags).exec('')?.groups ?? {},\n )\n\n for (const groupName of Object.keys(transformRecord)) {\n if (!namedGroups.includes(groupName)) {\n throw new Error(\n `defineTextTransformRule: \\`transform\\` targets the group \"${groupName}\", but \\`on\\` (${config.on}) has no such named capture group` +\n (namedGroups.length > 0\n ? `. Named groups: ${namedGroups\n .map((name) => `\"${name}\"`)\n .join(', ')}`\n : `. The pattern has no named capture groups`),\n )\n }\n }\n }\n\n return {\n on: config.on,\n guard: config.guard ?? (() => true as TGuardResponse),\n actions: [\n ({snapshot, event}, guardResponse) => {\n const targets = event.matches\n .flatMap(\n (\n match,\n ): Array<{\n location: InputRuleMatchLocation\n transform: TextTransform<TGuardResponse>\n }> => {\n if (!transformRecord) {\n return [\n {\n location: match,\n transform:\n config.transform as TextTransform<TGuardResponse>,\n },\n ]\n }\n\n // Only participating keyed groups are replaced; a match in\n // which none of them participated is skipped.\n return Object.entries(transformRecord).flatMap(\n ([groupName, groupTransform]) => {\n const location = match.groups[groupName]\n\n return location ? [{location, transform: groupTransform}] : []\n },\n )\n },\n )\n // Right-to-left processing below relies on document order, which\n // the `replace` array's order doesn't guarantee.\n .sort(\n (a, b) =>\n a.location.targetOffsets.anchor.offset -\n b.location.targetOffsets.anchor.offset,\n )\n const newText = event.textBefore + event.textInserted\n\n let textLengthDelta = 0\n const actions: Array<BehaviorAction> = []\n\n for (const {location, transform} of targets.reverse()) {\n const text = transform({location}, guardResponse)\n\n textLengthDelta =\n textLengthDelta -\n (text.length -\n (location.targetOffsets.focus.offset -\n location.targetOffsets.anchor.offset))\n\n actions.push(raise({type: 'select', at: location.targetOffsets}))\n actions.push(raise({type: 'delete', at: location.targetOffsets}))\n actions.push(\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text,\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: location.selection.anchor,\n focus: {\n path: location.selection.focus.path,\n offset: Math.min(\n location.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n )\n }\n\n const endCaretPosition = {\n path: event.focusBlock.path,\n offset: newText.length - textLengthDelta,\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","focusBlock","originalTextBefore","allowedInlineObjectRanges","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","context","inlineObjectsBefore","getPreviousInlineObjects","inlineObjectsInMatch","filter","inlineObjectAfter","some","inlineObjectBefore","node","_key","inlineObject","inlineObjectOffset","childSelectionPointToBlockOffset","selectionPoint","range","defineInputRuleBehavior","defineBehavior","on","guard","event","dom","isSelectionCollapsed","getFocusBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","safeUserFlags","flags","replace","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","inlineObjects","allow","groupName","span","groups","matchLocation","foundMatch","Object","entries","groupLocation","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","groupLocations","values","location","slice","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","endSelection","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","subscription","getSnapshot","unsubscribe","inputRuleSetup","setup","types","events","actors","fromCallback","guards","selection changed","contextStartBlock","eventStartBlock","contextEndBlock","eventEndBlock","isKeyedSegment","startChanged","endChanged","isEqualSelections","assignEndState","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","transformRecord","transform","probeFlags","namedGroups","keys","exec","includes","Error","name","join","guardResponse","targets","groupTransform","sort","a","b","textLengthDelta","reverse","child","_type","schema","marks","getMarkState","endCaretPosition"],"mappings":";;;;;;;AAmFO,SAASA,gBACdC,QAC2B;AAC3B,SAAOA;AACT;AChDO,SAASC,0BAA0B;AAAA,EACxCC;AAAAA,EACAC;AAAAA,EACAC;AAAAA,EACAC;AAAAA,EACAC;AAAAA,EACAC;AAeF,GAAuC;AACrC,QAAM,CAACC,MAAMC,OAAOC,GAAG,IAAIR,OACrBS,gBAAgBF,QAAQN,eAExBS,gBAAgB;AAAA,IACpBC,QAAQ;AAAA,MACNC,MAAMT,WAAWS;AAAAA,MACjBC,QAAQJ;AAAAA,IAAAA;AAAAA,IAEVK,OAAO;AAAA,MACLF,MAAMT,WAAWS;AAAAA,MACjBC,QAAQJ,gBAAgBD,MAAMD;AAAAA,IAAAA;AAAAA,IAEhCQ,UAAU;AAAA,EAAA,GAENC,oBAAoB;AAAA,IACxBL,QAAQ;AAAA,MACNC,MAAMT,WAAWS;AAAAA,MACjBC,QAAQI,KAAKC,IAAIR,cAAcC,OAAOE,QAAQT,mBAAmBe,MAAM;AAAA,IAAA;AAAA,IAEzEL,OAAO;AAAA,MACLF,MAAMT,WAAWS;AAAAA,MACjBC,QAAQI,KAAKC,IAAIR,cAAcI,MAAMD,QAAQT,mBAAmBe,MAAM;AAAA,IAAA;AAAA,EAG1E,GAEMC,kBAAkBC,gCAAgC;AAAA,IACtDnB;AAAAA,IACAoB,aAAaN,kBAAkBL;AAAAA,IAC/BY,WAAW;AAAA,EAAA,CACZ,GACKC,gBAAgBH,gCAAgC;AAAA,IACpDnB;AAAAA,IACAoB,aAAaN,kBAAkBF;AAAAA,IAC/BS,WAAW;AAAA,EAAA,CACZ;AAED,MAAI,CAACH,mBAAmB,CAACI;AACvB;AAGF,QAAMC,YAAY;AAAA,IAChBd,QAAQS;AAAAA,IACRN,OAAOU;AAAAA,EAAAA,GAGHE,0BAA0BC,qBAAqB;AAAA,IACnD,GAAGzB;AAAAA,IACH0B,SAAS;AAAA,MACP,GAAG1B,SAAS0B;AAAAA,MACZH,WAAW;AAAA,QACTd,QAAQc,UAAUd;AAAAA,QAClBG,OAAOW,UAAUd;AAAAA,MAAAA;AAAAA,IACnB;AAAA,EACF,CACD,GACKkB,sBAAsBC,yBAAyB5B,QAAQ,GAEvD6B,uBAAuBL,wBAAwBM,OAClDC,uBACCJ,oBAAoBK,KACjBC,wBACCF,kBAAkBG,KAAKC,SAASF,mBAAmBC,KAAKC,IAC5D,CACJ;AAEA,aAAWC,gBAAgBP,sBAAsB;AAC/C,UAAMQ,qBAAqBC,iCAAiC;AAAA,MAC1DtC;AAAAA,MACAuC,gBAAgB;AAAA,QAAC7B,MAAM0B,aAAa1B;AAAAA,QAAMC,QAAQ;AAAA,MAAA;AAAA,IAAC,CACpD;AAYD,QAVI,CAAC0B,sBAUD,CANYlC,0BAA0B6B,KACvCQ,CAAAA,UACCH,mBAAmB1B,UAAU6B,MAAMnC,QAAQN,iBAC3CsC,mBAAmB1B,UAAU6B,MAAMlC,MAAMP,aAC7C;AAGE;AAAA,EAEJ;AAEA,SAAO;AAAA,IACLK;AAAAA,IACAmB;AAAAA,IACAf;AAAAA,EAAAA;AAEJ;AClHO,SAASiC,wBAAwB7C,QASrC;AACD,SAAO8C,eAAe;AAAA,IACpBC,IAAI;AAAA,IACJC,OAAOA,CAAC;AAAA,MAAC5C;AAAAA,MAAU6C;AAAAA,MAAOC;AAAAA,IAAAA,MAAS;AACjC,UACE,CAAC9C,SAAS0B,QAAQH,aAClB,CAACwB,qBAAqB/C,SAAS0B,QAAQH,SAAS;AAEhD,eAAO;AAGT,YAAMtB,aAAa+C,cAAchD,QAAQ;AAEzC,UAAI,CAACC;AACH,eAAO;AAGT,YAAMC,qBAAqB+C,mBAAmBjD,QAAQ;AACtD,UAAIkD,aAAahD;AACjB,YAAMiD,kBAAkBD,aAAaL,MAAMzC;AAC3C,UAAIgD,UAAUD;AAEd,YAAME,eAA8C,IAC9CC,eAAsC,CAAA;AAE5C,iBAAWC,QAAQ3D,OAAO4D,OAAO;AAU/B,cAAMC,gBAAgBF,KAAKZ,GAAGe,MAAMC,QAAQ,YAAY,EAAE,GACpDC,UAAU,IAAIC,OAAON,KAAKZ,GAAGmB,QAAQ,KAAKL,aAAa,EAAE;AAE/D,mBAAa;AAEX,gBAAMM,cAAc,CAAC,GAAGX,QAAQY,SAASJ,OAAO,CAAC,EAAEK,QAChDC,CAAAA,gBAAgB;AACf,gBAAIA,YAAYC,YAAYC;AAC1B,qBAAO,CAAA;AAGT,kBAAMtE,QAAQoE,YAAYC,QAAQE,GAAG,CAAC;AAEtC,gBAAI,CAACvE;AACH,qBAAO,CAAA;AAMT,kBAAMK,6BACJoD,KAAKe,eAAeC,SAAS,IAC7BN,QAASO,CAAAA,cAAc;AACvB,oBAAMC,OAAOP,YAAYC,SAASO,SAASF,SAAS;AAEpD,qBAAOC,OAAO,CAAC;AAAA,gBAACpE,OAAOoE,KAAK,CAAC;AAAA,gBAAGnE,KAAKmE,KAAK,CAAC;AAAA,cAAA,CAAE,IAAI,CAAA;AAAA,YACnD,CAAC,GAEKE,gBAAgB9E,0BAA0B;AAAA,cAC9CC,OAAO,CAACoE,YAAYG,GAAG,CAAC,KAAK,IAAI,GAAGvE,KAAK;AAAA,cACzCC,eAAeoD,gBAAgBlC,SAASmC,QAAQnC;AAAAA,cAChDjB;AAAAA,cACAC;AAAAA,cACAC;AAAAA,cACAC;AAAAA,YAAAA,CACD;AAED,gBAAI,CAACwE;AACH,qBAAO,CAAA;AAQT,gBAJEA,cAAcnE,cAAcI,MAAMD,UAClCT,mBAAmBe;AAInB,qBAAO,CAAA;AAUT,gBAPqBoC,aAAarB,KAC/B4C,CAAAA,eACCA,WAAWpE,cAAcC,OAAOE,WAChCgE,cAAcnE,cAAcC,OAAOE,MACvC;AAIE,qBAAO,CAAA;AAGT,kBAAM+D,SACJ,CAAA;AAEF,uBAAW,CAACF,WAAWC,IAAI,KAAKI,OAAOC,QACrCZ,YAAYC,QAAQO,UAAU,CAAA,CAChC,GAAG;AACD,kBAAI,CAACD;AACH;AAGF,oBAAMM,gBAAgBlF,0BAA0B;AAAA,gBAC9CC,OAAO,CACLoE,YAAYQ,SAASF,SAAS,KAAK,IACnCC,KAAK,CAAC,GACNA,KAAK,CAAC,CAAC;AAAA,gBAET1E,eAAeoD,gBAAgBlC,SAASmC,QAAQnC;AAAAA,gBAChDjB;AAAAA,gBACAC;AAAAA,gBACAC;AAAAA,gBACAC;AAAAA,cAAAA,CACD;AAEG4E,gCACFL,OAAOF,SAAS,IAAIO;AAAAA,YAExB;AASA,mBAAO,CAPW;AAAA,cAChB3E,MAAMuE,cAAcvE;AAAAA,cACpBmB,WAAWoD,cAAcpD;AAAAA,cACzBf,eAAemE,cAAcnE;AAAAA,cAC7BkE;AAAAA,YAAAA,CAGe;AAAA,UACnB,CACF;AAEA,cAAIX,YAAY9C,SAAS,GAAG;AAC1B,kBAAM+D,cACJzB,KAAKX,QAAQ;AAAA,cACX5C;AAAAA,cACA6C,OAAO;AAAA,gBACLoC,MAAM;AAAA,gBACNC,SAASnB;AAAAA,gBACT9D;AAAAA,gBACAiD,YAAYhD;AAAAA,gBACZiF,cAActC,MAAMzC;AAAAA,cAAAA;AAAAA,cAEtB0C;AAAAA,YAAAA,CACD,KAAK;AAER,gBAAI,CAACkC;AACH;AAGF,kBAAMI,aAAa7B,KAAK8B,QAAQC,IAAKC,YACnCA,OACE;AAAA,cACEvF;AAAAA,cACA6C,OAAO;AAAA,gBACLoC,MAAM;AAAA,gBACNC,SAASnB;AAAAA,gBACT9D;AAAAA,gBACAiD,YAAYhD;AAAAA,gBACZiF,cAActC,MAAMzC;AAAAA,cAAAA;AAAAA,cAEtB0C;AAAAA,YAAAA,GAEFkC,WACF,CACF;AAEA,uBAAWQ,aAAaJ;AACtB,yBAAWG,UAAUC;AACnBlC,6BAAamC,KAAKF,MAAM;AAI5B,kBAAML,UAAUnB,YAAYE,QAASnE,CAAAA,UAAU;AAC7C,oBAAM4F,iBAAiBb,OAAOc,OAAO7F,MAAM4E,MAAM,EAAE5C,OAChD8D,CAAAA,aAAaA,aAAaxB,MAC7B;AAEA,qBAAOsB,eAAezE,WAAW,IAAI,CAACnB,KAAK,IAAI4F;AAAAA,YACjD,CAAC;AAED,uBAAW5F,SAASoF;AAGlB7B,2BAAaoC,KAAK3F,KAAK,GACvBoD,aAAaE,QAAQyC,MACnB,GACA/F,MAAMU,cAAcI,MAAMD,UAAU,CACtC,GACAyC,UAAUD,gBAAgB0C,MACxB/F,MAAMU,cAAcI,MAAMD,UAAU,CACtC;AAAA,UAEJ;AAGE;AAAA,QAEJ;AAAA,MACF;AAEA,aAAI2C,aAAarC,WAAW,IACnB,KAGF;AAAA,QAACoE,SAAS/B;AAAAA,MAAAA;AAAAA,IACnB;AAAA,IACA+B,SAAS,CACP,CAAC;AAAA,MAACxC;AAAAA,IAAAA,MAAW,CAACiD,QAAQjD,KAAK,CAAC,GAC5B,CAACkD,GAAG;AAAA,MAACV;AAAAA,IAAAA,MAAaA,SAClB,CAAC;AAAA,MAACrF;AAAAA,IAAAA,MAAc,CACdgG,OAAO,MAAM;AACX,YAAMC,eAAeC,gBAAgBlG,QAAQ;AAE7CJ,aAAOuG,UAAU;AAAA,QACfC,YAAYH;AAAAA,QACZI,cAAcrG,SAAS0B,QAAQH;AAAAA,MAAAA,CAChC;AAAA,IACH,CAAC,CAAC,CACH;AAAA,EAAA,CAEJ;AACH;AAoBO,SAAA+E,gBAAAC,OAAA;AAAA,QAAAC,IAAAC,EAAA,CAAA,GACLC,SAAeC,UAAAA;AAAW,MAAAC;AAAA,SAAAJ,SAAAE,UAAAF,EAAA,CAAA,MAAAD,MAAA/C,SAEIoD,KAAA;AAAA,IAAAC,OACrB;AAAA,MAAAH;AAAAA,MAAAlD,OAAgB+C,MAAK/C;AAAAA,IAAAA;AAAAA,EAAM,GACnCgD,OAAAE,QAAAF,EAAA,CAAA,IAAAD,MAAA/C,OAAAgD,OAAAI,MAAAA,KAAAJ,EAAA,CAAA,GAFDM,YAAYC,kBAAkBH,EAE7B,GAEM;AAAI;AAgBb,MAAMI,4BAOFA,CAAC;AAAA,EAACH;AAAAA,EAAOI;AAAQ,MAAM;AACzB,QAAMC,aAAaL,MAAMH,OAAOS,iBAAiB;AAAA,IAC/CC,UAAU3E,wBAAwB;AAAA,MAChCe,OAAOqD,MAAMrD;AAAAA,MACb2C,SAASA,CAAC;AAAA,QAACC;AAAAA,QAAYC;AAAAA,MAAAA,MAAkB;AACvCY,iBAAS;AAAA,UAAChC,MAAM;AAAA,UAAqBmB;AAAAA,UAAYC;AAAAA,QAAAA,CAAa;AAAA,MAChE;AAAA,IAAA,CACD;AAAA,EAAA,CACF;AAED,SAAO,MAAM;AACXa,eAAAA;AAAAA,EACF;AACF,GAEMG,iCAIFA,CAAC;AAAA,EAACR;AAAAA,EAAOI;AAAQ,MACZJ,MAAMH,OAAOS,iBAAiB;AAAA,EACnCC,UAAU1E,eAAe;AAAA,IACvBC,IAAI;AAAA,IACJ0C,SAAS,CACP,MAAM,CACJiC,MAAM;AAAA,MAACrC,MAAM;AAAA,IAAA,CAAe,GAC5Be,OAAO,MAAM;AACXiB,eAAS;AAAA,QAAChC,MAAM;AAAA,MAAA,CAAsB;AAAA,IACxC,CAAC,CAAC,CACH;AAAA,EAAA,CAEJ;AACH,CAAC,GAGGsC,4BAIFA,CAAC;AAAA,EAACN;AAAAA,EAAUJ;AAAK,MAAM;AAIzB,QAAMW,eAAeX,MAAMH,OAAO/D,GAAG,aAAcE,CAAAA,UAAU;AAC3D,UAAMoD,eAAeC,gBAAgB;AAAA,MACnC,GAAGW,MAAMH,OAAOe,YAAAA;AAAAA,MAChB/F,SAAS;AAAA,QACP,GAAGmF,MAAMH,OAAOe,YAAAA,EAAc/F;AAAAA,QAC9BH,WAAWsB,MAAMtB;AAAAA,MAAAA;AAAAA,IACnB,CACD;AAED0F,aAAS;AAAA,MACPhC,MAAM;AAAA,MACNgB;AAAAA,MACA1E,WAAWsB,MAAMtB;AAAAA,IAAAA,CAClB;AAAA,EACH,CAAC;AAED,SAAO,MAAMiG,aAAaE,YAAAA;AAC5B,GAEMC,iBAAiBC,MAAM;AAAA,EAC3BC,OAAO;AAAA,IACLnG,SAAS,CAAA;AAAA,IAMTmF,OAAO,CAAA;AAAA,IAIPiB,QAAQ,CAAA;AAAA,EAAC;AAAA,EAEXC,QAAQ;AAAA,IACN,4BAA4BC,aAAaX,8BAA8B;AAAA,IACvE,uBAAuBW,aAAahB,yBAAyB;AAAA,IAC7D,sBAAsBgB,aAAaT,yBAAyB;AAAA,EAAA;AAAA,EAE9DU,QAAQ;AAAA,IACN,qBAAqBC,CAAC;AAAA,MAACxG;AAAAA,MAASmB;AAAAA,IAAAA,MAAW;AACzC,UAAIA,MAAMoC,SAAS;AACjB,eAAO;AAOT,UAAIpC,MAAMoD,gBAAgBvE,QAAQ0E,YAAY;AAC5C,cAAM+B,oBAAoBzG,QAAQ0E,WAAW/F,MAAMK,KAAK2D,GAAG,EAAE,GACvD+D,kBAAkBvF,MAAMoD,aAAa5F,MAAMK,KAAK2D,GAAG,EAAE,GACrDgE,kBAAkB3G,QAAQ0E,WAAW9F,IAAII,KAAK2D,GAAG,EAAE,GACnDiE,gBAAgBzF,MAAMoD,aAAa3F,IAAII,KAAK2D,GAAG,EAAE;AAEvD,YACE,CAACkE,eAAeJ,iBAAiB,KACjC,CAACI,eAAeH,eAAe,KAC/B,CAACG,eAAeF,eAAe,KAC/B,CAACE,eAAeD,aAAa;AAE7B,iBAAO;AAGT,cAAME,eACJL,kBAAkBhG,SAASiG,gBAAgBjG,QAC3CT,QAAQ0E,WAAW/F,MAAMM,WAAWkC,MAAMoD,aAAa5F,MAAMM,QACzD8H,aACJJ,gBAAgBlG,SAASmG,cAAcnG,QACvCT,QAAQ0E,WAAW9F,IAAIK,WAAWkC,MAAMoD,aAAa3F,IAAIK;AAE3D,eAAO6H,gBAAgBC;AAAAA,MACzB;AAKA,aAAO,CAACC,kBAAkBhH,QAAQ2E,cAAcxD,MAAMtB,SAAS;AAAA,IACjE;AAAA,EAAA;AAEJ,CAAC,GAEKoH,iBAAiBhB,eAAeiB,OAAO;AAAA,EAC3CxC,YAAYA,CAAC;AAAA,IAAC1E;AAAAA,IAASmB;AAAAA,EAAAA,MACrBA,MAAMoC,SAAS,sBAAsBpC,MAAMuD,aAAa1E,QAAQ0E;AAAAA,EAClEC,cAAcA,CAAC;AAAA,IAAC3E;AAAAA,IAASmB;AAAAA,EAAAA,MACvBA,MAAMoC,SAAS,sBACXpC,MAAMwD,eACN3E,QAAQ2E;AAChB,CAAC,GAEKU,mBAAmBY,eAAekB,cAAc;AAAA,EACpDC,IAAI;AAAA,EACJpH,SAASA,CAAC;AAAA,IAACmF;AAAAA,EAAAA,OAAY;AAAA,IACrBH,QAAQG,MAAMH;AAAAA,IACdlD,OAAOqD,MAAMrD;AAAAA,IACb4C,YAAYhC;AAAAA,IACZiC,cAAc;AAAA,EAAA;AAAA,EAEhB0C,SAAS;AAAA,EACTC,QAAQ;AAAA,IACNC,KAAK;AAAA,IACLpC,OAAOA,CAAC;AAAA,MAACnF;AAAAA,IAAAA,OAAc;AAAA,MACrBgF,QAAQhF,QAAQgF;AAAAA,MAChBlD,OAAO9B,QAAQ8B;AAAAA,IAAAA;AAAAA,EACjB;AAAA,EAEFb,IAAI;AAAA,IACF,qBAAqB;AAAA,MACnBuG,QAAQ;AAAA,MACR7D,SAASsD;AAAAA,IAAAA;AAAAA,EACX;AAAA,EAEFQ,QAAQ;AAAA,IACN,MAAQ,CAAA;AAAA,IACR,sBAAsB;AAAA,MACpBH,QAAQ,CACN;AAAA,QACEC,KAAK;AAAA,QACLpC,OAAOA,CAAC;AAAA,UAACnF;AAAAA,QAAAA,OAAc;AAAA,UAACgF,QAAQhF,QAAQgF;AAAAA,QAAAA;AAAAA,MAAM,GAEhD;AAAA,QACEuC,KAAK;AAAA,QACLpC,OAAOA,CAAC;AAAA,UAACnF;AAAAA,QAAAA,OAAc;AAAA,UAACgF,QAAQhF,QAAQgF;AAAAA,QAAAA;AAAAA,MAAM,CAC/C;AAAA,MAEH/D,IAAI;AAAA,QACF,qBAAqB;AAAA,UACnBuG,QAAQ;AAAA,UACRtG,OAAO;AAAA,QAAA;AAAA,QAET,uBAAuB;AAAA,UACrBsG,QAAQ;AAAA,QAAA;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEJ,CAAC;AC/bM,SAASE,wBACdxJ,QAC2B;AAC3B,QAAMyJ,kBACJ,OAAOzJ,OAAO0J,aAAc,aAAalF,SAAYxE,OAAO0J;AAE9D,MAAID,iBAAiB;AAOnB,UAAME,aAAa3J,OAAO+C,GAAGe,MAAMC,QAAQ,UAAU,EAAE,GACjD6F,cAAc3E,OAAO4E,KACzB,IAAI5F,OAAO,GAAGjE,OAAO+C,GAAGmB,MAAM,KAAKyF,UAAU,EAAEG,KAAK,EAAE,GAAGhF,UAAU,CAAA,CACrE;AAEA,eAAWF,aAAaK,OAAO4E,KAAKJ,eAAe;AACjD,UAAI,CAACG,YAAYG,SAASnF,SAAS;AACjC,cAAM,IAAIoF,MACR,6DAA6DpF,SAAS,kBAAkB5E,OAAO+C,EAAE,uCAC9F6G,YAAYvI,SAAS,IAClB,mBAAmBuI,YAChBlE,IAAKuE,CAAAA,SAAS,IAAIA,IAAI,GAAG,EACzBC,KAAK,IAAI,CAAC,KACb,4CACR;AAAA,EAGN;AAEA,SAAO;AAAA,IACLnH,IAAI/C,OAAO+C;AAAAA,IACXC,OAAOhD,OAAOgD,UAAU,MAAM;AAAA,IAC9ByC,SAAS,CACP,CAAC;AAAA,MAACrF;AAAAA,MAAU6C;AAAAA,IAAAA,GAAQkH,kBAAkB;AACpC,YAAMC,UAAUnH,MAAMqC,QACnBjB,QAEGnE,WAKKuJ,kBAYExE,OAAOC,QAAQuE,eAAe,EAAEpF,QACrC,CAAC,CAACO,WAAWyF,cAAc,MAAM;AAC/B,cAAMrE,WAAW9F,MAAM4E,OAAOF,SAAS;AAEvC,eAAOoB,WAAW,CAAC;AAAA,UAACA;AAAAA,UAAU0D,WAAWW;AAAAA,QAAAA,CAAe,IAAI,CAAA;AAAA,MAC9D,CACF,IAjBS,CACL;AAAA,QACErE,UAAU9F;AAAAA,QACVwJ,WACE1J,OAAO0J;AAAAA,MAAAA,CACV,CAcT,EAGCY,KACC,CAACC,GAAGC,MACFD,EAAEvE,SAASpF,cAAcC,OAAOE,SAChCyJ,EAAExE,SAASpF,cAAcC,OAAOE,MACpC,GACIyC,UAAUP,MAAMK,aAAaL,MAAMsC;AAEzC,UAAIkF,kBAAkB;AACtB,YAAMhF,UAAiC,CAAA;AAEvC,iBAAW;AAAA,QAACO;AAAAA,QAAU0D;AAAAA,MAAAA,KAAcU,QAAQM,WAAW;AACrD,cAAMlK,OAAOkJ,UAAU;AAAA,UAAC1D;AAAAA,QAAAA,GAAWmE,aAAa;AAEhDM,0BACEA,mBACCjK,KAAKa,UACH2E,SAASpF,cAAcI,MAAMD,SAC5BiF,SAASpF,cAAcC,OAAOE,UAEpC0E,QAAQI,KAAK6B,MAAM;AAAA,UAACrC,MAAM;AAAA,UAAUZ,IAAIuB,SAASpF;AAAAA,QAAAA,CAAc,CAAC,GAChE6E,QAAQI,KAAK6B,MAAM;AAAA,UAACrC,MAAM;AAAA,UAAUZ,IAAIuB,SAASpF;AAAAA,QAAAA,CAAc,CAAC,GAChE6E,QAAQI,KACN6B,MAAM;AAAA,UACJrC,MAAM;AAAA,UACNsF,OAAO;AAAA,YACLC,OAAOxK,SAAS0B,QAAQ+I,OAAOhG,KAAKoF;AAAAA,YACpCzJ;AAAAA,YACAsK,OACEC,aAAa;AAAA,cACX,GAAG3K;AAAAA,cACH0B,SAAS;AAAA,gBACP,GAAG1B,SAAS0B;AAAAA,gBACZH,WAAW;AAAA,kBACTd,QAAQmF,SAASrE,UAAUd;AAAAA,kBAC3BG,OAAO;AAAA,oBACLF,MAAMkF,SAASrE,UAAUX,MAAMF;AAAAA,oBAC/BC,QAAQI,KAAKC,IACX4E,SAASrE,UAAUX,MAAMD,QACzBkC,MAAMK,WAAWjC,MACnB;AAAA,kBAAA;AAAA,gBACF;AAAA,cACF;AAAA,YACF,CACD,GAAGyJ,SAAS,CAAA;AAAA,UAAA;AAAA,QACjB,CACD,CACH;AAAA,MACF;AAEA,YAAME,mBAAmB;AAAA,QACvBlK,MAAMmC,MAAM5C,WAAWS;AAAAA,QACvBC,QAAQyC,QAAQnC,SAASoJ;AAAAA,MAAAA;AAG3B,aAAO,CACL,GAAGhF,SACHiC,MAAM;AAAA,QACJrC,MAAM;AAAA,QACNZ,IAAI;AAAA,UACF5D,QAAQmK;AAAAA,UACRhK,OAAOgK;AAAAA,QAAAA;AAAAA,MACT,CACD,CAAC;AAAA,IAEN,CAAC;AAAA,EAAA;AAGP;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/plugin-input-rule",
3
- "version": "5.0.29",
3
+ "version": "6.0.0",
4
4
  "description": "Easily configure Input Rules in the Portable Text Editor",
5
5
  "keywords": [
6
6
  "input-rule",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@xstate/react": "^6.1.0",
34
- "xstate": "^5.32.2"
34
+ "xstate": "^5.32.4"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@sanity/pkg-utils": "^10.8.2",
@@ -48,12 +48,12 @@
48
48
  "typescript": "6.0.3",
49
49
  "typescript-eslint": "^8.62.0",
50
50
  "vitest": "^4.1.9",
51
- "@portabletext/editor": "^7.10.2",
51
+ "@portabletext/editor": "^7.10.3",
52
52
  "@portabletext/schema": "2.2.2",
53
53
  "racejar": "2.0.9"
54
54
  },
55
55
  "peerDependencies": {
56
- "@portabletext/editor": "^7.10.2",
56
+ "@portabletext/editor": "^7.10.3",
57
57
  "react": "^19.2"
58
58
  },
59
59
  "engines": {
@@ -75,6 +75,7 @@
75
75
  "test:browser:firefox": "vitest run --project \"browser (firefox)\"",
76
76
  "test:browser:firefox:watch": "vitest watch --project \"browser (firefox)\"",
77
77
  "test:browser:webkit": "vitest run --project \"browser (webkit)\"",
78
- "test:browser:webkit:watch": "vitest watch --project \"browser (webkit)\""
78
+ "test:browser:webkit:watch": "vitest watch --project \"browser (webkit)\"",
79
+ "test:unit": "vitest run --project unit"
79
80
  }
80
81
  }