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