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