stimeo-ui 0.5.0 → 0.6.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 +116 -0
- package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +195 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +85 -9
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +31 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +7 -1
- package/dist/controllers/color_picker_controller.js +35 -9
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +157 -30
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +7 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -3
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +3 -3
- package/dist/controllers/idle_controller.js +27 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +46 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +24 -6
- package/dist/controllers/number_input_controller.js +275 -51
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +4 -0
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.js +33 -0
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +3 -3
- package/dist/controllers/password_strength_controller.js +20 -2
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +4 -4
- package/dist/controllers/persist_controller.js +24 -5
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +41 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +36 -31
- package/dist/controllers/rating_controller.js +272 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +4 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +6 -0
- package/dist/controllers/scroll_area_controller.js +154 -22
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +41 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +42 -13
- package/dist/controllers/time_picker_controller.js +296 -107
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +39 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.js +5 -3
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +2637 -781
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +16 -3
- package/dist/inspector/cli.js +22 -6
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +22 -6
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +169 -37
- package/dist/positioning/index.d.ts +2 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":"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","file":"checkbox_controller.js","sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\n/** Aggregate selection state of a parent/child checkbox group. */\ntype CheckboxState = \"all\" | \"partial\" | \"none\";\n\n/**\n * Headless, accessible checkbox behavior (including the tri-state / parent-child\n * \"select all\" pattern).\n *\n * Markup contract (identifier: `stimeo--checkbox`):\n * <fieldset data-controller=\"stimeo--checkbox\" role=\"group\" aria-labelledby=\"all-label\">\n * <label id=\"all-label\">\n * <input type=\"checkbox\" data-stimeo--checkbox-target=\"parent\"\n * data-action=\"change->stimeo--checkbox#onParentChange\" /> Select all\n * </label>\n * <label><input type=\"checkbox\" data-stimeo--checkbox-target=\"child\"\n * data-action=\"change->stimeo--checkbox#onChildChange\" /> Item A</label>\n * <!-- more children -->\n * </fieldset>\n *\n * Implements the WAI-ARIA APG **Checkbox** pattern. Native `<input type=\"checkbox\">`\n * is used throughout (so Space toggle, focus order, and `mixed` exposure are the\n * browser's); the controller adds what HTML cannot express: the parent's\n * `indeterminate` property (announced as `mixed`) derived from its children, and\n * the cascade from a parent toggle to its children.\n *\n * @remarks\n * Behavior only — the check mark and any \"mixed\" affordance are the consumer's\n * CSS (keyed off `:checked` / `:indeterminate` / `data-state`). A lone tri-state\n * checkbox can use just `parent` and drive `indeterminate` externally.\n *\n * Behavior provided:\n * - Parent toggle checks/unchecks every child and clears its own `indeterminate`.\n * - A child change recomputes the parent: all → checked, none → unchecked,\n * some → `indeterminate`.\n * - The aggregate (`all` / `partial` / `none`) is mirrored to `data-state` on the\n * root, and `stimeo--checkbox:change` is dispatched on every change.\n */\nexport class CheckboxController extends Controller<HTMLElement> {\n static override targets = [\"parent\", \"child\"];\n static actions = [\"onChildChange\", \"onParentChange\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly parentTarget: HTMLInputElement;\n declare readonly hasParentTarget: boolean;\n declare readonly childTargets: HTMLInputElement[];\n\n /** Reflects the initial aggregate (e.g. from server-rendered child states). */\n override connect(): void {\n if (this.childTargets.length > 0) {\n this.#syncFromChildren(false);\n } else {\n this.element.setAttribute(\"data-state\", this.#aggregate());\n }\n }\n\n /** Cascades the parent's state to every child. Bound via `data-action` (change). */\n onParentChange(): void {\n if (!this.hasParentTarget) return;\n const checked = this.parentTarget.checked;\n for (const child of this.childTargets) {\n child.checked = checked;\n }\n this.parentTarget.indeterminate = false;\n const state: CheckboxState = checked ? \"all\" : \"none\";\n this.element.setAttribute(\"data-state\", state);\n this.dispatch(\"change\", { detail: { checked, indeterminate: false, state } });\n }\n\n /** Recomputes the parent from its children. Bound via `data-action` (change). */\n onChildChange(): void {\n this.#syncFromChildren(true);\n }\n\n /**\n * Derives the parent's `checked`/`indeterminate` and the root `data-state` from\n * the children, optionally dispatching `change`.\n */\n #syncFromChildren(dispatch: boolean): void {\n const state = this.#aggregate();\n if (this.hasParentTarget) {\n this.parentTarget.checked = state === \"all\";\n this.parentTarget.indeterminate = state === \"partial\";\n }\n this.element.setAttribute(\"data-state\", state);\n if (dispatch) {\n this.dispatch(\"change\", {\n detail: {\n checked: this.hasParentTarget ? this.parentTarget.checked : state === \"all\",\n indeterminate: this.hasParentTarget ? this.parentTarget.indeterminate : false,\n state,\n },\n });\n }\n }\n\n /**\n * Computes the aggregate state. With children it counts them; with none it\n * reads the parent so a lone tri-state checkbox still reports a state without\n * its externally-set `indeterminate` being clobbered.\n */\n #aggregate(): CheckboxState {\n const children = this.childTargets;\n if (children.length === 0) {\n if (this.hasParentTarget && this.parentTarget.indeterminate) return \"partial\";\n return this.hasParentTarget && this.parentTarget.checked ? \"all\" : \"none\";\n }\n const checked = children.filter((child) => child.checked).length;\n if (checked === 0) return \"none\";\n if (checked === children.length) return \"all\";\n return \"partial\";\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/microtask_coalescer.ts","../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":"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","file":"checkbox_controller.js","sourcesContent":["/**\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 { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** Aggregate selection state of a parent/child checkbox group. */\ntype CheckboxState = \"all\" | \"partial\" | \"none\";\n\n/**\n * Headless, accessible checkbox behavior (including the tri-state / parent-child\n * \"select all\" pattern).\n *\n * Markup contract (identifier: `stimeo--checkbox`):\n * <fieldset data-controller=\"stimeo--checkbox\" role=\"group\" aria-labelledby=\"all-label\">\n * <label id=\"all-label\">\n * <input type=\"checkbox\" data-stimeo--checkbox-target=\"parent\"\n * data-action=\"change->stimeo--checkbox#onParentChange\" /> Select all\n * </label>\n * <label><input type=\"checkbox\" data-stimeo--checkbox-target=\"child\"\n * data-action=\"change->stimeo--checkbox#onChildChange\" /> Item A</label>\n * <!-- more children -->\n * </fieldset>\n *\n * Implements the WAI-ARIA APG **Checkbox** pattern. Native `<input type=\"checkbox\">`\n * is used throughout (so Space toggle, focus order, and `mixed` exposure are the\n * browser's); the controller adds what HTML cannot express: the parent's\n * `indeterminate` property (announced as `mixed`) derived from its children, and\n * the cascade from a parent toggle to its children.\n *\n * @remarks\n * Behavior only — the check mark and any \"mixed\" affordance are the consumer's\n * CSS (keyed off `:checked` / `:indeterminate` / `data-state`). The `parent`\n * target is optional: a child-only group still exposes its aggregate on the root,\n * while a lone tri-state checkbox can use just `parent` and drive\n * `indeterminate` externally — with no `child` target the parent's own `checked`\n * and `indeterminate` stay the consumer's, because only children are\n * authoritative over them. At most one `parent` may be present.\n *\n * Behavior provided:\n * - Parent toggle checks/unchecks every child and clears its own `indeterminate`.\n * All child targets, including disabled ones, participate; omit the target to\n * exclude an input. The cascade does not synthesize native child `change`\n * events — one aggregate custom event describes the action.\n * - A child change recomputes the parent: all → checked, none → unchecked,\n * some → `indeterminate`.\n * - The aggregate (`all` / `partial` / `none`) is mirrored to `data-state` on the\n * root. Dynamic targets, checked-attribute changes, Turbo morphs, and native\n * form resets are reconciled from the children. Callers that assign the live\n * `checked` property directly must dispatch `change`, because property writes\n * are not observable by a `MutationObserver`.\n * - `stimeo--checkbox:change` is dispatched for the two public change actions;\n * `stimeo--checkbox:reconcile` is dispatched instead when a reconciliation —\n * not the user — moves the aggregate. Both carry `{ checked: boolean,\n * indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }` and describe\n * the aggregate even without a parent. Neither fires on connect.\n */\nexport class CheckboxController extends Controller<HTMLElement> {\n static override targets = [\"parent\", \"child\"];\n static actions = [\"onChildChange\", \"onParentChange\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly parentTarget: HTMLInputElement;\n declare readonly parentTargets: HTMLInputElement[];\n declare readonly hasParentTarget: boolean;\n declare readonly childTargets: HTMLInputElement[];\n\n /** Collapses every lifecycle signal from one DOM update into one derived pass. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileFromChildren());\n /** Aggregate this root last settled on, so a derived repair is reported once. */\n #committedState: CheckboxState | null = null;\n /** Watches authored checked-attribute changes on retained target elements. */\n readonly #checkedObserver = new MutationObserver((records) => {\n if (records.some((record) => this.#isManagedCheckbox(record.target))) {\n this.#reconcile.schedule();\n }\n });\n\n /** Reflects the initial aggregate and starts retained-DOM reconciliation. */\n override connect(): void {\n this.#reconcile.activate();\n this.#syncFromChildren();\n this.#checkedObserver.observe(this.element, {\n attributes: true,\n attributeFilter: [\"checked\"],\n subtree: true,\n });\n this.element.addEventListener(\"turbo:morph-element\", this.#onMorph);\n document.addEventListener(\"reset\", this.#onReset, true);\n }\n\n /** Releases the observer, global reset listener, and every pending reconciliation. */\n override disconnect(): void {\n this.#reconcile.cancel();\n this.#checkedObserver.disconnect();\n this.element.removeEventListener(\"turbo:morph-element\", this.#onMorph);\n document.removeEventListener(\"reset\", this.#onReset, true);\n }\n\n /** Reconciles the aggregate for a parent added or replaced at runtime. */\n parentTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a parent target leaves the group. */\n parentTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate for a child added at runtime. */\n childTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a child leaves the group. */\n childTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Cascades the parent's state to every child. Bound via `data-action` (change). */\n onParentChange(): void {\n if (!this.hasParentTarget) return;\n const checked = this.parentTarget.checked;\n for (const child of this.childTargets) {\n child.checked = checked;\n }\n this.#reflect(checked ? \"all\" : \"none\", true);\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /** Recomputes the parent from its children. Bound via `data-action` (change). */\n onChildChange(): void {\n this.#syncFromChildren();\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /**\n * Announces an aggregate this pass derived rather than the user. `change` stays\n * reserved for the two public actions, so automation never reads a repair as an edit.\n */\n #reconcileFromChildren(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"reconcile\", { detail });\n }\n\n /**\n * Derives the parent's `checked`/`indeterminate` and the root `data-state` from\n * the children. Writing state and reporting it are separate so the caller — not\n * a flag threaded through the write — decides which event describes the cause.\n */\n #syncFromChildren(): void {\n // A childless root reads its aggregate off the parent itself, so writing the\n // derived value back would clear a `checked` the consumer drives alongside\n // `indeterminate`. Only the children are authoritative over the parent.\n this.#reflect(this.#aggregate(), this.childTargets.length > 0);\n }\n\n /**\n * Reflects one aggregate state.\n *\n * @param writeParent - whether the state is authoritative over the `parent`\n * target's own `checked` / `indeterminate`.\n */\n #reflect(state: CheckboxState, writeParent: boolean): void {\n if (writeParent && this.hasParentTarget) {\n this.parentTarget.checked = state === \"all\";\n this.parentTarget.indeterminate = state === \"partial\";\n }\n this.element.setAttribute(\"data-state\", state);\n this.#committedState = state;\n }\n\n /** The settled aggregate as event detail, or `null` before anything has settled. */\n #settledDetail(): { checked: boolean; indeterminate: boolean; state: CheckboxState } | null {\n const state = this.#committedState;\n if (state === null) return null;\n return { checked: state === \"all\", indeterminate: state === \"partial\", state };\n }\n\n /** Reconciles retained targets after Turbo has finished morphing their live state. */\n readonly #onMorph = (): void => {\n this.#reconcile.schedule();\n };\n\n /** Reconciles after a non-cancelled reset restores any managed checkbox. */\n readonly #onReset = (event: Event): void => {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !this.#hasCheckboxOwnedBy(form)) return;\n queueMicrotask(() => {\n if (!event.defaultPrevented) this.#reconcile.schedule();\n });\n };\n\n /** Whether a form owns at least one current parent or child target. */\n #hasCheckboxOwnedBy(form: HTMLFormElement): boolean {\n return [...this.parentTargets, ...this.childTargets].some((checkbox) => checkbox.form === form);\n }\n\n /** Whether an observed attribute mutation belongs to this controller's target set. */\n #isManagedCheckbox(node: Node): boolean {\n return (\n this.parentTargets.some((checkbox) => checkbox === node) ||\n this.childTargets.some((checkbox) => checkbox === node)\n );\n }\n\n /**\n * Computes the aggregate state. With children it counts them; with none it\n * reads the parent so a lone tri-state checkbox still reports a state without\n * its externally-set `indeterminate` being clobbered.\n */\n #aggregate(): CheckboxState {\n const children = this.childTargets;\n if (children.length === 0) {\n if (this.hasParentTarget && this.parentTarget.indeterminate) return \"partial\";\n return this.hasParentTarget && this.parentTarget.checked ? \"all\" : \"none\";\n }\n const checked = children.filter((child) => child.checked).length;\n if (checked === 0) return \"none\";\n if (checked === children.length) return \"all\";\n return \"partial\";\n }\n}\n"]}
|
|
@@ -44,6 +44,11 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
44
44
|
* exactly bijective: a typed hex can normalize to a near (not identical) value
|
|
45
45
|
* once the HSL sliders are touched. This keeps the model small and zero-dep; use a
|
|
46
46
|
* dedicated color library on the consumer side if exact hex preservation matters.
|
|
47
|
+
*
|
|
48
|
+
* A color the user set through a slider or the hex input is reported as
|
|
49
|
+
* `stimeo--color-picker:change`. Toggling `alpha` at runtime can move the
|
|
50
|
+
* committed color without a user edit, and that arrives as
|
|
51
|
+
* `stimeo--color-picker:reconcile` with the same detail. Neither fires on connect.
|
|
47
52
|
*/
|
|
48
53
|
declare class ColorPickerController extends Controller<HTMLElement> {
|
|
49
54
|
#private;
|
|
@@ -63,7 +68,7 @@ declare class ColorPickerController extends Controller<HTMLElement> {
|
|
|
63
68
|
};
|
|
64
69
|
};
|
|
65
70
|
static actions: readonly ["onHexInput", "onKeydown", "onPointerDown"];
|
|
66
|
-
static events: readonly ["change"];
|
|
71
|
+
static events: readonly ["change", "reconcile"];
|
|
67
72
|
readonly sliderTargets: HTMLElement[];
|
|
68
73
|
readonly hexTarget: HTMLInputElement;
|
|
69
74
|
readonly hasHexTarget: boolean;
|
|
@@ -72,6 +77,7 @@ declare class ColorPickerController extends Controller<HTMLElement> {
|
|
|
72
77
|
valueValue: string;
|
|
73
78
|
alphaValue: boolean;
|
|
74
79
|
logicalTrackValue: boolean;
|
|
80
|
+
/** Seeds the model from the initial hex value and renders every surface. */
|
|
75
81
|
connect(): void;
|
|
76
82
|
/** Cancels any active pointer drag so document listeners never leak. */
|
|
77
83
|
disconnect(): void;
|
|
@@ -74,7 +74,7 @@ var ColorPickerController = class extends Controller {
|
|
|
74
74
|
logicalTrack: { type: Boolean, default: false }
|
|
75
75
|
};
|
|
76
76
|
static actions = ["onHexInput", "onKeydown", "onPointerDown"];
|
|
77
|
-
static events = ["change"];
|
|
77
|
+
static events = ["change", "reconcile"];
|
|
78
78
|
/** Whether the consumer declared a mirroring track and the direction mirrors it. */
|
|
79
79
|
get #mirrored() {
|
|
80
80
|
return this.logicalTrackValue && isRtl(this.element);
|
|
@@ -83,14 +83,14 @@ var ColorPickerController = class extends Controller {
|
|
|
83
83
|
#color = { hue: 0, saturation: 0, lightness: 0, alpha: 100 };
|
|
84
84
|
/** Aborts in-progress pointer-drag listeners on drag end / teardown. */
|
|
85
85
|
#dragAbort = null;
|
|
86
|
-
/**
|
|
86
|
+
/** Color the last repaint settled on, so a configuration-driven move is reported once. */
|
|
87
|
+
#committedHex = null;
|
|
87
88
|
/**
|
|
88
89
|
* Collapses a morph that swaps render inputs into one repaint, and refuses the
|
|
89
90
|
* pass Stimulus delivers before `connect()`.
|
|
90
91
|
*/
|
|
91
|
-
#repaint = new MicrotaskCoalescer(() =>
|
|
92
|
-
|
|
93
|
-
});
|
|
92
|
+
#repaint = new MicrotaskCoalescer(() => this.#reconcileColor());
|
|
93
|
+
/** Seeds the model from the initial hex value and renders every surface. */
|
|
94
94
|
connect() {
|
|
95
95
|
this.#repaint.activate();
|
|
96
96
|
const parsed = hexToHsla(this.valueValue);
|
|
@@ -181,12 +181,24 @@ var ColorPickerController = class extends Controller {
|
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
183
|
this.#color = this.alphaValue ? parsed : { ...parsed, alpha: 100 };
|
|
184
|
-
this.#
|
|
184
|
+
this.#commitColor();
|
|
185
185
|
}
|
|
186
186
|
/** Clamps and snaps one channel to an integer, then re-renders + emits change. */
|
|
187
187
|
#setChannel(channel, raw, min, max) {
|
|
188
188
|
this.#color[channel] = Math.round(Math.min(max, Math.max(min, raw)));
|
|
189
|
+
this.#commitColor();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Renders the model and reports a color the user actually moved. A key pressed
|
|
193
|
+
* at a bound, a pointer that lands on the step already showing, and a re-confirmed
|
|
194
|
+
* hex all leave the committed color where it was, so no `change` describes them.
|
|
195
|
+
*/
|
|
196
|
+
#commitColor() {
|
|
197
|
+
const previous = this.#committedHex;
|
|
189
198
|
this.#render();
|
|
199
|
+
if (this.#committedHex !== previous) {
|
|
200
|
+
this.dispatch("change", { detail: this.#settledDetail() });
|
|
201
|
+
}
|
|
190
202
|
}
|
|
191
203
|
/**
|
|
192
204
|
* Reflects the model onto sliders, the hex input, preview, and form field.
|
|
@@ -202,14 +214,28 @@ var ColorPickerController = class extends Controller {
|
|
|
202
214
|
slider.setAttribute("aria-valuetext", valueText(channel, value));
|
|
203
215
|
}
|
|
204
216
|
const hex = this.#hexString();
|
|
217
|
+
this.#committedHex = hex;
|
|
205
218
|
if (this.hasHexTarget) this.hexTarget.value = hex;
|
|
206
219
|
for (const field of this.fieldTargets) field.value = hex;
|
|
207
220
|
for (const preview of this.previewTargets) preview.style.setProperty(COLOR_PROPERTY, hex);
|
|
208
221
|
this.element.style.setProperty(COLOR_PROPERTY, hex);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Repaints after `alpha` changed at runtime and reports a color this controller
|
|
225
|
+
* settled on. Disabling alpha drops it from the model, so the committed color can
|
|
226
|
+
* move without a user edit; `change` stays reserved for the picker's own actions.
|
|
227
|
+
*/
|
|
228
|
+
#reconcileColor() {
|
|
229
|
+
const previous = this.#committedHex;
|
|
230
|
+
this.#render();
|
|
231
|
+
if (previous !== null && this.#committedHex !== previous) {
|
|
232
|
+
this.dispatch("reconcile", { detail: this.#settledDetail() });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** The settled color as event detail, shared by both report paths. */
|
|
236
|
+
#settledDetail() {
|
|
209
237
|
const rgb = hslToRgb(this.#color.hue, this.#color.saturation, this.#color.lightness);
|
|
210
|
-
this
|
|
211
|
-
detail: { value: hex, rgba: { ...rgb, a: this.#color.alpha / 100 } }
|
|
212
|
-
});
|
|
238
|
+
return { value: this.#hexString(), rgba: { ...rgb, a: this.#color.alpha / 100 } };
|
|
213
239
|
}
|
|
214
240
|
/** The current color as `#RRGGBB`, or `#RRGGBBAA` when alpha is enabled. */
|
|
215
241
|
#hexString() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/coerce.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/color_picker_controller.ts"],"names":[],"mappings":"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","file":"color_picker_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 * Numeric coercion shared by the value-bearing controllers (progress, meter,\n * color-picker).\n *\n * Stimulus already coerces numeric action params to numbers, but a value can also\n * arrive as a string — via a `*:set` CustomEvent `detail`, or an action param\n * whose attribute does not look numeric. Centralizing the parse keeps `setValue`\n * tolerant of either form while rejecting anything that is not a finite number.\n */\n\n/**\n * Coerces `raw` to a finite number, or returns `null` when it is absent, empty,\n * or not parseable. Empty strings are treated as \"no value\" rather than `0`, so a\n * stray blank param cannot silently reset the value.\n */\nexport function toFiniteNumber(raw: number | string | null | undefined): number | null {\n if (raw === null || raw === undefined || raw === \"\") return null;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { toFiniteNumber } from \"../utils/coerce\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** CSS custom property exposing the current color to consumer CSS. */\nconst COLOR_PROPERTY = \"--stimeo--color\";\n\n/** A color channel slider, identified by its `data-channel` attribute. */\ntype Channel = \"hue\" | \"saturation\" | \"lightness\" | \"alpha\";\n\n/** Default `[min, max]` per channel when the slider omits aria-valuemin/max. */\nconst CHANNEL_RANGE: Record<Channel, [number, number]> = {\n hue: [0, 360],\n saturation: [0, 100],\n lightness: [0, 100],\n alpha: [0, 100],\n};\n\n/** Internal color model: HSL plus an alpha percentage (0–100). */\ninterface Hsla {\n hue: number;\n saturation: number;\n lightness: number;\n alpha: number;\n}\n\n/**\n * Headless, accessible **Color Picker** behavior.\n *\n * Markup contract (identifier: `stimeo--color-picker`):\n * <div data-controller=\"stimeo--color-picker\"\n * data-stimeo--color-picker-value-value=\"#3366cc\">\n * <div role=\"slider\" aria-label=\"Hue\" data-channel=\"hue\" tabindex=\"0\"\n * aria-valuemin=\"0\" aria-valuemax=\"360\" aria-valuenow=\"210\"\n * data-stimeo--color-picker-target=\"slider\"\n * data-action=\"keydown->stimeo--color-picker#onKeydown\n * pointerdown->stimeo--color-picker#onPointerDown\"></div>\n * <!-- saturation / lightness / alpha sliders share the same contract -->\n * <input type=\"text\" aria-label=\"Hex color\"\n * data-stimeo--color-picker-target=\"hex\"\n * data-action=\"change->stimeo--color-picker#onHexInput\" />\n * <div data-stimeo--color-picker-target=\"preview\" aria-hidden=\"true\"></div>\n * <input type=\"hidden\" data-stimeo--color-picker-target=\"field\" />\n * </div>\n *\n * Decomposes color selection into independent APG **Slider** channels (hue,\n * saturation, lightness, optional alpha) instead of a 2-D palette, so every\n * adjustment is keyboard- and screen-reader-operable. Each slider exposes\n * `aria-valuenow` and a human-readable `aria-valuetext` (e.g. \"Hue 210 degrees\");\n * the hex input stays two-way synced; the current color is published on the\n * preview and root as the `--stimeo--color` custom property and mirrored into a\n * hidden form field.\n *\n * @remarks\n * Behavior only — the swatch/gradient visuals are the consumer's CSS/canvas, fed\n * by `--stimeo--color`. Only the consumer knows whether a channel track mirrors\n * under RTL: set `logicalTrack` to declare that it does, and the pointer mapping\n * and horizontal arrow pair follow the writing direction. Left unset, nothing\n * here reads `direction`. A gradient has no logical `to` keyword, so mirroring\n * one means swapping `to right`/`to left` under a `:dir(rtl)` selector.\n *\n * Pointer-drag listeners on `document` are bound to an\n * {@link AbortController} and released on drag end and on `disconnect()` (Turbo\n * navigation included). Color is fully reconstructable from the `value` (hex), so\n * there is no transient state to restore after a Turbo cache/morph.\n *\n * The internal model is integer HSL(A), so a hex → HSL → hex round-trip is not\n * exactly bijective: a typed hex can normalize to a near (not identical) value\n * once the HSL sliders are touched. This keeps the model small and zero-dep; use a\n * dedicated color library on the consumer side if exact hex preservation matters.\n */\nexport class ColorPickerController extends Controller<HTMLElement> {\n static override targets = [\"slider\", \"hex\", \"preview\", \"field\"];\n static override values = {\n value: { type: String, default: \"#000000\" },\n alpha: { type: Boolean, default: false },\n logicalTrack: { type: Boolean, default: false },\n };\n static actions = [\"onHexInput\", \"onKeydown\", \"onPointerDown\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly sliderTargets: HTMLElement[];\n declare readonly hexTarget: HTMLInputElement;\n declare readonly hasHexTarget: boolean;\n declare readonly previewTargets: HTMLElement[];\n declare readonly fieldTargets: HTMLInputElement[];\n declare valueValue: string;\n declare alphaValue: boolean;\n declare logicalTrackValue: boolean;\n\n /** Whether the consumer declared a mirroring track and the direction mirrors it. */\n get #mirrored(): boolean {\n return this.logicalTrackValue && isRtl(this.element);\n }\n\n /** The current color in the editing model. */\n #color: Hsla = { hue: 0, saturation: 0, lightness: 0, alpha: 100 };\n /** Aborts in-progress pointer-drag listeners on drag end / teardown. */\n #dragAbort: AbortController | null = null;\n\n /** Seeds the model from the initial hex value and renders every surface. */\n /**\n * Collapses a morph that swaps render inputs into one repaint, and refuses the\n * pass Stimulus delivers before `connect()`.\n */\n readonly #repaint = new MicrotaskCoalescer(() => {\n this.#render();\n });\n\n override connect(): void {\n this.#repaint.activate();\n const parsed = hexToHsla(this.valueValue);\n // When alpha is disabled, drop any alpha carried by an `#RRGGBBAA` value so\n // the model stays opaque — otherwise `hexString()` would emit `#RRGGBB`\n // while `change` reported `rgba.a < 1`. Mirrors `onHexInput()`.\n if (parsed) this.#color = this.alphaValue ? parsed : { ...parsed, alpha: 100 };\n this.#render();\n }\n\n /** Cancels any active pointer drag so document listeners never leak. */\n override disconnect(): void {\n this.#repaint.cancel();\n this.#dragAbort?.abort();\n this.#dragAbort = null;\n }\n\n /** Repaints when application code (or a Turbo morph) changes `alpha` at runtime. */\n alphaValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Keyboard stepping on the focused channel slider (APG Slider model). */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n const slider = event.currentTarget as HTMLElement;\n const channel = this.#channelOf(slider);\n if (!channel) return;\n\n const [min, max] = this.#rangeOf(slider, channel);\n const value = this.#color[channel];\n let next: number | null = null;\n // On a mirrored track the greater value sits at the visual left, so the\n // horizontal pair trades places; the vertical pair passes through.\n switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = value + 1;\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = value - 1;\n break;\n case \"PageUp\":\n next = value + 10;\n break;\n case \"PageDown\":\n next = value - 10;\n break;\n case \"Home\":\n next = min;\n break;\n case \"End\":\n next = max;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#setChannel(channel, next, min, max);\n }\n\n /** Begins a pointer drag on a channel slider and tracks movement. */\n onPointerDown(event: PointerEvent): void {\n const slider = event.currentTarget as HTMLElement;\n const channel = this.#channelOf(slider);\n if (!channel) return;\n event.preventDefault();\n slider.focus();\n\n const [min, max] = this.#rangeOf(slider, channel);\n // Resolve the direction once for the whole gesture: reading it per move\n // would query computed style on every frame, and a drag that flipped\n // mid-gesture would be incoherent anyway.\n const mirrored = this.#mirrored;\n const update = (clientX: number): void => {\n const rect = slider.getBoundingClientRect();\n if (rect.width === 0) return;\n const offset = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));\n const fraction = mirrored ? 1 - offset : offset;\n this.#setChannel(channel, min + fraction * (max - min), min, max);\n };\n update(event.clientX);\n\n this.#dragAbort?.abort();\n const abort = new AbortController();\n this.#dragAbort = abort;\n const onMove = (move: PointerEvent): void => update(move.clientX);\n const onUp = (): void => {\n abort.abort();\n this.#dragAbort = null;\n };\n document.addEventListener(\"pointermove\", onMove, { signal: abort.signal });\n document.addEventListener(\"pointerup\", onUp, { signal: abort.signal });\n document.addEventListener(\"pointercancel\", onUp, { signal: abort.signal });\n }\n\n /** Parses the hex input on confirm and syncs every channel + surface. */\n onHexInput(): void {\n if (!this.hasHexTarget) return;\n const parsed = hexToHsla(this.hexTarget.value);\n if (!parsed) {\n // Reject invalid input by restoring the last valid hex.\n this.hexTarget.value = this.#hexString();\n return;\n }\n this.#color = this.alphaValue ? parsed : { ...parsed, alpha: 100 };\n this.#render();\n }\n\n /** Clamps and snaps one channel to an integer, then re-renders + emits change. */\n #setChannel(channel: Channel, raw: number, min: number, max: number): void {\n this.#color[channel] = Math.round(Math.min(max, Math.max(min, raw)));\n this.#render();\n }\n\n /**\n * Reflects the model onto sliders, the hex input, preview, and form field.\n *\n * @stimeoRenderRoot\n */\n #render(): void {\n for (const slider of this.sliderTargets) {\n const channel = this.#channelOf(slider);\n if (!channel) continue;\n const value = this.#color[channel];\n slider.setAttribute(\"aria-valuenow\", String(value));\n slider.setAttribute(\"aria-valuetext\", valueText(channel, value));\n }\n\n const hex = this.#hexString();\n if (this.hasHexTarget) this.hexTarget.value = hex;\n for (const field of this.fieldTargets) field.value = hex;\n for (const preview of this.previewTargets) preview.style.setProperty(COLOR_PROPERTY, hex);\n this.element.style.setProperty(COLOR_PROPERTY, hex);\n\n const rgb = hslToRgb(this.#color.hue, this.#color.saturation, this.#color.lightness);\n this.dispatch(\"change\", {\n detail: { value: hex, rgba: { ...rgb, a: this.#color.alpha / 100 } },\n });\n }\n\n /** The current color as `#RRGGBB`, or `#RRGGBBAA` when alpha is enabled. */\n #hexString(): string {\n const rgb = hslToRgb(this.#color.hue, this.#color.saturation, this.#color.lightness);\n const base = `#${hex2(rgb.r)}${hex2(rgb.g)}${hex2(rgb.b)}`;\n if (!this.alphaValue) return base;\n return `${base}${hex2(Math.round((this.#color.alpha / 100) * 255))}`;\n }\n\n /** Reads a slider's `data-channel`, if it is a known channel. */\n #channelOf(slider: HTMLElement): Channel | null {\n const channel = slider.getAttribute(\"data-channel\");\n return channel && channel in CHANNEL_RANGE ? (channel as Channel) : null;\n }\n\n /**\n * A slider's `[min, max]` from aria-valuemin/max, falling back per channel.\n * An absent or blank attribute means \"not authored\", not zero — coercing it to\n * a number would pin the channel at `0` and make the per-channel default\n * unreachable.\n */\n #rangeOf(slider: HTMLElement, channel: Channel): [number, number] {\n const [defMin, defMax] = CHANNEL_RANGE[channel];\n return [\n toFiniteNumber(slider.getAttribute(\"aria-valuemin\")) ?? defMin,\n toFiniteNumber(slider.getAttribute(\"aria-valuemax\")) ?? defMax,\n ];\n }\n}\n\n/** Capitalizes a channel name for `aria-valuetext` (e.g. \"Hue\"). */\nfunction valueText(channel: Channel, value: number): string {\n const label = channel.charAt(0).toUpperCase() + channel.slice(1);\n const unit = channel === \"hue\" ? \"degrees\" : \"percent\";\n return `${label} ${value} ${unit}`;\n}\n\n/** Formats a 0–255 byte as a 2-digit lowercase hex pair. */\nfunction hex2(value: number): string {\n return Math.round(Math.min(255, Math.max(0, value)))\n .toString(16)\n .padStart(2, \"0\");\n}\n\n/** Converts HSL (h:0–360, s/l:0–100) to RGB bytes (0–255). */\nfunction hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {\n const sat = s / 100;\n const light = l / 100;\n const c = (1 - Math.abs(2 * light - 1)) * sat;\n const hp = (((h % 360) + 360) % 360) / 60;\n const x = c * (1 - Math.abs((hp % 2) - 1));\n let r = 0;\n let g = 0;\n let b = 0;\n if (hp >= 0 && hp < 1) [r, g, b] = [c, x, 0];\n else if (hp < 2) [r, g, b] = [x, c, 0];\n else if (hp < 3) [r, g, b] = [0, c, x];\n else if (hp < 4) [r, g, b] = [0, x, c];\n else if (hp < 5) [r, g, b] = [x, 0, c];\n else [r, g, b] = [c, 0, x];\n const m = light - c / 2;\n return {\n r: Math.round((r + m) * 255),\n g: Math.round((g + m) * 255),\n b: Math.round((b + m) * 255),\n };\n}\n\n/** Converts RGB bytes (0–255) to HSL (h:0–360, s/l:0–100). */\nfunction rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n const delta = max - min;\n const l = (max + min) / 2;\n\n let h = 0;\n if (delta !== 0) {\n if (max === rn) h = ((gn - bn) / delta) % 6;\n else if (max === gn) h = (bn - rn) / delta + 2;\n else h = (rn - gn) / delta + 4;\n h = (h * 60 + 360) % 360;\n }\n const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\n/**\n * Parses `#RGB`, `#RGBA`, `#RRGGBB`, or `#RRGGBBAA` into the HSLA model, or\n * returns null when the string is not a valid hex color.\n */\nfunction hexToHsla(input: string): Hsla | null {\n const hex = input.trim().replace(/^#/, \"\");\n if (!/^(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(hex)) return null;\n\n // Expand shorthand (#RGB / #RGBA) so every channel is a full byte pair.\n const full =\n hex.length <= 4\n ? hex\n .split(\"\")\n .map((char) => char + char)\n .join(\"\")\n : hex;\n\n const r = Number.parseInt(full.slice(0, 2), 16);\n const g = Number.parseInt(full.slice(2, 4), 16);\n const b = Number.parseInt(full.slice(4, 6), 16);\n const a = full.length === 8 ? Number.parseInt(full.slice(6, 8), 16) : 255;\n\n const { h, s, l } = rgbToHsl(r, g, b);\n return { hue: h, saturation: s, lightness: l, alpha: Math.round((a / 255) * 100) };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/coerce.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/color_picker_controller.ts"],"names":[],"mappings":"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","file":"color_picker_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 * Numeric coercion shared by the value-bearing controllers (progress, meter,\n * color-picker).\n *\n * Stimulus already coerces numeric action params to numbers, but a value can also\n * arrive as a string — via a `*:set` CustomEvent `detail`, or an action param\n * whose attribute does not look numeric. Centralizing the parse keeps `setValue`\n * tolerant of either form while rejecting anything that is not a finite number.\n */\n\n/**\n * Coerces `raw` to a finite number, or returns `null` when it is absent, empty,\n * or not parseable. Empty strings are treated as \"no value\" rather than `0`, so a\n * stray blank param cannot silently reset the value.\n */\nexport function toFiniteNumber(raw: number | string | null | undefined): number | null {\n if (raw === null || raw === undefined || raw === \"\") return null;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { toFiniteNumber } from \"../utils/coerce\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** CSS custom property exposing the current color to consumer CSS. */\nconst COLOR_PROPERTY = \"--stimeo--color\";\n\n/** A color channel slider, identified by its `data-channel` attribute. */\ntype Channel = \"hue\" | \"saturation\" | \"lightness\" | \"alpha\";\n\n/** Default `[min, max]` per channel when the slider omits aria-valuemin/max. */\nconst CHANNEL_RANGE: Record<Channel, [number, number]> = {\n hue: [0, 360],\n saturation: [0, 100],\n lightness: [0, 100],\n alpha: [0, 100],\n};\n\n/** Internal color model: HSL plus an alpha percentage (0–100). */\ninterface Hsla {\n hue: number;\n saturation: number;\n lightness: number;\n alpha: number;\n}\n\n/**\n * Headless, accessible **Color Picker** behavior.\n *\n * Markup contract (identifier: `stimeo--color-picker`):\n * <div data-controller=\"stimeo--color-picker\"\n * data-stimeo--color-picker-value-value=\"#3366cc\">\n * <div role=\"slider\" aria-label=\"Hue\" data-channel=\"hue\" tabindex=\"0\"\n * aria-valuemin=\"0\" aria-valuemax=\"360\" aria-valuenow=\"210\"\n * data-stimeo--color-picker-target=\"slider\"\n * data-action=\"keydown->stimeo--color-picker#onKeydown\n * pointerdown->stimeo--color-picker#onPointerDown\"></div>\n * <!-- saturation / lightness / alpha sliders share the same contract -->\n * <input type=\"text\" aria-label=\"Hex color\"\n * data-stimeo--color-picker-target=\"hex\"\n * data-action=\"change->stimeo--color-picker#onHexInput\" />\n * <div data-stimeo--color-picker-target=\"preview\" aria-hidden=\"true\"></div>\n * <input type=\"hidden\" data-stimeo--color-picker-target=\"field\" />\n * </div>\n *\n * Decomposes color selection into independent APG **Slider** channels (hue,\n * saturation, lightness, optional alpha) instead of a 2-D palette, so every\n * adjustment is keyboard- and screen-reader-operable. Each slider exposes\n * `aria-valuenow` and a human-readable `aria-valuetext` (e.g. \"Hue 210 degrees\");\n * the hex input stays two-way synced; the current color is published on the\n * preview and root as the `--stimeo--color` custom property and mirrored into a\n * hidden form field.\n *\n * @remarks\n * Behavior only — the swatch/gradient visuals are the consumer's CSS/canvas, fed\n * by `--stimeo--color`. Only the consumer knows whether a channel track mirrors\n * under RTL: set `logicalTrack` to declare that it does, and the pointer mapping\n * and horizontal arrow pair follow the writing direction. Left unset, nothing\n * here reads `direction`. A gradient has no logical `to` keyword, so mirroring\n * one means swapping `to right`/`to left` under a `:dir(rtl)` selector.\n *\n * Pointer-drag listeners on `document` are bound to an\n * {@link AbortController} and released on drag end and on `disconnect()` (Turbo\n * navigation included). Color is fully reconstructable from the `value` (hex), so\n * there is no transient state to restore after a Turbo cache/morph.\n *\n * The internal model is integer HSL(A), so a hex → HSL → hex round-trip is not\n * exactly bijective: a typed hex can normalize to a near (not identical) value\n * once the HSL sliders are touched. This keeps the model small and zero-dep; use a\n * dedicated color library on the consumer side if exact hex preservation matters.\n *\n * A color the user set through a slider or the hex input is reported as\n * `stimeo--color-picker:change`. Toggling `alpha` at runtime can move the\n * committed color without a user edit, and that arrives as\n * `stimeo--color-picker:reconcile` with the same detail. Neither fires on connect.\n */\nexport class ColorPickerController extends Controller<HTMLElement> {\n static override targets = [\"slider\", \"hex\", \"preview\", \"field\"];\n static override values = {\n value: { type: String, default: \"#000000\" },\n alpha: { type: Boolean, default: false },\n logicalTrack: { type: Boolean, default: false },\n };\n static actions = [\"onHexInput\", \"onKeydown\", \"onPointerDown\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly sliderTargets: HTMLElement[];\n declare readonly hexTarget: HTMLInputElement;\n declare readonly hasHexTarget: boolean;\n declare readonly previewTargets: HTMLElement[];\n declare readonly fieldTargets: HTMLInputElement[];\n declare valueValue: string;\n declare alphaValue: boolean;\n declare logicalTrackValue: boolean;\n\n /** Whether the consumer declared a mirroring track and the direction mirrors it. */\n get #mirrored(): boolean {\n return this.logicalTrackValue && isRtl(this.element);\n }\n\n /** The current color in the editing model. */\n #color: Hsla = { hue: 0, saturation: 0, lightness: 0, alpha: 100 };\n /** Aborts in-progress pointer-drag listeners on drag end / teardown. */\n #dragAbort: AbortController | null = null;\n /** Color the last repaint settled on, so a configuration-driven move is reported once. */\n #committedHex: string | null = null;\n\n /**\n * Collapses a morph that swaps render inputs into one repaint, and refuses the\n * pass Stimulus delivers before `connect()`.\n */\n readonly #repaint = new MicrotaskCoalescer(() => this.#reconcileColor());\n\n /** Seeds the model from the initial hex value and renders every surface. */\n override connect(): void {\n this.#repaint.activate();\n const parsed = hexToHsla(this.valueValue);\n // When alpha is disabled, drop any alpha carried by an `#RRGGBBAA` value so\n // the model stays opaque — otherwise `hexString()` would emit `#RRGGBB`\n // while `change` reported `rgba.a < 1`. Mirrors `onHexInput()`.\n if (parsed) this.#color = this.alphaValue ? parsed : { ...parsed, alpha: 100 };\n this.#render();\n }\n\n /** Cancels any active pointer drag so document listeners never leak. */\n override disconnect(): void {\n this.#repaint.cancel();\n this.#dragAbort?.abort();\n this.#dragAbort = null;\n }\n\n /** Repaints when application code (or a Turbo morph) changes `alpha` at runtime. */\n alphaValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Keyboard stepping on the focused channel slider (APG Slider model). */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n const slider = event.currentTarget as HTMLElement;\n const channel = this.#channelOf(slider);\n if (!channel) return;\n\n const [min, max] = this.#rangeOf(slider, channel);\n const value = this.#color[channel];\n let next: number | null = null;\n // On a mirrored track the greater value sits at the visual left, so the\n // horizontal pair trades places; the vertical pair passes through.\n switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = value + 1;\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = value - 1;\n break;\n case \"PageUp\":\n next = value + 10;\n break;\n case \"PageDown\":\n next = value - 10;\n break;\n case \"Home\":\n next = min;\n break;\n case \"End\":\n next = max;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#setChannel(channel, next, min, max);\n }\n\n /** Begins a pointer drag on a channel slider and tracks movement. */\n onPointerDown(event: PointerEvent): void {\n const slider = event.currentTarget as HTMLElement;\n const channel = this.#channelOf(slider);\n if (!channel) return;\n event.preventDefault();\n slider.focus();\n\n const [min, max] = this.#rangeOf(slider, channel);\n // Resolve the direction once for the whole gesture: reading it per move\n // would query computed style on every frame, and a drag that flipped\n // mid-gesture would be incoherent anyway.\n const mirrored = this.#mirrored;\n const update = (clientX: number): void => {\n const rect = slider.getBoundingClientRect();\n if (rect.width === 0) return;\n const offset = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));\n const fraction = mirrored ? 1 - offset : offset;\n this.#setChannel(channel, min + fraction * (max - min), min, max);\n };\n update(event.clientX);\n\n this.#dragAbort?.abort();\n const abort = new AbortController();\n this.#dragAbort = abort;\n const onMove = (move: PointerEvent): void => update(move.clientX);\n const onUp = (): void => {\n abort.abort();\n this.#dragAbort = null;\n };\n document.addEventListener(\"pointermove\", onMove, { signal: abort.signal });\n document.addEventListener(\"pointerup\", onUp, { signal: abort.signal });\n document.addEventListener(\"pointercancel\", onUp, { signal: abort.signal });\n }\n\n /** Parses the hex input on confirm and syncs every channel + surface. */\n onHexInput(): void {\n if (!this.hasHexTarget) return;\n const parsed = hexToHsla(this.hexTarget.value);\n if (!parsed) {\n // Reject invalid input by restoring the last valid hex.\n this.hexTarget.value = this.#hexString();\n return;\n }\n this.#color = this.alphaValue ? parsed : { ...parsed, alpha: 100 };\n this.#commitColor();\n }\n\n /** Clamps and snaps one channel to an integer, then re-renders + emits change. */\n #setChannel(channel: Channel, raw: number, min: number, max: number): void {\n this.#color[channel] = Math.round(Math.min(max, Math.max(min, raw)));\n this.#commitColor();\n }\n\n /**\n * Renders the model and reports a color the user actually moved. A key pressed\n * at a bound, a pointer that lands on the step already showing, and a re-confirmed\n * hex all leave the committed color where it was, so no `change` describes them.\n */\n #commitColor(): void {\n const previous = this.#committedHex;\n this.#render();\n if (this.#committedHex !== previous) {\n this.dispatch(\"change\", { detail: this.#settledDetail() });\n }\n }\n\n /**\n * Reflects the model onto sliders, the hex input, preview, and form field.\n *\n * @stimeoRenderRoot\n */\n #render(): void {\n for (const slider of this.sliderTargets) {\n const channel = this.#channelOf(slider);\n if (!channel) continue;\n const value = this.#color[channel];\n slider.setAttribute(\"aria-valuenow\", String(value));\n slider.setAttribute(\"aria-valuetext\", valueText(channel, value));\n }\n\n const hex = this.#hexString();\n this.#committedHex = hex;\n if (this.hasHexTarget) this.hexTarget.value = hex;\n for (const field of this.fieldTargets) field.value = hex;\n for (const preview of this.previewTargets) preview.style.setProperty(COLOR_PROPERTY, hex);\n this.element.style.setProperty(COLOR_PROPERTY, hex);\n }\n\n /**\n * Repaints after `alpha` changed at runtime and reports a color this controller\n * settled on. Disabling alpha drops it from the model, so the committed color can\n * move without a user edit; `change` stays reserved for the picker's own actions.\n */\n #reconcileColor(): void {\n // Compared against the last rendered color, not against the model: the Value\n // callback that scheduled this pass has already moved `alphaValue`, so\n // re-deriving the \"before\" state here would always match the \"after\" one.\n const previous = this.#committedHex;\n this.#render();\n if (previous !== null && this.#committedHex !== previous) {\n this.dispatch(\"reconcile\", { detail: this.#settledDetail() });\n }\n }\n\n /** The settled color as event detail, shared by both report paths. */\n #settledDetail(): { value: string; rgba: { r: number; g: number; b: number; a: number } } {\n const rgb = hslToRgb(this.#color.hue, this.#color.saturation, this.#color.lightness);\n return { value: this.#hexString(), rgba: { ...rgb, a: this.#color.alpha / 100 } };\n }\n\n /** The current color as `#RRGGBB`, or `#RRGGBBAA` when alpha is enabled. */\n #hexString(): string {\n const rgb = hslToRgb(this.#color.hue, this.#color.saturation, this.#color.lightness);\n const base = `#${hex2(rgb.r)}${hex2(rgb.g)}${hex2(rgb.b)}`;\n if (!this.alphaValue) return base;\n return `${base}${hex2(Math.round((this.#color.alpha / 100) * 255))}`;\n }\n\n /** Reads a slider's `data-channel`, if it is a known channel. */\n #channelOf(slider: HTMLElement): Channel | null {\n const channel = slider.getAttribute(\"data-channel\");\n return channel && channel in CHANNEL_RANGE ? (channel as Channel) : null;\n }\n\n /**\n * A slider's `[min, max]` from aria-valuemin/max, falling back per channel.\n * An absent or blank attribute means \"not authored\", not zero — coercing it to\n * a number would pin the channel at `0` and make the per-channel default\n * unreachable.\n */\n #rangeOf(slider: HTMLElement, channel: Channel): [number, number] {\n const [defMin, defMax] = CHANNEL_RANGE[channel];\n return [\n toFiniteNumber(slider.getAttribute(\"aria-valuemin\")) ?? defMin,\n toFiniteNumber(slider.getAttribute(\"aria-valuemax\")) ?? defMax,\n ];\n }\n}\n\n/** Capitalizes a channel name for `aria-valuetext` (e.g. \"Hue\"). */\nfunction valueText(channel: Channel, value: number): string {\n const label = channel.charAt(0).toUpperCase() + channel.slice(1);\n const unit = channel === \"hue\" ? \"degrees\" : \"percent\";\n return `${label} ${value} ${unit}`;\n}\n\n/** Formats a 0–255 byte as a 2-digit lowercase hex pair. */\nfunction hex2(value: number): string {\n return Math.round(Math.min(255, Math.max(0, value)))\n .toString(16)\n .padStart(2, \"0\");\n}\n\n/** Converts HSL (h:0–360, s/l:0–100) to RGB bytes (0–255). */\nfunction hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {\n const sat = s / 100;\n const light = l / 100;\n const c = (1 - Math.abs(2 * light - 1)) * sat;\n const hp = (((h % 360) + 360) % 360) / 60;\n const x = c * (1 - Math.abs((hp % 2) - 1));\n let r = 0;\n let g = 0;\n let b = 0;\n if (hp >= 0 && hp < 1) [r, g, b] = [c, x, 0];\n else if (hp < 2) [r, g, b] = [x, c, 0];\n else if (hp < 3) [r, g, b] = [0, c, x];\n else if (hp < 4) [r, g, b] = [0, x, c];\n else if (hp < 5) [r, g, b] = [x, 0, c];\n else [r, g, b] = [c, 0, x];\n const m = light - c / 2;\n return {\n r: Math.round((r + m) * 255),\n g: Math.round((g + m) * 255),\n b: Math.round((b + m) * 255),\n };\n}\n\n/** Converts RGB bytes (0–255) to HSL (h:0–360, s/l:0–100). */\nfunction rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n const delta = max - min;\n const l = (max + min) / 2;\n\n let h = 0;\n if (delta !== 0) {\n if (max === rn) h = ((gn - bn) / delta) % 6;\n else if (max === gn) h = (bn - rn) / delta + 2;\n else h = (rn - gn) / delta + 4;\n h = (h * 60 + 360) % 360;\n }\n const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\n/**\n * Parses `#RGB`, `#RGBA`, `#RRGGBB`, or `#RRGGBBAA` into the HSLA model, or\n * returns null when the string is not a valid hex color.\n */\nfunction hexToHsla(input: string): Hsla | null {\n const hex = input.trim().replace(/^#/, \"\");\n if (!/^(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(hex)) return null;\n\n // Expand shorthand (#RGB / #RGBA) so every channel is a full byte pair.\n const full =\n hex.length <= 4\n ? hex\n .split(\"\")\n .map((char) => char + char)\n .join(\"\")\n : hex;\n\n const r = Number.parseInt(full.slice(0, 2), 16);\n const g = Number.parseInt(full.slice(2, 4), 16);\n const b = Number.parseInt(full.slice(4, 6), 16);\n const a = full.length === 8 ? Number.parseInt(full.slice(6, 8), 16) : 255;\n\n const { h, s, l } = rgbToHsl(r, g, b);\n return { hue: h, saturation: s, lightness: l, alpha: Math.round((a / 255) * 100) };\n}\n"]}
|
|
@@ -8,7 +8,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
8
8
|
* Markup contract (identifier: `stimeo--date-range-picker`):
|
|
9
9
|
* <div data-controller="stimeo--date-range-picker"
|
|
10
10
|
* data-stimeo--date-range-picker-min-value="2026-01-01"
|
|
11
|
-
* data-stimeo--date-range-picker-max-value="2026-12-31"
|
|
11
|
+
* data-stimeo--date-range-picker-max-value="2026-12-31"
|
|
12
|
+
* data-stimeo--date-range-picker-disabled-dates-value='["2026-06-15"]'>
|
|
12
13
|
* <button data-action="stimeo--date-range-picker#prev">Prev</button>
|
|
13
14
|
* <span data-stimeo--date-range-picker-target="monthLabel" aria-live="polite"></span>
|
|
14
15
|
* <button data-action="stimeo--date-range-picker#next">Next</button>
|
|
@@ -36,8 +37,18 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
36
37
|
*
|
|
37
38
|
* Selection model: the first click/Enter sets a *pending* start and enters
|
|
38
39
|
* "selecting" mode (preview follows the pointer/focus); the second confirms the
|
|
39
|
-
* end (auto-swapped if earlier than the start) and dispatches `change
|
|
40
|
-
* abandons an in-progress selection,
|
|
40
|
+
* end (auto-swapped if earlier than the start) and dispatches `change` with
|
|
41
|
+
* `{ start, end }` ISO dates. Escape abandons an in-progress selection,
|
|
42
|
+
* restoring the last confirmed range.
|
|
43
|
+
*
|
|
44
|
+
* Availability is declarative on both axes: `min` / `max` bound the selectable
|
|
45
|
+
* interval and `disabledDates` excludes individual dates inside it. Both are
|
|
46
|
+
* render inputs, so a cell's `aria-disabled` is derived during the paint that
|
|
47
|
+
* needs it rather than written back afterwards — selection, preview, and the
|
|
48
|
+
* grid paint all read one predicate and cannot disagree. A painted-month
|
|
49
|
+
* navigation dispatches `monthchange` with `{ month }` as a notification (for
|
|
50
|
+
* fetching a month's availability, say); the fetched result comes back in
|
|
51
|
+
* through `disabledDates`, which repaints on its own.
|
|
41
52
|
*/
|
|
42
53
|
declare class DateRangePickerController extends Controller<HTMLElement> {
|
|
43
54
|
#private;
|
|
@@ -51,9 +62,13 @@ declare class DateRangePickerController extends Controller<HTMLElement> {
|
|
|
51
62
|
type: StringConstructor;
|
|
52
63
|
default: string;
|
|
53
64
|
};
|
|
65
|
+
disabledDates: {
|
|
66
|
+
type: StringConstructor;
|
|
67
|
+
default: string;
|
|
68
|
+
};
|
|
54
69
|
};
|
|
55
70
|
static actions: readonly ["applyPreset", "next", "onKeydown", "prev", "previewTo", "selectDate"];
|
|
56
|
-
static events: readonly ["change"];
|
|
71
|
+
static events: readonly ["change", "monthchange"];
|
|
57
72
|
readonly gridTarget: HTMLElement;
|
|
58
73
|
readonly hasGridTarget: boolean;
|
|
59
74
|
readonly monthLabelTarget: HTMLElement;
|
|
@@ -67,6 +82,8 @@ declare class DateRangePickerController extends Controller<HTMLElement> {
|
|
|
67
82
|
readonly hasEndFieldTarget: boolean;
|
|
68
83
|
minValue: string;
|
|
69
84
|
maxValue: string;
|
|
85
|
+
disabledDatesValue: string;
|
|
86
|
+
/** Seeds and normalizes the range from optional hidden fields, then paints the grid. */
|
|
70
87
|
connect(): void;
|
|
71
88
|
/** Cancels any pending deferred focus so it never fires on a detached element. */
|
|
72
89
|
disconnect(): void;
|
|
@@ -74,6 +91,8 @@ declare class DateRangePickerController extends Controller<HTMLElement> {
|
|
|
74
91
|
minValueChanged(): void;
|
|
75
92
|
/** Repaints when application code (or a Turbo morph) changes `max` at runtime. */
|
|
76
93
|
maxValueChanged(): void;
|
|
94
|
+
/** Re-indexes the unavailable dates and repaints when the declared set changes. */
|
|
95
|
+
disabledDatesValueChanged(): void;
|
|
77
96
|
/** Navigates to the previous month. */
|
|
78
97
|
prev(event?: Event): void;
|
|
79
98
|
/** Navigates to the next month. */
|