@surdeddd/bottom-sheet 0.11.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 +68 -1
  2. package/dist/{BottomSheetEngine-DzsIwD28.d.cts → BottomSheetCore-CsIjyq7C.d.cts} +61 -5
  3. package/dist/{BottomSheetEngine-DRjCU73e.d.ts → BottomSheetCore-D16lsa6g.d.ts} +61 -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 +994 -697
  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 +994 -697
  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 +823 -542
  26. package/dist/index.d.cts +6 -15
  27. package/dist/index.d.ts +6 -15
  28. package/dist/index.js +817 -543
  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 +13 -8
  34. package/dist/overlay.js +13 -8
  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 +816 -542
  40. package/dist/qwik.d.cts +2 -2
  41. package/dist/qwik.d.ts +2 -2
  42. package/dist/qwik.js +816 -542
  43. package/dist/react.cjs +816 -542
  44. package/dist/react.d.cts +3 -2
  45. package/dist/react.d.ts +3 -2
  46. package/dist/react.js +816 -542
  47. package/dist/solid.cjs +816 -542
  48. package/dist/solid.d.cts +64 -4
  49. package/dist/solid.d.ts +64 -4
  50. package/dist/solid.js +816 -542
  51. package/dist/solid.source.jsx +816 -542
  52. package/dist/svelte-core.cjs +816 -542
  53. package/dist/svelte-core.js +816 -542
  54. package/dist/svelte.cjs +816 -542
  55. package/dist/svelte.js +816 -542
  56. package/dist/vue.cjs +927 -645
  57. package/dist/vue.js +927 -645
  58. package/package.json +63 -11
package/dist/react.cjs CHANGED
@@ -358,74 +358,6 @@ var SnapResolver = class {
358
358
  }
359
359
  };
360
360
 
361
- // src/core/features/persist.ts
362
- function readPersistedId(key) {
363
- if (!key) return null;
364
- if (typeof window === "undefined") return null;
365
- if (typeof localStorage === "undefined") return null;
366
- try {
367
- return localStorage.getItem(key);
368
- } catch {
369
- return null;
370
- }
371
- }
372
- function installPersist(engine, key) {
373
- const unsubscribe = engine.on("snap", ({ id }) => {
374
- if (typeof window === "undefined") return;
375
- if (typeof localStorage === "undefined") return;
376
- try {
377
- localStorage.setItem(key, id);
378
- } catch {
379
- }
380
- });
381
- return unsubscribe;
382
- }
383
-
384
- // src/core/features/auto-collapse.ts
385
- function installAutoCollapse(deps) {
386
- const { ms } = deps;
387
- if (ms === void 0 || ms <= 0) {
388
- return () => {
389
- };
390
- }
391
- let timer = null;
392
- const clearTimer = () => {
393
- if (timer !== null) {
394
- clearTimeout(timer);
395
- timer = null;
396
- }
397
- };
398
- const fire = () => {
399
- if (deps.isDestroyed()) return;
400
- if (deps.isDragging()) return;
401
- const active = deps.resolveSnap(deps.getActiveId());
402
- if (!active || active.size === 0) return;
403
- let target = null;
404
- for (const id of deps.getAllowedIds()) {
405
- const snap = deps.resolveSnap(id);
406
- if (!snap || snap.size <= 0) continue;
407
- if (!target || snap.size < target.size) target = snap;
408
- }
409
- if (!target) return;
410
- if (target.id === active.id || target.size >= active.size) return;
411
- deps.snapTo(target.id);
412
- };
413
- const reset = () => {
414
- clearTimer();
415
- if (deps.isDestroyed()) return;
416
- timer = setTimeout(() => {
417
- timer = null;
418
- fire();
419
- }, ms);
420
- };
421
- const unsubscribe = deps.on("snap", () => reset());
422
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
423
- return () => {
424
- clearTimer();
425
- unsubscribe();
426
- };
427
- }
428
-
429
361
  // src/core/features/linked-sheets.ts
430
362
  function notifyLinkedSheets(sheets, self) {
431
363
  if (sheets.length === 0) return;
@@ -448,390 +380,65 @@ function notifyLinkedSheets(sheets, self) {
448
380
  }
449
381
  }
450
382
 
451
- // src/core/features/history-coordinator.ts
452
- var entries = [];
453
- var subscribers = /* @__PURE__ */ new Set();
454
- var suppress = 0;
455
- var internalDepth = 0;
456
- var pendingRebrand = null;
457
- var listenerInstalled = false;
458
- var patchInstalled = false;
459
- var origPushState = null;
460
- var origReplaceState = null;
461
- var patchedPushState = null;
462
- var patchedReplaceState = null;
463
- var hasWindow = () => typeof window !== "undefined";
464
- var hasHistory = () => typeof history !== "undefined";
465
- var runInternal = (fn) => {
466
- internalDepth += 1;
467
- try {
468
- fn();
469
- } finally {
470
- internalDepth -= 1;
471
- }
472
- };
473
- var notifyRouteChange = () => {
474
- const snapshot = Array.from(subscribers);
475
- for (const fn of snapshot) fn();
476
- };
477
- var ensureListener = () => {
478
- if (listenerInstalled || !hasWindow()) return;
479
- window.addEventListener("popstate", onPopState);
480
- listenerInstalled = true;
481
- };
482
- var maybeTeardownListener = () => {
483
- if (!listenerInstalled) return;
484
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
485
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
486
- listenerInstalled = false;
487
- };
488
- var installPatch = () => {
489
- if (patchInstalled || !hasHistory()) return;
490
- origPushState = history.pushState;
491
- origReplaceState = history.replaceState;
492
- patchedPushState = function(...args) {
493
- origPushState.apply(this, args);
494
- if (internalDepth === 0) notifyRouteChange();
495
- };
496
- patchedReplaceState = function(...args) {
497
- origReplaceState.apply(this, args);
498
- if (internalDepth === 0) notifyRouteChange();
499
- };
500
- history.pushState = patchedPushState;
501
- history.replaceState = patchedReplaceState;
502
- patchInstalled = true;
503
- };
504
- var removePatch = () => {
505
- if (!patchInstalled) return;
506
- if (history.pushState === patchedPushState && origPushState) {
507
- history.pushState = origPushState;
508
- }
509
- if (history.replaceState === patchedReplaceState && origReplaceState) {
510
- history.replaceState = origReplaceState;
511
- }
512
- origPushState = null;
513
- origReplaceState = null;
514
- patchedPushState = null;
515
- patchedReplaceState = null;
516
- patchInstalled = false;
517
- };
518
- function onPopState() {
519
- if (suppress > 0) {
520
- suppress -= 1;
521
- const rebrand = pendingRebrand;
522
- pendingRebrand = null;
523
- if (rebrand) rebrand();
524
- maybeTeardownListener();
525
- return;
526
- }
527
- if (entries.length === 0) {
528
- notifyRouteChange();
529
- maybeTeardownListener();
530
- return;
383
+ // src/core/features/resize-observer.ts
384
+ function installResizeObserver(deps) {
385
+ if (typeof window === "undefined") {
386
+ return () => {
387
+ };
531
388
  }
532
- let target = null;
533
- let targetIdx = -1;
534
- for (let i = entries.length - 1; i >= 0; i -= 1) {
535
- const entry = entries[i];
536
- if (!entry.surface.isOpen()) continue;
537
- if (!target) {
538
- target = entry;
539
- targetIdx = i;
540
- }
541
- if (entry.surface.isTop()) {
542
- target = entry;
543
- targetIdx = i;
544
- break;
389
+ const onResize = () => {
390
+ if (deps.isDestroyed()) return;
391
+ deps.recomputeSnaps();
392
+ const mode = deps.getMode();
393
+ const isVerticalAxis = mode === "bottom" || mode === "top";
394
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
395
+ const max = deps.getMaxAxisSize();
396
+ if (viewportSize2 > 0 && viewportSize2 < max) {
397
+ deps.setMaxAxisSize(viewportSize2);
545
398
  }
546
- }
547
- if (!target) {
548
- const removed = entries.pop();
549
- if (removed) removed.live = false;
550
- maybeTeardownListener();
551
- return;
552
- }
553
- target.live = false;
554
- entries.splice(targetIdx, 1);
555
- const settled = target;
556
- void Promise.resolve(settled.surface.close()).then(() => {
557
- if (settled.surface.isOpen()) restore(settled);
558
- });
559
- maybeTeardownListener();
560
- }
561
- function restore(entry) {
562
- if (!hasWindow() || !hasHistory()) return;
563
- entry.live = true;
564
- runInternal(() => {
565
- try {
566
- history.pushState(entry.surface.markerState(), "", entry.surface.url);
567
- } catch {
568
- entry.live = false;
399
+ const wasAnimating = deps.isAnimating?.() ?? false;
400
+ deps.cancelInFlight();
401
+ deps.newCycle();
402
+ const current = deps.resolveActiveSnap();
403
+ const newMax = deps.getMaxAxisSize();
404
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
405
+ deps.setSize(targetSize);
406
+ if (current && !deps.isDragging()) {
407
+ deps.applySize(targetSize);
408
+ if (wasAnimating) deps.resyncAfterCancel?.();
569
409
  }
570
- });
571
- if (entry.live) {
572
- entries.push(entry);
573
- ensureListener();
410
+ };
411
+ let resizeObserver = null;
412
+ if (typeof ResizeObserver !== "undefined") {
413
+ resizeObserver = new ResizeObserver(onResize);
414
+ resizeObserver.observe(document.documentElement);
415
+ } else {
416
+ window.addEventListener("resize", onResize);
574
417
  }
418
+ window.addEventListener("orientationchange", onResize);
419
+ const onVisibility = () => {
420
+ if (deps.isDestroyed()) return;
421
+ if (document.hidden) deps.cancelInFlight();
422
+ };
423
+ document.addEventListener("visibilitychange", onVisibility);
424
+ return () => {
425
+ resizeObserver?.disconnect();
426
+ window.removeEventListener("resize", onResize);
427
+ window.removeEventListener("orientationchange", onResize);
428
+ document.removeEventListener("visibilitychange", onVisibility);
429
+ };
575
430
  }
576
- function pushBackMarker(surface) {
577
- const entry = { surface, priorUrl: void 0, live: false };
578
- if (!hasWindow() || !hasHistory()) {
579
- return entry;
580
- }
581
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
582
- runInternal(() => {
583
- try {
584
- history.pushState(surface.markerState(), "", surface.url);
585
- entry.live = true;
586
- } catch {
587
- entry.live = false;
588
- }
589
- });
590
- if (entry.live) {
591
- entries.push(entry);
592
- ensureListener();
593
- }
594
- return entry;
595
- }
596
- function popBackMarker(handle) {
597
- if (!hasWindow() || !hasHistory()) return;
598
- const entry = handle;
599
- const idx = entries.indexOf(entry);
600
- if (idx === -1 || !entry.live) return;
601
- const last = entries.length - 1;
602
- entry.live = false;
603
- entries.splice(idx, 1);
604
- if (idx !== last) {
605
- const rebrandSurface = entries[entries.length - 1].surface;
606
- const rebrandUrl = entry.priorUrl;
607
- pendingRebrand = () => {
608
- runInternal(() => {
609
- history.replaceState(
610
- rebrandSurface.markerState(),
611
- "",
612
- rebrandUrl !== void 0 ? rebrandUrl : location.href
613
- );
614
- });
615
- };
616
- }
617
- suppress += 1;
618
- runInternal(() => {
619
- try {
620
- history.back();
621
- } catch {
622
- suppress = Math.max(0, suppress - 1);
623
- pendingRebrand = null;
624
- }
625
- });
626
- maybeTeardownListener();
627
- }
628
- function subscribeRouteChange(fn) {
629
- if (!hasWindow() || !hasHistory()) return () => {
630
- };
631
- subscribers.add(fn);
632
- if (subscribers.size === 1) installPatch();
633
- ensureListener();
634
- return () => {
635
- if (!subscribers.has(fn)) return;
636
- subscribers.delete(fn);
637
- if (subscribers.size === 0) removePatch();
638
- maybeTeardownListener();
639
- };
640
- }
641
-
642
- // src/core/features/route.ts
643
- function installRoute(deps) {
644
- if (typeof window === "undefined") {
645
- return () => {
646
- };
647
- }
648
- if (!deps.routedTo && !deps.closeOnBack) {
649
- return () => {
650
- };
651
- }
652
- let handle = null;
653
- const onOpen = () => {
654
- if (handle) return;
655
- handle = pushBackMarker({
656
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
657
- isTop: deps.isTopSheet,
658
- close: deps.close,
659
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
660
- url: deps.routedTo
661
- });
662
- };
663
- const onClose = () => {
664
- if (!handle) return;
665
- popBackMarker(handle);
666
- handle = null;
667
- };
668
- const unsubscribeOpen = deps.on("open", onOpen);
669
- const unsubscribeClose = deps.on("close", onClose);
670
- if (deps.getSize() > 0) onOpen();
671
- return () => {
672
- unsubscribeOpen();
673
- unsubscribeClose();
674
- if (handle) {
675
- popBackMarker(handle);
676
- handle = null;
677
- }
678
- };
679
- }
680
- function installRouteChange(deps) {
681
- return subscribeRouteChange(() => {
682
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
683
- void deps.close();
684
- });
685
- }
686
-
687
- // src/core/features/content-swipe.ts
688
- var CONTENT_PULL_THRESHOLD = 60;
689
- function installContentSwipe(deps) {
690
- const { container } = deps;
691
- let startY = 0;
692
- let startScrollTop = 0;
693
- let moved = false;
694
- const onTouchStart = (e) => {
695
- const t = e.touches[0];
696
- if (!t) return;
697
- startY = t.clientY;
698
- startScrollTop = container.scrollTop;
699
- moved = false;
700
- };
701
- const onTouchMove = (e) => {
702
- const t = e.touches[0];
703
- if (!t) return;
704
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
705
- };
706
- const onTouchEnd = (e) => {
707
- if (!moved) return;
708
- if (deps.isDragging() || deps.isAnimating()) return;
709
- if (container.scrollTop !== startScrollTop) return;
710
- const t = e.changedTouches[0];
711
- if (!t) return;
712
- const deltaY = t.clientY - startY;
713
- const atTop = container.scrollTop <= 0;
714
- const allowed = deps.getAllowedIds();
715
- const idx = allowed.indexOf(deps.getActiveId());
716
- if (idx === -1) return;
717
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
718
- deps.snapTo(allowed[idx - 1]);
719
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
720
- deps.snapTo(allowed[idx + 1]);
721
- }
722
- };
723
- container.addEventListener("touchstart", onTouchStart, { passive: true });
724
- container.addEventListener("touchmove", onTouchMove, { passive: true });
725
- container.addEventListener("touchend", onTouchEnd, { passive: true });
726
- return () => {
727
- container.removeEventListener("touchstart", onTouchStart);
728
- container.removeEventListener("touchmove", onTouchMove);
729
- container.removeEventListener("touchend", onTouchEnd);
730
- };
731
- }
732
-
733
- // src/core/features/visual-viewport.ts
734
- var SAFETY_PAD = 8;
735
- function installVisualViewport(deps) {
736
- if (typeof window === "undefined" || !window.visualViewport) {
737
- return () => {
738
- };
739
- }
740
- const vv = window.visualViewport;
741
- let rafId = null;
742
- const onResize = () => {
743
- if (rafId !== null) return;
744
- rafId = requestAnimationFrame(() => {
745
- rafId = null;
746
- if (deps.isDestroyed()) return;
747
- deps.recomputeSnaps();
748
- const isVertical = deps.isVerticalAxis();
749
- const visible = isVertical ? vv.height : vv.width;
750
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
751
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
752
- if (clamp < deps.getMaxAxisSize()) {
753
- deps.setMaxAxisSize(clamp);
754
- }
755
- const wasAnimating = deps.isAnimating?.() ?? false;
756
- deps.cancelInFlight();
757
- deps.newCycle();
758
- const max = deps.getMaxAxisSize();
759
- deps.setSize(Math.min(deps.getSize(), max));
760
- const current = deps.resolveActiveSnap();
761
- if (current && !deps.isDragging()) {
762
- deps.applySize(Math.min(current.size, max));
763
- if (wasAnimating) deps.resyncAfterCancel?.();
764
- } else {
765
- deps.applySize(deps.getSize());
766
- }
767
- });
768
- };
769
- vv.addEventListener("resize", onResize);
770
- return () => {
771
- vv.removeEventListener("resize", onResize);
772
- if (rafId !== null) cancelAnimationFrame(rafId);
773
- };
774
- }
775
-
776
- // src/core/features/resize-observer.ts
777
- function installResizeObserver(deps) {
778
- if (typeof window === "undefined") {
779
- return () => {
780
- };
781
- }
782
- const onResize = () => {
783
- if (deps.isDestroyed()) return;
784
- deps.recomputeSnaps();
785
- const mode = deps.getMode();
786
- const isVerticalAxis = mode === "bottom" || mode === "top";
787
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
788
- const max = deps.getMaxAxisSize();
789
- if (viewportSize2 > 0 && viewportSize2 < max) {
790
- deps.setMaxAxisSize(viewportSize2);
791
- }
792
- const wasAnimating = deps.isAnimating?.() ?? false;
793
- deps.cancelInFlight();
794
- deps.newCycle();
795
- const current = deps.resolveActiveSnap();
796
- const newMax = deps.getMaxAxisSize();
797
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
798
- deps.setSize(targetSize);
799
- if (current && !deps.isDragging()) {
800
- deps.applySize(targetSize);
801
- if (wasAnimating) deps.resyncAfterCancel?.();
802
- }
803
- };
804
- let resizeObserver = null;
805
- if (typeof ResizeObserver !== "undefined") {
806
- resizeObserver = new ResizeObserver(onResize);
807
- resizeObserver.observe(document.documentElement);
808
- } else {
809
- window.addEventListener("resize", onResize);
810
- }
811
- window.addEventListener("orientationchange", onResize);
812
- const onVisibility = () => {
813
- if (deps.isDestroyed()) return;
814
- if (document.hidden) deps.cancelInFlight();
815
- };
816
- document.addEventListener("visibilitychange", onVisibility);
817
- return () => {
818
- resizeObserver?.disconnect();
819
- window.removeEventListener("resize", onResize);
820
- window.removeEventListener("orientationchange", onResize);
821
- document.removeEventListener("visibilitychange", onVisibility);
822
- };
823
- }
824
-
825
- // src/core/features/fit-observer.ts
826
- var contentTargets = (scroller) => {
827
- if (!scroller) return [];
828
- const hasSlot = typeof HTMLSlotElement !== "undefined";
829
- const out = [];
830
- for (const child of Array.from(scroller.children)) {
831
- if (hasSlot && child instanceof HTMLSlotElement) {
832
- for (const el of child.assignedElements()) out.push(el);
833
- } else {
834
- out.push(child);
431
+
432
+ // src/core/features/fit-observer.ts
433
+ var contentTargets = (scroller) => {
434
+ if (!scroller) return [];
435
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
436
+ const out = [];
437
+ for (const child of Array.from(scroller.children)) {
438
+ if (hasSlot && child instanceof HTMLSlotElement) {
439
+ for (const el of child.assignedElements()) out.push(el);
440
+ } else {
441
+ out.push(child);
835
442
  }
836
443
  }
837
444
  return out;
@@ -951,11 +558,15 @@ function createMaxHeightController(deps) {
951
558
 
952
559
  // src/core/features/fit-measurement.ts
953
560
  var measureSheetNatural = (element, content, vertical) => {
954
- if (!content) {
955
- return vertical ? element.offsetHeight : element.offsetWidth;
956
- }
957
561
  const axis = vertical ? "height" : "width";
958
562
  const sheetStyle = element.style;
563
+ if (!content) {
564
+ const prev = sheetStyle[axis];
565
+ sheetStyle[axis] = "auto";
566
+ const poked2 = vertical ? element.offsetHeight : element.offsetWidth;
567
+ sheetStyle[axis] = prev;
568
+ return poked2;
569
+ }
959
570
  const cs = content.style;
960
571
  const prevSheet = sheetStyle[axis];
961
572
  const prevFlex = cs.flex;
@@ -1669,6 +1280,52 @@ function resolveAnimationPreset(animation) {
1669
1280
  }
1670
1281
  }
1671
1282
 
1283
+ // src/core/animation/waapi-settle.ts
1284
+ var SIM_HZ = 240;
1285
+ var SAMPLE_STRIDE = 4;
1286
+ var MAX_SIM_MS = 3e3;
1287
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1288
+ const cfg = { ...DEFAULT_SPRING, ...config };
1289
+ const subDt = 1 / SIM_HZ;
1290
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1291
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1292
+ const values = [from];
1293
+ let x = from;
1294
+ let v = velocityPxPerS;
1295
+ for (let i = 1; i <= maxSteps; i += 1) {
1296
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1297
+ v += force / cfg.mass * subDt;
1298
+ x += v * subDt;
1299
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1300
+ values.push(to);
1301
+ break;
1302
+ }
1303
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1304
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1305
+ if (atRest) break;
1306
+ }
1307
+ if (values[values.length - 1] !== to) values.push(to);
1308
+ return {
1309
+ values,
1310
+ stepMs,
1311
+ durationMs: (values.length - 1) * stepMs
1312
+ };
1313
+ }
1314
+ function sampleTweenSettle(from, to, durationMs, easing) {
1315
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1316
+ const values = [];
1317
+ for (let i = 0; i <= count; i += 1) {
1318
+ const t = i / count;
1319
+ values.push(from + (to - from) * easing(t));
1320
+ }
1321
+ values[values.length - 1] = to;
1322
+ return {
1323
+ values,
1324
+ stepMs: durationMs / count,
1325
+ durationMs
1326
+ };
1327
+ }
1328
+
1672
1329
  // src/core/controllers/animation-runner.ts
1673
1330
  var DEFAULT_DURATION2 = 220;
1674
1331
  var VELOCITY_PX_PER_S = 1e3;
@@ -1679,6 +1336,10 @@ var AnimationRunner = class {
1679
1336
  __publicField(this, "applySizeFn");
1680
1337
  __publicField(this, "getSize");
1681
1338
  __publicField(this, "isDragging");
1339
+ __publicField(this, "applyAux");
1340
+ __publicField(this, "getTransformFor");
1341
+ __publicField(this, "settleWaapi");
1342
+ __publicField(this, "currentWaapi", null);
1682
1343
  __publicField(this, "animationKind");
1683
1344
  __publicField(this, "springConfig");
1684
1345
  __publicField(this, "duration");
@@ -1694,6 +1355,9 @@ var AnimationRunner = class {
1694
1355
  this.applySizeFn = deps.applySize;
1695
1356
  this.getSize = deps.getSize;
1696
1357
  this.isDragging = deps.isDragging;
1358
+ this.applyAux = deps.applyAux;
1359
+ this.getTransformFor = deps.getTransformFor;
1360
+ this.settleWaapi = opts.settleAnimation === "waapi";
1697
1361
  const preset = resolveAnimationPreset(opts.animation);
1698
1362
  this.animationKind = preset.kind;
1699
1363
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1718,11 +1382,20 @@ var AnimationRunner = class {
1718
1382
  }
1719
1383
  }
1720
1384
  get isAnimating() {
1721
- return this.currentTween !== null || this.currentSpring !== null;
1385
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1722
1386
  }
1723
1387
  cancel() {
1724
1388
  this.currentTween?.cancel();
1725
1389
  this.currentSpring?.cancel();
1390
+ if (this.currentWaapi) {
1391
+ const entry = this.currentWaapi;
1392
+ this.currentWaapi = null;
1393
+ entry.stop();
1394
+ try {
1395
+ entry.anim.cancel();
1396
+ } catch {
1397
+ }
1398
+ }
1726
1399
  }
1727
1400
  async animateTo(target, velocityPxPerMs) {
1728
1401
  if (target === this.getSize()) {
@@ -1741,7 +1414,28 @@ var AnimationRunner = class {
1741
1414
  if (!this.isDragging()) this.element.style.willChange = "";
1742
1415
  return;
1743
1416
  }
1744
- if (this.animationKind === "spring") {
1417
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1418
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1419
+ this.getSize(),
1420
+ target,
1421
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1422
+ this.springConfig
1423
+ ) : sampleTweenSettle(
1424
+ this.getSize(),
1425
+ target,
1426
+ this.duration,
1427
+ this.easing
1428
+ );
1429
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1430
+ await this.runWaapiSettle(samples, target);
1431
+ if (!this.isAnimating) {
1432
+ this.getRootEl()?.removeAttribute("data-animating");
1433
+ if (!this.isDragging()) this.element.style.willChange = "";
1434
+ }
1435
+ return;
1436
+ }
1437
+ }
1438
+ if (this.animationKind === "spring") {
1745
1439
  const spring = runSpring({
1746
1440
  from: this.getSize(),
1747
1441
  to: target,
@@ -1769,11 +1463,66 @@ var AnimationRunner = class {
1769
1463
  if (!this.isDragging()) this.element.style.willChange = "";
1770
1464
  }
1771
1465
  }
1466
+ async runWaapiSettle(samples, target) {
1467
+ const applyAux = this.applyAux;
1468
+ const getTransformFor = this.getTransformFor;
1469
+ const frames = samples.values.map((v) => ({
1470
+ transform: getTransformFor(v)
1471
+ }));
1472
+ const anim = this.element.animate(frames, {
1473
+ duration: samples.durationMs,
1474
+ easing: "linear",
1475
+ fill: "forwards"
1476
+ });
1477
+ let stopped = false;
1478
+ let auxRaf = 0;
1479
+ const start = performance.now();
1480
+ const lastIdx = samples.values.length - 1;
1481
+ const auxTick = () => {
1482
+ if (stopped) return;
1483
+ const elapsed = performance.now() - start;
1484
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1485
+ applyAux(samples.values[idx]);
1486
+ auxRaf = requestAnimationFrame(auxTick);
1487
+ };
1488
+ auxRaf = requestAnimationFrame(auxTick);
1489
+ const entry = {
1490
+ anim,
1491
+ stop: () => {
1492
+ stopped = true;
1493
+ cancelAnimationFrame(auxRaf);
1494
+ }
1495
+ };
1496
+ this.currentWaapi = entry;
1497
+ try {
1498
+ await anim.finished;
1499
+ } catch {
1500
+ }
1501
+ entry.stop();
1502
+ const wasCurrent = this.currentWaapi === entry;
1503
+ if (wasCurrent) this.currentWaapi = null;
1504
+ if (wasCurrent && anim.playState === "finished") {
1505
+ this.applySizeFn(target);
1506
+ try {
1507
+ anim.cancel();
1508
+ } catch {
1509
+ }
1510
+ }
1511
+ }
1772
1512
  destroy() {
1773
1513
  this.detachReducedMotion?.();
1774
1514
  this.detachReducedMotion = null;
1775
1515
  this.currentTween = null;
1776
1516
  this.currentSpring = null;
1517
+ if (this.currentWaapi) {
1518
+ const entry = this.currentWaapi;
1519
+ this.currentWaapi = null;
1520
+ entry.stop();
1521
+ try {
1522
+ entry.anim.cancel();
1523
+ } catch {
1524
+ }
1525
+ }
1777
1526
  }
1778
1527
  };
1779
1528
 
@@ -2630,7 +2379,7 @@ function resolveEngineOptions(opts, extras) {
2630
2379
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2631
2380
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2632
2381
  if (opts.persistKey) {
2633
- const restored = readPersistedId(opts.persistKey);
2382
+ const restored = readPersistedIdSafe(opts.persistKey);
2634
2383
  if (restored && initialAllowed.includes(restored)) {
2635
2384
  initialId = restored;
2636
2385
  }
@@ -2661,6 +2410,7 @@ function resolveEngineOptions(opts, extras) {
2661
2410
  },
2662
2411
  animation: {
2663
2412
  animation: opts.animation,
2413
+ settleAnimation: opts.settleAnimation,
2664
2414
  duration: opts.duration,
2665
2415
  easing: opts.easing,
2666
2416
  spring: opts.spring,
@@ -2678,6 +2428,15 @@ function resolveEngineOptions(opts, extras) {
2678
2428
  }
2679
2429
  };
2680
2430
  }
2431
+ var readPersistedIdSafe = (key) => {
2432
+ if (typeof window === "undefined") return null;
2433
+ if (typeof localStorage === "undefined") return null;
2434
+ try {
2435
+ return localStorage.getItem(key);
2436
+ } catch {
2437
+ return null;
2438
+ }
2439
+ };
2681
2440
 
2682
2441
  // src/core/primitives/teardown-stack.ts
2683
2442
  var TeardownStack = class {
@@ -2700,9 +2459,9 @@ var TeardownStack = class {
2700
2459
  }
2701
2460
  };
2702
2461
 
2703
- // src/core/BottomSheetEngine.ts
2462
+ // src/core/BottomSheetCore.ts
2704
2463
  var HAPTIC_DURATION_MS = 8;
2705
- var BottomSheetEngine = class {
2464
+ var BottomSheetCore = class {
2706
2465
  constructor(opts) {
2707
2466
  __publicField(this, "id", nextInstanceId("bs"));
2708
2467
  __publicField(this, "element");
@@ -2763,6 +2522,8 @@ var BottomSheetEngine = class {
2763
2522
  __publicField(this, "stackEffectPrimed", false);
2764
2523
  __publicField(this, "fitDeps");
2765
2524
  __publicField(this, "transformTemplate");
2525
+ __publicField(this, "featureList", []);
2526
+ __publicField(this, "featureCtx");
2766
2527
  const resolved = resolveEngineOptions(opts, {
2767
2528
  shouldApplyInertSiblings: () => {
2768
2529
  if (typeof document === "undefined") return false;
@@ -2809,7 +2570,9 @@ var BottomSheetEngine = class {
2809
2570
  getRootEl: () => this.rootEl,
2810
2571
  applySize: (size) => this.applySize(size),
2811
2572
  getSize: () => this.size,
2812
- isDragging: () => this.gesture?.isDragging ?? false
2573
+ isDragging: () => this.gesture?.isDragging ?? false,
2574
+ applyAux: (size) => this.applySize(size, true),
2575
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2813
2576
  },
2814
2577
  resolved.animation
2815
2578
  );
@@ -2832,6 +2595,11 @@ var BottomSheetEngine = class {
2832
2595
  if (opts.linkedSheets) {
2833
2596
  this.linkedSheets = opts.linkedSheets;
2834
2597
  }
2598
+ const byName = /* @__PURE__ */ new Map();
2599
+ for (const feature of opts.features ?? []) {
2600
+ byName.set(feature.name, feature);
2601
+ }
2602
+ this.featureList = Array.from(byName.values());
2835
2603
  this.activeId = resolved.initialId;
2836
2604
  auditVhUsage(this.snapPointsRaw);
2837
2605
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2868,6 +2636,7 @@ var BottomSheetEngine = class {
2868
2636
  scrollContainer: this.scrollContainer,
2869
2637
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2870
2638
  });
2639
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2871
2640
  this.attach();
2872
2641
  this.registerInStack();
2873
2642
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2876,12 +2645,7 @@ var BottomSheetEngine = class {
2876
2645
  if (!entry.isIntersecting) continue;
2877
2646
  if (this.destroyed) return;
2878
2647
  this.snaps.recompute();
2879
- const current = this.snaps.findById(this.activeId);
2880
- if (current) {
2881
- this.size = current.size;
2882
- this.applySize(this.size);
2883
- this.updateAriaSlider();
2884
- }
2648
+ this.healActiveSnap();
2885
2649
  io.disconnect();
2886
2650
  return;
2887
2651
  }
@@ -2904,46 +2668,77 @@ var BottomSheetEngine = class {
2904
2668
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2905
2669
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2906
2670
  this.applyAutoAriaLabelledBy();
2907
- if (this.closeOnRouteChange) {
2908
- this.teardowns.add(
2909
- installRouteChange({
2910
- isDestroyed: () => this.destroyed,
2911
- getSize: () => this.size,
2912
- close: () => this.close()
2913
- })
2914
- );
2915
- }
2916
- if (this.persistKey) {
2917
- this.teardowns.add(installPersist(this, this.persistKey));
2671
+ this.runFeatureStage("post");
2672
+ this.warnOrphanFeatureOptions();
2673
+ if (this.size > 0) {
2674
+ this.handleOpen();
2918
2675
  }
2919
- this.teardowns.add(
2920
- installAutoCollapse({
2921
- ms: opts.autoCollapseAfter,
2922
- isDestroyed: () => this.destroyed,
2923
- isDragging: () => this.gesture?.isDragging ?? false,
2924
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2925
- getActiveId: () => this.activeId,
2926
- resolveSnap: (id) => this.snaps.findById(id),
2927
- snapTo: (id) => {
2928
- void this.snapTo(id);
2929
- },
2930
- on: (event, fn) => this.on(event, fn)
2931
- })
2932
- );
2933
- this.teardowns.add(
2934
- installRoute({
2676
+ }
2677
+ buildFeatureContext(autoCollapseAfter) {
2678
+ return {
2679
+ element: this.element,
2680
+ scrollContainer: this.scrollContainer,
2681
+ sheetId: this.id,
2682
+ options: {
2935
2683
  routedTo: this.routedTo,
2936
2684
  closeOnBack: this.closeOnBack,
2937
- isTopSheet: () => this.isTopSheet,
2938
- getSize: () => this.size,
2939
- isDestroyed: () => this.destroyed,
2940
- close: () => this.close("back"),
2941
- on: (event, fn) => this.on(event, fn),
2942
- sheetId: this.id
2943
- })
2944
- );
2945
- if (this.size > 0) {
2946
- this.handleOpen();
2685
+ closeOnRouteChange: this.closeOnRouteChange,
2686
+ persistKey: this.persistKey,
2687
+ autoCollapseAfter
2688
+ },
2689
+ isDestroyed: () => this.destroyed,
2690
+ isDragging: () => this.gesture?.isDragging ?? false,
2691
+ isAnimating: () => this.animation.isAnimating,
2692
+ isTopSheet: () => this.isTopSheet,
2693
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2694
+ getSize: () => this.size,
2695
+ getActiveId: () => this.activeId,
2696
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2697
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2698
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2699
+ resolveSnap: (id) => this.snaps.findById(id),
2700
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2701
+ setSize: (size) => {
2702
+ this.size = size;
2703
+ },
2704
+ setMaxAxisSize: (size) => {
2705
+ this.snaps.setMaxAxisSize(size);
2706
+ },
2707
+ applySize: (size) => this.applySize(size),
2708
+ recomputeSnaps: () => this.recomputeSnaps(),
2709
+ newCycle: () => {
2710
+ this.newCycle();
2711
+ },
2712
+ cancelInFlight: () => {
2713
+ this.animation.cancel();
2714
+ },
2715
+ resyncAfterResize: () => this.resyncAfterResize(),
2716
+ snapTo: (id) => {
2717
+ void this.snapTo(id);
2718
+ },
2719
+ close: (reason) => this.close(reason),
2720
+ on: (event, fn) => this.on(event, fn),
2721
+ addTeardown: (fn) => this.teardowns.add(fn)
2722
+ };
2723
+ }
2724
+ runFeatureStage(stage) {
2725
+ for (const feature of this.featureList) {
2726
+ if ((feature.stage ?? "post") !== stage) continue;
2727
+ const teardown = feature.install(this.featureCtx);
2728
+ if (teardown) this.teardowns.add(teardown);
2729
+ }
2730
+ }
2731
+ warnOrphanFeatureOptions() {
2732
+ const names = new Set(this.featureList.map((f) => f.name));
2733
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2734
+ devWarn(
2735
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2736
+ );
2737
+ }
2738
+ if (this.persistKey && !names.has("persist")) {
2739
+ devWarn(
2740
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2741
+ );
2947
2742
  }
2948
2743
  }
2949
2744
  get state() {
@@ -3045,6 +2840,8 @@ var BottomSheetEngine = class {
3045
2840
  const vt = document.startViewTransition(() => {
3046
2841
  this.applySize(target.size);
3047
2842
  });
2843
+ vt.ready?.catch(() => {
2844
+ });
3048
2845
  this.currentViewTransition = vt;
3049
2846
  try {
3050
2847
  await vt.finished;
@@ -3296,11 +3093,16 @@ var BottomSheetEngine = class {
3296
3093
  this.maxHeight.clampTo();
3297
3094
  this.scrim.invalidateOpacityCache();
3298
3095
  if (this.gesture?.isDragging) return;
3096
+ this.healActiveSnap();
3097
+ }
3098
+ healActiveSnap() {
3299
3099
  const current = this.snaps.findById(this.activeId);
3300
- if (current) {
3301
- this.size = current.size;
3302
- this.applySize(this.size);
3303
- this.updateAriaSlider();
3100
+ if (!current) return;
3101
+ this.size = current.size;
3102
+ this.applySize(this.size);
3103
+ this.updateAriaSlider();
3104
+ if (this.size > 0 && !this.lifecycle.isInstalled && !this.opening && !this.animation.isAnimating) {
3105
+ this.emitOpenSequence(this.activeId);
3304
3106
  }
3305
3107
  }
3306
3108
  destroy() {
@@ -3479,18 +3281,7 @@ var BottomSheetEngine = class {
3479
3281
  }
3480
3282
  }
3481
3283
  attachFeatures() {
3482
- if (this.scrollContainer) {
3483
- this.teardowns.add(installContentSwipe({
3484
- container: this.scrollContainer,
3485
- isDragging: () => this.gesture?.isDragging ?? false,
3486
- isAnimating: () => this.animation.isAnimating,
3487
- getAllowedIds: () => this.allowedIdsBySize(),
3488
- getActiveId: () => this.activeId,
3489
- snapTo: (id) => {
3490
- void this.snapTo(id);
3491
- }
3492
- }));
3493
- }
3284
+ this.runFeatureStage("attach");
3494
3285
  if (typeof window !== "undefined") {
3495
3286
  this.teardowns.add(installResizeObserver({
3496
3287
  element: this.element,
@@ -3530,31 +3321,6 @@ var BottomSheetEngine = class {
3530
3321
  () => document.removeEventListener("keydown", onKey)
3531
3322
  );
3532
3323
  }
3533
- this.teardowns.add(installVisualViewport({
3534
- element: this.element,
3535
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3536
- isDestroyed: () => this.destroyed,
3537
- isDragging: () => this.gesture?.isDragging ?? false,
3538
- recomputeSnaps: () => this.recomputeSnaps(),
3539
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3540
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3541
- getSize: () => this.size,
3542
- setMaxAxisSize: (size) => {
3543
- this.snaps.setMaxAxisSize(size);
3544
- },
3545
- setSize: (size) => {
3546
- this.size = size;
3547
- },
3548
- applySize: (size) => this.applySize(size),
3549
- cancelInFlight: () => {
3550
- this.animation.cancel();
3551
- },
3552
- newCycle: () => {
3553
- this.newCycle();
3554
- },
3555
- isAnimating: () => this.animation.isAnimating,
3556
- resyncAfterCancel: () => this.resyncAfterResize()
3557
- }));
3558
3324
  }
3559
3325
  this.teardowns.add(installSliderKeyboard({
3560
3326
  handle: this.handle,
@@ -3631,13 +3397,13 @@ var BottomSheetEngine = class {
3631
3397
  }
3632
3398
  }
3633
3399
  }
3634
- applySize(size) {
3400
+ applySize(size, skipTransform = false) {
3635
3401
  const cap = this.snaps.getMaxAxisSize();
3636
3402
  const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3637
3403
  this.size = clamped;
3638
3404
  const offset = cap - clamped;
3639
3405
  const style = this.element.style;
3640
- style.transform = this.transformTemplate(offset);
3406
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3641
3407
  if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3642
3408
  style.setProperty("--bs-size", `${clamped}px`);
3643
3409
  if (this.scrimParent) {
@@ -3714,6 +3480,514 @@ var BottomSheetEngine = class {
3714
3480
  sheetStack.update();
3715
3481
  }
3716
3482
  };
3483
+
3484
+ // src/core/features/history-coordinator.ts
3485
+ var entries = [];
3486
+ var subscribers = /* @__PURE__ */ new Set();
3487
+ var suppress = 0;
3488
+ var internalDepth = 0;
3489
+ var pendingRebrands = [];
3490
+ var listenerInstalled = false;
3491
+ var patchInstalled = false;
3492
+ var origPushState = null;
3493
+ var origReplaceState = null;
3494
+ var patchedPushState = null;
3495
+ var patchedReplaceState = null;
3496
+ var hasWindow = () => typeof window !== "undefined";
3497
+ var hasHistory = () => typeof history !== "undefined";
3498
+ var brandMarker = (surface) => ({
3499
+ ...surface.markerState(),
3500
+ __bs: true
3501
+ });
3502
+ var runInternal = (fn) => {
3503
+ internalDepth += 1;
3504
+ try {
3505
+ fn();
3506
+ } finally {
3507
+ internalDepth -= 1;
3508
+ }
3509
+ };
3510
+ var notifyRouteChange = () => {
3511
+ const snapshot = Array.from(subscribers);
3512
+ for (const fn of snapshot) fn();
3513
+ };
3514
+ var ensureListener = () => {
3515
+ if (listenerInstalled || !hasWindow()) return;
3516
+ window.addEventListener("popstate", onPopState);
3517
+ listenerInstalled = true;
3518
+ };
3519
+ var maybeTeardownListener = () => {
3520
+ if (!listenerInstalled) return;
3521
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3522
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3523
+ listenerInstalled = false;
3524
+ };
3525
+ var installPatch = () => {
3526
+ if (patchInstalled || !hasHistory()) return;
3527
+ origPushState = history.pushState;
3528
+ origReplaceState = history.replaceState;
3529
+ patchedPushState = function(...args) {
3530
+ origPushState.apply(this, args);
3531
+ if (internalDepth === 0) notifyRouteChange();
3532
+ };
3533
+ patchedReplaceState = function(...args) {
3534
+ origReplaceState.apply(this, args);
3535
+ if (internalDepth === 0) notifyRouteChange();
3536
+ };
3537
+ history.pushState = patchedPushState;
3538
+ history.replaceState = patchedReplaceState;
3539
+ patchInstalled = true;
3540
+ };
3541
+ var removePatch = () => {
3542
+ if (!patchInstalled) return;
3543
+ if (history.pushState === patchedPushState && origPushState) {
3544
+ history.pushState = origPushState;
3545
+ }
3546
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3547
+ history.replaceState = origReplaceState;
3548
+ }
3549
+ origPushState = null;
3550
+ origReplaceState = null;
3551
+ patchedPushState = null;
3552
+ patchedReplaceState = null;
3553
+ patchInstalled = false;
3554
+ };
3555
+ function onPopState() {
3556
+ if (suppress > 0) {
3557
+ suppress -= 1;
3558
+ const rebrand = pendingRebrands.shift();
3559
+ if (rebrand) rebrand();
3560
+ maybeTeardownListener();
3561
+ return;
3562
+ }
3563
+ if (entries.length === 0) {
3564
+ notifyRouteChange();
3565
+ maybeTeardownListener();
3566
+ return;
3567
+ }
3568
+ let target = null;
3569
+ let targetIdx = -1;
3570
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3571
+ const entry = entries[i];
3572
+ if (!entry.surface.isOpen()) continue;
3573
+ if (!target) {
3574
+ target = entry;
3575
+ targetIdx = i;
3576
+ }
3577
+ if (entry.surface.isTop()) {
3578
+ target = entry;
3579
+ targetIdx = i;
3580
+ break;
3581
+ }
3582
+ }
3583
+ if (!target) {
3584
+ const removed = entries.pop();
3585
+ if (removed) removed.live = false;
3586
+ maybeTeardownListener();
3587
+ return;
3588
+ }
3589
+ target.live = false;
3590
+ entries.splice(targetIdx, 1);
3591
+ const settled = target;
3592
+ void Promise.resolve(settled.surface.close()).then(() => {
3593
+ if (settled.surface.isOpen()) restore(settled);
3594
+ });
3595
+ maybeTeardownListener();
3596
+ }
3597
+ function restore(entry) {
3598
+ if (!hasWindow() || !hasHistory()) return;
3599
+ entry.live = true;
3600
+ runInternal(() => {
3601
+ try {
3602
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3603
+ } catch {
3604
+ entry.live = false;
3605
+ }
3606
+ });
3607
+ if (entry.live) {
3608
+ entries.push(entry);
3609
+ ensureListener();
3610
+ }
3611
+ }
3612
+ function pushBackMarker(surface) {
3613
+ const entry = { surface, priorUrl: void 0, live: false };
3614
+ if (!hasWindow() || !hasHistory()) {
3615
+ return entry;
3616
+ }
3617
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3618
+ runInternal(() => {
3619
+ try {
3620
+ history.pushState(brandMarker(surface), "", surface.url);
3621
+ entry.live = true;
3622
+ } catch {
3623
+ entry.live = false;
3624
+ }
3625
+ });
3626
+ if (entry.live) {
3627
+ entries.push(entry);
3628
+ ensureListener();
3629
+ }
3630
+ return entry;
3631
+ }
3632
+ function popBackMarker(handle) {
3633
+ if (!hasWindow() || !hasHistory()) return;
3634
+ const entry = handle;
3635
+ const idx = entries.indexOf(entry);
3636
+ if (idx === -1 || !entry.live) return;
3637
+ const last = entries.length - 1;
3638
+ entry.live = false;
3639
+ entries.splice(idx, 1);
3640
+ let rebrand = null;
3641
+ if (idx !== last) {
3642
+ const rebrandSurface = entries[entries.length - 1].surface;
3643
+ const rebrandUrl = entry.priorUrl;
3644
+ rebrand = () => {
3645
+ runInternal(() => {
3646
+ history.replaceState(
3647
+ brandMarker(rebrandSurface),
3648
+ "",
3649
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3650
+ );
3651
+ });
3652
+ };
3653
+ }
3654
+ pendingRebrands.push(rebrand);
3655
+ suppress += 1;
3656
+ runInternal(() => {
3657
+ try {
3658
+ history.back();
3659
+ } catch {
3660
+ suppress = Math.max(0, suppress - 1);
3661
+ pendingRebrands.pop();
3662
+ }
3663
+ });
3664
+ maybeTeardownListener();
3665
+ }
3666
+ function subscribeRouteChange(fn) {
3667
+ if (!hasWindow() || !hasHistory()) return () => {
3668
+ };
3669
+ subscribers.add(fn);
3670
+ if (subscribers.size === 1) installPatch();
3671
+ ensureListener();
3672
+ return () => {
3673
+ if (!subscribers.has(fn)) return;
3674
+ subscribers.delete(fn);
3675
+ if (subscribers.size === 0) removePatch();
3676
+ maybeTeardownListener();
3677
+ };
3678
+ }
3679
+
3680
+ // src/core/features/route.ts
3681
+ function installRoute(deps) {
3682
+ if (typeof window === "undefined") {
3683
+ return () => {
3684
+ };
3685
+ }
3686
+ if (!deps.routedTo && !deps.closeOnBack) {
3687
+ return () => {
3688
+ };
3689
+ }
3690
+ let handle = null;
3691
+ const onOpen = () => {
3692
+ if (handle) return;
3693
+ handle = pushBackMarker({
3694
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3695
+ isTop: deps.isTopSheet,
3696
+ close: deps.close,
3697
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3698
+ url: deps.routedTo
3699
+ });
3700
+ };
3701
+ const onClose = () => {
3702
+ if (!handle) return;
3703
+ popBackMarker(handle);
3704
+ handle = null;
3705
+ };
3706
+ const unsubscribeOpen = deps.on("open", onOpen);
3707
+ const unsubscribeClose = deps.on("close", onClose);
3708
+ if (deps.getSize() > 0) onOpen();
3709
+ return () => {
3710
+ unsubscribeOpen();
3711
+ unsubscribeClose();
3712
+ if (handle) {
3713
+ popBackMarker(handle);
3714
+ handle = null;
3715
+ }
3716
+ };
3717
+ }
3718
+ function installRouteChange(deps) {
3719
+ return subscribeRouteChange(() => {
3720
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3721
+ void deps.close();
3722
+ });
3723
+ }
3724
+
3725
+ // src/core/features/persist.ts
3726
+ function installPersist(engine, key) {
3727
+ const unsubscribe = engine.on("snap", ({ id }) => {
3728
+ if (typeof window === "undefined") return;
3729
+ if (typeof localStorage === "undefined") return;
3730
+ try {
3731
+ localStorage.setItem(key, id);
3732
+ } catch {
3733
+ }
3734
+ });
3735
+ return unsubscribe;
3736
+ }
3737
+
3738
+ // src/core/features/auto-collapse.ts
3739
+ function installAutoCollapse(deps) {
3740
+ const { ms } = deps;
3741
+ if (ms === void 0 || ms <= 0) {
3742
+ return () => {
3743
+ };
3744
+ }
3745
+ let timer = null;
3746
+ const clearTimer = () => {
3747
+ if (timer !== null) {
3748
+ clearTimeout(timer);
3749
+ timer = null;
3750
+ }
3751
+ };
3752
+ const fire = () => {
3753
+ if (deps.isDestroyed()) return;
3754
+ if (deps.isDragging()) return;
3755
+ const active = deps.resolveSnap(deps.getActiveId());
3756
+ if (!active || active.size === 0) return;
3757
+ let target = null;
3758
+ for (const id of deps.getAllowedIds()) {
3759
+ const snap = deps.resolveSnap(id);
3760
+ if (!snap || snap.size <= 0) continue;
3761
+ if (!target || snap.size < target.size) target = snap;
3762
+ }
3763
+ if (!target) return;
3764
+ if (target.id === active.id || target.size >= active.size) return;
3765
+ deps.snapTo(target.id);
3766
+ };
3767
+ const reset = () => {
3768
+ clearTimer();
3769
+ if (deps.isDestroyed()) return;
3770
+ timer = setTimeout(() => {
3771
+ timer = null;
3772
+ fire();
3773
+ }, ms);
3774
+ };
3775
+ const unsubscribe = deps.on("snap", () => reset());
3776
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3777
+ return () => {
3778
+ clearTimer();
3779
+ unsubscribe();
3780
+ };
3781
+ }
3782
+
3783
+ // src/core/features/content-swipe.ts
3784
+ var CONTENT_PULL_THRESHOLD = 60;
3785
+ function installContentSwipe(deps) {
3786
+ const { container } = deps;
3787
+ let startY = 0;
3788
+ let startScrollTop = 0;
3789
+ let moved = false;
3790
+ const onTouchStart = (e) => {
3791
+ const t = e.touches[0];
3792
+ if (!t) return;
3793
+ startY = t.clientY;
3794
+ startScrollTop = container.scrollTop;
3795
+ moved = false;
3796
+ };
3797
+ const onTouchMove = (e) => {
3798
+ const t = e.touches[0];
3799
+ if (!t) return;
3800
+ if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3801
+ };
3802
+ const onTouchEnd = (e) => {
3803
+ if (!moved) return;
3804
+ if (deps.isDragging() || deps.isAnimating()) return;
3805
+ if (container.scrollTop !== startScrollTop) return;
3806
+ const t = e.changedTouches[0];
3807
+ if (!t) return;
3808
+ const deltaY = t.clientY - startY;
3809
+ const atTop = container.scrollTop <= 0;
3810
+ const allowed = deps.getAllowedIds();
3811
+ const idx = allowed.indexOf(deps.getActiveId());
3812
+ if (idx === -1) return;
3813
+ if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3814
+ deps.snapTo(allowed[idx - 1]);
3815
+ } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3816
+ deps.snapTo(allowed[idx + 1]);
3817
+ }
3818
+ };
3819
+ container.addEventListener("touchstart", onTouchStart, { passive: true });
3820
+ container.addEventListener("touchmove", onTouchMove, { passive: true });
3821
+ container.addEventListener("touchend", onTouchEnd, { passive: true });
3822
+ return () => {
3823
+ container.removeEventListener("touchstart", onTouchStart);
3824
+ container.removeEventListener("touchmove", onTouchMove);
3825
+ container.removeEventListener("touchend", onTouchEnd);
3826
+ };
3827
+ }
3828
+
3829
+ // src/core/features/visual-viewport.ts
3830
+ var SAFETY_PAD = 8;
3831
+ function installVisualViewport(deps) {
3832
+ if (typeof window === "undefined" || !window.visualViewport) {
3833
+ return () => {
3834
+ };
3835
+ }
3836
+ const vv = window.visualViewport;
3837
+ let rafId = null;
3838
+ const onResize = () => {
3839
+ if (rafId !== null) return;
3840
+ rafId = requestAnimationFrame(() => {
3841
+ rafId = null;
3842
+ if (deps.isDestroyed()) return;
3843
+ deps.recomputeSnaps();
3844
+ const isVertical = deps.isVerticalAxis();
3845
+ const visible = isVertical ? vv.height : vv.width;
3846
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
3847
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
3848
+ if (clamp < deps.getMaxAxisSize()) {
3849
+ deps.setMaxAxisSize(clamp);
3850
+ }
3851
+ const wasAnimating = deps.isAnimating?.() ?? false;
3852
+ deps.cancelInFlight();
3853
+ deps.newCycle();
3854
+ const max = deps.getMaxAxisSize();
3855
+ deps.setSize(Math.min(deps.getSize(), max));
3856
+ const current = deps.resolveActiveSnap();
3857
+ if (current && !deps.isDragging()) {
3858
+ deps.applySize(Math.min(current.size, max));
3859
+ if (wasAnimating) deps.resyncAfterCancel?.();
3860
+ } else {
3861
+ deps.applySize(deps.getSize());
3862
+ }
3863
+ });
3864
+ };
3865
+ vv.addEventListener("resize", onResize);
3866
+ return () => {
3867
+ vv.removeEventListener("resize", onResize);
3868
+ if (rafId !== null) cancelAnimationFrame(rafId);
3869
+ };
3870
+ }
3871
+
3872
+ // src/core/features/engine-features.ts
3873
+ function routeFeature() {
3874
+ return {
3875
+ name: "route",
3876
+ install: (ctx) => {
3877
+ const teardowns = new TeardownStack();
3878
+ if (ctx.options.closeOnRouteChange) {
3879
+ teardowns.add(
3880
+ installRouteChange({
3881
+ isDestroyed: ctx.isDestroyed,
3882
+ getSize: ctx.getSize,
3883
+ close: () => ctx.close()
3884
+ })
3885
+ );
3886
+ }
3887
+ teardowns.add(
3888
+ installRoute({
3889
+ routedTo: ctx.options.routedTo,
3890
+ closeOnBack: ctx.options.closeOnBack,
3891
+ isTopSheet: ctx.isTopSheet,
3892
+ getSize: ctx.getSize,
3893
+ isDestroyed: ctx.isDestroyed,
3894
+ close: () => ctx.close("back"),
3895
+ on: ctx.on,
3896
+ sheetId: ctx.sheetId
3897
+ })
3898
+ );
3899
+ return () => teardowns.drain();
3900
+ }
3901
+ };
3902
+ }
3903
+ function persistFeature() {
3904
+ return {
3905
+ name: "persist",
3906
+ install: (ctx) => {
3907
+ const key = ctx.options.persistKey;
3908
+ if (!key) return;
3909
+ return installPersist({ on: ctx.on }, key);
3910
+ }
3911
+ };
3912
+ }
3913
+ function autoCollapseFeature() {
3914
+ return {
3915
+ name: "auto-collapse",
3916
+ install: (ctx) => installAutoCollapse({
3917
+ ms: ctx.options.autoCollapseAfter,
3918
+ isDestroyed: ctx.isDestroyed,
3919
+ isDragging: ctx.isDragging,
3920
+ getAllowedIds: ctx.getAllowedIds,
3921
+ getActiveId: ctx.getActiveId,
3922
+ resolveSnap: ctx.resolveSnap,
3923
+ snapTo: ctx.snapTo,
3924
+ on: ctx.on
3925
+ })
3926
+ };
3927
+ }
3928
+ function contentSwipeFeature() {
3929
+ return {
3930
+ name: "content-swipe",
3931
+ stage: "attach",
3932
+ install: (ctx) => {
3933
+ if (!ctx.scrollContainer) return;
3934
+ return installContentSwipe({
3935
+ container: ctx.scrollContainer,
3936
+ isDragging: ctx.isDragging,
3937
+ isAnimating: ctx.isAnimating,
3938
+ getAllowedIds: ctx.allowedIdsBySize,
3939
+ getActiveId: ctx.getActiveId,
3940
+ snapTo: ctx.snapTo
3941
+ });
3942
+ }
3943
+ };
3944
+ }
3945
+ function visualViewportFeature() {
3946
+ return {
3947
+ name: "visual-viewport",
3948
+ install: (ctx) => {
3949
+ if (typeof window === "undefined") return;
3950
+ return installVisualViewport({
3951
+ element: ctx.element,
3952
+ isVerticalAxis: ctx.isVerticalAxis,
3953
+ isDestroyed: ctx.isDestroyed,
3954
+ isDragging: ctx.isDragging,
3955
+ recomputeSnaps: ctx.recomputeSnaps,
3956
+ resolveActiveSnap: ctx.resolveActiveSnap,
3957
+ getMaxAxisSize: ctx.getMaxAxisSize,
3958
+ getSize: ctx.getSize,
3959
+ setMaxAxisSize: ctx.setMaxAxisSize,
3960
+ setSize: ctx.setSize,
3961
+ applySize: ctx.applySize,
3962
+ cancelInFlight: ctx.cancelInFlight,
3963
+ newCycle: ctx.newCycle,
3964
+ isAnimating: ctx.isAnimating,
3965
+ resyncAfterCancel: ctx.resyncAfterResize
3966
+ });
3967
+ }
3968
+ };
3969
+ }
3970
+
3971
+ // src/core/default-features.ts
3972
+ function defaultEngineFeatures() {
3973
+ return [
3974
+ contentSwipeFeature(),
3975
+ visualViewportFeature(),
3976
+ persistFeature(),
3977
+ autoCollapseFeature(),
3978
+ routeFeature()
3979
+ ];
3980
+ }
3981
+
3982
+ // src/core/BottomSheetEngine.ts
3983
+ var BottomSheetEngine = class extends BottomSheetCore {
3984
+ constructor(opts) {
3985
+ super({
3986
+ ...opts,
3987
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
3988
+ });
3989
+ }
3990
+ };
3717
3991
  function useEnginePropSync(engineRef, opts) {
3718
3992
  const optsRef = react.useRef(opts);
3719
3993
  optsRef.current = opts;