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
@@ -1,5 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
+ function fillTemplate(template, values) {
4
+ return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
5
+ const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
6
+ return replacement === void 0 ? match : String(replacement);
7
+ });
8
+ }
9
+
3
10
  function announce(message, options = {}) {
4
11
  const text = message.trim();
5
12
  if (text.length === 0) return;
@@ -9,11 +16,12 @@ function announce(message, options = {}) {
9
16
  })
10
17
  );
11
18
  }
12
- function fillTemplate(template, values) {
13
- return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
14
- const replacement = values[name];
15
- return replacement === void 0 ? match : String(replacement);
16
- });
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;
17
25
  }
18
26
 
19
27
  function sharedRegistry(key, create) {
@@ -21,11 +29,15 @@ function sharedRegistry(key, create) {
21
29
  const scope = globalThis;
22
30
  const existing = scope[symbol];
23
31
  if (existing !== void 0) return existing;
24
- const registry = create();
25
- Object.defineProperty(globalThis, symbol, { value: registry });
26
- return registry;
32
+ const registry2 = create();
33
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
34
+ return registry2;
27
35
  }
28
36
 
37
+ var registry = sharedRegistry(
38
+ "stimeo-ui.attribute-lease.registry.v1",
39
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
40
+ );
29
41
  function isAttributeValue(value) {
30
42
  return value === null || typeof value === "string";
31
43
  }
@@ -42,24 +54,46 @@ function readLeaseRecord(element, record) {
42
54
  return void 0;
43
55
  }
44
56
  }
45
- var AttributeLease = class _AttributeLease {
46
- static #registry = sharedRegistry(
47
- "stimeo-ui.attribute-lease.registry.v1",
48
- () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
49
- );
57
+ function attributeStack(element, attribute) {
58
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
59
+ registry.stacks.set(element, byAttribute);
60
+ const known = byAttribute.get(attribute);
61
+ if (known) return known;
62
+ const found = [];
63
+ for (const record of registry.names.get(attribute) ?? []) {
64
+ const read = readLeaseRecord(element, record);
65
+ if (read) found.push({ ...read, record });
66
+ }
67
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
68
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
69
+ lease: null,
70
+ record,
71
+ written,
72
+ original,
73
+ linked: linked && index > 0
74
+ }));
75
+ byAttribute.set(attribute, stack);
76
+ return stack;
77
+ }
78
+ function findHold(stack, lease, record) {
79
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
80
+ }
81
+ var AttributeLease = class {
50
82
  #attribute;
51
83
  #record;
52
84
  #base;
85
+ #preserveAuthor;
53
86
  #held = /* @__PURE__ */ new Set();
54
87
  constructor(attribute, owner, options = {}) {
55
88
  this.#attribute = attribute;
56
89
  this.#record = `data-${owner}-${attribute}-lease`;
57
90
  this.#base = options.base === true;
58
- const names = _AttributeLease.#registry.names;
91
+ this.#preserveAuthor = options.preserveAuthor === true;
92
+ const names = registry.names;
59
93
  names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
60
94
  }
61
95
  write(element, value) {
62
- const stack = this.#stack(element);
96
+ const stack = attributeStack(element, this.#attribute);
63
97
  const hold = this.#take(stack) ?? this.#acquire(element, stack);
64
98
  this.#held.add(element);
65
99
  const shows = !this.#covered(element, stack, hold);
@@ -69,8 +103,8 @@ var AttributeLease = class _AttributeLease {
69
103
  }
70
104
  return(element) {
71
105
  const held = this.#held.delete(element);
72
- const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
73
- const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
106
+ const known = registry.stacks.get(element)?.has(this.#attribute);
107
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
74
108
  const hold = this.#take(stack);
75
109
  if (!hold) return false;
76
110
  const carries = element.getAttribute(this.#attribute) === hold.written;
@@ -88,31 +122,8 @@ var AttributeLease = class _AttributeLease {
88
122
  returnAll() {
89
123
  for (const element of Array.from(this.#held)) this.return(element);
90
124
  }
91
- #stack(element) {
92
- const stacks = _AttributeLease.#registry.stacks;
93
- const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
94
- stacks.set(element, byAttribute);
95
- const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
96
- byAttribute.set(this.#attribute, stack);
97
- return stack;
98
- }
99
- #seed(element) {
100
- const found = [];
101
- for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
102
- const read = readLeaseRecord(element, record);
103
- if (read) found.push({ ...read, record });
104
- }
105
- const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
106
- return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
107
- lease: null,
108
- record,
109
- written,
110
- original,
111
- linked: linked && index > 0
112
- }));
113
- }
114
125
  #take(stack) {
115
- const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
126
+ const hold = findHold(stack, this, this.#record);
116
127
  if (hold) hold.lease = this;
117
128
  return hold;
118
129
  }
@@ -121,7 +132,7 @@ var AttributeLease = class _AttributeLease {
121
132
  const top = stack.at(-1);
122
133
  const bottom = stack[0];
123
134
  const over = top?.written === current ? top : void 0;
124
- const from = this.#base ? bottom : over;
135
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
125
136
  const hold = {
126
137
  lease: this,
127
138
  record: this.#record,
@@ -151,15 +162,11 @@ var AttributeLease = class _AttributeLease {
151
162
  );
152
163
  });
153
164
  for (const [name, recorded] of records) {
154
- if (element.getAttribute(name) === recorded) continue;
155
- if (recorded === null) element.removeAttribute(name);
156
- else element.setAttribute(name, recorded);
165
+ setAttributeIfChanged(element, name, recorded);
157
166
  }
158
167
  }
159
168
  #reflect(element, value) {
160
- if (element.getAttribute(this.#attribute) === value) return;
161
- if (value === null) element.removeAttribute(this.#attribute);
162
- else element.setAttribute(this.#attribute, value);
169
+ setAttributeIfChanged(element, this.#attribute, value);
163
170
  }
164
171
  };
165
172
 
@@ -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 writeField(field, value) {
18
28
  if (field.value === value) return false;
@@ -50,6 +60,9 @@ var MicrotaskCoalescer = class {
50
60
  this.#queued = false;
51
61
  this.#generation += 1;
52
62
  }
63
+ get pending() {
64
+ return this.#queued;
65
+ }
53
66
  schedule() {
54
67
  if (!this.#active || this.#queued) return;
55
68
  this.#queued = true;
@@ -80,6 +93,9 @@ var MorphRenderWatcher = class {
80
93
  schedule() {
81
94
  this.#pass.schedule();
82
95
  }
96
+ get pending() {
97
+ return this.#pass.pending;
98
+ }
83
99
  disconnect() {
84
100
  this.#listeners.dispose();
85
101
  this.#pass.cancel();
@@ -189,7 +205,7 @@ var OwnedPointerSession = class {
189
205
  function rangeFraction(value, min, max) {
190
206
  const span = max - min;
191
207
  if (!(span > 0)) return 0;
192
- const clamped = Math.min(max, Math.max(min, value));
208
+ const clamped = clampRange(value, min, max);
193
209
  let fraction;
194
210
  if (Number.isFinite(span)) {
195
211
  fraction = (clamped - min) / span;
@@ -200,6 +216,9 @@ function rangeFraction(value, min, max) {
200
216
  if (!Number.isFinite(fraction)) return 0;
201
217
  return Math.min(1, Math.max(0, fraction));
202
218
  }
219
+ function clampRange(value, min, max) {
220
+ return Math.min(max, Math.max(min, value));
221
+ }
203
222
 
204
223
  function effectiveStep(step) {
205
224
  return Number.isFinite(step) && step > 0 ? step : 1;
@@ -330,8 +349,8 @@ var SliderController = class _SliderController extends Controller {
330
349
  #drag = null;
331
350
  #settled = 0;
332
351
  #repaint = new MorphRenderWatcher(() => this.#render());
333
- get #mirrored() {
334
- return this.logicalTrackValue && isRtl(this.element);
352
+ get #directionElement() {
353
+ return this.hasTrackTarget ? this.trackTarget : this.element;
335
354
  }
336
355
  connect() {
337
356
  this.#repaint.observe(this.element);
@@ -378,10 +397,12 @@ var SliderController = class _SliderController extends Controller {
378
397
  }
379
398
  onKeydown(event) {
380
399
  if (isReservedArrowChord(event)) return;
400
+ if ((event.key === "Home" || event.key === "End" || event.key === "PageUp" || event.key === "PageDown") && hasModifierChord(event))
401
+ return;
381
402
  let next = null;
382
403
  const declared = this.#safeValue;
383
404
  const range = this.#steppedRange;
384
- switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {
405
+ switch (this.logicalTrackValue ? logicalArrowKey(event.key, this.#directionElement) : event.key) {
385
406
  case "ArrowRight":
386
407
  case "ArrowUp":
387
408
  next = stepSteppedValue(declared, 1, range);
@@ -412,7 +433,7 @@ var SliderController = class _SliderController extends Controller {
412
433
  if (event.button !== 0 || this.#drag || !this.hasTrackTarget || !this.hasThumbTarget) return;
413
434
  const track = this.trackTarget;
414
435
  const thumb = this.thumbTarget;
415
- const mirrored = this.#mirrored;
436
+ const mirrored = this.logicalTrackValue && isRtl(track);
416
437
  const value = this.#valueFromClientX(event.clientX, mirrored, track);
417
438
  if (value === null) return;
418
439
  event.preventDefault();
@@ -479,7 +500,7 @@ var SliderController = class _SliderController extends Controller {
479
500
  "aria-valuenow": String(value)
480
501
  };
481
502
  for (const [name, next] of Object.entries(attributes)) {
482
- if (thumb.getAttribute(name) !== next) thumb.setAttribute(name, next);
503
+ setAttributeIfChanged(thumb, name, next);
483
504
  }
484
505
  }
485
506
  #renderFraction(value) {
@@ -62,6 +62,9 @@ var MicrotaskCoalescer = class {
62
62
  this.#queued = false;
63
63
  this.#generation += 1;
64
64
  }
65
+ get pending() {
66
+ return this.#queued;
67
+ }
65
68
  schedule() {
66
69
  if (!this.#active || this.#queued) return;
67
70
  this.#queued = true;
@@ -92,6 +95,9 @@ var MorphRenderWatcher = class {
92
95
  schedule() {
93
96
  this.#pass.schedule();
94
97
  }
98
+ get pending() {
99
+ return this.#pass.pending;
100
+ }
95
101
  disconnect() {
96
102
  this.#listeners.dispose();
97
103
  this.#pass.cancel();
@@ -170,6 +176,8 @@ var SmartStickyHeaderController = class _SmartStickyHeaderController extends Con
170
176
  static events = ["change"];
171
177
  #frames = new FrameCoalescer();
172
178
  #scrollerEl = window;
179
+ #scrollListeners = new ListenerSet();
180
+ #listeners = new ListenerSet();
173
181
  #containerSelector = "";
174
182
  #lastY = 0;
175
183
  #hidden = null;
@@ -193,25 +201,25 @@ var SmartStickyHeaderController = class _SmartStickyHeaderController extends Con
193
201
  this.#morphRender.observe(this.element);
194
202
  this.#hidden = null;
195
203
  this.#listenTo(resolveScrollSource(this.#containerSelector));
196
- this.element.addEventListener("focusin", this.#onFocusin);
204
+ this.#listeners.add(this.element, "focusin", this.#onFocusin);
197
205
  this.#apply(false, false);
198
206
  }
199
207
  disconnect() {
200
208
  this.#morphRender.disconnect();
201
- this.#scrollerEl.removeEventListener("scroll", this.#onScroll);
202
- this.element.removeEventListener("focusin", this.#onFocusin);
209
+ this.#scrollListeners.dispose();
210
+ this.#listeners.dispose();
203
211
  this.#frames.cancel();
204
212
  }
205
213
  #followSource() {
206
214
  const source = resolveScrollSource(this.#containerSelector);
207
215
  if (source === this.#scrollerEl) return;
208
- this.#scrollerEl.removeEventListener("scroll", this.#onScroll);
216
+ this.#scrollListeners.dispose();
209
217
  this.#listenTo(source);
210
218
  }
211
219
  #listenTo(source) {
212
220
  this.#scrollerEl = source;
213
221
  this.#lastY = scrollOffset(source);
214
- source.addEventListener("scroll", this.#onScroll, { passive: true });
222
+ this.#scrollListeners.add(source, "scroll", this.#onScroll, { passive: true });
215
223
  }
216
224
  #measure() {
217
225
  const y = scrollOffset(this.#scrollerEl);
@@ -1,5 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
+ function fillTemplate(template, values) {
4
+ return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
5
+ const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
6
+ return replacement === void 0 ? match : String(replacement);
7
+ });
8
+ }
9
+
3
10
  function announce(message, options = {}) {
4
11
  const text = message.trim();
5
12
  if (text.length === 0) return;
@@ -9,12 +16,17 @@ function announce(message, options = {}) {
9
16
  })
10
17
  );
11
18
  }
12
- function fillTemplate(template, values) {
13
- return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
14
- const replacement = values[name];
15
- return replacement === void 0 ? match : String(replacement);
16
- });
17
- }
19
+
20
+ var ListenerSet = class {
21
+ #abort = new AbortController();
22
+ add(target, type, handler, options) {
23
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
24
+ }
25
+ dispose() {
26
+ this.#abort.abort();
27
+ this.#abort = new AbortController();
28
+ }
29
+ };
18
30
 
19
31
  function isRtl(element) {
20
32
  return window.getComputedStyle(element).direction === "rtl";
@@ -36,6 +48,9 @@ var MicrotaskCoalescer = class {
36
48
  this.#queued = false;
37
49
  this.#generation += 1;
38
50
  }
51
+ get pending() {
52
+ return this.#queued;
53
+ }
39
54
  schedule() {
40
55
  if (!this.#active || this.#queued) return;
41
56
  this.#queued = true;
@@ -71,19 +86,17 @@ var SortableController = class extends Controller {
71
86
  static events = ["reorder"];
72
87
  #session = null;
73
88
  #settle = new MicrotaskCoalescer(() => this.#dropLostSession());
89
+ #listeners = new ListenerSet();
74
90
  connect() {
75
91
  TRANSIENT.reset(this.element);
76
92
  this.#settle.activate();
77
- this.element.addEventListener("stimeo--pointer-drag:start", this.#onDragStart);
78
- this.element.addEventListener("stimeo--pointer-drag:move", this.#onDragMove);
79
- this.element.addEventListener("stimeo--pointer-drag:end", this.#onDragEnd);
80
- this.element.addEventListener("stimeo--pointer-drag:cancel", this.#onDragCancel);
93
+ this.#listeners.add(this.element, "stimeo--pointer-drag:start", this.#onDragStart);
94
+ this.#listeners.add(this.element, "stimeo--pointer-drag:move", this.#onDragMove);
95
+ this.#listeners.add(this.element, "stimeo--pointer-drag:end", this.#onDragEnd);
96
+ this.#listeners.add(this.element, "stimeo--pointer-drag:cancel", this.#onDragCancel);
81
97
  }
82
98
  disconnect() {
83
- this.element.removeEventListener("stimeo--pointer-drag:start", this.#onDragStart);
84
- this.element.removeEventListener("stimeo--pointer-drag:move", this.#onDragMove);
85
- this.element.removeEventListener("stimeo--pointer-drag:end", this.#onDragEnd);
86
- this.element.removeEventListener("stimeo--pointer-drag:cancel", this.#onDragCancel);
99
+ this.#listeners.dispose();
87
100
  this.#settle.cancel();
88
101
  this.#session = null;
89
102
  this.element.removeAttribute("data-sortable-dragging");
@@ -1,5 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
+ function fillTemplate(template, values) {
4
+ return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
5
+ const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
6
+ return replacement === void 0 ? match : String(replacement);
7
+ });
8
+ }
9
+
3
10
  function announce(message, options = {}) {
4
11
  const text = message.trim();
5
12
  if (text.length === 0) return;
@@ -9,11 +16,12 @@ function announce(message, options = {}) {
9
16
  })
10
17
  );
11
18
  }
12
- function fillTemplate(template, values) {
13
- return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
14
- const replacement = values[name];
15
- return replacement === void 0 ? match : String(replacement);
16
- });
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;
17
25
  }
18
26
 
19
27
  function sharedRegistry(key, create) {
@@ -21,11 +29,15 @@ function sharedRegistry(key, create) {
21
29
  const scope = globalThis;
22
30
  const existing = scope[symbol];
23
31
  if (existing !== void 0) return existing;
24
- const registry = create();
25
- Object.defineProperty(globalThis, symbol, { value: registry });
26
- return registry;
32
+ const registry2 = create();
33
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
34
+ return registry2;
27
35
  }
28
36
 
37
+ var registry = sharedRegistry(
38
+ "stimeo-ui.attribute-lease.registry.v1",
39
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
40
+ );
29
41
  function isAttributeValue(value) {
30
42
  return value === null || typeof value === "string";
31
43
  }
@@ -42,24 +54,46 @@ function readLeaseRecord(element, record) {
42
54
  return void 0;
43
55
  }
44
56
  }
45
- var AttributeLease = class _AttributeLease {
46
- static #registry = sharedRegistry(
47
- "stimeo-ui.attribute-lease.registry.v1",
48
- () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
49
- );
57
+ function attributeStack(element, attribute) {
58
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
59
+ registry.stacks.set(element, byAttribute);
60
+ const known = byAttribute.get(attribute);
61
+ if (known) return known;
62
+ const found = [];
63
+ for (const record of registry.names.get(attribute) ?? []) {
64
+ const read = readLeaseRecord(element, record);
65
+ if (read) found.push({ ...read, record });
66
+ }
67
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
68
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
69
+ lease: null,
70
+ record,
71
+ written,
72
+ original,
73
+ linked: linked && index > 0
74
+ }));
75
+ byAttribute.set(attribute, stack);
76
+ return stack;
77
+ }
78
+ function findHold(stack, lease, record) {
79
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
80
+ }
81
+ var AttributeLease = class {
50
82
  #attribute;
51
83
  #record;
52
84
  #base;
85
+ #preserveAuthor;
53
86
  #held = /* @__PURE__ */ new Set();
54
87
  constructor(attribute, owner, options = {}) {
55
88
  this.#attribute = attribute;
56
89
  this.#record = `data-${owner}-${attribute}-lease`;
57
90
  this.#base = options.base === true;
58
- const names = _AttributeLease.#registry.names;
91
+ this.#preserveAuthor = options.preserveAuthor === true;
92
+ const names = registry.names;
59
93
  names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
60
94
  }
61
95
  write(element, value) {
62
- const stack = this.#stack(element);
96
+ const stack = attributeStack(element, this.#attribute);
63
97
  const hold = this.#take(stack) ?? this.#acquire(element, stack);
64
98
  this.#held.add(element);
65
99
  const shows = !this.#covered(element, stack, hold);
@@ -69,8 +103,8 @@ var AttributeLease = class _AttributeLease {
69
103
  }
70
104
  return(element) {
71
105
  const held = this.#held.delete(element);
72
- const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
73
- const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
106
+ const known = registry.stacks.get(element)?.has(this.#attribute);
107
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
74
108
  const hold = this.#take(stack);
75
109
  if (!hold) return false;
76
110
  const carries = element.getAttribute(this.#attribute) === hold.written;
@@ -88,31 +122,8 @@ var AttributeLease = class _AttributeLease {
88
122
  returnAll() {
89
123
  for (const element of Array.from(this.#held)) this.return(element);
90
124
  }
91
- #stack(element) {
92
- const stacks = _AttributeLease.#registry.stacks;
93
- const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
94
- stacks.set(element, byAttribute);
95
- const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
96
- byAttribute.set(this.#attribute, stack);
97
- return stack;
98
- }
99
- #seed(element) {
100
- const found = [];
101
- for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
102
- const read = readLeaseRecord(element, record);
103
- if (read) found.push({ ...read, record });
104
- }
105
- const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
106
- return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
107
- lease: null,
108
- record,
109
- written,
110
- original,
111
- linked: linked && index > 0
112
- }));
113
- }
114
125
  #take(stack) {
115
- const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
126
+ const hold = findHold(stack, this, this.#record);
116
127
  if (hold) hold.lease = this;
117
128
  return hold;
118
129
  }
@@ -121,7 +132,7 @@ var AttributeLease = class _AttributeLease {
121
132
  const top = stack.at(-1);
122
133
  const bottom = stack[0];
123
134
  const over = top?.written === current ? top : void 0;
124
- const from = this.#base ? bottom : over;
135
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
125
136
  const hold = {
126
137
  lease: this,
127
138
  record: this.#record,
@@ -151,15 +162,11 @@ var AttributeLease = class _AttributeLease {
151
162
  );
152
163
  });
153
164
  for (const [name, recorded] of records) {
154
- if (element.getAttribute(name) === recorded) continue;
155
- if (recorded === null) element.removeAttribute(name);
156
- else element.setAttribute(name, recorded);
165
+ setAttributeIfChanged(element, name, recorded);
157
166
  }
158
167
  }
159
168
  #reflect(element, value) {
160
- if (element.getAttribute(this.#attribute) === value) return;
161
- if (value === null) element.removeAttribute(this.#attribute);
162
- else element.setAttribute(this.#attribute, value);
169
+ setAttributeIfChanged(element, this.#attribute, value);
163
170
  }
164
171
  };
165
172
 
@@ -27,6 +27,9 @@ var MicrotaskCoalescer = class {
27
27
  this.#queued = false;
28
28
  this.#generation += 1;
29
29
  }
30
+ get pending() {
31
+ return this.#queued;
32
+ }
30
33
  schedule() {
31
34
  if (!this.#active || this.#queued) return;
32
35
  this.#queued = true;
@@ -57,6 +60,9 @@ var MorphRenderWatcher = class {
57
60
  schedule() {
58
61
  this.#pass.schedule();
59
62
  }
63
+ get pending() {
64
+ return this.#pass.pending;
65
+ }
60
66
  disconnect() {
61
67
  this.#listeners.dispose();
62
68
  this.#pass.cancel();
@@ -53,6 +53,9 @@ var MicrotaskCoalescer = class {
53
53
  this.#queued = false;
54
54
  this.#generation += 1;
55
55
  }
56
+ get pending() {
57
+ return this.#queued;
58
+ }
56
59
  schedule() {
57
60
  if (!this.#active || this.#queued) return;
58
61
  this.#queued = true;
@@ -83,6 +86,9 @@ var MorphRenderWatcher = class {
83
86
  schedule() {
84
87
  this.#pass.schedule();
85
88
  }
89
+ get pending() {
90
+ return this.#pass.pending;
91
+ }
86
92
  disconnect() {
87
93
  this.#listeners.dispose();
88
94
  this.#pass.cancel();