raqam 0.2.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +344 -0
  3. package/dist/chunk-IG7CVIA2.js +14 -0
  4. package/dist/chunk-IG7CVIA2.js.map +1 -0
  5. package/dist/chunk-NBAZIJ5W.js +25 -0
  6. package/dist/chunk-NBAZIJ5W.js.map +1 -0
  7. package/dist/chunk-NSFX2EAT.js +14 -0
  8. package/dist/chunk-NSFX2EAT.js.map +1 -0
  9. package/dist/chunk-NTROGAES.js +14 -0
  10. package/dist/chunk-NTROGAES.js.map +1 -0
  11. package/dist/chunk-VOBTYII2.js +14 -0
  12. package/dist/chunk-VOBTYII2.js.map +1 -0
  13. package/dist/chunk-WTS5RY7S.js +16 -0
  14. package/dist/chunk-WTS5RY7S.js.map +1 -0
  15. package/dist/core.cjs +2 -0
  16. package/dist/core.cjs.map +1 -0
  17. package/dist/core.d.cts +351 -0
  18. package/dist/core.d.ts +351 -0
  19. package/dist/core.js +2 -0
  20. package/dist/core.js.map +1 -0
  21. package/dist/index-B8X3-9h1.d.cts +343 -0
  22. package/dist/index-B8X3-9h1.d.ts +343 -0
  23. package/dist/index.cjs +3 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +193 -0
  26. package/dist/index.d.ts +193 -0
  27. package/dist/index.js +3 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/locales/ar.cjs +36 -0
  30. package/dist/locales/ar.cjs.map +1 -0
  31. package/dist/locales/ar.d.cts +4 -0
  32. package/dist/locales/ar.d.ts +4 -0
  33. package/dist/locales/ar.js +4 -0
  34. package/dist/locales/ar.js.map +1 -0
  35. package/dist/locales/bn.cjs +36 -0
  36. package/dist/locales/bn.cjs.map +1 -0
  37. package/dist/locales/bn.d.cts +4 -0
  38. package/dist/locales/bn.d.ts +4 -0
  39. package/dist/locales/bn.js +4 -0
  40. package/dist/locales/bn.js.map +1 -0
  41. package/dist/locales/fa.cjs +38 -0
  42. package/dist/locales/fa.cjs.map +1 -0
  43. package/dist/locales/fa.d.cts +4 -0
  44. package/dist/locales/fa.d.ts +4 -0
  45. package/dist/locales/fa.js +4 -0
  46. package/dist/locales/fa.js.map +1 -0
  47. package/dist/locales/hi.cjs +36 -0
  48. package/dist/locales/hi.cjs.map +1 -0
  49. package/dist/locales/hi.d.cts +4 -0
  50. package/dist/locales/hi.d.ts +4 -0
  51. package/dist/locales/hi.js +4 -0
  52. package/dist/locales/hi.js.map +1 -0
  53. package/dist/locales/index.cjs +78 -0
  54. package/dist/locales/index.cjs.map +1 -0
  55. package/dist/locales/index.d.cts +5 -0
  56. package/dist/locales/index.d.ts +5 -0
  57. package/dist/locales/index.js +8 -0
  58. package/dist/locales/index.js.map +1 -0
  59. package/dist/locales/th.cjs +36 -0
  60. package/dist/locales/th.cjs.map +1 -0
  61. package/dist/locales/th.d.cts +4 -0
  62. package/dist/locales/th.d.ts +4 -0
  63. package/dist/locales/th.js +4 -0
  64. package/dist/locales/th.js.map +1 -0
  65. package/dist/react.cjs +3 -0
  66. package/dist/react.cjs.map +1 -0
  67. package/dist/react.d.cts +3 -0
  68. package/dist/react.d.ts +3 -0
  69. package/dist/react.js +3 -0
  70. package/dist/react.js.map +1 -0
  71. package/package.json +170 -0
package/dist/core.d.ts ADDED
@@ -0,0 +1,351 @@
1
+ interface LocaleInfo {
2
+ /** Decimal separator for this locale (e.g. "." en-US, "," de-DE, "٫" fa-IR) */
3
+ decimalSeparator: string;
4
+ /** Grouping (thousands) separator (e.g. "," en-US, "." de-DE, "٬" fa-IR) */
5
+ groupingSeparator: string;
6
+ /** Minus sign character (usually "-" but can differ) */
7
+ minusSign: string;
8
+ /** Locale's representation of "0" (e.g. "0" for Latin, "۰" for Persian) */
9
+ zero: string;
10
+ /** Whether this is an RTL locale */
11
+ isRTL: boolean;
12
+ }
13
+ interface FormatResult {
14
+ formatted: string;
15
+ parts: Intl.NumberFormatPart[];
16
+ }
17
+ interface ParseResult {
18
+ /** The parsed number, or null if empty / un-parseable */
19
+ value: number | null;
20
+ /** True for valid numbers */
21
+ isValid: boolean;
22
+ /**
23
+ * True for valid-but-incomplete input that must not be reformatted yet:
24
+ * "-", "1.", "1.0", "1.00", etc.
25
+ */
26
+ isIntermediate: boolean;
27
+ }
28
+ /**
29
+ * boolean[] of length formattedValue.length + 1.
30
+ * true → cursor may rest at this index
31
+ * false → cursor must not rest here (e.g. inside a thousands separator)
32
+ */
33
+ type CaretBoundary = boolean[];
34
+ /** Unicode codepoint range [start, end] inclusive representing a digit block */
35
+ type DigitBlock = readonly [number, number];
36
+ interface UseNumberFieldStateOptions {
37
+ /** Controlled numeric value */
38
+ value?: number | null;
39
+ /** Uncontrolled default value */
40
+ defaultValue?: number | null;
41
+ /** Fires on every meaningful value change */
42
+ onChange?: (value: number | null) => void;
43
+ /** BCP 47 locale tag (default: browser locale) */
44
+ locale?: string;
45
+ /** Full Intl.NumberFormatOptions — currency, percent, decimal, etc. */
46
+ formatOptions?: Intl.NumberFormatOptions;
47
+ /** Minimum allowed value */
48
+ minValue?: number;
49
+ /** Maximum allowed value */
50
+ maxValue?: number;
51
+ /** Step amount for increment/decrement (default: 1) */
52
+ step?: number;
53
+ /** Large step — Shift+Arrow / PageUp/Down (default: step * 10) */
54
+ largeStep?: number;
55
+ /** Small step — Meta/Ctrl+Arrow (default: step * 0.1) */
56
+ smallStep?: number;
57
+ /** Allow negative values (default: true) */
58
+ allowNegative?: boolean;
59
+ /** Allow decimal values (default: true) */
60
+ allowDecimal?: boolean;
61
+ /** Override maximumFractionDigits from formatOptions */
62
+ maximumFractionDigits?: number;
63
+ /** Override minimumFractionDigits from formatOptions */
64
+ minimumFractionDigits?: number;
65
+ /** Always show exactly maximumFractionDigits decimal places */
66
+ fixedDecimalScale?: boolean;
67
+ /** When clamping happens (default: "blur") */
68
+ clampBehavior?: "blur" | "strict" | "none";
69
+ /** Apply live formatting while typing (default: true) */
70
+ liveFormat?: boolean;
71
+ /** Arbitrary prefix string (e.g. "$") */
72
+ prefix?: string;
73
+ /** Arbitrary suffix string (e.g. " تومان") */
74
+ suffix?: string;
75
+ /** Disable the field */
76
+ disabled?: boolean;
77
+ /** Make the field read-only */
78
+ readOnly?: boolean;
79
+ /** Mark the field as required */
80
+ required?: boolean;
81
+ /**
82
+ * Allow values outside min/max to be typed and committed without clamping.
83
+ * Useful when server-side validation handles clamping.
84
+ * When true, aria-invalid is set when value is out of range.
85
+ * default: false
86
+ */
87
+ allowOutOfRange?: boolean;
88
+ /**
89
+ * Custom validation function. Called on every value change.
90
+ * - Return `true` or `null`/`undefined` → valid
91
+ * - Return `false` → invalid (aria-invalid set, no error message)
92
+ * - Return a `string` → invalid with that string as the error message
93
+ */
94
+ validate?: (value: number | null) => boolean | string | null | undefined;
95
+ /**
96
+ * Fires with the raw unformatted string the user typed, preserving full
97
+ * decimal precision before JS float conversion. Useful for financial apps
98
+ * that need arbitrary-precision string arithmetic.
99
+ * Fires alongside `onChange`.
100
+ */
101
+ onRawChange?: (rawValue: string | null) => void;
102
+ /**
103
+ * Custom format function. When provided, replaces the built-in Intl.NumberFormat
104
+ * formatter for display purposes. Also used for initial display value.
105
+ */
106
+ formatValue?: (value: number) => string;
107
+ /**
108
+ * Custom parse function. When provided, replaces the built-in locale-aware parser.
109
+ * Must return `{ value: number | null, isIntermediate: boolean }`.
110
+ */
111
+ parseValue?: (input: string) => {
112
+ value: number | null;
113
+ isIntermediate: boolean;
114
+ };
115
+ }
116
+ interface NumberFieldState {
117
+ /** The display string shown in the input */
118
+ inputValue: string;
119
+ /** The parsed numeric value (null for empty/invalid) */
120
+ numberValue: number | null;
121
+ /**
122
+ * The raw string value exactly as the user typed it (before formatting).
123
+ * Preserves full decimal precision — useful for financial arbitrary-precision math.
124
+ */
125
+ rawValue: string | null;
126
+ /** Whether increment is currently possible */
127
+ canIncrement: boolean;
128
+ /** Whether decrement is currently possible */
129
+ canDecrement: boolean;
130
+ /** Whether the ScrubArea is currently being dragged */
131
+ isScrubbing: boolean;
132
+ /** Update the isScrubbing state (called by useScrubArea) */
133
+ setIsScrubbing: (val: boolean) => void;
134
+ /** Whether the input is currently focused */
135
+ isFocused: boolean;
136
+ /** Update the isFocused state (called by useNumberField) */
137
+ setIsFocused: (val: boolean) => void;
138
+ /** Current validation state — 'valid' if no validate prop, or based on validate result */
139
+ validationState: "valid" | "invalid";
140
+ /** Error message from validate() if it returned a string, otherwise null */
141
+ validationError: string | null;
142
+ /** Internal: set the reason for the next onChange call (used by useNumberField) */
143
+ _setLastChangeReason: (reason: ChangeReason) => void;
144
+ /** Internal: read the current change reason (used by NumberField.Root) */
145
+ _getLastChangeReason: () => ChangeReason;
146
+ /** Update display string (triggers parse + onChange) */
147
+ setInputValue: (val: string) => void;
148
+ /** Directly set the numeric value (triggers format + onChange) */
149
+ setNumberValue: (val: number | null) => void;
150
+ /** Format + clamp on blur — call from onBlur */
151
+ commit: () => void;
152
+ /** Increment by step */
153
+ increment: (amount?: number) => void;
154
+ /** Decrement by step */
155
+ decrement: (amount?: number) => void;
156
+ /** Jump to maxValue */
157
+ incrementToMax: () => void;
158
+ /** Jump to minValue */
159
+ decrementToMin: () => void;
160
+ /** Raw options (for hooks that need them) */
161
+ options: UseNumberFieldStateOptions;
162
+ }
163
+ /** Reason for a value change — propagated through onValueChange details */
164
+ type ChangeReason = "input" | "clear" | "blur" | "paste" | "keyboard" | "increment" | "decrement" | "wheel" | "scrub";
165
+ interface UseNumberFieldProps extends UseNumberFieldStateOptions {
166
+ /** Visible label text (used for aria-label fallback) */
167
+ label?: string;
168
+ "aria-label"?: string;
169
+ "aria-describedby"?: string;
170
+ "aria-labelledby"?: string;
171
+ /** Form field name — renders a hidden input */
172
+ name?: string;
173
+ /** id for the input element */
174
+ id?: string;
175
+ /** Enable mouse-wheel increment/decrement */
176
+ allowMouseWheel?: boolean;
177
+ /**
178
+ * Controls what is placed in the clipboard on copy/cut.
179
+ * - 'formatted' (default): browser-native copy of display string
180
+ * - 'raw': numberValue as a plain JS number string (e.g. "1234.56")
181
+ * - 'number': alias for 'raw'
182
+ */
183
+ copyBehavior?: "formatted" | "raw" | "number";
184
+ /** Milliseconds before press-and-hold repeat starts (default: 400) */
185
+ stepHoldDelay?: number;
186
+ /** Initial milliseconds between repeats during press-and-hold (default: 200) */
187
+ stepHoldInterval?: number;
188
+ onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
189
+ onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
190
+ }
191
+ interface NumberFieldAria {
192
+ labelProps: React.LabelHTMLAttributes<HTMLLabelElement>;
193
+ groupProps: React.HTMLAttributes<HTMLDivElement>;
194
+ inputProps: React.InputHTMLAttributes<HTMLInputElement>;
195
+ hiddenInputProps: React.InputHTMLAttributes<HTMLInputElement> | null;
196
+ incrementButtonProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
197
+ decrementButtonProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
198
+ descriptionProps: React.HTMLAttributes<HTMLElement>;
199
+ errorMessageProps: React.HTMLAttributes<HTMLElement>;
200
+ }
201
+ type StateRenderFn = (props: Record<string, unknown>, state: NumberFieldState) => React.ReactElement;
202
+ type RenderProp = React.ReactElement | StateRenderFn;
203
+ interface NumberFieldRootProps extends UseNumberFieldProps {
204
+ children?: React.ReactNode;
205
+ /** CSS class for the root wrapper div */
206
+ className?: string;
207
+ /** Inline style for the root wrapper div */
208
+ style?: React.CSSProperties;
209
+ /** Fires on every meaningful value change */
210
+ onValueChange?: (value: number | null, details: {
211
+ reason: ChangeReason;
212
+ formattedValue: string;
213
+ event?: React.SyntheticEvent;
214
+ }) => void;
215
+ /** Fires only on commit (blur, Enter) */
216
+ onValueCommitted?: (value: number | null, details: {
217
+ reason: "blur" | "keyboard";
218
+ }) => void;
219
+ }
220
+
221
+ /**
222
+ * Format presets — named Intl.NumberFormatOptions configurations for common
223
+ * number input patterns. Use these as the `formatOptions` prop value.
224
+ *
225
+ * @example
226
+ * import { presets } from 'raqam'
227
+ * <NumberField.Root formatOptions={presets.currency('USD')} />
228
+ * <NumberField.Root formatOptions={presets.percent} />
229
+ * <NumberField.Root formatOptions={presets.compact} />
230
+ */
231
+ declare const presets: {
232
+ /** Currency with standard sign display. Shorthand for `{ style:'currency', currency:code }`. */
233
+ readonly currency: (code: string) => Intl.NumberFormatOptions;
234
+ /**
235
+ * Accounting currency — negatives shown as `(1,234.56)` instead of `-$1,234.56`.
236
+ * Requires the accounting format parser fix (built-in to raqam).
237
+ */
238
+ readonly accounting: (code: string) => Intl.NumberFormatOptions;
239
+ /** Percentage — formats 0.42 as "42%" */
240
+ readonly percent: Intl.NumberFormatOptions;
241
+ /** Compact short — "1.2K", "3.4M" */
242
+ readonly compact: Intl.NumberFormatOptions;
243
+ /** Compact long — "1.2 thousand", "3.4 million" */
244
+ readonly compactLong: Intl.NumberFormatOptions;
245
+ /** Scientific notation — "1.234E3" */
246
+ readonly scientific: Intl.NumberFormatOptions;
247
+ /** Engineering notation — exponents always multiples of 3 */
248
+ readonly engineering: Intl.NumberFormatOptions;
249
+ /** Integer — no decimal places */
250
+ readonly integer: Intl.NumberFormatOptions;
251
+ /**
252
+ * Financial — always exactly 2 decimal places (fixed scale).
253
+ * Combine with `fixedDecimalScale` prop for strict display.
254
+ */
255
+ readonly financial: Intl.NumberFormatOptions;
256
+ /**
257
+ * Unit — shorthand for `{ style:'unit', unit:unitCode }`.
258
+ * @example presets.unit('kilometer-per-hour') → { style:'unit', unit:'kilometer-per-hour' }
259
+ */
260
+ readonly unit: (unit: string) => Intl.NumberFormatOptions;
261
+ };
262
+
263
+ interface LocaleConfig {
264
+ /** Extra digit block ranges to register */
265
+ digitBlocks?: DigitBlock[];
266
+ }
267
+ /**
268
+ * Register additional digit blocks (called by locale plugins as a side effect).
269
+ * Duplicate ranges are silently ignored.
270
+ */
271
+ declare function registerLocale(config: LocaleConfig): void;
272
+ /**
273
+ * Normalise any Unicode decimal digit in `input` to its ASCII equivalent (0–9).
274
+ * Non-digit characters pass through unchanged.
275
+ */
276
+ declare function normalizeDigits(input: string): string;
277
+
278
+ interface FormatterOptions {
279
+ locale?: string;
280
+ formatOptions?: Intl.NumberFormatOptions;
281
+ prefix?: string;
282
+ suffix?: string;
283
+ minimumFractionDigits?: number;
284
+ maximumFractionDigits?: number;
285
+ fixedDecimalScale?: boolean;
286
+ }
287
+ interface Formatter {
288
+ format(value: number): string;
289
+ formatToParts(value: number): Intl.NumberFormatPart[];
290
+ getLocaleInfo(): LocaleInfo;
291
+ formatResult(value: number): FormatResult;
292
+ }
293
+ /**
294
+ * Create a formatter instance. Intl.NumberFormat is cached — safe to call
295
+ * on every render.
296
+ */
297
+ declare function createFormatter(opts: FormatterOptions): Formatter;
298
+
299
+ interface ParserOptions {
300
+ locale?: string;
301
+ formatOptions?: Intl.NumberFormatOptions;
302
+ allowNegative?: boolean;
303
+ allowDecimal?: boolean;
304
+ prefix?: string;
305
+ suffix?: string;
306
+ }
307
+ interface Parser {
308
+ parse(input: string): ParseResult;
309
+ isIntermediate(input: string): boolean;
310
+ getLocaleInfo(): LocaleInfo;
311
+ }
312
+ /**
313
+ * Create a locale-aware parser. Separator characters are extracted from
314
+ * Intl.NumberFormat — never hardcoded.
315
+ */
316
+ declare function createParser(opts?: ParserOptions): Parser;
317
+
318
+ /**
319
+ * Build a boolean array of length `formattedValue.length + 1`.
320
+ * `true` → cursor may rest at this position.
321
+ * `false` → cursor must snap away (sits inside a formatting-only character
322
+ * such as a grouping separator or a currency prefix).
323
+ *
324
+ * Rules:
325
+ * - Start and end positions are always valid.
326
+ * - A position immediately AFTER a grouping separator is invalid (the
327
+ * cursor would look like it's between two digits but moving left would
328
+ * skip the comma).
329
+ * - A position immediately BEFORE a grouping separator is valid.
330
+ */
331
+ declare function getCaretBoundary(formattedValue: string, info: LocaleInfo): CaretBoundary;
332
+ /**
333
+ * Compute the new cursor position in `newFormatted` that corresponds
334
+ * semantically to `oldCursor` in `oldInput`.
335
+ *
336
+ * Algorithm (3 stages from the spec):
337
+ *
338
+ * 1. Count accepted characters before `oldCursor` in `oldInput`.
339
+ * 2. Adjust for backspace-over-separator edge case.
340
+ * 3. Walk `newFormatted` to find the position where the same count of
341
+ * accepted chars precede it; snap to the nearest valid boundary.
342
+ *
343
+ * @param oldInput The raw string the user just typed into (pre-format)
344
+ * @param oldCursor selectionStart captured from the native event
345
+ * @param newFormatted The formatted string we're about to display
346
+ * @param info Locale separators
347
+ * @param inputType e.nativeEvent.inputType (optional — for backspace detection)
348
+ */
349
+ declare function computeNewCursorPosition(oldInput: string, oldCursor: number, newFormatted: string, info: LocaleInfo, inputType?: string): number;
350
+
351
+ export { type CaretBoundary, type ChangeReason, type DigitBlock, type FormatResult, type Formatter, type FormatterOptions, type LocaleConfig, type LocaleInfo, type NumberFieldAria, type NumberFieldRootProps, type NumberFieldState, type ParseResult, type Parser, type ParserOptions, type RenderProp, type StateRenderFn, type UseNumberFieldProps, type UseNumberFieldStateOptions, computeNewCursorPosition, createFormatter, createParser, getCaretBoundary, normalizeDigits, presets, registerLocale };
package/dist/core.js ADDED
@@ -0,0 +1,2 @@
1
+ var L={currency:e=>({style:"currency",currency:e}),accounting:e=>({style:"currency",currency:e,currencySign:"accounting"}),percent:{style:"percent"},compact:{notation:"compact"},compactLong:{notation:"compact",compactDisplay:"long"},scientific:{notation:"scientific"},engineering:{notation:"engineering"},integer:{maximumFractionDigits:0},financial:{minimumFractionDigits:2,maximumFractionDigits:2},unit:e=>({style:"unit",unit:e})};var O=[[1632,1641],[1776,1785],[2406,2415],[2534,2543],[3664,3673]],p=[...O];function D(e){if(e.digitBlocks)for(let t of e.digitBlocks)p.some(([i])=>i===t[0])||p.push(t);}function g(e){return /[^\u0020-\u007e]/.test(e)?e.replace(/\p{Nd}/gu,t=>{let r=t.codePointAt(0);for(let[o,s]of p)if(r>=o&&r<=s)return String(r-o);let i=Number.parseInt(t,10);return Number.isNaN(i)?t:String(i)}):e}var x=new Map;function F(e,t){let r=`${e??""}::${JSON.stringify(t??{})}`,i=x.get(r);return i||(i=new Intl.NumberFormat(e,t),x.set(r,i)),i}function P(e,t){let r=F(e,t),i=r.formatToParts(12345.6),o=".",s=",",f="-",c="0";for(let m of i)m.type==="decimal"&&(o=m.value),m.type==="group"&&(s=m.value),m.type==="minusSign"&&(f=m.value);let u=r.formatToParts(0);for(let m of u)if(m.type==="integer"){c=m.value;break}let a=/^(ar|he|fa|ur|syc|nqo|ug|yi)/i,n=r.resolvedOptions().locale,l=a.test(n);return {decimalSeparator:o,groupingSeparator:s,minusSign:f,zero:c,isRTL:l}}function d(e){let t={...e.formatOptions};e.minimumFractionDigits!==void 0&&(t.minimumFractionDigits=e.minimumFractionDigits),e.maximumFractionDigits!==void 0&&(t.maximumFractionDigits=e.maximumFractionDigits),e.fixedDecimalScale&&e.maximumFractionDigits!==void 0&&(t.minimumFractionDigits=e.maximumFractionDigits,t.maximumFractionDigits=e.maximumFractionDigits);let r=F(e.locale,t),i=null;function o(){return i||(i=P(e.locale,t)),i}function s(u){let a=r.formatToParts(u);if(!e.prefix&&!e.suffix)return a;let n=[];return e.prefix&&n.push({type:"literal",value:e.prefix}),n.push(...a),e.suffix&&n.push({type:"literal",value:e.suffix}),n}function f(u){if(!Number.isFinite(u))return "";let a=r.format(u);return (e.prefix??"")+a+(e.suffix??"")}function c(u){let a=s(u);return {formatted:a.map(l=>l.value).join(""),parts:a}}return {format:f,formatToParts:s,getLocaleInfo:o,formatResult:c}}function I(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function b(e,t,r,i){let o=I(t.decimalSeparator),s=I(t.minusSign);return !!(r&&(e==="-"||e===t.minusSign)||i&&new RegExp(`^${s}?\\d+${o}$`).test(e)||i&&new RegExp(`^${s}?\\d+${o}\\d*0+$`).test(e))}function S(e={}){let t=e.allowNegative??true,r=e.allowDecimal??true,i=d({locale:e.locale,formatOptions:e.formatOptions,prefix:e.prefix,suffix:e.suffix});function o(){return i.getLocaleInfo()}function s(u){let a=o(),n=g(u),l=n.match(/^\((.+)\)$/);return l&&(n=`-${l[1]}`),e.prefix&&n.startsWith(e.prefix)&&(n=n.slice(e.prefix.length)),e.suffix&&n.endsWith(e.suffix)&&(n=n.slice(0,-e.suffix.length)),a.groupingSeparator&&(n=n.split(a.groupingSeparator).join("")),a.decimalSeparator!=="."&&(n=n.split(a.decimalSeparator).join(".")),a.minusSign!=="-"&&(n=n.split(a.minusSign).join("-")),n=n.replace(/[^\d.\-]/g,"").trim(),n}function f(u){if(!u||u.trim()==="")return {value:null,isValid:false,isIntermediate:false};let a=o();if(b(u,a,t,r))return {value:null,isValid:false,isIntermediate:true};let n=s(u);if(n==="")return {value:null,isValid:false,isIntermediate:false};if(n==="-")return {value:null,isValid:false,isIntermediate:t};if(!/^-?\d+\.?\d*$/.test(n))return {value:null,isValid:false,isIntermediate:false};if(!t&&n.startsWith("-"))return {value:null,isValid:false,isIntermediate:false};if(!r&&n.includes("."))return {value:null,isValid:false,isIntermediate:false};let l=Number.parseFloat(n);return Number.isFinite(l)?{value:l,isValid:true,isIntermediate:false}:{value:null,isValid:false,isIntermediate:false}}function c(u){let a=o();return b(u,a,t,r)}return {parse:f,isIntermediate:c,getLocaleInfo:o}}function y(e,t){return e>="0"&&e<="9"||e===t.decimalSeparator||e===t.minusSign||e==="-"}function v(e,t,r){let i=g(e),o=0;for(let s=0;s<t&&s<i.length;s++)y(i[s],r)&&o++;return o}function N(e,t){let r=e.length,i=new Array(r+1).fill(true);for(let o=0;o<r;o++)e[o]===t.groupingSeparator&&(i[o+1]=false);return i[0]=true,i[r]=true,i}function B(e,t,r,i,o){let s=g(e),f=v(s,t,i);o==="deleteContentBackward"&&t>0&&e[t-1]===i.groupingSeparator&&(f=Math.max(0,f-1));let c=N(r,i),u=g(r),a=0,n=0;for(let l=0;l<u.length;l++){if(a===f){n=l;break}y(u[l],i)&&a++,n=l+1;}return f>0&&a<f&&(n=r.length),n=R(n,c),n}function R(e,t){if(t[e])return e;for(let r=e+1;r<t.length;r++)if(t[r])return r;for(let r=e-1;r>=0;r--)if(t[r])return r;return 0}export{B as computeNewCursorPosition,d as createFormatter,S as createParser,N as getCaretBoundary,g as normalizeDigits,L as presets,D as registerLocale};//# sourceMappingURL=core.js.map
2
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/presets.ts","../src/core/normalizer.ts","../src/core/formatter.ts","../src/core/parser.ts","../src/core/cursor.ts"],"names":["presets","code","unit","BUILTIN_DIGIT_BLOCKS","registeredBlocks","registerLocale","config","block","s","normalizeDigits","input","ch","start","end","digit","formatterCache","getFormatter","locale","options","key","fmt","extractLocaleInfo","parts","decimalSeparator","groupingSeparator","minusSign","zero","part","zeroParts","rtlLocales","resolvedLocale","isRTL","createFormatter","opts","intlOptions","intlFmt","cachedLocaleInfo","getLocaleInfo","formatToParts","value","result","format","formatted","formatResult","p","escapeRegex","checkIntermediate","normalized","info","allowNegative","allowDecimal","dec","minus","createParser","stripAffordances","raw","accountingMatch","parse","stripped","n","isIntermediate","isAccepted","countAcceptedBefore","str","cursor","normalised","count","i","getCaretBoundary","formattedValue","len","boundary","computeNewCursorPosition","oldInput","oldCursor","newFormatted","inputType","acceptedCount","normNew","pos","snapToBoundary"],"mappings":"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","file":"core.js","sourcesContent":["/**\n * Format presets — named Intl.NumberFormatOptions configurations for common\n * number input patterns. Use these as the `formatOptions` prop value.\n *\n * @example\n * import { presets } from 'raqam'\n * <NumberField.Root formatOptions={presets.currency('USD')} />\n * <NumberField.Root formatOptions={presets.percent} />\n * <NumberField.Root formatOptions={presets.compact} />\n */\n\nexport const presets = {\n /** Currency with standard sign display. Shorthand for `{ style:'currency', currency:code }`. */\n currency: (code: string): Intl.NumberFormatOptions => ({\n style: \"currency\",\n currency: code,\n }),\n\n /**\n * Accounting currency — negatives shown as `(1,234.56)` instead of `-$1,234.56`.\n * Requires the accounting format parser fix (built-in to raqam).\n */\n accounting: (code: string): Intl.NumberFormatOptions => ({\n style: \"currency\",\n currency: code,\n currencySign: \"accounting\",\n }),\n\n /** Percentage — formats 0.42 as \"42%\" */\n percent: { style: \"percent\" } as Intl.NumberFormatOptions,\n\n /** Compact short — \"1.2K\", \"3.4M\" */\n compact: { notation: \"compact\" } as Intl.NumberFormatOptions,\n\n /** Compact long — \"1.2 thousand\", \"3.4 million\" */\n compactLong: {\n notation: \"compact\",\n compactDisplay: \"long\",\n } as Intl.NumberFormatOptions,\n\n /** Scientific notation — \"1.234E3\" */\n scientific: { notation: \"scientific\" } as Intl.NumberFormatOptions,\n\n /** Engineering notation — exponents always multiples of 3 */\n engineering: { notation: \"engineering\" } as Intl.NumberFormatOptions,\n\n /** Integer — no decimal places */\n integer: { maximumFractionDigits: 0 } as Intl.NumberFormatOptions,\n\n /**\n * Financial — always exactly 2 decimal places (fixed scale).\n * Combine with `fixedDecimalScale` prop for strict display.\n */\n financial: {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n } as Intl.NumberFormatOptions,\n\n /**\n * Unit — shorthand for `{ style:'unit', unit:unitCode }`.\n * @example presets.unit('kilometer-per-hour') → { style:'unit', unit:'kilometer-per-hour' }\n */\n unit: (unit: string): Intl.NumberFormatOptions => ({\n style: \"unit\",\n unit,\n }),\n} as const;\n","import type { DigitBlock } from \"./types.js\";\n\n// ── Built-in digit blocks ────────────────────────────────────────────────────\n// These cover the digit systems required by the spec.\n// Additional blocks can be registered via registerLocale().\n\nconst BUILTIN_DIGIT_BLOCKS: DigitBlock[] = [\n [0x0660, 0x0669], // Arabic-Indic (arab)\n [0x06f0, 0x06f9], // Extended Arabic-Indic / Persian (arabext)\n [0x0966, 0x096f], // Devanagari / Hindi (deva)\n [0x09e6, 0x09ef], // Bengali (beng)\n [0x0e50, 0x0e59], // Thai (thai)\n];\n\n// Mutable registry — locale plugins can add blocks here\nconst registeredBlocks: DigitBlock[] = [...BUILTIN_DIGIT_BLOCKS];\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport interface LocaleConfig {\n /** Extra digit block ranges to register */\n digitBlocks?: DigitBlock[];\n}\n\n/**\n * Register additional digit blocks (called by locale plugins as a side effect).\n * Duplicate ranges are silently ignored.\n */\nexport function registerLocale(config: LocaleConfig): void {\n if (!config.digitBlocks) return;\n for (const block of config.digitBlocks) {\n const already = registeredBlocks.some(([s]) => s === block[0]);\n if (!already) registeredBlocks.push(block);\n }\n}\n\n/**\n * Normalise any Unicode decimal digit in `input` to its ASCII equivalent (0–9).\n * Non-digit characters pass through unchanged.\n */\nexport function normalizeDigits(input: string): string {\n // Fast path: if there are no non-ASCII chars, return as-is\n if (!/[^\\u0020-\\u007e]/.test(input)) return input;\n\n return input.replace(/\\p{Nd}/gu, (ch) => {\n const code = ch.codePointAt(0)!;\n for (const [start, end] of registeredBlocks) {\n if (code >= start && code <= end) {\n return String(code - start);\n }\n }\n // Fallback: let JS try to parse it as a decimal digit\n const digit = Number.parseInt(ch, 10);\n return Number.isNaN(digit) ? ch : String(digit);\n });\n}\n\n/**\n * Returns true if the character is a non-Latin Unicode decimal digit\n * (i.e. would need normalization).\n */\nexport function isNonLatinDigit(ch: string): boolean {\n const code = ch.codePointAt(0);\n if (code === undefined) return false;\n if (code >= 0x30 && code <= 0x39) return false; // ASCII 0-9\n for (const [start, end] of registeredBlocks) {\n if (code >= start && code <= end) return true;\n }\n return false;\n}\n","import type { FormatResult, LocaleInfo } from \"./types.js\";\n\n// ── Internal ──────────────────────────────────────────────────────────────────\n\n/** Probe value that will surface decimal AND grouping parts */\nconst PROBE_VALUE = 12345.6;\n\n/** Cache key = locale + JSON.stringify(options) */\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\nfunction getFormatter(\n locale: string | undefined,\n options: Intl.NumberFormatOptions | undefined\n): Intl.NumberFormat {\n const key = `${locale ?? \"\"}::${JSON.stringify(options ?? {})}`;\n let fmt = formatterCache.get(key);\n if (!fmt) {\n fmt = new Intl.NumberFormat(locale, options);\n formatterCache.set(key, fmt);\n }\n return fmt;\n}\n\n/** Extract locale meta from formatToParts — never hardcoded. */\nfunction extractLocaleInfo(\n locale: string | undefined,\n options: Intl.NumberFormatOptions | undefined\n): LocaleInfo {\n const fmt = getFormatter(locale, options);\n // Use a simple decimal number — we only need the separators\n const parts = fmt.formatToParts(PROBE_VALUE);\n\n let decimalSeparator = \".\";\n let groupingSeparator = \",\";\n let minusSign = \"-\";\n let zero = \"0\";\n\n for (const part of parts) {\n if (part.type === \"decimal\") decimalSeparator = part.value;\n if (part.type === \"group\") groupingSeparator = part.value;\n if (part.type === \"minusSign\") minusSign = part.value;\n }\n\n // Detect locale zero digit\n const zeroParts = fmt.formatToParts(0);\n for (const part of zeroParts) {\n if (part.type === \"integer\") {\n zero = part.value;\n break;\n }\n }\n\n // RTL locales: Arabic / Hebrew / Persian / Urdu / Syriac etc.\n const rtlLocales = /^(ar|he|fa|ur|syc|nqo|ug|yi)/i;\n const resolvedLocale = fmt.resolvedOptions().locale;\n const isRTL = rtlLocales.test(resolvedLocale);\n\n return { decimalSeparator, groupingSeparator, minusSign, zero, isRTL };\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport interface FormatterOptions {\n locale?: string;\n formatOptions?: Intl.NumberFormatOptions;\n prefix?: string;\n suffix?: string;\n minimumFractionDigits?: number;\n maximumFractionDigits?: number;\n fixedDecimalScale?: boolean;\n}\n\nexport interface Formatter {\n format(value: number): string;\n formatToParts(value: number): Intl.NumberFormatPart[];\n getLocaleInfo(): LocaleInfo;\n formatResult(value: number): FormatResult;\n}\n\n/**\n * Create a formatter instance. Intl.NumberFormat is cached — safe to call\n * on every render.\n */\nexport function createFormatter(opts: FormatterOptions): Formatter {\n // Merge fraction digit overrides into formatOptions\n const intlOptions: Intl.NumberFormatOptions = { ...opts.formatOptions };\n\n if (opts.minimumFractionDigits !== undefined) {\n intlOptions.minimumFractionDigits = opts.minimumFractionDigits;\n }\n if (opts.maximumFractionDigits !== undefined) {\n intlOptions.maximumFractionDigits = opts.maximumFractionDigits;\n }\n if (opts.fixedDecimalScale && opts.maximumFractionDigits !== undefined) {\n intlOptions.minimumFractionDigits = opts.maximumFractionDigits;\n intlOptions.maximumFractionDigits = opts.maximumFractionDigits;\n }\n\n const intlFmt = getFormatter(opts.locale, intlOptions);\n // Lazy — computed once on first call\n let cachedLocaleInfo: LocaleInfo | null = null;\n\n function getLocaleInfo(): LocaleInfo {\n if (!cachedLocaleInfo) {\n cachedLocaleInfo = extractLocaleInfo(opts.locale, intlOptions);\n }\n return cachedLocaleInfo;\n }\n\n function formatToParts(value: number): Intl.NumberFormatPart[] {\n const parts = intlFmt.formatToParts(value);\n if (!opts.prefix && !opts.suffix) return parts;\n\n const result: Intl.NumberFormatPart[] = [];\n if (opts.prefix) result.push({ type: \"literal\", value: opts.prefix });\n result.push(...parts);\n if (opts.suffix) result.push({ type: \"literal\", value: opts.suffix });\n return result;\n }\n\n function format(value: number): string {\n if (!Number.isFinite(value)) return \"\";\n const formatted = intlFmt.format(value);\n return (opts.prefix ?? \"\") + formatted + (opts.suffix ?? \"\");\n }\n\n function formatResult(value: number): FormatResult {\n const parts = formatToParts(value);\n const formatted = parts.map((p) => p.value).join(\"\");\n return { formatted, parts };\n }\n\n return { format, formatToParts, getLocaleInfo, formatResult };\n}\n","import { createFormatter } from \"./formatter.js\";\nimport { normalizeDigits } from \"./normalizer.js\";\nimport type { LocaleInfo, ParseResult } from \"./types.js\";\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\n/**\n * Escape a string so it can be used literally inside a RegExp.\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Returns true if `s` represents a valid-but-incomplete number:\n * \"-\" lone minus\n * \"1.\" trailing decimal separator (any locale)\n * \"1.0\" trailing zero after decimal\n * \"1.00\" etc.\n *\n * These should NOT be reformatted while the user is still typing.\n */\nfunction checkIntermediate(\n normalized: string,\n info: LocaleInfo,\n allowNegative: boolean,\n allowDecimal: boolean\n): boolean {\n const dec = escapeRegex(info.decimalSeparator);\n const minus = escapeRegex(info.minusSign);\n\n // Lone minus sign\n if (allowNegative && (normalized === \"-\" || normalized === info.minusSign)) {\n return true;\n }\n // Trailing decimal separator\n if (allowDecimal && new RegExp(`^${minus}?\\\\d+${dec}$`).test(normalized)) {\n return true;\n }\n // Trailing zeros after decimal (e.g. \"1.0\", \"1.00\", \"-1.0\")\n if (allowDecimal && new RegExp(`^${minus}?\\\\d+${dec}\\\\d*0+$`).test(normalized)) {\n return true;\n }\n return false;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport interface ParserOptions {\n locale?: string;\n formatOptions?: Intl.NumberFormatOptions;\n allowNegative?: boolean;\n allowDecimal?: boolean;\n prefix?: string;\n suffix?: string;\n}\n\nexport interface Parser {\n parse(input: string): ParseResult;\n isIntermediate(input: string): boolean;\n getLocaleInfo(): LocaleInfo;\n}\n\n/**\n * Create a locale-aware parser. Separator characters are extracted from\n * Intl.NumberFormat — never hardcoded.\n */\nexport function createParser(opts: ParserOptions = {}): Parser {\n const allowNegative = opts.allowNegative ?? true;\n const allowDecimal = opts.allowDecimal ?? true;\n\n // Re-use the formatter to get locale info\n const fmt = createFormatter({\n locale: opts.locale,\n formatOptions: opts.formatOptions,\n prefix: opts.prefix,\n suffix: opts.suffix,\n });\n\n function getLocaleInfo(): LocaleInfo {\n return fmt.getLocaleInfo();\n }\n\n function stripAffordances(raw: string): string {\n const info = getLocaleInfo();\n\n // 1. Normalise non-Latin digits to ASCII\n let s = normalizeDigits(raw);\n\n // 2. Accounting format: \"(1,234.56)\" or \"($1,234.56)\" → negative\n // Intl.NumberFormat with currencySign:\"accounting\" wraps negatives in parens\n const accountingMatch = s.match(/^\\((.+)\\)$/);\n if (accountingMatch) {\n s = `-${accountingMatch[1]}`;\n }\n\n // 3. Strip prefix / suffix\n if (opts.prefix && s.startsWith(opts.prefix)) {\n s = s.slice(opts.prefix.length);\n }\n if (opts.suffix && s.endsWith(opts.suffix)) {\n s = s.slice(0, -opts.suffix.length);\n }\n\n // 4. Strip grouping separators (escape special chars)\n if (info.groupingSeparator) {\n s = s.split(info.groupingSeparator).join(\"\");\n }\n\n // 5. Replace locale decimal separator with ASCII \".\"\n if (info.decimalSeparator !== \".\") {\n s = s.split(info.decimalSeparator).join(\".\");\n }\n\n // 6. Replace locale minus sign with ASCII \"-\"\n if (info.minusSign !== \"-\") {\n s = s.split(info.minusSign).join(\"-\");\n }\n\n // 7. Strip currency symbol, percent sign, spaces that Intl might prepend/append\n // Strip any remaining non-numeric chars except digits, \".\", \"-\"\n // (handles currency prefixes/suffixes from Intl)\n s = s.replace(/[^\\d.\\-]/g, \"\").trim();\n\n return s;\n }\n\n function parse(input: string): ParseResult {\n if (!input || input.trim() === \"\") {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n const info = getLocaleInfo();\n\n // Check for intermediate state before stripping\n if (checkIntermediate(input, info, allowNegative, allowDecimal)) {\n return { value: null, isValid: false, isIntermediate: true };\n }\n\n const stripped = stripAffordances(input);\n\n if (stripped === \"\") {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n if (stripped === \"-\") {\n // Lone minus after stripping — only intermediate if negatives are allowed\n return { value: null, isValid: false, isIntermediate: allowNegative };\n }\n\n // Reject if not a valid numeric string\n if (!/^-?\\d+\\.?\\d*$/.test(stripped)) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n if (!allowNegative && stripped.startsWith(\"-\")) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n if (!allowDecimal && stripped.includes(\".\")) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n const n = Number.parseFloat(stripped);\n if (!Number.isFinite(n)) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n return { value: n, isValid: true, isIntermediate: false };\n }\n\n function isIntermediate(input: string): boolean {\n const info = getLocaleInfo();\n return checkIntermediate(input, info, allowNegative, allowDecimal);\n }\n\n return { parse, isIntermediate, getLocaleInfo };\n}\n","import { normalizeDigits } from \"./normalizer.js\";\nimport type { CaretBoundary, LocaleInfo } from \"./types.js\";\n\n// ── Accepted-character helpers ────────────────────────────────────────────────\n\n/**\n * Returns true if `ch` is an \"accepted\" character — one that the user typed\n * intentionally and that contributes to the numeric value:\n * - ASCII digit 0-9\n * - The locale decimal separator\n * - The locale minus sign\n */\nfunction isAccepted(ch: string, info: LocaleInfo): boolean {\n if (ch >= \"0\" && ch <= \"9\") return true;\n if (ch === info.decimalSeparator) return true;\n if (ch === info.minusSign || ch === \"-\") return true;\n return false;\n}\n\n/**\n * Count how many \"accepted\" characters appear before position `cursor`\n * in `str` (after normalising non-Latin digits to ASCII).\n */\nfunction countAcceptedBefore(str: string, cursor: number, info: LocaleInfo): number {\n const normalised = normalizeDigits(str);\n let count = 0;\n for (let i = 0; i < cursor && i < normalised.length; i++) {\n if (isAccepted(normalised[i]!, info)) count++;\n }\n return count;\n}\n\n// ── Caret boundary ────────────────────────────────────────────────────────────\n\n/**\n * Build a boolean array of length `formattedValue.length + 1`.\n * `true` → cursor may rest at this position.\n * `false` → cursor must snap away (sits inside a formatting-only character\n * such as a grouping separator or a currency prefix).\n *\n * Rules:\n * - Start and end positions are always valid.\n * - A position immediately AFTER a grouping separator is invalid (the\n * cursor would look like it's between two digits but moving left would\n * skip the comma).\n * - A position immediately BEFORE a grouping separator is valid.\n */\nexport function getCaretBoundary(formattedValue: string, info: LocaleInfo): CaretBoundary {\n const len = formattedValue.length;\n const boundary: CaretBoundary = new Array(len + 1).fill(true) as boolean[];\n\n for (let i = 0; i < len; i++) {\n const ch = formattedValue[i]!;\n if (ch === info.groupingSeparator) {\n // Position immediately after the grouping separator is invalid\n boundary[i + 1] = false;\n }\n }\n\n // First and last are always valid\n boundary[0] = true;\n boundary[len] = true;\n\n return boundary;\n}\n\n// ── Cursor computation ────────────────────────────────────────────────────────\n\n/**\n * Compute the new cursor position in `newFormatted` that corresponds\n * semantically to `oldCursor` in `oldInput`.\n *\n * Algorithm (3 stages from the spec):\n *\n * 1. Count accepted characters before `oldCursor` in `oldInput`.\n * 2. Adjust for backspace-over-separator edge case.\n * 3. Walk `newFormatted` to find the position where the same count of\n * accepted chars precede it; snap to the nearest valid boundary.\n *\n * @param oldInput The raw string the user just typed into (pre-format)\n * @param oldCursor selectionStart captured from the native event\n * @param newFormatted The formatted string we're about to display\n * @param info Locale separators\n * @param inputType e.nativeEvent.inputType (optional — for backspace detection)\n */\nexport function computeNewCursorPosition(\n oldInput: string,\n oldCursor: number,\n newFormatted: string,\n info: LocaleInfo,\n inputType?: string\n): number {\n const normalised = normalizeDigits(oldInput);\n\n // Stage 1: count accepted chars before cursor in old input\n let acceptedCount = countAcceptedBefore(normalised, oldCursor, info);\n\n // Stage 2: backspace on grouping separator — also delete the preceding digit\n if (\n inputType === \"deleteContentBackward\" &&\n oldCursor > 0 &&\n oldInput[oldCursor - 1] === info.groupingSeparator\n ) {\n // The separator was deleted but we want to remove the preceding digit too\n acceptedCount = Math.max(0, acceptedCount - 1);\n }\n\n // Stage 3: build boundary and walk new formatted string\n const boundary = getCaretBoundary(newFormatted, info);\n const normNew = normalizeDigits(newFormatted);\n let count = 0;\n let pos = 0;\n\n for (let i = 0; i < normNew.length; i++) {\n if (count === acceptedCount) {\n pos = i;\n break;\n }\n if (isAccepted(normNew[i]!, info)) count++;\n pos = i + 1;\n }\n\n // If we ran through the entire string without reaching the target count,\n // place the cursor at the end. Note: acceptedCount === 0 means we correctly\n // want pos = 0 (set in the loop), so we must NOT override that case.\n if (acceptedCount > 0 && count < acceptedCount) {\n pos = newFormatted.length;\n }\n\n // Snap to nearest valid boundary\n pos = snapToBoundary(pos, boundary);\n\n return pos;\n}\n\n/**\n * If `pos` is at a false (invalid) boundary position, find the nearest\n * true position. Prefers moving forward; falls back to backward.\n */\nfunction snapToBoundary(pos: number, boundary: CaretBoundary): number {\n if (boundary[pos]) return pos;\n\n // Try forward first\n for (let i = pos + 1; i < boundary.length; i++) {\n if (boundary[i]) return i;\n }\n // Fall back to backward\n for (let i = pos - 1; i >= 0; i--) {\n if (boundary[i]) return i;\n }\n return 0;\n}\n"]}