@surdeddd/bottom-sheet 0.12.0 → 0.13.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 (58) hide show
  1. package/README.md +52 -1
  2. package/dist/{BottomSheetEngine-D6oiEfZ4.d.cts → BottomSheetCore-CsIjyq7C.d.cts} +60 -5
  3. package/dist/{BottomSheetEngine-Dhy0X_e9.d.ts → BottomSheetCore-D16lsa6g.d.ts} +60 -5
  4. package/dist/BottomSheetEngine-BYjAVrmd.d.cts +7 -0
  5. package/dist/BottomSheetEngine-YuCNugZn.d.ts +7 -0
  6. package/dist/core.cjs +3476 -0
  7. package/dist/core.d.cts +3 -0
  8. package/dist/core.d.ts +3 -0
  9. package/dist/core.js +3474 -0
  10. package/dist/debug.cjs +109 -0
  11. package/dist/debug.d.cts +22 -0
  12. package/dist/debug.d.ts +22 -0
  13. package/dist/debug.js +107 -0
  14. package/dist/element.cjs +972 -685
  15. package/dist/element.d.cts +2 -1
  16. package/dist/element.d.ts +2 -1
  17. package/dist/element.iife.global.js +2 -2
  18. package/dist/element.js +972 -685
  19. package/dist/features.cjs +533 -0
  20. package/dist/features.d.cts +13 -0
  21. package/dist/features.d.ts +13 -0
  22. package/dist/features.js +526 -0
  23. package/dist/index-DdYLOIIT.d.ts +15 -0
  24. package/dist/index-dfrzw-lI.d.cts +15 -0
  25. package/dist/index.cjs +805 -534
  26. package/dist/index.d.cts +6 -15
  27. package/dist/index.d.ts +6 -15
  28. package/dist/index.js +799 -535
  29. package/dist/integrations/formik.d.cts +1 -1
  30. package/dist/integrations/formik.d.ts +1 -1
  31. package/dist/integrations/react-hook-form.d.cts +1 -1
  32. package/dist/integrations/react-hook-form.d.ts +1 -1
  33. package/dist/overlay.cjs +6 -5
  34. package/dist/overlay.js +6 -5
  35. package/dist/preact.d.cts +2 -1
  36. package/dist/preact.d.ts +2 -1
  37. package/dist/qwik-lib/index.qwik.cjs +4267 -0
  38. package/dist/qwik-lib/index.qwik.mjs +4267 -0
  39. package/dist/qwik.cjs +798 -534
  40. package/dist/qwik.d.cts +2 -2
  41. package/dist/qwik.d.ts +2 -2
  42. package/dist/qwik.js +798 -534
  43. package/dist/react.cjs +798 -534
  44. package/dist/react.d.cts +3 -2
  45. package/dist/react.d.ts +3 -2
  46. package/dist/react.js +798 -534
  47. package/dist/solid.cjs +798 -534
  48. package/dist/solid.d.cts +63 -4
  49. package/dist/solid.d.ts +63 -4
  50. package/dist/solid.js +798 -534
  51. package/dist/solid.source.jsx +798 -534
  52. package/dist/svelte-core.cjs +798 -534
  53. package/dist/svelte-core.js +798 -534
  54. package/dist/svelte.cjs +798 -534
  55. package/dist/svelte.js +798 -534
  56. package/dist/vue.cjs +909 -637
  57. package/dist/vue.js +909 -637
  58. package/package.json +63 -11
package/dist/vue.cjs CHANGED
@@ -350,74 +350,6 @@ var SnapResolver = class {
350
350
  }
351
351
  };
352
352
 
353
- // src/core/features/persist.ts
354
- function readPersistedId(key) {
355
- if (!key) return null;
356
- if (typeof window === "undefined") return null;
357
- if (typeof localStorage === "undefined") return null;
358
- try {
359
- return localStorage.getItem(key);
360
- } catch {
361
- return null;
362
- }
363
- }
364
- function installPersist(engine, key) {
365
- const unsubscribe = engine.on("snap", ({ id }) => {
366
- if (typeof window === "undefined") return;
367
- if (typeof localStorage === "undefined") return;
368
- try {
369
- localStorage.setItem(key, id);
370
- } catch {
371
- }
372
- });
373
- return unsubscribe;
374
- }
375
-
376
- // src/core/features/auto-collapse.ts
377
- function installAutoCollapse(deps) {
378
- const { ms } = deps;
379
- if (ms === void 0 || ms <= 0) {
380
- return () => {
381
- };
382
- }
383
- let timer = null;
384
- const clearTimer = () => {
385
- if (timer !== null) {
386
- clearTimeout(timer);
387
- timer = null;
388
- }
389
- };
390
- const fire = () => {
391
- if (deps.isDestroyed()) return;
392
- if (deps.isDragging()) return;
393
- const active = deps.resolveSnap(deps.getActiveId());
394
- if (!active || active.size === 0) return;
395
- let target = null;
396
- for (const id of deps.getAllowedIds()) {
397
- const snap = deps.resolveSnap(id);
398
- if (!snap || snap.size <= 0) continue;
399
- if (!target || snap.size < target.size) target = snap;
400
- }
401
- if (!target) return;
402
- if (target.id === active.id || target.size >= active.size) return;
403
- deps.snapTo(target.id);
404
- };
405
- const reset = () => {
406
- clearTimer();
407
- if (deps.isDestroyed()) return;
408
- timer = setTimeout(() => {
409
- timer = null;
410
- fire();
411
- }, ms);
412
- };
413
- const unsubscribe = deps.on("snap", () => reset());
414
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
415
- return () => {
416
- clearTimer();
417
- unsubscribe();
418
- };
419
- }
420
-
421
353
  // src/core/features/linked-sheets.ts
422
354
  function notifyLinkedSheets(sheets, self) {
423
355
  if (sheets.length === 0) return;
@@ -440,394 +372,65 @@ function notifyLinkedSheets(sheets, self) {
440
372
  }
441
373
  }
442
374
 
443
- // src/core/features/history-coordinator.ts
444
- var entries = [];
445
- var subscribers = /* @__PURE__ */ new Set();
446
- var suppress = 0;
447
- var internalDepth = 0;
448
- var pendingRebrand = null;
449
- var listenerInstalled = false;
450
- var patchInstalled = false;
451
- var origPushState = null;
452
- var origReplaceState = null;
453
- var patchedPushState = null;
454
- var patchedReplaceState = null;
455
- var hasWindow = () => typeof window !== "undefined";
456
- var hasHistory = () => typeof history !== "undefined";
457
- var brandMarker = (surface) => ({
458
- ...surface.markerState(),
459
- __bs: true
460
- });
461
- var runInternal = (fn) => {
462
- internalDepth += 1;
463
- try {
464
- fn();
465
- } finally {
466
- internalDepth -= 1;
467
- }
468
- };
469
- var notifyRouteChange = () => {
470
- const snapshot = Array.from(subscribers);
471
- for (const fn of snapshot) fn();
472
- };
473
- var ensureListener = () => {
474
- if (listenerInstalled || !hasWindow()) return;
475
- window.addEventListener("popstate", onPopState);
476
- listenerInstalled = true;
477
- };
478
- var maybeTeardownListener = () => {
479
- if (!listenerInstalled) return;
480
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
481
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
482
- listenerInstalled = false;
483
- };
484
- var installPatch = () => {
485
- if (patchInstalled || !hasHistory()) return;
486
- origPushState = history.pushState;
487
- origReplaceState = history.replaceState;
488
- patchedPushState = function(...args) {
489
- origPushState.apply(this, args);
490
- if (internalDepth === 0) notifyRouteChange();
491
- };
492
- patchedReplaceState = function(...args) {
493
- origReplaceState.apply(this, args);
494
- if (internalDepth === 0) notifyRouteChange();
495
- };
496
- history.pushState = patchedPushState;
497
- history.replaceState = patchedReplaceState;
498
- patchInstalled = true;
499
- };
500
- var removePatch = () => {
501
- if (!patchInstalled) return;
502
- if (history.pushState === patchedPushState && origPushState) {
503
- history.pushState = origPushState;
504
- }
505
- if (history.replaceState === patchedReplaceState && origReplaceState) {
506
- history.replaceState = origReplaceState;
507
- }
508
- origPushState = null;
509
- origReplaceState = null;
510
- patchedPushState = null;
511
- patchedReplaceState = null;
512
- patchInstalled = false;
513
- };
514
- function onPopState() {
515
- if (suppress > 0) {
516
- suppress -= 1;
517
- const rebrand = pendingRebrand;
518
- pendingRebrand = null;
519
- if (rebrand) rebrand();
520
- maybeTeardownListener();
521
- return;
522
- }
523
- if (entries.length === 0) {
524
- notifyRouteChange();
525
- maybeTeardownListener();
526
- return;
375
+ // src/core/features/resize-observer.ts
376
+ function installResizeObserver(deps) {
377
+ if (typeof window === "undefined") {
378
+ return () => {
379
+ };
527
380
  }
528
- let target = null;
529
- let targetIdx = -1;
530
- for (let i = entries.length - 1; i >= 0; i -= 1) {
531
- const entry = entries[i];
532
- if (!entry.surface.isOpen()) continue;
533
- if (!target) {
534
- target = entry;
535
- targetIdx = i;
381
+ const onResize = () => {
382
+ if (deps.isDestroyed()) return;
383
+ deps.recomputeSnaps();
384
+ const mode = deps.getMode();
385
+ const isVerticalAxis = mode === "bottom" || mode === "top";
386
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
387
+ const max = deps.getMaxAxisSize();
388
+ if (viewportSize2 > 0 && viewportSize2 < max) {
389
+ deps.setMaxAxisSize(viewportSize2);
536
390
  }
537
- if (entry.surface.isTop()) {
538
- target = entry;
539
- targetIdx = i;
540
- break;
391
+ const wasAnimating = deps.isAnimating?.() ?? false;
392
+ deps.cancelInFlight();
393
+ deps.newCycle();
394
+ const current = deps.resolveActiveSnap();
395
+ const newMax = deps.getMaxAxisSize();
396
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
397
+ deps.setSize(targetSize);
398
+ if (current && !deps.isDragging()) {
399
+ deps.applySize(targetSize);
400
+ if (wasAnimating) deps.resyncAfterCancel?.();
541
401
  }
402
+ };
403
+ let resizeObserver = null;
404
+ if (typeof ResizeObserver !== "undefined") {
405
+ resizeObserver = new ResizeObserver(onResize);
406
+ resizeObserver.observe(document.documentElement);
407
+ } else {
408
+ window.addEventListener("resize", onResize);
542
409
  }
543
- if (!target) {
544
- const removed = entries.pop();
545
- if (removed) removed.live = false;
546
- maybeTeardownListener();
547
- return;
548
- }
549
- target.live = false;
550
- entries.splice(targetIdx, 1);
551
- const settled = target;
552
- void Promise.resolve(settled.surface.close()).then(() => {
553
- if (settled.surface.isOpen()) restore(settled);
554
- });
555
- maybeTeardownListener();
410
+ window.addEventListener("orientationchange", onResize);
411
+ const onVisibility = () => {
412
+ if (deps.isDestroyed()) return;
413
+ if (document.hidden) deps.cancelInFlight();
414
+ };
415
+ document.addEventListener("visibilitychange", onVisibility);
416
+ return () => {
417
+ resizeObserver?.disconnect();
418
+ window.removeEventListener("resize", onResize);
419
+ window.removeEventListener("orientationchange", onResize);
420
+ document.removeEventListener("visibilitychange", onVisibility);
421
+ };
556
422
  }
557
- function restore(entry) {
558
- if (!hasWindow() || !hasHistory()) return;
559
- entry.live = true;
560
- runInternal(() => {
561
- try {
562
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
563
- } catch {
564
- entry.live = false;
565
- }
566
- });
567
- if (entry.live) {
568
- entries.push(entry);
569
- ensureListener();
570
- }
571
- }
572
- function pushBackMarker(surface) {
573
- const entry = { surface, priorUrl: void 0, live: false };
574
- if (!hasWindow() || !hasHistory()) {
575
- return entry;
576
- }
577
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
578
- runInternal(() => {
579
- try {
580
- history.pushState(brandMarker(surface), "", surface.url);
581
- entry.live = true;
582
- } catch {
583
- entry.live = false;
584
- }
585
- });
586
- if (entry.live) {
587
- entries.push(entry);
588
- ensureListener();
589
- }
590
- return entry;
591
- }
592
- function popBackMarker(handle) {
593
- if (!hasWindow() || !hasHistory()) return;
594
- const entry = handle;
595
- const idx = entries.indexOf(entry);
596
- if (idx === -1 || !entry.live) return;
597
- const last = entries.length - 1;
598
- entry.live = false;
599
- entries.splice(idx, 1);
600
- if (idx !== last) {
601
- const rebrandSurface = entries[entries.length - 1].surface;
602
- const rebrandUrl = entry.priorUrl;
603
- pendingRebrand = () => {
604
- runInternal(() => {
605
- history.replaceState(
606
- brandMarker(rebrandSurface),
607
- "",
608
- rebrandUrl !== void 0 ? rebrandUrl : location.href
609
- );
610
- });
611
- };
612
- }
613
- suppress += 1;
614
- runInternal(() => {
615
- try {
616
- history.back();
617
- } catch {
618
- suppress = Math.max(0, suppress - 1);
619
- pendingRebrand = null;
620
- }
621
- });
622
- maybeTeardownListener();
623
- }
624
- function subscribeRouteChange(fn) {
625
- if (!hasWindow() || !hasHistory()) return () => {
626
- };
627
- subscribers.add(fn);
628
- if (subscribers.size === 1) installPatch();
629
- ensureListener();
630
- return () => {
631
- if (!subscribers.has(fn)) return;
632
- subscribers.delete(fn);
633
- if (subscribers.size === 0) removePatch();
634
- maybeTeardownListener();
635
- };
636
- }
637
-
638
- // src/core/features/route.ts
639
- function installRoute(deps) {
640
- if (typeof window === "undefined") {
641
- return () => {
642
- };
643
- }
644
- if (!deps.routedTo && !deps.closeOnBack) {
645
- return () => {
646
- };
647
- }
648
- let handle = null;
649
- const onOpen = () => {
650
- if (handle) return;
651
- handle = pushBackMarker({
652
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
653
- isTop: deps.isTopSheet,
654
- close: deps.close,
655
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
656
- url: deps.routedTo
657
- });
658
- };
659
- const onClose = () => {
660
- if (!handle) return;
661
- popBackMarker(handle);
662
- handle = null;
663
- };
664
- const unsubscribeOpen = deps.on("open", onOpen);
665
- const unsubscribeClose = deps.on("close", onClose);
666
- if (deps.getSize() > 0) onOpen();
667
- return () => {
668
- unsubscribeOpen();
669
- unsubscribeClose();
670
- if (handle) {
671
- popBackMarker(handle);
672
- handle = null;
673
- }
674
- };
675
- }
676
- function installRouteChange(deps) {
677
- return subscribeRouteChange(() => {
678
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
679
- void deps.close();
680
- });
681
- }
682
-
683
- // src/core/features/content-swipe.ts
684
- var CONTENT_PULL_THRESHOLD = 60;
685
- function installContentSwipe(deps) {
686
- const { container } = deps;
687
- let startY = 0;
688
- let startScrollTop = 0;
689
- let moved = false;
690
- const onTouchStart = (e) => {
691
- const t = e.touches[0];
692
- if (!t) return;
693
- startY = t.clientY;
694
- startScrollTop = container.scrollTop;
695
- moved = false;
696
- };
697
- const onTouchMove = (e) => {
698
- const t = e.touches[0];
699
- if (!t) return;
700
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
701
- };
702
- const onTouchEnd = (e) => {
703
- if (!moved) return;
704
- if (deps.isDragging() || deps.isAnimating()) return;
705
- if (container.scrollTop !== startScrollTop) return;
706
- const t = e.changedTouches[0];
707
- if (!t) return;
708
- const deltaY = t.clientY - startY;
709
- const atTop = container.scrollTop <= 0;
710
- const allowed = deps.getAllowedIds();
711
- const idx = allowed.indexOf(deps.getActiveId());
712
- if (idx === -1) return;
713
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
714
- deps.snapTo(allowed[idx - 1]);
715
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
716
- deps.snapTo(allowed[idx + 1]);
717
- }
718
- };
719
- container.addEventListener("touchstart", onTouchStart, { passive: true });
720
- container.addEventListener("touchmove", onTouchMove, { passive: true });
721
- container.addEventListener("touchend", onTouchEnd, { passive: true });
722
- return () => {
723
- container.removeEventListener("touchstart", onTouchStart);
724
- container.removeEventListener("touchmove", onTouchMove);
725
- container.removeEventListener("touchend", onTouchEnd);
726
- };
727
- }
728
-
729
- // src/core/features/visual-viewport.ts
730
- var SAFETY_PAD = 8;
731
- function installVisualViewport(deps) {
732
- if (typeof window === "undefined" || !window.visualViewport) {
733
- return () => {
734
- };
735
- }
736
- const vv = window.visualViewport;
737
- let rafId = null;
738
- const onResize = () => {
739
- if (rafId !== null) return;
740
- rafId = requestAnimationFrame(() => {
741
- rafId = null;
742
- if (deps.isDestroyed()) return;
743
- deps.recomputeSnaps();
744
- const isVertical = deps.isVerticalAxis();
745
- const visible = isVertical ? vv.height : vv.width;
746
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
747
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
748
- if (clamp < deps.getMaxAxisSize()) {
749
- deps.setMaxAxisSize(clamp);
750
- }
751
- const wasAnimating = deps.isAnimating?.() ?? false;
752
- deps.cancelInFlight();
753
- deps.newCycle();
754
- const max = deps.getMaxAxisSize();
755
- deps.setSize(Math.min(deps.getSize(), max));
756
- const current = deps.resolveActiveSnap();
757
- if (current && !deps.isDragging()) {
758
- deps.applySize(Math.min(current.size, max));
759
- if (wasAnimating) deps.resyncAfterCancel?.();
760
- } else {
761
- deps.applySize(deps.getSize());
762
- }
763
- });
764
- };
765
- vv.addEventListener("resize", onResize);
766
- return () => {
767
- vv.removeEventListener("resize", onResize);
768
- if (rafId !== null) cancelAnimationFrame(rafId);
769
- };
770
- }
771
-
772
- // src/core/features/resize-observer.ts
773
- function installResizeObserver(deps) {
774
- if (typeof window === "undefined") {
775
- return () => {
776
- };
777
- }
778
- const onResize = () => {
779
- if (deps.isDestroyed()) return;
780
- deps.recomputeSnaps();
781
- const mode = deps.getMode();
782
- const isVerticalAxis = mode === "bottom" || mode === "top";
783
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
784
- const max = deps.getMaxAxisSize();
785
- if (viewportSize2 > 0 && viewportSize2 < max) {
786
- deps.setMaxAxisSize(viewportSize2);
787
- }
788
- const wasAnimating = deps.isAnimating?.() ?? false;
789
- deps.cancelInFlight();
790
- deps.newCycle();
791
- const current = deps.resolveActiveSnap();
792
- const newMax = deps.getMaxAxisSize();
793
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
794
- deps.setSize(targetSize);
795
- if (current && !deps.isDragging()) {
796
- deps.applySize(targetSize);
797
- if (wasAnimating) deps.resyncAfterCancel?.();
798
- }
799
- };
800
- let resizeObserver = null;
801
- if (typeof ResizeObserver !== "undefined") {
802
- resizeObserver = new ResizeObserver(onResize);
803
- resizeObserver.observe(document.documentElement);
804
- } else {
805
- window.addEventListener("resize", onResize);
806
- }
807
- window.addEventListener("orientationchange", onResize);
808
- const onVisibility = () => {
809
- if (deps.isDestroyed()) return;
810
- if (document.hidden) deps.cancelInFlight();
811
- };
812
- document.addEventListener("visibilitychange", onVisibility);
813
- return () => {
814
- resizeObserver?.disconnect();
815
- window.removeEventListener("resize", onResize);
816
- window.removeEventListener("orientationchange", onResize);
817
- document.removeEventListener("visibilitychange", onVisibility);
818
- };
819
- }
820
-
821
- // src/core/features/fit-observer.ts
822
- var contentTargets = (scroller) => {
823
- if (!scroller) return [];
824
- const hasSlot = typeof HTMLSlotElement !== "undefined";
825
- const out = [];
826
- for (const child of Array.from(scroller.children)) {
827
- if (hasSlot && child instanceof HTMLSlotElement) {
828
- for (const el of child.assignedElements()) out.push(el);
829
- } else {
830
- out.push(child);
423
+
424
+ // src/core/features/fit-observer.ts
425
+ var contentTargets = (scroller) => {
426
+ if (!scroller) return [];
427
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
428
+ const out = [];
429
+ for (const child of Array.from(scroller.children)) {
430
+ if (hasSlot && child instanceof HTMLSlotElement) {
431
+ for (const el of child.assignedElements()) out.push(el);
432
+ } else {
433
+ out.push(child);
831
434
  }
832
435
  }
833
436
  return out;
@@ -1669,6 +1272,52 @@ function resolveAnimationPreset(animation) {
1669
1272
  }
1670
1273
  }
1671
1274
 
1275
+ // src/core/animation/waapi-settle.ts
1276
+ var SIM_HZ = 240;
1277
+ var SAMPLE_STRIDE = 4;
1278
+ var MAX_SIM_MS = 3e3;
1279
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1280
+ const cfg = { ...DEFAULT_SPRING, ...config };
1281
+ const subDt = 1 / SIM_HZ;
1282
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1283
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1284
+ const values = [from];
1285
+ let x = from;
1286
+ let v = velocityPxPerS;
1287
+ for (let i = 1; i <= maxSteps; i += 1) {
1288
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1289
+ v += force / cfg.mass * subDt;
1290
+ x += v * subDt;
1291
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1292
+ values.push(to);
1293
+ break;
1294
+ }
1295
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1296
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1297
+ if (atRest) break;
1298
+ }
1299
+ if (values[values.length - 1] !== to) values.push(to);
1300
+ return {
1301
+ values,
1302
+ stepMs,
1303
+ durationMs: (values.length - 1) * stepMs
1304
+ };
1305
+ }
1306
+ function sampleTweenSettle(from, to, durationMs, easing) {
1307
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1308
+ const values = [];
1309
+ for (let i = 0; i <= count; i += 1) {
1310
+ const t = i / count;
1311
+ values.push(from + (to - from) * easing(t));
1312
+ }
1313
+ values[values.length - 1] = to;
1314
+ return {
1315
+ values,
1316
+ stepMs: durationMs / count,
1317
+ durationMs
1318
+ };
1319
+ }
1320
+
1672
1321
  // src/core/controllers/animation-runner.ts
1673
1322
  var DEFAULT_DURATION2 = 220;
1674
1323
  var VELOCITY_PX_PER_S = 1e3;
@@ -1679,6 +1328,10 @@ var AnimationRunner = class {
1679
1328
  __publicField(this, "applySizeFn");
1680
1329
  __publicField(this, "getSize");
1681
1330
  __publicField(this, "isDragging");
1331
+ __publicField(this, "applyAux");
1332
+ __publicField(this, "getTransformFor");
1333
+ __publicField(this, "settleWaapi");
1334
+ __publicField(this, "currentWaapi", null);
1682
1335
  __publicField(this, "animationKind");
1683
1336
  __publicField(this, "springConfig");
1684
1337
  __publicField(this, "duration");
@@ -1694,6 +1347,9 @@ var AnimationRunner = class {
1694
1347
  this.applySizeFn = deps.applySize;
1695
1348
  this.getSize = deps.getSize;
1696
1349
  this.isDragging = deps.isDragging;
1350
+ this.applyAux = deps.applyAux;
1351
+ this.getTransformFor = deps.getTransformFor;
1352
+ this.settleWaapi = opts.settleAnimation === "waapi";
1697
1353
  const preset = resolveAnimationPreset(opts.animation);
1698
1354
  this.animationKind = preset.kind;
1699
1355
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1718,13 +1374,22 @@ var AnimationRunner = class {
1718
1374
  }
1719
1375
  }
1720
1376
  get isAnimating() {
1721
- return this.currentTween !== null || this.currentSpring !== null;
1377
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1722
1378
  }
1723
1379
  cancel() {
1724
1380
  this.currentTween?.cancel();
1725
1381
  this.currentSpring?.cancel();
1726
- }
1727
- async animateTo(target, velocityPxPerMs) {
1382
+ if (this.currentWaapi) {
1383
+ const entry = this.currentWaapi;
1384
+ this.currentWaapi = null;
1385
+ entry.stop();
1386
+ try {
1387
+ entry.anim.cancel();
1388
+ } catch {
1389
+ }
1390
+ }
1391
+ }
1392
+ async animateTo(target, velocityPxPerMs) {
1728
1393
  if (target === this.getSize()) {
1729
1394
  if (!this.isAnimating && !this.isDragging()) {
1730
1395
  this.element.style.willChange = "";
@@ -1741,6 +1406,27 @@ var AnimationRunner = class {
1741
1406
  if (!this.isDragging()) this.element.style.willChange = "";
1742
1407
  return;
1743
1408
  }
1409
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1410
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1411
+ this.getSize(),
1412
+ target,
1413
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1414
+ this.springConfig
1415
+ ) : sampleTweenSettle(
1416
+ this.getSize(),
1417
+ target,
1418
+ this.duration,
1419
+ this.easing
1420
+ );
1421
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1422
+ await this.runWaapiSettle(samples, target);
1423
+ if (!this.isAnimating) {
1424
+ this.getRootEl()?.removeAttribute("data-animating");
1425
+ if (!this.isDragging()) this.element.style.willChange = "";
1426
+ }
1427
+ return;
1428
+ }
1429
+ }
1744
1430
  if (this.animationKind === "spring") {
1745
1431
  const spring = runSpring({
1746
1432
  from: this.getSize(),
@@ -1769,11 +1455,66 @@ var AnimationRunner = class {
1769
1455
  if (!this.isDragging()) this.element.style.willChange = "";
1770
1456
  }
1771
1457
  }
1458
+ async runWaapiSettle(samples, target) {
1459
+ const applyAux = this.applyAux;
1460
+ const getTransformFor = this.getTransformFor;
1461
+ const frames = samples.values.map((v) => ({
1462
+ transform: getTransformFor(v)
1463
+ }));
1464
+ const anim = this.element.animate(frames, {
1465
+ duration: samples.durationMs,
1466
+ easing: "linear",
1467
+ fill: "forwards"
1468
+ });
1469
+ let stopped = false;
1470
+ let auxRaf = 0;
1471
+ const start = performance.now();
1472
+ const lastIdx = samples.values.length - 1;
1473
+ const auxTick = () => {
1474
+ if (stopped) return;
1475
+ const elapsed = performance.now() - start;
1476
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1477
+ applyAux(samples.values[idx]);
1478
+ auxRaf = requestAnimationFrame(auxTick);
1479
+ };
1480
+ auxRaf = requestAnimationFrame(auxTick);
1481
+ const entry = {
1482
+ anim,
1483
+ stop: () => {
1484
+ stopped = true;
1485
+ cancelAnimationFrame(auxRaf);
1486
+ }
1487
+ };
1488
+ this.currentWaapi = entry;
1489
+ try {
1490
+ await anim.finished;
1491
+ } catch {
1492
+ }
1493
+ entry.stop();
1494
+ const wasCurrent = this.currentWaapi === entry;
1495
+ if (wasCurrent) this.currentWaapi = null;
1496
+ if (wasCurrent && anim.playState === "finished") {
1497
+ this.applySizeFn(target);
1498
+ try {
1499
+ anim.cancel();
1500
+ } catch {
1501
+ }
1502
+ }
1503
+ }
1772
1504
  destroy() {
1773
1505
  this.detachReducedMotion?.();
1774
1506
  this.detachReducedMotion = null;
1775
1507
  this.currentTween = null;
1776
1508
  this.currentSpring = null;
1509
+ if (this.currentWaapi) {
1510
+ const entry = this.currentWaapi;
1511
+ this.currentWaapi = null;
1512
+ entry.stop();
1513
+ try {
1514
+ entry.anim.cancel();
1515
+ } catch {
1516
+ }
1517
+ }
1777
1518
  }
1778
1519
  };
1779
1520
 
@@ -2630,7 +2371,7 @@ function resolveEngineOptions(opts, extras) {
2630
2371
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2631
2372
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2632
2373
  if (opts.persistKey) {
2633
- const restored = readPersistedId(opts.persistKey);
2374
+ const restored = readPersistedIdSafe(opts.persistKey);
2634
2375
  if (restored && initialAllowed.includes(restored)) {
2635
2376
  initialId = restored;
2636
2377
  }
@@ -2661,6 +2402,7 @@ function resolveEngineOptions(opts, extras) {
2661
2402
  },
2662
2403
  animation: {
2663
2404
  animation: opts.animation,
2405
+ settleAnimation: opts.settleAnimation,
2664
2406
  duration: opts.duration,
2665
2407
  easing: opts.easing,
2666
2408
  spring: opts.spring,
@@ -2678,6 +2420,15 @@ function resolveEngineOptions(opts, extras) {
2678
2420
  }
2679
2421
  };
2680
2422
  }
2423
+ var readPersistedIdSafe = (key) => {
2424
+ if (typeof window === "undefined") return null;
2425
+ if (typeof localStorage === "undefined") return null;
2426
+ try {
2427
+ return localStorage.getItem(key);
2428
+ } catch {
2429
+ return null;
2430
+ }
2431
+ };
2681
2432
 
2682
2433
  // src/core/primitives/teardown-stack.ts
2683
2434
  var TeardownStack = class {
@@ -2700,9 +2451,9 @@ var TeardownStack = class {
2700
2451
  }
2701
2452
  };
2702
2453
 
2703
- // src/core/BottomSheetEngine.ts
2454
+ // src/core/BottomSheetCore.ts
2704
2455
  var HAPTIC_DURATION_MS = 8;
2705
- var BottomSheetEngine = class {
2456
+ var BottomSheetCore = class {
2706
2457
  constructor(opts) {
2707
2458
  __publicField(this, "id", nextInstanceId("bs"));
2708
2459
  __publicField(this, "element");
@@ -2763,6 +2514,8 @@ var BottomSheetEngine = class {
2763
2514
  __publicField(this, "stackEffectPrimed", false);
2764
2515
  __publicField(this, "fitDeps");
2765
2516
  __publicField(this, "transformTemplate");
2517
+ __publicField(this, "featureList", []);
2518
+ __publicField(this, "featureCtx");
2766
2519
  const resolved = resolveEngineOptions(opts, {
2767
2520
  shouldApplyInertSiblings: () => {
2768
2521
  if (typeof document === "undefined") return false;
@@ -2809,7 +2562,9 @@ var BottomSheetEngine = class {
2809
2562
  getRootEl: () => this.rootEl,
2810
2563
  applySize: (size) => this.applySize(size),
2811
2564
  getSize: () => this.size,
2812
- isDragging: () => this.gesture?.isDragging ?? false
2565
+ isDragging: () => this.gesture?.isDragging ?? false,
2566
+ applyAux: (size) => this.applySize(size, true),
2567
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2813
2568
  },
2814
2569
  resolved.animation
2815
2570
  );
@@ -2832,6 +2587,11 @@ var BottomSheetEngine = class {
2832
2587
  if (opts.linkedSheets) {
2833
2588
  this.linkedSheets = opts.linkedSheets;
2834
2589
  }
2590
+ const byName = /* @__PURE__ */ new Map();
2591
+ for (const feature of opts.features ?? []) {
2592
+ byName.set(feature.name, feature);
2593
+ }
2594
+ this.featureList = Array.from(byName.values());
2835
2595
  this.activeId = resolved.initialId;
2836
2596
  auditVhUsage(this.snapPointsRaw);
2837
2597
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2868,6 +2628,7 @@ var BottomSheetEngine = class {
2868
2628
  scrollContainer: this.scrollContainer,
2869
2629
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2870
2630
  });
2631
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2871
2632
  this.attach();
2872
2633
  this.registerInStack();
2873
2634
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2899,46 +2660,77 @@ var BottomSheetEngine = class {
2899
2660
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2900
2661
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2901
2662
  this.applyAutoAriaLabelledBy();
2902
- if (this.closeOnRouteChange) {
2903
- this.teardowns.add(
2904
- installRouteChange({
2905
- isDestroyed: () => this.destroyed,
2906
- getSize: () => this.size,
2907
- close: () => this.close()
2908
- })
2909
- );
2910
- }
2911
- if (this.persistKey) {
2912
- this.teardowns.add(installPersist(this, this.persistKey));
2663
+ this.runFeatureStage("post");
2664
+ this.warnOrphanFeatureOptions();
2665
+ if (this.size > 0) {
2666
+ this.handleOpen();
2913
2667
  }
2914
- this.teardowns.add(
2915
- installAutoCollapse({
2916
- ms: opts.autoCollapseAfter,
2917
- isDestroyed: () => this.destroyed,
2918
- isDragging: () => this.gesture?.isDragging ?? false,
2919
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2920
- getActiveId: () => this.activeId,
2921
- resolveSnap: (id) => this.snaps.findById(id),
2922
- snapTo: (id) => {
2923
- void this.snapTo(id);
2924
- },
2925
- on: (event, fn) => this.on(event, fn)
2926
- })
2927
- );
2928
- this.teardowns.add(
2929
- installRoute({
2668
+ }
2669
+ buildFeatureContext(autoCollapseAfter) {
2670
+ return {
2671
+ element: this.element,
2672
+ scrollContainer: this.scrollContainer,
2673
+ sheetId: this.id,
2674
+ options: {
2930
2675
  routedTo: this.routedTo,
2931
2676
  closeOnBack: this.closeOnBack,
2932
- isTopSheet: () => this.isTopSheet,
2933
- getSize: () => this.size,
2934
- isDestroyed: () => this.destroyed,
2935
- close: () => this.close("back"),
2936
- on: (event, fn) => this.on(event, fn),
2937
- sheetId: this.id
2938
- })
2939
- );
2940
- if (this.size > 0) {
2941
- this.handleOpen();
2677
+ closeOnRouteChange: this.closeOnRouteChange,
2678
+ persistKey: this.persistKey,
2679
+ autoCollapseAfter
2680
+ },
2681
+ isDestroyed: () => this.destroyed,
2682
+ isDragging: () => this.gesture?.isDragging ?? false,
2683
+ isAnimating: () => this.animation.isAnimating,
2684
+ isTopSheet: () => this.isTopSheet,
2685
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2686
+ getSize: () => this.size,
2687
+ getActiveId: () => this.activeId,
2688
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2689
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2690
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2691
+ resolveSnap: (id) => this.snaps.findById(id),
2692
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2693
+ setSize: (size) => {
2694
+ this.size = size;
2695
+ },
2696
+ setMaxAxisSize: (size) => {
2697
+ this.snaps.setMaxAxisSize(size);
2698
+ },
2699
+ applySize: (size) => this.applySize(size),
2700
+ recomputeSnaps: () => this.recomputeSnaps(),
2701
+ newCycle: () => {
2702
+ this.newCycle();
2703
+ },
2704
+ cancelInFlight: () => {
2705
+ this.animation.cancel();
2706
+ },
2707
+ resyncAfterResize: () => this.resyncAfterResize(),
2708
+ snapTo: (id) => {
2709
+ void this.snapTo(id);
2710
+ },
2711
+ close: (reason) => this.close(reason),
2712
+ on: (event, fn) => this.on(event, fn),
2713
+ addTeardown: (fn) => this.teardowns.add(fn)
2714
+ };
2715
+ }
2716
+ runFeatureStage(stage) {
2717
+ for (const feature of this.featureList) {
2718
+ if ((feature.stage ?? "post") !== stage) continue;
2719
+ const teardown = feature.install(this.featureCtx);
2720
+ if (teardown) this.teardowns.add(teardown);
2721
+ }
2722
+ }
2723
+ warnOrphanFeatureOptions() {
2724
+ const names = new Set(this.featureList.map((f) => f.name));
2725
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2726
+ devWarn(
2727
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2728
+ );
2729
+ }
2730
+ if (this.persistKey && !names.has("persist")) {
2731
+ devWarn(
2732
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2733
+ );
2942
2734
  }
2943
2735
  }
2944
2736
  get state() {
@@ -3481,18 +3273,7 @@ var BottomSheetEngine = class {
3481
3273
  }
3482
3274
  }
3483
3275
  attachFeatures() {
3484
- if (this.scrollContainer) {
3485
- this.teardowns.add(installContentSwipe({
3486
- container: this.scrollContainer,
3487
- isDragging: () => this.gesture?.isDragging ?? false,
3488
- isAnimating: () => this.animation.isAnimating,
3489
- getAllowedIds: () => this.allowedIdsBySize(),
3490
- getActiveId: () => this.activeId,
3491
- snapTo: (id) => {
3492
- void this.snapTo(id);
3493
- }
3494
- }));
3495
- }
3276
+ this.runFeatureStage("attach");
3496
3277
  if (typeof window !== "undefined") {
3497
3278
  this.teardowns.add(installResizeObserver({
3498
3279
  element: this.element,
@@ -3532,31 +3313,6 @@ var BottomSheetEngine = class {
3532
3313
  () => document.removeEventListener("keydown", onKey)
3533
3314
  );
3534
3315
  }
3535
- this.teardowns.add(installVisualViewport({
3536
- element: this.element,
3537
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3538
- isDestroyed: () => this.destroyed,
3539
- isDragging: () => this.gesture?.isDragging ?? false,
3540
- recomputeSnaps: () => this.recomputeSnaps(),
3541
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3542
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3543
- getSize: () => this.size,
3544
- setMaxAxisSize: (size) => {
3545
- this.snaps.setMaxAxisSize(size);
3546
- },
3547
- setSize: (size) => {
3548
- this.size = size;
3549
- },
3550
- applySize: (size) => this.applySize(size),
3551
- cancelInFlight: () => {
3552
- this.animation.cancel();
3553
- },
3554
- newCycle: () => {
3555
- this.newCycle();
3556
- },
3557
- isAnimating: () => this.animation.isAnimating,
3558
- resyncAfterCancel: () => this.resyncAfterResize()
3559
- }));
3560
3316
  }
3561
3317
  this.teardowns.add(installSliderKeyboard({
3562
3318
  handle: this.handle,
@@ -3597,123 +3353,631 @@ var BottomSheetEngine = class {
3597
3353
  }
3598
3354
  return;
3599
3355
  }
3600
- const previousSize = this.size;
3601
- const previousSnapSize = this.snaps.findById(previousId)?.size;
3602
- this.scrollCache.cache(previousId, previousSize, target.size);
3603
- this.activeId = target.id;
3604
- if (previousSnapSize === 0 && target.size > 0) {
3605
- this.opening = true;
3606
- sheetStack.promote(this.id);
3356
+ const previousSize = this.size;
3357
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
3358
+ this.scrollCache.cache(previousId, previousSize, target.size);
3359
+ this.activeId = target.id;
3360
+ if (previousSnapSize === 0 && target.size > 0) {
3361
+ this.opening = true;
3362
+ sheetStack.promote(this.id);
3363
+ }
3364
+ const settleCap = this.snaps.getMaxAxisSize();
3365
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3366
+ this.allowOvershoot = true;
3367
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3368
+ if (signal.aborted) return;
3369
+ this.allowOvershoot = false;
3370
+ this.opening = false;
3371
+ this.completeSnap(
3372
+ target.id,
3373
+ target.size,
3374
+ previousSize,
3375
+ previousSnapSize === 0,
3376
+ true
3377
+ );
3378
+ });
3379
+ }
3380
+ clearWillChangeIfIdle() {
3381
+ if (this.animation.isAnimating || this.gesture?.isDragging) return;
3382
+ this.element.style.willChange = "";
3383
+ }
3384
+ haptic() {
3385
+ if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3386
+ try {
3387
+ navigator.vibrate(HAPTIC_DURATION_MS);
3388
+ } catch {
3389
+ }
3390
+ }
3391
+ }
3392
+ applySize(size, skipTransform = false) {
3393
+ const cap = this.snaps.getMaxAxisSize();
3394
+ const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3395
+ this.size = clamped;
3396
+ const offset = cap - clamped;
3397
+ const style = this.element.style;
3398
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3399
+ if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3400
+ style.setProperty("--bs-size", `${clamped}px`);
3401
+ if (this.scrimParent) {
3402
+ this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
3403
+ }
3404
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3405
+ this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
3406
+ }
3407
+ if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3408
+ this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
3409
+ }
3410
+ }
3411
+ const progress = this.computeProgress(clamped);
3412
+ const progressChanged = this.progressWriteSentinel.shouldWrite(
3413
+ progress,
3414
+ OPACITY_WRITE_EPSILON
3415
+ );
3416
+ if (progressChanged) {
3417
+ style.setProperty("--bs-progress", String(progress));
3418
+ if (this.anchorHost) {
3419
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3420
+ }
3421
+ if (this.rootEl && this.rootEl !== this.anchorHost) {
3422
+ this.rootEl.style.setProperty("--bs-progress", String(progress));
3423
+ }
3424
+ }
3425
+ this.scrim.applyOpacity(progress, progressChanged);
3426
+ if (progressChanged && this.bus.listenerCount("progress") > 0) {
3427
+ this.progressPayload.value = progress;
3428
+ this.progressPayload.size = clamped;
3429
+ this.emit("progress", this.progressPayload);
3430
+ }
3431
+ }
3432
+ computeProgress(size) {
3433
+ const { min, max } = this.getAllowedRange();
3434
+ const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3435
+ if (reachableMax <= min) return 0;
3436
+ return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3437
+ }
3438
+ handleOpen() {
3439
+ sheetStack.promote(this.id);
3440
+ try {
3441
+ this.lifecycle.install();
3442
+ } catch (err) {
3443
+ if (!this.destroyed) this.newCycle();
3444
+ this.handleClose();
3445
+ this.applySize(0);
3446
+ this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3447
+ this.size = 0;
3448
+ queueMicrotask(() => {
3449
+ throw err;
3450
+ });
3451
+ }
3452
+ }
3453
+ resyncAfterResize() {
3454
+ if (this.destroyed) return;
3455
+ const target = this.snaps.findById(this.activeId);
3456
+ if (!target) return;
3457
+ this.updateAriaSlider();
3458
+ this.emit("snap", {
3459
+ id: target.id,
3460
+ size: this.size,
3461
+ progress: this.computeProgress(this.size)
3462
+ });
3463
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3464
+ this.emitOpenSequence(target.id);
3465
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3466
+ this.emitCloseSequence();
3467
+ }
3468
+ }
3469
+ handleClose() {
3470
+ this.opening = false;
3471
+ this.lifecycle.release();
3472
+ sheetStack.update();
3473
+ }
3474
+ };
3475
+
3476
+ // src/core/features/history-coordinator.ts
3477
+ var entries = [];
3478
+ var subscribers = /* @__PURE__ */ new Set();
3479
+ var suppress = 0;
3480
+ var internalDepth = 0;
3481
+ var pendingRebrands = [];
3482
+ var listenerInstalled = false;
3483
+ var patchInstalled = false;
3484
+ var origPushState = null;
3485
+ var origReplaceState = null;
3486
+ var patchedPushState = null;
3487
+ var patchedReplaceState = null;
3488
+ var hasWindow = () => typeof window !== "undefined";
3489
+ var hasHistory = () => typeof history !== "undefined";
3490
+ var brandMarker = (surface) => ({
3491
+ ...surface.markerState(),
3492
+ __bs: true
3493
+ });
3494
+ var runInternal = (fn) => {
3495
+ internalDepth += 1;
3496
+ try {
3497
+ fn();
3498
+ } finally {
3499
+ internalDepth -= 1;
3500
+ }
3501
+ };
3502
+ var notifyRouteChange = () => {
3503
+ const snapshot = Array.from(subscribers);
3504
+ for (const fn of snapshot) fn();
3505
+ };
3506
+ var ensureListener = () => {
3507
+ if (listenerInstalled || !hasWindow()) return;
3508
+ window.addEventListener("popstate", onPopState);
3509
+ listenerInstalled = true;
3510
+ };
3511
+ var maybeTeardownListener = () => {
3512
+ if (!listenerInstalled) return;
3513
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3514
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3515
+ listenerInstalled = false;
3516
+ };
3517
+ var installPatch = () => {
3518
+ if (patchInstalled || !hasHistory()) return;
3519
+ origPushState = history.pushState;
3520
+ origReplaceState = history.replaceState;
3521
+ patchedPushState = function(...args) {
3522
+ origPushState.apply(this, args);
3523
+ if (internalDepth === 0) notifyRouteChange();
3524
+ };
3525
+ patchedReplaceState = function(...args) {
3526
+ origReplaceState.apply(this, args);
3527
+ if (internalDepth === 0) notifyRouteChange();
3528
+ };
3529
+ history.pushState = patchedPushState;
3530
+ history.replaceState = patchedReplaceState;
3531
+ patchInstalled = true;
3532
+ };
3533
+ var removePatch = () => {
3534
+ if (!patchInstalled) return;
3535
+ if (history.pushState === patchedPushState && origPushState) {
3536
+ history.pushState = origPushState;
3537
+ }
3538
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3539
+ history.replaceState = origReplaceState;
3540
+ }
3541
+ origPushState = null;
3542
+ origReplaceState = null;
3543
+ patchedPushState = null;
3544
+ patchedReplaceState = null;
3545
+ patchInstalled = false;
3546
+ };
3547
+ function onPopState() {
3548
+ if (suppress > 0) {
3549
+ suppress -= 1;
3550
+ const rebrand = pendingRebrands.shift();
3551
+ if (rebrand) rebrand();
3552
+ maybeTeardownListener();
3553
+ return;
3554
+ }
3555
+ if (entries.length === 0) {
3556
+ notifyRouteChange();
3557
+ maybeTeardownListener();
3558
+ return;
3559
+ }
3560
+ let target = null;
3561
+ let targetIdx = -1;
3562
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3563
+ const entry = entries[i];
3564
+ if (!entry.surface.isOpen()) continue;
3565
+ if (!target) {
3566
+ target = entry;
3567
+ targetIdx = i;
3568
+ }
3569
+ if (entry.surface.isTop()) {
3570
+ target = entry;
3571
+ targetIdx = i;
3572
+ break;
3573
+ }
3574
+ }
3575
+ if (!target) {
3576
+ const removed = entries.pop();
3577
+ if (removed) removed.live = false;
3578
+ maybeTeardownListener();
3579
+ return;
3580
+ }
3581
+ target.live = false;
3582
+ entries.splice(targetIdx, 1);
3583
+ const settled = target;
3584
+ void Promise.resolve(settled.surface.close()).then(() => {
3585
+ if (settled.surface.isOpen()) restore(settled);
3586
+ });
3587
+ maybeTeardownListener();
3588
+ }
3589
+ function restore(entry) {
3590
+ if (!hasWindow() || !hasHistory()) return;
3591
+ entry.live = true;
3592
+ runInternal(() => {
3593
+ try {
3594
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3595
+ } catch {
3596
+ entry.live = false;
3597
+ }
3598
+ });
3599
+ if (entry.live) {
3600
+ entries.push(entry);
3601
+ ensureListener();
3602
+ }
3603
+ }
3604
+ function pushBackMarker(surface) {
3605
+ const entry = { surface, priorUrl: void 0, live: false };
3606
+ if (!hasWindow() || !hasHistory()) {
3607
+ return entry;
3608
+ }
3609
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3610
+ runInternal(() => {
3611
+ try {
3612
+ history.pushState(brandMarker(surface), "", surface.url);
3613
+ entry.live = true;
3614
+ } catch {
3615
+ entry.live = false;
3616
+ }
3617
+ });
3618
+ if (entry.live) {
3619
+ entries.push(entry);
3620
+ ensureListener();
3621
+ }
3622
+ return entry;
3623
+ }
3624
+ function popBackMarker(handle) {
3625
+ if (!hasWindow() || !hasHistory()) return;
3626
+ const entry = handle;
3627
+ const idx = entries.indexOf(entry);
3628
+ if (idx === -1 || !entry.live) return;
3629
+ const last = entries.length - 1;
3630
+ entry.live = false;
3631
+ entries.splice(idx, 1);
3632
+ let rebrand = null;
3633
+ if (idx !== last) {
3634
+ const rebrandSurface = entries[entries.length - 1].surface;
3635
+ const rebrandUrl = entry.priorUrl;
3636
+ rebrand = () => {
3637
+ runInternal(() => {
3638
+ history.replaceState(
3639
+ brandMarker(rebrandSurface),
3640
+ "",
3641
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3642
+ );
3643
+ });
3644
+ };
3645
+ }
3646
+ pendingRebrands.push(rebrand);
3647
+ suppress += 1;
3648
+ runInternal(() => {
3649
+ try {
3650
+ history.back();
3651
+ } catch {
3652
+ suppress = Math.max(0, suppress - 1);
3653
+ pendingRebrands.pop();
3654
+ }
3655
+ });
3656
+ maybeTeardownListener();
3657
+ }
3658
+ function subscribeRouteChange(fn) {
3659
+ if (!hasWindow() || !hasHistory()) return () => {
3660
+ };
3661
+ subscribers.add(fn);
3662
+ if (subscribers.size === 1) installPatch();
3663
+ ensureListener();
3664
+ return () => {
3665
+ if (!subscribers.has(fn)) return;
3666
+ subscribers.delete(fn);
3667
+ if (subscribers.size === 0) removePatch();
3668
+ maybeTeardownListener();
3669
+ };
3670
+ }
3671
+
3672
+ // src/core/features/route.ts
3673
+ function installRoute(deps) {
3674
+ if (typeof window === "undefined") {
3675
+ return () => {
3676
+ };
3677
+ }
3678
+ if (!deps.routedTo && !deps.closeOnBack) {
3679
+ return () => {
3680
+ };
3681
+ }
3682
+ let handle = null;
3683
+ const onOpen = () => {
3684
+ if (handle) return;
3685
+ handle = pushBackMarker({
3686
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3687
+ isTop: deps.isTopSheet,
3688
+ close: deps.close,
3689
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3690
+ url: deps.routedTo
3691
+ });
3692
+ };
3693
+ const onClose = () => {
3694
+ if (!handle) return;
3695
+ popBackMarker(handle);
3696
+ handle = null;
3697
+ };
3698
+ const unsubscribeOpen = deps.on("open", onOpen);
3699
+ const unsubscribeClose = deps.on("close", onClose);
3700
+ if (deps.getSize() > 0) onOpen();
3701
+ return () => {
3702
+ unsubscribeOpen();
3703
+ unsubscribeClose();
3704
+ if (handle) {
3705
+ popBackMarker(handle);
3706
+ handle = null;
3707
+ }
3708
+ };
3709
+ }
3710
+ function installRouteChange(deps) {
3711
+ return subscribeRouteChange(() => {
3712
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3713
+ void deps.close();
3714
+ });
3715
+ }
3716
+
3717
+ // src/core/features/persist.ts
3718
+ function installPersist(engine, key) {
3719
+ const unsubscribe = engine.on("snap", ({ id }) => {
3720
+ if (typeof window === "undefined") return;
3721
+ if (typeof localStorage === "undefined") return;
3722
+ try {
3723
+ localStorage.setItem(key, id);
3724
+ } catch {
3725
+ }
3726
+ });
3727
+ return unsubscribe;
3728
+ }
3729
+
3730
+ // src/core/features/auto-collapse.ts
3731
+ function installAutoCollapse(deps) {
3732
+ const { ms } = deps;
3733
+ if (ms === void 0 || ms <= 0) {
3734
+ return () => {
3735
+ };
3736
+ }
3737
+ let timer = null;
3738
+ const clearTimer = () => {
3739
+ if (timer !== null) {
3740
+ clearTimeout(timer);
3741
+ timer = null;
3742
+ }
3743
+ };
3744
+ const fire = () => {
3745
+ if (deps.isDestroyed()) return;
3746
+ if (deps.isDragging()) return;
3747
+ const active = deps.resolveSnap(deps.getActiveId());
3748
+ if (!active || active.size === 0) return;
3749
+ let target = null;
3750
+ for (const id of deps.getAllowedIds()) {
3751
+ const snap = deps.resolveSnap(id);
3752
+ if (!snap || snap.size <= 0) continue;
3753
+ if (!target || snap.size < target.size) target = snap;
3607
3754
  }
3608
- const settleCap = this.snaps.getMaxAxisSize();
3609
- const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3610
- this.allowOvershoot = true;
3611
- void this.animation.animateTo(settleSize, velocity).then(() => {
3612
- if (signal.aborted) return;
3613
- this.allowOvershoot = false;
3614
- this.opening = false;
3615
- this.completeSnap(
3616
- target.id,
3617
- target.size,
3618
- previousSize,
3619
- previousSnapSize === 0,
3620
- true
3621
- );
3622
- });
3623
- }
3624
- clearWillChangeIfIdle() {
3625
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3626
- this.element.style.willChange = "";
3627
- }
3628
- haptic() {
3629
- if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3630
- try {
3631
- navigator.vibrate(HAPTIC_DURATION_MS);
3632
- } catch {
3633
- }
3755
+ if (!target) return;
3756
+ if (target.id === active.id || target.size >= active.size) return;
3757
+ deps.snapTo(target.id);
3758
+ };
3759
+ const reset = () => {
3760
+ clearTimer();
3761
+ if (deps.isDestroyed()) return;
3762
+ timer = setTimeout(() => {
3763
+ timer = null;
3764
+ fire();
3765
+ }, ms);
3766
+ };
3767
+ const unsubscribe = deps.on("snap", () => reset());
3768
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3769
+ return () => {
3770
+ clearTimer();
3771
+ unsubscribe();
3772
+ };
3773
+ }
3774
+
3775
+ // src/core/features/content-swipe.ts
3776
+ var CONTENT_PULL_THRESHOLD = 60;
3777
+ function installContentSwipe(deps) {
3778
+ const { container } = deps;
3779
+ let startY = 0;
3780
+ let startScrollTop = 0;
3781
+ let moved = false;
3782
+ const onTouchStart = (e) => {
3783
+ const t = e.touches[0];
3784
+ if (!t) return;
3785
+ startY = t.clientY;
3786
+ startScrollTop = container.scrollTop;
3787
+ moved = false;
3788
+ };
3789
+ const onTouchMove = (e) => {
3790
+ const t = e.touches[0];
3791
+ if (!t) return;
3792
+ if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3793
+ };
3794
+ const onTouchEnd = (e) => {
3795
+ if (!moved) return;
3796
+ if (deps.isDragging() || deps.isAnimating()) return;
3797
+ if (container.scrollTop !== startScrollTop) return;
3798
+ const t = e.changedTouches[0];
3799
+ if (!t) return;
3800
+ const deltaY = t.clientY - startY;
3801
+ const atTop = container.scrollTop <= 0;
3802
+ const allowed = deps.getAllowedIds();
3803
+ const idx = allowed.indexOf(deps.getActiveId());
3804
+ if (idx === -1) return;
3805
+ if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3806
+ deps.snapTo(allowed[idx - 1]);
3807
+ } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3808
+ deps.snapTo(allowed[idx + 1]);
3634
3809
  }
3810
+ };
3811
+ container.addEventListener("touchstart", onTouchStart, { passive: true });
3812
+ container.addEventListener("touchmove", onTouchMove, { passive: true });
3813
+ container.addEventListener("touchend", onTouchEnd, { passive: true });
3814
+ return () => {
3815
+ container.removeEventListener("touchstart", onTouchStart);
3816
+ container.removeEventListener("touchmove", onTouchMove);
3817
+ container.removeEventListener("touchend", onTouchEnd);
3818
+ };
3819
+ }
3820
+
3821
+ // src/core/features/visual-viewport.ts
3822
+ var SAFETY_PAD = 8;
3823
+ function installVisualViewport(deps) {
3824
+ if (typeof window === "undefined" || !window.visualViewport) {
3825
+ return () => {
3826
+ };
3635
3827
  }
3636
- applySize(size) {
3637
- const cap = this.snaps.getMaxAxisSize();
3638
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3639
- this.size = clamped;
3640
- const offset = cap - clamped;
3641
- const style = this.element.style;
3642
- style.transform = this.transformTemplate(offset);
3643
- if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3644
- style.setProperty("--bs-size", `${clamped}px`);
3645
- if (this.scrimParent) {
3646
- this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
3828
+ const vv = window.visualViewport;
3829
+ let rafId = null;
3830
+ const onResize = () => {
3831
+ if (rafId !== null) return;
3832
+ rafId = requestAnimationFrame(() => {
3833
+ rafId = null;
3834
+ if (deps.isDestroyed()) return;
3835
+ deps.recomputeSnaps();
3836
+ const isVertical = deps.isVerticalAxis();
3837
+ const visible = isVertical ? vv.height : vv.width;
3838
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
3839
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
3840
+ if (clamp < deps.getMaxAxisSize()) {
3841
+ deps.setMaxAxisSize(clamp);
3647
3842
  }
3648
- if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3649
- this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
3843
+ const wasAnimating = deps.isAnimating?.() ?? false;
3844
+ deps.cancelInFlight();
3845
+ deps.newCycle();
3846
+ const max = deps.getMaxAxisSize();
3847
+ deps.setSize(Math.min(deps.getSize(), max));
3848
+ const current = deps.resolveActiveSnap();
3849
+ if (current && !deps.isDragging()) {
3850
+ deps.applySize(Math.min(current.size, max));
3851
+ if (wasAnimating) deps.resyncAfterCancel?.();
3852
+ } else {
3853
+ deps.applySize(deps.getSize());
3650
3854
  }
3651
- if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3652
- this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
3855
+ });
3856
+ };
3857
+ vv.addEventListener("resize", onResize);
3858
+ return () => {
3859
+ vv.removeEventListener("resize", onResize);
3860
+ if (rafId !== null) cancelAnimationFrame(rafId);
3861
+ };
3862
+ }
3863
+
3864
+ // src/core/features/engine-features.ts
3865
+ function routeFeature() {
3866
+ return {
3867
+ name: "route",
3868
+ install: (ctx) => {
3869
+ const teardowns = new TeardownStack();
3870
+ if (ctx.options.closeOnRouteChange) {
3871
+ teardowns.add(
3872
+ installRouteChange({
3873
+ isDestroyed: ctx.isDestroyed,
3874
+ getSize: ctx.getSize,
3875
+ close: () => ctx.close()
3876
+ })
3877
+ );
3653
3878
  }
3879
+ teardowns.add(
3880
+ installRoute({
3881
+ routedTo: ctx.options.routedTo,
3882
+ closeOnBack: ctx.options.closeOnBack,
3883
+ isTopSheet: ctx.isTopSheet,
3884
+ getSize: ctx.getSize,
3885
+ isDestroyed: ctx.isDestroyed,
3886
+ close: () => ctx.close("back"),
3887
+ on: ctx.on,
3888
+ sheetId: ctx.sheetId
3889
+ })
3890
+ );
3891
+ return () => teardowns.drain();
3654
3892
  }
3655
- const progress = this.computeProgress(clamped);
3656
- const progressChanged = this.progressWriteSentinel.shouldWrite(
3657
- progress,
3658
- OPACITY_WRITE_EPSILON
3659
- );
3660
- if (progressChanged) {
3661
- style.setProperty("--bs-progress", String(progress));
3662
- if (this.anchorHost) {
3663
- this.anchorHost.style.setProperty("--bs-progress", String(progress));
3664
- }
3665
- if (this.rootEl && this.rootEl !== this.anchorHost) {
3666
- this.rootEl.style.setProperty("--bs-progress", String(progress));
3667
- }
3893
+ };
3894
+ }
3895
+ function persistFeature() {
3896
+ return {
3897
+ name: "persist",
3898
+ install: (ctx) => {
3899
+ const key = ctx.options.persistKey;
3900
+ if (!key) return;
3901
+ return installPersist({ on: ctx.on }, key);
3668
3902
  }
3669
- this.scrim.applyOpacity(progress, progressChanged);
3670
- if (progressChanged && this.bus.listenerCount("progress") > 0) {
3671
- this.progressPayload.value = progress;
3672
- this.progressPayload.size = clamped;
3673
- this.emit("progress", this.progressPayload);
3903
+ };
3904
+ }
3905
+ function autoCollapseFeature() {
3906
+ return {
3907
+ name: "auto-collapse",
3908
+ install: (ctx) => installAutoCollapse({
3909
+ ms: ctx.options.autoCollapseAfter,
3910
+ isDestroyed: ctx.isDestroyed,
3911
+ isDragging: ctx.isDragging,
3912
+ getAllowedIds: ctx.getAllowedIds,
3913
+ getActiveId: ctx.getActiveId,
3914
+ resolveSnap: ctx.resolveSnap,
3915
+ snapTo: ctx.snapTo,
3916
+ on: ctx.on
3917
+ })
3918
+ };
3919
+ }
3920
+ function contentSwipeFeature() {
3921
+ return {
3922
+ name: "content-swipe",
3923
+ stage: "attach",
3924
+ install: (ctx) => {
3925
+ if (!ctx.scrollContainer) return;
3926
+ return installContentSwipe({
3927
+ container: ctx.scrollContainer,
3928
+ isDragging: ctx.isDragging,
3929
+ isAnimating: ctx.isAnimating,
3930
+ getAllowedIds: ctx.allowedIdsBySize,
3931
+ getActiveId: ctx.getActiveId,
3932
+ snapTo: ctx.snapTo
3933
+ });
3674
3934
  }
3675
- }
3676
- computeProgress(size) {
3677
- const { min, max } = this.getAllowedRange();
3678
- const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3679
- if (reachableMax <= min) return 0;
3680
- return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3681
- }
3682
- handleOpen() {
3683
- sheetStack.promote(this.id);
3684
- try {
3685
- this.lifecycle.install();
3686
- } catch (err) {
3687
- if (!this.destroyed) this.newCycle();
3688
- this.handleClose();
3689
- this.applySize(0);
3690
- this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3691
- this.size = 0;
3692
- queueMicrotask(() => {
3693
- throw err;
3935
+ };
3936
+ }
3937
+ function visualViewportFeature() {
3938
+ return {
3939
+ name: "visual-viewport",
3940
+ install: (ctx) => {
3941
+ if (typeof window === "undefined") return;
3942
+ return installVisualViewport({
3943
+ element: ctx.element,
3944
+ isVerticalAxis: ctx.isVerticalAxis,
3945
+ isDestroyed: ctx.isDestroyed,
3946
+ isDragging: ctx.isDragging,
3947
+ recomputeSnaps: ctx.recomputeSnaps,
3948
+ resolveActiveSnap: ctx.resolveActiveSnap,
3949
+ getMaxAxisSize: ctx.getMaxAxisSize,
3950
+ getSize: ctx.getSize,
3951
+ setMaxAxisSize: ctx.setMaxAxisSize,
3952
+ setSize: ctx.setSize,
3953
+ applySize: ctx.applySize,
3954
+ cancelInFlight: ctx.cancelInFlight,
3955
+ newCycle: ctx.newCycle,
3956
+ isAnimating: ctx.isAnimating,
3957
+ resyncAfterCancel: ctx.resyncAfterResize
3694
3958
  });
3695
3959
  }
3696
- }
3697
- resyncAfterResize() {
3698
- if (this.destroyed) return;
3699
- const target = this.snaps.findById(this.activeId);
3700
- if (!target) return;
3701
- this.updateAriaSlider();
3702
- this.emit("snap", {
3703
- id: target.id,
3704
- size: this.size,
3705
- progress: this.computeProgress(this.size)
3960
+ };
3961
+ }
3962
+
3963
+ // src/core/default-features.ts
3964
+ function defaultEngineFeatures() {
3965
+ return [
3966
+ contentSwipeFeature(),
3967
+ visualViewportFeature(),
3968
+ persistFeature(),
3969
+ autoCollapseFeature(),
3970
+ routeFeature()
3971
+ ];
3972
+ }
3973
+
3974
+ // src/core/BottomSheetEngine.ts
3975
+ var BottomSheetEngine = class extends BottomSheetCore {
3976
+ constructor(opts) {
3977
+ super({
3978
+ ...opts,
3979
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
3706
3980
  });
3707
- if (target.size > 0 && !this.lifecycle.isInstalled) {
3708
- this.emitOpenSequence(target.id);
3709
- } else if (target.size === 0 && this.lifecycle.isInstalled) {
3710
- this.emitCloseSequence();
3711
- }
3712
- }
3713
- handleClose() {
3714
- this.opening = false;
3715
- this.lifecycle.release();
3716
- sheetStack.update();
3717
3981
  }
3718
3982
  };
3719
3983
 
@@ -3952,6 +4216,10 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
3952
4216
  type: String,
3953
4217
  required: false
3954
4218
  },
4219
+ settleAnimation: {
4220
+ type: String,
4221
+ required: false
4222
+ },
3955
4223
  spring: {
3956
4224
  type: Object,
3957
4225
  required: false
@@ -4034,6 +4302,10 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
4034
4302
  type: Boolean,
4035
4303
  required: false
4036
4304
  },
4305
+ features: {
4306
+ type: Array,
4307
+ required: false
4308
+ },
4037
4309
  snapPoints: {
4038
4310
  type: Array,
4039
4311
  required: true