@portabletext/plugin-input-rule 6.0.19 → 7.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/dist/index.d.ts CHANGED
@@ -108,7 +108,7 @@ declare function defineInputRuleBehavior(config: {
108
108
  } | undefined;
109
109
  endSelection: EditorSelection;
110
110
  }) => void;
111
- }): import("@portabletext/editor/behaviors").Behavior<"*" | "insert.text" | "annotation.add" | "annotation.remove" | "block.set" | "block.unset" | "child.set" | "child.unset" | "decorator.add" | "decorator.remove" | "delete" | "history.redo" | "history.undo" | "insert" | "insert.block" | "insert.child" | "move.backward" | "move.forward" | "remove.text" | "select" | "set" | "unset" | "annotation.set" | "annotation.toggle" | "decorator.toggle" | "delete.backward" | "delete.block" | "delete.child" | "delete.forward" | "delete.text" | "deserialize" | "deserialize.data" | "deserialization.success" | "deserialization.failure" | "insert.blocks" | "insert.break" | "insert.inline object" | "insert.soft break" | "insert.span" | "list item.add" | "list item.remove" | "list item.toggle" | "move.block" | "move.block down" | "move.block up" | "select.block" | "select.previous block" | "select.next block" | "serialize" | "serialize.data" | "serialization.success" | "serialization.failure" | "split" | "style.add" | "style.remove" | "style.toggle" | "clipboard.copy" | "clipboard.cut" | "clipboard.paste" | "drag.dragstart" | "drag.drag" | "drag.dragend" | "drag.dragenter" | "drag.dragover" | "drag.dragleave" | "drag.drop" | "input.*" | "keyboard.keydown" | "keyboard.keyup" | "mouse.click" | "delete.*" | "insert.*" | "select.*" | "set.*" | "unset.*" | "deserialize.*" | "serialize.*" | "split.*" | "annotation.*" | "remove.*" | "block.*" | "child.*" | "decorator.*" | "history.*" | "move.*" | "deserialization.*" | "list item.*" | "serialization.*" | "style.*" | "clipboard.*" | "drag.*" | "keyboard.*" | "mouse.*" | `custom.${string}`, true, import("@portabletext/editor/behaviors").BehaviorEvent>;
111
+ }): import("@portabletext/editor/behaviors").Behavior<"*" | "annotation.*" | "annotation.add" | "annotation.remove" | "annotation.set" | "annotation.toggle" | "block.*" | "block.set" | "block.unset" | "child.*" | "child.set" | "child.unset" | "clipboard.*" | "clipboard.copy" | "clipboard.cut" | "clipboard.paste" | "decorator.*" | "decorator.add" | "decorator.remove" | "decorator.toggle" | "delete" | "delete.*" | "delete.backward" | "delete.block" | "delete.child" | "delete.forward" | "delete.text" | "deserialization.*" | "deserialization.failure" | "deserialization.success" | "deserialize" | "deserialize.*" | "deserialize.data" | "drag.*" | "drag.drag" | "drag.dragend" | "drag.dragenter" | "drag.dragleave" | "drag.dragover" | "drag.dragstart" | "drag.drop" | "history.*" | "history.redo" | "history.undo" | "input.*" | "insert" | "insert.*" | "insert.block" | "insert.blocks" | "insert.break" | "insert.child" | "insert.inline object" | "insert.soft break" | "insert.span" | "insert.text" | "keyboard.*" | "keyboard.keydown" | "keyboard.keyup" | "list item.*" | "list item.add" | "list item.remove" | "list item.toggle" | "mouse.*" | "mouse.click" | "move.*" | "move.backward" | "move.block" | "move.block down" | "move.block up" | "move.forward" | "remove.*" | "remove.text" | "select" | "select.*" | "select.block" | "select.next block" | "select.previous block" | "serialization.*" | "serialization.failure" | "serialization.success" | "serialize" | "serialize.*" | "serialize.data" | "set" | "set.*" | "split" | "split.*" | "style.*" | "style.add" | "style.remove" | "style.toggle" | "unset" | "unset.*" | `custom.${string}`, true, import("@portabletext/editor/behaviors").BehaviorEvent>;
112
112
  type InputRulePluginProps = {
113
113
  rules: Array<InputRule<any>>;
114
114
  };
package/dist/index.js CHANGED
@@ -313,7 +313,7 @@ function defineTextTransformRule(config) {
313
313
  let transformRecord = typeof config.transform == "function" ? void 0 : config.transform;
314
314
  if (transformRecord) {
315
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"));
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
317
  }
318
318
  return {
319
319
  on: config.on,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["BlockPath","PortableTextBlock","BehaviorActionSet","BehaviorGuard","InputRuleMatchLocation","InputRuleMatch","groups","Record","InputRuleEvent","type","matches","Array","textBefore","textInserted","focusBlock","path","node","InputRuleGuard","TGuardResponse","InputRule","on","RegExp","inlineObjects","allow","guard","actions","defineInputRule","config","BlockOffset","BlockPath","EditorSelection","EditorSnapshot","getNextInlineObjects","getPreviousInlineObjects","blockOffsetToSpanSelectionPoint","childSelectionPointToBlockOffset","InputRuleMatchLocation","text","selection","NonNullable","targetOffsets","anchor","focus","backward","getInputRuleMatchLocation","match","adjustIndexBy","snapshot","focusBlock","originalTextBefore","allowedInlineObjectRanges","path","Array","start","end","adjustedIndex","offset","normalizedOffsets","Math","min","length","anchorBackwards","blockOffset","direction","focusForwards","undefined","inlineObjectsAfterMatch","context","inlineObjectsBefore","inlineObjectsInMatch","filter","inlineObjectAfter","some","inlineObjectBefore","node","_key","inlineObject","inlineObjectOffset","selectionPoint","allowed","range","useEditor","BlockOffset","Editor","EditorSelection","defineBehavior","effect","forward","raise","BehaviorAction","getBlockOffsets","getBlockTextBefore","getFocusBlock","isEqualSelections","isKeyedSegment","isSelectionCollapsed","useActorRef","fromCallback","setup","AnyEventObject","CallbackLogicFunction","InputRule","getInputRuleMatchLocation","InputRuleMatchLocation","defineInputRuleBehavior","config","rules","Array","onApply","endOffsets","endSelection","start","end","on","guard","snapshot","event","dom","context","selection","focusBlock","originalTextBefore","textBefore","originalNewText","text","newText","foundMatches","foundActions","rule","safeUserFlags","flags","replace","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","match","at","allowedInlineObjectRanges","inlineObjects","allow","groupName","span","groups","matchLocation","adjustIndexBy","length","existsInTextBefore","targetOffsets","focus","offset","alreadyFound","some","foundMatch","anchor","Record","Object","entries","groupLocation","ruleMatch","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","groupLocations","values","filter","location","slice","_","blockOffsets","InputRulePluginProps","InputRulePlugin","props","$","_c","editor","t0","input","inputRuleMachine","InputRuleMachineEvent","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","selectionListenerCallback","subscription","getSnapshot","unsubscribe","inputRuleSetup","types","events","actors","guards","selection changed","contextStartBlock","path","eventStartBlock","contextEndBlock","eventEndBlock","startChanged","_key","endChanged","assignEndState","assign","createMachine","id","initial","invoke","src","target","states","raise","BehaviorAction","getMarkState","InputRule","InputRuleGuard","InputRuleMatchLocation","TextTransform","location","guardResponse","TGuardResponse","TextTransformRule","on","RegExp","guard","transform","Record","defineTextTransformRule","config","transformRecord","undefined","probeFlags","flags","replace","namedGroups","Object","keys","source","exec","groups","groupName","includes","Error","length","map","name","join","actions","snapshot","event","targets","matches","flatMap","match","Array","entries","groupTransform","sort","a","b","targetOffsets","anchor","offset","newText","textBefore","textInserted","textLengthDelta","reverse","text","focus","push","type","at","child","_type","context","schema","span","marks","selection","path","Math","min","endCaretPosition","focusBlock"],"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 focus block's text from its start to the caret, as it was before\n * the insertion\n */\n textBefore: string\n /**\n * The text carried by the triggering `insert.text` event\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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["{\n input: {editor, rules: props.rules},\n }","rules"],"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 focus block's text from its start to the caret, as it was before\n * the insertion\n */\n textBefore: string\n /**\n * The text carried by the triggering `insert.text` event\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\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/plugin-input-rule",
3
- "version": "6.0.19",
3
+ "version": "7.0.0",
4
4
  "description": "Easily configure Input Rules in the Portable Text Editor",
5
5
  "keywords": [
6
6
  "input-rule",
@@ -23,8 +23,6 @@
23
23
  ],
24
24
  "type": "module",
25
25
  "sideEffects": false,
26
- "main": "./dist/index.js",
27
- "module": "./dist/index.js",
28
26
  "types": "./dist/index.d.ts",
29
27
  "exports": {
30
28
  ".": "./dist/index.js",
@@ -35,43 +33,35 @@
35
33
  "xstate": "^5.32.5"
36
34
  },
37
35
  "devDependencies": {
38
- "@rolldown/plugin-babel": "^0.2.3",
39
- "@sanity/pkg-utils": "^12.1.0",
36
+ "@sanity/pkg-utils": "^12.3.0",
40
37
  "@sanity/tsconfig": "^2.1.0",
41
38
  "@types/react": "^19.2.17",
42
- "@vitejs/plugin-react": "^6.0.5",
39
+ "@vitejs/plugin-react": "^6.1.0",
43
40
  "@vitest/browser": "^4.1.10",
44
41
  "@vitest/browser-playwright": "^4.1.10",
45
- "babel-plugin-react-compiler": "^1.0.0",
46
- "eslint": "^9.39.4",
47
- "eslint-formatter-gha": "^1.6.0",
48
- "eslint-plugin-react-hooks": "^7.1.1",
42
+ "oxc-transform-react": "^0.145.0",
49
43
  "react": "^19.2.8",
50
- "typescript": "6.0.3",
51
- "typescript-eslint": "^8.62.0",
44
+ "typescript": "^7.0.2",
52
45
  "vite": "^8.2.0",
53
46
  "vitest": "^4.1.10",
54
- "@portabletext/editor": "^7.12.1",
55
- "@portabletext/schema": "2.2.4",
56
- "@portabletext/test": "^1.0.5",
57
- "racejar": "2.0.11"
47
+ "@portabletext/editor": "^8.0.0",
48
+ "@portabletext/schema": "3.0.0",
49
+ "@portabletext/test": "^2.0.0",
50
+ "racejar": "3.0.0"
58
51
  },
59
52
  "peerDependencies": {
60
- "@portabletext/editor": "^7.12.1",
53
+ "@portabletext/editor": "^8.0.0",
61
54
  "react": "^19.2"
62
55
  },
63
56
  "engines": {
64
- "node": ">=20.19 <22 || >=22.12"
57
+ "node": ">=22.12"
65
58
  },
66
59
  "scripts": {
67
60
  "build": "pkg-utils build --strict --check",
68
- "check:lint": "biome lint .",
69
- "check:react-compiler": "eslint --cache .",
70
61
  "check:types": "tsc",
71
62
  "check:types:watch": "tsc --watch",
72
63
  "clean": "del .turbo && del lib && del node_modules",
73
64
  "dev": "pkg-utils watch",
74
- "lint:fix": "biome lint --write .",
75
65
  "test": "vitest --run",
76
66
  "test:browser": "vitest --run --project browser",
77
67
  "test:browser:chromium": "vitest run --project \"browser (chromium)\"",