@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/index.js CHANGED
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
- // src/core/lifecycle/sheetStack.ts
5
+ // src/core/lifecycle/sheet-stack.ts
6
6
  var BASE_Z = 100;
7
7
  var STEP = 10;
8
8
  var SheetStack = class {
@@ -84,12 +84,6 @@ function buildTransformTemplate(axis) {
84
84
  return (offset) => `translate3d(${offset}px, 0, 0)`;
85
85
  }
86
86
  }
87
- var CLOSED_TRANSFORM = {
88
- bottom: "translate3d(0, 100%, 0)",
89
- top: "translate3d(0, -100%, 0)",
90
- left: "translate3d(-100%, 0, 0)",
91
- right: "translate3d(100%, 0, 0)"
92
- };
93
87
  var layoutAxis = (axis) => axis === "left" || axis === "right" ? "width" : "height";
94
88
 
95
89
  // src/core/primitives/instance-id.ts
@@ -133,7 +127,7 @@ function createEventBus() {
133
127
  };
134
128
  }
135
129
 
136
- // src/core/primitives/cssLength.ts
130
+ // src/core/primitives/css-length.ts
137
131
  var probe = null;
138
132
  var PROBE_ATTR = "data-bs-probe";
139
133
  var ensureProbe = (axis) => {
@@ -187,7 +181,7 @@ var resolveSnap = (point, mode, measureFit) => {
187
181
  return Math.max(0, el.getBoundingClientRect().width);
188
182
  };
189
183
 
190
- // src/core/primitives/devWarn.ts
184
+ // src/core/primitives/dev-warn.ts
191
185
  function devWarn(...args) {
192
186
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
193
187
  console.warn(...args);
@@ -352,74 +346,6 @@ var SnapResolver = class {
352
346
  }
353
347
  };
354
348
 
355
- // src/core/features/persist.ts
356
- function readPersistedId(key) {
357
- if (!key) return null;
358
- if (typeof window === "undefined") return null;
359
- if (typeof localStorage === "undefined") return null;
360
- try {
361
- return localStorage.getItem(key);
362
- } catch {
363
- return null;
364
- }
365
- }
366
- function installPersist(engine, key) {
367
- const unsubscribe = engine.on("snap", ({ id }) => {
368
- if (typeof window === "undefined") return;
369
- if (typeof localStorage === "undefined") return;
370
- try {
371
- localStorage.setItem(key, id);
372
- } catch {
373
- }
374
- });
375
- return unsubscribe;
376
- }
377
-
378
- // src/core/features/auto-collapse.ts
379
- function installAutoCollapse(deps) {
380
- const { ms } = deps;
381
- if (ms === void 0 || ms <= 0) {
382
- return () => {
383
- };
384
- }
385
- let timer = null;
386
- const clearTimer = () => {
387
- if (timer !== null) {
388
- clearTimeout(timer);
389
- timer = null;
390
- }
391
- };
392
- const fire = () => {
393
- if (deps.isDestroyed()) return;
394
- if (deps.isDragging()) return;
395
- const active = deps.resolveSnap(deps.getActiveId());
396
- if (!active || active.size === 0) return;
397
- let target = null;
398
- for (const id of deps.getAllowedIds()) {
399
- const snap = deps.resolveSnap(id);
400
- if (!snap || snap.size <= 0) continue;
401
- if (!target || snap.size < target.size) target = snap;
402
- }
403
- if (!target) return;
404
- if (target.id === active.id || target.size >= active.size) return;
405
- deps.snapTo(target.id);
406
- };
407
- const reset = () => {
408
- clearTimer();
409
- if (deps.isDestroyed()) return;
410
- timer = setTimeout(() => {
411
- timer = null;
412
- fire();
413
- }, ms);
414
- };
415
- const unsubscribe = deps.on("snap", () => reset());
416
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
417
- return () => {
418
- clearTimer();
419
- unsubscribe();
420
- };
421
- }
422
-
423
349
  // src/core/features/linked-sheets.ts
424
350
  function notifyLinkedSheets(sheets, self) {
425
351
  if (sheets.length === 0) return;
@@ -442,394 +368,65 @@ function notifyLinkedSheets(sheets, self) {
442
368
  }
443
369
  }
444
370
 
445
- // src/core/features/history-coordinator.ts
446
- var entries = [];
447
- var subscribers = /* @__PURE__ */ new Set();
448
- var suppress = 0;
449
- var internalDepth = 0;
450
- var pendingRebrand = null;
451
- var listenerInstalled = false;
452
- var patchInstalled = false;
453
- var origPushState = null;
454
- var origReplaceState = null;
455
- var patchedPushState = null;
456
- var patchedReplaceState = null;
457
- var hasWindow = () => typeof window !== "undefined";
458
- var hasHistory = () => typeof history !== "undefined";
459
- var brandMarker = (surface) => ({
460
- ...surface.markerState(),
461
- __bs: true
462
- });
463
- var runInternal = (fn) => {
464
- internalDepth += 1;
465
- try {
466
- fn();
467
- } finally {
468
- internalDepth -= 1;
469
- }
470
- };
471
- var notifyRouteChange = () => {
472
- const snapshot = Array.from(subscribers);
473
- for (const fn of snapshot) fn();
474
- };
475
- var ensureListener = () => {
476
- if (listenerInstalled || !hasWindow()) return;
477
- window.addEventListener("popstate", onPopState);
478
- listenerInstalled = true;
479
- };
480
- var maybeTeardownListener = () => {
481
- if (!listenerInstalled) return;
482
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
483
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
484
- listenerInstalled = false;
485
- };
486
- var installPatch = () => {
487
- if (patchInstalled || !hasHistory()) return;
488
- origPushState = history.pushState;
489
- origReplaceState = history.replaceState;
490
- patchedPushState = function(...args) {
491
- origPushState.apply(this, args);
492
- if (internalDepth === 0) notifyRouteChange();
493
- };
494
- patchedReplaceState = function(...args) {
495
- origReplaceState.apply(this, args);
496
- if (internalDepth === 0) notifyRouteChange();
497
- };
498
- history.pushState = patchedPushState;
499
- history.replaceState = patchedReplaceState;
500
- patchInstalled = true;
501
- };
502
- var removePatch = () => {
503
- if (!patchInstalled) return;
504
- if (history.pushState === patchedPushState && origPushState) {
505
- history.pushState = origPushState;
506
- }
507
- if (history.replaceState === patchedReplaceState && origReplaceState) {
508
- history.replaceState = origReplaceState;
509
- }
510
- origPushState = null;
511
- origReplaceState = null;
512
- patchedPushState = null;
513
- patchedReplaceState = null;
514
- patchInstalled = false;
515
- };
516
- function onPopState() {
517
- if (suppress > 0) {
518
- suppress -= 1;
519
- const rebrand = pendingRebrand;
520
- pendingRebrand = null;
521
- if (rebrand) rebrand();
522
- maybeTeardownListener();
523
- return;
524
- }
525
- if (entries.length === 0) {
526
- notifyRouteChange();
527
- maybeTeardownListener();
528
- return;
371
+ // src/core/features/resize-observer.ts
372
+ function installResizeObserver(deps) {
373
+ if (typeof window === "undefined") {
374
+ return () => {
375
+ };
529
376
  }
530
- let target = null;
531
- let targetIdx = -1;
532
- for (let i = entries.length - 1; i >= 0; i -= 1) {
533
- const entry = entries[i];
534
- if (!entry.surface.isOpen()) continue;
535
- if (!target) {
536
- target = entry;
537
- targetIdx = i;
377
+ const onResize = () => {
378
+ if (deps.isDestroyed()) return;
379
+ deps.recomputeSnaps();
380
+ const mode = deps.getMode();
381
+ const isVerticalAxis = mode === "bottom" || mode === "top";
382
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
383
+ const max = deps.getMaxAxisSize();
384
+ if (viewportSize2 > 0 && viewportSize2 < max) {
385
+ deps.setMaxAxisSize(viewportSize2);
538
386
  }
539
- if (entry.surface.isTop()) {
540
- target = entry;
541
- targetIdx = i;
542
- break;
387
+ const wasAnimating = deps.isAnimating?.() ?? false;
388
+ deps.cancelInFlight();
389
+ deps.newCycle();
390
+ const current = deps.resolveActiveSnap();
391
+ const newMax = deps.getMaxAxisSize();
392
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
393
+ deps.setSize(targetSize);
394
+ if (current && !deps.isDragging()) {
395
+ deps.applySize(targetSize);
396
+ if (wasAnimating) deps.resyncAfterCancel?.();
543
397
  }
398
+ };
399
+ let resizeObserver = null;
400
+ if (typeof ResizeObserver !== "undefined") {
401
+ resizeObserver = new ResizeObserver(onResize);
402
+ resizeObserver.observe(document.documentElement);
403
+ } else {
404
+ window.addEventListener("resize", onResize);
544
405
  }
545
- if (!target) {
546
- const removed = entries.pop();
547
- if (removed) removed.live = false;
548
- maybeTeardownListener();
549
- return;
550
- }
551
- target.live = false;
552
- entries.splice(targetIdx, 1);
553
- const settled = target;
554
- void Promise.resolve(settled.surface.close()).then(() => {
555
- if (settled.surface.isOpen()) restore(settled);
556
- });
557
- maybeTeardownListener();
406
+ window.addEventListener("orientationchange", onResize);
407
+ const onVisibility = () => {
408
+ if (deps.isDestroyed()) return;
409
+ if (document.hidden) deps.cancelInFlight();
410
+ };
411
+ document.addEventListener("visibilitychange", onVisibility);
412
+ return () => {
413
+ resizeObserver?.disconnect();
414
+ window.removeEventListener("resize", onResize);
415
+ window.removeEventListener("orientationchange", onResize);
416
+ document.removeEventListener("visibilitychange", onVisibility);
417
+ };
558
418
  }
559
- function restore(entry) {
560
- if (!hasWindow() || !hasHistory()) return;
561
- entry.live = true;
562
- runInternal(() => {
563
- try {
564
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
565
- } catch {
566
- entry.live = false;
567
- }
568
- });
569
- if (entry.live) {
570
- entries.push(entry);
571
- ensureListener();
572
- }
573
- }
574
- function pushBackMarker(surface) {
575
- const entry = { surface, priorUrl: void 0, live: false };
576
- if (!hasWindow() || !hasHistory()) {
577
- return entry;
578
- }
579
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
580
- runInternal(() => {
581
- try {
582
- history.pushState(brandMarker(surface), "", surface.url);
583
- entry.live = true;
584
- } catch {
585
- entry.live = false;
586
- }
587
- });
588
- if (entry.live) {
589
- entries.push(entry);
590
- ensureListener();
591
- }
592
- return entry;
593
- }
594
- function popBackMarker(handle) {
595
- if (!hasWindow() || !hasHistory()) return;
596
- const entry = handle;
597
- const idx = entries.indexOf(entry);
598
- if (idx === -1 || !entry.live) return;
599
- const last = entries.length - 1;
600
- entry.live = false;
601
- entries.splice(idx, 1);
602
- if (idx !== last) {
603
- const rebrandSurface = entries[entries.length - 1].surface;
604
- const rebrandUrl = entry.priorUrl;
605
- pendingRebrand = () => {
606
- runInternal(() => {
607
- history.replaceState(
608
- brandMarker(rebrandSurface),
609
- "",
610
- rebrandUrl !== void 0 ? rebrandUrl : location.href
611
- );
612
- });
613
- };
614
- }
615
- suppress += 1;
616
- runInternal(() => {
617
- try {
618
- history.back();
619
- } catch {
620
- suppress = Math.max(0, suppress - 1);
621
- pendingRebrand = null;
622
- }
623
- });
624
- maybeTeardownListener();
625
- }
626
- function subscribeRouteChange(fn) {
627
- if (!hasWindow() || !hasHistory()) return () => {
628
- };
629
- subscribers.add(fn);
630
- if (subscribers.size === 1) installPatch();
631
- ensureListener();
632
- return () => {
633
- if (!subscribers.has(fn)) return;
634
- subscribers.delete(fn);
635
- if (subscribers.size === 0) removePatch();
636
- maybeTeardownListener();
637
- };
638
- }
639
-
640
- // src/core/features/route.ts
641
- function installRoute(deps) {
642
- if (typeof window === "undefined") {
643
- return () => {
644
- };
645
- }
646
- if (!deps.routedTo && !deps.closeOnBack) {
647
- return () => {
648
- };
649
- }
650
- let handle = null;
651
- const onOpen = () => {
652
- if (handle) return;
653
- handle = pushBackMarker({
654
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
655
- isTop: deps.isTopSheet,
656
- close: deps.close,
657
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
658
- url: deps.routedTo
659
- });
660
- };
661
- const onClose = () => {
662
- if (!handle) return;
663
- popBackMarker(handle);
664
- handle = null;
665
- };
666
- const unsubscribeOpen = deps.on("open", onOpen);
667
- const unsubscribeClose = deps.on("close", onClose);
668
- if (deps.getSize() > 0) onOpen();
669
- return () => {
670
- unsubscribeOpen();
671
- unsubscribeClose();
672
- if (handle) {
673
- popBackMarker(handle);
674
- handle = null;
675
- }
676
- };
677
- }
678
- function installRouteChange(deps) {
679
- return subscribeRouteChange(() => {
680
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
681
- void deps.close();
682
- });
683
- }
684
-
685
- // src/core/features/content-swipe.ts
686
- var CONTENT_PULL_THRESHOLD = 60;
687
- function installContentSwipe(deps) {
688
- const { container } = deps;
689
- let startY = 0;
690
- let startScrollTop = 0;
691
- let moved = false;
692
- const onTouchStart = (e) => {
693
- const t = e.touches[0];
694
- if (!t) return;
695
- startY = t.clientY;
696
- startScrollTop = container.scrollTop;
697
- moved = false;
698
- };
699
- const onTouchMove = (e) => {
700
- const t = e.touches[0];
701
- if (!t) return;
702
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
703
- };
704
- const onTouchEnd = (e) => {
705
- if (!moved) return;
706
- if (deps.isDragging() || deps.isAnimating()) return;
707
- if (container.scrollTop !== startScrollTop) return;
708
- const t = e.changedTouches[0];
709
- if (!t) return;
710
- const deltaY = t.clientY - startY;
711
- const atTop = container.scrollTop <= 0;
712
- const allowed = deps.getAllowedIds();
713
- const idx = allowed.indexOf(deps.getActiveId());
714
- if (idx === -1) return;
715
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
716
- deps.snapTo(allowed[idx - 1]);
717
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
718
- deps.snapTo(allowed[idx + 1]);
719
- }
720
- };
721
- container.addEventListener("touchstart", onTouchStart, { passive: true });
722
- container.addEventListener("touchmove", onTouchMove, { passive: true });
723
- container.addEventListener("touchend", onTouchEnd, { passive: true });
724
- return () => {
725
- container.removeEventListener("touchstart", onTouchStart);
726
- container.removeEventListener("touchmove", onTouchMove);
727
- container.removeEventListener("touchend", onTouchEnd);
728
- };
729
- }
730
-
731
- // src/core/features/visual-viewport.ts
732
- var SAFETY_PAD = 8;
733
- function installVisualViewport(deps) {
734
- if (typeof window === "undefined" || !window.visualViewport) {
735
- return () => {
736
- };
737
- }
738
- const vv = window.visualViewport;
739
- let rafId = null;
740
- const onResize = () => {
741
- if (rafId !== null) return;
742
- rafId = requestAnimationFrame(() => {
743
- rafId = null;
744
- if (deps.isDestroyed()) return;
745
- deps.recomputeSnaps();
746
- const isVertical = deps.isVerticalAxis();
747
- const visible = isVertical ? vv.height : vv.width;
748
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
749
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
750
- if (clamp < deps.getMaxAxisSize()) {
751
- deps.setMaxAxisSize(clamp);
752
- }
753
- const wasAnimating = deps.isAnimating?.() ?? false;
754
- deps.cancelInFlight();
755
- deps.newCycle();
756
- const max = deps.getMaxAxisSize();
757
- deps.setSize(Math.min(deps.getSize(), max));
758
- const current = deps.resolveActiveSnap();
759
- if (current && !deps.isDragging()) {
760
- deps.applySize(Math.min(current.size, max));
761
- if (wasAnimating) deps.resyncAfterCancel?.();
762
- } else {
763
- deps.applySize(deps.getSize());
764
- }
765
- });
766
- };
767
- vv.addEventListener("resize", onResize);
768
- return () => {
769
- vv.removeEventListener("resize", onResize);
770
- if (rafId !== null) cancelAnimationFrame(rafId);
771
- };
772
- }
773
-
774
- // src/core/features/resize-observer.ts
775
- function installResizeObserver(deps) {
776
- if (typeof window === "undefined") {
777
- return () => {
778
- };
779
- }
780
- const onResize = () => {
781
- if (deps.isDestroyed()) return;
782
- deps.recomputeSnaps();
783
- const mode = deps.getMode();
784
- const isVerticalAxis = mode === "bottom" || mode === "top";
785
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
786
- const max = deps.getMaxAxisSize();
787
- if (viewportSize2 > 0 && viewportSize2 < max) {
788
- deps.setMaxAxisSize(viewportSize2);
789
- }
790
- const wasAnimating = deps.isAnimating?.() ?? false;
791
- deps.cancelInFlight();
792
- deps.newCycle();
793
- const current = deps.resolveActiveSnap();
794
- const newMax = deps.getMaxAxisSize();
795
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
796
- deps.setSize(targetSize);
797
- if (current && !deps.isDragging()) {
798
- deps.applySize(targetSize);
799
- if (wasAnimating) deps.resyncAfterCancel?.();
800
- }
801
- };
802
- let resizeObserver = null;
803
- if (typeof ResizeObserver !== "undefined") {
804
- resizeObserver = new ResizeObserver(onResize);
805
- resizeObserver.observe(document.documentElement);
806
- } else {
807
- window.addEventListener("resize", onResize);
808
- }
809
- window.addEventListener("orientationchange", onResize);
810
- const onVisibility = () => {
811
- if (deps.isDestroyed()) return;
812
- if (document.hidden) deps.cancelInFlight();
813
- };
814
- document.addEventListener("visibilitychange", onVisibility);
815
- return () => {
816
- resizeObserver?.disconnect();
817
- window.removeEventListener("resize", onResize);
818
- window.removeEventListener("orientationchange", onResize);
819
- document.removeEventListener("visibilitychange", onVisibility);
820
- };
821
- }
822
-
823
- // src/core/features/fit-observer.ts
824
- var contentTargets = (scroller) => {
825
- if (!scroller) return [];
826
- const hasSlot = typeof HTMLSlotElement !== "undefined";
827
- const out = [];
828
- for (const child of Array.from(scroller.children)) {
829
- if (hasSlot && child instanceof HTMLSlotElement) {
830
- for (const el of child.assignedElements()) out.push(el);
831
- } else {
832
- out.push(child);
419
+
420
+ // src/core/features/fit-observer.ts
421
+ var contentTargets = (scroller) => {
422
+ if (!scroller) return [];
423
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
424
+ const out = [];
425
+ for (const child of Array.from(scroller.children)) {
426
+ if (hasSlot && child instanceof HTMLSlotElement) {
427
+ for (const el of child.assignedElements()) out.push(el);
428
+ } else {
429
+ out.push(child);
833
430
  }
834
431
  }
835
432
  return out;
@@ -1671,6 +1268,52 @@ function resolveAnimationPreset(animation) {
1671
1268
  }
1672
1269
  }
1673
1270
 
1271
+ // src/core/animation/waapi-settle.ts
1272
+ var SIM_HZ = 240;
1273
+ var SAMPLE_STRIDE = 4;
1274
+ var MAX_SIM_MS = 3e3;
1275
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1276
+ const cfg = { ...DEFAULT_SPRING, ...config };
1277
+ const subDt = 1 / SIM_HZ;
1278
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1279
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1280
+ const values = [from];
1281
+ let x = from;
1282
+ let v = velocityPxPerS;
1283
+ for (let i = 1; i <= maxSteps; i += 1) {
1284
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1285
+ v += force / cfg.mass * subDt;
1286
+ x += v * subDt;
1287
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1288
+ values.push(to);
1289
+ break;
1290
+ }
1291
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1292
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1293
+ if (atRest) break;
1294
+ }
1295
+ if (values[values.length - 1] !== to) values.push(to);
1296
+ return {
1297
+ values,
1298
+ stepMs,
1299
+ durationMs: (values.length - 1) * stepMs
1300
+ };
1301
+ }
1302
+ function sampleTweenSettle(from, to, durationMs, easing) {
1303
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1304
+ const values = [];
1305
+ for (let i = 0; i <= count; i += 1) {
1306
+ const t = i / count;
1307
+ values.push(from + (to - from) * easing(t));
1308
+ }
1309
+ values[values.length - 1] = to;
1310
+ return {
1311
+ values,
1312
+ stepMs: durationMs / count,
1313
+ durationMs
1314
+ };
1315
+ }
1316
+
1674
1317
  // src/core/controllers/animation-runner.ts
1675
1318
  var DEFAULT_DURATION2 = 220;
1676
1319
  var VELOCITY_PX_PER_S = 1e3;
@@ -1681,6 +1324,10 @@ var AnimationRunner = class {
1681
1324
  __publicField(this, "applySizeFn");
1682
1325
  __publicField(this, "getSize");
1683
1326
  __publicField(this, "isDragging");
1327
+ __publicField(this, "applyAux");
1328
+ __publicField(this, "getTransformFor");
1329
+ __publicField(this, "settleWaapi");
1330
+ __publicField(this, "currentWaapi", null);
1684
1331
  __publicField(this, "animationKind");
1685
1332
  __publicField(this, "springConfig");
1686
1333
  __publicField(this, "duration");
@@ -1696,6 +1343,9 @@ var AnimationRunner = class {
1696
1343
  this.applySizeFn = deps.applySize;
1697
1344
  this.getSize = deps.getSize;
1698
1345
  this.isDragging = deps.isDragging;
1346
+ this.applyAux = deps.applyAux;
1347
+ this.getTransformFor = deps.getTransformFor;
1348
+ this.settleWaapi = opts.settleAnimation === "waapi";
1699
1349
  const preset = resolveAnimationPreset(opts.animation);
1700
1350
  this.animationKind = preset.kind;
1701
1351
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1720,11 +1370,20 @@ var AnimationRunner = class {
1720
1370
  }
1721
1371
  }
1722
1372
  get isAnimating() {
1723
- return this.currentTween !== null || this.currentSpring !== null;
1373
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1724
1374
  }
1725
1375
  cancel() {
1726
1376
  this.currentTween?.cancel();
1727
1377
  this.currentSpring?.cancel();
1378
+ if (this.currentWaapi) {
1379
+ const entry = this.currentWaapi;
1380
+ this.currentWaapi = null;
1381
+ entry.stop();
1382
+ try {
1383
+ entry.anim.cancel();
1384
+ } catch {
1385
+ }
1386
+ }
1728
1387
  }
1729
1388
  async animateTo(target, velocityPxPerMs) {
1730
1389
  if (target === this.getSize()) {
@@ -1743,6 +1402,27 @@ var AnimationRunner = class {
1743
1402
  if (!this.isDragging()) this.element.style.willChange = "";
1744
1403
  return;
1745
1404
  }
1405
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1406
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1407
+ this.getSize(),
1408
+ target,
1409
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1410
+ this.springConfig
1411
+ ) : sampleTweenSettle(
1412
+ this.getSize(),
1413
+ target,
1414
+ this.duration,
1415
+ this.easing
1416
+ );
1417
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1418
+ await this.runWaapiSettle(samples, target);
1419
+ if (!this.isAnimating) {
1420
+ this.getRootEl()?.removeAttribute("data-animating");
1421
+ if (!this.isDragging()) this.element.style.willChange = "";
1422
+ }
1423
+ return;
1424
+ }
1425
+ }
1746
1426
  if (this.animationKind === "spring") {
1747
1427
  const spring = runSpring({
1748
1428
  from: this.getSize(),
@@ -1771,15 +1451,70 @@ var AnimationRunner = class {
1771
1451
  if (!this.isDragging()) this.element.style.willChange = "";
1772
1452
  }
1773
1453
  }
1454
+ async runWaapiSettle(samples, target) {
1455
+ const applyAux = this.applyAux;
1456
+ const getTransformFor = this.getTransformFor;
1457
+ const frames = samples.values.map((v) => ({
1458
+ transform: getTransformFor(v)
1459
+ }));
1460
+ const anim = this.element.animate(frames, {
1461
+ duration: samples.durationMs,
1462
+ easing: "linear",
1463
+ fill: "forwards"
1464
+ });
1465
+ let stopped = false;
1466
+ let auxRaf = 0;
1467
+ const start = performance.now();
1468
+ const lastIdx = samples.values.length - 1;
1469
+ const auxTick = () => {
1470
+ if (stopped) return;
1471
+ const elapsed = performance.now() - start;
1472
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1473
+ applyAux(samples.values[idx]);
1474
+ auxRaf = requestAnimationFrame(auxTick);
1475
+ };
1476
+ auxRaf = requestAnimationFrame(auxTick);
1477
+ const entry = {
1478
+ anim,
1479
+ stop: () => {
1480
+ stopped = true;
1481
+ cancelAnimationFrame(auxRaf);
1482
+ }
1483
+ };
1484
+ this.currentWaapi = entry;
1485
+ try {
1486
+ await anim.finished;
1487
+ } catch {
1488
+ }
1489
+ entry.stop();
1490
+ const wasCurrent = this.currentWaapi === entry;
1491
+ if (wasCurrent) this.currentWaapi = null;
1492
+ if (wasCurrent && anim.playState === "finished") {
1493
+ this.applySizeFn(target);
1494
+ try {
1495
+ anim.cancel();
1496
+ } catch {
1497
+ }
1498
+ }
1499
+ }
1774
1500
  destroy() {
1775
1501
  this.detachReducedMotion?.();
1776
1502
  this.detachReducedMotion = null;
1777
1503
  this.currentTween = null;
1778
1504
  this.currentSpring = null;
1505
+ if (this.currentWaapi) {
1506
+ const entry = this.currentWaapi;
1507
+ this.currentWaapi = null;
1508
+ entry.stop();
1509
+ try {
1510
+ entry.anim.cancel();
1511
+ } catch {
1512
+ }
1513
+ }
1779
1514
  }
1780
1515
  };
1781
1516
 
1782
- // src/core/lifecycle/focusTrap.ts
1517
+ // src/core/lifecycle/focus-trap.ts
1783
1518
  var FOCUSABLE = [
1784
1519
  "a[href]",
1785
1520
  "area[href]",
@@ -1897,7 +1632,7 @@ var installFocusTrap = (container, options = {}) => {
1897
1632
  };
1898
1633
  };
1899
1634
 
1900
- // src/core/lifecycle/scrollLock.ts
1635
+ // src/core/lifecycle/scroll-lock.ts
1901
1636
  var lockCount = 0;
1902
1637
  var savedStyles = null;
1903
1638
  var lockBodyScroll = () => {
@@ -2079,30 +1814,44 @@ var SampleRing = class {
2079
1814
  this.size = 0;
2080
1815
  }
2081
1816
  };
2082
- var installGestures = (handle, mode, callbacks) => {
1817
+ var installGestures = (handle, mode, callbacks, options = {}) => {
2083
1818
  let activePointerId = null;
2084
1819
  let activePointerType = "touch";
2085
1820
  let startCoord = 0;
2086
1821
  let lastCoord = 0;
1822
+ let pendingStart = false;
2087
1823
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
2088
1824
  const MAX_SAMPLES = 32;
2089
1825
  const samples = new SampleRing(MAX_SAMPLES);
2090
1826
  const axis = isAxisVertical(mode) ? "Y" : "X";
2091
1827
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
2092
1828
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
2093
- const onPointerDown = (e) => {
2094
- if (activePointerId !== null) return;
2095
- if (e.button !== void 0 && e.button !== 0) return;
2096
- activePointerId = e.pointerId;
2097
- activePointerType = e.pointerType || "touch";
2098
- startCoord = coordOf(e);
1829
+ const capture = (pointerId) => {
1830
+ try {
1831
+ handle.setPointerCapture(pointerId);
1832
+ } catch {
1833
+ }
1834
+ };
1835
+ const releaseTracking = () => {
1836
+ activePointerId = null;
1837
+ pendingStart = false;
1838
+ samples.reset();
1839
+ };
1840
+ const onPointerDown = (e) => {
1841
+ if (activePointerId !== null) return;
1842
+ if (e.button !== void 0 && e.button !== 0) return;
1843
+ if (options.shouldStart && !options.shouldStart(e)) return;
1844
+ activePointerId = e.pointerId;
1845
+ activePointerType = e.pointerType || "touch";
1846
+ startCoord = coordOf(e);
2099
1847
  lastCoord = startCoord;
2100
1848
  samples.reset();
2101
1849
  samples.push(e.timeStamp, startCoord);
2102
- try {
2103
- handle.setPointerCapture(e.pointerId);
2104
- } catch {
1850
+ if (options.deferStart) {
1851
+ pendingStart = true;
1852
+ return;
2105
1853
  }
1854
+ capture(e.pointerId);
2106
1855
  callbacks.onStart(startCoord, activePointerType);
2107
1856
  };
2108
1857
  const onPointerMove = (e) => {
@@ -2110,6 +1859,22 @@ var installGestures = (handle, mode, callbacks) => {
2110
1859
  const coord = coordOf(e);
2111
1860
  const rawDelta = coord - startCoord;
2112
1861
  const delta = rawDelta * sign;
1862
+ if (pendingStart) {
1863
+ const verdict = options.deferStart(e, delta);
1864
+ if (verdict === false) {
1865
+ releaseTracking();
1866
+ return;
1867
+ }
1868
+ if (verdict !== true) return;
1869
+ pendingStart = false;
1870
+ startCoord = coord;
1871
+ lastCoord = coord;
1872
+ samples.reset();
1873
+ samples.push(e.timeStamp, coord);
1874
+ capture(e.pointerId);
1875
+ callbacks.onStart(startCoord, activePointerType);
1876
+ return;
1877
+ }
2113
1878
  lastCoord = coord;
2114
1879
  samples.push(e.timeStamp, coord);
2115
1880
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -2118,6 +1883,10 @@ var installGestures = (handle, mode, callbacks) => {
2118
1883
  };
2119
1884
  const finishGesture = (e) => {
2120
1885
  if (e.pointerId !== activePointerId) return;
1886
+ if (pendingStart) {
1887
+ releaseTracking();
1888
+ return;
1889
+ }
2121
1890
  const rawDelta = lastCoord - startCoord;
2122
1891
  const delta = rawDelta * sign;
2123
1892
  let velocity = 0;
@@ -2140,28 +1909,31 @@ var installGestures = (handle, mode, callbacks) => {
2140
1909
  };
2141
1910
  const onPointerCancel = (e) => {
2142
1911
  if (e.pointerId !== activePointerId) return;
2143
- activePointerId = null;
2144
- samples.reset();
1912
+ const wasPending = pendingStart;
1913
+ releaseTracking();
1914
+ if (wasPending) return;
2145
1915
  callbacks.onCancel?.();
2146
1916
  };
2147
1917
  handle.addEventListener("pointerdown", onPointerDown);
2148
1918
  handle.addEventListener("pointermove", onPointerMove);
2149
1919
  handle.addEventListener("pointerup", finishGesture);
2150
1920
  handle.addEventListener("pointercancel", onPointerCancel);
1921
+ const managesTouchAction = options.manageTouchAction !== false;
2151
1922
  const prevTouchAction = handle.style.touchAction;
2152
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1923
+ if (managesTouchAction) {
1924
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1925
+ }
2153
1926
  return () => {
2154
1927
  handle.removeEventListener("pointerdown", onPointerDown);
2155
1928
  handle.removeEventListener("pointermove", onPointerMove);
2156
1929
  handle.removeEventListener("pointerup", finishGesture);
2157
1930
  handle.removeEventListener("pointercancel", onPointerCancel);
2158
- handle.style.touchAction = prevTouchAction;
1931
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
2159
1932
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
2160
1933
  handle.releasePointerCapture(activePointerId);
2161
1934
  }
2162
1935
  };
2163
1936
  };
2164
- var attachGestures = installGestures;
2165
1937
 
2166
1938
  // src/core/primitives/rubber-band.ts
2167
1939
  function rubberBand(overshoot, maxAxisSize) {
@@ -2275,7 +2047,7 @@ var GestureController = class {
2275
2047
  const restoreTo = this.dragStartSize;
2276
2048
  void this.deps.animateTo(restoreTo, 0);
2277
2049
  }
2278
- });
2050
+ }, this.deps.gestureOptions);
2279
2051
  return this.detach;
2280
2052
  }
2281
2053
  forceClearDragState() {
@@ -2287,6 +2059,41 @@ var GestureController = class {
2287
2059
  }
2288
2060
  };
2289
2061
 
2062
+ // src/core/primitives/drag-zones.ts
2063
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2064
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2065
+ var asElement = (target) => {
2066
+ const candidate = target;
2067
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2068
+ };
2069
+ function isDragAllowedFrom(target, mode) {
2070
+ const el = asElement(target);
2071
+ if (!el) return mode !== "zones";
2072
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2073
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2074
+ return true;
2075
+ }
2076
+
2077
+ // src/core/primitives/content-gesture.ts
2078
+ var CONTENT_DRAG_SLOP = 6;
2079
+ function decideContentGesture(input) {
2080
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2081
+ if (Math.abs(input.delta) < slop) return "pending";
2082
+ if (input.scrollTop > 0) return "scroll";
2083
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2084
+ return "drag";
2085
+ }
2086
+
2087
+ // src/core/primitives/touch-scroll-guard.ts
2088
+ function installTouchScrollGuard(surface, isDragging) {
2089
+ const onTouchMove = (e) => {
2090
+ if (!isDragging()) return;
2091
+ if (e.cancelable) e.preventDefault();
2092
+ };
2093
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2094
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2095
+ }
2096
+
2290
2097
  // src/core/primitives/aria-slider-writer.ts
2291
2098
  var AriaSliderWriter = class {
2292
2099
  constructor(handle, mode) {
@@ -2633,7 +2440,7 @@ function resolveEngineOptions(opts, extras) {
2633
2440
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2634
2441
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2635
2442
  if (opts.persistKey) {
2636
- const restored = readPersistedId(opts.persistKey);
2443
+ const restored = readPersistedIdSafe(opts.persistKey);
2637
2444
  if (restored && initialAllowed.includes(restored)) {
2638
2445
  initialId = restored;
2639
2446
  }
@@ -2642,6 +2449,8 @@ function resolveEngineOptions(opts, extras) {
2642
2449
  mode: opts.mode ?? "bottom",
2643
2450
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2644
2451
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2452
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2453
+ dragFromContent: opts.dragFromContent ?? true,
2645
2454
  rubberBandEnabled: opts.rubberBand ?? true,
2646
2455
  closeOnBack: opts.closeOnBack ?? false,
2647
2456
  persistent: opts.persistent ?? false,
@@ -2664,6 +2473,7 @@ function resolveEngineOptions(opts, extras) {
2664
2473
  },
2665
2474
  animation: {
2666
2475
  animation: opts.animation,
2476
+ settleAnimation: opts.settleAnimation,
2667
2477
  duration: opts.duration,
2668
2478
  easing: opts.easing,
2669
2479
  spring: opts.spring,
@@ -2681,6 +2491,15 @@ function resolveEngineOptions(opts, extras) {
2681
2491
  }
2682
2492
  };
2683
2493
  }
2494
+ var readPersistedIdSafe = (key) => {
2495
+ if (typeof window === "undefined") return null;
2496
+ if (typeof localStorage === "undefined") return null;
2497
+ try {
2498
+ return localStorage.getItem(key);
2499
+ } catch {
2500
+ return null;
2501
+ }
2502
+ };
2684
2503
 
2685
2504
  // src/core/primitives/teardown-stack.ts
2686
2505
  var TeardownStack = class {
@@ -2703,9 +2522,9 @@ var TeardownStack = class {
2703
2522
  }
2704
2523
  };
2705
2524
 
2706
- // src/core/BottomSheetEngine.ts
2525
+ // src/core/BottomSheetCore.ts
2707
2526
  var HAPTIC_DURATION_MS = 8;
2708
- var BottomSheetEngine = class {
2527
+ var BottomSheetCore = class {
2709
2528
  constructor(opts) {
2710
2529
  __publicField(this, "id", nextInstanceId("bs"));
2711
2530
  __publicField(this, "element");
@@ -2717,6 +2536,8 @@ var BottomSheetEngine = class {
2717
2536
  __publicField(this, "mode");
2718
2537
  __publicField(this, "flickVelocity");
2719
2538
  __publicField(this, "dragThreshold");
2539
+ __publicField(this, "dragFromMode");
2540
+ __publicField(this, "dragFromContentDefault");
2720
2541
  __publicField(this, "rubberBandEnabled");
2721
2542
  __publicField(this, "scrim");
2722
2543
  __publicField(this, "aria");
@@ -2734,8 +2555,11 @@ var BottomSheetEngine = class {
2734
2555
  __publicField(this, "activeId");
2735
2556
  __publicField(this, "size", 0);
2736
2557
  __publicField(this, "gesture");
2558
+ __publicField(this, "contentGesture");
2559
+ __publicField(this, "detachSheetGesture", null);
2737
2560
  __publicField(this, "rootEl", null);
2738
2561
  __publicField(this, "destroyed", false);
2562
+ __publicField(this, "restClosed", false);
2739
2563
  __publicField(this, "currentViewTransition", null);
2740
2564
  __publicField(this, "currentAbort", new AbortController());
2741
2565
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2766,6 +2590,8 @@ var BottomSheetEngine = class {
2766
2590
  __publicField(this, "stackEffectPrimed", false);
2767
2591
  __publicField(this, "fitDeps");
2768
2592
  __publicField(this, "transformTemplate");
2593
+ __publicField(this, "featureList", []);
2594
+ __publicField(this, "featureCtx");
2769
2595
  const resolved = resolveEngineOptions(opts, {
2770
2596
  shouldApplyInertSiblings: () => {
2771
2597
  if (typeof document === "undefined") return false;
@@ -2786,6 +2612,8 @@ var BottomSheetEngine = class {
2786
2612
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2787
2613
  this.flickVelocity = resolved.flickVelocity;
2788
2614
  this.dragThreshold = resolved.dragThreshold;
2615
+ this.dragFromMode = resolved.dragFrom;
2616
+ this.dragFromContentDefault = resolved.dragFromContent;
2789
2617
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2790
2618
  this.scrim = new ScrimController(
2791
2619
  {
@@ -2812,7 +2640,9 @@ var BottomSheetEngine = class {
2812
2640
  getRootEl: () => this.rootEl,
2813
2641
  applySize: (size) => this.applySize(size),
2814
2642
  getSize: () => this.size,
2815
- isDragging: () => this.gesture?.isDragging ?? false
2643
+ isDragging: () => this.isDraggingAny(),
2644
+ applyAux: (size) => this.applySize(size, true),
2645
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2816
2646
  },
2817
2647
  resolved.animation
2818
2648
  );
@@ -2835,6 +2665,11 @@ var BottomSheetEngine = class {
2835
2665
  if (opts.linkedSheets) {
2836
2666
  this.linkedSheets = opts.linkedSheets;
2837
2667
  }
2668
+ const byName = /* @__PURE__ */ new Map();
2669
+ for (const feature of opts.features ?? []) {
2670
+ byName.set(feature.name, feature);
2671
+ }
2672
+ this.featureList = Array.from(byName.values());
2838
2673
  this.activeId = resolved.initialId;
2839
2674
  auditVhUsage(this.snapPointsRaw);
2840
2675
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2866,11 +2701,13 @@ var BottomSheetEngine = class {
2866
2701
  const initial = this.snaps.findById(this.activeId);
2867
2702
  if (initial) this.size = initial.size;
2868
2703
  this.applySize(this.size);
2704
+ this.setRestClosed(this.size === 0);
2869
2705
  this.updateAriaSlider();
2870
2706
  this.scrollCache = createScrollCache({
2871
2707
  scrollContainer: this.scrollContainer,
2872
2708
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2873
2709
  });
2710
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2874
2711
  this.attach();
2875
2712
  this.registerInStack();
2876
2713
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2895,60 +2732,92 @@ var BottomSheetEngine = class {
2895
2732
  (p) => p.size === "fit" || p.size === "content"
2896
2733
  ),
2897
2734
  isDestroyed: () => this.destroyed,
2898
- isDragging: () => this.gesture?.isDragging ?? false,
2735
+ isDragging: () => this.isDraggingAny(),
2899
2736
  recompute: () => this.recompute()
2900
2737
  })
2901
2738
  );
2902
2739
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2903
2740
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2904
2741
  this.applyAutoAriaLabelledBy();
2905
- if (this.closeOnRouteChange) {
2906
- this.teardowns.add(
2907
- installRouteChange({
2908
- isDestroyed: () => this.destroyed,
2909
- getSize: () => this.size,
2910
- close: () => this.close()
2911
- })
2912
- );
2913
- }
2914
- if (this.persistKey) {
2915
- this.teardowns.add(installPersist(this, this.persistKey));
2742
+ this.runFeatureStage("post");
2743
+ this.warnOrphanFeatureOptions();
2744
+ if (this.size > 0) {
2745
+ this.handleOpen();
2916
2746
  }
2917
- this.teardowns.add(
2918
- installAutoCollapse({
2919
- ms: opts.autoCollapseAfter,
2920
- isDestroyed: () => this.destroyed,
2921
- isDragging: () => this.gesture?.isDragging ?? false,
2922
- getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2923
- getActiveId: () => this.activeId,
2924
- resolveSnap: (id) => this.snaps.findById(id),
2925
- snapTo: (id) => {
2926
- void this.snapTo(id);
2927
- },
2928
- on: (event, fn) => this.on(event, fn)
2929
- })
2930
- );
2931
- this.teardowns.add(
2932
- installRoute({
2747
+ }
2748
+ buildFeatureContext(autoCollapseAfter) {
2749
+ return {
2750
+ element: this.element,
2751
+ scrollContainer: this.scrollContainer,
2752
+ sheetId: this.id,
2753
+ options: {
2933
2754
  routedTo: this.routedTo,
2934
2755
  closeOnBack: this.closeOnBack,
2935
- isTopSheet: () => this.isTopSheet,
2936
- getSize: () => this.size,
2937
- isDestroyed: () => this.destroyed,
2938
- close: () => this.close("back"),
2939
- on: (event, fn) => this.on(event, fn),
2940
- sheetId: this.id
2941
- })
2942
- );
2943
- if (this.size > 0) {
2944
- this.handleOpen();
2756
+ closeOnRouteChange: this.closeOnRouteChange,
2757
+ persistKey: this.persistKey,
2758
+ autoCollapseAfter
2759
+ },
2760
+ isDestroyed: () => this.destroyed,
2761
+ isDragging: () => this.isDraggingAny(),
2762
+ isAnimating: () => this.animation.isAnimating,
2763
+ isTopSheet: () => this.isTopSheet,
2764
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2765
+ getSize: () => this.size,
2766
+ getActiveId: () => this.activeId,
2767
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2768
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2769
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2770
+ resolveSnap: (id) => this.snaps.findById(id),
2771
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2772
+ setSize: (size) => {
2773
+ this.size = size;
2774
+ },
2775
+ setMaxAxisSize: (size) => {
2776
+ this.snaps.setMaxAxisSize(size);
2777
+ },
2778
+ applySize: (size) => this.applySize(size),
2779
+ recomputeSnaps: () => this.recomputeSnaps(),
2780
+ newCycle: () => {
2781
+ this.newCycle();
2782
+ },
2783
+ cancelInFlight: () => {
2784
+ this.animation.cancel();
2785
+ },
2786
+ resyncAfterResize: () => this.resyncAfterResize(),
2787
+ snapTo: (id) => {
2788
+ void this.snapTo(id);
2789
+ },
2790
+ close: (reason) => this.close(reason),
2791
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2792
+ on: (event, fn) => this.on(event, fn),
2793
+ addTeardown: (fn) => this.teardowns.add(fn)
2794
+ };
2795
+ }
2796
+ runFeatureStage(stage) {
2797
+ for (const feature of this.featureList) {
2798
+ if ((feature.stage ?? "post") !== stage) continue;
2799
+ const teardown = feature.install(this.featureCtx);
2800
+ if (teardown) this.teardowns.add(teardown);
2801
+ }
2802
+ }
2803
+ warnOrphanFeatureOptions() {
2804
+ const names = new Set(this.featureList.map((f) => f.name));
2805
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2806
+ devWarn(
2807
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2808
+ );
2809
+ }
2810
+ if (this.persistKey && !names.has("persist")) {
2811
+ devWarn(
2812
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2813
+ );
2945
2814
  }
2946
2815
  }
2947
2816
  get state() {
2948
2817
  return {
2949
2818
  size: this.size,
2950
2819
  activeId: this.activeId,
2951
- isDragging: this.gesture?.isDragging ?? false,
2820
+ isDragging: this.isDraggingAny(),
2952
2821
  isAnimating: this.animation.isAnimating,
2953
2822
  progress: this.computeProgress(this.size)
2954
2823
  };
@@ -3068,9 +2937,16 @@ var BottomSheetEngine = class {
3068
2937
  progress: this.computeProgress(this.size)
3069
2938
  });
3070
2939
  if (withHaptic) this.haptic();
2940
+ this.setRestClosed(targetSize === 0);
3071
2941
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
3072
2942
  if (targetSize === 0) this.emitCloseSequence();
3073
2943
  }
2944
+ setRestClosed(closed) {
2945
+ if (closed === this.restClosed) return;
2946
+ this.restClosed = closed;
2947
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2948
+ else this.element.removeAttribute("data-bs-rest");
2949
+ }
3074
2950
  emitOpenSequence(id) {
3075
2951
  this.emit("open", { id });
3076
2952
  this.handleOpen();
@@ -3126,6 +3002,20 @@ var BottomSheetEngine = class {
3126
3002
  if (this.destroyed) return;
3127
3003
  this.disableDragFlag = value;
3128
3004
  }
3005
+ setDragFromContent(value) {
3006
+ if (this.destroyed) return;
3007
+ this.dragFromContentDefault = value;
3008
+ }
3009
+ setDragFrom(mode) {
3010
+ if (this.destroyed || mode === this.dragFromMode) return;
3011
+ this.gesture?.forceClearDragState();
3012
+ this.detachSheetGesture?.();
3013
+ this.dragFromMode = mode;
3014
+ this.mountSheetGesture();
3015
+ }
3016
+ getDragFrom() {
3017
+ return this.dragFromMode;
3018
+ }
3129
3019
  isTop() {
3130
3020
  return this.isTopSheet;
3131
3021
  }
@@ -3280,12 +3170,24 @@ var BottomSheetEngine = class {
3280
3170
  auditVhUsage(points);
3281
3171
  this.auditDuplicateSnapIds(points);
3282
3172
  this.snaps.setRaw(points);
3283
- if (allowed) this.snaps.setAllowedIds(allowed);
3173
+ if (allowed) {
3174
+ this.snaps.setAllowedIds(allowed);
3175
+ } else {
3176
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3177
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3178
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3179
+ }
3284
3180
  this.scrollCache.clear();
3285
3181
  this.scrim.invalidateOpacityCache();
3286
3182
  const current = this.snaps.findById(this.activeId);
3287
3183
  if (current) {
3288
3184
  this.applySize(current.size);
3185
+ } else {
3186
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3187
+ if (fallback) {
3188
+ this.activeId = fallback.id;
3189
+ this.applySize(fallback.size);
3190
+ }
3289
3191
  }
3290
3192
  this.updateAriaSlider();
3291
3193
  }
@@ -3295,7 +3197,7 @@ var BottomSheetEngine = class {
3295
3197
  this.snaps.recompute();
3296
3198
  this.maxHeight.clampTo();
3297
3199
  this.scrim.invalidateOpacityCache();
3298
- if (this.gesture?.isDragging) return;
3200
+ if (this.isDraggingAny()) return;
3299
3201
  this.healActiveSnap();
3300
3202
  }
3301
3203
  healActiveSnap() {
@@ -3317,6 +3219,8 @@ var BottomSheetEngine = class {
3317
3219
  this.bus.clear();
3318
3220
  this.animation.cancel();
3319
3221
  this.gesture?.forceClearDragState();
3222
+ this.contentGesture?.forceClearDragState();
3223
+ this.setRestClosed(false);
3320
3224
  this.teardowns.drain();
3321
3225
  this.scrim.destroy();
3322
3226
  this.animation.destroy();
@@ -3429,11 +3333,12 @@ var BottomSheetEngine = class {
3429
3333
  const scale = Math.max(1 - depth * 0.04, 0.86);
3430
3334
  this.element.style.scale = scale === 1 ? "" : String(scale);
3431
3335
  }
3432
- attach() {
3433
- this.gesture = new GestureController({
3434
- handle: this.handle,
3336
+ buildGestureDeps(surface, gestureOptions) {
3337
+ return {
3338
+ handle: surface,
3435
3339
  element: this.element,
3436
3340
  mode: this.mode,
3341
+ gestureOptions,
3437
3342
  getRoot: () => this.rootEl,
3438
3343
  getDragContext: () => {
3439
3344
  const buf = this.dragContextBuf;
@@ -3452,8 +3357,88 @@ var BottomSheetEngine = class {
3452
3357
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3453
3358
  emit: (event, payload) => this.emit(event, payload),
3454
3359
  listenerCount: (event) => this.bus.listenerCount(event)
3360
+ };
3361
+ }
3362
+ isContentDragAllowed() {
3363
+ if (this.disableDragFlag) return false;
3364
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3365
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3366
+ }
3367
+ decideContentDrag(surface, e, delta) {
3368
+ if (this.destroyed) return false;
3369
+ if (e.pointerType === "mouse") return false;
3370
+ if (this.gesture?.isDragging) return false;
3371
+ if (!this.isContentDragAllowed()) return false;
3372
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3373
+ const decision = decideContentGesture({
3374
+ delta,
3375
+ scrollTop: surface.scrollTop,
3376
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3377
+ });
3378
+ if (decision === "pending") return null;
3379
+ return decision === "drag";
3380
+ }
3381
+ attachDragSurface(surface, kind) {
3382
+ if (this.destroyed || kind !== "content") return;
3383
+ if (this.contentGesture) return;
3384
+ const controller = new GestureController(
3385
+ this.buildGestureDeps(surface, {
3386
+ manageTouchAction: false,
3387
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3388
+ })
3389
+ );
3390
+ this.contentGesture = controller;
3391
+ const detachGestures = controller.install();
3392
+ const detachGuard = installTouchScrollGuard(
3393
+ surface,
3394
+ () => controller.isDragging
3395
+ );
3396
+ return () => {
3397
+ detachGuard();
3398
+ detachGestures();
3399
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3400
+ };
3401
+ }
3402
+ sheetDragSurface() {
3403
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3404
+ }
3405
+ canStartSheetDrag(e) {
3406
+ if (this.contentGesture?.isDragging) return false;
3407
+ const target = e.target;
3408
+ const scoped = typeof target?.closest === "function";
3409
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3410
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3411
+ return true;
3412
+ }
3413
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3414
+ if (this.dragFromMode === "handle") return true;
3415
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3416
+ return false;
3417
+ }
3418
+ return true;
3419
+ }
3420
+ mountSheetGesture() {
3421
+ const surface = this.sheetDragSurface();
3422
+ const controller = new GestureController(
3423
+ this.buildGestureDeps(surface, {
3424
+ shouldStart: (e) => this.canStartSheetDrag(e),
3425
+ manageTouchAction: this.dragFromMode === "handle"
3426
+ })
3427
+ );
3428
+ this.gesture = controller;
3429
+ const detachGestures = controller.install();
3430
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3431
+ this.detachSheetGesture = () => {
3432
+ detachGuard?.();
3433
+ detachGestures();
3434
+ };
3435
+ }
3436
+ attach() {
3437
+ this.mountSheetGesture();
3438
+ this.teardowns.add(() => {
3439
+ this.detachSheetGesture?.();
3440
+ this.detachSheetGesture = null;
3455
3441
  });
3456
- this.teardowns.add(this.gesture.install());
3457
3442
  try {
3458
3443
  this.attachFeatures();
3459
3444
  } catch (err) {
@@ -3484,24 +3469,13 @@ var BottomSheetEngine = class {
3484
3469
  }
3485
3470
  }
3486
3471
  attachFeatures() {
3487
- if (this.scrollContainer) {
3488
- this.teardowns.add(installContentSwipe({
3489
- container: this.scrollContainer,
3490
- isDragging: () => this.gesture?.isDragging ?? false,
3491
- isAnimating: () => this.animation.isAnimating,
3492
- getAllowedIds: () => this.allowedIdsBySize(),
3493
- getActiveId: () => this.activeId,
3494
- snapTo: (id) => {
3495
- void this.snapTo(id);
3496
- }
3497
- }));
3498
- }
3472
+ this.runFeatureStage("attach");
3499
3473
  if (typeof window !== "undefined") {
3500
3474
  this.teardowns.add(installResizeObserver({
3501
3475
  element: this.element,
3502
3476
  getMode: () => this.mode,
3503
3477
  isDestroyed: () => this.destroyed,
3504
- isDragging: () => this.gesture?.isDragging ?? false,
3478
+ isDragging: () => this.isDraggingAny(),
3505
3479
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3506
3480
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3507
3481
  getSize: () => this.size,
@@ -3535,31 +3509,6 @@ var BottomSheetEngine = class {
3535
3509
  () => document.removeEventListener("keydown", onKey)
3536
3510
  );
3537
3511
  }
3538
- this.teardowns.add(installVisualViewport({
3539
- element: this.element,
3540
- isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
3541
- isDestroyed: () => this.destroyed,
3542
- isDragging: () => this.gesture?.isDragging ?? false,
3543
- recomputeSnaps: () => this.recomputeSnaps(),
3544
- resolveActiveSnap: () => this.snaps.findById(this.activeId),
3545
- getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3546
- getSize: () => this.size,
3547
- setMaxAxisSize: (size) => {
3548
- this.snaps.setMaxAxisSize(size);
3549
- },
3550
- setSize: (size) => {
3551
- this.size = size;
3552
- },
3553
- applySize: (size) => this.applySize(size),
3554
- cancelInFlight: () => {
3555
- this.animation.cancel();
3556
- },
3557
- newCycle: () => {
3558
- this.newCycle();
3559
- },
3560
- isAnimating: () => this.animation.isAnimating,
3561
- resyncAfterCancel: () => this.resyncAfterResize()
3562
- }));
3563
3512
  }
3564
3513
  this.teardowns.add(installSliderKeyboard({
3565
3514
  handle: this.handle,
@@ -3624,8 +3573,11 @@ var BottomSheetEngine = class {
3624
3573
  );
3625
3574
  });
3626
3575
  }
3576
+ isDraggingAny() {
3577
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3578
+ }
3627
3579
  clearWillChangeIfIdle() {
3628
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3580
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3629
3581
  this.element.style.willChange = "";
3630
3582
  }
3631
3583
  haptic() {
@@ -3636,13 +3588,14 @@ var BottomSheetEngine = class {
3636
3588
  }
3637
3589
  }
3638
3590
  }
3639
- applySize(size) {
3591
+ applySize(size, skipTransform = false) {
3640
3592
  const cap = this.snaps.getMaxAxisSize();
3641
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3593
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3642
3594
  this.size = clamped;
3595
+ if (clamped > 0) this.setRestClosed(false);
3643
3596
  const offset = cap - clamped;
3644
3597
  const style = this.element.style;
3645
- style.transform = this.transformTemplate(offset);
3598
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3646
3599
  if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3647
3600
  style.setProperty("--bs-size", `${clamped}px`);
3648
3601
  if (this.scrimParent) {
@@ -3720,756 +3673,496 @@ var BottomSheetEngine = class {
3720
3673
  }
3721
3674
  };
3722
3675
 
3723
- // src/core/lifecycle/sheetManager.ts
3724
- var createSheetManager = (initial = {}) => {
3725
- const registry = /* @__PURE__ */ new Map();
3726
- for (const [key, config] of Object.entries(initial)) {
3727
- registry.set(key, { ...config, key });
3676
+ // src/core/features/history-coordinator.ts
3677
+ var entries = [];
3678
+ var subscribers = /* @__PURE__ */ new Set();
3679
+ var suppress = 0;
3680
+ var internalDepth = 0;
3681
+ var pendingRebrands = [];
3682
+ var listenerInstalled = false;
3683
+ var patchInstalled = false;
3684
+ var origPushState = null;
3685
+ var origReplaceState = null;
3686
+ var patchedPushState = null;
3687
+ var patchedReplaceState = null;
3688
+ var hasWindow = () => typeof window !== "undefined";
3689
+ var hasHistory = () => typeof history !== "undefined";
3690
+ var brandMarker = (surface) => ({
3691
+ ...surface.markerState(),
3692
+ __bs: true
3693
+ });
3694
+ var runInternal = (fn) => {
3695
+ internalDepth += 1;
3696
+ try {
3697
+ fn();
3698
+ } finally {
3699
+ internalDepth -= 1;
3728
3700
  }
3729
- return {
3730
- register(key, config) {
3731
- registry.set(key, { ...config, key });
3732
- },
3733
- unregister(key) {
3734
- registry.delete(key);
3735
- },
3736
- resolve(key) {
3737
- return registry.get(key) ?? null;
3738
- },
3739
- keys() {
3740
- return Array.from(registry.keys());
3741
- },
3742
- transition(prev, next) {
3743
- if (prev) registry.get(prev)?.onClose?.(prev);
3744
- if (next) registry.get(next)?.onOpen?.(next);
3745
- }
3746
- };
3747
3701
  };
3748
-
3749
- // src/core/primitives/overlay-transforms.ts
3750
- var closedTransform = (edge) => CLOSED_TRANSFORM[edge];
3751
- var closedTransformFor = (edge, animation, peekPx) => {
3752
- switch (animation) {
3753
- case "fade":
3754
- return "translate3d(0, 0, 0)";
3755
- case "scale":
3756
- return "translate3d(0, 0, 0) scale(0.94)";
3757
- case "slide":
3758
- default:
3759
- if (peekPx !== void 0 && edge === "bottom") {
3760
- return `translate3d(0, calc(100% - ${peekPx}px), 0)`;
3761
- }
3762
- return closedTransform(edge);
3763
- }
3702
+ var notifyRouteChange = () => {
3703
+ const snapshot = Array.from(subscribers);
3704
+ for (const fn of snapshot) fn();
3764
3705
  };
3765
- var openTransformFor = (animation) => animation === "scale" ? "translate3d(0, 0, 0) scale(1)" : "translate3d(0, 0, 0)";
3766
-
3767
- // src/core/features/overlay-swipe.ts
3768
- function installOverlaySwipe(deps) {
3769
- const el = deps.element;
3770
- const edge = deps.edge;
3771
- const isVertical = edge === "bottom" || edge === "top";
3772
- const sign = edge === "bottom" || edge === "right" ? 1 : -1;
3773
- const SLOP = 8;
3774
- let startX = 0;
3775
- let startY = 0;
3776
- let startT = 0;
3777
- let tracking = false;
3778
- let dragging = false;
3779
- let pointerId = null;
3780
- const previousTouchAction = el.style.touchAction;
3781
- el.style.touchAction = isVertical ? "pan-x" : "pan-y";
3782
- const onDown = (e) => {
3783
- if (e.button !== void 0 && e.button !== 0) return;
3784
- pointerId = e.pointerId;
3785
- startX = e.clientX;
3786
- startY = e.clientY;
3787
- startT = performance.now();
3788
- tracking = true;
3789
- dragging = false;
3790
- };
3791
- const onMove = (e) => {
3792
- if (!tracking || e.pointerId !== pointerId) return;
3793
- const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3794
- if (!dragging) {
3795
- if (Math.abs(delta) < SLOP) return;
3796
- dragging = true;
3797
- el.style.transition = "none";
3798
- try {
3799
- el.setPointerCapture(e.pointerId);
3800
- } catch {
3801
- }
3802
- }
3803
- const offset = Math.max(0, sign * delta);
3804
- el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
3805
- };
3806
- const finish = (e) => {
3807
- if (!tracking || e.pointerId !== pointerId) return;
3808
- tracking = false;
3809
- pointerId = null;
3810
- if (!dragging) return;
3811
- dragging = false;
3812
- const dt = performance.now() - startT || 1;
3813
- const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3814
- const offset = Math.max(0, sign * delta);
3815
- const velocity = sign * delta / dt;
3816
- const size = isVertical ? el.offsetHeight : el.offsetWidth;
3817
- const past = offset > size * deps.getThreshold() || velocity > deps.getVelocityThreshold();
3818
- el.style.transition = deps.getExitTransition();
3819
- if (past) {
3820
- deps.close();
3821
- } else {
3822
- el.style.transform = deps.getOpenTransform();
3823
- }
3706
+ var ensureListener = () => {
3707
+ if (listenerInstalled || !hasWindow()) return;
3708
+ window.addEventListener("popstate", onPopState);
3709
+ listenerInstalled = true;
3710
+ };
3711
+ var maybeTeardownListener = () => {
3712
+ if (!listenerInstalled) return;
3713
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3714
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3715
+ listenerInstalled = false;
3716
+ };
3717
+ var installPatch = () => {
3718
+ if (patchInstalled || !hasHistory()) return;
3719
+ origPushState = history.pushState;
3720
+ origReplaceState = history.replaceState;
3721
+ patchedPushState = function(...args) {
3722
+ origPushState.apply(this, args);
3723
+ if (internalDepth === 0) notifyRouteChange();
3824
3724
  };
3825
- el.addEventListener("pointerdown", onDown);
3826
- el.addEventListener("pointermove", onMove);
3827
- el.addEventListener("pointerup", finish);
3828
- el.addEventListener("pointercancel", finish);
3829
- return () => {
3830
- el.style.touchAction = previousTouchAction;
3831
- el.removeEventListener("pointerdown", onDown);
3832
- el.removeEventListener("pointermove", onMove);
3833
- el.removeEventListener("pointerup", finish);
3834
- el.removeEventListener("pointercancel", finish);
3725
+ patchedReplaceState = function(...args) {
3726
+ origReplaceState.apply(this, args);
3727
+ if (internalDepth === 0) notifyRouteChange();
3835
3728
  };
3836
- }
3837
-
3838
- // src/core/overlay-options.ts
3839
- var OVERLAY_PRESETS = Object.freeze({
3840
- sheet: Object.freeze({
3841
- enterAnimation: "slide",
3842
- exitAnimation: "slide",
3843
- backdropOpacity: 0.5,
3844
- backdropFilter: void 0,
3845
- swipeToClose: true
3846
- }),
3847
- dialog: Object.freeze({
3848
- enterAnimation: "scale",
3849
- exitAnimation: "scale",
3850
- backdropOpacity: 0.6,
3851
- backdropFilter: "blur(8px)",
3852
- swipeToClose: false
3853
- }),
3854
- sidebar: Object.freeze({
3855
- enterAnimation: "slide",
3856
- exitAnimation: "slide",
3857
- backdropOpacity: 0.4,
3858
- backdropFilter: void 0,
3859
- swipeToClose: true
3860
- }),
3861
- toast: Object.freeze({
3862
- enterAnimation: "fade",
3863
- exitAnimation: "fade",
3864
- backdropOpacity: 0,
3865
- backdropFilter: void 0,
3866
- swipeToClose: false
3867
- })
3868
- });
3869
-
3870
- // src/core/overlay.ts
3871
- var OverlayEngine = class {
3872
- constructor(opts) {
3873
- __publicField(this, "id", nextInstanceId("ovl"));
3874
- __publicField(this, "element");
3875
- __publicField(this, "backdrop");
3876
- __publicField(this, "edge");
3877
- __publicField(this, "closeOnEscape");
3878
- __publicField(this, "closeOnBackdropEnabled");
3879
- __publicField(this, "focusTrapEnabled");
3880
- __publicField(this, "initialFocus");
3881
- __publicField(this, "bodyScrollLockEnabled");
3882
- __publicField(this, "inertSiblingsEnabled");
3883
- __publicField(this, "closeOnBackEnabled");
3884
- __publicField(this, "duration");
3885
- __publicField(this, "enterEasing");
3886
- __publicField(this, "exitEasing");
3887
- __publicField(this, "backdropOpacity");
3888
- __publicField(this, "backdropFilter");
3889
- __publicField(this, "swipeToCloseEnabled");
3890
- __publicField(this, "swipeThreshold");
3891
- __publicField(this, "swipeVelocityThreshold");
3892
- __publicField(this, "enterAnimation");
3893
- __publicField(this, "exitAnimation");
3894
- __publicField(this, "mountTarget");
3895
- __publicField(this, "originalParent", null);
3896
- __publicField(this, "originalNextSibling", null);
3897
- __publicField(this, "mounted", false);
3898
- __publicField(this, "peekRaw");
3899
- __publicField(this, "peekPx");
3900
- __publicField(this, "closeOnOutsidePointerEnabled");
3901
- __publicField(this, "detachOutsidePointer", null);
3902
- __publicField(this, "detachSwipe", null);
3903
- __publicField(this, "respectReducedMotionEnabled");
3904
- __publicField(this, "isTop", true);
3905
- __publicField(this, "backMarker", null);
3906
- __publicField(this, "movedToTarget", false);
3907
- __publicField(this, "onOpenCb");
3908
- __publicField(this, "onCloseCb");
3909
- __publicField(this, "isOpen_", false);
3910
- __publicField(this, "isAnimating_", false);
3911
- __publicField(this, "destroyed", false);
3912
- __publicField(this, "hasInjectedChildren", false);
3913
- __publicField(this, "cycleNonce", 0);
3914
- __publicField(this, "bus", createEventBus());
3915
- __publicField(this, "detachEscape", null);
3916
- __publicField(this, "detachBackdrop", null);
3917
- __publicField(this, "releaseStackEntry", null);
3918
- __publicField(this, "lifecycle");
3919
- this.element = opts.element;
3920
- this.backdrop = opts.backdrop;
3921
- this.edge = opts.edge ?? "bottom";
3922
- this.closeOnEscape = opts.closeOnEscape ?? true;
3923
- this.closeOnBackdropEnabled = opts.closeOnBackdrop ?? true;
3924
- this.focusTrapEnabled = opts.focusTrap ?? true;
3925
- this.initialFocus = opts.initialFocus;
3926
- this.bodyScrollLockEnabled = opts.lockBodyScroll ?? true;
3927
- this.inertSiblingsEnabled = opts.inertSiblings ?? false;
3928
- this.closeOnBackEnabled = opts.closeOnBack ?? false;
3929
- this.duration = opts.duration ?? 320;
3930
- this.enterEasing = opts.enterEasing ?? "cubic-bezier(0.29, 1.24, 0.84, 0.99)";
3931
- this.exitEasing = opts.exitEasing ?? "cubic-bezier(0.4, 0, 1, 1)";
3932
- const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
3933
- this.backdropOpacity = opts.backdropOpacity ?? preset?.backdropOpacity ?? 1;
3934
- this.backdropFilter = opts.backdropFilter ?? preset?.backdropFilter ?? void 0;
3935
- const swipeRaw = opts.swipeToClose !== void 0 ? opts.swipeToClose : preset?.swipeToClose;
3936
- this.assignSwipeFromConfig(swipeRaw);
3937
- this.enterAnimation = opts.enterAnimation ?? preset?.enterAnimation ?? "slide";
3938
- this.exitAnimation = opts.exitAnimation ?? preset?.exitAnimation ?? this.enterAnimation;
3939
- this.mountTarget = opts.mountTo;
3940
- this.peekRaw = opts.peek;
3941
- this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
3942
- this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
3943
- this.onOpenCb = opts.onOpen;
3944
- this.onCloseCb = opts.onClose;
3945
- if (this.peekRaw !== void 0 && this.edge !== "bottom") {
3946
- devWarn(
3947
- "[OverlayEngine] `peek` is only supported on edge: 'bottom' \u2014 ignoring."
3948
- );
3949
- this.peekRaw = void 0;
3950
- }
3951
- if (this.peekRaw !== void 0) {
3952
- this.peekPx = this.resolvePeekPx(this.peekRaw);
3953
- }
3954
- this.lifecycle = new LifecycleController(
3955
- {
3956
- element: this.element,
3957
- close: () => this.close("escape")
3958
- },
3959
- {
3960
- focusTrap: this.focusTrapEnabled,
3961
- initialFocus: this.initialFocus,
3962
- closeOnEscape: this.closeOnEscape,
3963
- lockBodyScroll: this.bodyScrollLockEnabled,
3964
- inertSiblings: this.inertSiblingsEnabled,
3965
- shouldApplyInertSiblings: () => {
3966
- const bodyChildren = Array.from(document.body.children);
3967
- return bodyChildren.some(
3968
- (c) => c === this.element || c.contains(this.element)
3969
- );
3970
- },
3971
- returnFocus: opts.returnFocus
3972
- }
3973
- );
3974
- this.applyClosedStyles();
3975
- this.registerInStack();
3976
- if (opts.initialOpen) void this.open();
3977
- }
3978
- get state() {
3979
- return { isOpen: this.isOpen_, isAnimating: this.isAnimating_ };
3980
- }
3981
- on(event, fn) {
3982
- return this.bus.on(event, fn);
3729
+ history.pushState = patchedPushState;
3730
+ history.replaceState = patchedReplaceState;
3731
+ patchInstalled = true;
3732
+ };
3733
+ var removePatch = () => {
3734
+ if (!patchInstalled) return;
3735
+ if (history.pushState === patchedPushState && origPushState) {
3736
+ history.pushState = origPushState;
3983
3737
  }
3984
- open() {
3985
- if (this.destroyed || this.isOpen_) return Promise.resolve();
3986
- this.maybeMount();
3987
- this.emit("before-open", void 0);
3988
- this.isOpen_ = true;
3989
- this.isAnimating_ = true;
3990
- sheetStack.promote(this.id);
3991
- this.element.removeAttribute("hidden");
3992
- this.element.setAttribute("data-state", "entering");
3993
- if (this.focusTrapEnabled) {
3994
- this.element.setAttribute("aria-modal", "true");
3995
- }
3996
- const dur = this.effectiveDuration();
3997
- const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
3998
- this.element.style.transition = transitionStr;
3999
- if (this.backdrop) {
4000
- this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
4001
- }
4002
- this.setWillChange(true);
4003
- const cycle = ++this.cycleNonce;
4004
- return new Promise((resolve) => {
4005
- requestAnimationFrame(() => {
4006
- if (this.destroyed || cycle !== this.cycleNonce) return resolve();
4007
- requestAnimationFrame(() => {
4008
- if (this.destroyed || cycle !== this.cycleNonce) return resolve();
4009
- this.element.style.transform = openTransformFor(this.enterAnimation);
4010
- this.element.style.opacity = "1";
4011
- if (this.backdrop) {
4012
- this.backdrop.style.opacity = String(this.backdropOpacity);
4013
- this.syncBackdropPointerEvents();
4014
- if (this.backdropFilter) {
4015
- this.applyBackdropFilter(this.backdropFilter);
4016
- }
4017
- }
4018
- try {
4019
- this.installInteractiveListeners();
4020
- } catch (err) {
4021
- this.releaseInteractiveListeners();
4022
- this.isOpen_ = false;
4023
- this.isAnimating_ = false;
4024
- this.setWillChange(false);
4025
- this.element.style.transform = closedTransformFor(
4026
- this.edge,
4027
- this.enterAnimation,
4028
- this.peekPx
4029
- );
4030
- this.element.style.opacity = "0";
4031
- if (this.backdrop) {
4032
- this.backdrop.style.opacity = "0";
4033
- this.backdrop.style.pointerEvents = "none";
4034
- if (this.backdropFilter) {
4035
- this.applyBackdropFilter(void 0);
4036
- }
4037
- }
4038
- this.element.setAttribute("data-state", "closed");
4039
- this.element.setAttribute("hidden", "");
4040
- if (this.focusTrapEnabled) {
4041
- this.element.removeAttribute("aria-modal");
4042
- }
4043
- queueMicrotask(() => {
4044
- throw err;
4045
- });
4046
- return resolve();
4047
- }
4048
- this.awaitTransition(
4049
- cycle,
4050
- this.enterAnimation === "fade" ? "opacity" : "transform"
4051
- ).then(() => {
4052
- if (this.destroyed || cycle !== this.cycleNonce) return resolve();
4053
- this.isAnimating_ = false;
4054
- this.setWillChange(false);
4055
- this.element.setAttribute("data-state", "open");
4056
- this.emit("open", void 0);
4057
- this.onOpenCb?.();
4058
- resolve();
4059
- });
4060
- });
4061
- });
4062
- });
3738
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3739
+ history.replaceState = origReplaceState;
4063
3740
  }
4064
- close(reason = "programmatic") {
4065
- if (this.destroyed || !this.isOpen_) return Promise.resolve();
4066
- this.emit("before-close", void 0);
4067
- this.isOpen_ = false;
4068
- this.isAnimating_ = true;
4069
- sheetStack.update();
4070
- if (reason !== "back" && this.backMarker) {
4071
- popBackMarker(this.backMarker);
4072
- }
4073
- this.backMarker = null;
4074
- this.element.setAttribute("data-state", "exiting");
4075
- const dur = this.effectiveDuration();
4076
- const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
4077
- this.element.style.transition = transitionStr;
4078
- if (this.backdrop) {
4079
- this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
4080
- }
4081
- this.setWillChange(true);
4082
- const cycle = ++this.cycleNonce;
4083
- return new Promise((resolve) => {
4084
- requestAnimationFrame(() => {
4085
- if (this.destroyed || cycle !== this.cycleNonce) return resolve();
4086
- this.element.style.transform = closedTransformFor(
4087
- this.edge,
4088
- this.exitAnimation,
4089
- this.peekPx
4090
- );
4091
- this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
4092
- if (this.backdrop) {
4093
- this.backdrop.style.opacity = "0";
4094
- this.backdrop.style.pointerEvents = "none";
4095
- if (this.backdropFilter) {
4096
- this.applyBackdropFilter(void 0);
4097
- }
4098
- }
4099
- this.releaseInteractiveListeners();
4100
- this.awaitTransition(
4101
- cycle,
4102
- this.exitAnimation === "fade" ? "opacity" : "transform"
4103
- ).then(() => {
4104
- if (this.destroyed || cycle !== this.cycleNonce) return resolve();
4105
- this.isAnimating_ = false;
4106
- this.setWillChange(false);
4107
- if (this.peekPx === void 0) {
4108
- this.element.setAttribute("hidden", "");
4109
- }
4110
- this.element.setAttribute("data-state", "closed");
4111
- if (this.focusTrapEnabled) {
4112
- this.element.removeAttribute("aria-modal");
4113
- }
4114
- this.emit("close", { reason });
4115
- this.onCloseCb?.();
4116
- resolve();
4117
- });
4118
- });
4119
- });
3741
+ origPushState = null;
3742
+ origReplaceState = null;
3743
+ patchedPushState = null;
3744
+ patchedReplaceState = null;
3745
+ patchInstalled = false;
3746
+ };
3747
+ function onPopState() {
3748
+ if (suppress > 0) {
3749
+ suppress -= 1;
3750
+ const rebrand = pendingRebrands.shift();
3751
+ if (rebrand) rebrand();
3752
+ maybeTeardownListener();
3753
+ return;
4120
3754
  }
4121
- toggle() {
4122
- if (this.destroyed) return Promise.resolve();
4123
- return this.isOpen_ ? this.close() : this.open();
3755
+ if (entries.length === 0) {
3756
+ notifyRouteChange();
3757
+ maybeTeardownListener();
3758
+ return;
4124
3759
  }
4125
- destroy() {
4126
- this.destroyed = true;
4127
- this.cycleNonce++;
4128
- this.bus.clear();
4129
- this.setWillChange(false);
4130
- this.releaseInteractiveListeners();
4131
- if (this.backMarker) {
4132
- popBackMarker(this.backMarker);
4133
- this.backMarker = null;
4134
- }
4135
- this.releaseStackEntry?.();
4136
- this.releaseStackEntry = null;
4137
- const hadOriginalParent = this.originalParent !== null;
4138
- this.restoreMount();
4139
- if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
4140
- this.element.parentNode.removeChild(this.element);
3760
+ let target = null;
3761
+ let targetIdx = -1;
3762
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3763
+ const entry = entries[i];
3764
+ if (!entry.surface.isOpen()) continue;
3765
+ if (!target) {
3766
+ target = entry;
3767
+ targetIdx = i;
4141
3768
  }
4142
- }
4143
- setBackdropOpacity(opacity) {
4144
- if (this.destroyed) return;
4145
- this.backdropOpacity = opacity;
4146
- if (this.isOpen_ && this.backdrop) {
4147
- this.backdrop.style.opacity = String(opacity);
4148
- this.syncBackdropPointerEvents();
3769
+ if (entry.surface.isTop()) {
3770
+ target = entry;
3771
+ targetIdx = i;
3772
+ break;
4149
3773
  }
4150
3774
  }
4151
- setBackdropFilter(filter) {
4152
- if (this.destroyed) return;
4153
- this.backdropFilter = filter;
4154
- if (!this.isOpen_ || !this.backdrop) return;
4155
- this.applyBackdropFilter(filter);
3775
+ if (!target) {
3776
+ const removed = entries.pop();
3777
+ if (removed) removed.live = false;
3778
+ maybeTeardownListener();
3779
+ return;
4156
3780
  }
4157
- setSwipeToClose(config) {
4158
- if (this.destroyed) return;
4159
- const wasEnabled = this.swipeToCloseEnabled;
4160
- this.assignSwipeFromConfig(config);
4161
- if (!this.isOpen_) return;
4162
- if (this.swipeToCloseEnabled && !this.detachSwipe) {
4163
- this.detachSwipe = this.installSwipeToClose();
4164
- } else if (!this.swipeToCloseEnabled && wasEnabled && this.detachSwipe) {
4165
- this.detachSwipe();
4166
- this.detachSwipe = null;
3781
+ target.live = false;
3782
+ entries.splice(targetIdx, 1);
3783
+ const settled = target;
3784
+ void Promise.resolve(settled.surface.close()).then(() => {
3785
+ if (settled.surface.isOpen()) restore(settled);
3786
+ });
3787
+ maybeTeardownListener();
3788
+ }
3789
+ function restore(entry) {
3790
+ if (!hasWindow() || !hasHistory()) return;
3791
+ entry.live = true;
3792
+ runInternal(() => {
3793
+ try {
3794
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3795
+ } catch {
3796
+ entry.live = false;
4167
3797
  }
3798
+ });
3799
+ if (entry.live) {
3800
+ entries.push(entry);
3801
+ ensureListener();
4168
3802
  }
4169
- setEnterAnimation(animation) {
4170
- if (this.destroyed) return;
4171
- this.enterAnimation = animation;
4172
- }
4173
- setExitAnimation(animation) {
4174
- if (this.destroyed) return;
4175
- this.exitAnimation = animation;
4176
- }
4177
- setReturnFocus(target) {
4178
- if (this.destroyed) return;
4179
- this.lifecycle.setReturnFocus(target);
4180
- }
4181
- setOverlay(opts) {
4182
- if (this.destroyed) return;
4183
- const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
4184
- const merged = preset ? {
4185
- backdropOpacity: preset.backdropOpacity,
4186
- backdropFilter: preset.backdropFilter === void 0 ? null : preset.backdropFilter,
4187
- swipeToClose: preset.swipeToClose,
4188
- enterAnimation: preset.enterAnimation,
4189
- exitAnimation: preset.exitAnimation
4190
- } : {};
4191
- if (opts.backdropOpacity !== void 0) {
4192
- merged.backdropOpacity = opts.backdropOpacity;
4193
- }
4194
- if (opts.backdropFilter !== void 0) {
4195
- merged.backdropFilter = opts.backdropFilter;
4196
- }
4197
- if (opts.swipeToClose !== void 0) {
4198
- merged.swipeToClose = opts.swipeToClose;
4199
- }
4200
- if (opts.enterAnimation !== void 0) {
4201
- merged.enterAnimation = opts.enterAnimation;
4202
- }
4203
- if (opts.exitAnimation !== void 0) {
4204
- merged.exitAnimation = opts.exitAnimation;
4205
- }
4206
- if (merged.enterAnimation !== void 0) {
4207
- this.enterAnimation = merged.enterAnimation;
4208
- }
4209
- if (merged.exitAnimation !== void 0) {
4210
- this.exitAnimation = merged.exitAnimation;
4211
- }
4212
- if (merged.swipeToClose !== void 0) {
4213
- this.setSwipeToClose(merged.swipeToClose);
4214
- }
4215
- if (merged.backdropOpacity !== void 0) {
4216
- this.backdropOpacity = merged.backdropOpacity;
4217
- }
4218
- if (merged.backdropFilter !== void 0) {
4219
- this.backdropFilter = merged.backdropFilter === null ? void 0 : merged.backdropFilter;
4220
- }
4221
- if (this.isOpen_ && this.backdrop) {
4222
- if (merged.backdropOpacity !== void 0) {
4223
- this.backdrop.style.opacity = String(this.backdropOpacity);
4224
- this.syncBackdropPointerEvents();
4225
- }
4226
- if (merged.backdropFilter !== void 0) {
4227
- this.applyBackdropFilter(this.backdropFilter);
4228
- }
4229
- }
4230
- if (opts.duration !== void 0) this.duration = opts.duration;
4231
- if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
4232
- if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
4233
- if (opts.closeOnEscape !== void 0) {
4234
- this.closeOnEscape = opts.closeOnEscape;
4235
- if (!opts.closeOnEscape) {
4236
- this.detachEscape?.();
4237
- this.detachEscape = null;
4238
- } else if (this.isOpen_) {
4239
- this.installInteractiveListeners();
4240
- }
4241
- }
4242
- if (opts.closeOnBackdrop !== void 0) {
4243
- this.closeOnBackdropEnabled = opts.closeOnBackdrop;
4244
- if (!opts.closeOnBackdrop) {
4245
- this.detachBackdrop?.();
4246
- this.detachBackdrop = null;
4247
- } else if (this.isOpen_) {
4248
- this.installInteractiveListeners();
4249
- }
4250
- }
4251
- if (opts.closeOnOutsidePointer !== void 0) {
4252
- this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
4253
- if (!opts.closeOnOutsidePointer) {
4254
- this.detachOutsidePointer?.();
4255
- this.detachOutsidePointer = null;
4256
- } else if (this.isOpen_) {
4257
- this.installInteractiveListeners();
4258
- }
4259
- }
4260
- if (opts.children !== void 0) {
4261
- if (opts.children === null) {
4262
- this.clearOverlayChildren();
4263
- } else {
4264
- this.setOverlayChildren(opts.children);
4265
- }
4266
- }
3803
+ }
3804
+ function pushBackMarker(surface) {
3805
+ const entry = { surface, priorUrl: void 0, live: false };
3806
+ if (!hasWindow() || !hasHistory()) {
3807
+ return entry;
4267
3808
  }
4268
- setOverlayChildren(children) {
4269
- if (this.destroyed) return;
4270
- const node = typeof children === "function" ? children() : children;
4271
- this.element.replaceChildren(node);
4272
- this.hasInjectedChildren = true;
4273
- }
4274
- clearOverlayChildren() {
4275
- if (this.destroyed || !this.hasInjectedChildren) return;
4276
- this.element.replaceChildren();
4277
- this.hasInjectedChildren = false;
4278
- }
4279
- assignSwipeFromConfig(swipe) {
4280
- if (typeof swipe === "object" && swipe !== null) {
4281
- this.swipeToCloseEnabled = swipe.enabled ?? true;
4282
- this.swipeThreshold = swipe.threshold ?? 0.3;
4283
- this.swipeVelocityThreshold = swipe.velocityThreshold ?? 0.5;
4284
- } else {
4285
- this.swipeToCloseEnabled = swipe ?? false;
4286
- this.swipeThreshold = 0.3;
4287
- this.swipeVelocityThreshold = 0.5;
3809
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3810
+ runInternal(() => {
3811
+ try {
3812
+ history.pushState(brandMarker(surface), "", surface.url);
3813
+ entry.live = true;
3814
+ } catch {
3815
+ entry.live = false;
4288
3816
  }
3817
+ });
3818
+ if (entry.live) {
3819
+ entries.push(entry);
3820
+ ensureListener();
4289
3821
  }
4290
- emit(event, payload) {
4291
- this.bus.emit(event, payload);
4292
- }
4293
- effectiveDuration() {
4294
- return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
3822
+ return entry;
3823
+ }
3824
+ function popBackMarker(handle) {
3825
+ if (!hasWindow() || !hasHistory()) return;
3826
+ const entry = handle;
3827
+ const idx = entries.indexOf(entry);
3828
+ if (idx === -1 || !entry.live) return;
3829
+ const last = entries.length - 1;
3830
+ entry.live = false;
3831
+ entries.splice(idx, 1);
3832
+ let rebrand = null;
3833
+ if (idx !== last) {
3834
+ const rebrandSurface = entries[entries.length - 1].surface;
3835
+ const rebrandUrl = entry.priorUrl;
3836
+ rebrand = () => {
3837
+ runInternal(() => {
3838
+ history.replaceState(
3839
+ brandMarker(rebrandSurface),
3840
+ "",
3841
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3842
+ );
3843
+ });
3844
+ };
4295
3845
  }
4296
- resolvePeekPx(raw) {
4297
- if (typeof raw === "number") return Math.max(0, raw);
3846
+ pendingRebrands.push(rebrand);
3847
+ suppress += 1;
3848
+ runInternal(() => {
4298
3849
  try {
4299
- return Math.max(0, resolveSnap(raw, "bottom"));
3850
+ history.back();
4300
3851
  } catch {
4301
- return 0;
3852
+ suppress = Math.max(0, suppress - 1);
3853
+ pendingRebrands.pop();
4302
3854
  }
3855
+ });
3856
+ maybeTeardownListener();
3857
+ }
3858
+ function subscribeRouteChange(fn) {
3859
+ if (!hasWindow() || !hasHistory()) return () => {
3860
+ };
3861
+ subscribers.add(fn);
3862
+ if (subscribers.size === 1) installPatch();
3863
+ ensureListener();
3864
+ return () => {
3865
+ if (!subscribers.has(fn)) return;
3866
+ subscribers.delete(fn);
3867
+ if (subscribers.size === 0) removePatch();
3868
+ maybeTeardownListener();
3869
+ };
3870
+ }
3871
+
3872
+ // src/core/features/route.ts
3873
+ function installRoute(deps) {
3874
+ if (typeof window === "undefined") {
3875
+ return () => {
3876
+ };
4303
3877
  }
4304
- maybeMount() {
4305
- if (this.mounted || !this.mountTarget || this.mountTarget === "parent") {
4306
- this.mounted = true;
4307
- return;
4308
- }
4309
- const target = this.mountTarget === "body" ? document.body : this.mountTarget;
4310
- if (!target) {
4311
- this.mounted = true;
4312
- return;
4313
- }
4314
- this.originalParent = this.element.parentNode;
4315
- this.originalNextSibling = this.element.nextSibling;
4316
- target.appendChild(this.element);
4317
- this.movedToTarget = true;
4318
- this.mounted = true;
3878
+ if (!deps.routedTo && !deps.closeOnBack) {
3879
+ return () => {
3880
+ };
4319
3881
  }
4320
- restoreMount() {
4321
- if (!this.originalParent) return;
3882
+ let handle = null;
3883
+ const onOpen = () => {
3884
+ if (handle) return;
3885
+ handle = pushBackMarker({
3886
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3887
+ isTop: deps.isTopSheet,
3888
+ close: deps.close,
3889
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3890
+ url: deps.routedTo
3891
+ });
3892
+ };
3893
+ const onClose = () => {
3894
+ if (!handle) return;
3895
+ popBackMarker(handle);
3896
+ handle = null;
3897
+ };
3898
+ const unsubscribeOpen = deps.on("open", onOpen);
3899
+ const unsubscribeClose = deps.on("close", onClose);
3900
+ if (deps.getSize() > 0) onOpen();
3901
+ return () => {
3902
+ unsubscribeOpen();
3903
+ unsubscribeClose();
3904
+ if (handle) {
3905
+ popBackMarker(handle);
3906
+ handle = null;
3907
+ }
3908
+ };
3909
+ }
3910
+ function installRouteChange(deps) {
3911
+ return subscribeRouteChange(() => {
3912
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3913
+ void deps.close();
3914
+ });
3915
+ }
3916
+
3917
+ // src/core/features/persist.ts
3918
+ function installPersist(engine, key) {
3919
+ const unsubscribe = engine.on("snap", ({ id }) => {
3920
+ if (typeof window === "undefined") return;
3921
+ if (typeof localStorage === "undefined") return;
4322
3922
  try {
4323
- if (this.originalNextSibling && this.originalNextSibling.parentNode === this.originalParent) {
4324
- this.originalParent.insertBefore(this.element, this.originalNextSibling);
4325
- } else {
4326
- this.originalParent.appendChild(this.element);
4327
- }
3923
+ localStorage.setItem(key, id);
4328
3924
  } catch {
4329
3925
  }
4330
- this.originalParent = null;
4331
- this.originalNextSibling = null;
3926
+ });
3927
+ return unsubscribe;
3928
+ }
3929
+
3930
+ // src/core/features/auto-collapse.ts
3931
+ function installAutoCollapse(deps) {
3932
+ const { ms } = deps;
3933
+ if (ms === void 0 || ms <= 0) {
3934
+ return () => {
3935
+ };
4332
3936
  }
4333
- applyClosedStyles() {
4334
- this.element.style.transform = closedTransformFor(
4335
- this.edge,
4336
- this.enterAnimation,
4337
- this.peekPx
4338
- );
4339
- this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
4340
- if (this.peekPx === void 0) {
4341
- this.element.setAttribute("hidden", "");
3937
+ let timer = null;
3938
+ const clearTimer = () => {
3939
+ if (timer !== null) {
3940
+ clearTimeout(timer);
3941
+ timer = null;
4342
3942
  }
4343
- this.element.setAttribute("data-state", "closed");
4344
- if (this.backdrop) {
4345
- this.backdrop.style.opacity = "0";
4346
- this.backdrop.style.pointerEvents = "none";
3943
+ };
3944
+ const fire = () => {
3945
+ if (deps.isDestroyed()) return;
3946
+ if (deps.isDragging()) return;
3947
+ const active = deps.resolveSnap(deps.getActiveId());
3948
+ if (!active || active.size === 0) return;
3949
+ let target = null;
3950
+ for (const id of deps.getAllowedIds()) {
3951
+ const snap = deps.resolveSnap(id);
3952
+ if (!snap || snap.size <= 0) continue;
3953
+ if (!target || snap.size < target.size) target = snap;
4347
3954
  }
3955
+ if (!target) return;
3956
+ if (target.id === active.id || target.size >= active.size) return;
3957
+ deps.snapTo(target.id);
3958
+ };
3959
+ const reset = () => {
3960
+ clearTimer();
3961
+ if (deps.isDestroyed()) return;
3962
+ timer = setTimeout(() => {
3963
+ timer = null;
3964
+ fire();
3965
+ }, ms);
3966
+ };
3967
+ const unsubscribe = deps.on("snap", () => reset());
3968
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3969
+ return () => {
3970
+ clearTimer();
3971
+ unsubscribe();
3972
+ };
3973
+ }
3974
+
3975
+ // src/core/features/content-drag.ts
3976
+ function installContentDrag(deps) {
3977
+ const detach = deps.attachDragSurface(deps.container, "content");
3978
+ return () => detach?.();
3979
+ }
3980
+
3981
+ // src/core/features/visual-viewport.ts
3982
+ var SAFETY_PAD = 8;
3983
+ function installVisualViewport(deps) {
3984
+ if (typeof window === "undefined" || !window.visualViewport) {
3985
+ return () => {
3986
+ };
4348
3987
  }
4349
- syncBackdropPointerEvents() {
4350
- if (!this.backdrop) return;
4351
- this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
4352
- }
4353
- applyBackdropFilter(filter) {
4354
- if (!this.backdrop) return;
4355
- const style = this.backdrop.style;
4356
- if (filter) {
4357
- this.backdrop.style.backdropFilter = filter;
4358
- style.webkitBackdropFilter = filter;
4359
- } else {
4360
- this.backdrop.style.backdropFilter = "";
4361
- style.webkitBackdropFilter = "";
3988
+ const vv = window.visualViewport;
3989
+ let rafId = null;
3990
+ const onResize = () => {
3991
+ if (rafId !== null) return;
3992
+ rafId = requestAnimationFrame(() => {
3993
+ rafId = null;
3994
+ if (deps.isDestroyed()) return;
3995
+ deps.recomputeSnaps();
3996
+ const isVertical = deps.isVerticalAxis();
3997
+ const visible = isVertical ? vv.height : vv.width;
3998
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
3999
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
4000
+ if (clamp < deps.getMaxAxisSize()) {
4001
+ deps.setMaxAxisSize(clamp);
4002
+ }
4003
+ const wasAnimating = deps.isAnimating?.() ?? false;
4004
+ deps.cancelInFlight();
4005
+ deps.newCycle();
4006
+ const max = deps.getMaxAxisSize();
4007
+ deps.setSize(Math.min(deps.getSize(), max));
4008
+ const current = deps.resolveActiveSnap();
4009
+ if (current && !deps.isDragging()) {
4010
+ deps.applySize(Math.min(current.size, max));
4011
+ if (wasAnimating) deps.resyncAfterCancel?.();
4012
+ } else {
4013
+ deps.applySize(deps.getSize());
4014
+ }
4015
+ });
4016
+ };
4017
+ vv.addEventListener("resize", onResize);
4018
+ return () => {
4019
+ vv.removeEventListener("resize", onResize);
4020
+ if (rafId !== null) cancelAnimationFrame(rafId);
4021
+ };
4022
+ }
4023
+
4024
+ // src/core/features/engine-features.ts
4025
+ function routeFeature() {
4026
+ return {
4027
+ name: "route",
4028
+ install: (ctx) => {
4029
+ const teardowns = new TeardownStack();
4030
+ if (ctx.options.closeOnRouteChange) {
4031
+ teardowns.add(
4032
+ installRouteChange({
4033
+ isDestroyed: ctx.isDestroyed,
4034
+ getSize: ctx.getSize,
4035
+ close: () => ctx.close()
4036
+ })
4037
+ );
4038
+ }
4039
+ teardowns.add(
4040
+ installRoute({
4041
+ routedTo: ctx.options.routedTo,
4042
+ closeOnBack: ctx.options.closeOnBack,
4043
+ isTopSheet: ctx.isTopSheet,
4044
+ getSize: ctx.getSize,
4045
+ isDestroyed: ctx.isDestroyed,
4046
+ close: () => ctx.close("back"),
4047
+ on: ctx.on,
4048
+ sheetId: ctx.sheetId
4049
+ })
4050
+ );
4051
+ return () => teardowns.drain();
4362
4052
  }
4363
- }
4364
- setWillChange(on) {
4365
- this.element.style.willChange = on ? "transform, opacity" : "";
4366
- if (this.backdrop) {
4367
- this.backdrop.style.willChange = on ? "opacity" : "";
4053
+ };
4054
+ }
4055
+ function persistFeature() {
4056
+ return {
4057
+ name: "persist",
4058
+ install: (ctx) => {
4059
+ const key = ctx.options.persistKey;
4060
+ if (!key) return;
4061
+ return installPersist({ on: ctx.on }, key);
4368
4062
  }
4369
- }
4370
- awaitTransition(cycle, property = "transform") {
4371
- return new Promise((resolve) => {
4372
- let done = false;
4373
- const finish = () => {
4374
- if (done) return;
4375
- done = true;
4376
- this.element.removeEventListener("transitionend", onEnd);
4377
- clearTimeout(timer);
4378
- resolve();
4379
- };
4380
- const onEnd = (e) => {
4381
- if (cycle !== this.cycleNonce) {
4382
- finish();
4383
- return;
4384
- }
4385
- if (e.target === this.element && e.propertyName === property) finish();
4386
- };
4387
- this.element.addEventListener("transitionend", onEnd);
4388
- const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
4389
- });
4390
- }
4391
- installInteractiveListeners() {
4392
- this.lifecycle.install();
4393
- if (this.swipeToCloseEnabled && !this.detachSwipe) {
4394
- this.detachSwipe = this.installSwipeToClose();
4063
+ };
4064
+ }
4065
+ function autoCollapseFeature() {
4066
+ return {
4067
+ name: "auto-collapse",
4068
+ install: (ctx) => installAutoCollapse({
4069
+ ms: ctx.options.autoCollapseAfter,
4070
+ isDestroyed: ctx.isDestroyed,
4071
+ isDragging: ctx.isDragging,
4072
+ getAllowedIds: ctx.getAllowedIds,
4073
+ getActiveId: ctx.getActiveId,
4074
+ resolveSnap: ctx.resolveSnap,
4075
+ snapTo: ctx.snapTo,
4076
+ on: ctx.on
4077
+ })
4078
+ };
4079
+ }
4080
+ function contentSwipeFeature() {
4081
+ return {
4082
+ name: "content-swipe",
4083
+ stage: "attach",
4084
+ install: (ctx) => {
4085
+ if (!ctx.scrollContainer) return;
4086
+ return installContentDrag({
4087
+ container: ctx.scrollContainer,
4088
+ attachDragSurface: ctx.attachDragSurface
4089
+ });
4395
4090
  }
4396
- if (this.closeOnEscape && !this.detachEscape) {
4397
- const handler = (e) => {
4398
- if (e.defaultPrevented) return;
4399
- if (e.key === "Escape" && this.isOpen_ && this.isTop) {
4400
- void this.close("escape");
4401
- }
4402
- };
4403
- document.addEventListener("keydown", handler);
4404
- this.detachEscape = () => document.removeEventListener("keydown", handler);
4405
- }
4406
- if (this.closeOnBackdropEnabled && this.backdrop && !this.detachBackdrop) {
4407
- const handler = () => void this.close("backdrop");
4408
- this.backdrop.addEventListener("click", handler);
4409
- this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
4410
- }
4411
- if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
4412
- const handler = (e) => {
4413
- if (!this.isOpen_) return;
4414
- const target = e.target;
4415
- if (!target) return;
4416
- if (this.element.contains(target)) return;
4417
- if (this.backdrop && this.backdrop.contains(target)) return;
4418
- void this.close("outside-pointer");
4419
- };
4420
- document.addEventListener("pointerdown", handler, true);
4421
- this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
4422
- }
4423
- if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
4424
- this.backMarker = pushBackMarker({
4425
- isOpen: () => this.isOpen_,
4426
- isTop: () => this.isTop,
4427
- close: () => this.close("back"),
4428
- markerState: () => ({ __bsOverlay: this.id })
4091
+ };
4092
+ }
4093
+ function visualViewportFeature() {
4094
+ return {
4095
+ name: "visual-viewport",
4096
+ install: (ctx) => {
4097
+ if (typeof window === "undefined") return;
4098
+ return installVisualViewport({
4099
+ element: ctx.element,
4100
+ isVerticalAxis: ctx.isVerticalAxis,
4101
+ isDestroyed: ctx.isDestroyed,
4102
+ isDragging: ctx.isDragging,
4103
+ recomputeSnaps: ctx.recomputeSnaps,
4104
+ resolveActiveSnap: ctx.resolveActiveSnap,
4105
+ getMaxAxisSize: ctx.getMaxAxisSize,
4106
+ getSize: ctx.getSize,
4107
+ setMaxAxisSize: ctx.setMaxAxisSize,
4108
+ setSize: ctx.setSize,
4109
+ applySize: ctx.applySize,
4110
+ cancelInFlight: ctx.cancelInFlight,
4111
+ newCycle: ctx.newCycle,
4112
+ isAnimating: ctx.isAnimating,
4113
+ resyncAfterCancel: ctx.resyncAfterResize
4429
4114
  });
4430
4115
  }
4431
- }
4432
- releaseInteractiveListeners() {
4433
- this.detachEscape?.();
4434
- this.detachEscape = null;
4435
- this.detachBackdrop?.();
4436
- this.detachBackdrop = null;
4437
- this.detachSwipe?.();
4438
- this.detachSwipe = null;
4439
- this.detachOutsidePointer?.();
4440
- this.detachOutsidePointer = null;
4441
- this.lifecycle.release();
4442
- }
4443
- installSwipeToClose() {
4444
- return installOverlaySwipe({
4445
- element: this.element,
4446
- edge: this.edge,
4447
- getThreshold: () => this.swipeThreshold,
4448
- getVelocityThreshold: () => this.swipeVelocityThreshold,
4449
- getExitTransition: () => `transform ${this.effectiveDuration()}ms ${this.exitEasing}`,
4450
- getOpenTransform: () => openTransformFor(this.enterAnimation),
4451
- close: () => void this.close("swipe")
4116
+ };
4117
+ }
4118
+
4119
+ // src/core/default-features.ts
4120
+ function defaultEngineFeatures() {
4121
+ return [
4122
+ contentSwipeFeature(),
4123
+ visualViewportFeature(),
4124
+ persistFeature(),
4125
+ autoCollapseFeature(),
4126
+ routeFeature()
4127
+ ];
4128
+ }
4129
+
4130
+ // src/core/BottomSheetEngine.ts
4131
+ var BottomSheetEngine = class extends BottomSheetCore {
4132
+ constructor(opts) {
4133
+ super({
4134
+ ...opts,
4135
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
4452
4136
  });
4453
4137
  }
4454
- registerInStack() {
4455
- this.releaseStackEntry = sheetStack.push({
4456
- id: this.id,
4457
- setZIndex: (z) => {
4458
- this.element.style.zIndex = String(z);
4459
- if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
4460
- },
4461
- setIsTop: (isTop) => {
4462
- this.isTop = isTop;
4463
- if (this.backdrop) {
4464
- this.backdrop.style.display = isTop ? "" : "none";
4465
- }
4466
- },
4467
- isOpen: () => this.isOpen_
4468
- });
4138
+ };
4139
+
4140
+ // src/core/lifecycle/sheet-manager.ts
4141
+ var createSheetManager = (initial = {}) => {
4142
+ const registry = /* @__PURE__ */ new Map();
4143
+ for (const [key, config] of Object.entries(initial)) {
4144
+ registry.set(key, { ...config, key });
4469
4145
  }
4146
+ return {
4147
+ register(key, config) {
4148
+ registry.set(key, { ...config, key });
4149
+ },
4150
+ unregister(key) {
4151
+ registry.delete(key);
4152
+ },
4153
+ resolve(key) {
4154
+ return registry.get(key) ?? null;
4155
+ },
4156
+ keys() {
4157
+ return Array.from(registry.keys());
4158
+ },
4159
+ transition(prev, next) {
4160
+ if (prev) registry.get(prev)?.onClose?.(prev);
4161
+ if (next) registry.get(next)?.onOpen?.(next);
4162
+ }
4163
+ };
4470
4164
  };
4471
- var createOverlay = (opts) => new OverlayEngine(opts);
4472
4165
 
4473
- export { BottomSheetEngine, DEFAULT_SPRING, OVERLAY_PRESETS, OverlayEngine as Overlay, OverlayEngine, SCRIM_PRESETS, allowedRange, applyOverlayPosition, attachGestures, createOverlay, createSheetManager, easeOutBack, easeOutCubic, findById, findNearest, installFocusTrap, installGestures, lockBodyScroll, prefersReducedMotion, resolveSheetAnchoredStyle, resolveSnap, resolveSnapList, runAnchorTransition, runSpring, sheetStack, tween };
4166
+ export { BottomSheetCore, BottomSheetEngine, SCRIM_PRESETS, applyOverlayPosition, autoCollapseFeature, contentSwipeFeature, createSheetManager, defaultEngineFeatures, installFocusTrap, installGestures, lockBodyScroll, persistFeature, resolveSheetAnchoredStyle, resolveSnap, resolveSnapList, routeFeature, runAnchorTransition, sheetStack, visualViewportFeature };
4474
4167
  //# sourceMappingURL=index.js.map
4475
4168
  //# sourceMappingURL=index.js.map