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