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,40 +1,42 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/resizable_controller.ts
4
-
5
- // src/utils/arrow_step.ts
6
3
  function isReservedArrowChord(event, allow = []) {
7
4
  if (!event.key.startsWith("Arrow")) return false;
8
5
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
9
6
  }
10
7
 
11
- // src/utils/event_owner.ts
12
8
  function ownerIndex(candidates, node) {
13
9
  if (!(node instanceof Node)) return -1;
14
10
  return candidates.findIndex((candidate) => candidate.contains(node));
15
11
  }
16
12
 
17
- // src/utils/microtask_coalescer.ts
13
+ var ListenerSet = class {
14
+ #abort = new AbortController();
15
+ add(target, type, handler, options) {
16
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
17
+ }
18
+ dispose() {
19
+ this.#abort.abort();
20
+ this.#abort = new AbortController();
21
+ }
22
+ };
23
+
18
24
  var MicrotaskCoalescer = class {
19
25
  #run;
20
26
  #queued = false;
21
27
  #active = false;
22
28
  #generation = 0;
23
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
24
29
  constructor(run) {
25
30
  this.#run = run;
26
31
  }
27
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
28
32
  activate() {
29
33
  this.#active = true;
30
34
  }
31
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
32
35
  cancel() {
33
36
  this.#active = false;
34
37
  this.#queued = false;
35
38
  this.#generation += 1;
36
39
  }
37
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
38
40
  schedule() {
39
41
  if (!this.#active || this.#queued) return;
40
42
  this.#queued = true;
@@ -47,85 +49,197 @@ var MicrotaskCoalescer = class {
47
49
  }
48
50
  };
49
51
 
50
- // src/utils/before_cache_reset.ts
51
- var BeforeCacheReset = class _BeforeCacheReset {
52
- /** Every subscribed instance, iterated by the one shared document listener. */
53
- static #subscribers = /* @__PURE__ */ new Set();
54
- /** The shared listener; installed while at least one instance is subscribed. */
55
- static #onBeforeCache = () => {
56
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
57
- };
58
- #rewind;
59
- /** @param rewind - the pass that returns this controller's state to its initial form. */
60
- constructor(rewind) {
61
- this.#rewind = rewind;
52
+ var MorphRenderWatcher = class {
53
+ #listeners = new ListenerSet();
54
+ #pass;
55
+ #element = null;
56
+ #onMorph = () => this.#pass.schedule();
57
+ constructor(run) {
58
+ this.#pass = new MicrotaskCoalescer(run);
62
59
  }
63
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
64
- activate() {
65
- const first = _BeforeCacheReset.#subscribers.size === 0;
66
- _BeforeCacheReset.#subscribers.add(this);
67
- if (first) {
68
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
69
- }
60
+ observe(element) {
61
+ if (this.#element === element) return;
62
+ this.disconnect();
63
+ this.#element = element;
64
+ this.#pass.activate();
65
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
66
+ }
67
+ schedule() {
68
+ this.#pass.schedule();
69
+ }
70
+ disconnect() {
71
+ this.#listeners.dispose();
72
+ this.#pass.cancel();
73
+ this.#element = null;
74
+ }
75
+ };
76
+
77
+ var NUMBER_BOUNDS = {
78
+ finite: { finite: true },
79
+ positive: { finite: true, exclusiveMin: 0 }};
80
+ function matchesNumberBounds(value, bounds) {
81
+ if (!Number.isFinite(value)) {
82
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
83
+ if (direction === null) return false;
84
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
70
85
  }
71
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
72
- deactivate() {
73
- _BeforeCacheReset.#subscribers.delete(this);
74
- if (_BeforeCacheReset.#subscribers.size > 0) return;
75
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
86
+ if (bounds.min !== void 0 && value < bounds.min) return false;
87
+ if (bounds.max !== void 0 && value > bounds.max) return false;
88
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
89
+ if (bounds.integer && !Number.isInteger(value)) return false;
90
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
91
+ return true;
92
+ }
93
+
94
+ function readNumber(raw, fallback, bounds) {
95
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
96
+ }
97
+
98
+ var NumberValueReader = class {
99
+ #lastRejected = /* @__PURE__ */ new Map();
100
+ read(owner, name, raw, fallback, bounds) {
101
+ const resolved = readNumber(raw, fallback, bounds);
102
+ if (matchesNumberBounds(raw, bounds)) {
103
+ this.#lastRejected.delete(name);
104
+ return resolved;
105
+ }
106
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
107
+ const literal = owner.element.getAttribute(attribute);
108
+ if (literal === null) {
109
+ this.#lastRejected.delete(name);
110
+ return resolved;
111
+ }
112
+ if (this.#lastRejected.get(name) !== literal) {
113
+ this.#lastRejected.set(name, literal);
114
+ console.warn(
115
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
116
+ );
117
+ }
118
+ return resolved;
76
119
  }
77
120
  };
78
121
 
79
- // src/utils/tabindex_loan.ts
80
- var TabindexLoan = class {
122
+ function queryOpenDescendants(root, selector) {
123
+ const matches = [];
124
+ const roots = [root];
125
+ for (const scope of roots) {
126
+ matches.push(...scope.querySelectorAll(selector));
127
+ if (scope.nodeType === Node.ELEMENT_NODE) {
128
+ const shadow = scope.shadowRoot;
129
+ if (shadow) roots.push(shadow);
130
+ }
131
+ for (const element of scope.querySelectorAll("*")) {
132
+ if (element.shadowRoot) roots.push(element.shadowRoot);
133
+ }
134
+ }
135
+ return matches;
136
+ }
137
+
138
+ function sharedRegistry(key, create) {
139
+ const symbol = Symbol.for(key);
140
+ const scope = globalThis;
141
+ const existing = scope[symbol];
142
+ if (existing !== void 0) return existing;
143
+ const registry = create();
144
+ Object.defineProperty(globalThis, symbol, { value: registry });
145
+ return registry;
146
+ }
147
+
148
+ var TabindexLoan = class _TabindexLoan {
149
+ static #registry = sharedRegistry(
150
+ "stimeo-ui.tabindex-loan.registry.v1",
151
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
152
+ );
81
153
  #value;
154
+ #record;
82
155
  #lent = /* @__PURE__ */ new Set();
83
- /** Returns live loans before Turbo can copy them into its page snapshot. */
84
- #beforeCache = new BeforeCacheReset(() => this.returnAll());
85
- /**
86
- * @param value - the `tabindex` to lend. `"-1"` (the default) is
87
- * programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
88
- * which a scroll region with no focusable content of its own needs.
89
- */
90
- constructor(value = "-1") {
156
+ constructor(value, owner) {
91
157
  this.#value = value;
158
+ this.#record = `data-${owner}-tabindex-loan`;
159
+ _TabindexLoan.#registry.names.add(this.#record);
92
160
  }
93
- /** Lends `element` the value; no-ops when it already carries a `tabindex`. */
94
161
  lend(element) {
95
- if (element.hasAttribute("tabindex")) return;
96
- element.setAttribute("tabindex", this.#value);
162
+ const stack = this.#stack(element);
163
+ const current = element.getAttribute("tabindex");
164
+ const held = stack.find((hold) => hold.loan === this) ?? stack.find(
165
+ (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
166
+ );
167
+ const linked = stack.at(-1)?.value === current;
168
+ if (!held && current !== null && !linked) return;
97
169
  this.#lent.add(element);
98
- this.#beforeCache.activate();
170
+ if (held) held.loan = this;
171
+ else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
172
+ this.#mark(element, stack);
173
+ if (current === null || !held && current !== this.#value) {
174
+ element.setAttribute("tabindex", this.#value);
175
+ }
99
176
  }
100
- /** Takes back every loan whose value is still the one that was lent. */
101
177
  returnAll() {
102
178
  for (const element of this.#lent) {
103
- if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
179
+ const stack = this.#stack(element);
180
+ const hold = stack.find((held) => held.loan === this);
181
+ if (hold) this.#release(element, stack, hold);
104
182
  }
105
183
  this.#lent.clear();
106
- this.#beforeCache.deactivate();
107
184
  }
108
- };
109
-
110
- // src/utils/transient_hooks.ts
111
- var TransientHooks = class {
112
- #attributes;
113
- constructor(options) {
114
- this.#attributes = options.attributes;
185
+ reclaim(element) {
186
+ if (element.getAttribute(this.#record) !== this.#value) return;
187
+ const stack = this.#stack(element);
188
+ const hold = stack.find((held) => held.loan === null && held.record === this.#record);
189
+ if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
190
+ }
191
+ reclaimWithin(root) {
192
+ for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
193
+ this.reclaim(element);
194
+ }
115
195
  }
116
- /** Drops the declared hooks from `element`; what it carries besides stays. */
117
- reset(element) {
118
- for (const attribute of this.#attributes) element.removeAttribute(attribute);
196
+ #stack(element) {
197
+ const stacks = _TabindexLoan.#registry.stacks;
198
+ const stack = stacks.get(element) ?? this.#seed(element);
199
+ stacks.set(element, stack);
200
+ return stack;
201
+ }
202
+ #seed(element) {
203
+ const current = element.getAttribute("tabindex");
204
+ const holds = [];
205
+ for (const record of _TabindexLoan.#registry.names) {
206
+ const value = element.getAttribute(record);
207
+ if (value !== null) holds.push({ loan: null, record, value, linked: true });
208
+ }
209
+ holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
210
+ for (const [index, hold] of holds.entries()) hold.linked = index > 0;
211
+ return holds;
212
+ }
213
+ #release(element, stack, hold) {
214
+ const current = element.getAttribute("tabindex");
215
+ const index = stack.indexOf(hold);
216
+ const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
217
+ stack.splice(index, 1);
218
+ const above = stack[index];
219
+ if (above) above.linked &&= hold.linked;
220
+ this.#mark(element, stack);
221
+ if (!owns) return;
222
+ const below = hold.linked ? stack[index - 1] : void 0;
223
+ if (!below) element.removeAttribute("tabindex");
224
+ else if (below.value !== current) element.setAttribute("tabindex", below.value);
225
+ }
226
+ #mark(element, stack) {
227
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
228
+ for (const hold of stack) records.set(hold.record, hold.value);
229
+ for (const [name, recorded] of records) {
230
+ if (element.getAttribute(name) === recorded) continue;
231
+ if (recorded === null) element.removeAttribute(name);
232
+ else element.setAttribute(name, recorded);
233
+ }
119
234
  }
120
235
  };
121
236
 
122
- // src/controllers/resizable_controller.ts
123
- var TRANSIENT = new TransientHooks({ attributes: ["data-dragging"] });
124
237
  var DEFAULT_MIN = 0;
125
238
  var DEFAULT_MAX = 100;
126
239
  var DEFAULT_VALUE = 50;
127
240
  var DEFAULT_STEP = 1;
128
- var ResizableController = class extends Controller {
241
+ var ResizableController = class _ResizableController extends Controller {
242
+ #numbers = new NumberValueReader();
129
243
  static targets = ["primary", "secondary", "separator"];
130
244
  static values = {
131
245
  min: { type: Number, default: DEFAULT_MIN },
@@ -133,32 +247,24 @@ var ResizableController = class extends Controller {
133
247
  step: { type: Number, default: DEFAULT_STEP },
134
248
  value: { type: Number, default: DEFAULT_VALUE }
135
249
  };
250
+ static valueConstraints = {
251
+ min: NUMBER_BOUNDS.finite,
252
+ max: NUMBER_BOUNDS.finite,
253
+ step: NUMBER_BOUNDS.positive,
254
+ value: NUMBER_BOUNDS.finite
255
+ };
136
256
  static actions = ["onKeydown", "onPointerDown", "toggle"];
137
257
  static events = ["change", "reconcile"];
138
- /** Where the divider sat before the current collapse; `null` when unknown. */
139
258
  #valueBeforeCollapse = null;
140
- /**
141
- * The position last painted: moved by each move the user makes — every step of
142
- * a drag included — and by each repaint that finds a page-driven move.
143
- */
144
259
  #painted = DEFAULT_VALUE;
145
- /**
146
- * The position last confirmed: the one connecting published, then moved by
147
- * each `change` and `reconcile`. A drag moves the painted position step by
148
- * step and this one only when it ends.
149
- */
150
260
  #confirmed = DEFAULT_VALUE;
151
- /** Aborts in-progress pointer-drag listeners when the drag ends or on teardown. */
152
- #dragAbort = null;
153
- /** `tabindex` lent to a pane so `F6` can put focus on it; panes carry none. */
154
- #paneTabindex = new TabindexLoan();
155
- /** Releases the pane cycle listener bound in {@link connect}. */
261
+ #drag = null;
262
+ #paneTabindex = new TabindexLoan("-1", this.identifier);
156
263
  #cycleAbort = null;
157
- /** Folds the range and position inputs of one morph batch into a single paint. */
158
- #repaint = new MicrotaskCoalescer(() => this.#render());
264
+ #repaint = new MorphRenderWatcher(() => this.#render());
159
265
  connect() {
160
- TRANSIENT.reset(this.element);
161
- this.#repaint.activate();
266
+ this.#paneTabindex.reclaimWithin(this.element);
267
+ this.#repaint.observe(this.element);
162
268
  this.#confirmed = this.#position;
163
269
  this.#render();
164
270
  this.#cycleAbort = new AbortController();
@@ -166,17 +272,13 @@ var ResizableController = class extends Controller {
166
272
  signal: this.#cycleAbort.signal
167
273
  });
168
274
  }
169
- /** Cancels any active pointer drag so listeners never leak past disconnect. */
170
275
  disconnect() {
171
- this.#dragAbort?.abort();
172
- this.#dragAbort = null;
276
+ this.#endDrag();
173
277
  this.#cycleAbort?.abort();
174
278
  this.#cycleAbort = null;
175
- this.#repaint.cancel();
279
+ this.#repaint.disconnect();
176
280
  this.#paneTabindex.returnAll();
177
- this.element.removeAttribute("data-dragging");
178
281
  }
179
- /** Moves focus to the next pane on `F6`, wrapping; entering at the first one. */
180
282
  #onCycleKeydown = (event) => {
181
283
  if (event.key !== "F6") return;
182
284
  if (event.isComposing) return;
@@ -192,38 +294,39 @@ var ResizableController = class extends Controller {
192
294
  this.#paneTabindex.lend(next);
193
295
  next.focus();
194
296
  };
195
- /** Re-renders when application code (or a Turbo morph) changes `value` at runtime. */
196
297
  valueValueChanged() {
197
298
  this.#repaint.schedule();
198
299
  }
199
- /** Re-renders when application code (or a Turbo morph) changes `min` at runtime. */
200
300
  minValueChanged() {
201
301
  this.#repaint.schedule();
202
302
  }
203
- /** Re-renders when application code (or a Turbo morph) changes `max` at runtime. */
204
303
  maxValueChanged() {
205
304
  this.#repaint.schedule();
206
305
  }
207
- /** Re-publishes range and position onto a separator swapped in after connect. */
208
306
  separatorTargetConnected() {
209
307
  this.#repaint.schedule();
210
308
  }
211
- /** Starts active pointer drag tracking and locks capture. */
309
+ separatorTargetDisconnected(separator) {
310
+ if (this.#drag?.separator === separator && !this.separatorTargets.includes(separator)) {
311
+ this.#endDrag();
312
+ }
313
+ this.#repaint.schedule();
314
+ }
212
315
  onPointerDown(event) {
213
316
  if (!this.hasSeparatorTarget || event.button !== 0) return;
214
317
  event.preventDefault();
215
- const separator = this.separatorTarget;
318
+ const separators = this.separatorTargets;
319
+ const separator = separators[ownerIndex(separators, event.target)] ?? this.separatorTarget;
216
320
  separator.setPointerCapture(event.pointerId);
217
321
  separator.focus();
218
322
  this.element.setAttribute("data-dragging", "true");
219
- this.#dragAbort?.abort();
323
+ this.#drag?.abort.abort();
220
324
  const abort = new AbortController();
221
- this.#dragAbort = abort;
325
+ this.#drag = { separator, pointerId: event.pointerId, abort };
222
326
  separator.addEventListener("pointermove", this.#onPointerMove, { signal: abort.signal });
223
327
  separator.addEventListener("pointerup", this.#onPointerUp, { signal: abort.signal });
224
328
  separator.addEventListener("pointercancel", this.#onPointerUp, { signal: abort.signal });
225
329
  }
226
- /** Keydown adjustments for ArrowUp/Down/Left/Right and Home/End. */
227
330
  onKeydown(event) {
228
331
  if (isReservedArrowChord(event)) return;
229
332
  if (!this.hasSeparatorTarget) return;
@@ -279,7 +382,6 @@ var ResizableController = class extends Controller {
279
382
  this.#commit(nextValue);
280
383
  }
281
384
  }
282
- /** Collapses the primary pane to its minimum, or returns it to the last position. */
283
385
  toggle() {
284
386
  const { min, max } = this.#range;
285
387
  if (this.#position > min) {
@@ -298,26 +400,25 @@ var ResizableController = class extends Controller {
298
400
  const fraction = Number.isFinite(raw) ? Math.max(0, Math.min(raw, 1)) : 0;
299
401
  this.#move(Math.round(fraction * 100));
300
402
  };
301
- #onPointerUp = (event) => {
302
- this.element.removeAttribute("data-dragging");
303
- this.#dragAbort?.abort();
304
- this.#dragAbort = null;
305
- if (this.hasSeparatorTarget) {
306
- this.separatorTarget.releasePointerCapture(event.pointerId);
307
- }
403
+ #onPointerUp = () => {
404
+ this.#endDrag();
308
405
  this.#confirm();
309
406
  };
310
- /** Moves to a position the user chose and reports it if it moved. */
407
+ #endDrag() {
408
+ this.element.removeAttribute("data-dragging");
409
+ const drag = this.#drag;
410
+ this.#drag = null;
411
+ if (!drag) return;
412
+ drag.abort.abort();
413
+ try {
414
+ drag.separator.releasePointerCapture?.(drag.pointerId);
415
+ } catch {
416
+ }
417
+ }
311
418
  #commit(raw) {
312
419
  this.#move(raw);
313
420
  this.#confirm();
314
421
  }
315
- /**
316
- * Stores the clamped position the user moved to and paints it. The painted
317
- * position moves with it before anything is reported, so the repaint the Value
318
- * write schedules — and any move a subscriber makes while `change` is
319
- * dispatched — is measured from this position.
320
- */
321
422
  #move(raw) {
322
423
  const { min, max } = this.#range;
323
424
  const position = Math.max(min, Math.min(raw, max));
@@ -325,14 +426,6 @@ var ResizableController = class extends Controller {
325
426
  this.#painted = position;
326
427
  this.#paint();
327
428
  }
328
- /**
329
- * Publishes the position the Values declare without writing it back. A
330
- * position the user did not paint is the page's move, reported once as
331
- * `reconcile` unless it lands on the position last confirmed — a drag the page
332
- * puts back where it started has nothing new to report.
333
- *
334
- * @stimeoRenderRoot
335
- */
336
429
  #render() {
337
430
  this.#paint();
338
431
  const position = this.#position;
@@ -342,11 +435,9 @@ var ResizableController = class extends Controller {
342
435
  this.#confirmed = position;
343
436
  this.dispatch("reconcile", { detail: this.#detail(position) });
344
437
  }
345
- /**
346
- * Publishes the position: the fraction consumer CSS multiplies a pane by, and
347
- * the range assistive tech reads off the separator.
348
- */
349
438
  #paint() {
439
+ if (this.#drag) this.element.setAttribute("data-dragging", "true");
440
+ else this.element.removeAttribute("data-dragging");
350
441
  const { min, max } = this.#range;
351
442
  const position = this.#position;
352
443
  this.element.style.setProperty("--stimeo--resizable-fraction", String(position / 100));
@@ -356,43 +447,68 @@ var ResizableController = class extends Controller {
356
447
  this.separatorTarget.setAttribute("aria-valuemax", String(max));
357
448
  }
358
449
  }
359
- /**
360
- * Reports the published position as `change` when the user's operation moved
361
- * it from the position last confirmed. A key at an edge, a press released
362
- * without moving and a drag released where it started report nothing.
363
- */
364
450
  #confirm() {
365
451
  const position = this.#position;
366
452
  if (position === this.#confirmed) return;
367
453
  this.#confirmed = position;
368
454
  this.dispatch("change", { detail: this.#detail(position) });
369
455
  }
370
- /** The detail both events carry for `position`. */
371
456
  #detail(position) {
372
457
  return { value: position, fraction: position / 100 };
373
458
  }
374
- /** The declared range after validation; an unreadable bound uses its default. */
375
459
  get #range() {
376
- const min = Number.isFinite(this.minValue) ? this.minValue : DEFAULT_MIN;
377
- const max = Number.isFinite(this.maxValue) ? this.maxValue : DEFAULT_MAX;
460
+ const min = this.#safeMin;
461
+ const max = this.#safeMax;
378
462
  return { min, max: Math.max(min, max) };
379
463
  }
380
- /** The declared position, clamped into the validated range. */
381
464
  get #position() {
382
465
  const { min, max } = this.#range;
383
- const value = Number.isFinite(this.valueValue) ? this.valueValue : DEFAULT_VALUE;
466
+ const value = this.#safeValue;
384
467
  return Math.max(min, Math.min(value, max));
385
468
  }
386
- /** The keyboard increment; a non-positive or unreadable one uses the default. */
387
469
  get #step() {
388
- return Number.isFinite(this.stepValue) && this.stepValue > 0 ? this.stepValue : DEFAULT_STEP;
470
+ return this.#safeStep;
389
471
  }
390
- /** Whether the divider runs vertically; a separator that does not say is horizontal. */
391
472
  get #isVertical() {
392
473
  return this.hasSeparatorTarget && this.separatorTarget.getAttribute("aria-orientation") === "vertical";
393
474
  }
475
+ get #safeMin() {
476
+ return this.#numbers.read(
477
+ this,
478
+ "min",
479
+ this.minValue,
480
+ _ResizableController.values.min.default,
481
+ _ResizableController.valueConstraints.min
482
+ );
483
+ }
484
+ get #safeMax() {
485
+ return this.#numbers.read(
486
+ this,
487
+ "max",
488
+ this.maxValue,
489
+ _ResizableController.values.max.default,
490
+ _ResizableController.valueConstraints.max
491
+ );
492
+ }
493
+ get #safeStep() {
494
+ return this.#numbers.read(
495
+ this,
496
+ "step",
497
+ this.stepValue,
498
+ _ResizableController.values.step.default,
499
+ _ResizableController.valueConstraints.step
500
+ );
501
+ }
502
+ get #safeValue() {
503
+ return this.#numbers.read(
504
+ this,
505
+ "value",
506
+ this.valueValue,
507
+ _ResizableController.values.value.default,
508
+ _ResizableController.valueConstraints.value
509
+ );
510
+ }
394
511
  };
395
512
 
396
513
  export { ResizableController };
397
- //# sourceMappingURL=resizable_controller.js.map
398
514
  //# sourceMappingURL=resizable_controller.js.map