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