stimeo-ui 0.17.0 → 0.18.1

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 +136 -2
  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 +290 -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 +66 -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 +200 -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 +434 -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 +272 -95
  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 +255 -24
  67. data/dist/controllers/popover_controller.js +91 -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 +2515 -1315
  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
@@ -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
 
@@ -252,6 +258,9 @@ var MicrotaskCoalescer = class {
252
258
  this.#queued = false;
253
259
  this.#generation += 1;
254
260
  }
261
+ get pending() {
262
+ return this.#queued;
263
+ }
255
264
  schedule() {
256
265
  if (!this.#active || this.#queued) return;
257
266
  this.#queued = true;
@@ -282,6 +291,9 @@ var MorphRenderWatcher = class {
282
291
  schedule() {
283
292
  this.#pass.schedule();
284
293
  }
294
+ get pending() {
295
+ return this.#pass.pending;
296
+ }
285
297
  disconnect() {
286
298
  this.#listeners.dispose();
287
299
  this.#pass.cancel();
@@ -347,6 +359,11 @@ function resolveScrollSource(selector) {
347
359
  function scrollOffset(source) {
348
360
  return source === window ? window.scrollY ?? window.pageYOffset ?? 0 : source.scrollTop;
349
361
  }
362
+ function deepActiveElement(ownerDocument) {
363
+ let active = ownerDocument.activeElement;
364
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
365
+ return active;
366
+ }
350
367
 
351
368
  function queryOpenDescendants(root, selector) {
352
369
  const matches = [];
@@ -364,6 +381,9 @@ function queryOpenDescendants(root, selector) {
364
381
  return matches;
365
382
  }
366
383
 
384
+ function holdsFocus(element) {
385
+ return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
386
+ }
367
387
  var TabindexLoan = class _TabindexLoan {
368
388
  static #registry = sharedRegistry(
369
389
  "stimeo-ui.tabindex-loan.registry.v1",
@@ -371,19 +391,22 @@ var TabindexLoan = class _TabindexLoan {
371
391
  );
372
392
  #value;
373
393
  #record;
394
+ #preserveFocus;
374
395
  #lent = /* @__PURE__ */ new Set();
375
- constructor(value, owner) {
396
+ constructor(value, owner, options = {}) {
376
397
  this.#value = value;
377
398
  this.#record = `data-${owner}-tabindex-loan`;
399
+ this.#preserveFocus = options.preserveFocus === true;
378
400
  _TabindexLoan.#registry.names.add(this.#record);
379
401
  }
380
- lend(element) {
402
+ lend(element, options = {}) {
381
403
  const stack = this.#stack(element);
382
404
  const current = element.getAttribute("tabindex");
383
405
  const held = stack.find((hold) => hold.loan === this) ?? stack.find(
384
406
  (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
385
407
  );
386
408
  const linked = stack.at(-1)?.value === current;
409
+ if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
387
410
  if (!held && current !== null && !linked) return;
388
411
  this.#lent.add(element);
389
412
  if (held) held.loan = this;
@@ -393,19 +416,28 @@ var TabindexLoan = class _TabindexLoan {
393
416
  element.setAttribute("tabindex", this.#value);
394
417
  }
395
418
  }
396
- returnAll() {
397
- for (const element of this.#lent) {
398
- const stack = this.#stack(element);
399
- const hold = stack.find((held) => held.loan === this);
400
- if (hold) this.#release(element, stack, hold);
419
+ return(element) {
420
+ const stack = this.#stack(element);
421
+ const hold = stack.find((held) => held.loan === this) ?? stack.find(
422
+ (held) => held.loan === null && held.record === this.#record && held.value === this.#value
423
+ );
424
+ if (!hold) return false;
425
+ if (!this.#release(element, stack, hold, this.#preserveFocus)) {
426
+ hold.loan = this;
427
+ this.#lent.add(element);
428
+ return false;
401
429
  }
402
- this.#lent.clear();
430
+ this.#lent.delete(element);
431
+ return true;
432
+ }
433
+ returnAll() {
434
+ for (const element of Array.from(this.#lent)) this.return(element);
403
435
  }
404
436
  reclaim(element) {
405
437
  if (element.getAttribute(this.#record) !== this.#value) return;
406
438
  const stack = this.#stack(element);
407
439
  const hold = stack.find((held) => held.loan === null && held.record === this.#record);
408
- if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
440
+ if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
409
441
  }
410
442
  reclaimWithin(root) {
411
443
  for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
@@ -429,26 +461,26 @@ var TabindexLoan = class _TabindexLoan {
429
461
  for (const [index, hold] of holds.entries()) hold.linked = index > 0;
430
462
  return holds;
431
463
  }
432
- #release(element, stack, hold) {
464
+ #release(element, stack, hold, preserveFocus = false) {
433
465
  const current = element.getAttribute("tabindex");
434
466
  const index = stack.indexOf(hold);
435
467
  const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
468
+ if (owns && preserveFocus && holdsFocus(element)) return false;
436
469
  stack.splice(index, 1);
437
470
  const above = stack[index];
438
471
  if (above) above.linked &&= hold.linked;
439
472
  this.#mark(element, stack);
440
- if (!owns) return;
473
+ if (!owns) return true;
441
474
  const below = hold.linked ? stack[index - 1] : void 0;
442
475
  if (!below) element.removeAttribute("tabindex");
443
476
  else if (below.value !== current) element.setAttribute("tabindex", below.value);
477
+ return true;
444
478
  }
445
479
  #mark(element, stack) {
446
480
  const records = /* @__PURE__ */ new Map([[this.#record, null]]);
447
481
  for (const hold of stack) records.set(hold.record, hold.value);
448
482
  for (const [name, recorded] of records) {
449
- if (element.getAttribute(name) === recorded) continue;
450
- if (recorded === null) element.removeAttribute(name);
451
- else element.setAttribute(name, recorded);
483
+ setAttributeIfChanged(element, name, recorded);
452
484
  }
453
485
  }
454
486
  };
@@ -472,6 +504,7 @@ var ScrollVisibilityController = class _ScrollVisibilityController extends Contr
472
504
  #lastScrollY = 0;
473
505
  #visible = null;
474
506
  #scrollSource = window;
507
+ #scrollListeners = new ListenerSet();
475
508
  #connected = false;
476
509
  #offset = DEFAULT_OFFSET;
477
510
  #rootSelector = "";
@@ -496,7 +529,7 @@ var ScrollVisibilityController = class _ScrollVisibilityController extends Contr
496
529
  disconnect() {
497
530
  this.#morphRender.disconnect();
498
531
  this.#connected = false;
499
- this.#scrollSource.removeEventListener("scroll", this.#onScroll);
532
+ this.#scrollListeners.dispose();
500
533
  this.#frames.cancel();
501
534
  this.#pendingHide.releaseAll();
502
535
  this.#tabindex.returnAll();
@@ -540,13 +573,13 @@ var ScrollVisibilityController = class _ScrollVisibilityController extends Contr
540
573
  #followSource() {
541
574
  const source = resolveScrollSource(this.#rootSelector);
542
575
  if (source === this.#scrollSource) return;
543
- this.#scrollSource.removeEventListener("scroll", this.#onScroll);
576
+ this.#scrollListeners.dispose();
544
577
  this.#listenTo(source);
545
578
  }
546
579
  #listenTo(source) {
547
580
  this.#scrollSource = source;
548
581
  this.#lastScrollY = scrollOffset(source);
549
- source.addEventListener("scroll", this.#onScroll, { passive: true });
582
+ this.#scrollListeners.add(source, "scroll", this.#onScroll, { passive: true });
550
583
  }
551
584
  #evaluate(notify = true) {
552
585
  const y = scrollOffset(this.#scrollSource);
@@ -157,6 +157,9 @@ var MicrotaskCoalescer = class {
157
157
  this.#queued = false;
158
158
  this.#generation += 1;
159
159
  }
160
+ get pending() {
161
+ return this.#queued;
162
+ }
160
163
  schedule() {
161
164
  if (!this.#active || this.#queued) return;
162
165
  this.#queued = true;
@@ -187,6 +190,9 @@ var MorphRenderWatcher = class {
187
190
  schedule() {
188
191
  this.#pass.schedule();
189
192
  }
193
+ get pending() {
194
+ return this.#pass.pending;
195
+ }
190
196
  disconnect() {
191
197
  this.#listeners.dispose();
192
198
  this.#pass.cancel();
@@ -277,7 +283,7 @@ var ScrollspyController = class _ScrollspyController extends Controller {
277
283
  #intersectionStates = /* @__PURE__ */ new Map();
278
284
  #activeSectionId = "";
279
285
  #rootElement = null;
280
- #scrollSource = null;
286
+ #scrollListeners = new ListenerSet();
281
287
  #frames = new FrameCoalescer();
282
288
  #rootSelector = "";
283
289
  #anchorObserver = null;
@@ -389,12 +395,12 @@ var ScrollspyController = class _ScrollspyController extends Controller {
389
395
  #onScroll = () => this.#frames.schedule(() => this.#evaluateActiveSection());
390
396
  #syncScrollListener() {
391
397
  this.#detachScrollListener();
392
- this.#scrollSource = this.#rootElement ?? window;
393
- this.#scrollSource.addEventListener("scroll", this.#onScroll, { passive: true });
398
+ this.#scrollListeners.add(this.#rootElement ?? window, "scroll", this.#onScroll, {
399
+ passive: true
400
+ });
394
401
  }
395
402
  #detachScrollListener() {
396
- this.#scrollSource?.removeEventListener("scroll", this.#onScroll);
397
- this.#scrollSource = null;
403
+ this.#scrollListeners.dispose();
398
404
  }
399
405
  #initializeObserver(retainStates = false) {
400
406
  this.#watcher.stop();
@@ -4,17 +4,31 @@ function isReservedArrowChord(event, allow = []) {
4
4
  if (!event.key.startsWith("Arrow")) return false;
5
5
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
6
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
+ }
7
17
 
8
18
  function sharedRegistry(key, create) {
9
19
  const symbol = Symbol.for(key);
10
20
  const scope = globalThis;
11
21
  const existing = scope[symbol];
12
22
  if (existing !== void 0) return existing;
13
- const registry = create();
14
- Object.defineProperty(globalThis, symbol, { value: registry });
15
- return registry;
23
+ const registry2 = create();
24
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
25
+ return registry2;
16
26
  }
17
27
 
28
+ var registry = sharedRegistry(
29
+ "stimeo-ui.attribute-lease.registry.v1",
30
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
31
+ );
18
32
  function isAttributeValue(value) {
19
33
  return value === null || typeof value === "string";
20
34
  }
@@ -31,24 +45,46 @@ function readLeaseRecord(element, record) {
31
45
  return void 0;
32
46
  }
33
47
  }
34
- var AttributeLease = class _AttributeLease {
35
- static #registry = sharedRegistry(
36
- "stimeo-ui.attribute-lease.registry.v1",
37
- () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
38
- );
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
+ }
72
+ var AttributeLease = class {
39
73
  #attribute;
40
74
  #record;
41
75
  #base;
76
+ #preserveAuthor;
42
77
  #held = /* @__PURE__ */ new Set();
43
78
  constructor(attribute, owner, options = {}) {
44
79
  this.#attribute = attribute;
45
80
  this.#record = `data-${owner}-${attribute}-lease`;
46
81
  this.#base = options.base === true;
47
- const names = _AttributeLease.#registry.names;
82
+ this.#preserveAuthor = options.preserveAuthor === true;
83
+ const names = registry.names;
48
84
  names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
49
85
  }
50
86
  write(element, value) {
51
- const stack = this.#stack(element);
87
+ const stack = attributeStack(element, this.#attribute);
52
88
  const hold = this.#take(stack) ?? this.#acquire(element, stack);
53
89
  this.#held.add(element);
54
90
  const shows = !this.#covered(element, stack, hold);
@@ -58,8 +94,8 @@ var AttributeLease = class _AttributeLease {
58
94
  }
59
95
  return(element) {
60
96
  const held = this.#held.delete(element);
61
- const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
62
- const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
97
+ const known = registry.stacks.get(element)?.has(this.#attribute);
98
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
63
99
  const hold = this.#take(stack);
64
100
  if (!hold) return false;
65
101
  const carries = element.getAttribute(this.#attribute) === hold.written;
@@ -77,31 +113,8 @@ var AttributeLease = class _AttributeLease {
77
113
  returnAll() {
78
114
  for (const element of Array.from(this.#held)) this.return(element);
79
115
  }
80
- #stack(element) {
81
- const stacks = _AttributeLease.#registry.stacks;
82
- const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
83
- stacks.set(element, byAttribute);
84
- const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
85
- byAttribute.set(this.#attribute, stack);
86
- return stack;
87
- }
88
- #seed(element) {
89
- const found = [];
90
- for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
91
- const read = readLeaseRecord(element, record);
92
- if (read) found.push({ ...read, record });
93
- }
94
- const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
95
- return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
96
- lease: null,
97
- record,
98
- written,
99
- original,
100
- linked: linked && index > 0
101
- }));
102
- }
103
116
  #take(stack) {
104
- const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
117
+ const hold = findHold(stack, this, this.#record);
105
118
  if (hold) hold.lease = this;
106
119
  return hold;
107
120
  }
@@ -110,7 +123,7 @@ var AttributeLease = class _AttributeLease {
110
123
  const top = stack.at(-1);
111
124
  const bottom = stack[0];
112
125
  const over = top?.written === current ? top : void 0;
113
- const from = this.#base ? bottom : over;
126
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
114
127
  const hold = {
115
128
  lease: this,
116
129
  record: this.#record,
@@ -140,15 +153,11 @@ var AttributeLease = class _AttributeLease {
140
153
  );
141
154
  });
142
155
  for (const [name, recorded] of records) {
143
- if (element.getAttribute(name) === recorded) continue;
144
- if (recorded === null) element.removeAttribute(name);
145
- else element.setAttribute(name, recorded);
156
+ setAttributeIfChanged(element, name, recorded);
146
157
  }
147
158
  }
148
159
  #reflect(element, value) {
149
- if (element.getAttribute(this.#attribute) === value) return;
150
- if (value === null) element.removeAttribute(this.#attribute);
151
- else element.setAttribute(this.#attribute, value);
160
+ setAttributeIfChanged(element, this.#attribute, value);
152
161
  }
153
162
  };
154
163
 
@@ -179,6 +188,9 @@ var MicrotaskCoalescer = class {
179
188
  this.#queued = false;
180
189
  this.#generation += 1;
181
190
  }
191
+ get pending() {
192
+ return this.#queued;
193
+ }
182
194
  schedule() {
183
195
  if (!this.#active || this.#queued) return;
184
196
  this.#queued = true;
@@ -209,6 +221,9 @@ var MorphRenderWatcher = class {
209
221
  schedule() {
210
222
  this.#pass.schedule();
211
223
  }
224
+ get pending() {
225
+ return this.#pass.pending;
226
+ }
212
227
  disconnect() {
213
228
  this.#listeners.dispose();
214
229
  this.#pass.cancel();
@@ -424,6 +439,7 @@ var SeparatorController = class _SeparatorController extends Controller {
424
439
  }
425
440
  onKeydown(event) {
426
441
  if (isReservedArrowChord(event) || !this.focusableValue) return;
442
+ if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
427
443
  const horizontal = this.#effectiveOrientation === "horizontal";
428
444
  const range = this.#effectiveRange;
429
445
  const declared = this.#safeValue;