stimeo-ui 0.1.0.pre.alpha.1

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.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +72 -0
  4. data/dist/controllers/accordion_controller.js +76 -0
  5. data/dist/controllers/announcer_controller.js +184 -0
  6. data/dist/controllers/aspect_ratio_controller.js +36 -0
  7. data/dist/controllers/auto_submit_controller.js +147 -0
  8. data/dist/controllers/avatar_controller.js +66 -0
  9. data/dist/controllers/breadcrumb_controller.js +123 -0
  10. data/dist/controllers/bulk_select_controller.js +104 -0
  11. data/dist/controllers/calendar_controller.js +394 -0
  12. data/dist/controllers/character_counter_controller.js +179 -0
  13. data/dist/controllers/checkbox_controller.js +73 -0
  14. data/dist/controllers/combobox_controller.js +186 -0
  15. data/dist/controllers/command_palette_controller.js +381 -0
  16. data/dist/controllers/conditional_fields_controller.js +112 -0
  17. data/dist/controllers/confirm_controller.js +276 -0
  18. data/dist/controllers/context_menu_controller.js +112 -0
  19. data/dist/controllers/countdown_controller.js +202 -0
  20. data/dist/controllers/dialog_controller.js +207 -0
  21. data/dist/controllers/direct_upload_controller.js +212 -0
  22. data/dist/controllers/dirty_form_controller.js +128 -0
  23. data/dist/controllers/dropdown_controller.js +66 -0
  24. data/dist/controllers/empty_state_controller.js +67 -0
  25. data/dist/controllers/flash_controller.js +221 -0
  26. data/dist/controllers/focus_controller.js +216 -0
  27. data/dist/controllers/form_field_controller.js +154 -0
  28. data/dist/controllers/form_validation_controller.js +202 -0
  29. data/dist/controllers/frame_loading_controller.js +177 -0
  30. data/dist/controllers/highlight_controller.js +107 -0
  31. data/dist/controllers/hover_card_controller.js +165 -0
  32. data/dist/controllers/idle_controller.js +141 -0
  33. data/dist/controllers/input_mask_controller.js +166 -0
  34. data/dist/controllers/lazy_frame_controller.js +68 -0
  35. data/dist/controllers/listbox_controller.js +256 -0
  36. data/dist/controllers/local_time_controller.js +81 -0
  37. data/dist/controllers/menu_controller.js +134 -0
  38. data/dist/controllers/meter_controller.js +96 -0
  39. data/dist/controllers/nested_form_controller.js +131 -0
  40. data/dist/controllers/network_status_controller.js +126 -0
  41. data/dist/controllers/number_input_controller.js +306 -0
  42. data/dist/controllers/otp_controller.js +201 -0
  43. data/dist/controllers/overflow_indicator_controller.js +169 -0
  44. data/dist/controllers/overflow_menu_controller.js +274 -0
  45. data/dist/controllers/pagination_controller.js +89 -0
  46. data/dist/controllers/password_strength_controller.js +175 -0
  47. data/dist/controllers/persist_controller.js +259 -0
  48. data/dist/controllers/popover_controller.js +94 -0
  49. data/dist/controllers/portal_controller.js +63 -0
  50. data/dist/controllers/preview_guard_controller.js +69 -0
  51. data/dist/controllers/progress_controller.js +93 -0
  52. data/dist/controllers/radio_group_controller.js +128 -0
  53. data/dist/controllers/rating_controller.js +179 -0
  54. data/dist/controllers/relative_time_controller.js +129 -0
  55. data/dist/controllers/reset_before_cache_controller.js +62 -0
  56. data/dist/controllers/resizable_controller.js +163 -0
  57. data/dist/controllers/roving_controller.js +116 -0
  58. data/dist/controllers/scroll_area_controller.js +183 -0
  59. data/dist/controllers/scroll_visibility_controller.js +103 -0
  60. data/dist/controllers/scrollspy_controller.js +171 -0
  61. data/dist/controllers/skeleton_controller.js +125 -0
  62. data/dist/controllers/slider_controller.js +109 -0
  63. data/dist/controllers/spinner_controller.js +164 -0
  64. data/dist/controllers/step_indicator_controller.js +55 -0
  65. data/dist/controllers/stepper_controller.js +78 -0
  66. data/dist/controllers/stick_to_bottom_controller.js +100 -0
  67. data/dist/controllers/sticky_observer_controller.js +53 -0
  68. data/dist/controllers/submit_once_controller.js +206 -0
  69. data/dist/controllers/switch_controller.js +50 -0
  70. data/dist/controllers/tabs_controller.js +63 -0
  71. data/dist/controllers/textarea_autosize_controller.js +72 -0
  72. data/dist/controllers/theme_controller.js +154 -0
  73. data/dist/controllers/toast_controller.js +310 -0
  74. data/dist/controllers/toggle_group_controller.js +130 -0
  75. data/dist/controllers/toolbar_controller.js +113 -0
  76. data/dist/controllers/tooltip_controller.js +165 -0
  77. data/dist/controllers/transition_controller.js +203 -0
  78. data/dist/index.js +12241 -0
  79. data/dist/positioning/index.js +145 -0
  80. data/lib/generators/stimeo/install/install_generator.rb +114 -0
  81. data/lib/generators/stimeo/install/templates/stimeo.js +12 -0
  82. data/lib/stimeo/ui/version.rb +10 -0
  83. data/lib/stimeo/ui.rb +19 -0
  84. data/lib/stimeo-ui.rb +4 -0
  85. metadata +152 -0
@@ -0,0 +1,381 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ // src/controllers/command_palette_controller.ts
4
+
5
+ // src/utils/focus_trap.ts
6
+ var FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
7
+ var FocusTrap = class {
8
+ /** The element focused before activation, restored on deactivation. */
9
+ #previouslyFocused = null;
10
+ /** The body's inline `overflow` before locking, restored on deactivation. */
11
+ #previousBodyOverflow = "";
12
+ /** Whether scroll was locked this activation (so it is only restored if applied). */
13
+ #scrollLocked = false;
14
+ /** Background siblings made `inert` while active, restored on deactivation. */
15
+ #inertedSiblings = [];
16
+ /** Whether the modal side effects are currently applied. */
17
+ #activeState = false;
18
+ /** Returns the trapped element; called on every operation for the live target. */
19
+ #getContainer;
20
+ /** Closing/focus hooks; see {@link FocusTrapOptions}. */
21
+ #options;
22
+ /**
23
+ * @param getContainer - Returns the trapped element. Called on every operation
24
+ * so the live target is always used.
25
+ * @param options - Closing/focus hooks; see {@link FocusTrapOptions}.
26
+ */
27
+ constructor(getContainer, options = {}) {
28
+ this.#getContainer = getContainer;
29
+ this.#options = options;
30
+ }
31
+ /** Whether the trap is currently active. */
32
+ get active() {
33
+ return this.#activeState;
34
+ }
35
+ /**
36
+ * Applies the trap: records the current focus, optionally locks background scroll
37
+ * and makes background siblings `inert`, listens for `Tab`/`Escape`, and (unless
38
+ * `autoFocus` is off) moves focus inside. No-ops if already active.
39
+ */
40
+ activate() {
41
+ if (this.#activeState) return;
42
+ this.#activeState = true;
43
+ const active = document.activeElement;
44
+ this.#previouslyFocused = active instanceof HTMLElement && active !== document.body ? active : null;
45
+ if (this.#flag(this.#options.lockScroll, true)) {
46
+ this.#previousBodyOverflow = document.body.style.overflow;
47
+ document.body.style.overflow = "hidden";
48
+ this.#scrollLocked = true;
49
+ }
50
+ if (this.#flag(this.#options.isolate, true)) this.#isolateBackground();
51
+ document.addEventListener("keydown", this.#onKeydown);
52
+ if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
53
+ }
54
+ /**
55
+ * Reverts every side effect applied by {@link activate}. No-ops if inactive, so
56
+ * a controller can call it defensively from both `close()` and `disconnect()`.
57
+ *
58
+ * @param restoreFocus - Move focus back to the opener (default `true`). Pass
59
+ * `false` on teardown (`disconnect`), where yanking focus is undesirable.
60
+ */
61
+ deactivate({ restoreFocus = true } = {}) {
62
+ if (!this.#activeState) return;
63
+ this.#activeState = false;
64
+ document.removeEventListener("keydown", this.#onKeydown);
65
+ if (this.#scrollLocked) {
66
+ document.body.style.overflow = this.#previousBodyOverflow;
67
+ this.#scrollLocked = false;
68
+ }
69
+ this.#releaseBackground();
70
+ if (restoreFocus) {
71
+ const target = this.#previouslyFocused ?? this.#options.fallbackFocus?.() ?? null;
72
+ target?.focus();
73
+ }
74
+ }
75
+ /** Resolves a boolean-or-getter option, defaulting when it was not provided. */
76
+ #flag(option, fallback) {
77
+ if (option === void 0) return fallback;
78
+ return typeof option === "function" ? option() : option;
79
+ }
80
+ /** Handles `Escape` (delegated) and `Tab` (focus trap) while active. */
81
+ #onKeydown = (event) => {
82
+ if (event.key === "Escape") {
83
+ if (this.#options.onEscape) {
84
+ event.preventDefault();
85
+ this.#options.onEscape();
86
+ }
87
+ return;
88
+ }
89
+ if (event.key === "Tab") this.#trapTab(event);
90
+ };
91
+ /** Keeps `Tab` focus cycling within the container's focusable elements. */
92
+ #trapTab(event) {
93
+ const focusable = this.#focusableElements();
94
+ if (focusable.length === 0) {
95
+ event.preventDefault();
96
+ return;
97
+ }
98
+ const first = focusable[0];
99
+ const last = focusable[focusable.length - 1];
100
+ const active = document.activeElement;
101
+ if (!(active instanceof Node) || !this.#getContainer().contains(active)) {
102
+ event.preventDefault();
103
+ first?.focus();
104
+ return;
105
+ }
106
+ if (event.shiftKey && active === first) {
107
+ event.preventDefault();
108
+ last?.focus();
109
+ } else if (!event.shiftKey && active === last) {
110
+ event.preventDefault();
111
+ first?.focus();
112
+ }
113
+ }
114
+ /**
115
+ * Marks every element outside the container's subtree as `inert` so background
116
+ * content cannot be focused or reached by assistive technology, honoring the
117
+ * `aria-modal="true"` contract. An element that was *already* `inert` is left
118
+ * untracked so `#releaseBackground` does not wrongly clear it.
119
+ */
120
+ #isolateBackground() {
121
+ const container = this.#getContainer();
122
+ this.#inertedSiblings = [];
123
+ for (const sibling of Array.from(document.body.children)) {
124
+ if (!(sibling instanceof HTMLElement)) continue;
125
+ if (sibling.contains(container) || sibling.inert) continue;
126
+ sibling.inert = true;
127
+ this.#inertedSiblings.push(sibling);
128
+ }
129
+ }
130
+ /** Reverts the `inert` flags applied by `#isolateBackground`. */
131
+ #releaseBackground() {
132
+ for (const sibling of this.#inertedSiblings) {
133
+ sibling.inert = false;
134
+ }
135
+ this.#inertedSiblings = [];
136
+ }
137
+ /** Moves focus to the initial target, the first focusable, or the container. */
138
+ #focusInitial() {
139
+ const preferred = this.#options.initialFocus?.();
140
+ if (preferred) {
141
+ preferred.focus();
142
+ return;
143
+ }
144
+ const focusable = this.#focusableElements();
145
+ if (focusable[0]) {
146
+ focusable[0].focus();
147
+ return;
148
+ }
149
+ const container = this.#getContainer();
150
+ container.tabIndex = -1;
151
+ container.focus();
152
+ }
153
+ /** Collects the container's currently focusable descendants in DOM order. */
154
+ #focusableElements() {
155
+ return Array.from(this.#getContainer().querySelectorAll(FOCUSABLE)).filter(
156
+ (el) => !el.hidden
157
+ );
158
+ }
159
+ };
160
+
161
+ // src/controllers/command_palette_controller.ts
162
+ var CommandPaletteController = class extends Controller {
163
+ static targets = ["dialog", "input", "list", "option", "empty"];
164
+ static values = {
165
+ hotkey: { type: String, default: "mod+k" },
166
+ open: { type: Boolean, default: false }
167
+ };
168
+ static actions = [
169
+ "close",
170
+ "closeOnBackdrop",
171
+ "filter",
172
+ "onKeydown",
173
+ "open",
174
+ "selectByClick",
175
+ "toggle"
176
+ ];
177
+ static events = ["select"];
178
+ /** The index of the currently active option within the visible subset. */
179
+ #activeIndex = -1;
180
+ /**
181
+ * Owns the modal side effects (focus trap, scroll lock, background `inert`, focus
182
+ * restore). Escape closes; focus on open goes to the input, and is restored to
183
+ * whatever opened the palette on close.
184
+ */
185
+ #trap = new FocusTrap(() => this.dialogTarget, {
186
+ onEscape: () => this.close(),
187
+ initialFocus: () => this.hasInputTarget ? this.inputTarget : null
188
+ });
189
+ /**
190
+ * Initializes the global hotkey handler and establishes the initial open state.
191
+ *
192
+ * The DOM is the source of truth on reconnect (Turbo cache restore / morph): if
193
+ * the restored snapshot already shows the dialog open, honor that rather than
194
+ * re-deriving from the declarative `open` Value (which would slam a user-opened
195
+ * palette shut). The `open` Value only seeds the initial state of a genuinely
196
+ * fresh render. We normalize to a clean closed baseline first so {@link open}
197
+ * runs its full setup — the {@link FocusTrap} is a fresh instance after a
198
+ * reconnect and must be re-activated.
199
+ */
200
+ connect() {
201
+ document.addEventListener("keydown", this.#onGlobalKeydown);
202
+ const shouldOpen = this.#isOpen || this.openValue;
203
+ this.#resetToClosedState();
204
+ if (shouldOpen) this.open();
205
+ }
206
+ /** Tears down the global hotkey listener and reverts the modal side effects. */
207
+ disconnect() {
208
+ document.removeEventListener("keydown", this.#onGlobalKeydown);
209
+ this.#trap.deactivate({ restoreFocus: false });
210
+ this.#resetToClosedState();
211
+ }
212
+ /** Toggles the open state of the command palette. */
213
+ toggle() {
214
+ if (this.#isOpen) {
215
+ this.close();
216
+ } else {
217
+ this.open();
218
+ }
219
+ }
220
+ /** Opens the palette, traps focus, and shifts focus to the input. */
221
+ open() {
222
+ if (!this.hasDialogTarget || this.#isOpen) return;
223
+ this.dialogTarget.hidden = false;
224
+ this.openValue = true;
225
+ if (this.hasInputTarget) this.inputTarget.setAttribute("aria-expanded", "true");
226
+ this.#resetFilter();
227
+ this.#trap.activate();
228
+ }
229
+ /** Closes the palette and restores focus back to the opener. */
230
+ close() {
231
+ if (!this.hasDialogTarget || !this.#isOpen) return;
232
+ this.#resetToClosedState();
233
+ this.#trap.deactivate();
234
+ }
235
+ /** Filters option elements in-memory matching the input value. Bound to input target. */
236
+ filter() {
237
+ if (!this.hasInputTarget) return;
238
+ const query = this.inputTarget.value.trim().toLowerCase();
239
+ let hasSelectableMatch = false;
240
+ for (const option of this.optionTargets) {
241
+ const searchText = (option.dataset.searchValue || option.textContent || "").trim().toLowerCase();
242
+ const matches = searchText.includes(query);
243
+ if (matches) {
244
+ option.removeAttribute("hidden");
245
+ if (!this.#isDisabled(option)) hasSelectableMatch = true;
246
+ } else {
247
+ option.setAttribute("hidden", "true");
248
+ }
249
+ }
250
+ if (this.hasEmptyTarget) {
251
+ this.emptyTarget.hidden = hasSelectableMatch;
252
+ }
253
+ this.#setActiveIndex(hasSelectableMatch ? 0 : -1);
254
+ }
255
+ /** Selects the clicked option. Bound to option targets. */
256
+ selectByClick(event) {
257
+ const target = event.currentTarget;
258
+ if (!target) return;
259
+ const option = target.closest("[role='option']");
260
+ if (option && !this.#isDisabled(option)) {
261
+ this.#confirmSelection(option);
262
+ }
263
+ }
264
+ /** Closes when the backdrop (the dialog target itself) is clicked, ignoring inner clicks. */
265
+ closeOnBackdrop(event) {
266
+ if (event.target === this.dialogTarget) this.close();
267
+ }
268
+ /**
269
+ * Combobox navigation keys (arrows / Home / End / Enter), bound to the input.
270
+ * `Tab` (focus trap) and `Escape` (close) are owned by the {@link FocusTrap} at
271
+ * the document level, so they work no matter which element inside the dialog has
272
+ * focus — not only the input.
273
+ */
274
+ onKeydown(event) {
275
+ if (!this.#isOpen) return;
276
+ switch (event.key) {
277
+ case "ArrowDown":
278
+ event.preventDefault();
279
+ this.#navigate(1);
280
+ break;
281
+ case "ArrowUp":
282
+ event.preventDefault();
283
+ this.#navigate(-1);
284
+ break;
285
+ case "Home":
286
+ event.preventDefault();
287
+ this.#setActiveIndex(0);
288
+ break;
289
+ case "End":
290
+ event.preventDefault();
291
+ this.#setActiveIndex(this.#visibleOptions.length - 1);
292
+ break;
293
+ case "Enter": {
294
+ event.preventDefault();
295
+ const activeOption = this.#visibleOptions[this.#activeIndex];
296
+ if (activeOption) this.#confirmSelection(activeOption);
297
+ break;
298
+ }
299
+ }
300
+ }
301
+ #navigate(direction) {
302
+ const visible = this.#visibleOptions;
303
+ if (visible.length === 0) return;
304
+ let newIndex = this.#activeIndex + direction;
305
+ if (newIndex >= visible.length) newIndex = 0;
306
+ if (newIndex < 0) newIndex = visible.length - 1;
307
+ this.#setActiveIndex(newIndex);
308
+ }
309
+ #setActiveIndex(index) {
310
+ const visible = this.#visibleOptions;
311
+ this.#activeIndex = index;
312
+ visible.forEach((option, i) => {
313
+ if (i === index) {
314
+ option.setAttribute("aria-selected", "true");
315
+ option.setAttribute("data-active", "true");
316
+ if (this.hasInputTarget) {
317
+ this.inputTarget.setAttribute("aria-activedescendant", option.id || "");
318
+ }
319
+ option.scrollIntoView({ block: "nearest" });
320
+ } else {
321
+ option.setAttribute("aria-selected", "false");
322
+ option.removeAttribute("data-active");
323
+ }
324
+ });
325
+ if (index === -1 && this.hasInputTarget) {
326
+ this.inputTarget.removeAttribute("aria-activedescendant");
327
+ }
328
+ }
329
+ #confirmSelection(option) {
330
+ const value = option.dataset.value || option.textContent || "";
331
+ this.dispatch("select", { detail: { value, option } });
332
+ this.close();
333
+ }
334
+ #resetFilter() {
335
+ if (this.hasInputTarget) this.inputTarget.value = "";
336
+ for (const option of this.optionTargets) {
337
+ option.removeAttribute("hidden");
338
+ }
339
+ if (this.hasEmptyTarget) this.emptyTarget.hidden = true;
340
+ this.#setActiveIndex(0);
341
+ }
342
+ get #visibleOptions() {
343
+ return this.optionTargets.filter(
344
+ (option) => !option.hasAttribute("hidden") && !this.#isDisabled(option)
345
+ );
346
+ }
347
+ #isDisabled(option) {
348
+ return option.dataset.disabled === "true";
349
+ }
350
+ get #isOpen() {
351
+ return this.hasDialogTarget && !this.dialogTarget.hidden;
352
+ }
353
+ #onGlobalKeydown = (event) => {
354
+ const hotkey = this.hotkeyValue.toLowerCase();
355
+ const isMod = hotkey.includes("mod+");
356
+ const key = hotkey.split("+").pop();
357
+ if (!key) return;
358
+ const modPressed = isMod ? event.metaKey || event.ctrlKey : !event.metaKey && !event.ctrlKey;
359
+ const keyMatch = event.key.toLowerCase() === key;
360
+ if (modPressed && keyMatch) {
361
+ event.preventDefault();
362
+ this.toggle();
363
+ }
364
+ };
365
+ /** Resets transient open state so reconnect starts from a predictable closed snapshot. */
366
+ #resetToClosedState() {
367
+ this.#activeIndex = -1;
368
+ this.openValue = false;
369
+ if (this.hasDialogTarget) {
370
+ this.dialogTarget.hidden = true;
371
+ }
372
+ if (this.hasInputTarget) {
373
+ this.inputTarget.setAttribute("aria-expanded", "false");
374
+ this.inputTarget.removeAttribute("aria-activedescendant");
375
+ }
376
+ }
377
+ };
378
+
379
+ export { CommandPaletteController };
380
+ //# sourceMappingURL=command_palette_controller.js.map
381
+ //# sourceMappingURL=command_palette_controller.js.map
@@ -0,0 +1,112 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ // src/controllers/conditional_fields_controller.ts
4
+ var DISABLED_MARKER = "data-conditional-disabled";
5
+ var ConditionalFieldsController = class extends Controller {
6
+ static targets = ["trigger", "region"];
7
+ static values = {
8
+ disableHidden: { type: Boolean, default: true },
9
+ match: { type: String, default: "any" }
10
+ };
11
+ static actions = ["evaluate"];
12
+ static events = ["change"];
13
+ #lastVisible = /* @__PURE__ */ new WeakMap();
14
+ #onChange = () => {
15
+ this.evaluate();
16
+ };
17
+ connect() {
18
+ this.evaluate();
19
+ this.element.addEventListener("change", this.#onChange);
20
+ this.element.addEventListener("input", this.#onChange);
21
+ }
22
+ disconnect() {
23
+ this.element.removeEventListener("change", this.#onChange);
24
+ this.element.removeEventListener("input", this.#onChange);
25
+ }
26
+ /** Re-evaluates every region against the current trigger state. */
27
+ evaluate() {
28
+ const triggers = this.triggerTargets;
29
+ for (const region of this.regionTargets) {
30
+ this.#applyRegion(region, this.#isVisible(region, triggers));
31
+ }
32
+ }
33
+ /** Applies a region's visibility, syncing hidden/aria/disabled and emitting change. */
34
+ #applyRegion(region, visible) {
35
+ const previous = this.#lastVisible.get(region) ?? !region.hidden;
36
+ if (visible === previous && this.#lastVisible.has(region)) return;
37
+ this.#lastVisible.set(region, visible);
38
+ if (visible) {
39
+ region.hidden = false;
40
+ region.removeAttribute("aria-hidden");
41
+ region.setAttribute("data-visible", "true");
42
+ this.#setRegionDisabled(region, false);
43
+ } else {
44
+ this.#retreatFocus(region);
45
+ region.hidden = true;
46
+ region.setAttribute("aria-hidden", "true");
47
+ region.removeAttribute("data-visible");
48
+ this.#setRegionDisabled(region, true);
49
+ }
50
+ if (visible !== previous) {
51
+ this.dispatch("change", { detail: { region, visible } });
52
+ }
53
+ }
54
+ /** Whether a region's declared condition holds across `triggers` per `match`. */
55
+ #isVisible(region, triggers) {
56
+ const predicate = this.#predicateFor(region);
57
+ if (predicate === null) return !region.hidden;
58
+ if (triggers.length === 0) return false;
59
+ return this.matchValue === "all" ? triggers.every(predicate) : triggers.some(predicate);
60
+ }
61
+ /** Builds the per-trigger predicate from the region's `data-when-*` attribute. */
62
+ #predicateFor(region) {
63
+ if (region.hasAttribute("data-when-checked")) {
64
+ return (trigger) => this.#isChecked(trigger);
65
+ }
66
+ if (region.hasAttribute("data-when-unchecked")) {
67
+ return (trigger) => !this.#isChecked(trigger);
68
+ }
69
+ const wanted = region.dataset.whenValue;
70
+ if (wanted !== void 0) {
71
+ return (trigger) => this.#matchesValue(trigger, wanted);
72
+ }
73
+ return null;
74
+ }
75
+ #isChecked(trigger) {
76
+ return trigger instanceof HTMLInputElement && trigger.checked;
77
+ }
78
+ /** A trigger "has" a value: selected radio/checkbox value, or the control value. */
79
+ #matchesValue(trigger, wanted) {
80
+ if (trigger instanceof HTMLInputElement && (trigger.type === "checkbox" || trigger.type === "radio")) {
81
+ return trigger.checked && trigger.value === wanted;
82
+ }
83
+ return trigger.value === wanted;
84
+ }
85
+ /** Enables/disables a region's inputs, tracking only the ones we disabled. */
86
+ #setRegionDisabled(region, disabled) {
87
+ if (!this.disableHiddenValue) return;
88
+ const controls = region.querySelectorAll("input, textarea, select, button");
89
+ for (const control of Array.from(controls)) {
90
+ if (disabled) {
91
+ if (!control.disabled) {
92
+ control.disabled = true;
93
+ control.setAttribute(DISABLED_MARKER, "true");
94
+ }
95
+ } else if (control.hasAttribute(DISABLED_MARKER)) {
96
+ control.disabled = false;
97
+ control.removeAttribute(DISABLED_MARKER);
98
+ }
99
+ }
100
+ }
101
+ /** Moves focus out of a region about to be hidden, to a trigger when possible. */
102
+ #retreatFocus(region) {
103
+ const active = document.activeElement;
104
+ if (!(active instanceof HTMLElement) || !region.contains(active)) return;
105
+ active.blur();
106
+ if (this.hasTriggerTarget) this.triggerTargets[0]?.focus();
107
+ }
108
+ };
109
+
110
+ export { ConditionalFieldsController };
111
+ //# sourceMappingURL=conditional_fields_controller.js.map
112
+ //# sourceMappingURL=conditional_fields_controller.js.map