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