@surdeddd/bottom-sheet 0.12.0 → 0.13.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 (58) hide show
  1. package/README.md +52 -1
  2. package/dist/{BottomSheetEngine-D6oiEfZ4.d.cts → BottomSheetCore-CsIjyq7C.d.cts} +60 -5
  3. package/dist/{BottomSheetEngine-Dhy0X_e9.d.ts → BottomSheetCore-D16lsa6g.d.ts} +60 -5
  4. package/dist/BottomSheetEngine-BYjAVrmd.d.cts +7 -0
  5. package/dist/BottomSheetEngine-YuCNugZn.d.ts +7 -0
  6. package/dist/core.cjs +3476 -0
  7. package/dist/core.d.cts +3 -0
  8. package/dist/core.d.ts +3 -0
  9. package/dist/core.js +3474 -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 +972 -685
  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 +972 -685
  19. package/dist/features.cjs +533 -0
  20. package/dist/features.d.cts +13 -0
  21. package/dist/features.d.ts +13 -0
  22. package/dist/features.js +526 -0
  23. package/dist/index-DdYLOIIT.d.ts +15 -0
  24. package/dist/index-dfrzw-lI.d.cts +15 -0
  25. package/dist/index.cjs +805 -534
  26. package/dist/index.d.cts +6 -15
  27. package/dist/index.d.ts +6 -15
  28. package/dist/index.js +799 -535
  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 +6 -5
  34. package/dist/overlay.js +6 -5
  35. package/dist/preact.d.cts +2 -1
  36. package/dist/preact.d.ts +2 -1
  37. package/dist/qwik-lib/index.qwik.cjs +4267 -0
  38. package/dist/qwik-lib/index.qwik.mjs +4267 -0
  39. package/dist/qwik.cjs +798 -534
  40. package/dist/qwik.d.cts +2 -2
  41. package/dist/qwik.d.ts +2 -2
  42. package/dist/qwik.js +798 -534
  43. package/dist/react.cjs +798 -534
  44. package/dist/react.d.cts +3 -2
  45. package/dist/react.d.ts +3 -2
  46. package/dist/react.js +798 -534
  47. package/dist/solid.cjs +798 -534
  48. package/dist/solid.d.cts +63 -4
  49. package/dist/solid.d.ts +63 -4
  50. package/dist/solid.js +798 -534
  51. package/dist/solid.source.jsx +798 -534
  52. package/dist/svelte-core.cjs +798 -534
  53. package/dist/svelte-core.js +798 -534
  54. package/dist/svelte.cjs +798 -534
  55. package/dist/svelte.js +798 -534
  56. package/dist/vue.cjs +909 -637
  57. package/dist/vue.js +909 -637
  58. package/package.json +63 -11
package/dist/index.cjs CHANGED
@@ -354,74 +354,6 @@ var SnapResolver = class {
354
354
  }
355
355
  };
356
356
 
357
- // src/core/features/persist.ts
358
- function readPersistedId(key) {
359
- if (!key) return null;
360
- if (typeof window === "undefined") return null;
361
- if (typeof localStorage === "undefined") return null;
362
- try {
363
- return localStorage.getItem(key);
364
- } catch {
365
- return null;
366
- }
367
- }
368
- function installPersist(engine, key) {
369
- const unsubscribe = engine.on("snap", ({ id }) => {
370
- if (typeof window === "undefined") return;
371
- if (typeof localStorage === "undefined") return;
372
- try {
373
- localStorage.setItem(key, id);
374
- } catch {
375
- }
376
- });
377
- return unsubscribe;
378
- }
379
-
380
- // src/core/features/auto-collapse.ts
381
- function installAutoCollapse(deps) {
382
- const { ms } = deps;
383
- if (ms === void 0 || ms <= 0) {
384
- return () => {
385
- };
386
- }
387
- let timer = null;
388
- const clearTimer = () => {
389
- if (timer !== null) {
390
- clearTimeout(timer);
391
- timer = null;
392
- }
393
- };
394
- const fire = () => {
395
- if (deps.isDestroyed()) return;
396
- if (deps.isDragging()) return;
397
- const active = deps.resolveSnap(deps.getActiveId());
398
- if (!active || active.size === 0) return;
399
- let target = null;
400
- for (const id of deps.getAllowedIds()) {
401
- const snap = deps.resolveSnap(id);
402
- if (!snap || snap.size <= 0) continue;
403
- if (!target || snap.size < target.size) target = snap;
404
- }
405
- if (!target) return;
406
- if (target.id === active.id || target.size >= active.size) return;
407
- deps.snapTo(target.id);
408
- };
409
- const reset = () => {
410
- clearTimer();
411
- if (deps.isDestroyed()) return;
412
- timer = setTimeout(() => {
413
- timer = null;
414
- fire();
415
- }, ms);
416
- };
417
- const unsubscribe = deps.on("snap", () => reset());
418
- if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
419
- return () => {
420
- clearTimer();
421
- unsubscribe();
422
- };
423
- }
424
-
425
357
  // src/core/features/linked-sheets.ts
426
358
  function notifyLinkedSheets(sheets, self) {
427
359
  if (sheets.length === 0) return;
@@ -444,394 +376,65 @@ function notifyLinkedSheets(sheets, self) {
444
376
  }
445
377
  }
446
378
 
447
- // src/core/features/history-coordinator.ts
448
- var entries = [];
449
- var subscribers = /* @__PURE__ */ new Set();
450
- var suppress = 0;
451
- var internalDepth = 0;
452
- var pendingRebrand = null;
453
- var listenerInstalled = false;
454
- var patchInstalled = false;
455
- var origPushState = null;
456
- var origReplaceState = null;
457
- var patchedPushState = null;
458
- var patchedReplaceState = null;
459
- var hasWindow = () => typeof window !== "undefined";
460
- var hasHistory = () => typeof history !== "undefined";
461
- var brandMarker = (surface) => ({
462
- ...surface.markerState(),
463
- __bs: true
464
- });
465
- var runInternal = (fn) => {
466
- internalDepth += 1;
467
- try {
468
- fn();
469
- } finally {
470
- internalDepth -= 1;
471
- }
472
- };
473
- var notifyRouteChange = () => {
474
- const snapshot = Array.from(subscribers);
475
- for (const fn of snapshot) fn();
476
- };
477
- var ensureListener = () => {
478
- if (listenerInstalled || !hasWindow()) return;
479
- window.addEventListener("popstate", onPopState);
480
- listenerInstalled = true;
481
- };
482
- var maybeTeardownListener = () => {
483
- if (!listenerInstalled) return;
484
- if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
485
- if (hasWindow()) window.removeEventListener("popstate", onPopState);
486
- listenerInstalled = false;
487
- };
488
- var installPatch = () => {
489
- if (patchInstalled || !hasHistory()) return;
490
- origPushState = history.pushState;
491
- origReplaceState = history.replaceState;
492
- patchedPushState = function(...args) {
493
- origPushState.apply(this, args);
494
- if (internalDepth === 0) notifyRouteChange();
495
- };
496
- patchedReplaceState = function(...args) {
497
- origReplaceState.apply(this, args);
498
- if (internalDepth === 0) notifyRouteChange();
499
- };
500
- history.pushState = patchedPushState;
501
- history.replaceState = patchedReplaceState;
502
- patchInstalled = true;
503
- };
504
- var removePatch = () => {
505
- if (!patchInstalled) return;
506
- if (history.pushState === patchedPushState && origPushState) {
507
- history.pushState = origPushState;
508
- }
509
- if (history.replaceState === patchedReplaceState && origReplaceState) {
510
- history.replaceState = origReplaceState;
511
- }
512
- origPushState = null;
513
- origReplaceState = null;
514
- patchedPushState = null;
515
- patchedReplaceState = null;
516
- patchInstalled = false;
517
- };
518
- function onPopState() {
519
- if (suppress > 0) {
520
- suppress -= 1;
521
- const rebrand = pendingRebrand;
522
- pendingRebrand = null;
523
- if (rebrand) rebrand();
524
- maybeTeardownListener();
525
- return;
526
- }
527
- if (entries.length === 0) {
528
- notifyRouteChange();
529
- maybeTeardownListener();
530
- return;
379
+ // src/core/features/resize-observer.ts
380
+ function installResizeObserver(deps) {
381
+ if (typeof window === "undefined") {
382
+ return () => {
383
+ };
531
384
  }
532
- let target = null;
533
- let targetIdx = -1;
534
- for (let i = entries.length - 1; i >= 0; i -= 1) {
535
- const entry = entries[i];
536
- if (!entry.surface.isOpen()) continue;
537
- if (!target) {
538
- target = entry;
539
- targetIdx = i;
385
+ const onResize = () => {
386
+ if (deps.isDestroyed()) return;
387
+ deps.recomputeSnaps();
388
+ const mode = deps.getMode();
389
+ const isVerticalAxis = mode === "bottom" || mode === "top";
390
+ const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
391
+ const max = deps.getMaxAxisSize();
392
+ if (viewportSize2 > 0 && viewportSize2 < max) {
393
+ deps.setMaxAxisSize(viewportSize2);
540
394
  }
541
- if (entry.surface.isTop()) {
542
- target = entry;
543
- targetIdx = i;
544
- break;
395
+ const wasAnimating = deps.isAnimating?.() ?? false;
396
+ deps.cancelInFlight();
397
+ deps.newCycle();
398
+ const current = deps.resolveActiveSnap();
399
+ const newMax = deps.getMaxAxisSize();
400
+ const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
401
+ deps.setSize(targetSize);
402
+ if (current && !deps.isDragging()) {
403
+ deps.applySize(targetSize);
404
+ if (wasAnimating) deps.resyncAfterCancel?.();
545
405
  }
406
+ };
407
+ let resizeObserver = null;
408
+ if (typeof ResizeObserver !== "undefined") {
409
+ resizeObserver = new ResizeObserver(onResize);
410
+ resizeObserver.observe(document.documentElement);
411
+ } else {
412
+ window.addEventListener("resize", onResize);
546
413
  }
547
- if (!target) {
548
- const removed = entries.pop();
549
- if (removed) removed.live = false;
550
- maybeTeardownListener();
551
- return;
552
- }
553
- target.live = false;
554
- entries.splice(targetIdx, 1);
555
- const settled = target;
556
- void Promise.resolve(settled.surface.close()).then(() => {
557
- if (settled.surface.isOpen()) restore(settled);
558
- });
559
- maybeTeardownListener();
414
+ window.addEventListener("orientationchange", onResize);
415
+ const onVisibility = () => {
416
+ if (deps.isDestroyed()) return;
417
+ if (document.hidden) deps.cancelInFlight();
418
+ };
419
+ document.addEventListener("visibilitychange", onVisibility);
420
+ return () => {
421
+ resizeObserver?.disconnect();
422
+ window.removeEventListener("resize", onResize);
423
+ window.removeEventListener("orientationchange", onResize);
424
+ document.removeEventListener("visibilitychange", onVisibility);
425
+ };
560
426
  }
561
- function restore(entry) {
562
- if (!hasWindow() || !hasHistory()) return;
563
- entry.live = true;
564
- runInternal(() => {
565
- try {
566
- history.pushState(brandMarker(entry.surface), "", entry.surface.url);
567
- } catch {
568
- entry.live = false;
569
- }
570
- });
571
- if (entry.live) {
572
- entries.push(entry);
573
- ensureListener();
574
- }
575
- }
576
- function pushBackMarker(surface) {
577
- const entry = { surface, priorUrl: void 0, live: false };
578
- if (!hasWindow() || !hasHistory()) {
579
- return entry;
580
- }
581
- entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
582
- runInternal(() => {
583
- try {
584
- history.pushState(brandMarker(surface), "", surface.url);
585
- entry.live = true;
586
- } catch {
587
- entry.live = false;
588
- }
589
- });
590
- if (entry.live) {
591
- entries.push(entry);
592
- ensureListener();
593
- }
594
- return entry;
595
- }
596
- function popBackMarker(handle) {
597
- if (!hasWindow() || !hasHistory()) return;
598
- const entry = handle;
599
- const idx = entries.indexOf(entry);
600
- if (idx === -1 || !entry.live) return;
601
- const last = entries.length - 1;
602
- entry.live = false;
603
- entries.splice(idx, 1);
604
- if (idx !== last) {
605
- const rebrandSurface = entries[entries.length - 1].surface;
606
- const rebrandUrl = entry.priorUrl;
607
- pendingRebrand = () => {
608
- runInternal(() => {
609
- history.replaceState(
610
- brandMarker(rebrandSurface),
611
- "",
612
- rebrandUrl !== void 0 ? rebrandUrl : location.href
613
- );
614
- });
615
- };
616
- }
617
- suppress += 1;
618
- runInternal(() => {
619
- try {
620
- history.back();
621
- } catch {
622
- suppress = Math.max(0, suppress - 1);
623
- pendingRebrand = null;
624
- }
625
- });
626
- maybeTeardownListener();
627
- }
628
- function subscribeRouteChange(fn) {
629
- if (!hasWindow() || !hasHistory()) return () => {
630
- };
631
- subscribers.add(fn);
632
- if (subscribers.size === 1) installPatch();
633
- ensureListener();
634
- return () => {
635
- if (!subscribers.has(fn)) return;
636
- subscribers.delete(fn);
637
- if (subscribers.size === 0) removePatch();
638
- maybeTeardownListener();
639
- };
640
- }
641
-
642
- // src/core/features/route.ts
643
- function installRoute(deps) {
644
- if (typeof window === "undefined") {
645
- return () => {
646
- };
647
- }
648
- if (!deps.routedTo && !deps.closeOnBack) {
649
- return () => {
650
- };
651
- }
652
- let handle = null;
653
- const onOpen = () => {
654
- if (handle) return;
655
- handle = pushBackMarker({
656
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
657
- isTop: deps.isTopSheet,
658
- close: deps.close,
659
- markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
660
- url: deps.routedTo
661
- });
662
- };
663
- const onClose = () => {
664
- if (!handle) return;
665
- popBackMarker(handle);
666
- handle = null;
667
- };
668
- const unsubscribeOpen = deps.on("open", onOpen);
669
- const unsubscribeClose = deps.on("close", onClose);
670
- if (deps.getSize() > 0) onOpen();
671
- return () => {
672
- unsubscribeOpen();
673
- unsubscribeClose();
674
- if (handle) {
675
- popBackMarker(handle);
676
- handle = null;
677
- }
678
- };
679
- }
680
- function installRouteChange(deps) {
681
- return subscribeRouteChange(() => {
682
- if (deps.isDestroyed() || deps.getSize() <= 0) return;
683
- void deps.close();
684
- });
685
- }
686
-
687
- // src/core/features/content-swipe.ts
688
- var CONTENT_PULL_THRESHOLD = 60;
689
- function installContentSwipe(deps) {
690
- const { container } = deps;
691
- let startY = 0;
692
- let startScrollTop = 0;
693
- let moved = false;
694
- const onTouchStart = (e) => {
695
- const t = e.touches[0];
696
- if (!t) return;
697
- startY = t.clientY;
698
- startScrollTop = container.scrollTop;
699
- moved = false;
700
- };
701
- const onTouchMove = (e) => {
702
- const t = e.touches[0];
703
- if (!t) return;
704
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
705
- };
706
- const onTouchEnd = (e) => {
707
- if (!moved) return;
708
- if (deps.isDragging() || deps.isAnimating()) return;
709
- if (container.scrollTop !== startScrollTop) return;
710
- const t = e.changedTouches[0];
711
- if (!t) return;
712
- const deltaY = t.clientY - startY;
713
- const atTop = container.scrollTop <= 0;
714
- const allowed = deps.getAllowedIds();
715
- const idx = allowed.indexOf(deps.getActiveId());
716
- if (idx === -1) return;
717
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
718
- deps.snapTo(allowed[idx - 1]);
719
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
720
- deps.snapTo(allowed[idx + 1]);
721
- }
722
- };
723
- container.addEventListener("touchstart", onTouchStart, { passive: true });
724
- container.addEventListener("touchmove", onTouchMove, { passive: true });
725
- container.addEventListener("touchend", onTouchEnd, { passive: true });
726
- return () => {
727
- container.removeEventListener("touchstart", onTouchStart);
728
- container.removeEventListener("touchmove", onTouchMove);
729
- container.removeEventListener("touchend", onTouchEnd);
730
- };
731
- }
732
-
733
- // src/core/features/visual-viewport.ts
734
- var SAFETY_PAD = 8;
735
- function installVisualViewport(deps) {
736
- if (typeof window === "undefined" || !window.visualViewport) {
737
- return () => {
738
- };
739
- }
740
- const vv = window.visualViewport;
741
- let rafId = null;
742
- const onResize = () => {
743
- if (rafId !== null) return;
744
- rafId = requestAnimationFrame(() => {
745
- rafId = null;
746
- if (deps.isDestroyed()) return;
747
- deps.recomputeSnaps();
748
- const isVertical = deps.isVerticalAxis();
749
- const visible = isVertical ? vv.height : vv.width;
750
- const windowSize = isVertical ? window.innerHeight : window.innerWidth;
751
- const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
752
- if (clamp < deps.getMaxAxisSize()) {
753
- deps.setMaxAxisSize(clamp);
754
- }
755
- const wasAnimating = deps.isAnimating?.() ?? false;
756
- deps.cancelInFlight();
757
- deps.newCycle();
758
- const max = deps.getMaxAxisSize();
759
- deps.setSize(Math.min(deps.getSize(), max));
760
- const current = deps.resolveActiveSnap();
761
- if (current && !deps.isDragging()) {
762
- deps.applySize(Math.min(current.size, max));
763
- if (wasAnimating) deps.resyncAfterCancel?.();
764
- } else {
765
- deps.applySize(deps.getSize());
766
- }
767
- });
768
- };
769
- vv.addEventListener("resize", onResize);
770
- return () => {
771
- vv.removeEventListener("resize", onResize);
772
- if (rafId !== null) cancelAnimationFrame(rafId);
773
- };
774
- }
775
-
776
- // src/core/features/resize-observer.ts
777
- function installResizeObserver(deps) {
778
- if (typeof window === "undefined") {
779
- return () => {
780
- };
781
- }
782
- const onResize = () => {
783
- if (deps.isDestroyed()) return;
784
- deps.recomputeSnaps();
785
- const mode = deps.getMode();
786
- const isVerticalAxis = mode === "bottom" || mode === "top";
787
- const viewportSize2 = isVerticalAxis ? window.innerHeight : window.innerWidth;
788
- const max = deps.getMaxAxisSize();
789
- if (viewportSize2 > 0 && viewportSize2 < max) {
790
- deps.setMaxAxisSize(viewportSize2);
791
- }
792
- const wasAnimating = deps.isAnimating?.() ?? false;
793
- deps.cancelInFlight();
794
- deps.newCycle();
795
- const current = deps.resolveActiveSnap();
796
- const newMax = deps.getMaxAxisSize();
797
- const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
798
- deps.setSize(targetSize);
799
- if (current && !deps.isDragging()) {
800
- deps.applySize(targetSize);
801
- if (wasAnimating) deps.resyncAfterCancel?.();
802
- }
803
- };
804
- let resizeObserver = null;
805
- if (typeof ResizeObserver !== "undefined") {
806
- resizeObserver = new ResizeObserver(onResize);
807
- resizeObserver.observe(document.documentElement);
808
- } else {
809
- window.addEventListener("resize", onResize);
810
- }
811
- window.addEventListener("orientationchange", onResize);
812
- const onVisibility = () => {
813
- if (deps.isDestroyed()) return;
814
- if (document.hidden) deps.cancelInFlight();
815
- };
816
- document.addEventListener("visibilitychange", onVisibility);
817
- return () => {
818
- resizeObserver?.disconnect();
819
- window.removeEventListener("resize", onResize);
820
- window.removeEventListener("orientationchange", onResize);
821
- document.removeEventListener("visibilitychange", onVisibility);
822
- };
823
- }
824
-
825
- // src/core/features/fit-observer.ts
826
- var contentTargets = (scroller) => {
827
- if (!scroller) return [];
828
- const hasSlot = typeof HTMLSlotElement !== "undefined";
829
- const out = [];
830
- for (const child of Array.from(scroller.children)) {
831
- if (hasSlot && child instanceof HTMLSlotElement) {
832
- for (const el of child.assignedElements()) out.push(el);
833
- } else {
834
- out.push(child);
427
+
428
+ // src/core/features/fit-observer.ts
429
+ var contentTargets = (scroller) => {
430
+ if (!scroller) return [];
431
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
432
+ const out = [];
433
+ for (const child of Array.from(scroller.children)) {
434
+ if (hasSlot && child instanceof HTMLSlotElement) {
435
+ for (const el of child.assignedElements()) out.push(el);
436
+ } else {
437
+ out.push(child);
835
438
  }
836
439
  }
837
440
  return out;
@@ -1673,6 +1276,52 @@ function resolveAnimationPreset(animation) {
1673
1276
  }
1674
1277
  }
1675
1278
 
1279
+ // src/core/animation/waapi-settle.ts
1280
+ var SIM_HZ = 240;
1281
+ var SAMPLE_STRIDE = 4;
1282
+ var MAX_SIM_MS = 3e3;
1283
+ function sampleSpringSettle(from, to, velocityPxPerS, config) {
1284
+ const cfg = { ...DEFAULT_SPRING, ...config };
1285
+ const subDt = 1 / SIM_HZ;
1286
+ const stepMs = SAMPLE_STRIDE / SIM_HZ * 1e3;
1287
+ const maxSteps = Math.ceil(MAX_SIM_MS / 1e3 * SIM_HZ);
1288
+ const values = [from];
1289
+ let x = from;
1290
+ let v = velocityPxPerS;
1291
+ for (let i = 1; i <= maxSteps; i += 1) {
1292
+ const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1293
+ v += force / cfg.mass * subDt;
1294
+ x += v * subDt;
1295
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1296
+ values.push(to);
1297
+ break;
1298
+ }
1299
+ if (i % SAMPLE_STRIDE === 0) values.push(x);
1300
+ const atRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1301
+ if (atRest) break;
1302
+ }
1303
+ if (values[values.length - 1] !== to) values.push(to);
1304
+ return {
1305
+ values,
1306
+ stepMs,
1307
+ durationMs: (values.length - 1) * stepMs
1308
+ };
1309
+ }
1310
+ function sampleTweenSettle(from, to, durationMs, easing) {
1311
+ const count = Math.max(2, Math.min(60, Math.round(durationMs / 16)));
1312
+ const values = [];
1313
+ for (let i = 0; i <= count; i += 1) {
1314
+ const t = i / count;
1315
+ values.push(from + (to - from) * easing(t));
1316
+ }
1317
+ values[values.length - 1] = to;
1318
+ return {
1319
+ values,
1320
+ stepMs: durationMs / count,
1321
+ durationMs
1322
+ };
1323
+ }
1324
+
1676
1325
  // src/core/controllers/animation-runner.ts
1677
1326
  var DEFAULT_DURATION2 = 220;
1678
1327
  var VELOCITY_PX_PER_S = 1e3;
@@ -1683,6 +1332,10 @@ var AnimationRunner = class {
1683
1332
  __publicField(this, "applySizeFn");
1684
1333
  __publicField(this, "getSize");
1685
1334
  __publicField(this, "isDragging");
1335
+ __publicField(this, "applyAux");
1336
+ __publicField(this, "getTransformFor");
1337
+ __publicField(this, "settleWaapi");
1338
+ __publicField(this, "currentWaapi", null);
1686
1339
  __publicField(this, "animationKind");
1687
1340
  __publicField(this, "springConfig");
1688
1341
  __publicField(this, "duration");
@@ -1698,6 +1351,9 @@ var AnimationRunner = class {
1698
1351
  this.applySizeFn = deps.applySize;
1699
1352
  this.getSize = deps.getSize;
1700
1353
  this.isDragging = deps.isDragging;
1354
+ this.applyAux = deps.applyAux;
1355
+ this.getTransformFor = deps.getTransformFor;
1356
+ this.settleWaapi = opts.settleAnimation === "waapi";
1701
1357
  const preset = resolveAnimationPreset(opts.animation);
1702
1358
  this.animationKind = preset.kind;
1703
1359
  this.duration = opts.duration ?? preset.duration ?? DEFAULT_DURATION2;
@@ -1722,13 +1378,22 @@ var AnimationRunner = class {
1722
1378
  }
1723
1379
  }
1724
1380
  get isAnimating() {
1725
- return this.currentTween !== null || this.currentSpring !== null;
1381
+ return this.currentTween !== null || this.currentSpring !== null || this.currentWaapi !== null;
1726
1382
  }
1727
1383
  cancel() {
1728
1384
  this.currentTween?.cancel();
1729
1385
  this.currentSpring?.cancel();
1730
- }
1731
- async animateTo(target, velocityPxPerMs) {
1386
+ if (this.currentWaapi) {
1387
+ const entry = this.currentWaapi;
1388
+ this.currentWaapi = null;
1389
+ entry.stop();
1390
+ try {
1391
+ entry.anim.cancel();
1392
+ } catch {
1393
+ }
1394
+ }
1395
+ }
1396
+ async animateTo(target, velocityPxPerMs) {
1732
1397
  if (target === this.getSize()) {
1733
1398
  if (!this.isAnimating && !this.isDragging()) {
1734
1399
  this.element.style.willChange = "";
@@ -1745,6 +1410,27 @@ var AnimationRunner = class {
1745
1410
  if (!this.isDragging()) this.element.style.willChange = "";
1746
1411
  return;
1747
1412
  }
1413
+ if (this.settleWaapi && this.applyAux && this.getTransformFor && typeof this.element.animate === "function") {
1414
+ const samples = this.animationKind === "spring" ? sampleSpringSettle(
1415
+ this.getSize(),
1416
+ target,
1417
+ velocityPxPerMs * VELOCITY_PX_PER_S,
1418
+ this.springConfig
1419
+ ) : sampleTweenSettle(
1420
+ this.getSize(),
1421
+ target,
1422
+ this.duration,
1423
+ this.easing
1424
+ );
1425
+ if (samples.values.length >= 2 && samples.durationMs > 0) {
1426
+ await this.runWaapiSettle(samples, target);
1427
+ if (!this.isAnimating) {
1428
+ this.getRootEl()?.removeAttribute("data-animating");
1429
+ if (!this.isDragging()) this.element.style.willChange = "";
1430
+ }
1431
+ return;
1432
+ }
1433
+ }
1748
1434
  if (this.animationKind === "spring") {
1749
1435
  const spring = runSpring({
1750
1436
  from: this.getSize(),
@@ -1773,11 +1459,66 @@ var AnimationRunner = class {
1773
1459
  if (!this.isDragging()) this.element.style.willChange = "";
1774
1460
  }
1775
1461
  }
1462
+ async runWaapiSettle(samples, target) {
1463
+ const applyAux = this.applyAux;
1464
+ const getTransformFor = this.getTransformFor;
1465
+ const frames = samples.values.map((v) => ({
1466
+ transform: getTransformFor(v)
1467
+ }));
1468
+ const anim = this.element.animate(frames, {
1469
+ duration: samples.durationMs,
1470
+ easing: "linear",
1471
+ fill: "forwards"
1472
+ });
1473
+ let stopped = false;
1474
+ let auxRaf = 0;
1475
+ const start = performance.now();
1476
+ const lastIdx = samples.values.length - 1;
1477
+ const auxTick = () => {
1478
+ if (stopped) return;
1479
+ const elapsed = performance.now() - start;
1480
+ const idx = Math.min(Math.floor(elapsed / samples.stepMs), lastIdx);
1481
+ applyAux(samples.values[idx]);
1482
+ auxRaf = requestAnimationFrame(auxTick);
1483
+ };
1484
+ auxRaf = requestAnimationFrame(auxTick);
1485
+ const entry = {
1486
+ anim,
1487
+ stop: () => {
1488
+ stopped = true;
1489
+ cancelAnimationFrame(auxRaf);
1490
+ }
1491
+ };
1492
+ this.currentWaapi = entry;
1493
+ try {
1494
+ await anim.finished;
1495
+ } catch {
1496
+ }
1497
+ entry.stop();
1498
+ const wasCurrent = this.currentWaapi === entry;
1499
+ if (wasCurrent) this.currentWaapi = null;
1500
+ if (wasCurrent && anim.playState === "finished") {
1501
+ this.applySizeFn(target);
1502
+ try {
1503
+ anim.cancel();
1504
+ } catch {
1505
+ }
1506
+ }
1507
+ }
1776
1508
  destroy() {
1777
1509
  this.detachReducedMotion?.();
1778
1510
  this.detachReducedMotion = null;
1779
1511
  this.currentTween = null;
1780
1512
  this.currentSpring = null;
1513
+ if (this.currentWaapi) {
1514
+ const entry = this.currentWaapi;
1515
+ this.currentWaapi = null;
1516
+ entry.stop();
1517
+ try {
1518
+ entry.anim.cancel();
1519
+ } catch {
1520
+ }
1521
+ }
1781
1522
  }
1782
1523
  };
1783
1524
 
@@ -2635,7 +2376,7 @@ function resolveEngineOptions(opts, extras) {
2635
2376
  const initialAllowed = opts.allowed ?? opts.snapPoints.map((p) => p.id);
2636
2377
  let initialId = opts.initial ?? initialAllowed[0] ?? opts.snapPoints[0]?.id ?? "default";
2637
2378
  if (opts.persistKey) {
2638
- const restored = readPersistedId(opts.persistKey);
2379
+ const restored = readPersistedIdSafe(opts.persistKey);
2639
2380
  if (restored && initialAllowed.includes(restored)) {
2640
2381
  initialId = restored;
2641
2382
  }
@@ -2666,6 +2407,7 @@ function resolveEngineOptions(opts, extras) {
2666
2407
  },
2667
2408
  animation: {
2668
2409
  animation: opts.animation,
2410
+ settleAnimation: opts.settleAnimation,
2669
2411
  duration: opts.duration,
2670
2412
  easing: opts.easing,
2671
2413
  spring: opts.spring,
@@ -2683,6 +2425,15 @@ function resolveEngineOptions(opts, extras) {
2683
2425
  }
2684
2426
  };
2685
2427
  }
2428
+ var readPersistedIdSafe = (key) => {
2429
+ if (typeof window === "undefined") return null;
2430
+ if (typeof localStorage === "undefined") return null;
2431
+ try {
2432
+ return localStorage.getItem(key);
2433
+ } catch {
2434
+ return null;
2435
+ }
2436
+ };
2686
2437
 
2687
2438
  // src/core/primitives/teardown-stack.ts
2688
2439
  var TeardownStack = class {
@@ -2705,9 +2456,9 @@ var TeardownStack = class {
2705
2456
  }
2706
2457
  };
2707
2458
 
2708
- // src/core/BottomSheetEngine.ts
2459
+ // src/core/BottomSheetCore.ts
2709
2460
  var HAPTIC_DURATION_MS = 8;
2710
- var BottomSheetEngine = class {
2461
+ var BottomSheetCore = class {
2711
2462
  constructor(opts) {
2712
2463
  __publicField(this, "id", nextInstanceId("bs"));
2713
2464
  __publicField(this, "element");
@@ -2768,6 +2519,8 @@ var BottomSheetEngine = class {
2768
2519
  __publicField(this, "stackEffectPrimed", false);
2769
2520
  __publicField(this, "fitDeps");
2770
2521
  __publicField(this, "transformTemplate");
2522
+ __publicField(this, "featureList", []);
2523
+ __publicField(this, "featureCtx");
2771
2524
  const resolved = resolveEngineOptions(opts, {
2772
2525
  shouldApplyInertSiblings: () => {
2773
2526
  if (typeof document === "undefined") return false;
@@ -2814,7 +2567,9 @@ var BottomSheetEngine = class {
2814
2567
  getRootEl: () => this.rootEl,
2815
2568
  applySize: (size) => this.applySize(size),
2816
2569
  getSize: () => this.size,
2817
- isDragging: () => this.gesture?.isDragging ?? false
2570
+ isDragging: () => this.gesture?.isDragging ?? false,
2571
+ applyAux: (size) => this.applySize(size, true),
2572
+ getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2818
2573
  },
2819
2574
  resolved.animation
2820
2575
  );
@@ -2837,6 +2592,11 @@ var BottomSheetEngine = class {
2837
2592
  if (opts.linkedSheets) {
2838
2593
  this.linkedSheets = opts.linkedSheets;
2839
2594
  }
2595
+ const byName = /* @__PURE__ */ new Map();
2596
+ for (const feature of opts.features ?? []) {
2597
+ byName.set(feature.name, feature);
2598
+ }
2599
+ this.featureList = Array.from(byName.values());
2840
2600
  this.activeId = resolved.initialId;
2841
2601
  auditVhUsage(this.snapPointsRaw);
2842
2602
  this.auditDuplicateSnapIds(this.snapPointsRaw);
@@ -2873,6 +2633,7 @@ var BottomSheetEngine = class {
2873
2633
  scrollContainer: this.scrollContainer,
2874
2634
  getMaxAxisSize: () => this.snaps.getMaxAxisSize()
2875
2635
  });
2636
+ this.featureCtx = this.buildFeatureContext(opts.autoCollapseAfter);
2876
2637
  this.attach();
2877
2638
  this.registerInStack();
2878
2639
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
@@ -2904,46 +2665,77 @@ var BottomSheetEngine = class {
2904
2665
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2905
2666
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2906
2667
  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));
2668
+ this.runFeatureStage("post");
2669
+ this.warnOrphanFeatureOptions();
2670
+ if (this.size > 0) {
2671
+ this.handleOpen();
2918
2672
  }
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({
2673
+ }
2674
+ buildFeatureContext(autoCollapseAfter) {
2675
+ return {
2676
+ element: this.element,
2677
+ scrollContainer: this.scrollContainer,
2678
+ sheetId: this.id,
2679
+ options: {
2935
2680
  routedTo: this.routedTo,
2936
2681
  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();
2682
+ closeOnRouteChange: this.closeOnRouteChange,
2683
+ persistKey: this.persistKey,
2684
+ autoCollapseAfter
2685
+ },
2686
+ isDestroyed: () => this.destroyed,
2687
+ isDragging: () => this.gesture?.isDragging ?? false,
2688
+ isAnimating: () => this.animation.isAnimating,
2689
+ isTopSheet: () => this.isTopSheet,
2690
+ isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
2691
+ getSize: () => this.size,
2692
+ getActiveId: () => this.activeId,
2693
+ getAllowedIds: () => this.snaps.getAllowedIds().slice(),
2694
+ allowedIdsBySize: () => this.allowedIdsBySize(),
2695
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2696
+ resolveSnap: (id) => this.snaps.findById(id),
2697
+ resolveActiveSnap: () => this.snaps.findById(this.activeId),
2698
+ setSize: (size) => {
2699
+ this.size = size;
2700
+ },
2701
+ setMaxAxisSize: (size) => {
2702
+ this.snaps.setMaxAxisSize(size);
2703
+ },
2704
+ applySize: (size) => this.applySize(size),
2705
+ recomputeSnaps: () => this.recomputeSnaps(),
2706
+ newCycle: () => {
2707
+ this.newCycle();
2708
+ },
2709
+ cancelInFlight: () => {
2710
+ this.animation.cancel();
2711
+ },
2712
+ resyncAfterResize: () => this.resyncAfterResize(),
2713
+ snapTo: (id) => {
2714
+ void this.snapTo(id);
2715
+ },
2716
+ close: (reason) => this.close(reason),
2717
+ on: (event, fn) => this.on(event, fn),
2718
+ addTeardown: (fn) => this.teardowns.add(fn)
2719
+ };
2720
+ }
2721
+ runFeatureStage(stage) {
2722
+ for (const feature of this.featureList) {
2723
+ if ((feature.stage ?? "post") !== stage) continue;
2724
+ const teardown = feature.install(this.featureCtx);
2725
+ if (teardown) this.teardowns.add(teardown);
2726
+ }
2727
+ }
2728
+ warnOrphanFeatureOptions() {
2729
+ const names = new Set(this.featureList.map((f) => f.name));
2730
+ if ((this.closeOnBack || this.routedTo !== void 0 || this.closeOnRouteChange) && !names.has("route")) {
2731
+ devWarn(
2732
+ "[BottomSheet] closeOnBack/routedTo/closeOnRouteChange need routeFeature() \u2014 option ignored"
2733
+ );
2734
+ }
2735
+ if (this.persistKey && !names.has("persist")) {
2736
+ devWarn(
2737
+ "[BottomSheet] persistKey needs persistFeature() \u2014 snaps will not be saved"
2738
+ );
2947
2739
  }
2948
2740
  }
2949
2741
  get state() {
@@ -3486,18 +3278,7 @@ var BottomSheetEngine = class {
3486
3278
  }
3487
3279
  }
3488
3280
  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
- }
3281
+ this.runFeatureStage("attach");
3501
3282
  if (typeof window !== "undefined") {
3502
3283
  this.teardowns.add(installResizeObserver({
3503
3284
  element: this.element,
@@ -3537,31 +3318,6 @@ var BottomSheetEngine = class {
3537
3318
  () => document.removeEventListener("keydown", onKey)
3538
3319
  );
3539
3320
  }
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
3321
  }
3566
3322
  this.teardowns.add(installSliderKeyboard({
3567
3323
  handle: this.handle,
@@ -3638,13 +3394,13 @@ var BottomSheetEngine = class {
3638
3394
  }
3639
3395
  }
3640
3396
  }
3641
- applySize(size) {
3397
+ applySize(size, skipTransform = false) {
3642
3398
  const cap = this.snaps.getMaxAxisSize();
3643
3399
  const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3644
3400
  this.size = clamped;
3645
3401
  const offset = cap - clamped;
3646
3402
  const style = this.element.style;
3647
- style.transform = this.transformTemplate(offset);
3403
+ if (!skipTransform) style.transform = this.transformTemplate(offset);
3648
3404
  if (this.sizeWriteSentinel.shouldWrite(clamped, SIZE_WRITE_EPSILON)) {
3649
3405
  style.setProperty("--bs-size", `${clamped}px`);
3650
3406
  if (this.scrimParent) {
@@ -3722,6 +3478,514 @@ var BottomSheetEngine = class {
3722
3478
  }
3723
3479
  };
3724
3480
 
3481
+ // src/core/features/history-coordinator.ts
3482
+ var entries = [];
3483
+ var subscribers = /* @__PURE__ */ new Set();
3484
+ var suppress = 0;
3485
+ var internalDepth = 0;
3486
+ var pendingRebrands = [];
3487
+ var listenerInstalled = false;
3488
+ var patchInstalled = false;
3489
+ var origPushState = null;
3490
+ var origReplaceState = null;
3491
+ var patchedPushState = null;
3492
+ var patchedReplaceState = null;
3493
+ var hasWindow = () => typeof window !== "undefined";
3494
+ var hasHistory = () => typeof history !== "undefined";
3495
+ var brandMarker = (surface) => ({
3496
+ ...surface.markerState(),
3497
+ __bs: true
3498
+ });
3499
+ var runInternal = (fn) => {
3500
+ internalDepth += 1;
3501
+ try {
3502
+ fn();
3503
+ } finally {
3504
+ internalDepth -= 1;
3505
+ }
3506
+ };
3507
+ var notifyRouteChange = () => {
3508
+ const snapshot = Array.from(subscribers);
3509
+ for (const fn of snapshot) fn();
3510
+ };
3511
+ var ensureListener = () => {
3512
+ if (listenerInstalled || !hasWindow()) return;
3513
+ window.addEventListener("popstate", onPopState);
3514
+ listenerInstalled = true;
3515
+ };
3516
+ var maybeTeardownListener = () => {
3517
+ if (!listenerInstalled) return;
3518
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
3519
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
3520
+ listenerInstalled = false;
3521
+ };
3522
+ var installPatch = () => {
3523
+ if (patchInstalled || !hasHistory()) return;
3524
+ origPushState = history.pushState;
3525
+ origReplaceState = history.replaceState;
3526
+ patchedPushState = function(...args) {
3527
+ origPushState.apply(this, args);
3528
+ if (internalDepth === 0) notifyRouteChange();
3529
+ };
3530
+ patchedReplaceState = function(...args) {
3531
+ origReplaceState.apply(this, args);
3532
+ if (internalDepth === 0) notifyRouteChange();
3533
+ };
3534
+ history.pushState = patchedPushState;
3535
+ history.replaceState = patchedReplaceState;
3536
+ patchInstalled = true;
3537
+ };
3538
+ var removePatch = () => {
3539
+ if (!patchInstalled) return;
3540
+ if (history.pushState === patchedPushState && origPushState) {
3541
+ history.pushState = origPushState;
3542
+ }
3543
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
3544
+ history.replaceState = origReplaceState;
3545
+ }
3546
+ origPushState = null;
3547
+ origReplaceState = null;
3548
+ patchedPushState = null;
3549
+ patchedReplaceState = null;
3550
+ patchInstalled = false;
3551
+ };
3552
+ function onPopState() {
3553
+ if (suppress > 0) {
3554
+ suppress -= 1;
3555
+ const rebrand = pendingRebrands.shift();
3556
+ if (rebrand) rebrand();
3557
+ maybeTeardownListener();
3558
+ return;
3559
+ }
3560
+ if (entries.length === 0) {
3561
+ notifyRouteChange();
3562
+ maybeTeardownListener();
3563
+ return;
3564
+ }
3565
+ let target = null;
3566
+ let targetIdx = -1;
3567
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3568
+ const entry = entries[i];
3569
+ if (!entry.surface.isOpen()) continue;
3570
+ if (!target) {
3571
+ target = entry;
3572
+ targetIdx = i;
3573
+ }
3574
+ if (entry.surface.isTop()) {
3575
+ target = entry;
3576
+ targetIdx = i;
3577
+ break;
3578
+ }
3579
+ }
3580
+ if (!target) {
3581
+ const removed = entries.pop();
3582
+ if (removed) removed.live = false;
3583
+ maybeTeardownListener();
3584
+ return;
3585
+ }
3586
+ target.live = false;
3587
+ entries.splice(targetIdx, 1);
3588
+ const settled = target;
3589
+ void Promise.resolve(settled.surface.close()).then(() => {
3590
+ if (settled.surface.isOpen()) restore(settled);
3591
+ });
3592
+ maybeTeardownListener();
3593
+ }
3594
+ function restore(entry) {
3595
+ if (!hasWindow() || !hasHistory()) return;
3596
+ entry.live = true;
3597
+ runInternal(() => {
3598
+ try {
3599
+ history.pushState(brandMarker(entry.surface), "", entry.surface.url);
3600
+ } catch {
3601
+ entry.live = false;
3602
+ }
3603
+ });
3604
+ if (entry.live) {
3605
+ entries.push(entry);
3606
+ ensureListener();
3607
+ }
3608
+ }
3609
+ function pushBackMarker(surface) {
3610
+ const entry = { surface, priorUrl: void 0, live: false };
3611
+ if (!hasWindow() || !hasHistory()) {
3612
+ return entry;
3613
+ }
3614
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
3615
+ runInternal(() => {
3616
+ try {
3617
+ history.pushState(brandMarker(surface), "", surface.url);
3618
+ entry.live = true;
3619
+ } catch {
3620
+ entry.live = false;
3621
+ }
3622
+ });
3623
+ if (entry.live) {
3624
+ entries.push(entry);
3625
+ ensureListener();
3626
+ }
3627
+ return entry;
3628
+ }
3629
+ function popBackMarker(handle) {
3630
+ if (!hasWindow() || !hasHistory()) return;
3631
+ const entry = handle;
3632
+ const idx = entries.indexOf(entry);
3633
+ if (idx === -1 || !entry.live) return;
3634
+ const last = entries.length - 1;
3635
+ entry.live = false;
3636
+ entries.splice(idx, 1);
3637
+ let rebrand = null;
3638
+ if (idx !== last) {
3639
+ const rebrandSurface = entries[entries.length - 1].surface;
3640
+ const rebrandUrl = entry.priorUrl;
3641
+ rebrand = () => {
3642
+ runInternal(() => {
3643
+ history.replaceState(
3644
+ brandMarker(rebrandSurface),
3645
+ "",
3646
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
3647
+ );
3648
+ });
3649
+ };
3650
+ }
3651
+ pendingRebrands.push(rebrand);
3652
+ suppress += 1;
3653
+ runInternal(() => {
3654
+ try {
3655
+ history.back();
3656
+ } catch {
3657
+ suppress = Math.max(0, suppress - 1);
3658
+ pendingRebrands.pop();
3659
+ }
3660
+ });
3661
+ maybeTeardownListener();
3662
+ }
3663
+ function subscribeRouteChange(fn) {
3664
+ if (!hasWindow() || !hasHistory()) return () => {
3665
+ };
3666
+ subscribers.add(fn);
3667
+ if (subscribers.size === 1) installPatch();
3668
+ ensureListener();
3669
+ return () => {
3670
+ if (!subscribers.has(fn)) return;
3671
+ subscribers.delete(fn);
3672
+ if (subscribers.size === 0) removePatch();
3673
+ maybeTeardownListener();
3674
+ };
3675
+ }
3676
+
3677
+ // src/core/features/route.ts
3678
+ function installRoute(deps) {
3679
+ if (typeof window === "undefined") {
3680
+ return () => {
3681
+ };
3682
+ }
3683
+ if (!deps.routedTo && !deps.closeOnBack) {
3684
+ return () => {
3685
+ };
3686
+ }
3687
+ let handle = null;
3688
+ const onOpen = () => {
3689
+ if (handle) return;
3690
+ handle = pushBackMarker({
3691
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
3692
+ isTop: deps.isTopSheet,
3693
+ close: deps.close,
3694
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
3695
+ url: deps.routedTo
3696
+ });
3697
+ };
3698
+ const onClose = () => {
3699
+ if (!handle) return;
3700
+ popBackMarker(handle);
3701
+ handle = null;
3702
+ };
3703
+ const unsubscribeOpen = deps.on("open", onOpen);
3704
+ const unsubscribeClose = deps.on("close", onClose);
3705
+ if (deps.getSize() > 0) onOpen();
3706
+ return () => {
3707
+ unsubscribeOpen();
3708
+ unsubscribeClose();
3709
+ if (handle) {
3710
+ popBackMarker(handle);
3711
+ handle = null;
3712
+ }
3713
+ };
3714
+ }
3715
+ function installRouteChange(deps) {
3716
+ return subscribeRouteChange(() => {
3717
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
3718
+ void deps.close();
3719
+ });
3720
+ }
3721
+
3722
+ // src/core/features/persist.ts
3723
+ function installPersist(engine, key) {
3724
+ const unsubscribe = engine.on("snap", ({ id }) => {
3725
+ if (typeof window === "undefined") return;
3726
+ if (typeof localStorage === "undefined") return;
3727
+ try {
3728
+ localStorage.setItem(key, id);
3729
+ } catch {
3730
+ }
3731
+ });
3732
+ return unsubscribe;
3733
+ }
3734
+
3735
+ // src/core/features/auto-collapse.ts
3736
+ function installAutoCollapse(deps) {
3737
+ const { ms } = deps;
3738
+ if (ms === void 0 || ms <= 0) {
3739
+ return () => {
3740
+ };
3741
+ }
3742
+ let timer = null;
3743
+ const clearTimer = () => {
3744
+ if (timer !== null) {
3745
+ clearTimeout(timer);
3746
+ timer = null;
3747
+ }
3748
+ };
3749
+ const fire = () => {
3750
+ if (deps.isDestroyed()) return;
3751
+ if (deps.isDragging()) return;
3752
+ const active = deps.resolveSnap(deps.getActiveId());
3753
+ if (!active || active.size === 0) return;
3754
+ let target = null;
3755
+ for (const id of deps.getAllowedIds()) {
3756
+ const snap = deps.resolveSnap(id);
3757
+ if (!snap || snap.size <= 0) continue;
3758
+ if (!target || snap.size < target.size) target = snap;
3759
+ }
3760
+ if (!target) return;
3761
+ if (target.id === active.id || target.size >= active.size) return;
3762
+ deps.snapTo(target.id);
3763
+ };
3764
+ const reset = () => {
3765
+ clearTimer();
3766
+ if (deps.isDestroyed()) return;
3767
+ timer = setTimeout(() => {
3768
+ timer = null;
3769
+ fire();
3770
+ }, ms);
3771
+ };
3772
+ const unsubscribe = deps.on("snap", () => reset());
3773
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
3774
+ return () => {
3775
+ clearTimer();
3776
+ unsubscribe();
3777
+ };
3778
+ }
3779
+
3780
+ // src/core/features/content-swipe.ts
3781
+ var CONTENT_PULL_THRESHOLD = 60;
3782
+ function installContentSwipe(deps) {
3783
+ const { container } = deps;
3784
+ let startY = 0;
3785
+ let startScrollTop = 0;
3786
+ let moved = false;
3787
+ const onTouchStart = (e) => {
3788
+ const t = e.touches[0];
3789
+ if (!t) return;
3790
+ startY = t.clientY;
3791
+ startScrollTop = container.scrollTop;
3792
+ moved = false;
3793
+ };
3794
+ const onTouchMove = (e) => {
3795
+ const t = e.touches[0];
3796
+ if (!t) return;
3797
+ if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3798
+ };
3799
+ const onTouchEnd = (e) => {
3800
+ if (!moved) return;
3801
+ if (deps.isDragging() || deps.isAnimating()) return;
3802
+ if (container.scrollTop !== startScrollTop) return;
3803
+ const t = e.changedTouches[0];
3804
+ if (!t) return;
3805
+ const deltaY = t.clientY - startY;
3806
+ const atTop = container.scrollTop <= 0;
3807
+ const allowed = deps.getAllowedIds();
3808
+ const idx = allowed.indexOf(deps.getActiveId());
3809
+ if (idx === -1) return;
3810
+ if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3811
+ deps.snapTo(allowed[idx - 1]);
3812
+ } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3813
+ deps.snapTo(allowed[idx + 1]);
3814
+ }
3815
+ };
3816
+ container.addEventListener("touchstart", onTouchStart, { passive: true });
3817
+ container.addEventListener("touchmove", onTouchMove, { passive: true });
3818
+ container.addEventListener("touchend", onTouchEnd, { passive: true });
3819
+ return () => {
3820
+ container.removeEventListener("touchstart", onTouchStart);
3821
+ container.removeEventListener("touchmove", onTouchMove);
3822
+ container.removeEventListener("touchend", onTouchEnd);
3823
+ };
3824
+ }
3825
+
3826
+ // src/core/features/visual-viewport.ts
3827
+ var SAFETY_PAD = 8;
3828
+ function installVisualViewport(deps) {
3829
+ if (typeof window === "undefined" || !window.visualViewport) {
3830
+ return () => {
3831
+ };
3832
+ }
3833
+ const vv = window.visualViewport;
3834
+ let rafId = null;
3835
+ const onResize = () => {
3836
+ if (rafId !== null) return;
3837
+ rafId = requestAnimationFrame(() => {
3838
+ rafId = null;
3839
+ if (deps.isDestroyed()) return;
3840
+ deps.recomputeSnaps();
3841
+ const isVertical = deps.isVerticalAxis();
3842
+ const visible = isVertical ? vv.height : vv.width;
3843
+ const windowSize = isVertical ? window.innerHeight : window.innerWidth;
3844
+ const clamp = Math.max(0, Math.min(windowSize, visible) - SAFETY_PAD);
3845
+ if (clamp < deps.getMaxAxisSize()) {
3846
+ deps.setMaxAxisSize(clamp);
3847
+ }
3848
+ const wasAnimating = deps.isAnimating?.() ?? false;
3849
+ deps.cancelInFlight();
3850
+ deps.newCycle();
3851
+ const max = deps.getMaxAxisSize();
3852
+ deps.setSize(Math.min(deps.getSize(), max));
3853
+ const current = deps.resolveActiveSnap();
3854
+ if (current && !deps.isDragging()) {
3855
+ deps.applySize(Math.min(current.size, max));
3856
+ if (wasAnimating) deps.resyncAfterCancel?.();
3857
+ } else {
3858
+ deps.applySize(deps.getSize());
3859
+ }
3860
+ });
3861
+ };
3862
+ vv.addEventListener("resize", onResize);
3863
+ return () => {
3864
+ vv.removeEventListener("resize", onResize);
3865
+ if (rafId !== null) cancelAnimationFrame(rafId);
3866
+ };
3867
+ }
3868
+
3869
+ // src/core/features/engine-features.ts
3870
+ function routeFeature() {
3871
+ return {
3872
+ name: "route",
3873
+ install: (ctx) => {
3874
+ const teardowns = new TeardownStack();
3875
+ if (ctx.options.closeOnRouteChange) {
3876
+ teardowns.add(
3877
+ installRouteChange({
3878
+ isDestroyed: ctx.isDestroyed,
3879
+ getSize: ctx.getSize,
3880
+ close: () => ctx.close()
3881
+ })
3882
+ );
3883
+ }
3884
+ teardowns.add(
3885
+ installRoute({
3886
+ routedTo: ctx.options.routedTo,
3887
+ closeOnBack: ctx.options.closeOnBack,
3888
+ isTopSheet: ctx.isTopSheet,
3889
+ getSize: ctx.getSize,
3890
+ isDestroyed: ctx.isDestroyed,
3891
+ close: () => ctx.close("back"),
3892
+ on: ctx.on,
3893
+ sheetId: ctx.sheetId
3894
+ })
3895
+ );
3896
+ return () => teardowns.drain();
3897
+ }
3898
+ };
3899
+ }
3900
+ function persistFeature() {
3901
+ return {
3902
+ name: "persist",
3903
+ install: (ctx) => {
3904
+ const key = ctx.options.persistKey;
3905
+ if (!key) return;
3906
+ return installPersist({ on: ctx.on }, key);
3907
+ }
3908
+ };
3909
+ }
3910
+ function autoCollapseFeature() {
3911
+ return {
3912
+ name: "auto-collapse",
3913
+ install: (ctx) => installAutoCollapse({
3914
+ ms: ctx.options.autoCollapseAfter,
3915
+ isDestroyed: ctx.isDestroyed,
3916
+ isDragging: ctx.isDragging,
3917
+ getAllowedIds: ctx.getAllowedIds,
3918
+ getActiveId: ctx.getActiveId,
3919
+ resolveSnap: ctx.resolveSnap,
3920
+ snapTo: ctx.snapTo,
3921
+ on: ctx.on
3922
+ })
3923
+ };
3924
+ }
3925
+ function contentSwipeFeature() {
3926
+ return {
3927
+ name: "content-swipe",
3928
+ stage: "attach",
3929
+ install: (ctx) => {
3930
+ if (!ctx.scrollContainer) return;
3931
+ return installContentSwipe({
3932
+ container: ctx.scrollContainer,
3933
+ isDragging: ctx.isDragging,
3934
+ isAnimating: ctx.isAnimating,
3935
+ getAllowedIds: ctx.allowedIdsBySize,
3936
+ getActiveId: ctx.getActiveId,
3937
+ snapTo: ctx.snapTo
3938
+ });
3939
+ }
3940
+ };
3941
+ }
3942
+ function visualViewportFeature() {
3943
+ return {
3944
+ name: "visual-viewport",
3945
+ install: (ctx) => {
3946
+ if (typeof window === "undefined") return;
3947
+ return installVisualViewport({
3948
+ element: ctx.element,
3949
+ isVerticalAxis: ctx.isVerticalAxis,
3950
+ isDestroyed: ctx.isDestroyed,
3951
+ isDragging: ctx.isDragging,
3952
+ recomputeSnaps: ctx.recomputeSnaps,
3953
+ resolveActiveSnap: ctx.resolveActiveSnap,
3954
+ getMaxAxisSize: ctx.getMaxAxisSize,
3955
+ getSize: ctx.getSize,
3956
+ setMaxAxisSize: ctx.setMaxAxisSize,
3957
+ setSize: ctx.setSize,
3958
+ applySize: ctx.applySize,
3959
+ cancelInFlight: ctx.cancelInFlight,
3960
+ newCycle: ctx.newCycle,
3961
+ isAnimating: ctx.isAnimating,
3962
+ resyncAfterCancel: ctx.resyncAfterResize
3963
+ });
3964
+ }
3965
+ };
3966
+ }
3967
+
3968
+ // src/core/default-features.ts
3969
+ function defaultEngineFeatures() {
3970
+ return [
3971
+ contentSwipeFeature(),
3972
+ visualViewportFeature(),
3973
+ persistFeature(),
3974
+ autoCollapseFeature(),
3975
+ routeFeature()
3976
+ ];
3977
+ }
3978
+
3979
+ // src/core/BottomSheetEngine.ts
3980
+ var BottomSheetEngine = class extends BottomSheetCore {
3981
+ constructor(opts) {
3982
+ super({
3983
+ ...opts,
3984
+ features: [...defaultEngineFeatures(), ...opts.features ?? []]
3985
+ });
3986
+ }
3987
+ };
3988
+
3725
3989
  // src/core/lifecycle/sheetManager.ts
3726
3990
  var createSheetManager = (initial = {}) => {
3727
3991
  const registry = /* @__PURE__ */ new Map();
@@ -4472,6 +4736,7 @@ var OverlayEngine = class {
4472
4736
  };
4473
4737
  var createOverlay = (opts) => new OverlayEngine(opts);
4474
4738
 
4739
+ exports.BottomSheetCore = BottomSheetCore;
4475
4740
  exports.BottomSheetEngine = BottomSheetEngine;
4476
4741
  exports.DEFAULT_SPRING = DEFAULT_SPRING;
4477
4742
  exports.OVERLAY_PRESETS = OVERLAY_PRESETS;
@@ -4481,8 +4746,11 @@ exports.SCRIM_PRESETS = SCRIM_PRESETS;
4481
4746
  exports.allowedRange = allowedRange;
4482
4747
  exports.applyOverlayPosition = applyOverlayPosition;
4483
4748
  exports.attachGestures = attachGestures;
4749
+ exports.autoCollapseFeature = autoCollapseFeature;
4750
+ exports.contentSwipeFeature = contentSwipeFeature;
4484
4751
  exports.createOverlay = createOverlay;
4485
4752
  exports.createSheetManager = createSheetManager;
4753
+ exports.defaultEngineFeatures = defaultEngineFeatures;
4486
4754
  exports.easeOutBack = easeOutBack;
4487
4755
  exports.easeOutCubic = easeOutCubic;
4488
4756
  exports.findById = findById;
@@ -4490,13 +4758,16 @@ exports.findNearest = findNearest;
4490
4758
  exports.installFocusTrap = installFocusTrap;
4491
4759
  exports.installGestures = installGestures;
4492
4760
  exports.lockBodyScroll = lockBodyScroll;
4761
+ exports.persistFeature = persistFeature;
4493
4762
  exports.prefersReducedMotion = prefersReducedMotion;
4494
4763
  exports.resolveSheetAnchoredStyle = resolveSheetAnchoredStyle;
4495
4764
  exports.resolveSnap = resolveSnap;
4496
4765
  exports.resolveSnapList = resolveSnapList;
4766
+ exports.routeFeature = routeFeature;
4497
4767
  exports.runAnchorTransition = runAnchorTransition;
4498
4768
  exports.runSpring = runSpring;
4499
4769
  exports.sheetStack = sheetStack;
4500
4770
  exports.tween = tween;
4771
+ exports.visualViewportFeature = visualViewportFeature;
4501
4772
  //# sourceMappingURL=index.cjs.map
4502
4773
  //# sourceMappingURL=index.cjs.map