stimeo-ui 0.17.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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -1
  3. data/README.md +19 -10
  4. data/dist/cable/index.js +22 -14
  5. data/dist/controllers/accordion_controller.js +14 -6
  6. data/dist/controllers/alert_dialog_controller.js +145 -118
  7. data/dist/controllers/announcer_controller.js +113 -17
  8. data/dist/controllers/auto_submit_controller.js +185 -60
  9. data/dist/controllers/avatar_controller.js +55 -43
  10. data/dist/controllers/bulk_select_controller.js +64 -50
  11. data/dist/controllers/calendar_controller.js +55 -53
  12. data/dist/controllers/carousel_controller.js +124 -86
  13. data/dist/controllers/character_counter_controller.js +291 -95
  14. data/dist/controllers/checkbox_controller.js +6 -0
  15. data/dist/controllers/clipboard_controller.js +118 -27
  16. data/dist/controllers/collapsible_controller.js +52 -49
  17. data/dist/controllers/color_picker_controller.js +29 -7
  18. data/dist/controllers/combobox_controller.js +210 -71
  19. data/dist/controllers/command_palette_controller.js +289 -140
  20. data/dist/controllers/conditional_fields_controller.js +145 -58
  21. data/dist/controllers/confirm_controller.js +145 -118
  22. data/dist/controllers/context_menu_controller.js +138 -73
  23. data/dist/controllers/countdown_controller.js +109 -21
  24. data/dist/controllers/currency_input_controller.js +446 -72
  25. data/dist/controllers/data_grid_controller.js +134 -28
  26. data/dist/controllers/date_range_picker_controller.js +93 -72
  27. data/dist/controllers/dialog_controller.js +145 -118
  28. data/dist/controllers/direct_upload_controller.js +24 -14
  29. data/dist/controllers/dirty_form_controller.js +68 -31
  30. data/dist/controllers/dismissible_controller.js +64 -46
  31. data/dist/controllers/drawer_controller.js +145 -118
  32. data/dist/controllers/dropdown_controller.js +76 -63
  33. data/dist/controllers/editable_controller.js +199 -62
  34. data/dist/controllers/empty_state_controller.js +13 -6
  35. data/dist/controllers/file_dropzone_controller.js +55 -48
  36. data/dist/controllers/filter_controller.js +9 -2
  37. data/dist/controllers/flash_controller.js +66 -33
  38. data/dist/controllers/focus_controller.js +39 -14
  39. data/dist/controllers/form_field_controller.js +52 -43
  40. data/dist/controllers/form_validation_controller.js +18 -11
  41. data/dist/controllers/frame_loading_controller.js +55 -48
  42. data/dist/controllers/hover_card_controller.js +67 -61
  43. data/dist/controllers/idle_controller.js +10 -6
  44. data/dist/controllers/input_mask_controller.js +411 -24
  45. data/dist/controllers/intersection_controller.js +3 -0
  46. data/dist/controllers/lazy_frame_controller.js +15 -3
  47. data/dist/controllers/listbox_controller.js +260 -79
  48. data/dist/controllers/local_time_controller.js +48 -13
  49. data/dist/controllers/masonry_controller.js +11 -4
  50. data/dist/controllers/menu_controller.js +138 -82
  51. data/dist/controllers/menubar_controller.js +65 -19
  52. data/dist/controllers/meter_controller.js +270 -24
  53. data/dist/controllers/multi_select_controller.js +251 -94
  54. data/dist/controllers/navigation_menu_controller.js +67 -19
  55. data/dist/controllers/nested_form_controller.js +98 -64
  56. data/dist/controllers/network_status_controller.js +70 -52
  57. data/dist/controllers/number_input_controller.js +344 -94
  58. data/dist/controllers/optimistic_controller.js +29 -11
  59. data/dist/controllers/otp_controller.js +502 -104
  60. data/dist/controllers/overflow_indicator_controller.js +6 -0
  61. data/dist/controllers/overflow_menu_controller.js +136 -21
  62. data/dist/controllers/pagination_controller.js +47 -14
  63. data/dist/controllers/password_reveal_controller.js +52 -49
  64. data/dist/controllers/password_strength_controller.js +13 -6
  65. data/dist/controllers/persist_controller.js +48 -128
  66. data/dist/controllers/pointer_drag_controller.js +249 -23
  67. data/dist/controllers/popover_controller.js +82 -65
  68. data/dist/controllers/portal_controller.js +6 -0
  69. data/dist/controllers/preview_guard_controller.js +14 -3
  70. data/dist/controllers/progress_controller.js +271 -27
  71. data/dist/controllers/radio_group_controller.js +167 -80
  72. data/dist/controllers/range_slider_controller.js +27 -7
  73. data/dist/controllers/rating_controller.js +93 -57
  74. data/dist/controllers/read_more_controller.js +10 -6
  75. data/dist/controllers/reading_progress_controller.js +22 -5
  76. data/dist/controllers/relative_time_controller.js +75 -11
  77. data/dist/controllers/reset_on_restore_controller.js +6 -0
  78. data/dist/controllers/resizable_controller.js +55 -22
  79. data/dist/controllers/roving_controller.js +10 -4
  80. data/dist/controllers/scroll_area_controller.js +129 -88
  81. data/dist/controllers/scroll_restore_controller.js +14 -2
  82. data/dist/controllers/scroll_visibility_controller.js +93 -60
  83. data/dist/controllers/scrollspy_controller.js +11 -5
  84. data/dist/controllers/separator_controller.js +59 -43
  85. data/dist/controllers/sidebar_controller.js +150 -122
  86. data/dist/controllers/skeleton_controller.js +55 -48
  87. data/dist/controllers/slider_controller.js +27 -6
  88. data/dist/controllers/smart_sticky_header_controller.js +13 -5
  89. data/dist/controllers/sortable_controller.js +27 -14
  90. data/dist/controllers/spinner_controller.js +55 -48
  91. data/dist/controllers/step_indicator_controller.js +6 -0
  92. data/dist/controllers/stepper_controller.js +6 -0
  93. data/dist/controllers/stick_to_bottom_controller.js +19 -8
  94. data/dist/controllers/sticky_observer_controller.js +49 -43
  95. data/dist/controllers/submit_once_controller.js +145 -85
  96. data/dist/controllers/switch_controller.js +172 -28
  97. data/dist/controllers/tabs_controller.js +6 -1
  98. data/dist/controllers/tags_input_controller.js +157 -28
  99. data/dist/controllers/textarea_autosize_controller.js +40 -19
  100. data/dist/controllers/theme_controller.js +10 -4
  101. data/dist/controllers/time_picker_controller.js +192 -54
  102. data/dist/controllers/toast_controller.js +58 -27
  103. data/dist/controllers/toggle_group_controller.js +158 -56
  104. data/dist/controllers/toolbar_controller.js +150 -21
  105. data/dist/controllers/tree_view_controller.js +134 -25
  106. data/dist/index.js +2451 -1312
  107. data/dist/positioning/index.js +6 -0
  108. data/lib/generators/stimeo/install/install_generator.rb +7 -2
  109. data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
  110. data/lib/stimeo/ui/version.rb +1 -1
  111. metadata +2 -2
@@ -43,6 +43,9 @@ var MicrotaskCoalescer = class {
43
43
  this.#queued = false;
44
44
  this.#generation += 1;
45
45
  }
46
+ get pending() {
47
+ return this.#queued;
48
+ }
46
49
  schedule() {
47
50
  if (!this.#active || this.#queued) return;
48
51
  this.#queued = true;
@@ -73,6 +76,9 @@ var MorphRenderWatcher = class {
73
76
  schedule() {
74
77
  this.#pass.schedule();
75
78
  }
79
+ get pending() {
80
+ return this.#pass.pending;
81
+ }
76
82
  disconnect() {
77
83
  this.#listeners.dispose();
78
84
  this.#pass.cancel();
@@ -126,6 +132,94 @@ var NumberValueReader = class {
126
132
  }
127
133
  };
128
134
 
135
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
136
+ if (element.getAttribute(name) === value) return false;
137
+ if (value === null) element.removeAttribute(name);
138
+ else element.setAttribute(name, value);
139
+ return true;
140
+ }
141
+
142
+ var OwnedText = class {
143
+ #attribute;
144
+ #channel;
145
+ #guard;
146
+ #writes = /* @__PURE__ */ new WeakMap();
147
+ constructor(attribute, channel = "text", guard = () => true) {
148
+ this.#attribute = attribute;
149
+ this.#channel = channel;
150
+ this.#guard = guard;
151
+ }
152
+ hasRecord(element) {
153
+ return element.hasAttribute(this.#attribute);
154
+ }
155
+ read(element, evidence = {}) {
156
+ if (evidence.source === "live") return this.#writes.get(element) ?? null;
157
+ const raw = element.getAttribute(this.#attribute);
158
+ if (raw === null) return null;
159
+ try {
160
+ const record = JSON.parse(raw);
161
+ if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
162
+ return null;
163
+ return { original: record[1], written: record[2] };
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ owns(element, evidence = {}) {
169
+ return this.#ownedRecord(element, evidence) !== null;
170
+ }
171
+ #ownedRecord(element, evidence) {
172
+ const record = this.read(element, evidence);
173
+ if (record === null || !this.#guard(element)) return null;
174
+ if (this.#channel === "text" && element.firstElementChild !== null) return null;
175
+ const current = this.#current(element);
176
+ return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
177
+ }
178
+ write(element, written, original, force = false) {
179
+ const current = this.#current(element);
180
+ if (current === null) return;
181
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
182
+ if (force || current !== written) this.#put(element, written);
183
+ this.remember(element, written, before);
184
+ }
185
+ remember(element, written, original) {
186
+ const current = this.#current(element);
187
+ if (current === null) return;
188
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
189
+ this.#writes.set(element, { original: before, written });
190
+ setAttributeIfChanged(
191
+ element,
192
+ this.#attribute,
193
+ JSON.stringify([this.#channel, before, written])
194
+ );
195
+ }
196
+ clear(element, evidence = {}) {
197
+ if (!this.owns(element, evidence)) return false;
198
+ if (this.#current(element) !== "") this.#put(element, "");
199
+ this.forget(element);
200
+ return true;
201
+ }
202
+ restore(element, evidence = {}) {
203
+ const record = this.#ownedRecord(element, evidence);
204
+ if (record === null) return false;
205
+ if (this.#current(element) !== record.original) this.#put(element, record.original);
206
+ this.forget(element);
207
+ return true;
208
+ }
209
+ forget(element) {
210
+ setAttributeIfChanged(element, this.#attribute, null);
211
+ this.#writes.delete(element);
212
+ }
213
+ #current(element) {
214
+ if (this.#channel === "text") return element.textContent ?? "";
215
+ return element instanceof HTMLInputElement ? element.value : null;
216
+ }
217
+ #put(element, text) {
218
+ if (this.#channel === "text") element.textContent = text;
219
+ else if (element instanceof HTMLInputElement) element.value = text;
220
+ }
221
+ };
222
+
129
223
  var TimerRegistry = class {
130
224
  ids = /* @__PURE__ */ new Set();
131
225
  clear(id) {
@@ -181,17 +275,14 @@ var ClipboardController = class _ClipboardController extends Controller {
181
275
  #connected = false;
182
276
  #result = null;
183
277
  #feedback = null;
184
- #written = /* @__PURE__ */ new Map();
278
+ #feedbackText = new OwnedText(`data-${this.identifier}-feedback-text`);
279
+ #written = /* @__PURE__ */ new Set();
185
280
  #morphRender = new MorphRenderWatcher(() => this.#renderResult());
186
281
  connect() {
187
282
  this.#connected = true;
188
283
  this.#adopt();
189
284
  this.#result = null;
190
- this.#feedback = this.hasFeedbackTarget ? {
191
- element: this.feedbackTarget,
192
- original: this.feedbackTarget.textContent ?? "",
193
- text: this.feedbackTarget.textContent ?? ""
194
- } : null;
285
+ this.#feedback = this.hasFeedbackTarget ? { element: this.feedbackTarget, original: this.feedbackTarget.textContent ?? "" } : null;
195
286
  this.#morphRender.observe(this.element);
196
287
  }
197
288
  disconnect() {
@@ -230,9 +321,8 @@ var ClipboardController = class _ClipboardController extends Controller {
230
321
  }
231
322
  #adopt() {
232
323
  if (this.#inTransientState()) {
233
- const restored = this.#written.size === 0;
234
324
  this.#reset();
235
- if (restored && this.hasFeedbackTarget) this.feedbackTarget.textContent = "";
325
+ for (const element of this.feedbackTargets) this.#feedbackText.clear(element);
236
326
  return;
237
327
  }
238
328
  setDefaultAttribute(this.element, "data-state", "idle");
@@ -255,17 +345,15 @@ var ClipboardController = class _ClipboardController extends Controller {
255
345
  this.element.setAttribute("data-state", this.#result ? "copied" : "error");
256
346
  if (!this.hasFeedbackTarget) return;
257
347
  const element = this.feedbackTarget;
258
- const held = this.#feedback;
259
- if (!force && (!held || element !== held.element || element.firstElementChild !== null || element.textContent !== held.text && element.textContent !== held.original))
260
- return;
348
+ if (!force && (element !== this.#feedback?.element || !this.#ownsFeedback(element))) return;
261
349
  const text = this.#result ? this.copiedLabelValue : this.errorLabelValue;
262
- this.#feedback = {
263
- element,
264
- original: held?.element === element ? held.original : element.textContent ?? "",
265
- text
266
- };
267
- element.textContent = text;
268
- this.#written.set(element, text);
350
+ const original = element === this.#feedback?.element ? this.#feedback.original : element.textContent ?? "";
351
+ this.#feedback = { element, original };
352
+ this.#feedbackText.write(element, text, original, force);
353
+ this.#written.add(element);
354
+ }
355
+ #ownsFeedback(element) {
356
+ return this.#feedbackText.owns(element) || !this.#feedbackText.hasRecord(element) && this.#feedbackText.owns(element, { source: "live", allowOriginal: true });
269
357
  }
270
358
  #announceResult(success) {
271
359
  announce(success ? this.announceCopiedTextValue : this.announceErrorTextValue);
@@ -279,19 +367,22 @@ var ClipboardController = class _ClipboardController extends Controller {
279
367
  #followLabel(state, label, previous) {
280
368
  if (!this.#connected) return;
281
369
  if (this.element.getAttribute("data-state") !== state || !this.hasFeedbackTarget) return;
282
- if (this.feedbackTarget.textContent !== previous) return;
283
- this.feedbackTarget.textContent = label;
284
- this.#written.set(this.feedbackTarget, label);
285
- if (this.#feedback?.element === this.feedbackTarget) this.#feedback.text = label;
370
+ const element = this.feedbackTarget;
371
+ if (element !== this.#feedback?.element || element.textContent !== previous || !this.#ownsFeedback(element))
372
+ return;
373
+ this.#feedbackText.write(element, label);
374
+ this.#written.add(element);
286
375
  }
287
376
  #reset() {
288
377
  this.#result = null;
289
378
  this.element.setAttribute("data-state", "idle");
290
- for (const [element, text] of this.#written) {
291
- if (!this.feedbackTargets.includes(element)) continue;
292
- if (element.firstElementChild === null && element.textContent === text) {
293
- element.textContent = "";
294
- }
379
+ for (const element of this.#written) {
380
+ if (this.feedbackTargets.includes(element))
381
+ this.#feedbackText.clear(
382
+ element,
383
+ this.#feedbackText.hasRecord(element) ? {} : { source: "live" }
384
+ );
385
+ this.#feedbackText.forget(element);
295
386
  }
296
387
  this.#written.clear();
297
388
  }
@@ -1,15 +1,26 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
4
+ if (element.getAttribute(name) === value) return false;
5
+ if (value === null) element.removeAttribute(name);
6
+ else element.setAttribute(name, value);
7
+ return true;
8
+ }
9
+
3
10
  function sharedRegistry(key, create) {
4
11
  const symbol = Symbol.for(key);
5
12
  const scope = globalThis;
6
13
  const existing = scope[symbol];
7
14
  if (existing !== void 0) return existing;
8
- const registry = create();
9
- Object.defineProperty(globalThis, symbol, { value: registry });
10
- return registry;
15
+ const registry2 = create();
16
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
17
+ return registry2;
11
18
  }
12
19
 
20
+ var registry = sharedRegistry(
21
+ "stimeo-ui.attribute-lease.registry.v1",
22
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
23
+ );
13
24
  function isAttributeValue(value) {
14
25
  return value === null || typeof value === "string";
15
26
  }
@@ -26,24 +37,46 @@ function readLeaseRecord(element, record) {
26
37
  return void 0;
27
38
  }
28
39
  }
29
- var AttributeLease = class _AttributeLease {
30
- static #registry = sharedRegistry(
31
- "stimeo-ui.attribute-lease.registry.v1",
32
- () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
33
- );
40
+ function attributeStack(element, attribute) {
41
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
42
+ registry.stacks.set(element, byAttribute);
43
+ const known = byAttribute.get(attribute);
44
+ if (known) return known;
45
+ const found = [];
46
+ for (const record of registry.names.get(attribute) ?? []) {
47
+ const read = readLeaseRecord(element, record);
48
+ if (read) found.push({ ...read, record });
49
+ }
50
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
51
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
52
+ lease: null,
53
+ record,
54
+ written,
55
+ original,
56
+ linked: linked && index > 0
57
+ }));
58
+ byAttribute.set(attribute, stack);
59
+ return stack;
60
+ }
61
+ function findHold(stack, lease, record) {
62
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
63
+ }
64
+ var AttributeLease = class {
34
65
  #attribute;
35
66
  #record;
36
67
  #base;
68
+ #preserveAuthor;
37
69
  #held = /* @__PURE__ */ new Set();
38
70
  constructor(attribute, owner, options = {}) {
39
71
  this.#attribute = attribute;
40
72
  this.#record = `data-${owner}-${attribute}-lease`;
41
73
  this.#base = options.base === true;
42
- const names = _AttributeLease.#registry.names;
74
+ this.#preserveAuthor = options.preserveAuthor === true;
75
+ const names = registry.names;
43
76
  names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
44
77
  }
45
78
  write(element, value) {
46
- const stack = this.#stack(element);
79
+ const stack = attributeStack(element, this.#attribute);
47
80
  const hold = this.#take(stack) ?? this.#acquire(element, stack);
48
81
  this.#held.add(element);
49
82
  const shows = !this.#covered(element, stack, hold);
@@ -53,8 +86,8 @@ var AttributeLease = class _AttributeLease {
53
86
  }
54
87
  return(element) {
55
88
  const held = this.#held.delete(element);
56
- const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
57
- const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
89
+ const known = registry.stacks.get(element)?.has(this.#attribute);
90
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
58
91
  const hold = this.#take(stack);
59
92
  if (!hold) return false;
60
93
  const carries = element.getAttribute(this.#attribute) === hold.written;
@@ -72,31 +105,8 @@ var AttributeLease = class _AttributeLease {
72
105
  returnAll() {
73
106
  for (const element of Array.from(this.#held)) this.return(element);
74
107
  }
75
- #stack(element) {
76
- const stacks = _AttributeLease.#registry.stacks;
77
- const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
78
- stacks.set(element, byAttribute);
79
- const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
80
- byAttribute.set(this.#attribute, stack);
81
- return stack;
82
- }
83
- #seed(element) {
84
- const found = [];
85
- for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
86
- const read = readLeaseRecord(element, record);
87
- if (read) found.push({ ...read, record });
88
- }
89
- const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
90
- return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
91
- lease: null,
92
- record,
93
- written,
94
- original,
95
- linked: linked && index > 0
96
- }));
97
- }
98
108
  #take(stack) {
99
- const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
109
+ const hold = findHold(stack, this, this.#record);
100
110
  if (hold) hold.lease = this;
101
111
  return hold;
102
112
  }
@@ -105,7 +115,7 @@ var AttributeLease = class _AttributeLease {
105
115
  const top = stack.at(-1);
106
116
  const bottom = stack[0];
107
117
  const over = top?.written === current ? top : void 0;
108
- const from = this.#base ? bottom : over;
118
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
109
119
  const hold = {
110
120
  lease: this,
111
121
  record: this.#record,
@@ -135,15 +145,11 @@ var AttributeLease = class _AttributeLease {
135
145
  );
136
146
  });
137
147
  for (const [name, recorded] of records) {
138
- if (element.getAttribute(name) === recorded) continue;
139
- if (recorded === null) element.removeAttribute(name);
140
- else element.setAttribute(name, recorded);
148
+ setAttributeIfChanged(element, name, recorded);
141
149
  }
142
150
  }
143
151
  #reflect(element, value) {
144
- if (element.getAttribute(this.#attribute) === value) return;
145
- if (value === null) element.removeAttribute(this.#attribute);
146
- else element.setAttribute(this.#attribute, value);
152
+ setAttributeIfChanged(element, this.#attribute, value);
147
153
  }
148
154
  };
149
155
 
@@ -200,9 +206,8 @@ var StateRegions = class _StateRegions {
200
206
  const below = hold.linked ? stack[index - 1] : void 0;
201
207
  if (below) {
202
208
  if (region.hidden !== below.written) region.hidden = below.written;
203
- } else if (region.getAttribute("hidden") !== hold.authored) {
204
- if (hold.authored === null) region.removeAttribute("hidden");
205
- else region.setAttribute("hidden", hold.authored);
209
+ } else {
210
+ setAttributeIfChanged(region, "hidden", hold.authored);
206
211
  }
207
212
  }
208
213
  this.#mark(region, stack);
@@ -277,9 +282,7 @@ var StateRegions = class _StateRegions {
277
282
  );
278
283
  });
279
284
  for (const [name, recorded] of records) {
280
- if (region.getAttribute(name) === recorded) continue;
281
- if (recorded === null) region.removeAttribute(name);
282
- else region.setAttribute(name, recorded);
285
+ setAttributeIfChanged(region, name, recorded);
283
286
  }
284
287
  }
285
288
  #recorded(region, name) {
@@ -13,6 +13,16 @@ function isReservedArrowChord(event, allow = []) {
13
13
  if (!event.key.startsWith("Arrow")) return false;
14
14
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
15
15
  }
16
+ function hasModifierChord(event) {
17
+ return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
18
+ }
19
+
20
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
21
+ if (element.getAttribute(name) === value) return false;
22
+ if (value === null) element.removeAttribute(name);
23
+ else element.setAttribute(name, value);
24
+ return true;
25
+ }
16
26
 
17
27
  function toFiniteNumber(raw) {
18
28
  if (typeof raw !== "number" && typeof raw !== "string") return null;
@@ -65,6 +75,9 @@ var MicrotaskCoalescer = class {
65
75
  this.#queued = false;
66
76
  this.#generation += 1;
67
77
  }
78
+ get pending() {
79
+ return this.#queued;
80
+ }
68
81
  schedule() {
69
82
  if (!this.#active || this.#queued) return;
70
83
  this.#queued = true;
@@ -95,6 +108,9 @@ var MorphRenderWatcher = class {
95
108
  schedule() {
96
109
  this.#pass.schedule();
97
110
  }
111
+ get pending() {
112
+ return this.#pass.pending;
113
+ }
98
114
  disconnect() {
99
115
  this.#listeners.dispose();
100
116
  this.#pass.cancel();
@@ -156,6 +172,13 @@ var OwnedPointerSession = class {
156
172
  };
157
173
  };
158
174
 
175
+ function fillTemplate(template, values) {
176
+ return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
177
+ const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
178
+ return replacement === void 0 ? match : String(replacement);
179
+ });
180
+ }
181
+
159
182
  var COLOR_PROPERTY = "--stimeo--color";
160
183
  var VALUE_TEXT_ATTRIBUTE = "data-value-text";
161
184
  var CHANNEL_RANGE = {
@@ -179,9 +202,6 @@ var ColorPickerController = class extends Controller {
179
202
  };
180
203
  static actions = ["onHexInput", "onKeydown", "onPointerDown"];
181
204
  static events = ["change", "reconcile"];
182
- get #mirrored() {
183
- return this.logicalTrackValue && isRtl(this.element);
184
- }
185
205
  #color = { hue: 0, saturation: 0, lightness: 0, alpha: 100 };
186
206
  #drag = null;
187
207
  #committedHex = null;
@@ -225,6 +245,8 @@ var ColorPickerController = class extends Controller {
225
245
  }
226
246
  onKeydown(event) {
227
247
  if (isReservedArrowChord(event)) return;
248
+ if ((event.key === "Home" || event.key === "End" || event.key === "PageUp" || event.key === "PageDown") && hasModifierChord(event))
249
+ return;
228
250
  const slider = event.currentTarget;
229
251
  const channel = this.#editableChannel(slider);
230
252
  if (!channel) return;
@@ -232,7 +254,7 @@ var ColorPickerController = class extends Controller {
232
254
  const [min, max] = this.#rangeOf(slider, channel);
233
255
  const value = this.#color[channel];
234
256
  let next = null;
235
- switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {
257
+ switch (this.logicalTrackValue ? logicalArrowKey(event.key, slider) : event.key) {
236
258
  case "ArrowRight":
237
259
  case "ArrowUp":
238
260
  next = value + 1;
@@ -265,7 +287,7 @@ var ColorPickerController = class extends Controller {
265
287
  const channel = this.#editableChannel(slider);
266
288
  if (!channel) return;
267
289
  const [min, max] = this.#rangeOf(slider, channel);
268
- const mirrored = this.#mirrored;
290
+ const mirrored = this.logicalTrackValue && isRtl(slider);
269
291
  const update = (clientX) => {
270
292
  const rect = slider.getBoundingClientRect();
271
293
  if (rect.width === 0) return false;
@@ -380,7 +402,7 @@ var ColorPickerController = class extends Controller {
380
402
  "aria-valuetext": this.#valueText(slider, channel, value)
381
403
  };
382
404
  for (const [name, next] of Object.entries(attributes)) {
383
- if (slider.getAttribute(name) !== next) slider.setAttribute(name, next);
405
+ setAttributeIfChanged(slider, name, next);
384
406
  }
385
407
  }
386
408
  #writeHex(input, hex) {
@@ -439,7 +461,7 @@ var ColorPickerController = class extends Controller {
439
461
  }
440
462
  #valueText(slider, channel, value) {
441
463
  const template = slider.getAttribute(VALUE_TEXT_ATTRIBUTE);
442
- return template ? template.replaceAll("{value}", String(value)) : defaultValueText(channel, value);
464
+ return template ? fillTemplate(template, { value }) : defaultValueText(channel, value);
443
465
  }
444
466
  #endDrag() {
445
467
  const drag = this.#drag;