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,5 +1,38 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
+ var ListenerSet = class {
4
+ #abort = new AbortController();
5
+ add(target, type, handler, options) {
6
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
7
+ }
8
+ dispose() {
9
+ this.#abort.abort();
10
+ this.#abort = new AbortController();
11
+ }
12
+ };
13
+
14
+ var FontLoadWatcher = class {
15
+ #listeners = new ListenerSet();
16
+ #onSettled;
17
+ #fonts = null;
18
+ constructor(run) {
19
+ this.#onSettled = () => run();
20
+ }
21
+ observe(element) {
22
+ const fonts = element.ownerDocument.fonts ?? null;
23
+ if (fonts === this.#fonts) return;
24
+ this.disconnect();
25
+ if (!fonts) return;
26
+ this.#fonts = fonts;
27
+ this.#listeners.add(fonts, "loadingdone", this.#onSettled);
28
+ this.#listeners.add(fonts, "loadingerror", this.#onSettled);
29
+ }
30
+ disconnect() {
31
+ this.#listeners.dispose();
32
+ this.#fonts = null;
33
+ }
34
+ };
35
+
3
36
  var LayoutObserver = class {
4
37
  #callback;
5
38
  #resizeObserverFactory;
@@ -119,7 +152,6 @@ var TextareaAutosizeController = class _TextareaAutosizeController extends Contr
119
152
  #lastWidth = -1;
120
153
  #started = false;
121
154
  #pending = false;
122
- #fonts = null;
123
155
  #remeasure = () => {
124
156
  if (this.element.clientWidth === 0) {
125
157
  this.#pending = true;
@@ -127,6 +159,7 @@ var TextareaAutosizeController = class _TextareaAutosizeController extends Contr
127
159
  }
128
160
  this.resize();
129
161
  };
162
+ #fonts = new FontLoadWatcher(this.#remeasure);
130
163
  #layout = new LayoutObserver(() => {
131
164
  if (this.element.clientWidth === 0) return;
132
165
  const width = this.#contentWidth();
@@ -135,22 +168,22 @@ var TextareaAutosizeController = class _TextareaAutosizeController extends Contr
135
168
  this.#lastWidth = width;
136
169
  this.resize();
137
170
  });
171
+ #listeners = new ListenerSet();
138
172
  connect() {
139
173
  this.#lastHeight = Math.round(px(this.element.style.height));
140
174
  this.#lastWidth = this.#contentWidth();
141
- this.element.addEventListener("input", this.#remeasure);
142
- this.element.addEventListener("change", this.#remeasure);
175
+ this.#listeners.add(this.element, "input", this.#remeasure);
176
+ this.#listeners.add(this.element, "change", this.#remeasure);
143
177
  this.#layout.observe(this.element);
144
- this.#bindFonts();
178
+ this.#fonts.observe(this.element);
145
179
  this.#started = true;
146
180
  this.resize();
147
181
  }
148
182
  disconnect() {
149
183
  this.#started = false;
150
- this.element.removeEventListener("input", this.#remeasure);
151
- this.element.removeEventListener("change", this.#remeasure);
184
+ this.#listeners.dispose();
152
185
  this.#layout.disconnect();
153
- this.#unbindFonts();
186
+ this.#fonts.disconnect();
154
187
  }
155
188
  minRowsValueChanged() {
156
189
  if (this.#started) this.#remeasure();
@@ -203,18 +236,6 @@ var TextareaAutosizeController = class _TextareaAutosizeController extends Contr
203
236
  const fontSize = px(style.fontSize);
204
237
  return fontSize > 0 ? fontSize * 1.2 : 16;
205
238
  }
206
- #bindFonts() {
207
- const fonts = this.element.ownerDocument.fonts;
208
- if (!fonts) return;
209
- this.#fonts = fonts;
210
- fonts.addEventListener("loadingdone", this.#remeasure);
211
- fonts.addEventListener("loadingerror", this.#remeasure);
212
- }
213
- #unbindFonts() {
214
- this.#fonts?.removeEventListener("loadingdone", this.#remeasure);
215
- this.#fonts?.removeEventListener("loadingerror", this.#remeasure);
216
- this.#fonts = null;
217
- }
218
239
  get #safeMinRows() {
219
240
  return this.#numbers.read(
220
241
  this,
@@ -87,6 +87,9 @@ var MicrotaskCoalescer = class {
87
87
  this.#queued = false;
88
88
  this.#generation += 1;
89
89
  }
90
+ get pending() {
91
+ return this.#queued;
92
+ }
90
93
  schedule() {
91
94
  if (!this.#active || this.#queued) return;
92
95
  this.#queued = true;
@@ -117,6 +120,9 @@ var MorphRenderWatcher = class {
117
120
  schedule() {
118
121
  this.#pass.schedule();
119
122
  }
123
+ get pending() {
124
+ return this.#pass.pending;
125
+ }
120
126
  disconnect() {
121
127
  this.#listeners.dispose();
122
128
  this.#pass.cancel();
@@ -181,6 +187,7 @@ var ThemeController = class extends Controller {
181
187
  static actions = ["set", "toggle"];
182
188
  static events = ["change", "reconcile"];
183
189
  #media = null;
190
+ #listeners = new ListenerSet();
184
191
  #connected = false;
185
192
  #targetSelector = DEFAULT_TARGET;
186
193
  #storageKeyRead = null;
@@ -234,8 +241,8 @@ var ThemeController = class extends Controller {
234
241
  const stored = this.#readStored();
235
242
  if (stored) this.modeValue = stored;
236
243
  this.#media = window.matchMedia?.("(prefers-color-scheme: dark)") ?? null;
237
- this.#media?.addEventListener("change", this.#onMediaChange);
238
- this.element.addEventListener("keydown", this.#onKeydown);
244
+ if (this.#media) this.#listeners.add(this.#media, "change", this.#onMediaChange);
245
+ this.#listeners.add(this.element, "keydown", this.#onKeydown);
239
246
  this.#connected = true;
240
247
  this.#applyTheme();
241
248
  this.#syncControls();
@@ -244,8 +251,7 @@ var ThemeController = class extends Controller {
244
251
  disconnect() {
245
252
  this.#morph.disconnect();
246
253
  this.#connected = false;
247
- this.#media?.removeEventListener("change", this.#onMediaChange);
248
- this.element.removeEventListener("keydown", this.#onKeydown);
254
+ this.#listeners.dispose();
249
255
  }
250
256
  targetValueChanged() {
251
257
  this.#targetSelector = validSelector(this.element, this.targetValue, DEFAULT_TARGET);
@@ -13,6 +13,107 @@ 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
+ beforeWrite?.();
23
+ if (value === null) element.removeAttribute(name);
24
+ else element.setAttribute(name, value);
25
+ return true;
26
+ }
27
+
28
+ var AuthoredMutationWatcher = class {
29
+ #onAuthored;
30
+ #claims = null;
31
+ #observer = null;
32
+ constructor(onAuthored) {
33
+ this.#onAuthored = onAuthored;
34
+ }
35
+ observe(root, options) {
36
+ this.disconnect();
37
+ const observer = new MutationObserver((records) => {
38
+ if (this.#observer !== observer) return;
39
+ const authored = this.#filter(records);
40
+ if (authored.length > 0) this.#onAuthored(authored);
41
+ });
42
+ this.#observer = observer;
43
+ this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
44
+ observer.observe(root, {
45
+ attributes: true,
46
+ attributeFilter: [...options.attributes],
47
+ subtree: options.subtree ?? false,
48
+ childList: options.childList ?? false
49
+ });
50
+ if (options.ancestorAttributes) {
51
+ let ancestor = root.parentElement;
52
+ while (ancestor) {
53
+ observer.observe(ancestor, {
54
+ attributes: true,
55
+ attributeFilter: [...options.ancestorAttributes]
56
+ });
57
+ ancestor = ancestor.parentElement;
58
+ }
59
+ }
60
+ }
61
+ claim(target, name, value) {
62
+ const claims = this.#claims?.attributes;
63
+ let attributes = claims?.get(target);
64
+ if (!attributes) {
65
+ attributes = /* @__PURE__ */ new Map();
66
+ claims?.set(target, attributes);
67
+ }
68
+ const pending = attributes.get(name);
69
+ attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
70
+ }
71
+ claimCurrent(target, name) {
72
+ this.claim(target, name, target.getAttribute(name));
73
+ }
74
+ claimChildList(target, added, removed) {
75
+ const claims = this.#claims?.children;
76
+ const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
77
+ for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
78
+ for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
79
+ claims?.set(target, pending);
80
+ }
81
+ take() {
82
+ return this.#filter(this.#observer?.takeRecords() ?? []);
83
+ }
84
+ disconnect() {
85
+ this.#observer?.disconnect();
86
+ this.#observer = null;
87
+ this.#claims = null;
88
+ }
89
+ #filter(records) {
90
+ const authored = records.filter((record) => !this.#consume(record));
91
+ this.#claims?.attributes.clear();
92
+ this.#claims?.children.clear();
93
+ return authored;
94
+ }
95
+ #consume(record) {
96
+ if (record.type === "attributes") {
97
+ const target = record.target;
98
+ const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
99
+ if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
100
+ return false;
101
+ }
102
+ pending2.count -= 1;
103
+ return true;
104
+ }
105
+ const pending = this.#claims?.children.get(record.target);
106
+ if (!pending) return false;
107
+ const added = [...record.addedNodes];
108
+ const removed = [...record.removedNodes];
109
+ if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
110
+ return false;
111
+ }
112
+ for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
113
+ for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
114
+ return true;
115
+ }
116
+ };
16
117
 
17
118
  function writeField(field, value) {
18
119
  if (field.value === value) return false;
@@ -50,6 +151,9 @@ var MicrotaskCoalescer = class {
50
151
  this.#queued = false;
51
152
  this.#generation += 1;
52
153
  }
154
+ get pending() {
155
+ return this.#queued;
156
+ }
53
157
  schedule() {
54
158
  if (!this.#active || this.#queued) return;
55
159
  this.#queued = true;
@@ -80,6 +184,9 @@ var MorphRenderWatcher = class {
80
184
  schedule() {
81
185
  this.#pass.schedule();
82
186
  }
187
+ get pending() {
188
+ return this.#pass.pending;
189
+ }
83
190
  disconnect() {
84
191
  this.#listeners.dispose();
85
192
  this.#pass.cancel();
@@ -151,17 +258,20 @@ var PM = 1;
151
258
  var SECONDS_PER_MINUTE = 60;
152
259
  var SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
153
260
  var SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
154
- var hasModifier = (event) => event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
155
261
  var TimePickerController = class _TimePickerController extends Controller {
156
262
  #numbers = new NumberValueReader();
157
263
  #moves = new MoveCounter();
264
+ #connectionGeneration = 0;
158
265
  #move = 0;
159
266
  static targets = ["segment", "field"];
160
267
  static values = {
161
268
  hourCycle: { type: Number, default: 24 },
269
+ hour12ZeroBased: { type: Boolean, default: false },
162
270
  step: { type: Number, default: 1 },
163
271
  seconds: { type: Boolean, default: false },
164
- wrap: { type: Boolean, default: true }
272
+ wrap: { type: Boolean, default: true },
273
+ amLabel: { type: String, default: "AM" },
274
+ pmLabel: { type: String, default: "PM" }
165
275
  };
166
276
  static valueConstraints = {
167
277
  hourCycle: { finite: true, allowedValues: [12, 24] },
@@ -174,39 +284,56 @@ var TimePickerController = class _TimePickerController extends Controller {
174
284
  #typeBuffer = "";
175
285
  #typeSegment = null;
176
286
  #lastValue = "";
177
- #observer = null;
178
- #connected = false;
287
+ #renderedHourMode = null;
288
+ #renderedSegments = /* @__PURE__ */ new WeakSet();
289
+ #authoredSegments = /* @__PURE__ */ new Set();
290
+ #observer = new AuthoredMutationWatcher((records) => {
291
+ if (!this.#recordAuthoredSegments(records)) return;
292
+ this.#reconcile.schedule();
293
+ });
179
294
  #domDirty = false;
295
+ #listeners = new ListenerSet();
180
296
  connect() {
181
- this.#connected = true;
182
297
  this.#reconcile.observe(this.element);
183
- this.element.addEventListener("focusout", this.#onFocusOut);
298
+ this.#listeners.add(this.element, "focusout", this.#onFocusOut);
184
299
  this.#domDirty = false;
185
300
  this.#adoptDomState();
186
301
  this.#render();
187
302
  this.#observeMutations();
188
303
  }
189
304
  disconnect() {
190
- this.#connected = false;
305
+ this.#connectionGeneration += 1;
191
306
  this.#reconcile.disconnect();
192
- this.element.removeEventListener("focusout", this.#onFocusOut);
193
- this.#observer?.disconnect();
194
- this.#observer = null;
307
+ this.#listeners.dispose();
308
+ this.#observer.disconnect();
195
309
  this.#domDirty = false;
310
+ this.#renderedHourMode = null;
311
+ this.#renderedSegments = /* @__PURE__ */ new WeakSet();
312
+ this.#authoredSegments.clear();
196
313
  this.#clearTypeBuffer();
197
314
  this.#lastValue = "";
198
315
  }
199
316
  hourCycleValueChanged() {
200
317
  this.#reconcile.schedule();
201
318
  }
319
+ hour12ZeroBasedValueChanged() {
320
+ this.#reconcile.schedule();
321
+ }
202
322
  secondsValueChanged() {
203
323
  this.#reconcile.schedule();
204
324
  }
325
+ amLabelValueChanged() {
326
+ this.#reconcile.schedule();
327
+ }
328
+ pmLabelValueChanged() {
329
+ this.#reconcile.schedule();
330
+ }
205
331
  segmentTargetConnected() {
206
332
  this.#domDirty = true;
207
333
  this.#reconcile.schedule();
208
334
  }
209
- segmentTargetDisconnected() {
335
+ segmentTargetDisconnected(segment) {
336
+ if (segment && !this.segmentTargets.includes(segment)) this.#renderedSegments.delete(segment);
210
337
  this.#domDirty = true;
211
338
  this.#reconcile.schedule();
212
339
  }
@@ -225,8 +352,9 @@ var TimePickerController = class _TimePickerController extends Controller {
225
352
  const kind = this.#kindOf(segment);
226
353
  if (!kind) return;
227
354
  const key = logicalArrowKey(event.key, this.element);
228
- if ((key === "Home" || key === "End") && hasModifier(event)) return;
355
+ if ((key === "Home" || key === "End") && hasModifierChord(event)) return;
229
356
  if (this.#adoptPendingDom()) this.#reconcile.schedule();
357
+ if (this.#renderedHourMode !== this.#hourMode) this.#clearTypeBuffer();
230
358
  switch (key) {
231
359
  case "ArrowUp":
232
360
  event.preventDefault();
@@ -255,7 +383,7 @@ var TimePickerController = class _TimePickerController extends Controller {
255
383
  this.#commitRender();
256
384
  break;
257
385
  default:
258
- if (kind !== "meridiem" && /^[0-9]$/.test(event.key) && !hasModifier(event)) {
386
+ if (kind !== "meridiem" && /^[0-9]$/.test(event.key) && !event.ctrlKey && !event.altKey && !event.metaKey) {
259
387
  event.preventDefault();
260
388
  this.#typeDigit(segment, kind, event.key);
261
389
  return;
@@ -298,7 +426,7 @@ var TimePickerController = class _TimePickerController extends Controller {
298
426
  const candidate = Number(`${this.#typeBuffer}${digit}`);
299
427
  this.#typeBuffer = candidate <= max ? `${this.#typeBuffer}${digit}` : digit;
300
428
  this.#setDisplayed(kind, Number(this.#typeBuffer));
301
- this.#commitRender();
429
+ this.#commitRender(true);
302
430
  if (this.#typeBuffer.length >= 2 || Number(this.#typeBuffer) * 10 > max) {
303
431
  this.#clearTypeBuffer();
304
432
  this.#focusSibling(segment, 1);
@@ -314,21 +442,30 @@ var TimePickerController = class _TimePickerController extends Controller {
314
442
  this.#typeSegment = null;
315
443
  }
316
444
  #adoptPendingDom() {
317
- const pending = this.#observer?.takeRecords() ?? [];
318
- if (pending.some(({ target }) => this.segmentTargets.includes(target))) {
319
- this.#domDirty = true;
320
- }
445
+ this.#recordAuthoredSegments(this.#observer.take());
321
446
  if (!this.#domDirty) return false;
322
447
  this.#domDirty = false;
323
448
  this.#clearTypeBuffer();
324
449
  this.#adoptDomState();
325
450
  return true;
326
451
  }
452
+ #recordAuthoredSegments(records) {
453
+ let changed = false;
454
+ for (const { target } of records) {
455
+ if (!(target instanceof HTMLElement) || !this.segmentTargets.includes(target)) continue;
456
+ this.#authoredSegments.add(target);
457
+ changed = true;
458
+ }
459
+ if (changed) this.#domDirty = true;
460
+ return changed;
461
+ }
327
462
  #reconcileDom() {
328
463
  this.#adoptPendingDom();
329
464
  this.#render();
330
465
  }
331
466
  #adoptDomState() {
467
+ const mode = this.#hourMode;
468
+ const presentationChanged = this.#renderedHourMode !== null && this.#renderedHourMode !== mode;
332
469
  let displayedHour = 0;
333
470
  let meridiem = AM;
334
471
  let minute = 0;
@@ -336,7 +473,15 @@ var TimePickerController = class _TimePickerController extends Controller {
336
473
  for (const segment of this.segmentTargets) {
337
474
  const kind = this.#kindOf(segment);
338
475
  if (!kind) continue;
339
- const value = this.#authoredValue(segment, kind);
476
+ const retained = presentationChanged && this.#renderedSegments.has(segment) && !this.#authoredSegments.has(segment);
477
+ let value;
478
+ if (retained && kind === "hour") {
479
+ value = mode === "h23" ? this.#state.hour : this.#state.hour % 12;
480
+ } else if (retained && kind === "meridiem") {
481
+ value = this.#state.hour >= 12 ? PM : AM;
482
+ } else {
483
+ value = this.#authoredValue(segment, kind);
484
+ }
340
485
  if (kind === "hour") displayedHour = value;
341
486
  else if (kind === "minute") minute = value;
342
487
  else if (kind === "second") second = value;
@@ -347,6 +492,7 @@ var TimePickerController = class _TimePickerController extends Controller {
347
492
  minute,
348
493
  second
349
494
  };
495
+ this.#authoredSegments.clear();
350
496
  }
351
497
  #authoredValue(segment, kind) {
352
498
  const raw = Number(segment.getAttribute("aria-valuenow"));
@@ -371,10 +517,11 @@ var TimePickerController = class _TimePickerController extends Controller {
371
517
  this.#state.minute = Math.floor(seconds % SECONDS_PER_HOUR / SECONDS_PER_MINUTE);
372
518
  this.#state.second = seconds % SECONDS_PER_MINUTE;
373
519
  }
374
- #renderSegments() {
375
- this.#withoutObservation(() => {
376
- for (const segment of this.segmentTargets) this.#renderSegment(segment);
377
- });
520
+ #renderSegments(preserveTypeBuffer = false) {
521
+ const mode = this.#hourMode;
522
+ if (this.#renderedHourMode !== mode && !preserveTypeBuffer) this.#clearTypeBuffer();
523
+ this.#renderedHourMode = mode;
524
+ for (const segment of this.segmentTargets) this.#renderSegment(segment);
378
525
  }
379
526
  #render() {
380
527
  this.#renderSegments();
@@ -387,24 +534,29 @@ var TimePickerController = class _TimePickerController extends Controller {
387
534
  this.dispatch("reconcile", { detail: { value } });
388
535
  }
389
536
  }
390
- #commitRender() {
391
- this.#renderSegments();
537
+ #commitRender(preserveTypeBuffer = false) {
538
+ const generation = this.#connectionGeneration;
539
+ this.#renderSegments(preserveTypeBuffer);
392
540
  const value = this.#composedValue;
393
541
  const changed = value !== this.#lastValue;
394
542
  this.#lastValue = value;
395
543
  if (changed) this.#move = this.#moves.record();
396
544
  const move = this.#move;
397
545
  if (this.#writeField(value)) commitField(this.fieldTarget);
398
- if (!this.#moves.isLatest(move)) return;
546
+ if (generation !== this.#connectionGeneration || !this.#moves.isLatest(move)) return;
399
547
  if (changed) this.dispatch("change", { detail: { value } });
400
548
  }
401
549
  #renderSegment(segment) {
402
550
  const kind = this.#kindOf(segment);
403
551
  if (!kind) return;
552
+ this.#renderedSegments.add(segment);
404
553
  const value = this.#displayValue(kind);
405
- const text = kind === "meridiem" ? value === PM ? "PM" : "AM" : pad(value);
554
+ const text = kind === "meridiem" ? value === PM ? this.pmLabelValue : this.amLabelValue : pad(value);
406
555
  const { min, max } = this.#bounds(kind);
407
- segment.setAttribute("aria-valuenow", String(value));
556
+ const serialized = String(value);
557
+ setAttributeIfChanged(segment, "aria-valuenow", serialized, () => {
558
+ this.#observer.claim(segment, "aria-valuenow", serialized);
559
+ });
408
560
  segment.setAttribute("aria-valuetext", text);
409
561
  segment.setAttribute("aria-valuemin", String(min));
410
562
  segment.setAttribute("aria-valuemax", String(max));
@@ -420,7 +572,10 @@ var TimePickerController = class _TimePickerController extends Controller {
420
572
  }
421
573
  #displayValue(kind) {
422
574
  if (kind === "hour") {
423
- return this.#safeHourCycle === 12 ? this.#state.hour % 12 || 12 : this.#state.hour;
575
+ const mode = this.#hourMode;
576
+ if (mode === "h23") return this.#state.hour;
577
+ const hour = this.#state.hour % 12;
578
+ return mode === "h11" ? hour : hour || 12;
424
579
  }
425
580
  if (kind === "meridiem") return this.#state.hour >= 12 ? PM : AM;
426
581
  return this.#state[kind];
@@ -428,7 +583,8 @@ var TimePickerController = class _TimePickerController extends Controller {
428
583
  #bounds(kind) {
429
584
  switch (kind) {
430
585
  case "hour":
431
- return this.#safeHourCycle === 12 ? { min: 1, max: 12 } : { min: 0, max: 23 };
586
+ if (this.#hourMode === "h23") return { min: 0, max: 23 };
587
+ return this.#hourMode === "h11" ? { min: 0, max: 11 } : { min: 1, max: 12 };
432
588
  case "minute":
433
589
  case "second":
434
590
  return { min: 0, max: 59 };
@@ -437,33 +593,11 @@ var TimePickerController = class _TimePickerController extends Controller {
437
593
  }
438
594
  }
439
595
  #observeMutations() {
440
- const observer = new MutationObserver((records) => {
441
- const changed = records.some(
442
- ({ target }) => target instanceof HTMLElement && this.segmentTargets.includes(target)
443
- );
444
- if (!changed) return;
445
- this.#domDirty = true;
446
- this.#reconcile.schedule();
447
- });
448
- this.#observer = observer;
449
- this.#observeWith(observer);
450
- }
451
- #observeWith(observer) {
452
- observer.observe(this.element, {
596
+ this.#observer.observe(this.element, {
453
597
  subtree: true,
454
- attributes: true,
455
- attributeFilter: ["aria-valuenow", "data-segment"]
598
+ attributes: ["aria-valuenow", "data-segment"]
456
599
  });
457
600
  }
458
- #withoutObservation(run) {
459
- const observer = this.#observer;
460
- observer?.disconnect();
461
- try {
462
- run();
463
- } finally {
464
- if (observer && this.#connected) this.#observeWith(observer);
465
- }
466
- }
467
601
  #kindOf(segment) {
468
602
  const kind = segment.getAttribute("data-segment");
469
603
  if (kind === "hour" || kind === "minute" || kind === "second" || kind === "meridiem") {
@@ -474,6 +608,10 @@ var TimePickerController = class _TimePickerController extends Controller {
474
608
  get #secondsOfDay() {
475
609
  return this.#state.hour * SECONDS_PER_HOUR + this.#state.minute * SECONDS_PER_MINUTE + this.#state.second;
476
610
  }
611
+ get #hourMode() {
612
+ if (this.#safeHourCycle !== 12) return "h23";
613
+ return this.hour12ZeroBasedValue ? "h11" : "h12";
614
+ }
477
615
  get #safeHourCycle() {
478
616
  return this.#numbers.read(
479
617
  this,