stimeo-ui 0.15.0 → 0.16.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 +178 -0
- package/README.md +120 -0
- package/dist/cable/index.js +63 -15
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +30 -3
- package/dist/controllers/accordion_controller.js +98 -9
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +8 -7
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +3 -2
- package/dist/controllers/announcer_controller.js +8 -1
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.js +43 -2
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +1 -1
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +10 -7
- package/dist/controllers/breadcrumb_controller.js +9 -8
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +4 -4
- package/dist/controllers/bulk_select_controller.js +7 -6
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +85 -23
- package/dist/controllers/calendar_controller.js +323 -123
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +67 -22
- package/dist/controllers/carousel_controller.js +263 -38
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +1 -1
- package/dist/controllers/character_counter_controller.js +40 -2
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.js +54 -10
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +28 -2
- package/dist/controllers/clipboard_controller.js +63 -5
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +25 -1
- package/dist/controllers/collapsible_controller.js +99 -14
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +29 -8
- package/dist/controllers/color_picker_controller.js +80 -34
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +10 -1
- package/dist/controllers/combobox_controller.js +106 -16
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +3 -3
- package/dist/controllers/command_palette_controller.js +35 -3
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.js +56 -11
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.js +3 -0
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +6 -0
- package/dist/controllers/context_menu_controller.js +32 -12
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +30 -1
- package/dist/controllers/countdown_controller.js +129 -26
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +74 -15
- package/dist/controllers/currency_input_controller.js +205 -58
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +63 -18
- package/dist/controllers/data_grid_controller.js +195 -29
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +34 -6
- package/dist/controllers/date_range_picker_controller.js +139 -27
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +10 -3
- package/dist/controllers/dialog_controller.js +35 -8
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.js +22 -4
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +2 -2
- package/dist/controllers/dirty_form_controller.js +14 -1
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.js +1 -0
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +18 -10
- package/dist/controllers/drawer_controller.js +54 -19
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +9 -3
- package/dist/controllers/dropdown_controller.js +36 -9
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.js +34 -0
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.js +144 -51
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +10 -4
- package/dist/controllers/filter_controller.js +20 -6
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +26 -6
- package/dist/controllers/flash_controller.js +305 -46
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.js +1 -0
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +7 -5
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +19 -13
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.js +16 -2
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.js +19 -2
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +10 -2
- package/dist/controllers/hover_card_controller.js +40 -14
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +16 -3
- package/dist/controllers/idle_controller.js +90 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +5 -2
- package/dist/controllers/input_mask_controller.js +65 -9
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +3 -0
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +11 -2
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +50 -8
- package/dist/controllers/listbox_controller.js +203 -45
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.js +0 -2
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +9 -3
- package/dist/controllers/menu_controller.js +45 -16
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +11 -0
- package/dist/controllers/menubar_controller.js +58 -24
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.js +9 -5
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +18 -3
- package/dist/controllers/multi_select_controller.js +278 -104
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +11 -0
- package/dist/controllers/navigation_menu_controller.js +48 -15
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.js +37 -8
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.js +9 -1
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +36 -8
- package/dist/controllers/number_input_controller.js +124 -21
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +14 -1
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +22 -7
- package/dist/controllers/otp_controller.js +198 -55
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
- package/dist/controllers/overflow_indicator_controller.js +84 -13
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +26 -6
- package/dist/controllers/overflow_menu_controller.js +112 -41
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +19 -11
- package/dist/controllers/pagination_controller.js +74 -28
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +15 -1
- package/dist/controllers/password_reveal_controller.js +59 -2
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/persist_controller.js +30 -8
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +131 -52
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +9 -3
- package/dist/controllers/popover_controller.js +45 -11
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -1
- package/dist/controllers/portal_controller.js +6 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.js +16 -1
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.js +8 -4
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +6 -4
- package/dist/controllers/radio_group_controller.js +42 -17
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +49 -1
- package/dist/controllers/range_slider_controller.js +88 -42
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +14 -3
- package/dist/controllers/rating_controller.js +39 -15
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +24 -2
- package/dist/controllers/read_more_controller.js +69 -2
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +16 -2
- package/dist/controllers/resizable_controller.js +82 -22
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.js +5 -1
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +0 -1
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +3 -2
- package/dist/controllers/scrollspy_controller.js +6 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +41 -14
- package/dist/controllers/separator_controller.js +66 -37
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +20 -3
- package/dist/controllers/sidebar_controller.js +77 -18
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.js +6 -1
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +45 -7
- package/dist/controllers/slider_controller.js +82 -47
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.js +11 -1
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +17 -2
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.js +10 -2
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +19 -17
- package/dist/controllers/step_indicator_controller.js +18 -17
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +34 -9
- package/dist/controllers/stepper_controller.js +101 -19
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
- package/dist/controllers/stick_to_bottom_controller.js +75 -5
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +3 -2
- package/dist/controllers/submit_once_controller.js +16 -3
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +29 -7
- package/dist/controllers/switch_controller.js +101 -10
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +12 -0
- package/dist/controllers/tabs_controller.js +21 -2
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +15 -3
- package/dist/controllers/tags_input_controller.js +209 -59
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +20 -3
- package/dist/controllers/theme_controller.js +64 -14
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.js +23 -8
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +52 -15
- package/dist/controllers/toast_controller.js +337 -54
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +49 -7
- package/dist/controllers/toggle_group_controller.js +159 -23
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +32 -0
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +8 -0
- package/dist/controllers/tooltip_controller.js +39 -13
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.js +4 -0
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +39 -8
- package/dist/controllers/tree_view_controller.js +169 -16
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +4588 -1535
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +72 -6
- package/dist/inspector/cli.js +262 -51
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +309 -51
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +40 -40
- package/dist/inspector/manifest.json +525 -59
- package/dist/positioning/index.js +2 -0
- package/dist/positioning/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/coerce.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/range.ts","../../src/controllers/meter_controller.ts"],"names":[],"mappings":"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","file":"meter_controller.js","sourcesContent":["/**\n * Sends one message to the page's shared `stimeo--announcer`.\n *\n * A component that has to reach assistive tech does not carry a live region of its\n * own: a region only announces what changes *after* assistive tech already knows\n * about it, which a region that appears (or is un-hidden) with its message cannot\n * satisfy. The one region that can is the announcer sitting in the page from the\n * start, so state changes are handed to it as an event and it does the reading.\n *\n * The event goes to `window` because the announcer is usually a sibling high in the\n * document rather than an ancestor of the component dispatching it.\n *\n * Wording comes from the consumer — the library ships no English strings — so an\n * empty message is silently dropped and nothing is announced.\n *\n * @example\n * ```ts\n * announce(this.announceTextValue, { assertive: false });\n * ```\n */\nexport function announce(message: string, options: { assertive?: boolean } = {}): void {\n const text = message.trim();\n if (text.length === 0) return;\n window.dispatchEvent(\n new CustomEvent(\"stimeo--announcer:announce\", {\n detail: { message: text, assertive: options.assertive === true },\n }),\n );\n}\n\n/**\n * Fills `{name}` placeholders in an announcement template from `values`.\n *\n * The same substitution the value-text templates use, so a consumer writes\n * `\"{percent}% complete\"` in one attribute and gets the same rules everywhere. A\n * placeholder with no matching entry is left as authored rather than blanked, which\n * keeps a typo visible instead of silently swallowing the word.\n */\nexport function fillTemplate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, name: string) => {\n const replacement = values[name];\n return replacement === undefined ? match : String(replacement);\n });\n}\n","/**\n * Numeric coercion for controllers that accept a number through a Value, an action\n * param, or an event detail.\n *\n * Stimulus already coerces numeric action params to numbers, but a value can also\n * arrive as a string — via a `*:set` CustomEvent `detail`, or an action param\n * whose attribute does not look numeric. Centralizing the parse keeps `setValue`\n * tolerant of either form while rejecting anything that is not a finite number.\n */\n\n/**\n * Coerces `raw` to a finite number, or returns `null` when it is absent, empty,\n * or not parseable. Empty strings are treated as \"no value\" rather than `0`, so a\n * stray blank param cannot silently reset the value.\n */\nexport function toFiniteNumber(raw: number | string | null | undefined): number | null {\n if (raw === null || raw === undefined || raw === \"\") return null;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Range normalization shared by the value-bearing controllers (progress, meter,\n * slider, range-slider).\n *\n * Each of them publishes \"where the value sits inside `[min, max]`\" as a CSS\n * custom property the consumer multiplies a track by, and each has to answer the\n * same degenerate cases: empty and inverted ranges cannot express progress, and\n * malformed Number Values can produce `NaN`. Keeping the rule in one place is\n * what makes those answers identical across the four.\n */\n\n/**\n * Fraction of `[min, max]` that `value` occupies, always within `[0, 1]`.\n *\n * `value` is clamped into the range first, so a value outside it reports a full\n * or empty track rather than pushing the fraction past the ends.\n *\n * An empty, inverted, or non-numeric range yields `0`. That is a deliberate\n * floor rather than a computed result: `min === max` would produce `NaN`, and\n * `min > max` would report a full track for a value that is really out of range.\n * Non-finite results are floored for the same reason. These results reach\n * assistive tech, because the same fraction drives the percentage substituted\n * into `aria-valuetext`.\n */\nexport function rangeFraction(value: number, min: number, max: number): number {\n const span = max - min;\n if (!(span > 0)) return 0;\n\n const clamped = Math.min(max, Math.max(min, value));\n let fraction: number;\n if (Number.isFinite(span)) {\n fraction = (clamped - min) / span;\n } else {\n // Scaling preserves the ratio when two finite endpoints straddle zero so\n // widely that their subtraction overflows to Infinity.\n const scale = Math.max(Math.abs(min), Math.abs(max));\n fraction = (clamped / scale - min / scale) / (max / scale - min / scale);\n }\n\n if (!Number.isFinite(fraction)) return 0;\n return Math.min(1, Math.max(0, fraction));\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { toFiniteNumber } from \"../utils/coerce\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { rangeFraction } from \"../utils/range\";\n\n/**\n * Event shape `setValue` accepts: an action param `amount` or a `detail.value`.\n * Both are typed `number | string` because, while Stimulus coerces numeric action\n * params to numbers, a `meter:set` CustomEvent (or a non-numeric-looking param)\n * may carry a string; {@link MeterController.setValue} normalizes either form.\n */\ntype SetValueEvent = Event & {\n params?: { amount?: number | string };\n detail?: { value?: number | string };\n};\n\n/** Threshold segment a value falls into, reflected on `data-state`. */\ntype MeterState = \"low\" | \"medium\" | \"high\";\n\n/** One point-in-time reading: the clamped value with the ratio and segment it implies. */\ntype MeterReading = { value: number; ratio: number; state: MeterState };\n\n/**\n * Marks an `aria-valuetext` this controller wrote, so a render takes back only\n * its own text. `aria-valuetext` is shared: a consumer may author it instead of\n * supplying a template, and that text is what carries the threshold segment to\n * readers who cannot see the colour — clearing it would take the segment with it.\n */\nconst OWNED_VALUE_TEXT = \"data-stimeo--meter-owns-valuetext\";\n\n/**\n * Headless meter behavior backed by the WAI-ARIA `meter` role.\n *\n * Markup contract (identifier: `stimeo--meter`):\n * <div data-controller=\"stimeo--meter\" role=\"meter\" aria-label=\"Disk usage\"\n * aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"72\"\n * data-stimeo--meter-value-value=\"72\"\n * data-stimeo--meter-low-value=\"50\" data-stimeo--meter-high-value=\"80\">\n * <div data-stimeo--meter-target=\"bar\"></div>\n * </div>\n *\n * A `meter` is a *point-in-time* scalar within a known range (disk usage,\n * battery, score) — distinct from {@link ProgressController}'s task progress.\n * The controller syncs the ARIA value attributes and, when `low`/`high`\n * thresholds are present, classifies the value into a `low`/`medium`/`high`\n * segment on `data-state` so the consumer can color the bar.\n *\n * `change` dispatches `{ value: number, ratio: number, state: string }`.\n *\n * @remarks\n * Behavior only. Because state must not be conveyed by color alone (WCAG 1.4.1),\n * a consumer-provided `valueText` template feeds `aria-valuetext` so the segment\n * is also available as text; a consumer that authors `aria-valuetext` itself keeps\n * it instead: only text this controller marked as its own is ever taken back. Threshold presence is read from the\n * *attributes* (an absent attribute means \"no threshold\"), not from a sentinel value.\n */\nexport class MeterController extends Controller<HTMLElement> {\n static override targets = [\"bar\"];\n static override values = {\n announceText: { type: String, default: \"\" },\n value: { type: Number, default: 0 },\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n low: { type: Number, default: 0 },\n high: { type: Number, default: 0 },\n optimum: { type: Number, default: 0 },\n valueText: { type: String, default: \"\" },\n };\n static actions = [\"setValue\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly barTarget: HTMLElement;\n declare readonly hasBarTarget: boolean;\n\n declare valueValue: number;\n declare minValue: number;\n declare maxValue: number;\n declare lowValue: number;\n declare highValue: number;\n declare optimumValue: number;\n declare valueTextValue: string;\n declare announceTextValue: string;\n\n /**\n * Collapses a morph that swaps several render inputs at once into one repaint.\n * A single update usually rewrites the whole set, and each Value would otherwise\n * repaint on its own.\n */\n readonly #repaint = new MicrotaskCoalescer(() => {\n this.#render();\n });\n\n /** The segment last announced, so only a change is read out. */\n #announcedState: MeterState | null = null;\n\n override connect(): void {\n this.#repaint.activate();\n this.#render();\n }\n\n /** Closes the window in which a queued repaint may still run. */\n override disconnect(): void {\n this.#repaint.cancel();\n }\n\n /**\n * Updates the measured value from an action param (`amount`) or a\n * `detail.value` CustomEvent, syncs ARIA and `data-state`, and dispatches\n * `change` with the value, ratio, and computed segment.\n */\n setValue(event: SetValueEvent): void {\n const next = toFiniteNumber(event.params?.amount ?? event.detail?.value);\n if (next === null) return;\n this.valueValue = this.#clamp(next);\n const reading = this.#render();\n this.dispatch(\"change\", { detail: reading });\n // Only the segment is news: reading every value would be unusable, and the\n // number itself is already exposed through `aria-valuenow`.\n if (reading.state !== this.#announcedState) {\n this.#announcedState = reading.state;\n announce(\n fillTemplate(this.announceTextValue, { state: reading.state, value: reading.value }),\n );\n }\n }\n\n /** Repaints when application code (or a Turbo morph) changes `value` at runtime. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `min` at runtime. */\n minValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `max` at runtime. */\n maxValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `low` at runtime. */\n lowValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `high` at runtime. */\n highValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `valueText` at runtime. */\n valueTextValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Clamps `raw` into the configured `[min, max]` range. */\n #clamp(raw: number): number {\n return Math.min(this.maxValue, Math.max(this.minValue, raw));\n }\n\n /** Whether a threshold attribute is present (absent = no threshold). */\n #hasThreshold(name: \"low\" | \"high\"): boolean {\n return this.element.hasAttribute(`data-stimeo--meter-${name}-value`);\n }\n\n /**\n * Classifies `value` into a `low`/`medium`/`high` segment. Values at or below\n * `low` are `low`; at or above `high` are `high`; otherwise `medium`. With\n * neither threshold present, everything is `medium`.\n */\n #stateOf(value: number): MeterState {\n if (this.#hasThreshold(\"low\") && value <= this.lowValue) return \"low\";\n if (this.#hasThreshold(\"high\") && value >= this.highValue) return \"high\";\n return \"medium\";\n }\n\n /**\n * Reflects value/range onto ARIA, the segment onto `data-state`, and the ratio.\n * The reading is derived once and returned, so the `change` detail reports the\n * same numbers the DOM just received.\n *\n * @stimeoRenderRoot\n */\n #render(): MeterReading {\n const value = this.#clamp(this.valueValue);\n const reading: MeterReading = {\n value,\n ratio: rangeFraction(value, this.minValue, this.maxValue),\n state: this.#stateOf(value),\n };\n this.element.setAttribute(\"aria-valuemin\", String(this.minValue));\n this.element.setAttribute(\"aria-valuemax\", String(this.maxValue));\n this.element.setAttribute(\"aria-valuenow\", String(reading.value));\n this.element.style.setProperty(\"--stimeo--meter-ratio\", String(reading.ratio));\n this.element.setAttribute(\"data-state\", reading.state);\n this.#applyValueText(reading);\n return reading;\n }\n\n /**\n * Sets `aria-valuetext` from the consumer-provided template, substituting\n * `{value}`, `{percent}`, and `{state}`. Kept i18n-neutral in the library.\n * With no template the attribute belongs to the consumer, so only a text this\n * controller wrote is taken back ({@link OWNED_VALUE_TEXT}).\n */\n #applyValueText({ value, ratio, state }: MeterReading): void {\n if (this.valueTextValue.length === 0) {\n if (this.element.hasAttribute(OWNED_VALUE_TEXT)) {\n this.element.removeAttribute(\"aria-valuetext\");\n this.element.removeAttribute(OWNED_VALUE_TEXT);\n }\n return;\n }\n const percent = Math.round(ratio * 100);\n const text = this.valueTextValue\n .replaceAll(\"{value}\", String(value))\n .replaceAll(\"{percent}\", String(percent))\n .replaceAll(\"{state}\", state);\n this.element.setAttribute(\"aria-valuetext\", text);\n this.element.setAttribute(OWNED_VALUE_TEXT, \"\");\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/coerce.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/range.ts","../../src/controllers/meter_controller.ts"],"names":[],"mappings":"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","file":"meter_controller.js","sourcesContent":["/**\n * Sends one message to the page's shared `stimeo--announcer`.\n *\n * A component that has to reach assistive tech does not carry a live region of its\n * own: a region only announces what changes *after* assistive tech already knows\n * about it, which a region that appears (or is un-hidden) with its message cannot\n * satisfy. The one region that can is the announcer sitting in the page from the\n * start, so state changes are handed to it as an event and it does the reading.\n *\n * The event goes to `window` because the announcer is usually a sibling high in the\n * document rather than an ancestor of the component dispatching it.\n *\n * Wording comes from the consumer — the library ships no English strings — so an\n * empty message is silently dropped and nothing is announced.\n *\n * @example\n * ```ts\n * announce(this.announceTextValue, { assertive: false });\n * ```\n */\nexport function announce(message: string, options: { assertive?: boolean } = {}): void {\n const text = message.trim();\n if (text.length === 0) return;\n window.dispatchEvent(\n new CustomEvent(\"stimeo--announcer:announce\", {\n detail: { message: text, assertive: options.assertive === true },\n }),\n );\n}\n\n/**\n * Fills `{name}` placeholders in an announcement template from `values`.\n *\n * The same substitution the value-text templates use, so a consumer writes\n * `\"{percent}% complete\"` in one attribute and gets the same rules everywhere. A\n * placeholder with no matching entry is left as authored rather than blanked, which\n * keeps a typo visible instead of silently swallowing the word.\n */\nexport function fillTemplate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, name: string) => {\n const replacement = values[name];\n return replacement === undefined ? match : String(replacement);\n });\n}\n","/**\n * Numeric coercion for controllers that accept a number through a Value, an action\n * param, or an event detail.\n *\n * Stimulus already coerces numeric action params to numbers, but a value can also\n * arrive as a string — via a `*:set` CustomEvent `detail`, or an action param\n * whose attribute does not look numeric. Centralizing the parse keeps `setValue`\n * tolerant of either form while rejecting anything that is not a finite number.\n */\n\n/**\n * Coerces `raw` to a finite number, or returns `null` when it is absent, empty,\n * or not parseable. Empty strings are treated as \"no value\" rather than `0`, so a\n * stray blank param cannot silently reset the value.\n */\nexport function toFiniteNumber(raw: number | string | null | undefined): number | null {\n if (raw === null || raw === undefined || raw === \"\") return null;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Range normalization shared by the value-bearing controllers (progress, meter,\n * slider, range-slider).\n *\n * Each of them publishes \"where the value sits inside `[min, max]`\" as a CSS\n * custom property the consumer multiplies a track by, and each has to answer the\n * same degenerate cases: empty and inverted ranges cannot express progress, and\n * malformed Number Values can produce `NaN`. Keeping the rule in one place is\n * what makes those answers identical across the four.\n */\n\n/**\n * Fraction of `[min, max]` that `value` occupies, always within `[0, 1]`.\n *\n * `value` is clamped into the range first, so a value outside it reports a full\n * or empty track rather than pushing the fraction past the ends.\n *\n * An empty, inverted, or non-numeric range yields `0`. That is a deliberate\n * floor rather than a computed result: `min === max` would produce `NaN`, and\n * `min > max` would report a full track for a value that is really out of range.\n * Non-finite results are floored for the same reason. These results reach\n * assistive tech, because the same fraction drives the percentage substituted\n * into `aria-valuetext`.\n */\nexport function rangeFraction(value: number, min: number, max: number): number {\n const span = max - min;\n if (!(span > 0)) return 0;\n\n const clamped = Math.min(max, Math.max(min, value));\n let fraction: number;\n if (Number.isFinite(span)) {\n fraction = (clamped - min) / span;\n } else {\n // Scaling preserves the ratio when two finite endpoints straddle zero so\n // widely that their subtraction overflows to Infinity.\n const scale = Math.max(Math.abs(min), Math.abs(max));\n fraction = (clamped / scale - min / scale) / (max / scale - min / scale);\n }\n\n if (!Number.isFinite(fraction)) return 0;\n return Math.min(1, Math.max(0, fraction));\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { toFiniteNumber } from \"../utils/coerce\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { rangeFraction } from \"../utils/range\";\n\n/**\n * Event shape `setValue` accepts: an action param `amount` or a `detail.value`.\n * Both are typed `number | string` because, while Stimulus coerces numeric action\n * params to numbers, a `meter:set` CustomEvent (or a non-numeric-looking param)\n * may carry a string; {@link MeterController.setValue} normalizes either form.\n */\ntype SetValueEvent = Event & {\n params?: { amount?: number | string };\n detail?: { value?: number | string };\n};\n\n/** Threshold segment a value falls into, reflected on `data-state`. */\ntype MeterState = \"low\" | \"medium\" | \"high\";\n\n/** One point-in-time reading: the clamped value with the ratio and segment it implies. */\ntype MeterReading = { value: number; ratio: number; state: MeterState };\n\n/**\n * Suffix of the marker on an `aria-valuetext` this controller wrote, so a render\n * takes back only its own text. `aria-valuetext` is shared: a consumer may author\n * it instead of supplying a template, and that text is what carries the threshold\n * segment to readers who cannot see the colour — clearing it would take the segment\n * with it.\n */\nconst OWNED_VALUE_TEXT = \"owns-valuetext\";\n\n/**\n * Headless meter behavior backed by the WAI-ARIA `meter` role.\n *\n * Markup contract (identifier: `stimeo--meter`):\n * <div data-controller=\"stimeo--meter\" role=\"meter\" aria-label=\"Disk usage\"\n * aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"72\"\n * data-stimeo--meter-value-value=\"72\"\n * data-stimeo--meter-low-value=\"50\" data-stimeo--meter-high-value=\"80\">\n * <div data-stimeo--meter-target=\"bar\"></div>\n * </div>\n *\n * A `meter` is a *point-in-time* scalar within a known range (disk usage,\n * battery, score) — distinct from {@link ProgressController}'s task progress.\n * The controller syncs the ARIA value attributes and, when `low`/`high`\n * thresholds are present, classifies the value into a `low`/`medium`/`high`\n * segment on `data-state` so the consumer can color the bar.\n *\n * `change` dispatches `{ value: number, ratio: number, state: string }`.\n *\n * @remarks\n * Behavior only. Because state must not be conveyed by color alone (WCAG 1.4.1),\n * a consumer-provided `valueText` template feeds `aria-valuetext` so the segment\n * is also available as text; a consumer that authors `aria-valuetext` itself keeps\n * it instead: only text this controller marked as its own is ever taken back. Threshold presence is read from the\n * *attributes* (an absent attribute means \"no threshold\"), not from a sentinel value.\n */\nexport class MeterController extends Controller<HTMLElement> {\n /** The marker above, in the namespace this controller is registered under. */\n get #ownedValueText(): string {\n return `data-${this.identifier}-${OWNED_VALUE_TEXT}`;\n }\n\n static override targets = [\"bar\"];\n static override values = {\n announceText: { type: String, default: \"\" },\n value: { type: Number, default: 0 },\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n low: { type: Number, default: 0 },\n high: { type: Number, default: 0 },\n optimum: { type: Number, default: 0 },\n valueText: { type: String, default: \"\" },\n };\n static actions = [\"setValue\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly barTarget: HTMLElement;\n declare readonly hasBarTarget: boolean;\n\n declare valueValue: number;\n declare minValue: number;\n declare maxValue: number;\n declare lowValue: number;\n declare highValue: number;\n declare optimumValue: number;\n declare valueTextValue: string;\n declare announceTextValue: string;\n\n /**\n * Collapses a morph that swaps several render inputs at once into one repaint.\n * A single update usually rewrites the whole set, and each Value would otherwise\n * repaint on its own.\n */\n readonly #repaint = new MicrotaskCoalescer(() => {\n this.#render();\n });\n\n /** The segment last announced, so only a change is read out. */\n #announcedState: MeterState | null = null;\n\n override connect(): void {\n this.#repaint.activate();\n this.#render();\n }\n\n /** Closes the window in which a queued repaint may still run. */\n override disconnect(): void {\n this.#repaint.cancel();\n }\n\n /**\n * Updates the measured value from an action param (`amount`) or a\n * `detail.value` CustomEvent, syncs ARIA and `data-state`, and dispatches\n * `change` with the value, ratio, and computed segment.\n */\n setValue(event: SetValueEvent): void {\n const next = toFiniteNumber(event.params?.amount ?? event.detail?.value);\n if (next === null) return;\n this.valueValue = this.#clamp(next);\n const reading = this.#render();\n this.dispatch(\"change\", { detail: reading });\n // Only the segment is news: reading every value would be unusable, and the\n // number itself is already exposed through `aria-valuenow`.\n if (reading.state !== this.#announcedState) {\n this.#announcedState = reading.state;\n announce(\n fillTemplate(this.announceTextValue, { state: reading.state, value: reading.value }),\n );\n }\n }\n\n /** Repaints when application code (or a Turbo morph) changes `value` at runtime. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `min` at runtime. */\n minValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `max` at runtime. */\n maxValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `low` at runtime. */\n lowValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `high` at runtime. */\n highValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `valueText` at runtime. */\n valueTextValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Clamps `raw` into the configured `[min, max]` range. */\n #clamp(raw: number): number {\n return Math.min(this.maxValue, Math.max(this.minValue, raw));\n }\n\n /** Whether a threshold attribute is present (absent = no threshold). */\n #hasThreshold(name: \"low\" | \"high\"): boolean {\n return this.element.hasAttribute(`data-${this.identifier}-${name}-value`);\n }\n\n /**\n * Classifies `value` into a `low`/`medium`/`high` segment. Values at or below\n * `low` are `low`; at or above `high` are `high`; otherwise `medium`. With\n * neither threshold present, everything is `medium`.\n */\n #stateOf(value: number): MeterState {\n if (this.#hasThreshold(\"low\") && value <= this.lowValue) return \"low\";\n if (this.#hasThreshold(\"high\") && value >= this.highValue) return \"high\";\n return \"medium\";\n }\n\n /**\n * Reflects value/range onto ARIA, the segment onto `data-state`, and the ratio.\n * The reading is derived once and returned, so the `change` detail reports the\n * same numbers the DOM just received.\n *\n * @stimeoRenderRoot\n */\n #render(): MeterReading {\n const value = this.#clamp(this.valueValue);\n const reading: MeterReading = {\n value,\n ratio: rangeFraction(value, this.minValue, this.maxValue),\n state: this.#stateOf(value),\n };\n this.element.setAttribute(\"aria-valuemin\", String(this.minValue));\n this.element.setAttribute(\"aria-valuemax\", String(this.maxValue));\n this.element.setAttribute(\"aria-valuenow\", String(reading.value));\n this.element.style.setProperty(\"--stimeo--meter-ratio\", String(reading.ratio));\n this.element.setAttribute(\"data-state\", reading.state);\n this.#applyValueText(reading);\n return reading;\n }\n\n /**\n * Sets `aria-valuetext` from the consumer-provided template, substituting\n * `{value}`, `{percent}`, and `{state}`. Kept i18n-neutral in the library.\n * With no template the attribute belongs to the consumer, so only a text this\n * controller wrote is taken back ({@link OWNED_VALUE_TEXT}).\n */\n #applyValueText({ value, ratio, state }: MeterReading): void {\n if (this.valueTextValue.length === 0) {\n if (this.element.hasAttribute(this.#ownedValueText)) {\n this.element.removeAttribute(\"aria-valuetext\");\n this.element.removeAttribute(this.#ownedValueText);\n }\n return;\n }\n const percent = Math.round(ratio * 100);\n const text = this.valueTextValue\n .replaceAll(\"{value}\", String(value))\n .replaceAll(\"{percent}\", String(percent))\n .replaceAll(\"{state}\", state);\n this.element.setAttribute(\"aria-valuetext\", text);\n this.element.setAttribute(this.#ownedValueText, \"\");\n }\n}\n"]}
|
|
@@ -20,6 +20,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
20
20
|
* data-stimeo--multi-select-target="option"
|
|
21
21
|
* data-action="click->stimeo--multi-select#toggleOption">Apple</li>
|
|
22
22
|
* </ul>
|
|
23
|
+
* <!-- Optional: the message shown while the open list has no match. -->
|
|
24
|
+
* <p data-stimeo--multi-select-target="empty" hidden>No fruits match</p>
|
|
23
25
|
* <!-- Optional: submit the selection as name="fruits[]" hidden inputs. -->
|
|
24
26
|
* <div data-stimeo--multi-select-target="fields"></div>
|
|
25
27
|
* <template data-stimeo--multi-select-target="tagTemplate">
|
|
@@ -35,7 +37,7 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
35
37
|
* multi-select form. Focus stays on the input; the active option is tracked with
|
|
36
38
|
* `aria-activedescendant` and selection with `aria-selected`. Selected options are
|
|
37
39
|
* mirrored as removable chips. For single selection use {@link ListboxController}
|
|
38
|
-
* or
|
|
40
|
+
* or {@link ComboboxController}; for free-text tags use {@link TagsInputController}.
|
|
39
41
|
*
|
|
40
42
|
* Behavior provided:
|
|
41
43
|
* - Typing filters options by substring and opens the list; `ArrowDown`/`ArrowUp`
|
|
@@ -52,6 +54,11 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
52
54
|
* `Delete`/`Backspace` remove the focused chip, and `Backspace` on an empty
|
|
53
55
|
* input removes the last and keeps input focus; removal from a chip re-homes
|
|
54
56
|
* focus to a neighbor or the input.
|
|
57
|
+
* - An open list with nothing left to offer is reported on the root as
|
|
58
|
+
* `data-stimeo--multi-select-empty`, and any declared `empty` region is shown
|
|
59
|
+
* for exactly that state and hidden for its opposite, whatever the markup
|
|
60
|
+
* authored: this controller owns the region's `hidden` and writes it at every
|
|
61
|
+
* reflection of the state, including the one a region gets as it connects.
|
|
55
62
|
* - `max` is a permanent selection invariant across initial markup, interaction,
|
|
56
63
|
* runtime options, and runtime cap changes (`0` = unlimited).
|
|
57
64
|
* - With a `fields` target the selected values are mirrored into named hidden
|
|
@@ -59,7 +66,11 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
59
66
|
* form and no consumer JS — parity with {@link TagsInputController}. An optional
|
|
60
67
|
* `form` value sets the hidden inputs' `form` attribute, associating them with a
|
|
61
68
|
* `<form>` by id even when the picker lives outside it. Without a `fields`
|
|
62
|
-
* target the hidden inputs are simply not written.
|
|
69
|
+
* target the hidden inputs are simply not written. A selection the user
|
|
70
|
+
* changed emits a native bubbling `change` from the container, the way a form
|
|
71
|
+
* control does, so `stimeo--auto-submit` and form-level validation hear it;
|
|
72
|
+
* the rebuilds that follow connect, a replacement container, option churn, or
|
|
73
|
+
* a `name` / `form` change stay silent.
|
|
63
74
|
* `change` and `reconcile` dispatch `{ values: string[] }`.
|
|
64
75
|
* `filter` dispatches `{ query: string }`.
|
|
65
76
|
*/
|
|
@@ -93,6 +104,7 @@ declare class MultiSelectController extends Controller<HTMLElement> {
|
|
|
93
104
|
readonly inputTarget: HTMLInputElement;
|
|
94
105
|
readonly listTarget: HTMLElement;
|
|
95
106
|
readonly optionTargets: HTMLElement[];
|
|
107
|
+
readonly emptyTargets: HTMLElement[];
|
|
96
108
|
readonly tagsTarget: HTMLElement;
|
|
97
109
|
readonly tagTargets: HTMLElement[];
|
|
98
110
|
readonly tagTemplateTarget: HTMLTemplateElement;
|
|
@@ -118,6 +130,8 @@ declare class MultiSelectController extends Controller<HTMLElement> {
|
|
|
118
130
|
optionTargetConnected(): void;
|
|
119
131
|
/** Cleans a removed target and reconciles active state against the surviving DOM. */
|
|
120
132
|
optionTargetDisconnected(option: HTMLElement): void;
|
|
133
|
+
/** Settles a region inserted after connect on the side of the current state. */
|
|
134
|
+
emptyTargetConnected(): void;
|
|
121
135
|
/** Rebinds delegated chip interaction when Turbo replaces the tags container. */
|
|
122
136
|
tagsTargetConnected(tags: HTMLElement): void;
|
|
123
137
|
/** Releases only the row that actually disconnected, never a newer replacement. */
|
|
@@ -147,7 +161,8 @@ declare class MultiSelectController extends Controller<HTMLElement> {
|
|
|
147
161
|
*
|
|
148
162
|
* Needs the input, which owns `aria-expanded` and `aria-activedescendant`: a
|
|
149
163
|
* list shown without one is a popup no assistive technology is told about. So
|
|
150
|
-
* opening is skipped entirely, where {@link close
|
|
164
|
+
* opening is skipped entirely, where {@link MultiSelectController.close | close}
|
|
165
|
+
* still closes.
|
|
151
166
|
*/
|
|
152
167
|
open(): void;
|
|
153
168
|
/**
|