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