@surdeddd/bottom-sheet 0.3.0 → 0.4.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 (45) hide show
  1. package/dist/{BottomSheetEngine-CAymrH-P.d.cts → BottomSheetEngine-BJrH7qY3.d.cts} +39 -3
  2. package/dist/{BottomSheetEngine-DcqcuudN.d.ts → BottomSheetEngine-L05jGZvU.d.ts} +39 -3
  3. package/dist/element.cjs +480 -89
  4. package/dist/element.d.cts +23 -4
  5. package/dist/element.d.ts +23 -4
  6. package/dist/element.iife.global.js +2 -2
  7. package/dist/element.js +480 -89
  8. package/dist/index.cjs +341 -80
  9. package/dist/index.d.cts +3 -2
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.js +341 -80
  12. package/dist/integrations/formik.d.cts +1 -1
  13. package/dist/integrations/formik.d.ts +1 -1
  14. package/dist/integrations/react-hook-form.d.cts +1 -1
  15. package/dist/integrations/react-hook-form.d.ts +1 -1
  16. package/dist/overlay.cjs +13 -2
  17. package/dist/overlay.js +13 -2
  18. package/dist/preact.d.cts +1 -1
  19. package/dist/preact.d.ts +1 -1
  20. package/dist/qwik.cjs +352 -81
  21. package/dist/qwik.d.cts +4 -2
  22. package/dist/qwik.d.ts +4 -2
  23. package/dist/qwik.js +352 -81
  24. package/dist/react.cjs +427 -87
  25. package/dist/react.d.cts +29 -4
  26. package/dist/react.d.ts +29 -4
  27. package/dist/react.js +427 -87
  28. package/dist/solid.cjs +341 -80
  29. package/dist/solid.d.cts +38 -2
  30. package/dist/solid.d.ts +38 -2
  31. package/dist/solid.js +341 -80
  32. package/dist/solid.source.jsx +341 -80
  33. package/dist/styles.css +57 -56
  34. package/dist/svelte-core.cjs +346 -80
  35. package/dist/svelte-core.js +346 -80
  36. package/dist/svelte-src/BottomSheet.svelte +103 -5
  37. package/dist/svelte.cjs +421 -88
  38. package/dist/svelte.d.cts +21 -0
  39. package/dist/svelte.d.ts +21 -0
  40. package/dist/svelte.js +421 -88
  41. package/dist/vue.cjs +584 -91
  42. package/dist/vue.d.cts +51 -2
  43. package/dist/vue.d.ts +51 -2
  44. package/dist/vue.js +585 -92
  45. package/package.json +6 -6
package/dist/react.cjs CHANGED
@@ -41,6 +41,15 @@ var SheetStack = class {
41
41
  size() {
42
42
  return this.entries.length;
43
43
  }
44
+ depthOf(id) {
45
+ const open = [];
46
+ for (const entry of this.entries) {
47
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
48
+ }
49
+ const idx = open.findIndex((e) => e.id === id);
50
+ if (idx === -1) return 0;
51
+ return open.length - 1 - idx;
52
+ }
44
53
  clear() {
45
54
  this.entries = [];
46
55
  }
@@ -165,7 +174,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
165
174
  var resolveSnap = (point, mode, measureFit) => {
166
175
  if (typeof point === "number") return Math.max(0, point);
167
176
  if (point === "full") return viewportSize(mode);
168
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
177
+ if (point === "fit" || point === "content") {
178
+ return Math.max(0, measureFit?.() ?? 0);
179
+ }
169
180
  const pctMatch = point.match(PERCENT);
170
181
  if (pctMatch) {
171
182
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -402,6 +413,42 @@ function notifyLinkedSheets(sheets, self) {
402
413
  }
403
414
 
404
415
  // src/core/features/route.ts
416
+ var closers = [];
417
+ var handlerInstalled = false;
418
+ 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;
426
+ }
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
+ }
436
+ }
437
+ if (!target) return;
438
+ target.consumed = true;
439
+ const idx = closers.indexOf(target);
440
+ if (idx !== -1) closers.splice(idx, 1);
441
+ target.close();
442
+ });
443
+ };
444
+ var programmaticBack = () => {
445
+ suppress += 1;
446
+ try {
447
+ history.back();
448
+ } catch {
449
+ suppress = Math.max(0, suppress - 1);
450
+ }
451
+ };
405
452
  function installRoute(deps) {
406
453
  if (typeof window === "undefined") {
407
454
  return () => {
@@ -411,82 +458,81 @@ function installRoute(deps) {
411
458
  return () => {
412
459
  };
413
460
  }
414
- let routePushed = false;
415
- let closeOnBackPushed = false;
416
- let detachPopstate = null;
417
- const popOurMarker = (key, expected) => {
418
- const state = history.state;
419
- if (state && state[key] === expected) {
420
- try {
421
- history.back();
422
- } catch {
423
- }
461
+ let pushed = false;
462
+ let closer = null;
463
+ const teardownEntry = () => {
464
+ if (closer) {
465
+ const idx = closers.indexOf(closer);
466
+ if (idx !== -1) closers.splice(idx, 1);
467
+ }
468
+ const wasConsumed = closer?.consumed ?? false;
469
+ closer = null;
470
+ if (pushed && !wasConsumed) {
471
+ pushed = false;
472
+ programmaticBack();
473
+ } else {
474
+ pushed = false;
424
475
  }
425
476
  };
426
477
  const onOpen = () => {
427
- if (deps.routedTo && !detachPopstate) {
428
- const target = deps.routedTo;
429
- try {
430
- history.pushState({ __bsRouted: target }, "", target);
431
- routePushed = true;
432
- } catch {
433
- }
434
- const onPop = (event) => {
435
- if (deps.isDestroyed()) return;
436
- const state = event.state;
437
- if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
438
- routePushed = false;
439
- void deps.close();
440
- }
441
- };
442
- window.addEventListener("popstate", onPop);
443
- detachPopstate = () => window.removeEventListener("popstate", onPop);
444
- } else if (deps.closeOnBack && !detachPopstate) {
445
- try {
478
+ if (closer) return;
479
+ closer = {
480
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
481
+ isTop: () => deps.isTopSheet(),
482
+ close: () => void deps.close(),
483
+ consumed: false
484
+ };
485
+ ensureHandler();
486
+ closers.push(closer);
487
+ try {
488
+ if (deps.routedTo !== void 0) {
489
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
490
+ } else {
446
491
  history.pushState({ __bsSheet: deps.sheetId }, "");
447
- closeOnBackPushed = true;
448
- } catch {
449
492
  }
450
- const onPop = () => {
451
- if (deps.isDestroyed()) return;
452
- if (deps.getSize() > 0 && deps.isTopSheet()) {
453
- const state = history.state;
454
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
455
- void deps.close();
456
- }
457
- };
458
- window.addEventListener("popstate", onPop);
459
- detachPopstate = () => window.removeEventListener("popstate", onPop);
460
- }
461
- };
462
- const onClose = () => {
463
- detachPopstate?.();
464
- detachPopstate = null;
465
- if (routePushed) {
466
- routePushed = false;
467
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
468
- }
469
- if (closeOnBackPushed) {
470
- closeOnBackPushed = false;
471
- popOurMarker("__bsSheet", deps.sheetId);
493
+ pushed = true;
494
+ } catch {
495
+ pushed = false;
472
496
  }
473
497
  };
474
498
  const unsubscribeOpen = deps.on("open", onOpen);
475
- const unsubscribeClose = deps.on("close", onClose);
499
+ const unsubscribeClose = deps.on("close", teardownEntry);
476
500
  if (deps.getSize() > 0) onOpen();
477
501
  return () => {
478
502
  unsubscribeOpen();
479
503
  unsubscribeClose();
480
- detachPopstate?.();
481
- detachPopstate = null;
482
- if (routePushed) {
483
- routePushed = false;
484
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
485
- }
486
- if (closeOnBackPushed) {
487
- closeOnBackPushed = false;
488
- popOurMarker("__bsSheet", deps.sheetId);
489
- }
504
+ teardownEntry();
505
+ };
506
+ }
507
+ function installRouteChange(deps) {
508
+ if (typeof window === "undefined" || typeof history === "undefined") {
509
+ return () => {
510
+ };
511
+ }
512
+ const onChange = () => {
513
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
514
+ void deps.close();
515
+ };
516
+ const wrap = (key) => {
517
+ const original = history[key];
518
+ const patched = function(...args) {
519
+ original.apply(this, args);
520
+ onChange();
521
+ };
522
+ history[key] = patched;
523
+ return () => {
524
+ if (history[key] === patched) {
525
+ history[key] = original;
526
+ }
527
+ };
528
+ };
529
+ window.addEventListener("popstate", onChange);
530
+ const restorePush = wrap("pushState");
531
+ const restoreReplace = wrap("replaceState");
532
+ return () => {
533
+ window.removeEventListener("popstate", onChange);
534
+ restorePush();
535
+ restoreReplace();
490
536
  };
491
537
  }
492
538
 
@@ -794,6 +840,7 @@ function applyOverlayPosition(ws, mode, position, inset) {
794
840
 
795
841
  // src/core/controllers/scrim-controller.ts
796
842
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
843
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
797
844
  var ScrimController = class {
798
845
  constructor(deps, opts) {
799
846
  __publicField(this, "screenComponent");
@@ -832,9 +879,11 @@ var ScrimController = class {
832
879
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
833
880
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
834
881
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
882
+ const explicitColor = opts.scrimColor ?? preset?.color;
883
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
835
884
  if (this.screenComponent) {
836
885
  this.applyScrimStyles({
837
- color: opts.scrimColor ?? preset?.color,
886
+ color: effectiveColor,
838
887
  blur: opts.scrimBlur ?? preset?.blur,
839
888
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
840
889
  });
@@ -1608,7 +1657,7 @@ var LifecycleController = class {
1608
1657
  this.releaseScrollLock = null;
1609
1658
  this.inertSiblings.remove();
1610
1659
  if (this.returnFocus) {
1611
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1660
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1612
1661
  target?.focus?.();
1613
1662
  }
1614
1663
  }
@@ -1775,6 +1824,7 @@ var GestureController = class {
1775
1824
  __publicField(this, "dragStartSize", 0);
1776
1825
  __publicField(this, "currentPointerKind", "touch");
1777
1826
  __publicField(this, "keyboardDismissed", false);
1827
+ __publicField(this, "dragSuppressed", false);
1778
1828
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1779
1829
  this.deps = deps;
1780
1830
  }
@@ -1785,6 +1835,11 @@ var GestureController = class {
1785
1835
  if (this.detach) return this.detach;
1786
1836
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1787
1837
  onStart: (_coord, kind) => {
1838
+ if (this.deps.getDisableDrag?.()) {
1839
+ this.dragSuppressed = true;
1840
+ return;
1841
+ }
1842
+ this.dragSuppressed = false;
1788
1843
  this.deps.cancelAnimation();
1789
1844
  this.isDragging_ = true;
1790
1845
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1795,6 +1850,7 @@ var GestureController = class {
1795
1850
  this.deps.emit("dragstart", { size: this.dragStartSize });
1796
1851
  },
1797
1852
  onMove: (delta) => {
1853
+ if (this.dragSuppressed) return;
1798
1854
  const ctx = this.deps.getDragContext();
1799
1855
  const { min, max } = ctx.range;
1800
1856
  const maxAxis = ctx.maxAxisSize;
@@ -1818,6 +1874,10 @@ var GestureController = class {
1818
1874
  }
1819
1875
  },
1820
1876
  onEnd: (delta, velocity, kind) => {
1877
+ if (this.dragSuppressed) {
1878
+ this.dragSuppressed = false;
1879
+ return;
1880
+ }
1821
1881
  this.isDragging_ = false;
1822
1882
  this.keyboardDismissed = false;
1823
1883
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1828,6 +1888,10 @@ var GestureController = class {
1828
1888
  this.deps.settleAfterDrag(delta, velocity, kind);
1829
1889
  },
1830
1890
  onCancel: () => {
1891
+ if (this.dragSuppressed) {
1892
+ this.dragSuppressed = false;
1893
+ return;
1894
+ }
1831
1895
  this.isDragging_ = false;
1832
1896
  this.keyboardDismissed = false;
1833
1897
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -2207,6 +2271,12 @@ function resolveEngineOptions(opts, extras) {
2207
2271
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2208
2272
  rubberBandEnabled: opts.rubberBand ?? true,
2209
2273
  closeOnBack: opts.closeOnBack ?? false,
2274
+ persistent: opts.persistent ?? false,
2275
+ disableClose: opts.disableClose ?? false,
2276
+ disableDrag: opts.disableDrag ?? false,
2277
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2278
+ radius: opts.radius,
2279
+ maxHeight: opts.maxHeight,
2210
2280
  initialAllowed,
2211
2281
  initialId,
2212
2282
  scrim: {
@@ -2233,7 +2303,8 @@ function resolveEngineOptions(opts, extras) {
2233
2303
  closeOnEscape: opts.closeOnEscape,
2234
2304
  lockBodyScroll: opts.lockBodyScroll,
2235
2305
  inertSiblings: opts.inertSiblings,
2236
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2306
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2307
+ returnFocus: opts.returnFocusTo
2237
2308
  }
2238
2309
  };
2239
2310
  }
@@ -2278,6 +2349,11 @@ var BottomSheetEngine = class {
2278
2349
  __publicField(this, "lifecycle");
2279
2350
  __publicField(this, "closeOnBack");
2280
2351
  __publicField(this, "routedTo");
2352
+ __publicField(this, "persistent");
2353
+ __publicField(this, "disableCloseFlag");
2354
+ __publicField(this, "disableDragFlag");
2355
+ __publicField(this, "closeOnRouteChange");
2356
+ __publicField(this, "maxHeightCap");
2281
2357
  __publicField(this, "snapPointsRaw");
2282
2358
  __publicField(this, "snaps");
2283
2359
  __publicField(this, "activeId");
@@ -2290,6 +2366,7 @@ var BottomSheetEngine = class {
2290
2366
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
2291
2367
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2292
2368
  __publicField(this, "isTopSheet", true);
2369
+ __publicField(this, "opening", false);
2293
2370
  __publicField(this, "progressPayload", {
2294
2371
  value: 0,
2295
2372
  size: 0
@@ -2346,7 +2423,8 @@ var BottomSheetEngine = class {
2346
2423
  void this.snapTo(id);
2347
2424
  },
2348
2425
  close: () => {
2349
- void this.close();
2426
+ if (!this.canDismiss()) return;
2427
+ void this.close("backdrop");
2350
2428
  }
2351
2429
  },
2352
2430
  resolved.scrim
@@ -2370,6 +2448,10 @@ var BottomSheetEngine = class {
2370
2448
  );
2371
2449
  this.closeOnBack = resolved.closeOnBack;
2372
2450
  this.routedTo = opts.routedTo;
2451
+ this.persistent = resolved.persistent;
2452
+ this.disableCloseFlag = resolved.disableClose;
2453
+ this.disableDragFlag = resolved.disableDrag;
2454
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2373
2455
  this.stackEffectEnabled = opts.stackEffect ?? false;
2374
2456
  this.snapPointsRaw = opts.snapPoints;
2375
2457
  this.persistKey = opts.persistKey;
@@ -2383,7 +2465,7 @@ var BottomSheetEngine = class {
2383
2465
  this.snapPointsRaw,
2384
2466
  resolved.initialAllowed,
2385
2467
  this.mode,
2386
- () => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
2468
+ () => this.measureFitSize(),
2387
2469
  (maxAxisSize) => {
2388
2470
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2389
2471
  }
@@ -2417,6 +2499,19 @@ var BottomSheetEngine = class {
2417
2499
  io.observe(this.element);
2418
2500
  this.teardowns.add(() => io.disconnect());
2419
2501
  }
2502
+ this.installFitObserver();
2503
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2504
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2505
+ this.applyAutoAriaLabelledBy();
2506
+ if (this.closeOnRouteChange) {
2507
+ this.teardowns.add(
2508
+ installRouteChange({
2509
+ isDestroyed: () => this.destroyed,
2510
+ getSize: () => this.size,
2511
+ close: () => this.close()
2512
+ })
2513
+ );
2514
+ }
2420
2515
  if (this.persistKey) {
2421
2516
  this.teardowns.add(installPersist(this, this.persistKey));
2422
2517
  }
@@ -2441,7 +2536,7 @@ var BottomSheetEngine = class {
2441
2536
  isTopSheet: () => this.isTopSheet,
2442
2537
  getSize: () => this.size,
2443
2538
  isDestroyed: () => this.destroyed,
2444
- close: () => this.close(),
2539
+ close: () => this.close("back"),
2445
2540
  on: (event, fn) => this.on(event, fn),
2446
2541
  sheetId: this.id
2447
2542
  })
@@ -2535,6 +2630,10 @@ var BottomSheetEngine = class {
2535
2630
  const previousSize = this.size;
2536
2631
  this.scrollCache.cache(previousId, previousSize, target.size);
2537
2632
  this.activeId = id;
2633
+ if (wasClosed && target.size > 0) {
2634
+ this.opening = true;
2635
+ sheetStack.promote(this.id);
2636
+ }
2538
2637
  if (this.animation.viewTransitionsAvailable) {
2539
2638
  this.currentViewTransition?.skipTransition?.();
2540
2639
  const vt = document.startViewTransition(() => {
@@ -2549,19 +2648,26 @@ var BottomSheetEngine = class {
2549
2648
  } else {
2550
2649
  await this.animation.animateTo(target.size, velocityPxPerMs);
2551
2650
  }
2651
+ this.opening = false;
2552
2652
  externalSignal?.removeEventListener("abort", onExternalAbort);
2553
2653
  if (signal.aborted) return;
2554
2654
  this.scrollCache.restore(id, previousSize, target.size);
2555
2655
  this.updateAriaSlider();
2556
- this.emit("snap", { id, size: target.size });
2656
+ this.emit("snap", {
2657
+ id,
2658
+ size: target.size,
2659
+ progress: this.computeProgress(target.size)
2660
+ });
2557
2661
  if (wasClosed && target.size > 0) {
2558
2662
  this.emit("open", { id });
2559
2663
  this.handleOpen();
2560
2664
  notifyLinkedSheets(this.linkedSheets, this);
2665
+ this.emit("opened", { id });
2561
2666
  }
2562
2667
  if (target.size === 0) {
2563
2668
  this.emit("close", void 0);
2564
2669
  this.handleClose();
2670
+ this.emit("closed", void 0);
2565
2671
  }
2566
2672
  }
2567
2673
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2586,11 +2692,74 @@ var BottomSheetEngine = class {
2586
2692
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2587
2693
  return this.snapTo(target);
2588
2694
  }
2589
- close() {
2695
+ close(reason = "programmatic") {
2590
2696
  if (this.destroyed) return Promise.resolve();
2697
+ if (this.disableCloseFlag) return Promise.resolve();
2698
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2591
2699
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2592
2700
  return this.snapTo(closedId ?? this.activeId);
2593
2701
  }
2702
+ canDismiss() {
2703
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2704
+ }
2705
+ isTop() {
2706
+ return this.isTopSheet;
2707
+ }
2708
+ depth() {
2709
+ return sheetStack.depthOf(this.id);
2710
+ }
2711
+ expand() {
2712
+ if (this.destroyed) return Promise.resolve();
2713
+ const ids = this.allowedIdsBySize();
2714
+ const target = ids[ids.length - 1];
2715
+ if (!target) return Promise.resolve();
2716
+ return this.snapTo(target);
2717
+ }
2718
+ collapse() {
2719
+ if (this.destroyed) return Promise.resolve();
2720
+ const ids = this.allowedIdsBySize();
2721
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2722
+ const target = nonZero ?? ids[0];
2723
+ if (!target) return Promise.resolve();
2724
+ return this.snapTo(target);
2725
+ }
2726
+ setRadius(r) {
2727
+ if (this.destroyed) return;
2728
+ this.element.style.setProperty(
2729
+ "--bs-radius",
2730
+ typeof r === "number" ? `${r}px` : r
2731
+ );
2732
+ }
2733
+ setMaxHeight(h) {
2734
+ if (this.destroyed) return;
2735
+ const value = typeof h === "number" ? `${h}px` : h;
2736
+ const axis = layoutAxis(this.mode);
2737
+ this.element.style.setProperty(
2738
+ axis === "height" ? "maxHeight" : "maxWidth",
2739
+ value
2740
+ );
2741
+ if (typeof h === "number") {
2742
+ this.maxHeightCap = h;
2743
+ this.clampToMaxHeight();
2744
+ } else if (typeof window !== "undefined") {
2745
+ const probe2 = document.createElement("div");
2746
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2747
+ document.body.appendChild(probe2);
2748
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2749
+ probe2.remove();
2750
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2751
+ this.clampToMaxHeight();
2752
+ }
2753
+ }
2754
+ clampToMaxHeight() {
2755
+ if (this.maxHeightCap === void 0) return;
2756
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2757
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2758
+ }
2759
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2760
+ this.applySize(this.maxHeightCap);
2761
+ }
2762
+ }
2594
2763
  setAllowed(ids, snap) {
2595
2764
  if (this.destroyed) return;
2596
2765
  this.snaps.setAllowedIds(ids);
@@ -2724,8 +2893,53 @@ var BottomSheetEngine = class {
2724
2893
  }
2725
2894
  this.updateAriaSlider();
2726
2895
  }
2896
+ recompute() {
2897
+ if (this.destroyed) return;
2898
+ this.snaps.recompute();
2899
+ this.scrim.invalidateOpacityCache();
2900
+ if (this.gesture?.isDragging) return;
2901
+ const current = this.snaps.findById(this.activeId);
2902
+ if (current) {
2903
+ this.size = current.size;
2904
+ this.applySize(this.size);
2905
+ this.updateAriaSlider();
2906
+ }
2907
+ }
2908
+ measureFitSize() {
2909
+ const vertical = layoutAxis(this.mode) === "height";
2910
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2911
+ const content = this.scrollContainer;
2912
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2913
+ if (typeof window === "undefined") return natural;
2914
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2915
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2916
+ }
2917
+ installFitObserver() {
2918
+ if (typeof ResizeObserver === "undefined") return;
2919
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2920
+ return;
2921
+ const targets = [this.handle];
2922
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2923
+ targets.push(this.scrollContainer);
2924
+ }
2925
+ let raf = 0;
2926
+ const ro = new ResizeObserver(() => {
2927
+ if (raf) return;
2928
+ raf = requestAnimationFrame(() => {
2929
+ raf = 0;
2930
+ if (this.destroyed || this.gesture?.isDragging) return;
2931
+ this.recompute();
2932
+ });
2933
+ });
2934
+ for (const t of targets) ro.observe(t);
2935
+ this.teardowns.add(() => {
2936
+ ro.disconnect();
2937
+ if (raf) cancelAnimationFrame(raf);
2938
+ });
2939
+ }
2727
2940
  destroy() {
2728
2941
  this.destroyed = true;
2942
+ this.opening = false;
2729
2943
  this.currentAbort.abort();
2730
2944
  this.currentViewTransition?.skipTransition?.();
2731
2945
  this.currentViewTransition = null;
@@ -2775,6 +2989,36 @@ var BottomSheetEngine = class {
2775
2989
  frozen = true;
2776
2990
  return cancelled;
2777
2991
  }
2992
+ emitBeforeClose(reason) {
2993
+ let cancelled = false;
2994
+ let frozen = false;
2995
+ this.emit("before-close", {
2996
+ reason,
2997
+ cancel: () => {
2998
+ if (frozen) {
2999
+ console.warn(
3000
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3001
+ );
3002
+ return;
3003
+ }
3004
+ cancelled = true;
3005
+ }
3006
+ });
3007
+ frozen = true;
3008
+ return cancelled;
3009
+ }
3010
+ applyAutoAriaLabelledBy() {
3011
+ if (typeof document === "undefined") return;
3012
+ if (!this.lifecycle.focusTrapEnabled) return;
3013
+ if (this.element.getAttribute("aria-labelledby")) return;
3014
+ const scope = this.handle ?? this.element;
3015
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3016
+ if (!titled) return;
3017
+ if (!titled.id) {
3018
+ titled.id = nextInstanceId("bs-title");
3019
+ }
3020
+ this.element.setAttribute("aria-labelledby", titled.id);
3021
+ }
2778
3022
  recomputeSnaps() {
2779
3023
  const seen = /* @__PURE__ */ new Set();
2780
3024
  for (const p of this.snapPointsRaw) {
@@ -2810,11 +3054,8 @@ var BottomSheetEngine = class {
2810
3054
  },
2811
3055
  setIsTop: (isTop) => {
2812
3056
  this.isTopSheet = isTop;
2813
- if (this.backdrop) {
2814
- this.backdrop.style.display = isTop ? "" : "none";
2815
- }
2816
3057
  },
2817
- isOpen: () => this.size > 0,
3058
+ isOpen: () => this.size > 0 || this.opening,
2818
3059
  setDepth: (depth) => this.applyStackDepth(depth)
2819
3060
  }));
2820
3061
  }
@@ -2846,6 +3087,7 @@ var BottomSheetEngine = class {
2846
3087
  buf.rubberBandEnabled = this.rubberBandEnabled;
2847
3088
  return buf;
2848
3089
  },
3090
+ getDisableDrag: () => this.disableDragFlag,
2849
3091
  cancelAnimation: () => this.animation.cancel(),
2850
3092
  applySize: (size) => this.applySize(size),
2851
3093
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2925,8 +3167,9 @@ var BottomSheetEngine = class {
2925
3167
  if (this.lifecycle.closeOnEscape) {
2926
3168
  const onKey = (e) => {
2927
3169
  if (this.destroyed || e.defaultPrevented) return;
3170
+ if (!this.canDismiss()) return;
2928
3171
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2929
- void this.close();
3172
+ void this.close("escape");
2930
3173
  }
2931
3174
  };
2932
3175
  document.addEventListener("keydown", onKey);
@@ -2989,20 +3232,31 @@ var BottomSheetEngine = class {
2989
3232
  const previousSnapSize = this.snaps.findById(previousId)?.size;
2990
3233
  this.scrollCache.cache(previousId, previousSize, target.size);
2991
3234
  this.activeId = target.id;
3235
+ if (previousSnapSize === 0 && target.size > 0) {
3236
+ this.opening = true;
3237
+ sheetStack.promote(this.id);
3238
+ }
2992
3239
  void this.animation.animateTo(target.size, velocity).then(() => {
3240
+ this.opening = false;
2993
3241
  if (signal.aborted) return;
2994
3242
  this.scrollCache.restore(target.id, previousSize, target.size);
2995
3243
  this.updateAriaSlider();
2996
- this.emit("snap", { id: target.id, size: target.size });
3244
+ this.emit("snap", {
3245
+ id: target.id,
3246
+ size: target.size,
3247
+ progress: this.computeProgress(target.size)
3248
+ });
2997
3249
  this.haptic();
2998
3250
  if (previousSnapSize === 0 && target.size > 0) {
2999
3251
  this.emit("open", { id: target.id });
3000
3252
  this.handleOpen();
3001
3253
  notifyLinkedSheets(this.linkedSheets, this);
3254
+ this.emit("opened", { id: target.id });
3002
3255
  }
3003
3256
  if (target.size === 0) {
3004
3257
  this.emit("close", void 0);
3005
3258
  this.handleClose();
3259
+ this.emit("closed", void 0);
3006
3260
  }
3007
3261
  });
3008
3262
  }
@@ -3071,17 +3325,24 @@ var BottomSheetEngine = class {
3071
3325
  const target = this.snaps.findById(this.activeId);
3072
3326
  if (!target) return;
3073
3327
  this.updateAriaSlider();
3074
- this.emit("snap", { id: target.id, size: target.size });
3328
+ this.emit("snap", {
3329
+ id: target.id,
3330
+ size: target.size,
3331
+ progress: this.computeProgress(target.size)
3332
+ });
3075
3333
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3076
3334
  this.emit("open", { id: target.id });
3077
3335
  this.handleOpen();
3078
3336
  notifyLinkedSheets(this.linkedSheets, this);
3337
+ this.emit("opened", { id: target.id });
3079
3338
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3080
3339
  this.emit("close", void 0);
3081
3340
  this.handleClose();
3341
+ this.emit("closed", void 0);
3082
3342
  }
3083
3343
  }
3084
3344
  handleClose() {
3345
+ this.opening = false;
3085
3346
  this.lifecycle.release();
3086
3347
  sheetStack.update();
3087
3348
  }
@@ -3131,8 +3392,23 @@ function useBottomSheet(opts) {
3131
3392
  refresh();
3132
3393
  optsRef.current.onSnap?.(payload.id);
3133
3394
  }),
3134
- engine.on("dragstart", refresh),
3135
- engine.on("dragend", refresh)
3395
+ engine.on("before-close", (payload) => {
3396
+ optsRef.current.onBeforeClose?.(payload);
3397
+ }),
3398
+ engine.on("opened", (payload) => {
3399
+ optsRef.current.onOpened?.(payload.id);
3400
+ }),
3401
+ engine.on("closed", () => {
3402
+ optsRef.current.onClosed?.();
3403
+ }),
3404
+ engine.on("dragstart", (payload) => {
3405
+ refresh();
3406
+ optsRef.current.onDragStart?.(payload);
3407
+ }),
3408
+ engine.on("dragend", (payload) => {
3409
+ refresh();
3410
+ optsRef.current.onDragEnd?.(payload);
3411
+ })
3136
3412
  ];
3137
3413
  return () => {
3138
3414
  offs.forEach((off) => off());
@@ -3170,9 +3446,19 @@ function useBottomSheet(opts) {
3170
3446
  []
3171
3447
  );
3172
3448
  const close = react.useCallback(
3173
- () => engineRef.current?.close() ?? Promise.resolve(),
3449
+ (reason) => engineRef.current?.close(reason) ?? Promise.resolve(),
3174
3450
  []
3175
3451
  );
3452
+ const expand = react.useCallback(
3453
+ () => engineRef.current?.expand() ?? Promise.resolve(),
3454
+ []
3455
+ );
3456
+ const collapse = react.useCallback(
3457
+ () => engineRef.current?.collapse() ?? Promise.resolve(),
3458
+ []
3459
+ );
3460
+ const isTop = react.useCallback(() => engineRef.current?.isTop() ?? false, []);
3461
+ const depth = react.useCallback(() => engineRef.current?.depth() ?? 0, []);
3176
3462
  const setAllowed = react.useCallback((ids, snap) => {
3177
3463
  engineRef.current?.setAllowed(ids, snap);
3178
3464
  }, []);
@@ -3187,6 +3473,7 @@ function useBottomSheet(opts) {
3187
3473
  }),
3188
3474
  []
3189
3475
  );
3476
+ const recompute = react.useCallback(() => engineRef.current?.recompute(), []);
3190
3477
  return react.useMemo(
3191
3478
  () => ({
3192
3479
  sheetRef,
@@ -3198,13 +3485,32 @@ function useBottomSheet(opts) {
3198
3485
  snapTo,
3199
3486
  open,
3200
3487
  close,
3488
+ expand,
3489
+ collapse,
3490
+ isTop,
3491
+ depth,
3201
3492
  setAllowed,
3202
3493
  addAnchor,
3203
3494
  setScrimStages,
3495
+ recompute,
3204
3496
  engine: engineRef.current,
3205
3497
  getEngine
3206
3498
  }),
3207
- [state, snapTo, open, close, setAllowed, addAnchor, setScrimStages, getEngine]
3499
+ [
3500
+ state,
3501
+ snapTo,
3502
+ open,
3503
+ close,
3504
+ expand,
3505
+ collapse,
3506
+ isTop,
3507
+ depth,
3508
+ setAllowed,
3509
+ addAnchor,
3510
+ setScrimStages,
3511
+ recompute,
3512
+ getEngine
3513
+ ]
3208
3514
  );
3209
3515
  }
3210
3516
  var useHasMounted = () => {
@@ -3227,6 +3533,11 @@ var BottomSheet = react.forwardRef(
3227
3533
  anchors,
3228
3534
  scrimStages,
3229
3535
  onChange,
3536
+ onBeforeClose,
3537
+ onOpened,
3538
+ onClosed,
3539
+ onDragStart,
3540
+ onDragEnd,
3230
3541
  ariaLabel = "Bottom sheet",
3231
3542
  ariaLabelledBy,
3232
3543
  noSSR = false,
@@ -3243,9 +3554,20 @@ var BottomSheet = react.forwardRef(
3243
3554
  snapTo,
3244
3555
  open,
3245
3556
  close,
3557
+ expand,
3558
+ collapse,
3559
+ isTop,
3560
+ depth,
3246
3561
  setAllowed,
3247
3562
  getEngine
3248
- } = useBottomSheet(engineOpts);
3563
+ } = useBottomSheet({
3564
+ ...engineOpts,
3565
+ onBeforeClose,
3566
+ onOpened,
3567
+ onClosed,
3568
+ onDragStart,
3569
+ onDragEnd
3570
+ });
3249
3571
  const ariaModal = engineOpts.focusTrap === true;
3250
3572
  const lastAnnouncedRef = react.useRef("");
3251
3573
  const [announce, setAnnounce] = react.useState("");
@@ -3261,6 +3583,10 @@ var BottomSheet = react.forwardRef(
3261
3583
  snapTo,
3262
3584
  open,
3263
3585
  close,
3586
+ expand,
3587
+ collapse,
3588
+ isTop,
3589
+ depth,
3264
3590
  setAllowed,
3265
3591
  setSnapPoints: (points, allowed) => getEngine()?.setSnapPoints(points, allowed),
3266
3592
  setScrim: (opts) => getEngine()?.setScrim(opts),
@@ -3270,12 +3596,24 @@ var BottomSheet = react.forwardRef(
3270
3596
  }),
3271
3597
  setScrimStages: (opts) => getEngine()?.setScrimStages(opts) ?? (() => {
3272
3598
  }),
3599
+ recompute: () => getEngine()?.recompute(),
3273
3600
  getEngine,
3274
3601
  get state() {
3275
3602
  return getEngine()?.state ?? state;
3276
3603
  }
3277
3604
  }),
3278
- [snapTo, open, close, setAllowed, getEngine, state]
3605
+ [
3606
+ snapTo,
3607
+ open,
3608
+ close,
3609
+ expand,
3610
+ collapse,
3611
+ isTop,
3612
+ depth,
3613
+ setAllowed,
3614
+ getEngine,
3615
+ state
3616
+ ]
3279
3617
  );
3280
3618
  react.useEffect(() => {
3281
3619
  onChange?.(state);
@@ -3342,7 +3680,9 @@ var BottomSheet = react.forwardRef(
3342
3680
  {
3343
3681
  ref: backdropRef,
3344
3682
  className: "bs-backdrop",
3345
- onClick: closeOnBackdrop ? () => void close() : void 0
3683
+ onClick: closeOnBackdrop ? () => {
3684
+ if (getEngine()?.canDismiss()) void close("backdrop");
3685
+ } : void 0
3346
3686
  }
3347
3687
  ),
3348
3688
  /* @__PURE__ */ jsxRuntime.jsx(