stimeo-ui 0.7.0 → 0.8.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 +62 -0
- package/dist/controllers/auto_submit_controller.d.ts +14 -3
- package/dist/controllers/auto_submit_controller.js +94 -16
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +81 -47
- package/dist/controllers/carousel_controller.js +451 -100
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +39 -5
- package/dist/controllers/currency_input_controller.js +305 -74
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +3 -1
- package/dist/controllers/direct_upload_controller.js +12 -2
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +123 -29
- package/dist/controllers/file_dropzone_controller.js +386 -63
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +3 -1
- package/dist/controllers/flash_controller.js +3 -1
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +4 -2
- package/dist/controllers/frame_loading_controller.js +2 -1
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +42 -16
- package/dist/controllers/input_mask_controller.js +251 -76
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +51 -14
- package/dist/controllers/nested_form_controller.js +450 -42
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +61 -25
- package/dist/controllers/otp_controller.js +472 -114
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +3 -2
- package/dist/controllers/spinner_controller.js +7 -5
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +4 -1
- package/dist/controllers/submit_once_controller.js +3 -1
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +30 -10
- package/dist/controllers/textarea_autosize_controller.js +131 -3
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/index.js +1927 -607
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +8 -1
- package/dist/inspector/cli.js +3 -1
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +3 -1
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +4 -4
- package/dist/inspector/manifest.json +182 -23
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/before_cache_reset.ts","../../src/utils/detach_gate.ts","../../src/utils/min_duration_floor.ts","../../src/utils/safe_timeout.ts","../../src/controllers/frame_loading_controller.ts"],"names":[],"mappings":"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","file":"frame_loading_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 * Runs a controller's \"return to the initial state\" pass just before Turbo\n * caches the page.\n *\n * **`disconnect()` cannot do this job, for two independent reasons.** Turbo\n * queues the clone from this event rather than taking it here, and the body swap\n * that runs the controller's `disconnect()` is queued separately — so which of\n * the two lands first is not something a controller can rely on, and a rewind\n * written in `disconnect()` may reach only the DOM being thrown away. In the\n * other direction, `disconnect()` also fires on an in-page move (Stimulus tears\n * down and reconnects the same element), where rewinding would wipe a\n * legitimately in-progress interaction — a spinner mid-load would vanish. One\n * timing is unreliable, the other is too eager; `turbo:before-cache` is the only\n * point that is exactly \"the page is about to be frozen\".\n *\n * Scope is the subscription only: registering on `activate()`, unregistering on\n * `deactivate()`, and one shared document listener no matter how many instances\n * are live. *What* to return to its initial state — which `data-state`, which\n * `hidden`, which `aria-busy` — stays in the controller, because no two\n * consumers answer it the same way (the `MicrotaskCoalescer` split).\n *\n * **Rewind state, not appearance.** The pass writes attributes the controller\n * itself owns; the visual result of those attributes is the consumer's CSS, and\n * a library that reached for style or class names would be guessing at markup\n * it does not own.\n *\n * Both entry points are idempotent, so the lifecycle hooks can call them\n * unconditionally: a second `activate()` does not double-subscribe and does not\n * make the callback run twice, and `deactivate()` on an instance that never\n * subscribed is a no-op.\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 #beforeCache = new BeforeCacheReset(() => this.#rewind());\n *\n * connect() { this.#beforeCache.activate(); }\n * disconnect() { this.#beforeCache.deactivate(); }\n * ```\n */\nexport class BeforeCacheReset {\n /** Every subscribed instance, iterated by the one shared document listener. */\n static readonly #subscribers = new Set<BeforeCacheReset>();\n\n /** The shared listener; installed while at least one instance is subscribed. */\n static readonly #onBeforeCache = (): void => {\n for (const subscriber of BeforeCacheReset.#subscribers) subscriber.#rewind();\n };\n\n readonly #rewind: () => void;\n\n /** @param rewind - the pass that returns this controller's state to its initial form. */\n constructor(rewind: () => void) {\n this.#rewind = rewind;\n }\n\n /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */\n activate(): void {\n const first = BeforeCacheReset.#subscribers.size === 0;\n BeforeCacheReset.#subscribers.add(this);\n if (first) {\n document.addEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n }\n\n /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */\n deactivate(): void {\n BeforeCacheReset.#subscribers.delete(this);\n if (BeforeCacheReset.#subscribers.size > 0) return;\n document.removeEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\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 { BeforeCacheReset } from \"../utils/before_cache_reset\";\nimport { DetachGate } from \"../utils/detach_gate\";\nimport { MinDurationFloor } from \"../utils/min_duration_floor\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/**\n * Headless `<turbo-frame>` loading-state behavior: while the frame is fetching it\n * sets `aria-busy` + `data-frame-loading`, reveals an optional skeleton / overlay,\n * suppresses interaction with the stale content, and retreats focus, restoring it on\n * completion (no dedicated APG pattern; supports WCAG 2.2 **4.1.3 Status Messages**\n * via `aria-busy` and 2.4.3 focus order via the retreat/restore).\n *\n * Markup contract (identifier: `stimeo--frame-loading`):\n * <turbo-frame id=\"panel\" data-controller=\"stimeo--frame-loading\">\n * <div data-stimeo--frame-loading-target=\"skeleton\" hidden>…</div>\n * <div data-stimeo--frame-loading-target=\"content\">…</div>\n * </turbo-frame>\n *\n * It subscribes on the frame to Turbo's own fetch lifecycle: `turbo:before-fetch-request`\n * (which bubbles from the frame's links/forms or the frame itself) starts the loading\n * state, and `turbo:frame-load` ends it (with `turbo:fetch-request-error` as a safety\n * net so the state never sticks). `minDuration` keeps the skeleton up long enough to\n * avoid a flicker.\n *\n * `start` and `end` dispatch `{}`.\n *\n * @remarks\n * Behavior only — it ships no skeleton markup or styling (pair with Skeleton/CSS);\n * loading is held purely in `aria-busy` / `data-frame-loading` and the optional\n * targets' `hidden`. The `content` target is marked `inert` while loading to block\n * double-submits, and focus inside the frame is explicitly blurred then restored\n * (when `restoreFocus`) so it is testable without relying on emergent `inert`\n * focus behavior. Listeners are torn down on `disconnect()` (Turbo navigation\n * included) along with the {@link MinDurationFloor} holding the finish back, kept\n * across an in-page move by {@link DetachGate}. A detach that keeps the element\n * returns the frame to its idle form, as does {@link BeforeCacheReset} for the\n * snapshot a cached page freezes; a frame render that swaps the targets mid-load\n * re-arms them.\n */\nexport class FrameLoadingController extends Controller<HTMLElement> {\n static override targets = [\"content\", \"skeleton\", \"overlay\"];\n static override values = {\n announceText: { type: String, default: \"\" },\n announceReadyText: { type: String, default: \"\" },\n minDuration: { type: Number, default: 0 },\n restoreFocus: { type: Boolean, default: true },\n };\n static events = [\"start\", \"end\"] as const;\n\n declare readonly contentTarget: HTMLElement;\n declare readonly skeletonTarget: HTMLElement;\n declare readonly overlayTarget: HTMLElement;\n declare readonly hasContentTarget: boolean;\n declare readonly hasSkeletonTarget: boolean;\n declare readonly hasOverlayTarget: boolean;\n\n declare minDurationValue: number;\n declare restoreFocusValue: boolean;\n declare announceTextValue: string;\n declare announceReadyTextValue: string;\n\n readonly #timeouts = new SafeTimeout();\n readonly #floor = new MinDurationFloor(this.#timeouts);\n readonly #gate = new DetachGate();\n readonly #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());\n #loading = false;\n /**\n * The optional targets this controller revealed, and the content it marked inert.\n * Held as references rather than re-resolved on the way out: a detach that keeps\n * the element takes the identifier off `data-controller` first, and a scope\n * without its identifier stops resolving targets — the elements to tidy would be\n * unreachable exactly when the tidying matters. They double as the ownership\n * marker, so a `hidden` or an `inert` the consumer wrote is never taken over.\n */\n #revealedSkeleton: HTMLElement | null = null;\n #revealedOverlay: HTMLElement | null = null;\n #inertTarget: HTMLElement | null = null;\n #previousFocus: HTMLElement | null = null;\n /** The id of the retreated element, used to re-find it if the load replaced it. */\n #previousFocusId = \"\";\n\n readonly #onStart = (): void => {\n // A (possibly new) fetch began: drop a finish the floor is still holding so the\n // loading state is not torn down mid-load, then begin if not already loading.\n this.#floor.cancel();\n if (!this.#loading) this.#begin();\n };\n\n readonly #onEnd = (): void => {\n if (!this.#loading) return;\n // The newest end signal owns the finish: the floor replaces whatever it was\n // holding rather than letting a second wait stack behind the first.\n this.#floor.schedule(this.minDurationValue, () => this.#finish());\n };\n\n override connect(): void {\n this.#gate.cancel();\n this.#beforeCache.activate();\n this.element.addEventListener(\"turbo:before-fetch-request\", this.#onStart);\n this.element.addEventListener(\"turbo:frame-load\", this.#onEnd);\n this.element.addEventListener(\"turbo:fetch-request-error\", this.#onEnd);\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"turbo:before-fetch-request\", this.#onStart);\n this.element.removeEventListener(\"turbo:frame-load\", this.#onEnd);\n this.element.removeEventListener(\"turbo:fetch-request-error\", this.#onEnd);\n this.#beforeCache.deactivate();\n this.#gate.disconnected(this, () => this.#teardown());\n }\n\n /**\n * Drops the held finish and the loading bookkeeping on a real detach, returning\n * the frame to its idle form. No reconnect is coming, so nothing is left that\n * could finish the load and clear the hooks — a detach that keeps the element\n * (a morph dropping the identifier, an exit from a scoped observed root) would\n * otherwise strand it busy and inert. Focus is left where it is: the element is\n * leaving this controller's care, and moving it now would be an unexplained jump.\n */\n #teardown(): void {\n this.#gate.cancel();\n this.#timeouts.clearAll();\n this.#floor.cancel();\n if (this.#loading) this.#rewindHooks();\n this.#loading = false;\n this.#previousFocus = null;\n }\n\n /**\n * Returns the frame to its idle form for the snapshot Turbo is about to take, so\n * a page reached with the Back button does not restore a frame that is busy and\n * inert with nothing left to finish it. State only — no `end` event and no focus\n * move, because the load did not actually complete.\n *\n * The load is abandoned rather than paused, so the flag and any finish the floor\n * still holds drop along with the hooks. A kept finish would surface after the\n * rewind as exactly the three things this pass exists to avoid — an `end`, a\n * completion announcement, and a focus move — and a kept flag would leave the\n * next fetch on a page that survives a cancelled visit skipping the loading\n * state, its idempotence guard already satisfied.\n */\n #rewindForCache(): void {\n if (!this.#loading) return;\n this.#loading = false;\n this.#floor.cancel();\n this.#rewindHooks();\n }\n\n /**\n * Clears every hook the loading state writes. Shared by the three ways a load can\n * stop — completion, detach, snapshot — so none of them can drift into tidying\n * only part of it.\n */\n #rewindHooks(): void {\n this.element.removeAttribute(\"aria-busy\");\n this.element.removeAttribute(\"data-frame-loading\");\n if (this.#revealedSkeleton) this.#revealedSkeleton.hidden = true;\n if (this.#revealedOverlay) this.#revealedOverlay.hidden = true;\n this.#revealedSkeleton = null;\n this.#revealedOverlay = null;\n this.#clearInert();\n }\n\n /**\n * Re-shows a `skeleton` that arrived mid-load. Turbo's frame renderer empties the\n * frame and re-inserts the response's children, so a response's authored (hidden)\n * skeleton can land while a later fetch is still running, and only the controller\n * knows the frame is still busy.\n */\n skeletonTargetConnected(): void {\n if (this.#loading) this.#revealSkeleton();\n }\n\n /** Re-shows an `overlay` that arrived mid-load — the same swap as the skeleton. */\n overlayTargetConnected(): void {\n if (this.#loading) this.#revealOverlay();\n }\n\n /**\n * Re-blocks a `content` that arrived mid-load, so the stale copy stays unusable.\n * The element that left is released first and ownership is then decided afresh, so\n * an `inert` the replacement authored stays the consumer's.\n */\n contentTargetConnected(): void {\n if (!this.#loading) return;\n this.#clearInert();\n this.#applyInert();\n }\n\n /** Reveals the optional `skeleton`, noting it as this controller's to hide again. */\n #revealSkeleton(): void {\n if (!this.hasSkeletonTarget) return;\n this.#revealedSkeleton = this.skeletonTarget;\n this.skeletonTarget.hidden = false;\n }\n\n /** Reveals the optional `overlay`, noting it as this controller's to hide again. */\n #revealOverlay(): void {\n if (!this.hasOverlayTarget) return;\n this.#revealedOverlay = this.overlayTarget;\n this.overlayTarget.hidden = false;\n }\n\n /** Enters the loading state: hooks, skeleton/overlay, inert content, focus retreat. */\n #begin(): void {\n this.#loading = true;\n this.#floor.begin();\n this.element.setAttribute(\"aria-busy\", \"true\");\n this.element.setAttribute(\"data-frame-loading\", \"true\");\n this.#revealSkeleton();\n this.#revealOverlay();\n this.#applyInert();\n this.#retreatFocus();\n this.dispatch(\"start\", { detail: {} });\n announce(fillTemplate(this.announceTextValue, {}));\n }\n\n /** Leaves the loading state: restore hooks, hide skeleton/overlay, restore focus. */\n #finish(): void {\n this.#loading = false;\n this.#rewindHooks();\n this.#restoreFocus();\n this.dispatch(\"end\", { detail: {} });\n announce(fillTemplate(this.announceReadyTextValue, {}));\n }\n\n /** Marks the content inert to block double-submits while stale (if we own it). */\n #applyInert(): void {\n if (!this.hasContentTarget || this.contentTarget.hasAttribute(\"inert\")) return;\n this.contentTarget.setAttribute(\"inert\", \"\");\n this.#inertTarget = this.contentTarget;\n }\n\n #clearInert(): void {\n this.#inertTarget?.removeAttribute(\"inert\");\n this.#inertTarget = null;\n }\n\n /** Saves and blurs focus if it sits inside the frame about to go stale. */\n #retreatFocus(): void {\n this.#previousFocus = null;\n this.#previousFocusId = \"\";\n if (!this.restoreFocusValue) return;\n const active = document.activeElement;\n if (active instanceof HTMLElement && active !== this.element && this.element.contains(active)) {\n this.#previousFocus = active;\n this.#previousFocusId = active.id;\n active.blur();\n }\n }\n\n /**\n * Restores focus after the load. The same node when it survived (e.g. a\n * non-replacing update), else the element re-rendered with the same id inside the\n * frame — Turbo frames typically re-emit the same controls. When neither is present\n * (an anonymous control was replaced) focus is left where the browser put it, to\n * avoid an unexpected jump (WCAG 3.2.x).\n */\n #restoreFocus(): void {\n const target = this.#previousFocus;\n const id = this.#previousFocusId;\n this.#previousFocus = null;\n this.#previousFocusId = \"\";\n if (!this.restoreFocusValue) return;\n if (target?.isConnected) {\n target.focus();\n return;\n }\n if (id) {\n const replacement = document.getElementById(id);\n if (replacement && this.element.contains(replacement)) replacement.focus();\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/before_cache_reset.ts","../../src/utils/detach_gate.ts","../../src/utils/min_duration_floor.ts","../../src/utils/safe_timeout.ts","../../src/controllers/frame_loading_controller.ts"],"names":[],"mappings":"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","file":"frame_loading_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 * Runs a controller's \"return to the initial state\" pass just before Turbo\n * caches the page.\n *\n * **`disconnect()` cannot do this job, for two independent reasons.** Turbo\n * queues the clone from this event rather than taking it here, and the body swap\n * that runs the controller's `disconnect()` is queued separately — so which of\n * the two lands first is not something a controller can rely on, and a rewind\n * written in `disconnect()` may reach only the DOM being thrown away. In the\n * other direction, `disconnect()` also fires on an in-page move (Stimulus tears\n * down and reconnects the same element), where rewinding would wipe a\n * legitimately in-progress interaction — a spinner mid-load would vanish. One\n * timing is unreliable, the other is too eager; `turbo:before-cache` is the only\n * point that is exactly \"the page is about to be frozen\".\n *\n * Scope is the subscription only: registering on `activate()`, unregistering on\n * `deactivate()`, and one shared document listener no matter how many instances\n * are live. *What* to return to its initial state — which `data-state`, which\n * `hidden`, which `aria-busy` — stays in the controller, because no two\n * consumers answer it the same way (the `MicrotaskCoalescer` split).\n *\n * **Rewind state, not appearance.** The pass writes attributes the controller\n * itself owns; the visual result of those attributes is the consumer's CSS, and\n * a library that reached for style or class names would be guessing at markup\n * it does not own.\n *\n * Both entry points are idempotent, so the lifecycle hooks can call them\n * unconditionally: a second `activate()` does not double-subscribe and does not\n * make the callback run twice, and `deactivate()` on an instance that never\n * subscribed is a no-op.\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 #beforeCache = new BeforeCacheReset(() => this.#rewind());\n *\n * connect() { this.#beforeCache.activate(); }\n * disconnect() { this.#beforeCache.deactivate(); }\n * ```\n */\nexport class BeforeCacheReset {\n /** Every subscribed instance, iterated by the one shared document listener. */\n static readonly #subscribers = new Set<BeforeCacheReset>();\n\n /** The shared listener; installed while at least one instance is subscribed. */\n static readonly #onBeforeCache = (): void => {\n for (const subscriber of BeforeCacheReset.#subscribers) subscriber.#rewind();\n };\n\n readonly #rewind: () => void;\n\n /** @param rewind - the pass that returns this controller's state to its initial form. */\n constructor(rewind: () => void) {\n this.#rewind = rewind;\n }\n\n /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */\n activate(): void {\n const first = BeforeCacheReset.#subscribers.size === 0;\n BeforeCacheReset.#subscribers.add(this);\n if (first) {\n document.addEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n }\n\n /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */\n deactivate(): void {\n BeforeCacheReset.#subscribers.delete(this);\n if (BeforeCacheReset.#subscribers.size > 0) return;\n document.removeEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\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 { BeforeCacheReset } from \"../utils/before_cache_reset\";\nimport { DetachGate } from \"../utils/detach_gate\";\nimport { MinDurationFloor } from \"../utils/min_duration_floor\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/**\n * Headless `<turbo-frame>` loading-state behavior: while the frame is fetching it\n * sets `aria-busy` + `data-frame-loading`, reveals an optional skeleton / overlay,\n * suppresses interaction with the stale content, and retreats focus, restoring it on\n * completion (no dedicated APG pattern; supports WCAG 2.2 **4.1.3 Status Messages**\n * via `aria-busy` and 2.4.3 focus order via the retreat/restore).\n *\n * Markup contract (identifier: `stimeo--frame-loading`):\n * <turbo-frame id=\"panel\" data-controller=\"stimeo--frame-loading\">\n * <div data-stimeo--frame-loading-target=\"skeleton\" hidden>…</div>\n * <div data-stimeo--frame-loading-target=\"content\">…</div>\n * </turbo-frame>\n *\n * It subscribes on the frame to Turbo's own fetch lifecycle: `turbo:before-fetch-request`\n * (which bubbles from the frame's links/forms or the frame itself) starts the loading\n * state, and `turbo:frame-load` ends it (with `turbo:fetch-request-error` as a safety\n * net so the state never sticks). `minDuration` keeps the skeleton up long enough to\n * avoid a flicker.\n *\n * `start`, `end`, and `reconcile` dispatch `{}`. The last of those reports that\n * the Turbo cache rewind ended a load the frame never finished — `end` would\n * claim the frame arrived.\n *\n * @remarks\n * Behavior only — it ships no skeleton markup or styling (pair with Skeleton/CSS);\n * loading is held purely in `aria-busy` / `data-frame-loading` and the optional\n * targets' `hidden`. The `content` target is marked `inert` while loading to block\n * double-submits, and focus inside the frame is explicitly blurred then restored\n * (when `restoreFocus`) so it is testable without relying on emergent `inert`\n * focus behavior. Listeners are torn down on `disconnect()` (Turbo navigation\n * included) along with the {@link MinDurationFloor} holding the finish back, kept\n * across an in-page move by {@link DetachGate}. A detach that keeps the element\n * returns the frame to its idle form, as does {@link BeforeCacheReset} for the\n * snapshot a cached page freezes; a frame render that swaps the targets mid-load\n * re-arms them.\n */\nexport class FrameLoadingController extends Controller<HTMLElement> {\n static override targets = [\"content\", \"skeleton\", \"overlay\"];\n static override values = {\n announceText: { type: String, default: \"\" },\n announceReadyText: { type: String, default: \"\" },\n minDuration: { type: Number, default: 0 },\n restoreFocus: { type: Boolean, default: true },\n };\n static events = [\"start\", \"end\", \"reconcile\"] as const;\n\n declare readonly contentTarget: HTMLElement;\n declare readonly skeletonTarget: HTMLElement;\n declare readonly overlayTarget: HTMLElement;\n declare readonly hasContentTarget: boolean;\n declare readonly hasSkeletonTarget: boolean;\n declare readonly hasOverlayTarget: boolean;\n\n declare minDurationValue: number;\n declare restoreFocusValue: boolean;\n declare announceTextValue: string;\n declare announceReadyTextValue: string;\n\n readonly #timeouts = new SafeTimeout();\n readonly #floor = new MinDurationFloor(this.#timeouts);\n readonly #gate = new DetachGate();\n readonly #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());\n #loading = false;\n /**\n * The optional targets this controller revealed, and the content it marked inert.\n * Held as references rather than re-resolved on the way out: a detach that keeps\n * the element takes the identifier off `data-controller` first, and a scope\n * without its identifier stops resolving targets — the elements to tidy would be\n * unreachable exactly when the tidying matters. They double as the ownership\n * marker, so a `hidden` or an `inert` the consumer wrote is never taken over.\n */\n #revealedSkeleton: HTMLElement | null = null;\n #revealedOverlay: HTMLElement | null = null;\n #inertTarget: HTMLElement | null = null;\n #previousFocus: HTMLElement | null = null;\n /** The id of the retreated element, used to re-find it if the load replaced it. */\n #previousFocusId = \"\";\n\n readonly #onStart = (): void => {\n // A (possibly new) fetch began: drop a finish the floor is still holding so the\n // loading state is not torn down mid-load, then begin if not already loading.\n this.#floor.cancel();\n if (!this.#loading) this.#begin();\n };\n\n readonly #onEnd = (): void => {\n if (!this.#loading) return;\n // The newest end signal owns the finish: the floor replaces whatever it was\n // holding rather than letting a second wait stack behind the first.\n this.#floor.schedule(this.minDurationValue, () => this.#finish());\n };\n\n override connect(): void {\n this.#gate.cancel();\n this.#beforeCache.activate();\n this.element.addEventListener(\"turbo:before-fetch-request\", this.#onStart);\n this.element.addEventListener(\"turbo:frame-load\", this.#onEnd);\n this.element.addEventListener(\"turbo:fetch-request-error\", this.#onEnd);\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"turbo:before-fetch-request\", this.#onStart);\n this.element.removeEventListener(\"turbo:frame-load\", this.#onEnd);\n this.element.removeEventListener(\"turbo:fetch-request-error\", this.#onEnd);\n this.#beforeCache.deactivate();\n this.#gate.disconnected(this, () => this.#teardown());\n }\n\n /**\n * Drops the held finish and the loading bookkeeping on a real detach, returning\n * the frame to its idle form. No reconnect is coming, so nothing is left that\n * could finish the load and clear the hooks — a detach that keeps the element\n * (a morph dropping the identifier, an exit from a scoped observed root) would\n * otherwise strand it busy and inert. Focus is left where it is: the element is\n * leaving this controller's care, and moving it now would be an unexplained jump.\n */\n #teardown(): void {\n this.#gate.cancel();\n this.#timeouts.clearAll();\n this.#floor.cancel();\n if (this.#loading) this.#rewindHooks();\n this.#loading = false;\n this.#previousFocus = null;\n }\n\n /**\n * Returns the frame to its idle form for the snapshot Turbo is about to take, so\n * a page reached with the Back button does not restore a frame that is busy and\n * inert with nothing left to finish it. State only — no `end` event and no focus\n * move, because the load did not actually complete.\n *\n * The load is abandoned rather than paused, so the flag and any finish the floor\n * still holds drop along with the hooks. A kept finish would surface after the\n * rewind as exactly the three things this pass exists to avoid — an `end`, a\n * completion announcement, and a focus move — and a kept flag would leave the\n * next fetch on a page that survives a cancelled visit skipping the loading\n * state, its idempotence guard already satisfied.\n */\n #rewindForCache(): void {\n if (!this.#loading) return;\n this.#loading = false;\n this.#floor.cancel();\n this.#rewindHooks();\n // The fetch does not survive the navigation, so a consumer still painting\n // \"loading\" from `start` would never be released by `end`.\n this.dispatch(\"reconcile\", { detail: {} });\n }\n\n /**\n * Clears every hook the loading state writes. Shared by the three ways a load can\n * stop — completion, detach, snapshot — so none of them can drift into tidying\n * only part of it.\n */\n #rewindHooks(): void {\n this.element.removeAttribute(\"aria-busy\");\n this.element.removeAttribute(\"data-frame-loading\");\n if (this.#revealedSkeleton) this.#revealedSkeleton.hidden = true;\n if (this.#revealedOverlay) this.#revealedOverlay.hidden = true;\n this.#revealedSkeleton = null;\n this.#revealedOverlay = null;\n this.#clearInert();\n }\n\n /**\n * Re-shows a `skeleton` that arrived mid-load. Turbo's frame renderer empties the\n * frame and re-inserts the response's children, so a response's authored (hidden)\n * skeleton can land while a later fetch is still running, and only the controller\n * knows the frame is still busy.\n */\n skeletonTargetConnected(): void {\n if (this.#loading) this.#revealSkeleton();\n }\n\n /** Re-shows an `overlay` that arrived mid-load — the same swap as the skeleton. */\n overlayTargetConnected(): void {\n if (this.#loading) this.#revealOverlay();\n }\n\n /**\n * Re-blocks a `content` that arrived mid-load, so the stale copy stays unusable.\n * The element that left is released first and ownership is then decided afresh, so\n * an `inert` the replacement authored stays the consumer's.\n */\n contentTargetConnected(): void {\n if (!this.#loading) return;\n this.#clearInert();\n this.#applyInert();\n }\n\n /** Reveals the optional `skeleton`, noting it as this controller's to hide again. */\n #revealSkeleton(): void {\n if (!this.hasSkeletonTarget) return;\n this.#revealedSkeleton = this.skeletonTarget;\n this.skeletonTarget.hidden = false;\n }\n\n /** Reveals the optional `overlay`, noting it as this controller's to hide again. */\n #revealOverlay(): void {\n if (!this.hasOverlayTarget) return;\n this.#revealedOverlay = this.overlayTarget;\n this.overlayTarget.hidden = false;\n }\n\n /** Enters the loading state: hooks, skeleton/overlay, inert content, focus retreat. */\n #begin(): void {\n this.#loading = true;\n this.#floor.begin();\n this.element.setAttribute(\"aria-busy\", \"true\");\n this.element.setAttribute(\"data-frame-loading\", \"true\");\n this.#revealSkeleton();\n this.#revealOverlay();\n this.#applyInert();\n this.#retreatFocus();\n this.dispatch(\"start\", { detail: {} });\n announce(fillTemplate(this.announceTextValue, {}));\n }\n\n /** Leaves the loading state: restore hooks, hide skeleton/overlay, restore focus. */\n #finish(): void {\n this.#loading = false;\n this.#rewindHooks();\n this.#restoreFocus();\n this.dispatch(\"end\", { detail: {} });\n announce(fillTemplate(this.announceReadyTextValue, {}));\n }\n\n /** Marks the content inert to block double-submits while stale (if we own it). */\n #applyInert(): void {\n if (!this.hasContentTarget || this.contentTarget.hasAttribute(\"inert\")) return;\n this.contentTarget.setAttribute(\"inert\", \"\");\n this.#inertTarget = this.contentTarget;\n }\n\n #clearInert(): void {\n this.#inertTarget?.removeAttribute(\"inert\");\n this.#inertTarget = null;\n }\n\n /** Saves and blurs focus if it sits inside the frame about to go stale. */\n #retreatFocus(): void {\n this.#previousFocus = null;\n this.#previousFocusId = \"\";\n if (!this.restoreFocusValue) return;\n const active = document.activeElement;\n if (active instanceof HTMLElement && active !== this.element && this.element.contains(active)) {\n this.#previousFocus = active;\n this.#previousFocusId = active.id;\n active.blur();\n }\n }\n\n /**\n * Restores focus after the load. The same node when it survived (e.g. a\n * non-replacing update), else the element re-rendered with the same id inside the\n * frame — Turbo frames typically re-emit the same controls. When neither is present\n * (an anonymous control was replaced) focus is left where the browser put it, to\n * avoid an unexpected jump (WCAG 3.2.x).\n */\n #restoreFocus(): void {\n const target = this.#previousFocus;\n const id = this.#previousFocusId;\n this.#previousFocus = null;\n this.#previousFocusId = \"\";\n if (!this.restoreFocusValue) return;\n if (target?.isConnected) {\n target.focus();\n return;\n }\n if (id) {\n const replacement = document.getElementById(id);\n if (replacement && this.element.contains(replacement)) replacement.focus();\n }\n }\n}\n"]}
|
|
@@ -14,7 +14,10 @@ interface MaskResult {
|
|
|
14
14
|
/**
|
|
15
15
|
* Applies a fixed mask `pattern` to `value` using `tokens` (placeholder → regex).
|
|
16
16
|
* Non-matching input characters are rejected (skipped); literals are auto-inserted
|
|
17
|
-
* and a typed literal that matches is consumed.
|
|
17
|
+
* and a typed literal that matches is consumed. A character an IME confirmed in
|
|
18
|
+
* full-width form fills a token — or consumes a literal — when its half-width
|
|
19
|
+
* form is the one the mask asked for, and the half-width form is what the output
|
|
20
|
+
* carries. Pure and exported for direct testing.
|
|
18
21
|
*/
|
|
19
22
|
declare function applyMask(value: string, pattern: string, tokens: ReadonlyMap<string, RegExp>): MaskResult;
|
|
20
23
|
/**
|
|
@@ -30,22 +33,39 @@ declare function applyMask(value: string, pattern: string, tokens: ReadonlyMap<s
|
|
|
30
33
|
* data-action="input->stimeo--input-mask#format">
|
|
31
34
|
* <input type="hidden" name="zip" data-stimeo--input-mask-unmask>
|
|
32
35
|
*
|
|
33
|
-
* `change`
|
|
36
|
+
* `change` and `reconcile` dispatch `{ masked, unmasked, complete }`.
|
|
34
37
|
*
|
|
35
38
|
* @remarks
|
|
36
39
|
* Behavior only and **idempotent** — the formatted value lives only in the input and
|
|
37
40
|
* the hidden field (no module-scope state), so `connect()` re-formats the existing
|
|
38
41
|
* value and is stable across Turbo restore/morph. The controller sits on the
|
|
39
42
|
* `<input>` itself (a void element), so the raw-value sink is not a Stimulus target;
|
|
40
|
-
* it is the `[data-stimeo--input-mask-unmask]` field resolved nearest-first
|
|
41
|
-
* the
|
|
42
|
-
* input's `id` wins; otherwise the closest ancestor's value-less
|
|
43
|
-
* form holds several masked inputs, wrap each input+sink pair in a
|
|
44
|
-
* pair them by `id`.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `
|
|
43
|
+
* it is the `[data-stimeo--input-mask-unmask]` field resolved nearest-first among
|
|
44
|
+
* the associated form's own controls (an explicit pairing by the sink attribute's
|
|
45
|
+
* value naming the input's `id` wins; otherwise the closest ancestor's value-less
|
|
46
|
+
* sink) — when one form holds several masked inputs, wrap each input+sink pair in a
|
|
47
|
+
* container or pair them by `id`.
|
|
48
|
+
*
|
|
49
|
+
* Formatting never steals keys: it rejects disallowed characters silently and keeps
|
|
50
|
+
* the caret on insert, Backspace, and range replacement (WCAG 2.2 3.3.2 / 1.3.5;
|
|
51
|
+
* the expected format is the consumer's `aria-describedby`). The caret anchors on
|
|
52
|
+
* the token slots the text before it fills, so a rejected character leaves it
|
|
53
|
+
* exactly where it was. Backspace and Delete always progress: when the keystroke
|
|
54
|
+
* only removed literals the mask re-inserts, the significant character it aimed at
|
|
55
|
+
* goes instead. Events fired mid-IME-composition are ignored; the confirmed text is
|
|
56
|
+
* formatted once on `compositionend`.
|
|
57
|
+
*
|
|
58
|
+
* It reflects `data-mask-complete` / `data-mask-empty`, dispatches
|
|
59
|
+
* `stimeo--input-mask:change` only when a user edit moves the committed value, and
|
|
60
|
+
* `stimeo--input-mask:reconcile` when the controller itself decides that value —
|
|
61
|
+
* a server-rendered or restored value normalized on connection, or a re-format
|
|
62
|
+
* after `pattern` / `tokens` / `unmaskToHidden` changed. A malformed `tokens`
|
|
63
|
+
* declaration falls back to the default tokens: it is parsed once in
|
|
64
|
+
* `tokensValueChanged`, and the hot path only ever sees the validated map. A field
|
|
65
|
+
* the user cannot edit (`readonly`, `disabled`, or inside a disabled `<fieldset>`)
|
|
66
|
+
* keeps the value the page authored — the sink and the state hooks still follow it,
|
|
67
|
+
* but the field itself is never rewritten. The composition listener is released on
|
|
68
|
+
* `disconnect()`.
|
|
49
69
|
*/
|
|
50
70
|
declare class InputMaskController extends Controller<HTMLInputElement> {
|
|
51
71
|
#private;
|
|
@@ -55,8 +75,8 @@ declare class InputMaskController extends Controller<HTMLInputElement> {
|
|
|
55
75
|
default: string;
|
|
56
76
|
};
|
|
57
77
|
tokens: {
|
|
58
|
-
type:
|
|
59
|
-
default:
|
|
78
|
+
type: StringConstructor;
|
|
79
|
+
default: string;
|
|
60
80
|
};
|
|
61
81
|
unmaskToHidden: {
|
|
62
82
|
type: BooleanConstructor;
|
|
@@ -64,13 +84,19 @@ declare class InputMaskController extends Controller<HTMLInputElement> {
|
|
|
64
84
|
};
|
|
65
85
|
};
|
|
66
86
|
static actions: readonly ["format"];
|
|
67
|
-
static events: readonly ["change"];
|
|
87
|
+
static events: readonly ["change", "reconcile"];
|
|
68
88
|
patternValue: string;
|
|
69
|
-
tokensValue:
|
|
89
|
+
tokensValue: string;
|
|
70
90
|
unmaskToHiddenValue: boolean;
|
|
91
|
+
/** Re-parses the declaration and re-formats under the tokens it now selects. */
|
|
92
|
+
tokensValueChanged(): void;
|
|
93
|
+
patternValueChanged(): void;
|
|
94
|
+
/** Clears a sink it stops maintaining so a submit cannot carry a stale raw value. */
|
|
95
|
+
unmaskToHiddenValueChanged(): void;
|
|
71
96
|
connect(): void;
|
|
97
|
+
disconnect(): void;
|
|
72
98
|
/** Formats the field on input, preserving the caret. Bound via `data-action`. */
|
|
73
|
-
format(): void;
|
|
99
|
+
format(event?: Event): void;
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
export { InputMaskController, type MaskResult, applyMask };
|