stimeo-ui 0.2.0 → 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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/dist/controllers/alert_dialog_controller.js +318 -0
  4. data/dist/controllers/carousel_controller.js +272 -0
  5. data/dist/controllers/clipboard_controller.js +144 -0
  6. data/dist/controllers/collapsible_controller.js +327 -0
  7. data/dist/controllers/color_picker_controller.js +213 -0
  8. data/dist/controllers/count_up_controller.js +8 -1
  9. data/dist/controllers/currency_input_controller.js +147 -0
  10. data/dist/controllers/data_grid_controller.js +168 -0
  11. data/dist/controllers/date_range_picker_controller.js +417 -0
  12. data/dist/controllers/dismissible_controller.js +117 -0
  13. data/dist/controllers/drawer_controller.js +630 -0
  14. data/dist/controllers/editable_controller.js +168 -0
  15. data/dist/controllers/file_dropzone_controller.js +165 -0
  16. data/dist/controllers/filter_controller.js +86 -0
  17. data/dist/controllers/flash_controller.js +36 -5
  18. data/dist/controllers/highlight_controller.js +6 -4
  19. data/dist/controllers/intersection_controller.js +41 -18
  20. data/dist/controllers/lazy_frame_controller.js +33 -11
  21. data/dist/controllers/masonry_controller.js +142 -0
  22. data/dist/controllers/menubar_controller.js +433 -0
  23. data/dist/controllers/multi_select_controller.js +472 -0
  24. data/dist/controllers/navigation_menu_controller.js +384 -0
  25. data/dist/controllers/overflow_indicator_controller.js +178 -27
  26. data/dist/controllers/password_reveal_controller.js +117 -0
  27. data/dist/controllers/range_slider_controller.js +166 -0
  28. data/dist/controllers/read_more_controller.js +194 -0
  29. data/dist/controllers/scroll_area_controller.js +15 -2
  30. data/dist/controllers/scroll_restore_controller.js +93 -0
  31. data/dist/controllers/scroll_visibility_controller.js +8 -4
  32. data/dist/controllers/scrollspy_controller.js +33 -11
  33. data/dist/controllers/separator_controller.js +87 -0
  34. data/dist/controllers/sidebar_controller.js +761 -0
  35. data/dist/controllers/stepper_controller.js +28 -12
  36. data/dist/controllers/stick_to_bottom_controller.js +8 -4
  37. data/dist/controllers/sticky_observer_controller.js +88 -20
  38. data/dist/controllers/tags_input_controller.js +275 -0
  39. data/dist/controllers/theme_controller.js +20 -10
  40. data/dist/controllers/time_picker_controller.js +212 -0
  41. data/dist/controllers/toast_controller.js +36 -9
  42. data/dist/controllers/transition_controller.js +153 -38
  43. data/dist/controllers/tree_view_controller.js +275 -0
  44. data/dist/index.js +811 -295
  45. data/lib/stimeo/ui/version.rb +1 -1
  46. metadata +28 -2
@@ -0,0 +1,212 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ // src/controllers/time_picker_controller.ts
4
+ var AM = 0;
5
+ var PM = 1;
6
+ var TimePickerController = class extends Controller {
7
+ static targets = ["segment", "field"];
8
+ static values = {
9
+ hourCycle: { type: Number, default: 24 },
10
+ step: { type: Number, default: 1 },
11
+ seconds: { type: Boolean, default: false },
12
+ wrap: { type: Boolean, default: true }
13
+ };
14
+ static actions = ["onKeydown"];
15
+ static events = ["change"];
16
+ /** Current numeric value per segment kind (hours are the *displayed* hours). */
17
+ #state = { hour: 0, minute: 0, second: 0, meridiem: AM };
18
+ /** Direct-entry digit buffer and the segment it belongs to. */
19
+ #typeBuffer = "";
20
+ #typeSegment = null;
21
+ /** Last composed field value, to suppress duplicate `change` dispatches. */
22
+ #lastValue = "";
23
+ /** Seeds each segment from its initial `aria-valuenow` and syncs the field. */
24
+ connect() {
25
+ for (const segment of this.segmentTargets) {
26
+ const kind = this.#kindOf(segment);
27
+ if (!kind) continue;
28
+ const now = Number(segment.getAttribute("aria-valuenow"));
29
+ const { min, max } = this.#bounds(kind);
30
+ this.#state[kind] = Number.isFinite(now) ? Math.min(max, Math.max(min, now)) : min;
31
+ }
32
+ for (const segment of this.segmentTargets) this.#renderSegment(segment);
33
+ this.#syncField(false);
34
+ }
35
+ /** Handles stepping, inter-segment focus moves, jumps, and direct entry. */
36
+ onKeydown(event) {
37
+ const segment = event.target?.closest(
38
+ "[data-stimeo--time-picker-target='segment']"
39
+ );
40
+ const kind = segment ? this.#kindOf(segment) : null;
41
+ if (!segment || !kind) return;
42
+ switch (event.key) {
43
+ case "ArrowUp":
44
+ event.preventDefault();
45
+ this.#step(kind, this.#delta(kind));
46
+ break;
47
+ case "ArrowDown":
48
+ event.preventDefault();
49
+ this.#step(kind, -this.#delta(kind));
50
+ break;
51
+ case "ArrowLeft":
52
+ event.preventDefault();
53
+ this.#focusSibling(segment, -1);
54
+ break;
55
+ case "ArrowRight":
56
+ event.preventDefault();
57
+ this.#focusSibling(segment, 1);
58
+ break;
59
+ case "Home":
60
+ event.preventDefault();
61
+ this.#set(kind, this.#bounds(kind).min);
62
+ break;
63
+ case "End":
64
+ event.preventDefault();
65
+ this.#set(kind, this.#bounds(kind).max);
66
+ break;
67
+ default:
68
+ if (/^[0-9]$/.test(event.key)) {
69
+ event.preventDefault();
70
+ this.#typeDigit(segment, kind, event.key);
71
+ }
72
+ return;
73
+ }
74
+ this.#typeBuffer = "";
75
+ this.#typeSegment = null;
76
+ }
77
+ /** The per-step delta: minutes step by `step`, others by 1, meridiem toggles. */
78
+ #delta(kind) {
79
+ return kind === "minute" ? this.stepValue : 1;
80
+ }
81
+ /** Steps a segment, wrapping at its bounds and carrying over when enabled. */
82
+ #step(kind, delta) {
83
+ if (kind === "meridiem") {
84
+ this.#set("meridiem", this.#state.meridiem === AM ? PM : AM);
85
+ return;
86
+ }
87
+ const { min, max } = this.#bounds(kind);
88
+ const span = max - min + 1;
89
+ const raw = this.#state[kind] + delta;
90
+ if (raw > max || raw < min) {
91
+ if (!this.wrapValue) {
92
+ this.#set(kind, Math.min(max, Math.max(min, raw)));
93
+ return;
94
+ }
95
+ const wrapped = ((raw - min) % span + span) % span + min;
96
+ const carry = Math.floor((raw - min) / span);
97
+ this.#set(kind, wrapped);
98
+ this.#carry(kind, carry);
99
+ return;
100
+ }
101
+ this.#set(kind, raw);
102
+ }
103
+ /** Propagates a wrap carry from `kind` into the next larger segment. */
104
+ #carry(kind, amount) {
105
+ if (amount === 0) return;
106
+ if (kind === "second") this.#step("minute", amount);
107
+ else if (kind === "minute") this.#step("hour", amount);
108
+ }
109
+ /**
110
+ * Sets a segment's value (clamped to its `[min, max]` bounds), re-renders it,
111
+ * and resyncs the composed field. Clamping here guards the direct-entry path:
112
+ * typing `0` into a 12-hour hour (min 1) must not commit an out-of-range
113
+ * `aria-valuenow="0"`. The stepping path already passes in-bounds values, so
114
+ * the clamp is a no-op there.
115
+ */
116
+ #set(kind, value) {
117
+ const { min, max } = this.#bounds(kind);
118
+ this.#state[kind] = Math.min(max, Math.max(min, value));
119
+ const segment = this.segmentTargets.find((s) => this.#kindOf(s) === kind);
120
+ if (segment) this.#renderSegment(segment);
121
+ this.#syncField(true);
122
+ }
123
+ /** Accumulates a typed digit, committing and advancing after two digits. */
124
+ #typeDigit(segment, kind, digit) {
125
+ if (kind === "meridiem") return;
126
+ if (this.#typeSegment !== kind) this.#typeBuffer = "";
127
+ this.#typeSegment = kind;
128
+ const { max } = this.#bounds(kind);
129
+ const candidate = Number(`${this.#typeBuffer}${digit}`);
130
+ if (candidate <= max) this.#typeBuffer = `${this.#typeBuffer}${digit}`;
131
+ else this.#typeBuffer = digit;
132
+ this.#set(kind, Number(this.#typeBuffer));
133
+ if (this.#typeBuffer.length >= 2 || Number(this.#typeBuffer) * 10 > max) {
134
+ this.#typeBuffer = "";
135
+ this.#typeSegment = null;
136
+ this.#focusSibling(segment, 1);
137
+ }
138
+ }
139
+ /** Moves focus to the previous/next segment, if one exists. */
140
+ #focusSibling(segment, direction) {
141
+ const index = this.segmentTargets.indexOf(segment);
142
+ const next = this.segmentTargets[index + direction];
143
+ next?.focus();
144
+ }
145
+ /** Reflects a segment's current value onto its ARIA/text representation. */
146
+ #renderSegment(segment) {
147
+ const kind = this.#kindOf(segment);
148
+ if (!kind) return;
149
+ const value = this.#state[kind];
150
+ if (kind === "meridiem") {
151
+ const text2 = value === PM ? "PM" : "AM";
152
+ segment.setAttribute("aria-valuenow", String(value));
153
+ segment.setAttribute("aria-valuetext", text2);
154
+ segment.setAttribute("aria-valuemin", String(AM));
155
+ segment.setAttribute("aria-valuemax", String(PM));
156
+ segment.textContent = text2;
157
+ return;
158
+ }
159
+ const { min, max } = this.#bounds(kind);
160
+ const text = String(value).padStart(2, "0");
161
+ segment.setAttribute("aria-valuenow", String(value));
162
+ segment.setAttribute("aria-valuetext", text);
163
+ segment.setAttribute("aria-valuemin", String(min));
164
+ segment.setAttribute("aria-valuemax", String(max));
165
+ segment.textContent = text;
166
+ }
167
+ /** Composes `HH:MM[:SS]` (24-hour) into the hidden field; dispatches `change`. */
168
+ #syncField(notify) {
169
+ const h24 = this.#hours24();
170
+ const parts = [pad(h24), pad(this.#state.minute)];
171
+ if (this.secondsValue) parts.push(pad(this.#state.second));
172
+ const value = parts.join(":");
173
+ if (this.hasFieldTarget && this.fieldTarget.value !== value) {
174
+ this.fieldTarget.value = value;
175
+ }
176
+ if (notify && value !== this.#lastValue) this.dispatch("change", { detail: { value } });
177
+ this.#lastValue = value;
178
+ }
179
+ /** Converts the displayed hour (+ meridiem in 12-hour mode) to 24-hour. */
180
+ #hours24() {
181
+ if (this.hourCycleValue !== 12) return this.#state.hour;
182
+ const base = this.#state.hour % 12;
183
+ return base + (this.#state.meridiem === PM ? 12 : 0);
184
+ }
185
+ /** The inclusive `[min, max]` bounds for a segment kind. */
186
+ #bounds(kind) {
187
+ switch (kind) {
188
+ case "hour":
189
+ return this.hourCycleValue === 12 ? { min: 1, max: 12 } : { min: 0, max: 23 };
190
+ case "minute":
191
+ case "second":
192
+ return { min: 0, max: 59 };
193
+ case "meridiem":
194
+ return { min: AM, max: PM };
195
+ }
196
+ }
197
+ /** Reads a segment's declared kind, or null when absent/invalid. */
198
+ #kindOf(segment) {
199
+ const kind = segment.getAttribute("data-segment");
200
+ if (kind === "hour" || kind === "minute" || kind === "second" || kind === "meridiem") {
201
+ return kind;
202
+ }
203
+ return null;
204
+ }
205
+ };
206
+ function pad(value) {
207
+ return String(value).padStart(2, "0");
208
+ }
209
+
210
+ export { TimePickerController };
211
+ //# sourceMappingURL=time_picker_controller.js.map
212
+ //# sourceMappingURL=time_picker_controller.js.map
@@ -55,6 +55,40 @@ var SafeTimeout = class extends TimerRegistry {
55
55
  }
56
56
  };
57
57
 
58
+ // src/utils/transition_completion.ts
59
+ function timeMs(value) {
60
+ const trimmed = value.trim();
61
+ const amount = Number.parseFloat(trimmed);
62
+ if (!Number.isFinite(amount)) return 0;
63
+ if (trimmed.endsWith("ms")) return amount;
64
+ if (trimmed.endsWith("s")) return amount * 1e3;
65
+ return 0;
66
+ }
67
+ function cssList(value) {
68
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
69
+ }
70
+ function transitionTimings(style) {
71
+ const properties = cssList(style.transitionProperty);
72
+ const durations = cssList(style.transitionDuration).map(timeMs);
73
+ const delays = cssList(style.transitionDelay).map(timeMs);
74
+ const effectiveProperties = properties.length > 0 ? properties : Array.from({ length: Math.max(durations.length, delays.length, 1) }, () => "all");
75
+ const effectiveDurations = durations.length > 0 ? durations : [0];
76
+ const effectiveDelays = delays.length > 0 ? delays : [0];
77
+ return effectiveProperties.filter((property) => property !== "none").map((property, index) => ({
78
+ property,
79
+ totalMs: Math.max(
80
+ 0,
81
+ (effectiveDurations[index % effectiveDurations.length] ?? 0) + (effectiveDelays[index % effectiveDelays.length] ?? 0)
82
+ )
83
+ }));
84
+ }
85
+ function maxTotalMs(timings) {
86
+ return timings.reduce((max, { totalMs }) => Math.max(max, totalMs), 0);
87
+ }
88
+ function maxTransitionTotalMs(style) {
89
+ return maxTotalMs(transitionTimings(style));
90
+ }
91
+
58
92
  // src/controllers/toast_controller.ts
59
93
  var DELEGATED_EVENTS = ["click", "focusin", "focusout", "keydown", "mouseover", "mouseout"];
60
94
  var ToastController = class extends Controller {
@@ -304,8 +338,7 @@ var ToastController = class extends Controller {
304
338
  this.listTarget.removeChild(element);
305
339
  this.dispatch("dismiss", { detail: { item: element, reason } });
306
340
  };
307
- const transitions = window.getComputedStyle(element).transitionDuration;
308
- const duration = cssTimeToMs(transitions);
341
+ const duration = maxTransitionTotalMs(window.getComputedStyle(element));
309
342
  if (duration > 0) {
310
343
  this.#timers.set(finalize, duration);
311
344
  } else {
@@ -377,13 +410,7 @@ var ToastController = class extends Controller {
377
410
  this.#rafHandles.delete(element);
378
411
  }
379
412
  };
380
- function cssTimeToMs(value) {
381
- const first = value.split(",")[0]?.trim() ?? "";
382
- const amount = Number.parseFloat(first);
383
- if (Number.isNaN(amount)) return 0;
384
- return first.endsWith("ms") ? amount : amount * 1e3;
385
- }
386
413
 
387
- export { ToastController, cssTimeToMs };
414
+ export { ToastController };
388
415
  //# sourceMappingURL=toast_controller.js.map
389
416
  //# sourceMappingURL=toast_controller.js.map
@@ -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 };