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