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