@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
package/dist/svelte.cjs CHANGED
@@ -28,7 +28,7 @@ var __defProp = Object.defineProperty;
28
28
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
29
29
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
30
30
 
31
- // src/core/lifecycle/sheetStack.ts
31
+ // src/core/lifecycle/sheet-stack.ts
32
32
  var BASE_Z = 100;
33
33
  var STEP = 10;
34
34
  var SheetStack = class {
@@ -153,7 +153,7 @@ function createEventBus() {
153
153
  };
154
154
  }
155
155
 
156
- // src/core/primitives/cssLength.ts
156
+ // src/core/primitives/css-length.ts
157
157
  var probe = null;
158
158
  var PROBE_ATTR = "data-bs-probe";
159
159
  var ensureProbe = (axis) => {
@@ -207,7 +207,7 @@ var resolveSnap = (point, mode, measureFit) => {
207
207
  return Math.max(0, el.getBoundingClientRect().width);
208
208
  };
209
209
 
210
- // src/core/primitives/devWarn.ts
210
+ // src/core/primitives/dev-warn.ts
211
211
  function devWarn(...args) {
212
212
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
213
213
  console.warn(...args);
@@ -372,74 +372,6 @@ var SnapResolver = class {
372
372
  }
373
373
  };
374
374
 
375
- // src/core/features/persist.ts
376
- function readPersistedId(key) {
377
- if (!key) return null;
378
- if (typeof window === "undefined") return null;
379
- if (typeof localStorage === "undefined") return null;
380
- try {
381
- return localStorage.getItem(key);
382
- } catch {
383
- return null;
384
- }
385
- }
386
- function installPersist(engine, key) {
387
- const unsubscribe = engine.on("snap", ({ id }) => {
388
- if (typeof window === "undefined") return;
389
- if (typeof localStorage === "undefined") return;
390
- try {
391
- localStorage.setItem(key, id);
392
- } catch {
393
- }
394
- });
395
- return unsubscribe;
396
- }
397
-
398
- // src/core/features/auto-collapse.ts
399
- function installAutoCollapse(deps) {
400
- const { ms } = deps;
401
- if (ms === void 0 || ms <= 0) {
402
- return () => {
403
- };
404
- }
405
- let timer = null;
406
- const clearTimer = () => {
407
- if (timer !== null) {
408
- clearTimeout(timer);
409
- timer = null;
410
- }
411
- };
412
- const fire = () => {
413
- if (deps.isDestroyed()) return;
414
- if (deps.isDragging()) return;
415
- const active = deps.resolveSnap(deps.getActiveId());
416
- if (!active || active.size === 0) return;
417
- let target = null;
418
- for (const id of deps.getAllowedIds()) {
419
- const snap = deps.resolveSnap(id);
420
- if (!snap || snap.size <= 0) continue;
421
- if (!target || snap.size < target.size) target = snap;
422
- }
423
- if (!target) return;
424
- if (target.id === active.id || target.size >= active.size) return;
425
- deps.snapTo(target.id);
426
- };
427
- const reset2 = () => {
428
- clearTimer();
429
- if (deps.isDestroyed()) return;
430
- timer = setTimeout(() => {
431
- timer = null;
432
- fire();
433
- }, ms);
434
- };
435
- const unsubscribe = deps.on("snap", () => reset2());
436
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset2();
437
- return () => {
438
- clearTimer();
439
- unsubscribe();
440
- };
441
- }
442
-
443
375
  // src/core/features/linked-sheets.ts
444
376
  function notifyLinkedSheets(sheets, self) {
445
377
  if (sheets.length === 0) return;
@@ -462,394 +394,65 @@ function notifyLinkedSheets(sheets, self) {
462
394
  }
463
395
  }
464
396
 
465
- // src/core/features/history-coordinator.ts
466
- var entries = [];
467
- var subscribers = /* @__PURE__ */ new Set();
468
- var suppress = 0;
469
- var internalDepth = 0;
470
- var pendingRebrand = null;
471
- var listenerInstalled = false;
472
- var patchInstalled = false;
473
- var origPushState = null;
474
- var origReplaceState = null;
475
- var patchedPushState = null;
476
- var patchedReplaceState = null;
477
- var hasWindow = () => typeof window !== "undefined";
478
- var hasHistory = () => typeof history !== "undefined";
479
- var brandMarker = (surface) => ({
480
- ...surface.markerState(),
481
- __bs: true
482
- });
483
- var runInternal = (fn) => {
484
- internalDepth += 1;
485
- try {
486
- fn();
487
- } finally {
488
- internalDepth -= 1;
489
- }
490
- };
491
- var notifyRouteChange = () => {
492
- const snapshot = Array.from(subscribers);
493
- for (const fn of snapshot) fn();
494
- };
495
- var ensureListener = () => {
496
- if (listenerInstalled || !hasWindow()) return;
497
- window.addEventListener("popstate", onPopState);
498
- listenerInstalled = true;
499
- };
500
- var maybeTeardownListener = () => {
501
- if (!listenerInstalled) return;
502
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
503
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
504
- listenerInstalled = false;
505
- };
506
- var installPatch = () => {
507
- if (patchInstalled || !hasHistory()) return;
508
- origPushState = history.pushState;
509
- origReplaceState = history.replaceState;
510
- patchedPushState = function(...args) {
511
- origPushState.apply(this, args);
512
- if (internalDepth === 0) notifyRouteChange();
513
- };
514
- patchedReplaceState = function(...args) {
515
- origReplaceState.apply(this, args);
516
- if (internalDepth === 0) notifyRouteChange();
517
- };
518
- history.pushState = patchedPushState;
519
- history.replaceState = patchedReplaceState;
520
- patchInstalled = true;
521
- };
522
- var removePatch = () => {
523
- if (!patchInstalled) return;
524
- if (history.pushState === patchedPushState && origPushState) {
525
- history.pushState = origPushState;
526
- }
527
- if (history.replaceState === patchedReplaceState && origReplaceState) {
528
- history.replaceState = origReplaceState;
529
- }
530
- origPushState = null;
531
- origReplaceState = null;
532
- patchedPushState = null;
533
- patchedReplaceState = null;
534
- patchInstalled = false;
535
- };
536
- function onPopState() {
537
- if (suppress > 0) {
538
- suppress -= 1;
539
- const rebrand = pendingRebrand;
540
- pendingRebrand = null;
541
- if (rebrand) rebrand();
542
- maybeTeardownListener();
543
- return;
544
- }
545
- if (entries.length === 0) {
546
- notifyRouteChange();
547
- maybeTeardownListener();
548
- return;
397
+ // src/core/features/resize-observer.ts
398
+ function installResizeObserver(deps) {
399
+ if (typeof window === "undefined") {
400
+ return () => {
401
+ };
549
402
  }
550
- let target = null;
551
- let targetIdx = -1;
552
- for (let i = entries.length - 1; i >= 0; i -= 1) {
553
- const entry = entries[i];
554
- if (!entry.surface.isOpen()) continue;
555
- if (!target) {
556
- target = entry;
557
- targetIdx = i;
403
+ const onResize = () => {
404
+ if (deps.isDestroyed()) return;
405
+ deps.recomputeSnaps();
406
+ const mode = deps.getMode();
407
+ const isVerticalAxis = mode === "bottom" || mode === "top";
408
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
409
+ const max = deps.getMaxAxisSize();
410
+ if (viewportSize2 > 0 && viewportSize2 < max) {
411
+ deps.setMaxAxisSize(viewportSize2);
558
412
  }
559
- if (entry.surface.isTop()) {
560
- target = entry;
561
- targetIdx = i;
562
- break;
413
+ const wasAnimating = deps.isAnimating?.() ?? false;
414
+ deps.cancelInFlight();
415
+ deps.newCycle();
416
+ const current = deps.resolveActiveSnap();
417
+ const newMax = deps.getMaxAxisSize();
418
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
419
+ deps.setSize(targetSize);
420
+ if (current && !deps.isDragging()) {
421
+ deps.applySize(targetSize);
422
+ if (wasAnimating) deps.resyncAfterCancel?.();
563
423
  }
424
+ };
425
+ let resizeObserver = null;
426
+ if (typeof ResizeObserver !== "undefined") {
427
+ resizeObserver = new ResizeObserver(onResize);
428
+ resizeObserver.observe(document.documentElement);
429
+ } else {
430
+ window.addEventListener("resize", onResize);
564
431
  }
565
- if (!target) {
566
- const removed = entries.pop();
567
- if (removed) removed.live = false;
568
- maybeTeardownListener();
569
- return;
570
- }
571
- target.live = false;
572
- entries.splice(targetIdx, 1);
573
- const settled = target;
574
- void Promise.resolve(settled.surface.close()).then(() => {
575
- if (settled.surface.isOpen()) restore(settled);
576
- });
577
- maybeTeardownListener();
432
+ window.addEventListener("orientationchange", onResize);
433
+ const onVisibility = () => {
434
+ if (deps.isDestroyed()) return;
435
+ if (document.hidden) deps.cancelInFlight();
436
+ };
437
+ document.addEventListener("visibilitychange", onVisibility);
438
+ return () => {
439
+ resizeObserver?.disconnect();
440
+ window.removeEventListener("resize", onResize);
441
+ window.removeEventListener("orientationchange", onResize);
442
+ document.removeEventListener("visibilitychange", onVisibility);
443
+ };
578
444
  }
579
- function restore(entry) {
580
- if (!hasWindow() || !hasHistory()) return;
581
- entry.live = true;
582
- runInternal(() => {
583
- try {
584
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
585
- } catch {
586
- entry.live = false;
587
- }
588
- });
589
- if (entry.live) {
590
- entries.push(entry);
591
- ensureListener();
592
- }
593
- }
594
- function pushBackMarker(surface) {
595
- const entry = { surface, priorUrl: void 0, live: false };
596
- if (!hasWindow() || !hasHistory()) {
597
- return entry;
598
- }
599
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
600
- runInternal(() => {
601
- try {
602
- history.pushState(brandMarker(surface), "", surface.url);
603
- entry.live = true;
604
- } catch {
605
- entry.live = false;
606
- }
607
- });
608
- if (entry.live) {
609
- entries.push(entry);
610
- ensureListener();
611
- }
612
- return entry;
613
- }
614
- function popBackMarker(handle) {
615
- if (!hasWindow() || !hasHistory()) return;
616
- const entry = handle;
617
- const idx = entries.indexOf(entry);
618
- if (idx === -1 || !entry.live) return;
619
- const last = entries.length - 1;
620
- entry.live = false;
621
- entries.splice(idx, 1);
622
- if (idx !== last) {
623
- const rebrandSurface = entries[entries.length - 1].surface;
624
- const rebrandUrl = entry.priorUrl;
625
- pendingRebrand = () => {
626
- runInternal(() => {
627
- history.replaceState(
628
- brandMarker(rebrandSurface),
629
- "",
630
- rebrandUrl !== void 0 ? rebrandUrl : location.href
631
- );
632
- });
633
- };
634
- }
635
- suppress += 1;
636
- runInternal(() => {
637
- try {
638
- history.back();
639
- } catch {
640
- suppress = Math.max(0, suppress - 1);
641
- pendingRebrand = null;
642
- }
643
- });
644
- maybeTeardownListener();
645
- }
646
- function subscribeRouteChange(fn) {
647
- if (!hasWindow() || !hasHistory()) return () => {
648
- };
649
- subscribers.add(fn);
650
- if (subscribers.size === 1) installPatch();
651
- ensureListener();
652
- return () => {
653
- if (!subscribers.has(fn)) return;
654
- subscribers.delete(fn);
655
- if (subscribers.size === 0) removePatch();
656
- maybeTeardownListener();
657
- };
658
- }
659
-
660
- // src/core/features/route.ts
661
- function installRoute(deps) {
662
- if (typeof window === "undefined") {
663
- return () => {
664
- };
665
- }
666
- if (!deps.routedTo && !deps.closeOnBack) {
667
- return () => {
668
- };
669
- }
670
- let handle = null;
671
- const onOpen = () => {
672
- if (handle) return;
673
- handle = pushBackMarker({
674
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
675
- isTop: deps.isTopSheet,
676
- close: deps.close,
677
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
678
- url: deps.routedTo
679
- });
680
- };
681
- const onClose = () => {
682
- if (!handle) return;
683
- popBackMarker(handle);
684
- handle = null;
685
- };
686
- const unsubscribeOpen = deps.on("open", onOpen);
687
- const unsubscribeClose = deps.on("close", onClose);
688
- if (deps.getSize() > 0) onOpen();
689
- return () => {
690
- unsubscribeOpen();
691
- unsubscribeClose();
692
- if (handle) {
693
- popBackMarker(handle);
694
- handle = null;
695
- }
696
- };
697
- }
698
- function installRouteChange(deps) {
699
- return subscribeRouteChange(() => {
700
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
701
- void deps.close();
702
- });
703
- }
704
-
705
- // src/core/features/content-swipe.ts
706
- var CONTENT_PULL_THRESHOLD = 60;
707
- function installContentSwipe(deps) {
708
- const { container } = deps;
709
- let startY = 0;
710
- let startScrollTop = 0;
711
- let moved = false;
712
- const onTouchStart = (e) => {
713
- const t = e.touches[0];
714
- if (!t) return;
715
- startY = t.clientY;
716
- startScrollTop = container.scrollTop;
717
- moved = false;
718
- };
719
- const onTouchMove = (e) => {
720
- const t = e.touches[0];
721
- if (!t) return;
722
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
723
- };
724
- const onTouchEnd = (e) => {
725
- if (!moved) return;
726
- if (deps.isDragging() || deps.isAnimating()) return;
727
- if (container.scrollTop !== startScrollTop) return;
728
- const t = e.changedTouches[0];
729
- if (!t) return;
730
- const deltaY = t.clientY - startY;
731
- const atTop = container.scrollTop <= 0;
732
- const allowed = deps.getAllowedIds();
733
- const idx = allowed.indexOf(deps.getActiveId());
734
- if (idx === -1) return;
735
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
736
- deps.snapTo(allowed[idx - 1]);
737
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
738
- deps.snapTo(allowed[idx + 1]);
739
- }
740
- };
741
- container.addEventListener("touchstart", onTouchStart, { passive: true });
742
- container.addEventListener("touchmove", onTouchMove, { passive: true });
743
- container.addEventListener("touchend", onTouchEnd, { passive: true });
744
- return () => {
745
- container.removeEventListener("touchstart", onTouchStart);
746
- container.removeEventListener("touchmove", onTouchMove);
747
- container.removeEventListener("touchend", onTouchEnd);
748
- };
749
- }
750
-
751
- // src/core/features/visual-viewport.ts
752
- var SAFETY_PAD = 8;
753
- function installVisualViewport(deps) {
754
- if (typeof window === "undefined" || !window.visualViewport) {
755
- return () => {
756
- };
757
- }
758
- const vv = window.visualViewport;
759
- let rafId = null;
760
- const onResize = () => {
761
- if (rafId !== null) return;
762
- rafId = requestAnimationFrame(() => {
763
- rafId = null;
764
- if (deps.isDestroyed()) return;
765
- deps.recomputeSnaps();
766
- const isVertical = deps.isVerticalAxis();
767
- const visible = isVertical ? vv.height : vv.width;
768
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
769
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
770
- if (clamp < deps.getMaxAxisSize()) {
771
- deps.setMaxAxisSize(clamp);
772
- }
773
- const wasAnimating = deps.isAnimating?.() ?? false;
774
- deps.cancelInFlight();
775
- deps.newCycle();
776
- const max = deps.getMaxAxisSize();
777
- deps.setSize(Math.min(deps.getSize(), max));
778
- const current = deps.resolveActiveSnap();
779
- if (current && !deps.isDragging()) {
780
- deps.applySize(Math.min(current.size, max));
781
- if (wasAnimating) deps.resyncAfterCancel?.();
782
- } else {
783
- deps.applySize(deps.getSize());
784
- }
785
- });
786
- };
787
- vv.addEventListener("resize", onResize);
788
- return () => {
789
- vv.removeEventListener("resize", onResize);
790
- if (rafId !== null) cancelAnimationFrame(rafId);
791
- };
792
- }
793
-
794
- // src/core/features/resize-observer.ts
795
- function installResizeObserver(deps) {
796
- if (typeof window === "undefined") {
797
- return () => {
798
- };
799
- }
800
- const onResize = () => {
801
- if (deps.isDestroyed()) return;
802
- deps.recomputeSnaps();
803
- const mode = deps.getMode();
804
- const isVerticalAxis = mode === "bottom" || mode === "top";
805
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
806
- const max = deps.getMaxAxisSize();
807
- if (viewportSize2 > 0 && viewportSize2 < max) {
808
- deps.setMaxAxisSize(viewportSize2);
809
- }
810
- const wasAnimating = deps.isAnimating?.() ?? false;
811
- deps.cancelInFlight();
812
- deps.newCycle();
813
- const current = deps.resolveActiveSnap();
814
- const newMax = deps.getMaxAxisSize();
815
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
816
- deps.setSize(targetSize);
817
- if (current && !deps.isDragging()) {
818
- deps.applySize(targetSize);
819
- if (wasAnimating) deps.resyncAfterCancel?.();
820
- }
821
- };
822
- let resizeObserver = null;
823
- if (typeof ResizeObserver !== "undefined") {
824
- resizeObserver = new ResizeObserver(onResize);
825
- resizeObserver.observe(document.documentElement);
826
- } else {
827
- window.addEventListener("resize", onResize);
828
- }
829
- window.addEventListener("orientationchange", onResize);
830
- const onVisibility = () => {
831
- if (deps.isDestroyed()) return;
832
- if (document.hidden) deps.cancelInFlight();
833
- };
834
- document.addEventListener("visibilitychange", onVisibility);
835
- return () => {
836
- resizeObserver?.disconnect();
837
- window.removeEventListener("resize", onResize);
838
- window.removeEventListener("orientationchange", onResize);
839
- document.removeEventListener("visibilitychange", onVisibility);
840
- };
841
- }
842
-
843
- // src/core/features/fit-observer.ts
844
- var contentTargets = (scroller) => {
845
- if (!scroller) return [];
846
- const hasSlot = typeof HTMLSlotElement !== "undefined";
847
- const out = [];
848
- for (const child2 of Array.from(scroller.children)) {
849
- if (hasSlot && child2 instanceof HTMLSlotElement) {
850
- for (const el of child2.assignedElements()) out.push(el);
851
- } else {
852
- out.push(child2);
445
+
446
+ // src/core/features/fit-observer.ts
447
+ var contentTargets = (scroller) => {
448
+ if (!scroller) return [];
449
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
450
+ const out = [];
451
+ for (const child2 of Array.from(scroller.children)) {
452
+ if (hasSlot && child2 instanceof HTMLSlotElement) {
453
+ for (const el of child2.assignedElements()) out.push(el);
454
+ } else {
455
+ out.push(child2);
853
456
  }
854
457
  }
855
458
  return out;
@@ -1691,6 +1294,52 @@ function resolveAnimationPreset(animation) {
1691
1294
  }
1692
1295
  }
1693
1296
 
1297
+ // src/core/animation/waapi-settle.ts
1298
+ var SIM_HZ = 240;
1299
+ var SAMPLE_STRIDE = 4;
1300
+ var MAX_SIM_MS = 3e3;
1301
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1302
+ const cfg = { ...DEFAULT_SPRING, ...config };
1303
+ const subDt = 1 / SIM_HZ;
1304
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1305
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1306
+ const values = [from];
1307
+ let x = from;
1308
+ let v = velocityPxPerS;
1309
+ for (let i = 1; i <= maxSteps; i += 1) {
1310
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1311
+ v += force / cfg.mass * subDt;
1312
+ x += v * subDt;
1313
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1314
+ values.push(to);
1315
+ break;
1316
+ }
1317
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1318
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1319
+ if (atRest) break;
1320
+ }
1321
+ if (values[values.length - 1] !== to) values.push(to);
1322
+ return {
1323
+ values,
1324
+ stepMs,
1325
+ durationMs: (values.length - 1) * stepMs
1326
+ };
1327
+ }
1328
+ function sampleTweenSettle(from, to, durationMs, easing) {
1329
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1330
+ const values = [];
1331
+ for (let i = 0; i <= count; i += 1) {
1332
+ const t = i / count;
1333
+ values.push(from + (to - from) * easing(t));
1334
+ }
1335
+ values[values.length - 1] = to;
1336
+ return {
1337
+ values,
1338
+ stepMs: durationMs / count,
1339
+ durationMs
1340
+ };
1341
+ }
1342
+
1694
1343
  // src/core/controllers/animation-runner.ts
1695
1344
  var DEFAULT_DURATION2 = 220;
1696
1345
  var VELOCITY_PX_PER_S = 1e3;
@@ -1701,6 +1350,10 @@ var AnimationRunner = class {
1701
1350
  __publicField(this, "applySizeFn");
1702
1351
  __publicField(this, "getSize");
1703
1352
  __publicField(this, "isDragging");
1353
+ __publicField(this, "applyAux");
1354
+ __publicField(this, "getTransformFor");
1355
+ __publicField(this, "settleWaapi");
1356
+ __publicField(this, "currentWaapi", null);
1704
1357
  __publicField(this, "animationKind");
1705
1358
  __publicField(this, "springConfig");
1706
1359
  __publicField(this, "duration");
@@ -1716,6 +1369,9 @@ var AnimationRunner = class {
1716
1369
  this.applySizeFn = deps.applySize;
1717
1370
  this.getSize = deps.getSize;
1718
1371
  this.isDragging = deps.isDragging;
1372
+ this.applyAux = deps.applyAux;
1373
+ this.getTransformFor = deps.getTransformFor;
1374
+ this.settleWaapi = opts.settleAnimation === "waapi";
1719
1375
  const preset = resolveAnimationPreset(opts.animation);
1720
1376
  this.animationKind = preset.kind;
1721
1377
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1740,11 +1396,20 @@ var AnimationRunner = class {
1740
1396
  }
1741
1397
  }
1742
1398
  get isAnimating() {
1743
- return this.currentTween !== null || this.currentSpring !== null;
1399
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1744
1400
  }
1745
1401
  cancel() {
1746
1402
  this.currentTween?.cancel();
1747
1403
  this.currentSpring?.cancel();
1404
+ if (this.currentWaapi) {
1405
+ const entry = this.currentWaapi;
1406
+ this.currentWaapi = null;
1407
+ entry.stop();
1408
+ try {
1409
+ entry.anim.cancel();
1410
+ } catch {
1411
+ }
1412
+ }
1748
1413
  }
1749
1414
  async animateTo(target, velocityPxPerMs) {
1750
1415
  if (target === this.getSize()) {
@@ -1763,6 +1428,27 @@ var AnimationRunner = class {
1763
1428
  if (!this.isDragging()) this.element.style.willChange = "";
1764
1429
  return;
1765
1430
  }
1431
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1432
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1433
+ this.getSize(),
1434
+ target,
1435
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1436
+ this.springConfig
1437
+ ) : sampleTweenSettle(
1438
+ this.getSize(),
1439
+ target,
1440
+ this.duration,
1441
+ this.easing
1442
+ );
1443
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1444
+ await this.runWaapiSettle(samples, target);
1445
+ if (!this.isAnimating) {
1446
+ this.getRootEl()?.removeAttribute("data-animating");
1447
+ if (!this.isDragging()) this.element.style.willChange = "";
1448
+ }
1449
+ return;
1450
+ }
1451
+ }
1766
1452
  if (this.animationKind === "spring") {
1767
1453
  const spring = runSpring({
1768
1454
  from: this.getSize(),
@@ -1791,15 +1477,70 @@ var AnimationRunner = class {
1791
1477
  if (!this.isDragging()) this.element.style.willChange = "";
1792
1478
  }
1793
1479
  }
1480
+ async runWaapiSettle(samples, target) {
1481
+ const applyAux = this.applyAux;
1482
+ const getTransformFor = this.getTransformFor;
1483
+ const frames = samples.values.map((v) => ({
1484
+ transform: getTransformFor(v)
1485
+ }));
1486
+ const anim = this.element.animate(frames, {
1487
+ duration: samples.durationMs,
1488
+ easing: "linear",
1489
+ fill: "forwards"
1490
+ });
1491
+ let stopped = false;
1492
+ let auxRaf = 0;
1493
+ const start = performance.now();
1494
+ const lastIdx = samples.values.length - 1;
1495
+ const auxTick = () => {
1496
+ if (stopped) return;
1497
+ const elapsed = performance.now() - start;
1498
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1499
+ applyAux(samples.values[idx]);
1500
+ auxRaf = requestAnimationFrame(auxTick);
1501
+ };
1502
+ auxRaf = requestAnimationFrame(auxTick);
1503
+ const entry = {
1504
+ anim,
1505
+ stop: () => {
1506
+ stopped = true;
1507
+ cancelAnimationFrame(auxRaf);
1508
+ }
1509
+ };
1510
+ this.currentWaapi = entry;
1511
+ try {
1512
+ await anim.finished;
1513
+ } catch {
1514
+ }
1515
+ entry.stop();
1516
+ const wasCurrent = this.currentWaapi === entry;
1517
+ if (wasCurrent) this.currentWaapi = null;
1518
+ if (wasCurrent && anim.playState === "finished") {
1519
+ this.applySizeFn(target);
1520
+ try {
1521
+ anim.cancel();
1522
+ } catch {
1523
+ }
1524
+ }
1525
+ }
1794
1526
  destroy() {
1795
1527
  this.detachReducedMotion?.();
1796
1528
  this.detachReducedMotion = null;
1797
1529
  this.currentTween = null;
1798
1530
  this.currentSpring = null;
1531
+ if (this.currentWaapi) {
1532
+ const entry = this.currentWaapi;
1533
+ this.currentWaapi = null;
1534
+ entry.stop();
1535
+ try {
1536
+ entry.anim.cancel();
1537
+ } catch {
1538
+ }
1539
+ }
1799
1540
  }
1800
1541
  };
1801
1542
 
1802
- // src/core/lifecycle/focusTrap.ts
1543
+ // src/core/lifecycle/focus-trap.ts
1803
1544
  var FOCUSABLE = [
1804
1545
  "a[href]",
1805
1546
  "area[href]",
@@ -1917,7 +1658,7 @@ var installFocusTrap = (container, options = {}) => {
1917
1658
  };
1918
1659
  };
1919
1660
 
1920
- // src/core/lifecycle/scrollLock.ts
1661
+ // src/core/lifecycle/scroll-lock.ts
1921
1662
  var lockCount = 0;
1922
1663
  var savedStyles = null;
1923
1664
  var lockBodyScroll = () => {
@@ -2099,30 +1840,44 @@ var SampleRing = class {
2099
1840
  this.size = 0;
2100
1841
  }
2101
1842
  };
2102
- var installGestures = (handle, mode, callbacks) => {
1843
+ var installGestures = (handle, mode, callbacks, options = {}) => {
2103
1844
  let activePointerId = null;
2104
1845
  let activePointerType = "touch";
2105
1846
  let startCoord = 0;
2106
1847
  let lastCoord = 0;
1848
+ let pendingStart = false;
2107
1849
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
2108
1850
  const MAX_SAMPLES = 32;
2109
1851
  const samples = new SampleRing(MAX_SAMPLES);
2110
1852
  const axis = isAxisVertical(mode) ? "Y" : "X";
2111
1853
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
2112
1854
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1855
+ const capture = (pointerId) => {
1856
+ try {
1857
+ handle.setPointerCapture(pointerId);
1858
+ } catch {
1859
+ }
1860
+ };
1861
+ const releaseTracking = () => {
1862
+ activePointerId = null;
1863
+ pendingStart = false;
1864
+ samples.reset();
1865
+ };
2113
1866
  const onPointerDown = (e) => {
2114
1867
  if (activePointerId !== null) return;
2115
1868
  if (e.button !== void 0 && e.button !== 0) return;
1869
+ if (options.shouldStart && !options.shouldStart(e)) return;
2116
1870
  activePointerId = e.pointerId;
2117
1871
  activePointerType = e.pointerType || "touch";
2118
1872
  startCoord = coordOf(e);
2119
1873
  lastCoord = startCoord;
2120
1874
  samples.reset();
2121
1875
  samples.push(e.timeStamp, startCoord);
2122
- try {
2123
- handle.setPointerCapture(e.pointerId);
2124
- } catch {
1876
+ if (options.deferStart) {
1877
+ pendingStart = true;
1878
+ return;
2125
1879
  }
1880
+ capture(e.pointerId);
2126
1881
  callbacks.onStart(startCoord, activePointerType);
2127
1882
  };
2128
1883
  const onPointerMove = (e) => {
@@ -2130,6 +1885,22 @@ var installGestures = (handle, mode, callbacks) => {
2130
1885
  const coord = coordOf(e);
2131
1886
  const rawDelta = coord - startCoord;
2132
1887
  const delta = rawDelta * sign;
1888
+ if (pendingStart) {
1889
+ const verdict = options.deferStart(e, delta);
1890
+ if (verdict === false) {
1891
+ releaseTracking();
1892
+ return;
1893
+ }
1894
+ if (verdict !== true) return;
1895
+ pendingStart = false;
1896
+ startCoord = coord;
1897
+ lastCoord = coord;
1898
+ samples.reset();
1899
+ samples.push(e.timeStamp, coord);
1900
+ capture(e.pointerId);
1901
+ callbacks.onStart(startCoord, activePointerType);
1902
+ return;
1903
+ }
2133
1904
  lastCoord = coord;
2134
1905
  samples.push(e.timeStamp, coord);
2135
1906
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -2138,6 +1909,10 @@ var installGestures = (handle, mode, callbacks) => {
2138
1909
  };
2139
1910
  const finishGesture = (e) => {
2140
1911
  if (e.pointerId !== activePointerId) return;
1912
+ if (pendingStart) {
1913
+ releaseTracking();
1914
+ return;
1915
+ }
2141
1916
  const rawDelta = lastCoord - startCoord;
2142
1917
  const delta = rawDelta * sign;
2143
1918
  let velocity = 0;
@@ -2160,22 +1935,26 @@ var installGestures = (handle, mode, callbacks) => {
2160
1935
  };
2161
1936
  const onPointerCancel = (e) => {
2162
1937
  if (e.pointerId !== activePointerId) return;
2163
- activePointerId = null;
2164
- samples.reset();
1938
+ const wasPending = pendingStart;
1939
+ releaseTracking();
1940
+ if (wasPending) return;
2165
1941
  callbacks.onCancel?.();
2166
1942
  };
2167
1943
  handle.addEventListener("pointerdown", onPointerDown);
2168
1944
  handle.addEventListener("pointermove", onPointerMove);
2169
1945
  handle.addEventListener("pointerup", finishGesture);
2170
1946
  handle.addEventListener("pointercancel", onPointerCancel);
1947
+ const managesTouchAction = options.manageTouchAction !== false;
2171
1948
  const prevTouchAction = handle.style.touchAction;
2172
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1949
+ if (managesTouchAction) {
1950
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1951
+ }
2173
1952
  return () => {
2174
1953
  handle.removeEventListener("pointerdown", onPointerDown);
2175
1954
  handle.removeEventListener("pointermove", onPointerMove);
2176
1955
  handle.removeEventListener("pointerup", finishGesture);
2177
1956
  handle.removeEventListener("pointercancel", onPointerCancel);
2178
- handle.style.touchAction = prevTouchAction;
1957
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
2179
1958
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
2180
1959
  handle.releasePointerCapture(activePointerId);
2181
1960
  }
@@ -2294,7 +2073,7 @@ var GestureController = class {
2294
2073
  const restoreTo = this.dragStartSize;
2295
2074
  void this.deps.animateTo(restoreTo, 0);
2296
2075
  }
2297
- });
2076
+ }, this.deps.gestureOptions);
2298
2077
  return this.detach;
2299
2078
  }
2300
2079
  forceClearDragState() {
@@ -2306,6 +2085,41 @@ var GestureController = class {
2306
2085
  }
2307
2086
  };
2308
2087
 
2088
+ // src/core/primitives/drag-zones.ts
2089
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2090
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2091
+ var asElement = (target) => {
2092
+ const candidate = target;
2093
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2094
+ };
2095
+ function isDragAllowedFrom(target, mode) {
2096
+ const el = asElement(target);
2097
+ if (!el) return mode !== "zones";
2098
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2099
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2100
+ return true;
2101
+ }
2102
+
2103
+ // src/core/primitives/content-gesture.ts
2104
+ var CONTENT_DRAG_SLOP = 6;
2105
+ function decideContentGesture(input) {
2106
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2107
+ if (Math.abs(input.delta) < slop) return "pending";
2108
+ if (input.scrollTop > 0) return "scroll";
2109
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2110
+ return "drag";
2111
+ }
2112
+
2113
+ // src/core/primitives/touch-scroll-guard.ts
2114
+ function installTouchScrollGuard(surface, isDragging) {
2115
+ const onTouchMove = (e) => {
2116
+ if (!isDragging()) return;
2117
+ if (e.cancelable) e.preventDefault();
2118
+ };
2119
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2120
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2121
+ }
2122
+
2309
2123
  // src/core/primitives/aria-slider-writer.ts
2310
2124
  var AriaSliderWriter = class {
2311
2125
  constructor(handle, mode) {
@@ -2652,7 +2466,7 @@ function resolveEngineOptions(opts, extras) {
2652
2466
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2653
2467
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2654
2468
  if (opts.persistKey) {
2655
- const restored = readPersistedId(opts.persistKey);
2469
+ const restored = readPersistedIdSafe(opts.persistKey);
2656
2470
  if (restored && initialAllowed.includes(restored)) {
2657
2471
  initialId = restored;
2658
2472
  }
@@ -2661,6 +2475,8 @@ function resolveEngineOptions(opts, extras) {
2661
2475
  mode: opts.mode ?? "bottom",
2662
2476
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2663
2477
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2478
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2479
+ dragFromContent: opts.dragFromContent ?? true,
2664
2480
  rubberBandEnabled: opts.rubberBand ?? true,
2665
2481
  closeOnBack: opts.closeOnBack ?? false,
2666
2482
  persistent: opts.persistent ?? false,
@@ -2683,6 +2499,7 @@ function resolveEngineOptions(opts, extras) {
2683
2499
  },
2684
2500
  animation: {
2685
2501
  animation: opts.animation,
2502
+ settleAnimation: opts.settleAnimation,
2686
2503
  duration: opts.duration,
2687
2504
  easing: opts.easing,
2688
2505
  spring: opts.spring,
@@ -2700,6 +2517,15 @@ function resolveEngineOptions(opts, extras) {
2700
2517
  }
2701
2518
  };
2702
2519
  }
2520
+ var readPersistedIdSafe = (key) => {
2521
+ if (typeof window === "undefined") return null;
2522
+ if (typeof localStorage === "undefined") return null;
2523
+ try {
2524
+ return localStorage.getItem(key);
2525
+ } catch {
2526
+ return null;
2527
+ }
2528
+ };
2703
2529
 
2704
2530
  // src/core/primitives/teardown-stack.ts
2705
2531
  var TeardownStack = class {
@@ -2722,9 +2548,9 @@ var TeardownStack = class {
2722
2548
  }
2723
2549
  };
2724
2550
 
2725
- // src/core/BottomSheetEngine.ts
2551
+ // src/core/BottomSheetCore.ts
2726
2552
  var HAPTIC_DURATION_MS = 8;
2727
- var BottomSheetEngine = class {
2553
+ var BottomSheetCore = class {
2728
2554
  constructor(opts) {
2729
2555
  __publicField(this, "id", nextInstanceId("bs"));
2730
2556
  __publicField(this, "element");
@@ -2736,6 +2562,8 @@ var BottomSheetEngine = class {
2736
2562
  __publicField(this, "mode");
2737
2563
  __publicField(this, "flickVelocity");
2738
2564
  __publicField(this, "dragThreshold");
2565
+ __publicField(this, "dragFromMode");
2566
+ __publicField(this, "dragFromContentDefault");
2739
2567
  __publicField(this, "rubberBandEnabled");
2740
2568
  __publicField(this, "scrim");
2741
2569
  __publicField(this, "aria");
@@ -2753,8 +2581,11 @@ var BottomSheetEngine = class {
2753
2581
  __publicField(this, "activeId");
2754
2582
  __publicField(this, "size", 0);
2755
2583
  __publicField(this, "gesture");
2584
+ __publicField(this, "contentGesture");
2585
+ __publicField(this, "detachSheetGesture", null);
2756
2586
  __publicField(this, "rootEl", null);
2757
2587
  __publicField(this, "destroyed", false);
2588
+ __publicField(this, "restClosed", false);
2758
2589
  __publicField(this, "currentViewTransition", null);
2759
2590
  __publicField(this, "currentAbort", new AbortController());
2760
2591
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2785,6 +2616,8 @@ var BottomSheetEngine = class {
2785
2616
  __publicField(this, "stackEffectPrimed", false);
2786
2617
  __publicField(this, "fitDeps");
2787
2618
  __publicField(this, "transformTemplate");
2619
+ __publicField(this, "featureList", []);
2620
+ __publicField(this, "featureCtx");
2788
2621
  const resolved = resolveEngineOptions(opts, {
2789
2622
  shouldApplyInertSiblings: () => {
2790
2623
  if (typeof document === "undefined") return false;
@@ -2805,6 +2638,8 @@ var BottomSheetEngine = class {
2805
2638
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2806
2639
  this.flickVelocity = resolved.flickVelocity;
2807
2640
  this.dragThreshold = resolved.dragThreshold;
2641
+ this.dragFromMode = resolved.dragFrom;
2642
+ this.dragFromContentDefault = resolved.dragFromContent;
2808
2643
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2809
2644
  this.scrim = new ScrimController(
2810
2645
  {
@@ -2831,7 +2666,9 @@ var BottomSheetEngine = class {
2831
2666
  getRootEl: () => this.rootEl,
2832
2667
  applySize: (size) => this.applySize(size),
2833
2668
  getSize: () => this.size,
2834
- isDragging: () => this.gesture?.isDragging ?? false
2669
+ isDragging: () => this.isDraggingAny(),
2670
+ applyAux: (size) => this.applySize(size, true),
2671
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2835
2672
  },
2836
2673
  resolved.animation
2837
2674
  );
@@ -2854,6 +2691,11 @@ var BottomSheetEngine = class {
2854
2691
  if (opts.linkedSheets) {
2855
2692
  this.linkedSheets = opts.linkedSheets;
2856
2693
  }
2694
+ const byName = /* @__PURE__ */ new Map();
2695
+ for (const feature of opts.features ?? []) {
2696
+ byName.set(feature.name, feature);
2697
+ }
2698
+ this.featureList = Array.from(byName.values());
2857
2699
  this.activeId = resolved.initialId;
2858
2700
  auditVhUsage(this.snapPointsRaw);
2859
2701
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2885,11 +2727,13 @@ var BottomSheetEngine = class {
2885
2727
  const initial = this.snaps.findById(this.activeId);
2886
2728
  if (initial) this.size = initial.size;
2887
2729
  this.applySize(this.size);
2730
+ this.setRestClosed(this.size === 0);
2888
2731
  this.updateAriaSlider();
2889
2732
  this.scrollCache = createScrollCache({
2890
2733
  scrollContainer: this.scrollContainer,
2891
2734
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2892
2735
  });
2736
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2893
2737
  this.attach();
2894
2738
  this.registerInStack();
2895
2739
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2914,60 +2758,92 @@ var BottomSheetEngine = class {
2914
2758
  (p) => p.size === "fit" || p.size === "content"
2915
2759
  ),
2916
2760
  isDestroyed: () => this.destroyed,
2917
- isDragging: () => this.gesture?.isDragging ?? false,
2761
+ isDragging: () => this.isDraggingAny(),
2918
2762
  recompute: () => this.recompute()
2919
2763
  })
2920
2764
  );
2921
2765
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2922
2766
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2923
2767
  this.applyAutoAriaLabelledBy();
2924
- if (this.closeOnRouteChange) {
2925
- this.teardowns.add(
2926
- installRouteChange({
2927
- isDestroyed: () => this.destroyed,
2928
- getSize: () => this.size,
2929
- close: () => this.close()
2930
- })
2931
- );
2932
- }
2933
- if (this.persistKey) {
2934
- this.teardowns.add(installPersist(this, this.persistKey));
2768
+ this.runFeatureStage("post");
2769
+ this.warnOrphanFeatureOptions();
2770
+ if (this.size > 0) {
2771
+ this.handleOpen();
2935
2772
  }
2936
- this.teardowns.add(
2937
- installAutoCollapse({
2938
- ms: opts.autoCollapseAfter,
2939
- isDestroyed: () => this.destroyed,
2940
- isDragging: () => this.gesture?.isDragging ?? false,
2941
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2942
- getActiveId: () => this.activeId,
2943
- resolveSnap: (id) => this.snaps.findById(id),
2944
- snapTo: (id) => {
2945
- void this.snapTo(id);
2946
- },
2947
- on: (event, fn) => this.on(event, fn)
2948
- })
2949
- );
2950
- this.teardowns.add(
2951
- installRoute({
2773
+ }
2774
+ buildFeatureContext(autoCollapseAfter) {
2775
+ return {
2776
+ element: this.element,
2777
+ scrollContainer: this.scrollContainer,
2778
+ sheetId: this.id,
2779
+ options: {
2952
2780
  routedTo: this.routedTo,
2953
2781
  closeOnBack: this.closeOnBack,
2954
- isTopSheet: () => this.isTopSheet,
2955
- getSize: () => this.size,
2956
- isDestroyed: () => this.destroyed,
2957
- close: () => this.close("back"),
2958
- on: (event, fn) => this.on(event, fn),
2959
- sheetId: this.id
2960
- })
2961
- );
2962
- if (this.size > 0) {
2963
- this.handleOpen();
2782
+ closeOnRouteChange: this.closeOnRouteChange,
2783
+ persistKey: this.persistKey,
2784
+ autoCollapseAfter
2785
+ },
2786
+ isDestroyed: () => this.destroyed,
2787
+ isDragging: () => this.isDraggingAny(),
2788
+ isAnimating: () => this.animation.isAnimating,
2789
+ isTopSheet: () => this.isTopSheet,
2790
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2791
+ getSize: () => this.size,
2792
+ getActiveId: () => this.activeId,
2793
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2794
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2795
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2796
+ resolveSnap: (id) => this.snaps.findById(id),
2797
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2798
+ setSize: (size) => {
2799
+ this.size = size;
2800
+ },
2801
+ setMaxAxisSize: (size) => {
2802
+ this.snaps.setMaxAxisSize(size);
2803
+ },
2804
+ applySize: (size) => this.applySize(size),
2805
+ recomputeSnaps: () => this.recomputeSnaps(),
2806
+ newCycle: () => {
2807
+ this.newCycle();
2808
+ },
2809
+ cancelInFlight: () => {
2810
+ this.animation.cancel();
2811
+ },
2812
+ resyncAfterResize: () => this.resyncAfterResize(),
2813
+ snapTo: (id) => {
2814
+ void this.snapTo(id);
2815
+ },
2816
+ close: (reason) => this.close(reason),
2817
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2818
+ on: (event, fn) => this.on(event, fn),
2819
+ addTeardown: (fn) => this.teardowns.add(fn)
2820
+ };
2821
+ }
2822
+ runFeatureStage(stage) {
2823
+ for (const feature of this.featureList) {
2824
+ if ((feature.stage ?? "post") !== stage) continue;
2825
+ const teardown = feature.install(this.featureCtx);
2826
+ if (teardown) this.teardowns.add(teardown);
2827
+ }
2828
+ }
2829
+ warnOrphanFeatureOptions() {
2830
+ const names = new Set(this.featureList.map((f) => f.name));
2831
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2832
+ devWarn(
2833
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2834
+ );
2835
+ }
2836
+ if (this.persistKey && !names.has("persist")) {
2837
+ devWarn(
2838
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2839
+ );
2964
2840
  }
2965
2841
  }
2966
2842
  get state() {
2967
2843
  return {
2968
2844
  size: this.size,
2969
2845
  activeId: this.activeId,
2970
- isDragging: this.gesture?.isDragging ?? false,
2846
+ isDragging: this.isDraggingAny(),
2971
2847
  isAnimating: this.animation.isAnimating,
2972
2848
  progress: this.computeProgress(this.size)
2973
2849
  };
@@ -3087,9 +2963,16 @@ var BottomSheetEngine = class {
3087
2963
  progress: this.computeProgress(this.size)
3088
2964
  });
3089
2965
  if (withHaptic) this.haptic();
2966
+ this.setRestClosed(targetSize === 0);
3090
2967
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
3091
2968
  if (targetSize === 0) this.emitCloseSequence();
3092
2969
  }
2970
+ setRestClosed(closed) {
2971
+ if (closed === this.restClosed) return;
2972
+ this.restClosed = closed;
2973
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2974
+ else this.element.removeAttribute("data-bs-rest");
2975
+ }
3093
2976
  emitOpenSequence(id) {
3094
2977
  this.emit("open", { id });
3095
2978
  this.handleOpen();
@@ -3145,6 +3028,20 @@ var BottomSheetEngine = class {
3145
3028
  if (this.destroyed) return;
3146
3029
  this.disableDragFlag = value;
3147
3030
  }
3031
+ setDragFromContent(value) {
3032
+ if (this.destroyed) return;
3033
+ this.dragFromContentDefault = value;
3034
+ }
3035
+ setDragFrom(mode) {
3036
+ if (this.destroyed || mode === this.dragFromMode) return;
3037
+ this.gesture?.forceClearDragState();
3038
+ this.detachSheetGesture?.();
3039
+ this.dragFromMode = mode;
3040
+ this.mountSheetGesture();
3041
+ }
3042
+ getDragFrom() {
3043
+ return this.dragFromMode;
3044
+ }
3148
3045
  isTop() {
3149
3046
  return this.isTopSheet;
3150
3047
  }
@@ -3299,12 +3196,24 @@ var BottomSheetEngine = class {
3299
3196
  auditVhUsage(points);
3300
3197
  this.auditDuplicateSnapIds(points);
3301
3198
  this.snaps.setRaw(points);
3302
- if (allowed) this.snaps.setAllowedIds(allowed);
3199
+ if (allowed) {
3200
+ this.snaps.setAllowedIds(allowed);
3201
+ } else {
3202
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3203
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3204
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3205
+ }
3303
3206
  this.scrollCache.clear();
3304
3207
  this.scrim.invalidateOpacityCache();
3305
3208
  const current = this.snaps.findById(this.activeId);
3306
3209
  if (current) {
3307
3210
  this.applySize(current.size);
3211
+ } else {
3212
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3213
+ if (fallback) {
3214
+ this.activeId = fallback.id;
3215
+ this.applySize(fallback.size);
3216
+ }
3308
3217
  }
3309
3218
  this.updateAriaSlider();
3310
3219
  }
@@ -3314,7 +3223,7 @@ var BottomSheetEngine = class {
3314
3223
  this.snaps.recompute();
3315
3224
  this.maxHeight.clampTo();
3316
3225
  this.scrim.invalidateOpacityCache();
3317
- if (this.gesture?.isDragging) return;
3226
+ if (this.isDraggingAny()) return;
3318
3227
  this.healActiveSnap();
3319
3228
  }
3320
3229
  healActiveSnap() {
@@ -3336,6 +3245,8 @@ var BottomSheetEngine = class {
3336
3245
  this.bus.clear();
3337
3246
  this.animation.cancel();
3338
3247
  this.gesture?.forceClearDragState();
3248
+ this.contentGesture?.forceClearDragState();
3249
+ this.setRestClosed(false);
3339
3250
  this.teardowns.drain();
3340
3251
  this.scrim.destroy();
3341
3252
  this.animation.destroy();
@@ -3448,11 +3359,12 @@ var BottomSheetEngine = class {
3448
3359
  const scale = Math.max(1 - depth * 0.04, 0.86);
3449
3360
  this.element.style.scale = scale === 1 ? "" : String(scale);
3450
3361
  }
3451
- attach() {
3452
- this.gesture = new GestureController({
3453
- handle: this.handle,
3362
+ buildGestureDeps(surface, gestureOptions) {
3363
+ return {
3364
+ handle: surface,
3454
3365
  element: this.element,
3455
3366
  mode: this.mode,
3367
+ gestureOptions,
3456
3368
  getRoot: () => this.rootEl,
3457
3369
  getDragContext: () => {
3458
3370
  const buf = this.dragContextBuf;
@@ -3471,8 +3383,88 @@ var BottomSheetEngine = class {
3471
3383
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3472
3384
  emit: (event, payload) => this.emit(event, payload),
3473
3385
  listenerCount: (event) => this.bus.listenerCount(event)
3386
+ };
3387
+ }
3388
+ isContentDragAllowed() {
3389
+ if (this.disableDragFlag) return false;
3390
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3391
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3392
+ }
3393
+ decideContentDrag(surface, e, delta) {
3394
+ if (this.destroyed) return false;
3395
+ if (e.pointerType === "mouse") return false;
3396
+ if (this.gesture?.isDragging) return false;
3397
+ if (!this.isContentDragAllowed()) return false;
3398
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3399
+ const decision = decideContentGesture({
3400
+ delta,
3401
+ scrollTop: surface.scrollTop,
3402
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3403
+ });
3404
+ if (decision === "pending") return null;
3405
+ return decision === "drag";
3406
+ }
3407
+ attachDragSurface(surface, kind) {
3408
+ if (this.destroyed || kind !== "content") return;
3409
+ if (this.contentGesture) return;
3410
+ const controller = new GestureController(
3411
+ this.buildGestureDeps(surface, {
3412
+ manageTouchAction: false,
3413
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3414
+ })
3415
+ );
3416
+ this.contentGesture = controller;
3417
+ const detachGestures = controller.install();
3418
+ const detachGuard = installTouchScrollGuard(
3419
+ surface,
3420
+ () => controller.isDragging
3421
+ );
3422
+ return () => {
3423
+ detachGuard();
3424
+ detachGestures();
3425
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3426
+ };
3427
+ }
3428
+ sheetDragSurface() {
3429
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3430
+ }
3431
+ canStartSheetDrag(e) {
3432
+ if (this.contentGesture?.isDragging) return false;
3433
+ const target = e.target;
3434
+ const scoped = typeof target?.closest === "function";
3435
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3436
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3437
+ return true;
3438
+ }
3439
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3440
+ if (this.dragFromMode === "handle") return true;
3441
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3442
+ return false;
3443
+ }
3444
+ return true;
3445
+ }
3446
+ mountSheetGesture() {
3447
+ const surface = this.sheetDragSurface();
3448
+ const controller = new GestureController(
3449
+ this.buildGestureDeps(surface, {
3450
+ shouldStart: (e) => this.canStartSheetDrag(e),
3451
+ manageTouchAction: this.dragFromMode === "handle"
3452
+ })
3453
+ );
3454
+ this.gesture = controller;
3455
+ const detachGestures = controller.install();
3456
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3457
+ this.detachSheetGesture = () => {
3458
+ detachGuard?.();
3459
+ detachGestures();
3460
+ };
3461
+ }
3462
+ attach() {
3463
+ this.mountSheetGesture();
3464
+ this.teardowns.add(() => {
3465
+ this.detachSheetGesture?.();
3466
+ this.detachSheetGesture = null;
3474
3467
  });
3475
- this.teardowns.add(this.gesture.install());
3476
3468
  try {
3477
3469
  this.attachFeatures();
3478
3470
  } catch (err) {
@@ -3503,24 +3495,13 @@ var BottomSheetEngine = class {
3503
3495
  }
3504
3496
  }
3505
3497
  attachFeatures() {
3506
- if (this.scrollContainer) {
3507
- this.teardowns.add(installContentSwipe({
3508
- container: this.scrollContainer,
3509
- isDragging: () => this.gesture?.isDragging ?? false,
3510
- isAnimating: () => this.animation.isAnimating,
3511
- getAllowedIds: () => this.allowedIdsBySize(),
3512
- getActiveId: () => this.activeId,
3513
- snapTo: (id) => {
3514
- void this.snapTo(id);
3515
- }
3516
- }));
3517
- }
3498
+ this.runFeatureStage("attach");
3518
3499
  if (typeof window !== "undefined") {
3519
3500
  this.teardowns.add(installResizeObserver({
3520
3501
  element: this.element,
3521
3502
  getMode: () => this.mode,
3522
3503
  isDestroyed: () => this.destroyed,
3523
- isDragging: () => this.gesture?.isDragging ?? false,
3504
+ isDragging: () => this.isDraggingAny(),
3524
3505
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3525
3506
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3526
3507
  getSize: () => this.size,
@@ -3554,31 +3535,6 @@ var BottomSheetEngine = class {
3554
3535
  () => document.removeEventListener("keydown", onKey)
3555
3536
  );
3556
3537
  }
3557
- this.teardowns.add(installVisualViewport({
3558
- element: this.element,
3559
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3560
- isDestroyed: () => this.destroyed,
3561
- isDragging: () => this.gesture?.isDragging ?? false,
3562
- recomputeSnaps: () => this.recomputeSnaps(),
3563
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3564
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3565
- getSize: () => this.size,
3566
- setMaxAxisSize: (size) => {
3567
- this.snaps.setMaxAxisSize(size);
3568
- },
3569
- setSize: (size) => {
3570
- this.size = size;
3571
- },
3572
- applySize: (size) => this.applySize(size),
3573
- cancelInFlight: () => {
3574
- this.animation.cancel();
3575
- },
3576
- newCycle: () => {
3577
- this.newCycle();
3578
- },
3579
- isAnimating: () => this.animation.isAnimating,
3580
- resyncAfterCancel: () => this.resyncAfterResize()
3581
- }));
3582
3538
  }
3583
3539
  this.teardowns.add(installSliderKeyboard({
3584
3540
  handle: this.handle,
@@ -3619,123 +3575,591 @@ var BottomSheetEngine = class {
3619
3575
  }
3620
3576
  return;
3621
3577
  }
3622
- const previousSize = this.size;
3623
- const previousSnapSize = this.snaps.findById(previousId)?.size;
3624
- this.scrollCache.cache(previousId, previousSize, target.size);
3625
- this.activeId = target.id;
3626
- if (previousSnapSize === 0 && target.size > 0) {
3627
- this.opening = true;
3628
- sheetStack.promote(this.id);
3578
+ const previousSize = this.size;
3579
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
3580
+ this.scrollCache.cache(previousId, previousSize, target.size);
3581
+ this.activeId = target.id;
3582
+ if (previousSnapSize === 0 && target.size > 0) {
3583
+ this.opening = true;
3584
+ sheetStack.promote(this.id);
3585
+ }
3586
+ const settleCap = this.snaps.getMaxAxisSize();
3587
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3588
+ this.allowOvershoot = true;
3589
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3590
+ if (signal.aborted) return;
3591
+ this.allowOvershoot = false;
3592
+ this.opening = false;
3593
+ this.completeSnap(
3594
+ target.id,
3595
+ target.size,
3596
+ previousSize,
3597
+ previousSnapSize === 0,
3598
+ true
3599
+ );
3600
+ });
3601
+ }
3602
+ isDraggingAny() {
3603
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3604
+ }
3605
+ clearWillChangeIfIdle() {
3606
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3607
+ this.element.style.willChange = "";
3608
+ }
3609
+ haptic() {
3610
+ if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3611
+ try {
3612
+ navigator.vibrate(HAPTIC_DURATION_MS);
3613
+ } catch {
3614
+ }
3615
+ }
3616
+ }
3617
+ applySize(size, skipTransform = false) {
3618
+ const cap = this.snaps.getMaxAxisSize();
3619
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3620
+ this.size = clamped;
3621
+ if (clamped > 0) this.setRestClosed(false);
3622
+ const offset = cap - clamped;
3623
+ const style = this.element.style;
3624
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3625
+ if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3626
+ style.setProperty("--bs-size", `${clamped}px`);
3627
+ if (this.scrimParent) {
3628
+ this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
3629
+ }
3630
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3631
+ this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
3632
+ }
3633
+ if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3634
+ this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
3635
+ }
3636
+ }
3637
+ const progress = this.computeProgress(clamped);
3638
+ const progressChanged = this.progressWriteSentinel.shouldWrite(
3639
+ progress,
3640
+ OPACITY_WRITE_EPSILON
3641
+ );
3642
+ if (progressChanged) {
3643
+ style.setProperty("--bs-progress", String(progress));
3644
+ if (this.anchorHost) {
3645
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3646
+ }
3647
+ if (this.rootEl && this.rootEl !== this.anchorHost) {
3648
+ this.rootEl.style.setProperty("--bs-progress", String(progress));
3649
+ }
3650
+ }
3651
+ this.scrim.applyOpacity(progress, progressChanged);
3652
+ if (progressChanged && this.bus.listenerCount("progress") > 0) {
3653
+ this.progressPayload.value = progress;
3654
+ this.progressPayload.size = clamped;
3655
+ this.emit("progress", this.progressPayload);
3656
+ }
3657
+ }
3658
+ computeProgress(size) {
3659
+ const { min, max } = this.getAllowedRange();
3660
+ const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3661
+ if (reachableMax <= min) return 0;
3662
+ return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3663
+ }
3664
+ handleOpen() {
3665
+ sheetStack.promote(this.id);
3666
+ try {
3667
+ this.lifecycle.install();
3668
+ } catch (err) {
3669
+ if (!this.destroyed) this.newCycle();
3670
+ this.handleClose();
3671
+ this.applySize(0);
3672
+ this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3673
+ this.size = 0;
3674
+ queueMicrotask(() => {
3675
+ throw err;
3676
+ });
3677
+ }
3678
+ }
3679
+ resyncAfterResize() {
3680
+ if (this.destroyed) return;
3681
+ const target = this.snaps.findById(this.activeId);
3682
+ if (!target) return;
3683
+ this.updateAriaSlider();
3684
+ this.emit("snap", {
3685
+ id: target.id,
3686
+ size: this.size,
3687
+ progress: this.computeProgress(this.size)
3688
+ });
3689
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3690
+ this.emitOpenSequence(target.id);
3691
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3692
+ this.emitCloseSequence();
3693
+ }
3694
+ }
3695
+ handleClose() {
3696
+ this.opening = false;
3697
+ this.lifecycle.release();
3698
+ sheetStack.update();
3699
+ }
3700
+ };
3701
+
3702
+ // src/core/features/history-coordinator.ts
3703
+ var entries = [];
3704
+ var subscribers = /* @__PURE__ */ new Set();
3705
+ var suppress = 0;
3706
+ var internalDepth = 0;
3707
+ var pendingRebrands = [];
3708
+ var listenerInstalled = false;
3709
+ var patchInstalled = false;
3710
+ var origPushState = null;
3711
+ var origReplaceState = null;
3712
+ var patchedPushState = null;
3713
+ var patchedReplaceState = null;
3714
+ var hasWindow = () => typeof window !== "undefined";
3715
+ var hasHistory = () => typeof history !== "undefined";
3716
+ var brandMarker = (surface) => ({
3717
+ ...surface.markerState(),
3718
+ __bs: true
3719
+ });
3720
+ var runInternal = (fn) => {
3721
+ internalDepth += 1;
3722
+ try {
3723
+ fn();
3724
+ } finally {
3725
+ internalDepth -= 1;
3726
+ }
3727
+ };
3728
+ var notifyRouteChange = () => {
3729
+ const snapshot = Array.from(subscribers);
3730
+ for (const fn of snapshot) fn();
3731
+ };
3732
+ var ensureListener = () => {
3733
+ if (listenerInstalled || !hasWindow()) return;
3734
+ window.addEventListener("popstate", onPopState);
3735
+ listenerInstalled = true;
3736
+ };
3737
+ var maybeTeardownListener = () => {
3738
+ if (!listenerInstalled) return;
3739
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3740
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3741
+ listenerInstalled = false;
3742
+ };
3743
+ var installPatch = () => {
3744
+ if (patchInstalled || !hasHistory()) return;
3745
+ origPushState = history.pushState;
3746
+ origReplaceState = history.replaceState;
3747
+ patchedPushState = function(...args) {
3748
+ origPushState.apply(this, args);
3749
+ if (internalDepth === 0) notifyRouteChange();
3750
+ };
3751
+ patchedReplaceState = function(...args) {
3752
+ origReplaceState.apply(this, args);
3753
+ if (internalDepth === 0) notifyRouteChange();
3754
+ };
3755
+ history.pushState = patchedPushState;
3756
+ history.replaceState = patchedReplaceState;
3757
+ patchInstalled = true;
3758
+ };
3759
+ var removePatch = () => {
3760
+ if (!patchInstalled) return;
3761
+ if (history.pushState === patchedPushState && origPushState) {
3762
+ history.pushState = origPushState;
3763
+ }
3764
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3765
+ history.replaceState = origReplaceState;
3766
+ }
3767
+ origPushState = null;
3768
+ origReplaceState = null;
3769
+ patchedPushState = null;
3770
+ patchedReplaceState = null;
3771
+ patchInstalled = false;
3772
+ };
3773
+ function onPopState() {
3774
+ if (suppress > 0) {
3775
+ suppress -= 1;
3776
+ const rebrand = pendingRebrands.shift();
3777
+ if (rebrand) rebrand();
3778
+ maybeTeardownListener();
3779
+ return;
3780
+ }
3781
+ if (entries.length === 0) {
3782
+ notifyRouteChange();
3783
+ maybeTeardownListener();
3784
+ return;
3785
+ }
3786
+ let target = null;
3787
+ let targetIdx = -1;
3788
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3789
+ const entry = entries[i];
3790
+ if (!entry.surface.isOpen()) continue;
3791
+ if (!target) {
3792
+ target = entry;
3793
+ targetIdx = i;
3794
+ }
3795
+ if (entry.surface.isTop()) {
3796
+ target = entry;
3797
+ targetIdx = i;
3798
+ break;
3799
+ }
3800
+ }
3801
+ if (!target) {
3802
+ const removed = entries.pop();
3803
+ if (removed) removed.live = false;
3804
+ maybeTeardownListener();
3805
+ return;
3806
+ }
3807
+ target.live = false;
3808
+ entries.splice(targetIdx, 1);
3809
+ const settled = target;
3810
+ void Promise.resolve(settled.surface.close()).then(() => {
3811
+ if (settled.surface.isOpen()) restore(settled);
3812
+ });
3813
+ maybeTeardownListener();
3814
+ }
3815
+ function restore(entry) {
3816
+ if (!hasWindow() || !hasHistory()) return;
3817
+ entry.live = true;
3818
+ runInternal(() => {
3819
+ try {
3820
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3821
+ } catch {
3822
+ entry.live = false;
3823
+ }
3824
+ });
3825
+ if (entry.live) {
3826
+ entries.push(entry);
3827
+ ensureListener();
3828
+ }
3829
+ }
3830
+ function pushBackMarker(surface) {
3831
+ const entry = { surface, priorUrl: void 0, live: false };
3832
+ if (!hasWindow() || !hasHistory()) {
3833
+ return entry;
3834
+ }
3835
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3836
+ runInternal(() => {
3837
+ try {
3838
+ history.pushState(brandMarker(surface), "", surface.url);
3839
+ entry.live = true;
3840
+ } catch {
3841
+ entry.live = false;
3842
+ }
3843
+ });
3844
+ if (entry.live) {
3845
+ entries.push(entry);
3846
+ ensureListener();
3847
+ }
3848
+ return entry;
3849
+ }
3850
+ function popBackMarker(handle) {
3851
+ if (!hasWindow() || !hasHistory()) return;
3852
+ const entry = handle;
3853
+ const idx = entries.indexOf(entry);
3854
+ if (idx === -1 || !entry.live) return;
3855
+ const last = entries.length - 1;
3856
+ entry.live = false;
3857
+ entries.splice(idx, 1);
3858
+ let rebrand = null;
3859
+ if (idx !== last) {
3860
+ const rebrandSurface = entries[entries.length - 1].surface;
3861
+ const rebrandUrl = entry.priorUrl;
3862
+ rebrand = () => {
3863
+ runInternal(() => {
3864
+ history.replaceState(
3865
+ brandMarker(rebrandSurface),
3866
+ "",
3867
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3868
+ );
3869
+ });
3870
+ };
3871
+ }
3872
+ pendingRebrands.push(rebrand);
3873
+ suppress += 1;
3874
+ runInternal(() => {
3875
+ try {
3876
+ history.back();
3877
+ } catch {
3878
+ suppress = Math.max(0, suppress - 1);
3879
+ pendingRebrands.pop();
3880
+ }
3881
+ });
3882
+ maybeTeardownListener();
3883
+ }
3884
+ function subscribeRouteChange(fn) {
3885
+ if (!hasWindow() || !hasHistory()) return () => {
3886
+ };
3887
+ subscribers.add(fn);
3888
+ if (subscribers.size === 1) installPatch();
3889
+ ensureListener();
3890
+ return () => {
3891
+ if (!subscribers.has(fn)) return;
3892
+ subscribers.delete(fn);
3893
+ if (subscribers.size === 0) removePatch();
3894
+ maybeTeardownListener();
3895
+ };
3896
+ }
3897
+
3898
+ // src/core/features/route.ts
3899
+ function installRoute(deps) {
3900
+ if (typeof window === "undefined") {
3901
+ return () => {
3902
+ };
3903
+ }
3904
+ if (!deps.routedTo && !deps.closeOnBack) {
3905
+ return () => {
3906
+ };
3907
+ }
3908
+ let handle = null;
3909
+ const onOpen = () => {
3910
+ if (handle) return;
3911
+ handle = pushBackMarker({
3912
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3913
+ isTop: deps.isTopSheet,
3914
+ close: deps.close,
3915
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3916
+ url: deps.routedTo
3917
+ });
3918
+ };
3919
+ const onClose = () => {
3920
+ if (!handle) return;
3921
+ popBackMarker(handle);
3922
+ handle = null;
3923
+ };
3924
+ const unsubscribeOpen = deps.on("open", onOpen);
3925
+ const unsubscribeClose = deps.on("close", onClose);
3926
+ if (deps.getSize() > 0) onOpen();
3927
+ return () => {
3928
+ unsubscribeOpen();
3929
+ unsubscribeClose();
3930
+ if (handle) {
3931
+ popBackMarker(handle);
3932
+ handle = null;
3933
+ }
3934
+ };
3935
+ }
3936
+ function installRouteChange(deps) {
3937
+ return subscribeRouteChange(() => {
3938
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3939
+ void deps.close();
3940
+ });
3941
+ }
3942
+
3943
+ // src/core/features/persist.ts
3944
+ function installPersist(engine, key) {
3945
+ const unsubscribe = engine.on("snap", ({ id }) => {
3946
+ if (typeof window === "undefined") return;
3947
+ if (typeof localStorage === "undefined") return;
3948
+ try {
3949
+ localStorage.setItem(key, id);
3950
+ } catch {
3629
3951
  }
3630
- const settleCap = this.snaps.getMaxAxisSize();
3631
- const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3632
- this.allowOvershoot = true;
3633
- void this.animation.animateTo(settleSize, velocity).then(() => {
3634
- if (signal.aborted) return;
3635
- this.allowOvershoot = false;
3636
- this.opening = false;
3637
- this.completeSnap(
3638
- target.id,
3639
- target.size,
3640
- previousSize,
3641
- previousSnapSize === 0,
3642
- true
3643
- );
3644
- });
3645
- }
3646
- clearWillChangeIfIdle() {
3647
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3648
- this.element.style.willChange = "";
3952
+ });
3953
+ return unsubscribe;
3954
+ }
3955
+
3956
+ // src/core/features/auto-collapse.ts
3957
+ function installAutoCollapse(deps) {
3958
+ const { ms } = deps;
3959
+ if (ms === void 0 || ms <= 0) {
3960
+ return () => {
3961
+ };
3649
3962
  }
3650
- haptic() {
3651
- if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3652
- try {
3653
- navigator.vibrate(HAPTIC_DURATION_MS);
3654
- } catch {
3655
- }
3963
+ let timer = null;
3964
+ const clearTimer = () => {
3965
+ if (timer !== null) {
3966
+ clearTimeout(timer);
3967
+ timer = null;
3968
+ }
3969
+ };
3970
+ const fire = () => {
3971
+ if (deps.isDestroyed()) return;
3972
+ if (deps.isDragging()) return;
3973
+ const active = deps.resolveSnap(deps.getActiveId());
3974
+ if (!active || active.size === 0) return;
3975
+ let target = null;
3976
+ for (const id of deps.getAllowedIds()) {
3977
+ const snap = deps.resolveSnap(id);
3978
+ if (!snap || snap.size <= 0) continue;
3979
+ if (!target || snap.size < target.size) target = snap;
3656
3980
  }
3981
+ if (!target) return;
3982
+ if (target.id === active.id || target.size >= active.size) return;
3983
+ deps.snapTo(target.id);
3984
+ };
3985
+ const reset2 = () => {
3986
+ clearTimer();
3987
+ if (deps.isDestroyed()) return;
3988
+ timer = setTimeout(() => {
3989
+ timer = null;
3990
+ fire();
3991
+ }, ms);
3992
+ };
3993
+ const unsubscribe = deps.on("snap", () => reset2());
3994
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset2();
3995
+ return () => {
3996
+ clearTimer();
3997
+ unsubscribe();
3998
+ };
3999
+ }
4000
+
4001
+ // src/core/features/content-drag.ts
4002
+ function installContentDrag(deps) {
4003
+ const detach = deps.attachDragSurface(deps.container, "content");
4004
+ return () => detach?.();
4005
+ }
4006
+
4007
+ // src/core/features/visual-viewport.ts
4008
+ var SAFETY_PAD = 8;
4009
+ function installVisualViewport(deps) {
4010
+ if (typeof window === "undefined" || !window.visualViewport) {
4011
+ return () => {
4012
+ };
3657
4013
  }
3658
- applySize(size) {
3659
- const cap = this.snaps.getMaxAxisSize();
3660
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3661
- this.size = clamped;
3662
- const offset = cap - clamped;
3663
- const style = this.element.style;
3664
- style.transform = this.transformTemplate(offset);
3665
- if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3666
- style.setProperty("--bs-size", `${clamped}px`);
3667
- if (this.scrimParent) {
3668
- this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
4014
+ const vv = window.visualViewport;
4015
+ let rafId = null;
4016
+ const onResize = () => {
4017
+ if (rafId !== null) return;
4018
+ rafId = requestAnimationFrame(() => {
4019
+ rafId = null;
4020
+ if (deps.isDestroyed()) return;
4021
+ deps.recomputeSnaps();
4022
+ const isVertical = deps.isVerticalAxis();
4023
+ const visible = isVertical ? vv.height : vv.width;
4024
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
4025
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
4026
+ if (clamp < deps.getMaxAxisSize()) {
4027
+ deps.setMaxAxisSize(clamp);
3669
4028
  }
3670
- if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3671
- this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
4029
+ const wasAnimating = deps.isAnimating?.() ?? false;
4030
+ deps.cancelInFlight();
4031
+ deps.newCycle();
4032
+ const max = deps.getMaxAxisSize();
4033
+ deps.setSize(Math.min(deps.getSize(), max));
4034
+ const current = deps.resolveActiveSnap();
4035
+ if (current && !deps.isDragging()) {
4036
+ deps.applySize(Math.min(current.size, max));
4037
+ if (wasAnimating) deps.resyncAfterCancel?.();
4038
+ } else {
4039
+ deps.applySize(deps.getSize());
3672
4040
  }
3673
- if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3674
- this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
4041
+ });
4042
+ };
4043
+ vv.addEventListener("resize", onResize);
4044
+ return () => {
4045
+ vv.removeEventListener("resize", onResize);
4046
+ if (rafId !== null) cancelAnimationFrame(rafId);
4047
+ };
4048
+ }
4049
+
4050
+ // src/core/features/engine-features.ts
4051
+ function routeFeature() {
4052
+ return {
4053
+ name: "route",
4054
+ install: (ctx) => {
4055
+ const teardowns = new TeardownStack();
4056
+ if (ctx.options.closeOnRouteChange) {
4057
+ teardowns.add(
4058
+ installRouteChange({
4059
+ isDestroyed: ctx.isDestroyed,
4060
+ getSize: ctx.getSize,
4061
+ close: () => ctx.close()
4062
+ })
4063
+ );
3675
4064
  }
4065
+ teardowns.add(
4066
+ installRoute({
4067
+ routedTo: ctx.options.routedTo,
4068
+ closeOnBack: ctx.options.closeOnBack,
4069
+ isTopSheet: ctx.isTopSheet,
4070
+ getSize: ctx.getSize,
4071
+ isDestroyed: ctx.isDestroyed,
4072
+ close: () => ctx.close("back"),
4073
+ on: ctx.on,
4074
+ sheetId: ctx.sheetId
4075
+ })
4076
+ );
4077
+ return () => teardowns.drain();
3676
4078
  }
3677
- const progress = this.computeProgress(clamped);
3678
- const progressChanged = this.progressWriteSentinel.shouldWrite(
3679
- progress,
3680
- OPACITY_WRITE_EPSILON
3681
- );
3682
- if (progressChanged) {
3683
- style.setProperty("--bs-progress", String(progress));
3684
- if (this.anchorHost) {
3685
- this.anchorHost.style.setProperty("--bs-progress", String(progress));
3686
- }
3687
- if (this.rootEl && this.rootEl !== this.anchorHost) {
3688
- this.rootEl.style.setProperty("--bs-progress", String(progress));
3689
- }
4079
+ };
4080
+ }
4081
+ function persistFeature() {
4082
+ return {
4083
+ name: "persist",
4084
+ install: (ctx) => {
4085
+ const key = ctx.options.persistKey;
4086
+ if (!key) return;
4087
+ return installPersist({ on: ctx.on }, key);
3690
4088
  }
3691
- this.scrim.applyOpacity(progress, progressChanged);
3692
- if (progressChanged && this.bus.listenerCount("progress") > 0) {
3693
- this.progressPayload.value = progress;
3694
- this.progressPayload.size = clamped;
3695
- this.emit("progress", this.progressPayload);
4089
+ };
4090
+ }
4091
+ function autoCollapseFeature() {
4092
+ return {
4093
+ name: "auto-collapse",
4094
+ install: (ctx) => installAutoCollapse({
4095
+ ms: ctx.options.autoCollapseAfter,
4096
+ isDestroyed: ctx.isDestroyed,
4097
+ isDragging: ctx.isDragging,
4098
+ getAllowedIds: ctx.getAllowedIds,
4099
+ getActiveId: ctx.getActiveId,
4100
+ resolveSnap: ctx.resolveSnap,
4101
+ snapTo: ctx.snapTo,
4102
+ on: ctx.on
4103
+ })
4104
+ };
4105
+ }
4106
+ function contentSwipeFeature() {
4107
+ return {
4108
+ name: "content-swipe",
4109
+ stage: "attach",
4110
+ install: (ctx) => {
4111
+ if (!ctx.scrollContainer) return;
4112
+ return installContentDrag({
4113
+ container: ctx.scrollContainer,
4114
+ attachDragSurface: ctx.attachDragSurface
4115
+ });
3696
4116
  }
3697
- }
3698
- computeProgress(size) {
3699
- const { min, max } = this.getAllowedRange();
3700
- const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3701
- if (reachableMax <= min) return 0;
3702
- return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3703
- }
3704
- handleOpen() {
3705
- sheetStack.promote(this.id);
3706
- try {
3707
- this.lifecycle.install();
3708
- } catch (err) {
3709
- if (!this.destroyed) this.newCycle();
3710
- this.handleClose();
3711
- this.applySize(0);
3712
- this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3713
- this.size = 0;
3714
- queueMicrotask(() => {
3715
- throw err;
4117
+ };
4118
+ }
4119
+ function visualViewportFeature() {
4120
+ return {
4121
+ name: "visual-viewport",
4122
+ install: (ctx) => {
4123
+ if (typeof window === "undefined") return;
4124
+ return installVisualViewport({
4125
+ element: ctx.element,
4126
+ isVerticalAxis: ctx.isVerticalAxis,
4127
+ isDestroyed: ctx.isDestroyed,
4128
+ isDragging: ctx.isDragging,
4129
+ recomputeSnaps: ctx.recomputeSnaps,
4130
+ resolveActiveSnap: ctx.resolveActiveSnap,
4131
+ getMaxAxisSize: ctx.getMaxAxisSize,
4132
+ getSize: ctx.getSize,
4133
+ setMaxAxisSize: ctx.setMaxAxisSize,
4134
+ setSize: ctx.setSize,
4135
+ applySize: ctx.applySize,
4136
+ cancelInFlight: ctx.cancelInFlight,
4137
+ newCycle: ctx.newCycle,
4138
+ isAnimating: ctx.isAnimating,
4139
+ resyncAfterCancel: ctx.resyncAfterResize
3716
4140
  });
3717
4141
  }
3718
- }
3719
- resyncAfterResize() {
3720
- if (this.destroyed) return;
3721
- const target = this.snaps.findById(this.activeId);
3722
- if (!target) return;
3723
- this.updateAriaSlider();
3724
- this.emit("snap", {
3725
- id: target.id,
3726
- size: this.size,
3727
- progress: this.computeProgress(this.size)
4142
+ };
4143
+ }
4144
+
4145
+ // src/core/default-features.ts
4146
+ function defaultEngineFeatures() {
4147
+ return [
4148
+ contentSwipeFeature(),
4149
+ visualViewportFeature(),
4150
+ persistFeature(),
4151
+ autoCollapseFeature(),
4152
+ routeFeature()
4153
+ ];
4154
+ }
4155
+
4156
+ // src/core/BottomSheetEngine.ts
4157
+ var BottomSheetEngine = class extends BottomSheetCore {
4158
+ constructor(opts) {
4159
+ super({
4160
+ ...opts,
4161
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
3728
4162
  });
3729
- if (target.size > 0 && !this.lifecycle.isInstalled) {
3730
- this.emitOpenSequence(target.id);
3731
- } else if (target.size === 0 && this.lifecycle.isInstalled) {
3732
- this.emitCloseSequence();
3733
- }
3734
- }
3735
- handleClose() {
3736
- this.opening = false;
3737
- this.lifecycle.release();
3738
- sheetStack.update();
3739
4163
  }
3740
4164
  };
3741
4165
 
@@ -3899,6 +4323,8 @@ function BottomSheet($$anchor, $$props) {
3899
4323
  persistent: persistent(),
3900
4324
  disableClose: disableClose(),
3901
4325
  disableDrag: disableDrag(),
4326
+ dragFrom: $$props.dragFrom,
4327
+ dragFromContent: $$props.dragFromContent,
3902
4328
  closeOnRouteChange: closeOnRouteChange(),
3903
4329
  stackEffect: stackEffect(),
3904
4330
  radius: $$props.radius,
@@ -3995,6 +4421,14 @@ function BottomSheet($$anchor, $$props) {
3995
4421
  $2__namespace.user_effect(() => {
3996
4422
  engine?.setDisableDrag(disableDrag());
3997
4423
  });
4424
+ $2__namespace.user_effect(() => {
4425
+ if ($$props.dragFromContent !== void 0) {
4426
+ engine?.setDragFromContent($$props.dragFromContent);
4427
+ }
4428
+ });
4429
+ $2__namespace.user_effect(() => {
4430
+ if ($$props.dragFrom !== void 0) engine?.setDragFrom($$props.dragFrom);
4431
+ });
3998
4432
  $2__namespace.user_effect(() => {
3999
4433
  if ($$props.radius !== void 0) engine?.setRadius($$props.radius);
4000
4434
  });