stimeo-ui 0.1.0.pre.beta.3 → 0.2.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -0
  3. data/README.md +6 -6
  4. data/dist/controllers/alert_dialog_controller.js +318 -0
  5. data/dist/controllers/auto_submit_controller.js +56 -21
  6. data/dist/controllers/carousel_controller.js +272 -0
  7. data/dist/controllers/character_counter_controller.js +52 -15
  8. data/dist/controllers/clipboard_controller.js +144 -0
  9. data/dist/controllers/collapsible_controller.js +327 -0
  10. data/dist/controllers/color_picker_controller.js +213 -0
  11. data/dist/controllers/combobox_controller.js +64 -3
  12. data/dist/controllers/command_palette_controller.js +243 -39
  13. data/dist/controllers/confirm_controller.js +94 -8
  14. data/dist/controllers/context_menu_controller.js +191 -13
  15. data/dist/controllers/count_up_controller.js +8 -1
  16. data/dist/controllers/countdown_controller.js +2 -2
  17. data/dist/controllers/currency_input_controller.js +147 -0
  18. data/dist/controllers/data_grid_controller.js +168 -0
  19. data/dist/controllers/date_range_picker_controller.js +417 -0
  20. data/dist/controllers/dialog_controller.js +94 -8
  21. data/dist/controllers/dismissible_controller.js +117 -0
  22. data/dist/controllers/drawer_controller.js +630 -0
  23. data/dist/controllers/dropdown_controller.js +139 -16
  24. data/dist/controllers/editable_controller.js +168 -0
  25. data/dist/controllers/file_dropzone_controller.js +165 -0
  26. data/dist/controllers/filter_controller.js +86 -0
  27. data/dist/controllers/flash_controller.js +36 -5
  28. data/dist/controllers/focus_controller.js +94 -8
  29. data/dist/controllers/form_validation_controller.js +2 -2
  30. data/dist/controllers/highlight_controller.js +6 -4
  31. data/dist/controllers/hover_card_controller.js +103 -27
  32. data/dist/controllers/intersection_controller.js +41 -18
  33. data/dist/controllers/lazy_frame_controller.js +33 -11
  34. data/dist/controllers/listbox_controller.js +1 -0
  35. data/dist/controllers/masonry_controller.js +142 -0
  36. data/dist/controllers/menu_controller.js +181 -8
  37. data/dist/controllers/menubar_controller.js +433 -0
  38. data/dist/controllers/multi_select_controller.js +472 -0
  39. data/dist/controllers/navigation_menu_controller.js +384 -0
  40. data/dist/controllers/otp_controller.js +62 -22
  41. data/dist/controllers/overflow_indicator_controller.js +178 -27
  42. data/dist/controllers/password_reveal_controller.js +117 -0
  43. data/dist/controllers/pointer_drag_controller.js +3 -3
  44. data/dist/controllers/popover_controller.js +118 -27
  45. data/dist/controllers/range_slider_controller.js +166 -0
  46. data/dist/controllers/read_more_controller.js +194 -0
  47. data/dist/controllers/scroll_area_controller.js +15 -2
  48. data/dist/controllers/scroll_restore_controller.js +93 -0
  49. data/dist/controllers/scroll_visibility_controller.js +8 -4
  50. data/dist/controllers/scrollspy_controller.js +33 -11
  51. data/dist/controllers/separator_controller.js +87 -0
  52. data/dist/controllers/sidebar_controller.js +761 -0
  53. data/dist/controllers/stepper_controller.js +28 -12
  54. data/dist/controllers/stick_to_bottom_controller.js +8 -4
  55. data/dist/controllers/sticky_observer_controller.js +88 -20
  56. data/dist/controllers/tags_input_controller.js +275 -0
  57. data/dist/controllers/theme_controller.js +20 -10
  58. data/dist/controllers/time_picker_controller.js +212 -0
  59. data/dist/controllers/toast_controller.js +144 -38
  60. data/dist/controllers/tooltip_controller.js +148 -42
  61. data/dist/controllers/transition_controller.js +153 -38
  62. data/dist/controllers/tree_view_controller.js +275 -0
  63. data/dist/index.js +1644 -643
  64. data/lib/stimeo/ui/version.rb +5 -4
  65. metadata +28 -2
@@ -2,6 +2,90 @@ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  // src/controllers/tooltip_controller.ts
4
4
 
5
+ // src/utils/escape_layer.ts
6
+ var EscapeLayer = class _EscapeLayer {
7
+ static #registries = /* @__PURE__ */ new WeakMap();
8
+ #ownerDocument = null;
9
+ /** Dismissal callback while active; `null` when inactive. */
10
+ #onDismiss = null;
11
+ /** Live predicate deciding whether the layer claims a press; `null` = always. */
12
+ #claims = null;
13
+ /**
14
+ * Activates this layer at the top of its document's Escape stack, installing
15
+ * the document's shared resolver listener if this is its first layer.
16
+ * Re-activating an already-active layer moves it to the top.
17
+ */
18
+ activate(ownerDocument = document, options) {
19
+ this.deactivate();
20
+ let registry = _EscapeLayer.#registries.get(ownerDocument);
21
+ if (!registry) {
22
+ registry = _EscapeLayer.#createRegistry();
23
+ _EscapeLayer.#registries.set(ownerDocument, registry);
24
+ ownerDocument.addEventListener("keydown", registry.onKeydown);
25
+ }
26
+ registry.stack.push(this);
27
+ this.#ownerDocument = ownerDocument;
28
+ this.#onDismiss = options.onDismiss;
29
+ this.#claims = options.claims ?? null;
30
+ }
31
+ /**
32
+ * Removes this layer from its document's Escape stack, uninstalling the
33
+ * shared listener when the stack empties. Safe to call when inactive.
34
+ */
35
+ deactivate() {
36
+ const ownerDocument = this.#ownerDocument;
37
+ if (!ownerDocument) return;
38
+ const registry = _EscapeLayer.#registries.get(ownerDocument);
39
+ if (registry) {
40
+ const index = registry.stack.lastIndexOf(this);
41
+ if (index >= 0) registry.stack.splice(index, 1);
42
+ if (registry.stack.length === 0) {
43
+ ownerDocument.removeEventListener("keydown", registry.onKeydown);
44
+ _EscapeLayer.#registries.delete(ownerDocument);
45
+ }
46
+ }
47
+ this.#ownerDocument = null;
48
+ this.#onDismiss = null;
49
+ this.#claims = null;
50
+ }
51
+ /**
52
+ * Whether this active layer would own a press right now: it is the topmost
53
+ * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
54
+ * and diagnostics — production dismissal goes through the shared listener.
55
+ */
56
+ get ownsEscape() {
57
+ const ownerDocument = this.#ownerDocument;
58
+ if (!ownerDocument) return false;
59
+ const registry = _EscapeLayer.#registries.get(ownerDocument);
60
+ if (!registry) return false;
61
+ return _EscapeLayer.#resolveOwner(registry.stack) === this;
62
+ }
63
+ /** Builds a document's registry with its shared resolver listener. */
64
+ static #createRegistry() {
65
+ const registry = {
66
+ stack: [],
67
+ onKeydown: (event) => {
68
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
69
+ const owner = _EscapeLayer.#resolveOwner(registry.stack);
70
+ if (!owner) return;
71
+ event.preventDefault();
72
+ owner.#onDismiss?.();
73
+ }
74
+ };
75
+ return registry;
76
+ }
77
+ /** The topmost stack layer whose claims predicate passes, or `null`. */
78
+ static #resolveOwner(stack) {
79
+ for (let index = stack.length - 1; index >= 0; index--) {
80
+ const layer = stack[index];
81
+ if (!layer) continue;
82
+ if (layer.#claims && !layer.#claims()) continue;
83
+ return layer;
84
+ }
85
+ return null;
86
+ }
87
+ };
88
+
5
89
  // src/utils/safe_timeout.ts
6
90
  var TimerRegistry = class {
7
91
  /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
@@ -88,27 +172,39 @@ var TooltipController = class extends Controller {
88
172
  hideDelay: { type: Number, default: 0 },
89
173
  closeOnScroll: { type: Boolean, default: false }
90
174
  };
91
- static actions = ["hide", "onKeydown", "show"];
92
- /** Pending show/hide timers, torn down together on disconnect. */
175
+ static actions = ["hide", "show"];
176
+ /** Registry whose pending timers are cancelled individually with their guard ids. */
93
177
  #timers = new SafeTimeout();
94
- /** The id of the currently pending show or hide timer, if any. */
178
+ /** Escape-stack membership while shown; the shared resolver dismisses via it. */
179
+ #escapeLayer = new EscapeLayer();
180
+ /** The id of the currently pending show timer, if any. */
95
181
  #pendingShow = null;
182
+ /** The id of the currently pending hide timer, if any. */
96
183
  #pendingHide = null;
184
+ /** Whether focus or the pointer currently requires the tooltip to persist. */
185
+ #focusActive = false;
186
+ #pointerActive = false;
97
187
  /** Cleanup for the dismiss-on-scroll listeners while shown, or `null`. */
98
188
  #stopScrollDismiss = null;
99
- /** Starts hidden. */
189
+ /** Starts hidden with no stale timer or interaction state from a prior connection. */
100
190
  connect() {
191
+ this.#cancelShow();
192
+ this.#cancelHide();
193
+ this.#resetInteractionState();
101
194
  this.#conceal();
102
195
  }
103
- /** Clears timers and the document `Escape` / scroll listeners so nothing outlives the element. */
196
+ /** Clears timers, the Escape-stack membership, and scroll listeners so nothing outlives the element. */
104
197
  disconnect() {
105
- this.#timers.clearAll();
106
- document.removeEventListener("keydown", this.#onDocumentKeydown);
198
+ this.#cancelShow();
199
+ this.#cancelHide();
200
+ this.#resetInteractionState();
201
+ this.#escapeLayer.deactivate();
107
202
  this.#stopScrollDismiss?.();
108
203
  this.#stopScrollDismiss = null;
109
204
  }
110
- /** Shows the tooltip, after `showDelay` ms (or immediately at 0). Cancels a pending hide. */
111
- show() {
205
+ /** Shows after `showDelay`, recording the focus/pointer reason supplied by an action event. */
206
+ show(event) {
207
+ this.#activateInteraction(event);
112
208
  this.#cancelHide();
113
209
  if (this.#isVisible || this.#pendingShow !== null) return;
114
210
  if (this.showDelayValue <= 0) {
@@ -120,8 +216,13 @@ var TooltipController = class extends Controller {
120
216
  this.#reveal();
121
217
  }, this.showDelayValue);
122
218
  }
123
- /** Hides the tooltip, after `hideDelay` ms (or immediately at 0). Cancels a pending show. */
124
- hide() {
219
+ /** Hides after `hideDelay` once no focus/pointer reason remains; eventless calls are explicit. */
220
+ hide(event) {
221
+ const interactionEnded = this.#deactivateInteraction(event);
222
+ if (interactionEnded && this.#hasActiveInteraction) {
223
+ this.#cancelHide();
224
+ return;
225
+ }
125
226
  this.#cancelShow();
126
227
  if (!this.#isVisible || this.#pendingHide !== null) return;
127
228
  if (this.hideDelayValue <= 0) {
@@ -133,52 +234,57 @@ var TooltipController = class extends Controller {
133
234
  this.#conceal();
134
235
  }, this.hideDelayValue);
135
236
  }
136
- /**
137
- * Dismisses the tooltip on `Escape` from the trigger. The authoritative
138
- * dismissal path is the document-level listener (added while shown) so a
139
- * hover-triggered tooltip is dismissible regardless of focus; this handler
140
- * keeps the documented `keydown->#onKeydown` binding meaningful too.
141
- */
142
- onKeydown(event) {
143
- if (event.key === "Escape" && this.#isVisible) {
144
- event.preventDefault();
145
- this.#cancelShow();
146
- this.#cancelHide();
147
- this.#conceal();
148
- }
149
- }
150
- /** Reveals the content and starts watching for a dismissing `Escape`/scroll. */
237
+ /** Reveals the content and joins the Escape stack / starts the scroll watcher. */
151
238
  #reveal() {
152
239
  if (!this.hasContentTarget) return;
153
240
  this.contentTarget.hidden = false;
154
241
  this.contentTarget.setAttribute("data-state", "open");
155
- document.addEventListener("keydown", this.#onDocumentKeydown);
242
+ this.#escapeLayer.activate(document, { onDismiss: () => this.#dismiss() });
156
243
  if (this.closeOnScrollValue && !this.#stopScrollDismiss) {
157
- this.#stopScrollDismiss = observeScrollDismiss(this.element, () => {
158
- this.#cancelShow();
159
- this.#cancelHide();
160
- this.#conceal();
161
- });
244
+ this.#stopScrollDismiss = observeScrollDismiss(this.element, () => this.#dismiss());
162
245
  }
163
246
  }
164
- /** Hides the content and stops watching for `Escape`/scroll. */
247
+ /** Hides the content and leaves the Escape stack / stops the scroll watcher. */
165
248
  #conceal() {
166
- document.removeEventListener("keydown", this.#onDocumentKeydown);
249
+ this.#escapeLayer.deactivate();
167
250
  this.#stopScrollDismiss?.();
168
251
  this.#stopScrollDismiss = null;
169
252
  if (!this.hasContentTarget) return;
170
253
  this.contentTarget.hidden = true;
171
254
  this.contentTarget.setAttribute("data-state", "closed");
172
255
  }
173
- /** Document-level `Escape` watcher (active only while shown). */
174
- #onDocumentKeydown = (event) => {
175
- if (event.key === "Escape") {
176
- event.preventDefault();
177
- this.#cancelShow();
178
- this.#cancelHide();
179
- this.#conceal();
256
+ /** Cancels pending timers and conceals immediately (shared Escape / scroll path). */
257
+ #dismiss() {
258
+ this.#cancelShow();
259
+ this.#cancelHide();
260
+ this.#conceal();
261
+ }
262
+ /** Records the modality whose enter/focus event requires the tooltip to stay visible. */
263
+ #activateInteraction(event) {
264
+ if (event?.type === "mouseenter") this.#pointerActive = true;
265
+ if (event?.type === "focusin") this.#focusActive = true;
266
+ }
267
+ /** Clears a modality on leave/blur and reports whether the event represented such a change. */
268
+ #deactivateInteraction(event) {
269
+ if (event?.type === "mouseleave") {
270
+ this.#pointerActive = false;
271
+ return true;
180
272
  }
181
- };
273
+ if (event?.type === "focusout") {
274
+ this.#focusActive = false;
275
+ return true;
276
+ }
277
+ return false;
278
+ }
279
+ /** Discards interaction reasons at a lifecycle boundary. */
280
+ #resetInteractionState() {
281
+ this.#focusActive = false;
282
+ this.#pointerActive = false;
283
+ }
284
+ /** Whether focus or pointer presence still requires a persistent tooltip. */
285
+ get #hasActiveInteraction() {
286
+ return this.#focusActive || this.#pointerActive;
287
+ }
182
288
  /** Cancels any pending show timer. */
183
289
  #cancelShow() {
184
290
  if (this.#pendingShow !== null) {
@@ -2,6 +2,11 @@ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  // src/controllers/transition_controller.ts
4
4
 
5
+ // src/utils/reduced_motion.ts
6
+ function prefersReducedMotion() {
7
+ return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
8
+ }
9
+
5
10
  // src/utils/safe_timeout.ts
6
11
  var TimerRegistry = class {
7
12
  /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
@@ -55,14 +60,149 @@ var SafeTimeout = class extends TimerRegistry {
55
60
  }
56
61
  };
57
62
 
63
+ // src/utils/transition_completion.ts
64
+ function timeMs(value) {
65
+ const trimmed = value.trim();
66
+ const amount = Number.parseFloat(trimmed);
67
+ if (!Number.isFinite(amount)) return 0;
68
+ if (trimmed.endsWith("ms")) return amount;
69
+ if (trimmed.endsWith("s")) return amount * 1e3;
70
+ return 0;
71
+ }
72
+ function cssList(value) {
73
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
74
+ }
75
+ function transitionTimings(style) {
76
+ const properties = cssList(style.transitionProperty);
77
+ const durations = cssList(style.transitionDuration).map(timeMs);
78
+ const delays = cssList(style.transitionDelay).map(timeMs);
79
+ const effectiveProperties = properties.length > 0 ? properties : Array.from({ length: Math.max(durations.length, delays.length, 1) }, () => "all");
80
+ const effectiveDurations = durations.length > 0 ? durations : [0];
81
+ const effectiveDelays = delays.length > 0 ? delays : [0];
82
+ return effectiveProperties.filter((property) => property !== "none").map((property, index) => ({
83
+ property,
84
+ totalMs: Math.max(
85
+ 0,
86
+ (effectiveDurations[index % effectiveDurations.length] ?? 0) + (effectiveDelays[index % effectiveDelays.length] ?? 0)
87
+ )
88
+ }));
89
+ }
90
+ function maxTotalMs(timings) {
91
+ return timings.reduce((max, { totalMs }) => Math.max(max, totalMs), 0);
92
+ }
93
+ var TransitionCompletion = class {
94
+ #timers = new SafeTimeout();
95
+ #element = null;
96
+ #complete = null;
97
+ #pendingProperties = null;
98
+ #deadline = 0;
99
+ /**
100
+ * Replaces any prior wait and invokes `complete` synchronously for a 0ms
101
+ * transition (including when `getComputedStyle` is unavailable).
102
+ *
103
+ * With a positive `options.timeoutMs` the synchronous fast-path is skipped and
104
+ * the override replaces the auto-computed fallback (see {@link TransitionWaitOptions}).
105
+ */
106
+ wait(element, complete, options = {}) {
107
+ this.cancel();
108
+ const requested = options.timeoutMs ?? 0;
109
+ const override = Number.isFinite(requested) && requested > 0 ? requested : 0;
110
+ const timings = typeof window.getComputedStyle === "function" ? transitionTimings(window.getComputedStyle(element)) : [];
111
+ const maximum = maxTotalMs(timings);
112
+ if (maximum <= 0 && override <= 0) {
113
+ complete();
114
+ return;
115
+ }
116
+ this.#element = element;
117
+ this.#complete = complete;
118
+ this.#deadline = Date.now() + maximum;
119
+ const activeProperties = this.#activeTransitionProperties(element);
120
+ this.#pendingProperties = activeProperties.size > 0 ? activeProperties : this.#explicitPendingProperties(timings);
121
+ element.addEventListener("transitionend", this.#onTerminal);
122
+ element.addEventListener("transitioncancel", this.#onTerminal);
123
+ this.#timers.set(() => this.#finish(), override > 0 ? override : maximum + 50);
124
+ }
125
+ /** Cancels the pending wait without invoking its completion callback. */
126
+ cancel() {
127
+ this.#complete = null;
128
+ this.#teardown();
129
+ }
130
+ /**
131
+ * Handles terminal events from the observed element only.
132
+ *
133
+ * For explicit property lists, every declared positive-time property must
134
+ * settle. For `all`, no reliable property set exists, so an event can finish
135
+ * only after the computed maximum time; the safety timeout owns the usual path.
136
+ */
137
+ #onTerminal = (event) => {
138
+ if (event.target !== this.#element) return;
139
+ const transitionEvent = event;
140
+ if (transitionEvent.pseudoElement) return;
141
+ if (this.#pendingProperties) {
142
+ const propertyName = transitionEvent.propertyName;
143
+ const active = this.#activeTransitionProperties(this.#element);
144
+ if (active.has(propertyName)) return;
145
+ if (!this.#pendingProperties.delete(propertyName)) return;
146
+ if (this.#pendingProperties.size > 0) return;
147
+ if (active.size > 0) {
148
+ this.#pendingProperties = active;
149
+ return;
150
+ }
151
+ this.#finish();
152
+ return;
153
+ }
154
+ if (Date.now() >= this.#deadline) this.#finish();
155
+ };
156
+ /**
157
+ * Returns active CSS transition properties expanded to the names reported by
158
+ * terminal events. CSS animations and pseudo-element effects are excluded.
159
+ */
160
+ #activeTransitionProperties(element) {
161
+ if (!element || typeof element.getAnimations !== "function") return /* @__PURE__ */ new Set();
162
+ try {
163
+ const properties = element.getAnimations().flatMap((animation) => {
164
+ if (animation.playState === "idle" || animation.playState === "finished") return [];
165
+ const effect = animation.effect;
166
+ if (effect?.pseudoElement) return [];
167
+ const target = effect?.target;
168
+ if (target && target !== element) return [];
169
+ const property = animation.transitionProperty;
170
+ return typeof property === "string" && property.length > 0 ? [property] : [];
171
+ });
172
+ return new Set(properties);
173
+ } catch {
174
+ return /* @__PURE__ */ new Set();
175
+ }
176
+ }
177
+ /** Returns explicit positive-time properties, or `null` for the ambiguous `all`. */
178
+ #explicitPendingProperties(timings) {
179
+ if (timings.some(({ property }) => property === "all")) return null;
180
+ const pending = new Set(
181
+ timings.filter(({ totalMs }) => totalMs > 0).map(({ property }) => property)
182
+ );
183
+ return pending.size > 0 ? pending : null;
184
+ }
185
+ /** Completes exactly once, releasing listeners and the fallback before the callback. */
186
+ #finish() {
187
+ const complete = this.#complete;
188
+ if (!complete) return;
189
+ this.#complete = null;
190
+ this.#teardown();
191
+ complete();
192
+ }
193
+ /** Releases the exact element listeners and timer owned by the current wait. */
194
+ #teardown() {
195
+ this.#timers.clearAll();
196
+ this.#element?.removeEventListener("transitionend", this.#onTerminal);
197
+ this.#element?.removeEventListener("transitioncancel", this.#onTerminal);
198
+ this.#element = null;
199
+ this.#pendingProperties = null;
200
+ this.#deadline = 0;
201
+ }
202
+ };
203
+
58
204
  // src/controllers/transition_controller.ts
59
205
  var tokensOf = (value) => value.split(/\s+/).filter(Boolean);
60
- var firstTimeMs = (value) => {
61
- const first = value.split(",")[0]?.trim() ?? "";
62
- const amount = Number.parseFloat(first);
63
- if (Number.isNaN(amount)) return 0;
64
- return first.endsWith("ms") ? amount : amount * 1e3;
65
- };
66
206
  var TransitionController = class extends Controller {
67
207
  static values = {
68
208
  enter: { type: String, default: "" },
@@ -75,9 +215,9 @@ var TransitionController = class extends Controller {
75
215
  };
76
216
  static actions = ["enter", "leave", "toggle"];
77
217
  static events = ["entered", "left"];
78
- #timers = new SafeTimeout();
218
+ /** Owns the cancellable completion wait (terminal events + bounded fallback). */
219
+ #transition = new TransitionCompletion();
79
220
  #rafId = null;
80
- #endListener = null;
81
221
  connect() {
82
222
  this.#strip();
83
223
  this.element.setAttribute("data-transition-state", this.element.hidden ? "left" : "entered");
@@ -104,7 +244,7 @@ var TransitionController = class extends Controller {
104
244
  const isEnter = kind === "enter";
105
245
  if (isEnter) this.element.hidden = false;
106
246
  this.element.setAttribute("data-transition-state", isEnter ? "entering" : "leaving");
107
- if (this.#prefersReducedMotion()) {
247
+ if (prefersReducedMotion()) {
108
248
  this.#finish(kind);
109
249
  return;
110
250
  }
@@ -116,12 +256,13 @@ var TransitionController = class extends Controller {
116
256
  this.#rafId = null;
117
257
  this.#remove(from);
118
258
  this.#add(to);
119
- this.#awaitEnd(() => this.#finish(kind));
259
+ this.#transition.wait(this.element, () => this.#finish(kind), {
260
+ timeoutMs: this.timeoutValue
261
+ });
120
262
  });
121
263
  }
122
264
  /** Settles the element into the completed state, clearing the stage classes. */
123
265
  #finish(kind) {
124
- this.#cleanupEnd();
125
266
  this.#strip();
126
267
  if (kind === "enter") {
127
268
  this.element.setAttribute("data-transition-state", "entered");
@@ -132,29 +273,13 @@ var TransitionController = class extends Controller {
132
273
  this.dispatch("left", { detail: {} });
133
274
  }
134
275
  }
135
- /** Resolves on the element's own `transitionend`, with a safety timeout fallback. */
136
- #awaitEnd(done) {
137
- this.#endListener = (event) => {
138
- if (event.target === this.element) done();
139
- };
140
- this.element.addEventListener("transitionend", this.#endListener);
141
- const ms = this.timeoutValue > 0 ? this.timeoutValue : this.#duration();
142
- this.#timers.set(done, ms);
143
- }
144
- #cleanupEnd() {
145
- if (this.#endListener) {
146
- this.element.removeEventListener("transitionend", this.#endListener);
147
- this.#endListener = null;
148
- }
149
- this.#timers.clearAll();
150
- }
151
276
  /** Cancels any in-flight transition (interruption / teardown). */
152
277
  #cancel() {
153
278
  if (this.#rafId !== null) {
154
279
  this.#cancelRaf(this.#rafId);
155
280
  this.#rafId = null;
156
281
  }
157
- this.#cleanupEnd();
282
+ this.#transition.cancel();
158
283
  this.#strip();
159
284
  }
160
285
  #add(...lists) {
@@ -176,13 +301,6 @@ var TransitionController = class extends Controller {
176
301
  this.leaveToValue
177
302
  );
178
303
  }
179
- /** Auto-computed safety duration (transition time + delay, with a small buffer). */
180
- #duration() {
181
- if (typeof window.getComputedStyle !== "function") return 0;
182
- const style = window.getComputedStyle(this.element);
183
- const total = firstTimeMs(style.transitionDuration) + firstTimeMs(style.transitionDelay);
184
- return total > 0 ? total + 50 : 0;
185
- }
186
304
  #raf(callback) {
187
305
  if (typeof window.requestAnimationFrame === "function") {
188
306
  return window.requestAnimationFrame(() => callback());
@@ -193,9 +311,6 @@ var TransitionController = class extends Controller {
193
311
  if (typeof window.cancelAnimationFrame === "function") window.cancelAnimationFrame(id);
194
312
  else window.clearTimeout(id);
195
313
  }
196
- #prefersReducedMotion() {
197
- return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
198
- }
199
314
  };
200
315
 
201
316
  export { TransitionController };