@surdeddd/bottom-sheet 0.12.0 → 0.14.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 (66) hide show
  1. package/README.md +85 -1
  2. package/dist/{BottomSheetEngine-D6oiEfZ4.d.cts → BottomSheetCore-Cp7dWy91.d.cts} +85 -5
  3. package/dist/{BottomSheetEngine-Dhy0X_e9.d.ts → BottomSheetCore-DJYwWOqt.d.ts} +85 -5
  4. package/dist/BottomSheetEngine-Lm7HIVHv.d.cts +7 -0
  5. package/dist/BottomSheetEngine-O5PbeKfe.d.ts +7 -0
  6. package/dist/core.cjs +3680 -0
  7. package/dist/core.d.cts +3 -0
  8. package/dist/core.d.ts +3 -0
  9. package/dist/core.js +3678 -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 +1314 -829
  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 +1314 -829
  19. package/dist/features.cjs +489 -0
  20. package/dist/features.d.cts +13 -0
  21. package/dist/features.d.ts +13 -0
  22. package/dist/features.js +482 -0
  23. package/dist/index-Bb5TRmyg.d.cts +9 -0
  24. package/dist/index-Cl9ebCP2.d.ts +9 -0
  25. package/dist/index.cjs +979 -1293
  26. package/dist/index.d.cts +43 -55
  27. package/dist/index.d.ts +43 -55
  28. package/dist/index.js +973 -1280
  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 +11 -10
  34. package/dist/overlay.js +11 -10
  35. package/dist/preact.d.cts +2 -1
  36. package/dist/preact.d.ts +2 -1
  37. package/dist/qwik-lib/index.qwik.cjs +4418 -0
  38. package/dist/qwik-lib/index.qwik.mjs +4418 -0
  39. package/dist/qwik.cjs +1089 -657
  40. package/dist/qwik.d.cts +4 -2
  41. package/dist/qwik.d.ts +4 -2
  42. package/dist/qwik.js +1089 -657
  43. package/dist/react.cjs +1100 -665
  44. package/dist/react.d.cts +3 -3
  45. package/dist/react.d.ts +3 -3
  46. package/dist/react.js +1100 -665
  47. package/dist/solid.cjs +1095 -657
  48. package/dist/solid.d.cts +90 -4
  49. package/dist/solid.d.ts +90 -4
  50. package/dist/solid.js +1095 -657
  51. package/dist/solid.source.jsx +1103 -657
  52. package/dist/styles.css +24 -4
  53. package/dist/svelte-core.cjs +981 -557
  54. package/dist/svelte-core.js +981 -557
  55. package/dist/svelte-src/BottomSheet.svelte +14 -0
  56. package/dist/svelte.cjs +1091 -657
  57. package/dist/svelte.d.cts +2 -0
  58. package/dist/svelte.d.ts +2 -0
  59. package/dist/svelte.js +1091 -657
  60. package/dist/vue.cjs +1105 -657
  61. package/dist/vue.js +1105 -657
  62. package/dist/webgl.cjs +583 -0
  63. package/dist/webgl.d.cts +17 -0
  64. package/dist/webgl.d.ts +17 -0
  65. package/dist/webgl.js +581 -0
  66. package/package.json +88 -12
@@ -4,7 +4,7 @@ var __defProp = Object.defineProperty;
4
4
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
5
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
6
 
7
- // src/core/lifecycle/sheetStack.ts
7
+ // src/core/lifecycle/sheet-stack.ts
8
8
  var BASE_Z = 100;
9
9
  var STEP = 10;
10
10
  var SheetStack = class {
@@ -129,7 +129,7 @@ function createEventBus() {
129
129
  };
130
130
  }
131
131
 
132
- // src/core/primitives/cssLength.ts
132
+ // src/core/primitives/css-length.ts
133
133
  var probe = null;
134
134
  var PROBE_ATTR = "data-bs-probe";
135
135
  var ensureProbe = (axis) => {
@@ -183,7 +183,7 @@ var resolveSnap = (point, mode, measureFit) => {
183
183
  return Math.max(0, el.getBoundingClientRect().width);
184
184
  };
185
185
 
186
- // src/core/primitives/devWarn.ts
186
+ // src/core/primitives/dev-warn.ts
187
187
  function devWarn(...args) {
188
188
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
189
189
  console.warn(...args);
@@ -348,74 +348,6 @@ var SnapResolver = class {
348
348
  }
349
349
  };
350
350
 
351
- // src/core/features/persist.ts
352
- function readPersistedId(key) {
353
- if (!key) return null;
354
- if (typeof window === "undefined") return null;
355
- if (typeof localStorage === "undefined") return null;
356
- try {
357
- return localStorage.getItem(key);
358
- } catch {
359
- return null;
360
- }
361
- }
362
- function installPersist(engine, key) {
363
- const unsubscribe = engine.on("snap", ({ id }) => {
364
- if (typeof window === "undefined") return;
365
- if (typeof localStorage === "undefined") return;
366
- try {
367
- localStorage.setItem(key, id);
368
- } catch {
369
- }
370
- });
371
- return unsubscribe;
372
- }
373
-
374
- // src/core/features/auto-collapse.ts
375
- function installAutoCollapse(deps) {
376
- const { ms } = deps;
377
- if (ms === void 0 || ms <= 0) {
378
- return () => {
379
- };
380
- }
381
- let timer = null;
382
- const clearTimer = () => {
383
- if (timer !== null) {
384
- clearTimeout(timer);
385
- timer = null;
386
- }
387
- };
388
- const fire = () => {
389
- if (deps.isDestroyed()) return;
390
- if (deps.isDragging()) return;
391
- const active = deps.resolveSnap(deps.getActiveId());
392
- if (!active || active.size === 0) return;
393
- let target = null;
394
- for (const id of deps.getAllowedIds()) {
395
- const snap = deps.resolveSnap(id);
396
- if (!snap || snap.size <= 0) continue;
397
- if (!target || snap.size < target.size) target = snap;
398
- }
399
- if (!target) return;
400
- if (target.id === active.id || target.size >= active.size) return;
401
- deps.snapTo(target.id);
402
- };
403
- const reset = () => {
404
- clearTimer();
405
- if (deps.isDestroyed()) return;
406
- timer = setTimeout(() => {
407
- timer = null;
408
- fire();
409
- }, ms);
410
- };
411
- const unsubscribe = deps.on("snap", () => reset());
412
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
413
- return () => {
414
- clearTimer();
415
- unsubscribe();
416
- };
417
- }
418
-
419
351
  // src/core/features/linked-sheets.ts
420
352
  function notifyLinkedSheets(sheets, self) {
421
353
  if (sheets.length === 0) return;
@@ -438,394 +370,65 @@ function notifyLinkedSheets(sheets, self) {
438
370
  }
439
371
  }
440
372
 
441
- // src/core/features/history-coordinator.ts
442
- var entries = [];
443
- var subscribers = /* @__PURE__ */ new Set();
444
- var suppress = 0;
445
- var internalDepth = 0;
446
- var pendingRebrand = null;
447
- var listenerInstalled = false;
448
- var patchInstalled = false;
449
- var origPushState = null;
450
- var origReplaceState = null;
451
- var patchedPushState = null;
452
- var patchedReplaceState = null;
453
- var hasWindow = () => typeof window !== "undefined";
454
- var hasHistory = () => typeof history !== "undefined";
455
- var brandMarker = (surface) => ({
456
- ...surface.markerState(),
457
- __bs: true
458
- });
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;
373
+ // src/core/features/resize-observer.ts
374
+ function installResizeObserver(deps) {
375
+ if (typeof window === "undefined") {
376
+ return () => {
377
+ };
525
378
  }
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;
379
+ const onResize = () => {
380
+ if (deps.isDestroyed()) return;
381
+ deps.recomputeSnaps();
382
+ const mode = deps.getMode();
383
+ const isVerticalAxis = mode === "bottom" || mode === "top";
384
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
385
+ const max = deps.getMaxAxisSize();
386
+ if (viewportSize2 > 0 && viewportSize2 < max) {
387
+ deps.setMaxAxisSize(viewportSize2);
534
388
  }
535
- if (entry.surface.isTop()) {
536
- target = entry;
537
- targetIdx = i;
538
- break;
389
+ const wasAnimating = deps.isAnimating?.() ?? false;
390
+ deps.cancelInFlight();
391
+ deps.newCycle();
392
+ const current = deps.resolveActiveSnap();
393
+ const newMax = deps.getMaxAxisSize();
394
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
395
+ deps.setSize(targetSize);
396
+ if (current && !deps.isDragging()) {
397
+ deps.applySize(targetSize);
398
+ if (wasAnimating) deps.resyncAfterCancel?.();
539
399
  }
400
+ };
401
+ let resizeObserver = null;
402
+ if (typeof ResizeObserver !== "undefined") {
403
+ resizeObserver = new ResizeObserver(onResize);
404
+ resizeObserver.observe(document.documentElement);
405
+ } else {
406
+ window.addEventListener("resize", onResize);
540
407
  }
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();
408
+ window.addEventListener("orientationchange", onResize);
409
+ const onVisibility = () => {
410
+ if (deps.isDestroyed()) return;
411
+ if (document.hidden) deps.cancelInFlight();
412
+ };
413
+ document.addEventListener("visibilitychange", onVisibility);
414
+ return () => {
415
+ resizeObserver?.disconnect();
416
+ window.removeEventListener("resize", onResize);
417
+ window.removeEventListener("orientationchange", onResize);
418
+ document.removeEventListener("visibilitychange", onVisibility);
419
+ };
554
420
  }
555
- function restore(entry) {
556
- if (!hasWindow() || !hasHistory()) return;
557
- entry.live = true;
558
- runInternal(() => {
559
- try {
560
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
561
- } catch {
562
- entry.live = false;
563
- }
564
- });
565
- if (entry.live) {
566
- entries.push(entry);
567
- ensureListener();
568
- }
569
- }
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(brandMarker(surface), "", 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
- brandMarker(rebrandSurface),
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);
421
+
422
+ // src/core/features/fit-observer.ts
423
+ var contentTargets = (scroller) => {
424
+ if (!scroller) return [];
425
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
426
+ const out = [];
427
+ for (const child of Array.from(scroller.children)) {
428
+ if (hasSlot && child instanceof HTMLSlotElement) {
429
+ for (const el of child.assignedElements()) out.push(el);
430
+ } else {
431
+ out.push(child);
829
432
  }
830
433
  }
831
434
  return out;
@@ -1667,6 +1270,52 @@ function resolveAnimationPreset(animation) {
1667
1270
  }
1668
1271
  }
1669
1272
 
1273
+ // src/core/animation/waapi-settle.ts
1274
+ var SIM_HZ = 240;
1275
+ var SAMPLE_STRIDE = 4;
1276
+ var MAX_SIM_MS = 3e3;
1277
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1278
+ const cfg = { ...DEFAULT_SPRING, ...config };
1279
+ const subDt = 1 / SIM_HZ;
1280
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1281
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1282
+ const values = [from];
1283
+ let x = from;
1284
+ let v = velocityPxPerS;
1285
+ for (let i = 1; i <= maxSteps; i += 1) {
1286
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1287
+ v += force / cfg.mass * subDt;
1288
+ x += v * subDt;
1289
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1290
+ values.push(to);
1291
+ break;
1292
+ }
1293
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1294
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1295
+ if (atRest) break;
1296
+ }
1297
+ if (values[values.length - 1] !== to) values.push(to);
1298
+ return {
1299
+ values,
1300
+ stepMs,
1301
+ durationMs: (values.length - 1) * stepMs
1302
+ };
1303
+ }
1304
+ function sampleTweenSettle(from, to, durationMs, easing) {
1305
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1306
+ const values = [];
1307
+ for (let i = 0; i <= count; i += 1) {
1308
+ const t = i / count;
1309
+ values.push(from + (to - from) * easing(t));
1310
+ }
1311
+ values[values.length - 1] = to;
1312
+ return {
1313
+ values,
1314
+ stepMs: durationMs / count,
1315
+ durationMs
1316
+ };
1317
+ }
1318
+
1670
1319
  // src/core/controllers/animation-runner.ts
1671
1320
  var DEFAULT_DURATION2 = 220;
1672
1321
  var VELOCITY_PX_PER_S = 1e3;
@@ -1677,6 +1326,10 @@ var AnimationRunner = class {
1677
1326
  __publicField(this, "applySizeFn");
1678
1327
  __publicField(this, "getSize");
1679
1328
  __publicField(this, "isDragging");
1329
+ __publicField(this, "applyAux");
1330
+ __publicField(this, "getTransformFor");
1331
+ __publicField(this, "settleWaapi");
1332
+ __publicField(this, "currentWaapi", null);
1680
1333
  __publicField(this, "animationKind");
1681
1334
  __publicField(this, "springConfig");
1682
1335
  __publicField(this, "duration");
@@ -1692,6 +1345,9 @@ var AnimationRunner = class {
1692
1345
  this.applySizeFn = deps.applySize;
1693
1346
  this.getSize = deps.getSize;
1694
1347
  this.isDragging = deps.isDragging;
1348
+ this.applyAux = deps.applyAux;
1349
+ this.getTransformFor = deps.getTransformFor;
1350
+ this.settleWaapi = opts.settleAnimation === "waapi";
1695
1351
  const preset = resolveAnimationPreset(opts.animation);
1696
1352
  this.animationKind = preset.kind;
1697
1353
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1716,11 +1372,20 @@ var AnimationRunner = class {
1716
1372
  }
1717
1373
  }
1718
1374
  get isAnimating() {
1719
- return this.currentTween !== null || this.currentSpring !== null;
1375
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1720
1376
  }
1721
1377
  cancel() {
1722
1378
  this.currentTween?.cancel();
1723
1379
  this.currentSpring?.cancel();
1380
+ if (this.currentWaapi) {
1381
+ const entry = this.currentWaapi;
1382
+ this.currentWaapi = null;
1383
+ entry.stop();
1384
+ try {
1385
+ entry.anim.cancel();
1386
+ } catch {
1387
+ }
1388
+ }
1724
1389
  }
1725
1390
  async animateTo(target, velocityPxPerMs) {
1726
1391
  if (target === this.getSize()) {
@@ -1739,6 +1404,27 @@ var AnimationRunner = class {
1739
1404
  if (!this.isDragging()) this.element.style.willChange = "";
1740
1405
  return;
1741
1406
  }
1407
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1408
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1409
+ this.getSize(),
1410
+ target,
1411
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1412
+ this.springConfig
1413
+ ) : sampleTweenSettle(
1414
+ this.getSize(),
1415
+ target,
1416
+ this.duration,
1417
+ this.easing
1418
+ );
1419
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1420
+ await this.runWaapiSettle(samples, target);
1421
+ if (!this.isAnimating) {
1422
+ this.getRootEl()?.removeAttribute("data-animating");
1423
+ if (!this.isDragging()) this.element.style.willChange = "";
1424
+ }
1425
+ return;
1426
+ }
1427
+ }
1742
1428
  if (this.animationKind === "spring") {
1743
1429
  const spring = runSpring({
1744
1430
  from: this.getSize(),
@@ -1767,15 +1453,70 @@ var AnimationRunner = class {
1767
1453
  if (!this.isDragging()) this.element.style.willChange = "";
1768
1454
  }
1769
1455
  }
1456
+ async runWaapiSettle(samples, target) {
1457
+ const applyAux = this.applyAux;
1458
+ const getTransformFor = this.getTransformFor;
1459
+ const frames = samples.values.map((v) => ({
1460
+ transform: getTransformFor(v)
1461
+ }));
1462
+ const anim = this.element.animate(frames, {
1463
+ duration: samples.durationMs,
1464
+ easing: "linear",
1465
+ fill: "forwards"
1466
+ });
1467
+ let stopped = false;
1468
+ let auxRaf = 0;
1469
+ const start = performance.now();
1470
+ const lastIdx = samples.values.length - 1;
1471
+ const auxTick = () => {
1472
+ if (stopped) return;
1473
+ const elapsed = performance.now() - start;
1474
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1475
+ applyAux(samples.values[idx]);
1476
+ auxRaf = requestAnimationFrame(auxTick);
1477
+ };
1478
+ auxRaf = requestAnimationFrame(auxTick);
1479
+ const entry = {
1480
+ anim,
1481
+ stop: () => {
1482
+ stopped = true;
1483
+ cancelAnimationFrame(auxRaf);
1484
+ }
1485
+ };
1486
+ this.currentWaapi = entry;
1487
+ try {
1488
+ await anim.finished;
1489
+ } catch {
1490
+ }
1491
+ entry.stop();
1492
+ const wasCurrent = this.currentWaapi === entry;
1493
+ if (wasCurrent) this.currentWaapi = null;
1494
+ if (wasCurrent && anim.playState === "finished") {
1495
+ this.applySizeFn(target);
1496
+ try {
1497
+ anim.cancel();
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ }
1770
1502
  destroy() {
1771
1503
  this.detachReducedMotion?.();
1772
1504
  this.detachReducedMotion = null;
1773
1505
  this.currentTween = null;
1774
1506
  this.currentSpring = null;
1507
+ if (this.currentWaapi) {
1508
+ const entry = this.currentWaapi;
1509
+ this.currentWaapi = null;
1510
+ entry.stop();
1511
+ try {
1512
+ entry.anim.cancel();
1513
+ } catch {
1514
+ }
1515
+ }
1775
1516
  }
1776
1517
  };
1777
1518
 
1778
- // src/core/lifecycle/focusTrap.ts
1519
+ // src/core/lifecycle/focus-trap.ts
1779
1520
  var FOCUSABLE = [
1780
1521
  "a[href]",
1781
1522
  "area[href]",
@@ -1893,7 +1634,7 @@ var installFocusTrap = (container, options = {}) => {
1893
1634
  };
1894
1635
  };
1895
1636
 
1896
- // src/core/lifecycle/scrollLock.ts
1637
+ // src/core/lifecycle/scroll-lock.ts
1897
1638
  var lockCount = 0;
1898
1639
  var savedStyles = null;
1899
1640
  var lockBodyScroll = () => {
@@ -2075,30 +1816,44 @@ var SampleRing = class {
2075
1816
  this.size = 0;
2076
1817
  }
2077
1818
  };
2078
- var installGestures = (handle, mode, callbacks) => {
1819
+ var installGestures = (handle, mode, callbacks, options = {}) => {
2079
1820
  let activePointerId = null;
2080
1821
  let activePointerType = "touch";
2081
1822
  let startCoord = 0;
2082
1823
  let lastCoord = 0;
1824
+ let pendingStart = false;
2083
1825
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
2084
1826
  const MAX_SAMPLES = 32;
2085
1827
  const samples = new SampleRing(MAX_SAMPLES);
2086
1828
  const axis = isAxisVertical(mode) ? "Y" : "X";
2087
1829
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
2088
1830
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1831
+ const capture = (pointerId) => {
1832
+ try {
1833
+ handle.setPointerCapture(pointerId);
1834
+ } catch {
1835
+ }
1836
+ };
1837
+ const releaseTracking = () => {
1838
+ activePointerId = null;
1839
+ pendingStart = false;
1840
+ samples.reset();
1841
+ };
2089
1842
  const onPointerDown = (e) => {
2090
1843
  if (activePointerId !== null) return;
2091
1844
  if (e.button !== void 0 && e.button !== 0) return;
1845
+ if (options.shouldStart && !options.shouldStart(e)) return;
2092
1846
  activePointerId = e.pointerId;
2093
1847
  activePointerType = e.pointerType || "touch";
2094
1848
  startCoord = coordOf(e);
2095
1849
  lastCoord = startCoord;
2096
1850
  samples.reset();
2097
1851
  samples.push(e.timeStamp, startCoord);
2098
- try {
2099
- handle.setPointerCapture(e.pointerId);
2100
- } catch {
1852
+ if (options.deferStart) {
1853
+ pendingStart = true;
1854
+ return;
2101
1855
  }
1856
+ capture(e.pointerId);
2102
1857
  callbacks.onStart(startCoord, activePointerType);
2103
1858
  };
2104
1859
  const onPointerMove = (e) => {
@@ -2106,6 +1861,22 @@ var installGestures = (handle, mode, callbacks) => {
2106
1861
  const coord = coordOf(e);
2107
1862
  const rawDelta = coord - startCoord;
2108
1863
  const delta = rawDelta * sign;
1864
+ if (pendingStart) {
1865
+ const verdict = options.deferStart(e, delta);
1866
+ if (verdict === false) {
1867
+ releaseTracking();
1868
+ return;
1869
+ }
1870
+ if (verdict !== true) return;
1871
+ pendingStart = false;
1872
+ startCoord = coord;
1873
+ lastCoord = coord;
1874
+ samples.reset();
1875
+ samples.push(e.timeStamp, coord);
1876
+ capture(e.pointerId);
1877
+ callbacks.onStart(startCoord, activePointerType);
1878
+ return;
1879
+ }
2109
1880
  lastCoord = coord;
2110
1881
  samples.push(e.timeStamp, coord);
2111
1882
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -2114,6 +1885,10 @@ var installGestures = (handle, mode, callbacks) => {
2114
1885
  };
2115
1886
  const finishGesture = (e) => {
2116
1887
  if (e.pointerId !== activePointerId) return;
1888
+ if (pendingStart) {
1889
+ releaseTracking();
1890
+ return;
1891
+ }
2117
1892
  const rawDelta = lastCoord - startCoord;
2118
1893
  const delta = rawDelta * sign;
2119
1894
  let velocity = 0;
@@ -2136,22 +1911,26 @@ var installGestures = (handle, mode, callbacks) => {
2136
1911
  };
2137
1912
  const onPointerCancel = (e) => {
2138
1913
  if (e.pointerId !== activePointerId) return;
2139
- activePointerId = null;
2140
- samples.reset();
1914
+ const wasPending = pendingStart;
1915
+ releaseTracking();
1916
+ if (wasPending) return;
2141
1917
  callbacks.onCancel?.();
2142
1918
  };
2143
1919
  handle.addEventListener("pointerdown", onPointerDown);
2144
1920
  handle.addEventListener("pointermove", onPointerMove);
2145
1921
  handle.addEventListener("pointerup", finishGesture);
2146
1922
  handle.addEventListener("pointercancel", onPointerCancel);
1923
+ const managesTouchAction = options.manageTouchAction !== false;
2147
1924
  const prevTouchAction = handle.style.touchAction;
2148
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1925
+ if (managesTouchAction) {
1926
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1927
+ }
2149
1928
  return () => {
2150
1929
  handle.removeEventListener("pointerdown", onPointerDown);
2151
1930
  handle.removeEventListener("pointermove", onPointerMove);
2152
1931
  handle.removeEventListener("pointerup", finishGesture);
2153
1932
  handle.removeEventListener("pointercancel", onPointerCancel);
2154
- handle.style.touchAction = prevTouchAction;
1933
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
2155
1934
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
2156
1935
  handle.releasePointerCapture(activePointerId);
2157
1936
  }
@@ -2270,7 +2049,7 @@ var GestureController = class {
2270
2049
  const restoreTo = this.dragStartSize;
2271
2050
  void this.deps.animateTo(restoreTo, 0);
2272
2051
  }
2273
- });
2052
+ }, this.deps.gestureOptions);
2274
2053
  return this.detach;
2275
2054
  }
2276
2055
  forceClearDragState() {
@@ -2282,6 +2061,41 @@ var GestureController = class {
2282
2061
  }
2283
2062
  };
2284
2063
 
2064
+ // src/core/primitives/drag-zones.ts
2065
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2066
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2067
+ var asElement = (target) => {
2068
+ const candidate = target;
2069
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2070
+ };
2071
+ function isDragAllowedFrom(target, mode) {
2072
+ const el = asElement(target);
2073
+ if (!el) return mode !== "zones";
2074
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2075
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2076
+ return true;
2077
+ }
2078
+
2079
+ // src/core/primitives/content-gesture.ts
2080
+ var CONTENT_DRAG_SLOP = 6;
2081
+ function decideContentGesture(input) {
2082
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2083
+ if (Math.abs(input.delta) < slop) return "pending";
2084
+ if (input.scrollTop > 0) return "scroll";
2085
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2086
+ return "drag";
2087
+ }
2088
+
2089
+ // src/core/primitives/touch-scroll-guard.ts
2090
+ function installTouchScrollGuard(surface, isDragging) {
2091
+ const onTouchMove = (e) => {
2092
+ if (!isDragging()) return;
2093
+ if (e.cancelable) e.preventDefault();
2094
+ };
2095
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2096
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2097
+ }
2098
+
2285
2099
  // src/core/primitives/aria-slider-writer.ts
2286
2100
  var AriaSliderWriter = class {
2287
2101
  constructor(handle, mode) {
@@ -2628,7 +2442,7 @@ function resolveEngineOptions(opts, extras) {
2628
2442
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2629
2443
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2630
2444
  if (opts.persistKey) {
2631
- const restored = readPersistedId(opts.persistKey);
2445
+ const restored = readPersistedIdSafe(opts.persistKey);
2632
2446
  if (restored && initialAllowed.includes(restored)) {
2633
2447
  initialId = restored;
2634
2448
  }
@@ -2637,6 +2451,8 @@ function resolveEngineOptions(opts, extras) {
2637
2451
  mode: opts.mode ?? "bottom",
2638
2452
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2639
2453
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2454
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2455
+ dragFromContent: opts.dragFromContent ?? true,
2640
2456
  rubberBandEnabled: opts.rubberBand ?? true,
2641
2457
  closeOnBack: opts.closeOnBack ?? false,
2642
2458
  persistent: opts.persistent ?? false,
@@ -2659,6 +2475,7 @@ function resolveEngineOptions(opts, extras) {
2659
2475
  },
2660
2476
  animation: {
2661
2477
  animation: opts.animation,
2478
+ settleAnimation: opts.settleAnimation,
2662
2479
  duration: opts.duration,
2663
2480
  easing: opts.easing,
2664
2481
  spring: opts.spring,
@@ -2676,6 +2493,15 @@ function resolveEngineOptions(opts, extras) {
2676
2493
  }
2677
2494
  };
2678
2495
  }
2496
+ var readPersistedIdSafe = (key) => {
2497
+ if (typeof window === "undefined") return null;
2498
+ if (typeof localStorage === "undefined") return null;
2499
+ try {
2500
+ return localStorage.getItem(key);
2501
+ } catch {
2502
+ return null;
2503
+ }
2504
+ };
2679
2505
 
2680
2506
  // src/core/primitives/teardown-stack.ts
2681
2507
  var TeardownStack = class {
@@ -2698,9 +2524,9 @@ var TeardownStack = class {
2698
2524
  }
2699
2525
  };
2700
2526
 
2701
- // src/core/BottomSheetEngine.ts
2527
+ // src/core/BottomSheetCore.ts
2702
2528
  var HAPTIC_DURATION_MS = 8;
2703
- var BottomSheetEngine = class {
2529
+ var BottomSheetCore = class {
2704
2530
  constructor(opts) {
2705
2531
  __publicField(this, "id", nextInstanceId("bs"));
2706
2532
  __publicField(this, "element");
@@ -2712,6 +2538,8 @@ var BottomSheetEngine = class {
2712
2538
  __publicField(this, "mode");
2713
2539
  __publicField(this, "flickVelocity");
2714
2540
  __publicField(this, "dragThreshold");
2541
+ __publicField(this, "dragFromMode");
2542
+ __publicField(this, "dragFromContentDefault");
2715
2543
  __publicField(this, "rubberBandEnabled");
2716
2544
  __publicField(this, "scrim");
2717
2545
  __publicField(this, "aria");
@@ -2729,8 +2557,11 @@ var BottomSheetEngine = class {
2729
2557
  __publicField(this, "activeId");
2730
2558
  __publicField(this, "size", 0);
2731
2559
  __publicField(this, "gesture");
2560
+ __publicField(this, "contentGesture");
2561
+ __publicField(this, "detachSheetGesture", null);
2732
2562
  __publicField(this, "rootEl", null);
2733
2563
  __publicField(this, "destroyed", false);
2564
+ __publicField(this, "restClosed", false);
2734
2565
  __publicField(this, "currentViewTransition", null);
2735
2566
  __publicField(this, "currentAbort", new AbortController());
2736
2567
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2761,6 +2592,8 @@ var BottomSheetEngine = class {
2761
2592
  __publicField(this, "stackEffectPrimed", false);
2762
2593
  __publicField(this, "fitDeps");
2763
2594
  __publicField(this, "transformTemplate");
2595
+ __publicField(this, "featureList", []);
2596
+ __publicField(this, "featureCtx");
2764
2597
  const resolved = resolveEngineOptions(opts, {
2765
2598
  shouldApplyInertSiblings: () => {
2766
2599
  if (typeof document === "undefined") return false;
@@ -2781,6 +2614,8 @@ var BottomSheetEngine = class {
2781
2614
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2782
2615
  this.flickVelocity = resolved.flickVelocity;
2783
2616
  this.dragThreshold = resolved.dragThreshold;
2617
+ this.dragFromMode = resolved.dragFrom;
2618
+ this.dragFromContentDefault = resolved.dragFromContent;
2784
2619
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2785
2620
  this.scrim = new ScrimController(
2786
2621
  {
@@ -2807,7 +2642,9 @@ var BottomSheetEngine = class {
2807
2642
  getRootEl: () => this.rootEl,
2808
2643
  applySize: (size) => this.applySize(size),
2809
2644
  getSize: () => this.size,
2810
- isDragging: () => this.gesture?.isDragging ?? false
2645
+ isDragging: () => this.isDraggingAny(),
2646
+ applyAux: (size) => this.applySize(size, true),
2647
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2811
2648
  },
2812
2649
  resolved.animation
2813
2650
  );
@@ -2830,6 +2667,11 @@ var BottomSheetEngine = class {
2830
2667
  if (opts.linkedSheets) {
2831
2668
  this.linkedSheets = opts.linkedSheets;
2832
2669
  }
2670
+ const byName = /* @__PURE__ */ new Map();
2671
+ for (const feature of opts.features ?? []) {
2672
+ byName.set(feature.name, feature);
2673
+ }
2674
+ this.featureList = Array.from(byName.values());
2833
2675
  this.activeId = resolved.initialId;
2834
2676
  auditVhUsage(this.snapPointsRaw);
2835
2677
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2861,11 +2703,13 @@ var BottomSheetEngine = class {
2861
2703
  const initial = this.snaps.findById(this.activeId);
2862
2704
  if (initial) this.size = initial.size;
2863
2705
  this.applySize(this.size);
2706
+ this.setRestClosed(this.size === 0);
2864
2707
  this.updateAriaSlider();
2865
2708
  this.scrollCache = createScrollCache({
2866
2709
  scrollContainer: this.scrollContainer,
2867
2710
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2868
2711
  });
2712
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2869
2713
  this.attach();
2870
2714
  this.registerInStack();
2871
2715
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2890,60 +2734,92 @@ var BottomSheetEngine = class {
2890
2734
  (p) => p.size === "fit" || p.size === "content"
2891
2735
  ),
2892
2736
  isDestroyed: () => this.destroyed,
2893
- isDragging: () => this.gesture?.isDragging ?? false,
2737
+ isDragging: () => this.isDraggingAny(),
2894
2738
  recompute: () => this.recompute()
2895
2739
  })
2896
2740
  );
2897
2741
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2898
2742
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2899
2743
  this.applyAutoAriaLabelledBy();
2900
- if (this.closeOnRouteChange) {
2901
- this.teardowns.add(
2902
- installRouteChange({
2903
- isDestroyed: () => this.destroyed,
2904
- getSize: () => this.size,
2905
- close: () => this.close()
2906
- })
2907
- );
2908
- }
2909
- if (this.persistKey) {
2910
- this.teardowns.add(installPersist(this, this.persistKey));
2744
+ this.runFeatureStage("post");
2745
+ this.warnOrphanFeatureOptions();
2746
+ if (this.size > 0) {
2747
+ this.handleOpen();
2911
2748
  }
2912
- this.teardowns.add(
2913
- installAutoCollapse({
2914
- ms: opts.autoCollapseAfter,
2915
- isDestroyed: () => this.destroyed,
2916
- isDragging: () => this.gesture?.isDragging ?? false,
2917
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2918
- getActiveId: () => this.activeId,
2919
- resolveSnap: (id) => this.snaps.findById(id),
2920
- snapTo: (id) => {
2921
- void this.snapTo(id);
2922
- },
2923
- on: (event, fn) => this.on(event, fn)
2924
- })
2925
- );
2926
- this.teardowns.add(
2927
- installRoute({
2749
+ }
2750
+ buildFeatureContext(autoCollapseAfter) {
2751
+ return {
2752
+ element: this.element,
2753
+ scrollContainer: this.scrollContainer,
2754
+ sheetId: this.id,
2755
+ options: {
2928
2756
  routedTo: this.routedTo,
2929
2757
  closeOnBack: this.closeOnBack,
2930
- isTopSheet: () => this.isTopSheet,
2931
- getSize: () => this.size,
2932
- isDestroyed: () => this.destroyed,
2933
- close: () => this.close("back"),
2934
- on: (event, fn) => this.on(event, fn),
2935
- sheetId: this.id
2936
- })
2937
- );
2938
- if (this.size > 0) {
2939
- this.handleOpen();
2758
+ closeOnRouteChange: this.closeOnRouteChange,
2759
+ persistKey: this.persistKey,
2760
+ autoCollapseAfter
2761
+ },
2762
+ isDestroyed: () => this.destroyed,
2763
+ isDragging: () => this.isDraggingAny(),
2764
+ isAnimating: () => this.animation.isAnimating,
2765
+ isTopSheet: () => this.isTopSheet,
2766
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2767
+ getSize: () => this.size,
2768
+ getActiveId: () => this.activeId,
2769
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2770
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2771
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2772
+ resolveSnap: (id) => this.snaps.findById(id),
2773
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2774
+ setSize: (size) => {
2775
+ this.size = size;
2776
+ },
2777
+ setMaxAxisSize: (size) => {
2778
+ this.snaps.setMaxAxisSize(size);
2779
+ },
2780
+ applySize: (size) => this.applySize(size),
2781
+ recomputeSnaps: () => this.recomputeSnaps(),
2782
+ newCycle: () => {
2783
+ this.newCycle();
2784
+ },
2785
+ cancelInFlight: () => {
2786
+ this.animation.cancel();
2787
+ },
2788
+ resyncAfterResize: () => this.resyncAfterResize(),
2789
+ snapTo: (id) => {
2790
+ void this.snapTo(id);
2791
+ },
2792
+ close: (reason) => this.close(reason),
2793
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2794
+ on: (event, fn) => this.on(event, fn),
2795
+ addTeardown: (fn) => this.teardowns.add(fn)
2796
+ };
2797
+ }
2798
+ runFeatureStage(stage) {
2799
+ for (const feature of this.featureList) {
2800
+ if ((feature.stage ?? "post") !== stage) continue;
2801
+ const teardown = feature.install(this.featureCtx);
2802
+ if (teardown) this.teardowns.add(teardown);
2803
+ }
2804
+ }
2805
+ warnOrphanFeatureOptions() {
2806
+ const names = new Set(this.featureList.map((f) => f.name));
2807
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2808
+ devWarn(
2809
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2810
+ );
2811
+ }
2812
+ if (this.persistKey && !names.has("persist")) {
2813
+ devWarn(
2814
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2815
+ );
2940
2816
  }
2941
2817
  }
2942
2818
  get state() {
2943
2819
  return {
2944
2820
  size: this.size,
2945
2821
  activeId: this.activeId,
2946
- isDragging: this.gesture?.isDragging ?? false,
2822
+ isDragging: this.isDraggingAny(),
2947
2823
  isAnimating: this.animation.isAnimating,
2948
2824
  progress: this.computeProgress(this.size)
2949
2825
  };
@@ -3063,9 +2939,16 @@ var BottomSheetEngine = class {
3063
2939
  progress: this.computeProgress(this.size)
3064
2940
  });
3065
2941
  if (withHaptic) this.haptic();
2942
+ this.setRestClosed(targetSize === 0);
3066
2943
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
3067
2944
  if (targetSize === 0) this.emitCloseSequence();
3068
2945
  }
2946
+ setRestClosed(closed) {
2947
+ if (closed === this.restClosed) return;
2948
+ this.restClosed = closed;
2949
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2950
+ else this.element.removeAttribute("data-bs-rest");
2951
+ }
3069
2952
  emitOpenSequence(id) {
3070
2953
  this.emit("open", { id });
3071
2954
  this.handleOpen();
@@ -3121,6 +3004,20 @@ var BottomSheetEngine = class {
3121
3004
  if (this.destroyed) return;
3122
3005
  this.disableDragFlag = value;
3123
3006
  }
3007
+ setDragFromContent(value) {
3008
+ if (this.destroyed) return;
3009
+ this.dragFromContentDefault = value;
3010
+ }
3011
+ setDragFrom(mode) {
3012
+ if (this.destroyed || mode === this.dragFromMode) return;
3013
+ this.gesture?.forceClearDragState();
3014
+ this.detachSheetGesture?.();
3015
+ this.dragFromMode = mode;
3016
+ this.mountSheetGesture();
3017
+ }
3018
+ getDragFrom() {
3019
+ return this.dragFromMode;
3020
+ }
3124
3021
  isTop() {
3125
3022
  return this.isTopSheet;
3126
3023
  }
@@ -3275,12 +3172,24 @@ var BottomSheetEngine = class {
3275
3172
  auditVhUsage(points);
3276
3173
  this.auditDuplicateSnapIds(points);
3277
3174
  this.snaps.setRaw(points);
3278
- if (allowed) this.snaps.setAllowedIds(allowed);
3175
+ if (allowed) {
3176
+ this.snaps.setAllowedIds(allowed);
3177
+ } else {
3178
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3179
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3180
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3181
+ }
3279
3182
  this.scrollCache.clear();
3280
3183
  this.scrim.invalidateOpacityCache();
3281
3184
  const current = this.snaps.findById(this.activeId);
3282
3185
  if (current) {
3283
3186
  this.applySize(current.size);
3187
+ } else {
3188
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3189
+ if (fallback) {
3190
+ this.activeId = fallback.id;
3191
+ this.applySize(fallback.size);
3192
+ }
3284
3193
  }
3285
3194
  this.updateAriaSlider();
3286
3195
  }
@@ -3290,7 +3199,7 @@ var BottomSheetEngine = class {
3290
3199
  this.snaps.recompute();
3291
3200
  this.maxHeight.clampTo();
3292
3201
  this.scrim.invalidateOpacityCache();
3293
- if (this.gesture?.isDragging) return;
3202
+ if (this.isDraggingAny()) return;
3294
3203
  this.healActiveSnap();
3295
3204
  }
3296
3205
  healActiveSnap() {
@@ -3312,6 +3221,8 @@ var BottomSheetEngine = class {
3312
3221
  this.bus.clear();
3313
3222
  this.animation.cancel();
3314
3223
  this.gesture?.forceClearDragState();
3224
+ this.contentGesture?.forceClearDragState();
3225
+ this.setRestClosed(false);
3315
3226
  this.teardowns.drain();
3316
3227
  this.scrim.destroy();
3317
3228
  this.animation.destroy();
@@ -3424,11 +3335,12 @@ var BottomSheetEngine = class {
3424
3335
  const scale = Math.max(1 - depth * 0.04, 0.86);
3425
3336
  this.element.style.scale = scale === 1 ? "" : String(scale);
3426
3337
  }
3427
- attach() {
3428
- this.gesture = new GestureController({
3429
- handle: this.handle,
3338
+ buildGestureDeps(surface, gestureOptions) {
3339
+ return {
3340
+ handle: surface,
3430
3341
  element: this.element,
3431
3342
  mode: this.mode,
3343
+ gestureOptions,
3432
3344
  getRoot: () => this.rootEl,
3433
3345
  getDragContext: () => {
3434
3346
  const buf = this.dragContextBuf;
@@ -3447,8 +3359,88 @@ var BottomSheetEngine = class {
3447
3359
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3448
3360
  emit: (event, payload) => this.emit(event, payload),
3449
3361
  listenerCount: (event) => this.bus.listenerCount(event)
3362
+ };
3363
+ }
3364
+ isContentDragAllowed() {
3365
+ if (this.disableDragFlag) return false;
3366
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3367
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3368
+ }
3369
+ decideContentDrag(surface, e, delta) {
3370
+ if (this.destroyed) return false;
3371
+ if (e.pointerType === "mouse") return false;
3372
+ if (this.gesture?.isDragging) return false;
3373
+ if (!this.isContentDragAllowed()) return false;
3374
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3375
+ const decision = decideContentGesture({
3376
+ delta,
3377
+ scrollTop: surface.scrollTop,
3378
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3379
+ });
3380
+ if (decision === "pending") return null;
3381
+ return decision === "drag";
3382
+ }
3383
+ attachDragSurface(surface, kind) {
3384
+ if (this.destroyed || kind !== "content") return;
3385
+ if (this.contentGesture) return;
3386
+ const controller = new GestureController(
3387
+ this.buildGestureDeps(surface, {
3388
+ manageTouchAction: false,
3389
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3390
+ })
3391
+ );
3392
+ this.contentGesture = controller;
3393
+ const detachGestures = controller.install();
3394
+ const detachGuard = installTouchScrollGuard(
3395
+ surface,
3396
+ () => controller.isDragging
3397
+ );
3398
+ return () => {
3399
+ detachGuard();
3400
+ detachGestures();
3401
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3402
+ };
3403
+ }
3404
+ sheetDragSurface() {
3405
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3406
+ }
3407
+ canStartSheetDrag(e) {
3408
+ if (this.contentGesture?.isDragging) return false;
3409
+ const target = e.target;
3410
+ const scoped = typeof target?.closest === "function";
3411
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3412
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3413
+ return true;
3414
+ }
3415
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3416
+ if (this.dragFromMode === "handle") return true;
3417
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3418
+ return false;
3419
+ }
3420
+ return true;
3421
+ }
3422
+ mountSheetGesture() {
3423
+ const surface = this.sheetDragSurface();
3424
+ const controller = new GestureController(
3425
+ this.buildGestureDeps(surface, {
3426
+ shouldStart: (e) => this.canStartSheetDrag(e),
3427
+ manageTouchAction: this.dragFromMode === "handle"
3428
+ })
3429
+ );
3430
+ this.gesture = controller;
3431
+ const detachGestures = controller.install();
3432
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3433
+ this.detachSheetGesture = () => {
3434
+ detachGuard?.();
3435
+ detachGestures();
3436
+ };
3437
+ }
3438
+ attach() {
3439
+ this.mountSheetGesture();
3440
+ this.teardowns.add(() => {
3441
+ this.detachSheetGesture?.();
3442
+ this.detachSheetGesture = null;
3450
3443
  });
3451
- this.teardowns.add(this.gesture.install());
3452
3444
  try {
3453
3445
  this.attachFeatures();
3454
3446
  } catch (err) {
@@ -3479,24 +3471,13 @@ var BottomSheetEngine = class {
3479
3471
  }
3480
3472
  }
3481
3473
  attachFeatures() {
3482
- if (this.scrollContainer) {
3483
- this.teardowns.add(installContentSwipe({
3484
- container: this.scrollContainer,
3485
- isDragging: () => this.gesture?.isDragging ?? false,
3486
- isAnimating: () => this.animation.isAnimating,
3487
- getAllowedIds: () => this.allowedIdsBySize(),
3488
- getActiveId: () => this.activeId,
3489
- snapTo: (id) => {
3490
- void this.snapTo(id);
3491
- }
3492
- }));
3493
- }
3474
+ this.runFeatureStage("attach");
3494
3475
  if (typeof window !== "undefined") {
3495
3476
  this.teardowns.add(installResizeObserver({
3496
3477
  element: this.element,
3497
3478
  getMode: () => this.mode,
3498
3479
  isDestroyed: () => this.destroyed,
3499
- isDragging: () => this.gesture?.isDragging ?? false,
3480
+ isDragging: () => this.isDraggingAny(),
3500
3481
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3501
3482
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3502
3483
  getSize: () => this.size,
@@ -3530,31 +3511,6 @@ var BottomSheetEngine = class {
3530
3511
  () => document.removeEventListener("keydown", onKey)
3531
3512
  );
3532
3513
  }
3533
- this.teardowns.add(installVisualViewport({
3534
- element: this.element,
3535
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3536
- isDestroyed: () => this.destroyed,
3537
- isDragging: () => this.gesture?.isDragging ?? false,
3538
- recomputeSnaps: () => this.recomputeSnaps(),
3539
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3540
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3541
- getSize: () => this.size,
3542
- setMaxAxisSize: (size) => {
3543
- this.snaps.setMaxAxisSize(size);
3544
- },
3545
- setSize: (size) => {
3546
- this.size = size;
3547
- },
3548
- applySize: (size) => this.applySize(size),
3549
- cancelInFlight: () => {
3550
- this.animation.cancel();
3551
- },
3552
- newCycle: () => {
3553
- this.newCycle();
3554
- },
3555
- isAnimating: () => this.animation.isAnimating,
3556
- resyncAfterCancel: () => this.resyncAfterResize()
3557
- }));
3558
3514
  }
3559
3515
  this.teardowns.add(installSliderKeyboard({
3560
3516
  handle: this.handle,
@@ -3619,8 +3575,11 @@ var BottomSheetEngine = class {
3619
3575
  );
3620
3576
  });
3621
3577
  }
3578
+ isDraggingAny() {
3579
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3580
+ }
3622
3581
  clearWillChangeIfIdle() {
3623
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3582
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3624
3583
  this.element.style.willChange = "";
3625
3584
  }
3626
3585
  haptic() {
@@ -3631,13 +3590,14 @@ var BottomSheetEngine = class {
3631
3590
  }
3632
3591
  }
3633
3592
  }
3634
- applySize(size) {
3593
+ applySize(size, skipTransform = false) {
3635
3594
  const cap = this.snaps.getMaxAxisSize();
3636
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3595
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3637
3596
  this.size = clamped;
3597
+ if (clamped > 0) this.setRestClosed(false);
3638
3598
  const offset = cap - clamped;
3639
3599
  const style = this.element.style;
3640
- style.transform = this.transformTemplate(offset);
3600
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3641
3601
  if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3642
3602
  style.setProperty("--bs-size", `${clamped}px`);
3643
3603
  if (this.scrimParent) {
@@ -3715,6 +3675,470 @@ var BottomSheetEngine = class {
3715
3675
  }
3716
3676
  };
3717
3677
 
3678
+ // src/core/features/history-coordinator.ts
3679
+ var entries = [];
3680
+ var subscribers = /* @__PURE__ */ new Set();
3681
+ var suppress = 0;
3682
+ var internalDepth = 0;
3683
+ var pendingRebrands = [];
3684
+ var listenerInstalled = false;
3685
+ var patchInstalled = false;
3686
+ var origPushState = null;
3687
+ var origReplaceState = null;
3688
+ var patchedPushState = null;
3689
+ var patchedReplaceState = null;
3690
+ var hasWindow = () => typeof window !== "undefined";
3691
+ var hasHistory = () => typeof history !== "undefined";
3692
+ var brandMarker = (surface) => ({
3693
+ ...surface.markerState(),
3694
+ __bs: true
3695
+ });
3696
+ var runInternal = (fn) => {
3697
+ internalDepth += 1;
3698
+ try {
3699
+ fn();
3700
+ } finally {
3701
+ internalDepth -= 1;
3702
+ }
3703
+ };
3704
+ var notifyRouteChange = () => {
3705
+ const snapshot = Array.from(subscribers);
3706
+ for (const fn of snapshot) fn();
3707
+ };
3708
+ var ensureListener = () => {
3709
+ if (listenerInstalled || !hasWindow()) return;
3710
+ window.addEventListener("popstate", onPopState);
3711
+ listenerInstalled = true;
3712
+ };
3713
+ var maybeTeardownListener = () => {
3714
+ if (!listenerInstalled) return;
3715
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3716
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3717
+ listenerInstalled = false;
3718
+ };
3719
+ var installPatch = () => {
3720
+ if (patchInstalled || !hasHistory()) return;
3721
+ origPushState = history.pushState;
3722
+ origReplaceState = history.replaceState;
3723
+ patchedPushState = function(...args) {
3724
+ origPushState.apply(this, args);
3725
+ if (internalDepth === 0) notifyRouteChange();
3726
+ };
3727
+ patchedReplaceState = function(...args) {
3728
+ origReplaceState.apply(this, args);
3729
+ if (internalDepth === 0) notifyRouteChange();
3730
+ };
3731
+ history.pushState = patchedPushState;
3732
+ history.replaceState = patchedReplaceState;
3733
+ patchInstalled = true;
3734
+ };
3735
+ var removePatch = () => {
3736
+ if (!patchInstalled) return;
3737
+ if (history.pushState === patchedPushState && origPushState) {
3738
+ history.pushState = origPushState;
3739
+ }
3740
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3741
+ history.replaceState = origReplaceState;
3742
+ }
3743
+ origPushState = null;
3744
+ origReplaceState = null;
3745
+ patchedPushState = null;
3746
+ patchedReplaceState = null;
3747
+ patchInstalled = false;
3748
+ };
3749
+ function onPopState() {
3750
+ if (suppress > 0) {
3751
+ suppress -= 1;
3752
+ const rebrand = pendingRebrands.shift();
3753
+ if (rebrand) rebrand();
3754
+ maybeTeardownListener();
3755
+ return;
3756
+ }
3757
+ if (entries.length === 0) {
3758
+ notifyRouteChange();
3759
+ maybeTeardownListener();
3760
+ return;
3761
+ }
3762
+ let target = null;
3763
+ let targetIdx = -1;
3764
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3765
+ const entry = entries[i];
3766
+ if (!entry.surface.isOpen()) continue;
3767
+ if (!target) {
3768
+ target = entry;
3769
+ targetIdx = i;
3770
+ }
3771
+ if (entry.surface.isTop()) {
3772
+ target = entry;
3773
+ targetIdx = i;
3774
+ break;
3775
+ }
3776
+ }
3777
+ if (!target) {
3778
+ const removed = entries.pop();
3779
+ if (removed) removed.live = false;
3780
+ maybeTeardownListener();
3781
+ return;
3782
+ }
3783
+ target.live = false;
3784
+ entries.splice(targetIdx, 1);
3785
+ const settled = target;
3786
+ void Promise.resolve(settled.surface.close()).then(() => {
3787
+ if (settled.surface.isOpen()) restore(settled);
3788
+ });
3789
+ maybeTeardownListener();
3790
+ }
3791
+ function restore(entry) {
3792
+ if (!hasWindow() || !hasHistory()) return;
3793
+ entry.live = true;
3794
+ runInternal(() => {
3795
+ try {
3796
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3797
+ } catch {
3798
+ entry.live = false;
3799
+ }
3800
+ });
3801
+ if (entry.live) {
3802
+ entries.push(entry);
3803
+ ensureListener();
3804
+ }
3805
+ }
3806
+ function pushBackMarker(surface) {
3807
+ const entry = { surface, priorUrl: void 0, live: false };
3808
+ if (!hasWindow() || !hasHistory()) {
3809
+ return entry;
3810
+ }
3811
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3812
+ runInternal(() => {
3813
+ try {
3814
+ history.pushState(brandMarker(surface), "", surface.url);
3815
+ entry.live = true;
3816
+ } catch {
3817
+ entry.live = false;
3818
+ }
3819
+ });
3820
+ if (entry.live) {
3821
+ entries.push(entry);
3822
+ ensureListener();
3823
+ }
3824
+ return entry;
3825
+ }
3826
+ function popBackMarker(handle) {
3827
+ if (!hasWindow() || !hasHistory()) return;
3828
+ const entry = handle;
3829
+ const idx = entries.indexOf(entry);
3830
+ if (idx === -1 || !entry.live) return;
3831
+ const last = entries.length - 1;
3832
+ entry.live = false;
3833
+ entries.splice(idx, 1);
3834
+ let rebrand = null;
3835
+ if (idx !== last) {
3836
+ const rebrandSurface = entries[entries.length - 1].surface;
3837
+ const rebrandUrl = entry.priorUrl;
3838
+ rebrand = () => {
3839
+ runInternal(() => {
3840
+ history.replaceState(
3841
+ brandMarker(rebrandSurface),
3842
+ "",
3843
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3844
+ );
3845
+ });
3846
+ };
3847
+ }
3848
+ pendingRebrands.push(rebrand);
3849
+ suppress += 1;
3850
+ runInternal(() => {
3851
+ try {
3852
+ history.back();
3853
+ } catch {
3854
+ suppress = Math.max(0, suppress - 1);
3855
+ pendingRebrands.pop();
3856
+ }
3857
+ });
3858
+ maybeTeardownListener();
3859
+ }
3860
+ function subscribeRouteChange(fn) {
3861
+ if (!hasWindow() || !hasHistory()) return () => {
3862
+ };
3863
+ subscribers.add(fn);
3864
+ if (subscribers.size === 1) installPatch();
3865
+ ensureListener();
3866
+ return () => {
3867
+ if (!subscribers.has(fn)) return;
3868
+ subscribers.delete(fn);
3869
+ if (subscribers.size === 0) removePatch();
3870
+ maybeTeardownListener();
3871
+ };
3872
+ }
3873
+
3874
+ // src/core/features/route.ts
3875
+ function installRoute(deps) {
3876
+ if (typeof window === "undefined") {
3877
+ return () => {
3878
+ };
3879
+ }
3880
+ if (!deps.routedTo && !deps.closeOnBack) {
3881
+ return () => {
3882
+ };
3883
+ }
3884
+ let handle = null;
3885
+ const onOpen = () => {
3886
+ if (handle) return;
3887
+ handle = pushBackMarker({
3888
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3889
+ isTop: deps.isTopSheet,
3890
+ close: deps.close,
3891
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3892
+ url: deps.routedTo
3893
+ });
3894
+ };
3895
+ const onClose = () => {
3896
+ if (!handle) return;
3897
+ popBackMarker(handle);
3898
+ handle = null;
3899
+ };
3900
+ const unsubscribeOpen = deps.on("open", onOpen);
3901
+ const unsubscribeClose = deps.on("close", onClose);
3902
+ if (deps.getSize() > 0) onOpen();
3903
+ return () => {
3904
+ unsubscribeOpen();
3905
+ unsubscribeClose();
3906
+ if (handle) {
3907
+ popBackMarker(handle);
3908
+ handle = null;
3909
+ }
3910
+ };
3911
+ }
3912
+ function installRouteChange(deps) {
3913
+ return subscribeRouteChange(() => {
3914
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3915
+ void deps.close();
3916
+ });
3917
+ }
3918
+
3919
+ // src/core/features/persist.ts
3920
+ function installPersist(engine, key) {
3921
+ const unsubscribe = engine.on("snap", ({ id }) => {
3922
+ if (typeof window === "undefined") return;
3923
+ if (typeof localStorage === "undefined") return;
3924
+ try {
3925
+ localStorage.setItem(key, id);
3926
+ } catch {
3927
+ }
3928
+ });
3929
+ return unsubscribe;
3930
+ }
3931
+
3932
+ // src/core/features/auto-collapse.ts
3933
+ function installAutoCollapse(deps) {
3934
+ const { ms } = deps;
3935
+ if (ms === void 0 || ms <= 0) {
3936
+ return () => {
3937
+ };
3938
+ }
3939
+ let timer = null;
3940
+ const clearTimer = () => {
3941
+ if (timer !== null) {
3942
+ clearTimeout(timer);
3943
+ timer = null;
3944
+ }
3945
+ };
3946
+ const fire = () => {
3947
+ if (deps.isDestroyed()) return;
3948
+ if (deps.isDragging()) return;
3949
+ const active = deps.resolveSnap(deps.getActiveId());
3950
+ if (!active || active.size === 0) return;
3951
+ let target = null;
3952
+ for (const id of deps.getAllowedIds()) {
3953
+ const snap = deps.resolveSnap(id);
3954
+ if (!snap || snap.size <= 0) continue;
3955
+ if (!target || snap.size < target.size) target = snap;
3956
+ }
3957
+ if (!target) return;
3958
+ if (target.id === active.id || target.size >= active.size) return;
3959
+ deps.snapTo(target.id);
3960
+ };
3961
+ const reset = () => {
3962
+ clearTimer();
3963
+ if (deps.isDestroyed()) return;
3964
+ timer = setTimeout(() => {
3965
+ timer = null;
3966
+ fire();
3967
+ }, ms);
3968
+ };
3969
+ const unsubscribe = deps.on("snap", () => reset());
3970
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3971
+ return () => {
3972
+ clearTimer();
3973
+ unsubscribe();
3974
+ };
3975
+ }
3976
+
3977
+ // src/core/features/content-drag.ts
3978
+ function installContentDrag(deps) {
3979
+ const detach = deps.attachDragSurface(deps.container, "content");
3980
+ return () => detach?.();
3981
+ }
3982
+
3983
+ // src/core/features/visual-viewport.ts
3984
+ var SAFETY_PAD = 8;
3985
+ function installVisualViewport(deps) {
3986
+ if (typeof window === "undefined" || !window.visualViewport) {
3987
+ return () => {
3988
+ };
3989
+ }
3990
+ const vv = window.visualViewport;
3991
+ let rafId = null;
3992
+ const onResize = () => {
3993
+ if (rafId !== null) return;
3994
+ rafId = requestAnimationFrame(() => {
3995
+ rafId = null;
3996
+ if (deps.isDestroyed()) return;
3997
+ deps.recomputeSnaps();
3998
+ const isVertical = deps.isVerticalAxis();
3999
+ const visible = isVertical ? vv.height : vv.width;
4000
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
4001
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
4002
+ if (clamp < deps.getMaxAxisSize()) {
4003
+ deps.setMaxAxisSize(clamp);
4004
+ }
4005
+ const wasAnimating = deps.isAnimating?.() ?? false;
4006
+ deps.cancelInFlight();
4007
+ deps.newCycle();
4008
+ const max = deps.getMaxAxisSize();
4009
+ deps.setSize(Math.min(deps.getSize(), max));
4010
+ const current = deps.resolveActiveSnap();
4011
+ if (current && !deps.isDragging()) {
4012
+ deps.applySize(Math.min(current.size, max));
4013
+ if (wasAnimating) deps.resyncAfterCancel?.();
4014
+ } else {
4015
+ deps.applySize(deps.getSize());
4016
+ }
4017
+ });
4018
+ };
4019
+ vv.addEventListener("resize", onResize);
4020
+ return () => {
4021
+ vv.removeEventListener("resize", onResize);
4022
+ if (rafId !== null) cancelAnimationFrame(rafId);
4023
+ };
4024
+ }
4025
+
4026
+ // src/core/features/engine-features.ts
4027
+ function routeFeature() {
4028
+ return {
4029
+ name: "route",
4030
+ install: (ctx) => {
4031
+ const teardowns = new TeardownStack();
4032
+ if (ctx.options.closeOnRouteChange) {
4033
+ teardowns.add(
4034
+ installRouteChange({
4035
+ isDestroyed: ctx.isDestroyed,
4036
+ getSize: ctx.getSize,
4037
+ close: () => ctx.close()
4038
+ })
4039
+ );
4040
+ }
4041
+ teardowns.add(
4042
+ installRoute({
4043
+ routedTo: ctx.options.routedTo,
4044
+ closeOnBack: ctx.options.closeOnBack,
4045
+ isTopSheet: ctx.isTopSheet,
4046
+ getSize: ctx.getSize,
4047
+ isDestroyed: ctx.isDestroyed,
4048
+ close: () => ctx.close("back"),
4049
+ on: ctx.on,
4050
+ sheetId: ctx.sheetId
4051
+ })
4052
+ );
4053
+ return () => teardowns.drain();
4054
+ }
4055
+ };
4056
+ }
4057
+ function persistFeature() {
4058
+ return {
4059
+ name: "persist",
4060
+ install: (ctx) => {
4061
+ const key = ctx.options.persistKey;
4062
+ if (!key) return;
4063
+ return installPersist({ on: ctx.on }, key);
4064
+ }
4065
+ };
4066
+ }
4067
+ function autoCollapseFeature() {
4068
+ return {
4069
+ name: "auto-collapse",
4070
+ install: (ctx) => installAutoCollapse({
4071
+ ms: ctx.options.autoCollapseAfter,
4072
+ isDestroyed: ctx.isDestroyed,
4073
+ isDragging: ctx.isDragging,
4074
+ getAllowedIds: ctx.getAllowedIds,
4075
+ getActiveId: ctx.getActiveId,
4076
+ resolveSnap: ctx.resolveSnap,
4077
+ snapTo: ctx.snapTo,
4078
+ on: ctx.on
4079
+ })
4080
+ };
4081
+ }
4082
+ function contentSwipeFeature() {
4083
+ return {
4084
+ name: "content-swipe",
4085
+ stage: "attach",
4086
+ install: (ctx) => {
4087
+ if (!ctx.scrollContainer) return;
4088
+ return installContentDrag({
4089
+ container: ctx.scrollContainer,
4090
+ attachDragSurface: ctx.attachDragSurface
4091
+ });
4092
+ }
4093
+ };
4094
+ }
4095
+ function visualViewportFeature() {
4096
+ return {
4097
+ name: "visual-viewport",
4098
+ install: (ctx) => {
4099
+ if (typeof window === "undefined") return;
4100
+ return installVisualViewport({
4101
+ element: ctx.element,
4102
+ isVerticalAxis: ctx.isVerticalAxis,
4103
+ isDestroyed: ctx.isDestroyed,
4104
+ isDragging: ctx.isDragging,
4105
+ recomputeSnaps: ctx.recomputeSnaps,
4106
+ resolveActiveSnap: ctx.resolveActiveSnap,
4107
+ getMaxAxisSize: ctx.getMaxAxisSize,
4108
+ getSize: ctx.getSize,
4109
+ setMaxAxisSize: ctx.setMaxAxisSize,
4110
+ setSize: ctx.setSize,
4111
+ applySize: ctx.applySize,
4112
+ cancelInFlight: ctx.cancelInFlight,
4113
+ newCycle: ctx.newCycle,
4114
+ isAnimating: ctx.isAnimating,
4115
+ resyncAfterCancel: ctx.resyncAfterResize
4116
+ });
4117
+ }
4118
+ };
4119
+ }
4120
+
4121
+ // src/core/default-features.ts
4122
+ function defaultEngineFeatures() {
4123
+ return [
4124
+ contentSwipeFeature(),
4125
+ visualViewportFeature(),
4126
+ persistFeature(),
4127
+ autoCollapseFeature(),
4128
+ routeFeature()
4129
+ ];
4130
+ }
4131
+
4132
+ // src/core/BottomSheetEngine.ts
4133
+ var BottomSheetEngine = class extends BottomSheetCore {
4134
+ constructor(opts) {
4135
+ super({
4136
+ ...opts,
4137
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
4138
+ });
4139
+ }
4140
+ };
4141
+
3718
4142
  // src/core/features/teleport.ts
3719
4143
  var resolveTeleportTarget = (target) => {
3720
4144
  if (!target) return null;