stimeo-ui 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -5
- package/dist/cable/index.d.ts +1 -1
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +1 -1
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -2
- package/dist/controllers/calendar_controller.js +31 -7
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.js +8 -6
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +1 -1
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +1 -1
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.js +7 -1
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.js +68 -23
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +3 -3
- package/dist/controllers/date_range_picker_controller.js +31 -7
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +2 -2
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.js +21 -10
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +1 -1
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +1 -1
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +7 -1
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +3 -2
- package/dist/controllers/input_mask_controller.js +33 -12
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +10 -5
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +10 -5
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +3 -2
- package/dist/controllers/local_time_controller.js +34 -7
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +1 -1
- package/dist/controllers/masonry_controller.js +1 -1
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.js +10 -2
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +10 -2
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.js +8 -2
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/otp_controller.js +23 -10
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +10 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +1 -1
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +10 -3
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/portal_controller.js +21 -10
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.js +11 -3
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +1 -1
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.js +31 -5
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_before_cache_controller.js +23 -10
- package/dist/controllers/reset_before_cache_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +7 -2
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +7 -3
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +1 -1
- package/dist/controllers/scroll_visibility_controller.js +22 -13
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +1 -1
- package/dist/controllers/scrollspy_controller.js +11 -6
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.js +23 -12
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.js +10 -5
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +1 -1
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +1 -1
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -3
- package/dist/controllers/theme_controller.js +23 -13
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.js +11 -3
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +7 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/index.js +165 -171
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +38 -1
- package/dist/inspector/cli.js +80 -7
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +103 -7
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +5 -5
- package/dist/inspector/manifest.json +127 -1
- package/package.json +9 -11
package/dist/cable/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/authored_integer.ts","../../src/utils/blur_deferral.ts","../../src/utils/safe_timeout.ts","../../src/cable/consumer.ts","../../src/cable/live_counter_controller.ts","../../src/cable/presence_controller.ts","../../src/cable/typing_indicator_controller.ts","../../src/cable/index.ts"],"names":["Controller","template"],"mappings":"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","file":"index.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","/** Digits plus the characters that may separate them inside one number. */\nconst TOKEN_BODY = /\\d[\\d.,'’’ _]*\\d|\\d/;\n/** A `-` opens a token only against one of these on its left. */\nconst SIGN_OPENER = /[\\s([]/;\n\n/**\n * Reads the integer that a human-formatted string displays.\n *\n * Takes the first numeric token, drops group separators, and truncates any\n * fractional part toward zero — so `\"1,200 users\"`, `\"$1,299.99\"` and\n * `\"1.299,99\"` read 1200, 1299 and 1299 without asking the platform which\n * locale is in play. A separator counts as grouping only where the digits that\n * follow it form a run of exactly three and the leading run could head a\n * grouped number; anything else is the decimal point, and the digits after it\n * are the fraction.\n *\n * `-` counts as a sign only where it opens the token — at the start of the\n * string, or after whitespace or an opening bracket — so a hyphen inside a\n * label (`\"Sign-ups: 1,200\"`, `\"Top-10 users\"`) never becomes one.\n *\n * `\"1.200\"` is genuinely ambiguous (1200 grouped, 1.2 with a decimal point) and\n * reads as 1200: display numbers group far more often than they carry a\n * fraction in exactly three digits.\n *\n * Returns `null` when the string holds no digits at all. What that means is the\n * caller's to decide — starting nothing, or falling back to a base value.\n */\nexport function authoredInteger(text: string): number | null {\n const firstDigit = /\\d/.exec(text);\n if (firstDigit === null) return null;\n\n const at = firstDigit.index;\n const signed =\n at > 0 && text[at - 1] === \"-\" && (at === 1 || SIGN_OPENER.test(text[at - 2] as string));\n\n const body = TOKEN_BODY.exec(text.slice(at)) as RegExpExecArray;\n const runs = body[0].split(/\\D+/);\n const separators = body[0].match(/\\D+/g) ?? [];\n\n // The leading run heads a grouped number only when it is short enough to be a\n // group itself and carries no leading zero; \"0.500\" and \"012,3\" are decimals.\n const lead = runs[0] as string;\n const heads = lead.length <= 3 && !lead.startsWith(\"0\");\n let digits = lead;\n for (let i = 1; heads && i < runs.length; i += 1) {\n if (separators[i - 1]?.length !== 1 || runs[i]?.length !== 3) break;\n digits += runs[i];\n }\n\n const magnitude = Number.parseInt(digits, 10);\n return signed ? -magnitude || 0 : magnitude;\n}\n","/**\n * Shared bookkeeping for updates that must wait until an element loses focus.\n *\n * A control that becomes redundant while the user is standing on it cannot be\n * removed from the page immediately: hiding a focused element (or turning it\n * into a native `disabled` one) drops focus to `<body>`, stranding a keyboard\n * user mid-widget. The fix every affected controller reached for is the same —\n * keep the element reachable, hold the destructive update, listen for `blur`,\n * and apply it then.\n *\n * {@link BlurDeferral} owns *only* the registry part of that: which elements are\n * currently holding an update back, attaching and detaching the `blur` listener,\n * and guaranteeing teardown. It is deliberately **policy-free** — what the\n * interim state looks like (`aria-disabled`, an ownership marker, nothing at\n * all), and what the deferred update actually is, stay in the controller, which\n * receives the element back through its release callback.\n *\n * Three contracts are worth stating up front, because each one is a hazard the\n * consumers would otherwise hit:\n *\n * - **The release callback fires only on a real `blur`.** `release()` /\n * `releaseAll()` *cancel* a deferral; they do not complete it. Conflating the\n * two makes `overflow-indicator` disable a button that just left the boundary.\n * - **`elements` is a snapshot.** Iterating the live key set while releasing\n * inside the loop is the failure this registry exists to prevent.\n * - **The entry is detached before the callback runs**, so a controller that\n * decides the update is still unsafe can simply defer again.\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 contracts belong on the members.\n *\n * @example\n * ```ts\n * readonly #deferred = new BlurDeferral<HTMLElement>((trigger) => {\n * if (this.#connected) this.#evaluate();\n * });\n *\n * #hide(trigger: HTMLElement) {\n * if (document.activeElement === trigger) {\n * this.#deferred.deferOnly(trigger); // stays visible until blur\n * return;\n * }\n * this.#deferred.releaseAll();\n * trigger.hidden = true;\n * }\n *\n * disconnect() {\n * this.#deferred.releaseAll();\n * }\n * ```\n */\nexport class BlurDeferral<T extends HTMLElement = HTMLElement> {\n /** Elements currently holding an update back, mapped to their `blur` listener. */\n readonly #pending = new Map<T, () => void>();\n /** Called after a pending element blurs and has been detached. */\n readonly #onRelease: (element: T) => void;\n\n /** @param onRelease - Invoked once `element` actually blurs; never on `release`. */\n constructor(onRelease: (element: T) => void) {\n this.#onRelease = onRelease;\n }\n\n /** Number of elements currently holding an update back. */\n get size(): number {\n return this.#pending.size;\n }\n\n /** Snapshot of the pending elements, safe to iterate while releasing them. */\n get elements(): T[] {\n return [...this.#pending.keys()];\n }\n\n /** Whether `element` is currently holding an update back. */\n has(element: T): boolean {\n return this.#pending.has(element);\n }\n\n /** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */\n defer(element: T): void {\n if (this.#pending.has(element)) return;\n const onBlur = (): void => {\n this.#detach(element);\n this.#onRelease(element);\n };\n this.#pending.set(element, onBlur);\n element.addEventListener(\"blur\", onBlur);\n }\n\n /** Defers `element` as the only pending entry, cancelling any others. */\n deferOnly(element: T): void {\n for (const pending of this.elements) {\n if (pending !== element) this.#detach(pending);\n }\n this.defer(element);\n }\n\n /** Cancels `element`'s deferral without completing it; no-ops when not pending. */\n release(element: T): void {\n this.#detach(element);\n }\n\n /** Cancels every deferral without completing any of them. */\n releaseAll(): void {\n for (const element of this.elements) this.#detach(element);\n }\n\n /** Removes the `blur` listener for `element` and forgets it. */\n #detach(element: T): void {\n const onBlur = this.#pending.get(element);\n if (onBlur) element.removeEventListener(\"blur\", onBlur);\n this.#pending.delete(element);\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 * Largest delay a timer can hold: the platform stores it in a 32-bit signed\n * integer, and anything larger overflows to `1`, so a delay meant to be far in\n * the future fires almost immediately. A declared delay above this bound names\n * no delay at all, and a controller reading one falls back to its default.\n */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\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 { createConsumer } from \"@rails/actioncable\";\n\n/**\n * Minimal structural view of an Action Cable subscription — the two members the\n * server-bound controllers use. Keeping our own narrow interface (instead of\n * re-exporting the full `@rails/actioncable` types) lets consumers hand us any\n * structurally compatible object, including test doubles.\n */\nexport interface CableSubscription {\n /** Invokes a channel action on the server (`ChannelName#action`). */\n perform(action: string, data?: Record<string, unknown>): void;\n /** Cancels the subscription (the channel's `unsubscribed` runs server-side). */\n unsubscribe(): void;\n}\n\n/** The mixin a controller passes to {@link CableConsumer.subscriptions}' create. */\nexport interface CableSubscriptionMixin {\n /** Called once the subscription is confirmed (`perform` is deliverable). */\n connected?(): void;\n /**\n * Called when the connection drops — perform() is silently undeliverable\n * until Action Cable reconnects and re-confirms (`connected` fires again).\n */\n disconnected?(): void;\n /** Called when the server refuses the subscription (it will never confirm). */\n rejected?(): void;\n /** Called with each broadcast the channel transmits to this client. */\n received?(data: unknown): void;\n}\n\n/** Minimal structural view of an Action Cable consumer (the websocket owner). */\nexport interface CableConsumer {\n subscriptions: {\n create(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n ): CableSubscription;\n };\n}\n\n/**\n * Parses the extra identifier parameters a subscription is declared with.\n *\n * Stimulus offers an `Object` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the controller's `connect()` — and rethrows on\n * malformed text. The throw propagates out of the observer, so a single\n * unparseable attribute stops the subscription from ever being created and\n * leaves a controller that is connected but deaf, with nothing on the element to\n * say so. Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares: the identifier falls back\n * to the channel alone. The attribute text is identical either way\n * (`'{\"room\":\"chat_42\"}'`), so markup does not change with the declaration.\n *\n * Only a JSON object survives. An array, a bare number, a string or `null` cannot\n * name identifier parameters, and spreading one into the identifier would either\n * do nothing or produce index keys no channel can reproduce server-side.\n */\nexport function parseSubscriptionParams(raw: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\n/**\n * The shared consumer. Deliberately module-scoped: an Action Cable consumer is\n * *connection infrastructure* (one websocket per app, the Rails\n * `channels/consumer.js` convention), not UI state — it must survive Turbo\n * navigations, so it is deliberately not rebuilt per `connect()` the way\n * DOM-derived state is.\n */\nlet sharedConsumer: CableConsumer | null = null;\n\n/**\n * Replaces (or clears, with `null`) the shared Action Cable consumer.\n *\n * Call this once at boot when the app already owns a consumer (the usual\n * `app/javascript/channels/consumer.js`), so the server-bound controllers reuse\n * its websocket instead of opening a second one. Tests use it to inject a\n * double. With `null`, the next {@link getCableConsumer} lazily re-creates one.\n */\nexport function setCableConsumer(consumer: CableConsumer | null): void {\n sharedConsumer = consumer;\n}\n\n/**\n * The shared Action Cable consumer, lazily created on first use via\n * `createConsumer()` (which reads the standard `action_cable_meta_tag` URL).\n */\nexport function getCableConsumer(): CableConsumer {\n if (!sharedConsumer) sharedConsumer = createConsumer();\n return sharedConsumer;\n}\n\n/**\n * A {@link CableSubscription} that also tracks its confirmation lifecycle.\n * Action Cable silently drops a `perform()` sent before the subscription is\n * confirmed or while the connection is down — every server-bound controller\n * must therefore gate its sends (and their local side effects: optimistic\n * updates, throttle bookkeeping) on {@link confirmed}.\n */\nexport interface ConfirmedCableSubscription extends CableSubscription {\n /** True while the subscription is confirmed — `perform()` is deliverable. */\n readonly confirmed: boolean;\n /** True once the server refused the subscription (it will never confirm). */\n readonly rejected: boolean;\n}\n\n/** One caller of {@link createConfirmedSubscription} riding a shared wire subscription. */\ninterface Member {\n readonly mixin: CableSubscriptionMixin;\n /**\n * True while a lifecycle callback the member missed (the identifier was already\n * confirmed or refused when it joined) is still owed to it. Any callback that arrives\n * from the wire in the meantime supersedes the owed one.\n */\n catchUp: boolean;\n}\n\n/** The one wire subscription an identifier has on a consumer, plus who rides it. */\ninterface SharedSubscription {\n readonly subscription: CableSubscription;\n readonly members: Set<Member>;\n confirmed: boolean;\n rejected: boolean;\n}\n\n/**\n * Wire subscriptions by identifier, per consumer. Keyed weakly so a replaced consumer\n * (see {@link setCableConsumer}) takes its bookkeeping with it.\n */\nconst sharedSubscriptions = new WeakMap<CableConsumer, Map<string, SharedSubscription>>();\n\n/**\n * The identifier Action Cable derives for a channel descriptor: the JSON of the\n * params object, a bare channel name standing for `{ channel }`.\n */\nexport function identifierOf(channel: string | Record<string, unknown>): string {\n return JSON.stringify(typeof channel === \"string\" ? { channel } : channel);\n}\n\n/**\n * Creates a subscription on the shared consumer with confirmation tracking\n * layered over the caller's mixin: `connected` / `disconnected` / `rejected`\n * flip the {@link ConfirmedCableSubscription.confirmed} flag *before* the\n * caller's own handler runs (so a handler reading `subscription.confirmed`\n * sees the post-transition state), and `received` passes straight through.\n *\n * **Callers with the same identifier (channel + params) share one wire\n * subscription.** The server confirms an identifier once and silently ignores a\n * repeated `subscribe` for it, so a second wire subscription would never be\n * confirmed — its sends would stay gated and the client would keep re-sending\n * `subscribe` until the next reconnect. Instead every caller is a member of the\n * identifier's one subscription: lifecycle callbacks and broadcasts fan out to all\n * members, a member joining an already confirmed (or refused) identifier is told\n * so on the next microtask — after its caller has stored the returned\n * subscription, the way a callback from the wire would arrive — and the wire is\n * unsubscribed when the last member leaves.\n *\n * This is deliberately a *tracker*, not an automatic `perform()` gate: the\n * controllers must skip the local side effects that surround a send (an\n * optimistic DOM bump, a throttle timestamp) together with the send itself,\n * which only the call site can decide — so they check `confirmed` and bail\n * before any of it.\n */\nexport function createConfirmedSubscription(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n): ConfirmedCableSubscription {\n const consumer = getCableConsumer();\n let registry = sharedSubscriptions.get(consumer);\n if (!registry) {\n registry = new Map();\n sharedSubscriptions.set(consumer, registry);\n }\n const identifier = identifierOf(channel);\n const member: Member = { mixin, catchUp: false };\n\n let shared = registry.get(identifier);\n if (!shared) {\n shared = openSharedSubscription(consumer, channel);\n registry.set(identifier, shared);\n } else if (shared.rejected || shared.confirmed) {\n // Missed lifecycle: deliver it once the caller holds the returned subscription.\n const owed = shared;\n member.catchUp = true;\n queueMicrotask(() => {\n if (!member.catchUp) return;\n member.catchUp = false;\n if (owed.rejected) mixin.rejected?.();\n else mixin.connected?.();\n });\n }\n shared.members.add(member);\n const owner = shared;\n const registered = registry;\n let active = true;\n\n return {\n perform: (action, data) => owner.subscription.perform(action, data),\n unsubscribe: () => {\n if (!active) return;\n active = false;\n // Nothing is owed to a member that left: a catch-up still queued must not fire.\n member.catchUp = false;\n owner.members.delete(member);\n if (owner.members.size === 0) {\n registered.delete(identifier);\n owner.subscription.unsubscribe();\n }\n },\n get confirmed() {\n return owner.confirmed;\n },\n get rejected() {\n return owner.rejected;\n },\n };\n}\n\n/** Opens the wire subscription for an identifier and wires the fan-out to its members. */\nfunction openSharedSubscription(\n consumer: CableConsumer,\n channel: string | Record<string, unknown>,\n): SharedSubscription {\n const members = new Set<Member>();\n // A callback from the wire supersedes whatever a late member was still owed.\n const fanOut = (deliver: (mixin: CableSubscriptionMixin) => void): void => {\n for (const member of [...members]) {\n member.catchUp = false;\n deliver(member.mixin);\n }\n };\n const shared: SharedSubscription = {\n members,\n confirmed: false,\n rejected: false,\n subscription: consumer.subscriptions.create(channel, {\n // A refusal is final: Action Cable never confirms a rejected subscription,\n // so a late connected/disconnected (only possible from a misbehaving\n // consumer double) must not reopen the gate `rejected` promised shut.\n connected: () => {\n if (shared.rejected) return;\n shared.confirmed = true;\n fanOut((mixin) => mixin.connected?.());\n },\n disconnected: () => {\n if (shared.rejected) return;\n shared.confirmed = false;\n fanOut((mixin) => mixin.disconnected?.());\n },\n rejected: () => {\n shared.confirmed = false;\n shared.rejected = true;\n fanOut((mixin) => mixin.rejected?.());\n },\n received: (data: unknown) => fanOut((mixin) => mixin.received?.(data)),\n }),\n };\n return shared;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { authoredInteger } from \"../utils/authored_integer\";\nimport { BlurDeferral } from \"../utils/blur_deferral\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/**\n * Marker on triggers this controller disabled: `disabled` is a shared\n * attribute, so only marked ones are ever re-enabled — an authored-disabled\n * trigger stays untouched. The marker lives in the DOM rather than in a lease\n * held by the instance, so it survives a Turbo cache snapshot.\n */\nconst DISABLED_MARKER = \"data-live-counter-disabled\";\n\n/**\n * Headless **live counter** — a *server-bound* behavior: a number bound to an\n * Action Cable stream (likes, views, active users) that ticks across every\n * connected client. A local action applies an **optimistic** increment\n * immediately, then reconciles with the broadcast; the own echo of a delta\n * broadcast is deduped so the increment is never applied twice. Ships in the\n * opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * Markup contract (identifier: `stimeo--live-counter`):\n * <div data-controller=\"stimeo--live-counter\"\n * data-stimeo--live-counter-channel-value=\"LikesChannel\"\n * data-stimeo--live-counter-params-value='{\"post\":42}'\n * data-stimeo--live-counter-id-value=\"<%= SecureRandom.uuid %>\">\n * <span data-stimeo--live-counter-target=\"value\">128</span>\n * <button type=\"button\" data-action=\"stimeo--live-counter#increment\"\n * data-stimeo--live-counter-target=\"trigger\">♥</button>\n * </div>\n *\n * Wire contract — the broadcast is either **authoritative** or a **delta**:\n * `{ count: 129 }` sets the absolute value (naturally idempotent; preferred —\n * the server owns the number), while `{ delta: 1, by: \"17\" }` adds to it,\n * skipped when `by` matches this client's `id` **and its amount matches an\n * optimistic bump this client is still waiting to see echoed**. A counter\n * holding no such guess — a sibling on the page, this user's other tab, one\n * stepping by a different amount — applies the delta like any other, so every\n * display converges. Only whole numbers are accepted: the\n * display is the state, and a value that cannot be read back from text is not\n * a count. `increment` performs `increment` on the channel with `{ id, delta }`;\n * the server persists and broadcasts. Without an own `id` the delta echo is\n * indistinguishable from a foreign delta, so the optimistic bump is skipped and\n * the broadcast applies the increment exactly once — set `id` (any per-client,\n * per-tab string, e.g. `SecureRandom.uuid`) for optimistic UX. Comparing `by`\n * to `id` is an echo guard, not authentication — identity belongs to the server.\n *\n * `change` dispatches `{ count }`.\n *\n * @remarks\n * Behavior only — the displayed number IS the state, and the **DOM is the\n * source of truth**: the server renders the initial count into the `value`\n * target (the element itself without one), so a Turbo cache restore needs no\n * reconciliation and `connect()` only (re)subscribes. Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * increments are dropped before confirmation AND while the connection is down\n * (`disconnected` shuts the gate until Action Cable re-confirms), so the\n * display never advances past what the server can receive. The gate reads the\n * declaration, not the subscription object: while a `channel` is declared it\n * stays shut until that subscription confirms, including the window where one\n * has yet to be opened. A refused\n * subscription publishes the `data-live-counter-rejected` hook (cleared on\n * `connect()` — rejection is transient server state) so the consumer's CSS can\n * disable the trigger. Optional `trigger` targets make that declarative: they\n * carry the real `disabled` attribute exactly while an increment would be\n * dropped (before confirmation, during an outage, after a rejection) — no CSS\n * required, and a disabled form control is announced as such by AT. An\n * authored `disabled` (set by the consumer for its own reasons) is respected:\n * only a disabled this controller applied — tracked via a marker attribute —\n * is ever lifted, and it is given back when the controller or the target goes\n * away. A trigger that holds focus keeps it: the disable waits for it to blur,\n * since taking focus away mid-outage strands a keyboard user. Channel-less\n * (local-only) counters never disable their triggers. `announceText` is opt-in\n * and empty by default; when set, reconciled counts reach the page's shared\n * announcer, debounced so a burst is one message. The subscription follows the\n * declaration — a `channel` or `params` change moves it — and is released on\n * `disconnect()` (Turbo navigation included) along with any pending\n * announcement.\n */\nexport class LiveCounterController extends Controller<HTMLElement> {\n static override targets = [\"value\", \"trigger\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n announceText: { type: String, default: \"\" },\n };\n /** Collapses a burst of broadcasts into one announcement. */\n static #announceDelay = 200;\n static actions = [\"increment\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly hasValueTarget: boolean;\n declare readonly valueTarget: HTMLElement;\n declare readonly triggerTargets: HTMLElement[];\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare announceTextValue: string;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n #connected = false;\n /**\n * The optimistic bumps this client has yet to see echoed back, in the order it\n * sent them. An echo belongs to the send that caused it, and the amount is what\n * names that send: a counter that guessed nothing — or guessed a different\n * amount — applies the delta like any other, so siblings and other tabs\n * converge whatever step each of them uses.\n */\n readonly #outstanding: number[] = [];\n /** Holds a disable back while the trigger owns focus, so focus is never taken away. */\n readonly #focusedTriggers = new BlurDeferral((trigger) => this.#syncTrigger(trigger));\n readonly #timers = new SafeTimeout();\n\n /**\n * Re-parses the identifier parameters and moves the subscription to the\n * identifier they now name.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n this.#resubscribe();\n }\n\n /** Moves the subscription when the declaration names a different channel. */\n channelValueChanged(): void {\n this.#resubscribe();\n }\n\n override connect(): void {\n this.#connected = true;\n this.#subscribe();\n }\n\n /**\n * Opens the subscription the declaration names, and reflects the gate.\n *\n * The rejected hook is transient server state: a Turbo cache snapshot must not\n * resurrect it — the fresh subscription re-decides it. An outstanding guess\n * belongs to the identifier that is going away, so it is dropped with it.\n */\n #subscribe(): void {\n this.element.removeAttribute(\"data-live-counter-rejected\");\n this.#outstanding.length = 0;\n if (this.channelValue) {\n this.#subscription = this.#open();\n }\n // Also covers a Turbo cache restore that snapshotted a disabled trigger:\n // the fresh (unconfirmed or absent) subscription re-decides the state.\n this.#syncTriggers();\n }\n\n /**\n * Opens the wire subscription, or returns null when it cannot be opened.\n *\n * A consumer that cannot be built (no Action Cable meta tag, a boot-order\n * mistake) would otherwise throw out of `connect()` and leave the element\n * half-wired: no gate reflected, and a trigger that looks live. The gate reads\n * the declaration, so a null subscription keeps it shut and the display never\n * moves past what the server can receive.\n */\n #open(): ConfirmedCableSubscription | null {\n try {\n return createConfirmedSubscription(this.#descriptor, {\n connected: () => this.#syncTriggers(),\n // A drop closes the send window (the shared subscription tracks it)\n // until Action Cable reconnects and re-confirms — an increment during\n // the outage would bump the display while its perform() is silently\n // discarded by the closed socket.\n disconnected: () => this.#syncTriggers(),\n // The server refused the subscription (auth, bad params): the gate\n // stays shut for good, and the hook lets the consumer's CSS disable\n // or hide the trigger instead of leaving a silently dead button.\n rejected: () => {\n this.element.setAttribute(\"data-live-counter-rejected\", \"true\");\n this.#syncTriggers();\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n } catch (error) {\n console.warn(\"Stimeo UI: the live counter could not open its subscription.\", error);\n return null;\n }\n }\n\n /**\n * The identifier this declaration names.\n *\n * `channel` comes first so the identifier text matches what Action Cable\n * builds, and a `channel` key inside `params` cannot displace the declared\n * one — `params` names *additional* identifier parameters.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _ignored, ...rest } = this.#params;\n return { channel: this.channelValue, ...rest };\n }\n\n /** Moves to the identifier the declaration now names. */\n #resubscribe(): void {\n if (!this.#connected) return; // value callbacks run before connect()\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#subscribe();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#timers.clearAll();\n this.#focusedTriggers.releaseAll();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.element.removeAttribute(\"data-live-counter-rejected\");\n // `disabled` is borrowed while the gate is shut: give back what is ours.\n for (const trigger of this.triggerTargets) this.#release(trigger);\n }\n\n /** Late-added triggers (e.g. via a Turbo Stream) pick up the current gate. */\n triggerTargetConnected(target: HTMLElement): void {\n this.#syncTrigger(target);\n }\n\n /** A trigger that stops being one keeps nothing this controller lent it. */\n triggerTargetDisconnected(target: HTMLElement): void {\n this.#focusedTriggers.release(target);\n this.#release(target);\n }\n\n /**\n * Optimistic local increment: bumps the display immediately, then asks the\n * server to persist and broadcast. The step comes from the action param\n * (`data-stimeo--live-counter-delta-param`), default 1. Bound via `data-action`.\n */\n increment(event?: Event & { params?: { delta?: number } }): void {\n // Before the confirmed subscription — and while the connection is down —\n // a perform() is silently dropped by Action Cable: bumping the display\n // then would diverge from the server, so the whole increment is dropped\n // (screen and server stay consistent). A declared channel with no live\n // subscription is the same window, so the gate reads the declaration.\n if (!this.#ready) return;\n // Action params arrive as authored strings; normalize and ignore garbage.\n const raw = Number(event?.params?.delta ?? 1);\n const delta = Number.isSafeInteger(raw) ? raw : 1;\n // Channel-less (local-only) counters have no echo coming, so they always bump\n // and keep no guess: nothing would ever cancel one. Without an own `id` the\n // delta echo cannot be deduped either, so the optimistic bump is skipped and\n // the broadcast applies the increment exactly once.\n if (this.#local) {\n this.#write(this.#current + delta);\n } else if (this.idValue !== \"\") {\n this.#outstanding.push(delta);\n this.#write(this.#current + delta);\n }\n this.#subscription?.perform(\"increment\", { id: this.idValue, delta });\n }\n\n /**\n * Reconciles a broadcast: absolute `count` wins; the echo of this client's own\n * guess is skipped.\n *\n * Only whole numbers reach the display. The wire carries values another client\n * named and the server relayed, and the display is the state this controller\n * reads back — a fraction, an infinity or a NaN would be written as text that\n * reads back as a different number, or as none at all.\n */\n #onReceived(data: unknown): void {\n const message = data as { count?: unknown; delta?: unknown; by?: unknown } | null;\n if (Number.isSafeInteger(message?.count)) {\n // Server truth settles every guess this client was still waiting on.\n this.#outstanding.length = 0;\n this.#reconcile(message?.count as number);\n return;\n }\n if (Number.isSafeInteger(message?.delta)) {\n const delta = message?.delta as number;\n const own =\n typeof message?.by === \"string\" && message.by !== \"\" && message.by === this.idValue;\n // An echo cancels the one guess it was the answer to, matched by amount. A\n // counter holding no guess of that size — a sibling on the page, this\n // user's other tab, a step someone else chose — applies it.\n const guess = own ? this.#outstanding.indexOf(delta) : -1;\n if (guess !== -1) {\n this.#outstanding.splice(guess, 1);\n return;\n }\n this.#reconcile(this.#current + delta);\n }\n }\n\n /** True when no channel is declared: the local-only mode, with no server to diverge from. */\n get #local(): boolean {\n return this.channelValue === \"\";\n }\n\n /** True while an increment would go through (local-only counters always are). */\n get #ready(): boolean {\n return this.#local || (this.#subscription?.confirmed ?? false);\n }\n\n /**\n * Reflects the send gate onto the optional `trigger` targets as the real\n * `disabled` attribute — the declarative alternative to styling off the\n * `data-live-counter-rejected` hook (a disabled control is also skipped by\n * keyboard focus and announced by AT, which CSS alone cannot do).\n */\n #syncTriggers(): void {\n for (const trigger of this.triggerTargets) this.#syncTrigger(trigger);\n }\n\n /**\n * `disabled` is a shared attribute: only disable what is currently enabled\n * (marking it ours), and only lift a disabled carrying our marker — so an\n * authored-disabled trigger (\"disabled until valid\", say) is never\n * re-enabled by the gate. A marked disabled restored from a Turbo cache\n * snapshot is recognized as ours and lifted once the gate opens.\n */\n #syncTrigger(trigger: HTMLElement): void {\n if (this.#ready) {\n this.#focusedTriggers.release(trigger);\n this.#release(trigger);\n return;\n }\n if (trigger.hasAttribute(\"disabled\")) return;\n // Disabling the control that holds focus drops it to the document body,\n // stranding a keyboard user mid-interaction. Hold the disable until it\n // blurs; the gate already refuses the increment either way.\n if (trigger.contains(document.activeElement)) {\n this.#focusedTriggers.defer(trigger);\n return;\n }\n trigger.setAttribute(\"disabled\", \"\");\n trigger.setAttribute(DISABLED_MARKER, \"\");\n }\n\n /** Gives back a `disabled` this controller lent, leaving an authored one alone. */\n #release(trigger: HTMLElement): void {\n if (!trigger.hasAttribute(DISABLED_MARKER)) return;\n trigger.removeAttribute(\"disabled\");\n trigger.removeAttribute(DISABLED_MARKER);\n }\n\n /** The displayed element: the `value` target, else the controller element. */\n get #display(): HTMLElement {\n return this.hasValueTarget ? this.valueTarget : this.element;\n }\n\n /** The current count, parsed from the DOM (the single source of truth). */\n get #current(): number {\n // A formatted server-rendered value (\"1,200 likes\") reads as the number it\n // displays; a display holding no number at all counts as zero.\n return authoredInteger(this.#display.textContent ?? \"\") ?? 0;\n }\n\n /**\n * Writes a whole count — every caller has already established that it is one —\n * and reports whether the display actually moved.\n */\n #write(count: number): boolean {\n if (count === this.#current) return false;\n this.#display.textContent = String(count);\n this.dispatch(\"change\", { detail: { count } });\n return true;\n }\n\n /**\n * Settles the display on a count the server stated, announcing what it settled\n * on. Announcing the optimistic bump instead would read a guess out and then\n * correct it, so the announcement rides this path only.\n */\n #reconcile(count: number): void {\n if (this.#write(count)) this.#announce(count);\n }\n\n /**\n * Hands a reconciled count to the page's shared announcer.\n *\n * Debounced: a burst of broadcasts is one announcement, not one per message,\n * and only the last one is ever sent. Wording comes from the consumer and\n * {@link announce} drops an empty message, so an undeclared template announces\n * nothing at all — a count that ticks constantly should not interrupt a reader\n * unless the page asked for it.\n */\n #announce(count: number): void {\n this.#timers.clearAll();\n this.#timers.set(() => {\n announce(fillTemplate(this.announceTextValue, { count }));\n }, LiveCounterController.#announceDelay);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { MAX_TIMER_DELAY_MS, SafeInterval, SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n identifierOf,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** A peer currently present (another client in the same room). */\ninterface Peer {\n name: string;\n /** Auto-expiry timer id, restarted by every beacon. */\n timer: number;\n}\n\n/**\n * Minimum ms between outgoing beacons. The steady state is the `heartbeat`\n * interval; this guard only bounds the *extra* beacons sent in response to\n * newly seen peers (see the roster-convergence note in the class doc), so a\n * burst of joiners cannot make this client flood the channel.\n */\nconst BEACON_THROTTLE_MS = 2000;\n\n/** Heartbeat period used when the declared one names no interval. */\nconst DEFAULT_HEARTBEAT_MS = 15_000;\n\n/** Peer expiry used when the declared one names no delay. */\nconst DEFAULT_TIMEOUT_MS = 40_000;\n\n/**\n * How many connected controllers currently speak for each peer, keyed by the\n * channel identifier and the peer's own id.\n *\n * The leaving notice says that a *peer* is gone, so only the last element\n * claiming that peer may send it. The shared subscription counts the members of\n * a wire identifier, which is a different set: elements claiming other ids —\n * and the other cable controllers — ride the same wire, and letting them speak\n * for this peer would either drop a peer that is still here or keep a departed\n * one in every roster until it expires.\n */\nconst speakers = new Map<string, number>();\n\n/**\n * Headless **presence** — a *server-bound* behavior: online dots / a\n * \"who's viewing this\" stack, bound to an Action Cable channel. Like\n * `stimeo--typing-indicator`, the server stays a trivial rebroadcast channel\n * with **zero presence state**: every client heartbeats an `appear` beacon\n * (`{ id, name }`), and each client expires peers it has not heard from for\n * `timeout` ms. Leaving (`{ id, leaving: true }`) is broadcast best-effort on\n * `disconnect()` and on `pagehide` (tab close / hard navigation, where\n * `disconnect()` never runs); a lost notice is caught by the expiry. Ships in\n * the opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * The notice speaks for a *peer*, not for a wire subscription: on `disconnect()`\n * only the last element claiming a given `id` on a given identifier sends it, so a\n * sibling that stays mounted keeps the peer in every roster. `pagehide` takes the\n * whole page with it, so there every element sends and peers absorb the duplicates.\n *\n * Roster convergence: a late joiner would otherwise see peers only as their\n * next heartbeats arrive, so on hearing a beacon from an *unknown* peer, each\n * client re-announces itself (at most one such answer every 2s) — the\n * roster converges in one round-trip instead of one heartbeat period.\n *\n * Markup contract (identifier: `stimeo--presence`):\n * <div data-controller=\"stimeo--presence\"\n * data-stimeo--presence-channel-value=\"PresenceChannel\"\n * data-stimeo--presence-params-value='{\"room\":\"doc_7\"}'\n * data-stimeo--presence-id-value=\"17\" data-stimeo--presence-name-value=\"Alice\">\n * <span data-stimeo--presence-target=\"count\" data-other=\"%{count} viewing\"></span>\n * <ul data-stimeo--presence-target=\"list\"></ul>\n * <template data-stimeo--presence-target=\"template\">\n * <li><span data-presence-name></span></li>\n * </template>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class PresenceChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"presence:#{params[:room]}\"\n * def appear(data) = ActionCable.server.broadcast(\"presence:#{params[:room]}\",\n * { id: data[\"id\"], name: data[\"name\"], leaving: data[\"leaving\"] })\n * end\n *\n * `join` dispatches `{ id, name }`; `leave` dispatches `{ id }`; `change` dispatches\n * `{ users }`.\n *\n * @remarks\n * Behavior only — the dot/stack look is the consumer's CSS, keyed off the\n * `data-present` / `data-present-count` hooks. The roster counts and renders\n * **other** clients (`id` ≠ own `id`); rendering is optional: given a `list` +\n * `template` pair, one clone per peer is appended (elements marked\n * `data-presence-name` receive the name; the clone root is tagged\n * `data-presence-id`), and the `count` target renders through localizable\n * `data-zero` / `data-one` / `data-other` templates (`%{count}`). Richer\n * per-user rendering (avatars, links) belongs to the consumer via the `join` /\n * `leave` / `change` events or a server-rendered Turbo Stream. A `template` holds\n * one root element: the clone appended for a peer, and the node removed when the\n * peer goes, are that same element. Roster changes reach assistive tech only when\n * the consumer opts in through `announceJoinText` / `announceLeaveText` — reading\n * out every entry and exit of a busy room is noise, so silence is the default. The `id`\n * comparison is display-level echo suppression, not authentication — identity\n * belongs to the server. Sending tracks the full subscription lifecycle (via\n * the shared confirmation-aware subscription): beacons — heartbeats, the\n * convergence answer, the leaving notice — are dropped before confirmation and\n * while the connection is down, where Action Cable would discard them anyway\n * (and a discarded beacon must not burn the convergence throttle); `connected`\n * re-fires on every reconnect and force-beacons, so the roster self-heals. A\n * refused subscription publishes the `data-presence-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Presence is transient: `connect()` clears\n * whatever a Turbo cache snapshot preserved (hooks + rendered clones), renders\n * the known-empty count (the `data-present*` hooks stay absent until the first\n * beacon), and the stream re-populates; a `count` or `list` target swapped in by a\n * Stream or a morph arrives empty and is drawn from the roster as it connects; the subscription, heartbeat interval,\n * per-peer expiry timers, and the `pagehide` listener are all released on\n * `disconnect()` (Turbo navigation included).\n */\nexport class PresenceController extends Controller<HTMLElement> {\n static override targets = [\"count\", \"list\", \"template\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n heartbeat: { type: Number, default: DEFAULT_HEARTBEAT_MS },\n timeout: { type: Number, default: DEFAULT_TIMEOUT_MS },\n announceJoinText: { type: String, default: \"\" },\n announceLeaveText: { type: String, default: \"\" },\n };\n static events = [\"join\", \"leave\", \"change\"] as const;\n\n declare readonly hasCountTarget: boolean;\n declare readonly countTarget: HTMLElement;\n declare readonly hasListTarget: boolean;\n declare readonly listTarget: HTMLElement;\n declare readonly hasTemplateTarget: boolean;\n declare readonly templateTarget: HTMLTemplateElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare nameValue: string;\n declare heartbeatValue: number;\n declare timeoutValue: number;\n declare announceJoinTextValue: string;\n declare announceLeaveTextValue: string;\n\n /** Delay (ms) before one roster change is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Present peers keyed by id (insertion order = join order). */\n readonly #peers = new Map<string, Peer>();\n readonly #timers = new SafeTimeout();\n readonly #intervals = new SafeInterval();\n /** Epoch ms of the last outgoing beacon, for the convergence throttle. */\n #lastBeaconAt = 0;\n /** Pending trailing-edge convergence beacon (at most one queued). */\n #pendingBeacon: number | null = null;\n /** The one outstanding announcement, so a newer roster change supersedes it. */\n #announceId: number | null = null;\n /** The `speakers` key this element holds, recomputed on every `connect()`. */\n #speakerKey = \"\";\n\n /**\n * Re-parses the identifier parameters when the declaration changes.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n }\n\n /**\n * The identifier this declaration names. `params` *adds* to the identifier, so a\n * `channel` key inside it names an extra parameter and never replaces the channel\n * the element declares.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _declared, ...rest } = this.#params;\n return { channel: this.channelValue, ...rest };\n }\n\n /**\n * The beacon period, in ms: a finite, positive number a timer can hold. Anything\n * else names no interval — `setInterval` reads `NaN`, a negative value, `Infinity`\n * and a value past {@link MAX_TIMER_DELAY_MS} alike as \"as often as possible\", so\n * the declaration would flood the channel rather than heartbeat on it. Zero is out\n * for the same reason, which is why this bound is tighter than the non-negative one\n * a throttle or a plain delay can use. Such a declaration falls back to the default.\n */\n get #heartbeat(): number {\n const declared = this.heartbeatValue;\n return Number.isFinite(declared) && declared > 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_HEARTBEAT_MS;\n }\n\n /**\n * The silence after which a peer is dropped, in ms: a finite, positive number a\n * timer can hold. Anything else names no delay and every peer expires in the task\n * that records it, so the roster could never hold anyone. Such a declaration falls\n * back to the default.\n */\n get #timeout(): number {\n const declared = this.timeoutValue;\n return Number.isFinite(declared) && declared > 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Draws the roster into a list target, which arrives empty from a Turbo Stream\n * replacement or a morph. Stimulus runs this before `connect()` on the first mount\n * too, where the roster is still empty and clearing whatever a cache snapshot\n * preserved is what `connect()` goes on to do anyway.\n */\n listTargetConnected(): void {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n for (const [id, peer] of this.#peers) this.#appendClone(id, peer.name);\n }\n\n /** Paints the roster size into a count target that arrives blank, for the same reason. */\n countTargetConnected(): void {\n this.countTarget.textContent = this.#countMessage(this.#peers.size);\n }\n\n override connect(): void {\n // Presence is transient: drop whatever a Turbo cache snapshot preserved\n // (hooks + rendered clones); the live stream re-populates the roster.\n // Rejection is transient server state too — the fresh subscription below\n // re-decides the hook.\n this.#reset();\n this.element.removeAttribute(\"data-presence-rejected\");\n // The roster is known-empty here, so the count target can say so right\n // away instead of sitting blank until the first roster change. The\n // data-present* hooks intentionally stay absent until the first beacon.\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(0);\n\n if (!this.channelValue) return;\n this.#claimSpeaker();\n this.#subscription = createConfirmedSubscription(this.#descriptor, {\n // The first beacon must wait for the confirmed subscription — a\n // perform() before that is silently dropped by Action Cable. Fires\n // again on every reconnect, so the roster self-heals after an outage.\n connected: () => this.#beacon(true),\n // The server refused the subscription: no beacon will ever go through,\n // and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-presence-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n this.#intervals.set(() => this.#beacon(true), this.#heartbeat);\n window.addEventListener(\"pagehide\", this.#onPageHide);\n }\n\n override disconnect(): void {\n window.removeEventListener(\"pagehide\", this.#onPageHide);\n // Best-effort graceful leave, and only from the last element speaking for\n // this peer; a lost notice is caught by peers' expiry timers instead.\n if (this.#releaseSpeaker()) this.#sendLeaveNotice();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#intervals.clearAll();\n this.#reset();\n this.element.removeAttribute(\"data-presence-rejected\");\n }\n\n /**\n * Registers this element as one of the voices for its peer, so a sibling on the\n * same channel claiming the same `id` keeps the peer present until they all go.\n * Whether the peer is worth announcing at all is {@link #sendLeaveNotice}'s\n * question, so an element with no own `id` claims a voice like any other and is\n * simply never heard.\n */\n #claimSpeaker(): void {\n this.#speakerKey = `${identifierOf(this.#descriptor)}\\n${this.idValue}`;\n speakers.set(this.#speakerKey, (speakers.get(this.#speakerKey) ?? 0) + 1);\n }\n\n /**\n * Gives up the claim, reporting whether this element was the last voice for the\n * peer. The key is spent here: an element that goes on to connect without a\n * channel claims nothing, and a stale key would spend a voice its siblings own.\n */\n #releaseSpeaker(): boolean {\n const key = this.#speakerKey;\n this.#speakerKey = \"\";\n const remaining = (speakers.get(key) ?? 1) - 1;\n if (remaining > 0) speakers.set(key, remaining);\n else speakers.delete(key);\n return remaining <= 0;\n }\n\n /**\n * Sends the best-effort leaving notice (skipped without an own `id`, and\n * outside the confirmed window, where Action Cable would discard it anyway).\n */\n #sendLeaveNotice(): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n this.#subscription.perform(\"appear\", { id: this.idValue, leaving: true });\n }\n\n /**\n * `pagehide` covers the leaves `disconnect()` cannot see: closing the tab or\n * a hard (non-Turbo) navigation destroys the page without running Stimulus\n * teardown, so this listener is the only chance to announce them. Best-effort\n * by nature (the socket may close before the frame flushes); peers' expiry\n * timers stay the safety net. If the page enters the bfcache and is restored\n * instead, the next heartbeat re-announces this client, so an over-eager\n * leave self-heals.\n */\n readonly #onPageHide = (): void => {\n this.#sendLeaveNotice();\n };\n\n /**\n * Broadcasts this client's beacon (skipped without an own `id`). A throttled\n * convergence answer is deferred to the trailing edge rather than dropped —\n * otherwise a peer joining right after a heartbeat would not learn about\n * this client until the next full heartbeat period.\n *\n * Gated on the confirmed subscription: before confirmation and during an\n * outage Action Cable silently discards perform(), so a beacon sent then is\n * pure waste — worse, it would burn `#lastBeaconAt` and delay the next real\n * convergence answer by up to the throttle window. The heartbeat interval\n * keeps ticking regardless; `connected` re-fires on reconfirm and\n * force-beacons immediately, so a gated tick is never missed for long.\n */\n #beacon(force: boolean): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n const now = Date.now();\n const wait = BEACON_THROTTLE_MS - (now - this.#lastBeaconAt);\n if (!force && wait > 0) {\n if (this.#pendingBeacon === null) {\n this.#pendingBeacon = this.#timers.set(() => {\n this.#pendingBeacon = null;\n this.#beacon(true);\n }, wait);\n }\n return;\n }\n this.#lastBeaconAt = now;\n this.#subscription.perform(\"appear\", { id: this.idValue, name: this.nameValue });\n }\n\n /**\n * Tracks a broadcast beacon: upserts the peer (restarting its expiry timer),\n * removes it on a `leaving` notice, and re-announces this client when the\n * peer was unknown (roster convergence — see the class doc).\n */\n #onReceived(data: unknown): void {\n const beacon = data as { id?: unknown; name?: unknown; leaving?: unknown } | null;\n const id = beacon?.id;\n if (typeof id !== \"string\" || id === \"\" || id === this.idValue) return;\n\n if (beacon?.leaving === true) {\n this.#drop(id);\n return;\n }\n\n const name = typeof beacon?.name === \"string\" ? beacon.name : \"\";\n const existing = this.#peers.get(id);\n if (existing !== undefined) this.#timers.clear(existing.timer);\n const timer = this.#timers.set(() => this.#drop(id), this.#timeout);\n this.#peers.set(id, { name, timer });\n\n if (existing === undefined) {\n this.#appendClone(id, name);\n this.#render();\n this.#announce(this.announceJoinTextValue, name);\n this.dispatch(\"join\", { detail: { id, name } });\n this.#beacon(false); // answer an unknown peer so its roster converges\n } else if (existing.name !== name) {\n this.#updateClone(id, name);\n this.#render();\n }\n }\n\n /** Removes a peer (expiry or graceful leave) and reflects the change. */\n #drop(id: string): void {\n const peer = this.#peers.get(id);\n if (peer === undefined) return;\n this.#timers.clear(peer.timer);\n this.#peers.delete(id);\n this.#removeClone(id);\n this.#render();\n this.#announce(this.announceLeaveTextValue, peer.name);\n this.dispatch(\"leave\", { detail: { id } });\n }\n\n /** Reflects the roster onto the hooks + count target and emits `change`. */\n #render(): void {\n const users = [...this.#peers.entries()].map(([id, peer]) => ({ id, name: peer.name }));\n this.element.setAttribute(\"data-present\", users.length > 0 ? \"true\" : \"false\");\n this.element.setAttribute(\"data-present-count\", String(users.length));\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(users.length);\n this.dispatch(\"change\", { detail: { users } });\n }\n\n /**\n * Hands one roster change to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per peer — reading\n * out every entry and exit of a busy room is the noise the default avoids. Wording\n * comes from the consumer (`{name}` / `{count}`, where the count is the roster\n * after the change), and an undeclared template does nothing at all — not even\n * cancel an announcement already pending for the other direction.\n */\n #announce(template: string, name: string): void {\n const message = fillTemplate(template, { name, count: this.#peers.size });\n if (message.trim() === \"\") return;\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, PresenceController.#announceDelay);\n }\n\n /**\n * Builds the count copy. Localizable through `data-zero` / `data-one` /\n * `data-other` templates on the count target (`%{count}`); the bare number is\n * the fallback (copy-free, so nothing to localize by default).\n */\n #countMessage(count: number): string {\n const templates = this.countTarget.dataset;\n const template =\n (count === 0 ? templates.zero : count === 1 ? templates.one : templates.other) ??\n templates.other;\n return template ? template.replace(\"%{count}\", String(count)) : String(count);\n }\n\n /** Appends one template clone for a newly present peer (list + template only). */\n #appendClone(id: string, name: string): void {\n if (!this.hasListTarget || !this.hasTemplateTarget) return;\n const root = this.templateTarget.content.firstElementChild?.cloneNode(true);\n if (!(root instanceof Element)) return;\n root.setAttribute(\"data-presence-id\", id);\n this.#fillName(root, name);\n this.listTarget.appendChild(root);\n }\n\n #updateClone(id: string, name: string): void {\n const root = this.#cloneFor(id);\n if (root) this.#fillName(root, name);\n }\n\n #removeClone(id: string): void {\n this.#cloneFor(id)?.remove();\n }\n\n #cloneFor(id: string): Element | null {\n if (!this.hasListTarget) return null;\n // Attribute selectors cannot escape arbitrary ids reliably; match manually.\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n if (child.getAttribute(\"data-presence-id\") === id) return child;\n }\n return null;\n }\n\n /** Writes the peer's name into the clone's `data-presence-name` slots. */\n #fillName(root: Element, name: string): void {\n const slots = root.querySelectorAll(\"[data-presence-name]\");\n for (const slot of slots) slot.textContent = name;\n if (slots.length === 0 && root.hasAttribute(\"data-presence-name\")) {\n root.textContent = name;\n }\n }\n\n /** Clears the transient roster state (connect reset + disconnect teardown). */\n #reset(): void {\n this.#timers.clearAll();\n this.#pendingBeacon = null;\n this.#announceId = null;\n this.#peers.clear();\n this.#lastBeaconAt = 0;\n this.element.removeAttribute(\"data-present\");\n this.element.removeAttribute(\"data-present-count\");\n if (this.hasCountTarget) this.countTarget.textContent = \"\";\n if (this.hasListTarget) {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { MAX_TIMER_DELAY_MS, SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** Milliseconds of silence after which a typer is dropped. */\nconst DEFAULT_TIMEOUT = 3000;\n/** Minimum milliseconds between outgoing typing signals. */\nconst DEFAULT_THROTTLE = 2000;\n\n/**\n * Fills `%{token}` placeholders in a status template, in one pass.\n *\n * The values come off the wire — another client's display name — so they must never\n * reach a replacement *string*, where `$&`, `` $` ``, `$'` and `$$` expand into the\n * template's own text. A single pass also stops a name that happens to contain a token\n * from being re-substituted by a later one. Every occurrence is filled; a token this\n * call has no value for is left as authored, which keeps a typo visible instead of\n * blanking the word.\n */\nconst fillTokens = (template: string, values: Record<string, string>): string =>\n template.replace(/%\\{(name|names|count)\\}/g, (match, token: string) => values[token] ?? match);\n\n/**\n * Headless **typing indicator** — a *server-bound* behavior: a controller whose\n * state lives on the server stream, not in client memory. Typing in the composer\n * throttle-broadcasts a `typing` signal over an Action Cable channel; received\n * signals from *other* clients render \"X is typing…\" into a visible `status` slot\n * and auto-clear after `timeout` ms of silence. The whole behavior is HTML + a\n * broadcast — no app JS, no client store, no reconciler. Ships in the opt-in\n * `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer); the core stays\n * zero-dep.\n *\n * Markup contract (identifier: `stimeo--typing-indicator`):\n * <div data-controller=\"stimeo--typing-indicator\"\n * data-stimeo--typing-indicator-channel-value=\"TypingChannel\"\n * data-stimeo--typing-indicator-params-value='{\"room\":\"chat_42\"}'\n * data-stimeo--typing-indicator-name-value=\"Alice\"\n * data-stimeo--typing-indicator-announce-one-text-value=\"{name} is typing\">\n * <textarea data-stimeo--typing-indicator-target=\"input\"></textarea>\n * <p data-stimeo--typing-indicator-target=\"status\"\n * data-one=\"%{name} is typing…\" data-many=\"%{names} are typing…\"></p>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class TypingChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"typing:#{params[:room]}\"\n * def typing(data) = ActionCable.server.broadcast(\"typing:#{params[:room]}\",\n * { name: data[\"name\"] })\n * end\n *\n * The `status` target is a plain visible slot, not a live region: assistive tech is\n * reached through the page's shared announcer instead, so a set that settles is sent\n * there as `announceOneText` / `announceManyText` (`{name}` / `{names}` / `{count}`,\n * debounced). Both default to empty, and an empty message announces nothing — a page\n * that wants the indicator seen but not heard simply leaves them off.\n *\n * `change` dispatches `{ names }`.\n *\n * @remarks\n * Behavior only — the indicator's look is the consumer's CSS, keyed off the\n * `data-typing` hook; the copy is localizable through the `data-one` /\n * `data-many` templates (`%{name}` / `%{names}` / `%{count}`, terse English\n * fallback — the same channel design as `stimeo--sortable`). The own echo is\n * dropped by comparing the broadcast `name` against `name` (a same-name guard,\n * not authentication — identity belongs to the server). Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * signals are dropped before confirmation and while the connection is down, so\n * an outage never burns the throttle window on undeliverable sends. A refused\n * subscription publishes the `data-typing-indicator-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Typing state is transient by nature:\n * `connect()` resets the hook and the status slot (a Turbo cache snapshot must\n * not resurrect a stale \"X is typing…\"), and re-population happens naturally\n * from the stream. The subscription, the per-typer timers, and the delegated\n * `input` listener are all released on `disconnect()` (Turbo navigation\n * included).\n */\nexport class TypingIndicatorController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"status\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n timeout: { type: Number, default: DEFAULT_TIMEOUT },\n // Must stay below `timeout`: the throttle is leading-edge only (no trailing\n // send), so a receiver's display survives continuous typing only while a\n // fresh signal lands within its timeout window.\n throttle: { type: Number, default: DEFAULT_THROTTLE },\n announceOneText: { type: String, default: \"\" },\n announceManyText: { type: String, default: \"\" },\n };\n static events = [\"change\"] as const;\n\n declare readonly hasStatusTarget: boolean;\n declare readonly statusTarget: HTMLElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare nameValue: string;\n declare timeoutValue: number;\n declare throttleValue: number;\n declare announceOneTextValue: string;\n declare announceManyTextValue: string;\n\n /** Delay (ms) before one settled typer set is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Names currently typing (other clients), each with its auto-clear timer id. */\n readonly #typers = new Map<string, number>();\n readonly #timers = new SafeTimeout();\n /** Epoch ms of the last broadcast, for leading-edge throttling. */\n #lastSentAt = 0;\n /** The one outstanding announcement, so a newer set supersedes it. */\n #announceId: number | null = null;\n\n /**\n * Re-parses the identifier parameters when the declaration changes.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n }\n\n /**\n * Paints the current copy into a `status` target that arrived at runtime.\n *\n * No event: the set of typers did not change, only the element that displays it.\n * Without this a region swapped in mid-conversation stays empty while `data-typing`\n * still says someone is typing, leaving the state in the visual hook alone. An empty\n * set needs no paint — a fresh region already shows it.\n */\n statusTargetConnected(): void {\n if (this.#typers.size > 0) this.#paint();\n }\n\n override connect(): void {\n // Typing state is transient: a Turbo cache snapshot must not resurrect a\n // stale indicator, and the live stream re-populates naturally. Rejection is\n // transient server state too — the fresh subscription re-decides the hook.\n this.element.removeAttribute(\"data-typing\");\n this.element.removeAttribute(\"data-typing-indicator-rejected\");\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n\n // Delegated on the container so the composer needs no per-input data-action\n // (and swapped/appended inputs keep working).\n this.element.addEventListener(\"input\", this.#onInput);\n if (this.channelValue) {\n // Confirmation tracking (connected / disconnected / rejected) lives in\n // the shared subscription; #onInput gates on its `confirmed` so an\n // outage doesn't burn the throttle window on dropped sends.\n this.#subscription = createConfirmedSubscription(\n { channel: this.channelValue, ...this.#params },\n {\n // The server refused the subscription: the send gate stays shut for\n // good, and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-typing-indicator-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n },\n );\n }\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"input\", this.#onInput);\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#timers.clearAll();\n this.#announceId = null;\n this.#typers.clear();\n this.element.removeAttribute(\"data-typing\");\n this.element.removeAttribute(\"data-typing-indicator-rejected\");\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n this.#lastSentAt = 0;\n }\n\n /** Throttled (leading-edge) broadcast of this client's typing signal. */\n readonly #onInput = (): void => {\n // Before the confirmed subscription a perform() is silently dropped by\n // Action Cable — and recording #lastSentAt would ALSO throttle away the\n // first real send, so bail without touching the throttle state.\n if (!this.#subscription?.confirmed) return;\n const now = Date.now();\n if (now - this.#lastSentAt < this.#throttle) return;\n this.#lastSentAt = now;\n this.#subscription.perform(\"typing\", { name: this.nameValue });\n };\n\n /**\n * Tracks a broadcast typer. The own echo is dropped (same `name`); every\n * further signal from a name restarts its auto-clear timer, so the indicator\n * survives continuous typing and clears `timeout` ms after the last signal.\n */\n #onReceived(data: unknown): void {\n const name = (data as { name?: unknown } | null)?.name;\n if (typeof name !== \"string\" || name === \"\" || name === this.nameValue) return;\n\n const existing = this.#typers.get(name);\n if (existing !== undefined) this.#timers.clear(existing);\n const added = existing === undefined;\n this.#typers.set(\n name,\n this.#timers.set(() => this.#untrack(name), this.#timeout),\n );\n if (added) this.#render();\n }\n\n /**\n * The silence after which a typer is dropped, in ms: a finite, non-negative number\n * a timer can hold. Anything else names no delay — `setTimeout` reads `NaN`, a\n * negative value and `Infinity` alike as \"now\", and a value past\n * {@link MAX_TIMER_DELAY_MS} overflows to the same place — so the typer would\n * vanish in the same task it appeared and the indicator could never be seen. Such\n * a declaration falls back to the default.\n */\n get #timeout(): number {\n const declared = this.timeoutValue;\n return Number.isFinite(declared) && declared >= 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_TIMEOUT;\n }\n\n /**\n * The minimum gap between outgoing signals, in ms: a finite, non-negative number.\n * Anything else names no interval, and the gate then settles the same way at every\n * keystroke — `NaN` and a negative gap leave it open, so every keystroke broadcasts,\n * while `Infinity` is never exceeded, so nothing is ever sent. Such a declaration\n * falls back to the default.\n */\n get #throttle(): number {\n const declared = this.throttleValue;\n return Number.isFinite(declared) && declared >= 0 ? declared : DEFAULT_THROTTLE;\n }\n\n #untrack(name: string): void {\n this.#typers.delete(name);\n this.#render();\n }\n\n /** Reflects the typer set onto the display, announces it, and emits `change`. */\n #render(): void {\n const names = this.#paint();\n this.#announce(names);\n this.dispatch(\"change\", { detail: { names } });\n }\n\n /**\n * Hands the settled typer set to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per name. Only a\n * non-empty set is announced — that typing stopped is not news worth interrupting\n * a reader for, and the visible copy already clears. Wording comes from the\n * consumer, and {@link announce} drops an empty message, so an undeclared template\n * announces nothing at all.\n */\n #announce(names: string[]): void {\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = null;\n if (names.length === 0) return;\n\n const message =\n names.length === 1\n ? fillTemplate(this.announceOneTextValue, { name: names[0] ?? \"\" })\n : fillTemplate(this.announceManyTextValue, {\n names: names.join(\", \"),\n count: names.length,\n });\n\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, TypingIndicatorController.#announceDelay);\n }\n\n /** Writes the current typer set onto the hook and the status slot. */\n #paint(): string[] {\n const names = [...this.#typers.keys()];\n this.element.setAttribute(\"data-typing\", names.length > 0 ? \"true\" : \"false\");\n if (this.hasStatusTarget) {\n this.statusTarget.textContent = this.#message(names);\n }\n return names;\n }\n\n /**\n * Builds the status copy. Localizable through `data-one` / `data-many`\n * templates on the status target (`%{name}` / `%{names}` / `%{count}`); terse\n * English is the fallback.\n */\n #message(names: string[]): string {\n if (names.length === 0) return \"\";\n const joined = names.join(\", \");\n if (names.length === 1) {\n const template = this.statusTarget.dataset.one;\n const name = names[0] ?? \"\";\n return template ? fillTokens(template, { name }) : `${name} is typing…`;\n }\n const template = this.statusTarget.dataset.many;\n return template\n ? fillTokens(template, { names: joined, count: String(names.length) })\n : `${joined} are typing…`;\n }\n}\n","import type { Application } from \"@hotwired/stimulus\";\nimport { LiveCounterController } from \"./live_counter_controller\";\nimport { PresenceController } from \"./presence_controller\";\nimport { TypingIndicatorController } from \"./typing_indicator_controller\";\n\nexport {\n type CableConsumer,\n type CableSubscription,\n type CableSubscriptionMixin,\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n getCableConsumer,\n setCableConsumer,\n} from \"./consumer\";\nexport { LiveCounterController, PresenceController, TypingIndicatorController };\n\n/**\n * Opt-in **server-bound** behaviors for Stimeo — controllers whose state lives\n * on an Action Cable / Turbo stream rather than in client memory: presence,\n * typing indicators, live counters.\n *\n * **Why this is a separate entry point.** The core library is zero-runtime-dep:\n * `import \"stimeo-ui\"` pulls in nothing but `@hotwired/stimulus`. Binding the\n * DOM to a server stream genuinely needs `@rails/actioncable`, so — exactly\n * like `stimeo-ui/positioning` and `@floating-ui/dom` — that cost is opt-in:\n * this module lives at `stimeo-ui/cable`, `@rails/actioncable` is an *optional*\n * peer, and nothing in the core imports it.\n *\n * When the app already owns a consumer (`app/javascript/channels/consumer.js`),\n * hand it over once at boot so no second websocket is opened:\n *\n * ```ts\n * import consumer from \"./channels/consumer\";\n * import { registerCable, setCableConsumer } from \"stimeo-ui/cable\";\n *\n * setCableConsumer(consumer);\n * registerCable(application);\n * ```\n */\nexport const cableControllers = {\n \"stimeo--live-counter\": LiveCounterController,\n \"stimeo--presence\": PresenceController,\n \"stimeo--typing-indicator\": TypingIndicatorController,\n} as const;\n\n/**\n * Registers the opt-in server-bound controllers on a Stimulus Application\n * (additive to `registerStimeo`, mirroring `registerPositioning`).\n */\nexport function registerCable(application: Application): void {\n for (const [identifier, controller] of Object.entries(cableControllers)) {\n application.register(identifier, controller);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/authored_integer.ts","../../src/utils/blur_deferral.ts","../../src/utils/safe_timeout.ts","../../src/cable/consumer.ts","../../src/cable/live_counter_controller.ts","../../src/cable/presence_controller.ts","../../src/cable/typing_indicator_controller.ts","../../src/cable/index.ts"],"names":["Controller","template"],"mappings":"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","file":"index.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","/** Digits plus the characters that may separate them inside one number. */\nconst TOKEN_BODY = /\\d[\\d.,'’’ _]*\\d|\\d/;\n/** A `-` opens a token only against one of these on its left. */\nconst SIGN_OPENER = /[\\s([]/;\n\n/**\n * Reads the integer that a human-formatted string displays.\n *\n * Takes the first numeric token, drops group separators, and truncates any\n * fractional part toward zero — so `\"1,200 users\"`, `\"$1,299.99\"` and\n * `\"1.299,99\"` read 1200, 1299 and 1299 without asking the platform which\n * locale is in play. A separator counts as grouping only where the digits that\n * follow it form a run of exactly three and the leading run could head a\n * grouped number; anything else is the decimal point, and the digits after it\n * are the fraction.\n *\n * `-` counts as a sign only where it opens the token — at the start of the\n * string, or after whitespace or an opening bracket — so a hyphen inside a\n * label (`\"Sign-ups: 1,200\"`, `\"Top-10 users\"`) never becomes one.\n *\n * `\"1.200\"` is genuinely ambiguous (1200 grouped, 1.2 with a decimal point) and\n * reads as 1200: display numbers group far more often than they carry a\n * fraction in exactly three digits.\n *\n * Returns `null` when the string holds no digits at all. What that means is the\n * caller's to decide — starting nothing, or falling back to a base value.\n */\nexport function authoredInteger(text: string): number | null {\n const firstDigit = /\\d/.exec(text);\n if (firstDigit === null) return null;\n\n const at = firstDigit.index;\n const signed =\n at > 0 && text[at - 1] === \"-\" && (at === 1 || SIGN_OPENER.test(text[at - 2] as string));\n\n const body = TOKEN_BODY.exec(text.slice(at)) as RegExpExecArray;\n const runs = body[0].split(/\\D+/);\n const separators = body[0].match(/\\D+/g) ?? [];\n\n // The leading run heads a grouped number only when it is short enough to be a\n // group itself and carries no leading zero; \"0.500\" and \"012,3\" are decimals.\n const lead = runs[0] as string;\n const heads = lead.length <= 3 && !lead.startsWith(\"0\");\n let digits = lead;\n for (let i = 1; heads && i < runs.length; i += 1) {\n if (separators[i - 1]?.length !== 1 || runs[i]?.length !== 3) break;\n digits += runs[i];\n }\n\n const magnitude = Number.parseInt(digits, 10);\n return signed ? -magnitude || 0 : magnitude;\n}\n","/**\n * Shared bookkeeping for updates that must wait until an element loses focus.\n *\n * A control that becomes redundant while the user is standing on it cannot be\n * removed from the page immediately: hiding a focused element (or turning it\n * into a native `disabled` one) drops focus to `<body>`, stranding a keyboard\n * user mid-widget. The fix every affected controller reached for is the same —\n * keep the element reachable, hold the destructive update, listen for `blur`,\n * and apply it then.\n *\n * {@link BlurDeferral} owns *only* the registry part of that: which elements are\n * currently holding an update back, attaching and detaching the `blur` listener,\n * and guaranteeing teardown. It is deliberately **policy-free** — what the\n * interim state looks like (`aria-disabled`, an ownership marker, nothing at\n * all), and what the deferred update actually is, stay in the controller, which\n * receives the element back through its release callback.\n *\n * Three contracts are worth stating up front, because each one is a hazard the\n * consumers would otherwise hit:\n *\n * - **The release callback fires only on a real `blur`.** `release()` /\n * `releaseAll()` *cancel* a deferral; they do not complete it. Conflating the\n * two makes `overflow-indicator` disable a button that just left the boundary.\n * - **`elements` is a snapshot.** Iterating the live key set while releasing\n * inside the loop is the failure this registry exists to prevent.\n * - **The entry is detached before the callback runs**, so a controller that\n * decides the update is still unsafe can simply defer again.\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 contracts belong on the members.\n *\n * @example\n * ```ts\n * readonly #deferred = new BlurDeferral<HTMLElement>((trigger) => {\n * if (this.#connected) this.#evaluate();\n * });\n *\n * #hide(trigger: HTMLElement) {\n * if (document.activeElement === trigger) {\n * this.#deferred.deferOnly(trigger); // stays visible until blur\n * return;\n * }\n * this.#deferred.releaseAll();\n * trigger.hidden = true;\n * }\n *\n * disconnect() {\n * this.#deferred.releaseAll();\n * }\n * ```\n */\nexport class BlurDeferral<T extends HTMLElement = HTMLElement> {\n /** Elements currently holding an update back, mapped to their `blur` listener. */\n readonly #pending = new Map<T, () => void>();\n /** Called after a pending element blurs and has been detached. */\n readonly #onRelease: (element: T) => void;\n\n /** @param onRelease - Invoked once `element` actually blurs; never on `release`. */\n constructor(onRelease: (element: T) => void) {\n this.#onRelease = onRelease;\n }\n\n /** Number of elements currently holding an update back. */\n get size(): number {\n return this.#pending.size;\n }\n\n /** Snapshot of the pending elements, safe to iterate while releasing them. */\n get elements(): T[] {\n return [...this.#pending.keys()];\n }\n\n /** Whether `element` is currently holding an update back. */\n has(element: T): boolean {\n return this.#pending.has(element);\n }\n\n /** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */\n defer(element: T): void {\n if (this.#pending.has(element)) return;\n const onBlur = (): void => {\n this.#detach(element);\n this.#onRelease(element);\n };\n this.#pending.set(element, onBlur);\n element.addEventListener(\"blur\", onBlur);\n }\n\n /** Defers `element` as the only pending entry, cancelling any others. */\n deferOnly(element: T): void {\n for (const pending of this.elements) {\n if (pending !== element) this.#detach(pending);\n }\n this.defer(element);\n }\n\n /** Cancels `element`'s deferral without completing it; no-ops when not pending. */\n release(element: T): void {\n this.#detach(element);\n }\n\n /** Cancels every deferral without completing any of them. */\n releaseAll(): void {\n for (const element of this.elements) this.#detach(element);\n }\n\n /** Removes the `blur` listener for `element` and forgets it. */\n #detach(element: T): void {\n const onBlur = this.#pending.get(element);\n if (onBlur) element.removeEventListener(\"blur\", onBlur);\n this.#pending.delete(element);\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 * Largest delay a timer can hold: the platform stores it in a 32-bit signed\n * integer, and anything larger overflows to `1`, so a delay meant to be far in\n * the future fires almost immediately. A declared delay above this bound names\n * no delay at all, and a controller reading one falls back to its default.\n */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\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 { createConsumer } from \"@rails/actioncable\";\n\n/**\n * Minimal structural view of an Action Cable subscription — the two members the\n * server-bound controllers use. Keeping our own narrow interface (instead of\n * re-exporting the full `@rails/actioncable` types) lets consumers hand us any\n * structurally compatible object, including test doubles.\n */\nexport interface CableSubscription {\n /** Invokes a channel action on the server (`ChannelName#action`). */\n perform(action: string, data?: Record<string, unknown>): void;\n /** Cancels the subscription (the channel's `unsubscribed` runs server-side). */\n unsubscribe(): void;\n}\n\n/** The mixin a controller passes to {@link CableConsumer.subscriptions}' create. */\nexport interface CableSubscriptionMixin {\n /** Called once the subscription is confirmed (`perform` is deliverable). */\n connected?(): void;\n /**\n * Called when the connection drops — perform() is silently undeliverable\n * until Action Cable reconnects and re-confirms (`connected` fires again).\n */\n disconnected?(): void;\n /** Called when the server refuses the subscription (it will never confirm). */\n rejected?(): void;\n /** Called with each broadcast the channel transmits to this client. */\n received?(data: unknown): void;\n}\n\n/** Minimal structural view of an Action Cable consumer (the websocket owner). */\nexport interface CableConsumer {\n subscriptions: {\n create(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n ): CableSubscription;\n };\n}\n\n/**\n * Parses the extra identifier parameters a subscription is declared with.\n *\n * Stimulus offers an `Object` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the controller's `connect()` — and rethrows on\n * malformed text. The throw propagates out of the observer, so a single\n * unparseable attribute stops the subscription from ever being created and\n * leaves a controller that is connected but deaf, with nothing on the element to\n * say so. Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares: the identifier falls back\n * to the channel alone. The attribute text is identical either way\n * (`'{\"room\":\"chat_42\"}'`), so markup does not change with the declaration.\n *\n * Only a JSON object survives. An array, a bare number, a string or `null` cannot\n * name identifier parameters, and spreading one into the identifier would either\n * do nothing or produce index keys no channel can reproduce server-side.\n */\nexport function parseSubscriptionParams(raw: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\n/**\n * The shared consumer. Deliberately module-scoped: an Action Cable consumer is\n * *connection infrastructure* (one websocket per app, the Rails\n * `channels/consumer.js` convention), not UI state — it must survive Turbo\n * navigations, so it is deliberately not rebuilt per `connect()` the way\n * DOM-derived state is.\n */\nlet sharedConsumer: CableConsumer | null = null;\n\n/**\n * Replaces (or clears, with `null`) the shared Action Cable consumer.\n *\n * Call this once at boot when the app already owns a consumer (the usual\n * `app/javascript/channels/consumer.js`), so the server-bound controllers reuse\n * its websocket instead of opening a second one. Tests use it to inject a\n * double. With `null`, the next {@link getCableConsumer} lazily re-creates one.\n */\nexport function setCableConsumer(consumer: CableConsumer | null): void {\n sharedConsumer = consumer;\n}\n\n/**\n * The shared Action Cable consumer, lazily created on first use via\n * `createConsumer()` (which reads the standard `action_cable_meta_tag` URL).\n */\nexport function getCableConsumer(): CableConsumer {\n if (!sharedConsumer) sharedConsumer = createConsumer();\n return sharedConsumer;\n}\n\n/**\n * A {@link CableSubscription} that also tracks its confirmation lifecycle.\n * Action Cable silently drops a `perform()` sent before the subscription is\n * confirmed or while the connection is down — every server-bound controller\n * must therefore gate its sends (and their local side effects: optimistic\n * updates, throttle bookkeeping) on {@link confirmed}.\n */\nexport interface ConfirmedCableSubscription extends CableSubscription {\n /** True while the subscription is confirmed — `perform()` is deliverable. */\n readonly confirmed: boolean;\n /** True once the server refused the subscription (it will never confirm). */\n readonly rejected: boolean;\n}\n\n/** One caller of {@link createConfirmedSubscription} riding a shared wire subscription. */\ninterface Member {\n readonly mixin: CableSubscriptionMixin;\n /**\n * True while a lifecycle callback the member missed (the identifier was already\n * confirmed or refused when it joined) is still owed to it. Any callback that arrives\n * from the wire in the meantime supersedes the owed one.\n */\n catchUp: boolean;\n}\n\n/** The one wire subscription an identifier has on a consumer, plus who rides it. */\ninterface SharedSubscription {\n readonly subscription: CableSubscription;\n readonly members: Set<Member>;\n confirmed: boolean;\n rejected: boolean;\n}\n\n/**\n * Wire subscriptions by identifier, per consumer. Keyed weakly so a replaced consumer\n * (see {@link setCableConsumer}) takes its bookkeeping with it.\n */\nconst sharedSubscriptions = new WeakMap<CableConsumer, Map<string, SharedSubscription>>();\n\n/**\n * The identifier Action Cable derives for a channel descriptor: the JSON of the\n * params object, a bare channel name standing for `{ channel }`.\n */\nexport function identifierOf(channel: string | Record<string, unknown>): string {\n return JSON.stringify(typeof channel === \"string\" ? { channel } : channel);\n}\n\n/**\n * Creates a subscription on the shared consumer with confirmation tracking\n * layered over the caller's mixin: `connected` / `disconnected` / `rejected`\n * flip the {@link ConfirmedCableSubscription.confirmed} flag *before* the\n * caller's own handler runs (so a handler reading `subscription.confirmed`\n * sees the post-transition state), and `received` passes straight through.\n *\n * **Callers with the same identifier (channel + params) share one wire\n * subscription.** The server confirms an identifier once and silently ignores a\n * repeated `subscribe` for it, so a second wire subscription would never be\n * confirmed — its sends would stay gated and the client would keep re-sending\n * `subscribe` until the next reconnect. Instead every caller is a member of the\n * identifier's one subscription: lifecycle callbacks and broadcasts fan out to all\n * members, a member joining an already confirmed (or refused) identifier is told\n * so on the next microtask — after its caller has stored the returned\n * subscription, the way a callback from the wire would arrive — and the wire is\n * unsubscribed when the last member leaves.\n *\n * This is deliberately a *tracker*, not an automatic `perform()` gate: the\n * controllers must skip the local side effects that surround a send (an\n * optimistic DOM bump, a throttle timestamp) together with the send itself,\n * which only the call site can decide — so they check `confirmed` and bail\n * before any of it.\n */\nexport function createConfirmedSubscription(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n): ConfirmedCableSubscription {\n const consumer = getCableConsumer();\n let registry = sharedSubscriptions.get(consumer);\n if (!registry) {\n registry = new Map();\n sharedSubscriptions.set(consumer, registry);\n }\n const identifier = identifierOf(channel);\n const member: Member = { mixin, catchUp: false };\n\n let shared = registry.get(identifier);\n if (!shared) {\n shared = openSharedSubscription(consumer, channel);\n registry.set(identifier, shared);\n } else if (shared.rejected || shared.confirmed) {\n // Missed lifecycle: deliver it once the caller holds the returned subscription.\n const owed = shared;\n member.catchUp = true;\n queueMicrotask(() => {\n if (!member.catchUp) return;\n member.catchUp = false;\n if (owed.rejected) mixin.rejected?.();\n else mixin.connected?.();\n });\n }\n shared.members.add(member);\n const owner = shared;\n const registered = registry;\n let active = true;\n\n return {\n perform: (action, data) => owner.subscription.perform(action, data),\n unsubscribe: () => {\n if (!active) return;\n active = false;\n // Nothing is owed to a member that left: a catch-up still queued must not fire.\n member.catchUp = false;\n owner.members.delete(member);\n if (owner.members.size === 0) {\n registered.delete(identifier);\n owner.subscription.unsubscribe();\n }\n },\n get confirmed() {\n return owner.confirmed;\n },\n get rejected() {\n return owner.rejected;\n },\n };\n}\n\n/** Opens the wire subscription for an identifier and wires the fan-out to its members. */\nfunction openSharedSubscription(\n consumer: CableConsumer,\n channel: string | Record<string, unknown>,\n): SharedSubscription {\n const members = new Set<Member>();\n // A callback from the wire supersedes whatever a late member was still owed.\n const fanOut = (deliver: (mixin: CableSubscriptionMixin) => void): void => {\n for (const member of [...members]) {\n member.catchUp = false;\n deliver(member.mixin);\n }\n };\n const shared: SharedSubscription = {\n members,\n confirmed: false,\n rejected: false,\n subscription: consumer.subscriptions.create(channel, {\n // A refusal is final: Action Cable never confirms a rejected subscription,\n // so a late connected/disconnected (only possible from a misbehaving\n // consumer double) must not reopen the gate `rejected` promised shut.\n connected: () => {\n if (shared.rejected) return;\n shared.confirmed = true;\n fanOut((mixin) => mixin.connected?.());\n },\n disconnected: () => {\n if (shared.rejected) return;\n shared.confirmed = false;\n fanOut((mixin) => mixin.disconnected?.());\n },\n rejected: () => {\n shared.confirmed = false;\n shared.rejected = true;\n fanOut((mixin) => mixin.rejected?.());\n },\n received: (data: unknown) => fanOut((mixin) => mixin.received?.(data)),\n }),\n };\n return shared;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { authoredInteger } from \"../utils/authored_integer\";\nimport { BlurDeferral } from \"../utils/blur_deferral\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/**\n * Marker on triggers this controller disabled: `disabled` is a shared\n * attribute, so only marked ones are ever re-enabled — an authored-disabled\n * trigger stays untouched. The marker lives in the DOM rather than in a lease\n * held by the instance, so it survives a Turbo cache snapshot.\n */\nconst DISABLED_MARKER = \"data-live-counter-disabled\";\n\n/**\n * Headless **live counter** — a *server-bound* behavior: a number bound to an\n * Action Cable stream (likes, views, active users) that ticks across every\n * connected client. A local action applies an **optimistic** increment\n * immediately, then reconciles with the broadcast; the own echo of a delta\n * broadcast is deduped so the increment is never applied twice. Ships in the\n * opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * Markup contract (identifier: `stimeo--live-counter`):\n * <div data-controller=\"stimeo--live-counter\"\n * data-stimeo--live-counter-channel-value=\"LikesChannel\"\n * data-stimeo--live-counter-params-value='{\"post\":42}'\n * data-stimeo--live-counter-id-value=\"<%= SecureRandom.uuid %>\">\n * <span data-stimeo--live-counter-target=\"value\">128</span>\n * <button type=\"button\" data-action=\"click->stimeo--live-counter#increment\"\n * data-stimeo--live-counter-target=\"trigger\">♥</button>\n * </div>\n *\n * Wire contract — the broadcast is either **authoritative** or a **delta**:\n * `{ count: 129 }` sets the absolute value (naturally idempotent; preferred —\n * the server owns the number), while `{ delta: 1, by: \"17\" }` adds to it,\n * skipped when `by` matches this client's `id` **and its amount matches an\n * optimistic bump this client is still waiting to see echoed**. A counter\n * holding no such guess — a sibling on the page, this user's other tab, one\n * stepping by a different amount — applies the delta like any other, so every\n * display converges. Only whole numbers are accepted: the\n * display is the state, and a value that cannot be read back from text is not\n * a count. `increment` performs `increment` on the channel with `{ id, delta }`;\n * the server persists and broadcasts. Without an own `id` the delta echo is\n * indistinguishable from a foreign delta, so the optimistic bump is skipped and\n * the broadcast applies the increment exactly once — set `id` (any per-client,\n * per-tab string, e.g. `SecureRandom.uuid`) for optimistic UX. Comparing `by`\n * to `id` is an echo guard, not authentication — identity belongs to the server.\n *\n * `change` dispatches `{ count }`.\n *\n * @remarks\n * Behavior only — the displayed number IS the state, and the **DOM is the\n * source of truth**: the server renders the initial count into the `value`\n * target (the element itself without one), so a Turbo cache restore needs no\n * reconciliation and `connect()` only (re)subscribes. Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * increments are dropped before confirmation AND while the connection is down\n * (`disconnected` shuts the gate until Action Cable re-confirms), so the\n * display never advances past what the server can receive. The gate reads the\n * declaration, not the subscription object: while a `channel` is declared it\n * stays shut until that subscription confirms, including the window where one\n * has yet to be opened. A refused\n * subscription publishes the `data-live-counter-rejected` hook (cleared on\n * `connect()` — rejection is transient server state) so the consumer's CSS can\n * disable the trigger. Optional `trigger` targets make that declarative: they\n * carry the real `disabled` attribute exactly while an increment would be\n * dropped (before confirmation, during an outage, after a rejection) — no CSS\n * required, and a disabled form control is announced as such by AT. An\n * authored `disabled` (set by the consumer for its own reasons) is respected:\n * only a disabled this controller applied — tracked via a marker attribute —\n * is ever lifted, and it is given back when the controller or the target goes\n * away. A trigger that holds focus keeps it: the disable waits for it to blur,\n * since taking focus away mid-outage strands a keyboard user. Channel-less\n * (local-only) counters never disable their triggers. `announceText` is opt-in\n * and empty by default; when set, reconciled counts reach the page's shared\n * announcer, debounced so a burst is one message. The subscription follows the\n * declaration — a `channel` or `params` change moves it — and is released on\n * `disconnect()` (Turbo navigation included) along with any pending\n * announcement.\n */\nexport class LiveCounterController extends Controller<HTMLElement> {\n static override targets = [\"value\", \"trigger\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n announceText: { type: String, default: \"\" },\n };\n /** Collapses a burst of broadcasts into one announcement. */\n static #announceDelay = 200;\n static actions = [\"increment\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly hasValueTarget: boolean;\n declare readonly valueTarget: HTMLElement;\n declare readonly triggerTargets: HTMLElement[];\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare announceTextValue: string;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n #connected = false;\n /**\n * The optimistic bumps this client has yet to see echoed back, in the order it\n * sent them. An echo belongs to the send that caused it, and the amount is what\n * names that send: a counter that guessed nothing — or guessed a different\n * amount — applies the delta like any other, so siblings and other tabs\n * converge whatever step each of them uses.\n */\n readonly #outstanding: number[] = [];\n /** Holds a disable back while the trigger owns focus, so focus is never taken away. */\n readonly #focusedTriggers = new BlurDeferral((trigger) => this.#syncTrigger(trigger));\n readonly #timers = new SafeTimeout();\n\n /**\n * Re-parses the identifier parameters and moves the subscription to the\n * identifier they now name.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n this.#resubscribe();\n }\n\n /** Moves the subscription when the declaration names a different channel. */\n channelValueChanged(): void {\n this.#resubscribe();\n }\n\n override connect(): void {\n this.#connected = true;\n this.#subscribe();\n }\n\n /**\n * Opens the subscription the declaration names, and reflects the gate.\n *\n * The rejected hook is transient server state: a Turbo cache snapshot must not\n * resurrect it — the fresh subscription re-decides it. An outstanding guess\n * belongs to the identifier that is going away, so it is dropped with it.\n */\n #subscribe(): void {\n this.element.removeAttribute(\"data-live-counter-rejected\");\n this.#outstanding.length = 0;\n if (this.channelValue) {\n this.#subscription = this.#open();\n }\n // Also covers a Turbo cache restore that snapshotted a disabled trigger:\n // the fresh (unconfirmed or absent) subscription re-decides the state.\n this.#syncTriggers();\n }\n\n /**\n * Opens the wire subscription, or returns null when it cannot be opened.\n *\n * A consumer that cannot be built (no Action Cable meta tag, a boot-order\n * mistake) would otherwise throw out of `connect()` and leave the element\n * half-wired: no gate reflected, and a trigger that looks live. The gate reads\n * the declaration, so a null subscription keeps it shut and the display never\n * moves past what the server can receive.\n */\n #open(): ConfirmedCableSubscription | null {\n try {\n return createConfirmedSubscription(this.#descriptor, {\n connected: () => this.#syncTriggers(),\n // A drop closes the send window (the shared subscription tracks it)\n // until Action Cable reconnects and re-confirms — an increment during\n // the outage would bump the display while its perform() is silently\n // discarded by the closed socket.\n disconnected: () => this.#syncTriggers(),\n // The server refused the subscription (auth, bad params): the gate\n // stays shut for good, and the hook lets the consumer's CSS disable\n // or hide the trigger instead of leaving a silently dead button.\n rejected: () => {\n this.element.setAttribute(\"data-live-counter-rejected\", \"true\");\n this.#syncTriggers();\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n } catch (error) {\n console.warn(\"Stimeo UI: the live counter could not open its subscription.\", error);\n return null;\n }\n }\n\n /**\n * The identifier this declaration names.\n *\n * `channel` comes first so the identifier text matches what Action Cable\n * builds, and a `channel` key inside `params` cannot displace the declared\n * one — `params` names *additional* identifier parameters.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _ignored, ...rest } = this.#params;\n return { channel: this.channelValue, ...rest };\n }\n\n /** Moves to the identifier the declaration now names. */\n #resubscribe(): void {\n if (!this.#connected) return; // value callbacks run before connect()\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#subscribe();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#timers.clearAll();\n this.#focusedTriggers.releaseAll();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.element.removeAttribute(\"data-live-counter-rejected\");\n // `disabled` is borrowed while the gate is shut: give back what is ours.\n for (const trigger of this.triggerTargets) this.#release(trigger);\n }\n\n /** Late-added triggers (e.g. via a Turbo Stream) pick up the current gate. */\n triggerTargetConnected(target: HTMLElement): void {\n this.#syncTrigger(target);\n }\n\n /** A trigger that stops being one keeps nothing this controller lent it. */\n triggerTargetDisconnected(target: HTMLElement): void {\n this.#focusedTriggers.release(target);\n this.#release(target);\n }\n\n /**\n * Optimistic local increment: bumps the display immediately, then asks the\n * server to persist and broadcast. The step comes from the action param\n * (`data-stimeo--live-counter-delta-param`), default 1. Bound via `data-action`.\n */\n increment(event?: Event & { params?: { delta?: number } }): void {\n // Before the confirmed subscription — and while the connection is down —\n // a perform() is silently dropped by Action Cable: bumping the display\n // then would diverge from the server, so the whole increment is dropped\n // (screen and server stay consistent). A declared channel with no live\n // subscription is the same window, so the gate reads the declaration.\n if (!this.#ready) return;\n // Action params arrive as authored strings; normalize and ignore garbage.\n const raw = Number(event?.params?.delta ?? 1);\n const delta = Number.isSafeInteger(raw) ? raw : 1;\n // Channel-less (local-only) counters have no echo coming, so they always bump\n // and keep no guess: nothing would ever cancel one. Without an own `id` the\n // delta echo cannot be deduped either, so the optimistic bump is skipped and\n // the broadcast applies the increment exactly once.\n if (this.#local) {\n this.#write(this.#current + delta);\n } else if (this.idValue !== \"\") {\n this.#outstanding.push(delta);\n this.#write(this.#current + delta);\n }\n this.#subscription?.perform(\"increment\", { id: this.idValue, delta });\n }\n\n /**\n * Reconciles a broadcast: absolute `count` wins; the echo of this client's own\n * guess is skipped.\n *\n * Only whole numbers reach the display. The wire carries values another client\n * named and the server relayed, and the display is the state this controller\n * reads back — a fraction, an infinity or a NaN would be written as text that\n * reads back as a different number, or as none at all.\n */\n #onReceived(data: unknown): void {\n const message = data as { count?: unknown; delta?: unknown; by?: unknown } | null;\n if (Number.isSafeInteger(message?.count)) {\n // Server truth settles every guess this client was still waiting on.\n this.#outstanding.length = 0;\n this.#reconcile(message?.count as number);\n return;\n }\n if (Number.isSafeInteger(message?.delta)) {\n const delta = message?.delta as number;\n const own =\n typeof message?.by === \"string\" && message.by !== \"\" && message.by === this.idValue;\n // An echo cancels the one guess it was the answer to, matched by amount. A\n // counter holding no guess of that size — a sibling on the page, this\n // user's other tab, a step someone else chose — applies it.\n const guess = own ? this.#outstanding.indexOf(delta) : -1;\n if (guess !== -1) {\n this.#outstanding.splice(guess, 1);\n return;\n }\n this.#reconcile(this.#current + delta);\n }\n }\n\n /** True when no channel is declared: the local-only mode, with no server to diverge from. */\n get #local(): boolean {\n return this.channelValue === \"\";\n }\n\n /** True while an increment would go through (local-only counters always are). */\n get #ready(): boolean {\n return this.#local || (this.#subscription?.confirmed ?? false);\n }\n\n /**\n * Reflects the send gate onto the optional `trigger` targets as the real\n * `disabled` attribute — the declarative alternative to styling off the\n * `data-live-counter-rejected` hook (a disabled control is also skipped by\n * keyboard focus and announced by AT, which CSS alone cannot do).\n */\n #syncTriggers(): void {\n for (const trigger of this.triggerTargets) this.#syncTrigger(trigger);\n }\n\n /**\n * `disabled` is a shared attribute: only disable what is currently enabled\n * (marking it ours), and only lift a disabled carrying our marker — so an\n * authored-disabled trigger (\"disabled until valid\", say) is never\n * re-enabled by the gate. A marked disabled restored from a Turbo cache\n * snapshot is recognized as ours and lifted once the gate opens.\n */\n #syncTrigger(trigger: HTMLElement): void {\n if (this.#ready) {\n this.#focusedTriggers.release(trigger);\n this.#release(trigger);\n return;\n }\n if (trigger.hasAttribute(\"disabled\")) return;\n // Disabling the control that holds focus drops it to the document body,\n // stranding a keyboard user mid-interaction. Hold the disable until it\n // blurs; the gate already refuses the increment either way.\n if (trigger.contains(document.activeElement)) {\n this.#focusedTriggers.defer(trigger);\n return;\n }\n trigger.setAttribute(\"disabled\", \"\");\n trigger.setAttribute(DISABLED_MARKER, \"\");\n }\n\n /** Gives back a `disabled` this controller lent, leaving an authored one alone. */\n #release(trigger: HTMLElement): void {\n if (!trigger.hasAttribute(DISABLED_MARKER)) return;\n trigger.removeAttribute(\"disabled\");\n trigger.removeAttribute(DISABLED_MARKER);\n }\n\n /** The displayed element: the `value` target, else the controller element. */\n get #display(): HTMLElement {\n return this.hasValueTarget ? this.valueTarget : this.element;\n }\n\n /** The current count, parsed from the DOM (the single source of truth). */\n get #current(): number {\n // A formatted server-rendered value (\"1,200 likes\") reads as the number it\n // displays; a display holding no number at all counts as zero.\n return authoredInteger(this.#display.textContent ?? \"\") ?? 0;\n }\n\n /**\n * Writes a whole count — every caller has already established that it is one —\n * and reports whether the display actually moved.\n */\n #write(count: number): boolean {\n if (count === this.#current) return false;\n this.#display.textContent = String(count);\n this.dispatch(\"change\", { detail: { count } });\n return true;\n }\n\n /**\n * Settles the display on a count the server stated, announcing what it settled\n * on. Announcing the optimistic bump instead would read a guess out and then\n * correct it, so the announcement rides this path only.\n */\n #reconcile(count: number): void {\n if (this.#write(count)) this.#announce(count);\n }\n\n /**\n * Hands a reconciled count to the page's shared announcer.\n *\n * Debounced: a burst of broadcasts is one announcement, not one per message,\n * and only the last one is ever sent. Wording comes from the consumer and\n * {@link announce} drops an empty message, so an undeclared template announces\n * nothing at all — a count that ticks constantly should not interrupt a reader\n * unless the page asked for it.\n */\n #announce(count: number): void {\n this.#timers.clearAll();\n this.#timers.set(() => {\n announce(fillTemplate(this.announceTextValue, { count }));\n }, LiveCounterController.#announceDelay);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { MAX_TIMER_DELAY_MS, SafeInterval, SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n identifierOf,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** A peer currently present (another client in the same room). */\ninterface Peer {\n name: string;\n /** Auto-expiry timer id, restarted by every beacon. */\n timer: number;\n}\n\n/**\n * Minimum ms between outgoing beacons. The steady state is the `heartbeat`\n * interval; this guard only bounds the *extra* beacons sent in response to\n * newly seen peers (see the roster-convergence note in the class doc), so a\n * burst of joiners cannot make this client flood the channel.\n */\nconst BEACON_THROTTLE_MS = 2000;\n\n/** Heartbeat period used when the declared one names no interval. */\nconst DEFAULT_HEARTBEAT_MS = 15_000;\n\n/** Peer expiry used when the declared one names no delay. */\nconst DEFAULT_TIMEOUT_MS = 40_000;\n\n/**\n * How many connected controllers currently speak for each peer, keyed by the\n * channel identifier and the peer's own id.\n *\n * The leaving notice says that a *peer* is gone, so only the last element\n * claiming that peer may send it. The shared subscription counts the members of\n * a wire identifier, which is a different set: elements claiming other ids —\n * and the other cable controllers — ride the same wire, and letting them speak\n * for this peer would either drop a peer that is still here or keep a departed\n * one in every roster until it expires.\n */\nconst speakers = new Map<string, number>();\n\n/**\n * Headless **presence** — a *server-bound* behavior: online dots / a\n * \"who's viewing this\" stack, bound to an Action Cable channel. Like\n * `stimeo--typing-indicator`, the server stays a trivial rebroadcast channel\n * with **zero presence state**: every client heartbeats an `appear` beacon\n * (`{ id, name }`), and each client expires peers it has not heard from for\n * `timeout` ms. Leaving (`{ id, leaving: true }`) is broadcast best-effort on\n * `disconnect()` and on `pagehide` (tab close / hard navigation, where\n * `disconnect()` never runs); a lost notice is caught by the expiry. Ships in\n * the opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * The notice speaks for a *peer*, not for a wire subscription: on `disconnect()`\n * only the last element claiming a given `id` on a given identifier sends it, so a\n * sibling that stays mounted keeps the peer in every roster. `pagehide` takes the\n * whole page with it, so there every element sends and peers absorb the duplicates.\n *\n * Roster convergence: a late joiner would otherwise see peers only as their\n * next heartbeats arrive, so on hearing a beacon from an *unknown* peer, each\n * client re-announces itself (at most one such answer every 2s) — the\n * roster converges in one round-trip instead of one heartbeat period.\n *\n * Markup contract (identifier: `stimeo--presence`):\n * <div data-controller=\"stimeo--presence\"\n * data-stimeo--presence-channel-value=\"PresenceChannel\"\n * data-stimeo--presence-params-value='{\"room\":\"doc_7\"}'\n * data-stimeo--presence-id-value=\"17\" data-stimeo--presence-name-value=\"Alice\">\n * <span data-stimeo--presence-target=\"count\" data-other=\"%{count} viewing\"></span>\n * <ul data-stimeo--presence-target=\"list\"></ul>\n * <template data-stimeo--presence-target=\"template\">\n * <li><span data-presence-name></span></li>\n * </template>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class PresenceChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"presence:#{params[:room]}\"\n * def appear(data) = ActionCable.server.broadcast(\"presence:#{params[:room]}\",\n * { id: data[\"id\"], name: data[\"name\"], leaving: data[\"leaving\"] })\n * end\n *\n * `join` dispatches `{ id, name }`; `leave` dispatches `{ id }`; `change` dispatches\n * `{ users }`.\n *\n * @remarks\n * Behavior only — the dot/stack look is the consumer's CSS, keyed off the\n * `data-present` / `data-present-count` hooks. The roster counts and renders\n * **other** clients (`id` ≠ own `id`); rendering is optional: given a `list` +\n * `template` pair, one clone per peer is appended (elements marked\n * `data-presence-name` receive the name; the clone root is tagged\n * `data-presence-id`), and the `count` target renders through localizable\n * `data-zero` / `data-one` / `data-other` templates (`%{count}`). Richer\n * per-user rendering (avatars, links) belongs to the consumer via the `join` /\n * `leave` / `change` events or a server-rendered Turbo Stream. A `template` holds\n * one root element: the clone appended for a peer, and the node removed when the\n * peer goes, are that same element. Roster changes reach assistive tech only when\n * the consumer opts in through `announceJoinText` / `announceLeaveText` — reading\n * out every entry and exit of a busy room is noise, so silence is the default. The `id`\n * comparison is display-level echo suppression, not authentication — identity\n * belongs to the server. Sending tracks the full subscription lifecycle (via\n * the shared confirmation-aware subscription): beacons — heartbeats, the\n * convergence answer, the leaving notice — are dropped before confirmation and\n * while the connection is down, where Action Cable would discard them anyway\n * (and a discarded beacon must not burn the convergence throttle); `connected`\n * re-fires on every reconnect and force-beacons, so the roster self-heals. A\n * refused subscription publishes the `data-presence-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Presence is transient: `connect()` clears\n * whatever a Turbo cache snapshot preserved (hooks + rendered clones), renders\n * the known-empty count (the `data-present*` hooks stay absent until the first\n * beacon), and the stream re-populates; a `count` or `list` target swapped in by a\n * Stream or a morph arrives empty and is drawn from the roster as it connects; the subscription, heartbeat interval,\n * per-peer expiry timers, and the `pagehide` listener are all released on\n * `disconnect()` (Turbo navigation included).\n */\nexport class PresenceController extends Controller<HTMLElement> {\n static override targets = [\"count\", \"list\", \"template\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n heartbeat: { type: Number, default: DEFAULT_HEARTBEAT_MS },\n timeout: { type: Number, default: DEFAULT_TIMEOUT_MS },\n announceJoinText: { type: String, default: \"\" },\n announceLeaveText: { type: String, default: \"\" },\n };\n static events = [\"join\", \"leave\", \"change\"] as const;\n\n declare readonly hasCountTarget: boolean;\n declare readonly countTarget: HTMLElement;\n declare readonly hasListTarget: boolean;\n declare readonly listTarget: HTMLElement;\n declare readonly hasTemplateTarget: boolean;\n declare readonly templateTarget: HTMLTemplateElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare nameValue: string;\n declare heartbeatValue: number;\n declare timeoutValue: number;\n declare announceJoinTextValue: string;\n declare announceLeaveTextValue: string;\n\n /** Delay (ms) before one roster change is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Present peers keyed by id (insertion order = join order). */\n readonly #peers = new Map<string, Peer>();\n readonly #timers = new SafeTimeout();\n readonly #intervals = new SafeInterval();\n /** Epoch ms of the last outgoing beacon, for the convergence throttle. */\n #lastBeaconAt = 0;\n /** Pending trailing-edge convergence beacon (at most one queued). */\n #pendingBeacon: number | null = null;\n /** The one outstanding announcement, so a newer roster change supersedes it. */\n #announceId: number | null = null;\n /** The `speakers` key this element holds, recomputed on every `connect()`. */\n #speakerKey = \"\";\n\n /**\n * Re-parses the identifier parameters when the declaration changes.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n }\n\n /**\n * The identifier this declaration names. `params` *adds* to the identifier, so a\n * `channel` key inside it names an extra parameter and never replaces the channel\n * the element declares.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _declared, ...rest } = this.#params;\n return { channel: this.channelValue, ...rest };\n }\n\n /**\n * The beacon period, in ms: a finite, positive number a timer can hold. Anything\n * else names no interval — `setInterval` reads `NaN`, a negative value, `Infinity`\n * and a value past {@link MAX_TIMER_DELAY_MS} alike as \"as often as possible\", so\n * the declaration would flood the channel rather than heartbeat on it. Zero is out\n * for the same reason, which is why this bound is tighter than the non-negative one\n * a throttle or a plain delay can use. Such a declaration falls back to the default.\n */\n get #heartbeat(): number {\n const declared = this.heartbeatValue;\n return Number.isFinite(declared) && declared > 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_HEARTBEAT_MS;\n }\n\n /**\n * The silence after which a peer is dropped, in ms: a finite, positive number a\n * timer can hold. Anything else names no delay and every peer expires in the task\n * that records it, so the roster could never hold anyone. Such a declaration falls\n * back to the default.\n */\n get #timeout(): number {\n const declared = this.timeoutValue;\n return Number.isFinite(declared) && declared > 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Draws the roster into a list target, which arrives empty from a Turbo Stream\n * replacement or a morph. Stimulus runs this before `connect()` on the first mount\n * too, where the roster is still empty and clearing whatever a cache snapshot\n * preserved is what `connect()` goes on to do anyway.\n */\n listTargetConnected(): void {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n for (const [id, peer] of this.#peers) this.#appendClone(id, peer.name);\n }\n\n /** Paints the roster size into a count target that arrives blank, for the same reason. */\n countTargetConnected(): void {\n this.countTarget.textContent = this.#countMessage(this.#peers.size);\n }\n\n override connect(): void {\n // Presence is transient: drop whatever a Turbo cache snapshot preserved\n // (hooks + rendered clones); the live stream re-populates the roster.\n // Rejection is transient server state too — the fresh subscription below\n // re-decides the hook.\n this.#reset();\n this.element.removeAttribute(\"data-presence-rejected\");\n // The roster is known-empty here, so the count target can say so right\n // away instead of sitting blank until the first roster change. The\n // data-present* hooks intentionally stay absent until the first beacon.\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(0);\n\n if (!this.channelValue) return;\n this.#claimSpeaker();\n this.#subscription = createConfirmedSubscription(this.#descriptor, {\n // The first beacon must wait for the confirmed subscription — a\n // perform() before that is silently dropped by Action Cable. Fires\n // again on every reconnect, so the roster self-heals after an outage.\n connected: () => this.#beacon(true),\n // The server refused the subscription: no beacon will ever go through,\n // and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-presence-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n this.#intervals.set(() => this.#beacon(true), this.#heartbeat);\n window.addEventListener(\"pagehide\", this.#onPageHide);\n }\n\n override disconnect(): void {\n window.removeEventListener(\"pagehide\", this.#onPageHide);\n // Best-effort graceful leave, and only from the last element speaking for\n // this peer; a lost notice is caught by peers' expiry timers instead.\n if (this.#releaseSpeaker()) this.#sendLeaveNotice();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#intervals.clearAll();\n this.#reset();\n this.element.removeAttribute(\"data-presence-rejected\");\n }\n\n /**\n * Registers this element as one of the voices for its peer, so a sibling on the\n * same channel claiming the same `id` keeps the peer present until they all go.\n * Whether the peer is worth announcing at all is {@link #sendLeaveNotice}'s\n * question, so an element with no own `id` claims a voice like any other and is\n * simply never heard.\n */\n #claimSpeaker(): void {\n this.#speakerKey = `${identifierOf(this.#descriptor)}\\n${this.idValue}`;\n speakers.set(this.#speakerKey, (speakers.get(this.#speakerKey) ?? 0) + 1);\n }\n\n /**\n * Gives up the claim, reporting whether this element was the last voice for the\n * peer. The key is spent here: an element that goes on to connect without a\n * channel claims nothing, and a stale key would spend a voice its siblings own.\n */\n #releaseSpeaker(): boolean {\n const key = this.#speakerKey;\n this.#speakerKey = \"\";\n const remaining = (speakers.get(key) ?? 1) - 1;\n if (remaining > 0) speakers.set(key, remaining);\n else speakers.delete(key);\n return remaining <= 0;\n }\n\n /**\n * Sends the best-effort leaving notice (skipped without an own `id`, and\n * outside the confirmed window, where Action Cable would discard it anyway).\n */\n #sendLeaveNotice(): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n this.#subscription.perform(\"appear\", { id: this.idValue, leaving: true });\n }\n\n /**\n * `pagehide` covers the leaves `disconnect()` cannot see: closing the tab or\n * a hard (non-Turbo) navigation destroys the page without running Stimulus\n * teardown, so this listener is the only chance to announce them. Best-effort\n * by nature (the socket may close before the frame flushes); peers' expiry\n * timers stay the safety net. If the page enters the bfcache and is restored\n * instead, the next heartbeat re-announces this client, so an over-eager\n * leave self-heals.\n */\n readonly #onPageHide = (): void => {\n this.#sendLeaveNotice();\n };\n\n /**\n * Broadcasts this client's beacon (skipped without an own `id`). A throttled\n * convergence answer is deferred to the trailing edge rather than dropped —\n * otherwise a peer joining right after a heartbeat would not learn about\n * this client until the next full heartbeat period.\n *\n * Gated on the confirmed subscription: before confirmation and during an\n * outage Action Cable silently discards perform(), so a beacon sent then is\n * pure waste — worse, it would burn `#lastBeaconAt` and delay the next real\n * convergence answer by up to the throttle window. The heartbeat interval\n * keeps ticking regardless; `connected` re-fires on reconfirm and\n * force-beacons immediately, so a gated tick is never missed for long.\n */\n #beacon(force: boolean): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n const now = Date.now();\n const wait = BEACON_THROTTLE_MS - (now - this.#lastBeaconAt);\n if (!force && wait > 0) {\n if (this.#pendingBeacon === null) {\n this.#pendingBeacon = this.#timers.set(() => {\n this.#pendingBeacon = null;\n this.#beacon(true);\n }, wait);\n }\n return;\n }\n this.#lastBeaconAt = now;\n this.#subscription.perform(\"appear\", { id: this.idValue, name: this.nameValue });\n }\n\n /**\n * Tracks a broadcast beacon: upserts the peer (restarting its expiry timer),\n * removes it on a `leaving` notice, and re-announces this client when the\n * peer was unknown (roster convergence — see the class doc).\n */\n #onReceived(data: unknown): void {\n const beacon = data as { id?: unknown; name?: unknown; leaving?: unknown } | null;\n const id = beacon?.id;\n if (typeof id !== \"string\" || id === \"\" || id === this.idValue) return;\n\n if (beacon?.leaving === true) {\n this.#drop(id);\n return;\n }\n\n const name = typeof beacon?.name === \"string\" ? beacon.name : \"\";\n const existing = this.#peers.get(id);\n if (existing !== undefined) this.#timers.clear(existing.timer);\n const timer = this.#timers.set(() => this.#drop(id), this.#timeout);\n this.#peers.set(id, { name, timer });\n\n if (existing === undefined) {\n this.#appendClone(id, name);\n this.#render();\n this.#announce(this.announceJoinTextValue, name);\n this.dispatch(\"join\", { detail: { id, name } });\n this.#beacon(false); // answer an unknown peer so its roster converges\n } else if (existing.name !== name) {\n this.#updateClone(id, name);\n this.#render();\n }\n }\n\n /** Removes a peer (expiry or graceful leave) and reflects the change. */\n #drop(id: string): void {\n const peer = this.#peers.get(id);\n if (peer === undefined) return;\n this.#timers.clear(peer.timer);\n this.#peers.delete(id);\n this.#removeClone(id);\n this.#render();\n this.#announce(this.announceLeaveTextValue, peer.name);\n this.dispatch(\"leave\", { detail: { id } });\n }\n\n /** Reflects the roster onto the hooks + count target and emits `change`. */\n #render(): void {\n const users = [...this.#peers.entries()].map(([id, peer]) => ({ id, name: peer.name }));\n this.element.setAttribute(\"data-present\", users.length > 0 ? \"true\" : \"false\");\n this.element.setAttribute(\"data-present-count\", String(users.length));\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(users.length);\n this.dispatch(\"change\", { detail: { users } });\n }\n\n /**\n * Hands one roster change to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per peer — reading\n * out every entry and exit of a busy room is the noise the default avoids. Wording\n * comes from the consumer (`{name}` / `{count}`, where the count is the roster\n * after the change), and an undeclared template does nothing at all — not even\n * cancel an announcement already pending for the other direction.\n */\n #announce(template: string, name: string): void {\n const message = fillTemplate(template, { name, count: this.#peers.size });\n if (message.trim() === \"\") return;\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, PresenceController.#announceDelay);\n }\n\n /**\n * Builds the count copy. Localizable through `data-zero` / `data-one` /\n * `data-other` templates on the count target (`%{count}`); the bare number is\n * the fallback (copy-free, so nothing to localize by default).\n */\n #countMessage(count: number): string {\n const templates = this.countTarget.dataset;\n const template =\n (count === 0 ? templates.zero : count === 1 ? templates.one : templates.other) ??\n templates.other;\n return template ? template.replace(\"%{count}\", String(count)) : String(count);\n }\n\n /** Appends one template clone for a newly present peer (list + template only). */\n #appendClone(id: string, name: string): void {\n if (!this.hasListTarget || !this.hasTemplateTarget) return;\n const root = this.templateTarget.content.firstElementChild?.cloneNode(true);\n if (!(root instanceof Element)) return;\n root.setAttribute(\"data-presence-id\", id);\n this.#fillName(root, name);\n this.listTarget.appendChild(root);\n }\n\n #updateClone(id: string, name: string): void {\n const root = this.#cloneFor(id);\n if (root) this.#fillName(root, name);\n }\n\n #removeClone(id: string): void {\n this.#cloneFor(id)?.remove();\n }\n\n #cloneFor(id: string): Element | null {\n if (!this.hasListTarget) return null;\n // Attribute selectors cannot escape arbitrary ids reliably; match manually.\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n if (child.getAttribute(\"data-presence-id\") === id) return child;\n }\n return null;\n }\n\n /** Writes the peer's name into the clone's `data-presence-name` slots. */\n #fillName(root: Element, name: string): void {\n const slots = root.querySelectorAll(\"[data-presence-name]\");\n for (const slot of slots) slot.textContent = name;\n if (slots.length === 0 && root.hasAttribute(\"data-presence-name\")) {\n root.textContent = name;\n }\n }\n\n /** Clears the transient roster state (connect reset + disconnect teardown). */\n #reset(): void {\n this.#timers.clearAll();\n this.#pendingBeacon = null;\n this.#announceId = null;\n this.#peers.clear();\n this.#lastBeaconAt = 0;\n this.element.removeAttribute(\"data-present\");\n this.element.removeAttribute(\"data-present-count\");\n if (this.hasCountTarget) this.countTarget.textContent = \"\";\n if (this.hasListTarget) {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { MAX_TIMER_DELAY_MS, SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** Milliseconds of silence after which a typer is dropped. */\nconst DEFAULT_TIMEOUT = 3000;\n/** Minimum milliseconds between outgoing typing signals. */\nconst DEFAULT_THROTTLE = 2000;\n\n/**\n * Fills `%{token}` placeholders in a status template, in one pass.\n *\n * The values come off the wire — another client's display name — so they must never\n * reach a replacement *string*, where `$&`, `` $` ``, `$'` and `$$` expand into the\n * template's own text. A single pass also stops a name that happens to contain a token\n * from being re-substituted by a later one. Every occurrence is filled; a token this\n * call has no value for is left as authored, which keeps a typo visible instead of\n * blanking the word.\n */\nconst fillTokens = (template: string, values: Record<string, string>): string =>\n template.replace(/%\\{(name|names|count)\\}/g, (match, token: string) => values[token] ?? match);\n\n/**\n * Headless **typing indicator** — a *server-bound* behavior: a controller whose\n * state lives on the server stream, not in client memory. Typing in the composer\n * throttle-broadcasts a `typing` signal over an Action Cable channel; received\n * signals from *other* clients render \"X is typing…\" into a visible `status` slot\n * and auto-clear after `timeout` ms of silence. The whole behavior is HTML + a\n * broadcast — no app JS, no client store, no reconciler. Ships in the opt-in\n * `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer); the core stays\n * zero-dep.\n *\n * Markup contract (identifier: `stimeo--typing-indicator`):\n * <div data-controller=\"stimeo--typing-indicator\"\n * data-stimeo--typing-indicator-channel-value=\"TypingChannel\"\n * data-stimeo--typing-indicator-params-value='{\"room\":\"chat_42\"}'\n * data-stimeo--typing-indicator-name-value=\"Alice\"\n * data-stimeo--typing-indicator-announce-one-text-value=\"{name} is typing\">\n * <textarea data-stimeo--typing-indicator-target=\"input\"></textarea>\n * <p data-stimeo--typing-indicator-target=\"status\"\n * data-one=\"%{name} is typing…\" data-many=\"%{names} are typing…\"></p>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class TypingChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"typing:#{params[:room]}\"\n * def typing(data) = ActionCable.server.broadcast(\"typing:#{params[:room]}\",\n * { name: data[\"name\"] })\n * end\n *\n * The `status` target is a plain visible slot, not a live region: assistive tech is\n * reached through the page's shared announcer instead, so a set that settles is sent\n * there as `announceOneText` / `announceManyText` (`{name}` / `{names}` / `{count}`,\n * debounced). Both default to empty, and an empty message announces nothing — a page\n * that wants the indicator seen but not heard simply leaves them off.\n *\n * `change` dispatches `{ names }`.\n *\n * @remarks\n * Behavior only — the indicator's look is the consumer's CSS, keyed off the\n * `data-typing` hook; the copy is localizable through the `data-one` /\n * `data-many` templates (`%{name}` / `%{names}` / `%{count}`, terse English\n * fallback — the same channel design as `stimeo--sortable`). The own echo is\n * dropped by comparing the broadcast `name` against `name` (a same-name guard,\n * not authentication — identity belongs to the server). Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * signals are dropped before confirmation and while the connection is down, so\n * an outage never burns the throttle window on undeliverable sends. A refused\n * subscription publishes the `data-typing-indicator-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Typing state is transient by nature:\n * `connect()` resets the hook and the status slot (a Turbo cache snapshot must\n * not resurrect a stale \"X is typing…\"), and re-population happens naturally\n * from the stream. The subscription, the per-typer timers, and the delegated\n * `input` listener are all released on `disconnect()` (Turbo navigation\n * included).\n */\nexport class TypingIndicatorController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"status\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n timeout: { type: Number, default: DEFAULT_TIMEOUT },\n // Must stay below `timeout`: the throttle is leading-edge only (no trailing\n // send), so a receiver's display survives continuous typing only while a\n // fresh signal lands within its timeout window.\n throttle: { type: Number, default: DEFAULT_THROTTLE },\n announceOneText: { type: String, default: \"\" },\n announceManyText: { type: String, default: \"\" },\n };\n static events = [\"change\"] as const;\n\n declare readonly hasStatusTarget: boolean;\n declare readonly statusTarget: HTMLElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare nameValue: string;\n declare timeoutValue: number;\n declare throttleValue: number;\n declare announceOneTextValue: string;\n declare announceManyTextValue: string;\n\n /** Delay (ms) before one settled typer set is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Names currently typing (other clients), each with its auto-clear timer id. */\n readonly #typers = new Map<string, number>();\n readonly #timers = new SafeTimeout();\n /** Epoch ms of the last broadcast, for leading-edge throttling. */\n #lastSentAt = 0;\n /** The one outstanding announcement, so a newer set supersedes it. */\n #announceId: number | null = null;\n\n /**\n * Re-parses the identifier parameters when the declaration changes.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n }\n\n /**\n * Paints the current copy into a `status` target that arrived at runtime.\n *\n * No event: the set of typers did not change, only the element that displays it.\n * Without this a region swapped in mid-conversation stays empty while `data-typing`\n * still says someone is typing, leaving the state in the visual hook alone. An empty\n * set needs no paint — a fresh region already shows it.\n */\n statusTargetConnected(): void {\n if (this.#typers.size > 0) this.#paint();\n }\n\n override connect(): void {\n // Typing state is transient: a Turbo cache snapshot must not resurrect a\n // stale indicator, and the live stream re-populates naturally. Rejection is\n // transient server state too — the fresh subscription re-decides the hook.\n this.element.removeAttribute(\"data-typing\");\n this.element.removeAttribute(\"data-typing-indicator-rejected\");\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n\n // Delegated on the container so the composer needs no per-input data-action\n // (and swapped/appended inputs keep working).\n this.element.addEventListener(\"input\", this.#onInput);\n if (this.channelValue) {\n // Confirmation tracking (connected / disconnected / rejected) lives in\n // the shared subscription; #onInput gates on its `confirmed` so an\n // outage doesn't burn the throttle window on dropped sends.\n this.#subscription = createConfirmedSubscription(\n { channel: this.channelValue, ...this.#params },\n {\n // The server refused the subscription: the send gate stays shut for\n // good, and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-typing-indicator-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n },\n );\n }\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"input\", this.#onInput);\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#timers.clearAll();\n this.#announceId = null;\n this.#typers.clear();\n this.element.removeAttribute(\"data-typing\");\n this.element.removeAttribute(\"data-typing-indicator-rejected\");\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n this.#lastSentAt = 0;\n }\n\n /** Throttled (leading-edge) broadcast of this client's typing signal. */\n readonly #onInput = (): void => {\n // Before the confirmed subscription a perform() is silently dropped by\n // Action Cable — and recording #lastSentAt would ALSO throttle away the\n // first real send, so bail without touching the throttle state.\n if (!this.#subscription?.confirmed) return;\n const now = Date.now();\n if (now - this.#lastSentAt < this.#throttle) return;\n this.#lastSentAt = now;\n this.#subscription.perform(\"typing\", { name: this.nameValue });\n };\n\n /**\n * Tracks a broadcast typer. The own echo is dropped (same `name`); every\n * further signal from a name restarts its auto-clear timer, so the indicator\n * survives continuous typing and clears `timeout` ms after the last signal.\n */\n #onReceived(data: unknown): void {\n const name = (data as { name?: unknown } | null)?.name;\n if (typeof name !== \"string\" || name === \"\" || name === this.nameValue) return;\n\n const existing = this.#typers.get(name);\n if (existing !== undefined) this.#timers.clear(existing);\n const added = existing === undefined;\n this.#typers.set(\n name,\n this.#timers.set(() => this.#untrack(name), this.#timeout),\n );\n if (added) this.#render();\n }\n\n /**\n * The silence after which a typer is dropped, in ms: a finite, non-negative number\n * a timer can hold. Anything else names no delay — `setTimeout` reads `NaN`, a\n * negative value and `Infinity` alike as \"now\", and a value past\n * {@link MAX_TIMER_DELAY_MS} overflows to the same place — so the typer would\n * vanish in the same task it appeared and the indicator could never be seen. Such\n * a declaration falls back to the default.\n */\n get #timeout(): number {\n const declared = this.timeoutValue;\n return Number.isFinite(declared) && declared >= 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_TIMEOUT;\n }\n\n /**\n * The minimum gap between outgoing signals, in ms: a finite, non-negative number.\n * Anything else names no interval, and the gate then settles the same way at every\n * keystroke — `NaN` and a negative gap leave it open, so every keystroke broadcasts,\n * while `Infinity` is never exceeded, so nothing is ever sent. Such a declaration\n * falls back to the default.\n */\n get #throttle(): number {\n const declared = this.throttleValue;\n return Number.isFinite(declared) && declared >= 0 ? declared : DEFAULT_THROTTLE;\n }\n\n #untrack(name: string): void {\n this.#typers.delete(name);\n this.#render();\n }\n\n /** Reflects the typer set onto the display, announces it, and emits `change`. */\n #render(): void {\n const names = this.#paint();\n this.#announce(names);\n this.dispatch(\"change\", { detail: { names } });\n }\n\n /**\n * Hands the settled typer set to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per name. Only a\n * non-empty set is announced — that typing stopped is not news worth interrupting\n * a reader for, and the visible copy already clears. Wording comes from the\n * consumer, and {@link announce} drops an empty message, so an undeclared template\n * announces nothing at all.\n */\n #announce(names: string[]): void {\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = null;\n if (names.length === 0) return;\n\n const message =\n names.length === 1\n ? fillTemplate(this.announceOneTextValue, { name: names[0] ?? \"\" })\n : fillTemplate(this.announceManyTextValue, {\n names: names.join(\", \"),\n count: names.length,\n });\n\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, TypingIndicatorController.#announceDelay);\n }\n\n /** Writes the current typer set onto the hook and the status slot. */\n #paint(): string[] {\n const names = [...this.#typers.keys()];\n this.element.setAttribute(\"data-typing\", names.length > 0 ? \"true\" : \"false\");\n if (this.hasStatusTarget) {\n this.statusTarget.textContent = this.#message(names);\n }\n return names;\n }\n\n /**\n * Builds the status copy. Localizable through `data-one` / `data-many`\n * templates on the status target (`%{name}` / `%{names}` / `%{count}`); terse\n * English is the fallback.\n */\n #message(names: string[]): string {\n if (names.length === 0) return \"\";\n const joined = names.join(\", \");\n if (names.length === 1) {\n const template = this.statusTarget.dataset.one;\n const name = names[0] ?? \"\";\n return template ? fillTokens(template, { name }) : `${name} is typing…`;\n }\n const template = this.statusTarget.dataset.many;\n return template\n ? fillTokens(template, { names: joined, count: String(names.length) })\n : `${joined} are typing…`;\n }\n}\n","import type { Application } from \"@hotwired/stimulus\";\nimport { LiveCounterController } from \"./live_counter_controller\";\nimport { PresenceController } from \"./presence_controller\";\nimport { TypingIndicatorController } from \"./typing_indicator_controller\";\n\nexport {\n type CableConsumer,\n type CableSubscription,\n type CableSubscriptionMixin,\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n getCableConsumer,\n setCableConsumer,\n} from \"./consumer\";\nexport { LiveCounterController, PresenceController, TypingIndicatorController };\n\n/**\n * Opt-in **server-bound** behaviors for Stimeo — controllers whose state lives\n * on an Action Cable / Turbo stream rather than in client memory: presence,\n * typing indicators, live counters.\n *\n * **Why this is a separate entry point.** The core library is zero-runtime-dep:\n * `import \"stimeo-ui\"` pulls in nothing but `@hotwired/stimulus`. Binding the\n * DOM to a server stream genuinely needs `@rails/actioncable`, so — exactly\n * like `stimeo-ui/positioning` and `@floating-ui/dom` — that cost is opt-in:\n * this module lives at `stimeo-ui/cable`, `@rails/actioncable` is an *optional*\n * peer, and nothing in the core imports it.\n *\n * When the app already owns a consumer (`app/javascript/channels/consumer.js`),\n * hand it over once at boot so no second websocket is opened:\n *\n * ```ts\n * import consumer from \"./channels/consumer\";\n * import { registerCable, setCableConsumer } from \"stimeo-ui/cable\";\n *\n * setCableConsumer(consumer);\n * registerCable(application);\n * ```\n */\nexport const cableControllers = {\n \"stimeo--live-counter\": LiveCounterController,\n \"stimeo--presence\": PresenceController,\n \"stimeo--typing-indicator\": TypingIndicatorController,\n} as const;\n\n/**\n * Registers the opt-in server-bound controllers on a Stimulus Application\n * (additive to `registerStimeo`, mirroring `registerPositioning`).\n */\nexport function registerCable(application: Application): void {\n for (const [identifier, controller] of Object.entries(cableControllers)) {\n application.register(identifier, controller);\n }\n}\n"]}
|
|
@@ -8,7 +8,7 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
8
8
|
* <h3>
|
|
9
9
|
* <button id="trigger-1"
|
|
10
10
|
* data-stimeo--accordion-target="trigger"
|
|
11
|
-
* data-action="stimeo--accordion#toggle
|
|
11
|
+
* data-action="click->stimeo--accordion#toggle
|
|
12
12
|
* keydown->stimeo--accordion#onKeydown"
|
|
13
13
|
* aria-expanded="false" aria-controls="panel-1">Section 1</button>
|
|
14
14
|
* </h3>
|