@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/vue.cjs 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 {
@@ -131,7 +131,7 @@ function createEventBus() {
131
131
  };
132
132
  }
133
133
 
134
- // src/core/primitives/cssLength.ts
134
+ // src/core/primitives/css-length.ts
135
135
  var probe = null;
136
136
  var PROBE_ATTR = "data-bs-probe";
137
137
  var ensureProbe = (axis) => {
@@ -185,7 +185,7 @@ var resolveSnap = (point, mode, measureFit) => {
185
185
  return Math.max(0, el.getBoundingClientRect().width);
186
186
  };
187
187
 
188
- // src/core/primitives/devWarn.ts
188
+ // src/core/primitives/dev-warn.ts
189
189
  function devWarn(...args) {
190
190
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
191
191
  console.warn(...args);
@@ -350,74 +350,6 @@ var SnapResolver = class {
350
350
  }
351
351
  };
352
352
 
353
- // src/core/features/persist.ts
354
- function readPersistedId(key) {
355
- if (!key) return null;
356
- if (typeof window === "undefined") return null;
357
- if (typeof localStorage === "undefined") return null;
358
- try {
359
- return localStorage.getItem(key);
360
- } catch {
361
- return null;
362
- }
363
- }
364
- function installPersist(engine, key) {
365
- const unsubscribe = engine.on("snap", ({ id }) => {
366
- if (typeof window === "undefined") return;
367
- if (typeof localStorage === "undefined") return;
368
- try {
369
- localStorage.setItem(key, id);
370
- } catch {
371
- }
372
- });
373
- return unsubscribe;
374
- }
375
-
376
- // src/core/features/auto-collapse.ts
377
- function installAutoCollapse(deps) {
378
- const { ms } = deps;
379
- if (ms === void 0 || ms <= 0) {
380
- return () => {
381
- };
382
- }
383
- let timer = null;
384
- const clearTimer = () => {
385
- if (timer !== null) {
386
- clearTimeout(timer);
387
- timer = null;
388
- }
389
- };
390
- const fire = () => {
391
- if (deps.isDestroyed()) return;
392
- if (deps.isDragging()) return;
393
- const active = deps.resolveSnap(deps.getActiveId());
394
- if (!active || active.size === 0) return;
395
- let target = null;
396
- for (const id of deps.getAllowedIds()) {
397
- const snap = deps.resolveSnap(id);
398
- if (!snap || snap.size <= 0) continue;
399
- if (!target || snap.size < target.size) target = snap;
400
- }
401
- if (!target) return;
402
- if (target.id === active.id || target.size >= active.size) return;
403
- deps.snapTo(target.id);
404
- };
405
- const reset = () => {
406
- clearTimer();
407
- if (deps.isDestroyed()) return;
408
- timer = setTimeout(() => {
409
- timer = null;
410
- fire();
411
- }, ms);
412
- };
413
- const unsubscribe = deps.on("snap", () => reset());
414
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
415
- return () => {
416
- clearTimer();
417
- unsubscribe();
418
- };
419
- }
420
-
421
353
  // src/core/features/linked-sheets.ts
422
354
  function notifyLinkedSheets(sheets, self) {
423
355
  if (sheets.length === 0) return;
@@ -440,394 +372,65 @@ function notifyLinkedSheets(sheets, self) {
440
372
  }
441
373
  }
442
374
 
443
- // src/core/features/history-coordinator.ts
444
- var entries = [];
445
- var subscribers = /* @__PURE__ */ new Set();
446
- var suppress = 0;
447
- var internalDepth = 0;
448
- var pendingRebrand = null;
449
- var listenerInstalled = false;
450
- var patchInstalled = false;
451
- var origPushState = null;
452
- var origReplaceState = null;
453
- var patchedPushState = null;
454
- var patchedReplaceState = null;
455
- var hasWindow = () => typeof window !== "undefined";
456
- var hasHistory = () => typeof history !== "undefined";
457
- var brandMarker = (surface) => ({
458
- ...surface.markerState(),
459
- __bs: true
460
- });
461
- var runInternal = (fn) => {
462
- internalDepth += 1;
463
- try {
464
- fn();
465
- } finally {
466
- internalDepth -= 1;
467
- }
468
- };
469
- var notifyRouteChange = () => {
470
- const snapshot = Array.from(subscribers);
471
- for (const fn of snapshot) fn();
472
- };
473
- var ensureListener = () => {
474
- if (listenerInstalled || !hasWindow()) return;
475
- window.addEventListener("popstate", onPopState);
476
- listenerInstalled = true;
477
- };
478
- var maybeTeardownListener = () => {
479
- if (!listenerInstalled) return;
480
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
481
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
482
- listenerInstalled = false;
483
- };
484
- var installPatch = () => {
485
- if (patchInstalled || !hasHistory()) return;
486
- origPushState = history.pushState;
487
- origReplaceState = history.replaceState;
488
- patchedPushState = function(...args) {
489
- origPushState.apply(this, args);
490
- if (internalDepth === 0) notifyRouteChange();
491
- };
492
- patchedReplaceState = function(...args) {
493
- origReplaceState.apply(this, args);
494
- if (internalDepth === 0) notifyRouteChange();
495
- };
496
- history.pushState = patchedPushState;
497
- history.replaceState = patchedReplaceState;
498
- patchInstalled = true;
499
- };
500
- var removePatch = () => {
501
- if (!patchInstalled) return;
502
- if (history.pushState === patchedPushState && origPushState) {
503
- history.pushState = origPushState;
504
- }
505
- if (history.replaceState === patchedReplaceState && origReplaceState) {
506
- history.replaceState = origReplaceState;
507
- }
508
- origPushState = null;
509
- origReplaceState = null;
510
- patchedPushState = null;
511
- patchedReplaceState = null;
512
- patchInstalled = false;
513
- };
514
- function onPopState() {
515
- if (suppress > 0) {
516
- suppress -= 1;
517
- const rebrand = pendingRebrand;
518
- pendingRebrand = null;
519
- if (rebrand) rebrand();
520
- maybeTeardownListener();
521
- return;
522
- }
523
- if (entries.length === 0) {
524
- notifyRouteChange();
525
- maybeTeardownListener();
526
- return;
375
+ // src/core/features/resize-observer.ts
376
+ function installResizeObserver(deps) {
377
+ if (typeof window === "undefined") {
378
+ return () => {
379
+ };
527
380
  }
528
- let target = null;
529
- let targetIdx = -1;
530
- for (let i = entries.length - 1; i >= 0; i -= 1) {
531
- const entry = entries[i];
532
- if (!entry.surface.isOpen()) continue;
533
- if (!target) {
534
- target = entry;
535
- targetIdx = i;
381
+ const onResize = () => {
382
+ if (deps.isDestroyed()) return;
383
+ deps.recomputeSnaps();
384
+ const mode = deps.getMode();
385
+ const isVerticalAxis = mode === "bottom" || mode === "top";
386
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
387
+ const max = deps.getMaxAxisSize();
388
+ if (viewportSize2 > 0 && viewportSize2 < max) {
389
+ deps.setMaxAxisSize(viewportSize2);
536
390
  }
537
- if (entry.surface.isTop()) {
538
- target = entry;
539
- targetIdx = i;
540
- break;
391
+ const wasAnimating = deps.isAnimating?.() ?? false;
392
+ deps.cancelInFlight();
393
+ deps.newCycle();
394
+ const current = deps.resolveActiveSnap();
395
+ const newMax = deps.getMaxAxisSize();
396
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
397
+ deps.setSize(targetSize);
398
+ if (current && !deps.isDragging()) {
399
+ deps.applySize(targetSize);
400
+ if (wasAnimating) deps.resyncAfterCancel?.();
541
401
  }
402
+ };
403
+ let resizeObserver = null;
404
+ if (typeof ResizeObserver !== "undefined") {
405
+ resizeObserver = new ResizeObserver(onResize);
406
+ resizeObserver.observe(document.documentElement);
407
+ } else {
408
+ window.addEventListener("resize", onResize);
542
409
  }
543
- if (!target) {
544
- const removed = entries.pop();
545
- if (removed) removed.live = false;
546
- maybeTeardownListener();
547
- return;
548
- }
549
- target.live = false;
550
- entries.splice(targetIdx, 1);
551
- const settled = target;
552
- void Promise.resolve(settled.surface.close()).then(() => {
553
- if (settled.surface.isOpen()) restore(settled);
554
- });
555
- maybeTeardownListener();
410
+ window.addEventListener("orientationchange", onResize);
411
+ const onVisibility = () => {
412
+ if (deps.isDestroyed()) return;
413
+ if (document.hidden) deps.cancelInFlight();
414
+ };
415
+ document.addEventListener("visibilitychange", onVisibility);
416
+ return () => {
417
+ resizeObserver?.disconnect();
418
+ window.removeEventListener("resize", onResize);
419
+ window.removeEventListener("orientationchange", onResize);
420
+ document.removeEventListener("visibilitychange", onVisibility);
421
+ };
556
422
  }
557
- function restore(entry) {
558
- if (!hasWindow() || !hasHistory()) return;
559
- entry.live = true;
560
- runInternal(() => {
561
- try {
562
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
563
- } catch {
564
- entry.live = false;
565
- }
566
- });
567
- if (entry.live) {
568
- entries.push(entry);
569
- ensureListener();
570
- }
571
- }
572
- function pushBackMarker(surface) {
573
- const entry = { surface, priorUrl: void 0, live: false };
574
- if (!hasWindow() || !hasHistory()) {
575
- return entry;
576
- }
577
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
578
- runInternal(() => {
579
- try {
580
- history.pushState(brandMarker(surface), "", surface.url);
581
- entry.live = true;
582
- } catch {
583
- entry.live = false;
584
- }
585
- });
586
- if (entry.live) {
587
- entries.push(entry);
588
- ensureListener();
589
- }
590
- return entry;
591
- }
592
- function popBackMarker(handle) {
593
- if (!hasWindow() || !hasHistory()) return;
594
- const entry = handle;
595
- const idx = entries.indexOf(entry);
596
- if (idx === -1 || !entry.live) return;
597
- const last = entries.length - 1;
598
- entry.live = false;
599
- entries.splice(idx, 1);
600
- if (idx !== last) {
601
- const rebrandSurface = entries[entries.length - 1].surface;
602
- const rebrandUrl = entry.priorUrl;
603
- pendingRebrand = () => {
604
- runInternal(() => {
605
- history.replaceState(
606
- brandMarker(rebrandSurface),
607
- "",
608
- rebrandUrl !== void 0 ? rebrandUrl : location.href
609
- );
610
- });
611
- };
612
- }
613
- suppress += 1;
614
- runInternal(() => {
615
- try {
616
- history.back();
617
- } catch {
618
- suppress = Math.max(0, suppress - 1);
619
- pendingRebrand = null;
620
- }
621
- });
622
- maybeTeardownListener();
623
- }
624
- function subscribeRouteChange(fn) {
625
- if (!hasWindow() || !hasHistory()) return () => {
626
- };
627
- subscribers.add(fn);
628
- if (subscribers.size === 1) installPatch();
629
- ensureListener();
630
- return () => {
631
- if (!subscribers.has(fn)) return;
632
- subscribers.delete(fn);
633
- if (subscribers.size === 0) removePatch();
634
- maybeTeardownListener();
635
- };
636
- }
637
-
638
- // src/core/features/route.ts
639
- function installRoute(deps) {
640
- if (typeof window === "undefined") {
641
- return () => {
642
- };
643
- }
644
- if (!deps.routedTo && !deps.closeOnBack) {
645
- return () => {
646
- };
647
- }
648
- let handle = null;
649
- const onOpen = () => {
650
- if (handle) return;
651
- handle = pushBackMarker({
652
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
653
- isTop: deps.isTopSheet,
654
- close: deps.close,
655
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
656
- url: deps.routedTo
657
- });
658
- };
659
- const onClose = () => {
660
- if (!handle) return;
661
- popBackMarker(handle);
662
- handle = null;
663
- };
664
- const unsubscribeOpen = deps.on("open", onOpen);
665
- const unsubscribeClose = deps.on("close", onClose);
666
- if (deps.getSize() > 0) onOpen();
667
- return () => {
668
- unsubscribeOpen();
669
- unsubscribeClose();
670
- if (handle) {
671
- popBackMarker(handle);
672
- handle = null;
673
- }
674
- };
675
- }
676
- function installRouteChange(deps) {
677
- return subscribeRouteChange(() => {
678
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
679
- void deps.close();
680
- });
681
- }
682
-
683
- // src/core/features/content-swipe.ts
684
- var CONTENT_PULL_THRESHOLD = 60;
685
- function installContentSwipe(deps) {
686
- const { container } = deps;
687
- let startY = 0;
688
- let startScrollTop = 0;
689
- let moved = false;
690
- const onTouchStart = (e) => {
691
- const t = e.touches[0];
692
- if (!t) return;
693
- startY = t.clientY;
694
- startScrollTop = container.scrollTop;
695
- moved = false;
696
- };
697
- const onTouchMove = (e) => {
698
- const t = e.touches[0];
699
- if (!t) return;
700
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
701
- };
702
- const onTouchEnd = (e) => {
703
- if (!moved) return;
704
- if (deps.isDragging() || deps.isAnimating()) return;
705
- if (container.scrollTop !== startScrollTop) return;
706
- const t = e.changedTouches[0];
707
- if (!t) return;
708
- const deltaY = t.clientY - startY;
709
- const atTop = container.scrollTop <= 0;
710
- const allowed = deps.getAllowedIds();
711
- const idx = allowed.indexOf(deps.getActiveId());
712
- if (idx === -1) return;
713
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
714
- deps.snapTo(allowed[idx - 1]);
715
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
716
- deps.snapTo(allowed[idx + 1]);
717
- }
718
- };
719
- container.addEventListener("touchstart", onTouchStart, { passive: true });
720
- container.addEventListener("touchmove", onTouchMove, { passive: true });
721
- container.addEventListener("touchend", onTouchEnd, { passive: true });
722
- return () => {
723
- container.removeEventListener("touchstart", onTouchStart);
724
- container.removeEventListener("touchmove", onTouchMove);
725
- container.removeEventListener("touchend", onTouchEnd);
726
- };
727
- }
728
-
729
- // src/core/features/visual-viewport.ts
730
- var SAFETY_PAD = 8;
731
- function installVisualViewport(deps) {
732
- if (typeof window === "undefined" || !window.visualViewport) {
733
- return () => {
734
- };
735
- }
736
- const vv = window.visualViewport;
737
- let rafId = null;
738
- const onResize = () => {
739
- if (rafId !== null) return;
740
- rafId = requestAnimationFrame(() => {
741
- rafId = null;
742
- if (deps.isDestroyed()) return;
743
- deps.recomputeSnaps();
744
- const isVertical = deps.isVerticalAxis();
745
- const visible = isVertical ? vv.height : vv.width;
746
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
747
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
748
- if (clamp < deps.getMaxAxisSize()) {
749
- deps.setMaxAxisSize(clamp);
750
- }
751
- const wasAnimating = deps.isAnimating?.() ?? false;
752
- deps.cancelInFlight();
753
- deps.newCycle();
754
- const max = deps.getMaxAxisSize();
755
- deps.setSize(Math.min(deps.getSize(), max));
756
- const current = deps.resolveActiveSnap();
757
- if (current && !deps.isDragging()) {
758
- deps.applySize(Math.min(current.size, max));
759
- if (wasAnimating) deps.resyncAfterCancel?.();
760
- } else {
761
- deps.applySize(deps.getSize());
762
- }
763
- });
764
- };
765
- vv.addEventListener("resize", onResize);
766
- return () => {
767
- vv.removeEventListener("resize", onResize);
768
- if (rafId !== null) cancelAnimationFrame(rafId);
769
- };
770
- }
771
-
772
- // src/core/features/resize-observer.ts
773
- function installResizeObserver(deps) {
774
- if (typeof window === "undefined") {
775
- return () => {
776
- };
777
- }
778
- const onResize = () => {
779
- if (deps.isDestroyed()) return;
780
- deps.recomputeSnaps();
781
- const mode = deps.getMode();
782
- const isVerticalAxis = mode === "bottom" || mode === "top";
783
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
784
- const max = deps.getMaxAxisSize();
785
- if (viewportSize2 > 0 && viewportSize2 < max) {
786
- deps.setMaxAxisSize(viewportSize2);
787
- }
788
- const wasAnimating = deps.isAnimating?.() ?? false;
789
- deps.cancelInFlight();
790
- deps.newCycle();
791
- const current = deps.resolveActiveSnap();
792
- const newMax = deps.getMaxAxisSize();
793
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
794
- deps.setSize(targetSize);
795
- if (current && !deps.isDragging()) {
796
- deps.applySize(targetSize);
797
- if (wasAnimating) deps.resyncAfterCancel?.();
798
- }
799
- };
800
- let resizeObserver = null;
801
- if (typeof ResizeObserver !== "undefined") {
802
- resizeObserver = new ResizeObserver(onResize);
803
- resizeObserver.observe(document.documentElement);
804
- } else {
805
- window.addEventListener("resize", onResize);
806
- }
807
- window.addEventListener("orientationchange", onResize);
808
- const onVisibility = () => {
809
- if (deps.isDestroyed()) return;
810
- if (document.hidden) deps.cancelInFlight();
811
- };
812
- document.addEventListener("visibilitychange", onVisibility);
813
- return () => {
814
- resizeObserver?.disconnect();
815
- window.removeEventListener("resize", onResize);
816
- window.removeEventListener("orientationchange", onResize);
817
- document.removeEventListener("visibilitychange", onVisibility);
818
- };
819
- }
820
-
821
- // src/core/features/fit-observer.ts
822
- var contentTargets = (scroller) => {
823
- if (!scroller) return [];
824
- const hasSlot = typeof HTMLSlotElement !== "undefined";
825
- const out = [];
826
- for (const child of Array.from(scroller.children)) {
827
- if (hasSlot && child instanceof HTMLSlotElement) {
828
- for (const el of child.assignedElements()) out.push(el);
829
- } else {
830
- out.push(child);
423
+
424
+ // src/core/features/fit-observer.ts
425
+ var contentTargets = (scroller) => {
426
+ if (!scroller) return [];
427
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
428
+ const out = [];
429
+ for (const child of Array.from(scroller.children)) {
430
+ if (hasSlot && child instanceof HTMLSlotElement) {
431
+ for (const el of child.assignedElements()) out.push(el);
432
+ } else {
433
+ out.push(child);
831
434
  }
832
435
  }
833
436
  return out;
@@ -1669,6 +1272,52 @@ function resolveAnimationPreset(animation) {
1669
1272
  }
1670
1273
  }
1671
1274
 
1275
+ // src/core/animation/waapi-settle.ts
1276
+ var SIM_HZ = 240;
1277
+ var SAMPLE_STRIDE = 4;
1278
+ var MAX_SIM_MS = 3e3;
1279
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1280
+ const cfg = { ...DEFAULT_SPRING, ...config };
1281
+ const subDt = 1 / SIM_HZ;
1282
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1283
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1284
+ const values = [from];
1285
+ let x = from;
1286
+ let v = velocityPxPerS;
1287
+ for (let i = 1; i <= maxSteps; i += 1) {
1288
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1289
+ v += force / cfg.mass * subDt;
1290
+ x += v * subDt;
1291
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1292
+ values.push(to);
1293
+ break;
1294
+ }
1295
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1296
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1297
+ if (atRest) break;
1298
+ }
1299
+ if (values[values.length - 1] !== to) values.push(to);
1300
+ return {
1301
+ values,
1302
+ stepMs,
1303
+ durationMs: (values.length - 1) * stepMs
1304
+ };
1305
+ }
1306
+ function sampleTweenSettle(from, to, durationMs, easing) {
1307
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1308
+ const values = [];
1309
+ for (let i = 0; i <= count; i += 1) {
1310
+ const t = i / count;
1311
+ values.push(from + (to - from) * easing(t));
1312
+ }
1313
+ values[values.length - 1] = to;
1314
+ return {
1315
+ values,
1316
+ stepMs: durationMs / count,
1317
+ durationMs
1318
+ };
1319
+ }
1320
+
1672
1321
  // src/core/controllers/animation-runner.ts
1673
1322
  var DEFAULT_DURATION2 = 220;
1674
1323
  var VELOCITY_PX_PER_S = 1e3;
@@ -1679,6 +1328,10 @@ var AnimationRunner = class {
1679
1328
  __publicField(this, "applySizeFn");
1680
1329
  __publicField(this, "getSize");
1681
1330
  __publicField(this, "isDragging");
1331
+ __publicField(this, "applyAux");
1332
+ __publicField(this, "getTransformFor");
1333
+ __publicField(this, "settleWaapi");
1334
+ __publicField(this, "currentWaapi", null);
1682
1335
  __publicField(this, "animationKind");
1683
1336
  __publicField(this, "springConfig");
1684
1337
  __publicField(this, "duration");
@@ -1694,6 +1347,9 @@ var AnimationRunner = class {
1694
1347
  this.applySizeFn = deps.applySize;
1695
1348
  this.getSize = deps.getSize;
1696
1349
  this.isDragging = deps.isDragging;
1350
+ this.applyAux = deps.applyAux;
1351
+ this.getTransformFor = deps.getTransformFor;
1352
+ this.settleWaapi = opts.settleAnimation === "waapi";
1697
1353
  const preset = resolveAnimationPreset(opts.animation);
1698
1354
  this.animationKind = preset.kind;
1699
1355
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1718,11 +1374,20 @@ var AnimationRunner = class {
1718
1374
  }
1719
1375
  }
1720
1376
  get isAnimating() {
1721
- return this.currentTween !== null || this.currentSpring !== null;
1377
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1722
1378
  }
1723
1379
  cancel() {
1724
1380
  this.currentTween?.cancel();
1725
1381
  this.currentSpring?.cancel();
1382
+ if (this.currentWaapi) {
1383
+ const entry = this.currentWaapi;
1384
+ this.currentWaapi = null;
1385
+ entry.stop();
1386
+ try {
1387
+ entry.anim.cancel();
1388
+ } catch {
1389
+ }
1390
+ }
1726
1391
  }
1727
1392
  async animateTo(target, velocityPxPerMs) {
1728
1393
  if (target === this.getSize()) {
@@ -1741,6 +1406,27 @@ var AnimationRunner = class {
1741
1406
  if (!this.isDragging()) this.element.style.willChange = "";
1742
1407
  return;
1743
1408
  }
1409
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1410
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1411
+ this.getSize(),
1412
+ target,
1413
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1414
+ this.springConfig
1415
+ ) : sampleTweenSettle(
1416
+ this.getSize(),
1417
+ target,
1418
+ this.duration,
1419
+ this.easing
1420
+ );
1421
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1422
+ await this.runWaapiSettle(samples, target);
1423
+ if (!this.isAnimating) {
1424
+ this.getRootEl()?.removeAttribute("data-animating");
1425
+ if (!this.isDragging()) this.element.style.willChange = "";
1426
+ }
1427
+ return;
1428
+ }
1429
+ }
1744
1430
  if (this.animationKind === "spring") {
1745
1431
  const spring = runSpring({
1746
1432
  from: this.getSize(),
@@ -1769,15 +1455,70 @@ var AnimationRunner = class {
1769
1455
  if (!this.isDragging()) this.element.style.willChange = "";
1770
1456
  }
1771
1457
  }
1458
+ async runWaapiSettle(samples, target) {
1459
+ const applyAux = this.applyAux;
1460
+ const getTransformFor = this.getTransformFor;
1461
+ const frames = samples.values.map((v) => ({
1462
+ transform: getTransformFor(v)
1463
+ }));
1464
+ const anim = this.element.animate(frames, {
1465
+ duration: samples.durationMs,
1466
+ easing: "linear",
1467
+ fill: "forwards"
1468
+ });
1469
+ let stopped = false;
1470
+ let auxRaf = 0;
1471
+ const start = performance.now();
1472
+ const lastIdx = samples.values.length - 1;
1473
+ const auxTick = () => {
1474
+ if (stopped) return;
1475
+ const elapsed = performance.now() - start;
1476
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1477
+ applyAux(samples.values[idx]);
1478
+ auxRaf = requestAnimationFrame(auxTick);
1479
+ };
1480
+ auxRaf = requestAnimationFrame(auxTick);
1481
+ const entry = {
1482
+ anim,
1483
+ stop: () => {
1484
+ stopped = true;
1485
+ cancelAnimationFrame(auxRaf);
1486
+ }
1487
+ };
1488
+ this.currentWaapi = entry;
1489
+ try {
1490
+ await anim.finished;
1491
+ } catch {
1492
+ }
1493
+ entry.stop();
1494
+ const wasCurrent = this.currentWaapi === entry;
1495
+ if (wasCurrent) this.currentWaapi = null;
1496
+ if (wasCurrent && anim.playState === "finished") {
1497
+ this.applySizeFn(target);
1498
+ try {
1499
+ anim.cancel();
1500
+ } catch {
1501
+ }
1502
+ }
1503
+ }
1772
1504
  destroy() {
1773
1505
  this.detachReducedMotion?.();
1774
1506
  this.detachReducedMotion = null;
1775
1507
  this.currentTween = null;
1776
1508
  this.currentSpring = null;
1509
+ if (this.currentWaapi) {
1510
+ const entry = this.currentWaapi;
1511
+ this.currentWaapi = null;
1512
+ entry.stop();
1513
+ try {
1514
+ entry.anim.cancel();
1515
+ } catch {
1516
+ }
1517
+ }
1777
1518
  }
1778
1519
  };
1779
1520
 
1780
- // src/core/lifecycle/focusTrap.ts
1521
+ // src/core/lifecycle/focus-trap.ts
1781
1522
  var FOCUSABLE = [
1782
1523
  "a[href]",
1783
1524
  "area[href]",
@@ -1895,7 +1636,7 @@ var installFocusTrap = (container, options = {}) => {
1895
1636
  };
1896
1637
  };
1897
1638
 
1898
- // src/core/lifecycle/scrollLock.ts
1639
+ // src/core/lifecycle/scroll-lock.ts
1899
1640
  var lockCount = 0;
1900
1641
  var savedStyles = null;
1901
1642
  var lockBodyScroll = () => {
@@ -2077,30 +1818,44 @@ var SampleRing = class {
2077
1818
  this.size = 0;
2078
1819
  }
2079
1820
  };
2080
- var installGestures = (handle, mode, callbacks) => {
1821
+ var installGestures = (handle, mode, callbacks, options = {}) => {
2081
1822
  let activePointerId = null;
2082
1823
  let activePointerType = "touch";
2083
1824
  let startCoord = 0;
2084
1825
  let lastCoord = 0;
1826
+ let pendingStart = false;
2085
1827
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
2086
1828
  const MAX_SAMPLES = 32;
2087
1829
  const samples = new SampleRing(MAX_SAMPLES);
2088
1830
  const axis = isAxisVertical(mode) ? "Y" : "X";
2089
1831
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
2090
1832
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1833
+ const capture = (pointerId) => {
1834
+ try {
1835
+ handle.setPointerCapture(pointerId);
1836
+ } catch {
1837
+ }
1838
+ };
1839
+ const releaseTracking = () => {
1840
+ activePointerId = null;
1841
+ pendingStart = false;
1842
+ samples.reset();
1843
+ };
2091
1844
  const onPointerDown = (e) => {
2092
1845
  if (activePointerId !== null) return;
2093
1846
  if (e.button !== void 0 && e.button !== 0) return;
1847
+ if (options.shouldStart && !options.shouldStart(e)) return;
2094
1848
  activePointerId = e.pointerId;
2095
1849
  activePointerType = e.pointerType || "touch";
2096
1850
  startCoord = coordOf(e);
2097
1851
  lastCoord = startCoord;
2098
1852
  samples.reset();
2099
1853
  samples.push(e.timeStamp, startCoord);
2100
- try {
2101
- handle.setPointerCapture(e.pointerId);
2102
- } catch {
1854
+ if (options.deferStart) {
1855
+ pendingStart = true;
1856
+ return;
2103
1857
  }
1858
+ capture(e.pointerId);
2104
1859
  callbacks.onStart(startCoord, activePointerType);
2105
1860
  };
2106
1861
  const onPointerMove = (e) => {
@@ -2108,6 +1863,22 @@ var installGestures = (handle, mode, callbacks) => {
2108
1863
  const coord = coordOf(e);
2109
1864
  const rawDelta = coord - startCoord;
2110
1865
  const delta = rawDelta * sign;
1866
+ if (pendingStart) {
1867
+ const verdict = options.deferStart(e, delta);
1868
+ if (verdict === false) {
1869
+ releaseTracking();
1870
+ return;
1871
+ }
1872
+ if (verdict !== true) return;
1873
+ pendingStart = false;
1874
+ startCoord = coord;
1875
+ lastCoord = coord;
1876
+ samples.reset();
1877
+ samples.push(e.timeStamp, coord);
1878
+ capture(e.pointerId);
1879
+ callbacks.onStart(startCoord, activePointerType);
1880
+ return;
1881
+ }
2111
1882
  lastCoord = coord;
2112
1883
  samples.push(e.timeStamp, coord);
2113
1884
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -2116,6 +1887,10 @@ var installGestures = (handle, mode, callbacks) => {
2116
1887
  };
2117
1888
  const finishGesture = (e) => {
2118
1889
  if (e.pointerId !== activePointerId) return;
1890
+ if (pendingStart) {
1891
+ releaseTracking();
1892
+ return;
1893
+ }
2119
1894
  const rawDelta = lastCoord - startCoord;
2120
1895
  const delta = rawDelta * sign;
2121
1896
  let velocity = 0;
@@ -2138,22 +1913,26 @@ var installGestures = (handle, mode, callbacks) => {
2138
1913
  };
2139
1914
  const onPointerCancel = (e) => {
2140
1915
  if (e.pointerId !== activePointerId) return;
2141
- activePointerId = null;
2142
- samples.reset();
1916
+ const wasPending = pendingStart;
1917
+ releaseTracking();
1918
+ if (wasPending) return;
2143
1919
  callbacks.onCancel?.();
2144
1920
  };
2145
1921
  handle.addEventListener("pointerdown", onPointerDown);
2146
1922
  handle.addEventListener("pointermove", onPointerMove);
2147
1923
  handle.addEventListener("pointerup", finishGesture);
2148
1924
  handle.addEventListener("pointercancel", onPointerCancel);
1925
+ const managesTouchAction = options.manageTouchAction !== false;
2149
1926
  const prevTouchAction = handle.style.touchAction;
2150
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1927
+ if (managesTouchAction) {
1928
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1929
+ }
2151
1930
  return () => {
2152
1931
  handle.removeEventListener("pointerdown", onPointerDown);
2153
1932
  handle.removeEventListener("pointermove", onPointerMove);
2154
1933
  handle.removeEventListener("pointerup", finishGesture);
2155
1934
  handle.removeEventListener("pointercancel", onPointerCancel);
2156
- handle.style.touchAction = prevTouchAction;
1935
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
2157
1936
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
2158
1937
  handle.releasePointerCapture(activePointerId);
2159
1938
  }
@@ -2272,7 +2051,7 @@ var GestureController = class {
2272
2051
  const restoreTo = this.dragStartSize;
2273
2052
  void this.deps.animateTo(restoreTo, 0);
2274
2053
  }
2275
- });
2054
+ }, this.deps.gestureOptions);
2276
2055
  return this.detach;
2277
2056
  }
2278
2057
  forceClearDragState() {
@@ -2284,6 +2063,41 @@ var GestureController = class {
2284
2063
  }
2285
2064
  };
2286
2065
 
2066
+ // src/core/primitives/drag-zones.ts
2067
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2068
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2069
+ var asElement = (target) => {
2070
+ const candidate = target;
2071
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2072
+ };
2073
+ function isDragAllowedFrom(target, mode) {
2074
+ const el = asElement(target);
2075
+ if (!el) return mode !== "zones";
2076
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2077
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2078
+ return true;
2079
+ }
2080
+
2081
+ // src/core/primitives/content-gesture.ts
2082
+ var CONTENT_DRAG_SLOP = 6;
2083
+ function decideContentGesture(input) {
2084
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2085
+ if (Math.abs(input.delta) < slop) return "pending";
2086
+ if (input.scrollTop > 0) return "scroll";
2087
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2088
+ return "drag";
2089
+ }
2090
+
2091
+ // src/core/primitives/touch-scroll-guard.ts
2092
+ function installTouchScrollGuard(surface, isDragging) {
2093
+ const onTouchMove = (e) => {
2094
+ if (!isDragging()) return;
2095
+ if (e.cancelable) e.preventDefault();
2096
+ };
2097
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2098
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2099
+ }
2100
+
2287
2101
  // src/core/primitives/aria-slider-writer.ts
2288
2102
  var AriaSliderWriter = class {
2289
2103
  constructor(handle, mode) {
@@ -2630,7 +2444,7 @@ function resolveEngineOptions(opts, extras) {
2630
2444
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2631
2445
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2632
2446
  if (opts.persistKey) {
2633
- const restored = readPersistedId(opts.persistKey);
2447
+ const restored = readPersistedIdSafe(opts.persistKey);
2634
2448
  if (restored && initialAllowed.includes(restored)) {
2635
2449
  initialId = restored;
2636
2450
  }
@@ -2639,6 +2453,8 @@ function resolveEngineOptions(opts, extras) {
2639
2453
  mode: opts.mode ?? "bottom",
2640
2454
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2641
2455
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2456
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2457
+ dragFromContent: opts.dragFromContent ?? true,
2642
2458
  rubberBandEnabled: opts.rubberBand ?? true,
2643
2459
  closeOnBack: opts.closeOnBack ?? false,
2644
2460
  persistent: opts.persistent ?? false,
@@ -2661,6 +2477,7 @@ function resolveEngineOptions(opts, extras) {
2661
2477
  },
2662
2478
  animation: {
2663
2479
  animation: opts.animation,
2480
+ settleAnimation: opts.settleAnimation,
2664
2481
  duration: opts.duration,
2665
2482
  easing: opts.easing,
2666
2483
  spring: opts.spring,
@@ -2678,6 +2495,15 @@ function resolveEngineOptions(opts, extras) {
2678
2495
  }
2679
2496
  };
2680
2497
  }
2498
+ var readPersistedIdSafe = (key) => {
2499
+ if (typeof window === "undefined") return null;
2500
+ if (typeof localStorage === "undefined") return null;
2501
+ try {
2502
+ return localStorage.getItem(key);
2503
+ } catch {
2504
+ return null;
2505
+ }
2506
+ };
2681
2507
 
2682
2508
  // src/core/primitives/teardown-stack.ts
2683
2509
  var TeardownStack = class {
@@ -2700,9 +2526,9 @@ var TeardownStack = class {
2700
2526
  }
2701
2527
  };
2702
2528
 
2703
- // src/core/BottomSheetEngine.ts
2529
+ // src/core/BottomSheetCore.ts
2704
2530
  var HAPTIC_DURATION_MS = 8;
2705
- var BottomSheetEngine = class {
2531
+ var BottomSheetCore = class {
2706
2532
  constructor(opts) {
2707
2533
  __publicField(this, "id", nextInstanceId("bs"));
2708
2534
  __publicField(this, "element");
@@ -2714,6 +2540,8 @@ var BottomSheetEngine = class {
2714
2540
  __publicField(this, "mode");
2715
2541
  __publicField(this, "flickVelocity");
2716
2542
  __publicField(this, "dragThreshold");
2543
+ __publicField(this, "dragFromMode");
2544
+ __publicField(this, "dragFromContentDefault");
2717
2545
  __publicField(this, "rubberBandEnabled");
2718
2546
  __publicField(this, "scrim");
2719
2547
  __publicField(this, "aria");
@@ -2731,8 +2559,11 @@ var BottomSheetEngine = class {
2731
2559
  __publicField(this, "activeId");
2732
2560
  __publicField(this, "size", 0);
2733
2561
  __publicField(this, "gesture");
2562
+ __publicField(this, "contentGesture");
2563
+ __publicField(this, "detachSheetGesture", null);
2734
2564
  __publicField(this, "rootEl", null);
2735
2565
  __publicField(this, "destroyed", false);
2566
+ __publicField(this, "restClosed", false);
2736
2567
  __publicField(this, "currentViewTransition", null);
2737
2568
  __publicField(this, "currentAbort", new AbortController());
2738
2569
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2763,6 +2594,8 @@ var BottomSheetEngine = class {
2763
2594
  __publicField(this, "stackEffectPrimed", false);
2764
2595
  __publicField(this, "fitDeps");
2765
2596
  __publicField(this, "transformTemplate");
2597
+ __publicField(this, "featureList", []);
2598
+ __publicField(this, "featureCtx");
2766
2599
  const resolved = resolveEngineOptions(opts, {
2767
2600
  shouldApplyInertSiblings: () => {
2768
2601
  if (typeof document === "undefined") return false;
@@ -2783,6 +2616,8 @@ var BottomSheetEngine = class {
2783
2616
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2784
2617
  this.flickVelocity = resolved.flickVelocity;
2785
2618
  this.dragThreshold = resolved.dragThreshold;
2619
+ this.dragFromMode = resolved.dragFrom;
2620
+ this.dragFromContentDefault = resolved.dragFromContent;
2786
2621
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2787
2622
  this.scrim = new ScrimController(
2788
2623
  {
@@ -2809,7 +2644,9 @@ var BottomSheetEngine = class {
2809
2644
  getRootEl: () => this.rootEl,
2810
2645
  applySize: (size) => this.applySize(size),
2811
2646
  getSize: () => this.size,
2812
- isDragging: () => this.gesture?.isDragging ?? false
2647
+ isDragging: () => this.isDraggingAny(),
2648
+ applyAux: (size) => this.applySize(size, true),
2649
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2813
2650
  },
2814
2651
  resolved.animation
2815
2652
  );
@@ -2832,6 +2669,11 @@ var BottomSheetEngine = class {
2832
2669
  if (opts.linkedSheets) {
2833
2670
  this.linkedSheets = opts.linkedSheets;
2834
2671
  }
2672
+ const byName = /* @__PURE__ */ new Map();
2673
+ for (const feature of opts.features ?? []) {
2674
+ byName.set(feature.name, feature);
2675
+ }
2676
+ this.featureList = Array.from(byName.values());
2835
2677
  this.activeId = resolved.initialId;
2836
2678
  auditVhUsage(this.snapPointsRaw);
2837
2679
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2863,11 +2705,13 @@ var BottomSheetEngine = class {
2863
2705
  const initial = this.snaps.findById(this.activeId);
2864
2706
  if (initial) this.size = initial.size;
2865
2707
  this.applySize(this.size);
2708
+ this.setRestClosed(this.size === 0);
2866
2709
  this.updateAriaSlider();
2867
2710
  this.scrollCache = createScrollCache({
2868
2711
  scrollContainer: this.scrollContainer,
2869
2712
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2870
2713
  });
2714
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2871
2715
  this.attach();
2872
2716
  this.registerInStack();
2873
2717
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2892,60 +2736,92 @@ var BottomSheetEngine = class {
2892
2736
  (p) => p.size === "fit" || p.size === "content"
2893
2737
  ),
2894
2738
  isDestroyed: () => this.destroyed,
2895
- isDragging: () => this.gesture?.isDragging ?? false,
2739
+ isDragging: () => this.isDraggingAny(),
2896
2740
  recompute: () => this.recompute()
2897
2741
  })
2898
2742
  );
2899
2743
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2900
2744
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2901
2745
  this.applyAutoAriaLabelledBy();
2902
- if (this.closeOnRouteChange) {
2903
- this.teardowns.add(
2904
- installRouteChange({
2905
- isDestroyed: () => this.destroyed,
2906
- getSize: () => this.size,
2907
- close: () => this.close()
2908
- })
2909
- );
2910
- }
2911
- if (this.persistKey) {
2912
- this.teardowns.add(installPersist(this, this.persistKey));
2746
+ this.runFeatureStage("post");
2747
+ this.warnOrphanFeatureOptions();
2748
+ if (this.size > 0) {
2749
+ this.handleOpen();
2913
2750
  }
2914
- this.teardowns.add(
2915
- installAutoCollapse({
2916
- ms: opts.autoCollapseAfter,
2917
- isDestroyed: () => this.destroyed,
2918
- isDragging: () => this.gesture?.isDragging ?? false,
2919
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2920
- getActiveId: () => this.activeId,
2921
- resolveSnap: (id) => this.snaps.findById(id),
2922
- snapTo: (id) => {
2923
- void this.snapTo(id);
2924
- },
2925
- on: (event, fn) => this.on(event, fn)
2926
- })
2927
- );
2928
- this.teardowns.add(
2929
- installRoute({
2751
+ }
2752
+ buildFeatureContext(autoCollapseAfter) {
2753
+ return {
2754
+ element: this.element,
2755
+ scrollContainer: this.scrollContainer,
2756
+ sheetId: this.id,
2757
+ options: {
2930
2758
  routedTo: this.routedTo,
2931
2759
  closeOnBack: this.closeOnBack,
2932
- isTopSheet: () => this.isTopSheet,
2933
- getSize: () => this.size,
2934
- isDestroyed: () => this.destroyed,
2935
- close: () => this.close("back"),
2936
- on: (event, fn) => this.on(event, fn),
2937
- sheetId: this.id
2938
- })
2939
- );
2940
- if (this.size > 0) {
2941
- this.handleOpen();
2760
+ closeOnRouteChange: this.closeOnRouteChange,
2761
+ persistKey: this.persistKey,
2762
+ autoCollapseAfter
2763
+ },
2764
+ isDestroyed: () => this.destroyed,
2765
+ isDragging: () => this.isDraggingAny(),
2766
+ isAnimating: () => this.animation.isAnimating,
2767
+ isTopSheet: () => this.isTopSheet,
2768
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2769
+ getSize: () => this.size,
2770
+ getActiveId: () => this.activeId,
2771
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2772
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2773
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2774
+ resolveSnap: (id) => this.snaps.findById(id),
2775
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2776
+ setSize: (size) => {
2777
+ this.size = size;
2778
+ },
2779
+ setMaxAxisSize: (size) => {
2780
+ this.snaps.setMaxAxisSize(size);
2781
+ },
2782
+ applySize: (size) => this.applySize(size),
2783
+ recomputeSnaps: () => this.recomputeSnaps(),
2784
+ newCycle: () => {
2785
+ this.newCycle();
2786
+ },
2787
+ cancelInFlight: () => {
2788
+ this.animation.cancel();
2789
+ },
2790
+ resyncAfterResize: () => this.resyncAfterResize(),
2791
+ snapTo: (id) => {
2792
+ void this.snapTo(id);
2793
+ },
2794
+ close: (reason) => this.close(reason),
2795
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2796
+ on: (event, fn) => this.on(event, fn),
2797
+ addTeardown: (fn) => this.teardowns.add(fn)
2798
+ };
2799
+ }
2800
+ runFeatureStage(stage) {
2801
+ for (const feature of this.featureList) {
2802
+ if ((feature.stage ?? "post") !== stage) continue;
2803
+ const teardown = feature.install(this.featureCtx);
2804
+ if (teardown) this.teardowns.add(teardown);
2805
+ }
2806
+ }
2807
+ warnOrphanFeatureOptions() {
2808
+ const names = new Set(this.featureList.map((f) => f.name));
2809
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2810
+ devWarn(
2811
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2812
+ );
2813
+ }
2814
+ if (this.persistKey && !names.has("persist")) {
2815
+ devWarn(
2816
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2817
+ );
2942
2818
  }
2943
2819
  }
2944
2820
  get state() {
2945
2821
  return {
2946
2822
  size: this.size,
2947
2823
  activeId: this.activeId,
2948
- isDragging: this.gesture?.isDragging ?? false,
2824
+ isDragging: this.isDraggingAny(),
2949
2825
  isAnimating: this.animation.isAnimating,
2950
2826
  progress: this.computeProgress(this.size)
2951
2827
  };
@@ -3065,9 +2941,16 @@ var BottomSheetEngine = class {
3065
2941
  progress: this.computeProgress(this.size)
3066
2942
  });
3067
2943
  if (withHaptic) this.haptic();
2944
+ this.setRestClosed(targetSize === 0);
3068
2945
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
3069
2946
  if (targetSize === 0) this.emitCloseSequence();
3070
2947
  }
2948
+ setRestClosed(closed) {
2949
+ if (closed === this.restClosed) return;
2950
+ this.restClosed = closed;
2951
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2952
+ else this.element.removeAttribute("data-bs-rest");
2953
+ }
3071
2954
  emitOpenSequence(id) {
3072
2955
  this.emit("open", { id });
3073
2956
  this.handleOpen();
@@ -3123,6 +3006,20 @@ var BottomSheetEngine = class {
3123
3006
  if (this.destroyed) return;
3124
3007
  this.disableDragFlag = value;
3125
3008
  }
3009
+ setDragFromContent(value) {
3010
+ if (this.destroyed) return;
3011
+ this.dragFromContentDefault = value;
3012
+ }
3013
+ setDragFrom(mode) {
3014
+ if (this.destroyed || mode === this.dragFromMode) return;
3015
+ this.gesture?.forceClearDragState();
3016
+ this.detachSheetGesture?.();
3017
+ this.dragFromMode = mode;
3018
+ this.mountSheetGesture();
3019
+ }
3020
+ getDragFrom() {
3021
+ return this.dragFromMode;
3022
+ }
3126
3023
  isTop() {
3127
3024
  return this.isTopSheet;
3128
3025
  }
@@ -3277,12 +3174,24 @@ var BottomSheetEngine = class {
3277
3174
  auditVhUsage(points);
3278
3175
  this.auditDuplicateSnapIds(points);
3279
3176
  this.snaps.setRaw(points);
3280
- if (allowed) this.snaps.setAllowedIds(allowed);
3177
+ if (allowed) {
3178
+ this.snaps.setAllowedIds(allowed);
3179
+ } else {
3180
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3181
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3182
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3183
+ }
3281
3184
  this.scrollCache.clear();
3282
3185
  this.scrim.invalidateOpacityCache();
3283
3186
  const current = this.snaps.findById(this.activeId);
3284
3187
  if (current) {
3285
3188
  this.applySize(current.size);
3189
+ } else {
3190
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3191
+ if (fallback) {
3192
+ this.activeId = fallback.id;
3193
+ this.applySize(fallback.size);
3194
+ }
3286
3195
  }
3287
3196
  this.updateAriaSlider();
3288
3197
  }
@@ -3292,7 +3201,7 @@ var BottomSheetEngine = class {
3292
3201
  this.snaps.recompute();
3293
3202
  this.maxHeight.clampTo();
3294
3203
  this.scrim.invalidateOpacityCache();
3295
- if (this.gesture?.isDragging) return;
3204
+ if (this.isDraggingAny()) return;
3296
3205
  this.healActiveSnap();
3297
3206
  }
3298
3207
  healActiveSnap() {
@@ -3314,6 +3223,8 @@ var BottomSheetEngine = class {
3314
3223
  this.bus.clear();
3315
3224
  this.animation.cancel();
3316
3225
  this.gesture?.forceClearDragState();
3226
+ this.contentGesture?.forceClearDragState();
3227
+ this.setRestClosed(false);
3317
3228
  this.teardowns.drain();
3318
3229
  this.scrim.destroy();
3319
3230
  this.animation.destroy();
@@ -3426,11 +3337,12 @@ var BottomSheetEngine = class {
3426
3337
  const scale = Math.max(1 - depth * 0.04, 0.86);
3427
3338
  this.element.style.scale = scale === 1 ? "" : String(scale);
3428
3339
  }
3429
- attach() {
3430
- this.gesture = new GestureController({
3431
- handle: this.handle,
3340
+ buildGestureDeps(surface, gestureOptions) {
3341
+ return {
3342
+ handle: surface,
3432
3343
  element: this.element,
3433
3344
  mode: this.mode,
3345
+ gestureOptions,
3434
3346
  getRoot: () => this.rootEl,
3435
3347
  getDragContext: () => {
3436
3348
  const buf = this.dragContextBuf;
@@ -3449,8 +3361,88 @@ var BottomSheetEngine = class {
3449
3361
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3450
3362
  emit: (event, payload) => this.emit(event, payload),
3451
3363
  listenerCount: (event) => this.bus.listenerCount(event)
3364
+ };
3365
+ }
3366
+ isContentDragAllowed() {
3367
+ if (this.disableDragFlag) return false;
3368
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3369
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3370
+ }
3371
+ decideContentDrag(surface, e, delta) {
3372
+ if (this.destroyed) return false;
3373
+ if (e.pointerType === "mouse") return false;
3374
+ if (this.gesture?.isDragging) return false;
3375
+ if (!this.isContentDragAllowed()) return false;
3376
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3377
+ const decision = decideContentGesture({
3378
+ delta,
3379
+ scrollTop: surface.scrollTop,
3380
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3381
+ });
3382
+ if (decision === "pending") return null;
3383
+ return decision === "drag";
3384
+ }
3385
+ attachDragSurface(surface, kind) {
3386
+ if (this.destroyed || kind !== "content") return;
3387
+ if (this.contentGesture) return;
3388
+ const controller = new GestureController(
3389
+ this.buildGestureDeps(surface, {
3390
+ manageTouchAction: false,
3391
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3392
+ })
3393
+ );
3394
+ this.contentGesture = controller;
3395
+ const detachGestures = controller.install();
3396
+ const detachGuard = installTouchScrollGuard(
3397
+ surface,
3398
+ () => controller.isDragging
3399
+ );
3400
+ return () => {
3401
+ detachGuard();
3402
+ detachGestures();
3403
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3404
+ };
3405
+ }
3406
+ sheetDragSurface() {
3407
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3408
+ }
3409
+ canStartSheetDrag(e) {
3410
+ if (this.contentGesture?.isDragging) return false;
3411
+ const target = e.target;
3412
+ const scoped = typeof target?.closest === "function";
3413
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3414
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3415
+ return true;
3416
+ }
3417
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3418
+ if (this.dragFromMode === "handle") return true;
3419
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3420
+ return false;
3421
+ }
3422
+ return true;
3423
+ }
3424
+ mountSheetGesture() {
3425
+ const surface = this.sheetDragSurface();
3426
+ const controller = new GestureController(
3427
+ this.buildGestureDeps(surface, {
3428
+ shouldStart: (e) => this.canStartSheetDrag(e),
3429
+ manageTouchAction: this.dragFromMode === "handle"
3430
+ })
3431
+ );
3432
+ this.gesture = controller;
3433
+ const detachGestures = controller.install();
3434
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3435
+ this.detachSheetGesture = () => {
3436
+ detachGuard?.();
3437
+ detachGestures();
3438
+ };
3439
+ }
3440
+ attach() {
3441
+ this.mountSheetGesture();
3442
+ this.teardowns.add(() => {
3443
+ this.detachSheetGesture?.();
3444
+ this.detachSheetGesture = null;
3452
3445
  });
3453
- this.teardowns.add(this.gesture.install());
3454
3446
  try {
3455
3447
  this.attachFeatures();
3456
3448
  } catch (err) {
@@ -3481,24 +3473,13 @@ var BottomSheetEngine = class {
3481
3473
  }
3482
3474
  }
3483
3475
  attachFeatures() {
3484
- if (this.scrollContainer) {
3485
- this.teardowns.add(installContentSwipe({
3486
- container: this.scrollContainer,
3487
- isDragging: () => this.gesture?.isDragging ?? false,
3488
- isAnimating: () => this.animation.isAnimating,
3489
- getAllowedIds: () => this.allowedIdsBySize(),
3490
- getActiveId: () => this.activeId,
3491
- snapTo: (id) => {
3492
- void this.snapTo(id);
3493
- }
3494
- }));
3495
- }
3476
+ this.runFeatureStage("attach");
3496
3477
  if (typeof window !== "undefined") {
3497
3478
  this.teardowns.add(installResizeObserver({
3498
3479
  element: this.element,
3499
3480
  getMode: () => this.mode,
3500
3481
  isDestroyed: () => this.destroyed,
3501
- isDragging: () => this.gesture?.isDragging ?? false,
3482
+ isDragging: () => this.isDraggingAny(),
3502
3483
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3503
3484
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3504
3485
  getSize: () => this.size,
@@ -3532,31 +3513,6 @@ var BottomSheetEngine = class {
3532
3513
  () => document.removeEventListener("keydown", onKey)
3533
3514
  );
3534
3515
  }
3535
- this.teardowns.add(installVisualViewport({
3536
- element: this.element,
3537
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3538
- isDestroyed: () => this.destroyed,
3539
- isDragging: () => this.gesture?.isDragging ?? false,
3540
- recomputeSnaps: () => this.recomputeSnaps(),
3541
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3542
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3543
- getSize: () => this.size,
3544
- setMaxAxisSize: (size) => {
3545
- this.snaps.setMaxAxisSize(size);
3546
- },
3547
- setSize: (size) => {
3548
- this.size = size;
3549
- },
3550
- applySize: (size) => this.applySize(size),
3551
- cancelInFlight: () => {
3552
- this.animation.cancel();
3553
- },
3554
- newCycle: () => {
3555
- this.newCycle();
3556
- },
3557
- isAnimating: () => this.animation.isAnimating,
3558
- resyncAfterCancel: () => this.resyncAfterResize()
3559
- }));
3560
3516
  }
3561
3517
  this.teardowns.add(installSliderKeyboard({
3562
3518
  handle: this.handle,
@@ -3597,123 +3553,591 @@ var BottomSheetEngine = class {
3597
3553
  }
3598
3554
  return;
3599
3555
  }
3600
- const previousSize = this.size;
3601
- const previousSnapSize = this.snaps.findById(previousId)?.size;
3602
- this.scrollCache.cache(previousId, previousSize, target.size);
3603
- this.activeId = target.id;
3604
- if (previousSnapSize === 0 && target.size > 0) {
3605
- this.opening = true;
3606
- sheetStack.promote(this.id);
3556
+ const previousSize = this.size;
3557
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
3558
+ this.scrollCache.cache(previousId, previousSize, target.size);
3559
+ this.activeId = target.id;
3560
+ if (previousSnapSize === 0 && target.size > 0) {
3561
+ this.opening = true;
3562
+ sheetStack.promote(this.id);
3563
+ }
3564
+ const settleCap = this.snaps.getMaxAxisSize();
3565
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3566
+ this.allowOvershoot = true;
3567
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3568
+ if (signal.aborted) return;
3569
+ this.allowOvershoot = false;
3570
+ this.opening = false;
3571
+ this.completeSnap(
3572
+ target.id,
3573
+ target.size,
3574
+ previousSize,
3575
+ previousSnapSize === 0,
3576
+ true
3577
+ );
3578
+ });
3579
+ }
3580
+ isDraggingAny() {
3581
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3582
+ }
3583
+ clearWillChangeIfIdle() {
3584
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3585
+ this.element.style.willChange = "";
3586
+ }
3587
+ haptic() {
3588
+ if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3589
+ try {
3590
+ navigator.vibrate(HAPTIC_DURATION_MS);
3591
+ } catch {
3592
+ }
3593
+ }
3594
+ }
3595
+ applySize(size, skipTransform = false) {
3596
+ const cap = this.snaps.getMaxAxisSize();
3597
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3598
+ this.size = clamped;
3599
+ if (clamped > 0) this.setRestClosed(false);
3600
+ const offset = cap - clamped;
3601
+ const style = this.element.style;
3602
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3603
+ if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3604
+ style.setProperty("--bs-size", `${clamped}px`);
3605
+ if (this.scrimParent) {
3606
+ this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
3607
+ }
3608
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3609
+ this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
3610
+ }
3611
+ if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3612
+ this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
3613
+ }
3614
+ }
3615
+ const progress = this.computeProgress(clamped);
3616
+ const progressChanged = this.progressWriteSentinel.shouldWrite(
3617
+ progress,
3618
+ OPACITY_WRITE_EPSILON
3619
+ );
3620
+ if (progressChanged) {
3621
+ style.setProperty("--bs-progress", String(progress));
3622
+ if (this.anchorHost) {
3623
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3624
+ }
3625
+ if (this.rootEl && this.rootEl !== this.anchorHost) {
3626
+ this.rootEl.style.setProperty("--bs-progress", String(progress));
3627
+ }
3628
+ }
3629
+ this.scrim.applyOpacity(progress, progressChanged);
3630
+ if (progressChanged && this.bus.listenerCount("progress") > 0) {
3631
+ this.progressPayload.value = progress;
3632
+ this.progressPayload.size = clamped;
3633
+ this.emit("progress", this.progressPayload);
3634
+ }
3635
+ }
3636
+ computeProgress(size) {
3637
+ const { min, max } = this.getAllowedRange();
3638
+ const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3639
+ if (reachableMax <= min) return 0;
3640
+ return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3641
+ }
3642
+ handleOpen() {
3643
+ sheetStack.promote(this.id);
3644
+ try {
3645
+ this.lifecycle.install();
3646
+ } catch (err) {
3647
+ if (!this.destroyed) this.newCycle();
3648
+ this.handleClose();
3649
+ this.applySize(0);
3650
+ this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3651
+ this.size = 0;
3652
+ queueMicrotask(() => {
3653
+ throw err;
3654
+ });
3655
+ }
3656
+ }
3657
+ resyncAfterResize() {
3658
+ if (this.destroyed) return;
3659
+ const target = this.snaps.findById(this.activeId);
3660
+ if (!target) return;
3661
+ this.updateAriaSlider();
3662
+ this.emit("snap", {
3663
+ id: target.id,
3664
+ size: this.size,
3665
+ progress: this.computeProgress(this.size)
3666
+ });
3667
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3668
+ this.emitOpenSequence(target.id);
3669
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3670
+ this.emitCloseSequence();
3671
+ }
3672
+ }
3673
+ handleClose() {
3674
+ this.opening = false;
3675
+ this.lifecycle.release();
3676
+ sheetStack.update();
3677
+ }
3678
+ };
3679
+
3680
+ // src/core/features/history-coordinator.ts
3681
+ var entries = [];
3682
+ var subscribers = /* @__PURE__ */ new Set();
3683
+ var suppress = 0;
3684
+ var internalDepth = 0;
3685
+ var pendingRebrands = [];
3686
+ var listenerInstalled = false;
3687
+ var patchInstalled = false;
3688
+ var origPushState = null;
3689
+ var origReplaceState = null;
3690
+ var patchedPushState = null;
3691
+ var patchedReplaceState = null;
3692
+ var hasWindow = () => typeof window !== "undefined";
3693
+ var hasHistory = () => typeof history !== "undefined";
3694
+ var brandMarker = (surface) => ({
3695
+ ...surface.markerState(),
3696
+ __bs: true
3697
+ });
3698
+ var runInternal = (fn) => {
3699
+ internalDepth += 1;
3700
+ try {
3701
+ fn();
3702
+ } finally {
3703
+ internalDepth -= 1;
3704
+ }
3705
+ };
3706
+ var notifyRouteChange = () => {
3707
+ const snapshot = Array.from(subscribers);
3708
+ for (const fn of snapshot) fn();
3709
+ };
3710
+ var ensureListener = () => {
3711
+ if (listenerInstalled || !hasWindow()) return;
3712
+ window.addEventListener("popstate", onPopState);
3713
+ listenerInstalled = true;
3714
+ };
3715
+ var maybeTeardownListener = () => {
3716
+ if (!listenerInstalled) return;
3717
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3718
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3719
+ listenerInstalled = false;
3720
+ };
3721
+ var installPatch = () => {
3722
+ if (patchInstalled || !hasHistory()) return;
3723
+ origPushState = history.pushState;
3724
+ origReplaceState = history.replaceState;
3725
+ patchedPushState = function(...args) {
3726
+ origPushState.apply(this, args);
3727
+ if (internalDepth === 0) notifyRouteChange();
3728
+ };
3729
+ patchedReplaceState = function(...args) {
3730
+ origReplaceState.apply(this, args);
3731
+ if (internalDepth === 0) notifyRouteChange();
3732
+ };
3733
+ history.pushState = patchedPushState;
3734
+ history.replaceState = patchedReplaceState;
3735
+ patchInstalled = true;
3736
+ };
3737
+ var removePatch = () => {
3738
+ if (!patchInstalled) return;
3739
+ if (history.pushState === patchedPushState && origPushState) {
3740
+ history.pushState = origPushState;
3741
+ }
3742
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3743
+ history.replaceState = origReplaceState;
3744
+ }
3745
+ origPushState = null;
3746
+ origReplaceState = null;
3747
+ patchedPushState = null;
3748
+ patchedReplaceState = null;
3749
+ patchInstalled = false;
3750
+ };
3751
+ function onPopState() {
3752
+ if (suppress > 0) {
3753
+ suppress -= 1;
3754
+ const rebrand = pendingRebrands.shift();
3755
+ if (rebrand) rebrand();
3756
+ maybeTeardownListener();
3757
+ return;
3758
+ }
3759
+ if (entries.length === 0) {
3760
+ notifyRouteChange();
3761
+ maybeTeardownListener();
3762
+ return;
3763
+ }
3764
+ let target = null;
3765
+ let targetIdx = -1;
3766
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3767
+ const entry = entries[i];
3768
+ if (!entry.surface.isOpen()) continue;
3769
+ if (!target) {
3770
+ target = entry;
3771
+ targetIdx = i;
3772
+ }
3773
+ if (entry.surface.isTop()) {
3774
+ target = entry;
3775
+ targetIdx = i;
3776
+ break;
3777
+ }
3778
+ }
3779
+ if (!target) {
3780
+ const removed = entries.pop();
3781
+ if (removed) removed.live = false;
3782
+ maybeTeardownListener();
3783
+ return;
3784
+ }
3785
+ target.live = false;
3786
+ entries.splice(targetIdx, 1);
3787
+ const settled = target;
3788
+ void Promise.resolve(settled.surface.close()).then(() => {
3789
+ if (settled.surface.isOpen()) restore(settled);
3790
+ });
3791
+ maybeTeardownListener();
3792
+ }
3793
+ function restore(entry) {
3794
+ if (!hasWindow() || !hasHistory()) return;
3795
+ entry.live = true;
3796
+ runInternal(() => {
3797
+ try {
3798
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3799
+ } catch {
3800
+ entry.live = false;
3801
+ }
3802
+ });
3803
+ if (entry.live) {
3804
+ entries.push(entry);
3805
+ ensureListener();
3806
+ }
3807
+ }
3808
+ function pushBackMarker(surface) {
3809
+ const entry = { surface, priorUrl: void 0, live: false };
3810
+ if (!hasWindow() || !hasHistory()) {
3811
+ return entry;
3812
+ }
3813
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3814
+ runInternal(() => {
3815
+ try {
3816
+ history.pushState(brandMarker(surface), "", surface.url);
3817
+ entry.live = true;
3818
+ } catch {
3819
+ entry.live = false;
3820
+ }
3821
+ });
3822
+ if (entry.live) {
3823
+ entries.push(entry);
3824
+ ensureListener();
3825
+ }
3826
+ return entry;
3827
+ }
3828
+ function popBackMarker(handle) {
3829
+ if (!hasWindow() || !hasHistory()) return;
3830
+ const entry = handle;
3831
+ const idx = entries.indexOf(entry);
3832
+ if (idx === -1 || !entry.live) return;
3833
+ const last = entries.length - 1;
3834
+ entry.live = false;
3835
+ entries.splice(idx, 1);
3836
+ let rebrand = null;
3837
+ if (idx !== last) {
3838
+ const rebrandSurface = entries[entries.length - 1].surface;
3839
+ const rebrandUrl = entry.priorUrl;
3840
+ rebrand = () => {
3841
+ runInternal(() => {
3842
+ history.replaceState(
3843
+ brandMarker(rebrandSurface),
3844
+ "",
3845
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3846
+ );
3847
+ });
3848
+ };
3849
+ }
3850
+ pendingRebrands.push(rebrand);
3851
+ suppress += 1;
3852
+ runInternal(() => {
3853
+ try {
3854
+ history.back();
3855
+ } catch {
3856
+ suppress = Math.max(0, suppress - 1);
3857
+ pendingRebrands.pop();
3858
+ }
3859
+ });
3860
+ maybeTeardownListener();
3861
+ }
3862
+ function subscribeRouteChange(fn) {
3863
+ if (!hasWindow() || !hasHistory()) return () => {
3864
+ };
3865
+ subscribers.add(fn);
3866
+ if (subscribers.size === 1) installPatch();
3867
+ ensureListener();
3868
+ return () => {
3869
+ if (!subscribers.has(fn)) return;
3870
+ subscribers.delete(fn);
3871
+ if (subscribers.size === 0) removePatch();
3872
+ maybeTeardownListener();
3873
+ };
3874
+ }
3875
+
3876
+ // src/core/features/route.ts
3877
+ function installRoute(deps) {
3878
+ if (typeof window === "undefined") {
3879
+ return () => {
3880
+ };
3881
+ }
3882
+ if (!deps.routedTo && !deps.closeOnBack) {
3883
+ return () => {
3884
+ };
3885
+ }
3886
+ let handle = null;
3887
+ const onOpen = () => {
3888
+ if (handle) return;
3889
+ handle = pushBackMarker({
3890
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3891
+ isTop: deps.isTopSheet,
3892
+ close: deps.close,
3893
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3894
+ url: deps.routedTo
3895
+ });
3896
+ };
3897
+ const onClose = () => {
3898
+ if (!handle) return;
3899
+ popBackMarker(handle);
3900
+ handle = null;
3901
+ };
3902
+ const unsubscribeOpen = deps.on("open", onOpen);
3903
+ const unsubscribeClose = deps.on("close", onClose);
3904
+ if (deps.getSize() > 0) onOpen();
3905
+ return () => {
3906
+ unsubscribeOpen();
3907
+ unsubscribeClose();
3908
+ if (handle) {
3909
+ popBackMarker(handle);
3910
+ handle = null;
3911
+ }
3912
+ };
3913
+ }
3914
+ function installRouteChange(deps) {
3915
+ return subscribeRouteChange(() => {
3916
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3917
+ void deps.close();
3918
+ });
3919
+ }
3920
+
3921
+ // src/core/features/persist.ts
3922
+ function installPersist(engine, key) {
3923
+ const unsubscribe = engine.on("snap", ({ id }) => {
3924
+ if (typeof window === "undefined") return;
3925
+ if (typeof localStorage === "undefined") return;
3926
+ try {
3927
+ localStorage.setItem(key, id);
3928
+ } catch {
3607
3929
  }
3608
- const settleCap = this.snaps.getMaxAxisSize();
3609
- const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3610
- this.allowOvershoot = true;
3611
- void this.animation.animateTo(settleSize, velocity).then(() => {
3612
- if (signal.aborted) return;
3613
- this.allowOvershoot = false;
3614
- this.opening = false;
3615
- this.completeSnap(
3616
- target.id,
3617
- target.size,
3618
- previousSize,
3619
- previousSnapSize === 0,
3620
- true
3621
- );
3622
- });
3623
- }
3624
- clearWillChangeIfIdle() {
3625
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3626
- this.element.style.willChange = "";
3930
+ });
3931
+ return unsubscribe;
3932
+ }
3933
+
3934
+ // src/core/features/auto-collapse.ts
3935
+ function installAutoCollapse(deps) {
3936
+ const { ms } = deps;
3937
+ if (ms === void 0 || ms <= 0) {
3938
+ return () => {
3939
+ };
3627
3940
  }
3628
- haptic() {
3629
- if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3630
- try {
3631
- navigator.vibrate(HAPTIC_DURATION_MS);
3632
- } catch {
3633
- }
3941
+ let timer = null;
3942
+ const clearTimer = () => {
3943
+ if (timer !== null) {
3944
+ clearTimeout(timer);
3945
+ timer = null;
3946
+ }
3947
+ };
3948
+ const fire = () => {
3949
+ if (deps.isDestroyed()) return;
3950
+ if (deps.isDragging()) return;
3951
+ const active = deps.resolveSnap(deps.getActiveId());
3952
+ if (!active || active.size === 0) return;
3953
+ let target = null;
3954
+ for (const id of deps.getAllowedIds()) {
3955
+ const snap = deps.resolveSnap(id);
3956
+ if (!snap || snap.size <= 0) continue;
3957
+ if (!target || snap.size < target.size) target = snap;
3634
3958
  }
3959
+ if (!target) return;
3960
+ if (target.id === active.id || target.size >= active.size) return;
3961
+ deps.snapTo(target.id);
3962
+ };
3963
+ const reset = () => {
3964
+ clearTimer();
3965
+ if (deps.isDestroyed()) return;
3966
+ timer = setTimeout(() => {
3967
+ timer = null;
3968
+ fire();
3969
+ }, ms);
3970
+ };
3971
+ const unsubscribe = deps.on("snap", () => reset());
3972
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3973
+ return () => {
3974
+ clearTimer();
3975
+ unsubscribe();
3976
+ };
3977
+ }
3978
+
3979
+ // src/core/features/content-drag.ts
3980
+ function installContentDrag(deps) {
3981
+ const detach = deps.attachDragSurface(deps.container, "content");
3982
+ return () => detach?.();
3983
+ }
3984
+
3985
+ // src/core/features/visual-viewport.ts
3986
+ var SAFETY_PAD = 8;
3987
+ function installVisualViewport(deps) {
3988
+ if (typeof window === "undefined" || !window.visualViewport) {
3989
+ return () => {
3990
+ };
3635
3991
  }
3636
- applySize(size) {
3637
- const cap = this.snaps.getMaxAxisSize();
3638
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3639
- this.size = clamped;
3640
- const offset = cap - clamped;
3641
- const style = this.element.style;
3642
- style.transform = this.transformTemplate(offset);
3643
- if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3644
- style.setProperty("--bs-size", `${clamped}px`);
3645
- if (this.scrimParent) {
3646
- this.scrimParent.style.setProperty("--bs-size", `${clamped}px`);
3992
+ const vv = window.visualViewport;
3993
+ let rafId = null;
3994
+ const onResize = () => {
3995
+ if (rafId !== null) return;
3996
+ rafId = requestAnimationFrame(() => {
3997
+ rafId = null;
3998
+ if (deps.isDestroyed()) return;
3999
+ deps.recomputeSnaps();
4000
+ const isVertical = deps.isVerticalAxis();
4001
+ const visible = isVertical ? vv.height : vv.width;
4002
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
4003
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
4004
+ if (clamp < deps.getMaxAxisSize()) {
4005
+ deps.setMaxAxisSize(clamp);
3647
4006
  }
3648
- if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3649
- this.anchorHost.style.setProperty("--bs-size", `${clamped}px`);
4007
+ const wasAnimating = deps.isAnimating?.() ?? false;
4008
+ deps.cancelInFlight();
4009
+ deps.newCycle();
4010
+ const max = deps.getMaxAxisSize();
4011
+ deps.setSize(Math.min(deps.getSize(), max));
4012
+ const current = deps.resolveActiveSnap();
4013
+ if (current && !deps.isDragging()) {
4014
+ deps.applySize(Math.min(current.size, max));
4015
+ if (wasAnimating) deps.resyncAfterCancel?.();
4016
+ } else {
4017
+ deps.applySize(deps.getSize());
3650
4018
  }
3651
- if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3652
- this.rootEl.style.setProperty("--bs-size", `${clamped}px`);
4019
+ });
4020
+ };
4021
+ vv.addEventListener("resize", onResize);
4022
+ return () => {
4023
+ vv.removeEventListener("resize", onResize);
4024
+ if (rafId !== null) cancelAnimationFrame(rafId);
4025
+ };
4026
+ }
4027
+
4028
+ // src/core/features/engine-features.ts
4029
+ function routeFeature() {
4030
+ return {
4031
+ name: "route",
4032
+ install: (ctx) => {
4033
+ const teardowns = new TeardownStack();
4034
+ if (ctx.options.closeOnRouteChange) {
4035
+ teardowns.add(
4036
+ installRouteChange({
4037
+ isDestroyed: ctx.isDestroyed,
4038
+ getSize: ctx.getSize,
4039
+ close: () => ctx.close()
4040
+ })
4041
+ );
3653
4042
  }
4043
+ teardowns.add(
4044
+ installRoute({
4045
+ routedTo: ctx.options.routedTo,
4046
+ closeOnBack: ctx.options.closeOnBack,
4047
+ isTopSheet: ctx.isTopSheet,
4048
+ getSize: ctx.getSize,
4049
+ isDestroyed: ctx.isDestroyed,
4050
+ close: () => ctx.close("back"),
4051
+ on: ctx.on,
4052
+ sheetId: ctx.sheetId
4053
+ })
4054
+ );
4055
+ return () => teardowns.drain();
3654
4056
  }
3655
- const progress = this.computeProgress(clamped);
3656
- const progressChanged = this.progressWriteSentinel.shouldWrite(
3657
- progress,
3658
- OPACITY_WRITE_EPSILON
3659
- );
3660
- if (progressChanged) {
3661
- style.setProperty("--bs-progress", String(progress));
3662
- if (this.anchorHost) {
3663
- this.anchorHost.style.setProperty("--bs-progress", String(progress));
3664
- }
3665
- if (this.rootEl && this.rootEl !== this.anchorHost) {
3666
- this.rootEl.style.setProperty("--bs-progress", String(progress));
3667
- }
4057
+ };
4058
+ }
4059
+ function persistFeature() {
4060
+ return {
4061
+ name: "persist",
4062
+ install: (ctx) => {
4063
+ const key = ctx.options.persistKey;
4064
+ if (!key) return;
4065
+ return installPersist({ on: ctx.on }, key);
3668
4066
  }
3669
- this.scrim.applyOpacity(progress, progressChanged);
3670
- if (progressChanged && this.bus.listenerCount("progress") > 0) {
3671
- this.progressPayload.value = progress;
3672
- this.progressPayload.size = clamped;
3673
- this.emit("progress", this.progressPayload);
4067
+ };
4068
+ }
4069
+ function autoCollapseFeature() {
4070
+ return {
4071
+ name: "auto-collapse",
4072
+ install: (ctx) => installAutoCollapse({
4073
+ ms: ctx.options.autoCollapseAfter,
4074
+ isDestroyed: ctx.isDestroyed,
4075
+ isDragging: ctx.isDragging,
4076
+ getAllowedIds: ctx.getAllowedIds,
4077
+ getActiveId: ctx.getActiveId,
4078
+ resolveSnap: ctx.resolveSnap,
4079
+ snapTo: ctx.snapTo,
4080
+ on: ctx.on
4081
+ })
4082
+ };
4083
+ }
4084
+ function contentSwipeFeature() {
4085
+ return {
4086
+ name: "content-swipe",
4087
+ stage: "attach",
4088
+ install: (ctx) => {
4089
+ if (!ctx.scrollContainer) return;
4090
+ return installContentDrag({
4091
+ container: ctx.scrollContainer,
4092
+ attachDragSurface: ctx.attachDragSurface
4093
+ });
3674
4094
  }
3675
- }
3676
- computeProgress(size) {
3677
- const { min, max } = this.getAllowedRange();
3678
- const reachableMax = Math.min(max, this.snaps.getMaxAxisSize());
3679
- if (reachableMax <= min) return 0;
3680
- return Math.min(Math.max((size - min) / (reachableMax - min), 0), 1);
3681
- }
3682
- handleOpen() {
3683
- sheetStack.promote(this.id);
3684
- try {
3685
- this.lifecycle.install();
3686
- } catch (err) {
3687
- if (!this.destroyed) this.newCycle();
3688
- this.handleClose();
3689
- this.applySize(0);
3690
- this.activeId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.activeId;
3691
- this.size = 0;
3692
- queueMicrotask(() => {
3693
- throw err;
4095
+ };
4096
+ }
4097
+ function visualViewportFeature() {
4098
+ return {
4099
+ name: "visual-viewport",
4100
+ install: (ctx) => {
4101
+ if (typeof window === "undefined") return;
4102
+ return installVisualViewport({
4103
+ element: ctx.element,
4104
+ isVerticalAxis: ctx.isVerticalAxis,
4105
+ isDestroyed: ctx.isDestroyed,
4106
+ isDragging: ctx.isDragging,
4107
+ recomputeSnaps: ctx.recomputeSnaps,
4108
+ resolveActiveSnap: ctx.resolveActiveSnap,
4109
+ getMaxAxisSize: ctx.getMaxAxisSize,
4110
+ getSize: ctx.getSize,
4111
+ setMaxAxisSize: ctx.setMaxAxisSize,
4112
+ setSize: ctx.setSize,
4113
+ applySize: ctx.applySize,
4114
+ cancelInFlight: ctx.cancelInFlight,
4115
+ newCycle: ctx.newCycle,
4116
+ isAnimating: ctx.isAnimating,
4117
+ resyncAfterCancel: ctx.resyncAfterResize
3694
4118
  });
3695
4119
  }
3696
- }
3697
- resyncAfterResize() {
3698
- if (this.destroyed) return;
3699
- const target = this.snaps.findById(this.activeId);
3700
- if (!target) return;
3701
- this.updateAriaSlider();
3702
- this.emit("snap", {
3703
- id: target.id,
3704
- size: this.size,
3705
- progress: this.computeProgress(this.size)
4120
+ };
4121
+ }
4122
+
4123
+ // src/core/default-features.ts
4124
+ function defaultEngineFeatures() {
4125
+ return [
4126
+ contentSwipeFeature(),
4127
+ visualViewportFeature(),
4128
+ persistFeature(),
4129
+ autoCollapseFeature(),
4130
+ routeFeature()
4131
+ ];
4132
+ }
4133
+
4134
+ // src/core/BottomSheetEngine.ts
4135
+ var BottomSheetEngine = class extends BottomSheetCore {
4136
+ constructor(opts) {
4137
+ super({
4138
+ ...opts,
4139
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
3706
4140
  });
3707
- if (target.size > 0 && !this.lifecycle.isInstalled) {
3708
- this.emitOpenSequence(target.id);
3709
- } else if (target.size === 0 && this.lifecycle.isInstalled) {
3710
- this.emitCloseSequence();
3711
- }
3712
- }
3713
- handleClose() {
3714
- this.opening = false;
3715
- this.lifecycle.release();
3716
- sheetStack.update();
3717
4141
  }
3718
4142
  };
3719
4143
 
@@ -3912,6 +4336,14 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
3912
4336
  type: Number,
3913
4337
  required: false
3914
4338
  },
4339
+ dragFrom: {
4340
+ type: String,
4341
+ required: false
4342
+ },
4343
+ dragFromContent: {
4344
+ type: Boolean,
4345
+ required: false
4346
+ },
3915
4347
  rubberBand: {
3916
4348
  type: Boolean,
3917
4349
  required: false
@@ -3952,6 +4384,10 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
3952
4384
  type: String,
3953
4385
  required: false
3954
4386
  },
4387
+ settleAnimation: {
4388
+ type: String,
4389
+ required: false
4390
+ },
3955
4391
  spring: {
3956
4392
  type: Object,
3957
4393
  required: false
@@ -4034,6 +4470,10 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
4034
4470
  type: Boolean,
4035
4471
  required: false
4036
4472
  },
4473
+ features: {
4474
+ type: Array,
4475
+ required: false
4476
+ },
4037
4477
  snapPoints: {
4038
4478
  type: Array,
4039
4479
  required: true
@@ -4199,6 +4639,14 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
4199
4639
  if (next === void 0) return;
4200
4640
  getEngine()?.setDisableDrag(next);
4201
4641
  });
4642
+ vue.watch(() => props.dragFromContent, (next) => {
4643
+ if (next === void 0) return;
4644
+ getEngine()?.setDragFromContent(next);
4645
+ });
4646
+ vue.watch(() => props.dragFrom, (next) => {
4647
+ if (next === void 0) return;
4648
+ getEngine()?.setDragFrom(next);
4649
+ });
4202
4650
  const mounted = vue.ref(false);
4203
4651
  const anchorHosts = vue.shallowRef({});
4204
4652
  const anchorDetachers = vue.ref([]);