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,104 +1,196 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/separator_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
  }
7
+ function hasModifierChord(event) {
8
+ return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
9
+ }
10
+
11
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
12
+ if (element.getAttribute(name) === value) return false;
13
+ if (value === null) element.removeAttribute(name);
14
+ else element.setAttribute(name, value);
15
+ return true;
16
+ }
17
+
18
+ function sharedRegistry(key, create) {
19
+ const symbol = Symbol.for(key);
20
+ const scope = globalThis;
21
+ const existing = scope[symbol];
22
+ if (existing !== void 0) return existing;
23
+ const registry2 = create();
24
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
25
+ return registry2;
26
+ }
10
27
 
11
- // src/utils/attribute_lease.ts
28
+ var registry = sharedRegistry(
29
+ "stimeo-ui.attribute-lease.registry.v1",
30
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
31
+ );
32
+ function isAttributeValue(value) {
33
+ return value === null || typeof value === "string";
34
+ }
35
+ function readLeaseRecord(element, record) {
36
+ try {
37
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
38
+ if (!Array.isArray(parsed)) return void 0;
39
+ const [original, written, ...rest] = parsed;
40
+ const linked = rest[0] !== false;
41
+ const beneath = linked ? rest : rest.slice(1);
42
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
43
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ function attributeStack(element, attribute) {
49
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
50
+ registry.stacks.set(element, byAttribute);
51
+ const known = byAttribute.get(attribute);
52
+ if (known) return known;
53
+ const found = [];
54
+ for (const record of registry.names.get(attribute) ?? []) {
55
+ const read = readLeaseRecord(element, record);
56
+ if (read) found.push({ ...read, record });
57
+ }
58
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
59
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
60
+ lease: null,
61
+ record,
62
+ written,
63
+ original,
64
+ linked: linked && index > 0
65
+ }));
66
+ byAttribute.set(attribute, stack);
67
+ return stack;
68
+ }
69
+ function findHold(stack, lease, record) {
70
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
71
+ }
12
72
  var AttributeLease = class {
13
73
  #attribute;
14
- #records = /* @__PURE__ */ new Map();
15
- /** @param attribute - The attribute whose temporary values this lease owns. */
16
- constructor(attribute) {
74
+ #record;
75
+ #base;
76
+ #preserveAuthor;
77
+ #held = /* @__PURE__ */ new Set();
78
+ constructor(attribute, owner, options = {}) {
17
79
  this.#attribute = attribute;
80
+ this.#record = `data-${owner}-${attribute}-lease`;
81
+ this.#base = options.base === true;
82
+ this.#preserveAuthor = options.preserveAuthor === true;
83
+ const names = registry.names;
84
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
18
85
  }
19
- /** Writes or removes the leased attribute while preserving its authored value. */
20
86
  write(element, value) {
21
- const existing = this.#records.get(element);
22
- if (existing) {
23
- existing.written = value;
24
- } else {
25
- this.#records.set(element, {
26
- original: element.getAttribute(this.#attribute),
27
- written: value
28
- });
29
- }
30
- this.#reflect(element, value);
87
+ const stack = attributeStack(element, this.#attribute);
88
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
89
+ this.#held.add(element);
90
+ const shows = !this.#covered(element, stack, hold);
91
+ hold.written = value;
92
+ this.#mark(element, stack);
93
+ if (shows) this.#reflect(element, value);
31
94
  }
32
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
33
95
  return(element) {
34
- const record = this.#records.get(element);
35
- if (!record) return;
36
- this.#records.delete(element);
37
- const stillOwned = element.getAttribute(this.#attribute) === record.written;
38
- if (stillOwned) this.#reflect(element, record.original);
96
+ const held = this.#held.delete(element);
97
+ const known = registry.stacks.get(element)?.has(this.#attribute);
98
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
99
+ const hold = this.#take(stack);
100
+ if (!hold) return false;
101
+ const carries = element.getAttribute(this.#attribute) === hold.written;
102
+ const owns = carries && !this.#covered(element, stack, hold);
103
+ const index = stack.indexOf(hold);
104
+ stack.splice(index, 1);
105
+ const above = stack[index];
106
+ if (above) above.linked &&= hold.linked;
107
+ this.#mark(element, stack);
108
+ if (!owns) return true;
109
+ const below = hold.linked ? stack[index - 1] : void 0;
110
+ this.#reflect(element, below ? below.written : hold.original);
111
+ return true;
39
112
  }
40
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
41
- #reflect(element, value) {
42
- if (element.getAttribute(this.#attribute) === value) return;
43
- if (value === null) element.removeAttribute(this.#attribute);
44
- else element.setAttribute(this.#attribute, value);
45
- }
46
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
47
113
  returnAll() {
48
- for (const element of Array.from(this.#records.keys())) this.return(element);
114
+ for (const element of Array.from(this.#held)) this.return(element);
115
+ }
116
+ #take(stack) {
117
+ const hold = findHold(stack, this, this.#record);
118
+ if (hold) hold.lease = this;
119
+ return hold;
120
+ }
121
+ #acquire(element, stack) {
122
+ const current = element.getAttribute(this.#attribute);
123
+ const top = stack.at(-1);
124
+ const bottom = stack[0];
125
+ const over = top?.written === current ? top : void 0;
126
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
127
+ const hold = {
128
+ lease: this,
129
+ record: this.#record,
130
+ written: current,
131
+ original: from ? from.original : current,
132
+ linked: !this.#base && over !== void 0
133
+ };
134
+ if (this.#base && bottom) {
135
+ bottom.linked = true;
136
+ stack.unshift(hold);
137
+ } else stack.push(hold);
138
+ return hold;
139
+ }
140
+ #covered(element, stack, hold) {
141
+ const current = element.getAttribute(this.#attribute);
142
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
143
+ }
144
+ #mark(element, stack) {
145
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
146
+ stack.forEach((hold, index) => {
147
+ const names = stack.slice(0, index).map((under) => under.record);
148
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
149
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
150
+ records.set(
151
+ hold.record,
152
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
153
+ );
154
+ });
155
+ for (const [name, recorded] of records) {
156
+ setAttributeIfChanged(element, name, recorded);
157
+ }
158
+ }
159
+ #reflect(element, value) {
160
+ setAttributeIfChanged(element, this.#attribute, value);
49
161
  }
50
162
  };
51
163
 
52
- // src/utils/before_cache_reset.ts
53
- var BeforeCacheReset = class _BeforeCacheReset {
54
- /** Every subscribed instance, iterated by the one shared document listener. */
55
- static #subscribers = /* @__PURE__ */ new Set();
56
- /** The shared listener; installed while at least one instance is subscribed. */
57
- static #onBeforeCache = () => {
58
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
59
- };
60
- #rewind;
61
- /** @param rewind - the pass that returns this controller's state to its initial form. */
62
- constructor(rewind) {
63
- this.#rewind = rewind;
64
- }
65
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
66
- activate() {
67
- const first = _BeforeCacheReset.#subscribers.size === 0;
68
- _BeforeCacheReset.#subscribers.add(this);
69
- if (first) {
70
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
71
- }
164
+ var ListenerSet = class {
165
+ #abort = new AbortController();
166
+ add(target, type, handler, options) {
167
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
72
168
  }
73
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
74
- deactivate() {
75
- _BeforeCacheReset.#subscribers.delete(this);
76
- if (_BeforeCacheReset.#subscribers.size > 0) return;
77
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
169
+ dispose() {
170
+ this.#abort.abort();
171
+ this.#abort = new AbortController();
78
172
  }
79
173
  };
80
174
 
81
- // src/utils/microtask_coalescer.ts
82
175
  var MicrotaskCoalescer = class {
83
176
  #run;
84
177
  #queued = false;
85
178
  #active = false;
86
179
  #generation = 0;
87
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
88
180
  constructor(run) {
89
181
  this.#run = run;
90
182
  }
91
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
92
183
  activate() {
93
184
  this.#active = true;
94
185
  }
95
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
96
186
  cancel() {
97
187
  this.#active = false;
98
188
  this.#queued = false;
99
189
  this.#generation += 1;
100
190
  }
101
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
191
+ get pending() {
192
+ return this.#queued;
193
+ }
102
194
  schedule() {
103
195
  if (!this.#active || this.#queued) return;
104
196
  this.#queued = true;
@@ -111,7 +203,79 @@ var MicrotaskCoalescer = class {
111
203
  }
112
204
  };
113
205
 
114
- // src/utils/stepped_value.ts
206
+ var MorphRenderWatcher = class {
207
+ #listeners = new ListenerSet();
208
+ #pass;
209
+ #element = null;
210
+ #onMorph = () => this.#pass.schedule();
211
+ constructor(run) {
212
+ this.#pass = new MicrotaskCoalescer(run);
213
+ }
214
+ observe(element) {
215
+ if (this.#element === element) return;
216
+ this.disconnect();
217
+ this.#element = element;
218
+ this.#pass.activate();
219
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
220
+ }
221
+ schedule() {
222
+ this.#pass.schedule();
223
+ }
224
+ get pending() {
225
+ return this.#pass.pending;
226
+ }
227
+ disconnect() {
228
+ this.#listeners.dispose();
229
+ this.#pass.cancel();
230
+ this.#element = null;
231
+ }
232
+ };
233
+
234
+ var NUMBER_BOUNDS = {
235
+ finite: { finite: true },
236
+ positive: { finite: true, exclusiveMin: 0 }};
237
+ function matchesNumberBounds(value, bounds) {
238
+ if (!Number.isFinite(value)) {
239
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
240
+ if (direction === null) return false;
241
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
242
+ }
243
+ if (bounds.min !== void 0 && value < bounds.min) return false;
244
+ if (bounds.max !== void 0 && value > bounds.max) return false;
245
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
246
+ if (bounds.integer && !Number.isInteger(value)) return false;
247
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
248
+ return true;
249
+ }
250
+
251
+ function readNumber(raw, fallback, bounds) {
252
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
253
+ }
254
+
255
+ var NumberValueReader = class {
256
+ #lastRejected = /* @__PURE__ */ new Map();
257
+ read(owner, name, raw, fallback, bounds) {
258
+ const resolved = readNumber(raw, fallback, bounds);
259
+ if (matchesNumberBounds(raw, bounds)) {
260
+ this.#lastRejected.delete(name);
261
+ return resolved;
262
+ }
263
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
264
+ const literal = owner.element.getAttribute(attribute);
265
+ if (literal === null) {
266
+ this.#lastRejected.delete(name);
267
+ return resolved;
268
+ }
269
+ if (this.#lastRejected.get(name) !== literal) {
270
+ this.#lastRejected.set(name, literal);
271
+ console.warn(
272
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
273
+ );
274
+ }
275
+ return resolved;
276
+ }
277
+ };
278
+
115
279
  function effectiveStep(step) {
116
280
  return Number.isFinite(step) && step > 0 ? step : 1;
117
281
  }
@@ -217,10 +381,10 @@ function finiteFallback(min, max) {
217
381
  return 0;
218
382
  }
219
383
 
220
- // src/controllers/separator_controller.ts
221
384
  var DEFAULT_MIN = 0;
222
385
  var DEFAULT_MAX = 100;
223
- var SeparatorController = class extends Controller {
386
+ var SeparatorController = class _SeparatorController extends Controller {
387
+ #numbers = new NumberValueReader();
224
388
  static values = {
225
389
  orientation: { type: String, default: "horizontal" },
226
390
  focusable: { type: Boolean, default: false },
@@ -229,76 +393,56 @@ var SeparatorController = class extends Controller {
229
393
  step: { type: Number, default: 1 },
230
394
  value: { type: Number, default: DEFAULT_MIN }
231
395
  };
396
+ static valueConstraints = {
397
+ min: NUMBER_BOUNDS.finite,
398
+ max: NUMBER_BOUNDS.finite,
399
+ step: NUMBER_BOUNDS.positive,
400
+ value: NUMBER_BOUNDS.finite
401
+ };
232
402
  static actions = ["onKeydown"];
233
403
  static events = ["change", "reconcile"];
234
- #role = new AttributeLease("role");
235
- #tabindex = new AttributeLease("tabindex");
236
- #orientation = new AttributeLease("aria-orientation");
237
- #minimum = new AttributeLease("aria-valuemin");
238
- #maximum = new AttributeLease("aria-valuemax");
239
- #current = new AttributeLease("aria-valuenow");
240
- /** Collapses a morph that changes several render Values into one repaint. */
241
- #repaint = new MicrotaskCoalescer(() => this.#render());
242
- /** Restores authored semantics before Turbo snapshots the element. */
243
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
244
- /**
245
- * The baseline a published value is compared with: the value last published
246
- * through `aria-valuenow`, or `null` while this connection has published none.
247
- * It moves to each value a key the user presses or a repaint publishes. While
248
- * nothing is published — the separator is decorative — it keeps the last
249
- * value published, so a value that moved meanwhile is reported once when the
250
- * separator publishes again; one that connected decorative keeps `null`, so
251
- * its first published value is only taken as the baseline.
252
- */
404
+ #role = new AttributeLease("role", this.identifier);
405
+ #tabindex = new AttributeLease("tabindex", this.identifier);
406
+ #orientation = new AttributeLease("aria-orientation", this.identifier);
407
+ #minimum = new AttributeLease("aria-valuemin", this.identifier);
408
+ #maximum = new AttributeLease("aria-valuemax", this.identifier);
409
+ #current = new AttributeLease("aria-valuenow", this.identifier);
410
+ #repaint = new MorphRenderWatcher(() => this.#render());
253
411
  #settled = null;
254
- /**
255
- * Hydrates authored range ARIA into absent Values once and renders them
256
- * without writing the normalized value back. What the first render publishes
257
- * is the baseline, so connecting reports nothing.
258
- */
259
412
  connect() {
260
413
  this.#hydrateValues();
261
- this.#repaint.activate();
262
- this.#beforeCache.activate();
414
+ this.#repaint.observe(this.element);
263
415
  this.#settled = null;
264
416
  this.#render();
265
417
  }
266
- /** Cancels repaint work and returns every semantic attribute this instance controlled. */
267
418
  disconnect() {
268
- this.#repaint.cancel();
269
- this.#beforeCache.deactivate();
419
+ this.#repaint.disconnect();
270
420
  this.#returnAttributes();
271
421
  }
272
- /** Repaints an orientation changed by application code or a Turbo morph. */
273
422
  orientationValueChanged() {
274
423
  this.#repaint.schedule();
275
424
  }
276
- /** Adds or removes the focusable range semantics at runtime. */
277
425
  focusableValueChanged() {
278
426
  this.#repaint.schedule();
279
427
  }
280
- /** Repaints a minimum changed by application code or a Turbo morph. */
281
428
  minValueChanged() {
282
429
  this.#repaint.schedule();
283
430
  }
284
- /** Repaints a maximum changed by application code or a Turbo morph. */
285
431
  maxValueChanged() {
286
432
  this.#repaint.schedule();
287
433
  }
288
- /** Repaints a step changed by application code or a Turbo morph. */
289
434
  stepValueChanged() {
290
435
  this.#repaint.schedule();
291
436
  }
292
- /** Repaints a current value changed by application code or a Turbo morph. */
293
437
  valueValueChanged() {
294
438
  this.#repaint.schedule();
295
439
  }
296
- /** Adjusts the value on arrow / Home / End keys (focusable variant only). */
297
440
  onKeydown(event) {
298
441
  if (isReservedArrowChord(event) || !this.focusableValue) return;
442
+ if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
299
443
  const horizontal = this.#effectiveOrientation === "horizontal";
300
444
  const range = this.#effectiveRange;
301
- const declared = this.valueValue;
445
+ const declared = this.#safeValue;
302
446
  let next = null;
303
447
  switch (event.key) {
304
448
  case "ArrowUp":
@@ -326,16 +470,6 @@ var SeparatorController = class extends Controller {
326
470
  event.preventDefault();
327
471
  this.#commit(next);
328
472
  }
329
- /**
330
- * Stores a normalized user value, renders immediately, and reports it as
331
- * `change` when it differs from the value last published. The comparison is
332
- * with the value last published, not with the value the key started from, so
333
- * a page write no repaint has published yet is reported once, as this change,
334
- * and a key that ends where the value was last published reports nothing. The
335
- * baseline moves before the render and the report, so neither the repaint the
336
- * Value write schedules nor a move a subscriber makes while `change` is
337
- * dispatched is measured from the value this key replaced.
338
- */
339
473
  #commit(raw) {
340
474
  const value = snapSteppedValue(raw, this.#effectiveRange);
341
475
  const reported = this.#settled;
@@ -344,20 +478,10 @@ var SeparatorController = class extends Controller {
344
478
  this.#render();
345
479
  if (value !== reported) this.dispatch("change", { detail: { value } });
346
480
  }
347
- /**
348
- * Reflects live Values into the connected element without writing them back,
349
- * then compares what it published with the baseline. A decorative separator
350
- * publishes no value (`null` here): the baseline stays the last value
351
- * published. A value is reported once as `reconcile` only when it moved from a
352
- * published baseline, so the first value a connection publishes is only taken
353
- * as the baseline.
354
- *
355
- * @stimeoRenderRoot
356
- */
357
481
  #render() {
358
482
  const range = this.#effectiveRange;
359
483
  const focusable = this.focusableValue;
360
- const published = focusable ? snapSteppedValue(this.valueValue, range) : null;
484
+ const published = focusable ? snapSteppedValue(this.#safeValue, range) : null;
361
485
  this.#role.write(this.element, "separator");
362
486
  this.#orientation.write(this.element, this.#effectiveOrientation);
363
487
  this.#tabindex.write(this.element, focusable ? "0" : null);
@@ -370,27 +494,19 @@ var SeparatorController = class extends Controller {
370
494
  this.dispatch("reconcile", { detail: { value: published } });
371
495
  }
372
496
  }
373
- /** Effective orientation; unknown strings fall back to the horizontal contract. */
374
497
  get #effectiveOrientation() {
375
498
  return this.orientationValue === "vertical" ? "vertical" : "horizontal";
376
499
  }
377
- /** Finite, ordered range shared by rendering and keyboard stepping. */
378
500
  get #effectiveRange() {
379
- const min = Number.isFinite(this.minValue) ? this.minValue : DEFAULT_MIN;
380
- const candidateMax = Number.isFinite(this.maxValue) ? this.maxValue : DEFAULT_MAX;
501
+ const min = this.#safeMin;
502
+ const candidateMax = this.#safeMax;
381
503
  return {
382
504
  min,
383
505
  max: Math.max(min, candidateMax),
384
- step: this.stepValue,
506
+ step: this.#safeStep,
385
507
  base: min
386
508
  };
387
509
  }
388
- /**
389
- * Copies valid authored ARIA into Values only when no explicit Value exists.
390
- * This fill is the one Value write `connect()` makes: it never overwrites a
391
- * declaration, and no Value callback runs it, so a declaration the page
392
- * changes later is only ever read.
393
- */
394
510
  #hydrateValues() {
395
511
  if (!this.#hasInput("orientation")) {
396
512
  const orientation = this.element.getAttribute("aria-orientation");
@@ -405,18 +521,15 @@ var SeparatorController = class extends Controller {
405
521
  const value = this.#finiteAttribute("aria-valuenow");
406
522
  if (!this.#hasInput("value") && value !== null) this.valueValue = value;
407
523
  }
408
- /** Whether the author supplied the namespaced Value instead of using its default. */
409
524
  #hasInput(name) {
410
525
  return this.element.hasAttribute(`data-${this.identifier}-${name}-value`);
411
526
  }
412
- /** Strict finite-number parser for the one-time progressive-enhancement bridge. */
413
527
  #finiteAttribute(name) {
414
528
  const raw = this.element.getAttribute(name);
415
529
  if (raw === null || raw.trim() === "") return null;
416
530
  const value = Number(raw);
417
531
  return Number.isFinite(value) ? value : null;
418
532
  }
419
- /** Returns attributes without overwriting values a consumer changed after render. */
420
533
  #returnAttributes() {
421
534
  this.#role.return(this.element);
422
535
  this.#tabindex.return(this.element);
@@ -425,13 +538,43 @@ var SeparatorController = class extends Controller {
425
538
  this.#maximum.return(this.element);
426
539
  this.#current.return(this.element);
427
540
  }
428
- /** Prevents a queued Value repaint from racing Turbo's restored snapshot. */
429
- #rewindForCache() {
430
- this.#repaint.cancel();
431
- this.#returnAttributes();
541
+ get #safeMin() {
542
+ return this.#numbers.read(
543
+ this,
544
+ "min",
545
+ this.minValue,
546
+ _SeparatorController.values.min.default,
547
+ _SeparatorController.valueConstraints.min
548
+ );
549
+ }
550
+ get #safeMax() {
551
+ return this.#numbers.read(
552
+ this,
553
+ "max",
554
+ this.maxValue,
555
+ _SeparatorController.values.max.default,
556
+ _SeparatorController.valueConstraints.max
557
+ );
558
+ }
559
+ get #safeStep() {
560
+ return this.#numbers.read(
561
+ this,
562
+ "step",
563
+ this.stepValue,
564
+ _SeparatorController.values.step.default,
565
+ _SeparatorController.valueConstraints.step
566
+ );
567
+ }
568
+ get #safeValue() {
569
+ return this.#numbers.read(
570
+ this,
571
+ "value",
572
+ this.valueValue,
573
+ this.#effectiveRange.min,
574
+ _SeparatorController.valueConstraints.value
575
+ );
432
576
  }
433
577
  };
434
578
 
435
579
  export { SeparatorController };
436
- //# sourceMappingURL=separator_controller.js.map
437
580
  //# sourceMappingURL=separator_controller.js.map