stimeo-ui 0.16.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 +31 -0
  3. data/README.md +8 -1
  4. data/dist/cable/index.js +363 -436
  5. data/dist/controllers/accordion_controller.js +166 -55
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -198
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +271 -127
  10. data/dist/controllers/avatar_controller.js +171 -105
  11. data/dist/controllers/breadcrumb_controller.js +0 -130
  12. data/dist/controllers/bulk_select_controller.js +262 -52
  13. data/dist/controllers/calendar_controller.js +175 -209
  14. data/dist/controllers/carousel_controller.js +461 -399
  15. data/dist/controllers/character_counter_controller.js +246 -156
  16. data/dist/controllers/checkbox_controller.js +32 -74
  17. data/dist/controllers/clipboard_controller.js +168 -147
  18. data/dist/controllers/collapsible_controller.js +301 -120
  19. data/dist/controllers/color_picker_controller.js +149 -139
  20. data/dist/controllers/combobox_controller.js +360 -133
  21. data/dist/controllers/command_palette_controller.js +970 -358
  22. data/dist/controllers/conditional_fields_controller.js +166 -124
  23. data/dist/controllers/confirm_controller.js +939 -248
  24. data/dist/controllers/context_menu_controller.js +256 -128
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +183 -148
  27. data/dist/controllers/currency_input_controller.js +134 -158
  28. data/dist/controllers/data_grid_controller.js +114 -186
  29. data/dist/controllers/date_range_picker_controller.js +288 -206
  30. data/dist/controllers/dialog_controller.js +858 -223
  31. data/dist/controllers/direct_upload_controller.js +73 -153
  32. data/dist/controllers/dirty_form_controller.js +14 -48
  33. data/dist/controllers/dismissible_controller.js +175 -21
  34. data/dist/controllers/drawer_controller.js +933 -341
  35. data/dist/controllers/dropdown_controller.js +312 -82
  36. data/dist/controllers/editable_controller.js +158 -68
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +311 -207
  39. data/dist/controllers/filter_controller.js +67 -39
  40. data/dist/controllers/flash_controller.js +172 -314
  41. data/dist/controllers/focus_controller.js +654 -240
  42. data/dist/controllers/form_field_controller.js +147 -132
  43. data/dist/controllers/form_validation_controller.js +13 -86
  44. data/dist/controllers/frame_loading_controller.js +264 -213
  45. data/dist/controllers/highlight_controller.js +107 -75
  46. data/dist/controllers/hover_card_controller.js +309 -118
  47. data/dist/controllers/idle_controller.js +267 -101
  48. data/dist/controllers/input_mask_controller.js +71 -72
  49. data/dist/controllers/intersection_controller.js +147 -106
  50. data/dist/controllers/lazy_frame_controller.js +9 -65
  51. data/dist/controllers/listbox_controller.js +306 -204
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -93
  54. data/dist/controllers/menu_controller.js +214 -156
  55. data/dist/controllers/menubar_controller.js +170 -329
  56. data/dist/controllers/meter_controller.js +151 -67
  57. data/dist/controllers/multi_select_controller.js +593 -358
  58. data/dist/controllers/navigation_menu_controller.js +124 -232
  59. data/dist/controllers/nested_form_controller.js +352 -182
  60. data/dist/controllers/network_status_controller.js +242 -54
  61. data/dist/controllers/number_input_controller.js +320 -263
  62. data/dist/controllers/optimistic_controller.js +16 -76
  63. data/dist/controllers/otp_controller.js +198 -193
  64. data/dist/controllers/overflow_indicator_controller.js +116 -113
  65. data/dist/controllers/overflow_menu_controller.js +343 -283
  66. data/dist/controllers/pagination_controller.js +295 -119
  67. data/dist/controllers/password_reveal_controller.js +389 -121
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +237 -103
  70. data/dist/controllers/pointer_drag_controller.js +185 -165
  71. data/dist/controllers/popover_controller.js +236 -97
  72. data/dist/controllers/portal_controller.js +209 -76
  73. data/dist/controllers/preview_guard_controller.js +232 -116
  74. data/dist/controllers/progress_controller.js +125 -56
  75. data/dist/controllers/radio_group_controller.js +117 -137
  76. data/dist/controllers/range_slider_controller.js +192 -105
  77. data/dist/controllers/rating_controller.js +418 -207
  78. data/dist/controllers/read_more_controller.js +134 -63
  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 +255 -139
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -225
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -145
  87. data/dist/controllers/scrollspy_controller.js +181 -242
  88. data/dist/controllers/separator_controller.js +267 -140
  89. data/dist/controllers/sidebar_controller.js +1023 -386
  90. data/dist/controllers/skeleton_controller.js +248 -97
  91. data/dist/controllers/slider_controller.js +159 -79
  92. data/dist/controllers/smart_sticky_header_controller.js +162 -63
  93. data/dist/controllers/sortable_controller.js +0 -112
  94. data/dist/controllers/spinner_controller.js +277 -169
  95. data/dist/controllers/step_indicator_controller.js +99 -46
  96. data/dist/controllers/stepper_controller.js +114 -73
  97. data/dist/controllers/stick_to_bottom_controller.js +262 -138
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -205
  100. data/dist/controllers/switch_controller.js +33 -60
  101. data/dist/controllers/tabs_controller.js +35 -29
  102. data/dist/controllers/tags_input_controller.js +136 -145
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +130 -112
  105. data/dist/controllers/time_picker_controller.js +146 -80
  106. data/dist/controllers/toast_controller.js +186 -297
  107. data/dist/controllers/toggle_group_controller.js +95 -136
  108. data/dist/controllers/toolbar_controller.js +68 -98
  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 +150 -345
  112. data/dist/index.js +6370 -9346
  113. data/dist/positioning/index.js +148 -47
  114. data/lib/stimeo/ui/version.rb +1 -1
  115. 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,48 @@ 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
+
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
+ );
23
45
  #whenTrue;
24
46
  #whenFalse;
25
- #taken = /* @__PURE__ */ new WeakSet();
26
- constructor(options) {
47
+ #record;
48
+ constructor(options, owner) {
27
49
  this.#whenTrue = options.whenTrue;
28
50
  this.#whenFalse = options.whenFalse ?? null;
51
+ this.#record = `data-${owner}-hidden-region`;
52
+ _StateRegions.#registry.names.add(this.#record);
29
53
  }
30
- /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
31
54
  reflect(host, isTrue) {
32
55
  const shown = this.#inside(host, this.#whenTrue());
33
56
  if (!this.#whenFalse) {
@@ -43,77 +66,175 @@ var StateRegions = class {
43
66
  for (const region of shown) this.#write(region, !isTrue);
44
67
  for (const region of hiddenSide) this.#write(region, isTrue);
45
68
  }
46
- /** Writes `hidden` where it moves, noting which regions it takes out of view. */
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
+ }
47
92
  #write(region, hidden) {
48
- if (hidden) this.#taken.add(region);
49
- else this.#taken.delete(region);
50
- if (region.hidden !== hidden) region.hidden = 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);
51
99
  }
52
- /** Returns a region this instance hid; one it never hid keeps what it carries. */
53
100
  #give(region) {
54
- if (this.#taken.delete(region)) region.hidden = false;
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
+ }
55
179
  }
56
- /** The declared regions that sit within `host`, which may be the host itself. */
57
180
  #inside(host, regions) {
58
181
  return regions.filter((region) => host.contains(region));
59
182
  }
60
183
  };
61
184
 
62
- // src/controllers/accordion_controller.ts
185
+ function targetSelector(identifier, name) {
186
+ return `[data-${identifier}-target~="${name}"]`;
187
+ }
188
+
63
189
  var AccordionController = class extends Controller {
64
190
  static targets = ["trigger", "panel", "expandedLabel", "collapsedLabel"];
65
191
  static actions = ["collapseAll", "expandAll", "onKeydown", "toggle"];
66
192
  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. */
193
+ #labels = new StateRegions(
194
+ {
195
+ whenTrue: () => this.expandedLabelTargets,
196
+ whenFalse: () => this.collapsedLabelTargets
197
+ },
198
+ this.identifier
199
+ );
73
200
  triggerTargetConnected(trigger) {
74
201
  this.#reflectLabels(trigger);
75
202
  }
76
- /** Settles the pair of the header an arriving expanded half belongs to. */
203
+ triggerTargetDisconnected(trigger) {
204
+ if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);
205
+ }
77
206
  expandedLabelTargetConnected(label) {
78
207
  this.#settleLabelHost(label);
79
208
  }
80
- /** Settles the pair of the header an arriving collapsed half belongs to. */
81
209
  collapsedLabelTargetConnected(label) {
82
210
  this.#settleLabelHost(label);
83
211
  }
84
- /** Reflects the pair of the header a label half sits in, where a header holds it. */
85
212
  #settleLabelHost(label) {
86
213
  const trigger = this.triggerTargets.find((candidate) => candidate.contains(label));
87
214
  if (trigger) this.#reflectLabels(trigger);
88
215
  }
89
- /** Toggles the panel controlled by the activated header. */
90
- toggle(event) {
91
- const trigger = event.currentTarget;
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;
92
222
  const panel = this.#panelFor(trigger);
93
223
  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. */
224
+ this.#setExpanded(trigger, panel, trigger.getAttribute("aria-expanded") !== "true", reason);
225
+ }
102
226
  expandAll(event) {
103
227
  this.#setAll(true, stateReasonFor(event));
104
228
  }
105
- /** Closes every panel. Bound via `data-action` on a "collapse all" control. */
106
229
  collapseAll(event) {
107
230
  this.#setAll(false, stateReasonFor(event));
108
231
  }
109
- /** Drives every header/panel pair to the same expanded state. */
110
232
  #setAll(open, reason) {
111
233
  for (const trigger of this.triggerTargets) {
112
234
  const panel = this.#panelFor(trigger);
113
235
  if (panel) this.#setExpanded(trigger, panel, open, reason);
114
236
  }
115
237
  }
116
- /** Reflects one header/panel pair's state through `aria-expanded` + `hidden`. */
117
238
  #setExpanded(trigger, panel, open, reason) {
118
239
  const was = trigger.getAttribute("aria-expanded") === "true";
119
240
  trigger.setAttribute("aria-expanded", open ? "true" : "false");
@@ -124,17 +245,9 @@ var AccordionController = class extends Controller {
124
245
  if (open) this.dispatch("open", { detail, cancelable: false });
125
246
  else this.dispatch("close", { detail, cancelable: false });
126
247
  }
127
- /** Shows the half of a header's label pair that belongs to its `aria-expanded`. */
128
248
  #reflectLabels(trigger) {
129
249
  this.#labels.reflect(trigger, trigger.getAttribute("aria-expanded") === "true");
130
250
  }
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
251
  onKeydown(event) {
139
252
  if (event.defaultPrevented) return;
140
253
  if (isReservedArrowChord(event)) return;
@@ -163,7 +276,6 @@ var AccordionController = class extends Controller {
163
276
  event.preventDefault();
164
277
  next?.focus();
165
278
  }
166
- /** Resolves the panel a header controls via its `aria-controls` reference. */
167
279
  #panelFor(trigger) {
168
280
  const id = trigger.getAttribute("aria-controls");
169
281
  return id ? this.panelTargets.find((panel) => panel.id === id) ?? null : null;
@@ -171,5 +283,4 @@ var AccordionController = class extends Controller {
171
283
  };
172
284
 
173
285
  export { AccordionController };
174
- //# sourceMappingURL=accordion_controller.js.map
175
286
  //# sourceMappingURL=accordion_controller.js.map