@surdeddd/bottom-sheet 0.3.0 → 0.4.1

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 +490 -91
  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 +490 -91
  8. package/dist/index.cjs +347 -80
  9. package/dist/index.d.cts +3 -2
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.js +347 -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 +358 -81
  21. package/dist/qwik.d.cts +4 -2
  22. package/dist/qwik.d.ts +4 -2
  23. package/dist/qwik.js +358 -81
  24. package/dist/react.cjs +451 -89
  25. package/dist/react.d.cts +29 -4
  26. package/dist/react.d.ts +29 -4
  27. package/dist/react.js +451 -89
  28. package/dist/solid.cjs +347 -80
  29. package/dist/solid.d.cts +38 -2
  30. package/dist/solid.d.ts +38 -2
  31. package/dist/solid.js +347 -80
  32. package/dist/solid.source.jsx +347 -80
  33. package/dist/styles.css +68 -55
  34. package/dist/svelte-core.cjs +352 -80
  35. package/dist/svelte-core.js +352 -80
  36. package/dist/svelte-src/BottomSheet.svelte +113 -15
  37. package/dist/svelte.cjs +474 -133
  38. package/dist/svelte.d.cts +21 -0
  39. package/dist/svelte.d.ts +21 -0
  40. package/dist/svelte.js +474 -133
  41. package/dist/vue.cjs +610 -109
  42. package/dist/vue.d.cts +51 -2
  43. package/dist/vue.d.ts +51 -2
  44. package/dist/vue.js +611 -110
  45. package/package.json +8 -8
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
  }
@@ -3025,6 +3279,9 @@ var BottomSheetEngine = class {
3025
3279
  if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3026
3280
  this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3027
3281
  }
3282
+ if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3283
+ this.rootEl.style.setProperty("--bs-size", `${size}px`);
3284
+ }
3028
3285
  }
3029
3286
  const progress = this.computeProgress(size);
3030
3287
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -3036,6 +3293,9 @@ var BottomSheetEngine = class {
3036
3293
  if (this.anchorHost) {
3037
3294
  this.anchorHost.style.setProperty("--bs-progress", String(progress));
3038
3295
  }
3296
+ if (this.rootEl && this.rootEl !== this.anchorHost) {
3297
+ this.rootEl.style.setProperty("--bs-progress", String(progress));
3298
+ }
3039
3299
  }
3040
3300
  this.scrim.applyOpacity(progress, progressChanged);
3041
3301
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -3069,17 +3329,24 @@ var BottomSheetEngine = class {
3069
3329
  const target = this.snaps.findById(this.activeId);
3070
3330
  if (!target) return;
3071
3331
  this.updateAriaSlider();
3072
- this.emit("snap", { id: target.id, size: target.size });
3332
+ this.emit("snap", {
3333
+ id: target.id,
3334
+ size: target.size,
3335
+ progress: this.computeProgress(target.size)
3336
+ });
3073
3337
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3074
3338
  this.emit("open", { id: target.id });
3075
3339
  this.handleOpen();
3076
3340
  notifyLinkedSheets(this.linkedSheets, this);
3341
+ this.emit("opened", { id: target.id });
3077
3342
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3078
3343
  this.emit("close", void 0);
3079
3344
  this.handleClose();
3345
+ this.emit("closed", void 0);
3080
3346
  }
3081
3347
  }
3082
3348
  handleClose() {
3349
+ this.opening = false;
3083
3350
  this.lifecycle.release();
3084
3351
  sheetStack.update();
3085
3352
  }
@@ -3129,8 +3396,23 @@ function useBottomSheet(opts) {
3129
3396
  refresh();
3130
3397
  optsRef.current.onSnap?.(payload.id);
3131
3398
  }),
3132
- engine.on("dragstart", refresh),
3133
- engine.on("dragend", refresh)
3399
+ engine.on("before-close", (payload) => {
3400
+ optsRef.current.onBeforeClose?.(payload);
3401
+ }),
3402
+ engine.on("opened", (payload) => {
3403
+ optsRef.current.onOpened?.(payload.id);
3404
+ }),
3405
+ engine.on("closed", () => {
3406
+ optsRef.current.onClosed?.();
3407
+ }),
3408
+ engine.on("dragstart", (payload) => {
3409
+ refresh();
3410
+ optsRef.current.onDragStart?.(payload);
3411
+ }),
3412
+ engine.on("dragend", (payload) => {
3413
+ refresh();
3414
+ optsRef.current.onDragEnd?.(payload);
3415
+ })
3134
3416
  ];
3135
3417
  return () => {
3136
3418
  offs.forEach((off) => off());
@@ -3168,9 +3450,19 @@ function useBottomSheet(opts) {
3168
3450
  []
3169
3451
  );
3170
3452
  const close = useCallback(
3171
- () => engineRef.current?.close() ?? Promise.resolve(),
3453
+ (reason) => engineRef.current?.close(reason) ?? Promise.resolve(),
3454
+ []
3455
+ );
3456
+ const expand = useCallback(
3457
+ () => engineRef.current?.expand() ?? Promise.resolve(),
3458
+ []
3459
+ );
3460
+ const collapse = useCallback(
3461
+ () => engineRef.current?.collapse() ?? Promise.resolve(),
3172
3462
  []
3173
3463
  );
3464
+ const isTop = useCallback(() => engineRef.current?.isTop() ?? false, []);
3465
+ const depth = useCallback(() => engineRef.current?.depth() ?? 0, []);
3174
3466
  const setAllowed = useCallback((ids, snap) => {
3175
3467
  engineRef.current?.setAllowed(ids, snap);
3176
3468
  }, []);
@@ -3185,6 +3477,7 @@ function useBottomSheet(opts) {
3185
3477
  }),
3186
3478
  []
3187
3479
  );
3480
+ const recompute = useCallback(() => engineRef.current?.recompute(), []);
3188
3481
  return useMemo(
3189
3482
  () => ({
3190
3483
  sheetRef,
@@ -3196,13 +3489,32 @@ function useBottomSheet(opts) {
3196
3489
  snapTo,
3197
3490
  open,
3198
3491
  close,
3492
+ expand,
3493
+ collapse,
3494
+ isTop,
3495
+ depth,
3199
3496
  setAllowed,
3200
3497
  addAnchor,
3201
3498
  setScrimStages,
3499
+ recompute,
3202
3500
  engine: engineRef.current,
3203
3501
  getEngine
3204
3502
  }),
3205
- [state, snapTo, open, close, setAllowed, addAnchor, setScrimStages, getEngine]
3503
+ [
3504
+ state,
3505
+ snapTo,
3506
+ open,
3507
+ close,
3508
+ expand,
3509
+ collapse,
3510
+ isTop,
3511
+ depth,
3512
+ setAllowed,
3513
+ addAnchor,
3514
+ setScrimStages,
3515
+ recompute,
3516
+ getEngine
3517
+ ]
3206
3518
  );
3207
3519
  }
3208
3520
  var useHasMounted = () => {
@@ -3225,6 +3537,11 @@ var BottomSheet = forwardRef(
3225
3537
  anchors,
3226
3538
  scrimStages,
3227
3539
  onChange,
3540
+ onBeforeClose,
3541
+ onOpened,
3542
+ onClosed,
3543
+ onDragStart,
3544
+ onDragEnd,
3228
3545
  ariaLabel = "Bottom sheet",
3229
3546
  ariaLabelledBy,
3230
3547
  noSSR = false,
@@ -3241,9 +3558,20 @@ var BottomSheet = forwardRef(
3241
3558
  snapTo,
3242
3559
  open,
3243
3560
  close,
3561
+ expand,
3562
+ collapse,
3563
+ isTop,
3564
+ depth,
3244
3565
  setAllowed,
3245
3566
  getEngine
3246
- } = useBottomSheet(engineOpts);
3567
+ } = useBottomSheet({
3568
+ ...engineOpts,
3569
+ onBeforeClose,
3570
+ onOpened,
3571
+ onClosed,
3572
+ onDragStart,
3573
+ onDragEnd
3574
+ });
3247
3575
  const ariaModal = engineOpts.focusTrap === true;
3248
3576
  const lastAnnouncedRef = useRef("");
3249
3577
  const [announce, setAnnounce] = useState("");
@@ -3259,6 +3587,10 @@ var BottomSheet = forwardRef(
3259
3587
  snapTo,
3260
3588
  open,
3261
3589
  close,
3590
+ expand,
3591
+ collapse,
3592
+ isTop,
3593
+ depth,
3262
3594
  setAllowed,
3263
3595
  setSnapPoints: (points, allowed) => getEngine()?.setSnapPoints(points, allowed),
3264
3596
  setScrim: (opts) => getEngine()?.setScrim(opts),
@@ -3268,12 +3600,24 @@ var BottomSheet = forwardRef(
3268
3600
  }),
3269
3601
  setScrimStages: (opts) => getEngine()?.setScrimStages(opts) ?? (() => {
3270
3602
  }),
3603
+ recompute: () => getEngine()?.recompute(),
3271
3604
  getEngine,
3272
3605
  get state() {
3273
3606
  return getEngine()?.state ?? state;
3274
3607
  }
3275
3608
  }),
3276
- [snapTo, open, close, setAllowed, getEngine, state]
3609
+ [
3610
+ snapTo,
3611
+ open,
3612
+ close,
3613
+ expand,
3614
+ collapse,
3615
+ isTop,
3616
+ depth,
3617
+ setAllowed,
3618
+ getEngine,
3619
+ state
3620
+ ]
3277
3621
  );
3278
3622
  useEffect(() => {
3279
3623
  onChange?.(state);
@@ -3340,7 +3684,9 @@ var BottomSheet = forwardRef(
3340
3684
  {
3341
3685
  ref: backdropRef,
3342
3686
  className: "bs-backdrop",
3343
- onClick: closeOnBackdrop ? () => void close() : void 0
3687
+ onClick: closeOnBackdrop ? () => {
3688
+ if (getEngine()?.canDismiss()) void close("backdrop");
3689
+ } : void 0
3344
3690
  }
3345
3691
  ),
3346
3692
  /* @__PURE__ */ jsx(
@@ -3364,8 +3710,6 @@ var BottomSheet = forwardRef(
3364
3710
  "aria-label": ariaLabelledBy ? void 0 : ariaLabel,
3365
3711
  "aria-labelledby": ariaLabelledBy,
3366
3712
  children: [
3367
- leftButton && /* @__PURE__ */ jsx("div", { className: "bs-button-slot", "data-side": "left", children: leftButton }),
3368
- rightButton && /* @__PURE__ */ jsx("div", { className: "bs-button-slot", "data-side": "right", children: rightButton }),
3369
3713
  /* @__PURE__ */ jsx(
3370
3714
  "div",
3371
3715
  {
@@ -3392,6 +3736,24 @@ var BottomSheet = forwardRef(
3392
3736
  ]
3393
3737
  }
3394
3738
  ),
3739
+ leftButton && /* @__PURE__ */ jsx(
3740
+ "div",
3741
+ {
3742
+ className: "bs-button-slot",
3743
+ "data-side": "left",
3744
+ "data-mode": engineOpts.mode ?? "bottom",
3745
+ children: leftButton
3746
+ }
3747
+ ),
3748
+ rightButton && /* @__PURE__ */ jsx(
3749
+ "div",
3750
+ {
3751
+ className: "bs-button-slot",
3752
+ "data-side": "right",
3753
+ "data-mode": engineOpts.mode ?? "bottom",
3754
+ children: rightButton
3755
+ }
3756
+ ),
3395
3757
  anchorHosts.map(
3396
3758
  (host, i) => anchors?.[i] ? createPortal(anchors[i].node, host) : null
3397
3759
  ),