@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.js CHANGED
@@ -187,11 +187,18 @@ var resolveSnap = (point, mode, measureFit) => {
187
187
  return Math.max(0, el.getBoundingClientRect().width);
188
188
  };
189
189
 
190
+ // src/core/primitives/devWarn.ts
191
+ function devWarn(...args) {
192
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
193
+ console.warn(...args);
194
+ }
195
+ }
196
+
190
197
  // src/core/primitives/snap-points.ts
191
198
  var resolveSnapList = (points, mode, measureFit) => points.map((p) => {
192
199
  const raw = resolveSnap(p.size, mode, measureFit);
193
200
  if (!Number.isFinite(raw) || raw < 0) {
194
- console.warn(
201
+ devWarn(
195
202
  `[BottomSheet] snap "${p.id}" resolved to invalid size (${String(p.size)} \u2192 ${raw}); clamped to 0.`
196
203
  );
197
204
  return { id: p.id, size: 0 };
@@ -203,7 +210,7 @@ var auditVhUsage = (points) => {
203
210
  for (const p of points) {
204
211
  if (typeof p.size !== "string") continue;
205
212
  if (!VH_NOT_DVH.test(p.size)) continue;
206
- console.warn(
213
+ devWarn(
207
214
  `[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`
208
215
  );
209
216
  }
@@ -257,6 +264,26 @@ var findDragSettleTarget = (input) => {
257
264
  );
258
265
  };
259
266
 
267
+ // src/core/primitives/cancelable-emit.ts
268
+ function emitCancelable(emit, base, warnLabel) {
269
+ let cancelled = false;
270
+ let frozen = false;
271
+ emit({
272
+ ...base,
273
+ cancel: () => {
274
+ if (frozen) {
275
+ devWarn(
276
+ `[BottomSheet] ${warnLabel}.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener.`
277
+ );
278
+ return;
279
+ }
280
+ cancelled = true;
281
+ }
282
+ });
283
+ frozen = true;
284
+ return cancelled;
285
+ }
286
+
260
287
  // src/core/primitives/snap-resolver.ts
261
288
  var SnapResolver = class {
262
289
  constructor(raw, allowed, mode, measureFit, onMaxAxisSizeChange) {
@@ -401,7 +428,9 @@ function notifyLinkedSheets(sheets, self) {
401
428
  try {
402
429
  if (linked.state.size === 0) continue;
403
430
  const ids = linked.getAllowedIds();
404
- const target = ids.find((id) => id !== "closed");
431
+ const resolved = linked.getResolvedSnaps();
432
+ const sizeOf = (id) => resolved.find((s) => s.id === id)?.size ?? 0;
433
+ const target = ids.find((id) => sizeOf(id) > 0);
405
434
  if (target && target !== linked.state.activeId) {
406
435
  void linked.snapTo(target);
407
436
  }
@@ -413,46 +442,198 @@ function notifyLinkedSheets(sheets, self) {
413
442
  }
414
443
  }
415
444
 
416
- // src/core/features/route.ts
417
- var closers = [];
418
- var handlerInstalled = false;
445
+ // src/core/features/history-coordinator.ts
446
+ var entries = [];
447
+ var subscribers = /* @__PURE__ */ new Set();
419
448
  var suppress = 0;
420
- var ensureHandler = () => {
421
- if (handlerInstalled || typeof window === "undefined") return;
422
- handlerInstalled = true;
423
- window.addEventListener("popstate", () => {
424
- if (suppress > 0) {
425
- suppress -= 1;
426
- return;
449
+ var internalDepth = 0;
450
+ var pendingRebrand = null;
451
+ var listenerInstalled = false;
452
+ var patchInstalled = false;
453
+ var origPushState = null;
454
+ var origReplaceState = null;
455
+ var patchedPushState = null;
456
+ var patchedReplaceState = null;
457
+ var hasWindow = () => typeof window !== "undefined";
458
+ var hasHistory = () => typeof history !== "undefined";
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;
525
+ }
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;
427
534
  }
428
- let target = null;
429
- for (let i = closers.length - 1; i >= 0; i -= 1) {
430
- const c = closers[i];
431
- if (!c.isOpen()) continue;
432
- if (!target) target = c;
433
- if (c.isTop()) {
434
- target = c;
435
- break;
436
- }
535
+ if (entry.surface.isTop()) {
536
+ target = entry;
537
+ targetIdx = i;
538
+ break;
437
539
  }
438
- if (!target) return;
439
- target.consumed = true;
440
- const idx = closers.indexOf(target);
441
- if (idx !== -1) closers.splice(idx, 1);
442
- const settled = target;
443
- void Promise.resolve(settled.close()).then(() => {
444
- if (settled.isOpen()) settled.restore();
445
- });
540
+ }
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);
446
552
  });
447
- };
448
- var programmaticBack = () => {
449
- suppress += 1;
450
- try {
451
- history.back();
452
- } catch {
453
- suppress = Math.max(0, suppress - 1);
553
+ maybeTeardownListener();
554
+ }
555
+ function restore(entry) {
556
+ if (!hasWindow() || !hasHistory()) return;
557
+ entry.live = true;
558
+ runInternal(() => {
559
+ try {
560
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
561
+ } catch {
562
+ entry.live = false;
563
+ }
564
+ });
565
+ if (entry.live) {
566
+ entries.push(entry);
567
+ ensureListener();
454
568
  }
455
- };
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(surface.markerState(), "", 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
+ rebrandSurface.markerState(),
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
456
637
  function installRoute(deps) {
457
638
  if (typeof window === "undefined") {
458
639
  return () => {
@@ -462,92 +643,39 @@ function installRoute(deps) {
462
643
  return () => {
463
644
  };
464
645
  }
465
- let pushed = false;
466
- let closer = null;
467
- const teardownEntry = () => {
468
- if (closer) {
469
- const idx = closers.indexOf(closer);
470
- if (idx !== -1) closers.splice(idx, 1);
471
- }
472
- const wasConsumed = closer?.consumed ?? false;
473
- closer = null;
474
- if (pushed && !wasConsumed) {
475
- pushed = false;
476
- programmaticBack();
477
- } else {
478
- pushed = false;
479
- }
480
- };
481
- const pushEntry = () => {
482
- try {
483
- if (deps.routedTo !== void 0) {
484
- history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
485
- } else {
486
- history.pushState({ __bsSheet: deps.sheetId }, "");
487
- }
488
- pushed = true;
489
- } catch {
490
- pushed = false;
491
- }
492
- };
646
+ let handle = null;
493
647
  const onOpen = () => {
494
- if (closer) return;
495
- const self = {
648
+ if (handle) return;
649
+ handle = pushBackMarker({
496
650
  isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
497
- isTop: () => deps.isTopSheet(),
498
- close: () => deps.close(),
499
- restore: () => {
500
- if (deps.isDestroyed() || closer !== self) return;
501
- self.consumed = false;
502
- if (!closers.includes(self)) closers.push(self);
503
- pushEntry();
504
- },
505
- consumed: false
506
- };
507
- closer = self;
508
- ensureHandler();
509
- closers.push(self);
510
- pushEntry();
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;
511
661
  };
512
662
  const unsubscribeOpen = deps.on("open", onOpen);
513
- const unsubscribeClose = deps.on("close", teardownEntry);
663
+ const unsubscribeClose = deps.on("close", onClose);
514
664
  if (deps.getSize() > 0) onOpen();
515
665
  return () => {
516
666
  unsubscribeOpen();
517
667
  unsubscribeClose();
518
- teardownEntry();
668
+ if (handle) {
669
+ popBackMarker(handle);
670
+ handle = null;
671
+ }
519
672
  };
520
673
  }
521
674
  function installRouteChange(deps) {
522
- if (typeof window === "undefined" || typeof history === "undefined") {
523
- return () => {
524
- };
525
- }
526
- const onChange = () => {
675
+ return subscribeRouteChange(() => {
527
676
  if (deps.isDestroyed() || deps.getSize() <= 0) return;
528
677
  void deps.close();
529
- };
530
- const wrap = (key) => {
531
- const original = history[key];
532
- const patched = function(...args) {
533
- original.apply(this, args);
534
- onChange();
535
- };
536
- history[key] = patched;
537
- return () => {
538
- if (history[key] === patched) {
539
- history[key] = original;
540
- }
541
- };
542
- };
543
- window.addEventListener("popstate", onChange);
544
- const restorePush = wrap("pushState");
545
- const restoreReplace = wrap("replaceState");
546
- return () => {
547
- window.removeEventListener("popstate", onChange);
548
- restorePush();
549
- restoreReplace();
550
- };
678
+ });
551
679
  }
552
680
 
553
681
  // src/core/features/content-swipe.ts
@@ -618,11 +746,15 @@ function installVisualViewport(deps) {
618
746
  if (clamp < deps.getMaxAxisSize()) {
619
747
  deps.setMaxAxisSize(clamp);
620
748
  }
749
+ const wasAnimating = deps.isAnimating?.() ?? false;
750
+ deps.cancelInFlight();
751
+ deps.newCycle();
621
752
  const max = deps.getMaxAxisSize();
622
753
  deps.setSize(Math.min(deps.getSize(), max));
623
754
  const current = deps.resolveActiveSnap();
624
755
  if (current && !deps.isDragging()) {
625
756
  deps.applySize(Math.min(current.size, max));
757
+ if (wasAnimating) deps.resyncAfterCancel?.();
626
758
  } else {
627
759
  deps.applySize(deps.getSize());
628
760
  }
@@ -763,6 +895,54 @@ function installFitObserver(deps) {
763
895
  };
764
896
  }
765
897
 
898
+ // src/core/features/max-height-controller.ts
899
+ function createMaxHeightController(deps) {
900
+ let maxHeightCap;
901
+ let maxHeightRaw;
902
+ const resolveCap = () => {
903
+ const raw = maxHeightRaw;
904
+ if (raw === void 0) {
905
+ maxHeightCap = void 0;
906
+ return;
907
+ }
908
+ if (typeof raw === "number") {
909
+ maxHeightCap = raw;
910
+ return;
911
+ }
912
+ if (typeof window === "undefined") return;
913
+ const measured = resolveSnap(raw, deps.mode);
914
+ maxHeightCap = measured > 0 ? measured : void 0;
915
+ };
916
+ const clampTo = () => {
917
+ if (maxHeightCap === void 0) return;
918
+ if (deps.getMaxAxisSize() > maxHeightCap) {
919
+ deps.setMaxAxisSize(maxHeightCap);
920
+ }
921
+ };
922
+ return {
923
+ setRadius(r) {
924
+ deps.element.style.setProperty(
925
+ "--bs-radius",
926
+ typeof r === "number" ? `${r}px` : r
927
+ );
928
+ },
929
+ setMaxHeight(h) {
930
+ const value = typeof h === "number" ? `${h}px` : h;
931
+ const axis = layoutAxis(deps.mode);
932
+ deps.element.style.setProperty(
933
+ axis === "height" ? "max-height" : "max-width",
934
+ value
935
+ );
936
+ maxHeightRaw = h;
937
+ resolveCap();
938
+ deps.recompute();
939
+ },
940
+ resolveCap,
941
+ clampTo,
942
+ getCap: () => maxHeightCap
943
+ };
944
+ }
945
+
766
946
  // src/core/features/fit-measurement.ts
767
947
  var measureSheetNatural = (element, content, vertical) => {
768
948
  if (!content) {
@@ -894,13 +1074,6 @@ var WriteSentinel = class {
894
1074
  }
895
1075
  };
896
1076
 
897
- // src/core/primitives/devWarn.ts
898
- function devWarn(...args) {
899
- if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
900
- console.warn(...args);
901
- }
902
- }
903
-
904
1077
  // src/core/primitives/overlay-position.ts
905
1078
  function resolveSheetAnchoredStyle(mode, position, inset) {
906
1079
  const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
@@ -2356,7 +2529,7 @@ var matchesRange = (def, progress) => {
2356
2529
  };
2357
2530
  function installScrimStages(deps, opts) {
2358
2531
  const doc = deps.host.ownerDocument;
2359
- const entries = opts.stages.map((def) => {
2532
+ const entries2 = opts.stages.map((def) => {
2360
2533
  const wrapper = doc.createElement("div");
2361
2534
  wrapper.className = "bs-scrim-stage";
2362
2535
  const ws = wrapper.style;
@@ -2377,9 +2550,9 @@ function installScrimStages(deps, opts) {
2377
2550
  const resolveActive = () => {
2378
2551
  const state = deps.getState();
2379
2552
  if (state.size === 0) {
2380
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2553
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? null;
2381
2554
  }
2382
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2555
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? entries2.find((e) => matchesRange(e.def, state.progress)) ?? null;
2383
2556
  };
2384
2557
  const hide = (entry, animate) => {
2385
2558
  entry.inFlight?.cancel();
@@ -2428,13 +2601,13 @@ function installScrimStages(deps, opts) {
2428
2601
  deps.on("open", () => update(true)),
2429
2602
  deps.on("close", () => update(true))
2430
2603
  ];
2431
- if (entries.some((e) => e.def.forRange)) {
2604
+ if (entries2.some((e) => e.def.forRange)) {
2432
2605
  offs.push(deps.on("progress", () => update(true)));
2433
2606
  }
2434
2607
  update(false);
2435
2608
  return () => {
2436
2609
  offs.forEach((off) => off());
2437
- for (const entry of entries) {
2610
+ for (const entry of entries2) {
2438
2611
  entry.inFlight?.cancel();
2439
2612
  entry.inFlight = null;
2440
2613
  if (entry.wrapper.parentNode) {
@@ -2547,8 +2720,7 @@ var BottomSheetEngine = class {
2547
2720
  __publicField(this, "disableCloseFlag");
2548
2721
  __publicField(this, "disableDragFlag");
2549
2722
  __publicField(this, "closeOnRouteChange");
2550
- __publicField(this, "maxHeightCap");
2551
- __publicField(this, "maxHeightRaw");
2723
+ __publicField(this, "maxHeight");
2552
2724
  __publicField(this, "snapPointsRaw");
2553
2725
  __publicField(this, "snaps");
2554
2726
  __publicField(this, "activeId");
@@ -2659,11 +2831,20 @@ var BottomSheetEngine = class {
2659
2831
  auditVhUsage(this.snapPointsRaw);
2660
2832
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2661
2833
  this.rootEl = this.element.closest(".bs-root");
2834
+ this.maxHeight = createMaxHeightController({
2835
+ element: this.element,
2836
+ mode: this.mode,
2837
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2838
+ setMaxAxisSize: (size) => {
2839
+ this.snaps.setMaxAxisSize(size);
2840
+ },
2841
+ recompute: () => this.recompute()
2842
+ });
2662
2843
  this.fitDeps = {
2663
2844
  element: this.element,
2664
2845
  scrollContainer: this.scrollContainer,
2665
2846
  mode: this.mode,
2666
- getMaxHeightCap: () => this.maxHeightCap
2847
+ getMaxHeightCap: () => this.maxHeight.getCap()
2667
2848
  };
2668
2849
  this.snaps = new SnapResolver(
2669
2850
  this.snapPointsRaw,
@@ -2685,8 +2866,8 @@ var BottomSheetEngine = class {
2685
2866
  this.attach();
2686
2867
  this.registerInStack();
2687
2868
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
2688
- const io = new IntersectionObserver((entries) => {
2689
- for (const entry of entries) {
2869
+ const io = new IntersectionObserver((entries2) => {
2870
+ for (const entry of entries2) {
2690
2871
  if (!entry.isIntersecting) continue;
2691
2872
  if (this.destroyed) return;
2692
2873
  this.snaps.recompute();
@@ -2774,7 +2955,7 @@ var BottomSheetEngine = class {
2774
2955
  }
2775
2956
  use(plugin) {
2776
2957
  if (this.destroyed) {
2777
- console.warn(
2958
+ devWarn(
2778
2959
  `[BottomSheet] use("${plugin.name}") called on destroyed engine \u2014 ignored.`
2779
2960
  );
2780
2961
  return this;
@@ -2816,6 +2997,9 @@ var BottomSheetEngine = class {
2816
2997
  getAllowedIds() {
2817
2998
  return this.snaps.getAllowedIds().slice();
2818
2999
  }
3000
+ getResolvedSnaps() {
3001
+ return this.snaps.getResolvedSnaps();
3002
+ }
2819
3003
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2820
3004
  if (this.destroyed) return;
2821
3005
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2824,11 +3008,11 @@ var BottomSheetEngine = class {
2824
3008
  if (externalSignal?.aborted) return;
2825
3009
  const target = this.snaps.findById(id);
2826
3010
  if (!target) {
2827
- console.warn(`[BottomSheet] unknown snap id: ${id}`);
3011
+ devWarn(`[BottomSheet] unknown snap id: ${id}`);
2828
3012
  return;
2829
3013
  }
2830
3014
  if (!this.snaps.getAllowedIds().includes(id)) {
2831
- console.warn(`[BottomSheet] snap "${id}" is not in allowed list`);
3015
+ devWarn(`[BottomSheet] snap "${id}" is not in allowed list`);
2832
3016
  return;
2833
3017
  }
2834
3018
  if (!_skipBeforeSnap && this.emitBeforeSnap(target, this.activeId)) {
@@ -2960,42 +3144,11 @@ var BottomSheetEngine = class {
2960
3144
  }
2961
3145
  setRadius(r) {
2962
3146
  if (this.destroyed) return;
2963
- this.element.style.setProperty(
2964
- "--bs-radius",
2965
- typeof r === "number" ? `${r}px` : r
2966
- );
3147
+ this.maxHeight.setRadius(r);
2967
3148
  }
2968
3149
  setMaxHeight(h) {
2969
3150
  if (this.destroyed) return;
2970
- const value = typeof h === "number" ? `${h}px` : h;
2971
- const axis = layoutAxis(this.mode);
2972
- this.element.style.setProperty(
2973
- axis === "height" ? "max-height" : "max-width",
2974
- value
2975
- );
2976
- this.maxHeightRaw = h;
2977
- this.resolveMaxHeightCap();
2978
- this.recompute();
2979
- }
2980
- resolveMaxHeightCap() {
2981
- const raw = this.maxHeightRaw;
2982
- if (raw === void 0) {
2983
- this.maxHeightCap = void 0;
2984
- return;
2985
- }
2986
- if (typeof raw === "number") {
2987
- this.maxHeightCap = raw;
2988
- return;
2989
- }
2990
- if (typeof window === "undefined") return;
2991
- const measured = resolveSnap(raw, this.mode);
2992
- this.maxHeightCap = measured > 0 ? measured : void 0;
2993
- }
2994
- clampToMaxHeight() {
2995
- if (this.maxHeightCap === void 0) return;
2996
- if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2997
- this.snaps.setMaxAxisSize(this.maxHeightCap);
2998
- }
3151
+ this.maxHeight.setMaxHeight(h);
2999
3152
  }
3000
3153
  setAllowed(ids, snap) {
3001
3154
  if (this.destroyed) return;
@@ -3083,7 +3236,7 @@ var BottomSheetEngine = class {
3083
3236
  };
3084
3237
  const host = this.screenComponent?.parentElement;
3085
3238
  if (!host) {
3086
- console.warn(
3239
+ devWarn(
3087
3240
  "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
3088
3241
  );
3089
3242
  return () => {
@@ -3133,9 +3286,9 @@ var BottomSheetEngine = class {
3133
3286
  }
3134
3287
  recompute() {
3135
3288
  if (this.destroyed) return;
3136
- this.resolveMaxHeightCap();
3289
+ this.maxHeight.resolveCap();
3137
3290
  this.snaps.recompute();
3138
- this.clampToMaxHeight();
3291
+ this.maxHeight.clampTo();
3139
3292
  this.scrim.invalidateOpacityCache();
3140
3293
  if (this.gesture?.isDragging) return;
3141
3294
  const current = this.snaps.findById(this.activeId);
@@ -3185,42 +3338,18 @@ var BottomSheetEngine = class {
3185
3338
  return this.currentAbort.signal;
3186
3339
  }
3187
3340
  emitBeforeSnap(target, previousId) {
3188
- let cancelled = false;
3189
- let frozen = false;
3190
- this.emit("before-snap", {
3191
- id: target.id,
3192
- size: target.size,
3193
- previousId,
3194
- cancel: () => {
3195
- if (frozen) {
3196
- console.warn(
3197
- "[BottomSheet] before-snap.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3198
- );
3199
- return;
3200
- }
3201
- cancelled = true;
3202
- }
3203
- });
3204
- frozen = true;
3205
- return cancelled;
3341
+ return emitCancelable(
3342
+ (payload) => this.emit("before-snap", payload),
3343
+ { id: target.id, size: target.size, previousId },
3344
+ "before-snap"
3345
+ );
3206
3346
  }
3207
3347
  emitBeforeClose(reason) {
3208
- let cancelled = false;
3209
- let frozen = false;
3210
- this.emit("before-close", {
3211
- reason,
3212
- cancel: () => {
3213
- if (frozen) {
3214
- console.warn(
3215
- "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3216
- );
3217
- return;
3218
- }
3219
- cancelled = true;
3220
- }
3221
- });
3222
- frozen = true;
3223
- return cancelled;
3348
+ return emitCancelable(
3349
+ (payload) => this.emit("before-close", payload),
3350
+ { reason },
3351
+ "before-close"
3352
+ );
3224
3353
  }
3225
3354
  applyAutoAriaLabelledBy() {
3226
3355
  if (typeof document === "undefined") return;
@@ -3235,16 +3364,16 @@ var BottomSheetEngine = class {
3235
3364
  this.element.setAttribute("aria-labelledby", titled.id);
3236
3365
  }
3237
3366
  recomputeSnaps() {
3238
- this.resolveMaxHeightCap();
3367
+ this.maxHeight.resolveCap();
3239
3368
  this.snaps.setRaw(this.snapPointsRaw);
3240
- this.clampToMaxHeight();
3369
+ this.maxHeight.clampTo();
3241
3370
  this.scrim.invalidateOpacityCache();
3242
3371
  }
3243
3372
  auditDuplicateSnapIds(points) {
3244
3373
  const seen = /* @__PURE__ */ new Set();
3245
3374
  for (const p of points) {
3246
3375
  if (seen.has(p.id)) {
3247
- console.warn(
3376
+ devWarn(
3248
3377
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
3249
3378
  );
3250
3379
  }
@@ -3411,7 +3540,15 @@ var BottomSheetEngine = class {
3411
3540
  setSize: (size) => {
3412
3541
  this.size = size;
3413
3542
  },
3414
- applySize: (size) => this.applySize(size)
3543
+ applySize: (size) => this.applySize(size),
3544
+ cancelInFlight: () => {
3545
+ this.animation.cancel();
3546
+ },
3547
+ newCycle: () => {
3548
+ this.newCycle();
3549
+ },
3550
+ isAnimating: () => this.animation.isAnimating,
3551
+ resyncAfterCancel: () => this.resyncAfterResize()
3415
3552
  }));
3416
3553
  }
3417
3554
  this.teardowns.add(installSliderKeyboard({
@@ -3447,9 +3584,9 @@ var BottomSheetEngine = class {
3447
3584
  return;
3448
3585
  }
3449
3586
  if (this.emitBeforeSnap(target, previousId)) {
3450
- const restore = this.snaps.findById(previousId);
3451
- if (restore) {
3452
- void this.animation.animateTo(restore.size, 0);
3587
+ const restore2 = this.snaps.findById(previousId);
3588
+ if (restore2) {
3589
+ void this.animation.animateTo(restore2.size, 0);
3453
3590
  }
3454
3591
  return;
3455
3592
  }
@@ -3688,7 +3825,7 @@ function installOverlaySwipe(deps) {
3688
3825
  };
3689
3826
  }
3690
3827
 
3691
- // src/core/overlay.ts
3828
+ // src/core/overlay-options.ts
3692
3829
  var OVERLAY_PRESETS = Object.freeze({
3693
3830
  sheet: Object.freeze({
3694
3831
  enterAnimation: "slide",
@@ -3719,6 +3856,8 @@ var OVERLAY_PRESETS = Object.freeze({
3719
3856
  swipeToClose: false
3720
3857
  })
3721
3858
  });
3859
+
3860
+ // src/core/overlay.ts
3722
3861
  var OverlayEngine = class {
3723
3862
  constructor(opts) {
3724
3863
  __publicField(this, "id", nextInstanceId("ovl"));
@@ -3753,7 +3892,7 @@ var OverlayEngine = class {
3753
3892
  __publicField(this, "detachSwipe", null);
3754
3893
  __publicField(this, "respectReducedMotionEnabled");
3755
3894
  __publicField(this, "isTop", true);
3756
- __publicField(this, "backPushed", false);
3895
+ __publicField(this, "backMarker", null);
3757
3896
  __publicField(this, "movedToTarget", false);
3758
3897
  __publicField(this, "onOpenCb");
3759
3898
  __publicField(this, "onCloseCb");
@@ -3765,7 +3904,6 @@ var OverlayEngine = class {
3765
3904
  __publicField(this, "bus", createEventBus());
3766
3905
  __publicField(this, "detachEscape", null);
3767
3906
  __publicField(this, "detachBackdrop", null);
3768
- __publicField(this, "detachHardwareBack", null);
3769
3907
  __publicField(this, "releaseStackEntry", null);
3770
3908
  __publicField(this, "lifecycle");
3771
3909
  this.element = opts.element;
@@ -3795,7 +3933,7 @@ var OverlayEngine = class {
3795
3933
  this.onOpenCb = opts.onOpen;
3796
3934
  this.onCloseCb = opts.onClose;
3797
3935
  if (this.peekRaw !== void 0 && this.edge !== "bottom") {
3798
- console.warn(
3936
+ devWarn(
3799
3937
  "[OverlayEngine] `peek` is only supported on edge: 'bottom' \u2014 ignoring."
3800
3938
  );
3801
3939
  this.peekRaw = void 0;
@@ -3862,10 +4000,9 @@ var OverlayEngine = class {
3862
4000
  this.element.style.opacity = "1";
3863
4001
  if (this.backdrop) {
3864
4002
  this.backdrop.style.opacity = String(this.backdropOpacity);
3865
- this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
4003
+ this.syncBackdropPointerEvents();
3866
4004
  if (this.backdropFilter) {
3867
- this.backdrop.style.backdropFilter = this.backdropFilter;
3868
- this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
4005
+ this.applyBackdropFilter(this.backdropFilter);
3869
4006
  }
3870
4007
  }
3871
4008
  try {
@@ -3885,8 +4022,7 @@ var OverlayEngine = class {
3885
4022
  this.backdrop.style.opacity = "0";
3886
4023
  this.backdrop.style.pointerEvents = "none";
3887
4024
  if (this.backdropFilter) {
3888
- this.backdrop.style.backdropFilter = "";
3889
- this.backdrop.style.webkitBackdropFilter = "";
4025
+ this.applyBackdropFilter(void 0);
3890
4026
  }
3891
4027
  }
3892
4028
  this.element.setAttribute("data-state", "closed");
@@ -3921,7 +4057,10 @@ var OverlayEngine = class {
3921
4057
  this.isOpen_ = false;
3922
4058
  this.isAnimating_ = true;
3923
4059
  sheetStack.update();
3924
- this.popBackMarker(reason);
4060
+ if (reason !== "back" && this.backMarker) {
4061
+ popBackMarker(this.backMarker);
4062
+ }
4063
+ this.backMarker = null;
3925
4064
  this.element.setAttribute("data-state", "exiting");
3926
4065
  const dur = this.effectiveDuration();
3927
4066
  const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
@@ -3944,8 +4083,7 @@ var OverlayEngine = class {
3944
4083
  this.backdrop.style.opacity = "0";
3945
4084
  this.backdrop.style.pointerEvents = "none";
3946
4085
  if (this.backdropFilter) {
3947
- this.backdrop.style.backdropFilter = "";
3948
- this.backdrop.style.webkitBackdropFilter = "";
4086
+ this.applyBackdropFilter(void 0);
3949
4087
  }
3950
4088
  }
3951
4089
  this.releaseInteractiveListeners();
@@ -3980,6 +4118,10 @@ var OverlayEngine = class {
3980
4118
  this.bus.clear();
3981
4119
  this.setWillChange(false);
3982
4120
  this.releaseInteractiveListeners();
4121
+ if (this.backMarker) {
4122
+ popBackMarker(this.backMarker);
4123
+ this.backMarker = null;
4124
+ }
3983
4125
  this.releaseStackEntry?.();
3984
4126
  this.releaseStackEntry = null;
3985
4127
  const hadOriginalParent = this.originalParent !== null;
@@ -3993,20 +4135,14 @@ var OverlayEngine = class {
3993
4135
  this.backdropOpacity = opacity;
3994
4136
  if (this.isOpen_ && this.backdrop) {
3995
4137
  this.backdrop.style.opacity = String(opacity);
4138
+ this.syncBackdropPointerEvents();
3996
4139
  }
3997
4140
  }
3998
4141
  setBackdropFilter(filter) {
3999
4142
  if (this.destroyed) return;
4000
4143
  this.backdropFilter = filter;
4001
4144
  if (!this.isOpen_ || !this.backdrop) return;
4002
- const style = this.backdrop.style;
4003
- if (filter) {
4004
- this.backdrop.style.backdropFilter = filter;
4005
- style.webkitBackdropFilter = filter;
4006
- } else {
4007
- this.backdrop.style.backdropFilter = "";
4008
- style.webkitBackdropFilter = "";
4009
- }
4145
+ this.applyBackdropFilter(filter);
4010
4146
  }
4011
4147
  setSwipeToClose(config) {
4012
4148
  if (this.destroyed) return;
@@ -4075,16 +4211,10 @@ var OverlayEngine = class {
4075
4211
  if (this.isOpen_ && this.backdrop) {
4076
4212
  if (merged.backdropOpacity !== void 0) {
4077
4213
  this.backdrop.style.opacity = String(this.backdropOpacity);
4214
+ this.syncBackdropPointerEvents();
4078
4215
  }
4079
4216
  if (merged.backdropFilter !== void 0) {
4080
- const style = this.backdrop.style;
4081
- if (this.backdropFilter) {
4082
- this.backdrop.style.backdropFilter = this.backdropFilter;
4083
- style.webkitBackdropFilter = this.backdropFilter;
4084
- } else {
4085
- this.backdrop.style.backdropFilter = "";
4086
- style.webkitBackdropFilter = "";
4087
- }
4217
+ this.applyBackdropFilter(this.backdropFilter);
4088
4218
  }
4089
4219
  }
4090
4220
  if (opts.duration !== void 0) this.duration = opts.duration;
@@ -4153,19 +4283,6 @@ var OverlayEngine = class {
4153
4283
  effectiveDuration() {
4154
4284
  return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
4155
4285
  }
4156
- popBackMarker(reason) {
4157
- if (!this.backPushed) return;
4158
- this.backPushed = false;
4159
- if (reason === "back") return;
4160
- const state = history.state;
4161
- if (state && state.__bsOverlay === this.id) {
4162
- try {
4163
- history.back();
4164
- } catch {
4165
- return;
4166
- }
4167
- }
4168
- }
4169
4286
  resolvePeekPx(raw) {
4170
4287
  if (typeof raw === "number") return Math.max(0, raw);
4171
4288
  try {
@@ -4219,6 +4336,21 @@ var OverlayEngine = class {
4219
4336
  this.backdrop.style.pointerEvents = "none";
4220
4337
  }
4221
4338
  }
4339
+ syncBackdropPointerEvents() {
4340
+ if (!this.backdrop) return;
4341
+ this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
4342
+ }
4343
+ applyBackdropFilter(filter) {
4344
+ if (!this.backdrop) return;
4345
+ const style = this.backdrop.style;
4346
+ if (filter) {
4347
+ this.backdrop.style.backdropFilter = filter;
4348
+ style.webkitBackdropFilter = filter;
4349
+ } else {
4350
+ this.backdrop.style.backdropFilter = "";
4351
+ style.webkitBackdropFilter = "";
4352
+ }
4353
+ }
4222
4354
  setWillChange(on) {
4223
4355
  this.element.style.willChange = on ? "transform, opacity" : "";
4224
4356
  if (this.backdrop) {
@@ -4266,21 +4398,6 @@ var OverlayEngine = class {
4266
4398
  this.backdrop.addEventListener("click", handler);
4267
4399
  this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
4268
4400
  }
4269
- if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
4270
- try {
4271
- history.pushState({ __bsOverlay: this.id }, "");
4272
- this.backPushed = true;
4273
- } catch {
4274
- }
4275
- const onPop = () => {
4276
- if (this.isOpen_ && this.isTop) {
4277
- this.backPushed = false;
4278
- void this.close("back");
4279
- }
4280
- };
4281
- window.addEventListener("popstate", onPop);
4282
- this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
4283
- }
4284
4401
  if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
4285
4402
  const handler = (e) => {
4286
4403
  if (!this.isOpen_) return;
@@ -4293,14 +4410,20 @@ var OverlayEngine = class {
4293
4410
  document.addEventListener("pointerdown", handler, true);
4294
4411
  this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
4295
4412
  }
4413
+ if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
4414
+ this.backMarker = pushBackMarker({
4415
+ isOpen: () => this.isOpen_,
4416
+ isTop: () => this.isTop,
4417
+ close: () => this.close("back"),
4418
+ markerState: () => ({ __bsOverlay: this.id })
4419
+ });
4420
+ }
4296
4421
  }
4297
4422
  releaseInteractiveListeners() {
4298
4423
  this.detachEscape?.();
4299
4424
  this.detachEscape = null;
4300
4425
  this.detachBackdrop?.();
4301
4426
  this.detachBackdrop = null;
4302
- this.detachHardwareBack?.();
4303
- this.detachHardwareBack = null;
4304
4427
  this.detachSwipe?.();
4305
4428
  this.detachSwipe = null;
4306
4429
  this.detachOutsidePointer?.();