@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/index.cjs CHANGED
@@ -37,6 +37,15 @@ var SheetStack = class {
37
37
  size() {
38
38
  return this.entries.length;
39
39
  }
40
+ depthOf(id) {
41
+ const open = [];
42
+ for (const entry of this.entries) {
43
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
44
+ }
45
+ const idx = open.findIndex((e) => e.id === id);
46
+ if (idx === -1) return 0;
47
+ return open.length - 1 - idx;
48
+ }
40
49
  clear() {
41
50
  this.entries = [];
42
51
  }
@@ -161,7 +170,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
161
170
  var resolveSnap = (point, mode, measureFit) => {
162
171
  if (typeof point === "number") return Math.max(0, point);
163
172
  if (point === "full") return viewportSize(mode);
164
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
173
+ if (point === "fit" || point === "content") {
174
+ return Math.max(0, measureFit?.() ?? 0);
175
+ }
165
176
  const pctMatch = point.match(PERCENT);
166
177
  if (pctMatch) {
167
178
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -398,6 +409,42 @@ function notifyLinkedSheets(sheets, self) {
398
409
  }
399
410
 
400
411
  // src/core/features/route.ts
412
+ var closers = [];
413
+ var handlerInstalled = false;
414
+ var suppress = 0;
415
+ var ensureHandler = () => {
416
+ if (handlerInstalled || typeof window === "undefined") return;
417
+ handlerInstalled = true;
418
+ window.addEventListener("popstate", () => {
419
+ if (suppress > 0) {
420
+ suppress -= 1;
421
+ return;
422
+ }
423
+ let target = null;
424
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
425
+ const c = closers[i];
426
+ if (!c.isOpen()) continue;
427
+ if (!target) target = c;
428
+ if (c.isTop()) {
429
+ target = c;
430
+ break;
431
+ }
432
+ }
433
+ if (!target) return;
434
+ target.consumed = true;
435
+ const idx = closers.indexOf(target);
436
+ if (idx !== -1) closers.splice(idx, 1);
437
+ target.close();
438
+ });
439
+ };
440
+ var programmaticBack = () => {
441
+ suppress += 1;
442
+ try {
443
+ history.back();
444
+ } catch {
445
+ suppress = Math.max(0, suppress - 1);
446
+ }
447
+ };
401
448
  function installRoute(deps) {
402
449
  if (typeof window === "undefined") {
403
450
  return () => {
@@ -407,82 +454,81 @@ function installRoute(deps) {
407
454
  return () => {
408
455
  };
409
456
  }
410
- let routePushed = false;
411
- let closeOnBackPushed = false;
412
- let detachPopstate = null;
413
- const popOurMarker = (key, expected) => {
414
- const state = history.state;
415
- if (state && state[key] === expected) {
416
- try {
417
- history.back();
418
- } catch {
419
- }
457
+ let pushed = false;
458
+ let closer = null;
459
+ const teardownEntry = () => {
460
+ if (closer) {
461
+ const idx = closers.indexOf(closer);
462
+ if (idx !== -1) closers.splice(idx, 1);
463
+ }
464
+ const wasConsumed = closer?.consumed ?? false;
465
+ closer = null;
466
+ if (pushed && !wasConsumed) {
467
+ pushed = false;
468
+ programmaticBack();
469
+ } else {
470
+ pushed = false;
420
471
  }
421
472
  };
422
473
  const onOpen = () => {
423
- if (deps.routedTo && !detachPopstate) {
424
- const target = deps.routedTo;
425
- try {
426
- history.pushState({ __bsRouted: target }, "", target);
427
- routePushed = true;
428
- } catch {
429
- }
430
- const onPop = (event) => {
431
- if (deps.isDestroyed()) return;
432
- const state = event.state;
433
- if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
434
- routePushed = false;
435
- void deps.close();
436
- }
437
- };
438
- window.addEventListener("popstate", onPop);
439
- detachPopstate = () => window.removeEventListener("popstate", onPop);
440
- } else if (deps.closeOnBack && !detachPopstate) {
441
- try {
474
+ if (closer) return;
475
+ closer = {
476
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
477
+ isTop: () => deps.isTopSheet(),
478
+ close: () => void deps.close(),
479
+ consumed: false
480
+ };
481
+ ensureHandler();
482
+ closers.push(closer);
483
+ try {
484
+ if (deps.routedTo !== void 0) {
485
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
486
+ } else {
442
487
  history.pushState({ __bsSheet: deps.sheetId }, "");
443
- closeOnBackPushed = true;
444
- } catch {
445
488
  }
446
- const onPop = () => {
447
- if (deps.isDestroyed()) return;
448
- if (deps.getSize() > 0 && deps.isTopSheet()) {
449
- const state = history.state;
450
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
451
- void deps.close();
452
- }
453
- };
454
- window.addEventListener("popstate", onPop);
455
- detachPopstate = () => window.removeEventListener("popstate", onPop);
456
- }
457
- };
458
- const onClose = () => {
459
- detachPopstate?.();
460
- detachPopstate = null;
461
- if (routePushed) {
462
- routePushed = false;
463
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
464
- }
465
- if (closeOnBackPushed) {
466
- closeOnBackPushed = false;
467
- popOurMarker("__bsSheet", deps.sheetId);
489
+ pushed = true;
490
+ } catch {
491
+ pushed = false;
468
492
  }
469
493
  };
470
494
  const unsubscribeOpen = deps.on("open", onOpen);
471
- const unsubscribeClose = deps.on("close", onClose);
495
+ const unsubscribeClose = deps.on("close", teardownEntry);
472
496
  if (deps.getSize() > 0) onOpen();
473
497
  return () => {
474
498
  unsubscribeOpen();
475
499
  unsubscribeClose();
476
- detachPopstate?.();
477
- detachPopstate = null;
478
- if (routePushed) {
479
- routePushed = false;
480
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
481
- }
482
- if (closeOnBackPushed) {
483
- closeOnBackPushed = false;
484
- popOurMarker("__bsSheet", deps.sheetId);
485
- }
500
+ teardownEntry();
501
+ };
502
+ }
503
+ function installRouteChange(deps) {
504
+ if (typeof window === "undefined" || typeof history === "undefined") {
505
+ return () => {
506
+ };
507
+ }
508
+ const onChange = () => {
509
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
510
+ void deps.close();
511
+ };
512
+ const wrap = (key) => {
513
+ const original = history[key];
514
+ const patched = function(...args) {
515
+ original.apply(this, args);
516
+ onChange();
517
+ };
518
+ history[key] = patched;
519
+ return () => {
520
+ if (history[key] === patched) {
521
+ history[key] = original;
522
+ }
523
+ };
524
+ };
525
+ window.addEventListener("popstate", onChange);
526
+ const restorePush = wrap("pushState");
527
+ const restoreReplace = wrap("replaceState");
528
+ return () => {
529
+ window.removeEventListener("popstate", onChange);
530
+ restorePush();
531
+ restoreReplace();
486
532
  };
487
533
  }
488
534
 
@@ -790,6 +836,7 @@ function applyOverlayPosition(ws, mode, position, inset) {
790
836
 
791
837
  // src/core/controllers/scrim-controller.ts
792
838
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
839
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
793
840
  var ScrimController = class {
794
841
  constructor(deps, opts) {
795
842
  __publicField(this, "screenComponent");
@@ -828,9 +875,11 @@ var ScrimController = class {
828
875
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
829
876
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
830
877
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
878
+ const explicitColor = opts.scrimColor ?? preset?.color;
879
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
831
880
  if (this.screenComponent) {
832
881
  this.applyScrimStyles({
833
- color: opts.scrimColor ?? preset?.color,
882
+ color: effectiveColor,
834
883
  blur: opts.scrimBlur ?? preset?.blur,
835
884
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
836
885
  });
@@ -1604,7 +1653,7 @@ var LifecycleController = class {
1604
1653
  this.releaseScrollLock = null;
1605
1654
  this.inertSiblings.remove();
1606
1655
  if (this.returnFocus) {
1607
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1656
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1608
1657
  target?.focus?.();
1609
1658
  }
1610
1659
  }
@@ -1772,6 +1821,7 @@ var GestureController = class {
1772
1821
  __publicField(this, "dragStartSize", 0);
1773
1822
  __publicField(this, "currentPointerKind", "touch");
1774
1823
  __publicField(this, "keyboardDismissed", false);
1824
+ __publicField(this, "dragSuppressed", false);
1775
1825
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1776
1826
  this.deps = deps;
1777
1827
  }
@@ -1782,6 +1832,11 @@ var GestureController = class {
1782
1832
  if (this.detach) return this.detach;
1783
1833
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1784
1834
  onStart: (_coord, kind) => {
1835
+ if (this.deps.getDisableDrag?.()) {
1836
+ this.dragSuppressed = true;
1837
+ return;
1838
+ }
1839
+ this.dragSuppressed = false;
1785
1840
  this.deps.cancelAnimation();
1786
1841
  this.isDragging_ = true;
1787
1842
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1792,6 +1847,7 @@ var GestureController = class {
1792
1847
  this.deps.emit("dragstart", { size: this.dragStartSize });
1793
1848
  },
1794
1849
  onMove: (delta) => {
1850
+ if (this.dragSuppressed) return;
1795
1851
  const ctx = this.deps.getDragContext();
1796
1852
  const { min, max } = ctx.range;
1797
1853
  const maxAxis = ctx.maxAxisSize;
@@ -1815,6 +1871,10 @@ var GestureController = class {
1815
1871
  }
1816
1872
  },
1817
1873
  onEnd: (delta, velocity, kind) => {
1874
+ if (this.dragSuppressed) {
1875
+ this.dragSuppressed = false;
1876
+ return;
1877
+ }
1818
1878
  this.isDragging_ = false;
1819
1879
  this.keyboardDismissed = false;
1820
1880
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1825,6 +1885,10 @@ var GestureController = class {
1825
1885
  this.deps.settleAfterDrag(delta, velocity, kind);
1826
1886
  },
1827
1887
  onCancel: () => {
1888
+ if (this.dragSuppressed) {
1889
+ this.dragSuppressed = false;
1890
+ return;
1891
+ }
1828
1892
  this.isDragging_ = false;
1829
1893
  this.keyboardDismissed = false;
1830
1894
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -2204,6 +2268,12 @@ function resolveEngineOptions(opts, extras) {
2204
2268
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2205
2269
  rubberBandEnabled: opts.rubberBand ?? true,
2206
2270
  closeOnBack: opts.closeOnBack ?? false,
2271
+ persistent: opts.persistent ?? false,
2272
+ disableClose: opts.disableClose ?? false,
2273
+ disableDrag: opts.disableDrag ?? false,
2274
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2275
+ radius: opts.radius,
2276
+ maxHeight: opts.maxHeight,
2207
2277
  initialAllowed,
2208
2278
  initialId,
2209
2279
  scrim: {
@@ -2230,7 +2300,8 @@ function resolveEngineOptions(opts, extras) {
2230
2300
  closeOnEscape: opts.closeOnEscape,
2231
2301
  lockBodyScroll: opts.lockBodyScroll,
2232
2302
  inertSiblings: opts.inertSiblings,
2233
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2303
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2304
+ returnFocus: opts.returnFocusTo
2234
2305
  }
2235
2306
  };
2236
2307
  }
@@ -2275,6 +2346,11 @@ var BottomSheetEngine = class {
2275
2346
  __publicField(this, "lifecycle");
2276
2347
  __publicField(this, "closeOnBack");
2277
2348
  __publicField(this, "routedTo");
2349
+ __publicField(this, "persistent");
2350
+ __publicField(this, "disableCloseFlag");
2351
+ __publicField(this, "disableDragFlag");
2352
+ __publicField(this, "closeOnRouteChange");
2353
+ __publicField(this, "maxHeightCap");
2278
2354
  __publicField(this, "snapPointsRaw");
2279
2355
  __publicField(this, "snaps");
2280
2356
  __publicField(this, "activeId");
@@ -2287,6 +2363,7 @@ var BottomSheetEngine = class {
2287
2363
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
2288
2364
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2289
2365
  __publicField(this, "isTopSheet", true);
2366
+ __publicField(this, "opening", false);
2290
2367
  __publicField(this, "progressPayload", {
2291
2368
  value: 0,
2292
2369
  size: 0
@@ -2343,7 +2420,8 @@ var BottomSheetEngine = class {
2343
2420
  void this.snapTo(id);
2344
2421
  },
2345
2422
  close: () => {
2346
- void this.close();
2423
+ if (!this.canDismiss()) return;
2424
+ void this.close("backdrop");
2347
2425
  }
2348
2426
  },
2349
2427
  resolved.scrim
@@ -2367,6 +2445,10 @@ var BottomSheetEngine = class {
2367
2445
  );
2368
2446
  this.closeOnBack = resolved.closeOnBack;
2369
2447
  this.routedTo = opts.routedTo;
2448
+ this.persistent = resolved.persistent;
2449
+ this.disableCloseFlag = resolved.disableClose;
2450
+ this.disableDragFlag = resolved.disableDrag;
2451
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2370
2452
  this.stackEffectEnabled = opts.stackEffect ?? false;
2371
2453
  this.snapPointsRaw = opts.snapPoints;
2372
2454
  this.persistKey = opts.persistKey;
@@ -2380,7 +2462,7 @@ var BottomSheetEngine = class {
2380
2462
  this.snapPointsRaw,
2381
2463
  resolved.initialAllowed,
2382
2464
  this.mode,
2383
- () => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
2465
+ () => this.measureFitSize(),
2384
2466
  (maxAxisSize) => {
2385
2467
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2386
2468
  }
@@ -2414,6 +2496,19 @@ var BottomSheetEngine = class {
2414
2496
  io.observe(this.element);
2415
2497
  this.teardowns.add(() => io.disconnect());
2416
2498
  }
2499
+ this.installFitObserver();
2500
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2501
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2502
+ this.applyAutoAriaLabelledBy();
2503
+ if (this.closeOnRouteChange) {
2504
+ this.teardowns.add(
2505
+ installRouteChange({
2506
+ isDestroyed: () => this.destroyed,
2507
+ getSize: () => this.size,
2508
+ close: () => this.close()
2509
+ })
2510
+ );
2511
+ }
2417
2512
  if (this.persistKey) {
2418
2513
  this.teardowns.add(installPersist(this, this.persistKey));
2419
2514
  }
@@ -2438,7 +2533,7 @@ var BottomSheetEngine = class {
2438
2533
  isTopSheet: () => this.isTopSheet,
2439
2534
  getSize: () => this.size,
2440
2535
  isDestroyed: () => this.destroyed,
2441
- close: () => this.close(),
2536
+ close: () => this.close("back"),
2442
2537
  on: (event, fn) => this.on(event, fn),
2443
2538
  sheetId: this.id
2444
2539
  })
@@ -2532,6 +2627,10 @@ var BottomSheetEngine = class {
2532
2627
  const previousSize = this.size;
2533
2628
  this.scrollCache.cache(previousId, previousSize, target.size);
2534
2629
  this.activeId = id;
2630
+ if (wasClosed && target.size > 0) {
2631
+ this.opening = true;
2632
+ sheetStack.promote(this.id);
2633
+ }
2535
2634
  if (this.animation.viewTransitionsAvailable) {
2536
2635
  this.currentViewTransition?.skipTransition?.();
2537
2636
  const vt = document.startViewTransition(() => {
@@ -2546,19 +2645,26 @@ var BottomSheetEngine = class {
2546
2645
  } else {
2547
2646
  await this.animation.animateTo(target.size, velocityPxPerMs);
2548
2647
  }
2648
+ this.opening = false;
2549
2649
  externalSignal?.removeEventListener("abort", onExternalAbort);
2550
2650
  if (signal.aborted) return;
2551
2651
  this.scrollCache.restore(id, previousSize, target.size);
2552
2652
  this.updateAriaSlider();
2553
- this.emit("snap", { id, size: target.size });
2653
+ this.emit("snap", {
2654
+ id,
2655
+ size: target.size,
2656
+ progress: this.computeProgress(target.size)
2657
+ });
2554
2658
  if (wasClosed && target.size > 0) {
2555
2659
  this.emit("open", { id });
2556
2660
  this.handleOpen();
2557
2661
  notifyLinkedSheets(this.linkedSheets, this);
2662
+ this.emit("opened", { id });
2558
2663
  }
2559
2664
  if (target.size === 0) {
2560
2665
  this.emit("close", void 0);
2561
2666
  this.handleClose();
2667
+ this.emit("closed", void 0);
2562
2668
  }
2563
2669
  }
2564
2670
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2583,11 +2689,74 @@ var BottomSheetEngine = class {
2583
2689
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2584
2690
  return this.snapTo(target);
2585
2691
  }
2586
- close() {
2692
+ close(reason = "programmatic") {
2587
2693
  if (this.destroyed) return Promise.resolve();
2694
+ if (this.disableCloseFlag) return Promise.resolve();
2695
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2588
2696
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2589
2697
  return this.snapTo(closedId ?? this.activeId);
2590
2698
  }
2699
+ canDismiss() {
2700
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2701
+ }
2702
+ isTop() {
2703
+ return this.isTopSheet;
2704
+ }
2705
+ depth() {
2706
+ return sheetStack.depthOf(this.id);
2707
+ }
2708
+ expand() {
2709
+ if (this.destroyed) return Promise.resolve();
2710
+ const ids = this.allowedIdsBySize();
2711
+ const target = ids[ids.length - 1];
2712
+ if (!target) return Promise.resolve();
2713
+ return this.snapTo(target);
2714
+ }
2715
+ collapse() {
2716
+ if (this.destroyed) return Promise.resolve();
2717
+ const ids = this.allowedIdsBySize();
2718
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2719
+ const target = nonZero ?? ids[0];
2720
+ if (!target) return Promise.resolve();
2721
+ return this.snapTo(target);
2722
+ }
2723
+ setRadius(r) {
2724
+ if (this.destroyed) return;
2725
+ this.element.style.setProperty(
2726
+ "--bs-radius",
2727
+ typeof r === "number" ? `${r}px` : r
2728
+ );
2729
+ }
2730
+ setMaxHeight(h) {
2731
+ if (this.destroyed) return;
2732
+ const value = typeof h === "number" ? `${h}px` : h;
2733
+ const axis = layoutAxis(this.mode);
2734
+ this.element.style.setProperty(
2735
+ axis === "height" ? "maxHeight" : "maxWidth",
2736
+ value
2737
+ );
2738
+ if (typeof h === "number") {
2739
+ this.maxHeightCap = h;
2740
+ this.clampToMaxHeight();
2741
+ } else if (typeof window !== "undefined") {
2742
+ const probe2 = document.createElement("div");
2743
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2744
+ document.body.appendChild(probe2);
2745
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2746
+ probe2.remove();
2747
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2748
+ this.clampToMaxHeight();
2749
+ }
2750
+ }
2751
+ clampToMaxHeight() {
2752
+ if (this.maxHeightCap === void 0) return;
2753
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2754
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2755
+ }
2756
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2757
+ this.applySize(this.maxHeightCap);
2758
+ }
2759
+ }
2591
2760
  setAllowed(ids, snap) {
2592
2761
  if (this.destroyed) return;
2593
2762
  this.snaps.setAllowedIds(ids);
@@ -2721,8 +2890,53 @@ var BottomSheetEngine = class {
2721
2890
  }
2722
2891
  this.updateAriaSlider();
2723
2892
  }
2893
+ recompute() {
2894
+ if (this.destroyed) return;
2895
+ this.snaps.recompute();
2896
+ this.scrim.invalidateOpacityCache();
2897
+ if (this.gesture?.isDragging) return;
2898
+ const current = this.snaps.findById(this.activeId);
2899
+ if (current) {
2900
+ this.size = current.size;
2901
+ this.applySize(this.size);
2902
+ this.updateAriaSlider();
2903
+ }
2904
+ }
2905
+ measureFitSize() {
2906
+ const vertical = layoutAxis(this.mode) === "height";
2907
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2908
+ const content = this.scrollContainer;
2909
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2910
+ if (typeof window === "undefined") return natural;
2911
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2912
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2913
+ }
2914
+ installFitObserver() {
2915
+ if (typeof ResizeObserver === "undefined") return;
2916
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2917
+ return;
2918
+ const targets = [this.handle];
2919
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2920
+ targets.push(this.scrollContainer);
2921
+ }
2922
+ let raf = 0;
2923
+ const ro = new ResizeObserver(() => {
2924
+ if (raf) return;
2925
+ raf = requestAnimationFrame(() => {
2926
+ raf = 0;
2927
+ if (this.destroyed || this.gesture?.isDragging) return;
2928
+ this.recompute();
2929
+ });
2930
+ });
2931
+ for (const t of targets) ro.observe(t);
2932
+ this.teardowns.add(() => {
2933
+ ro.disconnect();
2934
+ if (raf) cancelAnimationFrame(raf);
2935
+ });
2936
+ }
2724
2937
  destroy() {
2725
2938
  this.destroyed = true;
2939
+ this.opening = false;
2726
2940
  this.currentAbort.abort();
2727
2941
  this.currentViewTransition?.skipTransition?.();
2728
2942
  this.currentViewTransition = null;
@@ -2772,6 +2986,36 @@ var BottomSheetEngine = class {
2772
2986
  frozen = true;
2773
2987
  return cancelled;
2774
2988
  }
2989
+ emitBeforeClose(reason) {
2990
+ let cancelled = false;
2991
+ let frozen = false;
2992
+ this.emit("before-close", {
2993
+ reason,
2994
+ cancel: () => {
2995
+ if (frozen) {
2996
+ console.warn(
2997
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
2998
+ );
2999
+ return;
3000
+ }
3001
+ cancelled = true;
3002
+ }
3003
+ });
3004
+ frozen = true;
3005
+ return cancelled;
3006
+ }
3007
+ applyAutoAriaLabelledBy() {
3008
+ if (typeof document === "undefined") return;
3009
+ if (!this.lifecycle.focusTrapEnabled) return;
3010
+ if (this.element.getAttribute("aria-labelledby")) return;
3011
+ const scope = this.handle ?? this.element;
3012
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3013
+ if (!titled) return;
3014
+ if (!titled.id) {
3015
+ titled.id = nextInstanceId("bs-title");
3016
+ }
3017
+ this.element.setAttribute("aria-labelledby", titled.id);
3018
+ }
2775
3019
  recomputeSnaps() {
2776
3020
  const seen = /* @__PURE__ */ new Set();
2777
3021
  for (const p of this.snapPointsRaw) {
@@ -2807,11 +3051,8 @@ var BottomSheetEngine = class {
2807
3051
  },
2808
3052
  setIsTop: (isTop) => {
2809
3053
  this.isTopSheet = isTop;
2810
- if (this.backdrop) {
2811
- this.backdrop.style.display = isTop ? "" : "none";
2812
- }
2813
3054
  },
2814
- isOpen: () => this.size > 0,
3055
+ isOpen: () => this.size > 0 || this.opening,
2815
3056
  setDepth: (depth) => this.applyStackDepth(depth)
2816
3057
  }));
2817
3058
  }
@@ -2843,6 +3084,7 @@ var BottomSheetEngine = class {
2843
3084
  buf.rubberBandEnabled = this.rubberBandEnabled;
2844
3085
  return buf;
2845
3086
  },
3087
+ getDisableDrag: () => this.disableDragFlag,
2846
3088
  cancelAnimation: () => this.animation.cancel(),
2847
3089
  applySize: (size) => this.applySize(size),
2848
3090
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2922,8 +3164,9 @@ var BottomSheetEngine = class {
2922
3164
  if (this.lifecycle.closeOnEscape) {
2923
3165
  const onKey = (e) => {
2924
3166
  if (this.destroyed || e.defaultPrevented) return;
3167
+ if (!this.canDismiss()) return;
2925
3168
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2926
- void this.close();
3169
+ void this.close("escape");
2927
3170
  }
2928
3171
  };
2929
3172
  document.addEventListener("keydown", onKey);
@@ -2986,20 +3229,31 @@ var BottomSheetEngine = class {
2986
3229
  const previousSnapSize = this.snaps.findById(previousId)?.size;
2987
3230
  this.scrollCache.cache(previousId, previousSize, target.size);
2988
3231
  this.activeId = target.id;
3232
+ if (previousSnapSize === 0 && target.size > 0) {
3233
+ this.opening = true;
3234
+ sheetStack.promote(this.id);
3235
+ }
2989
3236
  void this.animation.animateTo(target.size, velocity).then(() => {
3237
+ this.opening = false;
2990
3238
  if (signal.aborted) return;
2991
3239
  this.scrollCache.restore(target.id, previousSize, target.size);
2992
3240
  this.updateAriaSlider();
2993
- this.emit("snap", { id: target.id, size: target.size });
3241
+ this.emit("snap", {
3242
+ id: target.id,
3243
+ size: target.size,
3244
+ progress: this.computeProgress(target.size)
3245
+ });
2994
3246
  this.haptic();
2995
3247
  if (previousSnapSize === 0 && target.size > 0) {
2996
3248
  this.emit("open", { id: target.id });
2997
3249
  this.handleOpen();
2998
3250
  notifyLinkedSheets(this.linkedSheets, this);
3251
+ this.emit("opened", { id: target.id });
2999
3252
  }
3000
3253
  if (target.size === 0) {
3001
3254
  this.emit("close", void 0);
3002
3255
  this.handleClose();
3256
+ this.emit("closed", void 0);
3003
3257
  }
3004
3258
  });
3005
3259
  }
@@ -3068,17 +3322,24 @@ var BottomSheetEngine = class {
3068
3322
  const target = this.snaps.findById(this.activeId);
3069
3323
  if (!target) return;
3070
3324
  this.updateAriaSlider();
3071
- this.emit("snap", { id: target.id, size: target.size });
3325
+ this.emit("snap", {
3326
+ id: target.id,
3327
+ size: target.size,
3328
+ progress: this.computeProgress(target.size)
3329
+ });
3072
3330
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3073
3331
  this.emit("open", { id: target.id });
3074
3332
  this.handleOpen();
3075
3333
  notifyLinkedSheets(this.linkedSheets, this);
3334
+ this.emit("opened", { id: target.id });
3076
3335
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3077
3336
  this.emit("close", void 0);
3078
3337
  this.handleClose();
3338
+ this.emit("closed", void 0);
3079
3339
  }
3080
3340
  }
3081
3341
  handleClose() {
3342
+ this.opening = false;
3082
3343
  this.lifecycle.release();
3083
3344
  sheetStack.update();
3084
3345
  }