stimeo-ui 0.15.0 → 0.17.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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +209 -0
  3. data/README.md +128 -1
  4. data/dist/cable/index.js +392 -417
  5. data/dist/controllers/accordion_controller.js +228 -28
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -191
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +295 -110
  10. data/dist/controllers/avatar_controller.js +172 -106
  11. data/dist/controllers/breadcrumb_controller.js +0 -129
  12. data/dist/controllers/bulk_select_controller.js +264 -53
  13. data/dist/controllers/calendar_controller.js +346 -180
  14. data/dist/controllers/carousel_controller.js +587 -300
  15. data/dist/controllers/character_counter_controller.js +271 -143
  16. data/dist/controllers/checkbox_controller.js +77 -75
  17. data/dist/controllers/clipboard_controller.js +187 -108
  18. data/dist/controllers/collapsible_controller.js +374 -108
  19. data/dist/controllers/color_picker_controller.js +183 -127
  20. data/dist/controllers/combobox_controller.js +426 -109
  21. data/dist/controllers/command_palette_controller.js +994 -350
  22. data/dist/controllers/conditional_fields_controller.js +212 -125
  23. data/dist/controllers/confirm_controller.js +939 -245
  24. data/dist/controllers/context_menu_controller.js +275 -127
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +235 -97
  27. data/dist/controllers/currency_input_controller.js +241 -118
  28. data/dist/controllers/data_grid_controller.js +229 -135
  29. data/dist/controllers/date_range_picker_controller.js +359 -165
  30. data/dist/controllers/dialog_controller.js +887 -225
  31. data/dist/controllers/direct_upload_controller.js +80 -142
  32. data/dist/controllers/dirty_form_controller.js +26 -47
  33. data/dist/controllers/dismissible_controller.js +175 -20
  34. data/dist/controllers/drawer_controller.js +963 -336
  35. data/dist/controllers/dropdown_controller.js +342 -85
  36. data/dist/controllers/editable_controller.js +182 -58
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +426 -229
  39. data/dist/controllers/filter_controller.js +78 -36
  40. data/dist/controllers/flash_controller.js +328 -211
  41. data/dist/controllers/focus_controller.js +654 -239
  42. data/dist/controllers/form_field_controller.js +153 -136
  43. data/dist/controllers/form_validation_controller.js +29 -96
  44. data/dist/controllers/frame_loading_controller.js +263 -198
  45. data/dist/controllers/highlight_controller.js +119 -70
  46. data/dist/controllers/hover_card_controller.js +335 -118
  47. data/dist/controllers/idle_controller.js +316 -65
  48. data/dist/controllers/input_mask_controller.js +116 -61
  49. data/dist/controllers/intersection_controller.js +147 -103
  50. data/dist/controllers/lazy_frame_controller.js +9 -56
  51. data/dist/controllers/listbox_controller.js +413 -153
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -95
  54. data/dist/controllers/menu_controller.js +254 -167
  55. data/dist/controllers/menubar_controller.js +206 -331
  56. data/dist/controllers/meter_controller.js +159 -71
  57. data/dist/controllers/multi_select_controller.js +797 -388
  58. data/dist/controllers/navigation_menu_controller.js +156 -231
  59. data/dist/controllers/nested_form_controller.js +373 -174
  60. data/dist/controllers/network_status_controller.js +241 -45
  61. data/dist/controllers/number_input_controller.js +387 -227
  62. data/dist/controllers/optimistic_controller.js +28 -75
  63. data/dist/controllers/otp_controller.js +344 -196
  64. data/dist/controllers/overflow_indicator_controller.js +167 -93
  65. data/dist/controllers/overflow_menu_controller.js +408 -277
  66. data/dist/controllers/pagination_controller.js +342 -120
  67. data/dist/controllers/password_reveal_controller.js +424 -99
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +252 -96
  70. data/dist/controllers/pointer_drag_controller.js +263 -164
  71. data/dist/controllers/popover_controller.js +267 -94
  72. data/dist/controllers/portal_controller.js +209 -72
  73. data/dist/controllers/preview_guard_controller.js +243 -112
  74. data/dist/controllers/progress_controller.js +132 -59
  75. data/dist/controllers/radio_group_controller.js +138 -133
  76. data/dist/controllers/range_slider_controller.js +245 -112
  77. data/dist/controllers/rating_controller.js +429 -194
  78. data/dist/controllers/read_more_controller.js +185 -47
  79. data/dist/controllers/reading_progress_controller.js +147 -135
  80. data/dist/controllers/relative_time_controller.js +145 -110
  81. data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  82. data/dist/controllers/resizable_controller.js +294 -118
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -221
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -146
  87. data/dist/controllers/scrollspy_controller.js +181 -236
  88. data/dist/controllers/separator_controller.js +288 -132
  89. data/dist/controllers/sidebar_controller.js +1071 -375
  90. data/dist/controllers/skeleton_controller.js +248 -92
  91. data/dist/controllers/slider_controller.js +211 -96
  92. data/dist/controllers/smart_sticky_header_controller.js +163 -54
  93. data/dist/controllers/sortable_controller.js +13 -110
  94. data/dist/controllers/spinner_controller.js +277 -161
  95. data/dist/controllers/step_indicator_controller.js +107 -53
  96. data/dist/controllers/stepper_controller.js +171 -48
  97. data/dist/controllers/stick_to_bottom_controller.js +310 -116
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -192
  100. data/dist/controllers/switch_controller.js +107 -43
  101. data/dist/controllers/tabs_controller.js +50 -25
  102. data/dist/controllers/tags_input_controller.js +290 -149
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +148 -80
  105. data/dist/controllers/time_picker_controller.js +162 -81
  106. data/dist/controllers/toast_controller.js +365 -193
  107. data/dist/controllers/toggle_group_controller.js +194 -99
  108. data/dist/controllers/toolbar_controller.js +92 -90
  109. data/dist/controllers/tooltip_controller.js +185 -125
  110. data/dist/controllers/transition_controller.js +123 -115
  111. data/dist/controllers/tree_view_controller.js +248 -290
  112. data/dist/index.js +8649 -8572
  113. data/dist/positioning/index.js +148 -45
  114. data/lib/stimeo/ui/version.rb +1 -1
  115. metadata +3 -3
@@ -1,51 +1,253 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/accordion_controller.ts
3
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
4
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
5
+ function stateReasonFor(event) {
6
+ if (!event) return "api";
7
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
8
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
9
+ return "user";
10
+ }
11
+
12
+ function actionSource(source) {
13
+ if (source instanceof HTMLElement) {
14
+ return { event: null, host: source, origin: source, reason: "api" };
15
+ }
16
+ const event = source ?? null;
17
+ return {
18
+ event,
19
+ host: event?.currentTarget instanceof Element ? event.currentTarget : null,
20
+ origin: event?.target instanceof Element ? event.target : null,
21
+ reason: stateReasonFor(event)
22
+ };
23
+ }
4
24
 
5
- // src/utils/arrow_step.ts
6
25
  function isReservedArrowChord(event, allow = []) {
7
26
  if (!event.key.startsWith("Arrow")) return false;
8
27
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
9
28
  }
10
29
 
11
- // src/controllers/accordion_controller.ts
30
+ function sharedRegistry(key, create) {
31
+ const symbol = Symbol.for(key);
32
+ const scope = globalThis;
33
+ const existing = scope[symbol];
34
+ if (existing !== void 0) return existing;
35
+ const registry = create();
36
+ Object.defineProperty(globalThis, symbol, { value: registry });
37
+ return registry;
38
+ }
39
+
40
+ var StateRegions = class _StateRegions {
41
+ static #registry = sharedRegistry(
42
+ "stimeo-ui.state-regions.registry.v1",
43
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
44
+ );
45
+ #whenTrue;
46
+ #whenFalse;
47
+ #record;
48
+ constructor(options, owner) {
49
+ this.#whenTrue = options.whenTrue;
50
+ this.#whenFalse = options.whenFalse ?? null;
51
+ this.#record = `data-${owner}-hidden-region`;
52
+ _StateRegions.#registry.names.add(this.#record);
53
+ }
54
+ reflect(host, isTrue) {
55
+ const shown = this.#inside(host, this.#whenTrue());
56
+ if (!this.#whenFalse) {
57
+ for (const region of shown) this.#write(region, !isTrue);
58
+ return;
59
+ }
60
+ const hiddenSide = this.#inside(host, this.#whenFalse());
61
+ if (shown.length === 0 || hiddenSide.length === 0) {
62
+ for (const region of shown) this.#give(region);
63
+ for (const region of hiddenSide) this.#give(region);
64
+ return;
65
+ }
66
+ for (const region of shown) this.#write(region, !isTrue);
67
+ for (const region of hiddenSide) this.#write(region, isTrue);
68
+ }
69
+ release(host) {
70
+ for (const region of [host, ...host.querySelectorAll("*")]) {
71
+ if (!(region instanceof HTMLElement)) continue;
72
+ const stack = this.#held(region);
73
+ const hold = stack && this.#take(stack);
74
+ if (!stack || !hold) continue;
75
+ const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
76
+ const index = stack.indexOf(hold);
77
+ stack.splice(index, 1);
78
+ const above = stack[index];
79
+ if (above) above.linked &&= hold.linked;
80
+ if (owns) {
81
+ const below = hold.linked ? stack[index - 1] : void 0;
82
+ if (below) {
83
+ if (region.hidden !== below.written) region.hidden = below.written;
84
+ } else if (region.getAttribute("hidden") !== hold.authored) {
85
+ if (hold.authored === null) region.removeAttribute("hidden");
86
+ else region.setAttribute("hidden", hold.authored);
87
+ }
88
+ }
89
+ this.#mark(region, stack);
90
+ }
91
+ }
92
+ #write(region, hidden) {
93
+ const stack = this.#stack(region);
94
+ const hold = this.#take(stack) ?? this.#acquire(region, stack);
95
+ const shows = !this.#covered(region, stack, hold);
96
+ hold.written = hidden;
97
+ if (shows && region.hidden !== hidden) region.hidden = hidden;
98
+ this.#mark(region, stack);
99
+ }
100
+ #give(region) {
101
+ const stack = this.#held(region);
102
+ if (stack && this.#take(stack)?.written) this.#write(region, false);
103
+ }
104
+ #held(region) {
105
+ return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
106
+ }
107
+ #stack(region) {
108
+ const stacks = _StateRegions.#registry.stacks;
109
+ const stack = stacks.get(region) ?? this.#seed(region);
110
+ stacks.set(region, stack);
111
+ return stack;
112
+ }
113
+ #seed(region) {
114
+ const found = [];
115
+ for (const record of _StateRegions.#registry.names) {
116
+ const read = this.#recorded(region, record);
117
+ if (read) found.push({ ...read, record });
118
+ }
119
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
120
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
121
+ instance: null,
122
+ record,
123
+ authored,
124
+ written,
125
+ linked: linked && index > 0
126
+ }));
127
+ }
128
+ #take(stack) {
129
+ const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
130
+ if (hold) hold.instance = this;
131
+ return hold;
132
+ }
133
+ #covered(region, stack, hold) {
134
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
135
+ }
136
+ #acquire(region, stack) {
137
+ const top = stack.at(-1);
138
+ const over = top && region.hidden === top.written ? top : void 0;
139
+ const hold = {
140
+ instance: this,
141
+ record: this.#record,
142
+ authored: over ? over.authored : region.getAttribute("hidden"),
143
+ written: false,
144
+ linked: over !== void 0
145
+ };
146
+ stack.push(hold);
147
+ return hold;
148
+ }
149
+ #mark(region, stack) {
150
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
151
+ stack.forEach((hold, index) => {
152
+ const names = stack.slice(0, index).map((under) => under.record);
153
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
154
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
155
+ records.set(
156
+ hold.record,
157
+ region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
158
+ );
159
+ });
160
+ for (const [name, recorded] of records) {
161
+ if (region.getAttribute(name) === recorded) continue;
162
+ if (recorded === null) region.removeAttribute(name);
163
+ else region.setAttribute(name, recorded);
164
+ }
165
+ }
166
+ #recorded(region, name) {
167
+ try {
168
+ const parsed = JSON.parse(region.getAttribute(name) ?? "null");
169
+ if (!Array.isArray(parsed)) return null;
170
+ const [authored, written, ...rest] = parsed;
171
+ if (authored !== null && typeof authored !== "string") return null;
172
+ const linked = rest[0] !== false;
173
+ const beneath = linked ? rest : rest.slice(1);
174
+ if (!beneath.every((under) => typeof under === "string")) return null;
175
+ return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+ #inside(host, regions) {
181
+ return regions.filter((region) => host.contains(region));
182
+ }
183
+ };
184
+
185
+ function targetSelector(identifier, name) {
186
+ return `[data-${identifier}-target~="${name}"]`;
187
+ }
188
+
12
189
  var AccordionController = class extends Controller {
13
- static targets = ["trigger", "panel"];
190
+ static targets = ["trigger", "panel", "expandedLabel", "collapsedLabel"];
14
191
  static actions = ["collapseAll", "expandAll", "onKeydown", "toggle"];
15
- /** Toggles the panel controlled by the activated header. */
16
- toggle(event) {
17
- const trigger = event.currentTarget;
192
+ static events = ["close", "open"];
193
+ #labels = new StateRegions(
194
+ {
195
+ whenTrue: () => this.expandedLabelTargets,
196
+ whenFalse: () => this.collapsedLabelTargets
197
+ },
198
+ this.identifier
199
+ );
200
+ triggerTargetConnected(trigger) {
201
+ this.#reflectLabels(trigger);
202
+ }
203
+ triggerTargetDisconnected(trigger) {
204
+ if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);
205
+ }
206
+ expandedLabelTargetConnected(label) {
207
+ this.#settleLabelHost(label);
208
+ }
209
+ collapsedLabelTargetConnected(label) {
210
+ this.#settleLabelHost(label);
211
+ }
212
+ #settleLabelHost(label) {
213
+ const trigger = this.triggerTargets.find((candidate) => candidate.contains(label));
214
+ if (trigger) this.#reflectLabels(trigger);
215
+ }
216
+ toggle(source) {
217
+ const { host, origin, reason } = actionSource(source);
218
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
219
+ return;
220
+ const trigger = host?.closest(targetSelector(this.identifier, "trigger"));
221
+ if (!trigger || !this.triggerTargets.includes(trigger)) return;
18
222
  const panel = this.#panelFor(trigger);
19
223
  if (!panel) return;
20
- this.#setExpanded(trigger, panel, trigger.getAttribute("aria-expanded") !== "true");
224
+ this.#setExpanded(trigger, panel, trigger.getAttribute("aria-expanded") !== "true", reason);
21
225
  }
22
- /** Opens every panel. Bound via `data-action` on an "expand all" control. */
23
- expandAll() {
24
- this.#setAll(true);
226
+ expandAll(event) {
227
+ this.#setAll(true, stateReasonFor(event));
25
228
  }
26
- /** Closes every panel. Bound via `data-action` on a "collapse all" control. */
27
- collapseAll() {
28
- this.#setAll(false);
229
+ collapseAll(event) {
230
+ this.#setAll(false, stateReasonFor(event));
29
231
  }
30
- /** Drives every header/panel pair to the same expanded state. */
31
- #setAll(open) {
232
+ #setAll(open, reason) {
32
233
  for (const trigger of this.triggerTargets) {
33
234
  const panel = this.#panelFor(trigger);
34
- if (panel) this.#setExpanded(trigger, panel, open);
235
+ if (panel) this.#setExpanded(trigger, panel, open, reason);
35
236
  }
36
237
  }
37
- /** Reflects one header/panel pair's state through `aria-expanded` + `hidden`. */
38
- #setExpanded(trigger, panel, open) {
238
+ #setExpanded(trigger, panel, open, reason) {
239
+ const was = trigger.getAttribute("aria-expanded") === "true";
39
240
  trigger.setAttribute("aria-expanded", open ? "true" : "false");
40
241
  panel.hidden = !open;
242
+ this.#labels.reflect(trigger, open);
243
+ if (was === open) return;
244
+ const detail = { reason, index: this.triggerTargets.indexOf(trigger), trigger, panel };
245
+ if (open) this.dispatch("open", { detail, cancelable: false });
246
+ else this.dispatch("close", { detail, cancelable: false });
247
+ }
248
+ #reflectLabels(trigger) {
249
+ this.#labels.reflect(trigger, trigger.getAttribute("aria-expanded") === "true");
41
250
  }
42
- /**
43
- * Moves focus between headers per the APG keyboard model, skipping any header
44
- * that is hidden or nested in a hidden subtree. A consumer may hide whole
45
- * sections (e.g. an `stimeo--filter` that collapses empty groups), and an
46
- * unperceivable header must never become an arrow-key target — otherwise
47
- * `.focus()` lands on nothing and navigation appears to stall.
48
- */
49
251
  onKeydown(event) {
50
252
  if (event.defaultPrevented) return;
51
253
  if (isReservedArrowChord(event)) return;
@@ -74,7 +276,6 @@ var AccordionController = class extends Controller {
74
276
  event.preventDefault();
75
277
  next?.focus();
76
278
  }
77
- /** Resolves the panel a header controls via its `aria-controls` reference. */
78
279
  #panelFor(trigger) {
79
280
  const id = trigger.getAttribute("aria-controls");
80
281
  return id ? this.panelTargets.find((panel) => panel.id === id) ?? null : null;
@@ -82,5 +283,4 @@ var AccordionController = class extends Controller {
82
283
  };
83
284
 
84
285
  export { AccordionController };
85
- //# sourceMappingURL=accordion_controller.js.map
86
286
  //# sourceMappingURL=accordion_controller.js.map