@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/react.cjs CHANGED
@@ -8,7 +8,7 @@ var __defProp = Object.defineProperty;
8
8
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
9
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
10
10
 
11
- // src/core/lifecycle/sheetStack.ts
11
+ // src/core/lifecycle/sheet-stack.ts
12
12
  var BASE_Z = 100;
13
13
  var STEP = 10;
14
14
  var SheetStack = class {
@@ -139,7 +139,7 @@ function createEventBus() {
139
139
  };
140
140
  }
141
141
 
142
- // src/core/primitives/cssLength.ts
142
+ // src/core/primitives/css-length.ts
143
143
  var probe = null;
144
144
  var PROBE_ATTR = "data-bs-probe";
145
145
  var ensureProbe = (axis) => {
@@ -193,7 +193,7 @@ var resolveSnap = (point, mode, measureFit) => {
193
193
  return Math.max(0, el.getBoundingClientRect().width);
194
194
  };
195
195
 
196
- // src/core/primitives/devWarn.ts
196
+ // src/core/primitives/dev-warn.ts
197
197
  function devWarn(...args) {
198
198
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
199
199
  console.warn(...args);
@@ -358,74 +358,6 @@ var SnapResolver = class {
358
358
  }
359
359
  };
360
360
 
361
- // src/core/features/persist.ts
362
- function readPersistedId(key) {
363
- if (!key) return null;
364
- if (typeof window === "undefined") return null;
365
- if (typeof localStorage === "undefined") return null;
366
- try {
367
- return localStorage.getItem(key);
368
- } catch {
369
- return null;
370
- }
371
- }
372
- function installPersist(engine, key) {
373
- const unsubscribe = engine.on("snap", ({ id }) => {
374
- if (typeof window === "undefined") return;
375
- if (typeof localStorage === "undefined") return;
376
- try {
377
- localStorage.setItem(key, id);
378
- } catch {
379
- }
380
- });
381
- return unsubscribe;
382
- }
383
-
384
- // src/core/features/auto-collapse.ts
385
- function installAutoCollapse(deps) {
386
- const { ms } = deps;
387
- if (ms === void 0 || ms <= 0) {
388
- return () => {
389
- };
390
- }
391
- let timer = null;
392
- const clearTimer = () => {
393
- if (timer !== null) {
394
- clearTimeout(timer);
395
- timer = null;
396
- }
397
- };
398
- const fire = () => {
399
- if (deps.isDestroyed()) return;
400
- if (deps.isDragging()) return;
401
- const active = deps.resolveSnap(deps.getActiveId());
402
- if (!active || active.size === 0) return;
403
- let target = null;
404
- for (const id of deps.getAllowedIds()) {
405
- const snap = deps.resolveSnap(id);
406
- if (!snap || snap.size <= 0) continue;
407
- if (!target || snap.size < target.size) target = snap;
408
- }
409
- if (!target) return;
410
- if (target.id === active.id || target.size >= active.size) return;
411
- deps.snapTo(target.id);
412
- };
413
- const reset = () => {
414
- clearTimer();
415
- if (deps.isDestroyed()) return;
416
- timer = setTimeout(() => {
417
- timer = null;
418
- fire();
419
- }, ms);
420
- };
421
- const unsubscribe = deps.on("snap", () => reset());
422
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
423
- return () => {
424
- clearTimer();
425
- unsubscribe();
426
- };
427
- }
428
-
429
361
  // src/core/features/linked-sheets.ts
430
362
  function notifyLinkedSheets(sheets, self) {
431
363
  if (sheets.length === 0) return;
@@ -448,394 +380,65 @@ function notifyLinkedSheets(sheets, self) {
448
380
  }
449
381
  }
450
382
 
451
- // src/core/features/history-coordinator.ts
452
- var entries = [];
453
- var subscribers = /* @__PURE__ */ new Set();
454
- var suppress = 0;
455
- var internalDepth = 0;
456
- var pendingRebrand = null;
457
- var listenerInstalled = false;
458
- var patchInstalled = false;
459
- var origPushState = null;
460
- var origReplaceState = null;
461
- var patchedPushState = null;
462
- var patchedReplaceState = null;
463
- var hasWindow = () => typeof window !== "undefined";
464
- var hasHistory = () => typeof history !== "undefined";
465
- var brandMarker = (surface) => ({
466
- ...surface.markerState(),
467
- __bs: true
468
- });
469
- var runInternal = (fn) => {
470
- internalDepth += 1;
471
- try {
472
- fn();
473
- } finally {
474
- internalDepth -= 1;
475
- }
476
- };
477
- var notifyRouteChange = () => {
478
- const snapshot = Array.from(subscribers);
479
- for (const fn of snapshot) fn();
480
- };
481
- var ensureListener = () => {
482
- if (listenerInstalled || !hasWindow()) return;
483
- window.addEventListener("popstate", onPopState);
484
- listenerInstalled = true;
485
- };
486
- var maybeTeardownListener = () => {
487
- if (!listenerInstalled) return;
488
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
489
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
490
- listenerInstalled = false;
491
- };
492
- var installPatch = () => {
493
- if (patchInstalled || !hasHistory()) return;
494
- origPushState = history.pushState;
495
- origReplaceState = history.replaceState;
496
- patchedPushState = function(...args) {
497
- origPushState.apply(this, args);
498
- if (internalDepth === 0) notifyRouteChange();
499
- };
500
- patchedReplaceState = function(...args) {
501
- origReplaceState.apply(this, args);
502
- if (internalDepth === 0) notifyRouteChange();
503
- };
504
- history.pushState = patchedPushState;
505
- history.replaceState = patchedReplaceState;
506
- patchInstalled = true;
507
- };
508
- var removePatch = () => {
509
- if (!patchInstalled) return;
510
- if (history.pushState === patchedPushState && origPushState) {
511
- history.pushState = origPushState;
512
- }
513
- if (history.replaceState === patchedReplaceState && origReplaceState) {
514
- history.replaceState = origReplaceState;
515
- }
516
- origPushState = null;
517
- origReplaceState = null;
518
- patchedPushState = null;
519
- patchedReplaceState = null;
520
- patchInstalled = false;
521
- };
522
- function onPopState() {
523
- if (suppress > 0) {
524
- suppress -= 1;
525
- const rebrand = pendingRebrand;
526
- pendingRebrand = null;
527
- if (rebrand) rebrand();
528
- maybeTeardownListener();
529
- return;
530
- }
531
- if (entries.length === 0) {
532
- notifyRouteChange();
533
- maybeTeardownListener();
534
- return;
383
+ // src/core/features/resize-observer.ts
384
+ function installResizeObserver(deps) {
385
+ if (typeof window === "undefined") {
386
+ return () => {
387
+ };
535
388
  }
536
- let target = null;
537
- let targetIdx = -1;
538
- for (let i = entries.length - 1; i >= 0; i -= 1) {
539
- const entry = entries[i];
540
- if (!entry.surface.isOpen()) continue;
541
- if (!target) {
542
- target = entry;
543
- targetIdx = i;
389
+ const onResize = () => {
390
+ if (deps.isDestroyed()) return;
391
+ deps.recomputeSnaps();
392
+ const mode = deps.getMode();
393
+ const isVerticalAxis = mode === "bottom" || mode === "top";
394
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
395
+ const max = deps.getMaxAxisSize();
396
+ if (viewportSize2 > 0 && viewportSize2 < max) {
397
+ deps.setMaxAxisSize(viewportSize2);
544
398
  }
545
- if (entry.surface.isTop()) {
546
- target = entry;
547
- targetIdx = i;
548
- break;
399
+ const wasAnimating = deps.isAnimating?.() ?? false;
400
+ deps.cancelInFlight();
401
+ deps.newCycle();
402
+ const current = deps.resolveActiveSnap();
403
+ const newMax = deps.getMaxAxisSize();
404
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
405
+ deps.setSize(targetSize);
406
+ if (current && !deps.isDragging()) {
407
+ deps.applySize(targetSize);
408
+ if (wasAnimating) deps.resyncAfterCancel?.();
549
409
  }
410
+ };
411
+ let resizeObserver = null;
412
+ if (typeof ResizeObserver !== "undefined") {
413
+ resizeObserver = new ResizeObserver(onResize);
414
+ resizeObserver.observe(document.documentElement);
415
+ } else {
416
+ window.addEventListener("resize", onResize);
550
417
  }
551
- if (!target) {
552
- const removed = entries.pop();
553
- if (removed) removed.live = false;
554
- maybeTeardownListener();
555
- return;
556
- }
557
- target.live = false;
558
- entries.splice(targetIdx, 1);
559
- const settled = target;
560
- void Promise.resolve(settled.surface.close()).then(() => {
561
- if (settled.surface.isOpen()) restore(settled);
562
- });
563
- maybeTeardownListener();
418
+ window.addEventListener("orientationchange", onResize);
419
+ const onVisibility = () => {
420
+ if (deps.isDestroyed()) return;
421
+ if (document.hidden) deps.cancelInFlight();
422
+ };
423
+ document.addEventListener("visibilitychange", onVisibility);
424
+ return () => {
425
+ resizeObserver?.disconnect();
426
+ window.removeEventListener("resize", onResize);
427
+ window.removeEventListener("orientationchange", onResize);
428
+ document.removeEventListener("visibilitychange", onVisibility);
429
+ };
564
430
  }
565
- function restore(entry) {
566
- if (!hasWindow() || !hasHistory()) return;
567
- entry.live = true;
568
- runInternal(() => {
569
- try {
570
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
571
- } catch {
572
- entry.live = false;
573
- }
574
- });
575
- if (entry.live) {
576
- entries.push(entry);
577
- ensureListener();
578
- }
579
- }
580
- function pushBackMarker(surface) {
581
- const entry = { surface, priorUrl: void 0, live: false };
582
- if (!hasWindow() || !hasHistory()) {
583
- return entry;
584
- }
585
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
586
- runInternal(() => {
587
- try {
588
- history.pushState(brandMarker(surface), "", surface.url);
589
- entry.live = true;
590
- } catch {
591
- entry.live = false;
592
- }
593
- });
594
- if (entry.live) {
595
- entries.push(entry);
596
- ensureListener();
597
- }
598
- return entry;
599
- }
600
- function popBackMarker(handle) {
601
- if (!hasWindow() || !hasHistory()) return;
602
- const entry = handle;
603
- const idx = entries.indexOf(entry);
604
- if (idx === -1 || !entry.live) return;
605
- const last = entries.length - 1;
606
- entry.live = false;
607
- entries.splice(idx, 1);
608
- if (idx !== last) {
609
- const rebrandSurface = entries[entries.length - 1].surface;
610
- const rebrandUrl = entry.priorUrl;
611
- pendingRebrand = () => {
612
- runInternal(() => {
613
- history.replaceState(
614
- brandMarker(rebrandSurface),
615
- "",
616
- rebrandUrl !== void 0 ? rebrandUrl : location.href
617
- );
618
- });
619
- };
620
- }
621
- suppress += 1;
622
- runInternal(() => {
623
- try {
624
- history.back();
625
- } catch {
626
- suppress = Math.max(0, suppress - 1);
627
- pendingRebrand = null;
628
- }
629
- });
630
- maybeTeardownListener();
631
- }
632
- function subscribeRouteChange(fn) {
633
- if (!hasWindow() || !hasHistory()) return () => {
634
- };
635
- subscribers.add(fn);
636
- if (subscribers.size === 1) installPatch();
637
- ensureListener();
638
- return () => {
639
- if (!subscribers.has(fn)) return;
640
- subscribers.delete(fn);
641
- if (subscribers.size === 0) removePatch();
642
- maybeTeardownListener();
643
- };
644
- }
645
-
646
- // src/core/features/route.ts
647
- function installRoute(deps) {
648
- if (typeof window === "undefined") {
649
- return () => {
650
- };
651
- }
652
- if (!deps.routedTo && !deps.closeOnBack) {
653
- return () => {
654
- };
655
- }
656
- let handle = null;
657
- const onOpen = () => {
658
- if (handle) return;
659
- handle = pushBackMarker({
660
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
661
- isTop: deps.isTopSheet,
662
- close: deps.close,
663
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
664
- url: deps.routedTo
665
- });
666
- };
667
- const onClose = () => {
668
- if (!handle) return;
669
- popBackMarker(handle);
670
- handle = null;
671
- };
672
- const unsubscribeOpen = deps.on("open", onOpen);
673
- const unsubscribeClose = deps.on("close", onClose);
674
- if (deps.getSize() > 0) onOpen();
675
- return () => {
676
- unsubscribeOpen();
677
- unsubscribeClose();
678
- if (handle) {
679
- popBackMarker(handle);
680
- handle = null;
681
- }
682
- };
683
- }
684
- function installRouteChange(deps) {
685
- return subscribeRouteChange(() => {
686
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
687
- void deps.close();
688
- });
689
- }
690
-
691
- // src/core/features/content-swipe.ts
692
- var CONTENT_PULL_THRESHOLD = 60;
693
- function installContentSwipe(deps) {
694
- const { container } = deps;
695
- let startY = 0;
696
- let startScrollTop = 0;
697
- let moved = false;
698
- const onTouchStart = (e) => {
699
- const t = e.touches[0];
700
- if (!t) return;
701
- startY = t.clientY;
702
- startScrollTop = container.scrollTop;
703
- moved = false;
704
- };
705
- const onTouchMove = (e) => {
706
- const t = e.touches[0];
707
- if (!t) return;
708
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
709
- };
710
- const onTouchEnd = (e) => {
711
- if (!moved) return;
712
- if (deps.isDragging() || deps.isAnimating()) return;
713
- if (container.scrollTop !== startScrollTop) return;
714
- const t = e.changedTouches[0];
715
- if (!t) return;
716
- const deltaY = t.clientY - startY;
717
- const atTop = container.scrollTop <= 0;
718
- const allowed = deps.getAllowedIds();
719
- const idx = allowed.indexOf(deps.getActiveId());
720
- if (idx === -1) return;
721
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
722
- deps.snapTo(allowed[idx - 1]);
723
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
724
- deps.snapTo(allowed[idx + 1]);
725
- }
726
- };
727
- container.addEventListener("touchstart", onTouchStart, { passive: true });
728
- container.addEventListener("touchmove", onTouchMove, { passive: true });
729
- container.addEventListener("touchend", onTouchEnd, { passive: true });
730
- return () => {
731
- container.removeEventListener("touchstart", onTouchStart);
732
- container.removeEventListener("touchmove", onTouchMove);
733
- container.removeEventListener("touchend", onTouchEnd);
734
- };
735
- }
736
-
737
- // src/core/features/visual-viewport.ts
738
- var SAFETY_PAD = 8;
739
- function installVisualViewport(deps) {
740
- if (typeof window === "undefined" || !window.visualViewport) {
741
- return () => {
742
- };
743
- }
744
- const vv = window.visualViewport;
745
- let rafId = null;
746
- const onResize = () => {
747
- if (rafId !== null) return;
748
- rafId = requestAnimationFrame(() => {
749
- rafId = null;
750
- if (deps.isDestroyed()) return;
751
- deps.recomputeSnaps();
752
- const isVertical = deps.isVerticalAxis();
753
- const visible = isVertical ? vv.height : vv.width;
754
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
755
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
756
- if (clamp < deps.getMaxAxisSize()) {
757
- deps.setMaxAxisSize(clamp);
758
- }
759
- const wasAnimating = deps.isAnimating?.() ?? false;
760
- deps.cancelInFlight();
761
- deps.newCycle();
762
- const max = deps.getMaxAxisSize();
763
- deps.setSize(Math.min(deps.getSize(), max));
764
- const current = deps.resolveActiveSnap();
765
- if (current && !deps.isDragging()) {
766
- deps.applySize(Math.min(current.size, max));
767
- if (wasAnimating) deps.resyncAfterCancel?.();
768
- } else {
769
- deps.applySize(deps.getSize());
770
- }
771
- });
772
- };
773
- vv.addEventListener("resize", onResize);
774
- return () => {
775
- vv.removeEventListener("resize", onResize);
776
- if (rafId !== null) cancelAnimationFrame(rafId);
777
- };
778
- }
779
-
780
- // src/core/features/resize-observer.ts
781
- function installResizeObserver(deps) {
782
- if (typeof window === "undefined") {
783
- return () => {
784
- };
785
- }
786
- const onResize = () => {
787
- if (deps.isDestroyed()) return;
788
- deps.recomputeSnaps();
789
- const mode = deps.getMode();
790
- const isVerticalAxis = mode === "bottom" || mode === "top";
791
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
792
- const max = deps.getMaxAxisSize();
793
- if (viewportSize2 > 0 && viewportSize2 < max) {
794
- deps.setMaxAxisSize(viewportSize2);
795
- }
796
- const wasAnimating = deps.isAnimating?.() ?? false;
797
- deps.cancelInFlight();
798
- deps.newCycle();
799
- const current = deps.resolveActiveSnap();
800
- const newMax = deps.getMaxAxisSize();
801
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
802
- deps.setSize(targetSize);
803
- if (current && !deps.isDragging()) {
804
- deps.applySize(targetSize);
805
- if (wasAnimating) deps.resyncAfterCancel?.();
806
- }
807
- };
808
- let resizeObserver = null;
809
- if (typeof ResizeObserver !== "undefined") {
810
- resizeObserver = new ResizeObserver(onResize);
811
- resizeObserver.observe(document.documentElement);
812
- } else {
813
- window.addEventListener("resize", onResize);
814
- }
815
- window.addEventListener("orientationchange", onResize);
816
- const onVisibility = () => {
817
- if (deps.isDestroyed()) return;
818
- if (document.hidden) deps.cancelInFlight();
819
- };
820
- document.addEventListener("visibilitychange", onVisibility);
821
- return () => {
822
- resizeObserver?.disconnect();
823
- window.removeEventListener("resize", onResize);
824
- window.removeEventListener("orientationchange", onResize);
825
- document.removeEventListener("visibilitychange", onVisibility);
826
- };
827
- }
828
-
829
- // src/core/features/fit-observer.ts
830
- var contentTargets = (scroller) => {
831
- if (!scroller) return [];
832
- const hasSlot = typeof HTMLSlotElement !== "undefined";
833
- const out = [];
834
- for (const child of Array.from(scroller.children)) {
835
- if (hasSlot && child instanceof HTMLSlotElement) {
836
- for (const el of child.assignedElements()) out.push(el);
837
- } else {
838
- out.push(child);
431
+
432
+ // src/core/features/fit-observer.ts
433
+ var contentTargets = (scroller) => {
434
+ if (!scroller) return [];
435
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
436
+ const out = [];
437
+ for (const child of Array.from(scroller.children)) {
438
+ if (hasSlot && child instanceof HTMLSlotElement) {
439
+ for (const el of child.assignedElements()) out.push(el);
440
+ } else {
441
+ out.push(child);
839
442
  }
840
443
  }
841
444
  return out;
@@ -1677,6 +1280,52 @@ function resolveAnimationPreset(animation) {
1677
1280
  }
1678
1281
  }
1679
1282
 
1283
+ // src/core/animation/waapi-settle.ts
1284
+ var SIM_HZ = 240;
1285
+ var SAMPLE_STRIDE = 4;
1286
+ var MAX_SIM_MS = 3e3;
1287
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1288
+ const cfg = { ...DEFAULT_SPRING, ...config };
1289
+ const subDt = 1 / SIM_HZ;
1290
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1291
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1292
+ const values = [from];
1293
+ let x = from;
1294
+ let v = velocityPxPerS;
1295
+ for (let i = 1; i <= maxSteps; i += 1) {
1296
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1297
+ v += force / cfg.mass * subDt;
1298
+ x += v * subDt;
1299
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1300
+ values.push(to);
1301
+ break;
1302
+ }
1303
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1304
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1305
+ if (atRest) break;
1306
+ }
1307
+ if (values[values.length - 1] !== to) values.push(to);
1308
+ return {
1309
+ values,
1310
+ stepMs,
1311
+ durationMs: (values.length - 1) * stepMs
1312
+ };
1313
+ }
1314
+ function sampleTweenSettle(from, to, durationMs, easing) {
1315
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1316
+ const values = [];
1317
+ for (let i = 0; i <= count; i += 1) {
1318
+ const t = i / count;
1319
+ values.push(from + (to - from) * easing(t));
1320
+ }
1321
+ values[values.length - 1] = to;
1322
+ return {
1323
+ values,
1324
+ stepMs: durationMs / count,
1325
+ durationMs
1326
+ };
1327
+ }
1328
+
1680
1329
  // src/core/controllers/animation-runner.ts
1681
1330
  var DEFAULT_DURATION2 = 220;
1682
1331
  var VELOCITY_PX_PER_S = 1e3;
@@ -1687,6 +1336,10 @@ var AnimationRunner = class {
1687
1336
  __publicField(this, "applySizeFn");
1688
1337
  __publicField(this, "getSize");
1689
1338
  __publicField(this, "isDragging");
1339
+ __publicField(this, "applyAux");
1340
+ __publicField(this, "getTransformFor");
1341
+ __publicField(this, "settleWaapi");
1342
+ __publicField(this, "currentWaapi", null);
1690
1343
  __publicField(this, "animationKind");
1691
1344
  __publicField(this, "springConfig");
1692
1345
  __publicField(this, "duration");
@@ -1702,6 +1355,9 @@ var AnimationRunner = class {
1702
1355
  this.applySizeFn = deps.applySize;
1703
1356
  this.getSize = deps.getSize;
1704
1357
  this.isDragging = deps.isDragging;
1358
+ this.applyAux = deps.applyAux;
1359
+ this.getTransformFor = deps.getTransformFor;
1360
+ this.settleWaapi = opts.settleAnimation === "waapi";
1705
1361
  const preset = resolveAnimationPreset(opts.animation);
1706
1362
  this.animationKind = preset.kind;
1707
1363
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1726,11 +1382,20 @@ var AnimationRunner = class {
1726
1382
  }
1727
1383
  }
1728
1384
  get isAnimating() {
1729
- return this.currentTween !== null || this.currentSpring !== null;
1385
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1730
1386
  }
1731
1387
  cancel() {
1732
1388
  this.currentTween?.cancel();
1733
1389
  this.currentSpring?.cancel();
1390
+ if (this.currentWaapi) {
1391
+ const entry = this.currentWaapi;
1392
+ this.currentWaapi = null;
1393
+ entry.stop();
1394
+ try {
1395
+ entry.anim.cancel();
1396
+ } catch {
1397
+ }
1398
+ }
1734
1399
  }
1735
1400
  async animateTo(target, velocityPxPerMs) {
1736
1401
  if (target === this.getSize()) {
@@ -1749,6 +1414,27 @@ var AnimationRunner = class {
1749
1414
  if (!this.isDragging()) this.element.style.willChange = "";
1750
1415
  return;
1751
1416
  }
1417
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1418
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1419
+ this.getSize(),
1420
+ target,
1421
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1422
+ this.springConfig
1423
+ ) : sampleTweenSettle(
1424
+ this.getSize(),
1425
+ target,
1426
+ this.duration,
1427
+ this.easing
1428
+ );
1429
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1430
+ await this.runWaapiSettle(samples, target);
1431
+ if (!this.isAnimating) {
1432
+ this.getRootEl()?.removeAttribute("data-animating");
1433
+ if (!this.isDragging()) this.element.style.willChange = "";
1434
+ }
1435
+ return;
1436
+ }
1437
+ }
1752
1438
  if (this.animationKind === "spring") {
1753
1439
  const spring = runSpring({
1754
1440
  from: this.getSize(),
@@ -1777,15 +1463,70 @@ var AnimationRunner = class {
1777
1463
  if (!this.isDragging()) this.element.style.willChange = "";
1778
1464
  }
1779
1465
  }
1466
+ async runWaapiSettle(samples, target) {
1467
+ const applyAux = this.applyAux;
1468
+ const getTransformFor = this.getTransformFor;
1469
+ const frames = samples.values.map((v) => ({
1470
+ transform: getTransformFor(v)
1471
+ }));
1472
+ const anim = this.element.animate(frames, {
1473
+ duration: samples.durationMs,
1474
+ easing: "linear",
1475
+ fill: "forwards"
1476
+ });
1477
+ let stopped = false;
1478
+ let auxRaf = 0;
1479
+ const start = performance.now();
1480
+ const lastIdx = samples.values.length - 1;
1481
+ const auxTick = () => {
1482
+ if (stopped) return;
1483
+ const elapsed = performance.now() - start;
1484
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1485
+ applyAux(samples.values[idx]);
1486
+ auxRaf = requestAnimationFrame(auxTick);
1487
+ };
1488
+ auxRaf = requestAnimationFrame(auxTick);
1489
+ const entry = {
1490
+ anim,
1491
+ stop: () => {
1492
+ stopped = true;
1493
+ cancelAnimationFrame(auxRaf);
1494
+ }
1495
+ };
1496
+ this.currentWaapi = entry;
1497
+ try {
1498
+ await anim.finished;
1499
+ } catch {
1500
+ }
1501
+ entry.stop();
1502
+ const wasCurrent = this.currentWaapi === entry;
1503
+ if (wasCurrent) this.currentWaapi = null;
1504
+ if (wasCurrent && anim.playState === "finished") {
1505
+ this.applySizeFn(target);
1506
+ try {
1507
+ anim.cancel();
1508
+ } catch {
1509
+ }
1510
+ }
1511
+ }
1780
1512
  destroy() {
1781
1513
  this.detachReducedMotion?.();
1782
1514
  this.detachReducedMotion = null;
1783
1515
  this.currentTween = null;
1784
1516
  this.currentSpring = null;
1517
+ if (this.currentWaapi) {
1518
+ const entry = this.currentWaapi;
1519
+ this.currentWaapi = null;
1520
+ entry.stop();
1521
+ try {
1522
+ entry.anim.cancel();
1523
+ } catch {
1524
+ }
1525
+ }
1785
1526
  }
1786
1527
  };
1787
1528
 
1788
- // src/core/lifecycle/focusTrap.ts
1529
+ // src/core/lifecycle/focus-trap.ts
1789
1530
  var FOCUSABLE = [
1790
1531
  "a[href]",
1791
1532
  "area[href]",
@@ -1903,7 +1644,7 @@ var installFocusTrap = (container, options = {}) => {
1903
1644
  };
1904
1645
  };
1905
1646
 
1906
- // src/core/lifecycle/scrollLock.ts
1647
+ // src/core/lifecycle/scroll-lock.ts
1907
1648
  var lockCount = 0;
1908
1649
  var savedStyles = null;
1909
1650
  var lockBodyScroll = () => {
@@ -2085,30 +1826,44 @@ var SampleRing = class {
2085
1826
  this.size = 0;
2086
1827
  }
2087
1828
  };
2088
- var installGestures = (handle, mode, callbacks) => {
1829
+ var installGestures = (handle, mode, callbacks, options = {}) => {
2089
1830
  let activePointerId = null;
2090
1831
  let activePointerType = "touch";
2091
1832
  let startCoord = 0;
2092
1833
  let lastCoord = 0;
1834
+ let pendingStart = false;
2093
1835
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
2094
1836
  const MAX_SAMPLES = 32;
2095
1837
  const samples = new SampleRing(MAX_SAMPLES);
2096
1838
  const axis = isAxisVertical(mode) ? "Y" : "X";
2097
1839
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
2098
1840
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
2099
- const onPointerDown = (e) => {
2100
- if (activePointerId !== null) return;
2101
- if (e.button !== void 0 && e.button !== 0) return;
2102
- activePointerId = e.pointerId;
2103
- activePointerType = e.pointerType || "touch";
2104
- startCoord = coordOf(e);
1841
+ const capture = (pointerId) => {
1842
+ try {
1843
+ handle.setPointerCapture(pointerId);
1844
+ } catch {
1845
+ }
1846
+ };
1847
+ const releaseTracking = () => {
1848
+ activePointerId = null;
1849
+ pendingStart = false;
1850
+ samples.reset();
1851
+ };
1852
+ const onPointerDown = (e) => {
1853
+ if (activePointerId !== null) return;
1854
+ if (e.button !== void 0 && e.button !== 0) return;
1855
+ if (options.shouldStart && !options.shouldStart(e)) return;
1856
+ activePointerId = e.pointerId;
1857
+ activePointerType = e.pointerType || "touch";
1858
+ startCoord = coordOf(e);
2105
1859
  lastCoord = startCoord;
2106
1860
  samples.reset();
2107
1861
  samples.push(e.timeStamp, startCoord);
2108
- try {
2109
- handle.setPointerCapture(e.pointerId);
2110
- } catch {
1862
+ if (options.deferStart) {
1863
+ pendingStart = true;
1864
+ return;
2111
1865
  }
1866
+ capture(e.pointerId);
2112
1867
  callbacks.onStart(startCoord, activePointerType);
2113
1868
  };
2114
1869
  const onPointerMove = (e) => {
@@ -2116,6 +1871,22 @@ var installGestures = (handle, mode, callbacks) => {
2116
1871
  const coord = coordOf(e);
2117
1872
  const rawDelta = coord - startCoord;
2118
1873
  const delta = rawDelta * sign;
1874
+ if (pendingStart) {
1875
+ const verdict = options.deferStart(e, delta);
1876
+ if (verdict === false) {
1877
+ releaseTracking();
1878
+ return;
1879
+ }
1880
+ if (verdict !== true) return;
1881
+ pendingStart = false;
1882
+ startCoord = coord;
1883
+ lastCoord = coord;
1884
+ samples.reset();
1885
+ samples.push(e.timeStamp, coord);
1886
+ capture(e.pointerId);
1887
+ callbacks.onStart(startCoord, activePointerType);
1888
+ return;
1889
+ }
2119
1890
  lastCoord = coord;
2120
1891
  samples.push(e.timeStamp, coord);
2121
1892
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -2124,6 +1895,10 @@ var installGestures = (handle, mode, callbacks) => {
2124
1895
  };
2125
1896
  const finishGesture = (e) => {
2126
1897
  if (e.pointerId !== activePointerId) return;
1898
+ if (pendingStart) {
1899
+ releaseTracking();
1900
+ return;
1901
+ }
2127
1902
  const rawDelta = lastCoord - startCoord;
2128
1903
  const delta = rawDelta * sign;
2129
1904
  let velocity = 0;
@@ -2146,22 +1921,26 @@ var installGestures = (handle, mode, callbacks) => {
2146
1921
  };
2147
1922
  const onPointerCancel = (e) => {
2148
1923
  if (e.pointerId !== activePointerId) return;
2149
- activePointerId = null;
2150
- samples.reset();
1924
+ const wasPending = pendingStart;
1925
+ releaseTracking();
1926
+ if (wasPending) return;
2151
1927
  callbacks.onCancel?.();
2152
1928
  };
2153
1929
  handle.addEventListener("pointerdown", onPointerDown);
2154
1930
  handle.addEventListener("pointermove", onPointerMove);
2155
1931
  handle.addEventListener("pointerup", finishGesture);
2156
1932
  handle.addEventListener("pointercancel", onPointerCancel);
1933
+ const managesTouchAction = options.manageTouchAction !== false;
2157
1934
  const prevTouchAction = handle.style.touchAction;
2158
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1935
+ if (managesTouchAction) {
1936
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1937
+ }
2159
1938
  return () => {
2160
1939
  handle.removeEventListener("pointerdown", onPointerDown);
2161
1940
  handle.removeEventListener("pointermove", onPointerMove);
2162
1941
  handle.removeEventListener("pointerup", finishGesture);
2163
1942
  handle.removeEventListener("pointercancel", onPointerCancel);
2164
- handle.style.touchAction = prevTouchAction;
1943
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
2165
1944
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
2166
1945
  handle.releasePointerCapture(activePointerId);
2167
1946
  }
@@ -2280,7 +2059,7 @@ var GestureController = class {
2280
2059
  const restoreTo = this.dragStartSize;
2281
2060
  void this.deps.animateTo(restoreTo, 0);
2282
2061
  }
2283
- });
2062
+ }, this.deps.gestureOptions);
2284
2063
  return this.detach;
2285
2064
  }
2286
2065
  forceClearDragState() {
@@ -2292,6 +2071,41 @@ var GestureController = class {
2292
2071
  }
2293
2072
  };
2294
2073
 
2074
+ // src/core/primitives/drag-zones.ts
2075
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2076
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2077
+ var asElement = (target) => {
2078
+ const candidate = target;
2079
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2080
+ };
2081
+ function isDragAllowedFrom(target, mode) {
2082
+ const el = asElement(target);
2083
+ if (!el) return mode !== "zones";
2084
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2085
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2086
+ return true;
2087
+ }
2088
+
2089
+ // src/core/primitives/content-gesture.ts
2090
+ var CONTENT_DRAG_SLOP = 6;
2091
+ function decideContentGesture(input) {
2092
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2093
+ if (Math.abs(input.delta) < slop) return "pending";
2094
+ if (input.scrollTop > 0) return "scroll";
2095
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2096
+ return "drag";
2097
+ }
2098
+
2099
+ // src/core/primitives/touch-scroll-guard.ts
2100
+ function installTouchScrollGuard(surface, isDragging) {
2101
+ const onTouchMove = (e) => {
2102
+ if (!isDragging()) return;
2103
+ if (e.cancelable) e.preventDefault();
2104
+ };
2105
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2106
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2107
+ }
2108
+
2295
2109
  // src/core/primitives/aria-slider-writer.ts
2296
2110
  var AriaSliderWriter = class {
2297
2111
  constructor(handle, mode) {
@@ -2638,7 +2452,7 @@ function resolveEngineOptions(opts, extras) {
2638
2452
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2639
2453
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2640
2454
  if (opts.persistKey) {
2641
- const restored = readPersistedId(opts.persistKey);
2455
+ const restored = readPersistedIdSafe(opts.persistKey);
2642
2456
  if (restored && initialAllowed.includes(restored)) {
2643
2457
  initialId = restored;
2644
2458
  }
@@ -2647,6 +2461,8 @@ function resolveEngineOptions(opts, extras) {
2647
2461
  mode: opts.mode ?? "bottom",
2648
2462
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2649
2463
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2464
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2465
+ dragFromContent: opts.dragFromContent ?? true,
2650
2466
  rubberBandEnabled: opts.rubberBand ?? true,
2651
2467
  closeOnBack: opts.closeOnBack ?? false,
2652
2468
  persistent: opts.persistent ?? false,
@@ -2669,6 +2485,7 @@ function resolveEngineOptions(opts, extras) {
2669
2485
  },
2670
2486
  animation: {
2671
2487
  animation: opts.animation,
2488
+ settleAnimation: opts.settleAnimation,
2672
2489
  duration: opts.duration,
2673
2490
  easing: opts.easing,
2674
2491
  spring: opts.spring,
@@ -2686,6 +2503,15 @@ function resolveEngineOptions(opts, extras) {
2686
2503
  }
2687
2504
  };
2688
2505
  }
2506
+ var readPersistedIdSafe = (key) => {
2507
+ if (typeof window === "undefined") return null;
2508
+ if (typeof localStorage === "undefined") return null;
2509
+ try {
2510
+ return localStorage.getItem(key);
2511
+ } catch {
2512
+ return null;
2513
+ }
2514
+ };
2689
2515
 
2690
2516
  // src/core/primitives/teardown-stack.ts
2691
2517
  var TeardownStack = class {
@@ -2708,9 +2534,9 @@ var TeardownStack = class {
2708
2534
  }
2709
2535
  };
2710
2536
 
2711
- // src/core/BottomSheetEngine.ts
2537
+ // src/core/BottomSheetCore.ts
2712
2538
  var HAPTIC_DURATION_MS = 8;
2713
- var BottomSheetEngine = class {
2539
+ var BottomSheetCore = class {
2714
2540
  constructor(opts) {
2715
2541
  __publicField(this, "id", nextInstanceId("bs"));
2716
2542
  __publicField(this, "element");
@@ -2722,6 +2548,8 @@ var BottomSheetEngine = class {
2722
2548
  __publicField(this, "mode");
2723
2549
  __publicField(this, "flickVelocity");
2724
2550
  __publicField(this, "dragThreshold");
2551
+ __publicField(this, "dragFromMode");
2552
+ __publicField(this, "dragFromContentDefault");
2725
2553
  __publicField(this, "rubberBandEnabled");
2726
2554
  __publicField(this, "scrim");
2727
2555
  __publicField(this, "aria");
@@ -2739,8 +2567,11 @@ var BottomSheetEngine = class {
2739
2567
  __publicField(this, "activeId");
2740
2568
  __publicField(this, "size", 0);
2741
2569
  __publicField(this, "gesture");
2570
+ __publicField(this, "contentGesture");
2571
+ __publicField(this, "detachSheetGesture", null);
2742
2572
  __publicField(this, "rootEl", null);
2743
2573
  __publicField(this, "destroyed", false);
2574
+ __publicField(this, "restClosed", false);
2744
2575
  __publicField(this, "currentViewTransition", null);
2745
2576
  __publicField(this, "currentAbort", new AbortController());
2746
2577
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2771,6 +2602,8 @@ var BottomSheetEngine = class {
2771
2602
  __publicField(this, "stackEffectPrimed", false);
2772
2603
  __publicField(this, "fitDeps");
2773
2604
  __publicField(this, "transformTemplate");
2605
+ __publicField(this, "featureList", []);
2606
+ __publicField(this, "featureCtx");
2774
2607
  const resolved = resolveEngineOptions(opts, {
2775
2608
  shouldApplyInertSiblings: () => {
2776
2609
  if (typeof document === "undefined") return false;
@@ -2791,6 +2624,8 @@ var BottomSheetEngine = class {
2791
2624
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2792
2625
  this.flickVelocity = resolved.flickVelocity;
2793
2626
  this.dragThreshold = resolved.dragThreshold;
2627
+ this.dragFromMode = resolved.dragFrom;
2628
+ this.dragFromContentDefault = resolved.dragFromContent;
2794
2629
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2795
2630
  this.scrim = new ScrimController(
2796
2631
  {
@@ -2817,7 +2652,9 @@ var BottomSheetEngine = class {
2817
2652
  getRootEl: () => this.rootEl,
2818
2653
  applySize: (size) => this.applySize(size),
2819
2654
  getSize: () => this.size,
2820
- isDragging: () => this.gesture?.isDragging ?? false
2655
+ isDragging: () => this.isDraggingAny(),
2656
+ applyAux: (size) => this.applySize(size, true),
2657
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2821
2658
  },
2822
2659
  resolved.animation
2823
2660
  );
@@ -2840,6 +2677,11 @@ var BottomSheetEngine = class {
2840
2677
  if (opts.linkedSheets) {
2841
2678
  this.linkedSheets = opts.linkedSheets;
2842
2679
  }
2680
+ const byName = /* @__PURE__ */ new Map();
2681
+ for (const feature of opts.features ?? []) {
2682
+ byName.set(feature.name, feature);
2683
+ }
2684
+ this.featureList = Array.from(byName.values());
2843
2685
  this.activeId = resolved.initialId;
2844
2686
  auditVhUsage(this.snapPointsRaw);
2845
2687
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2871,11 +2713,13 @@ var BottomSheetEngine = class {
2871
2713
  const initial = this.snaps.findById(this.activeId);
2872
2714
  if (initial) this.size = initial.size;
2873
2715
  this.applySize(this.size);
2716
+ this.setRestClosed(this.size === 0);
2874
2717
  this.updateAriaSlider();
2875
2718
  this.scrollCache = createScrollCache({
2876
2719
  scrollContainer: this.scrollContainer,
2877
2720
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2878
2721
  });
2722
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2879
2723
  this.attach();
2880
2724
  this.registerInStack();
2881
2725
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2900,60 +2744,92 @@ var BottomSheetEngine = class {
2900
2744
  (p) => p.size === "fit" || p.size === "content"
2901
2745
  ),
2902
2746
  isDestroyed: () => this.destroyed,
2903
- isDragging: () => this.gesture?.isDragging ?? false,
2747
+ isDragging: () => this.isDraggingAny(),
2904
2748
  recompute: () => this.recompute()
2905
2749
  })
2906
2750
  );
2907
2751
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2908
2752
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2909
2753
  this.applyAutoAriaLabelledBy();
2910
- if (this.closeOnRouteChange) {
2911
- this.teardowns.add(
2912
- installRouteChange({
2913
- isDestroyed: () => this.destroyed,
2914
- getSize: () => this.size,
2915
- close: () => this.close()
2916
- })
2917
- );
2918
- }
2919
- if (this.persistKey) {
2920
- this.teardowns.add(installPersist(this, this.persistKey));
2754
+ this.runFeatureStage("post");
2755
+ this.warnOrphanFeatureOptions();
2756
+ if (this.size > 0) {
2757
+ this.handleOpen();
2921
2758
  }
2922
- this.teardowns.add(
2923
- installAutoCollapse({
2924
- ms: opts.autoCollapseAfter,
2925
- isDestroyed: () => this.destroyed,
2926
- isDragging: () => this.gesture?.isDragging ?? false,
2927
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2928
- getActiveId: () => this.activeId,
2929
- resolveSnap: (id) => this.snaps.findById(id),
2930
- snapTo: (id) => {
2931
- void this.snapTo(id);
2932
- },
2933
- on: (event, fn) => this.on(event, fn)
2934
- })
2935
- );
2936
- this.teardowns.add(
2937
- installRoute({
2759
+ }
2760
+ buildFeatureContext(autoCollapseAfter) {
2761
+ return {
2762
+ element: this.element,
2763
+ scrollContainer: this.scrollContainer,
2764
+ sheetId: this.id,
2765
+ options: {
2938
2766
  routedTo: this.routedTo,
2939
2767
  closeOnBack: this.closeOnBack,
2940
- isTopSheet: () => this.isTopSheet,
2941
- getSize: () => this.size,
2942
- isDestroyed: () => this.destroyed,
2943
- close: () => this.close("back"),
2944
- on: (event, fn) => this.on(event, fn),
2945
- sheetId: this.id
2946
- })
2947
- );
2948
- if (this.size > 0) {
2949
- this.handleOpen();
2768
+ closeOnRouteChange: this.closeOnRouteChange,
2769
+ persistKey: this.persistKey,
2770
+ autoCollapseAfter
2771
+ },
2772
+ isDestroyed: () => this.destroyed,
2773
+ isDragging: () => this.isDraggingAny(),
2774
+ isAnimating: () => this.animation.isAnimating,
2775
+ isTopSheet: () => this.isTopSheet,
2776
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2777
+ getSize: () => this.size,
2778
+ getActiveId: () => this.activeId,
2779
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2780
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2781
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2782
+ resolveSnap: (id) => this.snaps.findById(id),
2783
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2784
+ setSize: (size) => {
2785
+ this.size = size;
2786
+ },
2787
+ setMaxAxisSize: (size) => {
2788
+ this.snaps.setMaxAxisSize(size);
2789
+ },
2790
+ applySize: (size) => this.applySize(size),
2791
+ recomputeSnaps: () => this.recomputeSnaps(),
2792
+ newCycle: () => {
2793
+ this.newCycle();
2794
+ },
2795
+ cancelInFlight: () => {
2796
+ this.animation.cancel();
2797
+ },
2798
+ resyncAfterResize: () => this.resyncAfterResize(),
2799
+ snapTo: (id) => {
2800
+ void this.snapTo(id);
2801
+ },
2802
+ close: (reason) => this.close(reason),
2803
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2804
+ on: (event, fn) => this.on(event, fn),
2805
+ addTeardown: (fn) => this.teardowns.add(fn)
2806
+ };
2807
+ }
2808
+ runFeatureStage(stage) {
2809
+ for (const feature of this.featureList) {
2810
+ if ((feature.stage ?? "post") !== stage) continue;
2811
+ const teardown = feature.install(this.featureCtx);
2812
+ if (teardown) this.teardowns.add(teardown);
2813
+ }
2814
+ }
2815
+ warnOrphanFeatureOptions() {
2816
+ const names = new Set(this.featureList.map((f) => f.name));
2817
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2818
+ devWarn(
2819
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2820
+ );
2821
+ }
2822
+ if (this.persistKey && !names.has("persist")) {
2823
+ devWarn(
2824
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2825
+ );
2950
2826
  }
2951
2827
  }
2952
2828
  get state() {
2953
2829
  return {
2954
2830
  size: this.size,
2955
2831
  activeId: this.activeId,
2956
- isDragging: this.gesture?.isDragging ?? false,
2832
+ isDragging: this.isDraggingAny(),
2957
2833
  isAnimating: this.animation.isAnimating,
2958
2834
  progress: this.computeProgress(this.size)
2959
2835
  };
@@ -3073,9 +2949,16 @@ var BottomSheetEngine = class {
3073
2949
  progress: this.computeProgress(this.size)
3074
2950
  });
3075
2951
  if (withHaptic) this.haptic();
2952
+ this.setRestClosed(targetSize === 0);
3076
2953
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
3077
2954
  if (targetSize === 0) this.emitCloseSequence();
3078
2955
  }
2956
+ setRestClosed(closed) {
2957
+ if (closed === this.restClosed) return;
2958
+ this.restClosed = closed;
2959
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2960
+ else this.element.removeAttribute("data-bs-rest");
2961
+ }
3079
2962
  emitOpenSequence(id) {
3080
2963
  this.emit("open", { id });
3081
2964
  this.handleOpen();
@@ -3131,6 +3014,20 @@ var BottomSheetEngine = class {
3131
3014
  if (this.destroyed) return;
3132
3015
  this.disableDragFlag = value;
3133
3016
  }
3017
+ setDragFromContent(value) {
3018
+ if (this.destroyed) return;
3019
+ this.dragFromContentDefault = value;
3020
+ }
3021
+ setDragFrom(mode) {
3022
+ if (this.destroyed || mode === this.dragFromMode) return;
3023
+ this.gesture?.forceClearDragState();
3024
+ this.detachSheetGesture?.();
3025
+ this.dragFromMode = mode;
3026
+ this.mountSheetGesture();
3027
+ }
3028
+ getDragFrom() {
3029
+ return this.dragFromMode;
3030
+ }
3134
3031
  isTop() {
3135
3032
  return this.isTopSheet;
3136
3033
  }
@@ -3285,12 +3182,24 @@ var BottomSheetEngine = class {
3285
3182
  auditVhUsage(points);
3286
3183
  this.auditDuplicateSnapIds(points);
3287
3184
  this.snaps.setRaw(points);
3288
- if (allowed) this.snaps.setAllowedIds(allowed);
3185
+ if (allowed) {
3186
+ this.snaps.setAllowedIds(allowed);
3187
+ } else {
3188
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3189
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3190
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3191
+ }
3289
3192
  this.scrollCache.clear();
3290
3193
  this.scrim.invalidateOpacityCache();
3291
3194
  const current = this.snaps.findById(this.activeId);
3292
3195
  if (current) {
3293
3196
  this.applySize(current.size);
3197
+ } else {
3198
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3199
+ if (fallback) {
3200
+ this.activeId = fallback.id;
3201
+ this.applySize(fallback.size);
3202
+ }
3294
3203
  }
3295
3204
  this.updateAriaSlider();
3296
3205
  }
@@ -3300,7 +3209,7 @@ var BottomSheetEngine = class {
3300
3209
  this.snaps.recompute();
3301
3210
  this.maxHeight.clampTo();
3302
3211
  this.scrim.invalidateOpacityCache();
3303
- if (this.gesture?.isDragging) return;
3212
+ if (this.isDraggingAny()) return;
3304
3213
  this.healActiveSnap();
3305
3214
  }
3306
3215
  healActiveSnap() {
@@ -3322,6 +3231,8 @@ var BottomSheetEngine = class {
3322
3231
  this.bus.clear();
3323
3232
  this.animation.cancel();
3324
3233
  this.gesture?.forceClearDragState();
3234
+ this.contentGesture?.forceClearDragState();
3235
+ this.setRestClosed(false);
3325
3236
  this.teardowns.drain();
3326
3237
  this.scrim.destroy();
3327
3238
  this.animation.destroy();
@@ -3434,11 +3345,12 @@ var BottomSheetEngine = class {
3434
3345
  const scale = Math.max(1 - depth * 0.04, 0.86);
3435
3346
  this.element.style.scale = scale === 1 ? "" : String(scale);
3436
3347
  }
3437
- attach() {
3438
- this.gesture = new GestureController({
3439
- handle: this.handle,
3348
+ buildGestureDeps(surface, gestureOptions) {
3349
+ return {
3350
+ handle: surface,
3440
3351
  element: this.element,
3441
3352
  mode: this.mode,
3353
+ gestureOptions,
3442
3354
  getRoot: () => this.rootEl,
3443
3355
  getDragContext: () => {
3444
3356
  const buf = this.dragContextBuf;
@@ -3457,8 +3369,88 @@ var BottomSheetEngine = class {
3457
3369
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3458
3370
  emit: (event, payload) => this.emit(event, payload),
3459
3371
  listenerCount: (event) => this.bus.listenerCount(event)
3372
+ };
3373
+ }
3374
+ isContentDragAllowed() {
3375
+ if (this.disableDragFlag) return false;
3376
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3377
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3378
+ }
3379
+ decideContentDrag(surface, e, delta) {
3380
+ if (this.destroyed) return false;
3381
+ if (e.pointerType === "mouse") return false;
3382
+ if (this.gesture?.isDragging) return false;
3383
+ if (!this.isContentDragAllowed()) return false;
3384
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3385
+ const decision = decideContentGesture({
3386
+ delta,
3387
+ scrollTop: surface.scrollTop,
3388
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3389
+ });
3390
+ if (decision === "pending") return null;
3391
+ return decision === "drag";
3392
+ }
3393
+ attachDragSurface(surface, kind) {
3394
+ if (this.destroyed || kind !== "content") return;
3395
+ if (this.contentGesture) return;
3396
+ const controller = new GestureController(
3397
+ this.buildGestureDeps(surface, {
3398
+ manageTouchAction: false,
3399
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3400
+ })
3401
+ );
3402
+ this.contentGesture = controller;
3403
+ const detachGestures = controller.install();
3404
+ const detachGuard = installTouchScrollGuard(
3405
+ surface,
3406
+ () => controller.isDragging
3407
+ );
3408
+ return () => {
3409
+ detachGuard();
3410
+ detachGestures();
3411
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3412
+ };
3413
+ }
3414
+ sheetDragSurface() {
3415
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3416
+ }
3417
+ canStartSheetDrag(e) {
3418
+ if (this.contentGesture?.isDragging) return false;
3419
+ const target = e.target;
3420
+ const scoped = typeof target?.closest === "function";
3421
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3422
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3423
+ return true;
3424
+ }
3425
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3426
+ if (this.dragFromMode === "handle") return true;
3427
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3428
+ return false;
3429
+ }
3430
+ return true;
3431
+ }
3432
+ mountSheetGesture() {
3433
+ const surface = this.sheetDragSurface();
3434
+ const controller = new GestureController(
3435
+ this.buildGestureDeps(surface, {
3436
+ shouldStart: (e) => this.canStartSheetDrag(e),
3437
+ manageTouchAction: this.dragFromMode === "handle"
3438
+ })
3439
+ );
3440
+ this.gesture = controller;
3441
+ const detachGestures = controller.install();
3442
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3443
+ this.detachSheetGesture = () => {
3444
+ detachGuard?.();
3445
+ detachGestures();
3446
+ };
3447
+ }
3448
+ attach() {
3449
+ this.mountSheetGesture();
3450
+ this.teardowns.add(() => {
3451
+ this.detachSheetGesture?.();
3452
+ this.detachSheetGesture = null;
3460
3453
  });
3461
- this.teardowns.add(this.gesture.install());
3462
3454
  try {
3463
3455
  this.attachFeatures();
3464
3456
  } catch (err) {
@@ -3489,24 +3481,13 @@ var BottomSheetEngine = class {
3489
3481
  }
3490
3482
  }
3491
3483
  attachFeatures() {
3492
- if (this.scrollContainer) {
3493
- this.teardowns.add(installContentSwipe({
3494
- container: this.scrollContainer,
3495
- isDragging: () => this.gesture?.isDragging ?? false,
3496
- isAnimating: () => this.animation.isAnimating,
3497
- getAllowedIds: () => this.allowedIdsBySize(),
3498
- getActiveId: () => this.activeId,
3499
- snapTo: (id) => {
3500
- void this.snapTo(id);
3501
- }
3502
- }));
3503
- }
3484
+ this.runFeatureStage("attach");
3504
3485
  if (typeof window !== "undefined") {
3505
3486
  this.teardowns.add(installResizeObserver({
3506
3487
  element: this.element,
3507
3488
  getMode: () => this.mode,
3508
3489
  isDestroyed: () => this.destroyed,
3509
- isDragging: () => this.gesture?.isDragging ?? false,
3490
+ isDragging: () => this.isDraggingAny(),
3510
3491
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3511
3492
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3512
3493
  getSize: () => this.size,
@@ -3540,31 +3521,6 @@ var BottomSheetEngine = class {
3540
3521
  () => document.removeEventListener("keydown", onKey)
3541
3522
  );
3542
3523
  }
3543
- this.teardowns.add(installVisualViewport({
3544
- element: this.element,
3545
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3546
- isDestroyed: () => this.destroyed,
3547
- isDragging: () => this.gesture?.isDragging ?? false,
3548
- recomputeSnaps: () => this.recomputeSnaps(),
3549
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3550
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3551
- getSize: () => this.size,
3552
- setMaxAxisSize: (size) => {
3553
- this.snaps.setMaxAxisSize(size);
3554
- },
3555
- setSize: (size) => {
3556
- this.size = size;
3557
- },
3558
- applySize: (size) => this.applySize(size),
3559
- cancelInFlight: () => {
3560
- this.animation.cancel();
3561
- },
3562
- newCycle: () => {
3563
- this.newCycle();
3564
- },
3565
- isAnimating: () => this.animation.isAnimating,
3566
- resyncAfterCancel: () => this.resyncAfterResize()
3567
- }));
3568
3524
  }
3569
3525
  this.teardowns.add(installSliderKeyboard({
3570
3526
  handle: this.handle,
@@ -3605,123 +3561,591 @@ var BottomSheetEngine = class {
3605
3561
  }
3606
3562
  return;
3607
3563
  }
3608
- const previousSize = this.size;
3609
- const previousSnapSize = this.snaps.findById(previousId)?.size;
3610
- this.scrollCache.cache(previousId, previousSize, target.size);
3611
- this.activeId = target.id;
3612
- if (previousSnapSize === 0 && target.size > 0) {
3613
- this.opening = true;
3614
- sheetStack.promote(this.id);
3564
+ const previousSize = this.size;
3565
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
3566
+ this.scrollCache.cache(previousId, previousSize, target.size);
3567
+ this.activeId = target.id;
3568
+ if (previousSnapSize === 0 && target.size > 0) {
3569
+ this.opening = true;
3570
+ sheetStack.promote(this.id);
3571
+ }
3572
+ const settleCap = this.snaps.getMaxAxisSize();
3573
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3574
+ this.allowOvershoot = true;
3575
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3576
+ if (signal.aborted) return;
3577
+ this.allowOvershoot = false;
3578
+ this.opening = false;
3579
+ this.completeSnap(
3580
+ target.id,
3581
+ target.size,
3582
+ previousSize,
3583
+ previousSnapSize === 0,
3584
+ true
3585
+ );
3586
+ });
3587
+ }
3588
+ isDraggingAny() {
3589
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3590
+ }
3591
+ clearWillChangeIfIdle() {
3592
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3593
+ this.element.style.willChange = "";
3594
+ }
3595
+ haptic() {
3596
+ if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3597
+ try {
3598
+ navigator.vibrate(HAPTIC_DURATION_MS);
3599
+ } catch {
3600
+ }
3601
+ }
3602
+ }
3603
+ applySize(size, skipTransform = false) {
3604
+ const cap = this.snaps.getMaxAxisSize();
3605
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3606
+ this.size = clamped;
3607
+ if (clamped > 0) this.setRestClosed(false);
3608
+ const offset = cap - clamped;
3609
+ const style = this.element.style;
3610
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3611
+ if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3612
+ style.setProperty("--bs-size", `${clamped}px`);
3613
+ if (this.scrimParent) {
3614
+ this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
3615
+ }
3616
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3617
+ this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
3618
+ }
3619
+ if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3620
+ this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
3621
+ }
3622
+ }
3623
+ const progress = this.computeProgress(clamped);
3624
+ const progressChanged = this.progressWriteSentinel.shouldWrite(
3625
+ progress,
3626
+ OPACITY_WRITE_EPSILON
3627
+ );
3628
+ if (progressChanged) {
3629
+ style.setProperty("--bs-progress", String(progress));
3630
+ if (this.anchorHost) {
3631
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3632
+ }
3633
+ if (this.rootEl && this.rootEl !== this.anchorHost) {
3634
+ this.rootEl.style.setProperty("--bs-progress", String(progress));
3635
+ }
3636
+ }
3637
+ this.scrim.applyOpacity(progress, progressChanged);
3638
+ if (progressChanged && this.bus.listenerCount("progress") > 0) {
3639
+ this.progressPayload.value = progress;
3640
+ this.progressPayload.size = clamped;
3641
+ this.emit("progress", this.progressPayload);
3642
+ }
3643
+ }
3644
+ computeProgress(size) {
3645
+ const { min, max } = this.getAllowedRange();
3646
+ const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3647
+ if (reachableMax <= min) return 0;
3648
+ return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3649
+ }
3650
+ handleOpen() {
3651
+ sheetStack.promote(this.id);
3652
+ try {
3653
+ this.lifecycle.install();
3654
+ } catch (err) {
3655
+ if (!this.destroyed) this.newCycle();
3656
+ this.handleClose();
3657
+ this.applySize(0);
3658
+ this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3659
+ this.size = 0;
3660
+ queueMicrotask(() => {
3661
+ throw err;
3662
+ });
3663
+ }
3664
+ }
3665
+ resyncAfterResize() {
3666
+ if (this.destroyed) return;
3667
+ const target = this.snaps.findById(this.activeId);
3668
+ if (!target) return;
3669
+ this.updateAriaSlider();
3670
+ this.emit("snap", {
3671
+ id: target.id,
3672
+ size: this.size,
3673
+ progress: this.computeProgress(this.size)
3674
+ });
3675
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3676
+ this.emitOpenSequence(target.id);
3677
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3678
+ this.emitCloseSequence();
3679
+ }
3680
+ }
3681
+ handleClose() {
3682
+ this.opening = false;
3683
+ this.lifecycle.release();
3684
+ sheetStack.update();
3685
+ }
3686
+ };
3687
+
3688
+ // src/core/features/history-coordinator.ts
3689
+ var entries = [];
3690
+ var subscribers = /* @__PURE__ */ new Set();
3691
+ var suppress = 0;
3692
+ var internalDepth = 0;
3693
+ var pendingRebrands = [];
3694
+ var listenerInstalled = false;
3695
+ var patchInstalled = false;
3696
+ var origPushState = null;
3697
+ var origReplaceState = null;
3698
+ var patchedPushState = null;
3699
+ var patchedReplaceState = null;
3700
+ var hasWindow = () => typeof window !== "undefined";
3701
+ var hasHistory = () => typeof history !== "undefined";
3702
+ var brandMarker = (surface) => ({
3703
+ ...surface.markerState(),
3704
+ __bs: true
3705
+ });
3706
+ var runInternal = (fn) => {
3707
+ internalDepth += 1;
3708
+ try {
3709
+ fn();
3710
+ } finally {
3711
+ internalDepth -= 1;
3712
+ }
3713
+ };
3714
+ var notifyRouteChange = () => {
3715
+ const snapshot = Array.from(subscribers);
3716
+ for (const fn of snapshot) fn();
3717
+ };
3718
+ var ensureListener = () => {
3719
+ if (listenerInstalled || !hasWindow()) return;
3720
+ window.addEventListener("popstate", onPopState);
3721
+ listenerInstalled = true;
3722
+ };
3723
+ var maybeTeardownListener = () => {
3724
+ if (!listenerInstalled) return;
3725
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3726
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3727
+ listenerInstalled = false;
3728
+ };
3729
+ var installPatch = () => {
3730
+ if (patchInstalled || !hasHistory()) return;
3731
+ origPushState = history.pushState;
3732
+ origReplaceState = history.replaceState;
3733
+ patchedPushState = function(...args) {
3734
+ origPushState.apply(this, args);
3735
+ if (internalDepth === 0) notifyRouteChange();
3736
+ };
3737
+ patchedReplaceState = function(...args) {
3738
+ origReplaceState.apply(this, args);
3739
+ if (internalDepth === 0) notifyRouteChange();
3740
+ };
3741
+ history.pushState = patchedPushState;
3742
+ history.replaceState = patchedReplaceState;
3743
+ patchInstalled = true;
3744
+ };
3745
+ var removePatch = () => {
3746
+ if (!patchInstalled) return;
3747
+ if (history.pushState === patchedPushState && origPushState) {
3748
+ history.pushState = origPushState;
3749
+ }
3750
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3751
+ history.replaceState = origReplaceState;
3752
+ }
3753
+ origPushState = null;
3754
+ origReplaceState = null;
3755
+ patchedPushState = null;
3756
+ patchedReplaceState = null;
3757
+ patchInstalled = false;
3758
+ };
3759
+ function onPopState() {
3760
+ if (suppress > 0) {
3761
+ suppress -= 1;
3762
+ const rebrand = pendingRebrands.shift();
3763
+ if (rebrand) rebrand();
3764
+ maybeTeardownListener();
3765
+ return;
3766
+ }
3767
+ if (entries.length === 0) {
3768
+ notifyRouteChange();
3769
+ maybeTeardownListener();
3770
+ return;
3771
+ }
3772
+ let target = null;
3773
+ let targetIdx = -1;
3774
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3775
+ const entry = entries[i];
3776
+ if (!entry.surface.isOpen()) continue;
3777
+ if (!target) {
3778
+ target = entry;
3779
+ targetIdx = i;
3780
+ }
3781
+ if (entry.surface.isTop()) {
3782
+ target = entry;
3783
+ targetIdx = i;
3784
+ break;
3785
+ }
3786
+ }
3787
+ if (!target) {
3788
+ const removed = entries.pop();
3789
+ if (removed) removed.live = false;
3790
+ maybeTeardownListener();
3791
+ return;
3792
+ }
3793
+ target.live = false;
3794
+ entries.splice(targetIdx, 1);
3795
+ const settled = target;
3796
+ void Promise.resolve(settled.surface.close()).then(() => {
3797
+ if (settled.surface.isOpen()) restore(settled);
3798
+ });
3799
+ maybeTeardownListener();
3800
+ }
3801
+ function restore(entry) {
3802
+ if (!hasWindow() || !hasHistory()) return;
3803
+ entry.live = true;
3804
+ runInternal(() => {
3805
+ try {
3806
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3807
+ } catch {
3808
+ entry.live = false;
3809
+ }
3810
+ });
3811
+ if (entry.live) {
3812
+ entries.push(entry);
3813
+ ensureListener();
3814
+ }
3815
+ }
3816
+ function pushBackMarker(surface) {
3817
+ const entry = { surface, priorUrl: void 0, live: false };
3818
+ if (!hasWindow() || !hasHistory()) {
3819
+ return entry;
3820
+ }
3821
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3822
+ runInternal(() => {
3823
+ try {
3824
+ history.pushState(brandMarker(surface), "", surface.url);
3825
+ entry.live = true;
3826
+ } catch {
3827
+ entry.live = false;
3828
+ }
3829
+ });
3830
+ if (entry.live) {
3831
+ entries.push(entry);
3832
+ ensureListener();
3833
+ }
3834
+ return entry;
3835
+ }
3836
+ function popBackMarker(handle) {
3837
+ if (!hasWindow() || !hasHistory()) return;
3838
+ const entry = handle;
3839
+ const idx = entries.indexOf(entry);
3840
+ if (idx === -1 || !entry.live) return;
3841
+ const last = entries.length - 1;
3842
+ entry.live = false;
3843
+ entries.splice(idx, 1);
3844
+ let rebrand = null;
3845
+ if (idx !== last) {
3846
+ const rebrandSurface = entries[entries.length - 1].surface;
3847
+ const rebrandUrl = entry.priorUrl;
3848
+ rebrand = () => {
3849
+ runInternal(() => {
3850
+ history.replaceState(
3851
+ brandMarker(rebrandSurface),
3852
+ "",
3853
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3854
+ );
3855
+ });
3856
+ };
3857
+ }
3858
+ pendingRebrands.push(rebrand);
3859
+ suppress += 1;
3860
+ runInternal(() => {
3861
+ try {
3862
+ history.back();
3863
+ } catch {
3864
+ suppress = Math.max(0, suppress - 1);
3865
+ pendingRebrands.pop();
3866
+ }
3867
+ });
3868
+ maybeTeardownListener();
3869
+ }
3870
+ function subscribeRouteChange(fn) {
3871
+ if (!hasWindow() || !hasHistory()) return () => {
3872
+ };
3873
+ subscribers.add(fn);
3874
+ if (subscribers.size === 1) installPatch();
3875
+ ensureListener();
3876
+ return () => {
3877
+ if (!subscribers.has(fn)) return;
3878
+ subscribers.delete(fn);
3879
+ if (subscribers.size === 0) removePatch();
3880
+ maybeTeardownListener();
3881
+ };
3882
+ }
3883
+
3884
+ // src/core/features/route.ts
3885
+ function installRoute(deps) {
3886
+ if (typeof window === "undefined") {
3887
+ return () => {
3888
+ };
3889
+ }
3890
+ if (!deps.routedTo && !deps.closeOnBack) {
3891
+ return () => {
3892
+ };
3893
+ }
3894
+ let handle = null;
3895
+ const onOpen = () => {
3896
+ if (handle) return;
3897
+ handle = pushBackMarker({
3898
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3899
+ isTop: deps.isTopSheet,
3900
+ close: deps.close,
3901
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3902
+ url: deps.routedTo
3903
+ });
3904
+ };
3905
+ const onClose = () => {
3906
+ if (!handle) return;
3907
+ popBackMarker(handle);
3908
+ handle = null;
3909
+ };
3910
+ const unsubscribeOpen = deps.on("open", onOpen);
3911
+ const unsubscribeClose = deps.on("close", onClose);
3912
+ if (deps.getSize() > 0) onOpen();
3913
+ return () => {
3914
+ unsubscribeOpen();
3915
+ unsubscribeClose();
3916
+ if (handle) {
3917
+ popBackMarker(handle);
3918
+ handle = null;
3919
+ }
3920
+ };
3921
+ }
3922
+ function installRouteChange(deps) {
3923
+ return subscribeRouteChange(() => {
3924
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3925
+ void deps.close();
3926
+ });
3927
+ }
3928
+
3929
+ // src/core/features/persist.ts
3930
+ function installPersist(engine, key) {
3931
+ const unsubscribe = engine.on("snap", ({ id }) => {
3932
+ if (typeof window === "undefined") return;
3933
+ if (typeof localStorage === "undefined") return;
3934
+ try {
3935
+ localStorage.setItem(key, id);
3936
+ } catch {
3615
3937
  }
3616
- const settleCap = this.snaps.getMaxAxisSize();
3617
- const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3618
- this.allowOvershoot = true;
3619
- void this.animation.animateTo(settleSize, velocity).then(() => {
3620
- if (signal.aborted) return;
3621
- this.allowOvershoot = false;
3622
- this.opening = false;
3623
- this.completeSnap(
3624
- target.id,
3625
- target.size,
3626
- previousSize,
3627
- previousSnapSize === 0,
3628
- true
3629
- );
3630
- });
3631
- }
3632
- clearWillChangeIfIdle() {
3633
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3634
- this.element.style.willChange = "";
3938
+ });
3939
+ return unsubscribe;
3940
+ }
3941
+
3942
+ // src/core/features/auto-collapse.ts
3943
+ function installAutoCollapse(deps) {
3944
+ const { ms } = deps;
3945
+ if (ms === void 0 || ms <= 0) {
3946
+ return () => {
3947
+ };
3635
3948
  }
3636
- haptic() {
3637
- if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3638
- try {
3639
- navigator.vibrate(HAPTIC_DURATION_MS);
3640
- } catch {
3641
- }
3949
+ let timer = null;
3950
+ const clearTimer = () => {
3951
+ if (timer !== null) {
3952
+ clearTimeout(timer);
3953
+ timer = null;
3954
+ }
3955
+ };
3956
+ const fire = () => {
3957
+ if (deps.isDestroyed()) return;
3958
+ if (deps.isDragging()) return;
3959
+ const active = deps.resolveSnap(deps.getActiveId());
3960
+ if (!active || active.size === 0) return;
3961
+ let target = null;
3962
+ for (const id of deps.getAllowedIds()) {
3963
+ const snap = deps.resolveSnap(id);
3964
+ if (!snap || snap.size <= 0) continue;
3965
+ if (!target || snap.size < target.size) target = snap;
3642
3966
  }
3967
+ if (!target) return;
3968
+ if (target.id === active.id || target.size >= active.size) return;
3969
+ deps.snapTo(target.id);
3970
+ };
3971
+ const reset = () => {
3972
+ clearTimer();
3973
+ if (deps.isDestroyed()) return;
3974
+ timer = setTimeout(() => {
3975
+ timer = null;
3976
+ fire();
3977
+ }, ms);
3978
+ };
3979
+ const unsubscribe = deps.on("snap", () => reset());
3980
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3981
+ return () => {
3982
+ clearTimer();
3983
+ unsubscribe();
3984
+ };
3985
+ }
3986
+
3987
+ // src/core/features/content-drag.ts
3988
+ function installContentDrag(deps) {
3989
+ const detach = deps.attachDragSurface(deps.container, "content");
3990
+ return () => detach?.();
3991
+ }
3992
+
3993
+ // src/core/features/visual-viewport.ts
3994
+ var SAFETY_PAD = 8;
3995
+ function installVisualViewport(deps) {
3996
+ if (typeof window === "undefined" || !window.visualViewport) {
3997
+ return () => {
3998
+ };
3643
3999
  }
3644
- applySize(size) {
3645
- const cap = this.snaps.getMaxAxisSize();
3646
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3647
- this.size = clamped;
3648
- const offset = cap - clamped;
3649
- const style = this.element.style;
3650
- style.transform = this.transformTemplate(offset);
3651
- if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3652
- style.setProperty("--bs-size", `${clamped}px`);
3653
- if (this.scrimParent) {
3654
- this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
4000
+ const vv = window.visualViewport;
4001
+ let rafId = null;
4002
+ const onResize = () => {
4003
+ if (rafId !== null) return;
4004
+ rafId = requestAnimationFrame(() => {
4005
+ rafId = null;
4006
+ if (deps.isDestroyed()) return;
4007
+ deps.recomputeSnaps();
4008
+ const isVertical = deps.isVerticalAxis();
4009
+ const visible = isVertical ? vv.height : vv.width;
4010
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
4011
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
4012
+ if (clamp < deps.getMaxAxisSize()) {
4013
+ deps.setMaxAxisSize(clamp);
3655
4014
  }
3656
- if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3657
- this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
4015
+ const wasAnimating = deps.isAnimating?.() ?? false;
4016
+ deps.cancelInFlight();
4017
+ deps.newCycle();
4018
+ const max = deps.getMaxAxisSize();
4019
+ deps.setSize(Math.min(deps.getSize(), max));
4020
+ const current = deps.resolveActiveSnap();
4021
+ if (current && !deps.isDragging()) {
4022
+ deps.applySize(Math.min(current.size, max));
4023
+ if (wasAnimating) deps.resyncAfterCancel?.();
4024
+ } else {
4025
+ deps.applySize(deps.getSize());
3658
4026
  }
3659
- if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3660
- this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
4027
+ });
4028
+ };
4029
+ vv.addEventListener("resize", onResize);
4030
+ return () => {
4031
+ vv.removeEventListener("resize", onResize);
4032
+ if (rafId !== null) cancelAnimationFrame(rafId);
4033
+ };
4034
+ }
4035
+
4036
+ // src/core/features/engine-features.ts
4037
+ function routeFeature() {
4038
+ return {
4039
+ name: "route",
4040
+ install: (ctx) => {
4041
+ const teardowns = new TeardownStack();
4042
+ if (ctx.options.closeOnRouteChange) {
4043
+ teardowns.add(
4044
+ installRouteChange({
4045
+ isDestroyed: ctx.isDestroyed,
4046
+ getSize: ctx.getSize,
4047
+ close: () => ctx.close()
4048
+ })
4049
+ );
3661
4050
  }
4051
+ teardowns.add(
4052
+ installRoute({
4053
+ routedTo: ctx.options.routedTo,
4054
+ closeOnBack: ctx.options.closeOnBack,
4055
+ isTopSheet: ctx.isTopSheet,
4056
+ getSize: ctx.getSize,
4057
+ isDestroyed: ctx.isDestroyed,
4058
+ close: () => ctx.close("back"),
4059
+ on: ctx.on,
4060
+ sheetId: ctx.sheetId
4061
+ })
4062
+ );
4063
+ return () => teardowns.drain();
3662
4064
  }
3663
- const progress = this.computeProgress(clamped);
3664
- const progressChanged = this.progressWriteSentinel.shouldWrite(
3665
- progress,
3666
- OPACITY_WRITE_EPSILON
3667
- );
3668
- if (progressChanged) {
3669
- style.setProperty("--bs-progress", String(progress));
3670
- if (this.anchorHost) {
3671
- this.anchorHost.style.setProperty("--bs-progress", String(progress));
3672
- }
3673
- if (this.rootEl && this.rootEl !== this.anchorHost) {
3674
- this.rootEl.style.setProperty("--bs-progress", String(progress));
3675
- }
4065
+ };
4066
+ }
4067
+ function persistFeature() {
4068
+ return {
4069
+ name: "persist",
4070
+ install: (ctx) => {
4071
+ const key = ctx.options.persistKey;
4072
+ if (!key) return;
4073
+ return installPersist({ on: ctx.on }, key);
3676
4074
  }
3677
- this.scrim.applyOpacity(progress, progressChanged);
3678
- if (progressChanged && this.bus.listenerCount("progress") > 0) {
3679
- this.progressPayload.value = progress;
3680
- this.progressPayload.size = clamped;
3681
- this.emit("progress", this.progressPayload);
4075
+ };
4076
+ }
4077
+ function autoCollapseFeature() {
4078
+ return {
4079
+ name: "auto-collapse",
4080
+ install: (ctx) => installAutoCollapse({
4081
+ ms: ctx.options.autoCollapseAfter,
4082
+ isDestroyed: ctx.isDestroyed,
4083
+ isDragging: ctx.isDragging,
4084
+ getAllowedIds: ctx.getAllowedIds,
4085
+ getActiveId: ctx.getActiveId,
4086
+ resolveSnap: ctx.resolveSnap,
4087
+ snapTo: ctx.snapTo,
4088
+ on: ctx.on
4089
+ })
4090
+ };
4091
+ }
4092
+ function contentSwipeFeature() {
4093
+ return {
4094
+ name: "content-swipe",
4095
+ stage: "attach",
4096
+ install: (ctx) => {
4097
+ if (!ctx.scrollContainer) return;
4098
+ return installContentDrag({
4099
+ container: ctx.scrollContainer,
4100
+ attachDragSurface: ctx.attachDragSurface
4101
+ });
3682
4102
  }
3683
- }
3684
- computeProgress(size) {
3685
- const { min, max } = this.getAllowedRange();
3686
- const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3687
- if (reachableMax <= min) return 0;
3688
- return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3689
- }
3690
- handleOpen() {
3691
- sheetStack.promote(this.id);
3692
- try {
3693
- this.lifecycle.install();
3694
- } catch (err) {
3695
- if (!this.destroyed) this.newCycle();
3696
- this.handleClose();
3697
- this.applySize(0);
3698
- this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3699
- this.size = 0;
3700
- queueMicrotask(() => {
3701
- throw err;
4103
+ };
4104
+ }
4105
+ function visualViewportFeature() {
4106
+ return {
4107
+ name: "visual-viewport",
4108
+ install: (ctx) => {
4109
+ if (typeof window === "undefined") return;
4110
+ return installVisualViewport({
4111
+ element: ctx.element,
4112
+ isVerticalAxis: ctx.isVerticalAxis,
4113
+ isDestroyed: ctx.isDestroyed,
4114
+ isDragging: ctx.isDragging,
4115
+ recomputeSnaps: ctx.recomputeSnaps,
4116
+ resolveActiveSnap: ctx.resolveActiveSnap,
4117
+ getMaxAxisSize: ctx.getMaxAxisSize,
4118
+ getSize: ctx.getSize,
4119
+ setMaxAxisSize: ctx.setMaxAxisSize,
4120
+ setSize: ctx.setSize,
4121
+ applySize: ctx.applySize,
4122
+ cancelInFlight: ctx.cancelInFlight,
4123
+ newCycle: ctx.newCycle,
4124
+ isAnimating: ctx.isAnimating,
4125
+ resyncAfterCancel: ctx.resyncAfterResize
3702
4126
  });
3703
4127
  }
3704
- }
3705
- resyncAfterResize() {
3706
- if (this.destroyed) return;
3707
- const target = this.snaps.findById(this.activeId);
3708
- if (!target) return;
3709
- this.updateAriaSlider();
3710
- this.emit("snap", {
3711
- id: target.id,
3712
- size: this.size,
3713
- progress: this.computeProgress(this.size)
4128
+ };
4129
+ }
4130
+
4131
+ // src/core/default-features.ts
4132
+ function defaultEngineFeatures() {
4133
+ return [
4134
+ contentSwipeFeature(),
4135
+ visualViewportFeature(),
4136
+ persistFeature(),
4137
+ autoCollapseFeature(),
4138
+ routeFeature()
4139
+ ];
4140
+ }
4141
+
4142
+ // src/core/BottomSheetEngine.ts
4143
+ var BottomSheetEngine = class extends BottomSheetCore {
4144
+ constructor(opts) {
4145
+ super({
4146
+ ...opts,
4147
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
3714
4148
  });
3715
- if (target.size > 0 && !this.lifecycle.isInstalled) {
3716
- this.emitOpenSequence(target.id);
3717
- } else if (target.size === 0 && this.lifecycle.isInstalled) {
3718
- this.emitCloseSequence();
3719
- }
3720
- }
3721
- handleClose() {
3722
- this.opening = false;
3723
- this.lifecycle.release();
3724
- sheetStack.update();
3725
4149
  }
3726
4150
  };
3727
4151
  function useEnginePropSync(engineRef, opts) {
@@ -3789,6 +4213,16 @@ function useEnginePropSync(engineRef, opts) {
3789
4213
  engineRef.current?.setDisableDrag(opts.disableDrag);
3790
4214
  }
3791
4215
  }, [opts.disableDrag]);
4216
+ react.useEffect(() => {
4217
+ if (opts.dragFrom !== void 0) {
4218
+ engineRef.current?.setDragFrom(opts.dragFrom);
4219
+ }
4220
+ }, [opts.dragFrom]);
4221
+ react.useEffect(() => {
4222
+ if (opts.dragFromContent !== void 0) {
4223
+ engineRef.current?.setDragFromContent(opts.dragFromContent);
4224
+ }
4225
+ }, [opts.disableDrag]);
3792
4226
  }
3793
4227
 
3794
4228
  // src/core/features/teleport.ts
@@ -3937,7 +4371,9 @@ function useBottomSheet(opts) {
3937
4371
  maxHeight: opts.maxHeight,
3938
4372
  persistent: opts.persistent,
3939
4373
  disableClose: opts.disableClose,
3940
- disableDrag: opts.disableDrag
4374
+ disableDrag: opts.disableDrag,
4375
+ dragFrom: opts.dragFrom,
4376
+ dragFromContent: opts.dragFromContent
3941
4377
  });
3942
4378
  const subscribe = react.useCallback((fn) => {
3943
4379
  subscribersRef.current.add(fn);
@@ -4005,7 +4441,6 @@ function useBottomSheet(opts) {
4005
4441
  addAnchor,
4006
4442
  setScrimStages,
4007
4443
  recompute,
4008
- engine: engineRef.current,
4009
4444
  getEngine
4010
4445
  }),
4011
4446
  [