@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/svelte.cjs CHANGED
@@ -372,74 +372,6 @@ var SnapResolver = class {
372
372
  }
373
373
  };
374
374
 
375
- // src/core/features/persist.ts
376
- function readPersistedId(key) {
377
- if (!key) return null;
378
- if (typeof window === "undefined") return null;
379
- if (typeof localStorage === "undefined") return null;
380
- try {
381
- return localStorage.getItem(key);
382
- } catch {
383
- return null;
384
- }
385
- }
386
- function installPersist(engine, key) {
387
- const unsubscribe = engine.on("snap", ({ id }) => {
388
- if (typeof window === "undefined") return;
389
- if (typeof localStorage === "undefined") return;
390
- try {
391
- localStorage.setItem(key, id);
392
- } catch {
393
- }
394
- });
395
- return unsubscribe;
396
- }
397
-
398
- // src/core/features/auto-collapse.ts
399
- function installAutoCollapse(deps) {
400
- const { ms } = deps;
401
- if (ms === void 0 || ms <= 0) {
402
- return () => {
403
- };
404
- }
405
- let timer = null;
406
- const clearTimer = () => {
407
- if (timer !== null) {
408
- clearTimeout(timer);
409
- timer = null;
410
- }
411
- };
412
- const fire = () => {
413
- if (deps.isDestroyed()) return;
414
- if (deps.isDragging()) return;
415
- const active = deps.resolveSnap(deps.getActiveId());
416
- if (!active || active.size === 0) return;
417
- let target = null;
418
- for (const id of deps.getAllowedIds()) {
419
- const snap = deps.resolveSnap(id);
420
- if (!snap || snap.size <= 0) continue;
421
- if (!target || snap.size < target.size) target = snap;
422
- }
423
- if (!target) return;
424
- if (target.id === active.id || target.size >= active.size) return;
425
- deps.snapTo(target.id);
426
- };
427
- const reset2 = () => {
428
- clearTimer();
429
- if (deps.isDestroyed()) return;
430
- timer = setTimeout(() => {
431
- timer = null;
432
- fire();
433
- }, ms);
434
- };
435
- const unsubscribe = deps.on("snap", () => reset2());
436
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset2();
437
- return () => {
438
- clearTimer();
439
- unsubscribe();
440
- };
441
- }
442
-
443
375
  // src/core/features/linked-sheets.ts
444
376
  function notifyLinkedSheets(sheets, self) {
445
377
  if (sheets.length === 0) return;
@@ -462,394 +394,65 @@ function notifyLinkedSheets(sheets, self) {
462
394
  }
463
395
  }
464
396
 
465
- // src/core/features/history-coordinator.ts
466
- var entries = [];
467
- var subscribers = /* @__PURE__ */ new Set();
468
- var suppress = 0;
469
- var internalDepth = 0;
470
- var pendingRebrand = null;
471
- var listenerInstalled = false;
472
- var patchInstalled = false;
473
- var origPushState = null;
474
- var origReplaceState = null;
475
- var patchedPushState = null;
476
- var patchedReplaceState = null;
477
- var hasWindow = () => typeof window !== "undefined";
478
- var hasHistory = () => typeof history !== "undefined";
479
- var brandMarker = (surface) => ({
480
- ...surface.markerState(),
481
- __bs: true
482
- });
483
- var runInternal = (fn) => {
484
- internalDepth += 1;
485
- try {
486
- fn();
487
- } finally {
488
- internalDepth -= 1;
489
- }
490
- };
491
- var notifyRouteChange = () => {
492
- const snapshot = Array.from(subscribers);
493
- for (const fn of snapshot) fn();
494
- };
495
- var ensureListener = () => {
496
- if (listenerInstalled || !hasWindow()) return;
497
- window.addEventListener("popstate", onPopState);
498
- listenerInstalled = true;
499
- };
500
- var maybeTeardownListener = () => {
501
- if (!listenerInstalled) return;
502
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
503
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
504
- listenerInstalled = false;
505
- };
506
- var installPatch = () => {
507
- if (patchInstalled || !hasHistory()) return;
508
- origPushState = history.pushState;
509
- origReplaceState = history.replaceState;
510
- patchedPushState = function(...args) {
511
- origPushState.apply(this, args);
512
- if (internalDepth === 0) notifyRouteChange();
513
- };
514
- patchedReplaceState = function(...args) {
515
- origReplaceState.apply(this, args);
516
- if (internalDepth === 0) notifyRouteChange();
517
- };
518
- history.pushState = patchedPushState;
519
- history.replaceState = patchedReplaceState;
520
- patchInstalled = true;
521
- };
522
- var removePatch = () => {
523
- if (!patchInstalled) return;
524
- if (history.pushState === patchedPushState && origPushState) {
525
- history.pushState = origPushState;
526
- }
527
- if (history.replaceState === patchedReplaceState && origReplaceState) {
528
- history.replaceState = origReplaceState;
529
- }
530
- origPushState = null;
531
- origReplaceState = null;
532
- patchedPushState = null;
533
- patchedReplaceState = null;
534
- patchInstalled = false;
535
- };
536
- function onPopState() {
537
- if (suppress > 0) {
538
- suppress -= 1;
539
- const rebrand = pendingRebrand;
540
- pendingRebrand = null;
541
- if (rebrand) rebrand();
542
- maybeTeardownListener();
543
- return;
544
- }
545
- if (entries.length === 0) {
546
- notifyRouteChange();
547
- maybeTeardownListener();
548
- return;
397
+ // src/core/features/resize-observer.ts
398
+ function installResizeObserver(deps) {
399
+ if (typeof window === "undefined") {
400
+ return () => {
401
+ };
549
402
  }
550
- let target = null;
551
- let targetIdx = -1;
552
- for (let i = entries.length - 1; i >= 0; i -= 1) {
553
- const entry = entries[i];
554
- if (!entry.surface.isOpen()) continue;
555
- if (!target) {
556
- target = entry;
557
- targetIdx = i;
403
+ const onResize = () => {
404
+ if (deps.isDestroyed()) return;
405
+ deps.recomputeSnaps();
406
+ const mode = deps.getMode();
407
+ const isVerticalAxis = mode === "bottom" || mode === "top";
408
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
409
+ const max = deps.getMaxAxisSize();
410
+ if (viewportSize2 > 0 && viewportSize2 < max) {
411
+ deps.setMaxAxisSize(viewportSize2);
558
412
  }
559
- if (entry.surface.isTop()) {
560
- target = entry;
561
- targetIdx = i;
562
- break;
413
+ const wasAnimating = deps.isAnimating?.() ?? false;
414
+ deps.cancelInFlight();
415
+ deps.newCycle();
416
+ const current = deps.resolveActiveSnap();
417
+ const newMax = deps.getMaxAxisSize();
418
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
419
+ deps.setSize(targetSize);
420
+ if (current && !deps.isDragging()) {
421
+ deps.applySize(targetSize);
422
+ if (wasAnimating) deps.resyncAfterCancel?.();
563
423
  }
424
+ };
425
+ let resizeObserver = null;
426
+ if (typeof ResizeObserver !== "undefined") {
427
+ resizeObserver = new ResizeObserver(onResize);
428
+ resizeObserver.observe(document.documentElement);
429
+ } else {
430
+ window.addEventListener("resize", onResize);
564
431
  }
565
- if (!target) {
566
- const removed = entries.pop();
567
- if (removed) removed.live = false;
568
- maybeTeardownListener();
569
- return;
570
- }
571
- target.live = false;
572
- entries.splice(targetIdx, 1);
573
- const settled = target;
574
- void Promise.resolve(settled.surface.close()).then(() => {
575
- if (settled.surface.isOpen()) restore(settled);
576
- });
577
- maybeTeardownListener();
432
+ window.addEventListener("orientationchange", onResize);
433
+ const onVisibility = () => {
434
+ if (deps.isDestroyed()) return;
435
+ if (document.hidden) deps.cancelInFlight();
436
+ };
437
+ document.addEventListener("visibilitychange", onVisibility);
438
+ return () => {
439
+ resizeObserver?.disconnect();
440
+ window.removeEventListener("resize", onResize);
441
+ window.removeEventListener("orientationchange", onResize);
442
+ document.removeEventListener("visibilitychange", onVisibility);
443
+ };
578
444
  }
579
- function restore(entry) {
580
- if (!hasWindow() || !hasHistory()) return;
581
- entry.live = true;
582
- runInternal(() => {
583
- try {
584
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
585
- } catch {
586
- entry.live = false;
587
- }
588
- });
589
- if (entry.live) {
590
- entries.push(entry);
591
- ensureListener();
592
- }
593
- }
594
- function pushBackMarker(surface) {
595
- const entry = { surface, priorUrl: void 0, live: false };
596
- if (!hasWindow() || !hasHistory()) {
597
- return entry;
598
- }
599
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
600
- runInternal(() => {
601
- try {
602
- history.pushState(brandMarker(surface), "", surface.url);
603
- entry.live = true;
604
- } catch {
605
- entry.live = false;
606
- }
607
- });
608
- if (entry.live) {
609
- entries.push(entry);
610
- ensureListener();
611
- }
612
- return entry;
613
- }
614
- function popBackMarker(handle) {
615
- if (!hasWindow() || !hasHistory()) return;
616
- const entry = handle;
617
- const idx = entries.indexOf(entry);
618
- if (idx === -1 || !entry.live) return;
619
- const last = entries.length - 1;
620
- entry.live = false;
621
- entries.splice(idx, 1);
622
- if (idx !== last) {
623
- const rebrandSurface = entries[entries.length - 1].surface;
624
- const rebrandUrl = entry.priorUrl;
625
- pendingRebrand = () => {
626
- runInternal(() => {
627
- history.replaceState(
628
- brandMarker(rebrandSurface),
629
- "",
630
- rebrandUrl !== void 0 ? rebrandUrl : location.href
631
- );
632
- });
633
- };
634
- }
635
- suppress += 1;
636
- runInternal(() => {
637
- try {
638
- history.back();
639
- } catch {
640
- suppress = Math.max(0, suppress - 1);
641
- pendingRebrand = null;
642
- }
643
- });
644
- maybeTeardownListener();
645
- }
646
- function subscribeRouteChange(fn) {
647
- if (!hasWindow() || !hasHistory()) return () => {
648
- };
649
- subscribers.add(fn);
650
- if (subscribers.size === 1) installPatch();
651
- ensureListener();
652
- return () => {
653
- if (!subscribers.has(fn)) return;
654
- subscribers.delete(fn);
655
- if (subscribers.size === 0) removePatch();
656
- maybeTeardownListener();
657
- };
658
- }
659
-
660
- // src/core/features/route.ts
661
- function installRoute(deps) {
662
- if (typeof window === "undefined") {
663
- return () => {
664
- };
665
- }
666
- if (!deps.routedTo && !deps.closeOnBack) {
667
- return () => {
668
- };
669
- }
670
- let handle = null;
671
- const onOpen = () => {
672
- if (handle) return;
673
- handle = pushBackMarker({
674
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
675
- isTop: deps.isTopSheet,
676
- close: deps.close,
677
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
678
- url: deps.routedTo
679
- });
680
- };
681
- const onClose = () => {
682
- if (!handle) return;
683
- popBackMarker(handle);
684
- handle = null;
685
- };
686
- const unsubscribeOpen = deps.on("open", onOpen);
687
- const unsubscribeClose = deps.on("close", onClose);
688
- if (deps.getSize() > 0) onOpen();
689
- return () => {
690
- unsubscribeOpen();
691
- unsubscribeClose();
692
- if (handle) {
693
- popBackMarker(handle);
694
- handle = null;
695
- }
696
- };
697
- }
698
- function installRouteChange(deps) {
699
- return subscribeRouteChange(() => {
700
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
701
- void deps.close();
702
- });
703
- }
704
-
705
- // src/core/features/content-swipe.ts
706
- var CONTENT_PULL_THRESHOLD = 60;
707
- function installContentSwipe(deps) {
708
- const { container } = deps;
709
- let startY = 0;
710
- let startScrollTop = 0;
711
- let moved = false;
712
- const onTouchStart = (e) => {
713
- const t = e.touches[0];
714
- if (!t) return;
715
- startY = t.clientY;
716
- startScrollTop = container.scrollTop;
717
- moved = false;
718
- };
719
- const onTouchMove = (e) => {
720
- const t = e.touches[0];
721
- if (!t) return;
722
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
723
- };
724
- const onTouchEnd = (e) => {
725
- if (!moved) return;
726
- if (deps.isDragging() || deps.isAnimating()) return;
727
- if (container.scrollTop !== startScrollTop) return;
728
- const t = e.changedTouches[0];
729
- if (!t) return;
730
- const deltaY = t.clientY - startY;
731
- const atTop = container.scrollTop <= 0;
732
- const allowed = deps.getAllowedIds();
733
- const idx = allowed.indexOf(deps.getActiveId());
734
- if (idx === -1) return;
735
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
736
- deps.snapTo(allowed[idx - 1]);
737
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
738
- deps.snapTo(allowed[idx + 1]);
739
- }
740
- };
741
- container.addEventListener("touchstart", onTouchStart, { passive: true });
742
- container.addEventListener("touchmove", onTouchMove, { passive: true });
743
- container.addEventListener("touchend", onTouchEnd, { passive: true });
744
- return () => {
745
- container.removeEventListener("touchstart", onTouchStart);
746
- container.removeEventListener("touchmove", onTouchMove);
747
- container.removeEventListener("touchend", onTouchEnd);
748
- };
749
- }
750
-
751
- // src/core/features/visual-viewport.ts
752
- var SAFETY_PAD = 8;
753
- function installVisualViewport(deps) {
754
- if (typeof window === "undefined" || !window.visualViewport) {
755
- return () => {
756
- };
757
- }
758
- const vv = window.visualViewport;
759
- let rafId = null;
760
- const onResize = () => {
761
- if (rafId !== null) return;
762
- rafId = requestAnimationFrame(() => {
763
- rafId = null;
764
- if (deps.isDestroyed()) return;
765
- deps.recomputeSnaps();
766
- const isVertical = deps.isVerticalAxis();
767
- const visible = isVertical ? vv.height : vv.width;
768
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
769
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
770
- if (clamp < deps.getMaxAxisSize()) {
771
- deps.setMaxAxisSize(clamp);
772
- }
773
- const wasAnimating = deps.isAnimating?.() ?? false;
774
- deps.cancelInFlight();
775
- deps.newCycle();
776
- const max = deps.getMaxAxisSize();
777
- deps.setSize(Math.min(deps.getSize(), max));
778
- const current = deps.resolveActiveSnap();
779
- if (current && !deps.isDragging()) {
780
- deps.applySize(Math.min(current.size, max));
781
- if (wasAnimating) deps.resyncAfterCancel?.();
782
- } else {
783
- deps.applySize(deps.getSize());
784
- }
785
- });
786
- };
787
- vv.addEventListener("resize", onResize);
788
- return () => {
789
- vv.removeEventListener("resize", onResize);
790
- if (rafId !== null) cancelAnimationFrame(rafId);
791
- };
792
- }
793
-
794
- // src/core/features/resize-observer.ts
795
- function installResizeObserver(deps) {
796
- if (typeof window === "undefined") {
797
- return () => {
798
- };
799
- }
800
- const onResize = () => {
801
- if (deps.isDestroyed()) return;
802
- deps.recomputeSnaps();
803
- const mode = deps.getMode();
804
- const isVerticalAxis = mode === "bottom" || mode === "top";
805
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
806
- const max = deps.getMaxAxisSize();
807
- if (viewportSize2 > 0 && viewportSize2 < max) {
808
- deps.setMaxAxisSize(viewportSize2);
809
- }
810
- const wasAnimating = deps.isAnimating?.() ?? false;
811
- deps.cancelInFlight();
812
- deps.newCycle();
813
- const current = deps.resolveActiveSnap();
814
- const newMax = deps.getMaxAxisSize();
815
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
816
- deps.setSize(targetSize);
817
- if (current && !deps.isDragging()) {
818
- deps.applySize(targetSize);
819
- if (wasAnimating) deps.resyncAfterCancel?.();
820
- }
821
- };
822
- let resizeObserver = null;
823
- if (typeof ResizeObserver !== "undefined") {
824
- resizeObserver = new ResizeObserver(onResize);
825
- resizeObserver.observe(document.documentElement);
826
- } else {
827
- window.addEventListener("resize", onResize);
828
- }
829
- window.addEventListener("orientationchange", onResize);
830
- const onVisibility = () => {
831
- if (deps.isDestroyed()) return;
832
- if (document.hidden) deps.cancelInFlight();
833
- };
834
- document.addEventListener("visibilitychange", onVisibility);
835
- return () => {
836
- resizeObserver?.disconnect();
837
- window.removeEventListener("resize", onResize);
838
- window.removeEventListener("orientationchange", onResize);
839
- document.removeEventListener("visibilitychange", onVisibility);
840
- };
841
- }
842
-
843
- // src/core/features/fit-observer.ts
844
- var contentTargets = (scroller) => {
845
- if (!scroller) return [];
846
- const hasSlot = typeof HTMLSlotElement !== "undefined";
847
- const out = [];
848
- for (const child2 of Array.from(scroller.children)) {
849
- if (hasSlot && child2 instanceof HTMLSlotElement) {
850
- for (const el of child2.assignedElements()) out.push(el);
851
- } else {
852
- out.push(child2);
445
+
446
+ // src/core/features/fit-observer.ts
447
+ var contentTargets = (scroller) => {
448
+ if (!scroller) return [];
449
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
450
+ const out = [];
451
+ for (const child2 of Array.from(scroller.children)) {
452
+ if (hasSlot && child2 instanceof HTMLSlotElement) {
453
+ for (const el of child2.assignedElements()) out.push(el);
454
+ } else {
455
+ out.push(child2);
853
456
  }
854
457
  }
855
458
  return out;
@@ -1691,6 +1294,52 @@ function resolveAnimationPreset(animation) {
1691
1294
  }
1692
1295
  }
1693
1296
 
1297
+ // src/core/animation/waapi-settle.ts
1298
+ var SIM_HZ = 240;
1299
+ var SAMPLE_STRIDE = 4;
1300
+ var MAX_SIM_MS = 3e3;
1301
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1302
+ const cfg = { ...DEFAULT_SPRING, ...config };
1303
+ const subDt = 1 / SIM_HZ;
1304
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1305
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1306
+ const values = [from];
1307
+ let x = from;
1308
+ let v = velocityPxPerS;
1309
+ for (let i = 1; i <= maxSteps; i += 1) {
1310
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1311
+ v += force / cfg.mass * subDt;
1312
+ x += v * subDt;
1313
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1314
+ values.push(to);
1315
+ break;
1316
+ }
1317
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1318
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1319
+ if (atRest) break;
1320
+ }
1321
+ if (values[values.length - 1] !== to) values.push(to);
1322
+ return {
1323
+ values,
1324
+ stepMs,
1325
+ durationMs: (values.length - 1) * stepMs
1326
+ };
1327
+ }
1328
+ function sampleTweenSettle(from, to, durationMs, easing) {
1329
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1330
+ const values = [];
1331
+ for (let i = 0; i <= count; i += 1) {
1332
+ const t = i / count;
1333
+ values.push(from + (to - from) * easing(t));
1334
+ }
1335
+ values[values.length - 1] = to;
1336
+ return {
1337
+ values,
1338
+ stepMs: durationMs / count,
1339
+ durationMs
1340
+ };
1341
+ }
1342
+
1694
1343
  // src/core/controllers/animation-runner.ts
1695
1344
  var DEFAULT_DURATION2 = 220;
1696
1345
  var VELOCITY_PX_PER_S = 1e3;
@@ -1701,6 +1350,10 @@ var AnimationRunner = class {
1701
1350
  __publicField(this, "applySizeFn");
1702
1351
  __publicField(this, "getSize");
1703
1352
  __publicField(this, "isDragging");
1353
+ __publicField(this, "applyAux");
1354
+ __publicField(this, "getTransformFor");
1355
+ __publicField(this, "settleWaapi");
1356
+ __publicField(this, "currentWaapi", null);
1704
1357
  __publicField(this, "animationKind");
1705
1358
  __publicField(this, "springConfig");
1706
1359
  __publicField(this, "duration");
@@ -1716,6 +1369,9 @@ var AnimationRunner = class {
1716
1369
  this.applySizeFn = deps.applySize;
1717
1370
  this.getSize = deps.getSize;
1718
1371
  this.isDragging = deps.isDragging;
1372
+ this.applyAux = deps.applyAux;
1373
+ this.getTransformFor = deps.getTransformFor;
1374
+ this.settleWaapi = opts.settleAnimation === "waapi";
1719
1375
  const preset = resolveAnimationPreset(opts.animation);
1720
1376
  this.animationKind = preset.kind;
1721
1377
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1740,13 +1396,22 @@ var AnimationRunner = class {
1740
1396
  }
1741
1397
  }
1742
1398
  get isAnimating() {
1743
- return this.currentTween !== null || this.currentSpring !== null;
1399
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1744
1400
  }
1745
1401
  cancel() {
1746
1402
  this.currentTween?.cancel();
1747
1403
  this.currentSpring?.cancel();
1748
- }
1749
- async animateTo(target, velocityPxPerMs) {
1404
+ if (this.currentWaapi) {
1405
+ const entry = this.currentWaapi;
1406
+ this.currentWaapi = null;
1407
+ entry.stop();
1408
+ try {
1409
+ entry.anim.cancel();
1410
+ } catch {
1411
+ }
1412
+ }
1413
+ }
1414
+ async animateTo(target, velocityPxPerMs) {
1750
1415
  if (target === this.getSize()) {
1751
1416
  if (!this.isAnimating && !this.isDragging()) {
1752
1417
  this.element.style.willChange = "";
@@ -1763,6 +1428,27 @@ var AnimationRunner = class {
1763
1428
  if (!this.isDragging()) this.element.style.willChange = "";
1764
1429
  return;
1765
1430
  }
1431
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1432
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1433
+ this.getSize(),
1434
+ target,
1435
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1436
+ this.springConfig
1437
+ ) : sampleTweenSettle(
1438
+ this.getSize(),
1439
+ target,
1440
+ this.duration,
1441
+ this.easing
1442
+ );
1443
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1444
+ await this.runWaapiSettle(samples, target);
1445
+ if (!this.isAnimating) {
1446
+ this.getRootEl()?.removeAttribute("data-animating");
1447
+ if (!this.isDragging()) this.element.style.willChange = "";
1448
+ }
1449
+ return;
1450
+ }
1451
+ }
1766
1452
  if (this.animationKind === "spring") {
1767
1453
  const spring = runSpring({
1768
1454
  from: this.getSize(),
@@ -1791,11 +1477,66 @@ var AnimationRunner = class {
1791
1477
  if (!this.isDragging()) this.element.style.willChange = "";
1792
1478
  }
1793
1479
  }
1480
+ async runWaapiSettle(samples, target) {
1481
+ const applyAux = this.applyAux;
1482
+ const getTransformFor = this.getTransformFor;
1483
+ const frames = samples.values.map((v) => ({
1484
+ transform: getTransformFor(v)
1485
+ }));
1486
+ const anim = this.element.animate(frames, {
1487
+ duration: samples.durationMs,
1488
+ easing: "linear",
1489
+ fill: "forwards"
1490
+ });
1491
+ let stopped = false;
1492
+ let auxRaf = 0;
1493
+ const start = performance.now();
1494
+ const lastIdx = samples.values.length - 1;
1495
+ const auxTick = () => {
1496
+ if (stopped) return;
1497
+ const elapsed = performance.now() - start;
1498
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1499
+ applyAux(samples.values[idx]);
1500
+ auxRaf = requestAnimationFrame(auxTick);
1501
+ };
1502
+ auxRaf = requestAnimationFrame(auxTick);
1503
+ const entry = {
1504
+ anim,
1505
+ stop: () => {
1506
+ stopped = true;
1507
+ cancelAnimationFrame(auxRaf);
1508
+ }
1509
+ };
1510
+ this.currentWaapi = entry;
1511
+ try {
1512
+ await anim.finished;
1513
+ } catch {
1514
+ }
1515
+ entry.stop();
1516
+ const wasCurrent = this.currentWaapi === entry;
1517
+ if (wasCurrent) this.currentWaapi = null;
1518
+ if (wasCurrent && anim.playState === "finished") {
1519
+ this.applySizeFn(target);
1520
+ try {
1521
+ anim.cancel();
1522
+ } catch {
1523
+ }
1524
+ }
1525
+ }
1794
1526
  destroy() {
1795
1527
  this.detachReducedMotion?.();
1796
1528
  this.detachReducedMotion = null;
1797
1529
  this.currentTween = null;
1798
1530
  this.currentSpring = null;
1531
+ if (this.currentWaapi) {
1532
+ const entry = this.currentWaapi;
1533
+ this.currentWaapi = null;
1534
+ entry.stop();
1535
+ try {
1536
+ entry.anim.cancel();
1537
+ } catch {
1538
+ }
1539
+ }
1799
1540
  }
1800
1541
  };
1801
1542
 
@@ -2652,7 +2393,7 @@ function resolveEngineOptions(opts, extras) {
2652
2393
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2653
2394
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2654
2395
  if (opts.persistKey) {
2655
- const restored = readPersistedId(opts.persistKey);
2396
+ const restored = readPersistedIdSafe(opts.persistKey);
2656
2397
  if (restored && initialAllowed.includes(restored)) {
2657
2398
  initialId = restored;
2658
2399
  }
@@ -2683,6 +2424,7 @@ function resolveEngineOptions(opts, extras) {
2683
2424
  },
2684
2425
  animation: {
2685
2426
  animation: opts.animation,
2427
+ settleAnimation: opts.settleAnimation,
2686
2428
  duration: opts.duration,
2687
2429
  easing: opts.easing,
2688
2430
  spring: opts.spring,
@@ -2700,6 +2442,15 @@ function resolveEngineOptions(opts, extras) {
2700
2442
  }
2701
2443
  };
2702
2444
  }
2445
+ var readPersistedIdSafe = (key) => {
2446
+ if (typeof window === "undefined") return null;
2447
+ if (typeof localStorage === "undefined") return null;
2448
+ try {
2449
+ return localStorage.getItem(key);
2450
+ } catch {
2451
+ return null;
2452
+ }
2453
+ };
2703
2454
 
2704
2455
  // src/core/primitives/teardown-stack.ts
2705
2456
  var TeardownStack = class {
@@ -2722,9 +2473,9 @@ var TeardownStack = class {
2722
2473
  }
2723
2474
  };
2724
2475
 
2725
- // src/core/BottomSheetEngine.ts
2476
+ // src/core/BottomSheetCore.ts
2726
2477
  var HAPTIC_DURATION_MS = 8;
2727
- var BottomSheetEngine = class {
2478
+ var BottomSheetCore = class {
2728
2479
  constructor(opts) {
2729
2480
  __publicField(this, "id", nextInstanceId("bs"));
2730
2481
  __publicField(this, "element");
@@ -2785,6 +2536,8 @@ var BottomSheetEngine = class {
2785
2536
  __publicField(this, "stackEffectPrimed", false);
2786
2537
  __publicField(this, "fitDeps");
2787
2538
  __publicField(this, "transformTemplate");
2539
+ __publicField(this, "featureList", []);
2540
+ __publicField(this, "featureCtx");
2788
2541
  const resolved = resolveEngineOptions(opts, {
2789
2542
  shouldApplyInertSiblings: () => {
2790
2543
  if (typeof document === "undefined") return false;
@@ -2831,7 +2584,9 @@ var BottomSheetEngine = class {
2831
2584
  getRootEl: () => this.rootEl,
2832
2585
  applySize: (size) => this.applySize(size),
2833
2586
  getSize: () => this.size,
2834
- isDragging: () => this.gesture?.isDragging ?? false
2587
+ isDragging: () => this.gesture?.isDragging ?? false,
2588
+ applyAux: (size) => this.applySize(size, true),
2589
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2835
2590
  },
2836
2591
  resolved.animation
2837
2592
  );
@@ -2854,6 +2609,11 @@ var BottomSheetEngine = class {
2854
2609
  if (opts.linkedSheets) {
2855
2610
  this.linkedSheets = opts.linkedSheets;
2856
2611
  }
2612
+ const byName = /* @__PURE__ */ new Map();
2613
+ for (const feature of opts.features ?? []) {
2614
+ byName.set(feature.name, feature);
2615
+ }
2616
+ this.featureList = Array.from(byName.values());
2857
2617
  this.activeId = resolved.initialId;
2858
2618
  auditVhUsage(this.snapPointsRaw);
2859
2619
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2890,6 +2650,7 @@ var BottomSheetEngine = class {
2890
2650
  scrollContainer: this.scrollContainer,
2891
2651
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2892
2652
  });
2653
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2893
2654
  this.attach();
2894
2655
  this.registerInStack();
2895
2656
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2921,46 +2682,77 @@ var BottomSheetEngine = class {
2921
2682
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2922
2683
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2923
2684
  this.applyAutoAriaLabelledBy();
2924
- if (this.closeOnRouteChange) {
2925
- this.teardowns.add(
2926
- installRouteChange({
2927
- isDestroyed: () => this.destroyed,
2928
- getSize: () => this.size,
2929
- close: () => this.close()
2930
- })
2931
- );
2932
- }
2933
- if (this.persistKey) {
2934
- this.teardowns.add(installPersist(this, this.persistKey));
2685
+ this.runFeatureStage("post");
2686
+ this.warnOrphanFeatureOptions();
2687
+ if (this.size > 0) {
2688
+ this.handleOpen();
2935
2689
  }
2936
- this.teardowns.add(
2937
- installAutoCollapse({
2938
- ms: opts.autoCollapseAfter,
2939
- isDestroyed: () => this.destroyed,
2940
- isDragging: () => this.gesture?.isDragging ?? false,
2941
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2942
- getActiveId: () => this.activeId,
2943
- resolveSnap: (id) => this.snaps.findById(id),
2944
- snapTo: (id) => {
2945
- void this.snapTo(id);
2946
- },
2947
- on: (event, fn) => this.on(event, fn)
2948
- })
2949
- );
2950
- this.teardowns.add(
2951
- installRoute({
2690
+ }
2691
+ buildFeatureContext(autoCollapseAfter) {
2692
+ return {
2693
+ element: this.element,
2694
+ scrollContainer: this.scrollContainer,
2695
+ sheetId: this.id,
2696
+ options: {
2952
2697
  routedTo: this.routedTo,
2953
2698
  closeOnBack: this.closeOnBack,
2954
- isTopSheet: () => this.isTopSheet,
2955
- getSize: () => this.size,
2956
- isDestroyed: () => this.destroyed,
2957
- close: () => this.close("back"),
2958
- on: (event, fn) => this.on(event, fn),
2959
- sheetId: this.id
2960
- })
2961
- );
2962
- if (this.size > 0) {
2963
- this.handleOpen();
2699
+ closeOnRouteChange: this.closeOnRouteChange,
2700
+ persistKey: this.persistKey,
2701
+ autoCollapseAfter
2702
+ },
2703
+ isDestroyed: () => this.destroyed,
2704
+ isDragging: () => this.gesture?.isDragging ?? false,
2705
+ isAnimating: () => this.animation.isAnimating,
2706
+ isTopSheet: () => this.isTopSheet,
2707
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2708
+ getSize: () => this.size,
2709
+ getActiveId: () => this.activeId,
2710
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2711
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2712
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2713
+ resolveSnap: (id) => this.snaps.findById(id),
2714
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2715
+ setSize: (size) => {
2716
+ this.size = size;
2717
+ },
2718
+ setMaxAxisSize: (size) => {
2719
+ this.snaps.setMaxAxisSize(size);
2720
+ },
2721
+ applySize: (size) => this.applySize(size),
2722
+ recomputeSnaps: () => this.recomputeSnaps(),
2723
+ newCycle: () => {
2724
+ this.newCycle();
2725
+ },
2726
+ cancelInFlight: () => {
2727
+ this.animation.cancel();
2728
+ },
2729
+ resyncAfterResize: () => this.resyncAfterResize(),
2730
+ snapTo: (id) => {
2731
+ void this.snapTo(id);
2732
+ },
2733
+ close: (reason) => this.close(reason),
2734
+ on: (event, fn) => this.on(event, fn),
2735
+ addTeardown: (fn) => this.teardowns.add(fn)
2736
+ };
2737
+ }
2738
+ runFeatureStage(stage) {
2739
+ for (const feature of this.featureList) {
2740
+ if ((feature.stage ?? "post") !== stage) continue;
2741
+ const teardown = feature.install(this.featureCtx);
2742
+ if (teardown) this.teardowns.add(teardown);
2743
+ }
2744
+ }
2745
+ warnOrphanFeatureOptions() {
2746
+ const names = new Set(this.featureList.map((f) => f.name));
2747
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2748
+ devWarn(
2749
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2750
+ );
2751
+ }
2752
+ if (this.persistKey && !names.has("persist")) {
2753
+ devWarn(
2754
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2755
+ );
2964
2756
  }
2965
2757
  }
2966
2758
  get state() {
@@ -3503,18 +3295,7 @@ var BottomSheetEngine = class {
3503
3295
  }
3504
3296
  }
3505
3297
  attachFeatures() {
3506
- if (this.scrollContainer) {
3507
- this.teardowns.add(installContentSwipe({
3508
- container: this.scrollContainer,
3509
- isDragging: () => this.gesture?.isDragging ?? false,
3510
- isAnimating: () => this.animation.isAnimating,
3511
- getAllowedIds: () => this.allowedIdsBySize(),
3512
- getActiveId: () => this.activeId,
3513
- snapTo: (id) => {
3514
- void this.snapTo(id);
3515
- }
3516
- }));
3517
- }
3298
+ this.runFeatureStage("attach");
3518
3299
  if (typeof window !== "undefined") {
3519
3300
  this.teardowns.add(installResizeObserver({
3520
3301
  element: this.element,
@@ -3554,31 +3335,6 @@ var BottomSheetEngine = class {
3554
3335
  () => document.removeEventListener("keydown", onKey)
3555
3336
  );
3556
3337
  }
3557
- this.teardowns.add(installVisualViewport({
3558
- element: this.element,
3559
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3560
- isDestroyed: () => this.destroyed,
3561
- isDragging: () => this.gesture?.isDragging ?? false,
3562
- recomputeSnaps: () => this.recomputeSnaps(),
3563
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3564
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3565
- getSize: () => this.size,
3566
- setMaxAxisSize: (size) => {
3567
- this.snaps.setMaxAxisSize(size);
3568
- },
3569
- setSize: (size) => {
3570
- this.size = size;
3571
- },
3572
- applySize: (size) => this.applySize(size),
3573
- cancelInFlight: () => {
3574
- this.animation.cancel();
3575
- },
3576
- newCycle: () => {
3577
- this.newCycle();
3578
- },
3579
- isAnimating: () => this.animation.isAnimating,
3580
- resyncAfterCancel: () => this.resyncAfterResize()
3581
- }));
3582
3338
  }
3583
3339
  this.teardowns.add(installSliderKeyboard({
3584
3340
  handle: this.handle,
@@ -3655,13 +3411,13 @@ var BottomSheetEngine = class {
3655
3411
  }
3656
3412
  }
3657
3413
  }
3658
- applySize(size) {
3414
+ applySize(size, skipTransform = false) {
3659
3415
  const cap = this.snaps.getMaxAxisSize();
3660
3416
  const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3661
3417
  this.size = clamped;
3662
3418
  const offset = cap - clamped;
3663
3419
  const style = this.element.style;
3664
- style.transform = this.transformTemplate(offset);
3420
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3665
3421
  if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3666
3422
  style.setProperty("--bs-size", `${clamped}px`);
3667
3423
  if (this.scrimParent) {
@@ -3739,6 +3495,514 @@ var BottomSheetEngine = class {
3739
3495
  }
3740
3496
  };
3741
3497
 
3498
+ // src/core/features/history-coordinator.ts
3499
+ var entries = [];
3500
+ var subscribers = /* @__PURE__ */ new Set();
3501
+ var suppress = 0;
3502
+ var internalDepth = 0;
3503
+ var pendingRebrands = [];
3504
+ var listenerInstalled = false;
3505
+ var patchInstalled = false;
3506
+ var origPushState = null;
3507
+ var origReplaceState = null;
3508
+ var patchedPushState = null;
3509
+ var patchedReplaceState = null;
3510
+ var hasWindow = () => typeof window !== "undefined";
3511
+ var hasHistory = () => typeof history !== "undefined";
3512
+ var brandMarker = (surface) => ({
3513
+ ...surface.markerState(),
3514
+ __bs: true
3515
+ });
3516
+ var runInternal = (fn) => {
3517
+ internalDepth += 1;
3518
+ try {
3519
+ fn();
3520
+ } finally {
3521
+ internalDepth -= 1;
3522
+ }
3523
+ };
3524
+ var notifyRouteChange = () => {
3525
+ const snapshot = Array.from(subscribers);
3526
+ for (const fn of snapshot) fn();
3527
+ };
3528
+ var ensureListener = () => {
3529
+ if (listenerInstalled || !hasWindow()) return;
3530
+ window.addEventListener("popstate", onPopState);
3531
+ listenerInstalled = true;
3532
+ };
3533
+ var maybeTeardownListener = () => {
3534
+ if (!listenerInstalled) return;
3535
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3536
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3537
+ listenerInstalled = false;
3538
+ };
3539
+ var installPatch = () => {
3540
+ if (patchInstalled || !hasHistory()) return;
3541
+ origPushState = history.pushState;
3542
+ origReplaceState = history.replaceState;
3543
+ patchedPushState = function(...args) {
3544
+ origPushState.apply(this, args);
3545
+ if (internalDepth === 0) notifyRouteChange();
3546
+ };
3547
+ patchedReplaceState = function(...args) {
3548
+ origReplaceState.apply(this, args);
3549
+ if (internalDepth === 0) notifyRouteChange();
3550
+ };
3551
+ history.pushState = patchedPushState;
3552
+ history.replaceState = patchedReplaceState;
3553
+ patchInstalled = true;
3554
+ };
3555
+ var removePatch = () => {
3556
+ if (!patchInstalled) return;
3557
+ if (history.pushState === patchedPushState && origPushState) {
3558
+ history.pushState = origPushState;
3559
+ }
3560
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3561
+ history.replaceState = origReplaceState;
3562
+ }
3563
+ origPushState = null;
3564
+ origReplaceState = null;
3565
+ patchedPushState = null;
3566
+ patchedReplaceState = null;
3567
+ patchInstalled = false;
3568
+ };
3569
+ function onPopState() {
3570
+ if (suppress > 0) {
3571
+ suppress -= 1;
3572
+ const rebrand = pendingRebrands.shift();
3573
+ if (rebrand) rebrand();
3574
+ maybeTeardownListener();
3575
+ return;
3576
+ }
3577
+ if (entries.length === 0) {
3578
+ notifyRouteChange();
3579
+ maybeTeardownListener();
3580
+ return;
3581
+ }
3582
+ let target = null;
3583
+ let targetIdx = -1;
3584
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3585
+ const entry = entries[i];
3586
+ if (!entry.surface.isOpen()) continue;
3587
+ if (!target) {
3588
+ target = entry;
3589
+ targetIdx = i;
3590
+ }
3591
+ if (entry.surface.isTop()) {
3592
+ target = entry;
3593
+ targetIdx = i;
3594
+ break;
3595
+ }
3596
+ }
3597
+ if (!target) {
3598
+ const removed = entries.pop();
3599
+ if (removed) removed.live = false;
3600
+ maybeTeardownListener();
3601
+ return;
3602
+ }
3603
+ target.live = false;
3604
+ entries.splice(targetIdx, 1);
3605
+ const settled = target;
3606
+ void Promise.resolve(settled.surface.close()).then(() => {
3607
+ if (settled.surface.isOpen()) restore(settled);
3608
+ });
3609
+ maybeTeardownListener();
3610
+ }
3611
+ function restore(entry) {
3612
+ if (!hasWindow() || !hasHistory()) return;
3613
+ entry.live = true;
3614
+ runInternal(() => {
3615
+ try {
3616
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3617
+ } catch {
3618
+ entry.live = false;
3619
+ }
3620
+ });
3621
+ if (entry.live) {
3622
+ entries.push(entry);
3623
+ ensureListener();
3624
+ }
3625
+ }
3626
+ function pushBackMarker(surface) {
3627
+ const entry = { surface, priorUrl: void 0, live: false };
3628
+ if (!hasWindow() || !hasHistory()) {
3629
+ return entry;
3630
+ }
3631
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3632
+ runInternal(() => {
3633
+ try {
3634
+ history.pushState(brandMarker(surface), "", surface.url);
3635
+ entry.live = true;
3636
+ } catch {
3637
+ entry.live = false;
3638
+ }
3639
+ });
3640
+ if (entry.live) {
3641
+ entries.push(entry);
3642
+ ensureListener();
3643
+ }
3644
+ return entry;
3645
+ }
3646
+ function popBackMarker(handle) {
3647
+ if (!hasWindow() || !hasHistory()) return;
3648
+ const entry = handle;
3649
+ const idx = entries.indexOf(entry);
3650
+ if (idx === -1 || !entry.live) return;
3651
+ const last = entries.length - 1;
3652
+ entry.live = false;
3653
+ entries.splice(idx, 1);
3654
+ let rebrand = null;
3655
+ if (idx !== last) {
3656
+ const rebrandSurface = entries[entries.length - 1].surface;
3657
+ const rebrandUrl = entry.priorUrl;
3658
+ rebrand = () => {
3659
+ runInternal(() => {
3660
+ history.replaceState(
3661
+ brandMarker(rebrandSurface),
3662
+ "",
3663
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3664
+ );
3665
+ });
3666
+ };
3667
+ }
3668
+ pendingRebrands.push(rebrand);
3669
+ suppress += 1;
3670
+ runInternal(() => {
3671
+ try {
3672
+ history.back();
3673
+ } catch {
3674
+ suppress = Math.max(0, suppress - 1);
3675
+ pendingRebrands.pop();
3676
+ }
3677
+ });
3678
+ maybeTeardownListener();
3679
+ }
3680
+ function subscribeRouteChange(fn) {
3681
+ if (!hasWindow() || !hasHistory()) return () => {
3682
+ };
3683
+ subscribers.add(fn);
3684
+ if (subscribers.size === 1) installPatch();
3685
+ ensureListener();
3686
+ return () => {
3687
+ if (!subscribers.has(fn)) return;
3688
+ subscribers.delete(fn);
3689
+ if (subscribers.size === 0) removePatch();
3690
+ maybeTeardownListener();
3691
+ };
3692
+ }
3693
+
3694
+ // src/core/features/route.ts
3695
+ function installRoute(deps) {
3696
+ if (typeof window === "undefined") {
3697
+ return () => {
3698
+ };
3699
+ }
3700
+ if (!deps.routedTo && !deps.closeOnBack) {
3701
+ return () => {
3702
+ };
3703
+ }
3704
+ let handle = null;
3705
+ const onOpen = () => {
3706
+ if (handle) return;
3707
+ handle = pushBackMarker({
3708
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3709
+ isTop: deps.isTopSheet,
3710
+ close: deps.close,
3711
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3712
+ url: deps.routedTo
3713
+ });
3714
+ };
3715
+ const onClose = () => {
3716
+ if (!handle) return;
3717
+ popBackMarker(handle);
3718
+ handle = null;
3719
+ };
3720
+ const unsubscribeOpen = deps.on("open", onOpen);
3721
+ const unsubscribeClose = deps.on("close", onClose);
3722
+ if (deps.getSize() > 0) onOpen();
3723
+ return () => {
3724
+ unsubscribeOpen();
3725
+ unsubscribeClose();
3726
+ if (handle) {
3727
+ popBackMarker(handle);
3728
+ handle = null;
3729
+ }
3730
+ };
3731
+ }
3732
+ function installRouteChange(deps) {
3733
+ return subscribeRouteChange(() => {
3734
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3735
+ void deps.close();
3736
+ });
3737
+ }
3738
+
3739
+ // src/core/features/persist.ts
3740
+ function installPersist(engine, key) {
3741
+ const unsubscribe = engine.on("snap", ({ id }) => {
3742
+ if (typeof window === "undefined") return;
3743
+ if (typeof localStorage === "undefined") return;
3744
+ try {
3745
+ localStorage.setItem(key, id);
3746
+ } catch {
3747
+ }
3748
+ });
3749
+ return unsubscribe;
3750
+ }
3751
+
3752
+ // src/core/features/auto-collapse.ts
3753
+ function installAutoCollapse(deps) {
3754
+ const { ms } = deps;
3755
+ if (ms === void 0 || ms <= 0) {
3756
+ return () => {
3757
+ };
3758
+ }
3759
+ let timer = null;
3760
+ const clearTimer = () => {
3761
+ if (timer !== null) {
3762
+ clearTimeout(timer);
3763
+ timer = null;
3764
+ }
3765
+ };
3766
+ const fire = () => {
3767
+ if (deps.isDestroyed()) return;
3768
+ if (deps.isDragging()) return;
3769
+ const active = deps.resolveSnap(deps.getActiveId());
3770
+ if (!active || active.size === 0) return;
3771
+ let target = null;
3772
+ for (const id of deps.getAllowedIds()) {
3773
+ const snap = deps.resolveSnap(id);
3774
+ if (!snap || snap.size <= 0) continue;
3775
+ if (!target || snap.size < target.size) target = snap;
3776
+ }
3777
+ if (!target) return;
3778
+ if (target.id === active.id || target.size >= active.size) return;
3779
+ deps.snapTo(target.id);
3780
+ };
3781
+ const reset2 = () => {
3782
+ clearTimer();
3783
+ if (deps.isDestroyed()) return;
3784
+ timer = setTimeout(() => {
3785
+ timer = null;
3786
+ fire();
3787
+ }, ms);
3788
+ };
3789
+ const unsubscribe = deps.on("snap", () => reset2());
3790
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset2();
3791
+ return () => {
3792
+ clearTimer();
3793
+ unsubscribe();
3794
+ };
3795
+ }
3796
+
3797
+ // src/core/features/content-swipe.ts
3798
+ var CONTENT_PULL_THRESHOLD = 60;
3799
+ function installContentSwipe(deps) {
3800
+ const { container } = deps;
3801
+ let startY = 0;
3802
+ let startScrollTop = 0;
3803
+ let moved = false;
3804
+ const onTouchStart = (e) => {
3805
+ const t = e.touches[0];
3806
+ if (!t) return;
3807
+ startY = t.clientY;
3808
+ startScrollTop = container.scrollTop;
3809
+ moved = false;
3810
+ };
3811
+ const onTouchMove = (e) => {
3812
+ const t = e.touches[0];
3813
+ if (!t) return;
3814
+ if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3815
+ };
3816
+ const onTouchEnd = (e) => {
3817
+ if (!moved) return;
3818
+ if (deps.isDragging() || deps.isAnimating()) return;
3819
+ if (container.scrollTop !== startScrollTop) return;
3820
+ const t = e.changedTouches[0];
3821
+ if (!t) return;
3822
+ const deltaY = t.clientY - startY;
3823
+ const atTop = container.scrollTop <= 0;
3824
+ const allowed = deps.getAllowedIds();
3825
+ const idx = allowed.indexOf(deps.getActiveId());
3826
+ if (idx === -1) return;
3827
+ if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3828
+ deps.snapTo(allowed[idx - 1]);
3829
+ } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3830
+ deps.snapTo(allowed[idx + 1]);
3831
+ }
3832
+ };
3833
+ container.addEventListener("touchstart", onTouchStart, { passive: true });
3834
+ container.addEventListener("touchmove", onTouchMove, { passive: true });
3835
+ container.addEventListener("touchend", onTouchEnd, { passive: true });
3836
+ return () => {
3837
+ container.removeEventListener("touchstart", onTouchStart);
3838
+ container.removeEventListener("touchmove", onTouchMove);
3839
+ container.removeEventListener("touchend", onTouchEnd);
3840
+ };
3841
+ }
3842
+
3843
+ // src/core/features/visual-viewport.ts
3844
+ var SAFETY_PAD = 8;
3845
+ function installVisualViewport(deps) {
3846
+ if (typeof window === "undefined" || !window.visualViewport) {
3847
+ return () => {
3848
+ };
3849
+ }
3850
+ const vv = window.visualViewport;
3851
+ let rafId = null;
3852
+ const onResize = () => {
3853
+ if (rafId !== null) return;
3854
+ rafId = requestAnimationFrame(() => {
3855
+ rafId = null;
3856
+ if (deps.isDestroyed()) return;
3857
+ deps.recomputeSnaps();
3858
+ const isVertical = deps.isVerticalAxis();
3859
+ const visible = isVertical ? vv.height : vv.width;
3860
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
3861
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
3862
+ if (clamp < deps.getMaxAxisSize()) {
3863
+ deps.setMaxAxisSize(clamp);
3864
+ }
3865
+ const wasAnimating = deps.isAnimating?.() ?? false;
3866
+ deps.cancelInFlight();
3867
+ deps.newCycle();
3868
+ const max = deps.getMaxAxisSize();
3869
+ deps.setSize(Math.min(deps.getSize(), max));
3870
+ const current = deps.resolveActiveSnap();
3871
+ if (current && !deps.isDragging()) {
3872
+ deps.applySize(Math.min(current.size, max));
3873
+ if (wasAnimating) deps.resyncAfterCancel?.();
3874
+ } else {
3875
+ deps.applySize(deps.getSize());
3876
+ }
3877
+ });
3878
+ };
3879
+ vv.addEventListener("resize", onResize);
3880
+ return () => {
3881
+ vv.removeEventListener("resize", onResize);
3882
+ if (rafId !== null) cancelAnimationFrame(rafId);
3883
+ };
3884
+ }
3885
+
3886
+ // src/core/features/engine-features.ts
3887
+ function routeFeature() {
3888
+ return {
3889
+ name: "route",
3890
+ install: (ctx) => {
3891
+ const teardowns = new TeardownStack();
3892
+ if (ctx.options.closeOnRouteChange) {
3893
+ teardowns.add(
3894
+ installRouteChange({
3895
+ isDestroyed: ctx.isDestroyed,
3896
+ getSize: ctx.getSize,
3897
+ close: () => ctx.close()
3898
+ })
3899
+ );
3900
+ }
3901
+ teardowns.add(
3902
+ installRoute({
3903
+ routedTo: ctx.options.routedTo,
3904
+ closeOnBack: ctx.options.closeOnBack,
3905
+ isTopSheet: ctx.isTopSheet,
3906
+ getSize: ctx.getSize,
3907
+ isDestroyed: ctx.isDestroyed,
3908
+ close: () => ctx.close("back"),
3909
+ on: ctx.on,
3910
+ sheetId: ctx.sheetId
3911
+ })
3912
+ );
3913
+ return () => teardowns.drain();
3914
+ }
3915
+ };
3916
+ }
3917
+ function persistFeature() {
3918
+ return {
3919
+ name: "persist",
3920
+ install: (ctx) => {
3921
+ const key = ctx.options.persistKey;
3922
+ if (!key) return;
3923
+ return installPersist({ on: ctx.on }, key);
3924
+ }
3925
+ };
3926
+ }
3927
+ function autoCollapseFeature() {
3928
+ return {
3929
+ name: "auto-collapse",
3930
+ install: (ctx) => installAutoCollapse({
3931
+ ms: ctx.options.autoCollapseAfter,
3932
+ isDestroyed: ctx.isDestroyed,
3933
+ isDragging: ctx.isDragging,
3934
+ getAllowedIds: ctx.getAllowedIds,
3935
+ getActiveId: ctx.getActiveId,
3936
+ resolveSnap: ctx.resolveSnap,
3937
+ snapTo: ctx.snapTo,
3938
+ on: ctx.on
3939
+ })
3940
+ };
3941
+ }
3942
+ function contentSwipeFeature() {
3943
+ return {
3944
+ name: "content-swipe",
3945
+ stage: "attach",
3946
+ install: (ctx) => {
3947
+ if (!ctx.scrollContainer) return;
3948
+ return installContentSwipe({
3949
+ container: ctx.scrollContainer,
3950
+ isDragging: ctx.isDragging,
3951
+ isAnimating: ctx.isAnimating,
3952
+ getAllowedIds: ctx.allowedIdsBySize,
3953
+ getActiveId: ctx.getActiveId,
3954
+ snapTo: ctx.snapTo
3955
+ });
3956
+ }
3957
+ };
3958
+ }
3959
+ function visualViewportFeature() {
3960
+ return {
3961
+ name: "visual-viewport",
3962
+ install: (ctx) => {
3963
+ if (typeof window === "undefined") return;
3964
+ return installVisualViewport({
3965
+ element: ctx.element,
3966
+ isVerticalAxis: ctx.isVerticalAxis,
3967
+ isDestroyed: ctx.isDestroyed,
3968
+ isDragging: ctx.isDragging,
3969
+ recomputeSnaps: ctx.recomputeSnaps,
3970
+ resolveActiveSnap: ctx.resolveActiveSnap,
3971
+ getMaxAxisSize: ctx.getMaxAxisSize,
3972
+ getSize: ctx.getSize,
3973
+ setMaxAxisSize: ctx.setMaxAxisSize,
3974
+ setSize: ctx.setSize,
3975
+ applySize: ctx.applySize,
3976
+ cancelInFlight: ctx.cancelInFlight,
3977
+ newCycle: ctx.newCycle,
3978
+ isAnimating: ctx.isAnimating,
3979
+ resyncAfterCancel: ctx.resyncAfterResize
3980
+ });
3981
+ }
3982
+ };
3983
+ }
3984
+
3985
+ // src/core/default-features.ts
3986
+ function defaultEngineFeatures() {
3987
+ return [
3988
+ contentSwipeFeature(),
3989
+ visualViewportFeature(),
3990
+ persistFeature(),
3991
+ autoCollapseFeature(),
3992
+ routeFeature()
3993
+ ];
3994
+ }
3995
+
3996
+ // src/core/BottomSheetEngine.ts
3997
+ var BottomSheetEngine = class extends BottomSheetCore {
3998
+ constructor(opts) {
3999
+ super({
4000
+ ...opts,
4001
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
4002
+ });
4003
+ }
4004
+ };
4005
+
3742
4006
  // src/core/features/teleport.ts
3743
4007
  var resolveTeleportTarget = (target) => {
3744
4008
  if (!target) return null;