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
@@ -15,9 +15,9 @@ function sharedRegistry(key, create) {
15
15
  const scope = globalThis;
16
16
  const existing = scope[symbol];
17
17
  if (existing !== void 0) return existing;
18
- const registry = create();
19
- Object.defineProperty(globalThis, symbol, { value: registry });
20
- return registry;
18
+ const registry2 = create();
19
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
20
+ return registry2;
21
21
  }
22
22
 
23
23
  var ids = sharedRegistry("stimeo-ui.aria-ids.registry.v1", () => ({ counter: 0 }));
@@ -36,6 +36,17 @@ function ensureId(element, prefix = "stimeo") {
36
36
  return id;
37
37
  }
38
38
 
39
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
40
+ if (element.getAttribute(name) === value) return false;
41
+ if (value === null) element.removeAttribute(name);
42
+ else element.setAttribute(name, value);
43
+ return true;
44
+ }
45
+
46
+ var registry = sharedRegistry(
47
+ "stimeo-ui.attribute-lease.registry.v1",
48
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
49
+ );
39
50
  function isAttributeValue(value) {
40
51
  return value === null || typeof value === "string";
41
52
  }
@@ -52,24 +63,46 @@ function readLeaseRecord(element, record) {
52
63
  return void 0;
53
64
  }
54
65
  }
55
- var AttributeLease = class _AttributeLease {
56
- static #registry = sharedRegistry(
57
- "stimeo-ui.attribute-lease.registry.v1",
58
- () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
59
- );
66
+ function attributeStack(element, attribute) {
67
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
68
+ registry.stacks.set(element, byAttribute);
69
+ const known = byAttribute.get(attribute);
70
+ if (known) return known;
71
+ const found = [];
72
+ for (const record of registry.names.get(attribute) ?? []) {
73
+ const read = readLeaseRecord(element, record);
74
+ if (read) found.push({ ...read, record });
75
+ }
76
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
77
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
78
+ lease: null,
79
+ record,
80
+ written,
81
+ original,
82
+ linked: linked && index > 0
83
+ }));
84
+ byAttribute.set(attribute, stack);
85
+ return stack;
86
+ }
87
+ function findHold(stack, lease, record) {
88
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
89
+ }
90
+ var AttributeLease = class {
60
91
  #attribute;
61
92
  #record;
62
93
  #base;
94
+ #preserveAuthor;
63
95
  #held = /* @__PURE__ */ new Set();
64
96
  constructor(attribute, owner, options = {}) {
65
97
  this.#attribute = attribute;
66
98
  this.#record = `data-${owner}-${attribute}-lease`;
67
99
  this.#base = options.base === true;
68
- const names = _AttributeLease.#registry.names;
100
+ this.#preserveAuthor = options.preserveAuthor === true;
101
+ const names = registry.names;
69
102
  names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
70
103
  }
71
104
  write(element, value) {
72
- const stack = this.#stack(element);
105
+ const stack = attributeStack(element, this.#attribute);
73
106
  const hold = this.#take(stack) ?? this.#acquire(element, stack);
74
107
  this.#held.add(element);
75
108
  const shows = !this.#covered(element, stack, hold);
@@ -79,8 +112,8 @@ var AttributeLease = class _AttributeLease {
79
112
  }
80
113
  return(element) {
81
114
  const held = this.#held.delete(element);
82
- const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
83
- const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
115
+ const known = registry.stacks.get(element)?.has(this.#attribute);
116
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
84
117
  const hold = this.#take(stack);
85
118
  if (!hold) return false;
86
119
  const carries = element.getAttribute(this.#attribute) === hold.written;
@@ -98,31 +131,8 @@ var AttributeLease = class _AttributeLease {
98
131
  returnAll() {
99
132
  for (const element of Array.from(this.#held)) this.return(element);
100
133
  }
101
- #stack(element) {
102
- const stacks = _AttributeLease.#registry.stacks;
103
- const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
104
- stacks.set(element, byAttribute);
105
- const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
106
- byAttribute.set(this.#attribute, stack);
107
- return stack;
108
- }
109
- #seed(element) {
110
- const found = [];
111
- for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
112
- const read = readLeaseRecord(element, record);
113
- if (read) found.push({ ...read, record });
114
- }
115
- const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
116
- return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
117
- lease: null,
118
- record,
119
- written,
120
- original,
121
- linked: linked && index > 0
122
- }));
123
- }
124
134
  #take(stack) {
125
- const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
135
+ const hold = findHold(stack, this, this.#record);
126
136
  if (hold) hold.lease = this;
127
137
  return hold;
128
138
  }
@@ -131,7 +141,7 @@ var AttributeLease = class _AttributeLease {
131
141
  const top = stack.at(-1);
132
142
  const bottom = stack[0];
133
143
  const over = top?.written === current ? top : void 0;
134
- const from = this.#base ? bottom : over;
144
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
135
145
  const hold = {
136
146
  lease: this,
137
147
  record: this.#record,
@@ -161,15 +171,11 @@ var AttributeLease = class _AttributeLease {
161
171
  );
162
172
  });
163
173
  for (const [name, recorded] of records) {
164
- if (element.getAttribute(name) === recorded) continue;
165
- if (recorded === null) element.removeAttribute(name);
166
- else element.setAttribute(name, recorded);
174
+ setAttributeIfChanged(element, name, recorded);
167
175
  }
168
176
  }
169
177
  #reflect(element, value) {
170
- if (element.getAttribute(this.#attribute) === value) return;
171
- if (value === null) element.removeAttribute(this.#attribute);
172
- else element.setAttribute(this.#attribute, value);
178
+ setAttributeIfChanged(element, this.#attribute, value);
173
179
  }
174
180
  };
175
181
 
@@ -194,6 +200,9 @@ var MicrotaskCoalescer = class {
194
200
  this.#queued = false;
195
201
  this.#generation += 1;
196
202
  }
203
+ get pending() {
204
+ return this.#queued;
205
+ }
197
206
  schedule() {
198
207
  if (!this.#active || this.#queued) return;
199
208
  this.#queued = true;
@@ -91,6 +91,17 @@ function firstTabStop(root) {
91
91
  return null;
92
92
  }
93
93
 
94
+ var ListenerSet = class {
95
+ #abort = new AbortController();
96
+ add(target, type, handler, options) {
97
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
98
+ }
99
+ dispose() {
100
+ this.#abort.abort();
101
+ this.#abort = new AbortController();
102
+ }
103
+ };
104
+
94
105
  var CONSTRAINT_MESSAGE_KEYS = [
95
106
  ["valueMissing", "value-missing"],
96
107
  ["typeMismatch", "type-mismatch"],
@@ -127,7 +138,7 @@ var FormValidationController = class _FormValidationController extends Controlle
127
138
  #touchedRadioGroups = /* @__PURE__ */ new Set();
128
139
  #ownedCustomErrors = /* @__PURE__ */ new Map();
129
140
  #reportedErrors = /* @__PURE__ */ new WeakMap();
130
- #listenerDocument = null;
141
+ #listeners = new ListenerSet();
131
142
  #onSubmit = (event) => {
132
143
  if (event.target !== this.element) return;
133
144
  const invalid = this.#validateAll();
@@ -169,18 +180,14 @@ var FormValidationController = class _FormValidationController extends Controlle
169
180
  if (setDefaultAttribute(this.element, "novalidate", "")) {
170
181
  this.element.setAttribute(this.#novalidateMarker, "");
171
182
  }
172
- this.#listenerDocument = this.element.ownerDocument;
173
- this.#listenerDocument.addEventListener("submit", this.#onSubmit, true);
174
- this.#listenerDocument.addEventListener("focusout", this.#onFocusOut);
175
- this.#listenerDocument.addEventListener("input", this.#onInput);
176
- this.#listenerDocument.addEventListener("change", this.#onChange);
183
+ const { ownerDocument } = this.element;
184
+ this.#listeners.add(ownerDocument, "submit", this.#onSubmit, { capture: true });
185
+ this.#listeners.add(ownerDocument, "focusout", this.#onFocusOut);
186
+ this.#listeners.add(ownerDocument, "input", this.#onInput);
187
+ this.#listeners.add(ownerDocument, "change", this.#onChange);
177
188
  }
178
189
  disconnect() {
179
- this.#listenerDocument?.removeEventListener("submit", this.#onSubmit, true);
180
- this.#listenerDocument?.removeEventListener("focusout", this.#onFocusOut);
181
- this.#listenerDocument?.removeEventListener("input", this.#onInput);
182
- this.#listenerDocument?.removeEventListener("change", this.#onChange);
183
- this.#listenerDocument = null;
190
+ this.#listeners.dispose();
184
191
  for (const [control, message] of this.#ownedCustomErrors) {
185
192
  if (control.validationMessage === message) control.setCustomValidity("");
186
193
  }
@@ -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
 
@@ -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
 
@@ -155,25 +161,25 @@ var EscapeLayer = class _EscapeLayer {
155
161
  #active = null;
156
162
  activate(ownerDocument = document, options) {
157
163
  this.deactivate();
158
- let registry = _EscapeLayer.#registries.get(ownerDocument);
159
- if (!registry) {
160
- registry = _EscapeLayer.#createRegistry();
161
- _EscapeLayer.#registries.set(ownerDocument, registry);
162
- ownerDocument.addEventListener("keydown", registry.onKeydown);
164
+ let registry2 = _EscapeLayer.#registries.get(ownerDocument);
165
+ if (!registry2) {
166
+ registry2 = _EscapeLayer.#createRegistry();
167
+ _EscapeLayer.#registries.set(ownerDocument, registry2);
168
+ ownerDocument.addEventListener("keydown", registry2.onKeydown);
163
169
  }
164
170
  const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
165
- registry.stack.push(entry);
171
+ registry2.stack.push(entry);
166
172
  this.#active = { document: ownerDocument, entry };
167
173
  }
168
174
  deactivate() {
169
175
  const active = this.#active;
170
176
  if (!active) return;
171
- const registry = _EscapeLayer.#registries.get(active.document);
172
- if (registry) {
173
- const index = registry.stack.lastIndexOf(active.entry);
174
- if (index >= 0) registry.stack.splice(index, 1);
175
- if (registry.stack.length === 0) {
176
- active.document.removeEventListener("keydown", registry.onKeydown);
177
+ const registry2 = _EscapeLayer.#registries.get(active.document);
178
+ if (registry2) {
179
+ const index = registry2.stack.lastIndexOf(active.entry);
180
+ if (index >= 0) registry2.stack.splice(index, 1);
181
+ if (registry2.stack.length === 0) {
182
+ active.document.removeEventListener("keydown", registry2.onKeydown);
177
183
  _EscapeLayer.#registries.delete(active.document);
178
184
  }
179
185
  }
@@ -182,22 +188,22 @@ var EscapeLayer = class _EscapeLayer {
182
188
  get ownsEscape() {
183
189
  const active = this.#active;
184
190
  if (!active) return false;
185
- const registry = _EscapeLayer.#registries.get(active.document);
186
- if (!registry) return false;
187
- return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
191
+ const registry2 = _EscapeLayer.#registries.get(active.document);
192
+ if (!registry2) return false;
193
+ return _EscapeLayer.#resolveOwner(registry2.stack) === active.entry;
188
194
  }
189
195
  static #createRegistry() {
190
- const registry = {
196
+ const registry2 = {
191
197
  stack: [],
192
198
  onKeydown: (event) => {
193
199
  if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
194
- const owner = _EscapeLayer.#resolveOwner(registry.stack);
200
+ const owner = _EscapeLayer.#resolveOwner(registry2.stack);
195
201
  if (!owner) return;
196
202
  event.preventDefault();
197
203
  owner.onDismiss();
198
204
  }
199
205
  };
200
- return registry;
206
+ return registry2;
201
207
  }
202
208
  static #resolveOwner(stack) {
203
209
  for (let index = stack.length - 1; index >= 0; index--) {
@@ -93,6 +93,13 @@ var SafeTimeout = class extends TimerRegistry {
93
93
  }
94
94
  };
95
95
 
96
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
97
+ if (element.getAttribute(name) === value) return false;
98
+ if (value === null) element.removeAttribute(name);
99
+ else element.setAttribute(name, value);
100
+ return true;
101
+ }
102
+
96
103
  function sharedRegistry(key, create) {
97
104
  const symbol = Symbol.for(key);
98
105
  const scope = globalThis;
@@ -147,9 +154,8 @@ var StateRegions = class _StateRegions {
147
154
  const below = hold.linked ? stack[index - 1] : void 0;
148
155
  if (below) {
149
156
  if (region.hidden !== below.written) region.hidden = below.written;
150
- } else if (region.getAttribute("hidden") !== hold.authored) {
151
- if (hold.authored === null) region.removeAttribute("hidden");
152
- else region.setAttribute("hidden", hold.authored);
157
+ } else {
158
+ setAttributeIfChanged(region, "hidden", hold.authored);
153
159
  }
154
160
  }
155
161
  this.#mark(region, stack);
@@ -224,9 +230,7 @@ var StateRegions = class _StateRegions {
224
230
  );
225
231
  });
226
232
  for (const [name, recorded] of records) {
227
- if (region.getAttribute(name) === recorded) continue;
228
- if (recorded === null) region.removeAttribute(name);
229
- else region.setAttribute(name, recorded);
233
+ setAttributeIfChanged(region, name, recorded);
230
234
  }
231
235
  }
232
236
  #recorded(region, name) {