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