@surdeddd/bottom-sheet 0.9.1 → 0.11.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 (50) hide show
  1. package/README.md +126 -83
  2. package/dist/{BottomSheetEngine-CBRcBwV-.d.ts → BottomSheetEngine-DRjCU73e.d.ts} +6 -5
  3. package/dist/{BottomSheetEngine-BcyruNP1.d.cts → BottomSheetEngine-DzsIwD28.d.cts} +6 -5
  4. package/dist/element.cjs +388 -222
  5. package/dist/element.d.cts +1 -1
  6. package/dist/element.d.ts +1 -1
  7. package/dist/element.iife.global.js +2 -2
  8. package/dist/element.js +388 -222
  9. package/dist/index.cjs +389 -266
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +389 -266
  13. package/dist/integrations/formik.d.cts +1 -1
  14. package/dist/integrations/formik.d.ts +1 -1
  15. package/dist/integrations/react-hook-form.d.cts +1 -1
  16. package/dist/integrations/react-hook-form.d.ts +1 -1
  17. package/dist/overlay.cjs +191 -58
  18. package/dist/overlay.d.cts +4 -3
  19. package/dist/overlay.d.ts +4 -3
  20. package/dist/overlay.js +191 -58
  21. package/dist/preact.d.cts +2 -1
  22. package/dist/preact.d.ts +2 -1
  23. package/dist/qwik.cjs +396 -211
  24. package/dist/qwik.d.cts +11 -2
  25. package/dist/qwik.d.ts +11 -2
  26. package/dist/qwik.js +396 -211
  27. package/dist/react.cjs +500 -332
  28. package/dist/react.d.cts +26 -4
  29. package/dist/react.d.ts +26 -4
  30. package/dist/react.js +500 -332
  31. package/dist/solid.cjs +362 -210
  32. package/dist/solid.d.cts +14 -4
  33. package/dist/solid.d.ts +14 -4
  34. package/dist/solid.js +362 -210
  35. package/dist/solid.source.jsx +362 -210
  36. package/dist/styles.css +17 -1
  37. package/dist/svelte-core.cjs +345 -208
  38. package/dist/svelte-core.js +345 -208
  39. package/dist/svelte-src/BottomSheet.svelte +6 -0
  40. package/dist/svelte.cjs +359 -220
  41. package/dist/svelte.d.cts +2 -0
  42. package/dist/svelte.d.ts +2 -0
  43. package/dist/svelte.js +359 -220
  44. package/dist/teleport-7uYNiEAT.d.cts +3 -0
  45. package/dist/teleport-7uYNiEAT.d.ts +3 -0
  46. package/dist/vue.cjs +353 -209
  47. package/dist/vue.d.cts +3 -1
  48. package/dist/vue.d.ts +3 -1
  49. package/dist/vue.js +353 -209
  50. package/package.json +11 -9
package/dist/index.cjs CHANGED
@@ -189,11 +189,18 @@ var resolveSnap = (point, mode, measureFit) => {
189
189
  return Math.max(0, el.getBoundingClientRect().width);
190
190
  };
191
191
 
192
+ // src/core/primitives/devWarn.ts
193
+ function devWarn(...args) {
194
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
195
+ console.warn(...args);
196
+ }
197
+ }
198
+
192
199
  // src/core/primitives/snap-points.ts
193
200
  var resolveSnapList = (points, mode, measureFit) => points.map((p) => {
194
201
  const raw = resolveSnap(p.size, mode, measureFit);
195
202
  if (!Number.isFinite(raw) || raw < 0) {
196
- console.warn(
203
+ devWarn(
197
204
  `[BottomSheet] snap "${p.id}" resolved to invalid size (${String(p.size)} \u2192 ${raw}); clamped to 0.`
198
205
  );
199
206
  return { id: p.id, size: 0 };
@@ -205,7 +212,7 @@ var auditVhUsage = (points) => {
205
212
  for (const p of points) {
206
213
  if (typeof p.size !== "string") continue;
207
214
  if (!VH_NOT_DVH.test(p.size)) continue;
208
- console.warn(
215
+ devWarn(
209
216
  `[BottomSheet] snap "${p.id}" uses "vh" \u2014 prefer "dvh" on mobile (iOS Safari URL bar makes vh unstable). See https://web.dev/blog/viewport-units`
210
217
  );
211
218
  }
@@ -259,6 +266,26 @@ var findDragSettleTarget = (input) => {
259
266
  );
260
267
  };
261
268
 
269
+ // src/core/primitives/cancelable-emit.ts
270
+ function emitCancelable(emit, base, warnLabel) {
271
+ let cancelled = false;
272
+ let frozen = false;
273
+ emit({
274
+ ...base,
275
+ cancel: () => {
276
+ if (frozen) {
277
+ devWarn(
278
+ `[BottomSheet] ${warnLabel}.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener.`
279
+ );
280
+ return;
281
+ }
282
+ cancelled = true;
283
+ }
284
+ });
285
+ frozen = true;
286
+ return cancelled;
287
+ }
288
+
262
289
  // src/core/primitives/snap-resolver.ts
263
290
  var SnapResolver = class {
264
291
  constructor(raw, allowed, mode, measureFit, onMaxAxisSizeChange) {
@@ -403,7 +430,9 @@ function notifyLinkedSheets(sheets, self) {
403
430
  try {
404
431
  if (linked.state.size === 0) continue;
405
432
  const ids = linked.getAllowedIds();
406
- const target = ids.find((id) => id !== "closed");
433
+ const resolved = linked.getResolvedSnaps();
434
+ const sizeOf = (id) => resolved.find((s) => s.id === id)?.size ?? 0;
435
+ const target = ids.find((id) => sizeOf(id) > 0);
407
436
  if (target && target !== linked.state.activeId) {
408
437
  void linked.snapTo(target);
409
438
  }
@@ -415,46 +444,198 @@ function notifyLinkedSheets(sheets, self) {
415
444
  }
416
445
  }
417
446
 
418
- // src/core/features/route.ts
419
- var closers = [];
420
- var handlerInstalled = false;
447
+ // src/core/features/history-coordinator.ts
448
+ var entries = [];
449
+ var subscribers = /* @__PURE__ */ new Set();
421
450
  var suppress = 0;
422
- var ensureHandler = () => {
423
- if (handlerInstalled || typeof window === "undefined") return;
424
- handlerInstalled = true;
425
- window.addEventListener("popstate", () => {
426
- if (suppress > 0) {
427
- suppress -= 1;
428
- return;
451
+ var internalDepth = 0;
452
+ var pendingRebrand = null;
453
+ var listenerInstalled = false;
454
+ var patchInstalled = false;
455
+ var origPushState = null;
456
+ var origReplaceState = null;
457
+ var patchedPushState = null;
458
+ var patchedReplaceState = null;
459
+ var hasWindow = () => typeof window !== "undefined";
460
+ var hasHistory = () => typeof history !== "undefined";
461
+ var runInternal = (fn) => {
462
+ internalDepth += 1;
463
+ try {
464
+ fn();
465
+ } finally {
466
+ internalDepth -= 1;
467
+ }
468
+ };
469
+ var notifyRouteChange = () => {
470
+ const snapshot = Array.from(subscribers);
471
+ for (const fn of snapshot) fn();
472
+ };
473
+ var ensureListener = () => {
474
+ if (listenerInstalled || !hasWindow()) return;
475
+ window.addEventListener("popstate", onPopState);
476
+ listenerInstalled = true;
477
+ };
478
+ var maybeTeardownListener = () => {
479
+ if (!listenerInstalled) return;
480
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
481
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
482
+ listenerInstalled = false;
483
+ };
484
+ var installPatch = () => {
485
+ if (patchInstalled || !hasHistory()) return;
486
+ origPushState = history.pushState;
487
+ origReplaceState = history.replaceState;
488
+ patchedPushState = function(...args) {
489
+ origPushState.apply(this, args);
490
+ if (internalDepth === 0) notifyRouteChange();
491
+ };
492
+ patchedReplaceState = function(...args) {
493
+ origReplaceState.apply(this, args);
494
+ if (internalDepth === 0) notifyRouteChange();
495
+ };
496
+ history.pushState = patchedPushState;
497
+ history.replaceState = patchedReplaceState;
498
+ patchInstalled = true;
499
+ };
500
+ var removePatch = () => {
501
+ if (!patchInstalled) return;
502
+ if (history.pushState === patchedPushState && origPushState) {
503
+ history.pushState = origPushState;
504
+ }
505
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
506
+ history.replaceState = origReplaceState;
507
+ }
508
+ origPushState = null;
509
+ origReplaceState = null;
510
+ patchedPushState = null;
511
+ patchedReplaceState = null;
512
+ patchInstalled = false;
513
+ };
514
+ function onPopState() {
515
+ if (suppress > 0) {
516
+ suppress -= 1;
517
+ const rebrand = pendingRebrand;
518
+ pendingRebrand = null;
519
+ if (rebrand) rebrand();
520
+ maybeTeardownListener();
521
+ return;
522
+ }
523
+ if (entries.length === 0) {
524
+ notifyRouteChange();
525
+ maybeTeardownListener();
526
+ return;
527
+ }
528
+ let target = null;
529
+ let targetIdx = -1;
530
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
531
+ const entry = entries[i];
532
+ if (!entry.surface.isOpen()) continue;
533
+ if (!target) {
534
+ target = entry;
535
+ targetIdx = i;
429
536
  }
430
- let target = null;
431
- for (let i = closers.length - 1; i >= 0; i -= 1) {
432
- const c = closers[i];
433
- if (!c.isOpen()) continue;
434
- if (!target) target = c;
435
- if (c.isTop()) {
436
- target = c;
437
- break;
438
- }
537
+ if (entry.surface.isTop()) {
538
+ target = entry;
539
+ targetIdx = i;
540
+ break;
439
541
  }
440
- if (!target) return;
441
- target.consumed = true;
442
- const idx = closers.indexOf(target);
443
- if (idx !== -1) closers.splice(idx, 1);
444
- const settled = target;
445
- void Promise.resolve(settled.close()).then(() => {
446
- if (settled.isOpen()) settled.restore();
447
- });
542
+ }
543
+ if (!target) {
544
+ const removed = entries.pop();
545
+ if (removed) removed.live = false;
546
+ maybeTeardownListener();
547
+ return;
548
+ }
549
+ target.live = false;
550
+ entries.splice(targetIdx, 1);
551
+ const settled = target;
552
+ void Promise.resolve(settled.surface.close()).then(() => {
553
+ if (settled.surface.isOpen()) restore(settled);
448
554
  });
449
- };
450
- var programmaticBack = () => {
451
- suppress += 1;
452
- try {
453
- history.back();
454
- } catch {
455
- suppress = Math.max(0, suppress - 1);
555
+ maybeTeardownListener();
556
+ }
557
+ function restore(entry) {
558
+ if (!hasWindow() || !hasHistory()) return;
559
+ entry.live = true;
560
+ runInternal(() => {
561
+ try {
562
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
563
+ } catch {
564
+ entry.live = false;
565
+ }
566
+ });
567
+ if (entry.live) {
568
+ entries.push(entry);
569
+ ensureListener();
456
570
  }
457
- };
571
+ }
572
+ function pushBackMarker(surface) {
573
+ const entry = { surface, priorUrl: void 0, live: false };
574
+ if (!hasWindow() || !hasHistory()) {
575
+ return entry;
576
+ }
577
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
578
+ runInternal(() => {
579
+ try {
580
+ history.pushState(surface.markerState(), "", surface.url);
581
+ entry.live = true;
582
+ } catch {
583
+ entry.live = false;
584
+ }
585
+ });
586
+ if (entry.live) {
587
+ entries.push(entry);
588
+ ensureListener();
589
+ }
590
+ return entry;
591
+ }
592
+ function popBackMarker(handle) {
593
+ if (!hasWindow() || !hasHistory()) return;
594
+ const entry = handle;
595
+ const idx = entries.indexOf(entry);
596
+ if (idx === -1 || !entry.live) return;
597
+ const last = entries.length - 1;
598
+ entry.live = false;
599
+ entries.splice(idx, 1);
600
+ if (idx !== last) {
601
+ const rebrandSurface = entries[entries.length - 1].surface;
602
+ const rebrandUrl = entry.priorUrl;
603
+ pendingRebrand = () => {
604
+ runInternal(() => {
605
+ history.replaceState(
606
+ rebrandSurface.markerState(),
607
+ "",
608
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
609
+ );
610
+ });
611
+ };
612
+ }
613
+ suppress += 1;
614
+ runInternal(() => {
615
+ try {
616
+ history.back();
617
+ } catch {
618
+ suppress = Math.max(0, suppress - 1);
619
+ pendingRebrand = null;
620
+ }
621
+ });
622
+ maybeTeardownListener();
623
+ }
624
+ function subscribeRouteChange(fn) {
625
+ if (!hasWindow() || !hasHistory()) return () => {
626
+ };
627
+ subscribers.add(fn);
628
+ if (subscribers.size === 1) installPatch();
629
+ ensureListener();
630
+ return () => {
631
+ if (!subscribers.has(fn)) return;
632
+ subscribers.delete(fn);
633
+ if (subscribers.size === 0) removePatch();
634
+ maybeTeardownListener();
635
+ };
636
+ }
637
+
638
+ // src/core/features/route.ts
458
639
  function installRoute(deps) {
459
640
  if (typeof window === "undefined") {
460
641
  return () => {
@@ -464,92 +645,39 @@ function installRoute(deps) {
464
645
  return () => {
465
646
  };
466
647
  }
467
- let pushed = false;
468
- let closer = null;
469
- const teardownEntry = () => {
470
- if (closer) {
471
- const idx = closers.indexOf(closer);
472
- if (idx !== -1) closers.splice(idx, 1);
473
- }
474
- const wasConsumed = closer?.consumed ?? false;
475
- closer = null;
476
- if (pushed && !wasConsumed) {
477
- pushed = false;
478
- programmaticBack();
479
- } else {
480
- pushed = false;
481
- }
482
- };
483
- const pushEntry = () => {
484
- try {
485
- if (deps.routedTo !== void 0) {
486
- history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
487
- } else {
488
- history.pushState({ __bsSheet: deps.sheetId }, "");
489
- }
490
- pushed = true;
491
- } catch {
492
- pushed = false;
493
- }
494
- };
648
+ let handle = null;
495
649
  const onOpen = () => {
496
- if (closer) return;
497
- const self = {
650
+ if (handle) return;
651
+ handle = pushBackMarker({
498
652
  isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
499
- isTop: () => deps.isTopSheet(),
500
- close: () => deps.close(),
501
- restore: () => {
502
- if (deps.isDestroyed() || closer !== self) return;
503
- self.consumed = false;
504
- if (!closers.includes(self)) closers.push(self);
505
- pushEntry();
506
- },
507
- consumed: false
508
- };
509
- closer = self;
510
- ensureHandler();
511
- closers.push(self);
512
- pushEntry();
653
+ isTop: deps.isTopSheet,
654
+ close: deps.close,
655
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
656
+ url: deps.routedTo
657
+ });
658
+ };
659
+ const onClose = () => {
660
+ if (!handle) return;
661
+ popBackMarker(handle);
662
+ handle = null;
513
663
  };
514
664
  const unsubscribeOpen = deps.on("open", onOpen);
515
- const unsubscribeClose = deps.on("close", teardownEntry);
665
+ const unsubscribeClose = deps.on("close", onClose);
516
666
  if (deps.getSize() > 0) onOpen();
517
667
  return () => {
518
668
  unsubscribeOpen();
519
669
  unsubscribeClose();
520
- teardownEntry();
670
+ if (handle) {
671
+ popBackMarker(handle);
672
+ handle = null;
673
+ }
521
674
  };
522
675
  }
523
676
  function installRouteChange(deps) {
524
- if (typeof window === "undefined" || typeof history === "undefined") {
525
- return () => {
526
- };
527
- }
528
- const onChange = () => {
677
+ return subscribeRouteChange(() => {
529
678
  if (deps.isDestroyed() || deps.getSize() <= 0) return;
530
679
  void deps.close();
531
- };
532
- const wrap = (key) => {
533
- const original = history[key];
534
- const patched = function(...args) {
535
- original.apply(this, args);
536
- onChange();
537
- };
538
- history[key] = patched;
539
- return () => {
540
- if (history[key] === patched) {
541
- history[key] = original;
542
- }
543
- };
544
- };
545
- window.addEventListener("popstate", onChange);
546
- const restorePush = wrap("pushState");
547
- const restoreReplace = wrap("replaceState");
548
- return () => {
549
- window.removeEventListener("popstate", onChange);
550
- restorePush();
551
- restoreReplace();
552
- };
680
+ });
553
681
  }
554
682
 
555
683
  // src/core/features/content-swipe.ts
@@ -620,11 +748,15 @@ function installVisualViewport(deps) {
620
748
  if (clamp < deps.getMaxAxisSize()) {
621
749
  deps.setMaxAxisSize(clamp);
622
750
  }
751
+ const wasAnimating = deps.isAnimating?.() ?? false;
752
+ deps.cancelInFlight();
753
+ deps.newCycle();
623
754
  const max = deps.getMaxAxisSize();
624
755
  deps.setSize(Math.min(deps.getSize(), max));
625
756
  const current = deps.resolveActiveSnap();
626
757
  if (current && !deps.isDragging()) {
627
758
  deps.applySize(Math.min(current.size, max));
759
+ if (wasAnimating) deps.resyncAfterCancel?.();
628
760
  } else {
629
761
  deps.applySize(deps.getSize());
630
762
  }
@@ -765,6 +897,54 @@ function installFitObserver(deps) {
765
897
  };
766
898
  }
767
899
 
900
+ // src/core/features/max-height-controller.ts
901
+ function createMaxHeightController(deps) {
902
+ let maxHeightCap;
903
+ let maxHeightRaw;
904
+ const resolveCap = () => {
905
+ const raw = maxHeightRaw;
906
+ if (raw === void 0) {
907
+ maxHeightCap = void 0;
908
+ return;
909
+ }
910
+ if (typeof raw === "number") {
911
+ maxHeightCap = raw;
912
+ return;
913
+ }
914
+ if (typeof window === "undefined") return;
915
+ const measured = resolveSnap(raw, deps.mode);
916
+ maxHeightCap = measured > 0 ? measured : void 0;
917
+ };
918
+ const clampTo = () => {
919
+ if (maxHeightCap === void 0) return;
920
+ if (deps.getMaxAxisSize() > maxHeightCap) {
921
+ deps.setMaxAxisSize(maxHeightCap);
922
+ }
923
+ };
924
+ return {
925
+ setRadius(r) {
926
+ deps.element.style.setProperty(
927
+ "--bs-radius",
928
+ typeof r === "number" ? `${r}px` : r
929
+ );
930
+ },
931
+ setMaxHeight(h) {
932
+ const value = typeof h === "number" ? `${h}px` : h;
933
+ const axis = layoutAxis(deps.mode);
934
+ deps.element.style.setProperty(
935
+ axis === "height" ? "max-height" : "max-width",
936
+ value
937
+ );
938
+ maxHeightRaw = h;
939
+ resolveCap();
940
+ deps.recompute();
941
+ },
942
+ resolveCap,
943
+ clampTo,
944
+ getCap: () => maxHeightCap
945
+ };
946
+ }
947
+
768
948
  // src/core/features/fit-measurement.ts
769
949
  var measureSheetNatural = (element, content, vertical) => {
770
950
  if (!content) {
@@ -896,13 +1076,6 @@ var WriteSentinel = class {
896
1076
  }
897
1077
  };
898
1078
 
899
- // src/core/primitives/devWarn.ts
900
- function devWarn(...args) {
901
- if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
902
- console.warn(...args);
903
- }
904
- }
905
-
906
1079
  // src/core/primitives/overlay-position.ts
907
1080
  function resolveSheetAnchoredStyle(mode, position, inset) {
908
1081
  const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
@@ -2358,7 +2531,7 @@ var matchesRange = (def, progress) => {
2358
2531
  };
2359
2532
  function installScrimStages(deps, opts) {
2360
2533
  const doc = deps.host.ownerDocument;
2361
- const entries = opts.stages.map((def) => {
2534
+ const entries2 = opts.stages.map((def) => {
2362
2535
  const wrapper = doc.createElement("div");
2363
2536
  wrapper.className = "bs-scrim-stage";
2364
2537
  const ws = wrapper.style;
@@ -2379,9 +2552,9 @@ function installScrimStages(deps, opts) {
2379
2552
  const resolveActive = () => {
2380
2553
  const state = deps.getState();
2381
2554
  if (state.size === 0) {
2382
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2555
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? null;
2383
2556
  }
2384
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2557
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? entries2.find((e) => matchesRange(e.def, state.progress)) ?? null;
2385
2558
  };
2386
2559
  const hide = (entry, animate) => {
2387
2560
  entry.inFlight?.cancel();
@@ -2430,13 +2603,13 @@ function installScrimStages(deps, opts) {
2430
2603
  deps.on("open", () => update(true)),
2431
2604
  deps.on("close", () => update(true))
2432
2605
  ];
2433
- if (entries.some((e) => e.def.forRange)) {
2606
+ if (entries2.some((e) => e.def.forRange)) {
2434
2607
  offs.push(deps.on("progress", () => update(true)));
2435
2608
  }
2436
2609
  update(false);
2437
2610
  return () => {
2438
2611
  offs.forEach((off) => off());
2439
- for (const entry of entries) {
2612
+ for (const entry of entries2) {
2440
2613
  entry.inFlight?.cancel();
2441
2614
  entry.inFlight = null;
2442
2615
  if (entry.wrapper.parentNode) {
@@ -2549,8 +2722,7 @@ var BottomSheetEngine = class {
2549
2722
  __publicField(this, "disableCloseFlag");
2550
2723
  __publicField(this, "disableDragFlag");
2551
2724
  __publicField(this, "closeOnRouteChange");
2552
- __publicField(this, "maxHeightCap");
2553
- __publicField(this, "maxHeightRaw");
2725
+ __publicField(this, "maxHeight");
2554
2726
  __publicField(this, "snapPointsRaw");
2555
2727
  __publicField(this, "snaps");
2556
2728
  __publicField(this, "activeId");
@@ -2661,11 +2833,20 @@ var BottomSheetEngine = class {
2661
2833
  auditVhUsage(this.snapPointsRaw);
2662
2834
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2663
2835
  this.rootEl = this.element.closest(".bs-root");
2836
+ this.maxHeight = createMaxHeightController({
2837
+ element: this.element,
2838
+ mode: this.mode,
2839
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2840
+ setMaxAxisSize: (size) => {
2841
+ this.snaps.setMaxAxisSize(size);
2842
+ },
2843
+ recompute: () => this.recompute()
2844
+ });
2664
2845
  this.fitDeps = {
2665
2846
  element: this.element,
2666
2847
  scrollContainer: this.scrollContainer,
2667
2848
  mode: this.mode,
2668
- getMaxHeightCap: () => this.maxHeightCap
2849
+ getMaxHeightCap: () => this.maxHeight.getCap()
2669
2850
  };
2670
2851
  this.snaps = new SnapResolver(
2671
2852
  this.snapPointsRaw,
@@ -2687,8 +2868,8 @@ var BottomSheetEngine = class {
2687
2868
  this.attach();
2688
2869
  this.registerInStack();
2689
2870
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
2690
- const io = new IntersectionObserver((entries) => {
2691
- for (const entry of entries) {
2871
+ const io = new IntersectionObserver((entries2) => {
2872
+ for (const entry of entries2) {
2692
2873
  if (!entry.isIntersecting) continue;
2693
2874
  if (this.destroyed) return;
2694
2875
  this.snaps.recompute();
@@ -2776,7 +2957,7 @@ var BottomSheetEngine = class {
2776
2957
  }
2777
2958
  use(plugin) {
2778
2959
  if (this.destroyed) {
2779
- console.warn(
2960
+ devWarn(
2780
2961
  `[BottomSheet] use("${plugin.name}") called on destroyed engine \u2014 ignored.`
2781
2962
  );
2782
2963
  return this;
@@ -2818,6 +2999,9 @@ var BottomSheetEngine = class {
2818
2999
  getAllowedIds() {
2819
3000
  return this.snaps.getAllowedIds().slice();
2820
3001
  }
3002
+ getResolvedSnaps() {
3003
+ return this.snaps.getResolvedSnaps();
3004
+ }
2821
3005
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2822
3006
  if (this.destroyed) return;
2823
3007
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2826,11 +3010,11 @@ var BottomSheetEngine = class {
2826
3010
  if (externalSignal?.aborted) return;
2827
3011
  const target = this.snaps.findById(id);
2828
3012
  if (!target) {
2829
- console.warn(`[BottomSheet] unknown snap id: ${id}`);
3013
+ devWarn(`[BottomSheet] unknown snap id: ${id}`);
2830
3014
  return;
2831
3015
  }
2832
3016
  if (!this.snaps.getAllowedIds().includes(id)) {
2833
- console.warn(`[BottomSheet] snap "${id}" is not in allowed list`);
3017
+ devWarn(`[BottomSheet] snap "${id}" is not in allowed list`);
2834
3018
  return;
2835
3019
  }
2836
3020
  if (!_skipBeforeSnap && this.emitBeforeSnap(target, this.activeId)) {
@@ -2962,42 +3146,11 @@ var BottomSheetEngine = class {
2962
3146
  }
2963
3147
  setRadius(r) {
2964
3148
  if (this.destroyed) return;
2965
- this.element.style.setProperty(
2966
- "--bs-radius",
2967
- typeof r === "number" ? `${r}px` : r
2968
- );
3149
+ this.maxHeight.setRadius(r);
2969
3150
  }
2970
3151
  setMaxHeight(h) {
2971
3152
  if (this.destroyed) return;
2972
- const value = typeof h === "number" ? `${h}px` : h;
2973
- const axis = layoutAxis(this.mode);
2974
- this.element.style.setProperty(
2975
- axis === "height" ? "max-height" : "max-width",
2976
- value
2977
- );
2978
- this.maxHeightRaw = h;
2979
- this.resolveMaxHeightCap();
2980
- this.recompute();
2981
- }
2982
- resolveMaxHeightCap() {
2983
- const raw = this.maxHeightRaw;
2984
- if (raw === void 0) {
2985
- this.maxHeightCap = void 0;
2986
- return;
2987
- }
2988
- if (typeof raw === "number") {
2989
- this.maxHeightCap = raw;
2990
- return;
2991
- }
2992
- if (typeof window === "undefined") return;
2993
- const measured = resolveSnap(raw, this.mode);
2994
- this.maxHeightCap = measured > 0 ? measured : void 0;
2995
- }
2996
- clampToMaxHeight() {
2997
- if (this.maxHeightCap === void 0) return;
2998
- if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2999
- this.snaps.setMaxAxisSize(this.maxHeightCap);
3000
- }
3153
+ this.maxHeight.setMaxHeight(h);
3001
3154
  }
3002
3155
  setAllowed(ids, snap) {
3003
3156
  if (this.destroyed) return;
@@ -3085,7 +3238,7 @@ var BottomSheetEngine = class {
3085
3238
  };
3086
3239
  const host = this.screenComponent?.parentElement;
3087
3240
  if (!host) {
3088
- console.warn(
3241
+ devWarn(
3089
3242
  "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
3090
3243
  );
3091
3244
  return () => {
@@ -3135,9 +3288,9 @@ var BottomSheetEngine = class {
3135
3288
  }
3136
3289
  recompute() {
3137
3290
  if (this.destroyed) return;
3138
- this.resolveMaxHeightCap();
3291
+ this.maxHeight.resolveCap();
3139
3292
  this.snaps.recompute();
3140
- this.clampToMaxHeight();
3293
+ this.maxHeight.clampTo();
3141
3294
  this.scrim.invalidateOpacityCache();
3142
3295
  if (this.gesture?.isDragging) return;
3143
3296
  const current = this.snaps.findById(this.activeId);
@@ -3187,42 +3340,18 @@ var BottomSheetEngine = class {
3187
3340
  return this.currentAbort.signal;
3188
3341
  }
3189
3342
  emitBeforeSnap(target, previousId) {
3190
- let cancelled = false;
3191
- let frozen = false;
3192
- this.emit("before-snap", {
3193
- id: target.id,
3194
- size: target.size,
3195
- previousId,
3196
- cancel: () => {
3197
- if (frozen) {
3198
- console.warn(
3199
- "[BottomSheet] before-snap.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3200
- );
3201
- return;
3202
- }
3203
- cancelled = true;
3204
- }
3205
- });
3206
- frozen = true;
3207
- return cancelled;
3343
+ return emitCancelable(
3344
+ (payload) => this.emit("before-snap", payload),
3345
+ { id: target.id, size: target.size, previousId },
3346
+ "before-snap"
3347
+ );
3208
3348
  }
3209
3349
  emitBeforeClose(reason) {
3210
- let cancelled = false;
3211
- let frozen = false;
3212
- this.emit("before-close", {
3213
- reason,
3214
- cancel: () => {
3215
- if (frozen) {
3216
- console.warn(
3217
- "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3218
- );
3219
- return;
3220
- }
3221
- cancelled = true;
3222
- }
3223
- });
3224
- frozen = true;
3225
- return cancelled;
3350
+ return emitCancelable(
3351
+ (payload) => this.emit("before-close", payload),
3352
+ { reason },
3353
+ "before-close"
3354
+ );
3226
3355
  }
3227
3356
  applyAutoAriaLabelledBy() {
3228
3357
  if (typeof document === "undefined") return;
@@ -3237,16 +3366,16 @@ var BottomSheetEngine = class {
3237
3366
  this.element.setAttribute("aria-labelledby", titled.id);
3238
3367
  }
3239
3368
  recomputeSnaps() {
3240
- this.resolveMaxHeightCap();
3369
+ this.maxHeight.resolveCap();
3241
3370
  this.snaps.setRaw(this.snapPointsRaw);
3242
- this.clampToMaxHeight();
3371
+ this.maxHeight.clampTo();
3243
3372
  this.scrim.invalidateOpacityCache();
3244
3373
  }
3245
3374
  auditDuplicateSnapIds(points) {
3246
3375
  const seen = /* @__PURE__ */ new Set();
3247
3376
  for (const p of points) {
3248
3377
  if (seen.has(p.id)) {
3249
- console.warn(
3378
+ devWarn(
3250
3379
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
3251
3380
  );
3252
3381
  }
@@ -3413,7 +3542,15 @@ var BottomSheetEngine = class {
3413
3542
  setSize: (size) => {
3414
3543
  this.size = size;
3415
3544
  },
3416
- applySize: (size) => this.applySize(size)
3545
+ applySize: (size) => this.applySize(size),
3546
+ cancelInFlight: () => {
3547
+ this.animation.cancel();
3548
+ },
3549
+ newCycle: () => {
3550
+ this.newCycle();
3551
+ },
3552
+ isAnimating: () => this.animation.isAnimating,
3553
+ resyncAfterCancel: () => this.resyncAfterResize()
3417
3554
  }));
3418
3555
  }
3419
3556
  this.teardowns.add(installSliderKeyboard({
@@ -3449,9 +3586,9 @@ var BottomSheetEngine = class {
3449
3586
  return;
3450
3587
  }
3451
3588
  if (this.emitBeforeSnap(target, previousId)) {
3452
- const restore = this.snaps.findById(previousId);
3453
- if (restore) {
3454
- void this.animation.animateTo(restore.size, 0);
3589
+ const restore2 = this.snaps.findById(previousId);
3590
+ if (restore2) {
3591
+ void this.animation.animateTo(restore2.size, 0);
3455
3592
  }
3456
3593
  return;
3457
3594
  }
@@ -3690,7 +3827,7 @@ function installOverlaySwipe(deps) {
3690
3827
  };
3691
3828
  }
3692
3829
 
3693
- // src/core/overlay.ts
3830
+ // src/core/overlay-options.ts
3694
3831
  var OVERLAY_PRESETS = Object.freeze({
3695
3832
  sheet: Object.freeze({
3696
3833
  enterAnimation: "slide",
@@ -3721,6 +3858,8 @@ var OVERLAY_PRESETS = Object.freeze({
3721
3858
  swipeToClose: false
3722
3859
  })
3723
3860
  });
3861
+
3862
+ // src/core/overlay.ts
3724
3863
  var OverlayEngine = class {
3725
3864
  constructor(opts) {
3726
3865
  __publicField(this, "id", nextInstanceId("ovl"));
@@ -3755,7 +3894,7 @@ var OverlayEngine = class {
3755
3894
  __publicField(this, "detachSwipe", null);
3756
3895
  __publicField(this, "respectReducedMotionEnabled");
3757
3896
  __publicField(this, "isTop", true);
3758
- __publicField(this, "backPushed", false);
3897
+ __publicField(this, "backMarker", null);
3759
3898
  __publicField(this, "movedToTarget", false);
3760
3899
  __publicField(this, "onOpenCb");
3761
3900
  __publicField(this, "onCloseCb");
@@ -3767,7 +3906,6 @@ var OverlayEngine = class {
3767
3906
  __publicField(this, "bus", createEventBus());
3768
3907
  __publicField(this, "detachEscape", null);
3769
3908
  __publicField(this, "detachBackdrop", null);
3770
- __publicField(this, "detachHardwareBack", null);
3771
3909
  __publicField(this, "releaseStackEntry", null);
3772
3910
  __publicField(this, "lifecycle");
3773
3911
  this.element = opts.element;
@@ -3797,7 +3935,7 @@ var OverlayEngine = class {
3797
3935
  this.onOpenCb = opts.onOpen;
3798
3936
  this.onCloseCb = opts.onClose;
3799
3937
  if (this.peekRaw !== void 0 && this.edge !== "bottom") {
3800
- console.warn(
3938
+ devWarn(
3801
3939
  "[OverlayEngine] `peek` is only supported on edge: 'bottom' \u2014 ignoring."
3802
3940
  );
3803
3941
  this.peekRaw = void 0;
@@ -3864,10 +4002,9 @@ var OverlayEngine = class {
3864
4002
  this.element.style.opacity = "1";
3865
4003
  if (this.backdrop) {
3866
4004
  this.backdrop.style.opacity = String(this.backdropOpacity);
3867
- this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
4005
+ this.syncBackdropPointerEvents();
3868
4006
  if (this.backdropFilter) {
3869
- this.backdrop.style.backdropFilter = this.backdropFilter;
3870
- this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
4007
+ this.applyBackdropFilter(this.backdropFilter);
3871
4008
  }
3872
4009
  }
3873
4010
  try {
@@ -3887,8 +4024,7 @@ var OverlayEngine = class {
3887
4024
  this.backdrop.style.opacity = "0";
3888
4025
  this.backdrop.style.pointerEvents = "none";
3889
4026
  if (this.backdropFilter) {
3890
- this.backdrop.style.backdropFilter = "";
3891
- this.backdrop.style.webkitBackdropFilter = "";
4027
+ this.applyBackdropFilter(void 0);
3892
4028
  }
3893
4029
  }
3894
4030
  this.element.setAttribute("data-state", "closed");
@@ -3923,7 +4059,10 @@ var OverlayEngine = class {
3923
4059
  this.isOpen_ = false;
3924
4060
  this.isAnimating_ = true;
3925
4061
  sheetStack.update();
3926
- this.popBackMarker(reason);
4062
+ if (reason !== "back" && this.backMarker) {
4063
+ popBackMarker(this.backMarker);
4064
+ }
4065
+ this.backMarker = null;
3927
4066
  this.element.setAttribute("data-state", "exiting");
3928
4067
  const dur = this.effectiveDuration();
3929
4068
  const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
@@ -3946,8 +4085,7 @@ var OverlayEngine = class {
3946
4085
  this.backdrop.style.opacity = "0";
3947
4086
  this.backdrop.style.pointerEvents = "none";
3948
4087
  if (this.backdropFilter) {
3949
- this.backdrop.style.backdropFilter = "";
3950
- this.backdrop.style.webkitBackdropFilter = "";
4088
+ this.applyBackdropFilter(void 0);
3951
4089
  }
3952
4090
  }
3953
4091
  this.releaseInteractiveListeners();
@@ -3982,6 +4120,10 @@ var OverlayEngine = class {
3982
4120
  this.bus.clear();
3983
4121
  this.setWillChange(false);
3984
4122
  this.releaseInteractiveListeners();
4123
+ if (this.backMarker) {
4124
+ popBackMarker(this.backMarker);
4125
+ this.backMarker = null;
4126
+ }
3985
4127
  this.releaseStackEntry?.();
3986
4128
  this.releaseStackEntry = null;
3987
4129
  const hadOriginalParent = this.originalParent !== null;
@@ -3995,20 +4137,14 @@ var OverlayEngine = class {
3995
4137
  this.backdropOpacity = opacity;
3996
4138
  if (this.isOpen_ && this.backdrop) {
3997
4139
  this.backdrop.style.opacity = String(opacity);
4140
+ this.syncBackdropPointerEvents();
3998
4141
  }
3999
4142
  }
4000
4143
  setBackdropFilter(filter) {
4001
4144
  if (this.destroyed) return;
4002
4145
  this.backdropFilter = filter;
4003
4146
  if (!this.isOpen_ || !this.backdrop) return;
4004
- const style = this.backdrop.style;
4005
- if (filter) {
4006
- this.backdrop.style.backdropFilter = filter;
4007
- style.webkitBackdropFilter = filter;
4008
- } else {
4009
- this.backdrop.style.backdropFilter = "";
4010
- style.webkitBackdropFilter = "";
4011
- }
4147
+ this.applyBackdropFilter(filter);
4012
4148
  }
4013
4149
  setSwipeToClose(config) {
4014
4150
  if (this.destroyed) return;
@@ -4077,16 +4213,10 @@ var OverlayEngine = class {
4077
4213
  if (this.isOpen_ && this.backdrop) {
4078
4214
  if (merged.backdropOpacity !== void 0) {
4079
4215
  this.backdrop.style.opacity = String(this.backdropOpacity);
4216
+ this.syncBackdropPointerEvents();
4080
4217
  }
4081
4218
  if (merged.backdropFilter !== void 0) {
4082
- const style = this.backdrop.style;
4083
- if (this.backdropFilter) {
4084
- this.backdrop.style.backdropFilter = this.backdropFilter;
4085
- style.webkitBackdropFilter = this.backdropFilter;
4086
- } else {
4087
- this.backdrop.style.backdropFilter = "";
4088
- style.webkitBackdropFilter = "";
4089
- }
4219
+ this.applyBackdropFilter(this.backdropFilter);
4090
4220
  }
4091
4221
  }
4092
4222
  if (opts.duration !== void 0) this.duration = opts.duration;
@@ -4155,19 +4285,6 @@ var OverlayEngine = class {
4155
4285
  effectiveDuration() {
4156
4286
  return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
4157
4287
  }
4158
- popBackMarker(reason) {
4159
- if (!this.backPushed) return;
4160
- this.backPushed = false;
4161
- if (reason === "back") return;
4162
- const state = history.state;
4163
- if (state && state.__bsOverlay === this.id) {
4164
- try {
4165
- history.back();
4166
- } catch {
4167
- return;
4168
- }
4169
- }
4170
- }
4171
4288
  resolvePeekPx(raw) {
4172
4289
  if (typeof raw === "number") return Math.max(0, raw);
4173
4290
  try {
@@ -4221,6 +4338,21 @@ var OverlayEngine = class {
4221
4338
  this.backdrop.style.pointerEvents = "none";
4222
4339
  }
4223
4340
  }
4341
+ syncBackdropPointerEvents() {
4342
+ if (!this.backdrop) return;
4343
+ this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
4344
+ }
4345
+ applyBackdropFilter(filter) {
4346
+ if (!this.backdrop) return;
4347
+ const style = this.backdrop.style;
4348
+ if (filter) {
4349
+ this.backdrop.style.backdropFilter = filter;
4350
+ style.webkitBackdropFilter = filter;
4351
+ } else {
4352
+ this.backdrop.style.backdropFilter = "";
4353
+ style.webkitBackdropFilter = "";
4354
+ }
4355
+ }
4224
4356
  setWillChange(on) {
4225
4357
  this.element.style.willChange = on ? "transform, opacity" : "";
4226
4358
  if (this.backdrop) {
@@ -4268,21 +4400,6 @@ var OverlayEngine = class {
4268
4400
  this.backdrop.addEventListener("click", handler);
4269
4401
  this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
4270
4402
  }
4271
- if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
4272
- try {
4273
- history.pushState({ __bsOverlay: this.id }, "");
4274
- this.backPushed = true;
4275
- } catch {
4276
- }
4277
- const onPop = () => {
4278
- if (this.isOpen_ && this.isTop) {
4279
- this.backPushed = false;
4280
- void this.close("back");
4281
- }
4282
- };
4283
- window.addEventListener("popstate", onPop);
4284
- this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
4285
- }
4286
4403
  if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
4287
4404
  const handler = (e) => {
4288
4405
  if (!this.isOpen_) return;
@@ -4295,14 +4412,20 @@ var OverlayEngine = class {
4295
4412
  document.addEventListener("pointerdown", handler, true);
4296
4413
  this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
4297
4414
  }
4415
+ if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
4416
+ this.backMarker = pushBackMarker({
4417
+ isOpen: () => this.isOpen_,
4418
+ isTop: () => this.isTop,
4419
+ close: () => this.close("back"),
4420
+ markerState: () => ({ __bsOverlay: this.id })
4421
+ });
4422
+ }
4298
4423
  }
4299
4424
  releaseInteractiveListeners() {
4300
4425
  this.detachEscape?.();
4301
4426
  this.detachEscape = null;
4302
4427
  this.detachBackdrop?.();
4303
4428
  this.detachBackdrop = null;
4304
- this.detachHardwareBack?.();
4305
- this.detachHardwareBack = null;
4306
4429
  this.detachSwipe?.();
4307
4430
  this.detachSwipe = null;
4308
4431
  this.detachOutsidePointer?.();