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