stimeo-ui 0.16.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +136 -0
  3. data/README.md +27 -11
  4. data/dist/cable/index.js +384 -449
  5. data/dist/controllers/accordion_controller.js +174 -55
  6. data/dist/controllers/alert_dialog_controller.js +903 -235
  7. data/dist/controllers/announcer_controller.js +238 -205
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +414 -145
  10. data/dist/controllers/avatar_controller.js +185 -107
  11. data/dist/controllers/breadcrumb_controller.js +0 -130
  12. data/dist/controllers/bulk_select_controller.js +282 -58
  13. data/dist/controllers/calendar_controller.js +228 -260
  14. data/dist/controllers/carousel_controller.js +536 -436
  15. data/dist/controllers/character_counter_controller.js +493 -207
  16. data/dist/controllers/checkbox_controller.js +38 -74
  17. data/dist/controllers/clipboard_controller.js +261 -149
  18. data/dist/controllers/collapsible_controller.js +304 -120
  19. data/dist/controllers/color_picker_controller.js +178 -146
  20. data/dist/controllers/combobox_controller.js +522 -156
  21. data/dist/controllers/command_palette_controller.js +1156 -395
  22. data/dist/controllers/conditional_fields_controller.js +300 -171
  23. data/dist/controllers/confirm_controller.js +980 -262
  24. data/dist/controllers/context_menu_controller.js +346 -153
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +281 -158
  27. data/dist/controllers/currency_input_controller.js +575 -225
  28. data/dist/controllers/data_grid_controller.js +248 -214
  29. data/dist/controllers/date_range_picker_controller.js +338 -235
  30. data/dist/controllers/dialog_controller.js +899 -237
  31. data/dist/controllers/direct_upload_controller.js +97 -167
  32. data/dist/controllers/dirty_form_controller.js +72 -69
  33. data/dist/controllers/dismissible_controller.js +196 -24
  34. data/dist/controllers/drawer_controller.js +974 -355
  35. data/dist/controllers/dropdown_controller.js +339 -96
  36. data/dist/controllers/editable_controller.js +315 -88
  37. data/dist/controllers/empty_state_controller.js +79 -58
  38. data/dist/controllers/file_dropzone_controller.js +322 -211
  39. data/dist/controllers/filter_controller.js +77 -42
  40. data/dist/controllers/flash_controller.js +238 -347
  41. data/dist/controllers/focus_controller.js +679 -240
  42. data/dist/controllers/form_field_controller.js +156 -132
  43. data/dist/controllers/form_validation_controller.js +31 -97
  44. data/dist/controllers/frame_loading_controller.js +275 -217
  45. data/dist/controllers/highlight_controller.js +107 -75
  46. data/dist/controllers/hover_card_controller.js +327 -130
  47. data/dist/controllers/idle_controller.js +271 -101
  48. data/dist/controllers/input_mask_controller.js +483 -97
  49. data/dist/controllers/intersection_controller.js +150 -106
  50. data/dist/controllers/lazy_frame_controller.js +24 -68
  51. data/dist/controllers/listbox_controller.js +523 -240
  52. data/dist/controllers/local_time_controller.js +102 -77
  53. data/dist/controllers/masonry_controller.js +80 -96
  54. data/dist/controllers/menu_controller.js +302 -188
  55. data/dist/controllers/menubar_controller.js +232 -345
  56. data/dist/controllers/meter_controller.js +417 -87
  57. data/dist/controllers/multi_select_controller.js +785 -393
  58. data/dist/controllers/navigation_menu_controller.js +190 -250
  59. data/dist/controllers/nested_form_controller.js +396 -192
  60. data/dist/controllers/network_status_controller.js +268 -62
  61. data/dist/controllers/number_input_controller.js +610 -303
  62. data/dist/controllers/optimistic_controller.js +45 -87
  63. data/dist/controllers/otp_controller.js +658 -255
  64. data/dist/controllers/overflow_indicator_controller.js +122 -113
  65. data/dist/controllers/overflow_menu_controller.js +470 -295
  66. data/dist/controllers/pagination_controller.js +332 -123
  67. data/dist/controllers/password_reveal_controller.js +392 -121
  68. data/dist/controllers/password_strength_controller.js +111 -158
  69. data/dist/controllers/persist_controller.js +165 -111
  70. data/dist/controllers/pointer_drag_controller.js +434 -188
  71. data/dist/controllers/popover_controller.js +269 -113
  72. data/dist/controllers/portal_controller.js +215 -76
  73. data/dist/controllers/preview_guard_controller.js +243 -116
  74. data/dist/controllers/progress_controller.js +391 -78
  75. data/dist/controllers/radio_group_controller.js +278 -211
  76. data/dist/controllers/range_slider_controller.js +220 -113
  77. data/dist/controllers/rating_controller.js +458 -211
  78. data/dist/controllers/read_more_controller.js +138 -63
  79. data/dist/controllers/reading_progress_controller.js +166 -137
  80. data/dist/controllers/relative_time_controller.js +211 -112
  81. data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  82. data/dist/controllers/resizable_controller.js +299 -150
  83. data/dist/controllers/roving_controller.js +49 -88
  84. data/dist/controllers/scroll_area_controller.js +490 -246
  85. data/dist/controllers/scroll_restore_controller.js +14 -55
  86. data/dist/controllers/scroll_visibility_controller.js +432 -149
  87. data/dist/controllers/scrollspy_controller.js +193 -248
  88. data/dist/controllers/separator_controller.js +285 -142
  89. data/dist/controllers/sidebar_controller.js +1069 -404
  90. data/dist/controllers/skeleton_controller.js +259 -101
  91. data/dist/controllers/slider_controller.js +187 -86
  92. data/dist/controllers/smart_sticky_header_controller.js +173 -66
  93. data/dist/controllers/sortable_controller.js +26 -125
  94. data/dist/controllers/spinner_controller.js +288 -173
  95. data/dist/controllers/step_indicator_controller.js +105 -46
  96. data/dist/controllers/stepper_controller.js +120 -73
  97. data/dist/controllers/stick_to_bottom_controller.js +275 -140
  98. data/dist/controllers/sticky_observer_controller.js +220 -49
  99. data/dist/controllers/submit_once_controller.js +376 -227
  100. data/dist/controllers/switch_controller.js +201 -84
  101. data/dist/controllers/tabs_controller.js +41 -30
  102. data/dist/controllers/tags_input_controller.js +293 -173
  103. data/dist/controllers/textarea_autosize_controller.js +108 -74
  104. data/dist/controllers/theme_controller.js +140 -116
  105. data/dist/controllers/time_picker_controller.js +331 -127
  106. data/dist/controllers/toast_controller.js +244 -324
  107. data/dist/controllers/toggle_group_controller.js +252 -191
  108. data/dist/controllers/toolbar_controller.js +219 -120
  109. data/dist/controllers/tooltip_controller.js +158 -124
  110. data/dist/controllers/transition_controller.js +123 -119
  111. data/dist/controllers/tree_view_controller.js +283 -369
  112. data/dist/index.js +8573 -10410
  113. data/dist/positioning/index.js +154 -47
  114. data/lib/generators/stimeo/install/install_generator.rb +7 -2
  115. data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
  116. data/lib/stimeo/ui/version.rb +1 -1
  117. metadata +3 -3
@@ -1,8 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/direct_upload_controller.ts
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
+ }
4
9
 
5
- // src/utils/announce.ts
6
10
  function announce(message, options = {}) {
7
11
  const text = message.trim();
8
12
  if (text.length === 0) return;
@@ -12,43 +16,7 @@ function announce(message, options = {}) {
12
16
  })
13
17
  );
14
18
  }
15
- function fillTemplate(template, values) {
16
- return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
17
- const replacement = values[name];
18
- return replacement === void 0 ? match : String(replacement);
19
- });
20
- }
21
-
22
- // src/utils/before_cache_reset.ts
23
- var BeforeCacheReset = class _BeforeCacheReset {
24
- /** Every subscribed instance, iterated by the one shared document listener. */
25
- static #subscribers = /* @__PURE__ */ new Set();
26
- /** The shared listener; installed while at least one instance is subscribed. */
27
- static #onBeforeCache = () => {
28
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
29
- };
30
- #rewind;
31
- /** @param rewind - the pass that returns this controller's state to its initial form. */
32
- constructor(rewind) {
33
- this.#rewind = rewind;
34
- }
35
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
36
- activate() {
37
- const first = _BeforeCacheReset.#subscribers.size === 0;
38
- _BeforeCacheReset.#subscribers.add(this);
39
- if (first) {
40
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
41
- }
42
- }
43
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
44
- deactivate() {
45
- _BeforeCacheReset.#subscribers.delete(this);
46
- if (_BeforeCacheReset.#subscribers.size > 0) return;
47
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
48
- }
49
- };
50
19
 
51
- // src/utils/declared_value.ts
52
20
  function parseDeclared(raw, parse, fallback) {
53
21
  try {
54
22
  return parse(raw);
@@ -68,43 +36,24 @@ function validSelector(element, raw, fallback) {
68
36
  );
69
37
  }
70
38
 
71
- // src/utils/safe_timeout.ts
72
39
  var TimerRegistry = class {
73
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
74
40
  ids = /* @__PURE__ */ new Set();
75
- /**
76
- * Cancels a single tracked timer.
77
- *
78
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
79
- * registry), so callers can clear defensively without guarding.
80
- */
81
41
  clear(id) {
82
42
  if (this.ids.delete(id)) {
83
43
  this.cancel(id);
84
44
  }
85
45
  }
86
- /**
87
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
88
- * to guarantee no timer outlives the element.
89
- */
90
46
  clearAll() {
91
47
  for (const id of this.ids) {
92
48
  this.cancel(id);
93
49
  }
94
50
  this.ids.clear();
95
51
  }
96
- /** Number of timers currently tracked (pending). */
97
52
  get size() {
98
53
  return this.ids.size;
99
54
  }
100
55
  };
101
56
  var SafeTimeout = class extends TimerRegistry {
102
- /**
103
- * Schedules `callback` after `delay` ms and returns the timer id.
104
- *
105
- * The id is removed from the registry automatically when the timeout fires,
106
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
107
- */
108
57
  set(callback, delay) {
109
58
  const id = this.schedule(() => {
110
59
  this.ids.delete(id);
@@ -121,14 +70,48 @@ var SafeTimeout = class extends TimerRegistry {
121
70
  }
122
71
  };
123
72
 
124
- // src/utils/template_row.ts
73
+ var KeyedTimers = class {
74
+ #timers = new SafeTimeout();
75
+ #pending = /* @__PURE__ */ new Map();
76
+ set(key, callback, delay) {
77
+ this.clear(key);
78
+ const id = this.#timers.set(() => {
79
+ this.#pending.delete(key);
80
+ callback();
81
+ }, delay);
82
+ this.#pending.set(key, id);
83
+ }
84
+ clear(key) {
85
+ this.#timers.clear(this.#pending.get(key) ?? -1);
86
+ this.#pending.delete(key);
87
+ }
88
+ clearAll() {
89
+ this.#timers.clearAll();
90
+ this.#pending.clear();
91
+ }
92
+ has(key) {
93
+ return this.#pending.has(key);
94
+ }
95
+ };
96
+
97
+ var ListenerSet = class {
98
+ #abort = new AbortController();
99
+ add(target, type, handler, options) {
100
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
101
+ }
102
+ dispose() {
103
+ this.#abort.abort();
104
+ this.#abort = new AbortController();
105
+ }
106
+ };
107
+
125
108
  function cloneTemplateRoot(template) {
126
109
  const root = template.content.firstElementChild;
127
110
  return root instanceof HTMLElement ? root.cloneNode(true) : null;
128
111
  }
129
112
 
130
- // src/controllers/direct_upload_controller.ts
131
113
  var REMOVE_DELAY = 4e3;
114
+ var GENERATED_ATTRIBUTE = "generated";
132
115
  var DirectUploadController = class extends Controller {
133
116
  static targets = ["list", "row"];
134
117
  static values = {
@@ -138,10 +121,9 @@ var DirectUploadController = class extends Controller {
138
121
  scope: { type: String, default: "" }
139
122
  };
140
123
  static events = ["progress", "done", "error", "reconcile"];
141
- #timeouts = new SafeTimeout();
124
+ #removals = new KeyedTimers();
142
125
  #rows = /* @__PURE__ */ new Map();
143
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
144
- /** The validated `scope` selector; a broken declaration falls back to `""`. */
126
+ #connected = false;
145
127
  #scopeSelector = "";
146
128
  #onInitialize = (event) => {
147
129
  if (!this.#inScope(event)) return;
@@ -164,27 +146,46 @@ var DirectUploadController = class extends Controller {
164
146
  const detail = this.#detail(event);
165
147
  this.#complete(this.#key(detail.id), this.#name(detail));
166
148
  };
149
+ #listeners = new ListenerSet();
167
150
  connect() {
168
- document.addEventListener("direct-upload:initialize", this.#onInitialize);
169
- document.addEventListener("direct-upload:progress", this.#onProgress);
170
- document.addEventListener("direct-upload:error", this.#onError);
171
- document.addEventListener("direct-upload:end", this.#onEnd);
172
- this.#beforeCache.activate();
173
- this.#rescheduleRemovals();
151
+ this.#connected = true;
152
+ this.#listeners.add(document, "direct-upload:initialize", this.#onInitialize);
153
+ this.#listeners.add(document, "direct-upload:progress", this.#onProgress);
154
+ this.#listeners.add(document, "direct-upload:error", this.#onError);
155
+ this.#listeners.add(document, "direct-upload:end", this.#onEnd);
156
+ this.#discardInherited();
157
+ this.#syncRemovals();
174
158
  }
175
159
  disconnect() {
176
- document.removeEventListener("direct-upload:initialize", this.#onInitialize);
177
- document.removeEventListener("direct-upload:progress", this.#onProgress);
178
- document.removeEventListener("direct-upload:error", this.#onError);
179
- document.removeEventListener("direct-upload:end", this.#onEnd);
180
- this.#beforeCache.deactivate();
181
- this.#timeouts.clearAll();
182
- }
183
- /** Validates `scope` once so the per-event path never parses or throws. */
160
+ this.#connected = false;
161
+ this.#listeners.dispose();
162
+ this.#removals.clearAll();
163
+ }
164
+ get #generatedAttribute() {
165
+ return `data-${this.identifier}-${GENERATED_ATTRIBUTE}`;
166
+ }
167
+ #discardInherited() {
168
+ if (this.#rows.size > 0 || !this.hasListTarget) return;
169
+ const ids = [];
170
+ for (const row of Array.from(this.listTarget.children)) {
171
+ const id = row.getAttribute(this.#generatedAttribute);
172
+ if (id === null) continue;
173
+ row.remove();
174
+ ids.push(id);
175
+ }
176
+ if (ids.length === 0) return;
177
+ this.#syncAggregate();
178
+ this.dispatch("reconcile", { detail: { ids } });
179
+ }
184
180
  scopeValueChanged() {
185
181
  this.#scopeSelector = validSelector(this.element, this.scopeValue, "");
186
182
  }
187
- /** Updates a row's progress and the aggregate, emitting `progress`. */
183
+ removeOnDoneValueChanged() {
184
+ if (this.#connected) this.#syncRemovals();
185
+ }
186
+ listTargetDisconnected() {
187
+ if (this.#connected) this.#syncAggregate();
188
+ }
188
189
  #updateProgress(id, percent, name) {
189
190
  const row = this.#rowFor(id, name);
190
191
  if (row === null || this.#isSettled(row)) return;
@@ -192,12 +193,6 @@ var DirectUploadController = class extends Controller {
192
193
  this.#syncAggregate();
193
194
  this.dispatch("progress", { detail: { id, percent: clamped } });
194
195
  }
195
- /**
196
- * Marks a not-yet-settled row done at 100%, announces it, and emits `done`.
197
- *
198
- * @stimeoRuntimeOnly `announceDoneText` words the one announcement of this completion and
199
- * `removeOnDone` decides whether it arms the row's removal.
200
- */
201
196
  #complete(id, name) {
202
197
  const row = this.#rowFor(id, name);
203
198
  if (row === null || this.#isSettled(row)) return;
@@ -206,18 +201,8 @@ var DirectUploadController = class extends Controller {
206
201
  this.#syncAggregate();
207
202
  this.#announce(this.announceDoneTextValue, name, row);
208
203
  this.dispatch("done", { detail: { id } });
209
- if (this.removeOnDoneValue) {
210
- this.#timeouts.set(() => this.#removeRow(id), REMOVE_DELAY);
211
- }
204
+ if (this.removeOnDoneValue) this.#scheduleRemoval(id, row);
212
205
  }
213
- /**
214
- * Marks a not-yet-settled row failed, announces it, and emits `error`.
215
- * Returns whether the failure is rendered by this widget (used to decide the
216
- * `direct-upload:error` default), which also holds when the row already
217
- * displays an earlier failure.
218
- *
219
- * @stimeoRuntimeOnly `announceErrorText` words the one announcement of this failure.
220
- */
221
206
  #fail(id, error, name) {
222
207
  const row = this.#rowFor(id, name);
223
208
  if (row === null) return false;
@@ -227,27 +212,25 @@ var DirectUploadController = class extends Controller {
227
212
  this.dispatch("error", { detail: { id, error } });
228
213
  return true;
229
214
  }
230
- /** Whether the row reached a terminal state; settled rows are never rewritten. */
231
215
  #isSettled(row) {
232
216
  const state = row.getAttribute("data-upload-state");
233
217
  return state === "done" || state === "error";
234
218
  }
235
- /**
236
- * Re-arms `removeOnDone` for completed rows after a reconnect.
237
- *
238
- * @stimeoRuntimeOnly `removeOnDone` decides whether reconnecting arms the removal of finished
239
- * rows again.
240
- */
241
- #rescheduleRemovals() {
242
- if (!this.removeOnDoneValue) return;
219
+ #syncRemovals() {
220
+ if (!this.removeOnDoneValue) {
221
+ this.#removals.clearAll();
222
+ return;
223
+ }
243
224
  this.#prune();
244
225
  for (const [id, row] of this.#rows) {
245
- if (row.getAttribute("data-upload-state") === "done") {
246
- this.#timeouts.set(() => this.#removeRow(id), REMOVE_DELAY);
226
+ if (row.getAttribute("data-upload-state") === "done" && !this.#removals.has(id)) {
227
+ this.#scheduleRemoval(id, row);
247
228
  }
248
229
  }
249
230
  }
250
- /** Returns the live row for `id`, creating (and labeling) one on first sight. */
231
+ #scheduleRemoval(id, row) {
232
+ this.#removals.set(id, () => this.#removeRow(row), REMOVE_DELAY);
233
+ }
251
234
  #rowFor(id, name) {
252
235
  const key = this.#key(id);
253
236
  const existing = this.#rows.get(key);
@@ -256,13 +239,13 @@ var DirectUploadController = class extends Controller {
256
239
  this.#applyName(existing, name);
257
240
  return existing;
258
241
  }
259
- existing.remove();
260
- this.#rows.delete(key);
242
+ this.#retire(key, existing);
261
243
  }
262
244
  if (!this.hasRowTarget || !this.hasListTarget) return null;
263
245
  const clone = cloneTemplateRoot(this.rowTarget);
264
246
  if (!clone) return null;
265
247
  this.#applyName(clone, name);
248
+ clone.setAttribute(this.#generatedAttribute, key);
266
249
  clone.setAttribute("data-upload-state", "uploading");
267
250
  this.#applyProgress(clone, 0);
268
251
  this.listTarget.appendChild(clone);
@@ -270,20 +253,12 @@ var DirectUploadController = class extends Controller {
270
253
  this.#syncAggregate();
271
254
  return clone;
272
255
  }
273
- /**
274
- * Writes the event's file name into `[data-field="name"]` and, unless the row
275
- * already carries a non-blank `aria-label` — authored on the template or
276
- * applied by an earlier event — makes it the accessible name too. The visible
277
- * name never depends on the label: an authored label keeps its wording while
278
- * the field still shows which file this row tracks.
279
- */
280
256
  #applyName(row, name) {
281
257
  if (name.length === 0) return;
282
258
  this.#setField(row, "name", name);
283
259
  if ((row.getAttribute("aria-label") ?? "").trim().length > 0) return;
284
260
  row.setAttribute("aria-label", name);
285
261
  }
286
- /** Writes one progress value to every per-row hook; returns the clamped percent. */
287
262
  #applyProgress(row, percent) {
288
263
  const clamped = Math.max(0, Math.min(100, Math.round(percent)));
289
264
  row.setAttribute("aria-valuenow", String(clamped));
@@ -292,17 +267,10 @@ var DirectUploadController = class extends Controller {
292
267
  this.#setField(row, "percent", `${clamped}%`);
293
268
  return clamped;
294
269
  }
295
- #removeRow(id) {
296
- const row = this.#rows.get(id);
297
- if (row === void 0) return;
270
+ #removeRow(row) {
298
271
  row.remove();
299
- this.#rows.delete(id);
300
272
  this.#syncAggregate();
301
273
  }
302
- /**
303
- * Reflects the average progress across live rows on the controller element,
304
- * withdrawing both hooks once no rows remain.
305
- */
306
274
  #syncAggregate() {
307
275
  this.#prune();
308
276
  if (this.#rows.size === 0) {
@@ -318,50 +286,21 @@ var DirectUploadController = class extends Controller {
318
286
  this.element.setAttribute("data-upload-progress", String(overall));
319
287
  this.element.style.setProperty("--stimeo--upload-progress", `${overall}%`);
320
288
  }
321
- /**
322
- * Whether a bookkept row is still this widget's live UI: connected, and — when
323
- * a `list` target is present — inside the *current* one, so re-pointing the
324
- * target attribute at a new element retires rows kept alive in the old list.
325
- */
326
289
  #tracksRow(row) {
327
290
  if (!row.isConnected) return false;
328
291
  return !this.hasListTarget || this.listTarget.contains(row);
329
292
  }
330
- /**
331
- * Retires rows that left the live UI (list swap, external removal). A retired
332
- * clone is removed outright — generated rows only ever live under the current
333
- * `list`, so the before-cache rewind never has an untracked leftover to miss.
334
- */
335
293
  #prune() {
336
294
  for (const [id, row] of this.#rows) {
337
295
  if (this.#tracksRow(row)) continue;
338
- row.remove();
339
- this.#rows.delete(id);
296
+ this.#retire(id, row);
340
297
  }
341
298
  }
342
- /**
343
- * Rewinds for the snapshot and reports what that discarded. An upload in flight
344
- * cannot survive the navigation, so a consumer mirroring the rows would keep a
345
- * progress bar that never resolves.
346
- */
347
- #rewindForCache() {
348
- const ids = [...this.#rows.keys()];
349
- this.#reset();
350
- if (ids.length > 0) this.dispatch("reconcile", { detail: { ids } });
351
- }
352
- #reset() {
353
- for (const row of this.#rows.values()) row.remove();
354
- this.#rows.clear();
355
- this.#timeouts.clearAll();
356
- this.#syncAggregate();
299
+ #retire(id, row) {
300
+ row.remove();
301
+ this.#rows.delete(id);
302
+ this.#removals.clear(id);
357
303
  }
358
- /**
359
- * Sends one consumer-worded message to the page's shared announcer. `{name}`
360
- * resolves to the row's displayed name first — the event that settles an
361
- * upload may omit the file although an earlier event already named the row —
362
- * then the event's own name, then the accessible name (which an authored
363
- * label owns, so it is the last resort, not the primary source).
364
- */
365
304
  #announce(template, name, row) {
366
305
  const stored = this.#field(row, "name")?.textContent ?? "";
367
306
  const label = stored.length > 0 ? stored : name.length > 0 ? name : row.getAttribute("aria-label") ?? "";
@@ -377,17 +316,9 @@ var DirectUploadController = class extends Controller {
377
316
  #detail(event) {
378
317
  return event.detail ?? {};
379
318
  }
380
- /** The event's file name, or `""` when it carries none. */
381
319
  #name(detail) {
382
320
  return detail.file?.name ?? "";
383
321
  }
384
- /**
385
- * Whether an event belongs to this controller. With `scope` set, only events
386
- * whose target (the file input) sits inside an element matching `scope` are
387
- * handled, so several upload widgets on one page do not cross-populate.
388
- * Resolved with `closest()` from the target itself, so the chatty `progress`
389
- * stream never pays a document-wide query. Empty `scope` handles all.
390
- */
391
322
  #inScope(event) {
392
323
  if (this.#scopeSelector.length === 0) return true;
393
324
  const target = event.target;
@@ -399,5 +330,4 @@ var DirectUploadController = class extends Controller {
399
330
  };
400
331
 
401
332
  export { DirectUploadController };
402
- //# sourceMappingURL=direct_upload_controller.js.map
403
333
  //# sourceMappingURL=direct_upload_controller.js.map