docs/solutions/best-practices/dynamic-input-rule-families-should-branch-inside-createRuleFactory.md
Some package-owned rule families need one semantic public entrypoint, but they do not map to one internal rule lane.
Examples:
MathRules.markdown(...) needs inline $...$ entry on insertText and
block $$ entry on blockFence.LinkRules.autolink(...) needs insertText, insertBreak, and
insertData variants under one semantic family.The bad fix is to keep the public family but hand-write a wrapper that chooses
between multiple internal createRuleFactory(...) calls.
markdown: (options: MathMarkdownRuleOptions) =>
options.variant === '$$'
? createRuleFactory(...)(options)
: createRuleFactory(...)(options)
enabled and
priority.The runtime still worked. The public typing rotted.
Let createRuleFactory(...) accept a dynamic config callback so the package
export still comes directly from the factory:
export const MathRules = {
markdown: createRuleFactory<
{ variant: '$' } | { on: 'break' | 'match'; variant: '$$' }
>((options) =>
options.variant === '$$'
? {
type: 'blockFence',
fence: '$$',
block: KEYS.p,
on: options.on,
apply: ({ editor }, match) => {
const blockMatch = match as BlockFenceInputRuleMatch;
// feature semantics
return true;
},
}
: {
type: 'insertText',
trigger: '$',
apply: ({ editor }, match) => {
const inlineMatch = match as InlineMathMatch;
// feature semantics
return true;
},
}
),
};
Same idea for link autolink:
export const LinkRules = {
autolink: createRuleFactory<{ variant: 'break' | 'paste' | 'space' }>(
(options) =>
options.variant === 'break'
? { type: 'insertBreak', ... }
: options.variant === 'paste'
? { type: 'insertData', ... }
: { type: 'insertText', ... }
),
};
The export is still factory-owned. The branching moved inside the factory callback, where it belongs.
enabled and priority come from the factory
contract automatically.The only compromise is local narrowing inside apply(...) for branch-specific
match payloads. That is still better than a wrapper export because the public
contract stays factory-owned.
createRuleFactory(...).options.apply(...) when the
runtime shape is known, instead of reintroducing a fake wrapper API.