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