stimeo-ui 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/dist/controllers/alert_dialog_controller.d.ts +2 -0
- package/dist/controllers/alert_dialog_controller.js +75 -4
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +2 -0
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +47 -5
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +2 -0
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +2 -0
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -0
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +52 -20
- package/dist/controllers/character_counter_controller.js +338 -63
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +3 -0
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +3 -0
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +2 -0
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +2 -0
- package/dist/controllers/command_palette_controller.js +75 -4
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +42 -14
- package/dist/controllers/conditional_fields_controller.js +345 -51
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +2 -0
- package/dist/controllers/confirm_controller.js +75 -4
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +2 -0
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +4 -0
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +2 -0
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +3 -0
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.js +75 -4
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +56 -24
- package/dist/controllers/direct_upload_controller.js +201 -45
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +14 -6
- package/dist/controllers/dirty_form_controller.js +192 -29
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +2 -0
- package/dist/controllers/dismissible_controller.js +83 -18
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +75 -4
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +2 -0
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +2 -0
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +2 -0
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +4 -0
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -0
- package/dist/controllers/focus_controller.js +75 -4
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.d.ts +50 -10
- package/dist/controllers/form_field_controller.js +280 -62
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +10 -8
- package/dist/controllers/form_validation_controller.js +208 -83
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +2 -0
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +2 -0
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +2 -0
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +2 -0
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +2 -0
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +2 -0
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +2 -0
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +2 -0
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +2 -0
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +2 -0
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +2 -0
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +2 -0
- package/dist/controllers/number_input_controller.js +47 -5
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +3 -0
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +2 -0
- package/dist/controllers/overflow_menu_controller.js +2 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +2 -0
- package/dist/controllers/pagination_controller.js +2 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +2 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +2 -0
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +30 -16
- package/dist/controllers/persist_controller.js +432 -122
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +3 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.js +77 -4
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +2 -0
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +2 -0
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +3 -0
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +3 -0
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +2 -0
- package/dist/controllers/rating_controller.js +9 -5
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +2 -0
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +2 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +2 -0
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +23 -15
- package/dist/controllers/scroll_area_controller.js +462 -162
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +2 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +54 -11
- package/dist/controllers/separator_controller.js +354 -38
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.js +83 -10
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -0
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +2 -0
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +2 -0
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +2 -0
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +2 -0
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +94 -38
- package/dist/controllers/submit_once_controller.js +399 -121
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +2 -0
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +2 -0
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -0
- package/dist/controllers/theme_controller.js +8 -6
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +2 -0
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +4 -0
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +2 -0
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -0
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +2 -0
- package/dist/controllers/tree_view_controller.js +2 -1
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +2387 -861
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +107 -3
- package/dist/inspector/cli.js +192 -14
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +254 -46
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +7 -7
- package/dist/inspector/manifest.json +434 -44
- package/dist/positioning/index.d.ts +2 -0
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/roving_tabindex.ts","../../src/utils/chip_row.ts","../../src/utils/composition_tracker.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/tags_input_controller.ts"],"names":["entry","button"],"mappings":"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","file":"tags_input_controller.js","sourcesContent":["/**\n * Sends one message to the page's shared `stimeo--announcer`.\n *\n * A component that has to reach assistive tech does not carry a live region of its\n * own: a region only announces what changes *after* assistive tech already knows\n * about it, which a region that appears (or is un-hidden) with its message cannot\n * satisfy. The one region that can is the announcer sitting in the page from the\n * start, so state changes are handed to it as an event and it does the reading.\n *\n * The event goes to `window` because the announcer is usually a sibling high in the\n * document rather than an ancestor of the component dispatching it.\n *\n * Wording comes from the consumer — the library ships no English strings — so an\n * empty message is silently dropped and nothing is announced.\n *\n * @example\n * ```ts\n * announce(this.announceTextValue, { assertive: false });\n * ```\n */\nexport function announce(message: string, options: { assertive?: boolean } = {}): void {\n const text = message.trim();\n if (text.length === 0) return;\n window.dispatchEvent(\n new CustomEvent(\"stimeo--announcer:announce\", {\n detail: { message: text, assertive: options.assertive === true },\n }),\n );\n}\n\n/**\n * Fills `{name}` placeholders in an announcement template from `values`.\n *\n * The same substitution the value-text templates use, so a consumer writes\n * `\"{percent}% complete\"` in one attribute and gets the same rules everywhere. A\n * placeholder with no matching entry is left as authored rather than blanked, which\n * keeps a typo visible instead of silently swallowing the word.\n */\nexport function fillTemplate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, name: string) => {\n const replacement = values[name];\n return replacement === undefined ? match : String(replacement);\n });\n}\n","/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Low-level roving-tabindex primitive shared by composite-widget controllers.\n *\n * The APG roving-tabindex pattern keeps a composite widget a single Tab stop:\n * exactly one item is in the Tab sequence (`tabindex=\"0\"`) while the rest are\n * removed from it (`tabindex=\"-1\"`), and the arrow keys move both DOM focus and\n * that single tabbable position together. {@link RovingTabindex} owns *only* that\n * mechanical bookkeeping — \"which one item is tabbable, and move focus there\".\n *\n * It is intentionally **policy-free**. Orientation, wrapping vs. clamping,\n * selection-follows-focus, typeahead, and `Home`/`End` semantics differ per APG\n * pattern (Radio Group, Toolbar, Rating, …); folding them into one helper would\n * flatten those widgets to a lowest common denominator and lose each pattern's\n * correctness. Those decisions therefore stay in each controller, which calls\n * {@link RovingTabindex.setActive} with an index it computed itself (optionally\n * via the pure {@link rovingMove} helper).\n *\n * @remarks\n * Items are read lazily through a getter so a controller can add or remove\n * targets (Stimulus re-scans the DOM) without re-wiring this helper. A caller\n * that already captured one event-scoped snapshot may pass it to\n * {@link RovingTabindex.setActive}, avoiding a second live-target scan while\n * preserving one coherent collection for the whole operation.\n */\nexport class RovingTabindex {\n /** Returns the current ordered item elements; called on every operation. */\n readonly #getItems: () => HTMLElement[];\n\n /**\n * @param getItems - Returns the current ordered item elements. Called on every\n * operation so the live target list is always used.\n */\n constructor(getItems: () => HTMLElement[]) {\n this.#getItems = getItems;\n }\n\n /** Index of the currently tabbable item (`tabindex=\"0\"`), or `-1` if none. */\n get activeIndex(): number {\n return this.#getItems().findIndex((item) => item.tabIndex === 0);\n }\n\n /**\n * Makes exactly the item at `index` tabbable (`tabindex=\"0\"`) and removes every\n * other item from the Tab sequence (`tabindex=\"-1\"`). An out-of-range `index`\n * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express\n * \"nothing is currently tabbable\".\n *\n * @param index - Position of the item to make tabbable.\n * @param options - Pass `{ focus: true }` to also move DOM focus to that item,\n * and `items` to reuse an event-scoped collection snapshot.\n */\n setActive(\n index: number,\n options: { focus?: boolean; items?: readonly HTMLElement[] } = {},\n ): void {\n const { focus = false } = options;\n const items = options.items ?? this.#getItems();\n items.forEach((item, i) => {\n item.tabIndex = i === index ? 0 : -1;\n });\n if (focus) items[index]?.focus();\n }\n}\n\n/** Edge behavior for {@link rovingMove}: cycle past the ends, or stop at them. */\nexport type RovingWrap = \"wrap\" | \"clamp\";\n\n/**\n * Pure helper that resolves the target index for a one-step directional move.\n *\n * Keyboard/orientation mapping stays in the caller: it decides that a key means\n * `delta` `+1` (next) or `-1` (previous) and whether the widget should `\"wrap\"`\n * (Radio Group, Toolbar) or `\"clamp\"` at the ends.\n *\n * @param current - The index focus is moving from.\n * @param length - Number of items in the set.\n * @param delta - `+1` to move to the next item, `-1` for the previous.\n * @param wrap - `\"wrap\"` cycles around the ends; `\"clamp\"` stops at them.\n * @returns The resolved index, or `-1` when there are no items.\n */\nexport function rovingMove(\n current: number,\n length: number,\n delta: number,\n wrap: RovingWrap,\n): number {\n if (length === 0) return -1;\n const next = current + delta;\n if (wrap === \"wrap\") return (next + length) % length;\n return Math.min(length - 1, Math.max(0, next));\n}\n","import { isReservedArrowChord, logicalArrowKey } from \"./arrow_step\";\nimport { RovingTabindex } from \"./roving_tabindex\";\n\n/** Consumer-owned behavior that differs between removable-chip widgets. */\nexport interface ChipRowOptions {\n /** Element whose writing direction defines logical previous/next. */\n readonly directionElement: HTMLElement;\n /** Returns the current chip elements in navigation order. */\n readonly getItems: () => readonly HTMLElement[];\n /** Resolves the one remove button owned by a chip. Defaults to its first button. */\n readonly getButton?: (item: HTMLElement) => HTMLButtonElement | null;\n /** Removes the chip currently occupying `index`. */\n readonly onRemove: (index: number) => void;\n /** Receives focus when navigation moves past the logical end of the row. */\n readonly focusAfterEnd: () => void;\n}\n\n/**\n * Delegated interaction and roving-tabindex mechanics for a row of removable chips.\n *\n * The selection model stays in the consuming controller: this helper knows only\n * how to resolve one consumer-declared remove button per chip, move between those\n * buttons, delegate click/delete requests by index, and bind those listeners to\n * a replaceable row target. Restricting the button set to the declared chip items\n * deliberately excludes sibling controls such as an authored \"Clear all\" button.\n */\nexport class ChipRow {\n readonly #directionElement: HTMLElement;\n readonly #getItems: () => readonly HTMLElement[];\n readonly #getButton: (item: HTMLElement) => HTMLButtonElement | null;\n readonly #onRemove: (index: number) => void;\n readonly #focusAfterEnd: () => void;\n readonly #roving = new RovingTabindex(() => this.buttons);\n #container: HTMLElement | null = null;\n\n constructor(options: ChipRowOptions) {\n this.#directionElement = options.directionElement;\n this.#getItems = options.getItems;\n this.#getButton = options.getButton ?? ((item) => item.querySelector(\"button\"));\n this.#onRemove = options.onRemove;\n this.#focusAfterEnd = options.focusAfterEnd;\n }\n\n /**\n * Binds delegation to `container`; any row currently bound is released first.\n * Idempotent for the same node, so target callbacks may call it freely.\n */\n connect(container: HTMLElement): void {\n if (this.#container === container) return;\n this.disconnect();\n this.#container = container;\n container.addEventListener(\"click\", this.#onClick);\n container.addEventListener(\"keydown\", this.#onKeydown);\n }\n\n /**\n * Releases the current row. When `container` is supplied, a stale disconnect\n * callback cannot tear listeners off a newer replacement target.\n */\n disconnect(container?: HTMLElement): void {\n const current = this.#container;\n if (!current || (container !== undefined && current !== container)) return;\n current.removeEventListener(\"click\", this.#onClick);\n current.removeEventListener(\"keydown\", this.#onKeydown);\n this.#container = null;\n }\n\n /** Current remove buttons, at most one consumer-resolved button per chip item. */\n get buttons(): HTMLButtonElement[] {\n return this.#entries.map(({ button }) => button);\n }\n\n /** Number of currently navigable remove buttons. */\n get length(): number {\n return this.buttons.length;\n }\n\n /** Declared-item index of the last navigable chip, or `-1` for an empty row. */\n get lastIndex(): number {\n return this.#entries.at(-1)?.itemIndex ?? -1;\n }\n\n /** Keeps exactly one button tabbable, preferring the first authored Tab stop. */\n ensureTabStop(): void {\n const buttons = this.#entries.map(({ button }) => button);\n const active = buttons.findIndex((button) => button.tabIndex === 0);\n const count = buttons.filter((button) => button.tabIndex === 0).length;\n if (buttons.length > 0 && count !== 1) {\n this.#roving.setActive(active === -1 ? 0 : active, { items: buttons });\n }\n }\n\n /** Focuses the last chip, returning false for an empty row. */\n focusLast(): boolean {\n const buttons = this.#entries.map(({ button }) => button);\n const last = buttons.length - 1;\n if (last < 0) return false;\n this.#roving.setActive(last, { focus: true, items: buttons });\n return true;\n }\n\n /**\n * Focuses the chip that followed a removed index, or the new last chip.\n * Returns false when removal emptied the row so the consumer can rescue focus.\n */\n focusAfterRemoval(index: number): boolean {\n const entries = this.#entries;\n if (entries.length === 0) return false;\n const following = entries.findIndex((entry) => entry.itemIndex >= index);\n this.#roving.setActive(following === -1 ? entries.length - 1 : following, {\n focus: true,\n items: entries.map(({ button }) => button),\n });\n return true;\n }\n\n /** Current remove-button entries, retaining each button's declared item index. */\n get #entries(): Array<{ button: HTMLButtonElement; itemIndex: number }> {\n return this.#getItems().flatMap((item, itemIndex) => {\n const button = this.#getButton(item);\n return button ? [{ button, itemIndex }] : [];\n });\n }\n\n /** Resolves a delegated event to the remove button owned by a declared chip. */\n #entry(event: Event): {\n button: HTMLButtonElement;\n buttonIndex: number;\n buttons: HTMLButtonElement[];\n itemIndex: number;\n } | null {\n const target = event.target;\n if (!(target instanceof Element)) return null;\n const button = target.closest<HTMLButtonElement>(\"button\");\n const entries = this.#entries;\n const buttonIndex = entries.findIndex((entry) => entry.button === button);\n if (buttonIndex === -1) return null;\n const entry = entries[buttonIndex] as { button: HTMLButtonElement; itemIndex: number };\n return { ...entry, buttonIndex, buttons: entries.map(({ button }) => button) };\n }\n\n /** Delegates removal clicks without waiting for Stimulus to wire a new chip. */\n readonly #onClick = (event: MouseEvent): void => {\n const entry = this.#entry(event);\n if (!entry) return;\n this.#onRemove(entry.itemIndex);\n };\n\n /** Applies the shared logical-arrow and removal policy within the chip row. */\n readonly #onKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || isReservedArrowChord(event)) return;\n const entry = this.#entry(event);\n if (!entry) return;\n const buttons = entry.buttons;\n const index = entry.buttonIndex;\n\n switch (logicalArrowKey(event.key, this.#directionElement)) {\n case \"ArrowLeft\":\n if (index > 0) {\n event.preventDefault();\n this.#roving.setActive(index - 1, { focus: true, items: buttons });\n }\n break;\n case \"ArrowRight\":\n event.preventDefault();\n if (index < buttons.length - 1) {\n this.#roving.setActive(index + 1, { focus: true, items: buttons });\n } else {\n this.#focusAfterEnd();\n }\n break;\n case \"Delete\":\n case \"Backspace\":\n event.preventDefault();\n this.#onRemove(entry.itemIndex);\n break;\n default:\n break;\n }\n };\n}\n","/** Minimal event shape carrying the platform's per-event composition signal. */\nexport interface CompositionSignal {\n readonly isComposing?: boolean;\n}\n\n/** Hooks that keep component-specific work outside {@link CompositionTracker}. */\nexport interface CompositionTrackerOptions {\n /** Runs after lifecycle state is set for a `compositionstart` event. */\n readonly onStart?: (event: Event) => void;\n /** Runs after lifecycle state is cleared for a `compositionend` event. */\n readonly onEnd?: (event: Event) => void;\n}\n\n/**\n * Owns IME composition listeners and transient state for one or more event targets.\n *\n * Some browsers omit `KeyboardEvent.isComposing` on the keydown that confirms a\n * conversion. Tracking `compositionstart` through `compositionend` supplies the\n * missing lifecycle signal without relying on deprecated numeric key codes.\n * Component policy stays with the consumer: filtering, validation, and submission\n * belong in the optional {@link CompositionTrackerOptions.onEnd} callback.\n *\n * @example\n * ```ts\n * #composition = new CompositionTracker({ onEnd: () => this.filter() });\n *\n * connect(): void {\n * this.#composition.observe(this.inputTarget);\n * }\n *\n * disconnect(): void {\n * this.#composition.disconnect();\n * }\n *\n * onKeydown(event: KeyboardEvent): void {\n * if (this.#composition.isComposing(event)) return;\n * }\n * ```\n */\nexport class CompositionTracker {\n readonly #observedTargets = new Set<EventTarget>();\n readonly #activeTargets = new Set<EventTarget>();\n readonly #onStart: ((event: Event) => void) | undefined;\n readonly #onEnd: ((event: Event) => void) | undefined;\n\n constructor(options: CompositionTrackerOptions = {}) {\n this.#onStart = options.onStart;\n this.#onEnd = options.onEnd;\n }\n\n /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */\n observe(target: EventTarget): void {\n if (this.#observedTargets.has(target)) return;\n target.addEventListener(\"compositionstart\", this.#handleStart);\n target.addEventListener(\"compositionend\", this.#handleEnd);\n this.#observedTargets.add(target);\n }\n\n /** Stops tracking one target and clears any active composition it owned. */\n unobserve(target: EventTarget): void {\n if (!this.#observedTargets.delete(target)) return;\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n this.#activeTargets.delete(target);\n }\n\n /** Releases every listener and clears state so reconnect starts cleanly. */\n disconnect(): void {\n for (const target of this.#observedTargets) {\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n }\n this.#observedTargets.clear();\n this.#activeTargets.clear();\n }\n\n /** True when lifecycle tracking or the current event reports composition. */\n isComposing(event?: CompositionSignal): boolean {\n return this.#activeTargets.size > 0 || event?.isComposing === true;\n }\n\n readonly #handleStart = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.add(event.currentTarget);\n this.#onStart?.(event);\n };\n\n readonly #handleEnd = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);\n this.#onEnd?.(event);\n };\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { ChipRow } from \"../utils/chip_row\";\nimport { CompositionTracker } from \"../utils/composition_tracker\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/**\n * Headless, accessible free-input tags / chips field.\n *\n * Markup contract (identifier: `stimeo--tags-input`):\n * <div data-controller=\"stimeo--tags-input\"\n * data-stimeo--tags-input-delimiter-value=\",\"\n * data-stimeo--tags-input-announce-text-value=\"{label} added; {count} total\"\n * data-stimeo--tags-input-announce-removed-text-value=\"{label} removed; {count} total\">\n * <ul role=\"list\" aria-label=\"Tags\" data-stimeo--tags-input-target=\"tags\"></ul>\n * <input type=\"text\" aria-label=\"Add tag\" aria-describedby=\"tags-help\"\n * data-stimeo--tags-input-target=\"input\"\n * data-action=\"keydown->stimeo--tags-input#onKeydown\" />\n * <div data-stimeo--tags-input-target=\"fields\"></div>\n * <template data-stimeo--tags-input-target=\"tagTemplate\">\n * <li role=\"listitem\" data-stimeo--tags-input-target=\"tag\">\n * <span data-stimeo--tags-input-target=\"label\"></span>\n * <!-- Removal is delegated on the tags container; no per-chip action needed. -->\n * <button type=\"button\" tabindex=\"-1\" aria-label=\"Remove {label}\"\n * data-stimeo--tags-input-target=\"remove\">×</button>\n * </li>\n * </template>\n * </div>\n *\n * There is no single established APG pattern; this composes a labeled text input\n * with a roving-tabindex list of removable chips, mapping to WCAG 2.1.1, 2.4.7,\n * 4.1.2, 4.1.3, and 1.3.1. Unlike {@link MultiSelectController} (pick from a\n * candidate list) the user types arbitrary strings.\n *\n * Behavior provided:\n * - `Enter` or the configured delimiter commits the trimmed input as a tag.\n * - Empty / duplicate / over-limit additions are rejected with\n * `stimeo--tags-input:reject`; duplicates are allowed when `allowDuplicates`.\n * - Tags render transactionally from the `tagTemplate`; its author-localized\n * remove-button `aria-label` expands `{label}` / `{value}`. The\n * `fields` container mirrors the tag set as `name`d hidden inputs for form\n * submission. User changes dispatch `stimeo--tags-input:change`; DOM/Turbo tag\n * changes dispatch `stimeo--tags-input:reconcile`; authored templates route\n * localized addition/removal text to the shared announcer.\n * - The remove buttons form one roving Tab stop: `ArrowLeft`/`ArrowRight` move\n * between them (right past the end returns to the input), `Delete`/`Backspace`\n * delete the focused tag, and `Backspace` on an empty input deletes the last.\n * - Removing from a chip moves focus to its neighbor (else the input); removing\n * via empty-input `Backspace` keeps focus in the input for continued editing.\n */\nexport class TagsInputController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"tags\", \"tag\", \"tagTemplate\", \"label\", \"remove\", \"fields\"];\n static override values = {\n delimiter: { type: String, default: \",\" },\n max: { type: Number, default: 0 },\n allowDuplicates: { type: Boolean, default: false },\n name: { type: String, default: \"tags[]\" },\n announceText: { type: String, default: \"\" },\n announceRemovedText: { type: String, default: \"\" },\n };\n static actions = [\"onKeydown\"] as const;\n static events = [\"change\", \"reconcile\", \"reject\"] as const;\n\n declare readonly inputTarget: HTMLInputElement;\n declare readonly tagsTarget: HTMLElement;\n declare readonly tagTargets: HTMLElement[];\n declare readonly tagTemplateTarget: HTMLTemplateElement;\n declare readonly labelTargets: HTMLElement[];\n declare readonly removeTargets: HTMLButtonElement[];\n declare readonly fieldsTarget: HTMLElement;\n declare readonly hasFieldsTarget: boolean;\n declare readonly hasTagTemplateTarget: boolean;\n declare readonly hasInputTarget: boolean;\n declare readonly hasTagsTarget: boolean;\n\n declare delimiterValue: string;\n declare maxValue: number;\n declare allowDuplicatesValue: boolean;\n declare nameValue: string;\n declare announceTextValue: string;\n declare announceRemovedTextValue: string;\n\n /** Last reconciled tag order, separating user edits from DOM/Turbo repair. */\n #tagValues: string[] = [];\n /** Whether this connection already reported its unusable chip template. */\n #warnedTemplate = false;\n /** Collapses one target/Value mutation batch into one final-DOM repair pass. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileTags());\n readonly #chipRow = new ChipRow({\n directionElement: this.element,\n getItems: () => this.tagTargets,\n getButton: (tag) =>\n tag.querySelector<HTMLButtonElement>('button[data-stimeo--tags-input-target~=\"remove\"]'),\n onRemove: (index) => this.#removeAt(index),\n focusAfterEnd: () => this.#focusInput(),\n });\n /** Owns IME lifecycle state for commit-key guarding. */\n readonly #composition = new CompositionTracker();\n\n /** Wires tag-list keyboard navigation and removal, and seeds the single Tab stop. */\n override connect(): void {\n this.#warnedTemplate = false;\n if (this.hasInputTarget) this.#composition.observe(this.inputTarget);\n if (this.hasTagsTarget) this.#chipRow.connect(this.tagsTarget);\n const tags = this.#values;\n this.#syncState(tags);\n this.#tagValues = tags;\n this.#reconcile.activate();\n }\n\n /** Releases the delegated listeners so no handler outlives the element. */\n override disconnect(): void {\n this.#reconcile.cancel();\n this.#composition.disconnect();\n this.#chipRow.disconnect();\n }\n\n /** Tracks an input added initially or after connect. */\n inputTargetConnected(input: HTMLInputElement): void {\n this.#composition.observe(input);\n }\n\n /** Removes composition listeners when the active input is replaced or removed. */\n inputTargetDisconnected(input: HTMLInputElement): void {\n this.#composition.unobserve(input);\n }\n\n /** Rebinds delegation and derived state when the tags container is replaced. */\n tagsTargetConnected(_tags: HTMLElement): void {\n this.#reconcile.schedule();\n }\n\n /** Releases only the row that disconnected, leaving a newer target intact. */\n tagsTargetDisconnected(tags: HTMLElement): void {\n this.#chipRow.disconnect(tags);\n this.#reconcile.schedule();\n }\n\n /** Reconciles a tag inserted by Turbo, a server render, or another controller. */\n tagTargetConnected(_tag: HTMLElement): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles a tag removed by Turbo, a server render, or another controller. */\n tagTargetDisconnected(_tag: HTMLElement): void {\n this.#reconcile.schedule();\n }\n\n /** Seeds a fields target inserted after connect. */\n fieldsTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Rebuilds submitted fields when the public name changes at runtime. */\n nameValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Recomputes the full hook when the cap changes at runtime. */\n maxValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Commits on `Enter`/delimiter and deletes the last tag on empty `Backspace`. */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key must not ALSO commit a tag\n // or reach into the chips — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n if (!this.hasInputTarget) return;\n // Ignore keys fired during IME composition: the `Enter` that confirms a\n // candidate (and arrows that move within it) must not commit/navigate the\n // chip list. Controller-owned lifecycle state covers confirming events that\n // omit the standard per-event signal.\n if (this.#composition.isComposing(event)) return;\n if (event.key === \"Enter\" || event.key === this.delimiterValue) {\n event.preventDefault();\n this.#commitInput();\n return;\n }\n if (event.key === \"Backspace\" && this.inputTarget.value === \"\") {\n const last = this.#chipRow.lastIndex;\n if (last >= 0) {\n event.preventDefault();\n this.#removeAt(last, \"input\");\n }\n return;\n }\n // Same normalisation as the chip handler below, read from the same element.\n if (logicalArrowKey(event.key, this.element) === \"ArrowLeft\" && this.inputTarget.value === \"\") {\n if (this.#chipRow.length > 0) {\n event.preventDefault();\n this.#chipRow.focusLast();\n }\n }\n }\n\n /** Validates and adds the current input value as a tag, then clears the input. */\n #commitInput(): void {\n const value = this.inputTarget.value.trim();\n if (value === \"\") {\n this.#reject(value, \"empty\");\n return;\n }\n if (this.maxValue > 0 && this.tagTargets.length >= this.maxValue) {\n this.#reject(value, \"max\");\n return;\n }\n if (!this.allowDuplicatesValue && this.#values.includes(value)) {\n this.#reject(value, \"duplicate\");\n return;\n }\n if (!this.#appendTag(value)) return;\n this.inputTarget.value = \"\";\n const tags = this.#values;\n this.#syncState(tags);\n this.#tagValues = tags;\n this.#announceTransition(true, value, tags.length);\n this.dispatch(\"change\", { detail: { tags } });\n }\n\n /** Builds one chip from the template and appends it to the tag list. */\n #appendTag(value: string): boolean {\n if (!this.hasTagsTarget) return false;\n if (!this.hasTagTemplateTarget) return this.#warnTemplate('a \"tagTemplate\" target');\n const fragment = this.tagTemplateTarget.content.cloneNode(true) as DocumentFragment;\n const tag = fragment.querySelector<HTMLElement>('[data-stimeo--tags-input-target~=\"tag\"]');\n const label = fragment.querySelector<HTMLElement>('[data-stimeo--tags-input-target~=\"label\"]');\n const button = fragment.querySelector<HTMLButtonElement>(\n 'button[data-stimeo--tags-input-target~=\"remove\"]',\n );\n const removeName = button?.getAttribute(\"aria-label\")?.trim() ?? \"\";\n if (!tag) return this.#warnTemplate('a \"tag\" target');\n if (!label) return this.#warnTemplate('a \"label\" target');\n if (!button) return this.#warnTemplate('a \"remove\" target <button>');\n if (removeName === \"\") {\n return this.#warnTemplate('a non-empty aria-label on its \"remove\" target');\n }\n tag.dataset.value = value;\n label.textContent = value;\n button.setAttribute(\"aria-label\", fillTemplate(removeName, { label: value, value }));\n button.tabIndex = -1;\n this.tagsTarget.appendChild(fragment);\n return true;\n }\n\n /**\n * Reports an unusable chip template to the author, once per connection.\n *\n * The commit itself stays a no-op — nothing about the input, the tag set, the\n * hidden fields, the announcement, or the events changes. Without this line\n * the only symptom is a field that accepts no tags at all, and the two causes\n * the Inspector cannot see statically (a name that renders empty from a\n * missing translation, a server-rendered template) would have no diagnostic\n * anywhere.\n */\n #warnTemplate(missing: string): false {\n if (!this.#warnedTemplate) {\n this.#warnedTemplate = true;\n console.warn(\n `Stimeo UI: \"${this.identifier}\" added no tag because its chip template lacks ${missing}.`,\n );\n }\n return false;\n }\n\n /** Removes the tag at `index`, then applies the interaction-origin focus policy. */\n #removeAt(index: number, focus: \"neighbor\" | \"input\" = \"neighbor\"): void {\n const tag = this.tagTargets[index];\n if (!tag) return;\n const value = tag.dataset.value ?? \"\";\n tag.remove();\n const tags = this.#values;\n this.#syncState(tags);\n this.#tagValues = tags;\n this.#announceTransition(false, value, tags.length);\n this.dispatch(\"change\", { detail: { tags } });\n if (focus === \"input\") {\n this.#focusInput();\n return;\n }\n if (!this.#chipRow.focusAfterRemoval(index)) this.#focusInput();\n }\n\n /** Moves focus to the current input when one exists. */\n #focusInput(): void {\n if (this.hasInputTarget) this.inputTarget.focus();\n }\n\n /** Rebuilds the hidden form fields, the `full` flag, and the roving Tab stop. */\n #syncState(values: readonly string[]): void {\n if (this.hasFieldsTarget) {\n this.fieldsTarget.replaceChildren(\n ...values.map((value) => {\n const input = document.createElement(\"input\");\n input.type = \"hidden\";\n input.name = this.nameValue;\n input.value = value;\n return input;\n }),\n );\n }\n const full = this.maxValue > 0 && values.length >= this.maxValue;\n this.element.toggleAttribute(\"data-stimeo--tags-input-full\", full);\n // Keep exactly one remove button tabbable so the chip list is a single stop.\n this.#chipRow.ensureTabStop();\n }\n\n /** Repairs derived state after DOM/Turbo changes and reports a changed tag order. */\n #reconcileTags(): void {\n if (this.hasTagsTarget) this.#chipRow.connect(this.tagsTarget);\n else this.#chipRow.disconnect();\n const tags = this.#values;\n this.#syncState(tags);\n const changed = !this.#sameTags(this.#tagValues, tags);\n this.#tagValues = tags;\n if (changed) this.dispatch(\"reconcile\", { detail: { tags } });\n }\n\n /** Sends one localized tag transition through the page's shared announcer. */\n #announceTransition(added: boolean, value: string, count: number): void {\n const template = added ? this.announceTextValue : this.announceRemovedTextValue;\n announce(fillTemplate(template, { label: value, value, count }));\n }\n\n /** Reports rejection with `{ value, reason: \"duplicate\" | \"empty\" | \"max\" }`. */\n #reject(value: string, reason: \"duplicate\" | \"empty\" | \"max\"): void {\n this.dispatch(\"reject\", { detail: { value, reason } });\n }\n\n /** Current tag values in order. */\n get #values(): string[] {\n return this.tagTargets.map((tag) => tag.dataset.value ?? \"\");\n }\n\n /** Whether two arrays carry the same values in the same submitted order. */\n #sameTags(left: readonly string[], right: readonly string[]): boolean {\n return left.length === right.length && left.every((value, index) => value === right[index]);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/roving_tabindex.ts","../../src/utils/chip_row.ts","../../src/utils/composition_tracker.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/tags_input_controller.ts"],"names":["entry","button"],"mappings":"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","file":"tags_input_controller.js","sourcesContent":["/**\n * Sends one message to the page's shared `stimeo--announcer`.\n *\n * A component that has to reach assistive tech does not carry a live region of its\n * own: a region only announces what changes *after* assistive tech already knows\n * about it, which a region that appears (or is un-hidden) with its message cannot\n * satisfy. The one region that can is the announcer sitting in the page from the\n * start, so state changes are handed to it as an event and it does the reading.\n *\n * The event goes to `window` because the announcer is usually a sibling high in the\n * document rather than an ancestor of the component dispatching it.\n *\n * Wording comes from the consumer — the library ships no English strings — so an\n * empty message is silently dropped and nothing is announced.\n *\n * @example\n * ```ts\n * announce(this.announceTextValue, { assertive: false });\n * ```\n */\nexport function announce(message: string, options: { assertive?: boolean } = {}): void {\n const text = message.trim();\n if (text.length === 0) return;\n window.dispatchEvent(\n new CustomEvent(\"stimeo--announcer:announce\", {\n detail: { message: text, assertive: options.assertive === true },\n }),\n );\n}\n\n/**\n * Fills `{name}` placeholders in an announcement template from `values`.\n *\n * The same substitution the value-text templates use, so a consumer writes\n * `\"{percent}% complete\"` in one attribute and gets the same rules everywhere. A\n * placeholder with no matching entry is left as authored rather than blanked, which\n * keeps a typo visible instead of silently swallowing the word.\n */\nexport function fillTemplate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, name: string) => {\n const replacement = values[name];\n return replacement === undefined ? match : String(replacement);\n });\n}\n","/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Low-level roving-tabindex primitive shared by composite-widget controllers.\n *\n * The APG roving-tabindex pattern keeps a composite widget a single Tab stop:\n * exactly one item is in the Tab sequence (`tabindex=\"0\"`) while the rest are\n * removed from it (`tabindex=\"-1\"`), and the arrow keys move both DOM focus and\n * that single tabbable position together. {@link RovingTabindex} owns *only* that\n * mechanical bookkeeping — \"which one item is tabbable, and move focus there\".\n *\n * It is intentionally **policy-free**. Orientation, wrapping vs. clamping,\n * selection-follows-focus, typeahead, and `Home`/`End` semantics differ per APG\n * pattern (Radio Group, Toolbar, Rating, …); folding them into one helper would\n * flatten those widgets to a lowest common denominator and lose each pattern's\n * correctness. Those decisions therefore stay in each controller, which calls\n * {@link RovingTabindex.setActive} with an index it computed itself (optionally\n * via the pure {@link rovingMove} helper).\n *\n * @remarks\n * Items are read lazily through a getter so a controller can add or remove\n * targets (Stimulus re-scans the DOM) without re-wiring this helper. A caller\n * that already captured one event-scoped snapshot may pass it to\n * {@link RovingTabindex.setActive}, avoiding a second live-target scan while\n * preserving one coherent collection for the whole operation.\n */\nexport class RovingTabindex {\n /** Returns the current ordered item elements; called on every operation. */\n readonly #getItems: () => HTMLElement[];\n\n /**\n * @param getItems - Returns the current ordered item elements. Called on every\n * operation so the live target list is always used.\n */\n constructor(getItems: () => HTMLElement[]) {\n this.#getItems = getItems;\n }\n\n /** Index of the currently tabbable item (`tabindex=\"0\"`), or `-1` if none. */\n get activeIndex(): number {\n return this.#getItems().findIndex((item) => item.tabIndex === 0);\n }\n\n /**\n * Makes exactly the item at `index` tabbable (`tabindex=\"0\"`) and removes every\n * other item from the Tab sequence (`tabindex=\"-1\"`). An out-of-range `index`\n * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express\n * \"nothing is currently tabbable\".\n *\n * @param index - Position of the item to make tabbable.\n * @param options - Pass `{ focus: true }` to also move DOM focus to that item,\n * and `items` to reuse an event-scoped collection snapshot.\n */\n setActive(\n index: number,\n options: { focus?: boolean; items?: readonly HTMLElement[] } = {},\n ): void {\n const { focus = false } = options;\n const items = options.items ?? this.#getItems();\n items.forEach((item, i) => {\n item.tabIndex = i === index ? 0 : -1;\n });\n if (focus) items[index]?.focus();\n }\n}\n\n/** Edge behavior for {@link rovingMove}: cycle past the ends, or stop at them. */\nexport type RovingWrap = \"wrap\" | \"clamp\";\n\n/**\n * Pure helper that resolves the target index for a one-step directional move.\n *\n * Keyboard/orientation mapping stays in the caller: it decides that a key means\n * `delta` `+1` (next) or `-1` (previous) and whether the widget should `\"wrap\"`\n * (Radio Group, Toolbar) or `\"clamp\"` at the ends.\n *\n * @param current - The index focus is moving from.\n * @param length - Number of items in the set.\n * @param delta - `+1` to move to the next item, `-1` for the previous.\n * @param wrap - `\"wrap\"` cycles around the ends; `\"clamp\"` stops at them.\n * @returns The resolved index, or `-1` when there are no items.\n */\nexport function rovingMove(\n current: number,\n length: number,\n delta: number,\n wrap: RovingWrap,\n): number {\n if (length === 0) return -1;\n const next = current + delta;\n if (wrap === \"wrap\") return (next + length) % length;\n return Math.min(length - 1, Math.max(0, next));\n}\n","import { isReservedArrowChord, logicalArrowKey } from \"./arrow_step\";\nimport { RovingTabindex } from \"./roving_tabindex\";\n\n/** Consumer-owned behavior that differs between removable-chip widgets. */\nexport interface ChipRowOptions {\n /** Element whose writing direction defines logical previous/next. */\n readonly directionElement: HTMLElement;\n /** Returns the current chip elements in navigation order. */\n readonly getItems: () => readonly HTMLElement[];\n /** Resolves the one remove button owned by a chip. Defaults to its first button. */\n readonly getButton?: (item: HTMLElement) => HTMLButtonElement | null;\n /** Removes the chip currently occupying `index`. */\n readonly onRemove: (index: number) => void;\n /** Receives focus when navigation moves past the logical end of the row. */\n readonly focusAfterEnd: () => void;\n}\n\n/**\n * Delegated interaction and roving-tabindex mechanics for a row of removable chips.\n *\n * The selection model stays in the consuming controller: this helper knows only\n * how to resolve one consumer-declared remove button per chip, move between those\n * buttons, delegate click/delete requests by index, and bind those listeners to\n * a replaceable row target. Restricting the button set to the declared chip items\n * deliberately excludes sibling controls such as an authored \"Clear all\" button.\n */\nexport class ChipRow {\n readonly #directionElement: HTMLElement;\n readonly #getItems: () => readonly HTMLElement[];\n readonly #getButton: (item: HTMLElement) => HTMLButtonElement | null;\n readonly #onRemove: (index: number) => void;\n readonly #focusAfterEnd: () => void;\n readonly #roving = new RovingTabindex(() => this.buttons);\n #container: HTMLElement | null = null;\n\n constructor(options: ChipRowOptions) {\n this.#directionElement = options.directionElement;\n this.#getItems = options.getItems;\n this.#getButton = options.getButton ?? ((item) => item.querySelector(\"button\"));\n this.#onRemove = options.onRemove;\n this.#focusAfterEnd = options.focusAfterEnd;\n }\n\n /**\n * Binds delegation to `container`; any row currently bound is released first.\n * Idempotent for the same node, so target callbacks may call it freely.\n */\n connect(container: HTMLElement): void {\n if (this.#container === container) return;\n this.disconnect();\n this.#container = container;\n container.addEventListener(\"click\", this.#onClick);\n container.addEventListener(\"keydown\", this.#onKeydown);\n }\n\n /**\n * Releases the current row. When `container` is supplied, a stale disconnect\n * callback cannot tear listeners off a newer replacement target.\n */\n disconnect(container?: HTMLElement): void {\n const current = this.#container;\n if (!current || (container !== undefined && current !== container)) return;\n current.removeEventListener(\"click\", this.#onClick);\n current.removeEventListener(\"keydown\", this.#onKeydown);\n this.#container = null;\n }\n\n /** Current remove buttons, at most one consumer-resolved button per chip item. */\n get buttons(): HTMLButtonElement[] {\n return this.#entries.map(({ button }) => button);\n }\n\n /** Number of currently navigable remove buttons. */\n get length(): number {\n return this.buttons.length;\n }\n\n /** Declared-item index of the last navigable chip, or `-1` for an empty row. */\n get lastIndex(): number {\n return this.#entries.at(-1)?.itemIndex ?? -1;\n }\n\n /** Keeps exactly one button tabbable, preferring the first authored Tab stop. */\n ensureTabStop(): void {\n const buttons = this.#entries.map(({ button }) => button);\n const active = buttons.findIndex((button) => button.tabIndex === 0);\n const count = buttons.filter((button) => button.tabIndex === 0).length;\n if (buttons.length > 0 && count !== 1) {\n this.#roving.setActive(active === -1 ? 0 : active, { items: buttons });\n }\n }\n\n /** Focuses the last chip, returning false for an empty row. */\n focusLast(): boolean {\n const buttons = this.#entries.map(({ button }) => button);\n const last = buttons.length - 1;\n if (last < 0) return false;\n this.#roving.setActive(last, { focus: true, items: buttons });\n return true;\n }\n\n /**\n * Focuses the chip that followed a removed index, or the new last chip.\n * Returns false when removal emptied the row so the consumer can rescue focus.\n */\n focusAfterRemoval(index: number): boolean {\n const entries = this.#entries;\n if (entries.length === 0) return false;\n const following = entries.findIndex((entry) => entry.itemIndex >= index);\n this.#roving.setActive(following === -1 ? entries.length - 1 : following, {\n focus: true,\n items: entries.map(({ button }) => button),\n });\n return true;\n }\n\n /** Current remove-button entries, retaining each button's declared item index. */\n get #entries(): Array<{ button: HTMLButtonElement; itemIndex: number }> {\n return this.#getItems().flatMap((item, itemIndex) => {\n const button = this.#getButton(item);\n return button ? [{ button, itemIndex }] : [];\n });\n }\n\n /** Resolves a delegated event to the remove button owned by a declared chip. */\n #entry(event: Event): {\n button: HTMLButtonElement;\n buttonIndex: number;\n buttons: HTMLButtonElement[];\n itemIndex: number;\n } | null {\n const target = event.target;\n if (!(target instanceof Element)) return null;\n const button = target.closest<HTMLButtonElement>(\"button\");\n const entries = this.#entries;\n const buttonIndex = entries.findIndex((entry) => entry.button === button);\n if (buttonIndex === -1) return null;\n const entry = entries[buttonIndex] as { button: HTMLButtonElement; itemIndex: number };\n return { ...entry, buttonIndex, buttons: entries.map(({ button }) => button) };\n }\n\n /** Delegates removal clicks without waiting for Stimulus to wire a new chip. */\n readonly #onClick = (event: MouseEvent): void => {\n const entry = this.#entry(event);\n if (!entry) return;\n this.#onRemove(entry.itemIndex);\n };\n\n /** Applies the shared logical-arrow and removal policy within the chip row. */\n readonly #onKeydown = (event: KeyboardEvent): void => {\n if (event.defaultPrevented || isReservedArrowChord(event)) return;\n const entry = this.#entry(event);\n if (!entry) return;\n const buttons = entry.buttons;\n const index = entry.buttonIndex;\n\n switch (logicalArrowKey(event.key, this.#directionElement)) {\n case \"ArrowLeft\":\n if (index > 0) {\n event.preventDefault();\n this.#roving.setActive(index - 1, { focus: true, items: buttons });\n }\n break;\n case \"ArrowRight\":\n event.preventDefault();\n if (index < buttons.length - 1) {\n this.#roving.setActive(index + 1, { focus: true, items: buttons });\n } else {\n this.#focusAfterEnd();\n }\n break;\n case \"Delete\":\n case \"Backspace\":\n event.preventDefault();\n this.#onRemove(entry.itemIndex);\n break;\n default:\n break;\n }\n };\n}\n","/** Minimal event shape carrying the platform's per-event composition signal. */\nexport interface CompositionSignal {\n readonly isComposing?: boolean;\n}\n\n/** Hooks that keep component-specific work outside {@link CompositionTracker}. */\nexport interface CompositionTrackerOptions {\n /** Runs after lifecycle state is set for a `compositionstart` event. */\n readonly onStart?: (event: Event) => void;\n /** Runs after lifecycle state is cleared for a `compositionend` event. */\n readonly onEnd?: (event: Event) => void;\n}\n\n/**\n * Owns IME composition listeners and transient state for one or more event targets.\n *\n * Some browsers omit `KeyboardEvent.isComposing` on the keydown that confirms a\n * conversion. Tracking `compositionstart` through `compositionend` supplies the\n * missing lifecycle signal without relying on deprecated numeric key codes.\n * Component policy stays with the consumer: filtering, validation, and submission\n * belong in the optional {@link CompositionTrackerOptions.onEnd} callback.\n *\n * @example\n * ```ts\n * #composition = new CompositionTracker({ onEnd: () => this.filter() });\n *\n * connect(): void {\n * this.#composition.observe(this.inputTarget);\n * }\n *\n * disconnect(): void {\n * this.#composition.disconnect();\n * }\n *\n * onKeydown(event: KeyboardEvent): void {\n * if (this.#composition.isComposing(event)) return;\n * }\n * ```\n */\nexport class CompositionTracker {\n readonly #observedTargets = new Set<EventTarget>();\n readonly #activeTargets = new Set<EventTarget>();\n readonly #onStart: ((event: Event) => void) | undefined;\n readonly #onEnd: ((event: Event) => void) | undefined;\n\n constructor(options: CompositionTrackerOptions = {}) {\n this.#onStart = options.onStart;\n this.#onEnd = options.onEnd;\n }\n\n /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */\n observe(target: EventTarget): void {\n if (this.#observedTargets.has(target)) return;\n target.addEventListener(\"compositionstart\", this.#handleStart);\n target.addEventListener(\"compositionend\", this.#handleEnd);\n this.#observedTargets.add(target);\n }\n\n /** Stops tracking one target and clears any active composition it owned. */\n unobserve(target: EventTarget): void {\n if (!this.#observedTargets.delete(target)) return;\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n this.#activeTargets.delete(target);\n }\n\n /** Releases every listener and clears state so reconnect starts cleanly. */\n disconnect(): void {\n for (const target of this.#observedTargets) {\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n }\n this.#observedTargets.clear();\n this.#activeTargets.clear();\n }\n\n /** True when lifecycle tracking or the current event reports composition. */\n isComposing(event?: CompositionSignal): boolean {\n return this.#activeTargets.size > 0 || event?.isComposing === true;\n }\n\n readonly #handleStart = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.add(event.currentTarget);\n this.#onStart?.(event);\n };\n\n readonly #handleEnd = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);\n this.#onEnd?.(event);\n };\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { ChipRow } from \"../utils/chip_row\";\nimport { CompositionTracker } from \"../utils/composition_tracker\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/**\n * Headless, accessible free-input tags / chips field.\n *\n * Markup contract (identifier: `stimeo--tags-input`):\n * <div data-controller=\"stimeo--tags-input\"\n * data-stimeo--tags-input-delimiter-value=\",\"\n * data-stimeo--tags-input-announce-text-value=\"{label} added; {count} total\"\n * data-stimeo--tags-input-announce-removed-text-value=\"{label} removed; {count} total\">\n * <ul role=\"list\" aria-label=\"Tags\" data-stimeo--tags-input-target=\"tags\"></ul>\n * <input type=\"text\" aria-label=\"Add tag\" aria-describedby=\"tags-help\"\n * data-stimeo--tags-input-target=\"input\"\n * data-action=\"keydown->stimeo--tags-input#onKeydown\" />\n * <div data-stimeo--tags-input-target=\"fields\"></div>\n * <template data-stimeo--tags-input-target=\"tagTemplate\">\n * <li role=\"listitem\" data-stimeo--tags-input-target=\"tag\">\n * <span data-stimeo--tags-input-target=\"label\"></span>\n * <!-- Removal is delegated on the tags container; no per-chip action needed. -->\n * <button type=\"button\" tabindex=\"-1\" aria-label=\"Remove {label}\"\n * data-stimeo--tags-input-target=\"remove\">×</button>\n * </li>\n * </template>\n * </div>\n *\n * There is no single established APG pattern; this composes a labeled text input\n * with a roving-tabindex list of removable chips, mapping to WCAG 2.1.1, 2.4.7,\n * 4.1.2, 4.1.3, and 1.3.1. Unlike {@link MultiSelectController} (pick from a\n * candidate list) the user types arbitrary strings.\n *\n * Behavior provided:\n * - `Enter` or the configured delimiter commits the trimmed input as a tag.\n * - Empty / duplicate / over-limit additions are rejected with\n * `stimeo--tags-input:reject`; duplicates are allowed when `allowDuplicates`.\n * - Tags render transactionally from the `tagTemplate`; its author-localized\n * remove-button `aria-label` expands `{label}` / `{value}`. The\n * `fields` container mirrors the tag set as `name`d hidden inputs for form\n * submission. User changes dispatch `stimeo--tags-input:change`; DOM/Turbo tag\n * changes dispatch `stimeo--tags-input:reconcile`; authored templates route\n * localized addition/removal text to the shared announcer.\n * - The remove buttons form one roving Tab stop: `ArrowLeft`/`ArrowRight` move\n * between them (right past the end returns to the input), `Delete`/`Backspace`\n * delete the focused tag, and `Backspace` on an empty input deletes the last.\n * - Removing from a chip moves focus to its neighbor (else the input); removing\n * via empty-input `Backspace` keeps focus in the input for continued editing.\n * `change` and `reconcile` dispatch `{ tags: string[] }`; `reject` dispatches\n * `{ value: string, reason: \"duplicate\" | \"empty\" | \"max\" }`.\n */\nexport class TagsInputController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"tags\", \"tag\", \"tagTemplate\", \"label\", \"remove\", \"fields\"];\n static override values = {\n delimiter: { type: String, default: \",\" },\n max: { type: Number, default: 0 },\n allowDuplicates: { type: Boolean, default: false },\n name: { type: String, default: \"tags[]\" },\n announceText: { type: String, default: \"\" },\n announceRemovedText: { type: String, default: \"\" },\n };\n static actions = [\"onKeydown\"] as const;\n static events = [\"change\", \"reconcile\", \"reject\"] as const;\n\n declare readonly inputTarget: HTMLInputElement;\n declare readonly tagsTarget: HTMLElement;\n declare readonly tagTargets: HTMLElement[];\n declare readonly tagTemplateTarget: HTMLTemplateElement;\n declare readonly labelTargets: HTMLElement[];\n declare readonly removeTargets: HTMLButtonElement[];\n declare readonly fieldsTarget: HTMLElement;\n declare readonly hasFieldsTarget: boolean;\n declare readonly hasTagTemplateTarget: boolean;\n declare readonly hasInputTarget: boolean;\n declare readonly hasTagsTarget: boolean;\n\n declare delimiterValue: string;\n declare maxValue: number;\n declare allowDuplicatesValue: boolean;\n declare nameValue: string;\n declare announceTextValue: string;\n declare announceRemovedTextValue: string;\n\n /** Last reconciled tag order, separating user edits from DOM/Turbo repair. */\n #tagValues: string[] = [];\n /** Whether this connection already reported its unusable chip template. */\n #warnedTemplate = false;\n /** Collapses one target/Value mutation batch into one final-DOM repair pass. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileTags());\n readonly #chipRow = new ChipRow({\n directionElement: this.element,\n getItems: () => this.tagTargets,\n getButton: (tag) =>\n tag.querySelector<HTMLButtonElement>('button[data-stimeo--tags-input-target~=\"remove\"]'),\n onRemove: (index) => this.#removeAt(index),\n focusAfterEnd: () => this.#focusInput(),\n });\n /** Owns IME lifecycle state for commit-key guarding. */\n readonly #composition = new CompositionTracker();\n\n /** Wires tag-list keyboard navigation and removal, and seeds the single Tab stop. */\n override connect(): void {\n this.#warnedTemplate = false;\n if (this.hasInputTarget) this.#composition.observe(this.inputTarget);\n if (this.hasTagsTarget) this.#chipRow.connect(this.tagsTarget);\n const tags = this.#values;\n this.#syncState(tags);\n this.#tagValues = tags;\n this.#reconcile.activate();\n }\n\n /** Releases the delegated listeners so no handler outlives the element. */\n override disconnect(): void {\n this.#reconcile.cancel();\n this.#composition.disconnect();\n this.#chipRow.disconnect();\n }\n\n /** Tracks an input added initially or after connect. */\n inputTargetConnected(input: HTMLInputElement): void {\n this.#composition.observe(input);\n }\n\n /** Removes composition listeners when the active input is replaced or removed. */\n inputTargetDisconnected(input: HTMLInputElement): void {\n this.#composition.unobserve(input);\n }\n\n /** Rebinds delegation and derived state when the tags container is replaced. */\n tagsTargetConnected(_tags: HTMLElement): void {\n this.#reconcile.schedule();\n }\n\n /** Releases only the row that disconnected, leaving a newer target intact. */\n tagsTargetDisconnected(tags: HTMLElement): void {\n this.#chipRow.disconnect(tags);\n this.#reconcile.schedule();\n }\n\n /** Reconciles a tag inserted by Turbo, a server render, or another controller. */\n tagTargetConnected(_tag: HTMLElement): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles a tag removed by Turbo, a server render, or another controller. */\n tagTargetDisconnected(_tag: HTMLElement): void {\n this.#reconcile.schedule();\n }\n\n /** Seeds a fields target inserted after connect. */\n fieldsTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Rebuilds submitted fields when the public name changes at runtime. */\n nameValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Recomputes the full hook when the cap changes at runtime. */\n maxValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Commits on `Enter`/delimiter and deletes the last tag on empty `Backspace`. */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key must not ALSO commit a tag\n // or reach into the chips — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n if (!this.hasInputTarget) return;\n // Ignore keys fired during IME composition: the `Enter` that confirms a\n // candidate (and arrows that move within it) must not commit/navigate the\n // chip list. Controller-owned lifecycle state covers confirming events that\n // omit the standard per-event signal.\n if (this.#composition.isComposing(event)) return;\n if (event.key === \"Enter\" || event.key === this.delimiterValue) {\n event.preventDefault();\n this.#commitInput();\n return;\n }\n if (event.key === \"Backspace\" && this.inputTarget.value === \"\") {\n const last = this.#chipRow.lastIndex;\n if (last >= 0) {\n event.preventDefault();\n this.#removeAt(last, \"input\");\n }\n return;\n }\n // Same normalisation as the chip handler below, read from the same element.\n if (logicalArrowKey(event.key, this.element) === \"ArrowLeft\" && this.inputTarget.value === \"\") {\n if (this.#chipRow.length > 0) {\n event.preventDefault();\n this.#chipRow.focusLast();\n }\n }\n }\n\n /** Validates and adds the current input value as a tag, then clears the input. */\n #commitInput(): void {\n const value = this.inputTarget.value.trim();\n if (value === \"\") {\n this.#reject(value, \"empty\");\n return;\n }\n if (this.maxValue > 0 && this.tagTargets.length >= this.maxValue) {\n this.#reject(value, \"max\");\n return;\n }\n if (!this.allowDuplicatesValue && this.#values.includes(value)) {\n this.#reject(value, \"duplicate\");\n return;\n }\n if (!this.#appendTag(value)) return;\n this.inputTarget.value = \"\";\n const tags = this.#values;\n this.#syncState(tags);\n this.#tagValues = tags;\n this.#announceTransition(true, value, tags.length);\n this.dispatch(\"change\", { detail: { tags } });\n }\n\n /** Builds one chip from the template and appends it to the tag list. */\n #appendTag(value: string): boolean {\n if (!this.hasTagsTarget) return false;\n if (!this.hasTagTemplateTarget) return this.#warnTemplate('a \"tagTemplate\" target');\n const fragment = this.tagTemplateTarget.content.cloneNode(true) as DocumentFragment;\n const tag = fragment.querySelector<HTMLElement>('[data-stimeo--tags-input-target~=\"tag\"]');\n const label = fragment.querySelector<HTMLElement>('[data-stimeo--tags-input-target~=\"label\"]');\n const button = fragment.querySelector<HTMLButtonElement>(\n 'button[data-stimeo--tags-input-target~=\"remove\"]',\n );\n const removeName = button?.getAttribute(\"aria-label\")?.trim() ?? \"\";\n if (!tag) return this.#warnTemplate('a \"tag\" target');\n if (!label) return this.#warnTemplate('a \"label\" target');\n if (!button) return this.#warnTemplate('a \"remove\" target <button>');\n if (removeName === \"\") {\n return this.#warnTemplate('a non-empty aria-label on its \"remove\" target');\n }\n tag.dataset.value = value;\n label.textContent = value;\n button.setAttribute(\"aria-label\", fillTemplate(removeName, { label: value, value }));\n button.tabIndex = -1;\n this.tagsTarget.appendChild(fragment);\n return true;\n }\n\n /**\n * Reports an unusable chip template to the author, once per connection.\n *\n * The commit itself stays a no-op — nothing about the input, the tag set, the\n * hidden fields, the announcement, or the events changes. Without this line\n * the only symptom is a field that accepts no tags at all, and the two causes\n * the Inspector cannot see statically (a name that renders empty from a\n * missing translation, a server-rendered template) would have no diagnostic\n * anywhere.\n */\n #warnTemplate(missing: string): false {\n if (!this.#warnedTemplate) {\n this.#warnedTemplate = true;\n console.warn(\n `Stimeo UI: \"${this.identifier}\" added no tag because its chip template lacks ${missing}.`,\n );\n }\n return false;\n }\n\n /** Removes the tag at `index`, then applies the interaction-origin focus policy. */\n #removeAt(index: number, focus: \"neighbor\" | \"input\" = \"neighbor\"): void {\n const tag = this.tagTargets[index];\n if (!tag) return;\n const value = tag.dataset.value ?? \"\";\n tag.remove();\n const tags = this.#values;\n this.#syncState(tags);\n this.#tagValues = tags;\n this.#announceTransition(false, value, tags.length);\n this.dispatch(\"change\", { detail: { tags } });\n if (focus === \"input\") {\n this.#focusInput();\n return;\n }\n if (!this.#chipRow.focusAfterRemoval(index)) this.#focusInput();\n }\n\n /** Moves focus to the current input when one exists. */\n #focusInput(): void {\n if (this.hasInputTarget) this.inputTarget.focus();\n }\n\n /** Rebuilds the hidden form fields, the `full` flag, and the roving Tab stop. */\n #syncState(values: readonly string[]): void {\n if (this.hasFieldsTarget) {\n this.fieldsTarget.replaceChildren(\n ...values.map((value) => {\n const input = document.createElement(\"input\");\n input.type = \"hidden\";\n input.name = this.nameValue;\n input.value = value;\n return input;\n }),\n );\n }\n const full = this.maxValue > 0 && values.length >= this.maxValue;\n this.element.toggleAttribute(\"data-stimeo--tags-input-full\", full);\n // Keep exactly one remove button tabbable so the chip list is a single stop.\n this.#chipRow.ensureTabStop();\n }\n\n /** Repairs derived state after DOM/Turbo changes and reports a changed tag order. */\n #reconcileTags(): void {\n if (this.hasTagsTarget) this.#chipRow.connect(this.tagsTarget);\n else this.#chipRow.disconnect();\n const tags = this.#values;\n this.#syncState(tags);\n const changed = !this.#sameTags(this.#tagValues, tags);\n this.#tagValues = tags;\n if (changed) this.dispatch(\"reconcile\", { detail: { tags } });\n }\n\n /** Sends one localized tag transition through the page's shared announcer. */\n #announceTransition(added: boolean, value: string, count: number): void {\n const template = added ? this.announceTextValue : this.announceRemovedTextValue;\n announce(fillTemplate(template, { label: value, value, count }));\n }\n\n /** Reports rejection with `{ value, reason: \"duplicate\" | \"empty\" | \"max\" }`. */\n #reject(value: string, reason: \"duplicate\" | \"empty\" | \"max\"): void {\n this.dispatch(\"reject\", { detail: { value, reason } });\n }\n\n /** Current tag values in order. */\n get #values(): string[] {\n return this.tagTargets.map((tag) => tag.dataset.value ?? \"\");\n }\n\n /** Whether two arrays carry the same values in the same submitted order. */\n #sameTags(left: readonly string[], right: readonly string[]): boolean {\n return left.length === right.length && left.every((value, index) => value === right[index]);\n }\n}\n"]}
|
|
@@ -15,6 +15,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
15
15
|
* `maxRows` (in line-height units), toggling internal scrolling and the
|
|
16
16
|
* `data-at-max-rows` hook at the cap. Height-only changes preserve focus and caret.
|
|
17
17
|
*
|
|
18
|
+
* `resize` dispatches `{ height, rows }`.
|
|
19
|
+
*
|
|
18
20
|
* @remarks
|
|
19
21
|
* Behavior only — the height is written to the element's own inline style, never a
|
|
20
22
|
* CSS class. State lives on the element (no module-scope state), so `connect()`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/controllers/textarea_autosize_controller.ts"],"names":[],"mappings":";;;AAGA,SAAS,GAAG,KAAA,EAAuB;AACjC,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AACjC,EAAA,OAAO,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA;AAC/B;
|
|
1
|
+
{"version":3,"sources":["../../src/controllers/textarea_autosize_controller.ts"],"names":[],"mappings":"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","file":"textarea_autosize_controller.js","sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\n/** Parses a CSS pixel length, defaulting to 0 for `auto` / empty / `normal`. */\nfunction px(value: string): number {\n const n = Number.parseFloat(value);\n return Number.isNaN(n) ? 0 : n;\n}\n\n/**\n * Headless autosize behavior for a `<textarea>`: grows the element to fit its\n * content (and clamps to `minRows` / `maxRows`, scrolling past the max). No APG\n * pattern; supports WCAG 1.4.4 by following the text as it grows.\n *\n * Markup contract (identifier: `stimeo--textarea-autosize`):\n * <textarea\n * data-controller=\"stimeo--textarea-autosize\"\n * data-stimeo--textarea-autosize-max-rows-value=\"10\"></textarea>\n *\n * On connect and on every input it collapses the element (`height:auto`), reads\n * `scrollHeight`, and sets an explicit pixel height clamped between `minRows` and\n * `maxRows` (in line-height units), toggling internal scrolling and the\n * `data-at-max-rows` hook at the cap. Height-only changes preserve focus and caret.\n *\n * `resize` dispatches `{ height, rows }`.\n *\n * @remarks\n * Behavior only — the height is written to the element's own inline style, never a\n * CSS class. State lives on the element (no module-scope state), so `connect()`\n * re-measures after a Turbo navigation / morph. The input listener is removed on\n * `disconnect()` (Turbo navigation included). Measurement is synchronous, so there\n * is no pending animation frame to tear down.\n */\nexport class TextareaAutosizeController extends Controller<HTMLTextAreaElement> {\n static override values = {\n minRows: { type: Number, default: 1 },\n maxRows: { type: Number, default: 0 },\n };\n static actions = [\"resize\"] as const;\n static events = [\"resize\"] as const;\n\n declare minRowsValue: number;\n declare maxRowsValue: number;\n\n #lastHeight = -1;\n\n readonly #onInput = (): void => {\n this.resize();\n };\n\n override connect(): void {\n this.element.addEventListener(\"input\", this.#onInput);\n this.resize();\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"input\", this.#onInput);\n }\n\n /** Re-measures the content and applies the clamped height. */\n resize(): void {\n const el = this.element;\n const style = window.getComputedStyle(el);\n const lineHeight = this.#lineHeight(style);\n const paddingV = px(style.paddingTop) + px(style.paddingBottom);\n const borderV = px(style.borderTopWidth) + px(style.borderBottomWidth);\n const borderBox = style.boxSizing === \"border-box\";\n\n // Collapse first so scrollHeight reflects the content, not the prior height.\n el.style.height = \"auto\";\n const contentHeight = Math.max(0, el.scrollHeight - paddingV);\n const rows = Math.max(1, Math.round(contentHeight / lineHeight));\n\n let targetContent = Math.max(contentHeight, this.minRowsValue * lineHeight);\n let atMax = false;\n if (this.maxRowsValue > 0) {\n const maxContent = this.maxRowsValue * lineHeight;\n if (targetContent > maxContent) {\n targetContent = maxContent;\n atMax = true;\n }\n }\n\n const boxExtra = borderBox ? paddingV + borderV : 0;\n const height = Math.round(targetContent + boxExtra);\n el.style.height = `${height}px`;\n el.style.overflowY = atMax ? \"auto\" : \"hidden\";\n\n if (atMax) {\n el.setAttribute(\"data-at-max-rows\", \"true\");\n } else {\n el.removeAttribute(\"data-at-max-rows\");\n }\n el.style.setProperty(\"--stimeo--textarea-rows\", String(rows));\n\n if (height !== this.#lastHeight) {\n this.#lastHeight = height;\n this.dispatch(\"resize\", { detail: { height, rows } });\n }\n }\n\n /** Resolved line height, falling back to ~1.2× font-size when `normal`. */\n #lineHeight(style: CSSStyleDeclaration): number {\n const lh = px(style.lineHeight);\n if (lh > 0) return lh;\n const fontSize = px(style.fontSize);\n return fontSize > 0 ? fontSize * 1.2 : 16;\n }\n}\n"]}
|
|
@@ -26,6 +26,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
26
26
|
* <button data-controller="stimeo--theme" data-action="click->stimeo--theme#toggle"
|
|
27
27
|
* aria-pressed="false">Dark mode</button>
|
|
28
28
|
*
|
|
29
|
+
* `change` dispatches `{ mode, resolved }`.
|
|
30
|
+
*
|
|
29
31
|
* @remarks
|
|
30
32
|
* Behavior only — the actual palette is the consumer's CSS keyed off `data-theme`
|
|
31
33
|
* on the root. It applies `data-theme` (the *resolved* light/dark) and a matching
|
|
@@ -23,15 +23,17 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
23
23
|
// src/utils/safe_storage.ts
|
|
24
24
|
function readLocalStorage(key) {
|
|
25
25
|
try {
|
|
26
|
-
return window.localStorage.getItem(key);
|
|
27
|
-
} catch {
|
|
28
|
-
return
|
|
26
|
+
return { ok: true, value: window.localStorage.getItem(key) };
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return { ok: false, error };
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
function writeLocalStorage(key, value) {
|
|
32
32
|
try {
|
|
33
33
|
window.localStorage.setItem(key, value);
|
|
34
|
-
|
|
34
|
+
return { ok: true, value: void 0 };
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return { ok: false, error };
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -169,8 +171,8 @@ var ThemeController = class extends Controller {
|
|
|
169
171
|
}
|
|
170
172
|
/** Reads a persisted, validated mode from `localStorage` (null when absent/blocked). */
|
|
171
173
|
#readStored() {
|
|
172
|
-
const
|
|
173
|
-
return isMode(value) ? value : null;
|
|
174
|
+
const result = readLocalStorage(this.storageKeyValue);
|
|
175
|
+
return result.ok && isMode(result.value) ? result.value : null;
|
|
174
176
|
}
|
|
175
177
|
/** Persists the mode, swallowing storage errors (private mode / quota). */
|
|
176
178
|
#writeStored(mode) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/safe_storage.ts","../../src/controllers/theme_controller.ts"],"names":[],"mappings":"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","file":"theme_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Guarded `localStorage` access shared by the preference-persisting controllers\n * (sidebar, theme).\n *\n * `window.localStorage` can throw on access or read/write — storage disabled by\n * browser policy, sandboxed frames, private modes, or quota exhaustion. These\n * helpers centralize the try/catch so callers treat persistence as best-effort:\n * a failed read behaves like an unset key and a failed write is dropped,\n * leaving the in-DOM state as this session's source of truth.\n *\n * @remarks\n * Only the guarded access lives here — interpreting the stored string\n * (validation, defaults, key derivation) stays in each controller. The\n * `stimeo--persist` controller keeps its own storage layer on purpose: storage\n * *is* its domain (engine selection, per-field bookkeeping, TTL semantics).\n */\n\n/**\n * Reads `key` from `localStorage`.\n *\n * @param key - The storage key to read.\n * @returns The stored string, or `null` when the key is unset or storage is\n * unavailable (the read threw).\n */\nexport function readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\n/**\n * Writes `value` under `key` in `localStorage`, ignoring failures (private\n * mode / quota / storage disabled) — persistence is best-effort by contract.\n *\n * @param key - The storage key to write.\n * @param value - The string to store.\n */\nexport function writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Best-effort: the in-DOM state still applies for this session.\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowStep } from \"../utils/arrow_step\";\nimport { readLocalStorage, writeLocalStorage } from \"../utils/safe_storage\";\n\n/** The three selectable modes; `system` follows the OS `prefers-color-scheme`. */\ntype ThemeMode = \"light\" | \"dark\" | \"system\";\n/** The two effective (resolved) themes applied to the root. */\ntype ResolvedTheme = \"light\" | \"dark\";\n\nconst MODES: readonly ThemeMode[] = [\"light\", \"dark\", \"system\"];\nconst isMode = (value: unknown): value is ThemeMode =>\n typeof value === \"string\" && (MODES as readonly string[]).includes(value);\n\n/**\n * Headless **theme / color-scheme toggle** — persists a light/dark/system choice to\n * `localStorage`, follows the OS setting while in `system`, and reflects the\n * effective theme onto the root for the consumer's CSS. Ships no colors; only state\n * hooks.\n *\n * Two markup contracts (identifier: `stimeo--theme`):\n *\n * Canonical 3-value radiogroup (use this when `system` is offered):\n * <div data-controller=\"stimeo--theme\" data-stimeo--theme-mode-value=\"system\"\n * role=\"radiogroup\" aria-label=\"Theme\">\n * <button data-stimeo--theme-target=\"option\" role=\"radio\"\n * data-action=\"click->stimeo--theme#set\"\n * data-stimeo--theme-mode-param=\"light\">Light</button>\n * <button data-stimeo--theme-target=\"option\" role=\"radio\"\n * data-action=\"click->stimeo--theme#set\"\n * data-stimeo--theme-mode-param=\"dark\">Dark</button>\n * <button data-stimeo--theme-target=\"option\" role=\"radio\"\n * data-action=\"click->stimeo--theme#set\"\n * data-stimeo--theme-mode-param=\"system\">System</button>\n * </div>\n *\n * Auxiliary 2-value toggle (light↔dark only — `system` is not representable):\n * <button data-controller=\"stimeo--theme\" data-action=\"click->stimeo--theme#toggle\"\n * aria-pressed=\"false\">Dark mode</button>\n *\n * @remarks\n * Behavior only — the actual palette is the consumer's CSS keyed off `data-theme`\n * on the root. It applies `data-theme` (the *resolved* light/dark) and a matching\n * `color-scheme` to the `target` element (`<html>` by default), keeps the radiogroup\n * `aria-checked` + roving tabindex (APG radio) or the single button's `aria-pressed`\n * in sync, and never moves focus. The `prefers-color-scheme` listener is attached on\n * `connect()` and removed on `disconnect()` (Turbo included). FOUC avoidance for the\n * very first paint is an inline `<head>` snippet, not this controller.\n */\nexport class ThemeController extends Controller<HTMLElement> {\n static override targets = [\"option\"];\n static override values = {\n mode: { type: String, default: \"system\" },\n storageKey: { type: String, default: \"stimeo-theme\" },\n target: { type: String, default: \"html\" },\n };\n static actions = [\"set\", \"toggle\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly optionTargets: HTMLElement[];\n declare readonly hasOptionTarget: boolean;\n\n declare modeValue: string;\n declare storageKeyValue: string;\n declare targetValue: string;\n\n /** The OS dark-mode query, watched so `system` tracks live changes. */\n #media: MediaQueryList | null = null;\n\n /** Re-resolves while in `system` mode when the OS preference flips. */\n readonly #onMediaChange = (): void => {\n if (this.modeValue === \"system\") {\n this.#applyTheme();\n // Re-sync controls too: a 2-value toggle's `aria-pressed` reflects the\n // *resolved* theme, so it must follow the OS flip — not only `data-theme`.\n this.#syncControls();\n this.#dispatchChange();\n }\n };\n\n /** Arrow/Home/End navigation for the radiogroup (APG radio pattern). */\n readonly #onKeydown = (event: KeyboardEvent): void => {\n // A descendant widget that already claimed the key (a grabbed drag handle, a\n // nested menu) must not ALSO act on it — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n // Resolved once per keydown: every `optionTargets` access re-queries the scope.\n const options = this.optionTargets;\n if (options.length === 0) return;\n const target = event.target as HTMLElement | null;\n const current = options.indexOf(target as HTMLElement);\n if (current === -1) return;\n\n const last = options.length - 1;\n let next = current;\n // Logical, not physical. The helper reverses only the horizontal pair, so\n // folding Down/Up into the same branch stays correct.\n const step = logicalArrowStep(event.key, this.element);\n switch (event.key) {\n case \"ArrowDown\":\n case \"ArrowRight\":\n case \"ArrowUp\":\n case \"ArrowLeft\":\n next =\n step === 1 ? (current === last ? 0 : current + 1) : current === 0 ? last : current - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = last;\n break;\n default:\n return;\n }\n event.preventDefault();\n const option = options[next];\n if (!option) return;\n option.focus();\n this.#setMode(this.#optionMode(option));\n };\n\n override connect(): void {\n const stored = this.#readStored();\n if (stored) this.modeValue = stored;\n\n this.#media = window.matchMedia?.(\"(prefers-color-scheme: dark)\") ?? null;\n this.#media?.addEventListener(\"change\", this.#onMediaChange);\n if (this.hasOptionTarget) this.element.addEventListener(\"keydown\", this.#onKeydown);\n\n this.#applyTheme();\n this.#syncControls();\n }\n\n override disconnect(): void {\n this.#media?.removeEventListener(\"change\", this.#onMediaChange);\n this.element.removeEventListener(\"keydown\", this.#onKeydown);\n }\n\n /** Selects an explicit mode from the `mode` action param (radiogroup option). */\n set(event: Event): void {\n const mode = (event as { params?: Record<string, unknown> }).params?.mode;\n if (isMode(mode)) this.#setMode(mode);\n }\n\n /** Toggles light↔dark for the 2-value single-button contract. */\n toggle(): void {\n this.#setMode(this.#resolved() === \"dark\" ? \"light\" : \"dark\");\n }\n\n /** Central mode change: persist, apply to the root, resync controls, announce. */\n #setMode(mode: ThemeMode): void {\n this.modeValue = mode;\n this.#writeStored(mode);\n this.#applyTheme();\n this.#syncControls();\n this.#dispatchChange();\n }\n\n /** Writes `data-theme` + `color-scheme` (the resolved theme) onto the target. */\n #applyTheme(): void {\n const root = this.#targetElement();\n if (!root) return;\n const resolved = this.#resolved();\n root.setAttribute(\"data-theme\", resolved);\n root.style.setProperty(\"color-scheme\", resolved);\n }\n\n /** Keeps the radiogroup (aria-checked + roving tabindex) or toggle (aria-pressed) in sync. */\n #syncControls(): void {\n const options = this.optionTargets;\n if (options.length > 0) {\n let hasTabbable = false;\n for (const option of options) {\n const selected = this.#optionMode(option) === this.modeValue;\n option.setAttribute(\"aria-checked\", String(selected));\n option.tabIndex = selected ? 0 : -1;\n hasTabbable ||= selected;\n }\n // APG: a radiogroup with no selection keeps its first radio tabbable.\n const first = options[0];\n if (!hasTabbable && first) first.tabIndex = 0;\n return;\n }\n this.element.setAttribute(\"aria-pressed\", String(this.#resolved() === \"dark\"));\n }\n\n /** Emits `change` with the selected mode and the resolved theme. */\n #dispatchChange(): void {\n this.dispatch(\"change\", { detail: { mode: this.modeValue, resolved: this.#resolved() } });\n }\n\n /** The effective theme: the OS preference when `system`, else the mode itself. */\n #resolved(): ResolvedTheme {\n if (this.modeValue === \"dark\") return \"dark\";\n if (this.modeValue === \"light\") return \"light\";\n return this.#media?.matches ? \"dark\" : \"light\";\n }\n\n /** Reads an option's mode from its action param attribute. */\n #optionMode(option: HTMLElement): ThemeMode {\n const mode = option.getAttribute(\"data-stimeo--theme-mode-param\");\n return isMode(mode) ? mode : \"system\";\n }\n\n /** Resolves the state-hook target (`<html>` by default). */\n #targetElement(): HTMLElement | null {\n if (this.targetValue === \"html\" || this.targetValue === \":root\")\n return document.documentElement;\n return document.querySelector<HTMLElement>(this.targetValue);\n }\n\n /** Reads a persisted, validated mode from `localStorage` (null when absent/blocked). */\n #readStored(): ThemeMode | null {\n const value = readLocalStorage(this.storageKeyValue);\n return isMode(value) ? value : null;\n }\n\n /** Persists the mode, swallowing storage errors (private mode / quota). */\n #writeStored(mode: ThemeMode): void {\n writeLocalStorage(this.storageKeyValue, mode);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/safe_storage.ts","../../src/controllers/theme_controller.ts"],"names":[],"mappings":"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","file":"theme_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/** A successful guarded storage operation. */\nexport interface StorageSuccess<T> {\n readonly ok: true;\n readonly value: T;\n}\n\n/** A guarded storage operation rejected by the browser. */\nexport interface StorageFailure {\n readonly ok: false;\n readonly error: unknown;\n}\n\n/** Result of accessing browser storage without allowing an exception to escape. */\nexport type StorageResult<T> = StorageSuccess<T> | StorageFailure;\n\n/**\n * Guarded `localStorage` access shared by controllers that persist client state.\n *\n * `window.localStorage` can throw while resolving the storage object or while\n * reading, writing, or removing a key. The result keeps an unavailable store\n * distinct from an available store whose key is unset, so callers can expose an\n * accurate success or failure state without duplicating exception guards.\n */\n\n/**\n * Reads `key` from `localStorage`.\n *\n * @param key - The storage key to read.\n * @returns A successful result containing the stored string or `null`, or a\n * failure result when storage is unavailable.\n */\nexport function readLocalStorage(key: string): StorageResult<string | null> {\n try {\n return { ok: true, value: window.localStorage.getItem(key) };\n } catch (error: unknown) {\n return { ok: false, error };\n }\n}\n\n/**\n * Writes `value` under `key` in `localStorage`.\n *\n * @param key - The storage key to write.\n * @param value - The string to store.\n * @returns Success only after `setItem` completes; otherwise the browser error.\n */\nexport function writeLocalStorage(key: string, value: string): StorageResult<void> {\n try {\n window.localStorage.setItem(key, value);\n return { ok: true, value: undefined };\n } catch (error: unknown) {\n return { ok: false, error };\n }\n}\n\n/**\n * Removes `key` from `localStorage`.\n *\n * @param key - The storage key to remove.\n * @returns Success only after `removeItem` completes; otherwise the browser error.\n */\nexport function removeLocalStorage(key: string): StorageResult<void> {\n try {\n window.localStorage.removeItem(key);\n return { ok: true, value: undefined };\n } catch (error: unknown) {\n return { ok: false, error };\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowStep } from \"../utils/arrow_step\";\nimport { readLocalStorage, writeLocalStorage } from \"../utils/safe_storage\";\n\n/** The three selectable modes; `system` follows the OS `prefers-color-scheme`. */\ntype ThemeMode = \"light\" | \"dark\" | \"system\";\n/** The two effective (resolved) themes applied to the root. */\ntype ResolvedTheme = \"light\" | \"dark\";\n\nconst MODES: readonly ThemeMode[] = [\"light\", \"dark\", \"system\"];\nconst isMode = (value: unknown): value is ThemeMode =>\n typeof value === \"string\" && (MODES as readonly string[]).includes(value);\n\n/**\n * Headless **theme / color-scheme toggle** — persists a light/dark/system choice to\n * `localStorage`, follows the OS setting while in `system`, and reflects the\n * effective theme onto the root for the consumer's CSS. Ships no colors; only state\n * hooks.\n *\n * Two markup contracts (identifier: `stimeo--theme`):\n *\n * Canonical 3-value radiogroup (use this when `system` is offered):\n * <div data-controller=\"stimeo--theme\" data-stimeo--theme-mode-value=\"system\"\n * role=\"radiogroup\" aria-label=\"Theme\">\n * <button data-stimeo--theme-target=\"option\" role=\"radio\"\n * data-action=\"click->stimeo--theme#set\"\n * data-stimeo--theme-mode-param=\"light\">Light</button>\n * <button data-stimeo--theme-target=\"option\" role=\"radio\"\n * data-action=\"click->stimeo--theme#set\"\n * data-stimeo--theme-mode-param=\"dark\">Dark</button>\n * <button data-stimeo--theme-target=\"option\" role=\"radio\"\n * data-action=\"click->stimeo--theme#set\"\n * data-stimeo--theme-mode-param=\"system\">System</button>\n * </div>\n *\n * Auxiliary 2-value toggle (light↔dark only — `system` is not representable):\n * <button data-controller=\"stimeo--theme\" data-action=\"click->stimeo--theme#toggle\"\n * aria-pressed=\"false\">Dark mode</button>\n *\n * `change` dispatches `{ mode, resolved }`.\n *\n * @remarks\n * Behavior only — the actual palette is the consumer's CSS keyed off `data-theme`\n * on the root. It applies `data-theme` (the *resolved* light/dark) and a matching\n * `color-scheme` to the `target` element (`<html>` by default), keeps the radiogroup\n * `aria-checked` + roving tabindex (APG radio) or the single button's `aria-pressed`\n * in sync, and never moves focus. The `prefers-color-scheme` listener is attached on\n * `connect()` and removed on `disconnect()` (Turbo included). FOUC avoidance for the\n * very first paint is an inline `<head>` snippet, not this controller.\n */\nexport class ThemeController extends Controller<HTMLElement> {\n static override targets = [\"option\"];\n static override values = {\n mode: { type: String, default: \"system\" },\n storageKey: { type: String, default: \"stimeo-theme\" },\n target: { type: String, default: \"html\" },\n };\n static actions = [\"set\", \"toggle\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly optionTargets: HTMLElement[];\n declare readonly hasOptionTarget: boolean;\n\n declare modeValue: string;\n declare storageKeyValue: string;\n declare targetValue: string;\n\n /** The OS dark-mode query, watched so `system` tracks live changes. */\n #media: MediaQueryList | null = null;\n\n /** Re-resolves while in `system` mode when the OS preference flips. */\n readonly #onMediaChange = (): void => {\n if (this.modeValue === \"system\") {\n this.#applyTheme();\n // Re-sync controls too: a 2-value toggle's `aria-pressed` reflects the\n // *resolved* theme, so it must follow the OS flip — not only `data-theme`.\n this.#syncControls();\n this.#dispatchChange();\n }\n };\n\n /** Arrow/Home/End navigation for the radiogroup (APG radio pattern). */\n readonly #onKeydown = (event: KeyboardEvent): void => {\n // A descendant widget that already claimed the key (a grabbed drag handle, a\n // nested menu) must not ALSO act on it — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n // Resolved once per keydown: every `optionTargets` access re-queries the scope.\n const options = this.optionTargets;\n if (options.length === 0) return;\n const target = event.target as HTMLElement | null;\n const current = options.indexOf(target as HTMLElement);\n if (current === -1) return;\n\n const last = options.length - 1;\n let next = current;\n // Logical, not physical. The helper reverses only the horizontal pair, so\n // folding Down/Up into the same branch stays correct.\n const step = logicalArrowStep(event.key, this.element);\n switch (event.key) {\n case \"ArrowDown\":\n case \"ArrowRight\":\n case \"ArrowUp\":\n case \"ArrowLeft\":\n next =\n step === 1 ? (current === last ? 0 : current + 1) : current === 0 ? last : current - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = last;\n break;\n default:\n return;\n }\n event.preventDefault();\n const option = options[next];\n if (!option) return;\n option.focus();\n this.#setMode(this.#optionMode(option));\n };\n\n override connect(): void {\n const stored = this.#readStored();\n if (stored) this.modeValue = stored;\n\n this.#media = window.matchMedia?.(\"(prefers-color-scheme: dark)\") ?? null;\n this.#media?.addEventListener(\"change\", this.#onMediaChange);\n if (this.hasOptionTarget) this.element.addEventListener(\"keydown\", this.#onKeydown);\n\n this.#applyTheme();\n this.#syncControls();\n }\n\n override disconnect(): void {\n this.#media?.removeEventListener(\"change\", this.#onMediaChange);\n this.element.removeEventListener(\"keydown\", this.#onKeydown);\n }\n\n /** Selects an explicit mode from the `mode` action param (radiogroup option). */\n set(event: Event): void {\n const mode = (event as { params?: Record<string, unknown> }).params?.mode;\n if (isMode(mode)) this.#setMode(mode);\n }\n\n /** Toggles light↔dark for the 2-value single-button contract. */\n toggle(): void {\n this.#setMode(this.#resolved() === \"dark\" ? \"light\" : \"dark\");\n }\n\n /** Central mode change: persist, apply to the root, resync controls, announce. */\n #setMode(mode: ThemeMode): void {\n this.modeValue = mode;\n this.#writeStored(mode);\n this.#applyTheme();\n this.#syncControls();\n this.#dispatchChange();\n }\n\n /** Writes `data-theme` + `color-scheme` (the resolved theme) onto the target. */\n #applyTheme(): void {\n const root = this.#targetElement();\n if (!root) return;\n const resolved = this.#resolved();\n root.setAttribute(\"data-theme\", resolved);\n root.style.setProperty(\"color-scheme\", resolved);\n }\n\n /** Keeps the radiogroup (aria-checked + roving tabindex) or toggle (aria-pressed) in sync. */\n #syncControls(): void {\n const options = this.optionTargets;\n if (options.length > 0) {\n let hasTabbable = false;\n for (const option of options) {\n const selected = this.#optionMode(option) === this.modeValue;\n option.setAttribute(\"aria-checked\", String(selected));\n option.tabIndex = selected ? 0 : -1;\n hasTabbable ||= selected;\n }\n // APG: a radiogroup with no selection keeps its first radio tabbable.\n const first = options[0];\n if (!hasTabbable && first) first.tabIndex = 0;\n return;\n }\n this.element.setAttribute(\"aria-pressed\", String(this.#resolved() === \"dark\"));\n }\n\n /** Emits `change` with the selected mode and the resolved theme. */\n #dispatchChange(): void {\n this.dispatch(\"change\", { detail: { mode: this.modeValue, resolved: this.#resolved() } });\n }\n\n /** The effective theme: the OS preference when `system`, else the mode itself. */\n #resolved(): ResolvedTheme {\n if (this.modeValue === \"dark\") return \"dark\";\n if (this.modeValue === \"light\") return \"light\";\n return this.#media?.matches ? \"dark\" : \"light\";\n }\n\n /** Reads an option's mode from its action param attribute. */\n #optionMode(option: HTMLElement): ThemeMode {\n const mode = option.getAttribute(\"data-stimeo--theme-mode-param\");\n return isMode(mode) ? mode : \"system\";\n }\n\n /** Resolves the state-hook target (`<html>` by default). */\n #targetElement(): HTMLElement | null {\n if (this.targetValue === \"html\" || this.targetValue === \":root\")\n return document.documentElement;\n return document.querySelector<HTMLElement>(this.targetValue);\n }\n\n /** Reads a persisted, validated mode from `localStorage` (null when absent/blocked). */\n #readStored(): ThemeMode | null {\n const result = readLocalStorage(this.storageKeyValue);\n return result.ok && isMode(result.value) ? result.value : null;\n }\n\n /** Persists the mode, swallowing storage errors (private mode / quota). */\n #writeStored(mode: ThemeMode): void {\n writeLocalStorage(this.storageKeyValue, mode);\n }\n}\n"]}
|
|
@@ -20,6 +20,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
20
20
|
* <input type="hidden" data-stimeo--time-picker-target="field" />
|
|
21
21
|
* </div>
|
|
22
22
|
*
|
|
23
|
+
* `reconcile` dispatches `{ value: string }`.
|
|
24
|
+
*
|
|
23
25
|
* @remarks
|
|
24
26
|
* `segment` targets declare the spinbuttons; the optional `field` target receives
|
|
25
27
|
* the composed form value. `hourCycle` selects 12- or 24-hour presentation
|