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