@portabletext/plugin-input-rule 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var compilerRuntime = require("react/compiler-runtime"), editor = require("@portabletext/editor"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), react = require("@xstate/react"), xstate = require("xstate"), utils = require("@portabletext/editor/utils");
3
+ var compilerRuntime = require("react/compiler-runtime"), editor = require("@portabletext/editor"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), utils = require("@portabletext/editor/utils"), react = require("@xstate/react"), xstate = require("xstate");
4
4
  function defineInputRule(config) {
5
5
  return config;
6
6
  }
@@ -69,6 +69,8 @@ function createInputRuleBehavior(config) {
69
69
  event,
70
70
  dom
71
71
  }) => {
72
+ if (!snapshot.context.selection || !utils.isSelectionCollapsed(snapshot.context.selection))
73
+ return !1;
72
74
  const focusTextBlock = selectors.getFocusTextBlock(snapshot);
73
75
  if (!focusTextBlock)
74
76
  return !1;
@@ -99,7 +101,7 @@ function createInputRuleBehavior(config) {
99
101
  return [];
100
102
  if (foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset))
101
103
  return [];
102
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1) : [];
104
+ const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).filter((indices) => indices !== void 0) : [];
103
105
  return [{
104
106
  text: matchLocation.text,
105
107
  selection: matchLocation.selection,
@@ -333,14 +335,14 @@ function defineTextTransformRule(config) {
333
335
  actions: [({
334
336
  snapshot,
335
337
  event
336
- }) => {
338
+ }, guardResponse) => {
337
339
  const locations = event.matches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches), newText = event.textBefore + event.textInserted;
338
340
  let textLengthDelta = 0;
339
341
  const actions = [];
340
342
  for (const location of locations.reverse()) {
341
343
  const text = config.transform({
342
344
  location
343
- });
345
+ }, guardResponse);
344
346
  textLengthDelta = textLengthDelta - (text.length - (location.targetOffsets.focus.offset - location.targetOffsets.anchor.offset)), actions.push(behaviors.raise({
345
347
  type: "select",
346
348
  at: location.targetOffsets
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/input-rule.ts","../src/input-rule-match-location.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {BlockPath, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {\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 focusTextBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\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: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\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 {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1)\n : []\n\n const ruleMatch = {\n 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 focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\n )\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise, 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 = {\n on: RegExp\n guard?: InputRuleGuard\n transform: ({location}: {location: InputRuleMatchLocation}) => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const 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})\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.focusTextBlock.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","focusTextBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","context","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","createInputRuleBehavior","defineBehavior","on","guard","event","dom","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","foundMatch","groupMatches","slice","index","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","locations","textLengthDelta","location","reverse","transform","child","_type","schema","span","name","marks","getMarkState","endCaretPosition"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/input-rule.ts","../src/input-rule-match-location.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {BlockPath, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard<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 focusTextBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\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: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\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 {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {isSelectionCollapsed} 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\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule<any>>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n if (\n !snapshot.context.selection ||\n !isSelectionCollapsed(snapshot.context.selection)\n ) {\n return false\n }\n\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices\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 focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\n )\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule<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 }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise, 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.focusTextBlock.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","focusTextBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","context","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","createInputRuleBehavior","defineBehavior","on","guard","event","dom","isSelectionCollapsed","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","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","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","endChanged","assignEndOffsets","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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"}
package/dist/index.d.cts CHANGED
@@ -12,7 +12,9 @@ import type {
12
12
  /**
13
13
  * @alpha
14
14
  */
15
- export declare function defineInputRule(config: InputRule): InputRule
15
+ export declare function defineInputRule<TGuardResponse = true>(
16
+ config: InputRule<TGuardResponse>,
17
+ ): InputRule<TGuardResponse>
16
18
 
17
19
  /**
18
20
  * Define an `InputRule` specifically designed to transform matched text into
@@ -28,17 +30,17 @@ export declare function defineInputRule(config: InputRule): InputRule
28
30
  *
29
31
  * @alpha
30
32
  */
31
- export declare function defineTextTransformRule(
32
- config: TextTransformRule,
33
- ): InputRule
33
+ export declare function defineTextTransformRule<TGuardResponse = true>(
34
+ config: TextTransformRule<TGuardResponse>,
35
+ ): InputRule<TGuardResponse>
34
36
 
35
37
  /**
36
38
  * @alpha
37
39
  */
38
- export declare type InputRule = {
40
+ export declare type InputRule<TGuardResponse = true> = {
39
41
  on: RegExp
40
- guard?: InputRuleGuard
41
- actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>
42
+ guard?: InputRuleGuard<TGuardResponse>
43
+ actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>
42
44
  }
43
45
 
44
46
  /**
@@ -70,7 +72,10 @@ export declare type InputRuleEvent = {
70
72
  /**
71
73
  * @alpha
72
74
  */
73
- export declare type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>
75
+ export declare type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<
76
+ InputRuleEvent,
77
+ TGuardResponse
78
+ >
74
79
 
75
80
  /**
76
81
  * Match found in the text after the insertion
@@ -118,16 +123,23 @@ declare type InputRuleMatchLocation = {
118
123
  export declare function InputRulePlugin(props: InputRulePluginProps): null
119
124
 
120
125
  declare type InputRulePluginProps = {
121
- rules: Array<InputRule>
126
+ rules: Array<InputRule<any>>
122
127
  }
123
128
 
124
129
  /**
125
130
  * @alpha
126
131
  */
127
- export declare type TextTransformRule = {
132
+ export declare type TextTransformRule<TGuardResponse = true> = {
128
133
  on: RegExp
129
- guard?: InputRuleGuard
130
- transform: ({location}: {location: InputRuleMatchLocation}) => string
134
+ guard?: InputRuleGuard<TGuardResponse>
135
+ transform: (
136
+ {
137
+ location,
138
+ }: {
139
+ location: InputRuleMatchLocation
140
+ },
141
+ guardResponse: TGuardResponse,
142
+ ) => string
131
143
  }
132
144
 
133
145
  export {}
package/dist/index.d.ts CHANGED
@@ -12,7 +12,9 @@ import type {
12
12
  /**
13
13
  * @alpha
14
14
  */
15
- export declare function defineInputRule(config: InputRule): InputRule
15
+ export declare function defineInputRule<TGuardResponse = true>(
16
+ config: InputRule<TGuardResponse>,
17
+ ): InputRule<TGuardResponse>
16
18
 
17
19
  /**
18
20
  * Define an `InputRule` specifically designed to transform matched text into
@@ -28,17 +30,17 @@ export declare function defineInputRule(config: InputRule): InputRule
28
30
  *
29
31
  * @alpha
30
32
  */
31
- export declare function defineTextTransformRule(
32
- config: TextTransformRule,
33
- ): InputRule
33
+ export declare function defineTextTransformRule<TGuardResponse = true>(
34
+ config: TextTransformRule<TGuardResponse>,
35
+ ): InputRule<TGuardResponse>
34
36
 
35
37
  /**
36
38
  * @alpha
37
39
  */
38
- export declare type InputRule = {
40
+ export declare type InputRule<TGuardResponse = true> = {
39
41
  on: RegExp
40
- guard?: InputRuleGuard
41
- actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>
42
+ guard?: InputRuleGuard<TGuardResponse>
43
+ actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>
42
44
  }
43
45
 
44
46
  /**
@@ -70,7 +72,10 @@ export declare type InputRuleEvent = {
70
72
  /**
71
73
  * @alpha
72
74
  */
73
- export declare type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>
75
+ export declare type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<
76
+ InputRuleEvent,
77
+ TGuardResponse
78
+ >
74
79
 
75
80
  /**
76
81
  * Match found in the text after the insertion
@@ -118,16 +123,23 @@ declare type InputRuleMatchLocation = {
118
123
  export declare function InputRulePlugin(props: InputRulePluginProps): null
119
124
 
120
125
  declare type InputRulePluginProps = {
121
- rules: Array<InputRule>
126
+ rules: Array<InputRule<any>>
122
127
  }
123
128
 
124
129
  /**
125
130
  * @alpha
126
131
  */
127
- export declare type TextTransformRule = {
132
+ export declare type TextTransformRule<TGuardResponse = true> = {
128
133
  on: RegExp
129
- guard?: InputRuleGuard
130
- transform: ({location}: {location: InputRuleMatchLocation}) => string
134
+ guard?: InputRuleGuard<TGuardResponse>
135
+ transform: (
136
+ {
137
+ location,
138
+ }: {
139
+ location: InputRuleMatchLocation
140
+ },
141
+ guardResponse: TGuardResponse,
142
+ ) => string
131
143
  }
132
144
 
133
145
  export {}
package/dist/index.js CHANGED
@@ -2,9 +2,9 @@ import { c } from "react/compiler-runtime";
2
2
  import { useEditor } from "@portabletext/editor";
3
3
  import { defineBehavior, effect, forward, raise } from "@portabletext/editor/behaviors";
4
4
  import { getNextInlineObjects, getPreviousInlineObjects, getBlockOffsets, getFocusTextBlock, getBlockTextBefore, getMarkState } from "@portabletext/editor/selectors";
5
+ import { blockOffsetToSpanSelectionPoint, isSelectionCollapsed } from "@portabletext/editor/utils";
5
6
  import { useActorRef } from "@xstate/react";
6
7
  import { setup, fromCallback } from "xstate";
7
- import { blockOffsetToSpanSelectionPoint } from "@portabletext/editor/utils";
8
8
  function defineInputRule(config) {
9
9
  return config;
10
10
  }
@@ -73,6 +73,8 @@ function createInputRuleBehavior(config) {
73
73
  event,
74
74
  dom
75
75
  }) => {
76
+ if (!snapshot.context.selection || !isSelectionCollapsed(snapshot.context.selection))
77
+ return !1;
76
78
  const focusTextBlock = getFocusTextBlock(snapshot);
77
79
  if (!focusTextBlock)
78
80
  return !1;
@@ -103,7 +105,7 @@ function createInputRuleBehavior(config) {
103
105
  return [];
104
106
  if (foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === matchLocation.targetOffsets.anchor.offset))
105
107
  return [];
106
- const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1) : [];
108
+ const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).filter((indices) => indices !== void 0) : [];
107
109
  return [{
108
110
  text: matchLocation.text,
109
111
  selection: matchLocation.selection,
@@ -337,14 +339,14 @@ function defineTextTransformRule(config) {
337
339
  actions: [({
338
340
  snapshot,
339
341
  event
340
- }) => {
342
+ }, guardResponse) => {
341
343
  const locations = event.matches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches), newText = event.textBefore + event.textInserted;
342
344
  let textLengthDelta = 0;
343
345
  const actions = [];
344
346
  for (const location of locations.reverse()) {
345
347
  const text = config.transform({
346
348
  location
347
- });
349
+ }, guardResponse);
348
350
  textLengthDelta = textLengthDelta - (text.length - (location.targetOffsets.focus.offset - location.targetOffsets.anchor.offset)), actions.push(raise({
349
351
  type: "select",
350
352
  at: location.targetOffsets
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, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {\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 focusTextBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\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: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\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 {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1)\n : []\n\n const ruleMatch = {\n 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 focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\n )\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise, 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 = {\n on: RegExp\n guard?: InputRuleGuard\n transform: ({location}: {location: InputRuleMatchLocation}) => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const 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})\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.focusTextBlock.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","focusTextBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","context","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","createInputRuleBehavior","defineBehavior","on","guard","event","dom","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","foundMatch","groupMatches","slice","index","guardResult","type","matches","textInserted","actionSets","actions","map","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","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, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard<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 focusTextBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\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: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\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 {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {isSelectionCollapsed} 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\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule<any>>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n if (\n !snapshot.context.selection ||\n !isSelectionCollapsed(snapshot.context.selection)\n ) {\n return false\n }\n\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices\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 focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\n )\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule<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 }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise, 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.focusTextBlock.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","focusTextBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","context","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","createInputRuleBehavior","defineBehavior","on","guard","event","dom","isSelectionCollapsed","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","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","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","endChanged","assignEndOffsets","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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/plugin-input-rule",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Easily configure input rules in the Portable Text Editor",
5
5
  "keywords": [
6
6
  "portabletext",
@@ -53,12 +53,12 @@
53
53
  "typescript": "5.9.3",
54
54
  "typescript-eslint": "^8.41.0",
55
55
  "vitest": "^3.2.4",
56
- "@portabletext/editor": "2.14.0",
56
+ "@portabletext/editor": "2.14.1",
57
57
  "@portabletext/schema": "1.2.0",
58
58
  "racejar": "1.3.1"
59
59
  },
60
60
  "peerDependencies": {
61
- "@portabletext/editor": "^2.14.0",
61
+ "@portabletext/editor": "^2.14.1",
62
62
  "react": "^19.1.1"
63
63
  },
64
64
  "publishConfig": {
@@ -156,3 +156,40 @@ Feature: Edge Cases
156
156
  | ",{stock-ticker},foo" | after "foo" | "{Shift}" | ",{stock-ticker},foo## new" |
157
157
  | ",{stock-ticker},f" | after "f" | "{Backspace}" | ",{stock-ticker},## new" |
158
158
  | "f,{stock-ticker}," | after "f" | "{Backspace}" | "new,{stock-ticker}," |
159
+
160
+ Scenario Outline: Unmatched Groups Rule
161
+ Given the text <text>
162
+ When the caret is put <position>
163
+ And <inserted text> is inserted
164
+ And "new" is typed
165
+ Then the text is <new text>
166
+
167
+ Examples:
168
+ | text | position | inserted text | new text |
169
+ | "" | after "" | "---" | "<hr />new" |
170
+
171
+ Scenario Outline: Expanded selection
172
+ Given the text <text>
173
+ When <selection> is selected
174
+ And <inserted text> is inserted
175
+ And "new" is typed
176
+ Then the text is <new text>
177
+
178
+ Examples:
179
+ | text | selection | inserted text | new text |
180
+ | "(foo" | "foo" | "c)" | "©new" |
181
+ | "(foo" | "oo" | "c)" | "(fc)new" |
182
+ | "(foo" | "(foo" | "c)" | "c)new" |
183
+ | "(coo\|bar" | "ooba" | ")" | "©newr" |
184
+
185
+ Scenario Outline: Undo after transform on expanded selection
186
+ Given the text <text>
187
+ When <selection> is selected
188
+ And <inserted text> is inserted
189
+ Then the text is <before undo>
190
+ When undo is performed
191
+ Then the text is <after undo>
192
+
193
+ Examples:
194
+ | text | selection | inserted text | before undo | after undo |
195
+ | "(cf" | "f" | ")" | "©" | "(c)" |
@@ -52,6 +52,11 @@ const betterH2Rule = defineTextTransformRule({
52
52
  transform: () => '',
53
53
  })
54
54
 
55
+ const unmatchedGroupsRule = defineTextTransformRule({
56
+ on: /^(---)|^(—-)|^(___)|^(\*\*\*)/,
57
+ transform: () => '<hr />',
58
+ })
59
+
55
60
  Feature({
56
61
  hooks: [
57
62
  Before(async (context: Context) => {
@@ -65,6 +70,7 @@ Feature({
65
70
  <InputRulePlugin rules={[h1Rule]} />
66
71
  <InputRulePlugin rules={[betterH2Rule]} />
67
72
  <InputRulePlugin rules={[replaceAandCRule]} />
73
+ <InputRulePlugin rules={[unmatchedGroupsRule]} />
68
74
  </>
69
75
  ),
70
76
  schemaDefinition: defineSchema({
package/src/input-rule.ts CHANGED
@@ -42,20 +42,25 @@ export type InputRuleEvent = {
42
42
  /**
43
43
  * @alpha
44
44
  */
45
- export type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>
45
+ export type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<
46
+ InputRuleEvent,
47
+ TGuardResponse
48
+ >
46
49
 
47
50
  /**
48
51
  * @alpha
49
52
  */
50
- export type InputRule = {
53
+ export type InputRule<TGuardResponse = true> = {
51
54
  on: RegExp
52
- guard?: InputRuleGuard
53
- actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>
55
+ guard?: InputRuleGuard<TGuardResponse>
56
+ actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>
54
57
  }
55
58
 
56
59
  /**
57
60
  * @alpha
58
61
  */
59
- export function defineInputRule(config: InputRule): InputRule {
62
+ export function defineInputRule<TGuardResponse = true>(
63
+ config: InputRule<TGuardResponse>,
64
+ ): InputRule<TGuardResponse> {
60
65
  return config
61
66
  }
@@ -11,6 +11,7 @@ import {
11
11
  getBlockTextBefore,
12
12
  getFocusTextBlock,
13
13
  } from '@portabletext/editor/selectors'
14
+ import {isSelectionCollapsed} from '@portabletext/editor/utils'
14
15
  import {useActorRef} from '@xstate/react'
15
16
  import {
16
17
  fromCallback,
@@ -22,7 +23,7 @@ import type {InputRule, InputRuleMatch} from './input-rule'
22
23
  import {getInputRuleMatchLocation} from './input-rule-match-location'
23
24
 
24
25
  function createInputRuleBehavior(config: {
25
- rules: Array<InputRule>
26
+ rules: Array<InputRule<any>>
26
27
  onApply: ({
27
28
  endOffsets,
28
29
  }: {
@@ -32,6 +33,13 @@ function createInputRuleBehavior(config: {
32
33
  return defineBehavior({
33
34
  on: 'insert.text',
34
35
  guard: ({snapshot, event, dom}) => {
36
+ if (
37
+ !snapshot.context.selection ||
38
+ !isSelectionCollapsed(snapshot.context.selection)
39
+ ) {
40
+ return false
41
+ }
42
+
35
43
  const focusTextBlock = getFocusTextBlock(snapshot)
36
44
 
37
45
  if (!focusTextBlock) {
@@ -97,7 +105,9 @@ function createInputRuleBehavior(config: {
97
105
 
98
106
  const groupMatches =
99
107
  regExpMatch.indices.length > 1
100
- ? regExpMatch.indices.slice(1)
108
+ ? regExpMatch.indices
109
+ .slice(1)
110
+ .filter((indices) => indices !== undefined)
101
111
  : []
102
112
 
103
113
  const ruleMatch = {
@@ -212,7 +222,7 @@ function createInputRuleBehavior(config: {
212
222
  }
213
223
 
214
224
  type InputRulePluginProps = {
215
- rules: Array<InputRule>
225
+ rules: Array<InputRule<any>>
216
226
  }
217
227
 
218
228
  /**
@@ -6,10 +6,13 @@ import type {InputRuleMatchLocation} from './input-rule-match-location'
6
6
  /**
7
7
  * @alpha
8
8
  */
9
- export type TextTransformRule = {
9
+ export type TextTransformRule<TGuardResponse = true> = {
10
10
  on: RegExp
11
- guard?: InputRuleGuard
12
- transform: ({location}: {location: InputRuleMatchLocation}) => string
11
+ guard?: InputRuleGuard<TGuardResponse>
12
+ transform: (
13
+ {location}: {location: InputRuleMatchLocation},
14
+ guardResponse: TGuardResponse,
15
+ ) => string
13
16
  }
14
17
 
15
18
  /**
@@ -26,12 +29,14 @@ export type TextTransformRule = {
26
29
  *
27
30
  * @alpha
28
31
  */
29
- export function defineTextTransformRule(config: TextTransformRule): InputRule {
32
+ export function defineTextTransformRule<TGuardResponse = true>(
33
+ config: TextTransformRule<TGuardResponse>,
34
+ ): InputRule<TGuardResponse> {
30
35
  return {
31
36
  on: config.on,
32
- guard: config.guard ?? (() => true),
37
+ guard: config.guard ?? (() => true as TGuardResponse),
33
38
  actions: [
34
- ({snapshot, event}) => {
39
+ ({snapshot, event}, guardResponse) => {
35
40
  const locations = event.matches.flatMap((match) =>
36
41
  match.groupMatches.length === 0 ? [match] : match.groupMatches,
37
42
  )
@@ -41,7 +46,7 @@ export function defineTextTransformRule(config: TextTransformRule): InputRule {
41
46
  const actions: Array<BehaviorAction> = []
42
47
 
43
48
  for (const location of locations.reverse()) {
44
- const text = config.transform({location})
49
+ const text = config.transform({location}, guardResponse)
45
50
 
46
51
  textLengthDelta =
47
52
  textLengthDelta -