stimeo-ui 0.5.0 → 0.7.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/dist/controllers/alert_dialog_controller.d.ts +2 -0
- package/dist/controllers/alert_dialog_controller.js +75 -4
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +2 -0
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +237 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +2 -0
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +2 -0
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -0
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +85 -9
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +52 -20
- package/dist/controllers/character_counter_controller.js +338 -63
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +34 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +10 -1
- package/dist/controllers/color_picker_controller.js +35 -9
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +2 -0
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +2 -0
- package/dist/controllers/command_palette_controller.js +75 -4
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +42 -14
- package/dist/controllers/conditional_fields_controller.js +345 -51
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +2 -0
- package/dist/controllers/confirm_controller.js +75 -4
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +2 -0
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +4 -0
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +2 -0
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +3 -0
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +157 -30
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.js +75 -4
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +56 -24
- package/dist/controllers/direct_upload_controller.js +201 -45
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +14 -6
- package/dist/controllers/dirty_form_controller.js +192 -29
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +2 -0
- package/dist/controllers/dismissible_controller.js +83 -18
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +75 -4
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +2 -0
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +9 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +2 -0
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +4 -0
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +4 -3
- package/dist/controllers/focus_controller.js +75 -4
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.d.ts +50 -10
- package/dist/controllers/form_field_controller.js +280 -62
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +10 -8
- package/dist/controllers/form_validation_controller.js +208 -83
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +2 -0
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +2 -0
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +5 -3
- package/dist/controllers/idle_controller.js +27 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +2 -0
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +2 -0
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +2 -0
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +2 -0
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +2 -0
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +48 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +2 -0
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +2 -0
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -6
- package/dist/controllers/number_input_controller.js +317 -51
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +3 -0
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +2 -0
- package/dist/controllers/overflow_menu_controller.js +6 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +2 -0
- package/dist/controllers/pagination_controller.js +35 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +2 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +5 -3
- package/dist/controllers/password_strength_controller.js +20 -2
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +33 -19
- package/dist/controllers/persist_controller.js +449 -120
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +3 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js +77 -4
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +3 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +2 -0
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +3 -0
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +44 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +38 -31
- package/dist/controllers/rating_controller.js +276 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +2 -0
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +2 -0
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +6 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +25 -11
- package/dist/controllers/scroll_area_controller.js +557 -125
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +2 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +54 -11
- package/dist/controllers/separator_controller.js +354 -38
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.js +83 -10
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -0
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +2 -0
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +2 -0
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +2 -0
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +2 -0
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +94 -38
- package/dist/controllers/submit_once_controller.js +399 -121
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +2 -0
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +43 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -0
- package/dist/controllers/theme_controller.js +8 -6
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +44 -13
- package/dist/controllers/time_picker_controller.js +296 -107
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +4 -0
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +41 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +4 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +2 -0
- package/dist/controllers/tree_view_controller.js +7 -4
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +4963 -1581
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +123 -6
- package/dist/inspector/cli.js +212 -18
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +274 -50
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +15 -15
- package/dist/inspector/manifest.json +602 -80
- package/dist/positioning/index.d.ts +4 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/detach_gate.ts","../../src/utils/min_duration_floor.ts","../../src/utils/safe_timeout.ts","../../src/controllers/skeleton_controller.ts"],"names":[],"mappings":"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","file":"skeleton_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 * Shared \"in-page move vs real detach\" discriminator for `disconnect()`.\n *\n * A mid-session `disconnect()` whose element is still in the document is\n * ambiguous. It can be:\n *\n * - an **in-page move** — a consumer (sortable, a teleport) re-inserted the\n * element; Stimulus reconnects the SAME instance in the same mutation batch\n * and the controller's runtime state must survive;\n * - a **detach that keeps the element** — `data-controller` no longer lists the\n * identifier (a Turbo 8 morph), or the element moved outside the observed\n * root (a scoped `Application.start(root)`, a shadow root); no reconnect will\n * ever come and the controller must tear down or it leaks its sessions,\n * document listeners, and moved nodes.\n *\n * Neither available signal decides this alone. A synchronous `data-controller`\n * token check catches the morph case but misses an observed-root exit (the\n * token stays, the reconnect never comes). Deferring teardown one microtask and\n * letting `connect()` cancel it catches the root exit, but leans on Stimulus\n * reconnecting moved elements within the same mutation batch — internal\n * behavior, not a public contract. {@link DetachGate} composes both: the token\n * check is the synchronous fast path (no probe, no reliance on batching), the\n * microtask probe is the fallback for the genuinely ambiguous remainder.\n *\n * Policy stays with the consumer (the `SafeTimeout` / `FocusTrap` lineage —\n * this util owns the discrimination only):\n *\n * - **Probe** ({@link disconnected}) when the teardown must eventually happen —\n * live drag sessions, document listeners, teleported *content*. Call\n * {@link cancel} from `connect()` (a reconnect means in-page move) and from\n * the head of any direct teardown path (see below).\n * - **Keep** (bare {@link DetachGate.isDetached}) when undoing the ambiguous\n * case would fight the controller's own effect: a controller that moves its\n * OWN element (portal's no-`content` form) exits a scoped observed root as\n * its normal job — a probe-driven teardown would restore the element into\n * the root, reconnect, re-teleport, and disconnect again, forever. Such\n * controllers keep their state on an ambiguous disconnect and tear down only\n * on {@link DetachGate.isDetached}.\n *\n * Event convention for probe consumers: teardown into a dead (detached) tree is\n * silent — consumers restore from their own `connect()`; a detach that keeps the\n * tree alive must end an in-flight session with a `cancel` event, or composing\n * consumers strand their session bookkeeping.\n */\n\n/** The slice of a Stimulus controller the gate inspects. */\nexport interface DetachGateHost {\n readonly element: Element;\n readonly identifier: string;\n}\n\n/**\n * Per-controller-instance gate deciding whether a `disconnect()` is a real\n * detach. Hold one per controller (`#gate = new DetachGate()`) — Stimulus\n * reuses the instance across reconnects, so the pending probe state carries\n * over exactly as the protocol needs.\n *\n * @example\n * ```ts\n * #gate = new DetachGate();\n *\n * connect(): void {\n * this.#gate.cancel(); // a reconnect: the element moved in-page\n * // …\n * }\n *\n * disconnect(): void {\n * this.#gate.disconnected(this, () => this.#teardown());\n * }\n *\n * #teardown(): void {\n * this.#gate.cancel(); // disarm a still-queued probe (double-run guard)\n * // …\n * }\n * ```\n */\nexport class DetachGate {\n /** Set while a probe is queued, waiting for a reconnect to cancel it. */\n #pending = false;\n\n /**\n * True while a probe is queued — the last disconnect was ambiguous and no\n * reconnect has cancelled it yet. Read it from `connect()` to tell the\n * reconnect half of an in-page move from a first connect: a controller whose\n * initialisation restarts a measurement (a min-duration floor, an elapsed\n * counter) must skip it for the move, where nothing actually restarted.\n */\n get pending(): boolean {\n return this.#pending;\n }\n\n /**\n * True when the disconnect is definitely a real detach — the element left\n * the document, or `data-controller` no longer lists the identifier. False\n * means ambiguous (in-page move or observed-root exit), NOT \"alive\".\n */\n static isDetached(host: DetachGateHost): boolean {\n if (!host.element.isConnected) return true;\n const tokens = (host.element.getAttribute(\"data-controller\") ?? \"\").split(/\\s+/);\n return !tokens.includes(host.identifier);\n }\n\n /**\n * Call from `disconnect()`: runs `teardown` synchronously on a definite\n * detach (fast path), otherwise defers it one microtask — a reconnect\n * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.\n * One microtask is the whole probe window: Stimulus reconnects a moved\n * element within the same mutation batch, before the checkpoint drains.\n */\n disconnected(host: DetachGateHost, teardown: () => void): void {\n if (DetachGate.isDetached(host)) {\n // Also disarms a probe a previous ambiguous disconnect left queued\n // (defer, element removed, disconnect again) — exactly one teardown.\n this.#pending = false;\n teardown();\n return;\n }\n this.#pending = true;\n queueMicrotask(() => {\n if (!this.#pending) return;\n this.#pending = false;\n teardown();\n });\n }\n\n /**\n * Disarms a pending probe. Call from `connect()` (the reconnect that proves\n * an in-page move) and from the head of any teardown path not routed through\n * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can\n * never run the teardown a second time.\n */\n cancel(): void {\n this.#pending = false;\n }\n}\n","import type { SafeTimeout } from \"./safe_timeout\";\n\n/**\n * Holds a transient state visible for a minimum time before letting it end.\n *\n * A loading indicator that appears and disappears within a few frames reads as a\n * flicker rather than as feedback, so several controllers keep one up for a floor\n * of `minDuration` milliseconds measured from the moment it became visible. The\n * shape is always the same: note the moment, and when the end signal arrives\n * either run it now, because the floor has already passed, or hold it on a timer\n * for whatever is left.\n *\n * The floor **borrows** a {@link SafeTimeout} instead of owning one, so a\n * controller that also schedules other work (a show delay) keeps every timer in a\n * single registry and one `clearAll()` in its teardown still drops them all.\n * Cancelling after such a bulk clear is safe: the registry no-ops on an id it no\n * longer holds.\n *\n * Scope is the floor itself. **When an end signal is allowed to arrive at all\n * stays with the controller**, because the answers differ — one keeps the first\n * queued finish and ignores repeats, another lets the newest replace it — and\n * flattening that into a shared default would silently change what the consumers\n * already promise.\n *\n * @example\n * ```ts\n * readonly #timers = new SafeTimeout();\n * readonly #floor = new MinDurationFloor(this.#timers);\n *\n * #show() { this.#floor.begin(); … }\n * stop() { this.#floor.schedule(this.minDurationValue, () => this.#hide()); }\n * ```\n */\nexport class MinDurationFloor {\n readonly #timers: SafeTimeout;\n\n /** Pending finish timer id, or `null` when nothing is held back. */\n #timerId: number | null = null;\n\n /** Epoch ms the floor is measured from. */\n #since = 0;\n\n /** @param timers - the controller's registry; the floor schedules into it. */\n constructor(timers: SafeTimeout) {\n this.#timers = timers;\n }\n\n /** Starts the floor: call when the state being held becomes visible. */\n begin(): void {\n this.#since = Date.now();\n }\n\n /** True while a finish is held back waiting for the floor to elapse. */\n get pending(): boolean {\n return this.#timerId !== null;\n }\n\n /**\n * Runs `finish` once the floor has elapsed, immediately when it already has.\n *\n * A held-back finish is **replaced**, never stacked: only the most recently\n * queued id is cancellable, so a second timer would outlive every cancel and\n * end a state that has since restarted. Controllers that want the first signal\n * to win guard on {@link pending} before calling.\n */\n schedule(minDuration: number, finish: () => void): void {\n this.cancel();\n const remaining = minDuration - (Date.now() - this.#since);\n if (remaining > 0) {\n this.#timerId = this.#timers.set(() => {\n this.#timerId = null;\n finish();\n }, remaining);\n } else {\n finish();\n }\n }\n\n /** Drops a held-back finish. Safe when none is queued, or after a bulk clear. */\n cancel(): void {\n if (this.#timerId !== null) {\n this.#timers.clear(this.#timerId);\n this.#timerId = null;\n }\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { DetachGate } from \"../utils/detach_gate\";\nimport { MinDurationFloor } from \"../utils/min_duration_floor\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/**\n * Headless skeleton/placeholder manager. No dedicated APG pattern; it follows\n * the `aria-busy` + show/hide practice.\n *\n * Markup contract (identifier: `stimeo--skeleton`):\n * <div data-controller=\"stimeo--skeleton\" aria-busy=\"true\"\n * data-stimeo--skeleton-min-duration-value=\"300\"\n * data-action=\"content:ready->stimeo--skeleton#ready\">\n * <div aria-hidden=\"true\" data-stimeo--skeleton-target=\"placeholder\"></div>\n * <div hidden data-stimeo--skeleton-target=\"content\"></div>\n * </div>\n *\n * Starts in the loading state (placeholder shown, real content hidden, region\n * `aria-busy=\"true\"`); `ready` swaps to the content and clears busy. The\n * placeholder is `aria-hidden` (decorative) so assistive tech never reads the\n * skeleton. `minDuration` keeps the placeholder up long enough to avoid a flash\n * when content arrives almost immediately.\n *\n * @remarks\n * Behavior only — skeleton shapes/animation are the consumer's. The\n * min-duration wait is held by {@link MinDurationFloor} on a {@link SafeTimeout},\n * kept across an in-page move and dropped on a real detach via {@link DetachGate}.\n */\nexport class SkeletonController extends Controller<HTMLElement> {\n static override targets = [\"placeholder\", \"content\"];\n static override values = {\n announceReadyText: { type: String, default: \"\" },\n minDuration: { type: Number, default: 0 },\n };\n static actions = [\"ready\", \"reset\"] as const;\n static events = [\"ready\"] as const;\n\n declare readonly placeholderTarget: HTMLElement;\n declare readonly contentTarget: HTMLElement;\n declare readonly hasPlaceholderTarget: boolean;\n declare readonly hasContentTarget: boolean;\n\n declare minDurationValue: number;\n declare announceReadyTextValue: string;\n\n readonly #timers = new SafeTimeout();\n readonly #floor = new MinDurationFloor(this.#timers);\n readonly #gate = new DetachGate();\n\n override connect(): void {\n // A probe still queued means this is the reconnect half of an in-page move.\n // The placeholder never left the screen, so the loading hooks already hold\n // the values `#enterLoading()` would write and the floor is still measuring\n // from the moment the skeleton actually appeared — restarting it here would\n // hold the reveal back for longer than `minDuration` asks.\n const moved = this.#gate.pending;\n this.#gate.cancel();\n if (!moved && this.#state !== \"ready\") {\n this.#enterLoading();\n }\n }\n\n override disconnect(): void {\n this.#gate.disconnected(this, () => this.#teardown());\n }\n\n /** Swaps to the real content. Honors `minDuration` to prevent a flash. */\n ready(): void {\n // The first signal wins: a repeat while the floor still holds the reveal back\n // must not restart the wait, or a stream of ready events keeps postponing it.\n if (this.#state === \"ready\" || this.#floor.pending) return;\n this.#floor.schedule(this.minDurationValue, () => this.#reveal());\n }\n\n /** Returns to the loading state (e.g. a Turbo Stream re-fetch). */\n reset(): void {\n this.#floor.cancel();\n this.#enterLoading();\n }\n\n /** Shows the placeholder, hides content, and marks the region busy. */\n #enterLoading(): void {\n this.#floor.begin();\n if (this.hasPlaceholderTarget) this.placeholderTarget.hidden = false;\n if (this.hasContentTarget) this.contentTarget.hidden = true;\n this.element.setAttribute(\"aria-busy\", \"true\");\n this.element.setAttribute(\"data-state\", \"loading\");\n }\n\n /** Hides the placeholder, shows content, and clears the busy state. */\n #reveal(): void {\n if (this.hasPlaceholderTarget) this.placeholderTarget.hidden = true;\n if (this.hasContentTarget) this.contentTarget.hidden = false;\n this.element.setAttribute(\"aria-busy\", \"false\");\n this.element.setAttribute(\"data-state\", \"ready\");\n this.dispatch(\"ready\", { detail: {} });\n // loading → ready is the transition; the skeleton itself carries no words.\n announce(fillTemplate(this.announceReadyTextValue, {}));\n }\n\n /**\n * Drops the held reveal on a real detach. The markup keeps whatever it last\n * held: an element on its way out of the document has no reader left, and one\n * whose `data-controller` dropped the identifier no longer resolves its own\n * targets, so the rollback could only ever be partial.\n */\n #teardown(): void {\n this.#gate.cancel();\n this.#timers.clearAll();\n this.#floor.cancel();\n }\n\n /** Current lifecycle phase as reflected on `data-state`. */\n get #state(): string {\n return this.element.getAttribute(\"data-state\") ?? \"loading\";\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/detach_gate.ts","../../src/utils/min_duration_floor.ts","../../src/utils/safe_timeout.ts","../../src/controllers/skeleton_controller.ts"],"names":[],"mappings":"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","file":"skeleton_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 * Shared \"in-page move vs real detach\" discriminator for `disconnect()`.\n *\n * A mid-session `disconnect()` whose element is still in the document is\n * ambiguous. It can be:\n *\n * - an **in-page move** — a consumer (sortable, a teleport) re-inserted the\n * element; Stimulus reconnects the SAME instance in the same mutation batch\n * and the controller's runtime state must survive;\n * - a **detach that keeps the element** — `data-controller` no longer lists the\n * identifier (a Turbo 8 morph), or the element moved outside the observed\n * root (a scoped `Application.start(root)`, a shadow root); no reconnect will\n * ever come and the controller must tear down or it leaks its sessions,\n * document listeners, and moved nodes.\n *\n * Neither available signal decides this alone. A synchronous `data-controller`\n * token check catches the morph case but misses an observed-root exit (the\n * token stays, the reconnect never comes). Deferring teardown one microtask and\n * letting `connect()` cancel it catches the root exit, but leans on Stimulus\n * reconnecting moved elements within the same mutation batch — internal\n * behavior, not a public contract. {@link DetachGate} composes both: the token\n * check is the synchronous fast path (no probe, no reliance on batching), the\n * microtask probe is the fallback for the genuinely ambiguous remainder.\n *\n * Policy stays with the consumer (the `SafeTimeout` / `FocusTrap` lineage —\n * this util owns the discrimination only):\n *\n * - **Probe** ({@link disconnected}) when the teardown must eventually happen —\n * live drag sessions, document listeners, teleported *content*. Call\n * {@link cancel} from `connect()` (a reconnect means in-page move) and from\n * the head of any direct teardown path (see below).\n * - **Keep** (bare {@link DetachGate.isDetached}) when undoing the ambiguous\n * case would fight the controller's own effect: a controller that moves its\n * OWN element (portal's no-`content` form) exits a scoped observed root as\n * its normal job — a probe-driven teardown would restore the element into\n * the root, reconnect, re-teleport, and disconnect again, forever. Such\n * controllers keep their state on an ambiguous disconnect and tear down only\n * on {@link DetachGate.isDetached}.\n *\n * Event convention for probe consumers: teardown into a dead (detached) tree is\n * silent — consumers restore from their own `connect()`; a detach that keeps the\n * tree alive must end an in-flight session with a `cancel` event, or composing\n * consumers strand their session bookkeeping.\n */\n\n/** The slice of a Stimulus controller the gate inspects. */\nexport interface DetachGateHost {\n readonly element: Element;\n readonly identifier: string;\n}\n\n/**\n * Per-controller-instance gate deciding whether a `disconnect()` is a real\n * detach. Hold one per controller (`#gate = new DetachGate()`) — Stimulus\n * reuses the instance across reconnects, so the pending probe state carries\n * over exactly as the protocol needs.\n *\n * @example\n * ```ts\n * #gate = new DetachGate();\n *\n * connect(): void {\n * this.#gate.cancel(); // a reconnect: the element moved in-page\n * // …\n * }\n *\n * disconnect(): void {\n * this.#gate.disconnected(this, () => this.#teardown());\n * }\n *\n * #teardown(): void {\n * this.#gate.cancel(); // disarm a still-queued probe (double-run guard)\n * // …\n * }\n * ```\n */\nexport class DetachGate {\n /** Set while a probe is queued, waiting for a reconnect to cancel it. */\n #pending = false;\n\n /**\n * True while a probe is queued — the last disconnect was ambiguous and no\n * reconnect has cancelled it yet. Read it from `connect()` to tell the\n * reconnect half of an in-page move from a first connect: a controller whose\n * initialisation restarts a measurement (a min-duration floor, an elapsed\n * counter) must skip it for the move, where nothing actually restarted.\n */\n get pending(): boolean {\n return this.#pending;\n }\n\n /**\n * True when the disconnect is definitely a real detach — the element left\n * the document, or `data-controller` no longer lists the identifier. False\n * means ambiguous (in-page move or observed-root exit), NOT \"alive\".\n */\n static isDetached(host: DetachGateHost): boolean {\n if (!host.element.isConnected) return true;\n const tokens = (host.element.getAttribute(\"data-controller\") ?? \"\").split(/\\s+/);\n return !tokens.includes(host.identifier);\n }\n\n /**\n * Call from `disconnect()`: runs `teardown` synchronously on a definite\n * detach (fast path), otherwise defers it one microtask — a reconnect\n * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.\n * One microtask is the whole probe window: Stimulus reconnects a moved\n * element within the same mutation batch, before the checkpoint drains.\n */\n disconnected(host: DetachGateHost, teardown: () => void): void {\n if (DetachGate.isDetached(host)) {\n // Also disarms a probe a previous ambiguous disconnect left queued\n // (defer, element removed, disconnect again) — exactly one teardown.\n this.#pending = false;\n teardown();\n return;\n }\n this.#pending = true;\n queueMicrotask(() => {\n if (!this.#pending) return;\n this.#pending = false;\n teardown();\n });\n }\n\n /**\n * Disarms a pending probe. Call from `connect()` (the reconnect that proves\n * an in-page move) and from the head of any teardown path not routed through\n * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can\n * never run the teardown a second time.\n */\n cancel(): void {\n this.#pending = false;\n }\n}\n","import type { SafeTimeout } from \"./safe_timeout\";\n\n/**\n * Holds a transient state visible for a minimum time before letting it end.\n *\n * A loading indicator that appears and disappears within a few frames reads as a\n * flicker rather than as feedback, so several controllers keep one up for a floor\n * of `minDuration` milliseconds measured from the moment it became visible. The\n * shape is always the same: note the moment, and when the end signal arrives\n * either run it now, because the floor has already passed, or hold it on a timer\n * for whatever is left.\n *\n * The floor **borrows** a {@link SafeTimeout} instead of owning one, so a\n * controller that also schedules other work (a show delay) keeps every timer in a\n * single registry and one `clearAll()` in its teardown still drops them all.\n * Cancelling after such a bulk clear is safe: the registry no-ops on an id it no\n * longer holds.\n *\n * Scope is the floor itself. **When an end signal is allowed to arrive at all\n * stays with the controller**, because the answers differ — one keeps the first\n * queued finish and ignores repeats, another lets the newest replace it — and\n * flattening that into a shared default would silently change what the consumers\n * already promise.\n *\n * @example\n * ```ts\n * readonly #timers = new SafeTimeout();\n * readonly #floor = new MinDurationFloor(this.#timers);\n *\n * #show() { this.#floor.begin(); … }\n * stop() { this.#floor.schedule(this.minDurationValue, () => this.#hide()); }\n * ```\n */\nexport class MinDurationFloor {\n readonly #timers: SafeTimeout;\n\n /** Pending finish timer id, or `null` when nothing is held back. */\n #timerId: number | null = null;\n\n /** Epoch ms the floor is measured from. */\n #since = 0;\n\n /** @param timers - the controller's registry; the floor schedules into it. */\n constructor(timers: SafeTimeout) {\n this.#timers = timers;\n }\n\n /** Starts the floor: call when the state being held becomes visible. */\n begin(): void {\n this.#since = Date.now();\n }\n\n /** True while a finish is held back waiting for the floor to elapse. */\n get pending(): boolean {\n return this.#timerId !== null;\n }\n\n /**\n * Runs `finish` once the floor has elapsed, immediately when it already has.\n *\n * A held-back finish is **replaced**, never stacked: only the most recently\n * queued id is cancellable, so a second timer would outlive every cancel and\n * end a state that has since restarted. Controllers that want the first signal\n * to win guard on {@link pending} before calling.\n */\n schedule(minDuration: number, finish: () => void): void {\n this.cancel();\n const remaining = minDuration - (Date.now() - this.#since);\n if (remaining > 0) {\n this.#timerId = this.#timers.set(() => {\n this.#timerId = null;\n finish();\n }, remaining);\n } else {\n finish();\n }\n }\n\n /** Drops a held-back finish. Safe when none is queued, or after a bulk clear. */\n cancel(): void {\n if (this.#timerId !== null) {\n this.#timers.clear(this.#timerId);\n this.#timerId = null;\n }\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { DetachGate } from \"../utils/detach_gate\";\nimport { MinDurationFloor } from \"../utils/min_duration_floor\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/**\n * Headless skeleton/placeholder manager. No dedicated APG pattern; it follows\n * the `aria-busy` + show/hide practice.\n *\n * Markup contract (identifier: `stimeo--skeleton`):\n * <div data-controller=\"stimeo--skeleton\" aria-busy=\"true\"\n * data-stimeo--skeleton-min-duration-value=\"300\"\n * data-action=\"content:ready->stimeo--skeleton#ready\">\n * <div aria-hidden=\"true\" data-stimeo--skeleton-target=\"placeholder\"></div>\n * <div hidden data-stimeo--skeleton-target=\"content\"></div>\n * </div>\n *\n * Starts in the loading state (placeholder shown, real content hidden, region\n * `aria-busy=\"true\"`); `ready` swaps to the content and clears busy. The\n * placeholder is `aria-hidden` (decorative) so assistive tech never reads the\n * skeleton. `minDuration` keeps the placeholder up long enough to avoid a flash\n * when content arrives almost immediately.\n *\n * `ready` dispatches `{}`.\n *\n * @remarks\n * Behavior only — skeleton shapes/animation are the consumer's. The\n * min-duration wait is held by {@link MinDurationFloor} on a {@link SafeTimeout},\n * kept across an in-page move and dropped on a real detach via {@link DetachGate}.\n */\nexport class SkeletonController extends Controller<HTMLElement> {\n static override targets = [\"placeholder\", \"content\"];\n static override values = {\n announceReadyText: { type: String, default: \"\" },\n minDuration: { type: Number, default: 0 },\n };\n static actions = [\"ready\", \"reset\"] as const;\n static events = [\"ready\"] as const;\n\n declare readonly placeholderTarget: HTMLElement;\n declare readonly contentTarget: HTMLElement;\n declare readonly hasPlaceholderTarget: boolean;\n declare readonly hasContentTarget: boolean;\n\n declare minDurationValue: number;\n declare announceReadyTextValue: string;\n\n readonly #timers = new SafeTimeout();\n readonly #floor = new MinDurationFloor(this.#timers);\n readonly #gate = new DetachGate();\n\n override connect(): void {\n // A probe still queued means this is the reconnect half of an in-page move.\n // The placeholder never left the screen, so the loading hooks already hold\n // the values `#enterLoading()` would write and the floor is still measuring\n // from the moment the skeleton actually appeared — restarting it here would\n // hold the reveal back for longer than `minDuration` asks.\n const moved = this.#gate.pending;\n this.#gate.cancel();\n if (!moved && this.#state !== \"ready\") {\n this.#enterLoading();\n }\n }\n\n override disconnect(): void {\n this.#gate.disconnected(this, () => this.#teardown());\n }\n\n /** Swaps to the real content. Honors `minDuration` to prevent a flash. */\n ready(): void {\n // The first signal wins: a repeat while the floor still holds the reveal back\n // must not restart the wait, or a stream of ready events keeps postponing it.\n if (this.#state === \"ready\" || this.#floor.pending) return;\n this.#floor.schedule(this.minDurationValue, () => this.#reveal());\n }\n\n /** Returns to the loading state (e.g. a Turbo Stream re-fetch). */\n reset(): void {\n this.#floor.cancel();\n this.#enterLoading();\n }\n\n /** Shows the placeholder, hides content, and marks the region busy. */\n #enterLoading(): void {\n this.#floor.begin();\n if (this.hasPlaceholderTarget) this.placeholderTarget.hidden = false;\n if (this.hasContentTarget) this.contentTarget.hidden = true;\n this.element.setAttribute(\"aria-busy\", \"true\");\n this.element.setAttribute(\"data-state\", \"loading\");\n }\n\n /** Hides the placeholder, shows content, and clears the busy state. */\n #reveal(): void {\n if (this.hasPlaceholderTarget) this.placeholderTarget.hidden = true;\n if (this.hasContentTarget) this.contentTarget.hidden = false;\n this.element.setAttribute(\"aria-busy\", \"false\");\n this.element.setAttribute(\"data-state\", \"ready\");\n this.dispatch(\"ready\", { detail: {} });\n // loading → ready is the transition; the skeleton itself carries no words.\n announce(fillTemplate(this.announceReadyTextValue, {}));\n }\n\n /**\n * Drops the held reveal on a real detach. The markup keeps whatever it last\n * held: an element on its way out of the document has no reader left, and one\n * whose `data-controller` dropped the identifier no longer resolves its own\n * targets, so the rollback could only ever be partial.\n */\n #teardown(): void {\n this.#gate.cancel();\n this.#timers.clearAll();\n this.#floor.cancel();\n }\n\n /** Current lifecycle phase as reflected on `data-state`. */\n get #state(): string {\n return this.element.getAttribute(\"data-state\") ?? \"loading\";\n }\n}\n"]}
|
|
@@ -23,6 +23,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
23
23
|
* property (a number in `[0, 1]`) set on the controller element — the library
|
|
24
24
|
* positions nothing itself.
|
|
25
25
|
*
|
|
26
|
+
* `change` dispatches `{ value: number }`.
|
|
27
|
+
*
|
|
26
28
|
* @remarks
|
|
27
29
|
* Behavior only. The consumer owns all layout (e.g. positioning the thumb from
|
|
28
30
|
* the fraction). Only the horizontal orientation is handled.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/owned_pointer_session.ts","../../src/utils/range.ts","../../src/utils/stepped_value.ts","../../src/controllers/slider_controller.ts"],"names":["position"],"mappings":"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","file":"slider_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\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","/** Callbacks driven by one owned pointer until it ends or is cancelled. */\nexport interface OwnedPointerSessionHandlers {\n /** Receives movement from the initiating pointer only. */\n readonly move: (event: PointerEvent) => void;\n /** Runs once after every listener and pointer capture have been released. */\n readonly end?: () => void;\n}\n\n/**\n * Owns one pointer across document-level movement and termination events.\n *\n * A controller may have at most one instance at a time. The session filters\n * every event by the initiating `pointerId`, captures the pointer when the DOM\n * implementation supports it, and binds all fallback document listeners to one\n * abort signal. Calling {@link OwnedPointerSession.end} is idempotent.\n */\nexport class OwnedPointerSession {\n readonly pointerId: number;\n readonly #owner: HTMLElement;\n readonly #handlers: OwnedPointerSessionHandlers;\n readonly #abort = new AbortController();\n #active = true;\n\n constructor(start: PointerEvent, owner: HTMLElement, handlers: OwnedPointerSessionHandlers) {\n this.pointerId = start.pointerId;\n this.#owner = owner;\n this.#handlers = handlers;\n\n const { signal } = this.#abort;\n owner.ownerDocument.addEventListener(\"pointermove\", this.#onMove, { signal });\n owner.ownerDocument.addEventListener(\"pointerup\", this.#onEndEvent, { signal });\n owner.ownerDocument.addEventListener(\"pointercancel\", this.#onEndEvent, { signal });\n owner.addEventListener(\"lostpointercapture\", this.#onLostCapture, { signal });\n\n // Pointer capture is progressive here: happy-dom and older DOM shims may not\n // implement it, while the document listeners still preserve ownership.\n try {\n owner.setPointerCapture?.(this.pointerId);\n } catch {\n // A synthetic or already-ended pointer cannot be captured; filtering and\n // AbortController teardown remain sufficient for that environment.\n }\n }\n\n /** Whether this session still owns its pointer and listeners. */\n get active(): boolean {\n return this.#active;\n }\n\n /** Whether `event` belongs to the initiating pointer of the live session. */\n owns(event: PointerEvent): boolean {\n return this.#active && event.pointerId === this.pointerId;\n }\n\n /** Releases capture/listeners and invokes the end callback exactly once. */\n end(): void {\n if (!this.#active) return;\n this.#active = false;\n this.#abort.abort();\n try {\n this.#owner.releasePointerCapture?.(this.pointerId);\n } catch {\n // Losing capture because the owner detached is already an ended session.\n }\n this.#handlers.end?.();\n }\n\n readonly #onMove = (event: PointerEvent): void => {\n if (this.owns(event)) this.#handlers.move(event);\n };\n\n readonly #onEndEvent = (event: PointerEvent): void => {\n if (this.owns(event)) this.end();\n };\n\n readonly #onLostCapture = (event: Event): void => {\n const pointerId = (event as PointerEvent).pointerId;\n if (typeof pointerId === \"number\" && pointerId !== this.pointerId) return;\n this.end();\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","/** Configuration for a value constrained to a stepped numeric range. */\nexport interface SteppedRange {\n /** Inclusive lower endpoint. May be infinite for an unbounded control. */\n readonly min: number;\n /** Inclusive upper endpoint. May be infinite for an unbounded control. */\n readonly max: number;\n /** Requested step. Non-finite and non-positive values fall back to `1`. */\n readonly step: number;\n /** Step-grid origin. Defaults to a finite `min`, otherwise `0`. */\n readonly base?: number;\n}\n\n/** The positive finite step used at runtime and by keyboard stepping. */\nexport function effectiveStep(step: number): number {\n return Number.isFinite(step) && step > 0 ? step : 1;\n}\n\n/**\n * Clamps a raw value and selects its nearest allowed value.\n *\n * The allowed set is the step grid plus each finite range endpoint. Keeping the\n * endpoints in that set makes an authored `aria-valuemin`/`aria-valuemax`\n * truthful even when the upper endpoint is not aligned to the grid. A value\n * exactly between two allowed values resolves upward.\n */\nexport function snapSteppedValue(raw: number, range: SteppedRange): number {\n if (!(range.min <= range.max)) return finiteFallback(range.min, range.max);\n\n const input = Number.isNaN(raw) ? finiteFallback(range.min, range.max) : raw;\n const clamped = Math.min(range.max, Math.max(range.min, input));\n const step = effectiveStep(range.step);\n const base = stepBase(range);\n const candidates: number[] = [];\n\n if (Number.isFinite(range.min)) candidates.push(range.min);\n if (Number.isFinite(range.max)) candidates.push(range.max);\n\n const gridPosition = (clamped - base) / step;\n if (Number.isFinite(gridPosition)) {\n addGridCandidate(candidates, Math.floor(gridPosition), range, base, step);\n addGridCandidate(candidates, Math.ceil(gridPosition), range, base, step);\n }\n\n if (candidates.length === 0) return clamped;\n let nearest = candidates[0] as number;\n let nearestDistance = Math.abs(clamped - nearest);\n for (const candidate of candidates.slice(1)) {\n const distance = Math.abs(clamped - candidate);\n if (\n distance < nearestDistance ||\n (nearlyEqual(distance, nearestDistance) && candidate > nearest)\n ) {\n nearest = candidate;\n nearestDistance = distance;\n }\n }\n return nearest;\n}\n\n/**\n * Moves through the ordered allowed-value set by an integer number of steps.\n *\n * This differs from adding `step * count` and snapping afterward at an\n * off-grid endpoint: with `max=94, step=10`, one decrement from `94` must land\n * on `90`, and one increment from `90` must reach the advertised maximum `94`.\n */\nexport function stepSteppedValue(current: number, count: number, range: SteppedRange): number {\n const value = snapSteppedValue(current, range);\n const distance = Math.abs(Math.trunc(count));\n if (!Number.isFinite(distance) || distance === 0) return value;\n const direction = Math.sign(count);\n const adjacent = adjacentSteppedValue(value, direction, range);\n\n // The first move accounts for a possibly off-grid endpoint. From there a\n // direct grid jump is equivalent to repeated adjacency, and avoids work that\n // grows with Page-step magnitudes or programmatic counts.\n const raw = adjacent + direction * (distance - 1) * effectiveStep(range.step);\n return snapSteppedValue(raw, range);\n}\n\n/** Returns the next member of the ordered endpoint-plus-grid set. */\nfunction adjacentSteppedValue(current: number, direction: number, range: SteppedRange): number {\n const step = effectiveStep(range.step);\n const base = stepBase(range);\n const candidates: number[] = [];\n\n if (direction > 0) {\n const position = (current - base) / step;\n if (Number.isFinite(position)) {\n let gridIndex = Math.floor(position) + 1;\n let candidate = cleanGridValue(base + gridIndex * step, base, step);\n if (candidate < current || nearlyEqual(candidate, current)) {\n gridIndex += 1;\n candidate = cleanGridValue(base + gridIndex * step, base, step);\n }\n if (\n candidate > current &&\n !nearlyEqual(candidate, current) &&\n within(candidate, range.min, range.max)\n ) {\n candidates.push(clamp(candidate, range));\n }\n }\n return clamp(Math.min(...candidates), range);\n }\n\n const position = (current - base) / step;\n if (Number.isFinite(position)) {\n let gridIndex = Math.ceil(position) - 1;\n let candidate = cleanGridValue(base + gridIndex * step, base, step);\n if (candidate > current || nearlyEqual(candidate, current)) {\n gridIndex -= 1;\n candidate = cleanGridValue(base + gridIndex * step, base, step);\n }\n if (\n candidate < current &&\n !nearlyEqual(candidate, current) &&\n within(candidate, range.min, range.max)\n ) {\n candidates.push(clamp(candidate, range));\n }\n }\n return clamp(Math.max(...candidates), range);\n}\n\n/** Adds one grid value when it falls inside the inclusive range. */\nfunction addGridCandidate(\n candidates: number[],\n index: number,\n range: SteppedRange,\n base: number,\n step: number,\n): void {\n const candidate = cleanGridValue(base + index * step, base, step);\n if (within(candidate, range.min, range.max)) candidates.push(clamp(candidate, range));\n}\n\n/** A finite authored base wins; otherwise the finite lower bound or zero does. */\nfunction stepBase(range: SteppedRange): number {\n if (range.base !== undefined && Number.isFinite(range.base)) return range.base;\n return Number.isFinite(range.min) ? range.min : 0;\n}\n\n/** Removes binary noise without discarding significant integer-place digits. */\nfunction cleanGridValue(value: number, base: number, step: number): number {\n const precision = Math.max(decimalPlaces(base), decimalPlaces(step));\n // `toFixed` accepts at most 100 fractional digits. Below that limit it rounds\n // according to the authored grid precision rather than an arbitrary total\n // significant-digit count, which keeps unit steps distinct at large bases.\n return precision <= 100 ? Number(value.toFixed(precision)) : value;\n}\n\n/** Number of fractional decimal places represented by a finite JS number. */\nfunction decimalPlaces(value: number): number {\n const [coefficient = \"\", exponentText] = Math.abs(value).toString().toLowerCase().split(\"e\");\n const fractionLength = coefficient.split(\".\")[1]?.length ?? 0;\n const exponent = exponentText === undefined ? 0 : Number(exponentText);\n return Math.max(0, fractionLength - exponent);\n}\n\n/** Inclusive range check tolerant only of arithmetic noise at an endpoint. */\nfunction within(value: number, min: number, max: number): boolean {\n return (value > min || nearlyEqual(value, min)) && (value < max || nearlyEqual(value, max));\n}\n\n/** Clamps a candidate whose only overshoot may be floating-point noise. */\nfunction clamp(value: number, range: SteppedRange): number {\n return Math.min(range.max, Math.max(range.min, value));\n}\n\n/** Relative comparison used only to collapse arithmetic noise, not real steps. */\nfunction nearlyEqual(left: number, right: number): boolean {\n const scale = Math.max(Math.abs(left), Math.abs(right));\n return (\n Number.isFinite(left) &&\n Number.isFinite(right) &&\n Math.abs(left - right) <= Number.EPSILON * scale\n );\n}\n\n/** Stable fallback for malformed ranges or `NaN` input. */\nfunction finiteFallback(min: number, max: number): number {\n if (Number.isFinite(min)) return min;\n if (Number.isFinite(max)) return max;\n return 0;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { OwnedPointerSession } from \"../utils/owned_pointer_session\";\nimport { rangeFraction } from \"../utils/range\";\nimport { snapSteppedValue, stepSteppedValue } from \"../utils/stepped_value\";\n\n/** Name of the CSS custom property exposing the thumb position (0..1). */\nconst FRACTION_PROPERTY = \"--stimeo--slider-fraction\";\n\n/**\n * Headless, accessible slider (single-thumb range) behavior.\n *\n * Markup contract (identifier: `stimeo--slider`):\n * <div data-controller=\"stimeo--slider\"\n * data-stimeo--slider-min-value=\"0\"\n * data-stimeo--slider-max-value=\"100\"\n * data-stimeo--slider-step-value=\"1\"\n * data-stimeo--slider-value-value=\"40\">\n * <div data-stimeo--slider-target=\"track\"\n * data-action=\"pointerdown->stimeo--slider#onPointerDown\">\n * <div data-stimeo--slider-target=\"thumb\" role=\"slider\" tabindex=\"0\" aria-label=\"Volume\"\n * aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"40\"\n * data-action=\"keydown->stimeo--slider#onKeydown\"></div>\n * </div>\n * </div>\n *\n * Implements the WAI-ARIA APG **Slider** pattern. The current value is exposed\n * to assistive tech via `aria-valuenow`/`aria-valuemin`/`aria-valuemax` on the\n * thumb, and to the consumer's CSS via the `--stimeo--slider-fraction` custom\n * property (a number in `[0, 1]`) set on the controller element — the library\n * positions nothing itself.\n *\n * @remarks\n * Behavior only. The consumer owns all layout (e.g. positioning the thumb from\n * the fraction). Only the horizontal orientation is handled.\n *\n * Behavior provided:\n * - `ArrowRight`/`ArrowUp` increase and `ArrowLeft`/`ArrowDown` decrease by one\n * step; `Home`/`End` jump to the min/max; `PageUp`/`PageDown` move by ten steps.\n * - Pointer press/drag on the track sets the value from the pointer position.\n * - `step` must be finite and positive; invalid runtime input falls back to `1`.\n * Finite endpoints remain allowed even when they do not align to the step grid.\n *\n * The fraction is a value ratio, not a position, so only the consumer knows\n * whether their track mirrors under RTL. Set `logicalTrack` to declare that it\n * does: the pointer mapping and the horizontal arrow pair then follow the\n * writing direction. Left unset, nothing here reads `direction`.\n */\nexport class SliderController extends Controller<HTMLElement> {\n static override targets = [\"track\", \"thumb\"];\n static override values = {\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n step: { type: Number, default: 1 },\n value: { type: Number, default: 0 },\n logicalTrack: { type: Boolean, default: false },\n };\n static actions = [\"onKeydown\", \"onPointerDown\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly trackTarget: HTMLElement;\n declare readonly thumbTarget: HTMLElement;\n declare readonly hasTrackTarget: boolean;\n declare readonly hasThumbTarget: boolean;\n declare minValue: number;\n declare maxValue: number;\n declare stepValue: number;\n declare valueValue: number;\n declare logicalTrackValue: boolean;\n\n /** One initiating pointer owns each live drag and its stable target snapshot. */\n #drag: SliderDrag | null = null;\n\n /** Collapses a morph that swaps several render Values into one silent repaint. */\n readonly #repaint = new MicrotaskCoalescer(() => this.#render());\n\n /** Whether the consumer declared a mirroring track and the direction mirrors it. */\n get #mirrored(): boolean {\n return this.logicalTrackValue && isRtl(this.element);\n }\n\n /** Clamps the initial value and renders the starting position. */\n override connect(): void {\n this.#repaint.activate();\n this.#commit(this.valueValue, false);\n }\n\n /** Cancels any active pointer drag so document listeners never leak. */\n override disconnect(): void {\n this.#repaint.cancel();\n this.#endDrag();\n }\n\n /** Silently repaints a minimum changed by application code or a Turbo morph. */\n minValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Silently repaints a maximum changed by application code or a Turbo morph. */\n maxValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Silently repaints a step changed by application code or a Turbo morph. */\n stepValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Silently repaints a value changed by application code or a Turbo morph. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Hydrates a thumb inserted or replaced at runtime with the current ARIA state. */\n thumbTargetConnected(thumb: HTMLElement): void {\n const value = this.#currentValue();\n this.#renderThumb(thumb, value);\n this.#renderFraction(value);\n if (this.#drag && this.#drag.thumb === null) {\n this.#drag.thumb = thumb;\n thumb.focus();\n }\n this.#repaint.schedule();\n }\n\n /** Drops the stale focus owner while allowing a live track gesture to continue. */\n thumbTargetDisconnected(thumb: HTMLElement): void {\n if (this.#drag?.thumb === thumb) this.#drag.thumb = null;\n }\n\n /** Ends a gesture whose geometry target disappeared or ceased being a target. */\n trackTargetDisconnected(track: HTMLElement): void {\n if (this.#drag?.track === track) this.#endDrag();\n }\n\n /** Handles keyboard stepping per the APG slider model. */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n let next: number | null = null;\n const current = this.#currentValue();\n const range = this.#steppedRange;\n // On a mirrored track the greater value sits at the visual left, so the\n // horizontal pair trades places; the vertical pair passes through.\n switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = stepSteppedValue(current, 1, range);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = stepSteppedValue(current, -1, range);\n break;\n case \"PageUp\":\n next = stepSteppedValue(current, 10, range);\n break;\n case \"PageDown\":\n next = stepSteppedValue(current, -10, range);\n break;\n case \"Home\":\n next = this.minValue;\n break;\n case \"End\":\n next = this.maxValue;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#commit(next, true);\n }\n\n /** Begins a pointer drag: sets the value and tracks subsequent movement. */\n onPointerDown(event: PointerEvent): void {\n if (event.button !== 0 || this.#drag || !this.hasTrackTarget || !this.hasThumbTarget) return;\n const track = this.trackTarget;\n const thumb = this.thumbTarget;\n // Resolve the direction once for the whole gesture: reading it per move\n // would query computed style on every frame, and a drag that flipped\n // mid-gesture would be incoherent anyway.\n const mirrored = this.#mirrored;\n const value = this.#valueFromClientX(event.clientX, mirrored, track);\n if (value === null) return;\n event.preventDefault();\n this.#commit(value, true);\n thumb.focus();\n\n const drag: SliderDrag = { pointer: null, track, thumb };\n drag.pointer = new OwnedPointerSession(event, track, {\n move: (move) => {\n if (!track.isConnected) {\n this.#endDrag();\n return;\n }\n const moved = this.#valueFromClientX(move.clientX, mirrored, track);\n if (moved !== null) this.#commit(moved, true);\n },\n end: () => {\n if (this.#drag === drag) this.#drag = null;\n },\n });\n this.#drag = drag;\n }\n\n /** Maps a pointer X coordinate to a raw value using a stable track snapshot. */\n #valueFromClientX(clientX: number, mirrored: boolean, track: HTMLElement): number | null {\n const rect = track.getBoundingClientRect();\n if (rect.width === 0) return null;\n const offset = (clientX - rect.left) / rect.width;\n const fraction = mirrored ? 1 - offset : offset;\n return this.minValue + fraction * (this.maxValue - this.minValue);\n }\n\n /**\n * Stores a normalized value and renders synchronously for responsive input.\n * Morph callbacks use {@link #render} instead, so they never write Values back\n * or dispatch a user-facing change event.\n */\n #commit(raw: number, notify: boolean): void {\n const previous = this.#currentValue();\n const value = snapSteppedValue(raw, this.#steppedRange);\n if (!Object.is(this.valueValue, value)) this.valueValue = value;\n this.#renderValue(value);\n if (notify && value !== previous) this.dispatch(\"change\", { detail: { value } });\n }\n\n /**\n * Reflects the normalized current Value without mutating or dispatching it.\n *\n * @stimeoRenderRoot\n */\n #render(): void {\n this.#renderValue(this.#currentValue());\n }\n\n /** Reflects one normalized value on the current target and CSS output. */\n #renderValue(value: number): void {\n if (this.hasThumbTarget) {\n this.#renderThumb(this.thumbTarget, value);\n }\n this.#renderFraction(value);\n }\n\n /** Writes only ARIA values that differ, including on a replacement target. */\n #renderThumb(thumb: HTMLElement, value: number): void {\n const attributes = {\n \"aria-valuemin\": String(this.minValue),\n \"aria-valuemax\": String(this.maxValue),\n \"aria-valuenow\": String(value),\n };\n for (const [name, next] of Object.entries(attributes)) {\n if (thumb.getAttribute(name) !== next) thumb.setAttribute(name, next);\n }\n }\n\n /** Writes the behavior-only positioning hook only when its value changed. */\n #renderFraction(value: number): void {\n const fraction = rangeFraction(value, this.minValue, this.maxValue);\n const next = String(fraction);\n if (this.element.style.getPropertyValue(FRACTION_PROPERTY) !== next) {\n this.element.style.setProperty(FRACTION_PROPERTY, next);\n }\n }\n\n /** Current normalized value derived from the live declarative inputs. */\n #currentValue(): number {\n return snapSteppedValue(this.valueValue, this.#steppedRange);\n }\n\n /** Shared range configuration; finite endpoints remain allowed off the grid. */\n get #steppedRange() {\n return { min: this.minValue, max: this.maxValue, step: this.stepValue };\n }\n\n /** Ends the current pointer session without dispatching another change. */\n #endDrag(): void {\n const drag = this.#drag;\n this.#drag = null;\n drag?.pointer?.end();\n }\n}\n\n/** Stable DOM and gesture state owned for the duration of one pointer drag. */\ninterface SliderDrag {\n pointer: OwnedPointerSession | null;\n readonly track: HTMLElement;\n thumb: HTMLElement | null;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/owned_pointer_session.ts","../../src/utils/range.ts","../../src/utils/stepped_value.ts","../../src/controllers/slider_controller.ts"],"names":["position"],"mappings":"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","file":"slider_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\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","/** Callbacks driven by one owned pointer until it ends or is cancelled. */\nexport interface OwnedPointerSessionHandlers {\n /** Receives movement from the initiating pointer only. */\n readonly move: (event: PointerEvent) => void;\n /** Runs once after every listener and pointer capture have been released. */\n readonly end?: () => void;\n}\n\n/**\n * Owns one pointer across document-level movement and termination events.\n *\n * A controller may have at most one instance at a time. The session filters\n * every event by the initiating `pointerId`, captures the pointer when the DOM\n * implementation supports it, and binds all fallback document listeners to one\n * abort signal. Calling {@link OwnedPointerSession.end} is idempotent.\n */\nexport class OwnedPointerSession {\n readonly pointerId: number;\n readonly #owner: HTMLElement;\n readonly #handlers: OwnedPointerSessionHandlers;\n readonly #abort = new AbortController();\n #active = true;\n\n constructor(start: PointerEvent, owner: HTMLElement, handlers: OwnedPointerSessionHandlers) {\n this.pointerId = start.pointerId;\n this.#owner = owner;\n this.#handlers = handlers;\n\n const { signal } = this.#abort;\n owner.ownerDocument.addEventListener(\"pointermove\", this.#onMove, { signal });\n owner.ownerDocument.addEventListener(\"pointerup\", this.#onEndEvent, { signal });\n owner.ownerDocument.addEventListener(\"pointercancel\", this.#onEndEvent, { signal });\n owner.addEventListener(\"lostpointercapture\", this.#onLostCapture, { signal });\n\n // Pointer capture is progressive here: happy-dom and older DOM shims may not\n // implement it, while the document listeners still preserve ownership.\n try {\n owner.setPointerCapture?.(this.pointerId);\n } catch {\n // A synthetic or already-ended pointer cannot be captured; filtering and\n // AbortController teardown remain sufficient for that environment.\n }\n }\n\n /** Whether this session still owns its pointer and listeners. */\n get active(): boolean {\n return this.#active;\n }\n\n /** Whether `event` belongs to the initiating pointer of the live session. */\n owns(event: PointerEvent): boolean {\n return this.#active && event.pointerId === this.pointerId;\n }\n\n /** Releases capture/listeners and invokes the end callback exactly once. */\n end(): void {\n if (!this.#active) return;\n this.#active = false;\n this.#abort.abort();\n try {\n this.#owner.releasePointerCapture?.(this.pointerId);\n } catch {\n // Losing capture because the owner detached is already an ended session.\n }\n this.#handlers.end?.();\n }\n\n readonly #onMove = (event: PointerEvent): void => {\n if (this.owns(event)) this.#handlers.move(event);\n };\n\n readonly #onEndEvent = (event: PointerEvent): void => {\n if (this.owns(event)) this.end();\n };\n\n readonly #onLostCapture = (event: Event): void => {\n const pointerId = (event as PointerEvent).pointerId;\n if (typeof pointerId === \"number\" && pointerId !== this.pointerId) return;\n this.end();\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","/** Configuration for a value constrained to a stepped numeric range. */\nexport interface SteppedRange {\n /** Inclusive lower endpoint. May be infinite for an unbounded control. */\n readonly min: number;\n /** Inclusive upper endpoint. May be infinite for an unbounded control. */\n readonly max: number;\n /** Requested step. Non-finite and non-positive values fall back to `1`. */\n readonly step: number;\n /** Step-grid origin. Defaults to a finite `min`, otherwise `0`. */\n readonly base?: number;\n}\n\n/** The positive finite step used at runtime and by keyboard stepping. */\nexport function effectiveStep(step: number): number {\n return Number.isFinite(step) && step > 0 ? step : 1;\n}\n\n/**\n * Clamps a raw value and selects its nearest allowed value.\n *\n * The allowed set is the step grid plus each finite range endpoint. Keeping the\n * endpoints in that set makes an authored `aria-valuemin`/`aria-valuemax`\n * truthful even when the upper endpoint is not aligned to the grid. A value\n * exactly between two allowed values resolves upward.\n */\nexport function snapSteppedValue(raw: number, range: SteppedRange): number {\n if (!(range.min <= range.max)) return finiteFallback(range.min, range.max);\n\n const input = Number.isNaN(raw) ? finiteFallback(range.min, range.max) : raw;\n const clamped = Math.min(range.max, Math.max(range.min, input));\n const step = effectiveStep(range.step);\n const base = stepBase(range);\n const candidates: number[] = [];\n\n if (Number.isFinite(range.min)) candidates.push(range.min);\n if (Number.isFinite(range.max)) candidates.push(range.max);\n\n const gridPosition = (clamped - base) / step;\n if (Number.isFinite(gridPosition)) {\n addGridCandidate(candidates, Math.floor(gridPosition), range, base, step);\n addGridCandidate(candidates, Math.ceil(gridPosition), range, base, step);\n }\n\n if (candidates.length === 0) return clamped;\n let nearest = candidates[0] as number;\n let nearestDistance = Math.abs(clamped - nearest);\n for (const candidate of candidates.slice(1)) {\n const distance = Math.abs(clamped - candidate);\n if (\n distance < nearestDistance ||\n (nearlyEqual(distance, nearestDistance) && candidate > nearest)\n ) {\n nearest = candidate;\n nearestDistance = distance;\n }\n }\n return nearest;\n}\n\n/**\n * Moves through the ordered allowed-value set by an integer number of steps.\n *\n * This differs from adding `step * count` and snapping afterward at an\n * off-grid endpoint: with `max=94, step=10`, one decrement from `94` must land\n * on `90`, and one increment from `90` must reach the advertised maximum `94`.\n */\nexport function stepSteppedValue(current: number, count: number, range: SteppedRange): number {\n const value = snapSteppedValue(current, range);\n const distance = Math.abs(Math.trunc(count));\n if (!Number.isFinite(distance) || distance === 0) return value;\n const direction = Math.sign(count);\n const adjacent = adjacentSteppedValue(value, direction, range);\n\n // The first move accounts for a possibly off-grid endpoint. From there a\n // direct grid jump is equivalent to repeated adjacency, and avoids work that\n // grows with Page-step magnitudes or programmatic counts.\n const raw = adjacent + direction * (distance - 1) * effectiveStep(range.step);\n return snapSteppedValue(raw, range);\n}\n\n/** Returns the next member of the ordered endpoint-plus-grid set. */\nfunction adjacentSteppedValue(current: number, direction: number, range: SteppedRange): number {\n const step = effectiveStep(range.step);\n const base = stepBase(range);\n const candidates: number[] = [];\n\n if (direction > 0) {\n const position = (current - base) / step;\n if (Number.isFinite(position)) {\n let gridIndex = Math.floor(position) + 1;\n let candidate = cleanGridValue(base + gridIndex * step, base, step);\n if (candidate < current || nearlyEqual(candidate, current)) {\n gridIndex += 1;\n candidate = cleanGridValue(base + gridIndex * step, base, step);\n }\n if (\n candidate > current &&\n !nearlyEqual(candidate, current) &&\n within(candidate, range.min, range.max)\n ) {\n candidates.push(clamp(candidate, range));\n }\n }\n return clamp(Math.min(...candidates), range);\n }\n\n const position = (current - base) / step;\n if (Number.isFinite(position)) {\n let gridIndex = Math.ceil(position) - 1;\n let candidate = cleanGridValue(base + gridIndex * step, base, step);\n if (candidate > current || nearlyEqual(candidate, current)) {\n gridIndex -= 1;\n candidate = cleanGridValue(base + gridIndex * step, base, step);\n }\n if (\n candidate < current &&\n !nearlyEqual(candidate, current) &&\n within(candidate, range.min, range.max)\n ) {\n candidates.push(clamp(candidate, range));\n }\n }\n return clamp(Math.max(...candidates), range);\n}\n\n/** Adds one grid value when it falls inside the inclusive range. */\nfunction addGridCandidate(\n candidates: number[],\n index: number,\n range: SteppedRange,\n base: number,\n step: number,\n): void {\n const candidate = cleanGridValue(base + index * step, base, step);\n if (within(candidate, range.min, range.max)) candidates.push(clamp(candidate, range));\n}\n\n/** A finite authored base wins; otherwise the finite lower bound or zero does. */\nfunction stepBase(range: SteppedRange): number {\n if (range.base !== undefined && Number.isFinite(range.base)) return range.base;\n return Number.isFinite(range.min) ? range.min : 0;\n}\n\n/** Removes binary noise without discarding significant integer-place digits. */\nfunction cleanGridValue(value: number, base: number, step: number): number {\n const precision = Math.max(decimalPlaces(base), decimalPlaces(step));\n // `toFixed` accepts at most 100 fractional digits. Below that limit it rounds\n // according to the authored grid precision rather than an arbitrary total\n // significant-digit count, which keeps unit steps distinct at large bases.\n return precision <= 100 ? Number(value.toFixed(precision)) : value;\n}\n\n/** Number of fractional decimal places represented by a finite JS number. */\nfunction decimalPlaces(value: number): number {\n const [coefficient = \"\", exponentText] = Math.abs(value).toString().toLowerCase().split(\"e\");\n const fractionLength = coefficient.split(\".\")[1]?.length ?? 0;\n const exponent = exponentText === undefined ? 0 : Number(exponentText);\n return Math.max(0, fractionLength - exponent);\n}\n\n/** Inclusive range check tolerant only of arithmetic noise at an endpoint. */\nfunction within(value: number, min: number, max: number): boolean {\n return (value > min || nearlyEqual(value, min)) && (value < max || nearlyEqual(value, max));\n}\n\n/** Clamps a candidate whose only overshoot may be floating-point noise. */\nfunction clamp(value: number, range: SteppedRange): number {\n return Math.min(range.max, Math.max(range.min, value));\n}\n\n/** Relative comparison used only to collapse arithmetic noise, not real steps. */\nfunction nearlyEqual(left: number, right: number): boolean {\n const scale = Math.max(Math.abs(left), Math.abs(right));\n return (\n Number.isFinite(left) &&\n Number.isFinite(right) &&\n Math.abs(left - right) <= Number.EPSILON * scale\n );\n}\n\n/** Stable fallback for malformed ranges or `NaN` input. */\nfunction finiteFallback(min: number, max: number): number {\n if (Number.isFinite(min)) return min;\n if (Number.isFinite(max)) return max;\n return 0;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { OwnedPointerSession } from \"../utils/owned_pointer_session\";\nimport { rangeFraction } from \"../utils/range\";\nimport { snapSteppedValue, stepSteppedValue } from \"../utils/stepped_value\";\n\n/** Name of the CSS custom property exposing the thumb position (0..1). */\nconst FRACTION_PROPERTY = \"--stimeo--slider-fraction\";\n\n/**\n * Headless, accessible slider (single-thumb range) behavior.\n *\n * Markup contract (identifier: `stimeo--slider`):\n * <div data-controller=\"stimeo--slider\"\n * data-stimeo--slider-min-value=\"0\"\n * data-stimeo--slider-max-value=\"100\"\n * data-stimeo--slider-step-value=\"1\"\n * data-stimeo--slider-value-value=\"40\">\n * <div data-stimeo--slider-target=\"track\"\n * data-action=\"pointerdown->stimeo--slider#onPointerDown\">\n * <div data-stimeo--slider-target=\"thumb\" role=\"slider\" tabindex=\"0\" aria-label=\"Volume\"\n * aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"40\"\n * data-action=\"keydown->stimeo--slider#onKeydown\"></div>\n * </div>\n * </div>\n *\n * Implements the WAI-ARIA APG **Slider** pattern. The current value is exposed\n * to assistive tech via `aria-valuenow`/`aria-valuemin`/`aria-valuemax` on the\n * thumb, and to the consumer's CSS via the `--stimeo--slider-fraction` custom\n * property (a number in `[0, 1]`) set on the controller element — the library\n * positions nothing itself.\n *\n * `change` dispatches `{ value: number }`.\n *\n * @remarks\n * Behavior only. The consumer owns all layout (e.g. positioning the thumb from\n * the fraction). Only the horizontal orientation is handled.\n *\n * Behavior provided:\n * - `ArrowRight`/`ArrowUp` increase and `ArrowLeft`/`ArrowDown` decrease by one\n * step; `Home`/`End` jump to the min/max; `PageUp`/`PageDown` move by ten steps.\n * - Pointer press/drag on the track sets the value from the pointer position.\n * - `step` must be finite and positive; invalid runtime input falls back to `1`.\n * Finite endpoints remain allowed even when they do not align to the step grid.\n *\n * The fraction is a value ratio, not a position, so only the consumer knows\n * whether their track mirrors under RTL. Set `logicalTrack` to declare that it\n * does: the pointer mapping and the horizontal arrow pair then follow the\n * writing direction. Left unset, nothing here reads `direction`.\n */\nexport class SliderController extends Controller<HTMLElement> {\n static override targets = [\"track\", \"thumb\"];\n static override values = {\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n step: { type: Number, default: 1 },\n value: { type: Number, default: 0 },\n logicalTrack: { type: Boolean, default: false },\n };\n static actions = [\"onKeydown\", \"onPointerDown\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly trackTarget: HTMLElement;\n declare readonly thumbTarget: HTMLElement;\n declare readonly hasTrackTarget: boolean;\n declare readonly hasThumbTarget: boolean;\n declare minValue: number;\n declare maxValue: number;\n declare stepValue: number;\n declare valueValue: number;\n declare logicalTrackValue: boolean;\n\n /** One initiating pointer owns each live drag and its stable target snapshot. */\n #drag: SliderDrag | null = null;\n\n /** Collapses a morph that swaps several render Values into one silent repaint. */\n readonly #repaint = new MicrotaskCoalescer(() => this.#render());\n\n /** Whether the consumer declared a mirroring track and the direction mirrors it. */\n get #mirrored(): boolean {\n return this.logicalTrackValue && isRtl(this.element);\n }\n\n /** Clamps the initial value and renders the starting position. */\n override connect(): void {\n this.#repaint.activate();\n this.#commit(this.valueValue, false);\n }\n\n /** Cancels any active pointer drag so document listeners never leak. */\n override disconnect(): void {\n this.#repaint.cancel();\n this.#endDrag();\n }\n\n /** Silently repaints a minimum changed by application code or a Turbo morph. */\n minValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Silently repaints a maximum changed by application code or a Turbo morph. */\n maxValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Silently repaints a step changed by application code or a Turbo morph. */\n stepValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Silently repaints a value changed by application code or a Turbo morph. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Hydrates a thumb inserted or replaced at runtime with the current ARIA state. */\n thumbTargetConnected(thumb: HTMLElement): void {\n const value = this.#currentValue();\n this.#renderThumb(thumb, value);\n this.#renderFraction(value);\n if (this.#drag && this.#drag.thumb === null) {\n this.#drag.thumb = thumb;\n thumb.focus();\n }\n this.#repaint.schedule();\n }\n\n /** Drops the stale focus owner while allowing a live track gesture to continue. */\n thumbTargetDisconnected(thumb: HTMLElement): void {\n if (this.#drag?.thumb === thumb) this.#drag.thumb = null;\n }\n\n /** Ends a gesture whose geometry target disappeared or ceased being a target. */\n trackTargetDisconnected(track: HTMLElement): void {\n if (this.#drag?.track === track) this.#endDrag();\n }\n\n /** Handles keyboard stepping per the APG slider model. */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n let next: number | null = null;\n const current = this.#currentValue();\n const range = this.#steppedRange;\n // On a mirrored track the greater value sits at the visual left, so the\n // horizontal pair trades places; the vertical pair passes through.\n switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = stepSteppedValue(current, 1, range);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = stepSteppedValue(current, -1, range);\n break;\n case \"PageUp\":\n next = stepSteppedValue(current, 10, range);\n break;\n case \"PageDown\":\n next = stepSteppedValue(current, -10, range);\n break;\n case \"Home\":\n next = this.minValue;\n break;\n case \"End\":\n next = this.maxValue;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#commit(next, true);\n }\n\n /** Begins a pointer drag: sets the value and tracks subsequent movement. */\n onPointerDown(event: PointerEvent): void {\n if (event.button !== 0 || this.#drag || !this.hasTrackTarget || !this.hasThumbTarget) return;\n const track = this.trackTarget;\n const thumb = this.thumbTarget;\n // Resolve the direction once for the whole gesture: reading it per move\n // would query computed style on every frame, and a drag that flipped\n // mid-gesture would be incoherent anyway.\n const mirrored = this.#mirrored;\n const value = this.#valueFromClientX(event.clientX, mirrored, track);\n if (value === null) return;\n event.preventDefault();\n this.#commit(value, true);\n thumb.focus();\n\n const drag: SliderDrag = { pointer: null, track, thumb };\n drag.pointer = new OwnedPointerSession(event, track, {\n move: (move) => {\n if (!track.isConnected) {\n this.#endDrag();\n return;\n }\n const moved = this.#valueFromClientX(move.clientX, mirrored, track);\n if (moved !== null) this.#commit(moved, true);\n },\n end: () => {\n if (this.#drag === drag) this.#drag = null;\n },\n });\n this.#drag = drag;\n }\n\n /** Maps a pointer X coordinate to a raw value using a stable track snapshot. */\n #valueFromClientX(clientX: number, mirrored: boolean, track: HTMLElement): number | null {\n const rect = track.getBoundingClientRect();\n if (rect.width === 0) return null;\n const offset = (clientX - rect.left) / rect.width;\n const fraction = mirrored ? 1 - offset : offset;\n return this.minValue + fraction * (this.maxValue - this.minValue);\n }\n\n /**\n * Stores a normalized value and renders synchronously for responsive input.\n * Morph callbacks use {@link #render} instead, so they never write Values back\n * or dispatch a user-facing change event.\n */\n #commit(raw: number, notify: boolean): void {\n const previous = this.#currentValue();\n const value = snapSteppedValue(raw, this.#steppedRange);\n if (!Object.is(this.valueValue, value)) this.valueValue = value;\n this.#renderValue(value);\n if (notify && value !== previous) this.dispatch(\"change\", { detail: { value } });\n }\n\n /**\n * Reflects the normalized current Value without mutating or dispatching it.\n *\n * @stimeoRenderRoot\n */\n #render(): void {\n this.#renderValue(this.#currentValue());\n }\n\n /** Reflects one normalized value on the current target and CSS output. */\n #renderValue(value: number): void {\n if (this.hasThumbTarget) {\n this.#renderThumb(this.thumbTarget, value);\n }\n this.#renderFraction(value);\n }\n\n /** Writes only ARIA values that differ, including on a replacement target. */\n #renderThumb(thumb: HTMLElement, value: number): void {\n const attributes = {\n \"aria-valuemin\": String(this.minValue),\n \"aria-valuemax\": String(this.maxValue),\n \"aria-valuenow\": String(value),\n };\n for (const [name, next] of Object.entries(attributes)) {\n if (thumb.getAttribute(name) !== next) thumb.setAttribute(name, next);\n }\n }\n\n /** Writes the behavior-only positioning hook only when its value changed. */\n #renderFraction(value: number): void {\n const fraction = rangeFraction(value, this.minValue, this.maxValue);\n const next = String(fraction);\n if (this.element.style.getPropertyValue(FRACTION_PROPERTY) !== next) {\n this.element.style.setProperty(FRACTION_PROPERTY, next);\n }\n }\n\n /** Current normalized value derived from the live declarative inputs. */\n #currentValue(): number {\n return snapSteppedValue(this.valueValue, this.#steppedRange);\n }\n\n /** Shared range configuration; finite endpoints remain allowed off the grid. */\n get #steppedRange() {\n return { min: this.minValue, max: this.maxValue, step: this.stepValue };\n }\n\n /** Ends the current pointer session without dispatching another change. */\n #endDrag(): void {\n const drag = this.#drag;\n this.#drag = null;\n drag?.pointer?.end();\n }\n}\n\n/** Stable DOM and gesture state owned for the duration of one pointer drag. */\ninterface SliderDrag {\n pointer: OwnedPointerSession | null;\n readonly track: HTMLElement;\n thumb: HTMLElement | null;\n}\n"]}
|
|
@@ -21,6 +21,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
21
21
|
* user must be able to see where focus went AND keep seeing the element that
|
|
22
22
|
* owns it (WCAG 2.4.7 / 2.4.11).
|
|
23
23
|
*
|
|
24
|
+
* `change` dispatches `{ hidden }`.
|
|
25
|
+
*
|
|
24
26
|
* @remarks
|
|
25
27
|
* Behavior only — `position: sticky`, the translate animation, and
|
|
26
28
|
* reduced-motion handling are the consumer's CSS (`prefers-reduced-motion`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/controllers/smart_sticky_header_controller.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"sources":["../../src/controllers/smart_sticky_header_controller.ts"],"names":[],"mappings":"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","file":"smart_sticky_header_controller.js","sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\n/**\n * Headless **smart sticky header**: hides the header on scroll-down and\n * reveals it on scroll-up, published as a `data-header-hidden` hook the\n * consumer's CSS translates away (`stimeo--sticky-observer` detects *stuck*,\n * but has no direction sense; this adds it). Core (zero dependencies).\n *\n * Markup contract (identifier: `stimeo--smart-sticky-header`):\n * <header data-controller=\"stimeo--smart-sticky-header\"\n * style=\"position: sticky; top: 0;\">…</header>\n *\n * The scroll source is the window by default; inside an overflow container,\n * point `containerSelector` at it (the header usually sits inside it too).\n * <!-- [data-header-hidden=\"true\"] { transform: translateY(-100%); } -->\n *\n * Scrolling down past `offset` px sets `data-header-hidden=\"true\"`; any\n * scroll-up (beyond the `tolerance` jitter guard) or returning above `offset`\n * reveals it again. Focus reaching the header always reveals it, and while\n * focus stays inside the header a scroll-down never hides it — a keyboard\n * user must be able to see where focus went AND keep seeing the element that\n * owns it (WCAG 2.4.7 / 2.4.11).\n *\n * `change` dispatches `{ hidden }`.\n *\n * @remarks\n * Behavior only — `position: sticky`, the translate animation, and\n * reduced-motion handling are the consumer's CSS (`prefers-reduced-motion`\n * should disable the transition, not the behavior). Scroll work is\n * rAF-throttled; listeners and any pending frame are released on\n * `disconnect()`, and `connect()` resets the transient hook (a Turbo cache\n * snapshot must not restore a hidden header at scroll top).\n */\nexport class SmartStickyHeaderController extends Controller<HTMLElement> {\n static override values = {\n containerSelector: { type: String, default: \"\" },\n offset: { type: Number, default: 80 },\n tolerance: { type: Number, default: 4 },\n };\n static events = [\"change\"] as const;\n\n declare containerSelectorValue: string;\n declare offsetValue: number;\n declare toleranceValue: number;\n\n #frame: number | null = null;\n /** The scroll source resolved at connect — disconnect must unbind the SAME node. */\n #scrollerEl: HTMLElement | Window = window;\n #lastY = 0;\n /** Last published state, so `change` fires only on transitions. */\n #hidden: boolean | null = null;\n\n readonly #onScroll = (): void => {\n if (this.#frame !== null) return;\n this.#frame = requestAnimationFrame(() => {\n this.#frame = null;\n this.#measure();\n });\n };\n\n /**\n * Focus inside a hidden header must reveal it (WCAG 2.4.7 / 2.4.11); the\n * hold while focus *stays* inside is the `#apply` hide invariant.\n */\n readonly #onFocusin = (): void => this.#apply(false);\n\n override connect(): void {\n // The hook is scroll-derived: recompute from the live scroll position\n // instead of trusting a cached snapshot (which may say hidden at y=0).\n this.#hidden = null;\n this.#scrollerEl = this.#resolveScroller();\n this.#lastY = this.#scrollY;\n this.#scrollerEl.addEventListener(\"scroll\", this.#onScroll, { passive: true });\n this.element.addEventListener(\"focusin\", this.#onFocusin);\n this.#apply(false);\n }\n\n override disconnect(): void {\n this.#scrollerEl.removeEventListener(\"scroll\", this.#onScroll);\n this.element.removeEventListener(\"focusin\", this.#onFocusin);\n if (this.#frame !== null) cancelAnimationFrame(this.#frame);\n this.#frame = null;\n }\n\n /** Resolves the scroll source: the `containerSelector` match, else the window. */\n #resolveScroller(): HTMLElement | Window {\n if (this.containerSelectorValue) {\n const container = document.querySelector<HTMLElement>(this.containerSelectorValue);\n if (container) return container;\n }\n return window;\n }\n\n get #scrollY(): number {\n const scroller = this.#scrollerEl;\n // Identity check, not `instanceof Window` — cross-realm/test DOMs fail it.\n return scroller === window ? window.scrollY : (scroller as HTMLElement).scrollTop;\n }\n\n #measure(): void {\n const y = this.#scrollY;\n const delta = y - this.#lastY;\n if (Math.abs(delta) < this.toleranceValue) return;\n this.#lastY = y;\n\n if (y <= this.offsetValue) this.#apply(false);\n else if (delta > 0) this.#apply(true);\n else this.#apply(false);\n }\n\n /** Reflects the state onto the hook and emits `change` on transitions. */\n #apply(hidden: boolean): void {\n // Invariant: a header holding focus is never hidden — `focusin` revealed\n // it, and no transition may slide the focus owner away mid-interaction\n // (WCAG 2.4.7 / 2.4.11). Vetoed here at the single state-transition choke\n // point (not in each caller), so any future hide path — a timer, an\n // action — cannot bypass it. Reveals are never vetoed.\n if (hidden && this.element.contains(document.activeElement)) return;\n if (hidden === this.#hidden) return;\n this.#hidden = hidden;\n this.element.setAttribute(\"data-header-hidden\", hidden ? \"true\" : \"false\");\n this.dispatch(\"change\", { detail: { hidden } });\n }\n}\n"]}
|
|
@@ -36,6 +36,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
36
36
|
* - `stimeo--spinner:timeout` — `timeout` elapsed with the load still running;
|
|
37
37
|
* the controller then ends it exactly as `stop` would, so a `hide` follows.
|
|
38
38
|
*
|
|
39
|
+
* `hide`, `show` and `timeout` dispatch `{}`.
|
|
40
|
+
*
|
|
39
41
|
* @remarks
|
|
40
42
|
* Behavior only — the visual spinner is the consumer's, alongside the text and
|
|
41
43
|
* `aria-hidden="true"`. Both timers are owned by {@link SafeTimeout}, kept across
|