@yorozu/animations 0.3.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/index.d.ts +1 -0
  4. package/index.js +2229 -0
  5. package/package.json +21 -0
  6. package/playground/src/catalog.d.ts +8 -0
  7. package/playground/src/level-switch.d.ts +1 -0
  8. package/playground/src/level.d.ts +7 -0
  9. package/playground/src/main.d.ts +0 -0
  10. package/playground/src/overlay.d.ts +2 -0
  11. package/playground/src/testers/digit-flip.d.ts +1 -0
  12. package/playground/src/testers/dock.d.ts +1 -0
  13. package/playground/src/testers/fade.d.ts +1 -0
  14. package/playground/src/testers/list-layer.d.ts +1 -0
  15. package/playground/src/testers/list-reorder.d.ts +1 -0
  16. package/playground/src/testers/pinch-zoom.d.ts +1 -0
  17. package/playground/src/testers/popover.d.ts +1 -0
  18. package/playground/src/testers/presence-pop.d.ts +1 -0
  19. package/playground/src/testers/ripple.d.ts +1 -0
  20. package/playground/src/testers/scroll-tween.d.ts +1 -0
  21. package/playground/src/testers/send-flight.d.ts +1 -0
  22. package/playground/src/testers/shared-element.d.ts +1 -0
  23. package/playground/src/testers/sliding-indicator.d.ts +1 -0
  24. package/playground/src/testers/spoiler.d.ts +1 -0
  25. package/playground/src/testers/swipe-reveal.d.ts +1 -0
  26. package/playground/src/testers/tabs.d.ts +1 -0
  27. package/playground/src/testers/view-slide-modes.d.ts +3 -0
  28. package/playground/src/testers/view-slide.d.ts +1 -0
  29. package/playground/src/testers/waveform.d.ts +1 -0
  30. package/src/core/level.d.ts +10 -0
  31. package/src/core/playback.d.ts +7 -0
  32. package/src/core/raf.d.ts +1 -0
  33. package/src/core/reduced-motion.d.ts +1 -0
  34. package/src/core/styles.d.ts +2 -0
  35. package/src/core/tween.d.ts +11 -0
  36. package/src/core/types.d.ts +9 -0
  37. package/src/digit-flip/play.d.ts +12 -0
  38. package/src/digit-flip/slots.d.ts +14 -0
  39. package/src/dock/dock.d.ts +23 -0
  40. package/src/dock/transforms.d.ts +14 -0
  41. package/src/fade/fade.d.ts +11 -0
  42. package/src/index.d.ts +45 -0
  43. package/src/list-reorder/classify.d.ts +10 -0
  44. package/src/list-reorder/reorder.d.ts +19 -0
  45. package/src/pinch-zoom/math.d.ts +21 -0
  46. package/src/pinch-zoom/pinch.d.ts +15 -0
  47. package/src/popover/popover.d.ts +14 -0
  48. package/src/rect/fit.d.ts +5 -0
  49. package/src/rect/types.d.ts +17 -0
  50. package/src/ripple/ripple.d.ts +13 -0
  51. package/src/scroll-tween/scroll.d.ts +8 -0
  52. package/src/send-flight/flight.d.ts +14 -0
  53. package/src/shared-element/math.d.ts +45 -0
  54. package/src/shared-element/player.d.ts +52 -0
  55. package/src/sliding-indicator/indicator.d.ts +15 -0
  56. package/src/spoiler/spoiler.d.ts +14 -0
  57. package/src/swipe-reveal/swipe.d.ts +16 -0
  58. package/src/view-slide/session.d.ts +27 -0
  59. package/src/view-slide/transforms.d.ts +34 -0
  60. package/src/waveform/decode.d.ts +2 -0
package/index.js ADDED
@@ -0,0 +1,2229 @@
1
+ //#region src/core/reduced-motion.ts
2
+ function prefersReducedMotion() {
3
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
4
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
5
+ }
6
+ //#endregion
7
+ //#region src/core/raf.ts
8
+ function dualRaf() {
9
+ return new Promise((resolve) => {
10
+ requestAnimationFrame(() => {
11
+ requestAnimationFrame(() => resolve());
12
+ });
13
+ });
14
+ }
15
+ //#endregion
16
+ //#region src/core/level.ts
17
+ var ANIMATION_LEVELS = [
18
+ "low",
19
+ "med",
20
+ "high"
21
+ ];
22
+ var DEFAULT_ANIMATION_LEVEL = "high";
23
+ function isAnimationLevel(value) {
24
+ return value === "low" || value === "med" || value === "high";
25
+ }
26
+ function parseAnimationLevel(raw) {
27
+ if (typeof raw !== "string") return null;
28
+ let next = raw.trim().toLowerCase();
29
+ return isAnimationLevel(next) ? next : null;
30
+ }
31
+ function defaultAnimationLevel(prefersReduced) {
32
+ return prefersReduced ? "med" : DEFAULT_ANIMATION_LEVEL;
33
+ }
34
+ function cycleAnimationLevel(level) {
35
+ let idx = ANIMATION_LEVELS.indexOf(level);
36
+ if (idx < 0) return DEFAULT_ANIMATION_LEVEL;
37
+ return ANIMATION_LEVELS[(idx + 1) % ANIMATION_LEVELS.length];
38
+ }
39
+ function canAnimate(level) {
40
+ return level !== "low";
41
+ }
42
+ function pickAnimationLevelFromRatio(t) {
43
+ if (t < 1 / 3) return "low";
44
+ if (t < 2 / 3) return "med";
45
+ return "high";
46
+ }
47
+ function stepAnimationLevel(level, delta) {
48
+ let idx = ANIMATION_LEVELS.indexOf(level);
49
+ if (idx < 0) return DEFAULT_ANIMATION_LEVEL;
50
+ let next = idx + delta;
51
+ if (next < 0) return ANIMATION_LEVELS[0];
52
+ if (next >= ANIMATION_LEVELS.length) return ANIMATION_LEVELS[ANIMATION_LEVELS.length - 1];
53
+ return ANIMATION_LEVELS[next];
54
+ }
55
+ //#endregion
56
+ //#region src/core/playback.ts
57
+ function createPlayback() {
58
+ let cancelled = false;
59
+ let settled = false;
60
+ let resolveDone;
61
+ let done = new Promise((resolve) => {
62
+ resolveDone = resolve;
63
+ });
64
+ let resolve = (ran) => {
65
+ if (settled) return;
66
+ settled = true;
67
+ resolveDone(cancelled ? false : ran);
68
+ };
69
+ return {
70
+ playback: {
71
+ done,
72
+ cancel: () => {
73
+ cancelled = true;
74
+ resolve(false);
75
+ }
76
+ },
77
+ resolve,
78
+ isCancelled: () => cancelled
79
+ };
80
+ }
81
+ function animateElement(el, keyframes, options) {
82
+ if (typeof el.animate !== "function") return null;
83
+ return el.animate(keyframes, options);
84
+ }
85
+ //#endregion
86
+ //#region src/core/tween.ts
87
+ function easeOutCubic(t) {
88
+ return 1 - (1 - (t < 0 ? 0 : t > 1 ? 1 : t)) ** 3;
89
+ }
90
+ function lerp(from, to, t) {
91
+ return from + (to - from) * t;
92
+ }
93
+ function tween(options) {
94
+ let { playback, resolve, isCancelled } = createPlayback();
95
+ let ease = options.easing ?? easeOutCubic;
96
+ let durationMs = Math.max(0, options.durationMs);
97
+ if (durationMs <= 0) {
98
+ options.onUpdate(options.to);
99
+ resolve(true);
100
+ return playback;
101
+ }
102
+ let start = performance.now();
103
+ let frame = 0;
104
+ let tick = (now) => {
105
+ if (isCancelled()) {
106
+ resolve(false);
107
+ return;
108
+ }
109
+ let t = Math.min(1, (now - start) / durationMs);
110
+ options.onUpdate(lerp(options.from, options.to, ease(t)));
111
+ if (t >= 1) {
112
+ resolve(true);
113
+ return;
114
+ }
115
+ frame = requestAnimationFrame(tick);
116
+ };
117
+ frame = requestAnimationFrame(tick);
118
+ let cancel = playback.cancel;
119
+ playback.cancel = () => {
120
+ cancelAnimationFrame(frame);
121
+ cancel();
122
+ };
123
+ return playback;
124
+ }
125
+ //#endregion
126
+ //#region src/rect/fit.ts
127
+ /** Fit natural size into box without upscaling past natural (CSS contain-like). */
128
+ function fitContain(natural, box) {
129
+ let natW = natural.width;
130
+ let natH = natural.height;
131
+ let boxW = box.width;
132
+ let boxH = box.height;
133
+ if (!(natW > 0) || !(natH > 0) || !(boxW > 0) || !(boxH > 0)) return null;
134
+ let scale = Math.min(boxW / natW, boxH / natH, 1);
135
+ return {
136
+ width: Math.max(1, Math.round(natW * scale)),
137
+ height: Math.max(1, Math.round(natH * scale))
138
+ };
139
+ }
140
+ /** Center a fitted size inside a padded viewport. */
141
+ function centerFitInViewport(fit, viewport, insets) {
142
+ let availW = Math.max(0, viewport.width - insets.left - insets.right);
143
+ let availH = Math.max(0, viewport.height - insets.top - insets.bottom);
144
+ return {
145
+ top: insets.top + (availH - fit.height) / 2,
146
+ left: insets.left + (availW - fit.width) / 2,
147
+ width: fit.width,
148
+ height: fit.height
149
+ };
150
+ }
151
+ //#endregion
152
+ //#region src/shared-element/math.ts
153
+ /** Expand a cover-cropped thumb so its aspect matches destination aspect. */
154
+ function uncoverByAspect(aspectWidth, aspectHeight, thumb) {
155
+ let { top, left, width, height } = thumb;
156
+ let realW = aspectWidth;
157
+ let realH = aspectHeight;
158
+ if (!(realW > 0) || !(realH > 0) || !(width > 0) || !(height > 0)) return { ...thumb };
159
+ if (realW === realH) {
160
+ let size = Math.max(width, height);
161
+ left -= (size - width) / 2;
162
+ top -= (size - height) / 2;
163
+ width = size;
164
+ height = size;
165
+ } else if (realW > realH) {
166
+ let srcWidth = width;
167
+ width = height * (realW / realH);
168
+ left -= (width - srcWidth) / 2;
169
+ } else {
170
+ let srcHeight = height;
171
+ height = width * (realH / realW);
172
+ top -= (height - srcHeight) / 2;
173
+ }
174
+ return {
175
+ top,
176
+ left,
177
+ width,
178
+ height
179
+ };
180
+ }
181
+ /** Center-based translate/scale so an element at `to` visually matches `from`. */
182
+ function computeFlight(from, to) {
183
+ if (!(to.width > 0) || !(to.height > 0) || !(from.width > 0) || !(from.height > 0)) return null;
184
+ let fromCx = from.left + from.width / 2;
185
+ let fromCy = from.top + from.height / 2;
186
+ let toCx = to.left + to.width / 2;
187
+ let toCy = to.top + to.height / 2;
188
+ return {
189
+ to: { ...to },
190
+ fromTranslateX: fromCx - toCx,
191
+ fromTranslateY: fromCy - toCy,
192
+ fromScaleX: from.width / to.width,
193
+ fromScaleY: from.height / to.height
194
+ };
195
+ }
196
+ /** Prefer natural size; fall back to thumb so fit is never empty. */
197
+ function resolveNaturalSize(naturalWidth, naturalHeight, thumb) {
198
+ if (naturalWidth && naturalHeight && naturalWidth > 0 && naturalHeight > 0) return {
199
+ width: naturalWidth,
200
+ height: naturalHeight
201
+ };
202
+ if (thumb.width > 0 && thumb.height > 0) return {
203
+ width: thumb.width,
204
+ height: thumb.height
205
+ };
206
+ return {
207
+ width: 1,
208
+ height: 1
209
+ };
210
+ }
211
+ /**
212
+ * Open flight: (possibly uncovered) thumb → stage fit box (or explicit live `to`).
213
+ * Cover thumbs uncover against destination aspect so scale is uniform.
214
+ */
215
+ function computeOpenFlight(opts) {
216
+ let natural = resolveNaturalSize(opts.naturalWidth, opts.naturalHeight, opts.thumb);
217
+ let to = opts.to && opts.to.width > 0 && opts.to.height > 0 ? { ...opts.to } : null;
218
+ if (!to) {
219
+ if (!opts.insets) return null;
220
+ let fit = fitContain(natural, {
221
+ width: Math.max(0, opts.viewport.width - opts.insets.left - opts.insets.right),
222
+ height: Math.max(0, opts.viewport.height - opts.insets.top - opts.insets.bottom)
223
+ });
224
+ if (!fit) return null;
225
+ to = centerFitInViewport(fit, opts.viewport, opts.insets);
226
+ }
227
+ let from;
228
+ if (opts.objectFit === "cover") from = uncoverByAspect(to.width, to.height, opts.thumb);
229
+ else from = { ...opts.thumb };
230
+ return computeFlight(from, to);
231
+ }
232
+ /**
233
+ * Close flight: stage box → thumb.
234
+ * Cover uses Math.min(sx, sy) so the land crop matches.
235
+ */
236
+ function computeCloseFlight(opts) {
237
+ let flight = computeFlight(opts.fromStage, opts.thumb);
238
+ if (!flight) return null;
239
+ if (opts.objectFit === "cover") {
240
+ let s = Math.min(flight.fromScaleX, flight.fromScaleY);
241
+ flight = {
242
+ ...flight,
243
+ fromScaleX: s,
244
+ fromScaleY: s
245
+ };
246
+ }
247
+ return flight;
248
+ }
249
+ /** Whether rect is fully inside clip (within epsilon). */
250
+ function isRectFullyVisibleIn(rect, clip, epsilon = 1) {
251
+ if (!(rect.width > 0) || !(rect.height > 0) || !(clip.width > 0) || !(clip.height > 0)) return false;
252
+ let right = rect.left + rect.width;
253
+ let bottom = rect.top + rect.height;
254
+ let clipRight = clip.left + clip.width;
255
+ let clipBottom = clip.top + clip.height;
256
+ return rect.left >= clip.left - epsilon && rect.top >= clip.top - epsilon && right <= clipRight + epsilon && bottom <= clipBottom + epsilon;
257
+ }
258
+ /** Whether rect intersects the viewport enough to land on. */
259
+ function isRectInViewport(rect, viewport, minVisiblePx = 8) {
260
+ let right = rect.left + rect.width;
261
+ let bottom = rect.top + rect.height;
262
+ let visW = Math.min(right, viewport.width) - Math.max(rect.left, 0);
263
+ let visH = Math.min(bottom, viewport.height) - Math.max(rect.top, 0);
264
+ return visW >= minVisiblePx && visH >= minVisiblePx;
265
+ }
266
+ /** Off-viewport landing: fly above or below toward the thumb's side. */
267
+ function offViewportLandingRect(from, thumb, viewportHeight) {
268
+ return {
269
+ top: thumb.top + thumb.height / 2 < from.top + from.height / 2 ? -thumb.height : viewportHeight,
270
+ left: thumb.left,
271
+ width: thumb.width,
272
+ height: thumb.height
273
+ };
274
+ }
275
+ //#endregion
276
+ //#region src/core/styles.ts
277
+ function applyStyles(el, styles) {
278
+ for (let key in styles) el.style.setProperty(key, styles[key]);
279
+ }
280
+ function clearStyles(el, keys) {
281
+ for (let key of keys) el.style.removeProperty(key);
282
+ }
283
+ //#endregion
284
+ //#region src/shared-element/player.ts
285
+ var SHARED_ELEMENT_MS = 200;
286
+ var SHARED_ELEMENT_EASING = "ease";
287
+ var SHARED_ELEMENT_END_MS = 16;
288
+ function defaultViewport(override) {
289
+ if (override) return override;
290
+ if (typeof window === "undefined") return {
291
+ width: 0,
292
+ height: 0
293
+ };
294
+ return {
295
+ width: window.innerWidth,
296
+ height: window.innerHeight
297
+ };
298
+ }
299
+ function createClone(imageUrl, objectFit, roundedStart) {
300
+ let el = document.createElement("div");
301
+ el.setAttribute("aria-hidden", "true");
302
+ applyStyles(el, {
303
+ position: "fixed",
304
+ "z-index": "2147483646",
305
+ overflow: "hidden",
306
+ "pointer-events": "none",
307
+ "transform-origin": "center center",
308
+ "will-change": "transform, opacity",
309
+ "border-radius": roundedStart ? "12px" : "0"
310
+ });
311
+ let img = document.createElement("img");
312
+ img.draggable = false;
313
+ img.alt = "";
314
+ applyStyles(img, {
315
+ display: "block",
316
+ width: "100%",
317
+ height: "100%",
318
+ "object-fit": objectFit,
319
+ "pointer-events": "none",
320
+ "user-select": "none"
321
+ });
322
+ if (imageUrl) img.src = imageUrl;
323
+ el.appendChild(img);
324
+ return el;
325
+ }
326
+ function applyStartPose(el, flight) {
327
+ applyStyles(el, {
328
+ top: `${flight.to.top}px`,
329
+ left: `${flight.to.left}px`,
330
+ width: `${flight.to.width}px`,
331
+ height: `${flight.to.height}px`,
332
+ transform: startTransform$1(flight)
333
+ });
334
+ }
335
+ function startTransform$1(flight) {
336
+ return `translate3d(${flight.fromTranslateX}px, ${flight.fromTranslateY}px, 0) scale(${flight.fromScaleX}, ${flight.fromScaleY})`;
337
+ }
338
+ function createSharedElement() {
339
+ let current = null;
340
+ let teardownDom = (active) => {
341
+ if (active.timer != null) {
342
+ clearTimeout(active.timer);
343
+ active.timer = null;
344
+ }
345
+ if (active.clone.parentNode) active.clone.remove();
346
+ if (active.hideTarget) active.hideTarget.style.visibility = active.hidePrev;
347
+ };
348
+ let abort = (active) => {
349
+ if (current === active) current = null;
350
+ active.anim?.cancel();
351
+ active.anim = null;
352
+ teardownDom(active);
353
+ active.settleCancel();
354
+ };
355
+ let cancel = () => {
356
+ if (!current) return;
357
+ abort(current);
358
+ };
359
+ let start = (opts, flight) => {
360
+ cancel();
361
+ let { playback, resolve, isCancelled } = createPlayback();
362
+ let objectFit = opts.objectFit ?? "contain";
363
+ let clone = createClone(opts.imageUrl, objectFit, !!opts.roundedStart);
364
+ let hideTarget = opts.hideTarget ?? null;
365
+ let hidePrev = hideTarget?.style.visibility ?? "";
366
+ let durationMs = opts.durationMs ?? 200;
367
+ let easing = opts.easing ?? "ease";
368
+ let active = {
369
+ clone,
370
+ hideTarget,
371
+ hidePrev,
372
+ settleCancel: playback.cancel,
373
+ resolve,
374
+ isCancelled,
375
+ anim: null,
376
+ timer: null,
377
+ finishing: false
378
+ };
379
+ current = active;
380
+ applyStartPose(clone, flight);
381
+ opts.host.appendChild(clone);
382
+ if (hideTarget) hideTarget.style.visibility = "hidden";
383
+ let handle = {
384
+ done: playback.done,
385
+ cancel: () => {
386
+ if (current === active) abort(active);
387
+ else playback.cancel();
388
+ }
389
+ };
390
+ let finish = async (ran) => {
391
+ if (current !== active || active.finishing) return;
392
+ if (isCancelled()) {
393
+ abort(active);
394
+ return;
395
+ }
396
+ active.finishing = true;
397
+ if (active.timer != null) {
398
+ clearTimeout(active.timer);
399
+ active.timer = null;
400
+ }
401
+ if (opts.onLand) try {
402
+ await opts.onLand();
403
+ } catch {}
404
+ if (isCancelled() || current !== active) return;
405
+ current = null;
406
+ teardownDom(active);
407
+ resolve(ran);
408
+ };
409
+ (async () => {
410
+ await dualRaf();
411
+ if (current !== active) return;
412
+ if (isCancelled()) {
413
+ abort(active);
414
+ return;
415
+ }
416
+ let fromFrame = { transform: startTransform$1(flight) };
417
+ let toFrame = { transform: "translate3d(0, 0, 0) scale(1, 1)" };
418
+ if (opts.fadeOut) {
419
+ fromFrame.opacity = "1";
420
+ toFrame.opacity = "0";
421
+ }
422
+ if (opts.roundedStart || opts.roundedEnd) {
423
+ fromFrame.borderRadius = opts.roundedStart ? "12px" : "0px";
424
+ toFrame.borderRadius = opts.roundedEnd ? "12px" : "0px";
425
+ }
426
+ active.anim = animateElement(clone, [fromFrame, toFrame], {
427
+ duration: durationMs,
428
+ easing,
429
+ fill: "forwards"
430
+ });
431
+ active.timer = setTimeout(() => {
432
+ finish(true);
433
+ }, durationMs + 16);
434
+ if (active.anim) active.anim.finished.then(() => finish(true), () => void 0);
435
+ })();
436
+ return handle;
437
+ };
438
+ let play = (opts) => {
439
+ let flight = computeFlight(opts.from, opts.to);
440
+ if (!flight) {
441
+ cancel();
442
+ return null;
443
+ }
444
+ return start(opts, flight);
445
+ };
446
+ let playOpen = (opts) => {
447
+ let flight = computeOpenFlight({
448
+ thumb: opts.seed.rect,
449
+ naturalWidth: opts.seed.naturalWidth,
450
+ naturalHeight: opts.seed.naturalHeight,
451
+ objectFit: opts.seed.objectFit ?? "contain",
452
+ viewport: defaultViewport(opts.viewport),
453
+ insets: opts.insets,
454
+ to: opts.to
455
+ });
456
+ if (!flight) {
457
+ cancel();
458
+ return null;
459
+ }
460
+ return start({
461
+ host: opts.host,
462
+ from: opts.seed.rect,
463
+ to: flight.to,
464
+ imageUrl: opts.seed.imageUrl,
465
+ objectFit: "contain",
466
+ durationMs: opts.durationMs,
467
+ roundedStart: true,
468
+ roundedEnd: false,
469
+ hideTarget: opts.hideTarget,
470
+ onLand: opts.onLand
471
+ }, flight);
472
+ };
473
+ let playClose = (opts) => {
474
+ let imageUrl = opts.imageUrl ?? opts.target?.imageUrl ?? null;
475
+ if (!opts.target) return start({
476
+ host: opts.host,
477
+ from: opts.fromStage,
478
+ to: opts.fromStage,
479
+ imageUrl,
480
+ objectFit: "contain",
481
+ durationMs: opts.durationMs,
482
+ fadeOut: true,
483
+ roundedStart: false,
484
+ roundedEnd: false,
485
+ hideTarget: opts.hideTarget
486
+ }, {
487
+ to: { ...opts.fromStage },
488
+ fromTranslateX: 0,
489
+ fromTranslateY: 0,
490
+ fromScaleX: 1,
491
+ fromScaleY: 1
492
+ });
493
+ let thumb = opts.target.rect;
494
+ let viewport = defaultViewport(opts.viewport);
495
+ if (!isRectInViewport(thumb, viewport)) thumb = offViewportLandingRect(opts.fromStage, thumb, viewport.height);
496
+ let objectFit = opts.target.objectFit ?? "contain";
497
+ let flight = computeCloseFlight({
498
+ fromStage: opts.fromStage,
499
+ thumb,
500
+ objectFit
501
+ });
502
+ if (!flight) {
503
+ cancel();
504
+ return null;
505
+ }
506
+ return start({
507
+ host: opts.host,
508
+ from: opts.fromStage,
509
+ to: flight.to,
510
+ imageUrl,
511
+ objectFit,
512
+ durationMs: opts.durationMs,
513
+ fadeOut: opts.fadeOut ?? false,
514
+ roundedStart: false,
515
+ roundedEnd: true,
516
+ hideTarget: opts.hideTarget
517
+ }, flight);
518
+ };
519
+ return {
520
+ play,
521
+ playOpen,
522
+ playClose,
523
+ cancel
524
+ };
525
+ }
526
+ var defaultController = null;
527
+ function playSharedElement(opts) {
528
+ if (!defaultController) defaultController = createSharedElement();
529
+ return defaultController.play(opts);
530
+ }
531
+ //#endregion
532
+ //#region src/view-slide/transforms.ts
533
+ var VIEW_SLIDE_FADE_OFFSET = "1.5rem";
534
+ var VIEW_SLIDE_MS = 300;
535
+ var VIEW_SLIDE_EASING = "cubic-bezier(0.25, 1, 0.5, 1)";
536
+ var VIEW_SLIDE_COVER_MS = 250;
537
+ var VIEW_SLIDE_COVER_EASING = "ease-in-out";
538
+ var VIEW_SLIDE_ZOOM_MS = 150;
539
+ var VIEW_SLIDE_ZOOM_EASING = "ease";
540
+ var VIEW_SLIDE_REVEAL_MS = 350;
541
+ var VIEW_SLIDE_REVEAL_EASING = "ease-in";
542
+ function slideDirectionByIndex(from, to, items) {
543
+ let fromIndex = items.findIndex((item) => item.id === from);
544
+ let toIndex = items.findIndex((item) => item.id === to);
545
+ if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return null;
546
+ return toIndex > fromIndex ? "forward" : "back";
547
+ }
548
+ function resolveViewSlideMode(level, kind) {
549
+ if (level === "low") return "none";
550
+ if (level === "med") return "crossfade";
551
+ return kind === "layer" ? "cover" : "push";
552
+ }
553
+ function viewSlideDurationMs(mode) {
554
+ if (mode === "cover") return 250;
555
+ if (mode === "zoom") return 150;
556
+ if (mode === "reveal") return 350;
557
+ return 300;
558
+ }
559
+ function viewSlideEasing(mode) {
560
+ if (mode === "cover") return VIEW_SLIDE_COVER_EASING;
561
+ if (mode === "zoom") return VIEW_SLIDE_ZOOM_EASING;
562
+ if (mode === "reveal") return VIEW_SLIDE_REVEAL_EASING;
563
+ return VIEW_SLIDE_EASING;
564
+ }
565
+ function viewSlideTransforms(direction, mode) {
566
+ if (mode === "crossfade") {
567
+ let leave = direction === "forward" ? `-${VIEW_SLIDE_FADE_OFFSET}` : VIEW_SLIDE_FADE_OFFSET;
568
+ let enter = direction === "forward" ? VIEW_SLIDE_FADE_OFFSET : `-${VIEW_SLIDE_FADE_OFFSET}`;
569
+ return {
570
+ fromStart: {
571
+ transform: "translate3d(0, 0, 0)",
572
+ opacity: "1"
573
+ },
574
+ fromEnd: {
575
+ transform: `translate3d(${leave}, 0, 0)`,
576
+ opacity: "0"
577
+ },
578
+ toStart: {
579
+ transform: `translate3d(${enter}, 0, 0)`,
580
+ opacity: "0"
581
+ },
582
+ toEnd: {
583
+ transform: "translate3d(0, 0, 0)",
584
+ opacity: "1"
585
+ }
586
+ };
587
+ }
588
+ if (mode === "cover") {
589
+ if (direction === "forward") return {
590
+ fromStart: {
591
+ transform: "scale(1)",
592
+ opacity: "1"
593
+ },
594
+ fromEnd: {
595
+ transform: "scale(0.7)",
596
+ opacity: "0"
597
+ },
598
+ toStart: {
599
+ transform: "translateX(200%)",
600
+ opacity: "1"
601
+ },
602
+ toEnd: {
603
+ transform: "translateX(0)",
604
+ opacity: "1"
605
+ }
606
+ };
607
+ return {
608
+ fromStart: {
609
+ transform: "translateX(0)",
610
+ opacity: "1"
611
+ },
612
+ fromEnd: {
613
+ transform: "translateX(200%)",
614
+ opacity: "1"
615
+ },
616
+ toStart: {
617
+ transform: "scale(0.7)",
618
+ opacity: "0"
619
+ },
620
+ toEnd: {
621
+ transform: "scale(1)",
622
+ opacity: "1"
623
+ }
624
+ };
625
+ }
626
+ if (mode === "peek") {
627
+ if (direction === "forward") return {
628
+ fromStart: {
629
+ transform: "translate3d(0, 0, 0)",
630
+ opacity: "1"
631
+ },
632
+ fromEnd: {
633
+ transform: "translate3d(-20%, 0, 0)",
634
+ opacity: "0.7"
635
+ },
636
+ toStart: {
637
+ transform: "translate3d(100%, 0, 0)",
638
+ opacity: "1"
639
+ },
640
+ toEnd: {
641
+ transform: "translate3d(0, 0, 0)",
642
+ opacity: "1"
643
+ }
644
+ };
645
+ return {
646
+ fromStart: {
647
+ transform: "translate3d(0, 0, 0)",
648
+ opacity: "1"
649
+ },
650
+ fromEnd: {
651
+ transform: "translate3d(100%, 0, 0)",
652
+ opacity: "1"
653
+ },
654
+ toStart: {
655
+ transform: "translate3d(-20%, 0, 0)",
656
+ opacity: "0.7"
657
+ },
658
+ toEnd: {
659
+ transform: "translate3d(0, 0, 0)",
660
+ opacity: "1"
661
+ }
662
+ };
663
+ }
664
+ if (mode === "lift") {
665
+ if (direction === "forward") return {
666
+ fromStart: {
667
+ transform: "translate3d(0, 0, 0)",
668
+ opacity: "1"
669
+ },
670
+ fromEnd: {
671
+ transform: "translate3d(0, -100%, 0)",
672
+ opacity: "1"
673
+ },
674
+ toStart: {
675
+ transform: "translate3d(0, 100%, 0)",
676
+ opacity: "1"
677
+ },
678
+ toEnd: {
679
+ transform: "translate3d(0, 0, 0)",
680
+ opacity: "1"
681
+ }
682
+ };
683
+ return {
684
+ fromStart: {
685
+ transform: "translate3d(0, 0, 0)",
686
+ opacity: "1"
687
+ },
688
+ fromEnd: {
689
+ transform: "translate3d(0, 100%, 0)",
690
+ opacity: "1"
691
+ },
692
+ toStart: {
693
+ transform: "translate3d(0, -100%, 0)",
694
+ opacity: "1"
695
+ },
696
+ toEnd: {
697
+ transform: "translate3d(0, 0, 0)",
698
+ opacity: "1"
699
+ }
700
+ };
701
+ }
702
+ if (mode === "zoom") {
703
+ if (direction === "forward") return {
704
+ fromStart: {
705
+ transform: "scale(1)",
706
+ opacity: "1"
707
+ },
708
+ fromEnd: {
709
+ transform: "scale(0.95)",
710
+ opacity: "0"
711
+ },
712
+ toStart: {
713
+ transform: "scale(1.1)",
714
+ opacity: "0"
715
+ },
716
+ toEnd: {
717
+ transform: "scale(1)",
718
+ opacity: "1"
719
+ }
720
+ };
721
+ return {
722
+ fromStart: {
723
+ transform: "scale(1)",
724
+ opacity: "1"
725
+ },
726
+ fromEnd: {
727
+ transform: "scale(1.1)",
728
+ opacity: "0"
729
+ },
730
+ toStart: {
731
+ transform: "scale(0.95)",
732
+ opacity: "0"
733
+ },
734
+ toEnd: {
735
+ transform: "scale(1)",
736
+ opacity: "1"
737
+ }
738
+ };
739
+ }
740
+ if (mode === "reveal") {
741
+ if (direction === "forward") return {
742
+ fromStart: {
743
+ transform: "translate3d(0, 0, 0)",
744
+ opacity: "1"
745
+ },
746
+ fromEnd: {
747
+ transform: "translate3d(0, 0, 0)",
748
+ opacity: "1"
749
+ },
750
+ toStart: {
751
+ transform: "translate3d(0, 0, 0)",
752
+ opacity: "1",
753
+ clipPath: "inset(0 100% 0 0)"
754
+ },
755
+ toEnd: {
756
+ transform: "translate3d(0, 0, 0)",
757
+ opacity: "1",
758
+ clipPath: "inset(0 0 0 0)"
759
+ }
760
+ };
761
+ return {
762
+ fromStart: {
763
+ transform: "translate3d(0, 0, 0)",
764
+ opacity: "1",
765
+ clipPath: "inset(0 0 0 0)"
766
+ },
767
+ fromEnd: {
768
+ transform: "translate3d(0, 0, 0)",
769
+ opacity: "1",
770
+ clipPath: "inset(0 100% 0 0)"
771
+ },
772
+ toStart: {
773
+ transform: "translate3d(0, 0, 0)",
774
+ opacity: "1"
775
+ },
776
+ toEnd: {
777
+ transform: "translate3d(0, 0, 0)",
778
+ opacity: "1"
779
+ }
780
+ };
781
+ }
782
+ if (direction === "forward") return {
783
+ fromStart: {
784
+ transform: "translate3d(0, 0, 0)",
785
+ opacity: "1"
786
+ },
787
+ fromEnd: {
788
+ transform: "translate3d(-100%, 0, 0)",
789
+ opacity: "1"
790
+ },
791
+ toStart: {
792
+ transform: "translate3d(100%, 0, 0)",
793
+ opacity: "1"
794
+ },
795
+ toEnd: {
796
+ transform: "translate3d(0, 0, 0)",
797
+ opacity: "1"
798
+ }
799
+ };
800
+ return {
801
+ fromStart: {
802
+ transform: "translate3d(0, 0, 0)",
803
+ opacity: "1"
804
+ },
805
+ fromEnd: {
806
+ transform: "translate3d(100%, 0, 0)",
807
+ opacity: "1"
808
+ },
809
+ toStart: {
810
+ transform: "translate3d(-100%, 0, 0)",
811
+ opacity: "1"
812
+ },
813
+ toEnd: {
814
+ transform: "translate3d(0, 0, 0)",
815
+ opacity: "1"
816
+ }
817
+ };
818
+ }
819
+ //#endregion
820
+ //#region src/view-slide/session.ts
821
+ var VIEW_SLIDE_SETTLE_SLACK_MS = 80;
822
+ var PANEL_STYLE_KEYS$1 = [
823
+ "transform",
824
+ "opacity",
825
+ "will-change",
826
+ "clip-path"
827
+ ];
828
+ function applyPanel(el, state) {
829
+ let styles = {
830
+ transform: state.transform,
831
+ opacity: state.opacity,
832
+ "will-change": state.clipPath != null ? "transform, opacity, clip-path" : "transform, opacity"
833
+ };
834
+ if (state.clipPath != null) styles["clip-path"] = state.clipPath;
835
+ applyStyles(el, styles);
836
+ }
837
+ function clearPanel(el) {
838
+ clearStyles(el, PANEL_STYLE_KEYS$1);
839
+ }
840
+ function createViewSlide(config) {
841
+ let mountPolicy = config.mountPolicy ?? "keep-visited";
842
+ let settleSlackMs = config.settleSlackMs ?? 80;
843
+ let resolveTransforms = config.transforms ?? viewSlideTransforms;
844
+ let resolveDuration = config.durationMs ?? viewSlideDurationMs;
845
+ let resolveEasing = config.easing ?? viewSlideEasing;
846
+ let mountedKeys = [];
847
+ let panelEls = /* @__PURE__ */ new Map();
848
+ let animating = false;
849
+ let leavingKey;
850
+ let lastRequestedKey;
851
+ let pending = null;
852
+ let pair = null;
853
+ let slideGen = 0;
854
+ let destroyed = false;
855
+ let isMounted = (key) => mountedKeys.includes(key);
856
+ let ensureMounted = (key) => {
857
+ if (!mountedKeys.includes(key)) mountedKeys = [...mountedKeys, key];
858
+ };
859
+ let retainOnly = (keys) => {
860
+ let wanted = keys.filter((key, index) => keys.indexOf(key) === index);
861
+ let ordered = mountedKeys.filter((key) => wanted.includes(key));
862
+ for (let key of wanted) if (!ordered.includes(key)) ordered.push(key);
863
+ mountedKeys = ordered;
864
+ };
865
+ let applyMountAfterSettle = (active) => {
866
+ if (mountPolicy === "keep-visited") return;
867
+ retainOnly([active]);
868
+ };
869
+ let isVisible = (key) => {
870
+ if (key === lastRequestedKey) return true;
871
+ return animating && key === leavingKey;
872
+ };
873
+ let role = (key) => {
874
+ if (animating) {
875
+ if (key === lastRequestedKey) return "entering";
876
+ if (key === leavingKey) return "leaving";
877
+ return "idle";
878
+ }
879
+ return key === lastRequestedKey ? "active" : "idle";
880
+ };
881
+ let detachPair = (active) => {
882
+ if (active.settleTimer != null) {
883
+ clearTimeout(active.settleTimer);
884
+ active.settleTimer = null;
885
+ }
886
+ active.fromAnim?.cancel();
887
+ active.toAnim?.cancel();
888
+ active.fromAnim = null;
889
+ active.toAnim = null;
890
+ active.fromEl.removeEventListener("transitionend", active.onEnd);
891
+ active.toEl.removeEventListener("transitionend", active.onEnd);
892
+ clearPanel(active.fromEl);
893
+ clearPanel(active.toEl);
894
+ };
895
+ let abortInFlight = () => {
896
+ pending = null;
897
+ if (!pair) return;
898
+ detachPair(pair);
899
+ pair = null;
900
+ };
901
+ let finishSlide = (gen, to) => {
902
+ if (gen !== slideGen || destroyed) return;
903
+ if (pair && pair.gen === gen) detachPair(pair);
904
+ pair = null;
905
+ pending = null;
906
+ animating = false;
907
+ leavingKey = void 0;
908
+ applyMountAfterSettle(to);
909
+ };
910
+ let abortToInstant = (from, to, gen) => {
911
+ if (gen !== slideGen || destroyed) return;
912
+ let fromEl = panelEls.get(from);
913
+ let toEl = panelEls.get(to);
914
+ if (fromEl) clearPanel(fromEl);
915
+ if (toEl) clearPanel(toEl);
916
+ animating = false;
917
+ leavingKey = void 0;
918
+ pending = null;
919
+ applyMountAfterSettle(to);
920
+ };
921
+ let performSlide = async (from, to, fromEl, toEl, dir, mode, gen) => {
922
+ if (gen !== slideGen || destroyed) return;
923
+ let tx = resolveTransforms(dir, mode);
924
+ let ms = resolveDuration(mode);
925
+ animating = true;
926
+ leavingKey = from;
927
+ applyPanel(fromEl, tx.fromStart);
928
+ applyPanel(toEl, tx.toStart);
929
+ fromEl.offsetWidth;
930
+ toEl.offsetWidth;
931
+ let onEnd = (event) => {
932
+ if (event.target !== toEl) return;
933
+ let propertyName = event.propertyName;
934
+ if (propertyName && propertyName !== "transform") return;
935
+ finishSlide(gen, to);
936
+ };
937
+ fromEl.addEventListener("transitionend", onEnd);
938
+ toEl.addEventListener("transitionend", onEnd);
939
+ let active = {
940
+ gen,
941
+ from,
942
+ to,
943
+ fromEl,
944
+ toEl,
945
+ fromAnim: null,
946
+ toAnim: null,
947
+ settleTimer: null,
948
+ onEnd
949
+ };
950
+ pair = active;
951
+ active.settleTimer = setTimeout(() => {
952
+ finishSlide(gen, to);
953
+ }, ms + settleSlackMs);
954
+ await dualRaf();
955
+ if (gen !== slideGen || destroyed || pair !== active) return;
956
+ let easing = resolveEasing(mode);
957
+ active.fromAnim = animateElement(fromEl, [tx.fromStart, tx.fromEnd], {
958
+ duration: ms,
959
+ easing,
960
+ fill: "forwards"
961
+ });
962
+ active.toAnim = animateElement(toEl, [tx.toStart, tx.toEnd], {
963
+ duration: ms,
964
+ easing,
965
+ fill: "forwards"
966
+ });
967
+ if (active.toAnim) active.toAnim.finished.then(() => finishSlide(gen, to), () => void 0);
968
+ };
969
+ /** Start the pair only after both panels have an attached node. */
970
+ let tryRunPending = () => {
971
+ if (!pending || destroyed) return;
972
+ let fromEl = panelEls.get(pending.from);
973
+ let toEl = panelEls.get(pending.to);
974
+ if (!fromEl || !toEl) return;
975
+ let { from, to, gen } = pending;
976
+ pending = null;
977
+ let mode = config.getMode();
978
+ let dir = config.getDirection(from, to);
979
+ if (!dir || mode === "none") {
980
+ abortToInstant(from, to, gen);
981
+ return;
982
+ }
983
+ performSlide(from, to, fromEl, toEl, dir, mode, gen);
984
+ };
985
+ let setActive = (key) => {
986
+ if (destroyed) return;
987
+ if (key === lastRequestedKey) return;
988
+ if (lastRequestedKey == null) {
989
+ lastRequestedKey = key;
990
+ if (key != null) ensureMounted(key);
991
+ return;
992
+ }
993
+ let from = lastRequestedKey;
994
+ lastRequestedKey = key;
995
+ slideGen++;
996
+ abortInFlight();
997
+ if (key == null) {
998
+ animating = false;
999
+ leavingKey = void 0;
1000
+ if (mountPolicy !== "keep-visited") retainOnly([]);
1001
+ return;
1002
+ }
1003
+ ensureMounted(from);
1004
+ ensureMounted(key);
1005
+ animating = true;
1006
+ leavingKey = from;
1007
+ pending = {
1008
+ from,
1009
+ to: key,
1010
+ gen: slideGen
1011
+ };
1012
+ tryRunPending();
1013
+ };
1014
+ let attach = (el, key) => {
1015
+ if (!destroyed) {
1016
+ panelEls.set(key, el);
1017
+ tryRunPending();
1018
+ }
1019
+ return {
1020
+ update: (next) => {
1021
+ if (destroyed || next === key) return;
1022
+ if (pair && (pair.from === key || pair.to === key) || pending && (pending.from === key || pending.to === key)) {
1023
+ slideGen++;
1024
+ abortInFlight();
1025
+ animating = false;
1026
+ leavingKey = void 0;
1027
+ }
1028
+ if (panelEls.get(key) === el) panelEls.delete(key);
1029
+ key = next;
1030
+ panelEls.set(key, el);
1031
+ tryRunPending();
1032
+ },
1033
+ destroy: () => {
1034
+ if (panelEls.get(key) === el) panelEls.delete(key);
1035
+ }
1036
+ };
1037
+ };
1038
+ let cancel = () => {
1039
+ if (destroyed) return;
1040
+ slideGen++;
1041
+ abortInFlight();
1042
+ animating = false;
1043
+ leavingKey = void 0;
1044
+ if (lastRequestedKey != null && mountPolicy !== "keep-visited") applyMountAfterSettle(lastRequestedKey);
1045
+ };
1046
+ let destroy = () => {
1047
+ if (destroyed) return;
1048
+ destroyed = true;
1049
+ slideGen++;
1050
+ abortInFlight();
1051
+ for (let el of panelEls.values()) clearPanel(el);
1052
+ animating = false;
1053
+ leavingKey = void 0;
1054
+ lastRequestedKey = void 0;
1055
+ pending = null;
1056
+ pair = null;
1057
+ panelEls.clear();
1058
+ mountedKeys = [];
1059
+ };
1060
+ return {
1061
+ get mountedKeys() {
1062
+ return mountedKeys;
1063
+ },
1064
+ get animating() {
1065
+ return animating;
1066
+ },
1067
+ get leavingKey() {
1068
+ return leavingKey;
1069
+ },
1070
+ setActive,
1071
+ attach,
1072
+ isMounted,
1073
+ isVisible,
1074
+ role,
1075
+ cancel,
1076
+ destroy
1077
+ };
1078
+ }
1079
+ //#endregion
1080
+ //#region src/sliding-indicator/indicator.ts
1081
+ var INDICATOR_MS = 300;
1082
+ var INDICATOR_EASING = "cubic-bezier(0.25, 1, 0.5, 1)";
1083
+ function translate3d(left, top) {
1084
+ return `translate3d(${left}px, ${top}px, 0)`;
1085
+ }
1086
+ function createSlidingIndicator(options) {
1087
+ let durationMs = options.durationMs ?? 300;
1088
+ let easing = options.easing ?? "cubic-bezier(0.25, 1, 0.5, 1)";
1089
+ let laidOut = false;
1090
+ let prevLeft = 0;
1091
+ let prevTop = 0;
1092
+ let currentAnim = null;
1093
+ let destroyed = false;
1094
+ let observedTrack = null;
1095
+ let cancelAnim = () => {
1096
+ if (!currentAnim) return;
1097
+ currentAnim.cancel();
1098
+ currentAnim = null;
1099
+ };
1100
+ let measure = () => {
1101
+ if (destroyed) return;
1102
+ let track = options.getTrack();
1103
+ if (track && track !== observedTrack) {
1104
+ if (observedTrack) observer.unobserve(observedTrack);
1105
+ observer.observe(track);
1106
+ observedTrack = track;
1107
+ }
1108
+ let indicator = options.getIndicator();
1109
+ let active = options.getActive();
1110
+ if (!indicator || !active) return;
1111
+ let left = active.offsetLeft;
1112
+ let top = active.offsetTop;
1113
+ let width = active.offsetWidth;
1114
+ let height = active.offsetHeight;
1115
+ let next = translate3d(left, top);
1116
+ let shouldAnimate = laidOut && (options.enabled?.() ?? true);
1117
+ applyStyles(indicator, {
1118
+ width: `${width}px`,
1119
+ height: `${height}px`
1120
+ });
1121
+ if (!shouldAnimate) {
1122
+ cancelAnim();
1123
+ applyStyles(indicator, { transform: next });
1124
+ prevLeft = left;
1125
+ prevTop = top;
1126
+ laidOut = true;
1127
+ return;
1128
+ }
1129
+ let from = translate3d(prevLeft, prevTop);
1130
+ cancelAnim();
1131
+ applyStyles(indicator, { transform: next });
1132
+ currentAnim = animateElement(indicator, [{ transform: from }, { transform: next }], {
1133
+ duration: durationMs,
1134
+ easing,
1135
+ fill: "forwards"
1136
+ });
1137
+ prevLeft = left;
1138
+ prevTop = top;
1139
+ };
1140
+ let observer = new ResizeObserver(() => {
1141
+ measure();
1142
+ });
1143
+ let initialTrack = options.getTrack();
1144
+ if (initialTrack) {
1145
+ observer.observe(initialTrack);
1146
+ observedTrack = initialTrack;
1147
+ }
1148
+ let destroy = () => {
1149
+ if (destroyed) return;
1150
+ destroyed = true;
1151
+ cancelAnim();
1152
+ observer.disconnect();
1153
+ observedTrack = null;
1154
+ };
1155
+ return {
1156
+ measure,
1157
+ destroy
1158
+ };
1159
+ }
1160
+ //#endregion
1161
+ //#region src/list-reorder/classify.ts
1162
+ /** orderDiff = newIndex − oldIndex for keys in curr; new keys → −Infinity. */
1163
+ function buildOrderDiff(prev, curr) {
1164
+ let diff = /* @__PURE__ */ new Map();
1165
+ for (let [key, newIdx] of curr) {
1166
+ let oldIdx = prev.get(key);
1167
+ if (oldIdx === void 0) {
1168
+ diff.set(key, -Infinity);
1169
+ continue;
1170
+ }
1171
+ diff.set(key, newIdx - oldIdx);
1172
+ }
1173
+ return diff;
1174
+ }
1175
+ /**
1176
+ * Majority vertical direction → move; minority / non-finite → opacity; zero → none.
1177
+ * Adjacent swap (1 up, 1 down): up is opacity, down is move.
1178
+ */
1179
+ function classifyReorderAnim(orderDiffByKey, key) {
1180
+ let orderDiff = orderDiffByKey.get(key);
1181
+ if (orderDiff === void 0 || orderDiff === 0) return "none";
1182
+ if (!Number.isFinite(orderDiff)) return "opacity";
1183
+ let numberOfUp = 0;
1184
+ let numberOfDown = 0;
1185
+ for (let d of orderDiffByKey.values()) {
1186
+ if (!Number.isFinite(d) || d === 0) continue;
1187
+ if (d < 0) numberOfUp++;
1188
+ else if (d > 0) numberOfDown++;
1189
+ }
1190
+ if (numberOfUp <= numberOfDown && orderDiff < 0 || numberOfDown < numberOfUp && orderDiff > 0) return "opacity";
1191
+ return "move";
1192
+ }
1193
+ //#endregion
1194
+ //#region src/list-reorder/reorder.ts
1195
+ var LIST_REORDER_MS = 200;
1196
+ var LIST_REORDER_EASING = "ease-out";
1197
+ var LIST_REORDER_EPSILON_PX = 1;
1198
+ function createListReorder(options) {
1199
+ let durationMs = options.durationMs ?? 200;
1200
+ let easing = options.easing ?? "ease-out";
1201
+ let isEnabled = options.isEnabled ?? (() => true);
1202
+ let isReduced = options.isReduced ?? (() => false);
1203
+ let isSuppressed = options.isSuppressed ?? (() => false);
1204
+ let itemEls = /* @__PURE__ */ new Map();
1205
+ let activeAnims = /* @__PURE__ */ new Map();
1206
+ let prevIndexByKey = null;
1207
+ let destroyed = false;
1208
+ let buildIndexMap = (items) => {
1209
+ let map = /* @__PURE__ */ new Map();
1210
+ for (let i = 0; i < items.length; i++) map.set(options.getKey(items[i], i), i);
1211
+ return map;
1212
+ };
1213
+ let cancelAnim = (key) => {
1214
+ let anim = activeAnims.get(key);
1215
+ if (!anim) return;
1216
+ anim.cancel();
1217
+ activeAnims.delete(key);
1218
+ };
1219
+ let cancelAll = () => {
1220
+ for (let key of [...activeAnims.keys()]) cancelAnim(key);
1221
+ };
1222
+ let register = (el, key) => {
1223
+ if (destroyed) return {
1224
+ update: () => {},
1225
+ destroy: () => {}
1226
+ };
1227
+ itemEls.set(key, el);
1228
+ return {
1229
+ update: (next) => {
1230
+ if (destroyed || next === key) return;
1231
+ cancelAnim(key);
1232
+ if (itemEls.get(key) === el) itemEls.delete(key);
1233
+ key = next;
1234
+ itemEls.set(key, el);
1235
+ },
1236
+ destroy: () => {
1237
+ if (itemEls.get(key) === el) itemEls.delete(key);
1238
+ cancelAnim(key);
1239
+ }
1240
+ };
1241
+ };
1242
+ let sync = (items) => {
1243
+ if (destroyed) return;
1244
+ if (!isEnabled() || isReduced()) {
1245
+ prevIndexByKey = null;
1246
+ return;
1247
+ }
1248
+ let curr = buildIndexMap(items);
1249
+ let prev = prevIndexByKey;
1250
+ let suppressed = isSuppressed();
1251
+ if (prev && !suppressed) {
1252
+ let orderDiffByKey = buildOrderDiff(prev, curr);
1253
+ let itemHeight = options.getItemHeight();
1254
+ for (let [key, el] of itemEls) {
1255
+ let kind = classifyReorderAnim(orderDiffByKey, key);
1256
+ if (kind === "none") continue;
1257
+ cancelAnim(key);
1258
+ let anim;
1259
+ if (kind === "opacity") anim = animateElement(el, [{ opacity: 0 }, { opacity: 1 }], {
1260
+ duration: durationMs,
1261
+ easing
1262
+ });
1263
+ else {
1264
+ let delta = -(orderDiffByKey.get(key) ?? 0) * itemHeight;
1265
+ if (Math.abs(delta) <= 1) continue;
1266
+ anim = animateElement(el, [{ transform: `translateY(${delta}px)` }, { transform: "translateY(0)" }], {
1267
+ duration: durationMs,
1268
+ easing
1269
+ });
1270
+ }
1271
+ if (!anim) continue;
1272
+ activeAnims.set(key, anim);
1273
+ let captured = anim;
1274
+ anim.finished.then(() => {
1275
+ if (activeAnims.get(key) === captured) activeAnims.delete(key);
1276
+ }).catch(() => {});
1277
+ }
1278
+ }
1279
+ prevIndexByKey = curr;
1280
+ };
1281
+ let cancel = () => {
1282
+ cancelAll();
1283
+ };
1284
+ let destroy = () => {
1285
+ if (destroyed) return;
1286
+ destroyed = true;
1287
+ cancelAll();
1288
+ itemEls.clear();
1289
+ prevIndexByKey = null;
1290
+ };
1291
+ return {
1292
+ register,
1293
+ sync,
1294
+ cancel,
1295
+ destroy
1296
+ };
1297
+ }
1298
+ //#endregion
1299
+ //#region src/fade/fade.ts
1300
+ var FADE_MS = 120;
1301
+ var FADE_EASING = "ease-out";
1302
+ var STYLE_KEYS$1 = ["will-change"];
1303
+ function createFade(el, options) {
1304
+ let durationMs = options?.durationMs ?? 120;
1305
+ let easing = options?.easing ?? "ease-out";
1306
+ let lastVisible;
1307
+ let current = null;
1308
+ let stopCurrent = () => {
1309
+ if (!current) return;
1310
+ current.anim?.cancel();
1311
+ current.resolve(false);
1312
+ current = null;
1313
+ };
1314
+ let setVisible = (visible) => {
1315
+ if (lastVisible === visible && !current) {
1316
+ let { playback, resolve } = createPlayback();
1317
+ resolve(true);
1318
+ return playback;
1319
+ }
1320
+ stopCurrent();
1321
+ lastVisible = visible;
1322
+ let { playback, resolve, isCancelled } = createPlayback();
1323
+ let from = visible ? "0" : "1";
1324
+ let to = visible ? "1" : "0";
1325
+ if (durationMs <= 0) {
1326
+ applyStyles(el, { opacity: to });
1327
+ resolve(true);
1328
+ return playback;
1329
+ }
1330
+ applyStyles(el, {
1331
+ opacity: from,
1332
+ "will-change": "opacity"
1333
+ });
1334
+ let anim = animateElement(el, [{ opacity: from }, { opacity: to }], {
1335
+ duration: durationMs,
1336
+ easing,
1337
+ fill: "forwards"
1338
+ });
1339
+ let run = {
1340
+ anim,
1341
+ resolve
1342
+ };
1343
+ current = run;
1344
+ let finish = (ran) => {
1345
+ if (current !== run) return;
1346
+ current = null;
1347
+ clearStyles(el, STYLE_KEYS$1);
1348
+ applyStyles(el, { opacity: to });
1349
+ resolve(ran);
1350
+ };
1351
+ let cancel = playback.cancel;
1352
+ playback.cancel = () => {
1353
+ if (current === run) {
1354
+ anim?.cancel();
1355
+ current = null;
1356
+ clearStyles(el, STYLE_KEYS$1);
1357
+ }
1358
+ cancel();
1359
+ };
1360
+ if (!anim) {
1361
+ finish(true);
1362
+ return playback;
1363
+ }
1364
+ anim.finished.then(() => finish(true), () => {
1365
+ if (!isCancelled() && current === run) finish(false);
1366
+ });
1367
+ return playback;
1368
+ };
1369
+ return { setVisible };
1370
+ }
1371
+ //#endregion
1372
+ //#region src/dock/transforms.ts
1373
+ var DOCK_FADE_OFFSET = "1.5rem";
1374
+ var DOCK_MS = 300;
1375
+ var DOCK_EASING = "cubic-bezier(0.25, 1, 0.5, 1)";
1376
+ function isVertical(edge) {
1377
+ return edge === "top" || edge === "bottom";
1378
+ }
1379
+ function isNegative(edge) {
1380
+ return edge === "left" || edge === "top";
1381
+ }
1382
+ function translate(edge, value) {
1383
+ return `translate${isVertical(edge) ? "Y" : "X"}(${value})`;
1384
+ }
1385
+ function dockTransforms(mode, edge) {
1386
+ let axisZero = translate(edge, "0");
1387
+ if (mode === "fade") return {
1388
+ closed: {
1389
+ transform: translate(edge, isNegative(edge) ? `-${DOCK_FADE_OFFSET}` : DOCK_FADE_OFFSET),
1390
+ opacity: "0"
1391
+ },
1392
+ open: {
1393
+ transform: axisZero,
1394
+ opacity: "1"
1395
+ }
1396
+ };
1397
+ return {
1398
+ closed: {
1399
+ transform: translate(edge, isNegative(edge) ? "-100%" : "100%"),
1400
+ opacity: "1"
1401
+ },
1402
+ open: {
1403
+ transform: axisZero,
1404
+ opacity: "1"
1405
+ }
1406
+ };
1407
+ }
1408
+ //#endregion
1409
+ //#region src/dock/dock.ts
1410
+ var PANEL_STYLE_KEYS = [
1411
+ "transform",
1412
+ "opacity",
1413
+ "will-change"
1414
+ ];
1415
+ var BACKDROP_STYLE_KEYS = ["opacity", "will-change"];
1416
+ function createDock(config) {
1417
+ let edge = config.edge ?? "right";
1418
+ let durationMs = config.durationMs ?? 300;
1419
+ let easing = config.easing ?? "cubic-bezier(0.25, 1, 0.5, 1)";
1420
+ let mounted = false;
1421
+ let leaving = false;
1422
+ let animating = false;
1423
+ let wantOpen = false;
1424
+ let destroyed = false;
1425
+ let panel = null;
1426
+ let backdrop = null;
1427
+ let active = null;
1428
+ let applyPanel = (el, state) => {
1429
+ applyStyles(el, {
1430
+ transform: state.transform,
1431
+ opacity: state.opacity,
1432
+ "will-change": "transform, opacity"
1433
+ });
1434
+ };
1435
+ let clearPanel = () => {
1436
+ if (panel) clearStyles(panel, PANEL_STYLE_KEYS);
1437
+ };
1438
+ let clearBackdrop = () => {
1439
+ if (backdrop) clearStyles(backdrop, BACKDROP_STYLE_KEYS);
1440
+ };
1441
+ let abortActive = () => {
1442
+ if (!active) return;
1443
+ active.panelAnim?.cancel();
1444
+ active.backdropAnim?.cancel();
1445
+ let resolve = active.resolve;
1446
+ active = null;
1447
+ resolve(false);
1448
+ };
1449
+ let settleInstant = (open) => {
1450
+ wantOpen = open;
1451
+ mounted = open;
1452
+ leaving = false;
1453
+ animating = false;
1454
+ if (open) {
1455
+ if (backdrop) applyStyles(backdrop, { opacity: "1" });
1456
+ return;
1457
+ }
1458
+ clearPanel();
1459
+ clearBackdrop();
1460
+ };
1461
+ let finish = (run, ran) => {
1462
+ if (active !== run) return;
1463
+ active = null;
1464
+ animating = false;
1465
+ leaving = false;
1466
+ if (wantOpen) {
1467
+ mounted = true;
1468
+ if (panel) {
1469
+ applyPanel(panel, dockTransforms(run.mode, edge).open);
1470
+ clearStyles(panel, ["will-change"]);
1471
+ }
1472
+ if (backdrop) {
1473
+ applyStyles(backdrop, { opacity: "1" });
1474
+ clearStyles(backdrop, ["will-change"]);
1475
+ }
1476
+ } else {
1477
+ mounted = false;
1478
+ clearPanel();
1479
+ clearBackdrop();
1480
+ }
1481
+ run.resolve(ran);
1482
+ };
1483
+ let animatePair = (run) => {
1484
+ let tx = dockTransforms(run.mode, edge);
1485
+ let from = run.opening ? tx.closed : tx.open;
1486
+ let to = run.opening ? tx.open : tx.closed;
1487
+ if (panel) run.panelAnim = animateElement(panel, [from, to], {
1488
+ duration: durationMs,
1489
+ easing,
1490
+ fill: "forwards"
1491
+ });
1492
+ if (backdrop) run.backdropAnim = animateElement(backdrop, run.opening ? [{ opacity: "0" }, { opacity: "1" }] : [{ opacity: "1" }, { opacity: "0" }], {
1493
+ duration: durationMs,
1494
+ easing,
1495
+ fill: "forwards"
1496
+ });
1497
+ let waiter = run.panelAnim ?? run.backdropAnim;
1498
+ if (!waiter) {
1499
+ finish(run, true);
1500
+ return;
1501
+ }
1502
+ waiter.finished.then(() => finish(run, true), () => void 0);
1503
+ };
1504
+ let performOpen = async (run) => {
1505
+ await dualRaf();
1506
+ if (active !== run || destroyed || run.isCancelled()) return;
1507
+ animatePair(run);
1508
+ };
1509
+ let kick = (run) => {
1510
+ if (run.started || run !== active || destroyed) return;
1511
+ if (run.opening) {
1512
+ if (!panel && !backdrop) return;
1513
+ run.started = true;
1514
+ let tx = dockTransforms(run.mode, edge);
1515
+ if (panel) {
1516
+ applyPanel(panel, tx.closed);
1517
+ panel.offsetWidth;
1518
+ }
1519
+ if (backdrop) applyStyles(backdrop, {
1520
+ opacity: "0",
1521
+ "will-change": "opacity"
1522
+ });
1523
+ performOpen(run);
1524
+ return;
1525
+ }
1526
+ if (!panel && !backdrop) {
1527
+ finish(run, true);
1528
+ return;
1529
+ }
1530
+ run.started = true;
1531
+ if (panel) {
1532
+ applyPanel(panel, dockTransforms(run.mode, edge).open);
1533
+ panel.offsetWidth;
1534
+ }
1535
+ if (backdrop) applyStyles(backdrop, {
1536
+ opacity: "1",
1537
+ "will-change": "opacity"
1538
+ });
1539
+ animatePair(run);
1540
+ };
1541
+ let bindCancel = (run) => {
1542
+ let cancel = run.playback.cancel;
1543
+ run.playback.cancel = () => {
1544
+ if (active === run) {
1545
+ run.panelAnim?.cancel();
1546
+ run.backdropAnim?.cancel();
1547
+ active = null;
1548
+ animating = false;
1549
+ leaving = false;
1550
+ if (!wantOpen) {
1551
+ mounted = false;
1552
+ clearPanel();
1553
+ clearBackdrop();
1554
+ }
1555
+ }
1556
+ cancel();
1557
+ };
1558
+ };
1559
+ let setOpen = (open) => {
1560
+ if (destroyed) {
1561
+ let { playback, resolve } = createPlayback();
1562
+ resolve(false);
1563
+ return playback;
1564
+ }
1565
+ if (open === wantOpen && mounted === open && !animating) {
1566
+ let { playback, resolve } = createPlayback();
1567
+ resolve(true);
1568
+ return playback;
1569
+ }
1570
+ abortActive();
1571
+ wantOpen = open;
1572
+ let mode = config.getMode();
1573
+ if (mode === "none" || durationMs <= 0) {
1574
+ let { playback, resolve } = createPlayback();
1575
+ settleInstant(open);
1576
+ resolve(true);
1577
+ return playback;
1578
+ }
1579
+ let { playback, resolve, isCancelled } = createPlayback();
1580
+ mounted = true;
1581
+ leaving = !open;
1582
+ animating = true;
1583
+ let run = {
1584
+ opening: open,
1585
+ mode,
1586
+ playback,
1587
+ resolve,
1588
+ isCancelled,
1589
+ panelAnim: null,
1590
+ backdropAnim: null,
1591
+ started: false
1592
+ };
1593
+ active = run;
1594
+ bindCancel(run);
1595
+ kick(run);
1596
+ return playback;
1597
+ };
1598
+ let attach = (el) => {
1599
+ if (!destroyed) {
1600
+ panel = el;
1601
+ if (active) kick(active);
1602
+ }
1603
+ return { destroy: () => {
1604
+ if (panel === el) panel = null;
1605
+ } };
1606
+ };
1607
+ let attachBackdrop = (el) => {
1608
+ if (!destroyed) {
1609
+ backdrop = el;
1610
+ if (active) kick(active);
1611
+ }
1612
+ return { destroy: () => {
1613
+ if (backdrop === el) backdrop = null;
1614
+ } };
1615
+ };
1616
+ let destroy = () => {
1617
+ if (destroyed) return;
1618
+ destroyed = true;
1619
+ abortActive();
1620
+ clearPanel();
1621
+ clearBackdrop();
1622
+ mounted = false;
1623
+ leaving = false;
1624
+ animating = false;
1625
+ wantOpen = false;
1626
+ panel = null;
1627
+ backdrop = null;
1628
+ };
1629
+ return {
1630
+ get mounted() {
1631
+ return mounted;
1632
+ },
1633
+ get leaving() {
1634
+ return leaving;
1635
+ },
1636
+ get animating() {
1637
+ return animating;
1638
+ },
1639
+ setOpen,
1640
+ attach,
1641
+ attachBackdrop,
1642
+ destroy
1643
+ };
1644
+ }
1645
+ //#endregion
1646
+ //#region src/popover/popover.ts
1647
+ var POPOVER_MS = 120;
1648
+ var POPOVER_EASING = "ease-out";
1649
+ var POPOVER_ORIGIN = "center top";
1650
+ var CLOSED = {
1651
+ transform: "scale(0.92)",
1652
+ opacity: "0"
1653
+ };
1654
+ var OPEN = {
1655
+ transform: "scale(1)",
1656
+ opacity: "1"
1657
+ };
1658
+ var STYLE_KEYS = ["will-change"];
1659
+ function createPopover() {
1660
+ let lastOrigin = POPOVER_ORIGIN;
1661
+ let current = null;
1662
+ let stopCurrent = () => {
1663
+ if (!current) return;
1664
+ current.anim?.cancel();
1665
+ current.resolve(false);
1666
+ current = null;
1667
+ };
1668
+ let play = (el, opening, options) => {
1669
+ stopCurrent();
1670
+ let { playback, resolve, isCancelled } = createPlayback();
1671
+ let durationMs = options?.durationMs ?? 120;
1672
+ let easing = options?.easing ?? "ease-out";
1673
+ if (options?.origin != null) lastOrigin = options.origin;
1674
+ applyStyles(el, { "transform-origin": options?.origin ?? lastOrigin });
1675
+ let from = opening ? CLOSED : OPEN;
1676
+ let to = opening ? OPEN : CLOSED;
1677
+ if (durationMs <= 0) {
1678
+ applyStyles(el, {
1679
+ transform: to.transform,
1680
+ opacity: to.opacity
1681
+ });
1682
+ resolve(true);
1683
+ return playback;
1684
+ }
1685
+ applyStyles(el, {
1686
+ transform: from.transform,
1687
+ opacity: from.opacity,
1688
+ "will-change": "transform, opacity"
1689
+ });
1690
+ let anim = animateElement(el, [from, to], {
1691
+ duration: durationMs,
1692
+ easing,
1693
+ fill: "forwards"
1694
+ });
1695
+ let run = {
1696
+ anim,
1697
+ resolve
1698
+ };
1699
+ current = run;
1700
+ let finish = (ran) => {
1701
+ if (current !== run) return;
1702
+ current = null;
1703
+ clearStyles(el, STYLE_KEYS);
1704
+ applyStyles(el, {
1705
+ transform: to.transform,
1706
+ opacity: to.opacity
1707
+ });
1708
+ resolve(ran);
1709
+ };
1710
+ let cancel = playback.cancel;
1711
+ playback.cancel = () => {
1712
+ if (current === run) {
1713
+ anim?.cancel();
1714
+ current = null;
1715
+ clearStyles(el, STYLE_KEYS);
1716
+ }
1717
+ cancel();
1718
+ };
1719
+ if (!anim) {
1720
+ finish(true);
1721
+ return playback;
1722
+ }
1723
+ anim.finished.then(() => finish(true), () => {
1724
+ if (!isCancelled() && current === run) finish(false);
1725
+ });
1726
+ return playback;
1727
+ };
1728
+ return {
1729
+ playOpen: (el, options) => play(el, true, options),
1730
+ playClose: (el, options) => play(el, false, options)
1731
+ };
1732
+ }
1733
+ //#endregion
1734
+ //#region src/digit-flip/slots.ts
1735
+ var DIGIT_FLIP_MS = 200;
1736
+ var MAX_SIMULTANEOUS_DIGIT_FLIPS = 10;
1737
+ function buildDigitSlots(text, prevText, shouldAnimate) {
1738
+ if (!shouldAnimate || prevText === void 0 || prevText === text) return [...text].map((char) => ({
1739
+ kind: "static",
1740
+ char
1741
+ }));
1742
+ let slots = [];
1743
+ let textLength = text.length;
1744
+ let prevLength = prevText.length;
1745
+ for (let i = 0; i < textLength; i++) {
1746
+ let charIndex = textLength - 1 - i;
1747
+ let prevIndex = prevLength - 1 - i;
1748
+ let char = text[charIndex];
1749
+ let prevChar = prevIndex >= 0 ? prevText[prevIndex] : "";
1750
+ if (char !== prevChar) slots.unshift({
1751
+ kind: "flip",
1752
+ char,
1753
+ prevChar
1754
+ });
1755
+ else slots.unshift({
1756
+ kind: "static",
1757
+ char
1758
+ });
1759
+ }
1760
+ return slots;
1761
+ }
1762
+ function formatCounterText(value) {
1763
+ let n = Math.max(0, Math.floor(value));
1764
+ return n >= 1e3 ? "999+" : String(n);
1765
+ }
1766
+ function shouldPresencePop(prev, next) {
1767
+ return prev === 0 && next > 0;
1768
+ }
1769
+ var scheduled = 0;
1770
+ var resetQueued = false;
1771
+ function scheduleDigitFlip(condition) {
1772
+ if (!condition || scheduled >= 10) return false;
1773
+ if (!resetQueued) {
1774
+ resetQueued = true;
1775
+ setTimeout(() => {
1776
+ scheduled = 0;
1777
+ resetQueued = false;
1778
+ }, 0);
1779
+ }
1780
+ scheduled += 1;
1781
+ return true;
1782
+ }
1783
+ //#endregion
1784
+ //#region src/digit-flip/play.ts
1785
+ var DIGIT_FLIP_EASING = "ease";
1786
+ var PRESENCE_POP_MS = 200;
1787
+ var PRESENCE_POP_EASING = "ease";
1788
+ function playTransform(el, frames, options, fallbackMs = 200, fallbackEasing = DIGIT_FLIP_EASING) {
1789
+ let { playback, resolve } = createPlayback();
1790
+ let anim = animateElement(el, frames, {
1791
+ duration: options?.durationMs ?? fallbackMs,
1792
+ easing: options?.easing ?? fallbackEasing,
1793
+ fill: "forwards"
1794
+ });
1795
+ if (!anim) {
1796
+ resolve(true);
1797
+ return playback;
1798
+ }
1799
+ let cancel = playback.cancel;
1800
+ playback.cancel = () => {
1801
+ anim.cancel();
1802
+ cancel();
1803
+ };
1804
+ anim.finished.then(() => resolve(true), () => resolve(false));
1805
+ return playback;
1806
+ }
1807
+ function playDigitFlip(el, options) {
1808
+ return playTransform(el, [{ transform: "rotateX(90deg)" }, { transform: "rotateX(0deg)" }], options);
1809
+ }
1810
+ function playPresencePop(el, options) {
1811
+ return playTransform(el, [{
1812
+ transform: "scale(0.6)",
1813
+ opacity: "0"
1814
+ }, {
1815
+ transform: "scale(1)",
1816
+ opacity: "1"
1817
+ }], options, 200, PRESENCE_POP_EASING);
1818
+ }
1819
+ //#endregion
1820
+ //#region src/send-flight/flight.ts
1821
+ var SEND_FLIGHT_MS = 200;
1822
+ var SEND_FLIGHT_EASING = "ease";
1823
+ function startTransform(tx, ty, sx, sy) {
1824
+ return `translate3d(${tx}px, ${ty}px, 0) scale(${sx}, ${sy})`;
1825
+ }
1826
+ function createImageClone(imageUrl) {
1827
+ let img = document.createElement("img");
1828
+ img.src = imageUrl;
1829
+ img.alt = "";
1830
+ img.draggable = false;
1831
+ return img;
1832
+ }
1833
+ function playSendFlight(options) {
1834
+ let flight = computeFlight(options.from, options.to);
1835
+ if (!flight) return null;
1836
+ let clone = options.node ? options.node.cloneNode(true) : createImageClone(options.imageUrl ?? "");
1837
+ applyStyles(clone, {
1838
+ position: "fixed",
1839
+ top: `${flight.to.top}px`,
1840
+ left: `${flight.to.left}px`,
1841
+ width: `${flight.to.width}px`,
1842
+ height: `${flight.to.height}px`,
1843
+ "transform-origin": "center center",
1844
+ "pointer-events": "none",
1845
+ "z-index": "2147483646",
1846
+ transform: startTransform(flight.fromTranslateX, flight.fromTranslateY, flight.fromScaleX, flight.fromScaleY)
1847
+ });
1848
+ options.host.appendChild(clone);
1849
+ let { playback, resolve } = createPlayback();
1850
+ let durationMs = options.durationMs ?? 200;
1851
+ let easing = options.easing ?? "ease";
1852
+ let anim = animateElement(clone, [{ transform: startTransform(flight.fromTranslateX, flight.fromTranslateY, flight.fromScaleX, flight.fromScaleY) }, { transform: "translate3d(0, 0, 0) scale(1, 1)" }], {
1853
+ duration: durationMs,
1854
+ easing,
1855
+ fill: "forwards"
1856
+ });
1857
+ let teardown = () => {
1858
+ if (clone.parentNode) clone.remove();
1859
+ };
1860
+ let cancel = playback.cancel;
1861
+ playback.cancel = () => {
1862
+ anim?.cancel();
1863
+ teardown();
1864
+ cancel();
1865
+ };
1866
+ let finish = (ran) => {
1867
+ teardown();
1868
+ resolve(ran);
1869
+ };
1870
+ if (!anim) {
1871
+ finish(true);
1872
+ return playback;
1873
+ }
1874
+ anim.finished.then(() => finish(true), () => finish(false));
1875
+ return playback;
1876
+ }
1877
+ //#endregion
1878
+ //#region src/swipe-reveal/swipe.ts
1879
+ var SWIPE_THRESHOLD = 56;
1880
+ var SWIPE_MAX = 80;
1881
+ var SWIPE_TWEEN_MS = 200;
1882
+ function shouldCommitSwipe(offset, threshold) {
1883
+ return offset >= threshold;
1884
+ }
1885
+ function rubberSwipeOffset(delta, max) {
1886
+ if (delta <= 0) return 0;
1887
+ if (delta <= max) return delta;
1888
+ let overflow = delta - max;
1889
+ return max + overflow / (1 + overflow / max);
1890
+ }
1891
+ function applyOffset(el, offset) {
1892
+ el.style.setProperty("transform", `translateX(${offset}px)`);
1893
+ }
1894
+ function createSwipeReveal(el, options) {
1895
+ let threshold = options.threshold ?? 56;
1896
+ let max = options.max ?? 80;
1897
+ let dragging = false;
1898
+ let startX = 0;
1899
+ let current = 0;
1900
+ let playback = null;
1901
+ let stopTween = () => {
1902
+ if (!playback) return;
1903
+ playback.cancel();
1904
+ playback = null;
1905
+ };
1906
+ let settleTo = (target) => {
1907
+ stopTween();
1908
+ let run = tween({
1909
+ from: current,
1910
+ to: target,
1911
+ durationMs: options.durationMs ?? 200,
1912
+ onUpdate: (value) => {
1913
+ current = value;
1914
+ applyOffset(el, value);
1915
+ }
1916
+ });
1917
+ playback = run;
1918
+ run.done.then(() => {
1919
+ if (playback === run) playback = null;
1920
+ });
1921
+ };
1922
+ let onPointerDown = (event) => {
1923
+ stopTween();
1924
+ dragging = true;
1925
+ startX = event.clientX - current;
1926
+ if (typeof el.setPointerCapture === "function") el.setPointerCapture(event.pointerId);
1927
+ };
1928
+ let onPointerMove = (event) => {
1929
+ if (!dragging) return;
1930
+ current = rubberSwipeOffset(event.clientX - startX, max);
1931
+ applyOffset(el, current);
1932
+ };
1933
+ let onPointerUp = (event) => {
1934
+ if (!dragging) return;
1935
+ dragging = false;
1936
+ if (typeof el.releasePointerCapture === "function") try {
1937
+ el.releasePointerCapture(event.pointerId);
1938
+ } catch {}
1939
+ if (shouldCommitSwipe(current, threshold)) {
1940
+ options.onCommit();
1941
+ settleTo(max);
1942
+ } else settleTo(0);
1943
+ };
1944
+ el.addEventListener("pointerdown", onPointerDown);
1945
+ el.addEventListener("pointermove", onPointerMove);
1946
+ el.addEventListener("pointerup", onPointerUp);
1947
+ el.addEventListener("pointercancel", onPointerUp);
1948
+ return { destroy: () => {
1949
+ stopTween();
1950
+ dragging = false;
1951
+ el.removeEventListener("pointerdown", onPointerDown);
1952
+ el.removeEventListener("pointermove", onPointerMove);
1953
+ el.removeEventListener("pointerup", onPointerUp);
1954
+ el.removeEventListener("pointercancel", onPointerUp);
1955
+ } };
1956
+ }
1957
+ //#endregion
1958
+ //#region src/scroll-tween/scroll.ts
1959
+ var SCROLL_TWEEN_MS = 200;
1960
+ function playScrollTween(el, options) {
1961
+ let fromLeft = el.scrollLeft;
1962
+ let fromTop = el.scrollTop;
1963
+ let toLeft = options.left ?? fromLeft;
1964
+ let toTop = options.top ?? fromTop;
1965
+ return tween({
1966
+ from: 0,
1967
+ to: 1,
1968
+ durationMs: options.durationMs ?? 200,
1969
+ onUpdate: (t) => {
1970
+ if (options.left != null) el.scrollLeft = lerp(fromLeft, toLeft, t);
1971
+ if (options.top != null) el.scrollTop = lerp(fromTop, toTop, t);
1972
+ }
1973
+ });
1974
+ }
1975
+ //#endregion
1976
+ //#region src/ripple/ripple.ts
1977
+ var RIPPLE_MS = 400;
1978
+ var RIPPLE_EASING = "ease-out";
1979
+ var RIPPLE_COLOR = "currentColor";
1980
+ var RIPPLE_SIZE_PX = 48;
1981
+ function playRipple(host, options) {
1982
+ let { playback, resolve } = createPlayback();
1983
+ let durationMs = options.durationMs ?? 400;
1984
+ let easing = options.easing ?? "ease-out";
1985
+ let ink = document.createElement("span");
1986
+ applyStyles(ink, {
1987
+ position: "absolute",
1988
+ left: `${options.x}px`,
1989
+ top: `${options.y}px`,
1990
+ width: `48px`,
1991
+ height: `48px`,
1992
+ "border-radius": "50%",
1993
+ background: options.color ?? "currentColor",
1994
+ "pointer-events": "none",
1995
+ "transform-origin": "center center"
1996
+ });
1997
+ host.appendChild(ink);
1998
+ let anim = animateElement(ink, [{
1999
+ transform: "translate(-50%, -50%) scale(0)",
2000
+ opacity: "1"
2001
+ }, {
2002
+ transform: "translate(-50%, -50%) scale(1)",
2003
+ opacity: "0"
2004
+ }], {
2005
+ duration: durationMs,
2006
+ easing,
2007
+ fill: "forwards"
2008
+ });
2009
+ let teardown = () => {
2010
+ if (ink.parentNode) ink.remove();
2011
+ };
2012
+ let cancel = playback.cancel;
2013
+ playback.cancel = () => {
2014
+ anim?.cancel();
2015
+ teardown();
2016
+ cancel();
2017
+ };
2018
+ let finish = (ran) => {
2019
+ teardown();
2020
+ resolve(ran);
2021
+ };
2022
+ if (!anim) {
2023
+ finish(true);
2024
+ return playback;
2025
+ }
2026
+ anim.finished.then(() => finish(true), () => finish(false));
2027
+ return playback;
2028
+ }
2029
+ //#endregion
2030
+ //#region src/pinch-zoom/math.ts
2031
+ var PINCH_MIN_SCALE = 1;
2032
+ var PINCH_MAX_SCALE = 20;
2033
+ var PINCH_WHEEL_FACTOR = .01;
2034
+ function clampScale(scale, min, max) {
2035
+ if (!Number.isFinite(scale)) return min;
2036
+ if (scale < min) return min;
2037
+ if (scale > max) return max;
2038
+ return scale;
2039
+ }
2040
+ function clampNumber(value, min, max) {
2041
+ let next = value;
2042
+ if (next < min) next = min;
2043
+ if (next > max) next = max;
2044
+ return next === 0 ? 0 : next;
2045
+ }
2046
+ function maxTranslate(scale, layoutW, layoutH, viewportW, viewportH) {
2047
+ return {
2048
+ translateX: Math.max(0, (layoutW * scale - viewportW) / 2),
2049
+ translateY: Math.max(0, (layoutH * scale - viewportH) / 2)
2050
+ };
2051
+ }
2052
+ function boundTranslate(translateX, translateY, scale, layoutW, layoutH, viewportW, viewportH) {
2053
+ let bounds = maxTranslate(scale, layoutW, layoutH, viewportW, viewportH);
2054
+ return {
2055
+ translateX: clampNumber(translateX, -bounds.translateX, bounds.translateX),
2056
+ translateY: clampNumber(translateY, -bounds.translateY, bounds.translateY)
2057
+ };
2058
+ }
2059
+ function boundState(state, layoutW, layoutH, viewportW, viewportH) {
2060
+ let bounded = boundTranslate(state.translateX, state.translateY, state.scale, layoutW, layoutH, viewportW, viewportH);
2061
+ return {
2062
+ scale: state.scale,
2063
+ translateX: bounded.translateX,
2064
+ translateY: bounded.translateY
2065
+ };
2066
+ }
2067
+ function zoomAtOrigin(state, nextScale, origin, layoutW, layoutH, viewportW, viewportH, minScale = 1, maxScale = 20) {
2068
+ let scale = clampScale(nextScale, minScale, maxScale);
2069
+ if (scale === state.scale) return boundState(state, layoutW, layoutH, viewportW, viewportH);
2070
+ let originX = origin?.offsetX ?? 0;
2071
+ let originY = origin?.offsetY ?? 0;
2072
+ let ratio = scale / state.scale;
2073
+ return boundState({
2074
+ scale,
2075
+ translateX: state.translateX + originX * (1 - ratio),
2076
+ translateY: state.translateY + originY * (1 - ratio)
2077
+ }, layoutW, layoutH, viewportW, viewportH);
2078
+ }
2079
+ function resetZoom() {
2080
+ return {
2081
+ scale: 1,
2082
+ translateX: 0,
2083
+ translateY: 0
2084
+ };
2085
+ }
2086
+ function zoomTransform(state) {
2087
+ return `translate3d(${state.translateX}px, ${state.translateY}px, 0) scale(${state.scale})`;
2088
+ }
2089
+ function nextScaleFromWheel(scale, deltaY, minScale = 1, maxScale = 20) {
2090
+ return clampScale(scale * Math.exp(-deltaY * PINCH_WHEEL_FACTOR), minScale, maxScale);
2091
+ }
2092
+ //#endregion
2093
+ //#region src/pinch-zoom/pinch.ts
2094
+ function applyState(el, state) {
2095
+ applyStyles(el, {
2096
+ transform: zoomTransform(state),
2097
+ "transform-origin": "0 0"
2098
+ });
2099
+ }
2100
+ function createPinchZoom(config) {
2101
+ let state = resetZoom();
2102
+ let el = config.getEl();
2103
+ let sizes = () => ({
2104
+ layout: config.getLayout(),
2105
+ viewport: config.getViewport()
2106
+ });
2107
+ let commit = (next) => {
2108
+ let { layout, viewport } = sizes();
2109
+ let scale = clampScale(next.scale, 1, 20);
2110
+ let bounded = boundTranslate(next.translateX, next.translateY, scale, layout.width, layout.height, viewport.width, viewport.height);
2111
+ state = {
2112
+ scale,
2113
+ translateX: bounded.translateX,
2114
+ translateY: bounded.translateY
2115
+ };
2116
+ let node = config.getEl();
2117
+ if (node) applyState(node, state);
2118
+ return state;
2119
+ };
2120
+ let onWheel = (event) => {
2121
+ let node = config.getEl();
2122
+ if (!node) return;
2123
+ let { layout, viewport } = sizes();
2124
+ if (event.ctrlKey || event.metaKey) {
2125
+ event.preventDefault();
2126
+ state = zoomAtOrigin(state, nextScaleFromWheel(state.scale, event.deltaY), {
2127
+ offsetX: event.offsetX,
2128
+ offsetY: event.offsetY
2129
+ }, layout.width, layout.height, viewport.width, viewport.height);
2130
+ applyState(node, state);
2131
+ return;
2132
+ }
2133
+ if (state.scale <= 1) return;
2134
+ event.preventDefault();
2135
+ let pan = boundTranslate(state.translateX - event.deltaX, state.translateY - event.deltaY, state.scale, layout.width, layout.height, viewport.width, viewport.height);
2136
+ state = {
2137
+ scale: state.scale,
2138
+ translateX: pan.translateX,
2139
+ translateY: pan.translateY
2140
+ };
2141
+ applyState(node, state);
2142
+ };
2143
+ if (el) el.addEventListener("wheel", onWheel, { passive: false });
2144
+ return {
2145
+ setState: (next) => commit(next),
2146
+ reset: () => commit(resetZoom()),
2147
+ destroy: () => {
2148
+ if (el) el.removeEventListener("wheel", onWheel);
2149
+ el = null;
2150
+ }
2151
+ };
2152
+ }
2153
+ //#endregion
2154
+ //#region src/waveform/decode.ts
2155
+ function decodeWaveform(encoded5bit) {
2156
+ let bitsCount = encoded5bit.length * 8;
2157
+ let valuesCount = Math.floor(bitsCount / 5);
2158
+ if (!valuesCount) return [];
2159
+ let result = [];
2160
+ for (let i = 0; i < valuesCount; i++) {
2161
+ let byteIndex = Math.floor(i * 5 / 8);
2162
+ let bitShift = i * 5 % 8;
2163
+ let lo = encoded5bit[byteIndex] ?? 0;
2164
+ let hi = encoded5bit[byteIndex + 1] ?? 0;
2165
+ result.push(lo + (hi << 8) >> bitShift & 31);
2166
+ }
2167
+ return result;
2168
+ }
2169
+ function fitWaveform(data, fitCount) {
2170
+ let count = Math.max(0, Math.floor(fitCount));
2171
+ if (count === 0) return [];
2172
+ if (data.length === 0) return Array.from({ length: count }, () => 0);
2173
+ let out = [];
2174
+ let spring = data.length / count;
2175
+ for (let i = 0; i < count; i++) {
2176
+ let idx = Math.min(data.length - 1, Math.floor(i * spring));
2177
+ out.push(data[idx] ?? 0);
2178
+ }
2179
+ return out;
2180
+ }
2181
+ //#endregion
2182
+ //#region src/spoiler/spoiler.ts
2183
+ var SPOILER_MS = 200;
2184
+ var SPOILER_EASING = "ease-out";
2185
+ var DOT_GAP = 6;
2186
+ var DOT_RADIUS = 1.15;
2187
+ function paintDots(canvas) {
2188
+ let ctx = canvas.getContext("2d");
2189
+ if (!ctx) return;
2190
+ let width = canvas.width;
2191
+ let height = canvas.height;
2192
+ ctx.clearRect(0, 0, width, height);
2193
+ ctx.fillStyle = "rgba(128, 128, 128, 0.9)";
2194
+ for (let y = DOT_GAP / 2; y < height; y += DOT_GAP) for (let x = DOT_GAP / 2; x < width; x += DOT_GAP) {
2195
+ ctx.beginPath();
2196
+ ctx.arc(x, y, DOT_RADIUS, 0, Math.PI * 2);
2197
+ ctx.fill();
2198
+ }
2199
+ }
2200
+ function createSpoiler(el, options) {
2201
+ let durationMs = options.durationMs ?? 200;
2202
+ let easing = options.easing ?? "ease-out";
2203
+ let canvas = document.createElement("canvas");
2204
+ applyStyles(canvas, {
2205
+ position: "absolute",
2206
+ inset: "0",
2207
+ width: "100%",
2208
+ height: "100%",
2209
+ "pointer-events": "none"
2210
+ });
2211
+ canvas.width = Math.max(1, el.clientWidth || 1);
2212
+ canvas.height = Math.max(1, el.clientHeight || 1);
2213
+ paintDots(canvas);
2214
+ el.appendChild(canvas);
2215
+ let fade = createFade(canvas, {
2216
+ durationMs,
2217
+ easing
2218
+ });
2219
+ if (options.revealed()) applyStyles(canvas, { opacity: "0" });
2220
+ return {
2221
+ reveal: () => fade.setVisible(false),
2222
+ reset: () => fade.setVisible(true),
2223
+ destroy: () => {
2224
+ if (canvas.parentNode) canvas.remove();
2225
+ }
2226
+ };
2227
+ }
2228
+ //#endregion
2229
+ export { ANIMATION_LEVELS, DEFAULT_ANIMATION_LEVEL, DIGIT_FLIP_EASING, DIGIT_FLIP_MS, DOCK_EASING, DOCK_FADE_OFFSET, DOCK_MS, FADE_EASING, FADE_MS, INDICATOR_EASING, INDICATOR_MS, LIST_REORDER_EASING, LIST_REORDER_EPSILON_PX, LIST_REORDER_MS, MAX_SIMULTANEOUS_DIGIT_FLIPS, PINCH_MAX_SCALE, PINCH_MIN_SCALE, PINCH_WHEEL_FACTOR, POPOVER_EASING, POPOVER_MS, POPOVER_ORIGIN, PRESENCE_POP_EASING, PRESENCE_POP_MS, RIPPLE_COLOR, RIPPLE_EASING, RIPPLE_MS, RIPPLE_SIZE_PX, SCROLL_TWEEN_MS, SEND_FLIGHT_EASING, SEND_FLIGHT_MS, SHARED_ELEMENT_EASING, SHARED_ELEMENT_END_MS, SHARED_ELEMENT_MS, SPOILER_EASING, SPOILER_MS, SWIPE_MAX, SWIPE_THRESHOLD, SWIPE_TWEEN_MS, VIEW_SLIDE_COVER_EASING, VIEW_SLIDE_COVER_MS, VIEW_SLIDE_EASING, VIEW_SLIDE_FADE_OFFSET, VIEW_SLIDE_MS, VIEW_SLIDE_REVEAL_EASING, VIEW_SLIDE_REVEAL_MS, VIEW_SLIDE_SETTLE_SLACK_MS, VIEW_SLIDE_ZOOM_EASING, VIEW_SLIDE_ZOOM_MS, boundTranslate, buildDigitSlots, buildOrderDiff, canAnimate, centerFitInViewport, clampScale, classifyReorderAnim, computeCloseFlight, computeFlight, computeOpenFlight, createDock, createFade, createListReorder, createPinchZoom, createPopover, createSharedElement, createSlidingIndicator, createSpoiler, createSwipeReveal, createViewSlide, cycleAnimationLevel, decodeWaveform, defaultAnimationLevel, dockTransforms, dualRaf, easeOutCubic, fitContain, fitWaveform, formatCounterText, isAnimationLevel, isRectFullyVisibleIn, isRectInViewport, lerp, maxTranslate, nextScaleFromWheel, offViewportLandingRect, parseAnimationLevel, pickAnimationLevelFromRatio, playDigitFlip, playPresencePop, playRipple, playScrollTween, playSendFlight, playSharedElement, prefersReducedMotion, resetZoom, resolveNaturalSize, resolveViewSlideMode, rubberSwipeOffset, scheduleDigitFlip, shouldCommitSwipe, shouldPresencePop, slideDirectionByIndex, stepAnimationLevel, tween, uncoverByAspect, viewSlideDurationMs, viewSlideEasing, viewSlideTransforms, zoomAtOrigin, zoomTransform };