mother-mask 3.44.0 → 3.46.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.
@@ -1 +1 @@
1
- {"version":3,"file":"mother-mask.cjs","names":[],"sources":["../src/chars.ts","../src/pattern.ts","../src/apply-mask.ts","../src/platform.ts","../src/bind-shared.ts","../src/bind.ts","../src/decimal-mask.ts","../src/bind-decimal.ts","../src/mask.ts"],"sourcesContent":["/** Shared by the mask-pattern engine and the decimal engine, which both need to spot ASCII digits. */\nexport function isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n","import type { ApplyMaskOptions, MaskPattern, MaskResult, MaskTokens, TokenMatcher } from './types'\nimport { isDigitChar } from './chars'\n\n/** A slot consumes one code point. Source/caret offsets remain UTF-16 DOM offsets. */\nexport interface Slot {\n match: (char: string) => boolean\n transform?: (char: string) => string\n maxLength: number\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction isDataChar(ch: string): boolean {\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\nconst builtins: ReadonlyArray<readonly [string, Slot]> = [\n ['9', { match: isDigitChar, maxLength: 1 }],\n ['Z', { match: isLetterChar, maxLength: 1 }],\n ['A', { match: isDataChar, maxLength: 1 }],\n]\n\nfunction matcher(match: TokenMatcher): (char: string) => boolean {\n if (typeof match === 'function') return match\n // Private, stateless copy. Never mutate a caller's RegExp (even a frozen one).\n const regex = new RegExp(match.source, match.flags.replace(/[gy]/g, ''))\n return (char) => regex.test(char)\n}\n\n/**\n * One representative code point per script whose IME holds a provisional\n * draft that reads nothing like what it commits — Pinyin/Zhuyin or Cangjie\n * romanizations resolving to a Han character, Kana toggling into Kanji,\n * jamo assembling into a Hangul syllable. `bind()` must leave composition\n * alone there, or a live reformat mid-draft overwrites text the IME still\n * expects to revise (see `android-fast-typing.test.ts`'s \"nihao\" → \"你\" case).\n *\n * A custom token whose alphabet provably never matches any of these can't\n * ever receive that kind of draft: whatever such an IME assembles, this\n * mask's own data check would filter it out exactly the same way once\n * composition ends, so reformatting one instant early changes nothing.\n * Plain ASCII/Latin alphabets (e.g. an uppercase-transforming alphanumeric\n * token) fall in this safe case — see `hasComposingRisk` below.\n */\nconst COMPOSING_SCRIPT_PROBES = ['中', 'あ', 'ア', '가']\n\n/** Whether a token's alphabet could ever accept one of {@link COMPOSING_SCRIPT_PROBES}. */\nfunction mayAcceptComposedScript(match: TokenMatcher): boolean {\n const test = matcher(match)\n return COMPOSING_SCRIPT_PROBES.some((ch) => {\n try {\n return test(ch)\n } catch {\n // A predicate that throws on ordinary input isn't provably safe either way.\n return true\n }\n })\n}\n\n/**\n * Representative non-BMP code points spanning the general categories a\n * class-based alphabet (`\\p{L}`, `\\p{N}`, `\\p{Emoji}`, a bespoke character\n * class, ...) is typically built from — one letter, one number, one symbol.\n * Each needs a UTF-16 surrogate pair (two code units) to encode, unlike\n * every BMP character (Latin, digits, and even the composing-risk scripts\n * above), which fits in one.\n *\n * A token whose alphabet accepts none of these can't produce a two-unit slot\n * either: it can't match one directly, and per `MaskTokenDefinition.transform`'s\n * documented match-preserving contract, a conforming transform can't turn a\n * one-unit input into a two-unit output that would no longer match. Plain\n * ASCII/BMP alphabets (e.g. an uppercase-transforming hex or alphanumeric\n * token) fall in this safe case — see {@link Slot.maxLength} sizing below.\n */\nconst ASTRAL_PROBES = ['𐐀', '𝟎', '😀']\n\n/** Whether a token's alphabet could ever accept one of {@link ASTRAL_PROBES}. */\nfunction mayAcceptAstral(match: TokenMatcher): boolean {\n const test = matcher(match)\n return ASTRAL_PROBES.some((ch) => {\n try {\n return test(ch)\n } catch {\n // A predicate that throws on ordinary input isn't provably safe either way.\n return true\n }\n })\n}\n\nexport function transformChar(char: string, slot: Slot): string {\n if (!slot.transform) return char\n const output = slot.transform(char)\n if (typeof output !== 'string' || Array.from(output).length !== 1) {\n throw new RangeError('A mask token transform must return exactly one Unicode code point')\n }\n return output\n}\n\nexport interface LiteralToken {\n kind: 'literal'\n text: string\n}\n\n/**\n * Upper bound on a bounded quantifier's repeat count.\n *\n * A run is expanded to `max` real slots at compile time, so an unbounded\n * number here would let a one-line pattern allocate arbitrarily much. No\n * realistic field needs more, and anything larger is treated as malformed\n * (the braces stay literal text) rather than throwing, which keeps the\n * conservative \"unknown brace sequences are literals\" rule intact.\n */\nconst MAX_QUANTIFIER = 1000\n\n/** A parsed `{n}` / `{min,max}` suffix; `end` indexes its closing brace. */\ninterface Quantifier {\n min: number\n max: number\n end: number\n}\n\n/**\n * Read a bounded quantifier whose `{` sits at `points[start]`.\n *\n * Only `{n}` and `{min,max}` with `1 <= min <= max <= MAX_QUANTIFIER` are\n * syntax; `{n,}`, `{,n}`, `{0}`, `{2,1}`, `{}` and anything non-numeric are\n * *not*, and return `undefined` so the caller leaves the braces as ordinary\n * literal characters — exactly how a mask containing them behaved before\n * quantifiers existed. No `*`, `+` or `?` forms are recognized at all.\n */\nfunction parseQuantifier(points: string[], start: number): Quantifier | undefined {\n if (points[start] !== '{') return undefined\n let i = start + 1\n // -1 means \"no digits here\" or \"over the cap\"; both are malformed.\n const readCount = (): number => {\n let n = -1\n while (i < points.length && points[i] >= '0' && points[i] <= '9') {\n n = (n < 0 ? 0 : n) * 10 + (points[i].charCodeAt(0) - 48)\n i++\n if (n > MAX_QUANTIFIER) return -1\n }\n return n\n }\n const min = readCount()\n if (min < 1) return undefined\n let max = min\n if (points[i] === ',') {\n i++\n max = readCount()\n if (max < min) return undefined\n }\n if (points[i] !== '}') return undefined\n return { min, max, end: i }\n}\n\nexport type MaskToken = LiteralToken | { kind: 'slots'; chars: Slot[] }\n\n/**\n * A mask string pre-chewed into the lookups both passes need.\n *\n * \"Run\" throughout means one uninterrupted stretch of slot characters — the\n * segment a user thinks of as a single field (\"999\", \"9999\", …). Runs are\n * numbered in mask order; token indices index {@link CompiledMask.tokens}.\n */\nexport interface CompiledMask {\n maxLength: number\n parts: Array<Slot | LiteralToken>\n dataSlots: Slot[]\n literals: Array<{ text: string; offset: number }>\n hasEscapes: boolean\n /** Alternating literal / slot-run tokens, in mask order. */\n tokens: MaskToken[]\n /** Slot characters of each run (e.g. `[\"999\", \"999\", \"999\", \"99\"]`). */\n runChars: Slot[][]\n /**\n * Fewest slots each run accepts before the literal after it may close it.\n *\n * `runChars[i].length` is the *maximum*; this is the minimum a bounded\n * quantifier declared (`9{1,2}` → `1`). A run with no quantifier — every\n * run in every pattern written before this syntax existed — has\n * `runMin[i] === runChars[i].length`, so \"at minimum but short of maximum\"\n * is vacuously impossible for it and nothing about fixed masks changes.\n */\n runMin: number[]\n /** Index into a flat, run-concatenated slot array where each run starts. */\n runOffset: number[]\n /** Token index of the literal directly before run `i`, or `-1` at the mask start. */\n literalBeforeRun: number[]\n /** Total slot capacity of runs `i..end`; `capacityFromRun[runCount]` is `0`. */\n capacityFromRun: number[]\n /** For each token, the run it *is* (`-1` for literals). */\n runOfToken: number[]\n /** For each literal token, the run directly before it (`-1` when it opens the mask). */\n runBeforeLiteral: number[]\n /** For each literal token, the run directly after it (`-1` when it closes the mask). */\n runAfterLiteral: number[]\n /** Total number of slots in the mask. */\n totalSlots: number\n}\n\n/**\n * Split a mask into alternating literal and slot-run tokens (e.g. \"99/99/9999\"\n * → slots\"99\", literal\"/\", slots\"99\", literal\"/\", slots\"9999\") and derive the\n * run/literal adjacency both passes rely on.\n */\nfunction compileMask(mask: string, definitions: Map<string, Slot>): CompiledMask {\n const tokens: MaskToken[] = []\n const runOfToken: number[] = []\n const runChars: Slot[][] = []\n const runMin: number[] = []\n const runToken: number[] = []\n\n const points = Array.from(mask)\n let maxLength = 0\n let hasEscapes = false\n for (let i = 0; i < points.length; i++) {\n let ch = points[i]\n let escaped = false\n if (ch === '\\\\' && (points[i + 1] === '\\\\' || definitions.has(points[i + 1]))) {\n ch = points[++i]\n escaped = true\n hasEscapes = true\n }\n const slot = escaped ? undefined : definitions.get(ch)\n const previous = tokens[tokens.length - 1]\n if (slot) {\n // A quantifier is only syntax directly after an unescaped token, so an\n // escaped `\\9{1,2}` keeps both the \"9\" and the braces as literal text.\n const quantifier = parseQuantifier(points, i + 1)\n const min = quantifier ? quantifier.min : 1\n const max = quantifier ? quantifier.max : 1\n if (quantifier) i = quantifier.end\n maxLength += slot.maxLength * max\n if (previous?.kind === 'slots') {\n for (let n = 0; n < max; n++) previous.chars.push(slot)\n runMin[runMin.length - 1] += min\n } else {\n const chars: Slot[] = []\n for (let n = 0; n < max; n++) chars.push(slot)\n runOfToken.push(runChars.length)\n runToken.push(tokens.length)\n runChars.push(chars)\n runMin.push(min)\n tokens.push({ kind: 'slots', chars })\n }\n } else {\n maxLength += ch.length\n if (previous?.kind === 'literal') previous.text += ch\n else {\n runOfToken.push(-1)\n tokens.push({ kind: 'literal', text: ch })\n }\n }\n }\n\n const runCount = runChars.length\n const runOffset: number[] = new Array(runCount)\n const literalBeforeRun: number[] = new Array(runCount)\n const capacityFromRun: number[] = new Array(runCount + 1)\n const runBeforeLiteral: number[] = new Array(tokens.length).fill(-1)\n const runAfterLiteral: number[] = new Array(tokens.length).fill(-1)\n\n let offset = 0\n for (let r = 0; r < runCount; r++) {\n runOffset[r] = offset\n offset += runChars[r].length\n // Tokens alternate, so the token just before a run is always a literal\n // when it exists at all.\n literalBeforeRun[r] = runToken[r] > 0 ? runToken[r] - 1 : -1\n }\n\n capacityFromRun[runCount] = 0\n for (let r = runCount - 1; r >= 0; r--) {\n capacityFromRun[r] = capacityFromRun[r + 1] + runChars[r].length\n }\n\n for (let t = 0; t < tokens.length; t++) {\n if (tokens[t].kind !== 'literal') continue\n runBeforeLiteral[t] = t > 0 ? runOfToken[t - 1] : -1\n runAfterLiteral[t] = t + 1 < tokens.length ? runOfToken[t + 1] : -1\n }\n\n const parts: CompiledMask['parts'] = []\n const literals: CompiledMask['literals'] = []\n const dataSlots = new Set<Slot>()\n for (let t = 0; t < tokens.length; t++) {\n const token = tokens[t]\n if (token.kind === 'literal') {\n parts.push(token)\n literals.push({ text: token.text, offset: runAfterLiteral[t] < 0 ? offset : runOffset[runAfterLiteral[t]] })\n } else {\n for (const slot of token.chars) {\n parts.push(slot)\n dataSlots.add(slot)\n }\n }\n }\n\n const compiled: CompiledMask = {\n maxLength,\n hasEscapes,\n dataSlots: [...dataSlots],\n literals,\n parts,\n tokens,\n runChars,\n runMin,\n runOffset,\n literalBeforeRun,\n capacityFromRun,\n runOfToken,\n runBeforeLiteral,\n runAfterLiteral,\n totalSlots: offset,\n }\n return compiled\n}\n\n/** Bounded per-operation/binding cache. No formatted values or callbacks in global caches. */\nexport class PatternCompiler {\n private readonly definitions = new Map(builtins)\n private readonly cache = new Map<string, CompiledMask>()\n private readonly custom: boolean\n /**\n * Whether some custom token's alphabet could ever accept a genuine\n * candidate-IME script (see {@link mayAcceptComposedScript}). `bind()`\n * uses this — not merely \"are there custom tokens at all\" — to decide\n * whether composition must be deferred: an ASCII-only custom alphabet\n * (an uppercase-transforming alphanumeric token, say) can safely reformat\n * live during composition exactly like the built-ins do, since Android's\n * autocorrect otherwise wraps plain Latin typing in a composition session\n * that may never fire `compositionend` while the field has no word\n * boundaries to type through.\n */\n readonly hasComposingRisk: boolean\n\n constructor(tokens?: MaskTokens) {\n this.custom = !!tokens && Object.keys(tokens).length > 0\n let composingRisk = false\n for (const [key, definition] of Object.entries(tokens ?? {})) {\n if (key === '\\\\' || Array.from(key).length !== 1) {\n throw new RangeError('Mask token keys must be one Unicode code point other than backslash')\n }\n const object = typeof definition === 'object' && 'match' in definition\n ? definition : { match: definition }\n if (mayAcceptComposedScript(object.match)) composingRisk = true\n // Reserving 2 UTF-16 units for every custom slot regardless of its\n // alphabet over-sizes `maxLength` (the DOM `maxlength` attribute and\n // `bind()`'s \"block insert when full\" gate share this number) whenever\n // a mask has more than a couple of such slots. That slack lets typing\n // continue past the field's real capacity instead of being blocked,\n // which corrupts a segmented mask's boundaries instead of just\n // refusing the keystroke — reserve the extra unit only where a slot\n // could actually need it.\n this.definitions.set(key, {\n match: matcher(object.match), transform: object.transform,\n maxLength: mayAcceptAstral(object.match) ? 2 : 1,\n })\n }\n this.hasComposingRisk = composingRisk\n }\n\n compile(mask: string): CompiledMask {\n const cached = this.cache.get(mask)\n if (cached) return cached\n const plan = compileMask(mask, this.definitions)\n if (this.cache.size >= 64) this.cache.delete(this.cache.keys().next().value!)\n this.cache.set(mask, plan)\n return plan\n }\n\n isData(char: string, plan?: CompiledMask): boolean {\n if (plan && (this.custom || plan.hasEscapes)) {\n return plan.dataSlots.some((slot) => slot.match(char))\n }\n for (const slot of this.definitions.values()) if (slot.match(char)) return true\n return false\n }\n\n /** Candidate stream from the fallback alphabet, without transformer side effects. */\n data(value: string, caret: number, plans?: CompiledMask[], readLiterals = true): MaskResult & { afterLiteral: boolean } {\n let output = ''\n let source = 0\n let outputCaret = 0\n let count = 0\n let lastLiteralOffset = -1\n let afterLiteral = false\n let slots: Slot[] | undefined\n const literals: CompiledMask['literals'] = []\n if (plans) {\n const unique = new Set<Slot>()\n for (const plan of plans) {\n for (const slot of plan.dataSlots) unique.add(slot)\n if (readLiterals) for (const literal of plan.literals) literals.push(literal)\n }\n slots = [...unique]\n }\n const hasEscapes = plans?.some((plan) => plan.hasEscapes)\n while (source < value.length) {\n // Complete literal runs at their data boundary are formatting, even\n // when a literal itself could match a slot (e.g. an escaped \"A\").\n const literal = lastLiteralOffset !== count && literals?.find((part) =>\n (part.offset === count || hasEscapes) && value.startsWith(part.text, source))\n if (literal) {\n if (source < caret) afterLiteral = true\n source += literal.text.length\n lastLiteralOffset = count\n continue\n }\n const char = String.fromCodePoint(value.codePointAt(source)!)\n const start = source\n source += char.length\n if (!(slots ? slots.some((slot) => slot.match(char)) : this.isData(char))) {\n if (start < caret && literals?.some((part) => value.startsWith(part.text, start))) afterLiteral = true\n continue\n }\n output += char\n count++\n if (source <= caret) {\n outputCaret = output.length\n afterLiteral = false\n }\n }\n return { value: output, caret: outputCaret, afterLiteral }\n }\n\n resolve(value: string, mask: MaskPattern, readLiterals = true): CompiledMask {\n if (!Array.isArray(mask)) return this.compile(mask)\n const plans = mask.map((pattern) => this.compile(pattern))\n const count = Array.from(this.data(value, 0,\n this.custom || plans.some((plan) => plan.hasEscapes) ? plans : undefined, readLiterals).value).length\n let i = 0\n while (i < plans.length - 1 && count > plans[i].totalSlots) i++\n return plans[i] ?? this.compile('')\n }\n}\n\n// Safe to share only the built-in alphabet; this instance never sees user callbacks.\nexport const defaultCompiler = new PatternCompiler()\n\n/** {@link getMaxLength} against an existing compiler — `bind()` reuses its own instead of validating and probing the tokens a second time. */\nexport function maskMaxLength(mask: MaskPattern, compiler: PatternCompiler): number {\n const patterns = Array.isArray(mask) ? mask : [mask]\n let max = 0\n for (const pattern of patterns) max = Math.max(max, compiler.compile(pattern).maxLength)\n return max\n}\n\n/** Maximum formatted UTF-16 length (custom slots allow two units). Infinity for a resolver. */\nexport function getMaxLength(mask: MaskPattern, options?: ApplyMaskOptions): number {\n if (options?.resolveMask) return Infinity\n return maskMaxLength(mask, options?.tokens ? new PatternCompiler(options.tokens) : defaultCompiler)\n}\n","import type { ApplyMaskOptions, MaskPattern, MaskResult } from './types'\nimport { PatternCompiler, defaultCompiler, transformChar } from './pattern'\nimport type { CompiledMask, LiteralToken } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Flat masking (opt-in) — treats the mask as one continuous character\n// stream. Best for continuous identifiers (phone numbers, CPF/CNPJ, credit\n// cards) where deleting/inserting a digit anywhere is expected to reflow\n// every digit after it — this is the classic mother-mask behavior and is\n// relied on by the majority of the test suite (paste, backspace, mid-string\n// insert, etc).\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nfunction applyFlatMask(\n value: string,\n plan: CompiledMask,\n inputCaret: number,\n eager: boolean,\n readLiterals: boolean,\n caretAfterLiteral: boolean,\n): MaskResult {\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n const leading = plan.tokens[0]\n let leadingConsumed = false\n\n for (const part of plan.parts) {\n if ('kind' in part) {\n pending += part.text\n if (readLiterals && value.startsWith(part.text, valueIdx)) {\n valueIdx += part.text.length\n if (part === leading) leadingConsumed = true\n }\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const literal = readLiterals && plan.hasEscapes && plan.literals.find(part => value.startsWith(part.text, valueIdx))\n if (literal) {\n valueIdx += literal.text.length\n continue\n }\n if (readLiterals && !leadingConsumed && leading?.kind === 'literal' &&\n value.startsWith(leading.text, valueIdx)) {\n valueIdx += leading.text.length\n leadingConsumed = true\n continue\n }\n const ch = String.fromCodePoint(value.codePointAt(valueIdx)!)\n valueIdx += ch.length\n\n if (part.match(ch)) {\n // Flush pending literals then write the matched char\n if (caretAfterLiteral && !caretResolved && valueIdx > inputCaret) outputCaret = output.length + pending.length\n output += pending + transformChar(ch, part)\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n // Eager mode: `pending` only survives to here holding the literal(s) that\n // directly follow the slot(s) just filled — see the doc comment on\n // `ApplyMaskOptions.eager`. Reveal it now instead of waiting for the next\n // matching keystroke, and carry the caret past it only if the caret was\n // already sitting at the end of the typed content (never yank it forward\n // during a mid-string edit).\n if (eager && pending) {\n const wasAtEnd = outputCaret === output.length\n output += pending\n if (wasAtEnd) outputCaret = output.length\n }\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Segmented masking (default) — treats literal separators as hard boundaries\n// between independent fields (e.g. day/month/year in \"99/99/9999\"). Editing\n// one segment never bleeds characters into a neighboring one, so replacing\n// the \"12\" in \"25/12/2025\" with a shorter or longer value keeps the year\n// exactly where it is instead of shifting digits across the \"/\".\n//\n// This runs in two passes rather than emitting characters as it scans:\n//\n// 1. **assign** — decide which mask slot each character of `value` lands in,\n// using the separators still present in `value` as positional anchors.\n// 2. **render** — walk the mask and emit the assigned characters plus the\n// literals that are actually justified, tracking the caret as it goes.\n//\n// Splitting them is what makes characters \"stick\" to their segment. A single\n// emit-as-you-scan pass can only ever look one separator ahead, so a value\n// like \"015-39\" (what the browser leaves behind when you select \"012.153.441\"\n// out of \"012.153.441-39\" and type \"015\") had no way to see that the \"-\"\n// pins \"39\" to the *last* segment — it treated the \"-\" as noise and repacked\n// the digits from the left into \"015.39\".\n// ---------------------------------------------------------------------------\n\n/**\n * Text of the separator that introduces run `run`.\n *\n * Tokens alternate, so every run except the very first is preceded by a\n * literal. Both callers only ask about a run they are advancing *into* — never\n * run 0 — so the lookup is always defined.\n */\nfunction separatorBefore(plan: CompiledMask, run: number): string {\n // literalBeforeRun[run] always indexes a literal token by construction (see CompiledMask docs).\n return (plan.tokens[plan.literalBeforeRun[run]] as LiteralToken).text\n}\n\n/**\n * Running count of data (digit/letter) characters in `value` up to each\n * UTF-16 offset; `counts[value.length]` is the total. Built once per\n * {@link assignToSlots} call so {@link remainingDataChars} is an O(1)\n * lookup instead of an O(remaining length) rescan.\n *\n * Without this table, a value containing many characters that read as a\n * separator by text but fail `findAnchor`'s capacity check below — a URL's\n * \"/\" pasted into a \"99/99/9999\" field, say — re-scans the shrinking tail\n * of `value` on every one of those characters: each individual rescan is\n * cheap, but repeating it at every position sums to O(n²) on an ordinary\n * large paste. One O(n) pass here replaces all of them.\n */\nfunction buildDataPrefixCounts(value: string, compiler: PatternCompiler, plan: CompiledMask): Uint32Array {\n const counts = new Uint32Array(value.length + 1)\n let count = 0\n for (let i = 0; i < value.length;) {\n const ch = String.fromCodePoint(value.codePointAt(i)!)\n counts[i] = count\n // A surrogate pair's second code unit must see the same \"before\" count\n // its first unit does — neither is a valid rescan start on its own.\n if (ch.length === 2) counts[i + 1] = count\n i += ch.length\n if (compiler.isData(ch, plan)) count++\n }\n counts[value.length] = count\n return counts\n}\n\n/** Count of remaining slot-matchable (digit/letter) characters in `value` from `fromIdx` onward — O(1) via a precomputed prefix table. */\nfunction remainingDataChars(fromIdx: number, prefixCounts: Uint32Array): number {\n return prefixCounts[prefixCounts.length - 1] - prefixCounts[fromIdx]\n}\n\n/** Where each character of `value` ended up, as produced by {@link assignToSlots}. */\ninterface Assignment {\n /** Flat, run-concatenated slot array: the character in each slot, or `''` when empty. */\n slotChar: string[]\n /** Exclusive source end of each filled code point; meaningless for empty slots. */\n slotSource: number[]\n /** How many slots of each run are filled (always a prefix of the run). */\n runFilled: number[]\n /**\n * Runs the user closed early by typing their own separator.\n *\n * Only ever true for a run holding at least `runMin` but fewer than\n * `runChars.length` characters — a state a bounded quantifier (`9{1,2}`)\n * makes reachable and a fixed run cannot reach at all, since its minimum\n * *is* its maximum. It records the one thing the rendered value would\n * otherwise lose: that \"3/\" is a finished one-digit day, not two digits\n * with the second still to come.\n */\n runCommitted: boolean[]\n /** For each literal token, the `value` index it was consumed from, or `-1` if it wasn't. */\n literalSource: number[]\n}\n\n/**\n * Length of the longest *proper* suffix of `text` sitting at `valueIdx`, or `0`.\n *\n * A selection that ends inside a multi-character separator leaves its tail\n * behind: deleting the \"(555)\" out of \"(555) 123-4567\" hands back\n * \" 123-4567\", where that lone space is all that survives of \") \". Single\n * character separators can never fragment, so this is always `0` for them.\n */\nfunction separatorTailLength(value: string, valueIdx: number, text: string): number {\n // Walk whole code points so a tail can never begin on a lone surrogate.\n for (let start = 0; start < text.length;) {\n start += String.fromCodePoint(text.codePointAt(start)!).length\n if (start < text.length && value.startsWith(text.slice(start), valueIdx)) {\n return text.length - start\n }\n }\n return 0\n}\n\n/**\n * Punctuation, symbols, and space separators — the shapes a person reaches\n * for when they mean \"this field is done\", regardless of which one this\n * particular mask happens to print.\n *\n * Deliberately excludes letters, digits, and every other script: a mistyped\n * \"a\" in a date field is a typo, not a decision to close the day, so it stays\n * the noise it has always been. See {@link isSeparatorIntent}.\n */\nconst SEPARATOR_INTENT = /[\\p{P}\\p{S}\\p{Zs}]/u\n\n/**\n * Whether `char` reads as a divider the user typed on purpose.\n *\n * A mask's own alphabet always wins: a custom token matching \".\" makes \".\"\n * content in that mask, never a boundary, so it is never a stand-in there.\n */\nfunction isSeparatorIntent(char: string, compiler: PatternCompiler, plan: CompiledMask): boolean {\n return SEPARATOR_INTENT.test(char) && !compiler.isData(char, plan)\n}\n\n/** A separator resolved to the segment it introduces, plus how much of `value` it occupies. */\ninterface Anchor {\n run: number\n /**\n * UTF-16 units to consume: the whole divider, the fragment that survived an\n * edit, or the single code point a stand-in was typed as.\n */\n length: number\n}\n\n/**\n * Find the segment that a separator sitting at `valueIdx` anchors the rest of\n * the value to, or `undefined` when the character is just noise.\n *\n * A separator is only trusted as an anchor when everything still left in\n * `value` actually fits in the slot capacity from that segment onward.\n * Otherwise honoring it would strand data the mask can no longer hold (e.g.\n * pasting into a later segment while an earlier one is still under-filled),\n * so the character is treated as stray noise instead and the current segment\n * takes the slot it needs. Because capacity only shrinks as you move right\n * through the mask, a nearer candidate that can't fit rules out every farther\n * one too — so the search stops at the first literal that matches by text.\n *\n * Intact separators are matched first, everywhere, before any fragment is\n * considered: a surviving tail (see {@link separatorTailLength}) is weaker\n * evidence than a whole separator, so it must never shadow one further along.\n * But it is still evidence — the fragment sits exactly where its separator\n * did, right in front of the segment it introduces, so it anchors the same\n * way. Without that, deleting an area code repacks the segment behind it:\n * \" 123-4567\" would render as \"(123) -4567\" instead of \"() 123-4567\".\n *\n * `committable` lifts the capacity veto for one candidate only: the run\n * immediately after a bounded-quantifier run that has already met its\n * minimum. There the separator is a width the *user chose*, not a guess the\n * mask is making, so it outranks a capacity count — and the count is\n * measuring the wrong thing anyway, since it silently assumes the ranged run\n * will grow to its maximum. Without this, typing a ninth digit into a full\n * `\"3/12/1986\"` withdraws the day's boundary and repacks every field into\n * `\"31/21/9861\"`; with it, the boundary holds and the digit that no longer\n * fits falls off the tail, exactly as an extra digit does on a full fixed\n * mask. Fixed runs never set it: their minimum is their maximum, and a run\n * that reaches its maximum leaves through {@link assignToSlots}'s\n * separator-consuming fast path without ever asking about anchors.\n *\n * `intent` is the last resort, for a character that reads as a divider but\n * matches none of this mask's by text (see {@link isSeparatorIntent}): typing\n * \".\" or \"-\" into `\"9{1,2}/9{1,2}/9{4}\"` means what typing \"/\" means, so it\n * stands in for the divider closing the segment being typed and the mask\n * prints its own \"/\" in its place.\n *\n * A stand-in is only ever read that way in the `committable` state above —\n * the one place a divider carries information the mask cannot supply itself,\n * because only the user knows whether a ranged segment is finished. Anywhere\n * else the mask owns where its dividers go: a segment that reaches its width\n * already reveals the next divider on its own (see `ApplyMaskOptions.eager`),\n * so the stray character has nothing left to say and stays the noise it has\n * always been. That keeps the rule honest in both directions — under\n * `\"9{1,2}/9{1,2}/9{4}\"`, `\"4.\"` and `\"4/\"` both give `\"4/\"`; under fixed\n * `\"99/99/9999\"`, where one digit is short of the day's width, both give\n * `\"4\"` — and leaves every mask without a bounded quantifier untouched.\n */\nfunction findAnchor(\n value: string,\n valueIdx: number,\n plan: CompiledMask,\n fromRun: number,\n committable: boolean,\n intent: boolean,\n prefixCounts: Uint32Array,\n): Anchor | undefined {\n const runCount = plan.runChars.length\n const fits = (run: number, length: number): boolean =>\n (committable && run === fromRun + 1) ||\n remainingDataChars(valueIdx + length, prefixCounts) <= plan.capacityFromRun[run]\n // A stand-in can only mean the divider that closes the segment being typed\n // — which of the ones further along was meant would be pure guesswork — and\n // only where `committable` says that divider is the user's call to make.\n // That is the same candidate `fits` already waives the capacity count for,\n // so a stand-in needs no separate test: it consumes the one code point it\n // was typed as, and the run it lands on is the one a real divider here\n // would have landed on.\n const standIn = (): Anchor | undefined =>\n intent && committable && fromRun + 1 < runCount\n ? { run: fromRun + 1, length: String.fromCodePoint(value.codePointAt(valueIdx)!).length }\n : undefined\n for (let pass = 0; pass < 2; pass++) {\n for (let run = fromRun + 1; run < runCount; run++) {\n const text = separatorBefore(plan, run)\n const length = pass === 0\n ? (value.startsWith(text, valueIdx) ? text.length : 0)\n : separatorTailLength(value, valueIdx, text)\n if (!length) continue\n if (fits(run, length)) return { run, length }\n // Capacity only shrinks rightward, so no farther candidate fits either.\n // The character is still a divider the user typed, though, so it can\n // fall back to closing the segment it was typed in.\n return standIn()\n }\n }\n return standIn()\n}\n\n/**\n * Pass 1 — place every character of `value` into a mask slot.\n *\n * Walks left to right filling the current run. Characters that don't match\n * the slot they land on are either an *anchor* (a separator that belongs to a\n * later segment, see {@link findAnchor} — jump there and keep the segments\n * in between empty) or noise (skip them). A run that fills up completely\n * advances to the next one, swallowing that segment's separator from `value`\n * if it's sitting right there.\n *\n * Within a run, filled slots are always a prefix — the walk never goes\n * backwards, so a run can be partially filled but never has holes.\n */\nfunction assignToSlots(\n value: string,\n plan: CompiledMask,\n compiler: PatternCompiler,\n readLiterals: boolean,\n inputCaret: number,\n): Assignment {\n const runCount = plan.runChars.length\n const slotChar: string[] = new Array(plan.totalSlots).fill('')\n const slotSource: number[] = new Array(plan.totalSlots).fill(-1)\n const runFilled: number[] = new Array(runCount).fill(0)\n const runCommitted: boolean[] = new Array(runCount).fill(false)\n const literalSource: number[] = new Array(plan.tokens.length).fill(-1)\n\n let runIdx = 0\n let slotIdx = 0\n let valueIdx = 0\n let caretBoundaryUsed = false\n // Built once so `findAnchor`'s capacity check below is O(1) per call\n // instead of rescanning the shrinking tail of `value` every time it's\n // asked and fails — see `buildDataPrefixCounts`.\n const prefixCounts = buildDataPrefixCounts(value, compiler, plan)\n const leading = plan.tokens[0]\n if (readLiterals && leading?.kind === 'literal' && value.startsWith(leading.text)) {\n literalSource[0] = 0\n valueIdx = leading.text.length\n }\n\n while (valueIdx < value.length && runIdx < runCount) {\n if (readLiterals && literalSource[0] < 0 && leading?.kind === 'literal' &&\n value.startsWith(leading.text, valueIdx)) {\n literalSource[0] = valueIdx\n valueIdx += leading.text.length\n continue\n }\n const chars = plan.runChars[runIdx]\n const ch = String.fromCodePoint(value.codePointAt(valueIdx)!)\n const matches = chars[slotIdx].match(ch)\n // This run has met its bounded-quantifier minimum but not its maximum —\n // the one state in which the literal that follows it is a boundary the\n // *user* sets rather than one the mask imposes. Always false for a fixed\n // run, whose minimum equals its maximum (see `CompiledMask.runMin`).\n const committable = runFilled[runIdx] >= plan.runMin[runIdx] && runFilled[runIdx] < chars.length\n const anchor = readLiterals && (!matches || plan.hasEscapes)\n ? findAnchor(value, valueIdx, plan, runIdx, committable,\n !matches && isSeparatorIntent(ch, compiler, plan), prefixCounts)\n : undefined\n if (anchor) {\n // The run's *own* closing separator, sitting right where the run stops:\n // the user ended this field deliberately, so the boundary is input\n // rather than decoration and must survive rendering whatever `eager` says.\n if (committable && anchor.run === runIdx + 1) runCommitted[runIdx] = true\n literalSource[plan.literalBeforeRun[anchor.run]] = valueIdx\n valueIdx += anchor.length\n runIdx = anchor.run\n slotIdx = 0\n continue\n }\n\n // An edit can take a whole divider with it, leaving nothing positional\n // behind: selecting \"(555) \" out of \"(555) 123-4567\" and typing \"9\" hands\n // back \"9123-4567\", where \"123\" reads exactly like the rest of the area\n // code. The caret is the one thing that still says where the edit ended,\n // and capacity turns it into proof: everything from the caret on fits the\n // following segments *exactly*, so it can only belong there — packing it\n // from the left would have to overflow the last segment. Anything less\n // than an exact fit is left to the ordinary left-to-right packing, which\n // is why this can never cascade (each further run has strictly less\n // capacity) and never fires mid-field, where the tail still needs the\n // slots of the run being typed into. It also takes a partly filled run\n // to fire at all — the caret has to sit behind something this edit put\n // here, or it carries no information at all: a caret left at 0 (the pure\n // API's default) would otherwise shift whole values rightwards.\n //\n // The divider has to be genuinely *gone* for any of this to apply. While\n // a copy of it survives further along, the anchoring above already knows\n // where everything belongs and the caret must not overrule it — that is\n // also what keeps rendering idempotent, since re-masking \"82--2\" at the\n // same caret has to give \"82--2\" back rather than \"8--22\".\n //\n // A capacity match across two runs with *different* alphabets is a\n // coincidence rather than evidence, so a character this segment can hold\n // and the next one cannot is not allowed to trigger the jump: with\n // `ZZZZ-999`, \"yABy\" leaves three letters over and the digit segment has\n // exactly three slots, and jumping there would drop all three as noise\n // and render \"y\" — a value that no longer re-masks to itself. A\n // character neither segment accepts carries no such counter-evidence\n // (it is noise wherever it lands), so it still lets the jump through.\n if (\n !caretBoundaryUsed && slotIdx > 0 && valueIdx === inputCaret && runIdx + 1 < runCount &&\n (!matches || plan.runChars[runIdx + 1][0].match(ch)) &&\n value.indexOf(separatorBefore(plan, runIdx + 1), valueIdx) < 0 &&\n remainingDataChars(valueIdx, prefixCounts) === plan.capacityFromRun[runIdx + 1]\n ) {\n caretBoundaryUsed = true\n runIdx++\n slotIdx = 0\n continue\n }\n\n if (matches) {\n const flat = plan.runOffset[runIdx] + slotIdx\n slotChar[flat] = transformChar(ch, chars[slotIdx])\n slotSource[flat] = valueIdx + ch.length\n runFilled[runIdx] = slotIdx + 1\n valueIdx += ch.length\n slotIdx++\n\n if (slotIdx === chars.length) {\n runIdx++\n slotIdx = 0\n // This segment is done: if its separator is the next thing in\n // `value`, consume it here so the following run starts clean.\n if (readLiterals && runIdx < runCount) {\n const text = separatorBefore(plan, runIdx)\n if (value.startsWith(text, valueIdx)) {\n literalSource[plan.literalBeforeRun[runIdx]] = valueIdx\n valueIdx += text.length\n }\n }\n }\n continue\n }\n\n valueIdx += ch.length // stray/noise char — skip it\n }\n\n return { slotChar, slotSource, runFilled, runCommitted, literalSource }\n}\n\n/**\n * Decide which literals the rendered value actually shows.\n *\n * A separator earns its place three ways:\n *\n * - **anchor** — the segment right after it holds data, so the separator is\n * what tells the reader (and the next parse) where that data belongs.\n * - **retained boundary** — it was present in the input and there is data in\n * a later segment. Emptying a field must not remove its untouched dividers:\n * \"(111) 222-3333\" becomes \"(111) -3333\", not \"(111-3333\".\n * - **committed boundary** — the segment right before it is a bounded-\n * quantifier run (`9{1,2}`) that the user closed early by typing this very\n * separator, at or past its minimum (see `Assignment.runCommitted`). \"3/\"\n * with `9{1,2}/9{1,2}/9{4}` is a finished one-digit day; dropping the \"/\"\n * would re-read it as an unfinished two-digit one and swallow the next\n * keystroke into the same field. Fixed runs can never be in this state, so\n * `\"25/\"` on `99/99/9999` still follows the eager rule alone.\n * - **intact frame** — it opens the mask, the value holds data, and the\n * divider closing the field it opens is still in the value. An opening\n * literal can only disappear by being deleted, and a deletion that cut\n * into the first field is not the same as one aimed at the frame itself;\n * the closing divider is what tells them apart. Deleting the \"(555)\" out\n * of \"(555) 123-4567\" leaves \") \" behind, so the frame comes back as\n * \"() 123-4567\" — while backspacing the \"(\" of \"(-4444\", where nothing of\n * \") \" survives, removes it for real instead of resurrecting it forever.\n * This holds with eager off, which is how `bind()` masks every deletion\n * (see `eagerForEdit`).\n * - **eager** — the segment right before it is completely filled, so the\n * separator is revealed before the user types the character that would\n * normally pull it in. See `ApplyMaskOptions.eager`.\n *\n * Absent separators around skipped segments are not invented, which keeps\n * \"015\" + skipped middle + \"-39\" compact instead of padding the gap. Existing\n * separators after the last filled segment still follow eager mode, so tail\n * deletion and clearing an input do not leave a trail of empty dividers.\n *\n * The second loop is a round-trip guard. `bind()` feeds the rendered value\n * straight back through this masking on the next keystroke, so a render that\n * doesn't parse back to the same assignment would make characters drift while\n * the user types. Dropping a separator is only safe when it can't be confused\n * for the next visible one: with `99/99/9999` holding \"1\" and \"2025\", hiding\n * the first \"/\" would leave \"1/2025\", which re-parses as 1 / 20 / 25. So any\n * hidden separator between two filled segments that reads the same as the one\n * introducing the later segment is put back — `1//2025`, which re-parses to\n * exactly what it renders. Masks with distinct separators need no such guard.\n */\nfunction resolveLiteralVisibility(\n plan: CompiledMask,\n assignment: Assignment,\n eager: boolean,\n): boolean[] {\n const { tokens, runBeforeLiteral, runAfterLiteral, literalBeforeRun, runChars } = plan\n const { runFilled, runCommitted, literalSource } = assignment\n const visible: boolean[] = new Array(tokens.length).fill(false)\n let lastFilledRun = runFilled.length - 1\n while (lastFilledRun >= 0 && runFilled[lastFilledRun] === 0) lastFilledRun--\n\n for (let t = 0; t < tokens.length; t++) {\n if (tokens[t].kind !== 'literal') continue\n const after = runAfterLiteral[t]\n const before = runBeforeLiteral[t]\n // Tokens alternate, so `t + 2` is the divider closing the field this\n // literal opens (when the mask has one at all), and `after + 1` is the\n // field that divider introduces. Either one still standing means the\n // frame survived the edit: the divider itself is direct evidence, and\n // data sitting in the field behind it is evidence just as good once the\n // divider was swallowed whole.\n const frameIntact = before < 0 && lastFilledRun >= 0 &&\n (literalSource[t + 2] >= 0 || runFilled[after + 1] > 0)\n visible[t] =\n (after >= 0 && (runFilled[after] > 0 || (literalSource[t] >= 0 && after < lastFilledRun))) ||\n frameIntact ||\n (before >= 0 && runCommitted[before]) ||\n (eager && (before < 0 || runFilled[before] === runChars[before].length))\n }\n\n let previousFilledRun = -1\n for (let run = 0; run < runChars.length; run++) {\n if (runFilled[run] === 0) continue\n const litToken = literalBeforeRun[run]\n if (litToken >= 0) {\n // Both casts are literal tokens by construction — literalBeforeRun only ever\n // points at the literal directly before a run (see CompiledMask docs).\n const text = (tokens[litToken] as LiteralToken).text\n for (let skipped = previousFilledRun + 1; skipped < run; skipped++) {\n const skippedLit = literalBeforeRun[skipped]\n if (skippedLit < 0) continue\n if ((tokens[skippedLit] as LiteralToken).text === text) visible[skippedLit] = true\n }\n }\n previousFilledRun = run\n }\n\n return visible\n}\n\n/**\n * Pass 2 — emit the assigned characters and justified literals, tracking the caret.\n *\n * **Caret algorithm**: every emitted character that came from a `value`\n * position *before* `inputCaret` pushes the output caret to the current\n * output length; the first character from at-or-after `inputCaret` freezes\n * it. A literal only carries the caret past itself while the caret is still\n * sitting at the frontier (everything emitted so far is behind it), and then\n * only when the literal isn't standing between the caret and text the user\n * hasn't reached yet: either it was revealed eagerly right after the segment\n * being typed, or it was already in `value` ahead of the caret. That's what\n * puts the caret at `015.|-39` — past the separator the just-completed \"015\"\n * revealed, but not past the \"-\" that anchors the untouched \"39\".\n *\n * Only a *full* field hands the caret across its divider, which is what\n * leaves a bounded-quantifier field open for the rest of what the user is\n * typing. Replacing the \"3/1\" of \"3/1/1998\" with \"2\" renders \"2//1998\" and\n * stays at `2|//1998`, so a second \"2\" makes the day \"22\" — the mask has no\n * way to know the day was finished, and guessing it was would cost the\n * keystroke. Once the day does reach its maximum the ordinary eager reveal\n * moves on by itself, landing at `22/|/1998`.\n */\nfunction renderAssignment(\n plan: CompiledMask,\n assignment: Assignment,\n visible: boolean[],\n inputCaret: number,\n eager: boolean,\n caretAfterLiteral: boolean,\n): MaskResult {\n const { tokens, runChars, runOffset, runBeforeLiteral, runAfterLiteral, runOfToken } = plan\n const { slotChar, slotSource, runFilled } = assignment\n\n let output = ''\n let outputCaret = 0\n let caretResolved = false\n\n for (let t = 0; t < tokens.length; t++) {\n const token = tokens[t]\n\n if (token.kind === 'literal') {\n if (!visible[t]) continue\n const before = runBeforeLiteral[t]\n const after = runAfterLiteral[t]\n // Revealed ahead of the user rather than typed by them, *and* nothing\n // waiting on the far side of it — this separator is the frontier of\n // what's been entered, so the caret belongs past it, ready for the next\n // segment. A separator dividing two segments that both already hold\n // text is not a frontier: the caret stays exactly where the browser\n // put it instead of jumping over content the user didn't touch.\n const opensEmptySegment = after < 0 || runFilled[after] === 0\n // A literal that opens the mask is framing rather than a reveal (see\n // `resolveLiteralVisibility`), so it carries the caret into the field\n // it opens whether or not eager is on — the caret belongs at \"(|)\",\n // inside the emptied area code, not outside the field at \"|()\".\n const revealed =\n before < 0 || (eager && runFilled[before] === runChars[before].length)\n const source = assignment.literalSource[t]\n const atFrontier = !caretResolved && outputCaret === output.length\n output += token.text\n if (\n atFrontier &&\n (caretAfterLiteral || (revealed && opensEmptySegment) || (source >= 0 && source < inputCaret))\n ) {\n outputCaret = output.length\n }\n continue\n }\n\n const run = runOfToken[t]\n const offset = runOffset[run]\n for (let s = 0; s < runFilled[run]; s++) {\n output += slotChar[offset + s]\n if (caretResolved) continue\n if (slotSource[offset + s] <= inputCaret) outputCaret = output.length\n else caretResolved = true\n }\n }\n\n return { value: output, caret: outputCaret }\n}\n\n/**\n * Same contract as {@link applyFlatMask}, but keeps every character in the\n * segment it belongs to instead of repacking the whole value from the left.\n */\nfunction applySegmentedMask(\n value: string,\n plan: CompiledMask,\n inputCaret: number,\n eager: boolean,\n compiler: PatternCompiler,\n readLiterals: boolean,\n caretAfterLiteral: boolean,\n): MaskResult {\n const assignment = assignToSlots(value, plan, compiler, readLiterals, inputCaret)\n const visible = resolveLiteralVisibility(plan, assignment, eager)\n return renderAssignment(plan, assignment, visible, inputCaret, eager, caretAfterLiteral)\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\n/** Internal entry shared by pure APIs and the binding's private compiler. */\nexport function applyWithCompiler(\n value: string,\n mask: MaskPattern,\n inputCaret: number,\n options: ApplyMaskOptions | undefined,\n compiler: PatternCompiler,\n): MaskResult {\n let effective: CompiledMask\n let caretAfterLiteral = false\n if (options?.resolveMask) {\n // Content-dependent layouts describe one continuous identifier. Resolve once\n // from its candidate stream, then render it without stale source separators.\n const patterns = Array.isArray(mask) ? mask : [mask]\n const data = compiler.data(value, inputCaret, patterns.map((pattern) => compiler.compile(pattern)))\n effective = compiler.resolve(data.value, options.resolveMask(data.value), false)\n value = data.value\n inputCaret = data.caret\n caretAfterLiteral = data.afterLiteral\n } else effective = compiler.resolve(value, mask)\n if (!value) return { value: '', caret: 0 }\n const eager = options?.eager !== false\n return options?.segmented === false\n ? applyFlatMask(value, effective, inputCaret, eager, !options.resolveMask, caretAfterLiteral)\n : applySegmentedMask(value, effective, inputCaret, eager, compiler, !options?.resolveMask, caretAfterLiteral)\n}\n\nexport function applyMask(\n value: string,\n mask: MaskPattern,\n inputCaret = 0,\n options?: ApplyMaskOptions,\n): MaskResult {\n const compiler = options?.tokens ? new PatternCompiler(options.tokens) : defaultCompiler\n return applyWithCompiler(value, mask, inputCaret, options, compiler)\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","// ---------------------------------------------------------------------------\n// DOM plumbing shared by `bind()` and `bindDecimal()` — caret access, the\n// requestAnimationFrame scheduler both use to read post-mutation state, and\n// the attribute/dispose bookkeeping that makes either binder idempotent and\n// re-bindable. None of this knows about masking; it's the same regardless of\n// which formatter a binder plugs in.\n// ---------------------------------------------------------------------------\n\nimport { isIos } from './platform'\nimport type { BindInputAttributes } from './types'\n\nexport const MASKED_ATTR = 'data-masked'\n\n/** Apply the binder-managed input attributes, using safe editing defaults. */\nexport function setBindInputAttributes(\n setIfMissing: (name: string, value: string) => void,\n options: BindInputAttributes,\n): void {\n setIfMissing('autocomplete', options.autocomplete ?? 'off')\n setIfMissing('autocorrect', options.autocorrect ?? 'off')\n setIfMissing('autocapitalize', options.autocapitalize ?? 'off')\n setIfMissing('spellcheck', String(options.spellcheck ?? false))\n}\n\n/** `bind()`/`bindDecimal()` are idempotent: a second call on the same element is a no-op. */\nexport function isAlreadyBound(input: Element): boolean {\n return input.getAttribute(MASKED_ATTR) !== null\n}\n\nexport function getCaret(target: HTMLInputElement): number {\n try {\n return target.selectionStart ?? target.value.length\n } catch {\n return target.value.length\n }\n}\n\nexport function setCaret(target: HTMLInputElement, caret: number): void {\n try {\n // DOM selections use UTF-16 offsets; never leave the caret inside a pair.\n if (\n caret > 0 && caret < target.value.length &&\n target.value.charCodeAt(caret) >= 0xdc00 && target.value.charCodeAt(caret) <= 0xdfff &&\n target.value.charCodeAt(caret - 1) >= 0xd800 && target.value.charCodeAt(caret - 1) <= 0xdbff\n ) caret--\n target.setSelectionRange(caret, caret)\n } catch {\n // Some input types (for example type=\"number\") do not support text selection.\n }\n}\n\n/** A retained dispose handle must not retain the binding after it has run. */\nexport function releaseOnce(cleanup: () => void): () => void {\n let release: (() => void) | undefined = cleanup\n return () => {\n const run = release\n release = undefined\n run?.()\n }\n}\n\n/**\n * The scheduled frame can fire before the browser has actually applied a\n * pending keystroke's default action (confirmed via real-Firefox tracing:\n * `target.value` is still unchanged at that point). If the selection is\n * still a real range then, it's the range the user had *before* typing —\n * not a post-edit collapsed caret — and the browser still intends to use it\n * to replace-with-the-typed-character. Reformatting now would collapse that\n * range out from under the pending native edit; the caller should bail and\n * let the next authoritative event (`input`, or the following frame) take\n * over instead.\n */\nexport function editStillPending(target: HTMLInputElement, oldValue: string): boolean {\n return target.value === oldValue && target.selectionStart !== target.selectionEnd\n}\n\n/**\n * requestAnimationFrame callbacks scheduled by a binder outlive a single\n * keystroke handler and close over the input element. If `dispose()` runs\n * before a frame fires — e.g. the field unmounts right after the user types\n * — an uncancelled callback keeps that element (and its closure) alive until\n * the next paint, which can be a very long time on a backgrounded tab. This\n * tracks every scheduled frame so disposal can cancel what's still pending.\n */\nexport function createFrameScheduler(): { scheduleFrame: (callback: () => void) => void; cancelPendingFrames: () => void } {\n const pendingFrames = new Set<number>()\n const scheduleFrame = (callback: () => void): void => {\n const id = requestAnimationFrame(() => {\n pendingFrames.delete(id)\n callback()\n })\n pendingFrames.add(id)\n }\n const cancelPendingFrames = (): void => {\n for (const id of pendingFrames) cancelAnimationFrame(id)\n pendingFrames.clear()\n }\n return { scheduleFrame, cancelPendingFrames }\n}\n\n/**\n * A selection-delete can take a whole field *and* the separator introducing\n * the next one with it, leaving nothing positional behind for the mask to\n * anchor to: selecting \"(11) \" (digits, closing paren, and the space) out of\n * \"(11) 98765-4321\" and deleting hands the engine \"98765-4321\", which reads\n * exactly like fresh digits for the area code — the untouched \"98765\" has\n * no way to say it was never touched. A shorter selection stopping at \"(11)\"\n * leaves the space behind, and the existing anchoring in `assignToSlots`\n * already gets that case right; this only fills the gap where the deletion\n * swallowed one or more separators whole. Widening the selection further —\n * through the \"98765\" too, out to \"(11) 98765-\" — swallows both the \") \"\n * and the \"-\": every field the deletion fully crossed reappears empty with\n * its own boundary intact, e.g. \"(11) -4321\", the same shape three plain\n * Backspaces (never touching the dividers themselves) would have left.\n *\n * `bind()` is the one layer that knows a deletion happened at all — pure\n * `applyMask` sees only the resulting `(value, caret)` and can't tell \"the\n * user just deleted through here\" from \"these are the first digits the user\n * ever typed\", which is exactly the ambiguity `eagerForEdit` exists for on\n * the eager side. So this restores, verbatim, every separator span an edit\n * deleted — but only when real data still follows the deletion untouched.\n * Restoring separators when nothing follows would resurrect ones `bind()`\n * is documented to drop for good, like backspacing the eager \".\" off \"012.\".\n *\n * A deletion that never touched any data at all is left alone too, whatever\n * its length — that's plain divider erosion, one keystroke (or a selection\n * confined to the divider) peeling back separator text the user is clearly\n * choosing to remove, exactly as backspacing through \"(111) \" down to\n * \"(111-4444\" and on to \"-4444\" is documented to work. Only a deletion that\n * destroys *some* field data is treated as having swallowed a separator by\n * accident rather than on purpose. `allowDividerOnly` lifts that rule for a\n * caller that has separately established the erosion would corrupt something\n * — see `bind()`, where a divider whose removal would re-segment untouched\n * text is put back instead.\n *\n * Restoring a separator only ever reproduces text that was standing exactly\n * there a moment ago, at the exact position it stood — it never invents\n * structure. That is also why it stays safe when the same separator repeats\n * elsewhere in the mask (`\"HH:HH:HH\"`'s two colons, say): the restored one\n * lands precisely where the deleted one did, so the engine's own capacity\n * check (`assignToSlots`/`findAnchor` in apply-mask.ts) still resolves it\n * to the one field it can — the surviving data plus everything after the\n * restored separator has to fit what follows, which pins the split uniquely\n * even with an identical separator later in the string.\n *\n * Typing a character straight over a selection destroys exactly the same\n * dividers the equivalent Delete would have, so `insertedLength` widens this\n * to that case: selecting the `\"3/12\"` of `\"3/12/1986\"` and typing `\"4\"`\n * takes the day, the month, *and* the divider between them, handing the\n * engine `\"4/1986\"`. On a mask whose separators all read alike there is\n * nothing left in the value to say which field the surviving `\"/\"` belongs\n * to, so the untouched year breaks apart into `\"4/19/86\"`. Putting the\n * divider back pins it where it never moved from. The inserted text keeps its\n * place — the separators go in directly behind it, exactly where they stood.\n *\n * `pos`, `removedLength` and `insertedLength` must describe a single splice —\n * `previousValue` with `[pos - insertedLength, pos - insertedLength +\n * removedLength)` replaced by the `insertedLength` characters ending at\n * `pos`, and nothing else changed. Any other shape (IME weirdness, a\n * multi-range edit) fails the checks below and is left untouched.\n */\nexport function restoreSwallowedSeparators(\n rawValue: string,\n pos: number,\n removedLength: number,\n previousValue: string,\n isData: (char: string) => boolean,\n insertedLength = 0,\n allowDividerOnly = false,\n): string {\n if (removedLength <= 0 || insertedLength < 0 || insertedLength > pos) return rawValue\n const cutStart = pos - insertedLength\n const deletedEnd = cutStart + removedLength\n if (deletedEnd > previousValue.length) return rawValue\n if (\n previousValue.slice(0, cutStart) !== rawValue.slice(0, cutStart) ||\n previousValue.slice(deletedEnd) !== rawValue.slice(pos)\n ) return rawValue\n\n // Nothing left past the cut means every field beyond it was cleared too —\n // that deletion is final, not a swallow (matches backspacing the eager \".\"\n // off \"012.\" for good, where the cut sits at the very end).\n const tail = previousValue.slice(deletedEnd)\n if (!Array.from(tail).some(isData)) return rawValue\n\n // `previousValue` is a rendered mask output, so every non-data code point\n // inside the deleted span is a genuine separator the deletion swallowed —\n // never a coincidence. Keep them, in order, and drop the data alongside\n // them that this edit did mean to delete.\n const removed = previousValue.slice(cutStart, deletedEnd)\n if (!allowDividerOnly && !Array.from(removed).some(isData)) return rawValue\n let literals = ''\n for (const ch of removed) if (!isData(ch)) literals += ch\n if (!literals) return rawValue\n\n return rawValue.slice(0, pos) + literals + rawValue.slice(pos)\n}\n\n/** Attributes a binder sets only if absent, so it never clobbers the caller's own and disposal only ever removes what it added. */\nexport function trackAttrs(input: Element): { setIfMissing: (name: string, value: string) => void; removeTracked: () => void } {\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n const removeTracked = (): void => {\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n return { setIfMissing, removeTracked }\n}\n\n/** A binder's handlers in the order their events are attached. The last one listens on `keyup` (iOS) or `keydown` (elsewhere) — see `isIos()`. */\nexport type BinderHandlers = readonly [\n paste: (e: Event) => void,\n input: (e: Event) => void,\n compositionstart: (e: Event) => void,\n compositionend: (e: Event) => void,\n key: (e: Event) => void,\n]\n\n/**\n * Everything a binder does to take — and later release — ownership of an\n * element, in one place: mark it bound (`data-masked` set to `marker`), apply\n * the managed attributes (plus `maxlength` when finite), attach the five\n * listeners, and return the dispose function that reverses each of those\n * steps and cancels any reformat frame still in flight. Keeping attach and\n * detach in a single helper makes the add/remove symmetry impossible to break\n * from a binder — exactly the class of leak `memory.test.ts` guards against.\n */\nexport function attachBinder(\n input: Element,\n marker: string,\n attributes: BindInputAttributes,\n maxLength: number,\n handlers: BinderHandlers,\n cancelPendingFrames: () => void,\n): () => void {\n const { setIfMissing, removeTracked } = trackAttrs(input)\n input.setAttribute(MASKED_ATTR, marker)\n setBindInputAttributes(setIfMissing, attributes)\n if (Number.isFinite(maxLength)) setIfMissing('maxlength', String(maxLength))\n\n const names = ['paste', 'input', 'compositionstart', 'compositionend', isIos() ? 'keyup' : 'keydown']\n for (let i = 0; i < names.length; i++) input.addEventListener(names[i], handlers[i])\n\n return releaseOnce(() => {\n for (let i = 0; i < names.length; i++) input.removeEventListener(names[i], handlers[i])\n input.removeAttribute(MASKED_ATTR)\n removeTracked()\n cancelPendingFrames()\n })\n}\n","import { applyWithCompiler } from './apply-mask'\nimport {\n attachBinder,\n createFrameScheduler,\n editStillPending,\n getCaret,\n isAlreadyBound,\n restoreSwallowedSeparators,\n setCaret,\n} from './bind-shared'\nimport { maskMaxLength, PatternCompiler } from './pattern'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern, MaskResolver, MaskResult } from './types'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\ntype InputEditKind = 'insert' | 'backspace' | 'delete' | 'unidentified'\n\n/**\n * A deletion can take the mask's opening structure with it, leaving the caret\n * at 0 with nothing in front of it to anchor to — selecting the \"(555)\" out\n * of \"(555) 123-4567\" and deleting renders \"() 123-4567\", where that \"(\" is\n * structure the mask restored rather than text the user is behind. Only at\n * position 0 is the whole rendered prefix known to be restored like this, so\n * only there does the render's own caret win: the user is editing the emptied\n * field at \"(|) 123-4567\", not sitting outside it at \"|() 123-4567\".\n *\n * An edit the mask *fully* undid is the exception. Backspacing the \"(\" of\n * \"(|) 123-4567\" deletes nothing the render doesn't put straight back, so\n * holding the caret in place would wedge it there forever. The keystroke\n * still gets to move it, exactly as it does over any other fixed character.\n */\nfunction restoredPrefixCaret(pos: number, masked: MaskResult, baselineValue: string): number {\n return pos === 0 && masked.value !== baselineValue ? masked.caret : pos\n}\n\n/**\n * Keep native movement within a retained divider. If formatting changed the\n * prefix, use its source-mapped caret instead of an offset into removed text.\n * Backspace must not advance across an untouched divider or into the next field.\n */\nfunction backwardCaret(rawValue: string, pos: number, masked: MaskResult, baselineValue: string): number {\n if (masked.value.startsWith(rawValue.slice(0, pos))) return restoredPrefixCaret(pos, masked, baselineValue)\n // Overlapping divider text can make a surviving fragment look like the\n // next divider. Stay before unchanged text on the right, even then.\n let tailStart = masked.value.length\n let rawEnd = rawValue.length\n while (rawEnd > pos && tailStart > 0 && rawValue[rawEnd - 1] === masked.value[tailStart - 1]) {\n rawEnd--\n tailStart--\n }\n return Math.min(pos, masked.caret, tailStart)\n}\n\n/** Classify a native `InputEvent.inputType` the same way `onKey` classifies `KeyboardEvent.key`. */\nfunction classifyInputType(inputType: string | undefined): InputEditKind {\n if (inputType?.startsWith('delete') && inputType.endsWith('Backward')) return 'backspace'\n if (inputType === 'deleteContentForward') return 'delete'\n if (inputType && inputType.startsWith('insert')) return 'insert'\n return 'unidentified'\n}\n\n/**\n * `eager`, unless this particular edit is a deletion.\n *\n * `applyMask`/`buildMask` are pure functions of `(value, caret)` — they have\n * no memory of *how* the value got there. That's a problem for eager mode\n * specifically: deleting the separator eager just added (e.g. backspacing\n * the \".\" off \"012.\") produces the exact same `(value, caret)` — raw digits,\n * caret right where the separator used to be — as the moment right before\n * that separator first appeared. A stateless recompute can't tell those two\n * apart, so eager would immediately re-add the separator the user just\n * deleted, making backspace look like it does nothing.\n *\n * `bind()` is the one layer that *does* know which happened (the DOM event\n * says so), so it's the right place to break the tie: suppress eager for the\n * single recompute that follows a delete-type edit, and let it resume on the\n * next insert. This never removes anything eager wouldn't otherwise have\n * added — it only stops eager from resurrecting a literal the user just\n * removed.\n */\nfunction eagerForEdit(eager: boolean | undefined, isDeleteLike: boolean): boolean | undefined {\n return isDeleteLike ? false : eager\n}\n\n/**\n * Where the caret lands after a reformat, shared by the `input`-event path\n * (`onInput`) and its `keydown`/`keyup` fallback (`onKey`) — both apply the\n * same reasoning once the edit is classified into an {@link InputEditKind},\n * just from differently-shaped signals (`InputEvent.inputType` vs.\n * `KeyboardEvent.key`).\n *\n * - A resolver mask that actually changed the value takes its own\n * source-mapped caret, since the candidate stream it reflowed can't be\n * reasoned about positionally like a fixed pattern.\n * - An unidentified edit (unreliable/missing `key`, or a directionless\n * `inputType` like `deleteByCut`) only trusts the masked caret once the\n * value visibly grew — otherwise it's likely a no-op or a delete\n * misreported as unidentified, so the pre-edit position holds, adjusted\n * for any structure the mask restored in front of it.\n * - A forward Delete that didn't shrink the value (e.g. it landed on a\n * literal and consumed nothing) leaves the caret one past where the user\n * pressed it, matching native forward-delete-through-a-literal behavior.\n * - Backspace defers to {@link backwardCaret}'s divider-aware logic.\n * - A plain insert takes the masked caret outright.\n */\nfunction resolveCaretAfterEdit(\n kind: InputEditKind,\n rawValue: string,\n pos: number,\n previousLength: number,\n masked: MaskResult,\n resolveMask: MaskResolver | undefined,\n baselineValue: string,\n): number {\n if (resolveMask && masked.value !== rawValue && masked.value !== baselineValue) return masked.caret\n if (kind === 'unidentified') {\n return masked.value.length > previousLength ? masked.caret : restoredPrefixCaret(pos, masked, baselineValue)\n }\n if (kind === 'delete') {\n return previousLength === masked.value.length ? pos + 1 : restoredPrefixCaret(pos, masked, baselineValue)\n }\n if (kind === 'backspace') return backwardCaret(rawValue, pos, masked, baselineValue)\n return masked.caret\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Reformats post-mutation `input` events (the reliable, timing-safe signal\n * on every modern browser, including mobile IME/autocorrect) with a\n * `keydown`/`requestAnimationFrame` fallback for older browsers.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (isAlreadyBound(input)) return () => {}\n\n const {\n onChange,\n segmented,\n eager,\n tokens,\n resolveMask,\n autocomplete,\n autocorrect,\n autocapitalize,\n spellcheck,\n } = toBindOptions(third)\n\n const compiler = new PatternCompiler(tokens)\n const format = (value: string, caret: number, editEager = eager) =>\n applyWithCompiler(value, mask, caret, { tokens, resolveMask, segmented, eager: editEager }, compiler)\n const isData = (ch: string): boolean => compiler.isData(ch)\n // Defer composition only when some custom token's alphabet could plausibly\n // accept a genuine candidate-IME script (Pinyin/Kana/Hangul) — where the\n // provisional draft reads nothing like what it commits, so a live reformat\n // would clobber text the IME still expects to revise. A custom token whose\n // alphabet is ASCII/Latin-only (an uppercase-transforming alphanumeric\n // token, say) gets the same live-formatting treatment as the built-ins:\n // Android's autocorrect otherwise wraps plain Latin typing in a\n // composition session that may never fire `compositionend` while the\n // field has no word boundaries to type through, leaving the mask looking\n // completely inert. See `PatternCompiler.hasComposingRisk`.\n const deferComposition = compiler.hasComposingRisk\n\n // Resolver capacity is unknowable; custom-token IME drafts may exceed even\n // the two-UTF-16-unit-per-slot bound. Enforce those capacities in the engine.\n // Author-supplied maxlength remains an intentional application constraint.\n // The binding's own compiler sizes this, so a custom token gets its own\n // definition (not the built-in one a reused key like \"A\" would otherwise\n // fall back to, or the literal a non-built-in key would be mistaken for).\n const maxLength = resolveMask || deferComposition ? Infinity : maskMaxLength(mask, compiler)\n\n let lockInput = false\n let isComposing = false\n let skipNextKeyup = false\n // Baseline the `input`-event path compares against to detect growth/no-op\n // edits (mirrors the role `oldValue` plays in `onKey`, but persisted\n // across calls since `input` fires once per real mutation — see `onInput`).\n let lastMaskedValue = (input as HTMLInputElement).value ?? ''\n\n const { scheduleFrame, cancelPendingFrames } = createFrameScheduler()\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n const oldValue = target.value\n scheduleFrame(() => {\n if (deferComposition && isComposing) return\n if (editStillPending(target, oldValue)) return\n const m = format(target.value, getCaret(target))\n target.value = m.value\n setCaret(target, m.caret)\n lastMaskedValue = target.value\n onChange?.(target.value)\n })\n }\n\n // `input` fires synchronously, once per real DOM mutation, right after the\n // browser (or IME/autocorrect) has already applied the edit — unlike\n // `keydown` + `requestAnimationFrame`, there's no batching window where\n // several keystrokes can queue up before we read `selectionStart`, which is\n // what let fast typing (especially Android Chrome, where composed/\n // autocorrected characters often arrive with an unreliable or missing\n // `key`) drift the caret. This is now the primary formatting path; `onKey`\n // below stays as a `requestAnimationFrame` fallback for browsers that don't\n // fire `input` reliably.\n //\n // Built-ins keep the existing Android autocorrect path. Custom alphabets\n // leave provisional composition text and selection completely untouched.\n const onInput = (e: Event): void => {\n const inputEvent = e as InputEvent\n const target = e.target as HTMLInputElement\n cancelPendingFrames()\n lockInput = false\n skipNextKeyup = true\n if (deferComposition && (isComposing || inputEvent.isComposing)) return\n if (editStillPending(target, lastMaskedValue)) return\n\n const pos = getCaret(target)\n const previousLength = lastMaskedValue.length\n const kind = classifyInputType(inputEvent.inputType)\n const rawValue = target.value\n const isDeleteLike = kind === 'backspace' || kind === 'delete'\n // Only a plain content delete — a selection Backspace/Delete/Cut, or a\n // single collapsed Backspace/Delete — gets the swallowed-separator\n // rescue below. Word/line deletes (`deleteWordBackward`,\n // `deleteSoftLineBackward`, ...) are a deliberate bulk clear —\n // resurrecting structure they removed would contradict `bind()`'s own\n // documented \"never resurrect a divider the user just removed\" rule, so\n // those are left to reformat the raw value exactly as struck.\n // Array/resolver masks are excluded too: which pattern applies can\n // change with the new, shorter data count, and a literal restored from\n // the old pattern's layout can land at a position the newly-resolved\n // one never had.\n const isStaticMask = !Array.isArray(mask) && !resolveMask\n const isPlainContentDelete =\n isStaticMask &&\n (inputEvent.inputType === 'deleteContentBackward' ||\n inputEvent.inputType === 'deleteContentForward' ||\n inputEvent.inputType === 'deleteByCut')\n // Typing over a selection destroys the same dividers the equivalent\n // Delete would have, so it gets the same rescue. `insertText` is the one\n // insert type that reports what it inserted, which is what makes the edit\n // a splice this can reason about; paste, IME commits and drag-and-drop\n // leave `data` null and stay a plain reformat.\n const insertedText =\n isStaticMask && inputEvent.inputType === 'insertText' && typeof inputEvent.data === 'string'\n ? inputEvent.data : ''\n const editEager = eagerForEdit(eager, isDeleteLike)\n\n const cutStart = pos - insertedText.length\n const removedLength = previousLength - rawValue.length + insertedText.length\n\n let formatValue = rawValue\n if (isPlainContentDelete || insertedText) {\n const rescued = restoreSwallowedSeparators(\n rawValue, pos, removedLength, lastMaskedValue, isData, insertedText.length, true,\n )\n if (rescued !== rawValue) {\n // A deletion that destroyed field data puts its dividers back\n // outright: every field it crossed reappears empty with its own\n // boundary intact, which is what keeps \"98765-4321\" from sliding\n // into an emptied area code.\n const destroyedFieldData = isPlainContentDelete &&\n Array.from(lastMaskedValue.slice(cutStart, cutStart + removedLength)).some(isData)\n // Everything else is the user editing forward — typing over a\n // selection, or peeling a divider off with Backspace — so the divider\n // only goes back when erasing it would re-segment text this edit never\n // touched. That text sits past the cut and the mask already formatted\n // it, so it has to come back out unchanged and at the end. Retyping a\n // CPF over \"012.153.441\" keeps its \"-39\" either way, and backspacing\n // the \") \" out of \"(111) -3333\" still erodes down to \"(111-3333\"; but\n // erasing the second \"/\" of \"13//1986\" would read the year as\n // \"13/19/86\", so that one is put back.\n // Measured from the surviving text's first *data* character: the mask\n // owns how dividers render, and a cut that stopped mid-divider leaves\n // a fragment it may legitimately absorb — deleting the \")\" out of\n // \"(111) -4444\" is still the documented erosion down to \"(111-4444\",\n // even though the stranded space goes with it. Field characters are\n // the thing that must not move.\n const suffix = lastMaskedValue.slice(cutStart + removedLength)\n let dataStart = 0\n while (dataStart < suffix.length) {\n const ch = String.fromCodePoint(suffix.codePointAt(dataStart)!)\n if (isData(ch)) break\n dataStart += ch.length\n }\n const untouchedTail = suffix.slice(dataStart)\n if (destroyedFieldData || !format(rawValue, pos, editEager).value.endsWith(untouchedTail)) {\n formatValue = rescued\n }\n }\n }\n const m = format(formatValue, pos, editEager)\n target.value = m.value\n setCaret(target, resolveCaretAfterEdit(kind, rawValue, pos, previousLength, m, resolveMask, lastMaskedValue))\n\n lastMaskedValue = target.value\n onChange?.(target.value)\n }\n\n const onCompositionStart = (): void => {\n isComposing = true\n cancelPendingFrames()\n lockInput = false\n }\n\n const onCompositionEnd = (e: Event): void => {\n isComposing = false\n skipNextKeyup = true\n\n const target = e.target as HTMLInputElement\n const pos = getCaret(target)\n const m = format(target.value, pos)\n target.value = m.value\n setCaret(target, m.caret)\n\n lastMaskedValue = target.value\n onChange?.(target.value)\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n if (isComposing) return\n\n // `input` already handled this keystroke (it fires before `keyup`); skip\n // the redundant iOS `keyup` pass so we don't reformat the value twice.\n if (isIos() && skipNextKeyup) {\n skipNextKeyup = false\n return\n }\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n scheduleFrame(() => {\n if (editStillPending(target, oldValue)) {\n lockInput = false\n return\n }\n const pos = target.selectionStart ?? 999\n // No reliable `key` here, so infer delete-vs-insert from the length\n // delta the browser's already-applied default action left behind.\n const isDeleteLike = target.value.length < oldValue.length\n const m = format(target.value, pos, eagerForEdit(eager, isDeleteLike))\n target.value = m.value\n setCaret(target, m.caret)\n lastMaskedValue = target.value\n scheduleFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = Array.from(ke.key).length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= maxLength && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke. Without this guard, a key like\n // Ctrl+A schedules a reformat frame that never gets cancelled (select-all\n // fires no `input` event), and that stray frame's `target.value =\n // m.process()` reassignment races the browser's own pending selection —\n // the reported Firefox bug where selecting all and retyping fast\n // occasionally drops the caret to the start instead of replacing the\n // selection.\n if (!isBackspace && !isDelete && !isCharInsert && !isUnidentified) return\n\n // Bailing here (rather than reformatting) matters when the frame fires\n // before the browser has applied this keystroke's default action — see\n // `editStillPending`'s doc comment. The authoritative `input` handler\n // takes over once the edit actually lands; a collapsed caret (every\n // other test/path exercises) is unaffected by this check.\n const kind: InputEditKind = isBackspace ? 'backspace' : isDelete ? 'delete' : isUnidentified ? 'unidentified' : 'insert'\n scheduleFrame(() => {\n if (editStillPending(target, oldValue)) return\n\n const pos = target.selectionStart ?? 999\n const rawValue = target.value\n const m = format(rawValue, pos, eagerForEdit(eager, isBackspace || isDelete))\n target.value = m.value\n setCaret(target, resolveCaretAfterEdit(kind, rawValue, pos, oldValue.length, m, resolveMask, oldValue))\n\n lastMaskedValue = target.value\n onChange?.(target.value)\n })\n }\n\n return attachBinder(\n input,\n Array.isArray(mask) ? mask.join('|') : mask,\n { autocomplete, autocorrect, autocapitalize, spellcheck },\n maxLength,\n [onPaste, onInput, onCompositionStart, onCompositionEnd, onKey],\n cancelPendingFrames,\n )\n}\n","import type { DecimalMaskOptions, MaskResult } from './types'\nimport { isDigitChar } from './chars'\n\n// ---------------------------------------------------------------------------\n// Option resolution\n// ---------------------------------------------------------------------------\n\ninterface ResolvedDecimalOptions {\n /**\n * `undefined` means an optional, uncapped fraction (default) — the\n * decimal separator and fraction only appear once the user actually types\n * them, and there's no limit on how many digits follow. `0` means no\n * fraction at all. A positive number is a fixed, zero-padded width that's\n * always shown, even before the user types anything.\n */\n decimalPlaces: number | undefined\n /** `undefined` means unlimited (default) — the integer part grows freely. */\n numberPlaces: number | undefined\n segmented: boolean\n separator: string\n decimalSeparator: string\n prefix: string\n suffix: string\n allowNegative: boolean\n}\n\n/** @internal exported for {@link bindDecimal}'s \".\" / \",\" key normalization */\nexport function resolveDecimalOptions(options?: DecimalMaskOptions): ResolvedDecimalOptions {\n const rawPlaces = options?.decimalPlaces\n // Capped at 100 — `Number.prototype.toFixed`'s own limit — so every API\n // accepts the same range and `formatDecimalValue` can never throw where\n // `processDecimal` succeeds.\n const decimalPlaces =\n rawPlaces != null && Number.isFinite(rawPlaces)\n ? Math.min(100, Math.max(0, Math.floor(rawPlaces)))\n : undefined\n const rawNumberPlaces = options?.numberPlaces\n const numberPlaces =\n rawNumberPlaces != null && Number.isFinite(rawNumberPlaces)\n ? Math.max(1, Math.floor(rawNumberPlaces))\n : undefined\n return {\n decimalPlaces,\n numberPlaces,\n segmented: options?.segmented ?? true,\n separator: options?.separator ?? ',',\n decimalSeparator: options?.decimalSeparator ?? '.',\n prefix: options?.prefix ?? '',\n suffix: options?.suffix ?? '',\n allowNegative: options?.allowNegative ?? false,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Digit-stream helpers\n// ---------------------------------------------------------------------------\n\n/** Strip leading zeros from a digit string, always keeping at least one digit. */\nfunction stripLeadingZeros(s: string): string {\n let i = 0\n while (i < s.length - 1 && s[i] === '0') i++\n return s.slice(i)\n}\n\n/** Insert `sep` every 3 digits from the right (e.g. \"1234567\" → \"1,234,567\"). */\nfunction groupThousands(s: string, sep: string): string {\n if (!sep || s.length <= 3) return s\n const parts: string[] = []\n let i = s.length\n while (i > 3) {\n parts.unshift(s.slice(i - 3, i))\n i -= 3\n }\n parts.unshift(s.slice(0, i))\n return parts.join(sep)\n}\n\n/**\n * Turn raw integer digits into their displayed form: zeros stripped, then\n * left-padded to `numberPlaces` (if set), then thousands-grouped (if\n * `segmented`). Shared by {@link applyDecimalMask} and\n * {@link formatDecimalValue} — the caret math in the former also needs\n * `intPart`/`paddedInt` individually to know how many synthetic zeros it\n * inserted, so all three stages are returned rather than just the result.\n */\nfunction formatIntegerPart(\n intDigits: string,\n opts: Pick<ResolvedDecimalOptions, 'numberPlaces' | 'segmented' | 'separator'>,\n): { intPart: string; paddedInt: string; groupedInt: string } {\n const intPart = stripLeadingZeros(intDigits || '0')\n const paddedInt = opts.numberPlaces != null ? intPart.padStart(opts.numberPlaces, '0') : intPart\n const groupedInt = opts.segmented ? groupThousands(paddedInt, opts.separator) : paddedInt\n return { intPart, paddedInt, groupedInt }\n}\n\n/**\n * Rewrite `Number` exponential notation (\"1e+21\", \"1.5e-7\") as plain\n * positional digits. `String()` and `toFixed()` fall back to exponential form\n * for |values| ≥ 1e21 (`String()` also for tiny fractions), and that text\n * would otherwise reach the digit-oriented formatter, which mangles it —\n * grouping \"1e+21\" into \"1e,+21\". Strings without an exponent pass through\n * unchanged.\n */\nfunction expandExponent(s: string): string {\n const e = s.indexOf('e')\n if (e < 0) return s\n const exponent = Number(s.slice(e + 1))\n const mantissa = s.slice(0, e)\n const dot = mantissa.indexOf('.')\n const digits = dot < 0 ? mantissa : mantissa.slice(0, dot) + mantissa.slice(dot + 1)\n const point = (dot < 0 ? mantissa.length : dot) + exponent\n if (point <= 0) return '0.' + '0'.repeat(-point) + digits\n if (point >= digits.length) return digits + '0'.repeat(point - digits.length)\n return digits.slice(0, point) + '.' + digits.slice(point)\n}\n\n/**\n * Find the position in `s` that leaves exactly `digitsBefore` digit\n * characters preceding it — the position immediately after that digit and\n * before any subsequent literal (grouping separator, ...), so the caret\n * stays glued to the last digit the user placed there.\n */\nfunction caretForDigitsBefore(s: string, digitsBefore: number): number {\n if (digitsBefore <= 0) return 0\n let count = 0\n for (let i = 0; i < s.length; i++) {\n if (isDigitChar(s[i])) {\n count++\n if (count === digitsBefore) return i + 1\n }\n }\n return s.length\n}\n\n// ---------------------------------------------------------------------------\n// Segmented parsing — integer digits before the decimal separator, fraction\n// digits after. Unlike a slot-pattern mask, the integer segment has no fixed\n// length. The fraction is only fixed-width when `decimalPlaces` is set to a\n// positive number — zero-padded on the right so a shorter fraction reads as\n// its low-order (trailing) digits being zero rather than reflowing/shifting\n// (e.g. editing \"423,42\" down to \"423,4\" produces \"423,40\", not \"42,34\").\n// When `decimalPlaces` is left unset, the fraction is optional and uncapped:\n// it only exists once the user types the separator, and grows to however\n// many digits they type.\n//\n// The decimal separator only has meaning as the *first* occurrence of\n// `opts.decimalSeparator`; every other non-digit character (thousands\n// separator, prefix/suffix text, a second stray separator, ...) is noise and\n// is dropped. This keeps re-parsing an already-masked value idempotent.\n// ---------------------------------------------------------------------------\n\ninterface DecimalParts {\n isNegative: boolean\n intDigits: string\n fracDigits: string\n hasSeparator: boolean\n}\n\n/** A raw value split into its sign, its editable number text, and where that text starts. */\ninterface AffixSplit {\n /** `'-'` when a leading sign was stripped, `''` otherwise. */\n sign: string\n /** The value with sign, prefix and suffix removed — the part the user is really editing. */\n body: string\n /** Index in the original string where `body` begins. */\n bodyStart: number\n}\n\n/**\n * Peel the sign, prefix and suffix off a raw value.\n *\n * The prefix and suffix are chrome, not content, so they must not reach the\n * digit parser at all: a prefix like `\"Q1 \"` would otherwise donate its `1`\n * to the number on every keystroke, and one like `\"No. \"` would have its `.`\n * read as the decimal separator and collapse the whole value. Everything\n * downstream works on `body` so affix text simply cannot be misread.\n *\n * Matching is deliberately strict — an affix is only peeled when it is\n * actually sitting at its edge. Mid-edit a value may have a partly deleted\n * prefix, and then nothing is stripped and the old \"drop unknown characters\n * as noise\" behavior still applies.\n */\nfunction splitAffixes(raw: string, opts: ResolvedDecimalOptions): AffixSplit {\n let start = 0\n let sign = ''\n // The prefix is tried at index 0 *before* a leading \"-\" is read as a sign,\n // so a prefix that itself begins with \"-\" is not mistaken for one. Only\n // when the prefix does not match there does a leading \"-\" become the sign,\n // and the prefix is then looked for just after it — which is exactly how\n // the formatter lays a negative value out (\"-\" + prefix + number).\n if (opts.prefix && raw.startsWith(opts.prefix)) {\n start = opts.prefix.length\n } else {\n if (opts.allowNegative && raw[0] === '-') {\n sign = '-'\n start = 1\n }\n if (opts.prefix && raw.startsWith(opts.prefix, start)) start += opts.prefix.length\n }\n\n let end = raw.length\n if (opts.suffix && raw.endsWith(opts.suffix) && end - opts.suffix.length >= start) {\n end -= opts.suffix.length\n }\n\n return { sign, body: raw.slice(start, end), bodyStart: start }\n}\n\nfunction computeDecimalParts(raw: string, opts: ResolvedDecimalOptions): DecimalParts {\n const split = splitAffixes(raw, opts)\n let intDigits = ''\n let fracDigits = ''\n let isNegative = split.sign === '-'\n let inFraction = false\n const canHaveFraction = opts.decimalPlaces !== 0\n\n for (const ch of split.body) {\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (opts.decimalPlaces == null || fracDigits.length < opts.decimalPlaces) fracDigits += ch\n } else if (opts.numberPlaces == null || intDigits.length < opts.numberPlaces) {\n intDigits += ch\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n continue\n }\n // A sign character anywhere in the body sets (not toggles) the sign —\n // \"-\" always forces negative, \"+\" always forces positive, regardless of\n // what the value's sign was before. Scanning left to right, the last\n // sign character in the body wins, so alternating \"+\"/\"-\" (however\n // unlikely outside of a paste) resolves the same way a human reads it:\n // by the one typed last.\n if (ch === '-' && opts.allowNegative) isNegative = true\n else if (ch === '+' && opts.allowNegative) isNegative = false\n // Anything else — thousands separator, prefix/suffix text, a repeated\n // separator, stray letters — is noise and is dropped.\n }\n\n return { isNegative, intDigits, fracDigits, hasSeparator: inFraction }\n}\n\n/**\n * Walk `raw[0:caret]` to find which segment the caret sits in (integer or\n * fraction) and how many digits of that segment precede it, so the same\n * position can be re-derived in the freshly formatted output.\n */\nfunction locateCaretSegment(\n raw: string,\n caret: number,\n opts: ResolvedDecimalOptions,\n): { inFraction: boolean; digitsBefore: number } {\n const canHaveFraction = opts.decimalPlaces !== 0\n let inFraction = false\n let digitsBefore = 0\n\n for (let i = 0; i < caret; i++) {\n const ch = raw[i]\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (opts.decimalPlaces == null || digitsBefore < opts.decimalPlaces) digitsBefore++\n } else if (opts.numberPlaces == null || digitsBefore < opts.numberPlaces) {\n digitsBefore++\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n digitsBefore = 0\n }\n }\n\n return { inFraction, digitsBefore }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a decimal/currency mask to a value, producing the masked output and\n * a computed caret position. Digits typed before the decimal separator\n * extend the integer part; the fraction only starts once the separator is\n * typed. With a fixed `decimalPlaces` it's always displayed zero-padded to\n * that width, even before the user types it; left unset, the fraction is\n * optional — it only appears once the separator is typed, and is shown\n * exactly as typed (no padding, no cap on how many digits).\n */\nexport function applyDecimalMask(\n value: string,\n inputCaret = 0,\n options?: DecimalMaskOptions,\n): MaskResult {\n const opts = resolveDecimalOptions(options)\n if (!value) return { value: '', caret: 0 }\n\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (intDigits === '' && fracDigits === '' && !hasSeparator) {\n // No digits typed yet, but with `allowNegative` a lone \"-\" still counts:\n // an otherwise-empty field the user just typed \"-\" into stays negative\n // (shown as just the sign, plus the prefix so it reads as \"-$\" rather\n // than losing which currency it is) instead of collapsing back to fully\n // empty. There's nothing to pad or group without real digits, so the\n // fraction and any numberPlaces padding still wait for the first one.\n // Deleting that \"-\" (or typing \"+\", which clears `isNegative` the same\n // way it does everywhere else) removes the only content left and this\n // falls back to the plain empty case below — \"positive\" here just means\n // \"no sign to show\".\n if (isNegative) {\n const signOutput = '-' + opts.prefix\n return { value: signOutput, caret: signOutput.length }\n }\n return { value: '', caret: 0 }\n }\n\n const { intPart, paddedInt, groupedInt } = formatIntegerPart(intDigits, opts)\n const fracPadded =\n opts.decimalPlaces != null && opts.decimalPlaces > 0\n ? fracDigits.padEnd(opts.decimalPlaces, '0')\n : fracDigits\n const showFraction = opts.decimalPlaces === 0 ? false : opts.decimalPlaces != null || hasSeparator\n const numberStr = groupedInt + (showFraction ? opts.decimalSeparator + fracPadded : '')\n const signStr = isNegative ? '-' : ''\n const output = signStr + opts.prefix + numberStr + opts.suffix\n\n // The caret is resolved inside the *body* for the same reason parsing is:\n // affix characters must not be counted as digits, and a caret parked in the\n // prefix (or out past the suffix) collapses to the nearest edge of the\n // number rather than landing somewhere inside the chrome.\n const split = splitAffixes(value, opts)\n const bodyCaret = Math.max(0, Math.min(inputCaret - split.bodyStart, split.body.length))\n const { inFraction, digitsBefore } = locateCaretSegment(split.body, bodyCaret, opts)\n const prefixLen = signStr.length + opts.prefix.length\n // Left-padding zeros are synthetic — prepended ahead of every real typed\n // digit — so they shift where the caret's `digitsBefore`-th real digit\n // lands in `groupedInt` by the padding's width.\n const padLength = paddedInt.length - intPart.length\n const caret = inFraction\n ? prefixLen + groupedInt.length + opts.decimalSeparator.length + digitsBefore\n : prefixLen + caretForDigitsBefore(groupedInt, digitsBefore + padLength)\n\n return { value: output, caret }\n}\n\n/** Apply a decimal mask to a raw value and return just the masked string. */\nexport function processDecimal(value: string, options?: DecimalMaskOptions): string {\n return applyDecimalMask(value, value.length, options).value\n}\n\n/**\n * Parse a raw or already-masked decimal value back into a JS number.\n * Ignores prefix/suffix/thousands separator; returns `0` for an empty or\n * digit-less value.\n *\n * @remarks IEEE-754 doubles represent integers exactly only up to\n * `Number.MAX_SAFE_INTEGER` (2^53 - 1, 16 digits). A masked value whose\n * integer part is longer — reachable whenever `numberPlaces` is left unset,\n * which is the default — parses back silently rounded (e.g. a typed\n * \"...345678\" can read back as \"...345680\"). The masked *string* stays\n * exact; only this numeric conversion loses digits. Fields that must not\n * lose one (money, identifiers) should either set `numberPlaces`, or check\n * {@link isDecimalValueSafe} first and fall back to parsing the masked\n * string with `BigInt`/a decimal library when it returns `false`.\n */\nexport function unmaskDecimal(value: string, options?: DecimalMaskOptions): number {\n const opts = resolveDecimalOptions(options)\n const { isNegative, intDigits, fracDigits } = computeDecimalParts(value, opts)\n const n = Number(fracDigits ? `${intDigits || '0'}.${fracDigits}` : intDigits || '0')\n // Never `-0`: a lone \"-\" with no digits is documented to parse as plain 0.\n return isNegative && n !== 0 ? -n : n\n}\n\n/**\n * Whether {@link unmaskDecimal}`(value, options)` can represent `value`'s\n * integer part exactly as a JS number, per the precision limit documented\n * there. Cheap enough to call on every change for a field where an\n * imprecise number would matter (money, account/ID-shaped fields).\n */\nexport function isDecimalValueSafe(value: string, options?: DecimalMaskOptions): boolean {\n const opts = resolveDecimalOptions(options)\n const { intDigits } = computeDecimalParts(value, opts)\n // 15 digits is always safe regardless of leading-digit magnitude; 16\n // sometimes is (up to 9,007,199,254,740,991) and sometimes isn't, so it's\n // deliberately treated as unsafe rather than checked digit-by-digit.\n return stripLeadingZeros(intDigits || '0').length <= 15\n}\n\n/**\n * After a Backspace removes the decimal separator itself, the integer and\n * fraction digit runs collapse into one continuous stream (e.g. \"25.00\"\n * with the caret right after \".\" → Backspace deletes the \".\" → \"2500\").\n * Left alone, that reads as one big integer (\"$2,500.00\"). This restores\n * the segment boundary instead: the trailing `decimalPlaces` digits are\n * still the fraction, and the digit right before them — the one that used\n * to sit at the end of the integer part — is the one Backspace actually\n * removed, so it's dropped (not kept) — \"$25.00\" → \"$2.00\".\n *\n * Only applies when `decimalPlaces` is a fixed positive number — that's\n * what \"the trailing N digits are the fraction\" means. Every reformat\n * re-appends `decimalSeparator` in that case, so its absence from `value`\n * is an unambiguous signal that this exact keystroke just deleted it — no\n * \"value before this keystroke\" snapshot is needed. With `decimalPlaces`\n * unset (optional, uncapped fraction) there's no fixed width to reconstruct\n * from, so the merged digits are left as one continuous integer instead —\n * the same reasoning `numberPlaces` uses for an unbounded integer part.\n * Returns `null` when there's nothing to restore (the separator is still\n * present, `decimalPlaces` is `0` or unset, or too few digits remain), so\n * the caller falls through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskUnmergingSeparator(\n value: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n if (opts.decimalPlaces == null || opts.decimalPlaces <= 0) return null\n\n const { isNegative, intDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (hasSeparator || intDigits.length < opts.decimalPlaces + 1) return null\n\n const preMergeIntLength = intDigits.length - opts.decimalPlaces\n const fracDigits = intDigits.slice(preMergeIntLength)\n const remainingInt = intDigits.slice(0, preMergeIntLength - 1)\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + remainingInt + opts.decimalSeparator + fracDigits\n return applyDecimalMask(raw, signPart.length + remainingInt.length, opts)\n}\n\n/**\n * Move a character the browser just inserted outside the editable number — at\n * or before the sign/prefix, at or after the suffix — to the nearest edge of\n * the number instead.\n *\n * The affixes are chrome, not content. Clicking at the far left of `\"$0.00\"`\n * and typing `\"2\"` means \"make this two dollars\", not \"put a 2 to the left of\n * the dollar sign\". Left alone the keystroke lands outside the number, reads\n * back as an extra leading digit (`\"$20.00\"`), and — worse — a caret one\n * position further right, which looks identical to the user, behaves\n * completely differently.\n *\n * Takes the inserted *length* rather than the text so it stays correct after\n * an earlier pass has rewritten the character in place (a numeric keypad's\n * `\",\"` normalized to the configured decimal separator, say); whatever now\n * occupies that span is what gets moved.\n *\n * `value`/`caret` are the state *after* the browser applied the insertion,\n * the same post-insertion snapshot the rest of this module expects. Returns\n * `null` when the character already landed inside the number, so the caller\n * carries on with the value it has.\n */\nexport function relocateAffixInsertion(\n value: string,\n caret: number,\n insertedLength: number,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n if (insertedLength <= 0) return null\n const insertIdx = caret - insertedLength\n if (insertIdx < 0 || caret > value.length) return null\n\n // The value as it stood before this keystroke — the only form in which the\n // affixes are guaranteed to still be sitting at their own edges.\n const before = value.slice(0, insertIdx) + value.slice(caret)\n const opts = resolveDecimalOptions(options)\n const { bodyStart, body } = splitAffixes(before, opts)\n const editEnd = bodyStart + body.length\n\n const moveTo = insertIdx < bodyStart ? bodyStart : insertIdx > editEnd ? editEnd : -1\n if (moveTo < 0) return null\n\n const inserted = value.slice(insertIdx, caret)\n return {\n value: before.slice(0, moveTo) + inserted + before.slice(moveTo),\n caret: moveTo + insertedLength,\n }\n}\n\n/**\n * Special-cases typing a single digit into an integer segment that isn't\n * yet full of *real* digits — either because it's still the auto-inserted\n * zero placeholder (\"0\", or a wider \"00\" from a `numberPlaces`-padded field\n * that hasn't been touched), or because `numberPlaces` is left-padding a\n * partially-typed segment with synthetic zeros (e.g. \"02\" is really just\n * the one real digit \"2\", padded out to width 2 for display). Those padding\n * zeros aren't editable content, so the new digit extends the real digit\n * stream instead of combining with a padding zero at the caret:\n *\n * - \"$0.00\" + \"2\" → \"$2.00\" (not \"$20.00\")\n * - a `numberPlaces: 2` time field's untouched \"00:00\" + \"5\" → \"05:00\"\n * - that same field's \"02:00\" (one real digit, one padding zero) + \"4\" →\n * \"24:00\" — the real \"2\" is kept, the padding \"0\" is not\n *\n * A segment that's already full of real digits — e.g. \"23:00\", both digits\n * genuinely typed — doesn't match here and falls through to the default\n * {@link applyDecimalMask}, which already drops the overflow keystroke\n * (typing a 3rd real digit leaves \"23:00\" unchanged).\n *\n * `value`/`caret` must be the state *after* the browser has already\n * inserted `digit` at `caret - 1` (the same post-insertion snapshot\n * `applyDecimalMask` itself expects from `bindDecimal`). Returns `null`\n * when the pattern doesn't apply, so the caller falls through to a plain\n * {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskReplacingLoneZero(\n value: string,\n caret: number,\n digit: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n const insertIdx = caret - 1\n if (insertIdx < 0 || value[insertIdx] !== digit) return null\n\n const withoutDigit = value.slice(0, insertIdx) + value.slice(caret)\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(withoutDigit, opts)\n\n const realDigits = stripLeadingZeros(intDigits || '0')\n const hasRealDigits = realDigits !== '0'\n const hasPaddingRoom =\n hasRealDigits && opts.numberPlaces != null && realDigits.length < opts.numberPlaces\n if (hasRealDigits && !hasPaddingRoom) return null\n\n const prefixLen = (isNegative ? 1 : 0) + opts.prefix.length\n if (insertIdx < prefixLen || insertIdx > prefixLen + intDigits.length) return null\n\n const signPart = isNegative ? '-' : ''\n const newIntDigits = (hasRealDigits ? realDigits : '') + digit\n const raw = signPart + newIntDigits + (hasSeparator ? opts.decimalSeparator + fracDigits : '')\n return applyDecimalMask(raw, signPart.length + newIntDigits.length, opts)\n}\n\n/**\n * Format a plain JS number into its masked display string. With a fixed\n * `decimalPlaces` the fraction is rounded/padded to that exact width, even\n * for a whole number; left unset, the fraction is only shown when the value\n * actually has one, with as many digits as `value` naturally carries (no\n * padding, no rounding).\n */\nexport function formatDecimalValue(value: number, options?: DecimalMaskOptions): string {\n const opts = resolveDecimalOptions(options)\n if (!Number.isFinite(value)) return ''\n\n const isNegative = opts.allowNegative && value < 0\n const abs = Math.abs(value)\n let fixed = opts.decimalPlaces != null ? abs.toFixed(opts.decimalPlaces) : String(abs)\n if (fixed.indexOf('e') >= 0) {\n // With `decimalPlaces` set, only |values| ≥ 1e21 reach here (`toFixed`\n // never exponentiates below that) and those floats are exact integers —\n // so expanding `String(abs)` loses nothing, and the fixed-width fraction\n // is pure zero padding.\n fixed = expandExponent(String(abs)) +\n (opts.decimalPlaces ? '.' + '0'.repeat(opts.decimalPlaces) : '')\n }\n const dotIdx = fixed.indexOf('.')\n const intRaw = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx)\n const fracPart = dotIdx === -1 ? '' : fixed.slice(dotIdx + 1)\n const { groupedInt } = formatIntegerPart(intRaw, opts)\n const showFraction = opts.decimalPlaces === 0 ? false : fracPart !== ''\n const numberStr = groupedInt + (showFraction ? opts.decimalSeparator + fracPart : '')\n\n return (isNegative ? '-' : '') + opts.prefix + numberStr + opts.suffix\n}\n","import {\n attachBinder,\n createFrameScheduler,\n editStillPending,\n getCaret,\n isAlreadyBound,\n setCaret,\n} from './bind-shared'\nimport { isDigitChar } from './chars'\nimport {\n applyDecimalMask,\n applyDecimalMaskReplacingLoneZero,\n applyDecimalMaskUnmergingSeparator,\n relocateAffixInsertion,\n resolveDecimalOptions,\n unmaskDecimal,\n} from './decimal-mask'\nimport { isIos } from './platform'\nimport type { BindDecimalOptions, DecimalMaskOptions, MaskResult } from './types'\n\nfunction toBindDecimalOptions(\n second:\n | BindDecimalOptions\n | ((value: string, numericValue: number) => void)\n | null\n | undefined,\n): BindDecimalOptions {\n if (second == null) return {}\n if (typeof second === 'function') return { onChange: second }\n return second\n}\n\ninterface DecimalEdit {\n insertedText?: string | null\n insertedAt?: number\n inputType?: string\n}\n\n/**\n * Bind a decimal/currency mask to an input element.\n *\n * Same contract as {@link bind}: idempotent (marked with `data-masked`),\n * returns a dispose function, and reformats post-mutation `input` events with\n * a keyboard/paste fallback for older browsers. Unlike the pattern masks,\n * there is no fixed pattern — the integer part grows and shrinks freely;\n * formatting is driven entirely by `options`.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.\n */\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n options?: BindDecimalOptions | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n onChange: ((value: string, numericValue: number) => void) | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n second?: BindDecimalOptions | ((value: string, numericValue: number) => void) | null,\n): () => void {\n if (isAlreadyBound(input)) return () => {}\n\n const {\n onChange,\n autocomplete,\n autocorrect,\n autocapitalize,\n spellcheck,\n ...maskOptions\n } = toBindDecimalOptions(second)\n const decimalOptions: DecimalMaskOptions = maskOptions\n const { decimalSeparator, decimalPlaces } = resolveDecimalOptions(decimalOptions)\n\n let lockInput = false\n let isComposing = false\n let skipNextKeyup = false\n let pendingSeparatorEdit: { text: string; starts: number[] } | null = null\n const { scheduleFrame, cancelPendingFrames } = createFrameScheduler()\n\n const applyResult = (target: HTMLInputElement, m: MaskResult): void => {\n target.value = m.value\n setCaret(target, m.caret)\n onChange?.(m.value, unmaskDecimal(m.value, decimalOptions))\n }\n\n const formatCurrentValue = (target: HTMLInputElement, edit: DecimalEdit = {}): void => {\n let pos = getCaret(target)\n\n const normalizeSeparator = (text: string, starts: Array<number | undefined>): boolean => {\n if (\n decimalPlaces === 0 ||\n text.length !== 1 ||\n (text !== '.' && text !== ',') ||\n text === decimalSeparator\n ) {\n return false\n }\n\n for (const start of starts) {\n if (\n start != null &&\n start >= 0 &&\n target.value.slice(start, start + text.length) === text\n ) {\n target.value =\n target.value.slice(0, start) +\n decimalSeparator +\n target.value.slice(start + text.length)\n pos = start + text.length <= pos ? pos + decimalSeparator.length - text.length : pos\n setCaret(target, pos)\n return true\n }\n }\n\n return false\n }\n\n if (pendingSeparatorEdit) {\n const { text, starts } = pendingSeparatorEdit\n pendingSeparatorEdit = null\n normalizeSeparator(text, starts)\n }\n\n // Backspace that just deleted the decimal separator merges the integer and\n // fraction digit runs into one continuous stream. On mobile this can arrive\n // as an `input` event without a reliable keyboard event, so the special case\n // lives in the shared post-mutation formatter.\n if (edit.inputType === 'deleteContentBackward') {\n const unmerged = applyDecimalMaskUnmergingSeparator(target.value, decimalOptions)\n if (unmerged) {\n applyResult(target, unmerged)\n return\n }\n }\n\n // A numeric keypad (or a locale mismatch) may only offer \".\" or \",\".\n // Normalize whichever one was just inserted to the configured\n // `decimalSeparator` before parsing, so mobile `input`-only edits still open\n // the fraction segment correctly.\n const insertedText = edit.insertedText\n if (insertedText != null) {\n normalizeSeparator(insertedText, [edit.insertedAt, pos - insertedText.length])\n }\n\n // The prefix and suffix are chrome, not content. A character the browser\n // dropped into them — caret parked at the far left of \"$0.00\", or out past\n // a suffix — is pulled to the nearest edge of the number, so every caret\n // position that *looks* like \"the start of the number\" behaves like it.\n // Runs after separator normalization so it moves whatever now occupies\n // that span, not the key the user originally pressed.\n if (insertedText != null && insertedText.length > 0) {\n const relocated = relocateAffixInsertion(\n target.value,\n pos,\n insertedText.length,\n decimalOptions,\n )\n if (relocated) {\n target.value = relocated.value\n pos = relocated.caret\n }\n }\n\n // Typing a digit into a field whose integer part isn't yet full of real\n // digits extends the real digit stream instead of combining with a padding\n // zero.\n const insertedDigit =\n insertedText != null && insertedText.length === 1 && isDigitChar(insertedText)\n ? insertedText\n : undefined\n const replaced = insertedDigit\n ? applyDecimalMaskReplacingLoneZero(target.value, pos, insertedDigit, decimalOptions)\n : null\n\n applyResult(target, replaced ?? applyDecimalMask(target.value, pos, decimalOptions))\n }\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n scheduleFrame(() => {\n formatCurrentValue(target)\n })\n }\n\n // Deliberately does NOT bail out while `isComposing` is true — see the\n // matching comment in `bind.ts`. Android wraps typing in a full-QWERTY\n // text field (e.g. a decimal input without `inputmode=\"decimal\"`) into an\n // IME composition session for autocorrect bookkeeping, not just genuine\n // multi-candidate input, and that composition may never end while the\n // user is still entering a space-less value — so waiting for\n // `compositionend` before formatting made the mask appear broken there.\n const onInput = (e: Event): void => {\n const inputEvent = e as InputEvent\n const target = e.target as HTMLInputElement\n cancelPendingFrames()\n lockInput = false\n pendingSeparatorEdit = null\n skipNextKeyup = true\n formatCurrentValue(target, {\n insertedText: typeof inputEvent.data === 'string' ? inputEvent.data : null,\n inputType: inputEvent.inputType,\n })\n }\n\n const onCompositionStart = (): void => {\n isComposing = true\n cancelPendingFrames()\n lockInput = false\n pendingSeparatorEdit = null\n }\n\n const onCompositionEnd = (e: Event): void => {\n isComposing = false\n skipNextKeyup = true\n formatCurrentValue(e.target as HTMLInputElement)\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const keyStart = getCaret(target)\n const oldValue = target.value\n\n if (isComposing) return\n\n if (isIos() && skipNextKeyup) {\n skipNextKeyup = false\n return\n }\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n scheduleFrame(() => {\n if (editStillPending(target, oldValue)) {\n lockInput = false\n return\n }\n formatCurrentValue(target)\n scheduleFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n if (\n isCharInsert &&\n decimalPlaces !== 0 &&\n (ke.key === '.' || ke.key === ',') &&\n ke.key !== decimalSeparator\n ) {\n pendingSeparatorEdit = { text: ke.key, starts: [keyStart, keyStart - ke.key.length] }\n }\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke.\n if (!isBackspace && !isDelete && !isCharInsert && !isUnidentified) return\n\n // Everything below reads `target.value`/`selectionStart` inside the rAF\n // callback rather than synchronously here, since the browser's native\n // character insertion for this keystroke isn't guaranteed to have landed\n // yet at the point a keydown listener runs — only by the next frame.\n scheduleFrame(() => {\n // Formatting now would collapse a still-pending native selection via\n // `setCaret` inside `formatCurrentValue` before the edit lands — see\n // `editStillPending`'s doc comment (the same Firefox race fixed in\n // `bind.ts`).\n if (editStillPending(target, oldValue)) return\n\n formatCurrentValue(target, {\n insertedText: isCharInsert ? ke.key : null,\n insertedAt: isCharInsert ? keyStart : undefined,\n inputType: isBackspace\n ? 'deleteContentBackward'\n : isDelete\n ? 'deleteContentForward'\n : undefined,\n })\n })\n }\n\n return attachBinder(\n input,\n 'decimal',\n { autocomplete, autocorrect, autocapitalize, spellcheck },\n Infinity,\n [onPaste, onInput, onCompositionStart, onCompositionEnd, onKey],\n cancelPendingFrames,\n )\n}\n","import { applyMask } from './apply-mask'\nimport type { ApplyMaskOptions, MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: MaskPattern\n private readonly _options: ApplyMaskOptions | undefined\n\n constructor(value: string, mask: MaskPattern, caret = 0, options?: ApplyMaskOptions) {\n this._value = value\n this._mask = mask\n this.caret = caret\n this._options = options\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret, this._options)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance; array patterns are resolved by data count when `process()` runs. */\nexport function buildMask(\n value: string,\n mask: MaskPattern,\n caret = 0,\n options?: ApplyMaskOptions,\n): Mask {\n return new Mask(value, mask, caret, options)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string {\n return buildMask(value, mask, 0, options).process()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mother-mask.cjs","names":[],"sources":["../src/chars.ts","../src/pattern.ts","../src/apply-mask.ts","../src/platform.ts","../src/bind-shared.ts","../src/bind.ts","../src/decimal-mask.ts","../src/bind-decimal.ts","../src/mask.ts"],"sourcesContent":["/** Shared by the mask-pattern engine and the decimal engine, which both need to spot ASCII digits. */\nexport function isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n","import type { ApplyMaskOptions, MaskPattern, MaskResult, MaskTokens, TokenMatcher } from './types'\nimport { isDigitChar } from './chars'\n\n/** A slot consumes one code point. Source/caret offsets remain UTF-16 DOM offsets. */\nexport interface Slot {\n match: (char: string) => boolean\n transform?: (char: string) => string\n maxLength: number\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction isDataChar(ch: string): boolean {\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\nconst builtins: ReadonlyArray<readonly [string, Slot]> = [\n ['9', { match: isDigitChar, maxLength: 1 }],\n ['Z', { match: isLetterChar, maxLength: 1 }],\n ['A', { match: isDataChar, maxLength: 1 }],\n]\n\nfunction matcher(match: TokenMatcher): (char: string) => boolean {\n if (typeof match === 'function') return match\n // Private, stateless copy. Never mutate a caller's RegExp (even a frozen one).\n const regex = new RegExp(match.source, match.flags.replace(/[gy]/g, ''))\n return (char) => regex.test(char)\n}\n\n/**\n * One representative code point per script whose IME holds a provisional\n * draft that reads nothing like what it commits — Pinyin/Zhuyin or Cangjie\n * romanizations resolving to a Han character, Kana toggling into Kanji,\n * jamo assembling into a Hangul syllable. `bind()` must leave composition\n * alone there, or a live reformat mid-draft overwrites text the IME still\n * expects to revise (see `android-fast-typing.test.ts`'s \"nihao\" → \"你\" case).\n *\n * A custom token whose alphabet provably never matches any of these can't\n * ever receive that kind of draft: whatever such an IME assembles, this\n * mask's own data check would filter it out exactly the same way once\n * composition ends, so reformatting one instant early changes nothing.\n * Plain ASCII/Latin alphabets (e.g. an uppercase-transforming alphanumeric\n * token) fall in this safe case — see `hasComposingRisk` below.\n */\nconst COMPOSING_SCRIPT_PROBES = ['中', 'あ', 'ア', '가']\n\n/** Whether a token's alphabet could ever accept one of {@link COMPOSING_SCRIPT_PROBES}. */\nfunction mayAcceptComposedScript(match: TokenMatcher): boolean {\n const test = matcher(match)\n return COMPOSING_SCRIPT_PROBES.some((ch) => {\n try {\n return test(ch)\n } catch {\n // A predicate that throws on ordinary input isn't provably safe either way.\n return true\n }\n })\n}\n\n/**\n * Representative non-BMP code points spanning the general categories a\n * class-based alphabet (`\\p{L}`, `\\p{N}`, `\\p{Emoji}`, a bespoke character\n * class, ...) is typically built from — one letter, one number, one symbol.\n * Each needs a UTF-16 surrogate pair (two code units) to encode, unlike\n * every BMP character (Latin, digits, and even the composing-risk scripts\n * above), which fits in one.\n *\n * A token whose alphabet accepts none of these can't produce a two-unit slot\n * either: it can't match one directly, and per `MaskTokenDefinition.transform`'s\n * documented match-preserving contract, a conforming transform can't turn a\n * one-unit input into a two-unit output that would no longer match. Plain\n * ASCII/BMP alphabets (e.g. an uppercase-transforming hex or alphanumeric\n * token) fall in this safe case — see {@link Slot.maxLength} sizing below.\n */\nconst ASTRAL_PROBES = ['𐐀', '𝟎', '😀']\n\n/** Whether a token's alphabet could ever accept one of {@link ASTRAL_PROBES}. */\nfunction mayAcceptAstral(match: TokenMatcher): boolean {\n const test = matcher(match)\n return ASTRAL_PROBES.some((ch) => {\n try {\n return test(ch)\n } catch {\n // A predicate that throws on ordinary input isn't provably safe either way.\n return true\n }\n })\n}\n\nexport function transformChar(char: string, slot: Slot): string {\n if (!slot.transform) return char\n const output = slot.transform(char)\n if (typeof output !== 'string' || Array.from(output).length !== 1) {\n throw new RangeError('A mask token transform must return exactly one Unicode code point')\n }\n return output\n}\n\nexport interface LiteralToken {\n kind: 'literal'\n text: string\n}\n\n/**\n * Upper bound on a bounded quantifier's repeat count.\n *\n * A run is expanded to `max` real slots at compile time, so an unbounded\n * number here would let a one-line pattern allocate arbitrarily much. No\n * realistic field needs more, and anything larger is treated as malformed\n * (the braces stay literal text) rather than throwing, which keeps the\n * conservative \"unknown brace sequences are literals\" rule intact.\n */\nconst MAX_QUANTIFIER = 1000\n\n/** A parsed `{n}` / `{min,max}` suffix; `end` indexes its closing brace. */\ninterface Quantifier {\n min: number\n max: number\n end: number\n}\n\n/**\n * Read a bounded quantifier whose `{` sits at `points[start]`.\n *\n * Only `{n}` and `{min,max}` with `1 <= min <= max <= MAX_QUANTIFIER` are\n * syntax; `{n,}`, `{,n}`, `{0}`, `{2,1}`, `{}` and anything non-numeric are\n * *not*, and return `undefined` so the caller leaves the braces as ordinary\n * literal characters — exactly how a mask containing them behaved before\n * quantifiers existed. No `*`, `+` or `?` forms are recognized at all.\n */\nfunction parseQuantifier(points: string[], start: number): Quantifier | undefined {\n if (points[start] !== '{') return undefined\n let i = start + 1\n // -1 means \"no digits here\" or \"over the cap\"; both are malformed.\n const readCount = (): number => {\n let n = -1\n while (i < points.length && points[i] >= '0' && points[i] <= '9') {\n n = (n < 0 ? 0 : n) * 10 + (points[i].charCodeAt(0) - 48)\n i++\n if (n > MAX_QUANTIFIER) return -1\n }\n return n\n }\n const min = readCount()\n if (min < 1) return undefined\n let max = min\n if (points[i] === ',') {\n i++\n max = readCount()\n if (max < min) return undefined\n }\n if (points[i] !== '}') return undefined\n return { min, max, end: i }\n}\n\nexport type MaskToken = LiteralToken | { kind: 'slots'; chars: Slot[] }\n\n/**\n * A mask string pre-chewed into the lookups both passes need.\n *\n * \"Run\" throughout means one uninterrupted stretch of slot characters — the\n * segment a user thinks of as a single field (\"999\", \"9999\", …). Runs are\n * numbered in mask order; token indices index {@link CompiledMask.tokens}.\n */\nexport interface CompiledMask {\n maxLength: number\n parts: Array<Slot | LiteralToken>\n dataSlots: Slot[]\n literals: Array<{ text: string; offset: number }>\n hasEscapes: boolean\n /** Alternating literal / slot-run tokens, in mask order. */\n tokens: MaskToken[]\n /** Slot characters of each run (e.g. `[\"999\", \"999\", \"999\", \"99\"]`). */\n runChars: Slot[][]\n /**\n * Fewest slots each run accepts before the literal after it may close it.\n *\n * `runChars[i].length` is the *maximum*; this is the minimum a bounded\n * quantifier declared (`9{1,2}` → `1`). A run with no quantifier — every\n * run in every pattern written before this syntax existed — has\n * `runMin[i] === runChars[i].length`, so \"at minimum but short of maximum\"\n * is vacuously impossible for it and nothing about fixed masks changes.\n */\n runMin: number[]\n /** Index into a flat, run-concatenated slot array where each run starts. */\n runOffset: number[]\n /** Token index of the literal directly before run `i`, or `-1` at the mask start. */\n literalBeforeRun: number[]\n /** Total slot capacity of runs `i..end`; `capacityFromRun[runCount]` is `0`. */\n capacityFromRun: number[]\n /** For each token, the run it *is* (`-1` for literals). */\n runOfToken: number[]\n /** For each literal token, the run directly before it (`-1` when it opens the mask). */\n runBeforeLiteral: number[]\n /** For each literal token, the run directly after it (`-1` when it closes the mask). */\n runAfterLiteral: number[]\n /** Total number of slots in the mask. */\n totalSlots: number\n}\n\n/**\n * Split a mask into alternating literal and slot-run tokens (e.g. \"99/99/9999\"\n * → slots\"99\", literal\"/\", slots\"99\", literal\"/\", slots\"9999\") and derive the\n * run/literal adjacency both passes rely on.\n */\nfunction compileMask(mask: string, definitions: Map<string, Slot>): CompiledMask {\n const tokens: MaskToken[] = []\n const runOfToken: number[] = []\n const runChars: Slot[][] = []\n const runMin: number[] = []\n const runToken: number[] = []\n\n const points = Array.from(mask)\n let maxLength = 0\n let hasEscapes = false\n for (let i = 0; i < points.length; i++) {\n let ch = points[i]\n let escaped = false\n if (ch === '\\\\' && (points[i + 1] === '\\\\' || definitions.has(points[i + 1]))) {\n ch = points[++i]\n escaped = true\n hasEscapes = true\n }\n const slot = escaped ? undefined : definitions.get(ch)\n const previous = tokens[tokens.length - 1]\n if (slot) {\n // A quantifier is only syntax directly after an unescaped token, so an\n // escaped `\\9{1,2}` keeps both the \"9\" and the braces as literal text.\n const quantifier = parseQuantifier(points, i + 1)\n const min = quantifier ? quantifier.min : 1\n const max = quantifier ? quantifier.max : 1\n if (quantifier) i = quantifier.end\n maxLength += slot.maxLength * max\n if (previous?.kind === 'slots') {\n for (let n = 0; n < max; n++) previous.chars.push(slot)\n runMin[runMin.length - 1] += min\n } else {\n const chars: Slot[] = []\n for (let n = 0; n < max; n++) chars.push(slot)\n runOfToken.push(runChars.length)\n runToken.push(tokens.length)\n runChars.push(chars)\n runMin.push(min)\n tokens.push({ kind: 'slots', chars })\n }\n } else {\n maxLength += ch.length\n if (previous?.kind === 'literal') previous.text += ch\n else {\n runOfToken.push(-1)\n tokens.push({ kind: 'literal', text: ch })\n }\n }\n }\n\n const runCount = runChars.length\n const runOffset: number[] = new Array(runCount)\n const literalBeforeRun: number[] = new Array(runCount)\n const capacityFromRun: number[] = new Array(runCount + 1)\n const runBeforeLiteral: number[] = new Array(tokens.length).fill(-1)\n const runAfterLiteral: number[] = new Array(tokens.length).fill(-1)\n\n let offset = 0\n for (let r = 0; r < runCount; r++) {\n runOffset[r] = offset\n offset += runChars[r].length\n // Tokens alternate, so the token just before a run is always a literal\n // when it exists at all.\n literalBeforeRun[r] = runToken[r] > 0 ? runToken[r] - 1 : -1\n }\n\n capacityFromRun[runCount] = 0\n for (let r = runCount - 1; r >= 0; r--) {\n capacityFromRun[r] = capacityFromRun[r + 1] + runChars[r].length\n }\n\n for (let t = 0; t < tokens.length; t++) {\n if (tokens[t].kind !== 'literal') continue\n runBeforeLiteral[t] = t > 0 ? runOfToken[t - 1] : -1\n runAfterLiteral[t] = t + 1 < tokens.length ? runOfToken[t + 1] : -1\n }\n\n const parts: CompiledMask['parts'] = []\n const literals: CompiledMask['literals'] = []\n const dataSlots = new Set<Slot>()\n for (let t = 0; t < tokens.length; t++) {\n const token = tokens[t]\n if (token.kind === 'literal') {\n parts.push(token)\n literals.push({ text: token.text, offset: runAfterLiteral[t] < 0 ? offset : runOffset[runAfterLiteral[t]] })\n } else {\n for (const slot of token.chars) {\n parts.push(slot)\n dataSlots.add(slot)\n }\n }\n }\n\n const compiled: CompiledMask = {\n maxLength,\n hasEscapes,\n dataSlots: [...dataSlots],\n literals,\n parts,\n tokens,\n runChars,\n runMin,\n runOffset,\n literalBeforeRun,\n capacityFromRun,\n runOfToken,\n runBeforeLiteral,\n runAfterLiteral,\n totalSlots: offset,\n }\n return compiled\n}\n\n/** Bounded per-operation/binding cache. No formatted values or callbacks in global caches. */\nexport class PatternCompiler {\n private readonly definitions = new Map(builtins)\n private readonly cache = new Map<string, CompiledMask>()\n private readonly custom: boolean\n /**\n * Whether some custom token's alphabet could ever accept a genuine\n * candidate-IME script (see {@link mayAcceptComposedScript}). `bind()`\n * uses this — not merely \"are there custom tokens at all\" — to decide\n * whether composition must be deferred: an ASCII-only custom alphabet\n * (an uppercase-transforming alphanumeric token, say) can safely reformat\n * live during composition exactly like the built-ins do, since Android's\n * autocorrect otherwise wraps plain Latin typing in a composition session\n * that may never fire `compositionend` while the field has no word\n * boundaries to type through.\n */\n readonly hasComposingRisk: boolean\n\n constructor(tokens?: MaskTokens) {\n this.custom = !!tokens && Object.keys(tokens).length > 0\n let composingRisk = false\n for (const [key, definition] of Object.entries(tokens ?? {})) {\n if (key === '\\\\' || Array.from(key).length !== 1) {\n throw new RangeError('Mask token keys must be one Unicode code point other than backslash')\n }\n const object = typeof definition === 'object' && 'match' in definition\n ? definition : { match: definition }\n if (mayAcceptComposedScript(object.match)) composingRisk = true\n // Reserving 2 UTF-16 units for every custom slot regardless of its\n // alphabet over-sizes `maxLength` (the DOM `maxlength` attribute and\n // `bind()`'s \"block insert when full\" gate share this number) whenever\n // a mask has more than a couple of such slots. That slack lets typing\n // continue past the field's real capacity instead of being blocked,\n // which corrupts a segmented mask's boundaries instead of just\n // refusing the keystroke — reserve the extra unit only where a slot\n // could actually need it.\n this.definitions.set(key, {\n match: matcher(object.match), transform: object.transform,\n maxLength: mayAcceptAstral(object.match) ? 2 : 1,\n })\n }\n this.hasComposingRisk = composingRisk\n }\n\n compile(mask: string): CompiledMask {\n const cached = this.cache.get(mask)\n if (cached) return cached\n const plan = compileMask(mask, this.definitions)\n if (this.cache.size >= 64) this.cache.delete(this.cache.keys().next().value!)\n this.cache.set(mask, plan)\n return plan\n }\n\n isData(char: string, plan?: CompiledMask): boolean {\n if (plan && (this.custom || plan.hasEscapes)) {\n return plan.dataSlots.some((slot) => slot.match(char))\n }\n for (const slot of this.definitions.values()) if (slot.match(char)) return true\n return false\n }\n\n /** Candidate stream from the fallback alphabet, without transformer side effects. */\n data(value: string, caret: number, plans?: CompiledMask[], readLiterals = true): MaskResult & { afterLiteral: boolean } {\n let output = ''\n let source = 0\n let outputCaret = 0\n let count = 0\n let lastLiteralOffset = -1\n let afterLiteral = false\n let slots: Slot[] | undefined\n const literals: CompiledMask['literals'] = []\n if (plans) {\n const unique = new Set<Slot>()\n for (const plan of plans) {\n for (const slot of plan.dataSlots) unique.add(slot)\n if (readLiterals) for (const literal of plan.literals) literals.push(literal)\n }\n slots = [...unique]\n }\n const hasEscapes = plans?.some((plan) => plan.hasEscapes)\n while (source < value.length) {\n // Complete literal runs at their data boundary are formatting, even\n // when a literal itself could match a slot (e.g. an escaped \"A\").\n const literal = lastLiteralOffset !== count && literals?.find((part) =>\n (part.offset === count || hasEscapes) && value.startsWith(part.text, source))\n if (literal) {\n if (source < caret) afterLiteral = true\n source += literal.text.length\n lastLiteralOffset = count\n continue\n }\n const char = String.fromCodePoint(value.codePointAt(source)!)\n const start = source\n source += char.length\n if (!(slots ? slots.some((slot) => slot.match(char)) : this.isData(char))) {\n if (start < caret && literals?.some((part) => value.startsWith(part.text, start))) afterLiteral = true\n continue\n }\n output += char\n count++\n if (source <= caret) {\n outputCaret = output.length\n afterLiteral = false\n }\n }\n return { value: output, caret: outputCaret, afterLiteral }\n }\n\n resolve(value: string, mask: MaskPattern, readLiterals = true): CompiledMask {\n if (!Array.isArray(mask)) return this.compile(mask)\n const plans = mask.map((pattern) => this.compile(pattern))\n const count = Array.from(this.data(value, 0,\n this.custom || plans.some((plan) => plan.hasEscapes) ? plans : undefined, readLiterals).value).length\n let i = 0\n while (i < plans.length - 1 && count > plans[i].totalSlots) i++\n return plans[i] ?? this.compile('')\n }\n}\n\n// Safe to share only the built-in alphabet; this instance never sees user callbacks.\nexport const defaultCompiler = new PatternCompiler()\n\n/** {@link getMaxLength} against an existing compiler — `bind()` reuses its own instead of validating and probing the tokens a second time. */\nexport function maskMaxLength(mask: MaskPattern, compiler: PatternCompiler): number {\n const patterns = Array.isArray(mask) ? mask : [mask]\n let max = 0\n for (const pattern of patterns) max = Math.max(max, compiler.compile(pattern).maxLength)\n return max\n}\n\n/** Maximum formatted UTF-16 length (custom slots allow two units). Infinity for a resolver. */\nexport function getMaxLength(mask: MaskPattern, options?: ApplyMaskOptions): number {\n if (options?.resolveMask) return Infinity\n return maskMaxLength(mask, options?.tokens ? new PatternCompiler(options.tokens) : defaultCompiler)\n}\n","import type { ApplyMaskOptions, MaskPattern, MaskResult } from './types'\nimport { PatternCompiler, defaultCompiler, transformChar } from './pattern'\nimport type { CompiledMask, LiteralToken } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Flat masking (opt-in) — treats the mask as one continuous character\n// stream. Best for continuous identifiers (phone numbers, CPF/CNPJ, credit\n// cards) where deleting/inserting a digit anywhere is expected to reflow\n// every digit after it — this is the classic mother-mask behavior and is\n// relied on by the majority of the test suite (paste, backspace, mid-string\n// insert, etc).\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nfunction applyFlatMask(\n value: string,\n plan: CompiledMask,\n inputCaret: number,\n eager: boolean,\n readLiterals: boolean,\n caretAfterLiteral: boolean,\n): MaskResult {\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n const leading = plan.tokens[0]\n let leadingConsumed = false\n\n for (const part of plan.parts) {\n if ('kind' in part) {\n pending += part.text\n if (readLiterals && value.startsWith(part.text, valueIdx)) {\n valueIdx += part.text.length\n if (part === leading) leadingConsumed = true\n }\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const literal = readLiterals && plan.hasEscapes && plan.literals.find(part => value.startsWith(part.text, valueIdx))\n if (literal) {\n valueIdx += literal.text.length\n continue\n }\n if (readLiterals && !leadingConsumed && leading?.kind === 'literal' &&\n value.startsWith(leading.text, valueIdx)) {\n valueIdx += leading.text.length\n leadingConsumed = true\n continue\n }\n const ch = String.fromCodePoint(value.codePointAt(valueIdx)!)\n valueIdx += ch.length\n\n if (part.match(ch)) {\n // Flush pending literals then write the matched char\n if (caretAfterLiteral && !caretResolved && valueIdx > inputCaret) outputCaret = output.length + pending.length\n output += pending + transformChar(ch, part)\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n // Eager mode: `pending` only survives to here holding the literal(s) that\n // directly follow the slot(s) just filled — see the doc comment on\n // `ApplyMaskOptions.eager`. Reveal it now instead of waiting for the next\n // matching keystroke, and carry the caret past it only if the caret was\n // already sitting at the end of the typed content (never yank it forward\n // during a mid-string edit).\n if (eager && pending) {\n const wasAtEnd = outputCaret === output.length\n output += pending\n if (wasAtEnd) outputCaret = output.length\n }\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Segmented masking (default) — treats literal separators as hard boundaries\n// between independent fields (e.g. day/month/year in \"99/99/9999\"). Editing\n// one segment never bleeds characters into a neighboring one, so replacing\n// the \"12\" in \"25/12/2025\" with a shorter or longer value keeps the year\n// exactly where it is instead of shifting digits across the \"/\".\n//\n// This runs in two passes rather than emitting characters as it scans:\n//\n// 1. **assign** — decide which mask slot each character of `value` lands in,\n// using the separators still present in `value` as positional anchors.\n// 2. **render** — walk the mask and emit the assigned characters plus the\n// literals that are actually justified, tracking the caret as it goes.\n//\n// Splitting them is what makes characters \"stick\" to their segment. A single\n// emit-as-you-scan pass can only ever look one separator ahead, so a value\n// like \"015-39\" (what the browser leaves behind when you select \"012.153.441\"\n// out of \"012.153.441-39\" and type \"015\") had no way to see that the \"-\"\n// pins \"39\" to the *last* segment — it treated the \"-\" as noise and repacked\n// the digits from the left into \"015.39\".\n// ---------------------------------------------------------------------------\n\n/**\n * Text of the separator that introduces run `run`.\n *\n * Tokens alternate, so every run except the very first is preceded by a\n * literal. Both callers only ask about a run they are advancing *into* — never\n * run 0 — so the lookup is always defined.\n */\nfunction separatorBefore(plan: CompiledMask, run: number): string {\n // literalBeforeRun[run] always indexes a literal token by construction (see CompiledMask docs).\n return (plan.tokens[plan.literalBeforeRun[run]] as LiteralToken).text\n}\n\n/**\n * Running count of data (digit/letter) characters in `value` up to each\n * UTF-16 offset; `counts[value.length]` is the total. Built once per\n * {@link assignToSlots} call so {@link remainingDataChars} is an O(1)\n * lookup instead of an O(remaining length) rescan.\n *\n * Without this table, a value containing many characters that read as a\n * separator by text but fail `findAnchor`'s capacity check below — a URL's\n * \"/\" pasted into a \"99/99/9999\" field, say — re-scans the shrinking tail\n * of `value` on every one of those characters: each individual rescan is\n * cheap, but repeating it at every position sums to O(n²) on an ordinary\n * large paste. One O(n) pass here replaces all of them.\n */\nfunction buildDataPrefixCounts(value: string, compiler: PatternCompiler, plan: CompiledMask): Uint32Array {\n const counts = new Uint32Array(value.length + 1)\n let count = 0\n for (let i = 0; i < value.length;) {\n const ch = String.fromCodePoint(value.codePointAt(i)!)\n counts[i] = count\n // A surrogate pair's second code unit must see the same \"before\" count\n // its first unit does — neither is a valid rescan start on its own.\n if (ch.length === 2) counts[i + 1] = count\n i += ch.length\n if (compiler.isData(ch, plan)) count++\n }\n counts[value.length] = count\n return counts\n}\n\n/** Count of remaining slot-matchable (digit/letter) characters in `value` from `fromIdx` onward — O(1) via a precomputed prefix table. */\nfunction remainingDataChars(fromIdx: number, prefixCounts: Uint32Array): number {\n return prefixCounts[prefixCounts.length - 1] - prefixCounts[fromIdx]\n}\n\n/** Where each character of `value` ended up, as produced by {@link assignToSlots}. */\ninterface Assignment {\n /** Flat, run-concatenated slot array: the character in each slot, or `''` when empty. */\n slotChar: string[]\n /** Exclusive source end of each filled code point; meaningless for empty slots. */\n slotSource: number[]\n /** How many slots of each run are filled (always a prefix of the run). */\n runFilled: number[]\n /**\n * Runs the user closed early by typing their own separator.\n *\n * Only ever true for a run holding at least `runMin` but fewer than\n * `runChars.length` characters — a state a bounded quantifier (`9{1,2}`)\n * makes reachable and a fixed run cannot reach at all, since its minimum\n * *is* its maximum. It records the one thing the rendered value would\n * otherwise lose: that \"3/\" is a finished one-digit day, not two digits\n * with the second still to come.\n */\n runCommitted: boolean[]\n /** For each literal token, the `value` index it was consumed from, or `-1` if it wasn't. */\n literalSource: number[]\n}\n\n/**\n * Length of the longest *proper* suffix of `text` sitting at `valueIdx`, or `0`.\n *\n * A selection that ends inside a multi-character separator leaves its tail\n * behind: deleting the \"(555)\" out of \"(555) 123-4567\" hands back\n * \" 123-4567\", where that lone space is all that survives of \") \". Single\n * character separators can never fragment, so this is always `0` for them.\n */\nfunction separatorTailLength(value: string, valueIdx: number, text: string): number {\n // Walk whole code points so a tail can never begin on a lone surrogate.\n for (let start = 0; start < text.length;) {\n start += String.fromCodePoint(text.codePointAt(start)!).length\n if (start < text.length && value.startsWith(text.slice(start), valueIdx)) {\n return text.length - start\n }\n }\n return 0\n}\n\n/**\n * Punctuation, symbols, and space separators — the shapes a person reaches\n * for when they mean \"this field is done\", regardless of which one this\n * particular mask happens to print.\n *\n * Deliberately excludes letters, digits, and every other script: a mistyped\n * \"a\" in a date field is a typo, not a decision to close the day, so it stays\n * the noise it has always been. See {@link isSeparatorIntent}.\n */\nconst SEPARATOR_INTENT = /[\\p{P}\\p{S}\\p{Zs}]/u\n\n/**\n * Whether `char` reads as a divider the user typed on purpose.\n *\n * A mask's own alphabet always wins: a custom token matching \".\" makes \".\"\n * content in that mask, never a boundary, so it is never a stand-in there.\n */\nfunction isSeparatorIntent(char: string, compiler: PatternCompiler, plan: CompiledMask): boolean {\n return SEPARATOR_INTENT.test(char) && !compiler.isData(char, plan)\n}\n\n/** A separator resolved to the segment it introduces, plus how much of `value` it occupies. */\ninterface Anchor {\n run: number\n /**\n * UTF-16 units to consume: the whole divider, the fragment that survived an\n * edit, or the single code point a stand-in was typed as.\n */\n length: number\n}\n\n/**\n * Find the segment that a separator sitting at `valueIdx` anchors the rest of\n * the value to, or `undefined` when the character is just noise.\n *\n * A separator is only trusted as an anchor when everything still left in\n * `value` actually fits in the slot capacity from that segment onward.\n * Otherwise honoring it would strand data the mask can no longer hold (e.g.\n * pasting into a later segment while an earlier one is still under-filled),\n * so the character is treated as stray noise instead and the current segment\n * takes the slot it needs. Because capacity only shrinks as you move right\n * through the mask, a nearer candidate that can't fit rules out every farther\n * one too — so the search stops at the first literal that matches by text.\n *\n * Intact separators are matched first, everywhere, before any fragment is\n * considered: a surviving tail (see {@link separatorTailLength}) is weaker\n * evidence than a whole separator, so it must never shadow one further along.\n * But it is still evidence — the fragment sits exactly where its separator\n * did, right in front of the segment it introduces, so it anchors the same\n * way. Without that, deleting an area code repacks the segment behind it:\n * \" 123-4567\" would render as \"(123) -4567\" instead of \"() 123-4567\".\n *\n * `committable` lifts the capacity veto for one candidate only: the run\n * immediately after a bounded-quantifier run that has already met its\n * minimum. There the separator is a width the *user chose*, not a guess the\n * mask is making, so it outranks a capacity count — and the count is\n * measuring the wrong thing anyway, since it silently assumes the ranged run\n * will grow to its maximum. Without this, typing a ninth digit into a full\n * `\"3/12/1986\"` withdraws the day's boundary and repacks every field into\n * `\"31/21/9861\"`; with it, the boundary holds and the digit that no longer\n * fits falls off the tail, exactly as an extra digit does on a full fixed\n * mask. Fixed runs never set it: their minimum is their maximum, and a run\n * that reaches its maximum leaves through {@link assignToSlots}'s\n * separator-consuming fast path without ever asking about anchors.\n *\n * `intent` is the last resort, for a character that reads as a divider but\n * matches none of this mask's by text (see {@link isSeparatorIntent}): typing\n * \".\" or \"-\" into `\"9{1,2}/9{1,2}/9{4}\"` means what typing \"/\" means, so it\n * stands in for the divider closing the segment being typed and the mask\n * prints its own \"/\" in its place.\n *\n * A stand-in is only ever read that way in the `committable` state above —\n * the one place a divider carries information the mask cannot supply itself,\n * because only the user knows whether a ranged segment is finished. Anywhere\n * else the mask owns where its dividers go: a segment that reaches its width\n * already reveals the next divider on its own (see `ApplyMaskOptions.eager`),\n * so the stray character has nothing left to say and stays the noise it has\n * always been. That keeps the rule honest in both directions — under\n * `\"9{1,2}/9{1,2}/9{4}\"`, `\"4.\"` and `\"4/\"` both give `\"4/\"`; under fixed\n * `\"99/99/9999\"`, where one digit is short of the day's width, both give\n * `\"4\"` — and leaves every mask without a bounded quantifier untouched.\n */\nfunction findAnchor(\n value: string,\n valueIdx: number,\n plan: CompiledMask,\n fromRun: number,\n committable: boolean,\n intent: boolean,\n prefixCounts: Uint32Array,\n): Anchor | undefined {\n const runCount = plan.runChars.length\n const fits = (run: number, length: number): boolean =>\n (committable && run === fromRun + 1) ||\n remainingDataChars(valueIdx + length, prefixCounts) <= plan.capacityFromRun[run]\n // A stand-in can only mean the divider that closes the segment being typed\n // — which of the ones further along was meant would be pure guesswork — and\n // only where `committable` says that divider is the user's call to make.\n // That is the same candidate `fits` already waives the capacity count for,\n // so a stand-in needs no separate test: it consumes the one code point it\n // was typed as, and the run it lands on is the one a real divider here\n // would have landed on.\n const standIn = (): Anchor | undefined =>\n intent && committable && fromRun + 1 < runCount\n ? { run: fromRun + 1, length: String.fromCodePoint(value.codePointAt(valueIdx)!).length }\n : undefined\n for (let pass = 0; pass < 2; pass++) {\n for (let run = fromRun + 1; run < runCount; run++) {\n const text = separatorBefore(plan, run)\n const length = pass === 0\n ? (value.startsWith(text, valueIdx) ? text.length : 0)\n : separatorTailLength(value, valueIdx, text)\n if (!length) continue\n if (fits(run, length)) return { run, length }\n // Capacity only shrinks rightward, so no farther candidate fits either.\n // The character is still a divider the user typed, though, so it can\n // fall back to closing the segment it was typed in.\n return standIn()\n }\n }\n return standIn()\n}\n\n/**\n * Pass 1 — place every character of `value` into a mask slot.\n *\n * Walks left to right filling the current run. Characters that don't match\n * the slot they land on are either an *anchor* (a separator that belongs to a\n * later segment, see {@link findAnchor} — jump there and keep the segments\n * in between empty) or noise (skip them). A run that fills up completely\n * advances to the next one, swallowing that segment's separator from `value`\n * if it's sitting right there.\n *\n * Within a run, filled slots are always a prefix — the walk never goes\n * backwards, so a run can be partially filled but never has holes.\n */\nfunction assignToSlots(\n value: string,\n plan: CompiledMask,\n compiler: PatternCompiler,\n readLiterals: boolean,\n inputCaret: number,\n): Assignment {\n const runCount = plan.runChars.length\n const slotChar: string[] = new Array(plan.totalSlots).fill('')\n const slotSource: number[] = new Array(plan.totalSlots).fill(-1)\n const runFilled: number[] = new Array(runCount).fill(0)\n const runCommitted: boolean[] = new Array(runCount).fill(false)\n const literalSource: number[] = new Array(plan.tokens.length).fill(-1)\n\n let runIdx = 0\n let slotIdx = 0\n let valueIdx = 0\n let caretBoundaryUsed = false\n // Built once so `findAnchor`'s capacity check below is O(1) per call\n // instead of rescanning the shrinking tail of `value` every time it's\n // asked and fails — see `buildDataPrefixCounts`.\n const prefixCounts = buildDataPrefixCounts(value, compiler, plan)\n const leading = plan.tokens[0]\n if (readLiterals && leading?.kind === 'literal' && value.startsWith(leading.text)) {\n literalSource[0] = 0\n valueIdx = leading.text.length\n }\n\n while (valueIdx < value.length && runIdx < runCount) {\n if (readLiterals && literalSource[0] < 0 && leading?.kind === 'literal' &&\n value.startsWith(leading.text, valueIdx)) {\n literalSource[0] = valueIdx\n valueIdx += leading.text.length\n continue\n }\n const chars = plan.runChars[runIdx]\n const ch = String.fromCodePoint(value.codePointAt(valueIdx)!)\n const matches = chars[slotIdx].match(ch)\n // This run has met its bounded-quantifier minimum but not its maximum —\n // the one state in which the literal that follows it is a boundary the\n // *user* sets rather than one the mask imposes. Always false for a fixed\n // run, whose minimum equals its maximum (see `CompiledMask.runMin`).\n const committable = runFilled[runIdx] >= plan.runMin[runIdx] && runFilled[runIdx] < chars.length\n const anchor = readLiterals && (!matches || plan.hasEscapes)\n ? findAnchor(value, valueIdx, plan, runIdx, committable,\n !matches && isSeparatorIntent(ch, compiler, plan), prefixCounts)\n : undefined\n if (anchor) {\n // The run's *own* closing separator, sitting right where the run stops:\n // the user ended this field deliberately, so the boundary is input\n // rather than decoration and must survive rendering whatever `eager` says.\n if (committable && anchor.run === runIdx + 1) runCommitted[runIdx] = true\n literalSource[plan.literalBeforeRun[anchor.run]] = valueIdx\n valueIdx += anchor.length\n runIdx = anchor.run\n slotIdx = 0\n continue\n }\n\n // An edit can take a whole divider with it, leaving nothing positional\n // behind: selecting \"(555) \" out of \"(555) 123-4567\" and typing \"9\" hands\n // back \"9123-4567\", where \"123\" reads exactly like the rest of the area\n // code. The caret is the one thing that still says where the edit ended,\n // and capacity turns it into proof: everything from the caret on fits the\n // following segments *exactly*, so it can only belong there — packing it\n // from the left would have to overflow the last segment. Anything less\n // than an exact fit is left to the ordinary left-to-right packing, which\n // is why this can never cascade (each further run has strictly less\n // capacity) and never fires mid-field, where the tail still needs the\n // slots of the run being typed into. It also takes a partly filled run\n // to fire at all — the caret has to sit behind something this edit put\n // here, or it carries no information at all: a caret left at 0 (the pure\n // API's default) would otherwise shift whole values rightwards.\n //\n // The divider has to be genuinely *gone* for any of this to apply. While\n // a copy of it survives further along, the anchoring above already knows\n // where everything belongs and the caret must not overrule it — that is\n // also what keeps rendering idempotent, since re-masking \"82--2\" at the\n // same caret has to give \"82--2\" back rather than \"8--22\".\n //\n // A capacity match across two runs with *different* alphabets is a\n // coincidence rather than evidence, so a character this segment can hold\n // and the next one cannot is not allowed to trigger the jump: with\n // `ZZZZ-999`, \"yABy\" leaves three letters over and the digit segment has\n // exactly three slots, and jumping there would drop all three as noise\n // and render \"y\" — a value that no longer re-masks to itself. A\n // character neither segment accepts carries no such counter-evidence\n // (it is noise wherever it lands), so it still lets the jump through.\n if (\n !caretBoundaryUsed && slotIdx > 0 && valueIdx === inputCaret && runIdx + 1 < runCount &&\n (!matches || plan.runChars[runIdx + 1][0].match(ch)) &&\n value.indexOf(separatorBefore(plan, runIdx + 1), valueIdx) < 0 &&\n remainingDataChars(valueIdx, prefixCounts) === plan.capacityFromRun[runIdx + 1]\n ) {\n caretBoundaryUsed = true\n runIdx++\n slotIdx = 0\n continue\n }\n\n if (matches) {\n const flat = plan.runOffset[runIdx] + slotIdx\n slotChar[flat] = transformChar(ch, chars[slotIdx])\n slotSource[flat] = valueIdx + ch.length\n runFilled[runIdx] = slotIdx + 1\n valueIdx += ch.length\n slotIdx++\n\n if (slotIdx === chars.length) {\n runIdx++\n slotIdx = 0\n // This segment is done: if its separator is the next thing in\n // `value`, consume it here so the following run starts clean.\n if (readLiterals && runIdx < runCount) {\n const text = separatorBefore(plan, runIdx)\n if (value.startsWith(text, valueIdx)) {\n literalSource[plan.literalBeforeRun[runIdx]] = valueIdx\n valueIdx += text.length\n }\n }\n }\n continue\n }\n\n valueIdx += ch.length // stray/noise char — skip it\n }\n\n return { slotChar, slotSource, runFilled, runCommitted, literalSource }\n}\n\n/**\n * Decide which literals the rendered value actually shows.\n *\n * A separator earns its place three ways:\n *\n * - **anchor** — the segment right after it holds data, so the separator is\n * what tells the reader (and the next parse) where that data belongs.\n * - **retained boundary** — it was present in the input and there is data in\n * a later segment. Emptying a field must not remove its untouched dividers:\n * \"(111) 222-3333\" becomes \"(111) -3333\", not \"(111-3333\".\n * - **committed boundary** — the segment right before it is a bounded-\n * quantifier run (`9{1,2}`) that the user closed early by typing this very\n * separator, at or past its minimum (see `Assignment.runCommitted`). \"3/\"\n * with `9{1,2}/9{1,2}/9{4}` is a finished one-digit day; dropping the \"/\"\n * would re-read it as an unfinished two-digit one and swallow the next\n * keystroke into the same field. Fixed runs can never be in this state, so\n * `\"25/\"` on `99/99/9999` still follows the eager rule alone.\n * - **intact frame** — it opens the mask, the value holds data, and the\n * divider closing the field it opens is still in the value. An opening\n * literal can only disappear by being deleted, and a deletion that cut\n * into the first field is not the same as one aimed at the frame itself;\n * the closing divider is what tells them apart. Deleting the \"(555)\" out\n * of \"(555) 123-4567\" leaves \") \" behind, so the frame comes back as\n * \"() 123-4567\" — while backspacing the \"(\" of \"(-4444\", where nothing of\n * \") \" survives, removes it for real instead of resurrecting it forever.\n * This holds with eager off, which is how `bind()` masks every deletion\n * (see `eagerForEdit`).\n * - **eager** — the segment right before it is completely filled, so the\n * separator is revealed before the user types the character that would\n * normally pull it in. See `ApplyMaskOptions.eager`.\n *\n * Absent separators around skipped segments are not invented, which keeps\n * \"015\" + skipped middle + \"-39\" compact instead of padding the gap. Existing\n * separators after the last filled segment still follow eager mode, so tail\n * deletion and clearing an input do not leave a trail of empty dividers.\n *\n * The second loop is a round-trip guard. `bind()` feeds the rendered value\n * straight back through this masking on the next keystroke, so a render that\n * doesn't parse back to the same assignment would make characters drift while\n * the user types. Dropping a separator is only safe when it can't be confused\n * for the next visible one: with `99/99/9999` holding \"1\" and \"2025\", hiding\n * the first \"/\" would leave \"1/2025\", which re-parses as 1 / 20 / 25. So any\n * hidden separator between two filled segments that reads the same as the one\n * introducing the later segment is put back — `1//2025`, which re-parses to\n * exactly what it renders. Masks with distinct separators need no such guard.\n */\nfunction resolveLiteralVisibility(\n plan: CompiledMask,\n assignment: Assignment,\n eager: boolean,\n): boolean[] {\n const { tokens, runBeforeLiteral, runAfterLiteral, literalBeforeRun, runChars } = plan\n const { runFilled, runCommitted, literalSource } = assignment\n const visible: boolean[] = new Array(tokens.length).fill(false)\n let lastFilledRun = runFilled.length - 1\n while (lastFilledRun >= 0 && runFilled[lastFilledRun] === 0) lastFilledRun--\n\n for (let t = 0; t < tokens.length; t++) {\n if (tokens[t].kind !== 'literal') continue\n const after = runAfterLiteral[t]\n const before = runBeforeLiteral[t]\n // Tokens alternate, so `t + 2` is the divider closing the field this\n // literal opens (when the mask has one at all), and `after + 1` is the\n // field that divider introduces. Either one still standing means the\n // frame survived the edit: the divider itself is direct evidence, and\n // data sitting in the field behind it is evidence just as good once the\n // divider was swallowed whole.\n const frameIntact = before < 0 && lastFilledRun >= 0 &&\n (literalSource[t + 2] >= 0 || runFilled[after + 1] > 0)\n visible[t] =\n (after >= 0 && (runFilled[after] > 0 || (literalSource[t] >= 0 && after < lastFilledRun))) ||\n frameIntact ||\n (before >= 0 && runCommitted[before]) ||\n (eager && (before < 0 || runFilled[before] === runChars[before].length))\n }\n\n let previousFilledRun = -1\n for (let run = 0; run < runChars.length; run++) {\n if (runFilled[run] === 0) continue\n const litToken = literalBeforeRun[run]\n if (litToken >= 0) {\n // Both casts are literal tokens by construction — literalBeforeRun only ever\n // points at the literal directly before a run (see CompiledMask docs).\n const text = (tokens[litToken] as LiteralToken).text\n for (let skipped = previousFilledRun + 1; skipped < run; skipped++) {\n const skippedLit = literalBeforeRun[skipped]\n if (skippedLit < 0) continue\n if ((tokens[skippedLit] as LiteralToken).text === text) visible[skippedLit] = true\n }\n }\n previousFilledRun = run\n }\n\n return visible\n}\n\n/**\n * Pass 2 — emit the assigned characters and justified literals, tracking the caret.\n *\n * **Caret algorithm**: every emitted character that came from a `value`\n * position *before* `inputCaret` pushes the output caret to the current\n * output length; the first character from at-or-after `inputCaret` freezes\n * it. A literal only carries the caret past itself while the caret is still\n * sitting at the frontier (everything emitted so far is behind it), and then\n * only when the literal isn't standing between the caret and text the user\n * hasn't reached yet: either it was revealed eagerly right after the segment\n * being typed, or it was already in `value` ahead of the caret. That's what\n * puts the caret at `015.|-39` — past the separator the just-completed \"015\"\n * revealed, but not past the \"-\" that anchors the untouched \"39\".\n *\n * Only a *full* field hands the caret across its divider, which is what\n * leaves a bounded-quantifier field open for the rest of what the user is\n * typing. Replacing the \"3/1\" of \"3/1/1998\" with \"2\" renders \"2//1998\" and\n * stays at `2|//1998`, so a second \"2\" makes the day \"22\" — the mask has no\n * way to know the day was finished, and guessing it was would cost the\n * keystroke. Once the day does reach its maximum the ordinary eager reveal\n * moves on by itself, landing at `22/|/1998`.\n */\nfunction renderAssignment(\n plan: CompiledMask,\n assignment: Assignment,\n visible: boolean[],\n inputCaret: number,\n eager: boolean,\n caretAfterLiteral: boolean,\n): MaskResult {\n const { tokens, runChars, runOffset, runBeforeLiteral, runAfterLiteral, runOfToken } = plan\n const { slotChar, slotSource, runFilled } = assignment\n\n let output = ''\n let outputCaret = 0\n let caretResolved = false\n\n for (let t = 0; t < tokens.length; t++) {\n const token = tokens[t]\n\n if (token.kind === 'literal') {\n if (!visible[t]) continue\n const before = runBeforeLiteral[t]\n const after = runAfterLiteral[t]\n // Revealed ahead of the user rather than typed by them, *and* nothing\n // waiting on the far side of it — this separator is the frontier of\n // what's been entered, so the caret belongs past it, ready for the next\n // segment. A separator dividing two segments that both already hold\n // text is not a frontier: the caret stays exactly where the browser\n // put it instead of jumping over content the user didn't touch.\n const opensEmptySegment = after < 0 || runFilled[after] === 0\n // A literal that opens the mask is framing rather than a reveal (see\n // `resolveLiteralVisibility`), so it carries the caret into the field\n // it opens whether or not eager is on — the caret belongs at \"(|)\",\n // inside the emptied area code, not outside the field at \"|()\".\n const revealed =\n before < 0 || (eager && runFilled[before] === runChars[before].length)\n const source = assignment.literalSource[t]\n const atFrontier = !caretResolved && outputCaret === output.length\n output += token.text\n if (\n atFrontier &&\n (caretAfterLiteral || (revealed && opensEmptySegment) || (source >= 0 && source < inputCaret))\n ) {\n outputCaret = output.length\n }\n continue\n }\n\n const run = runOfToken[t]\n const offset = runOffset[run]\n for (let s = 0; s < runFilled[run]; s++) {\n output += slotChar[offset + s]\n if (caretResolved) continue\n if (slotSource[offset + s] <= inputCaret) outputCaret = output.length\n else caretResolved = true\n }\n }\n\n return { value: output, caret: outputCaret }\n}\n\n/**\n * Same contract as {@link applyFlatMask}, but keeps every character in the\n * segment it belongs to instead of repacking the whole value from the left.\n */\nfunction applySegmentedMask(\n value: string,\n plan: CompiledMask,\n inputCaret: number,\n eager: boolean,\n compiler: PatternCompiler,\n readLiterals: boolean,\n caretAfterLiteral: boolean,\n): MaskResult {\n const assignment = assignToSlots(value, plan, compiler, readLiterals, inputCaret)\n const visible = resolveLiteralVisibility(plan, assignment, eager)\n return renderAssignment(plan, assignment, visible, inputCaret, eager, caretAfterLiteral)\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\n/** Internal entry shared by pure APIs and the binding's private compiler. */\nexport function applyWithCompiler(\n value: string,\n mask: MaskPattern,\n inputCaret: number,\n options: ApplyMaskOptions | undefined,\n compiler: PatternCompiler,\n): MaskResult {\n let effective: CompiledMask\n let caretAfterLiteral = false\n if (options?.resolveMask) {\n // Content-dependent layouts describe one continuous identifier. Resolve once\n // from its candidate stream, then render it without stale source separators.\n const patterns = Array.isArray(mask) ? mask : [mask]\n const data = compiler.data(value, inputCaret, patterns.map((pattern) => compiler.compile(pattern)))\n effective = compiler.resolve(data.value, options.resolveMask(data.value), false)\n value = data.value\n inputCaret = data.caret\n caretAfterLiteral = data.afterLiteral\n } else effective = compiler.resolve(value, mask)\n if (!value) return { value: '', caret: 0 }\n const eager = options?.eager !== false\n return options?.segmented === false\n ? applyFlatMask(value, effective, inputCaret, eager, !options.resolveMask, caretAfterLiteral)\n : applySegmentedMask(value, effective, inputCaret, eager, compiler, !options?.resolveMask, caretAfterLiteral)\n}\n\nexport function applyMask(\n value: string,\n mask: MaskPattern,\n inputCaret = 0,\n options?: ApplyMaskOptions,\n): MaskResult {\n const compiler = options?.tokens ? new PatternCompiler(options.tokens) : defaultCompiler\n return applyWithCompiler(value, mask, inputCaret, options, compiler)\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","// ---------------------------------------------------------------------------\n// DOM plumbing shared by `bind()` and `bindDecimal()` — caret access, the\n// requestAnimationFrame scheduler both use to read post-mutation state, and\n// the attribute/dispose bookkeeping that makes either binder idempotent and\n// re-bindable. None of this knows about masking; it's the same regardless of\n// which formatter a binder plugs in.\n// ---------------------------------------------------------------------------\n\nimport { isIos } from './platform'\nimport type { BindInputAttributes } from './types'\n\nexport const MASKED_ATTR = 'data-masked'\n\n/** Apply the binder-managed input attributes, using safe editing defaults. */\nexport function setBindInputAttributes(\n setIfMissing: (name: string, value: string) => void,\n options: BindInputAttributes,\n): void {\n setIfMissing('autocomplete', options.autocomplete ?? 'off')\n setIfMissing('autocorrect', options.autocorrect ?? 'off')\n setIfMissing('autocapitalize', options.autocapitalize ?? 'off')\n setIfMissing('spellcheck', String(options.spellcheck ?? false))\n}\n\n/** `bind()`/`bindDecimal()` are idempotent: a second call on the same element is a no-op. */\nexport function isAlreadyBound(input: Element): boolean {\n return input.getAttribute(MASKED_ATTR) !== null\n}\n\nexport function getCaret(target: HTMLInputElement): number {\n try {\n return target.selectionStart ?? target.value.length\n } catch {\n return target.value.length\n }\n}\n\nexport function setCaret(target: HTMLInputElement, caret: number): void {\n try {\n // DOM selections use UTF-16 offsets; never leave the caret inside a pair.\n if (\n caret > 0 && caret < target.value.length &&\n target.value.charCodeAt(caret) >= 0xdc00 && target.value.charCodeAt(caret) <= 0xdfff &&\n target.value.charCodeAt(caret - 1) >= 0xd800 && target.value.charCodeAt(caret - 1) <= 0xdbff\n ) caret--\n target.setSelectionRange(caret, caret)\n } catch {\n // Some input types (for example type=\"number\") do not support text selection.\n }\n}\n\n/** A retained dispose handle must not retain the binding after it has run. */\nexport function releaseOnce(cleanup: () => void): () => void {\n let release: (() => void) | undefined = cleanup\n return () => {\n const run = release\n release = undefined\n run?.()\n }\n}\n\n/**\n * The scheduled frame can fire before the browser has actually applied a\n * pending keystroke's default action (confirmed via real-Firefox tracing:\n * `target.value` is still unchanged at that point). If the selection is\n * still a real range then, it's the range the user had *before* typing —\n * not a post-edit collapsed caret — and the browser still intends to use it\n * to replace-with-the-typed-character. Reformatting now would collapse that\n * range out from under the pending native edit; the caller should bail and\n * let the next authoritative event (`input`, or the following frame) take\n * over instead.\n */\nexport function editStillPending(target: HTMLInputElement, oldValue: string): boolean {\n return target.value === oldValue && target.selectionStart !== target.selectionEnd\n}\n\n/**\n * requestAnimationFrame callbacks scheduled by a binder outlive a single\n * keystroke handler and close over the input element. If `dispose()` runs\n * before a frame fires — e.g. the field unmounts right after the user types\n * — an uncancelled callback keeps that element (and its closure) alive until\n * the next paint, which can be a very long time on a backgrounded tab. This\n * tracks every scheduled frame so disposal can cancel what's still pending.\n */\nexport function createFrameScheduler(): { scheduleFrame: (callback: () => void) => void; cancelPendingFrames: () => void } {\n const pendingFrames = new Set<number>()\n const scheduleFrame = (callback: () => void): void => {\n const id = requestAnimationFrame(() => {\n pendingFrames.delete(id)\n callback()\n })\n pendingFrames.add(id)\n }\n const cancelPendingFrames = (): void => {\n for (const id of pendingFrames) cancelAnimationFrame(id)\n pendingFrames.clear()\n }\n return { scheduleFrame, cancelPendingFrames }\n}\n\n/**\n * A selection-delete can take a whole field *and* the separator introducing\n * the next one with it, leaving nothing positional behind for the mask to\n * anchor to: selecting \"(11) \" (digits, closing paren, and the space) out of\n * \"(11) 98765-4321\" and deleting hands the engine \"98765-4321\", which reads\n * exactly like fresh digits for the area code — the untouched \"98765\" has\n * no way to say it was never touched. A shorter selection stopping at \"(11)\"\n * leaves the space behind, and the existing anchoring in `assignToSlots`\n * already gets that case right; this only fills the gap where the deletion\n * swallowed one or more separators whole. Widening the selection further —\n * through the \"98765\" too, out to \"(11) 98765-\" — swallows both the \") \"\n * and the \"-\": every field the deletion fully crossed reappears empty with\n * its own boundary intact, e.g. \"(11) -4321\", the same shape three plain\n * Backspaces (never touching the dividers themselves) would have left.\n *\n * `bind()` is the one layer that knows a deletion happened at all — pure\n * `applyMask` sees only the resulting `(value, caret)` and can't tell \"the\n * user just deleted through here\" from \"these are the first digits the user\n * ever typed\", which is exactly the ambiguity `eagerForEdit` exists for on\n * the eager side. So this restores, verbatim, every separator span an edit\n * deleted — but only when real data still follows the deletion untouched.\n * Restoring separators when nothing follows would resurrect ones `bind()`\n * is documented to drop for good, like backspacing the eager \".\" off \"012.\".\n *\n * A deletion that never touched any data at all is left alone too, whatever\n * its length — that's plain divider erosion, one keystroke (or a selection\n * confined to the divider) peeling back separator text the user is clearly\n * choosing to remove, exactly as backspacing through \"(111) \" down to\n * \"(111-4444\" and on to \"-4444\" is documented to work. Only a deletion that\n * destroys *some* field data is treated as having swallowed a separator by\n * accident rather than on purpose. `allowDividerOnly` lifts that rule for a\n * caller that has separately established the erosion would corrupt something\n * — see `bind()`, where a divider whose removal would re-segment untouched\n * text is put back instead.\n *\n * Restoring a separator only ever reproduces text that was standing exactly\n * there a moment ago, at the exact position it stood — it never invents\n * structure. That is also why it stays safe when the same separator repeats\n * elsewhere in the mask (`\"HH:HH:HH\"`'s two colons, say): the restored one\n * lands precisely where the deleted one did, so the engine's own capacity\n * check (`assignToSlots`/`findAnchor` in apply-mask.ts) still resolves it\n * to the one field it can — the surviving data plus everything after the\n * restored separator has to fit what follows, which pins the split uniquely\n * even with an identical separator later in the string.\n *\n * Typing a character straight over a selection destroys exactly the same\n * dividers the equivalent Delete would have, so `insertedLength` widens this\n * to that case: selecting the `\"3/12\"` of `\"3/12/1986\"` and typing `\"4\"`\n * takes the day, the month, *and* the divider between them, handing the\n * engine `\"4/1986\"`. On a mask whose separators all read alike there is\n * nothing left in the value to say which field the surviving `\"/\"` belongs\n * to, so the untouched year breaks apart into `\"4/19/86\"`. Putting the\n * divider back pins it where it never moved from. The inserted text keeps its\n * place — the separators go in directly behind it, exactly where they stood.\n *\n * `pos`, `removedLength` and `insertedLength` must describe a single splice —\n * `previousValue` with `[pos - insertedLength, pos - insertedLength +\n * removedLength)` replaced by the `insertedLength` characters ending at\n * `pos`, and nothing else changed. Any other shape (IME weirdness, a\n * multi-range edit) fails the checks below and is left untouched.\n */\nexport function restoreSwallowedSeparators(\n rawValue: string,\n pos: number,\n removedLength: number,\n previousValue: string,\n isData: (char: string) => boolean,\n insertedLength = 0,\n allowDividerOnly = false,\n): string {\n if (removedLength <= 0 || insertedLength < 0 || insertedLength > pos) return rawValue\n const cutStart = pos - insertedLength\n const deletedEnd = cutStart + removedLength\n if (deletedEnd > previousValue.length) return rawValue\n if (\n previousValue.slice(0, cutStart) !== rawValue.slice(0, cutStart) ||\n previousValue.slice(deletedEnd) !== rawValue.slice(pos)\n ) return rawValue\n\n // Nothing left past the cut means every field beyond it was cleared too —\n // that deletion is final, not a swallow (matches backspacing the eager \".\"\n // off \"012.\" for good, where the cut sits at the very end).\n const tail = previousValue.slice(deletedEnd)\n if (!Array.from(tail).some(isData)) return rawValue\n\n // `previousValue` is a rendered mask output, so every non-data code point\n // inside the deleted span is a genuine separator the deletion swallowed —\n // never a coincidence. Keep them, in order, and drop the data alongside\n // them that this edit did mean to delete.\n const removed = previousValue.slice(cutStart, deletedEnd)\n if (!allowDividerOnly && !Array.from(removed).some(isData)) return rawValue\n let literals = ''\n for (const ch of removed) if (!isData(ch)) literals += ch\n if (!literals) return rawValue\n\n return rawValue.slice(0, pos) + literals + rawValue.slice(pos)\n}\n\n/** Attributes a binder sets only if absent, so it never clobbers the caller's own and disposal only ever removes what it added. */\nexport function trackAttrs(input: Element): { setIfMissing: (name: string, value: string) => void; removeTracked: () => void } {\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n const removeTracked = (): void => {\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n return { setIfMissing, removeTracked }\n}\n\n/** A binder's handlers in the order their events are attached. The last one listens on `keyup` (iOS) or `keydown` (elsewhere) — see `isIos()`. */\nexport type BinderHandlers = readonly [\n paste: (e: Event) => void,\n input: (e: Event) => void,\n compositionstart: (e: Event) => void,\n compositionend: (e: Event) => void,\n key: (e: Event) => void,\n]\n\n/**\n * Everything a binder does to take — and later release — ownership of an\n * element, in one place: mark it bound (`data-masked` set to `marker`), apply\n * the managed attributes (plus `maxlength` when finite), attach the five\n * listeners, and return the dispose function that reverses each of those\n * steps and cancels any reformat frame still in flight. Keeping attach and\n * detach in a single helper makes the add/remove symmetry impossible to break\n * from a binder — exactly the class of leak `memory.test.ts` guards against.\n */\nexport function attachBinder(\n input: Element,\n marker: string,\n attributes: BindInputAttributes,\n maxLength: number,\n handlers: BinderHandlers,\n cancelPendingFrames: () => void,\n): () => void {\n const { setIfMissing, removeTracked } = trackAttrs(input)\n input.setAttribute(MASKED_ATTR, marker)\n setBindInputAttributes(setIfMissing, attributes)\n if (Number.isFinite(maxLength)) setIfMissing('maxlength', String(maxLength))\n\n const names = ['paste', 'input', 'compositionstart', 'compositionend', isIos() ? 'keyup' : 'keydown']\n for (let i = 0; i < names.length; i++) input.addEventListener(names[i], handlers[i])\n\n return releaseOnce(() => {\n for (let i = 0; i < names.length; i++) input.removeEventListener(names[i], handlers[i])\n input.removeAttribute(MASKED_ATTR)\n removeTracked()\n cancelPendingFrames()\n })\n}\n","import { applyWithCompiler } from './apply-mask'\nimport {\n attachBinder,\n createFrameScheduler,\n editStillPending,\n getCaret,\n isAlreadyBound,\n restoreSwallowedSeparators,\n setCaret,\n} from './bind-shared'\nimport { maskMaxLength, PatternCompiler } from './pattern'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern, MaskResolver, MaskResult } from './types'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\ntype InputEditKind = 'insert' | 'backspace' | 'delete' | 'unidentified'\n\n/**\n * A deletion can take the mask's opening structure with it, leaving the caret\n * at 0 with nothing in front of it to anchor to — selecting the \"(555)\" out\n * of \"(555) 123-4567\" and deleting renders \"() 123-4567\", where that \"(\" is\n * structure the mask restored rather than text the user is behind. Only at\n * position 0 is the whole rendered prefix known to be restored like this, so\n * only there does the render's own caret win: the user is editing the emptied\n * field at \"(|) 123-4567\", not sitting outside it at \"|() 123-4567\".\n *\n * An edit the mask *fully* undid is the exception. Backspacing the \"(\" of\n * \"(|) 123-4567\" deletes nothing the render doesn't put straight back, so\n * holding the caret in place would wedge it there forever. The keystroke\n * still gets to move it, exactly as it does over any other fixed character.\n */\nfunction restoredPrefixCaret(pos: number, masked: MaskResult, baselineValue: string): number {\n return pos === 0 && masked.value !== baselineValue ? masked.caret : pos\n}\n\n/**\n * Keep native movement within a retained divider. If formatting changed the\n * prefix, use its source-mapped caret instead of an offset into removed text.\n * Backspace must not advance across an untouched divider or into the next field.\n */\nfunction backwardCaret(rawValue: string, pos: number, masked: MaskResult, baselineValue: string): number {\n if (masked.value.startsWith(rawValue.slice(0, pos))) return restoredPrefixCaret(pos, masked, baselineValue)\n // Overlapping divider text can make a surviving fragment look like the\n // next divider. Stay before unchanged text on the right, even then.\n let tailStart = masked.value.length\n let rawEnd = rawValue.length\n while (rawEnd > pos && tailStart > 0 && rawValue[rawEnd - 1] === masked.value[tailStart - 1]) {\n rawEnd--\n tailStart--\n }\n return Math.min(pos, masked.caret, tailStart)\n}\n\n/** Classify a native `InputEvent.inputType` the same way `onKey` classifies `KeyboardEvent.key`. */\nfunction classifyInputType(inputType: string | undefined): InputEditKind {\n if (inputType?.startsWith('delete') && inputType.endsWith('Backward')) return 'backspace'\n if (inputType === 'deleteContentForward') return 'delete'\n if (inputType && inputType.startsWith('insert')) return 'insert'\n return 'unidentified'\n}\n\n/**\n * `eager`, unless this particular edit is a deletion.\n *\n * `applyMask`/`buildMask` are pure functions of `(value, caret)` — they have\n * no memory of *how* the value got there. That's a problem for eager mode\n * specifically: deleting the separator eager just added (e.g. backspacing\n * the \".\" off \"012.\") produces the exact same `(value, caret)` — raw digits,\n * caret right where the separator used to be — as the moment right before\n * that separator first appeared. A stateless recompute can't tell those two\n * apart, so eager would immediately re-add the separator the user just\n * deleted, making backspace look like it does nothing.\n *\n * `bind()` is the one layer that *does* know which happened (the DOM event\n * says so), so it's the right place to break the tie: suppress eager for the\n * single recompute that follows a delete-type edit, and let it resume on the\n * next insert. This never removes anything eager wouldn't otherwise have\n * added — it only stops eager from resurrecting a literal the user just\n * removed.\n */\nfunction eagerForEdit(eager: boolean | undefined, isDeleteLike: boolean): boolean | undefined {\n return isDeleteLike ? false : eager\n}\n\n/**\n * Where the caret lands after a reformat, shared by the `input`-event path\n * (`onInput`) and its `keydown`/`keyup` fallback (`onKey`) — both apply the\n * same reasoning once the edit is classified into an {@link InputEditKind},\n * just from differently-shaped signals (`InputEvent.inputType` vs.\n * `KeyboardEvent.key`).\n *\n * - A resolver mask that actually changed the value takes its own\n * source-mapped caret, since the candidate stream it reflowed can't be\n * reasoned about positionally like a fixed pattern.\n * - An unidentified edit (unreliable/missing `key`, or a directionless\n * `inputType` like `deleteByCut`) only trusts the masked caret once the\n * value visibly grew — otherwise it's likely a no-op or a delete\n * misreported as unidentified, so the pre-edit position holds, adjusted\n * for any structure the mask restored in front of it.\n * - A forward Delete that didn't shrink the value (e.g. it landed on a\n * literal and consumed nothing) leaves the caret one past where the user\n * pressed it, matching native forward-delete-through-a-literal behavior.\n * - Backspace defers to {@link backwardCaret}'s divider-aware logic.\n * - A plain insert takes the masked caret outright.\n */\nfunction resolveCaretAfterEdit(\n kind: InputEditKind,\n rawValue: string,\n pos: number,\n previousLength: number,\n masked: MaskResult,\n resolveMask: MaskResolver | undefined,\n baselineValue: string,\n): number {\n if (resolveMask && masked.value !== rawValue && masked.value !== baselineValue) return masked.caret\n if (kind === 'unidentified') {\n return masked.value.length > previousLength ? masked.caret : restoredPrefixCaret(pos, masked, baselineValue)\n }\n if (kind === 'delete') {\n return previousLength === masked.value.length ? pos + 1 : restoredPrefixCaret(pos, masked, baselineValue)\n }\n if (kind === 'backspace') return backwardCaret(rawValue, pos, masked, baselineValue)\n return masked.caret\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Reformats post-mutation `input` events (the reliable, timing-safe signal\n * on every modern browser, including mobile IME/autocorrect) with a\n * `keydown`/`requestAnimationFrame` fallback for older browsers.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (isAlreadyBound(input)) return () => {}\n\n const {\n onChange,\n segmented,\n eager,\n tokens,\n resolveMask,\n autocomplete,\n autocorrect,\n autocapitalize,\n spellcheck,\n } = toBindOptions(third)\n\n const compiler = new PatternCompiler(tokens)\n const format = (value: string, caret: number, editEager = eager) =>\n applyWithCompiler(value, mask, caret, { tokens, resolveMask, segmented, eager: editEager }, compiler)\n const isData = (ch: string): boolean => compiler.isData(ch)\n // Defer composition only when some custom token's alphabet could plausibly\n // accept a genuine candidate-IME script (Pinyin/Kana/Hangul) — where the\n // provisional draft reads nothing like what it commits, so a live reformat\n // would clobber text the IME still expects to revise. A custom token whose\n // alphabet is ASCII/Latin-only (an uppercase-transforming alphanumeric\n // token, say) gets the same live-formatting treatment as the built-ins:\n // Android's autocorrect otherwise wraps plain Latin typing in a\n // composition session that may never fire `compositionend` while the\n // field has no word boundaries to type through, leaving the mask looking\n // completely inert. See `PatternCompiler.hasComposingRisk`.\n const deferComposition = compiler.hasComposingRisk\n\n // Resolver capacity is unknowable; custom-token IME drafts may exceed even\n // the two-UTF-16-unit-per-slot bound. Enforce those capacities in the engine.\n // Author-supplied maxlength remains an intentional application constraint.\n // The binding's own compiler sizes this, so a custom token gets its own\n // definition (not the built-in one a reused key like \"A\" would otherwise\n // fall back to, or the literal a non-built-in key would be mistaken for).\n const maxLength = resolveMask || deferComposition ? Infinity : maskMaxLength(mask, compiler)\n\n let lockInput = false\n let isComposing = false\n let skipNextKeyup = false\n // Baseline the `input`-event path compares against to detect growth/no-op\n // edits (mirrors the role `oldValue` plays in `onKey`, but persisted\n // across calls since `input` fires once per real mutation — see `onInput`).\n let lastMaskedValue = (input as HTMLInputElement).value ?? ''\n\n const { scheduleFrame, cancelPendingFrames } = createFrameScheduler()\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n const oldValue = target.value\n scheduleFrame(() => {\n if (deferComposition && isComposing) return\n if (editStillPending(target, oldValue)) return\n const m = format(target.value, getCaret(target))\n target.value = m.value\n setCaret(target, m.caret)\n lastMaskedValue = target.value\n onChange?.(target.value)\n })\n }\n\n // `input` fires synchronously, once per real DOM mutation, right after the\n // browser (or IME/autocorrect) has already applied the edit — unlike\n // `keydown` + `requestAnimationFrame`, there's no batching window where\n // several keystrokes can queue up before we read `selectionStart`, which is\n // what let fast typing (especially Android Chrome, where composed/\n // autocorrected characters often arrive with an unreliable or missing\n // `key`) drift the caret. This is now the primary formatting path; `onKey`\n // below stays as a `requestAnimationFrame` fallback for browsers that don't\n // fire `input` reliably.\n //\n // Built-ins keep the existing Android autocorrect path. Custom alphabets\n // leave provisional composition text and selection completely untouched.\n const onInput = (e: Event): void => {\n const inputEvent = e as InputEvent\n const target = e.target as HTMLInputElement\n cancelPendingFrames()\n lockInput = false\n skipNextKeyup = true\n if (deferComposition && (isComposing || inputEvent.isComposing)) return\n if (editStillPending(target, lastMaskedValue)) return\n\n const pos = getCaret(target)\n const previousLength = lastMaskedValue.length\n const kind = classifyInputType(inputEvent.inputType)\n const rawValue = target.value\n const isDeleteLike = kind === 'backspace' || kind === 'delete'\n // Only a plain content delete — a selection Backspace/Delete/Cut, or a\n // single collapsed Backspace/Delete — gets the swallowed-separator\n // rescue below. Word/line deletes (`deleteWordBackward`,\n // `deleteSoftLineBackward`, ...) are a deliberate bulk clear —\n // resurrecting structure they removed would contradict `bind()`'s own\n // documented \"never resurrect a divider the user just removed\" rule, so\n // those are left to reformat the raw value exactly as struck.\n // Array/resolver masks are excluded too: which pattern applies can\n // change with the new, shorter data count, and a literal restored from\n // the old pattern's layout can land at a position the newly-resolved\n // one never had.\n const isStaticMask = !Array.isArray(mask) && !resolveMask\n const isPlainContentDelete =\n isStaticMask &&\n (inputEvent.inputType === 'deleteContentBackward' ||\n inputEvent.inputType === 'deleteContentForward' ||\n inputEvent.inputType === 'deleteByCut')\n // Typing over a selection destroys the same dividers the equivalent\n // Delete would have, so it gets the same rescue. `insertText` is the one\n // insert type that reports what it inserted, which is what makes the edit\n // a splice this can reason about; paste, IME commits and drag-and-drop\n // leave `data` null and stay a plain reformat.\n const insertedText =\n isStaticMask && inputEvent.inputType === 'insertText' && typeof inputEvent.data === 'string'\n ? inputEvent.data : ''\n const editEager = eagerForEdit(eager, isDeleteLike)\n\n const cutStart = pos - insertedText.length\n const removedLength = previousLength - rawValue.length + insertedText.length\n\n let formatValue = rawValue\n if (isPlainContentDelete || insertedText) {\n const rescued = restoreSwallowedSeparators(\n rawValue, pos, removedLength, lastMaskedValue, isData, insertedText.length, true,\n )\n if (rescued !== rawValue) {\n // A deletion that destroyed field data puts its dividers back\n // outright: every field it crossed reappears empty with its own\n // boundary intact, which is what keeps \"98765-4321\" from sliding\n // into an emptied area code.\n const destroyedFieldData = isPlainContentDelete &&\n Array.from(lastMaskedValue.slice(cutStart, cutStart + removedLength)).some(isData)\n // Everything else is the user editing forward — typing over a\n // selection, or peeling a divider off with Backspace — so the divider\n // only goes back when erasing it would re-segment text this edit never\n // touched. That text sits past the cut and the mask already formatted\n // it, so it has to come back out unchanged and at the end. Retyping a\n // CPF over \"012.153.441\" keeps its \"-39\" either way, and backspacing\n // the \") \" out of \"(111) -3333\" still erodes down to \"(111-3333\"; but\n // erasing the second \"/\" of \"13//1986\" would read the year as\n // \"13/19/86\", so that one is put back.\n // Measured from the surviving text's first *data* character: the mask\n // owns how dividers render, and a cut that stopped mid-divider leaves\n // a fragment it may legitimately absorb — deleting the \")\" out of\n // \"(111) -4444\" is still the documented erosion down to \"(111-4444\",\n // even though the stranded space goes with it. Field characters are\n // the thing that must not move.\n const suffix = lastMaskedValue.slice(cutStart + removedLength)\n let dataStart = 0\n while (dataStart < suffix.length) {\n const ch = String.fromCodePoint(suffix.codePointAt(dataStart)!)\n if (isData(ch)) break\n dataStart += ch.length\n }\n const untouchedTail = suffix.slice(dataStart)\n if (destroyedFieldData || !format(rawValue, pos, editEager).value.endsWith(untouchedTail)) {\n formatValue = rescued\n }\n }\n }\n const m = format(formatValue, pos, editEager)\n target.value = m.value\n setCaret(target, resolveCaretAfterEdit(kind, rawValue, pos, previousLength, m, resolveMask, lastMaskedValue))\n\n lastMaskedValue = target.value\n onChange?.(target.value)\n }\n\n const onCompositionStart = (): void => {\n isComposing = true\n cancelPendingFrames()\n lockInput = false\n }\n\n const onCompositionEnd = (e: Event): void => {\n isComposing = false\n skipNextKeyup = true\n\n const target = e.target as HTMLInputElement\n const pos = getCaret(target)\n const m = format(target.value, pos)\n target.value = m.value\n setCaret(target, m.caret)\n\n lastMaskedValue = target.value\n onChange?.(target.value)\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n if (isComposing) return\n\n // `input` already handled this keystroke (it fires before `keyup`); skip\n // the redundant iOS `keyup` pass so we don't reformat the value twice.\n if (isIos() && skipNextKeyup) {\n skipNextKeyup = false\n return\n }\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n scheduleFrame(() => {\n if (editStillPending(target, oldValue)) {\n lockInput = false\n return\n }\n const pos = target.selectionStart ?? 999\n // No reliable `key` here, so infer delete-vs-insert from the length\n // delta the browser's already-applied default action left behind.\n const isDeleteLike = target.value.length < oldValue.length\n const m = format(target.value, pos, eagerForEdit(eager, isDeleteLike))\n target.value = m.value\n setCaret(target, m.caret)\n lastMaskedValue = target.value\n scheduleFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = Array.from(ke.key).length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= maxLength && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke. Without this guard, a key like\n // Ctrl+A schedules a reformat frame that never gets cancelled (select-all\n // fires no `input` event), and that stray frame's `target.value =\n // m.process()` reassignment races the browser's own pending selection —\n // the reported Firefox bug where selecting all and retyping fast\n // occasionally drops the caret to the start instead of replacing the\n // selection.\n if (!isBackspace && !isDelete && !isCharInsert && !isUnidentified) return\n\n // Bailing here (rather than reformatting) matters when the frame fires\n // before the browser has applied this keystroke's default action — see\n // `editStillPending`'s doc comment. The authoritative `input` handler\n // takes over once the edit actually lands; a collapsed caret (every\n // other test/path exercises) is unaffected by this check.\n const kind: InputEditKind = isBackspace ? 'backspace' : isDelete ? 'delete' : isUnidentified ? 'unidentified' : 'insert'\n scheduleFrame(() => {\n if (editStillPending(target, oldValue)) return\n\n const pos = target.selectionStart ?? 999\n const rawValue = target.value\n const m = format(rawValue, pos, eagerForEdit(eager, isBackspace || isDelete))\n target.value = m.value\n setCaret(target, resolveCaretAfterEdit(kind, rawValue, pos, oldValue.length, m, resolveMask, oldValue))\n\n lastMaskedValue = target.value\n onChange?.(target.value)\n })\n }\n\n return attachBinder(\n input,\n Array.isArray(mask) ? mask.join('|') : mask,\n { autocomplete, autocorrect, autocapitalize, spellcheck },\n maxLength,\n [onPaste, onInput, onCompositionStart, onCompositionEnd, onKey],\n cancelPendingFrames,\n )\n}\n","import type { DecimalMaskOptions, MaskResult } from './types'\nimport { isDigitChar } from './chars'\n\n// ---------------------------------------------------------------------------\n// Option resolution\n// ---------------------------------------------------------------------------\n\ninterface ResolvedDecimalOptions {\n /**\n * `undefined` means an optional, uncapped fraction (default) — the\n * decimal separator and fraction only appear once the user actually types\n * them, and there's no limit on how many digits follow. `0` means no\n * fraction at all. A positive number is a fixed, zero-padded width that's\n * always shown, even before the user types anything.\n */\n decimalPlaces: number | undefined\n /** `undefined` means unlimited (default) — the integer part grows freely. */\n numberPlaces: number | undefined\n segmented: boolean\n separator: string\n decimalSeparator: string\n prefix: string\n suffix: string\n allowNegative: boolean\n}\n\n/** @internal exported for {@link bindDecimal}'s \".\" / \",\" key normalization */\nexport function resolveDecimalOptions(options?: DecimalMaskOptions): ResolvedDecimalOptions {\n const rawPlaces = options?.decimalPlaces\n // Capped at 100 — `Number.prototype.toFixed`'s own limit — so every API\n // accepts the same range and `formatDecimalValue` can never throw where\n // `processDecimal` succeeds.\n const decimalPlaces =\n rawPlaces != null && Number.isFinite(rawPlaces)\n ? Math.min(100, Math.max(0, Math.floor(rawPlaces)))\n : undefined\n const rawNumberPlaces = options?.numberPlaces\n const numberPlaces =\n rawNumberPlaces != null && Number.isFinite(rawNumberPlaces)\n ? Math.max(1, Math.floor(rawNumberPlaces))\n : undefined\n return {\n decimalPlaces,\n numberPlaces,\n segmented: options?.segmented ?? true,\n separator: options?.separator ?? ',',\n decimalSeparator: options?.decimalSeparator ?? '.',\n prefix: options?.prefix ?? '',\n suffix: options?.suffix ?? '',\n allowNegative: options?.allowNegative ?? false,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Digit-stream helpers\n// ---------------------------------------------------------------------------\n\n/** Strip leading zeros from a digit string, always keeping at least one digit. */\nfunction stripLeadingZeros(s: string): string {\n let i = 0\n while (i < s.length - 1 && s[i] === '0') i++\n return s.slice(i)\n}\n\n/** Insert `sep` every 3 digits from the right (e.g. \"1234567\" → \"1,234,567\"). */\nfunction groupThousands(s: string, sep: string): string {\n if (!sep || s.length <= 3) return s\n const parts: string[] = []\n let i = s.length\n while (i > 3) {\n parts.unshift(s.slice(i - 3, i))\n i -= 3\n }\n parts.unshift(s.slice(0, i))\n return parts.join(sep)\n}\n\n/**\n * Turn raw integer digits into their displayed form: zeros stripped, then\n * left-padded to `numberPlaces` (if set), then thousands-grouped (if\n * `segmented`). Shared by {@link applyDecimalMask} and\n * {@link formatDecimalValue} — the caret math in the former also needs\n * `intPart`/`paddedInt` individually to know how many synthetic zeros it\n * inserted, so all three stages are returned rather than just the result.\n */\nfunction formatIntegerPart(\n intDigits: string,\n opts: Pick<ResolvedDecimalOptions, 'numberPlaces' | 'segmented' | 'separator'>,\n): { intPart: string; paddedInt: string; groupedInt: string } {\n const intPart = stripLeadingZeros(intDigits || '0')\n const paddedInt = opts.numberPlaces != null ? intPart.padStart(opts.numberPlaces, '0') : intPart\n const groupedInt = opts.segmented ? groupThousands(paddedInt, opts.separator) : paddedInt\n return { intPart, paddedInt, groupedInt }\n}\n\n/**\n * Rewrite `Number` exponential notation (\"1e+21\", \"1.5e-7\") as plain\n * positional digits. `String()` and `toFixed()` fall back to exponential form\n * for |values| ≥ 1e21 (`String()` also for tiny fractions), and that text\n * would otherwise reach the digit-oriented formatter, which mangles it —\n * grouping \"1e+21\" into \"1e,+21\". Strings without an exponent pass through\n * unchanged.\n */\nfunction expandExponent(s: string): string {\n const e = s.indexOf('e')\n if (e < 0) return s\n const exponent = Number(s.slice(e + 1))\n const mantissa = s.slice(0, e)\n const dot = mantissa.indexOf('.')\n const digits = dot < 0 ? mantissa : mantissa.slice(0, dot) + mantissa.slice(dot + 1)\n const point = (dot < 0 ? mantissa.length : dot) + exponent\n if (point <= 0) return '0.' + '0'.repeat(-point) + digits\n if (point >= digits.length) return digits + '0'.repeat(point - digits.length)\n return digits.slice(0, point) + '.' + digits.slice(point)\n}\n\n/**\n * Find the position in `s` that leaves exactly `digitsBefore` digit\n * characters preceding it — the position immediately after that digit and\n * before any subsequent literal (grouping separator, ...), so the caret\n * stays glued to the last digit the user placed there.\n */\nfunction caretForDigitsBefore(s: string, digitsBefore: number): number {\n if (digitsBefore <= 0) return 0\n let count = 0\n for (let i = 0; i < s.length; i++) {\n if (isDigitChar(s[i])) {\n count++\n if (count === digitsBefore) return i + 1\n }\n }\n return s.length\n}\n\n// ---------------------------------------------------------------------------\n// Segmented parsing — integer digits before the decimal separator, fraction\n// digits after. Unlike a slot-pattern mask, the integer segment has no fixed\n// length. The fraction is only fixed-width when `decimalPlaces` is set to a\n// positive number — zero-padded on the right so a shorter fraction reads as\n// its low-order (trailing) digits being zero rather than reflowing/shifting\n// (e.g. editing \"423,42\" down to \"423,4\" produces \"423,40\", not \"42,34\").\n// When `decimalPlaces` is left unset, the fraction is optional and uncapped:\n// it only exists once the user types the separator, and grows to however\n// many digits they type.\n//\n// The decimal separator only has meaning as the *first* occurrence of\n// `opts.decimalSeparator`; every other non-digit character (thousands\n// separator, prefix/suffix text, a second stray separator, ...) is noise and\n// is dropped. This keeps re-parsing an already-masked value idempotent.\n// ---------------------------------------------------------------------------\n\ninterface DecimalParts {\n isNegative: boolean\n intDigits: string\n fracDigits: string\n hasSeparator: boolean\n}\n\n/** A raw value split into its sign, its editable number text, and where that text starts. */\ninterface AffixSplit {\n /** `'-'` when a leading sign was stripped, `''` otherwise. */\n sign: string\n /** The value with sign, prefix and suffix removed — the part the user is really editing. */\n body: string\n /** Index in the original string where `body` begins. */\n bodyStart: number\n}\n\n/**\n * Peel the sign, prefix and suffix off a raw value.\n *\n * The prefix and suffix are chrome, not content, so they must not reach the\n * digit parser at all: a prefix like `\"Q1 \"` would otherwise donate its `1`\n * to the number on every keystroke, and one like `\"No. \"` would have its `.`\n * read as the decimal separator and collapse the whole value. Everything\n * downstream works on `body` so affix text simply cannot be misread.\n *\n * Matching is deliberately strict — an affix is only peeled when it is\n * actually sitting at its edge. Mid-edit a value may have a partly deleted\n * prefix, and then nothing is stripped and the old \"drop unknown characters\n * as noise\" behavior still applies.\n */\nfunction splitAffixes(raw: string, opts: ResolvedDecimalOptions): AffixSplit {\n let start = 0\n let sign = ''\n // The prefix is tried at index 0 *before* a leading \"-\" is read as a sign,\n // so a prefix that itself begins with \"-\" is not mistaken for one. Only\n // when the prefix does not match there does a leading \"-\" become the sign,\n // and the prefix is then looked for just after it — which is exactly how\n // the formatter lays a negative value out (\"-\" + prefix + number).\n if (opts.prefix && raw.startsWith(opts.prefix)) {\n start = opts.prefix.length\n } else {\n if (opts.allowNegative && raw[0] === '-') {\n sign = '-'\n start = 1\n }\n if (opts.prefix && raw.startsWith(opts.prefix, start)) start += opts.prefix.length\n }\n\n let end = raw.length\n if (opts.suffix && raw.endsWith(opts.suffix) && end - opts.suffix.length >= start) {\n end -= opts.suffix.length\n }\n\n return { sign, body: raw.slice(start, end), bodyStart: start }\n}\n\nfunction computeDecimalParts(raw: string, opts: ResolvedDecimalOptions): DecimalParts {\n const split = splitAffixes(raw, opts)\n let intDigits = ''\n let fracDigits = ''\n let isNegative = split.sign === '-'\n let inFraction = false\n const canHaveFraction = opts.decimalPlaces !== 0\n\n for (const ch of split.body) {\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (opts.decimalPlaces == null || fracDigits.length < opts.decimalPlaces) fracDigits += ch\n } else if (opts.numberPlaces == null || intDigits.length < opts.numberPlaces) {\n intDigits += ch\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n continue\n }\n // A sign character anywhere in the body sets (not toggles) the sign —\n // \"-\" always forces negative, \"+\" always forces positive, regardless of\n // what the value's sign was before. Scanning left to right, the last\n // sign character in the body wins, so alternating \"+\"/\"-\" (however\n // unlikely outside of a paste) resolves the same way a human reads it:\n // by the one typed last.\n if (ch === '-' && opts.allowNegative) isNegative = true\n else if (ch === '+' && opts.allowNegative) isNegative = false\n // Anything else — thousands separator, prefix/suffix text, a repeated\n // separator, stray letters — is noise and is dropped.\n }\n\n return { isNegative, intDigits, fracDigits, hasSeparator: inFraction }\n}\n\n/**\n * Walk `raw[0:caret]` to find which segment the caret sits in (integer or\n * fraction) and how many digits of that segment precede it, so the same\n * position can be re-derived in the freshly formatted output.\n */\nfunction locateCaretSegment(\n raw: string,\n caret: number,\n opts: ResolvedDecimalOptions,\n): { inFraction: boolean; digitsBefore: number } {\n const canHaveFraction = opts.decimalPlaces !== 0\n let inFraction = false\n let digitsBefore = 0\n\n for (let i = 0; i < caret; i++) {\n const ch = raw[i]\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (opts.decimalPlaces == null || digitsBefore < opts.decimalPlaces) digitsBefore++\n } else if (opts.numberPlaces == null || digitsBefore < opts.numberPlaces) {\n digitsBefore++\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n digitsBefore = 0\n }\n }\n\n return { inFraction, digitsBefore }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a decimal/currency mask to a value, producing the masked output and\n * a computed caret position. Digits typed before the decimal separator\n * extend the integer part; the fraction only starts once the separator is\n * typed. With a fixed `decimalPlaces` it's always displayed zero-padded to\n * that width, even before the user types it; left unset, the fraction is\n * optional — it only appears once the separator is typed, and is shown\n * exactly as typed (no padding, no cap on how many digits).\n */\nexport function applyDecimalMask(\n value: string,\n inputCaret = 0,\n options?: DecimalMaskOptions,\n): MaskResult {\n const opts = resolveDecimalOptions(options)\n if (!value) return { value: '', caret: 0 }\n\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (intDigits === '' && fracDigits === '' && !hasSeparator) {\n // No digits typed yet, but with `allowNegative` a lone \"-\" still counts:\n // an otherwise-empty field the user just typed \"-\" into stays negative\n // (shown as just the sign, plus the prefix so it reads as \"-$\" rather\n // than losing which currency it is) instead of collapsing back to fully\n // empty. There's nothing to pad or group without real digits, so the\n // fraction and any numberPlaces padding still wait for the first one.\n // Deleting that \"-\" (or typing \"+\", which clears `isNegative` the same\n // way it does everywhere else) removes the only content left and this\n // falls back to the plain empty case below — \"positive\" here just means\n // \"no sign to show\".\n if (isNegative) {\n const signOutput = '-' + opts.prefix\n return { value: signOutput, caret: signOutput.length }\n }\n return { value: '', caret: 0 }\n }\n\n const { intPart, paddedInt, groupedInt } = formatIntegerPart(intDigits, opts)\n const fracPadded =\n opts.decimalPlaces != null && opts.decimalPlaces > 0\n ? fracDigits.padEnd(opts.decimalPlaces, '0')\n : fracDigits\n const showFraction = opts.decimalPlaces === 0 ? false : opts.decimalPlaces != null || hasSeparator\n const numberStr = groupedInt + (showFraction ? opts.decimalSeparator + fracPadded : '')\n const signStr = isNegative ? '-' : ''\n const output = signStr + opts.prefix + numberStr + opts.suffix\n\n // The caret is resolved inside the *body* for the same reason parsing is:\n // affix characters must not be counted as digits, and a caret parked in the\n // prefix (or out past the suffix) collapses to the nearest edge of the\n // number rather than landing somewhere inside the chrome.\n const split = splitAffixes(value, opts)\n const bodyCaret = Math.max(0, Math.min(inputCaret - split.bodyStart, split.body.length))\n const { inFraction, digitsBefore } = locateCaretSegment(split.body, bodyCaret, opts)\n const prefixLen = signStr.length + opts.prefix.length\n // Left-padding zeros are synthetic — prepended ahead of every real typed\n // digit — so they shift where the caret's `digitsBefore`-th real digit\n // lands in `groupedInt` by the padding's width.\n const padLength = paddedInt.length - intPart.length\n const caret = inFraction\n ? prefixLen + groupedInt.length + opts.decimalSeparator.length + digitsBefore\n : prefixLen + caretForDigitsBefore(groupedInt, digitsBefore + padLength)\n\n return { value: output, caret }\n}\n\n/** Apply a decimal mask to a raw value and return just the masked string. */\nexport function processDecimal(value: string, options?: DecimalMaskOptions): string {\n return applyDecimalMask(value, value.length, options).value\n}\n\n/**\n * Parse a raw or already-masked decimal value back into a JS number.\n * Ignores prefix/suffix/thousands separator; returns `0` for an empty or\n * digit-less value.\n *\n * @remarks IEEE-754 doubles represent integers exactly only up to\n * `Number.MAX_SAFE_INTEGER` (2^53 - 1, 16 digits). A masked value whose\n * integer part is longer — reachable whenever `numberPlaces` is left unset,\n * which is the default — parses back silently rounded (e.g. a typed\n * \"...345678\" can read back as \"...345680\"). The masked *string* stays\n * exact; only this numeric conversion loses digits. Fields that must not\n * lose one (money, identifiers) should either set `numberPlaces`, or check\n * {@link isDecimalValueSafe} first and fall back to parsing the masked\n * string with `BigInt`/a decimal library when it returns `false`.\n */\nexport function unmaskDecimal(value: string, options?: DecimalMaskOptions): number {\n const opts = resolveDecimalOptions(options)\n const { isNegative, intDigits, fracDigits } = computeDecimalParts(value, opts)\n const n = Number(fracDigits ? `${intDigits || '0'}.${fracDigits}` : intDigits || '0')\n // Never `-0`: a lone \"-\" with no digits is documented to parse as plain 0.\n return isNegative && n !== 0 ? -n : n\n}\n\n/**\n * Whether {@link unmaskDecimal}`(value, options)` can represent `value`'s\n * integer part exactly as a JS number, per the precision limit documented\n * there. Cheap enough to call on every change for a field where an\n * imprecise number would matter (money, account/ID-shaped fields).\n */\nexport function isDecimalValueSafe(value: string, options?: DecimalMaskOptions): boolean {\n const opts = resolveDecimalOptions(options)\n const { intDigits } = computeDecimalParts(value, opts)\n // 15 digits is always safe regardless of leading-digit magnitude; 16\n // sometimes is (up to 9,007,199,254,740,991) and sometimes isn't, so it's\n // deliberately treated as unsafe rather than checked digit-by-digit.\n return stripLeadingZeros(intDigits || '0').length <= 15\n}\n\n/**\n * After a Backspace removes the decimal separator itself, the integer and\n * fraction digit runs collapse into one continuous stream (e.g. \"25.00\"\n * with the caret right after \".\" → Backspace deletes the \".\" → \"2500\").\n * Left alone, that reads as one big integer (\"$2,500.00\"). This restores\n * the segment boundary instead: the trailing `decimalPlaces` digits are\n * still the fraction, and the digit right before them — the one that used\n * to sit at the end of the integer part — is the one Backspace actually\n * removed, so it's dropped (not kept) — \"$25.00\" → \"$2.00\".\n *\n * Only applies when `decimalPlaces` is a fixed positive number — that's\n * what \"the trailing N digits are the fraction\" means. Every reformat\n * re-appends `decimalSeparator` in that case, so its absence from `value`\n * is an unambiguous signal that this exact keystroke just deleted it — no\n * \"value before this keystroke\" snapshot is needed. With `decimalPlaces`\n * unset (optional, uncapped fraction) there's no fixed width to reconstruct\n * from, so the merged digits are left as one continuous integer instead —\n * the same reasoning `numberPlaces` uses for an unbounded integer part.\n * Returns `null` when there's nothing to restore (the separator is still\n * present, `decimalPlaces` is `0` or unset, or too few digits remain), so\n * the caller falls through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskUnmergingSeparator(\n value: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n if (opts.decimalPlaces == null || opts.decimalPlaces <= 0) return null\n\n const { isNegative, intDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (hasSeparator || intDigits.length < opts.decimalPlaces + 1) return null\n\n const preMergeIntLength = intDigits.length - opts.decimalPlaces\n const fracDigits = intDigits.slice(preMergeIntLength)\n const remainingInt = intDigits.slice(0, preMergeIntLength - 1)\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + remainingInt + opts.decimalSeparator + fracDigits\n return applyDecimalMask(raw, signPart.length + remainingInt.length, opts)\n}\n\n/**\n * Move a character the browser just inserted outside the editable number — at\n * or before the sign/prefix, at or after the suffix — to the nearest edge of\n * the number instead.\n *\n * The affixes are chrome, not content. Clicking at the far left of `\"$0.00\"`\n * and typing `\"2\"` means \"make this two dollars\", not \"put a 2 to the left of\n * the dollar sign\". Left alone the keystroke lands outside the number, reads\n * back as an extra leading digit (`\"$20.00\"`), and — worse — a caret one\n * position further right, which looks identical to the user, behaves\n * completely differently.\n *\n * Takes the inserted *length* rather than the text so it stays correct after\n * an earlier pass has rewritten the character in place (a numeric keypad's\n * `\",\"` normalized to the configured decimal separator, say); whatever now\n * occupies that span is what gets moved.\n *\n * `value`/`caret` are the state *after* the browser applied the insertion,\n * the same post-insertion snapshot the rest of this module expects. Returns\n * `null` when the character already landed inside the number, so the caller\n * carries on with the value it has.\n */\nexport function relocateAffixInsertion(\n value: string,\n caret: number,\n insertedLength: number,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n if (insertedLength <= 0) return null\n const insertIdx = caret - insertedLength\n if (insertIdx < 0 || caret > value.length) return null\n\n // The value as it stood before this keystroke — the only form in which the\n // affixes are guaranteed to still be sitting at their own edges.\n const before = value.slice(0, insertIdx) + value.slice(caret)\n const opts = resolveDecimalOptions(options)\n const { bodyStart, body } = splitAffixes(before, opts)\n const editEnd = bodyStart + body.length\n\n const moveTo = insertIdx < bodyStart ? bodyStart : insertIdx > editEnd ? editEnd : -1\n if (moveTo < 0) return null\n\n const inserted = value.slice(insertIdx, caret)\n return {\n value: before.slice(0, moveTo) + inserted + before.slice(moveTo),\n caret: moveTo + insertedLength,\n }\n}\n\n/**\n * Special-cases typing a single digit into an integer segment that isn't\n * yet full of *real* digits — either because it's still the auto-inserted\n * zero placeholder (\"0\", or a wider \"00\" from a `numberPlaces`-padded field\n * that hasn't been touched), or because `numberPlaces` is left-padding a\n * partially-typed segment with synthetic zeros (e.g. \"02\" is really just\n * the one real digit \"2\", padded out to width 2 for display). Those padding\n * zeros aren't editable content, so the new digit extends the real digit\n * stream instead of combining with a padding zero at the caret:\n *\n * - \"$0.00\" + \"2\" → \"$2.00\" (not \"$20.00\")\n * - a `numberPlaces: 2` time field's untouched \"00:00\" + \"5\" → \"05:00\"\n * - that same field's \"02:00\" (one real digit, one padding zero) + \"4\" →\n * \"24:00\" — the real \"2\" is kept, the padding \"0\" is not\n *\n * A segment that's already full of real digits — e.g. \"23:00\", both digits\n * genuinely typed — doesn't match here and falls through to the default\n * {@link applyDecimalMask}, which already drops the overflow keystroke\n * (typing a 3rd real digit leaves \"23:00\" unchanged).\n *\n * `value`/`caret` must be the state *after* the browser has already\n * inserted `digit` at `caret - 1` (the same post-insertion snapshot\n * `applyDecimalMask` itself expects from `bindDecimal`). Returns `null`\n * when the pattern doesn't apply, so the caller falls through to a plain\n * {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskReplacingLoneZero(\n value: string,\n caret: number,\n digit: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n const insertIdx = caret - 1\n if (insertIdx < 0 || value[insertIdx] !== digit) return null\n\n const withoutDigit = value.slice(0, insertIdx) + value.slice(caret)\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(withoutDigit, opts)\n\n const realDigits = stripLeadingZeros(intDigits || '0')\n const hasRealDigits = realDigits !== '0'\n const hasPaddingRoom =\n hasRealDigits && opts.numberPlaces != null && realDigits.length < opts.numberPlaces\n if (hasRealDigits && !hasPaddingRoom) return null\n\n const prefixLen = (isNegative ? 1 : 0) + opts.prefix.length\n if (insertIdx < prefixLen || insertIdx > prefixLen + intDigits.length) return null\n\n const signPart = isNegative ? '-' : ''\n const newIntDigits = (hasRealDigits ? realDigits : '') + digit\n const raw = signPart + newIntDigits + (hasSeparator ? opts.decimalSeparator + fracDigits : '')\n return applyDecimalMask(raw, signPart.length + newIntDigits.length, opts)\n}\n\n/**\n * Format a plain JS number into its masked display string. With a fixed\n * `decimalPlaces` the fraction is rounded/padded to that exact width, even\n * for a whole number; left unset, the fraction is only shown when the value\n * actually has one, with as many digits as `value` naturally carries (no\n * padding, no rounding).\n */\nexport function formatDecimalValue(value: number, options?: DecimalMaskOptions): string {\n const opts = resolveDecimalOptions(options)\n if (!Number.isFinite(value)) return ''\n\n const isNegative = opts.allowNegative && value < 0\n const abs = Math.abs(value)\n let fixed = opts.decimalPlaces != null ? abs.toFixed(opts.decimalPlaces) : String(abs)\n if (fixed.indexOf('e') >= 0) {\n // With `decimalPlaces` set, only |values| ≥ 1e21 reach here (`toFixed`\n // never exponentiates below that) and those floats are exact integers —\n // so expanding `String(abs)` loses nothing, and the fixed-width fraction\n // is pure zero padding.\n fixed = expandExponent(String(abs)) +\n (opts.decimalPlaces ? '.' + '0'.repeat(opts.decimalPlaces) : '')\n }\n const dotIdx = fixed.indexOf('.')\n const intRaw = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx)\n const fracPart = dotIdx === -1 ? '' : fixed.slice(dotIdx + 1)\n const { groupedInt } = formatIntegerPart(intRaw, opts)\n const showFraction = opts.decimalPlaces === 0 ? false : fracPart !== ''\n const numberStr = groupedInt + (showFraction ? opts.decimalSeparator + fracPart : '')\n\n return (isNegative ? '-' : '') + opts.prefix + numberStr + opts.suffix\n}\n","import {\n attachBinder,\n createFrameScheduler,\n editStillPending,\n getCaret,\n isAlreadyBound,\n setCaret,\n} from './bind-shared'\nimport { isDigitChar } from './chars'\nimport {\n applyDecimalMask,\n applyDecimalMaskReplacingLoneZero,\n applyDecimalMaskUnmergingSeparator,\n relocateAffixInsertion,\n resolveDecimalOptions,\n unmaskDecimal,\n} from './decimal-mask'\nimport { isIos } from './platform'\nimport type { BindDecimalOptions, DecimalMaskOptions, MaskResult } from './types'\n\nfunction toBindDecimalOptions(\n second:\n | BindDecimalOptions\n | ((value: string, numericValue: number) => void)\n | null\n | undefined,\n): BindDecimalOptions {\n if (second == null) return {}\n if (typeof second === 'function') return { onChange: second }\n return second\n}\n\ninterface DecimalEdit {\n insertedText?: string | null\n insertedAt?: number\n inputType?: string\n}\n\n/**\n * Bind a decimal/currency mask to an input element.\n *\n * Same contract as {@link bind}: idempotent (marked with `data-masked`),\n * returns a dispose function, and reformats post-mutation `input` events with\n * a keyboard/paste fallback for older browsers. Unlike the pattern masks,\n * there is no fixed pattern — the integer part grows and shrinks freely;\n * formatting is driven entirely by `options`.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.\n */\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n options?: BindDecimalOptions | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n onChange: ((value: string, numericValue: number) => void) | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n second?: BindDecimalOptions | ((value: string, numericValue: number) => void) | null,\n): () => void {\n if (isAlreadyBound(input)) return () => {}\n\n const {\n onChange,\n autocomplete,\n autocorrect,\n autocapitalize,\n spellcheck,\n ...maskOptions\n } = toBindDecimalOptions(second)\n const decimalOptions: DecimalMaskOptions = maskOptions\n const { decimalSeparator, decimalPlaces } = resolveDecimalOptions(decimalOptions)\n\n let lockInput = false\n let isComposing = false\n let skipNextKeyup = false\n let pendingSeparatorEdit: { text: string; starts: number[] } | null = null\n const { scheduleFrame, cancelPendingFrames } = createFrameScheduler()\n\n const applyResult = (target: HTMLInputElement, m: MaskResult): void => {\n target.value = m.value\n setCaret(target, m.caret)\n onChange?.(m.value, unmaskDecimal(m.value, decimalOptions))\n }\n\n const formatCurrentValue = (target: HTMLInputElement, edit: DecimalEdit = {}): void => {\n let pos = getCaret(target)\n\n const normalizeSeparator = (text: string, starts: Array<number | undefined>): boolean => {\n if (\n decimalPlaces === 0 ||\n text.length !== 1 ||\n (text !== '.' && text !== ',') ||\n text === decimalSeparator\n ) {\n return false\n }\n\n for (const start of starts) {\n if (\n start != null &&\n start >= 0 &&\n target.value.slice(start, start + text.length) === text\n ) {\n target.value =\n target.value.slice(0, start) +\n decimalSeparator +\n target.value.slice(start + text.length)\n pos = start + text.length <= pos ? pos + decimalSeparator.length - text.length : pos\n setCaret(target, pos)\n return true\n }\n }\n\n return false\n }\n\n if (pendingSeparatorEdit) {\n const { text, starts } = pendingSeparatorEdit\n pendingSeparatorEdit = null\n normalizeSeparator(text, starts)\n }\n\n // Backspace that just deleted the decimal separator merges the integer and\n // fraction digit runs into one continuous stream. On mobile this can arrive\n // as an `input` event without a reliable keyboard event, so the special case\n // lives in the shared post-mutation formatter.\n if (edit.inputType === 'deleteContentBackward') {\n const unmerged = applyDecimalMaskUnmergingSeparator(target.value, decimalOptions)\n if (unmerged) {\n applyResult(target, unmerged)\n return\n }\n }\n\n // A numeric keypad (or a locale mismatch) may only offer \".\" or \",\".\n // Normalize whichever one was just inserted to the configured\n // `decimalSeparator` before parsing, so mobile `input`-only edits still open\n // the fraction segment correctly.\n const insertedText = edit.insertedText\n if (insertedText != null) {\n normalizeSeparator(insertedText, [edit.insertedAt, pos - insertedText.length])\n }\n\n // The prefix and suffix are chrome, not content. A character the browser\n // dropped into them — caret parked at the far left of \"$0.00\", or out past\n // a suffix — is pulled to the nearest edge of the number, so every caret\n // position that *looks* like \"the start of the number\" behaves like it.\n // Runs after separator normalization so it moves whatever now occupies\n // that span, not the key the user originally pressed.\n if (insertedText != null && insertedText.length > 0) {\n const relocated = relocateAffixInsertion(\n target.value,\n pos,\n insertedText.length,\n decimalOptions,\n )\n if (relocated) {\n target.value = relocated.value\n pos = relocated.caret\n }\n }\n\n // Typing a digit into a field whose integer part isn't yet full of real\n // digits extends the real digit stream instead of combining with a padding\n // zero.\n const insertedDigit =\n insertedText != null && insertedText.length === 1 && isDigitChar(insertedText)\n ? insertedText\n : undefined\n const replaced = insertedDigit\n ? applyDecimalMaskReplacingLoneZero(target.value, pos, insertedDigit, decimalOptions)\n : null\n\n applyResult(target, replaced ?? applyDecimalMask(target.value, pos, decimalOptions))\n }\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n scheduleFrame(() => {\n formatCurrentValue(target)\n })\n }\n\n // Deliberately does NOT bail out while `isComposing` is true — see the\n // matching comment in `bind.ts`. Android wraps typing in a full-QWERTY\n // text field (e.g. a decimal input without `inputmode=\"decimal\"`) into an\n // IME composition session for autocorrect bookkeeping, not just genuine\n // multi-candidate input, and that composition may never end while the\n // user is still entering a space-less value — so waiting for\n // `compositionend` before formatting made the mask appear broken there.\n const onInput = (e: Event): void => {\n const inputEvent = e as InputEvent\n const target = e.target as HTMLInputElement\n cancelPendingFrames()\n lockInput = false\n pendingSeparatorEdit = null\n skipNextKeyup = true\n formatCurrentValue(target, {\n insertedText: typeof inputEvent.data === 'string' ? inputEvent.data : null,\n inputType: inputEvent.inputType,\n })\n }\n\n const onCompositionStart = (): void => {\n isComposing = true\n cancelPendingFrames()\n lockInput = false\n pendingSeparatorEdit = null\n }\n\n const onCompositionEnd = (e: Event): void => {\n isComposing = false\n skipNextKeyup = true\n formatCurrentValue(e.target as HTMLInputElement)\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const keyStart = getCaret(target)\n const oldValue = target.value\n\n if (isComposing) return\n\n if (isIos() && skipNextKeyup) {\n skipNextKeyup = false\n return\n }\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n scheduleFrame(() => {\n if (editStillPending(target, oldValue)) {\n lockInput = false\n return\n }\n formatCurrentValue(target)\n scheduleFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n if (\n isCharInsert &&\n decimalPlaces !== 0 &&\n (ke.key === '.' || ke.key === ',') &&\n ke.key !== decimalSeparator\n ) {\n pendingSeparatorEdit = { text: ke.key, starts: [keyStart, keyStart - ke.key.length] }\n }\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke.\n if (!isBackspace && !isDelete && !isCharInsert && !isUnidentified) return\n\n // Everything below reads `target.value`/`selectionStart` inside the rAF\n // callback rather than synchronously here, since the browser's native\n // character insertion for this keystroke isn't guaranteed to have landed\n // yet at the point a keydown listener runs — only by the next frame.\n scheduleFrame(() => {\n // Formatting now would collapse a still-pending native selection via\n // `setCaret` inside `formatCurrentValue` before the edit lands — see\n // `editStillPending`'s doc comment (the same Firefox race fixed in\n // `bind.ts`).\n if (editStillPending(target, oldValue)) return\n\n formatCurrentValue(target, {\n insertedText: isCharInsert ? ke.key : null,\n insertedAt: isCharInsert ? keyStart : undefined,\n inputType: isBackspace\n ? 'deleteContentBackward'\n : isDelete\n ? 'deleteContentForward'\n : undefined,\n })\n })\n }\n\n return attachBinder(\n input,\n 'decimal',\n { autocomplete, autocorrect, autocapitalize, spellcheck },\n Infinity,\n [onPaste, onInput, onCompositionStart, onCompositionEnd, onKey],\n cancelPendingFrames,\n )\n}\n","import { applyMask } from './apply-mask'\nimport type { ApplyMaskOptions, MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: MaskPattern\n private readonly _options: ApplyMaskOptions | undefined\n\n constructor(value: string, mask: MaskPattern, caret = 0, options?: ApplyMaskOptions) {\n this._value = value\n this._mask = mask\n this.caret = caret\n this._options = options\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret, this._options)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance; array patterns are resolved by data count when `process()` runs. */\nexport function buildMask(\n value: string,\n mask: MaskPattern,\n caret = 0,\n options?: ApplyMaskOptions,\n): Mask {\n return new Mask(value, mask, caret, options)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string {\n return buildMask(value, mask, 0, options).process()\n}\n"],"mappings":"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"}