stimeo-ui 0.13.0 → 0.14.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.
- package/CHANGELOG.md +39 -5
- package/dist/cable/index.d.ts +1 -1
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +1 -1
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -2
- package/dist/controllers/calendar_controller.js +31 -7
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.js +8 -6
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +1 -1
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +1 -1
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.js +7 -1
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.js +68 -23
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +3 -3
- package/dist/controllers/date_range_picker_controller.js +31 -7
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +2 -2
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.js +21 -10
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +1 -1
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +1 -1
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +7 -1
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +3 -2
- package/dist/controllers/input_mask_controller.js +33 -12
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +10 -5
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +10 -5
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +3 -2
- package/dist/controllers/local_time_controller.js +34 -7
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +1 -1
- package/dist/controllers/masonry_controller.js +1 -1
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.js +10 -2
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +10 -2
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.js +8 -2
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/otp_controller.js +23 -10
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +10 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +1 -1
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +10 -3
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/portal_controller.js +21 -10
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.js +11 -3
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +1 -1
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.js +31 -5
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_before_cache_controller.js +23 -10
- package/dist/controllers/reset_before_cache_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +7 -2
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +7 -3
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +1 -1
- package/dist/controllers/scroll_visibility_controller.js +22 -13
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +1 -1
- package/dist/controllers/scrollspy_controller.js +11 -6
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.js +23 -12
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.js +10 -5
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +1 -1
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +1 -1
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -3
- package/dist/controllers/theme_controller.js +23 -13
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.js +11 -3
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +7 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/index.js +165 -171
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +38 -1
- package/dist/inspector/cli.js +80 -7
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +103 -7
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +5 -5
- package/dist/inspector/manifest.json +127 -1
- package/package.json +9 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/composition_tracker.ts","../../src/utils/half_width.ts","../../src/controllers/currency_input_controller.ts"],"names":["formatted"],"mappings":"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","file":"currency_input_controller.js","sourcesContent":["/** Minimal event shape carrying the platform's per-event composition signal. */\nexport interface CompositionSignal {\n readonly isComposing?: boolean;\n}\n\n/** Hooks that keep component-specific work outside {@link CompositionTracker}. */\nexport interface CompositionTrackerOptions {\n /** Runs after lifecycle state is set for a `compositionstart` event. */\n readonly onStart?: (event: Event) => void;\n /** Runs after lifecycle state is cleared for a `compositionend` event. */\n readonly onEnd?: (event: Event) => void;\n}\n\n/**\n * Owns IME composition listeners and transient state for one or more event targets.\n *\n * Some browsers omit `KeyboardEvent.isComposing` on the keydown that confirms a\n * conversion. Tracking `compositionstart` through `compositionend` supplies the\n * missing lifecycle signal without relying on deprecated numeric key codes.\n * Component policy stays with the consumer: filtering, validation, and submission\n * belong in the optional {@link CompositionTrackerOptions.onEnd} callback.\n *\n * @example\n * ```ts\n * #composition = new CompositionTracker({ onEnd: () => this.filter() });\n *\n * connect(): void {\n * this.#composition.observe(this.inputTarget);\n * }\n *\n * disconnect(): void {\n * this.#composition.disconnect();\n * }\n *\n * onKeydown(event: KeyboardEvent): void {\n * if (this.#composition.isComposing(event)) return;\n * }\n * ```\n */\nexport class CompositionTracker {\n readonly #observedTargets = new Set<EventTarget>();\n readonly #activeTargets = new Set<EventTarget>();\n readonly #onStart: ((event: Event) => void) | undefined;\n readonly #onEnd: ((event: Event) => void) | undefined;\n\n constructor(options: CompositionTrackerOptions = {}) {\n this.#onStart = options.onStart;\n this.#onEnd = options.onEnd;\n }\n\n /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */\n observe(target: EventTarget): void {\n if (this.#observedTargets.has(target)) return;\n target.addEventListener(\"compositionstart\", this.#handleStart);\n target.addEventListener(\"compositionend\", this.#handleEnd);\n this.#observedTargets.add(target);\n }\n\n /** Stops tracking one target and clears any active composition it owned. */\n unobserve(target: EventTarget): void {\n if (!this.#observedTargets.delete(target)) return;\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n this.#activeTargets.delete(target);\n }\n\n /** Releases every listener and clears state so reconnect starts cleanly. */\n disconnect(): void {\n for (const target of this.#observedTargets) {\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n }\n this.#observedTargets.clear();\n this.#activeTargets.clear();\n }\n\n /** True when lifecycle tracking or the current event reports composition. */\n isComposing(event?: CompositionSignal): boolean {\n return this.#activeTargets.size > 0 || event?.isComposing === true;\n }\n\n readonly #handleStart = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.add(event.currentTarget);\n this.#onStart?.(event);\n };\n\n readonly #handleEnd = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);\n this.#onEnd?.(event);\n };\n}\n","/** The distance between a full-width ASCII code point and its half-width twin. */\nconst FULL_WIDTH_SHIFT = 0xfee0;\n\n/**\n * The half-width form of `char`, or `char` itself when it has none.\n *\n * An IME confirming in full-width mode produces U+FF01–U+FF5E (and U+3000 for\n * the space) where the page means ASCII — the digits of a phone number, the\n * letters of a code, the separators between them. Reading those as their ASCII\n * equivalents keeps confirmed text usable instead of discarding it. Characters\n * outside that block, including kana and astral code points, are returned\n * unchanged, so this never rewrites text a consumer meant to keep as typed.\n */\nexport function halfWidthChar(char: string): string {\n if (char >= \"!\" && char <= \"~\") {\n return String.fromCharCode(char.charCodeAt(0) - FULL_WIDTH_SHIFT);\n }\n return char === \" \" ? \" \" : char;\n}\n\n/** Every full-width ASCII character in `text` rewritten by {@link halfWidthChar}. */\nexport function toHalfWidth(text: string): string {\n let out = \"\";\n for (const char of text) out += halfWidthChar(char);\n return out;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { CompositionTracker } from \"../utils/composition_tracker\";\nimport { halfWidthChar } from \"../utils/half_width\";\n\n/** The number-shaped pieces of an in-progress entry, in typing order. */\ninterface EntryParts {\n /** The leading sign exactly as typed (`\"\"` when none). */\n sign: \"\" | \"-\" | \"+\";\n /** Integer digits with grouping stripped (may be empty mid-entry). */\n int: string;\n /** Whether a decimal mark was typed. */\n hasDot: boolean;\n /** Fraction digits exactly as typed; rounding happens only on blur. */\n frac: string;\n}\n\n/** One scan of the display text: its structure and the numeric value it holds. */\ninterface Scan {\n parts: EntryParts;\n /** The finite value, or `null` while no digit has been typed yet. */\n value: number | null;\n}\n\n/**\n * Headless currency / amount input behavior: groups digits for display while\n * keeping a machine-readable number in a hidden field, and mirrors the\n * normalized value to a visually-hidden span so assistive tech reads the real\n * amount rather than the grouped display string.\n *\n * Markup contract (identifier: `stimeo--currency-input`):\n * <div data-controller=\"stimeo--currency-input\"\n * data-stimeo--currency-input-locale-value=\"en-US\"\n * data-stimeo--currency-input-currency-value=\"USD\"\n * data-stimeo--currency-input-precision-value=\"2\">\n * <input type=\"text\" inputmode=\"decimal\"\n * aria-describedby=\"amt-sr\"\n * data-stimeo--currency-input-target=\"display\"\n * data-action=\"input->stimeo--currency-input#onInput\n * blur->stimeo--currency-input#format\" />\n * <span id=\"amt-sr\" class=\"visually-hidden\"\n * data-stimeo--currency-input-target=\"srValue\"></span>\n * <input type=\"hidden\" data-stimeo--currency-input-target=\"field\" />\n * </div>\n *\n * `change` dispatches `{ value: number | null, formatted: string }` — `null`\n * (with an empty `formatted`) when the amount is cleared, so consumers hear\n * every transition of the numeric value, including back to empty.\n *\n * @remarks\n * Behavior only — no styling, no validation (range/required belong to the\n * consumer or Form Field). The display field is the sole Tab stop and keeps its\n * native text-editing behavior; this controller never steals focus.\n *\n * While typing, the entry is preserved as typed: grouping is applied to the\n * integer digits per locale, a leading sign and a trailing decimal mark stay in\n * place, fraction digits are kept verbatim, and the caret is restored after the\n * same significant character it followed. Negative amounts are in scope. The\n * fixed-precision rounding is applied on `blur`, never mid-entry.\n * Full-width digits, signs, and marks (an IME confirming 0-9/./-) are\n * normalized to their ASCII forms rather than stripped; the locale's own\n * digits (a non-Latin default numbering system such as ar-EG's) are mapped\n * back the same way; and U+2212 MINUS SIGN is read as a sign. An ASCII \".\"\n * only counts as a decimal mark in locales where it is not the grouping\n * separator. Together these keep the controller's own output re-parseable to\n * the same value in every locale. Events fired mid-IME-composition are\n * ignored; the confirmed text is formatted once on `compositionend`.\n *\n * A malformed `locale`, `currency`, or `precision` declaration falls back to\n * that Value's default instead of throwing: each is validated once in its\n * `<name>ValueChanged`, and the hot path only ever sees validated values\n * through cached `Intl.NumberFormat` instances (rebuilt on Value changes, never\n * per keystroke). Late-arriving or swapped `field` / `srValue` / `display`\n * targets are re-synced on connection. The composition listeners are released\n * on `disconnect()`.\n *\n * Honest a11y note: a hidden `<input>` is not exposed to assistive tech, so the\n * normalized value is *also* published as text in the `srValue` span referenced\n * by the display's `aria-describedby` — that, not the hidden field, is what a\n * screen reader announces.\n */\nexport class CurrencyInputController extends Controller<HTMLElement> {\n static override targets = [\"display\", \"field\", \"srValue\"];\n static override values = {\n locale: { type: String, default: \"en-US\" },\n currency: { type: String, default: \"\" },\n precision: { type: Number, default: 2 },\n };\n static actions = [\"format\", \"onInput\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly displayTarget: HTMLInputElement;\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly srValueTarget: HTMLElement;\n declare readonly hasDisplayTarget: boolean;\n declare readonly hasFieldTarget: boolean;\n declare readonly hasSrValueTarget: boolean;\n declare localeValue: string;\n declare currencyValue: string;\n declare precisionValue: number;\n\n /** Last committed numeric value, to suppress duplicate `change` dispatches. */\n #lastValue: number | null = null;\n #started = false;\n\n /** Validated mirrors of the Values; the hot path never reads a raw Value. */\n #locale = \"en-US\";\n #currency = \"\";\n #precision = 2;\n\n /** Formatters rebuilt only when a Value changes, never per keystroke. */\n #grouping!: Intl.NumberFormat;\n #fixed!: Intl.NumberFormat;\n #accessible!: Intl.NumberFormat;\n #group = \",\";\n #decimal = \".\";\n /** The locale's non-Latin digits mapped back to ASCII (empty for Latin locales). */\n readonly #digits = new Map<string, string>();\n\n /** Holds mid-composition input so the IME's uncommitted text is never rewritten. */\n readonly #composition = new CompositionTracker({\n onEnd: () => this.#reformat(false),\n });\n\n /** Re-validates on declaration changes and re-renders the committed display. */\n localeValueChanged(): void {\n this.#applyValueChange();\n }\n\n currencyValueChanged(): void {\n this.#applyValueChange();\n }\n\n precisionValueChanged(): void {\n this.#applyValueChange();\n }\n\n /**\n * Scans the display under the *outgoing* configuration (its separators wrote\n * that text), then revalidates and re-renders under the new one — so a locale\n * switch re-interprets the value, never the old text with new separators.\n */\n #applyValueChange(): void {\n const scan =\n this.#started && this.hasDisplayTarget ? this.#scan(this.displayTarget.value) : null;\n this.#revalidate();\n if (!scan || !this.hasDisplayTarget) return;\n if (document.activeElement === this.displayTarget) {\n const formatted = this.#render(scan.parts);\n this.displayTarget.value = formatted;\n this.#reflect(scan.value, formatted);\n } else if (scan.value === null) {\n this.displayTarget.value = \"\";\n this.#reflect(null, \"\");\n } else {\n const rounded = round(scan.value, this.#precision);\n const formatted = this.#fixed.format(rounded);\n this.displayTarget.value = formatted;\n this.#reflect(rounded, formatted);\n }\n }\n\n /** Normalizes any pre-filled display value to its fixed-precision form. */\n override connect(): void {\n this.#started = true;\n if (!this.hasDisplayTarget) return;\n // Seed lastValue with the *rounded* initial value so the idempotent\n // connect-time reformat does not dispatch a spurious `change`.\n const { value } = this.#scan(this.displayTarget.value);\n this.#lastValue = value === null ? null : round(value, this.#precision);\n if (value === null) {\n this.displayTarget.value = \"\";\n this.#reflect(null, \"\");\n } else {\n this.#reformat(true);\n }\n }\n\n override disconnect(): void {\n this.#started = false;\n this.#composition.disconnect();\n }\n\n /** Tracks composition on an arriving (or swapped-in) display and normalizes it. */\n displayTargetConnected(target: HTMLInputElement): void {\n this.#composition.observe(target);\n if (this.#started) this.#reformat(true);\n }\n\n displayTargetDisconnected(target: HTMLInputElement): void {\n this.#composition.unobserve(target);\n }\n\n /** Syncs a late-arriving hidden field without touching the display or events. */\n fieldTargetConnected(): void {\n if (this.#started) this.#resync();\n }\n\n /** Syncs a late-arriving screen-reader span the same way. */\n srValueTargetConnected(): void {\n if (this.#started) this.#resync();\n }\n\n /** Re-groups digits as the user types, preserving the caret position. */\n onInput(event: Event): void {\n if (this.#composition.isComposing(event as InputEvent)) return;\n this.#reformat(false);\n }\n\n /** Applies the fixed-precision rounding on blur. */\n format(): void {\n this.#reformat(true);\n }\n\n /**\n * Parses the display value, rewrites it grouped (optionally at fixed\n * precision), keeps the caret stable by significant characters, and syncs the\n * field, the screen-reader span, and the `change` event.\n *\n * @stimeoRenderRoot\n */\n #reformat(fixedPrecision: boolean): void {\n if (!this.hasDisplayTarget) return;\n const raw = this.displayTarget.value;\n const { parts, value } = this.#scan(raw);\n\n if (fixedPrecision) {\n if (value === null) {\n // Blur with no digits (an abandoned sign or dot) clears the entry.\n this.displayTarget.value = \"\";\n this.#reflect(null, \"\");\n return;\n }\n const rounded = round(value, this.#precision);\n const formatted = this.#fixed.format(rounded);\n this.displayTarget.value = formatted;\n this.#reflect(rounded, formatted);\n return;\n }\n\n const formatted = this.#render(parts);\n if (formatted !== raw) {\n const caret = this.displayTarget.selectionStart;\n const anchor = caret === null ? null : this.#significantBefore(raw, caret);\n this.displayTarget.value = formatted;\n if (anchor !== null) this.#restoreCaret(formatted, anchor);\n }\n // The display may hold an in-progress \"-\", but a null value always rides\n // with an empty `formatted` so consumers can treat the pair as \"cleared\".\n this.#reflect(value, value === null ? \"\" : formatted);\n }\n\n /**\n * The in-progress rendering: grouped integer, sign and fraction as typed.\n *\n * @stimeoRenderRoot\n */\n #render(parts: EntryParts): string {\n const int = parts.int === \"\" ? \"\" : this.#grouping.format(BigInt(parts.int));\n const frac = parts.hasDot ? this.#decimal + parts.frac : \"\";\n return parts.sign + int + frac;\n }\n\n /** Restores the caret to sit just after the n-th significant character. */\n #restoreCaret(formatted: string, significantBefore: number): void {\n let seen = 0;\n let position = formatted.length;\n for (let i = 0; i < formatted.length; i++) {\n if (seen >= significantBefore) {\n position = i;\n break;\n }\n if (this.#isSignificant(formatted[i] as string)) seen += 1;\n }\n try {\n this.displayTarget.setSelectionRange(position, position);\n } catch {\n // Some hosts disallow selection on certain input states; the value is\n // already correct, so a failed caret restore is non-fatal.\n }\n }\n\n /**\n * Counts the characters before `caret` that survive into the rendering,\n * applying the same acceptance rules as {@link #scan} — a rejected keystroke\n * (a mid-string sign, a second decimal mark) must not shift the anchor.\n */\n #significantBefore(text: string, caret: number): number {\n let count = 0;\n let sawSign = false;\n let sawDigit = false;\n let sawDot = false;\n for (const ch of this.#normalize(text.slice(0, caret))) {\n if (ch >= \"0\" && ch <= \"9\") {\n count += 1;\n sawDigit = true;\n } else if ((ch === \"-\" || ch === \"+\") && !sawSign && !sawDigit && !sawDot) {\n count += 1;\n sawSign = true;\n } else if (this.#isDecimalMark(ch) && !sawDot) {\n count += 1;\n sawDot = true;\n }\n }\n return count;\n }\n\n /** Digits, signs, and the decimal mark anchor the caret; grouping does not. */\n #isSignificant(ch: string): boolean {\n if (ch >= \"0\" && ch <= \"9\") return true;\n if (ch === \"-\" || ch === \"+\") return true;\n return ch === this.#decimal || (ch === \".\" && this.#group !== \".\");\n }\n\n /** Writes the normalized value to the field, the SR span, and the empty hook. */\n #write(value: number | null): void {\n const isEmpty = value === null;\n if (this.hasFieldTarget) this.fieldTarget.value = isEmpty ? \"\" : String(value);\n if (this.hasSrValueTarget) {\n this.srValueTarget.textContent = isEmpty ? \"\" : this.#accessible.format(value);\n }\n this.element.toggleAttribute(\"data-stimeo--currency-input-empty\", isEmpty);\n }\n\n /** {@link #write}, then reports a moved value as `change` (`\"\"` rides with `null`). */\n #reflect(value: number | null, formatted: string): void {\n this.#write(value);\n if (value !== this.#lastValue) {\n this.#lastValue = value;\n this.dispatch(\"change\", { detail: { value, formatted } });\n }\n }\n\n /**\n * Scans arbitrary input text into its number-shaped parts and value. Keeps\n * digits, one leading sign, one decimal mark, and the fraction verbatim;\n * everything else (grouping, symbols, words) is dropped. An ASCII \".\" is only\n * a decimal mark where it is not the locale's grouping separator. Precision\n * plays no part here — rounding belongs to the blur-time format.\n */\n #scan(text: string): Scan {\n let sign: EntryParts[\"sign\"] = \"\";\n let int = \"\";\n let hasDot = false;\n let frac = \"\";\n for (const ch of this.#normalize(text)) {\n if (ch >= \"0\" && ch <= \"9\") {\n if (hasDot) frac += ch;\n else int += ch;\n } else if ((ch === \"-\" || ch === \"+\") && sign === \"\" && int === \"\" && !hasDot) {\n sign = ch;\n } else if (this.#isDecimalMark(ch) && !hasDot) {\n hasDot = true;\n }\n }\n const parts: EntryParts = { sign, int, hasDot, frac };\n // `Number` accepts \"12.\" and \".5\" alike, so one dotted form covers every\n // shape — and a digit-less entry (\"\", \"-\", \".\") reads as NaN, hence null.\n const value = Number(`${sign}${int}.${frac}`);\n return { parts, value: Number.isFinite(value) ? value : null };\n }\n\n /** Whether `ch` reads as this locale's decimal mark. */\n #isDecimalMark(ch: string): boolean {\n return ch === this.#decimal || (ch === \".\" && this.#group !== \".\");\n }\n\n /** Re-syncs field / srValue / hook from the current display without dispatching. */\n #resync(): void {\n if (!this.hasDisplayTarget) return;\n this.#write(this.#scan(this.displayTarget.value).value);\n }\n\n /**\n * Validates the declared Values, falling back to each Value's default when a\n * declaration cannot be interpreted (a malformed locale or currency tag, a\n * precision outside `Intl`'s 0–100 integer range), and rebuilds the cached\n * formatters from the validated set.\n */\n #revalidate(): void {\n this.#locale = \"en-US\";\n try {\n new Intl.NumberFormat(this.localeValue);\n this.#locale = this.localeValue;\n } catch {\n // Fall through to the default locale.\n }\n\n const precision = this.precisionValue;\n this.#precision =\n Number.isInteger(precision) && precision >= 0 && precision <= 100 ? precision : 2;\n\n this.#currency = \"\";\n if (this.currencyValue !== \"\") {\n try {\n new Intl.NumberFormat(this.#locale, { style: \"currency\", currency: this.currencyValue });\n this.#currency = this.currencyValue;\n } catch {\n // Fall through to the plain-number accessible text.\n }\n }\n\n this.#grouping = new Intl.NumberFormat(this.#locale, {\n useGrouping: true,\n maximumFractionDigits: 0,\n });\n this.#fixed = new Intl.NumberFormat(this.#locale, {\n useGrouping: true,\n minimumFractionDigits: this.#precision,\n maximumFractionDigits: this.#precision,\n });\n this.#accessible = this.#currency\n ? new Intl.NumberFormat(this.#locale, { style: \"currency\", currency: this.#currency })\n : this.#fixed;\n\n const parts = new Intl.NumberFormat(this.#locale).formatToParts(11111.1);\n this.#group = parts.find((p) => p.type === \"group\")?.value ?? \",\";\n this.#decimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\";\n\n // Locales whose default numbering system is not Latin (ar-EG, fa-IR, …)\n // format with their own digits; mapping them back keeps the controller's\n // own output re-parseable to the same value.\n this.#digits.clear();\n const digitFormatter = new Intl.NumberFormat(this.#locale, { useGrouping: false });\n for (let i = 0; i <= 9; i++) {\n const digit = digitFormatter.format(i);\n if (digit !== String(i)) this.#digits.set(digit, String(i));\n }\n }\n\n /**\n * Maps the locale's own digits to ASCII, folds full-width forms (the digits,\n * signs, and marks an IME confirms as 0-9+-.,) through the shared\n * half-width mapping, and reads U+2212 MINUS SIGN as \"-\", which some locales'\n * formatted output uses for negatives.\n */\n #normalize(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const mapped = this.#digits.get(ch);\n if (mapped !== undefined) {\n out += mapped;\n } else if (ch === \"−\") {\n out += \"-\";\n } else {\n out += halfWidthChar(ch);\n }\n }\n return out;\n }\n}\n\n/** Rounds `value` to `precision` decimal places, avoiding `-0`; overflow keeps `value`. */\nfunction round(value: number, precision: number): number {\n const factor = 10 ** Math.max(0, precision);\n const rounded = Math.round(value * factor) / factor;\n if (!Number.isFinite(rounded)) return value;\n return rounded === 0 ? 0 : rounded;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/composition_tracker.ts","../../src/utils/half_width.ts","../../src/utils/intl_format.ts","../../src/controllers/currency_input_controller.ts"],"names":["formatted"],"mappings":"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","file":"currency_input_controller.js","sourcesContent":["/** Minimal event shape carrying the platform's per-event composition signal. */\nexport interface CompositionSignal {\n readonly isComposing?: boolean;\n}\n\n/** Hooks that keep component-specific work outside {@link CompositionTracker}. */\nexport interface CompositionTrackerOptions {\n /** Runs after lifecycle state is set for a `compositionstart` event. */\n readonly onStart?: (event: Event) => void;\n /** Runs after lifecycle state is cleared for a `compositionend` event. */\n readonly onEnd?: (event: Event) => void;\n}\n\n/**\n * Owns IME composition listeners and transient state for one or more event targets.\n *\n * Some browsers omit `KeyboardEvent.isComposing` on the keydown that confirms a\n * conversion. Tracking `compositionstart` through `compositionend` supplies the\n * missing lifecycle signal without relying on deprecated numeric key codes.\n * Component policy stays with the consumer: filtering, validation, and submission\n * belong in the optional {@link CompositionTrackerOptions.onEnd} callback.\n *\n * @example\n * ```ts\n * #composition = new CompositionTracker({ onEnd: () => this.filter() });\n *\n * connect(): void {\n * this.#composition.observe(this.inputTarget);\n * }\n *\n * disconnect(): void {\n * this.#composition.disconnect();\n * }\n *\n * onKeydown(event: KeyboardEvent): void {\n * if (this.#composition.isComposing(event)) return;\n * }\n * ```\n */\nexport class CompositionTracker {\n readonly #observedTargets = new Set<EventTarget>();\n readonly #activeTargets = new Set<EventTarget>();\n readonly #onStart: ((event: Event) => void) | undefined;\n readonly #onEnd: ((event: Event) => void) | undefined;\n\n constructor(options: CompositionTrackerOptions = {}) {\n this.#onStart = options.onStart;\n this.#onEnd = options.onEnd;\n }\n\n /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */\n observe(target: EventTarget): void {\n if (this.#observedTargets.has(target)) return;\n target.addEventListener(\"compositionstart\", this.#handleStart);\n target.addEventListener(\"compositionend\", this.#handleEnd);\n this.#observedTargets.add(target);\n }\n\n /** Stops tracking one target and clears any active composition it owned. */\n unobserve(target: EventTarget): void {\n if (!this.#observedTargets.delete(target)) return;\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n this.#activeTargets.delete(target);\n }\n\n /** Releases every listener and clears state so reconnect starts cleanly. */\n disconnect(): void {\n for (const target of this.#observedTargets) {\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n }\n this.#observedTargets.clear();\n this.#activeTargets.clear();\n }\n\n /** True when lifecycle tracking or the current event reports composition. */\n isComposing(event?: CompositionSignal): boolean {\n return this.#activeTargets.size > 0 || event?.isComposing === true;\n }\n\n readonly #handleStart = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.add(event.currentTarget);\n this.#onStart?.(event);\n };\n\n readonly #handleEnd = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);\n this.#onEnd?.(event);\n };\n}\n","/** The distance between a full-width ASCII code point and its half-width twin. */\nconst FULL_WIDTH_SHIFT = 0xfee0;\n\n/**\n * The half-width form of `char`, or `char` itself when it has none.\n *\n * An IME confirming in full-width mode produces U+FF01–U+FF5E (and U+3000 for\n * the space) where the page means ASCII — the digits of a phone number, the\n * letters of a code, the separators between them. Reading those as their ASCII\n * equivalents keeps confirmed text usable instead of discarding it. Characters\n * outside that block, including kana and astral code points, are returned\n * unchanged, so this never rewrites text a consumer meant to keep as typed.\n */\nexport function halfWidthChar(char: string): string {\n if (char >= \"!\" && char <= \"~\") {\n return String.fromCharCode(char.charCodeAt(0) - FULL_WIDTH_SHIFT);\n }\n return char === \" \" ? \" \" : char;\n}\n\n/** Every full-width ASCII character in `text` rewritten by {@link halfWidthChar}. */\nexport function toHalfWidth(text: string): string {\n let out = \"\";\n for (const char of text) out += halfWidthChar(char);\n return out;\n}\n","/**\n * Construction of `Intl` formatters for the controllers that take a locale, and\n * the options that go with it, from a declaration.\n *\n * Every `Intl` constructor resolves locale data on the way in, which makes it\n * too costly to rebuild on a hot path, and rejects a malformed language tag,\n * currency code, time zone or digit count with a `RangeError`. Both concerns are\n * handled here once. A formatter is built at most once per distinct request and\n * shared by every caller that asks for the same one. A `RangeError` is caught\n * and answered the way the caller asked: by rebuilding under a fallback locale,\n * or by returning `null` so the caller can leave its authored text alone. Any\n * other failure is a programming fault and propagates.\n */\n\n/** The constructor shape the `Intl` formatter classes share. */\nexport interface IntlFormatterConstructor<F, O extends object> {\n new (locales?: string | string[], options?: O): F;\n}\n\n/** Built formatters, and refusals, per constructor; see `cacheKey` for the key. */\nconst formatters = new WeakMap<object, Map<string, unknown>>();\n\n/**\n * A formatter for `locale` with `options`.\n *\n * With `fallbackLocale`, a `RangeError` from the constructor is answered by\n * building the same options under the fallback instead, so the result is never\n * `null`; the fallback's own failure propagates. Without it, a `RangeError`\n * yields `null` and the caller decides what to show. Errors of any other kind\n * propagate in both forms.\n *\n * Results are cached per constructor and `(locale, options, fallback)`, `null`\n * included, so asking again on every render costs one map lookup.\n */\nexport function intlFormatter<F, O extends object>(\n factory: IntlFormatterConstructor<F, O>,\n locale: string | undefined,\n options: O,\n fallbackLocale: string,\n): F;\nexport function intlFormatter<F, O extends object>(\n factory: IntlFormatterConstructor<F, O>,\n locale: string | undefined,\n options: O,\n): F | null;\nexport function intlFormatter<F, O extends object>(\n factory: IntlFormatterConstructor<F, O>,\n locale: string | undefined,\n options: O,\n fallbackLocale?: string,\n): F | null {\n const cache = cacheFor(factory);\n const key = cacheKey(locale, options, fallbackLocale);\n if (cache.has(key)) return cache.get(key) as F | null;\n const formatter = construct(factory, locale, options, fallbackLocale);\n cache.set(key, formatter);\n return formatter;\n}\n\n/** The cache for one constructor, created on first use. */\nfunction cacheFor(factory: object): Map<string, unknown> {\n const existing = formatters.get(factory);\n if (existing) return existing;\n const created = new Map<string, unknown>();\n formatters.set(factory, created);\n return created;\n}\n\n/**\n * One key per distinct request. `JSON.stringify` drops an option left\n * `undefined`, which is how the constructor reads it too. Two spellings of the\n * same options differing only in key order key separately; that costs one extra\n * formatter, never a wrong one, and call sites pass a fixed literal anyway.\n */\nfunction cacheKey(\n locale: string | undefined,\n options: object,\n fallbackLocale: string | undefined,\n): string {\n return JSON.stringify([locale, options, fallbackLocale]);\n}\n\n/** Builds the formatter under the `RangeError` contract described on `intlFormatter`. */\nfunction construct<F, O extends object>(\n factory: IntlFormatterConstructor<F, O>,\n locale: string | undefined,\n options: O,\n fallbackLocale: string | undefined,\n): F | null {\n try {\n return new factory(locale, options);\n } catch (error) {\n if (!(error instanceof RangeError)) throw error;\n if (fallbackLocale === undefined) return null;\n return new factory(fallbackLocale, options);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { CompositionTracker } from \"../utils/composition_tracker\";\nimport { halfWidthChar } from \"../utils/half_width\";\nimport { intlFormatter } from \"../utils/intl_format\";\n\n/** The number-shaped pieces of an in-progress entry, in typing order. */\ninterface EntryParts {\n /** The leading sign exactly as typed (`\"\"` when none). */\n sign: \"\" | \"-\" | \"+\";\n /** Integer digits with grouping stripped (may be empty mid-entry). */\n int: string;\n /** Whether a decimal mark was typed. */\n hasDot: boolean;\n /** Fraction digits exactly as typed; rounding happens only on blur. */\n frac: string;\n}\n\n/** One scan of the display text: its structure and the numeric value it holds. */\ninterface Scan {\n parts: EntryParts;\n /** The finite value, or `null` while no digit has been typed yet. */\n value: number | null;\n}\n\n/**\n * Headless currency / amount input behavior: groups digits for display while\n * keeping a machine-readable number in a hidden field, and mirrors the\n * normalized value to a visually-hidden span so assistive tech reads the real\n * amount rather than the grouped display string.\n *\n * Markup contract (identifier: `stimeo--currency-input`):\n * <div data-controller=\"stimeo--currency-input\"\n * data-stimeo--currency-input-locale-value=\"en-US\"\n * data-stimeo--currency-input-currency-value=\"USD\"\n * data-stimeo--currency-input-precision-value=\"2\">\n * <input type=\"text\" inputmode=\"decimal\"\n * aria-describedby=\"amt-sr\"\n * data-stimeo--currency-input-target=\"display\"\n * data-action=\"input->stimeo--currency-input#onInput\n * blur->stimeo--currency-input#format\" />\n * <span id=\"amt-sr\" class=\"visually-hidden\"\n * data-stimeo--currency-input-target=\"srValue\"></span>\n * <input type=\"hidden\" data-stimeo--currency-input-target=\"field\" />\n * </div>\n *\n * `change` dispatches `{ value: number | null, formatted: string }` — `null`\n * (with an empty `formatted`) when the amount is cleared, so consumers hear\n * every transition of the numeric value, including back to empty.\n *\n * @remarks\n * Behavior only — no styling, no validation (range/required belong to the\n * consumer or Form Field). The display field is the sole Tab stop and keeps its\n * native text-editing behavior; this controller never steals focus.\n *\n * While typing, the entry is preserved as typed: grouping is applied to the\n * integer digits per locale, a leading sign and a trailing decimal mark stay in\n * place, fraction digits are kept verbatim, and the caret is restored after the\n * same significant character it followed. Negative amounts are in scope. The\n * fixed-precision rounding is applied on `blur`, never mid-entry.\n * Full-width digits, signs, and marks (an IME confirming 0-9/./-) are\n * normalized to their ASCII forms rather than stripped; the locale's own\n * digits (a non-Latin default numbering system such as ar-EG's) are mapped\n * back the same way; and U+2212 MINUS SIGN is read as a sign. An ASCII \".\"\n * only counts as a decimal mark in locales where it is not the grouping\n * separator. Together these keep the controller's own output re-parseable to\n * the same value in every locale. Events fired mid-IME-composition are\n * ignored; the confirmed text is formatted once on `compositionend`.\n *\n * A malformed `locale`, `currency`, or `precision` declaration falls back to\n * that Value's default instead of throwing: each is validated once in its\n * `<name>ValueChanged`, and the hot path only ever sees validated values\n * through cached `Intl.NumberFormat` instances (rebuilt on Value changes, never\n * per keystroke). Late-arriving or swapped `field` / `srValue` / `display`\n * targets are re-synced on connection. The composition listeners are released\n * on `disconnect()`.\n *\n * Honest a11y note: a hidden `<input>` is not exposed to assistive tech, so the\n * normalized value is *also* published as text in the `srValue` span referenced\n * by the display's `aria-describedby` — that, not the hidden field, is what a\n * screen reader announces.\n */\nexport class CurrencyInputController extends Controller<HTMLElement> {\n static override targets = [\"display\", \"field\", \"srValue\"];\n static override values = {\n locale: { type: String, default: \"en-US\" },\n currency: { type: String, default: \"\" },\n precision: { type: Number, default: 2 },\n };\n static actions = [\"format\", \"onInput\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly displayTarget: HTMLInputElement;\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly srValueTarget: HTMLElement;\n declare readonly hasDisplayTarget: boolean;\n declare readonly hasFieldTarget: boolean;\n declare readonly hasSrValueTarget: boolean;\n declare localeValue: string;\n declare currencyValue: string;\n declare precisionValue: number;\n\n /** Last committed numeric value, to suppress duplicate `change` dispatches. */\n #lastValue: number | null = null;\n #started = false;\n\n /** Validated mirrors of the Values; the hot path never reads a raw Value. */\n #locale = \"en-US\";\n #currency = \"\";\n #precision = 2;\n\n /** Formatters rebuilt only when a Value changes, never per keystroke. */\n #grouping!: Intl.NumberFormat;\n #fixed!: Intl.NumberFormat;\n #accessible!: Intl.NumberFormat;\n #group = \",\";\n #decimal = \".\";\n /** The locale's non-Latin digits mapped back to ASCII (empty for Latin locales). */\n readonly #digits = new Map<string, string>();\n\n /** Holds mid-composition input so the IME's uncommitted text is never rewritten. */\n readonly #composition = new CompositionTracker({\n onEnd: () => this.#reformat(false),\n });\n\n /** Re-validates on declaration changes and re-renders the committed display. */\n localeValueChanged(): void {\n this.#applyValueChange();\n }\n\n currencyValueChanged(): void {\n this.#applyValueChange();\n }\n\n precisionValueChanged(): void {\n this.#applyValueChange();\n }\n\n /**\n * Scans the display under the *outgoing* configuration (its separators wrote\n * that text), then revalidates and re-renders under the new one — so a locale\n * switch re-interprets the value, never the old text with new separators.\n */\n #applyValueChange(): void {\n const scan =\n this.#started && this.hasDisplayTarget ? this.#scan(this.displayTarget.value) : null;\n this.#revalidate();\n if (!scan || !this.hasDisplayTarget) return;\n if (document.activeElement === this.displayTarget) {\n const formatted = this.#render(scan.parts);\n this.displayTarget.value = formatted;\n this.#reflect(scan.value, formatted);\n } else if (scan.value === null) {\n this.displayTarget.value = \"\";\n this.#reflect(null, \"\");\n } else {\n const rounded = round(scan.value, this.#precision);\n const formatted = this.#fixed.format(rounded);\n this.displayTarget.value = formatted;\n this.#reflect(rounded, formatted);\n }\n }\n\n /** Normalizes any pre-filled display value to its fixed-precision form. */\n override connect(): void {\n this.#started = true;\n if (!this.hasDisplayTarget) return;\n // Seed lastValue with the *rounded* initial value so the idempotent\n // connect-time reformat does not dispatch a spurious `change`.\n const { value } = this.#scan(this.displayTarget.value);\n this.#lastValue = value === null ? null : round(value, this.#precision);\n if (value === null) {\n this.displayTarget.value = \"\";\n this.#reflect(null, \"\");\n } else {\n this.#reformat(true);\n }\n }\n\n override disconnect(): void {\n this.#started = false;\n this.#composition.disconnect();\n }\n\n /** Tracks composition on an arriving (or swapped-in) display and normalizes it. */\n displayTargetConnected(target: HTMLInputElement): void {\n this.#composition.observe(target);\n if (this.#started) this.#reformat(true);\n }\n\n displayTargetDisconnected(target: HTMLInputElement): void {\n this.#composition.unobserve(target);\n }\n\n /** Syncs a late-arriving hidden field without touching the display or events. */\n fieldTargetConnected(): void {\n if (this.#started) this.#resync();\n }\n\n /** Syncs a late-arriving screen-reader span the same way. */\n srValueTargetConnected(): void {\n if (this.#started) this.#resync();\n }\n\n /** Re-groups digits as the user types, preserving the caret position. */\n onInput(event: Event): void {\n if (this.#composition.isComposing(event as InputEvent)) return;\n this.#reformat(false);\n }\n\n /** Applies the fixed-precision rounding on blur. */\n format(): void {\n this.#reformat(true);\n }\n\n /**\n * Parses the display value, rewrites it grouped (optionally at fixed\n * precision), keeps the caret stable by significant characters, and syncs the\n * field, the screen-reader span, and the `change` event.\n *\n * @stimeoRenderRoot\n */\n #reformat(fixedPrecision: boolean): void {\n if (!this.hasDisplayTarget) return;\n const raw = this.displayTarget.value;\n const { parts, value } = this.#scan(raw);\n\n if (fixedPrecision) {\n if (value === null) {\n // Blur with no digits (an abandoned sign or dot) clears the entry.\n this.displayTarget.value = \"\";\n this.#reflect(null, \"\");\n return;\n }\n const rounded = round(value, this.#precision);\n const formatted = this.#fixed.format(rounded);\n this.displayTarget.value = formatted;\n this.#reflect(rounded, formatted);\n return;\n }\n\n const formatted = this.#render(parts);\n if (formatted !== raw) {\n const caret = this.displayTarget.selectionStart;\n const anchor = caret === null ? null : this.#significantBefore(raw, caret);\n this.displayTarget.value = formatted;\n if (anchor !== null) this.#restoreCaret(formatted, anchor);\n }\n // The display may hold an in-progress \"-\", but a null value always rides\n // with an empty `formatted` so consumers can treat the pair as \"cleared\".\n this.#reflect(value, value === null ? \"\" : formatted);\n }\n\n /**\n * The in-progress rendering: grouped integer, sign and fraction as typed.\n *\n * @stimeoRenderRoot\n */\n #render(parts: EntryParts): string {\n const int = parts.int === \"\" ? \"\" : this.#grouping.format(BigInt(parts.int));\n const frac = parts.hasDot ? this.#decimal + parts.frac : \"\";\n return parts.sign + int + frac;\n }\n\n /** Restores the caret to sit just after the n-th significant character. */\n #restoreCaret(formatted: string, significantBefore: number): void {\n let seen = 0;\n let position = formatted.length;\n for (let i = 0; i < formatted.length; i++) {\n if (seen >= significantBefore) {\n position = i;\n break;\n }\n if (this.#isSignificant(formatted[i] as string)) seen += 1;\n }\n try {\n this.displayTarget.setSelectionRange(position, position);\n } catch {\n // Some hosts disallow selection on certain input states; the value is\n // already correct, so a failed caret restore is non-fatal.\n }\n }\n\n /**\n * Counts the characters before `caret` that survive into the rendering,\n * applying the same acceptance rules as {@link #scan} — a rejected keystroke\n * (a mid-string sign, a second decimal mark) must not shift the anchor.\n */\n #significantBefore(text: string, caret: number): number {\n let count = 0;\n let sawSign = false;\n let sawDigit = false;\n let sawDot = false;\n for (const ch of this.#normalize(text.slice(0, caret))) {\n if (ch >= \"0\" && ch <= \"9\") {\n count += 1;\n sawDigit = true;\n } else if ((ch === \"-\" || ch === \"+\") && !sawSign && !sawDigit && !sawDot) {\n count += 1;\n sawSign = true;\n } else if (this.#isDecimalMark(ch) && !sawDot) {\n count += 1;\n sawDot = true;\n }\n }\n return count;\n }\n\n /** Digits, signs, and the decimal mark anchor the caret; grouping does not. */\n #isSignificant(ch: string): boolean {\n if (ch >= \"0\" && ch <= \"9\") return true;\n if (ch === \"-\" || ch === \"+\") return true;\n return ch === this.#decimal || (ch === \".\" && this.#group !== \".\");\n }\n\n /** Writes the normalized value to the field, the SR span, and the empty hook. */\n #write(value: number | null): void {\n const isEmpty = value === null;\n if (this.hasFieldTarget) this.fieldTarget.value = isEmpty ? \"\" : String(value);\n if (this.hasSrValueTarget) {\n this.srValueTarget.textContent = isEmpty ? \"\" : this.#accessible.format(value);\n }\n this.element.toggleAttribute(\"data-stimeo--currency-input-empty\", isEmpty);\n }\n\n /** {@link #write}, then reports a moved value as `change` (`\"\"` rides with `null`). */\n #reflect(value: number | null, formatted: string): void {\n this.#write(value);\n if (value !== this.#lastValue) {\n this.#lastValue = value;\n this.dispatch(\"change\", { detail: { value, formatted } });\n }\n }\n\n /**\n * Scans arbitrary input text into its number-shaped parts and value. Keeps\n * digits, one leading sign, one decimal mark, and the fraction verbatim;\n * everything else (grouping, symbols, words) is dropped. An ASCII \".\" is only\n * a decimal mark where it is not the locale's grouping separator. Precision\n * plays no part here — rounding belongs to the blur-time format.\n */\n #scan(text: string): Scan {\n let sign: EntryParts[\"sign\"] = \"\";\n let int = \"\";\n let hasDot = false;\n let frac = \"\";\n for (const ch of this.#normalize(text)) {\n if (ch >= \"0\" && ch <= \"9\") {\n if (hasDot) frac += ch;\n else int += ch;\n } else if ((ch === \"-\" || ch === \"+\") && sign === \"\" && int === \"\" && !hasDot) {\n sign = ch;\n } else if (this.#isDecimalMark(ch) && !hasDot) {\n hasDot = true;\n }\n }\n const parts: EntryParts = { sign, int, hasDot, frac };\n // `Number` accepts \"12.\" and \".5\" alike, so one dotted form covers every\n // shape — and a digit-less entry (\"\", \"-\", \".\") reads as NaN, hence null.\n const value = Number(`${sign}${int}.${frac}`);\n return { parts, value: Number.isFinite(value) ? value : null };\n }\n\n /** Whether `ch` reads as this locale's decimal mark. */\n #isDecimalMark(ch: string): boolean {\n return ch === this.#decimal || (ch === \".\" && this.#group !== \".\");\n }\n\n /** Re-syncs field / srValue / hook from the current display without dispatching. */\n #resync(): void {\n if (!this.hasDisplayTarget) return;\n this.#write(this.#scan(this.displayTarget.value).value);\n }\n\n /**\n * Validates the declared Values, falling back to each Value's default when a\n * declaration cannot be interpreted (a malformed locale or currency tag, a\n * precision outside `Intl`'s 0–100 integer range), and rebuilds the cached\n * formatters from the validated set.\n */\n #revalidate(): void {\n const declaredLocale = intlFormatter(Intl.NumberFormat, this.localeValue, {});\n this.#locale = declaredLocale === null ? \"en-US\" : this.localeValue;\n\n const precision = this.precisionValue;\n this.#precision =\n Number.isInteger(precision) && precision >= 0 && precision <= 100 ? precision : 2;\n\n this.#currency = \"\";\n if (this.currencyValue !== \"\") {\n const declaredCurrency = intlFormatter(Intl.NumberFormat, this.#locale, {\n style: \"currency\",\n currency: this.currencyValue,\n });\n // An unknown code falls through to the plain-number accessible text.\n if (declaredCurrency !== null) this.#currency = this.currencyValue;\n }\n\n this.#grouping = intlFormatter(\n Intl.NumberFormat,\n this.#locale,\n { useGrouping: true, maximumFractionDigits: 0 },\n this.#locale,\n );\n this.#fixed = intlFormatter(\n Intl.NumberFormat,\n this.#locale,\n {\n useGrouping: true,\n minimumFractionDigits: this.#precision,\n maximumFractionDigits: this.#precision,\n },\n this.#locale,\n );\n this.#accessible = this.#currency\n ? intlFormatter(\n Intl.NumberFormat,\n this.#locale,\n { style: \"currency\", currency: this.#currency },\n this.#locale,\n )\n : this.#fixed;\n\n const parts = intlFormatter(Intl.NumberFormat, this.#locale, {}, this.#locale).formatToParts(\n 11111.1,\n );\n this.#group = parts.find((p) => p.type === \"group\")?.value ?? \",\";\n this.#decimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\";\n\n // Locales whose default numbering system is not Latin (ar-EG, fa-IR, …)\n // format with their own digits; mapping them back keeps the controller's\n // own output re-parseable to the same value.\n this.#digits.clear();\n const digitFormatter = intlFormatter(\n Intl.NumberFormat,\n this.#locale,\n { useGrouping: false },\n this.#locale,\n );\n for (let i = 0; i <= 9; i++) {\n const digit = digitFormatter.format(i);\n if (digit !== String(i)) this.#digits.set(digit, String(i));\n }\n }\n\n /**\n * Maps the locale's own digits to ASCII, folds full-width forms (the digits,\n * signs, and marks an IME confirms as 0-9+-.,) through the shared\n * half-width mapping, and reads U+2212 MINUS SIGN as \"-\", which some locales'\n * formatted output uses for negatives.\n */\n #normalize(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const mapped = this.#digits.get(ch);\n if (mapped !== undefined) {\n out += mapped;\n } else if (ch === \"−\") {\n out += \"-\";\n } else {\n out += halfWidthChar(ch);\n }\n }\n return out;\n }\n}\n\n/** Rounds `value` to `precision` decimal places, avoiding `-0`; overflow keeps `value`. */\nfunction round(value: number, precision: number): number {\n const factor = 10 ** Math.max(0, precision);\n const rounded = Math.round(value * factor) / factor;\n if (!Number.isFinite(rounded)) return value;\n return rounded === 0 ? 0 : rounded;\n}\n"]}
|
|
@@ -10,9 +10,9 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
10
10
|
* data-stimeo--date-range-picker-min-value="2026-01-01"
|
|
11
11
|
* data-stimeo--date-range-picker-max-value="2026-12-31"
|
|
12
12
|
* data-stimeo--date-range-picker-disabled-dates-value='["2026-06-15"]'>
|
|
13
|
-
* <button data-action="stimeo--date-range-picker#prev">Prev</button>
|
|
13
|
+
* <button data-action="click->stimeo--date-range-picker#prev">Prev</button>
|
|
14
14
|
* <span data-stimeo--date-range-picker-target="monthLabel" aria-live="polite"></span>
|
|
15
|
-
* <button data-action="stimeo--date-range-picker#next">Next</button>
|
|
15
|
+
* <button data-action="click->stimeo--date-range-picker#next">Next</button>
|
|
16
16
|
* <div role="grid" data-stimeo--date-range-picker-target="grid">
|
|
17
17
|
* <!-- exactly 42 cell targets (7 days × 6 rows) -->
|
|
18
18
|
* <button role="gridcell" tabindex="-1"
|
|
@@ -22,7 +22,7 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
22
22
|
* focus->stimeo--date-range-picker#previewTo
|
|
23
23
|
* keydown->stimeo--date-range-picker#onKeydown"></button>
|
|
24
24
|
* </div>
|
|
25
|
-
* <button data-range="last7" data-action="stimeo--date-range-picker#applyPreset">…</button>
|
|
25
|
+
* <button data-range="last7" data-action="click->stimeo--date-range-picker#applyPreset">…</button>
|
|
26
26
|
* <span role="status" aria-live="polite" data-stimeo--date-range-picker-target="status"></span>
|
|
27
27
|
* <input type="hidden" data-stimeo--date-range-picker-target="startField" />
|
|
28
28
|
* <input type="hidden" data-stimeo--date-range-picker-target="endField" />
|
|
@@ -47,6 +47,35 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
// src/utils/intl_format.ts
|
|
51
|
+
var formatters = /* @__PURE__ */ new WeakMap();
|
|
52
|
+
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
53
|
+
const cache = cacheFor(factory);
|
|
54
|
+
const key = cacheKey(locale, options, fallbackLocale);
|
|
55
|
+
if (cache.has(key)) return cache.get(key);
|
|
56
|
+
const formatter = construct(factory, locale, options, fallbackLocale);
|
|
57
|
+
cache.set(key, formatter);
|
|
58
|
+
return formatter;
|
|
59
|
+
}
|
|
60
|
+
function cacheFor(factory) {
|
|
61
|
+
const existing = formatters.get(factory);
|
|
62
|
+
if (existing) return existing;
|
|
63
|
+
const created = /* @__PURE__ */ new Map();
|
|
64
|
+
formatters.set(factory, created);
|
|
65
|
+
return created;
|
|
66
|
+
}
|
|
67
|
+
function cacheKey(locale, options, fallbackLocale) {
|
|
68
|
+
return JSON.stringify([locale, options, fallbackLocale]);
|
|
69
|
+
}
|
|
70
|
+
function construct(factory, locale, options, fallbackLocale) {
|
|
71
|
+
try {
|
|
72
|
+
return new factory(locale, options);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (!(error instanceof RangeError)) throw error;
|
|
75
|
+
return new factory(fallbackLocale, options);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
// src/utils/dates.ts
|
|
51
80
|
function toISODateString(date) {
|
|
52
81
|
const year = date.getFullYear();
|
|
@@ -80,14 +109,9 @@ function parseISOMonthString(monthStr) {
|
|
|
80
109
|
function toISOMonthString(date) {
|
|
81
110
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
82
111
|
}
|
|
112
|
+
var MONTH_LABEL_OPTIONS = { month: "long", year: "numeric" };
|
|
83
113
|
function monthLabelFormatter(locale) {
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
return new Intl.DateTimeFormat(locale, options);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (!(error instanceof RangeError)) throw error;
|
|
89
|
-
return new Intl.DateTimeFormat("en", options);
|
|
90
|
-
}
|
|
114
|
+
return intlFormatter(Intl.DateTimeFormat, locale, MONTH_LABEL_OPTIONS, "en");
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
// src/utils/microtask_coalescer.ts
|