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