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