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