docs/solutions/best-practices/autoformat-insert-input-rules-should-resolve-once-and-pass-payload.md
insertTextRules and insertBreakRules only supported a boolean query gate.
That forced richer rules to compute the same match twice: once to decide whether
the rule should run, then again inside format to recover the payload.
getInlineEquationMatch and getBlockEquationTarget
helpers called from both query and format.query as the only gate and stuffing payload recovery into format.Add resolve to insert input rules and pass the resolved value into format.
Keep query working as a simple pre-check for rules that only need a boolean
gate.
export type AutoformatInsertTextRule<TMatch = true> = {
trigger?: readonly string[] | string;
query?: (editor, context) => boolean;
resolve?: (editor, context) => TMatch | undefined;
format: (editor, context, match: TMatch) => void;
};
Then the runtime resolves once and forwards the payload:
const match = resolveInsertTextRuleMatch(rule, currentEditor, context);
if (match === undefined) return false;
rule.format(currentEditor, context, match);
That lets math rules stay local but shorter:
export const autoformatInlineEquation: AutoformatInsertTextRule<{
deleteRange: TRange;
texExpression: string;
}> = {
trigger: '$',
resolve: (editor, { options, text }) => {
if (text !== '$' || options?.at || isEquationInputBlocked(editor)) return;
return getInlineEquationMatch(editor);
},
format: (editor, _context, match) => {
editor.tf.withoutNormalizing(() => {
editor.tf.delete({ at: match.deleteRange });
editor.tf.select(match.deleteRange.anchor);
editor.tf.insertNodes({
children: [{ text: '' }],
texExpression: match.texExpression,
type: editor.getType(KEYS.inlineEquation),
});
});
},
};
The runtime stays generic while richer rules get a real payload lane.
query only.resolve and skip duplicated recomputation.format, prefer a resolve payload over
recomputing it through query.query for cheap boolean gates; use resolve when the match carries
structure.