@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
@@ -186,11 +186,18 @@ var resolveSnap = (point, mode, measureFit) => {
186
186
  return Math.max(0, el.getBoundingClientRect().width);
187
187
  };
188
188
 
189
+ // src/core/primitives/devWarn.ts
190
+ function devWarn(...args) {
191
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
192
+ console.warn(...args);
193
+ }
194
+ }
195
+
189
196
  // src/core/primitives/snap-points.ts
190
197
  var resolveSnapList = (points, mode, measureFit) => points.map((p) => {
191
198
  const raw = resolveSnap(p.size, mode, measureFit);
192
199
  if (!Number.isFinite(raw) || raw < 0) {
193
- console.warn(
200
+ devWarn(
194
201
  `[BottomSheet] snap "${p.id}" resolved to invalid size (${String(p.size)} \u2192 ${raw}); clamped to 0.`
195
202
  );
196
203
  return { id: p.id, size: 0 };
@@ -202,7 +209,7 @@ var auditVhUsage = (points) => {
202
209
  for (const p of points) {
203
210
  if (typeof p.size !== "string") continue;
204
211
  if (!VH_NOT_DVH.test(p.size)) continue;
205
- console.warn(
212
+ devWarn(
206
213
  `[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`
207
214
  );
208
215
  }
@@ -256,6 +263,26 @@ var findDragSettleTarget = (input) => {
256
263
  );
257
264
  };
258
265
 
266
+ // src/core/primitives/cancelable-emit.ts
267
+ function emitCancelable(emit, base, warnLabel) {
268
+ let cancelled = false;
269
+ let frozen = false;
270
+ emit({
271
+ ...base,
272
+ cancel: () => {
273
+ if (frozen) {
274
+ devWarn(
275
+ `[BottomSheet] ${warnLabel}.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener.`
276
+ );
277
+ return;
278
+ }
279
+ cancelled = true;
280
+ }
281
+ });
282
+ frozen = true;
283
+ return cancelled;
284
+ }
285
+
259
286
  // src/core/primitives/snap-resolver.ts
260
287
  var SnapResolver = class {
261
288
  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) {
@@ -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) {
@@ -2544,8 +2717,7 @@ var BottomSheetEngine = class {
2544
2717
  disableCloseFlag;
2545
2718
  disableDragFlag;
2546
2719
  closeOnRouteChange;
2547
- maxHeightCap;
2548
- maxHeightRaw;
2720
+ maxHeight;
2549
2721
  snapPointsRaw;
2550
2722
  snaps;
2551
2723
  activeId;
@@ -2657,11 +2829,20 @@ var BottomSheetEngine = class {
2657
2829
  auditVhUsage(this.snapPointsRaw);
2658
2830
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2659
2831
  this.rootEl = this.element.closest(".bs-root");
2832
+ this.maxHeight = createMaxHeightController({
2833
+ element: this.element,
2834
+ mode: this.mode,
2835
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2836
+ setMaxAxisSize: (size) => {
2837
+ this.snaps.setMaxAxisSize(size);
2838
+ },
2839
+ recompute: () => this.recompute()
2840
+ });
2660
2841
  this.fitDeps = {
2661
2842
  element: this.element,
2662
2843
  scrollContainer: this.scrollContainer,
2663
2844
  mode: this.mode,
2664
- getMaxHeightCap: () => this.maxHeightCap
2845
+ getMaxHeightCap: () => this.maxHeight.getCap()
2665
2846
  };
2666
2847
  this.snaps = new SnapResolver(
2667
2848
  this.snapPointsRaw,
@@ -2683,8 +2864,8 @@ var BottomSheetEngine = class {
2683
2864
  this.attach();
2684
2865
  this.registerInStack();
2685
2866
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
2686
- const io = new IntersectionObserver((entries) => {
2687
- for (const entry of entries) {
2867
+ const io = new IntersectionObserver((entries2) => {
2868
+ for (const entry of entries2) {
2688
2869
  if (!entry.isIntersecting) continue;
2689
2870
  if (this.destroyed) return;
2690
2871
  this.snaps.recompute();
@@ -2772,7 +2953,7 @@ var BottomSheetEngine = class {
2772
2953
  }
2773
2954
  use(plugin) {
2774
2955
  if (this.destroyed) {
2775
- console.warn(
2956
+ devWarn(
2776
2957
  `[BottomSheet] use("${plugin.name}") called on destroyed engine \u2014 ignored.`
2777
2958
  );
2778
2959
  return this;
@@ -2814,6 +2995,9 @@ var BottomSheetEngine = class {
2814
2995
  getAllowedIds() {
2815
2996
  return this.snaps.getAllowedIds().slice();
2816
2997
  }
2998
+ getResolvedSnaps() {
2999
+ return this.snaps.getResolvedSnaps();
3000
+ }
2817
3001
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2818
3002
  if (this.destroyed) return;
2819
3003
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2822,11 +3006,11 @@ var BottomSheetEngine = class {
2822
3006
  if (externalSignal?.aborted) return;
2823
3007
  const target = this.snaps.findById(id);
2824
3008
  if (!target) {
2825
- console.warn(`[BottomSheet] unknown snap id: ${id}`);
3009
+ devWarn(`[BottomSheet] unknown snap id: ${id}`);
2826
3010
  return;
2827
3011
  }
2828
3012
  if (!this.snaps.getAllowedIds().includes(id)) {
2829
- console.warn(`[BottomSheet] snap "${id}" is not in allowed list`);
3013
+ devWarn(`[BottomSheet] snap "${id}" is not in allowed list`);
2830
3014
  return;
2831
3015
  }
2832
3016
  if (!_skipBeforeSnap && this.emitBeforeSnap(target, this.activeId)) {
@@ -2958,42 +3142,11 @@ var BottomSheetEngine = class {
2958
3142
  }
2959
3143
  setRadius(r) {
2960
3144
  if (this.destroyed) return;
2961
- this.element.style.setProperty(
2962
- "--bs-radius",
2963
- typeof r === "number" ? `${r}px` : r
2964
- );
3145
+ this.maxHeight.setRadius(r);
2965
3146
  }
2966
3147
  setMaxHeight(h) {
2967
3148
  if (this.destroyed) return;
2968
- const value = typeof h === "number" ? `${h}px` : h;
2969
- const axis = layoutAxis(this.mode);
2970
- this.element.style.setProperty(
2971
- axis === "height" ? "max-height" : "max-width",
2972
- value
2973
- );
2974
- this.maxHeightRaw = h;
2975
- this.resolveMaxHeightCap();
2976
- this.recompute();
2977
- }
2978
- resolveMaxHeightCap() {
2979
- const raw = this.maxHeightRaw;
2980
- if (raw === void 0) {
2981
- this.maxHeightCap = void 0;
2982
- return;
2983
- }
2984
- if (typeof raw === "number") {
2985
- this.maxHeightCap = raw;
2986
- return;
2987
- }
2988
- if (typeof window === "undefined") return;
2989
- const measured = resolveSnap(raw, this.mode);
2990
- this.maxHeightCap = measured > 0 ? measured : void 0;
2991
- }
2992
- clampToMaxHeight() {
2993
- if (this.maxHeightCap === void 0) return;
2994
- if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2995
- this.snaps.setMaxAxisSize(this.maxHeightCap);
2996
- }
3149
+ this.maxHeight.setMaxHeight(h);
2997
3150
  }
2998
3151
  setAllowed(ids, snap) {
2999
3152
  if (this.destroyed) return;
@@ -3081,7 +3234,7 @@ var BottomSheetEngine = class {
3081
3234
  };
3082
3235
  const host = this.screenComponent?.parentElement;
3083
3236
  if (!host) {
3084
- console.warn(
3237
+ devWarn(
3085
3238
  "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
3086
3239
  );
3087
3240
  return () => {
@@ -3131,9 +3284,9 @@ var BottomSheetEngine = class {
3131
3284
  }
3132
3285
  recompute() {
3133
3286
  if (this.destroyed) return;
3134
- this.resolveMaxHeightCap();
3287
+ this.maxHeight.resolveCap();
3135
3288
  this.snaps.recompute();
3136
- this.clampToMaxHeight();
3289
+ this.maxHeight.clampTo();
3137
3290
  this.scrim.invalidateOpacityCache();
3138
3291
  if (this.gesture?.isDragging) return;
3139
3292
  const current = this.snaps.findById(this.activeId);
@@ -3183,42 +3336,18 @@ var BottomSheetEngine = class {
3183
3336
  return this.currentAbort.signal;
3184
3337
  }
3185
3338
  emitBeforeSnap(target, previousId) {
3186
- let cancelled = false;
3187
- let frozen = false;
3188
- this.emit("before-snap", {
3189
- id: target.id,
3190
- size: target.size,
3191
- previousId,
3192
- cancel: () => {
3193
- if (frozen) {
3194
- console.warn(
3195
- "[BottomSheet] before-snap.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3196
- );
3197
- return;
3198
- }
3199
- cancelled = true;
3200
- }
3201
- });
3202
- frozen = true;
3203
- return cancelled;
3339
+ return emitCancelable(
3340
+ (payload) => this.emit("before-snap", payload),
3341
+ { id: target.id, size: target.size, previousId },
3342
+ "before-snap"
3343
+ );
3204
3344
  }
3205
3345
  emitBeforeClose(reason) {
3206
- let cancelled = false;
3207
- let frozen = false;
3208
- this.emit("before-close", {
3209
- reason,
3210
- cancel: () => {
3211
- if (frozen) {
3212
- console.warn(
3213
- "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3214
- );
3215
- return;
3216
- }
3217
- cancelled = true;
3218
- }
3219
- });
3220
- frozen = true;
3221
- return cancelled;
3346
+ return emitCancelable(
3347
+ (payload) => this.emit("before-close", payload),
3348
+ { reason },
3349
+ "before-close"
3350
+ );
3222
3351
  }
3223
3352
  applyAutoAriaLabelledBy() {
3224
3353
  if (typeof document === "undefined") return;
@@ -3233,16 +3362,16 @@ var BottomSheetEngine = class {
3233
3362
  this.element.setAttribute("aria-labelledby", titled.id);
3234
3363
  }
3235
3364
  recomputeSnaps() {
3236
- this.resolveMaxHeightCap();
3365
+ this.maxHeight.resolveCap();
3237
3366
  this.snaps.setRaw(this.snapPointsRaw);
3238
- this.clampToMaxHeight();
3367
+ this.maxHeight.clampTo();
3239
3368
  this.scrim.invalidateOpacityCache();
3240
3369
  }
3241
3370
  auditDuplicateSnapIds(points) {
3242
3371
  const seen = /* @__PURE__ */ new Set();
3243
3372
  for (const p of points) {
3244
3373
  if (seen.has(p.id)) {
3245
- console.warn(
3374
+ devWarn(
3246
3375
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
3247
3376
  );
3248
3377
  }
@@ -3409,7 +3538,15 @@ var BottomSheetEngine = class {
3409
3538
  setSize: (size) => {
3410
3539
  this.size = size;
3411
3540
  },
3412
- applySize: (size) => this.applySize(size)
3541
+ applySize: (size) => this.applySize(size),
3542
+ cancelInFlight: () => {
3543
+ this.animation.cancel();
3544
+ },
3545
+ newCycle: () => {
3546
+ this.newCycle();
3547
+ },
3548
+ isAnimating: () => this.animation.isAnimating,
3549
+ resyncAfterCancel: () => this.resyncAfterResize()
3413
3550
  }));
3414
3551
  }
3415
3552
  this.teardowns.add(installSliderKeyboard({
@@ -3445,9 +3582,9 @@ var BottomSheetEngine = class {
3445
3582
  return;
3446
3583
  }
3447
3584
  if (this.emitBeforeSnap(target, previousId)) {
3448
- const restore = this.snaps.findById(previousId);
3449
- if (restore) {
3450
- void this.animation.animateTo(restore.size, 0);
3585
+ const restore2 = this.snaps.findById(previousId);
3586
+ if (restore2) {
3587
+ void this.animation.animateTo(restore2.size, 0);
3451
3588
  }
3452
3589
  return;
3453
3590
  }
@@ -3670,8 +3807,23 @@ var BottomSheet = (props) => {
3670
3807
  props.onSnap?.(payload.id);
3671
3808
  });
3672
3809
  engine.on("before-snap", (payload) => props.onBeforeSnap?.(payload));
3673
- engine.on("dragstart", sync);
3674
- engine.on("dragend", sync);
3810
+ engine.on("before-close", (payload) => props.onBeforeClose?.(payload));
3811
+ engine.on("open", (payload) => props.onOpen?.(payload.id));
3812
+ engine.on("close", () => props.onClose?.());
3813
+ engine.on("opened", (payload) => props.onOpened?.(payload.id));
3814
+ engine.on("closed", () => props.onClosed?.());
3815
+ engine.on("dragstart", (payload) => {
3816
+ sync();
3817
+ props.onDragStart?.(payload);
3818
+ });
3819
+ engine.on("dragend", (payload) => {
3820
+ sync();
3821
+ props.onDragEnd?.(payload);
3822
+ });
3823
+ if (props.onDrag) engine.on("drag", (payload) => props.onDrag?.(payload));
3824
+ if (props.onProgress) {
3825
+ engine.on("progress", (payload) => props.onProgress?.(payload));
3826
+ }
3675
3827
  if (props.backdrop !== false && props.closeOnBackdrop !== false && backdropEl) {
3676
3828
  const onBackdropClick = () => {
3677
3829
  if (engine?.canDismiss()) void engine.close("backdrop");