@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/solid.cjs CHANGED
@@ -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) {
@@ -400,7 +427,9 @@ function notifyLinkedSheets(sheets, self) {
400
427
  try {
401
428
  if (linked.state.size === 0) continue;
402
429
  const ids = linked.getAllowedIds();
403
- const target = ids.find((id) => id !== "closed");
430
+ const resolved = linked.getResolvedSnaps();
431
+ const sizeOf = (id) => resolved.find((s) => s.id === id)?.size ?? 0;
432
+ const target = ids.find((id) => sizeOf(id) > 0);
404
433
  if (target && target !== linked.state.activeId) {
405
434
  void linked.snapTo(target);
406
435
  }
@@ -412,46 +441,198 @@ function notifyLinkedSheets(sheets, self) {
412
441
  }
413
442
  }
414
443
 
415
- // src/core/features/route.ts
416
- var closers = [];
417
- var handlerInstalled = false;
444
+ // src/core/features/history-coordinator.ts
445
+ var entries = [];
446
+ var subscribers = /* @__PURE__ */ new Set();
418
447
  var suppress = 0;
419
- var ensureHandler = () => {
420
- if (handlerInstalled || typeof window === "undefined") return;
421
- handlerInstalled = true;
422
- window.addEventListener("popstate", () => {
423
- if (suppress > 0) {
424
- suppress -= 1;
425
- return;
448
+ var internalDepth = 0;
449
+ var pendingRebrand = null;
450
+ var listenerInstalled = false;
451
+ var patchInstalled = false;
452
+ var origPushState = null;
453
+ var origReplaceState = null;
454
+ var patchedPushState = null;
455
+ var patchedReplaceState = null;
456
+ var hasWindow = () => typeof window !== "undefined";
457
+ var hasHistory = () => typeof history !== "undefined";
458
+ var runInternal = (fn) => {
459
+ internalDepth += 1;
460
+ try {
461
+ fn();
462
+ } finally {
463
+ internalDepth -= 1;
464
+ }
465
+ };
466
+ var notifyRouteChange = () => {
467
+ const snapshot = Array.from(subscribers);
468
+ for (const fn of snapshot) fn();
469
+ };
470
+ var ensureListener = () => {
471
+ if (listenerInstalled || !hasWindow()) return;
472
+ window.addEventListener("popstate", onPopState);
473
+ listenerInstalled = true;
474
+ };
475
+ var maybeTeardownListener = () => {
476
+ if (!listenerInstalled) return;
477
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
478
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
479
+ listenerInstalled = false;
480
+ };
481
+ var installPatch = () => {
482
+ if (patchInstalled || !hasHistory()) return;
483
+ origPushState = history.pushState;
484
+ origReplaceState = history.replaceState;
485
+ patchedPushState = function(...args) {
486
+ origPushState.apply(this, args);
487
+ if (internalDepth === 0) notifyRouteChange();
488
+ };
489
+ patchedReplaceState = function(...args) {
490
+ origReplaceState.apply(this, args);
491
+ if (internalDepth === 0) notifyRouteChange();
492
+ };
493
+ history.pushState = patchedPushState;
494
+ history.replaceState = patchedReplaceState;
495
+ patchInstalled = true;
496
+ };
497
+ var removePatch = () => {
498
+ if (!patchInstalled) return;
499
+ if (history.pushState === patchedPushState && origPushState) {
500
+ history.pushState = origPushState;
501
+ }
502
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
503
+ history.replaceState = origReplaceState;
504
+ }
505
+ origPushState = null;
506
+ origReplaceState = null;
507
+ patchedPushState = null;
508
+ patchedReplaceState = null;
509
+ patchInstalled = false;
510
+ };
511
+ function onPopState() {
512
+ if (suppress > 0) {
513
+ suppress -= 1;
514
+ const rebrand = pendingRebrand;
515
+ pendingRebrand = null;
516
+ if (rebrand) rebrand();
517
+ maybeTeardownListener();
518
+ return;
519
+ }
520
+ if (entries.length === 0) {
521
+ notifyRouteChange();
522
+ maybeTeardownListener();
523
+ return;
524
+ }
525
+ let target = null;
526
+ let targetIdx = -1;
527
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
528
+ const entry = entries[i];
529
+ if (!entry.surface.isOpen()) continue;
530
+ if (!target) {
531
+ target = entry;
532
+ targetIdx = i;
426
533
  }
427
- let target = null;
428
- for (let i = closers.length - 1; i >= 0; i -= 1) {
429
- const c = closers[i];
430
- if (!c.isOpen()) continue;
431
- if (!target) target = c;
432
- if (c.isTop()) {
433
- target = c;
434
- break;
435
- }
534
+ if (entry.surface.isTop()) {
535
+ target = entry;
536
+ targetIdx = i;
537
+ break;
436
538
  }
437
- if (!target) return;
438
- target.consumed = true;
439
- const idx = closers.indexOf(target);
440
- if (idx !== -1) closers.splice(idx, 1);
441
- const settled = target;
442
- void Promise.resolve(settled.close()).then(() => {
443
- if (settled.isOpen()) settled.restore();
444
- });
539
+ }
540
+ if (!target) {
541
+ const removed = entries.pop();
542
+ if (removed) removed.live = false;
543
+ maybeTeardownListener();
544
+ return;
545
+ }
546
+ target.live = false;
547
+ entries.splice(targetIdx, 1);
548
+ const settled = target;
549
+ void Promise.resolve(settled.surface.close()).then(() => {
550
+ if (settled.surface.isOpen()) restore(settled);
445
551
  });
446
- };
447
- var programmaticBack = () => {
448
- suppress += 1;
449
- try {
450
- history.back();
451
- } catch {
452
- suppress = Math.max(0, suppress - 1);
552
+ maybeTeardownListener();
553
+ }
554
+ function restore(entry) {
555
+ if (!hasWindow() || !hasHistory()) return;
556
+ entry.live = true;
557
+ runInternal(() => {
558
+ try {
559
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
560
+ } catch {
561
+ entry.live = false;
562
+ }
563
+ });
564
+ if (entry.live) {
565
+ entries.push(entry);
566
+ ensureListener();
453
567
  }
454
- };
568
+ }
569
+ function pushBackMarker(surface) {
570
+ const entry = { surface, priorUrl: void 0, live: false };
571
+ if (!hasWindow() || !hasHistory()) {
572
+ return entry;
573
+ }
574
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
575
+ runInternal(() => {
576
+ try {
577
+ history.pushState(surface.markerState(), "", surface.url);
578
+ entry.live = true;
579
+ } catch {
580
+ entry.live = false;
581
+ }
582
+ });
583
+ if (entry.live) {
584
+ entries.push(entry);
585
+ ensureListener();
586
+ }
587
+ return entry;
588
+ }
589
+ function popBackMarker(handle) {
590
+ if (!hasWindow() || !hasHistory()) return;
591
+ const entry = handle;
592
+ const idx = entries.indexOf(entry);
593
+ if (idx === -1 || !entry.live) return;
594
+ const last = entries.length - 1;
595
+ entry.live = false;
596
+ entries.splice(idx, 1);
597
+ if (idx !== last) {
598
+ const rebrandSurface = entries[entries.length - 1].surface;
599
+ const rebrandUrl = entry.priorUrl;
600
+ pendingRebrand = () => {
601
+ runInternal(() => {
602
+ history.replaceState(
603
+ rebrandSurface.markerState(),
604
+ "",
605
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
606
+ );
607
+ });
608
+ };
609
+ }
610
+ suppress += 1;
611
+ runInternal(() => {
612
+ try {
613
+ history.back();
614
+ } catch {
615
+ suppress = Math.max(0, suppress - 1);
616
+ pendingRebrand = null;
617
+ }
618
+ });
619
+ maybeTeardownListener();
620
+ }
621
+ function subscribeRouteChange(fn) {
622
+ if (!hasWindow() || !hasHistory()) return () => {
623
+ };
624
+ subscribers.add(fn);
625
+ if (subscribers.size === 1) installPatch();
626
+ ensureListener();
627
+ return () => {
628
+ if (!subscribers.has(fn)) return;
629
+ subscribers.delete(fn);
630
+ if (subscribers.size === 0) removePatch();
631
+ maybeTeardownListener();
632
+ };
633
+ }
634
+
635
+ // src/core/features/route.ts
455
636
  function installRoute(deps) {
456
637
  if (typeof window === "undefined") {
457
638
  return () => {
@@ -461,92 +642,39 @@ function installRoute(deps) {
461
642
  return () => {
462
643
  };
463
644
  }
464
- let pushed = false;
465
- let closer = null;
466
- const teardownEntry = () => {
467
- if (closer) {
468
- const idx = closers.indexOf(closer);
469
- if (idx !== -1) closers.splice(idx, 1);
470
- }
471
- const wasConsumed = closer?.consumed ?? false;
472
- closer = null;
473
- if (pushed && !wasConsumed) {
474
- pushed = false;
475
- programmaticBack();
476
- } else {
477
- pushed = false;
478
- }
479
- };
480
- const pushEntry = () => {
481
- try {
482
- if (deps.routedTo !== void 0) {
483
- history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
484
- } else {
485
- history.pushState({ __bsSheet: deps.sheetId }, "");
486
- }
487
- pushed = true;
488
- } catch {
489
- pushed = false;
490
- }
491
- };
645
+ let handle = null;
492
646
  const onOpen = () => {
493
- if (closer) return;
494
- const self = {
647
+ if (handle) return;
648
+ handle = pushBackMarker({
495
649
  isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
496
- isTop: () => deps.isTopSheet(),
497
- close: () => deps.close(),
498
- restore: () => {
499
- if (deps.isDestroyed() || closer !== self) return;
500
- self.consumed = false;
501
- if (!closers.includes(self)) closers.push(self);
502
- pushEntry();
503
- },
504
- consumed: false
505
- };
506
- closer = self;
507
- ensureHandler();
508
- closers.push(self);
509
- pushEntry();
650
+ isTop: deps.isTopSheet,
651
+ close: deps.close,
652
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
653
+ url: deps.routedTo
654
+ });
655
+ };
656
+ const onClose = () => {
657
+ if (!handle) return;
658
+ popBackMarker(handle);
659
+ handle = null;
510
660
  };
511
661
  const unsubscribeOpen = deps.on("open", onOpen);
512
- const unsubscribeClose = deps.on("close", teardownEntry);
662
+ const unsubscribeClose = deps.on("close", onClose);
513
663
  if (deps.getSize() > 0) onOpen();
514
664
  return () => {
515
665
  unsubscribeOpen();
516
666
  unsubscribeClose();
517
- teardownEntry();
667
+ if (handle) {
668
+ popBackMarker(handle);
669
+ handle = null;
670
+ }
518
671
  };
519
672
  }
520
673
  function installRouteChange(deps) {
521
- if (typeof window === "undefined" || typeof history === "undefined") {
522
- return () => {
523
- };
524
- }
525
- const onChange = () => {
674
+ return subscribeRouteChange(() => {
526
675
  if (deps.isDestroyed() || deps.getSize() <= 0) return;
527
676
  void deps.close();
528
- };
529
- const wrap = (key) => {
530
- const original = history[key];
531
- const patched = function(...args) {
532
- original.apply(this, args);
533
- onChange();
534
- };
535
- history[key] = patched;
536
- return () => {
537
- if (history[key] === patched) {
538
- history[key] = original;
539
- }
540
- };
541
- };
542
- window.addEventListener("popstate", onChange);
543
- const restorePush = wrap("pushState");
544
- const restoreReplace = wrap("replaceState");
545
- return () => {
546
- window.removeEventListener("popstate", onChange);
547
- restorePush();
548
- restoreReplace();
549
- };
677
+ });
550
678
  }
551
679
 
552
680
  // src/core/features/content-swipe.ts
@@ -617,11 +745,15 @@ function installVisualViewport(deps) {
617
745
  if (clamp < deps.getMaxAxisSize()) {
618
746
  deps.setMaxAxisSize(clamp);
619
747
  }
748
+ const wasAnimating = deps.isAnimating?.() ?? false;
749
+ deps.cancelInFlight();
750
+ deps.newCycle();
620
751
  const max = deps.getMaxAxisSize();
621
752
  deps.setSize(Math.min(deps.getSize(), max));
622
753
  const current = deps.resolveActiveSnap();
623
754
  if (current && !deps.isDragging()) {
624
755
  deps.applySize(Math.min(current.size, max));
756
+ if (wasAnimating) deps.resyncAfterCancel?.();
625
757
  } else {
626
758
  deps.applySize(deps.getSize());
627
759
  }
@@ -762,6 +894,54 @@ function installFitObserver(deps) {
762
894
  };
763
895
  }
764
896
 
897
+ // src/core/features/max-height-controller.ts
898
+ function createMaxHeightController(deps) {
899
+ let maxHeightCap;
900
+ let maxHeightRaw;
901
+ const resolveCap = () => {
902
+ const raw = maxHeightRaw;
903
+ if (raw === void 0) {
904
+ maxHeightCap = void 0;
905
+ return;
906
+ }
907
+ if (typeof raw === "number") {
908
+ maxHeightCap = raw;
909
+ return;
910
+ }
911
+ if (typeof window === "undefined") return;
912
+ const measured = resolveSnap(raw, deps.mode);
913
+ maxHeightCap = measured > 0 ? measured : void 0;
914
+ };
915
+ const clampTo = () => {
916
+ if (maxHeightCap === void 0) return;
917
+ if (deps.getMaxAxisSize() > maxHeightCap) {
918
+ deps.setMaxAxisSize(maxHeightCap);
919
+ }
920
+ };
921
+ return {
922
+ setRadius(r) {
923
+ deps.element.style.setProperty(
924
+ "--bs-radius",
925
+ typeof r === "number" ? `${r}px` : r
926
+ );
927
+ },
928
+ setMaxHeight(h) {
929
+ const value = typeof h === "number" ? `${h}px` : h;
930
+ const axis = layoutAxis(deps.mode);
931
+ deps.element.style.setProperty(
932
+ axis === "height" ? "max-height" : "max-width",
933
+ value
934
+ );
935
+ maxHeightRaw = h;
936
+ resolveCap();
937
+ deps.recompute();
938
+ },
939
+ resolveCap,
940
+ clampTo,
941
+ getCap: () => maxHeightCap
942
+ };
943
+ }
944
+
765
945
  // src/core/features/fit-measurement.ts
766
946
  var measureSheetNatural = (element, content, vertical) => {
767
947
  if (!content) {
@@ -893,13 +1073,6 @@ var WriteSentinel = class {
893
1073
  }
894
1074
  };
895
1075
 
896
- // src/core/primitives/devWarn.ts
897
- function devWarn(...args) {
898
- if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
899
- console.warn(...args);
900
- }
901
- }
902
-
903
1076
  // src/core/primitives/overlay-position.ts
904
1077
  function resolveSheetAnchoredStyle(mode, position, inset) {
905
1078
  const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
@@ -2354,7 +2527,7 @@ var matchesRange = (def, progress) => {
2354
2527
  };
2355
2528
  function installScrimStages(deps, opts) {
2356
2529
  const doc = deps.host.ownerDocument;
2357
- const entries = opts.stages.map((def) => {
2530
+ const entries2 = opts.stages.map((def) => {
2358
2531
  const wrapper = doc.createElement("div");
2359
2532
  wrapper.className = "bs-scrim-stage";
2360
2533
  const ws = wrapper.style;
@@ -2375,9 +2548,9 @@ function installScrimStages(deps, opts) {
2375
2548
  const resolveActive = () => {
2376
2549
  const state = deps.getState();
2377
2550
  if (state.size === 0) {
2378
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2551
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? null;
2379
2552
  }
2380
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2553
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? entries2.find((e) => matchesRange(e.def, state.progress)) ?? null;
2381
2554
  };
2382
2555
  const hide = (entry, animate) => {
2383
2556
  entry.inFlight?.cancel();
@@ -2426,13 +2599,13 @@ function installScrimStages(deps, opts) {
2426
2599
  deps.on("open", () => update(true)),
2427
2600
  deps.on("close", () => update(true))
2428
2601
  ];
2429
- if (entries.some((e) => e.def.forRange)) {
2602
+ if (entries2.some((e) => e.def.forRange)) {
2430
2603
  offs.push(deps.on("progress", () => update(true)));
2431
2604
  }
2432
2605
  update(false);
2433
2606
  return () => {
2434
2607
  offs.forEach((off) => off());
2435
- for (const entry of entries) {
2608
+ for (const entry of entries2) {
2436
2609
  entry.inFlight?.cancel();
2437
2610
  entry.inFlight = null;
2438
2611
  if (entry.wrapper.parentNode) {
@@ -2545,8 +2718,7 @@ var BottomSheetEngine = class {
2545
2718
  __publicField(this, "disableCloseFlag");
2546
2719
  __publicField(this, "disableDragFlag");
2547
2720
  __publicField(this, "closeOnRouteChange");
2548
- __publicField(this, "maxHeightCap");
2549
- __publicField(this, "maxHeightRaw");
2721
+ __publicField(this, "maxHeight");
2550
2722
  __publicField(this, "snapPointsRaw");
2551
2723
  __publicField(this, "snaps");
2552
2724
  __publicField(this, "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
  }
@@ -3675,8 +3812,23 @@ var BottomSheet = (props) => {
3675
3812
  props.onSnap?.(payload.id);
3676
3813
  });
3677
3814
  engine.on("before-snap", (payload) => props.onBeforeSnap?.(payload));
3678
- engine.on("dragstart", sync);
3679
- engine.on("dragend", sync);
3815
+ engine.on("before-close", (payload) => props.onBeforeClose?.(payload));
3816
+ engine.on("open", (payload) => props.onOpen?.(payload.id));
3817
+ engine.on("close", () => props.onClose?.());
3818
+ engine.on("opened", (payload) => props.onOpened?.(payload.id));
3819
+ engine.on("closed", () => props.onClosed?.());
3820
+ engine.on("dragstart", (payload) => {
3821
+ sync();
3822
+ props.onDragStart?.(payload);
3823
+ });
3824
+ engine.on("dragend", (payload) => {
3825
+ sync();
3826
+ props.onDragEnd?.(payload);
3827
+ });
3828
+ if (props.onDrag) engine.on("drag", (payload) => props.onDrag?.(payload));
3829
+ if (props.onProgress) {
3830
+ engine.on("progress", (payload) => props.onProgress?.(payload));
3831
+ }
3680
3832
  if (props.backdrop !== false && props.closeOnBackdrop !== false && backdropEl) {
3681
3833
  const onBackdropClick = () => {
3682
3834
  if (engine?.canDismiss()) void engine.close("backdrop");