@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
@@ -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
  }
@@ -155,7 +164,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
155
164
  var resolveSnap = (point, mode, measureFit) => {
156
165
  if (typeof point === "number") return Math.max(0, point);
157
166
  if (point === "full") return viewportSize(mode);
158
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
167
+ if (point === "fit" || point === "content") {
168
+ return Math.max(0, measureFit?.() ?? 0);
169
+ }
159
170
  const pctMatch = point.match(PERCENT);
160
171
  if (pctMatch) {
161
172
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -392,6 +403,42 @@ function notifyLinkedSheets(sheets, self) {
392
403
  }
393
404
 
394
405
  // src/core/features/route.ts
406
+ var closers = [];
407
+ var handlerInstalled = false;
408
+ var suppress = 0;
409
+ var ensureHandler = () => {
410
+ if (handlerInstalled || typeof window === "undefined") return;
411
+ handlerInstalled = true;
412
+ window.addEventListener("popstate", () => {
413
+ if (suppress > 0) {
414
+ suppress -= 1;
415
+ return;
416
+ }
417
+ let target = null;
418
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
419
+ const c = closers[i];
420
+ if (!c.isOpen()) continue;
421
+ if (!target) target = c;
422
+ if (c.isTop()) {
423
+ target = c;
424
+ break;
425
+ }
426
+ }
427
+ if (!target) return;
428
+ target.consumed = true;
429
+ const idx = closers.indexOf(target);
430
+ if (idx !== -1) closers.splice(idx, 1);
431
+ target.close();
432
+ });
433
+ };
434
+ var programmaticBack = () => {
435
+ suppress += 1;
436
+ try {
437
+ history.back();
438
+ } catch {
439
+ suppress = Math.max(0, suppress - 1);
440
+ }
441
+ };
395
442
  function installRoute(deps) {
396
443
  if (typeof window === "undefined") {
397
444
  return () => {
@@ -401,82 +448,81 @@ function installRoute(deps) {
401
448
  return () => {
402
449
  };
403
450
  }
404
- let routePushed = false;
405
- let closeOnBackPushed = false;
406
- let detachPopstate = null;
407
- const popOurMarker = (key, expected) => {
408
- const state = history.state;
409
- if (state && state[key] === expected) {
410
- try {
411
- history.back();
412
- } catch {
413
- }
451
+ let pushed = false;
452
+ let closer = null;
453
+ const teardownEntry = () => {
454
+ if (closer) {
455
+ const idx = closers.indexOf(closer);
456
+ if (idx !== -1) closers.splice(idx, 1);
457
+ }
458
+ const wasConsumed = closer?.consumed ?? false;
459
+ closer = null;
460
+ if (pushed && !wasConsumed) {
461
+ pushed = false;
462
+ programmaticBack();
463
+ } else {
464
+ pushed = false;
414
465
  }
415
466
  };
416
467
  const onOpen = () => {
417
- if (deps.routedTo && !detachPopstate) {
418
- const target = deps.routedTo;
419
- try {
420
- history.pushState({ __bsRouted: target }, "", target);
421
- routePushed = true;
422
- } catch {
423
- }
424
- const onPop = (event) => {
425
- if (deps.isDestroyed()) return;
426
- const state = event.state;
427
- if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
428
- routePushed = false;
429
- void deps.close();
430
- }
431
- };
432
- window.addEventListener("popstate", onPop);
433
- detachPopstate = () => window.removeEventListener("popstate", onPop);
434
- } else if (deps.closeOnBack && !detachPopstate) {
435
- try {
468
+ if (closer) return;
469
+ closer = {
470
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
471
+ isTop: () => deps.isTopSheet(),
472
+ close: () => void deps.close(),
473
+ consumed: false
474
+ };
475
+ ensureHandler();
476
+ closers.push(closer);
477
+ try {
478
+ if (deps.routedTo !== void 0) {
479
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
480
+ } else {
436
481
  history.pushState({ __bsSheet: deps.sheetId }, "");
437
- closeOnBackPushed = true;
438
- } catch {
439
482
  }
440
- const onPop = () => {
441
- if (deps.isDestroyed()) return;
442
- if (deps.getSize() > 0 && deps.isTopSheet()) {
443
- const state = history.state;
444
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
445
- void deps.close();
446
- }
447
- };
448
- window.addEventListener("popstate", onPop);
449
- detachPopstate = () => window.removeEventListener("popstate", onPop);
450
- }
451
- };
452
- const onClose = () => {
453
- detachPopstate?.();
454
- detachPopstate = null;
455
- if (routePushed) {
456
- routePushed = false;
457
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
458
- }
459
- if (closeOnBackPushed) {
460
- closeOnBackPushed = false;
461
- popOurMarker("__bsSheet", deps.sheetId);
483
+ pushed = true;
484
+ } catch {
485
+ pushed = false;
462
486
  }
463
487
  };
464
488
  const unsubscribeOpen = deps.on("open", onOpen);
465
- const unsubscribeClose = deps.on("close", onClose);
489
+ const unsubscribeClose = deps.on("close", teardownEntry);
466
490
  if (deps.getSize() > 0) onOpen();
467
491
  return () => {
468
492
  unsubscribeOpen();
469
493
  unsubscribeClose();
470
- detachPopstate?.();
471
- detachPopstate = null;
472
- if (routePushed) {
473
- routePushed = false;
474
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
475
- }
476
- if (closeOnBackPushed) {
477
- closeOnBackPushed = false;
478
- popOurMarker("__bsSheet", deps.sheetId);
479
- }
494
+ teardownEntry();
495
+ };
496
+ }
497
+ function installRouteChange(deps) {
498
+ if (typeof window === "undefined" || typeof history === "undefined") {
499
+ return () => {
500
+ };
501
+ }
502
+ const onChange = () => {
503
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
504
+ void deps.close();
505
+ };
506
+ const wrap = (key) => {
507
+ const original = history[key];
508
+ const patched = function(...args) {
509
+ original.apply(this, args);
510
+ onChange();
511
+ };
512
+ history[key] = patched;
513
+ return () => {
514
+ if (history[key] === patched) {
515
+ history[key] = original;
516
+ }
517
+ };
518
+ };
519
+ window.addEventListener("popstate", onChange);
520
+ const restorePush = wrap("pushState");
521
+ const restoreReplace = wrap("replaceState");
522
+ return () => {
523
+ window.removeEventListener("popstate", onChange);
524
+ restorePush();
525
+ restoreReplace();
480
526
  };
481
527
  }
482
528
 
@@ -784,6 +830,7 @@ function applyOverlayPosition(ws, mode, position, inset) {
784
830
 
785
831
  // src/core/controllers/scrim-controller.ts
786
832
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
833
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
787
834
  var ScrimController = class {
788
835
  constructor(deps, opts) {
789
836
  __publicField(this, "screenComponent");
@@ -822,9 +869,11 @@ var ScrimController = class {
822
869
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
823
870
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
824
871
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
872
+ const explicitColor = opts.scrimColor ?? preset?.color;
873
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
825
874
  if (this.screenComponent) {
826
875
  this.applyScrimStyles({
827
- color: opts.scrimColor ?? preset?.color,
876
+ color: effectiveColor,
828
877
  blur: opts.scrimBlur ?? preset?.blur,
829
878
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
830
879
  });
@@ -1598,7 +1647,7 @@ var LifecycleController = class {
1598
1647
  this.releaseScrollLock = null;
1599
1648
  this.inertSiblings.remove();
1600
1649
  if (this.returnFocus) {
1601
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1650
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1602
1651
  target?.focus?.();
1603
1652
  }
1604
1653
  }
@@ -1765,6 +1814,7 @@ var GestureController = class {
1765
1814
  __publicField(this, "dragStartSize", 0);
1766
1815
  __publicField(this, "currentPointerKind", "touch");
1767
1816
  __publicField(this, "keyboardDismissed", false);
1817
+ __publicField(this, "dragSuppressed", false);
1768
1818
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1769
1819
  this.deps = deps;
1770
1820
  }
@@ -1775,6 +1825,11 @@ var GestureController = class {
1775
1825
  if (this.detach) return this.detach;
1776
1826
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1777
1827
  onStart: (_coord, kind) => {
1828
+ if (this.deps.getDisableDrag?.()) {
1829
+ this.dragSuppressed = true;
1830
+ return;
1831
+ }
1832
+ this.dragSuppressed = false;
1778
1833
  this.deps.cancelAnimation();
1779
1834
  this.isDragging_ = true;
1780
1835
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1785,6 +1840,7 @@ var GestureController = class {
1785
1840
  this.deps.emit("dragstart", { size: this.dragStartSize });
1786
1841
  },
1787
1842
  onMove: (delta) => {
1843
+ if (this.dragSuppressed) return;
1788
1844
  const ctx = this.deps.getDragContext();
1789
1845
  const { min, max } = ctx.range;
1790
1846
  const maxAxis = ctx.maxAxisSize;
@@ -1808,6 +1864,10 @@ var GestureController = class {
1808
1864
  }
1809
1865
  },
1810
1866
  onEnd: (delta, velocity, kind) => {
1867
+ if (this.dragSuppressed) {
1868
+ this.dragSuppressed = false;
1869
+ return;
1870
+ }
1811
1871
  this.isDragging_ = false;
1812
1872
  this.keyboardDismissed = false;
1813
1873
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1818,6 +1878,10 @@ var GestureController = class {
1818
1878
  this.deps.settleAfterDrag(delta, velocity, kind);
1819
1879
  },
1820
1880
  onCancel: () => {
1881
+ if (this.dragSuppressed) {
1882
+ this.dragSuppressed = false;
1883
+ return;
1884
+ }
1821
1885
  this.isDragging_ = false;
1822
1886
  this.keyboardDismissed = false;
1823
1887
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -2197,6 +2261,12 @@ function resolveEngineOptions(opts, extras) {
2197
2261
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2198
2262
  rubberBandEnabled: opts.rubberBand ?? true,
2199
2263
  closeOnBack: opts.closeOnBack ?? false,
2264
+ persistent: opts.persistent ?? false,
2265
+ disableClose: opts.disableClose ?? false,
2266
+ disableDrag: opts.disableDrag ?? false,
2267
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2268
+ radius: opts.radius,
2269
+ maxHeight: opts.maxHeight,
2200
2270
  initialAllowed,
2201
2271
  initialId,
2202
2272
  scrim: {
@@ -2223,7 +2293,8 @@ function resolveEngineOptions(opts, extras) {
2223
2293
  closeOnEscape: opts.closeOnEscape,
2224
2294
  lockBodyScroll: opts.lockBodyScroll,
2225
2295
  inertSiblings: opts.inertSiblings,
2226
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2296
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2297
+ returnFocus: opts.returnFocusTo
2227
2298
  }
2228
2299
  };
2229
2300
  }
@@ -2268,6 +2339,11 @@ var BottomSheetEngine = class {
2268
2339
  __publicField(this, "lifecycle");
2269
2340
  __publicField(this, "closeOnBack");
2270
2341
  __publicField(this, "routedTo");
2342
+ __publicField(this, "persistent");
2343
+ __publicField(this, "disableCloseFlag");
2344
+ __publicField(this, "disableDragFlag");
2345
+ __publicField(this, "closeOnRouteChange");
2346
+ __publicField(this, "maxHeightCap");
2271
2347
  __publicField(this, "snapPointsRaw");
2272
2348
  __publicField(this, "snaps");
2273
2349
  __publicField(this, "activeId");
@@ -2280,6 +2356,7 @@ var BottomSheetEngine = class {
2280
2356
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
2281
2357
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2282
2358
  __publicField(this, "isTopSheet", true);
2359
+ __publicField(this, "opening", false);
2283
2360
  __publicField(this, "progressPayload", {
2284
2361
  value: 0,
2285
2362
  size: 0
@@ -2336,7 +2413,8 @@ var BottomSheetEngine = class {
2336
2413
  void this.snapTo(id);
2337
2414
  },
2338
2415
  close: () => {
2339
- void this.close();
2416
+ if (!this.canDismiss()) return;
2417
+ void this.close("backdrop");
2340
2418
  }
2341
2419
  },
2342
2420
  resolved.scrim
@@ -2360,6 +2438,10 @@ var BottomSheetEngine = class {
2360
2438
  );
2361
2439
  this.closeOnBack = resolved.closeOnBack;
2362
2440
  this.routedTo = opts.routedTo;
2441
+ this.persistent = resolved.persistent;
2442
+ this.disableCloseFlag = resolved.disableClose;
2443
+ this.disableDragFlag = resolved.disableDrag;
2444
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2363
2445
  this.stackEffectEnabled = opts.stackEffect ?? false;
2364
2446
  this.snapPointsRaw = opts.snapPoints;
2365
2447
  this.persistKey = opts.persistKey;
@@ -2373,7 +2455,7 @@ var BottomSheetEngine = class {
2373
2455
  this.snapPointsRaw,
2374
2456
  resolved.initialAllowed,
2375
2457
  this.mode,
2376
- () => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
2458
+ () => this.measureFitSize(),
2377
2459
  (maxAxisSize) => {
2378
2460
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2379
2461
  }
@@ -2407,6 +2489,19 @@ var BottomSheetEngine = class {
2407
2489
  io.observe(this.element);
2408
2490
  this.teardowns.add(() => io.disconnect());
2409
2491
  }
2492
+ this.installFitObserver();
2493
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2494
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2495
+ this.applyAutoAriaLabelledBy();
2496
+ if (this.closeOnRouteChange) {
2497
+ this.teardowns.add(
2498
+ installRouteChange({
2499
+ isDestroyed: () => this.destroyed,
2500
+ getSize: () => this.size,
2501
+ close: () => this.close()
2502
+ })
2503
+ );
2504
+ }
2410
2505
  if (this.persistKey) {
2411
2506
  this.teardowns.add(installPersist(this, this.persistKey));
2412
2507
  }
@@ -2431,7 +2526,7 @@ var BottomSheetEngine = class {
2431
2526
  isTopSheet: () => this.isTopSheet,
2432
2527
  getSize: () => this.size,
2433
2528
  isDestroyed: () => this.destroyed,
2434
- close: () => this.close(),
2529
+ close: () => this.close("back"),
2435
2530
  on: (event, fn) => this.on(event, fn),
2436
2531
  sheetId: this.id
2437
2532
  })
@@ -2525,6 +2620,10 @@ var BottomSheetEngine = class {
2525
2620
  const previousSize = this.size;
2526
2621
  this.scrollCache.cache(previousId, previousSize, target.size);
2527
2622
  this.activeId = id;
2623
+ if (wasClosed && target.size > 0) {
2624
+ this.opening = true;
2625
+ sheetStack.promote(this.id);
2626
+ }
2528
2627
  if (this.animation.viewTransitionsAvailable) {
2529
2628
  this.currentViewTransition?.skipTransition?.();
2530
2629
  const vt = document.startViewTransition(() => {
@@ -2539,19 +2638,26 @@ var BottomSheetEngine = class {
2539
2638
  } else {
2540
2639
  await this.animation.animateTo(target.size, velocityPxPerMs);
2541
2640
  }
2641
+ this.opening = false;
2542
2642
  externalSignal?.removeEventListener("abort", onExternalAbort);
2543
2643
  if (signal.aborted) return;
2544
2644
  this.scrollCache.restore(id, previousSize, target.size);
2545
2645
  this.updateAriaSlider();
2546
- this.emit("snap", { id, size: target.size });
2646
+ this.emit("snap", {
2647
+ id,
2648
+ size: target.size,
2649
+ progress: this.computeProgress(target.size)
2650
+ });
2547
2651
  if (wasClosed && target.size > 0) {
2548
2652
  this.emit("open", { id });
2549
2653
  this.handleOpen();
2550
2654
  notifyLinkedSheets(this.linkedSheets, this);
2655
+ this.emit("opened", { id });
2551
2656
  }
2552
2657
  if (target.size === 0) {
2553
2658
  this.emit("close", void 0);
2554
2659
  this.handleClose();
2660
+ this.emit("closed", void 0);
2555
2661
  }
2556
2662
  }
2557
2663
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2576,11 +2682,74 @@ var BottomSheetEngine = class {
2576
2682
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2577
2683
  return this.snapTo(target);
2578
2684
  }
2579
- close() {
2685
+ close(reason = "programmatic") {
2580
2686
  if (this.destroyed) return Promise.resolve();
2687
+ if (this.disableCloseFlag) return Promise.resolve();
2688
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2581
2689
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2582
2690
  return this.snapTo(closedId ?? this.activeId);
2583
2691
  }
2692
+ canDismiss() {
2693
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2694
+ }
2695
+ isTop() {
2696
+ return this.isTopSheet;
2697
+ }
2698
+ depth() {
2699
+ return sheetStack.depthOf(this.id);
2700
+ }
2701
+ expand() {
2702
+ if (this.destroyed) return Promise.resolve();
2703
+ const ids = this.allowedIdsBySize();
2704
+ const target = ids[ids.length - 1];
2705
+ if (!target) return Promise.resolve();
2706
+ return this.snapTo(target);
2707
+ }
2708
+ collapse() {
2709
+ if (this.destroyed) return Promise.resolve();
2710
+ const ids = this.allowedIdsBySize();
2711
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2712
+ const target = nonZero ?? ids[0];
2713
+ if (!target) return Promise.resolve();
2714
+ return this.snapTo(target);
2715
+ }
2716
+ setRadius(r) {
2717
+ if (this.destroyed) return;
2718
+ this.element.style.setProperty(
2719
+ "--bs-radius",
2720
+ typeof r === "number" ? `${r}px` : r
2721
+ );
2722
+ }
2723
+ setMaxHeight(h) {
2724
+ if (this.destroyed) return;
2725
+ const value = typeof h === "number" ? `${h}px` : h;
2726
+ const axis = layoutAxis(this.mode);
2727
+ this.element.style.setProperty(
2728
+ axis === "height" ? "maxHeight" : "maxWidth",
2729
+ value
2730
+ );
2731
+ if (typeof h === "number") {
2732
+ this.maxHeightCap = h;
2733
+ this.clampToMaxHeight();
2734
+ } else if (typeof window !== "undefined") {
2735
+ const probe2 = document.createElement("div");
2736
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2737
+ document.body.appendChild(probe2);
2738
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2739
+ probe2.remove();
2740
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2741
+ this.clampToMaxHeight();
2742
+ }
2743
+ }
2744
+ clampToMaxHeight() {
2745
+ if (this.maxHeightCap === void 0) return;
2746
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2747
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2748
+ }
2749
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2750
+ this.applySize(this.maxHeightCap);
2751
+ }
2752
+ }
2584
2753
  setAllowed(ids, snap) {
2585
2754
  if (this.destroyed) return;
2586
2755
  this.snaps.setAllowedIds(ids);
@@ -2714,8 +2883,53 @@ var BottomSheetEngine = class {
2714
2883
  }
2715
2884
  this.updateAriaSlider();
2716
2885
  }
2886
+ recompute() {
2887
+ if (this.destroyed) return;
2888
+ this.snaps.recompute();
2889
+ this.scrim.invalidateOpacityCache();
2890
+ if (this.gesture?.isDragging) return;
2891
+ const current = this.snaps.findById(this.activeId);
2892
+ if (current) {
2893
+ this.size = current.size;
2894
+ this.applySize(this.size);
2895
+ this.updateAriaSlider();
2896
+ }
2897
+ }
2898
+ measureFitSize() {
2899
+ const vertical = layoutAxis(this.mode) === "height";
2900
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2901
+ const content = this.scrollContainer;
2902
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2903
+ if (typeof window === "undefined") return natural;
2904
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2905
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2906
+ }
2907
+ installFitObserver() {
2908
+ if (typeof ResizeObserver === "undefined") return;
2909
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2910
+ return;
2911
+ const targets = [this.handle];
2912
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2913
+ targets.push(this.scrollContainer);
2914
+ }
2915
+ let raf = 0;
2916
+ const ro = new ResizeObserver(() => {
2917
+ if (raf) return;
2918
+ raf = requestAnimationFrame(() => {
2919
+ raf = 0;
2920
+ if (this.destroyed || this.gesture?.isDragging) return;
2921
+ this.recompute();
2922
+ });
2923
+ });
2924
+ for (const t of targets) ro.observe(t);
2925
+ this.teardowns.add(() => {
2926
+ ro.disconnect();
2927
+ if (raf) cancelAnimationFrame(raf);
2928
+ });
2929
+ }
2717
2930
  destroy() {
2718
2931
  this.destroyed = true;
2932
+ this.opening = false;
2719
2933
  this.currentAbort.abort();
2720
2934
  this.currentViewTransition?.skipTransition?.();
2721
2935
  this.currentViewTransition = null;
@@ -2765,6 +2979,36 @@ var BottomSheetEngine = class {
2765
2979
  frozen = true;
2766
2980
  return cancelled;
2767
2981
  }
2982
+ emitBeforeClose(reason) {
2983
+ let cancelled = false;
2984
+ let frozen = false;
2985
+ this.emit("before-close", {
2986
+ reason,
2987
+ cancel: () => {
2988
+ if (frozen) {
2989
+ console.warn(
2990
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
2991
+ );
2992
+ return;
2993
+ }
2994
+ cancelled = true;
2995
+ }
2996
+ });
2997
+ frozen = true;
2998
+ return cancelled;
2999
+ }
3000
+ applyAutoAriaLabelledBy() {
3001
+ if (typeof document === "undefined") return;
3002
+ if (!this.lifecycle.focusTrapEnabled) return;
3003
+ if (this.element.getAttribute("aria-labelledby")) return;
3004
+ const scope = this.handle ?? this.element;
3005
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3006
+ if (!titled) return;
3007
+ if (!titled.id) {
3008
+ titled.id = nextInstanceId("bs-title");
3009
+ }
3010
+ this.element.setAttribute("aria-labelledby", titled.id);
3011
+ }
2768
3012
  recomputeSnaps() {
2769
3013
  const seen = /* @__PURE__ */ new Set();
2770
3014
  for (const p of this.snapPointsRaw) {
@@ -2800,11 +3044,8 @@ var BottomSheetEngine = class {
2800
3044
  },
2801
3045
  setIsTop: (isTop) => {
2802
3046
  this.isTopSheet = isTop;
2803
- if (this.backdrop) {
2804
- this.backdrop.style.display = isTop ? "" : "none";
2805
- }
2806
3047
  },
2807
- isOpen: () => this.size > 0,
3048
+ isOpen: () => this.size > 0 || this.opening,
2808
3049
  setDepth: (depth) => this.applyStackDepth(depth)
2809
3050
  }));
2810
3051
  }
@@ -2836,6 +3077,7 @@ var BottomSheetEngine = class {
2836
3077
  buf.rubberBandEnabled = this.rubberBandEnabled;
2837
3078
  return buf;
2838
3079
  },
3080
+ getDisableDrag: () => this.disableDragFlag,
2839
3081
  cancelAnimation: () => this.animation.cancel(),
2840
3082
  applySize: (size) => this.applySize(size),
2841
3083
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2915,8 +3157,9 @@ var BottomSheetEngine = class {
2915
3157
  if (this.lifecycle.closeOnEscape) {
2916
3158
  const onKey = (e) => {
2917
3159
  if (this.destroyed || e.defaultPrevented) return;
3160
+ if (!this.canDismiss()) return;
2918
3161
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2919
- void this.close();
3162
+ void this.close("escape");
2920
3163
  }
2921
3164
  };
2922
3165
  document.addEventListener("keydown", onKey);
@@ -2979,20 +3222,31 @@ var BottomSheetEngine = class {
2979
3222
  const previousSnapSize = this.snaps.findById(previousId)?.size;
2980
3223
  this.scrollCache.cache(previousId, previousSize, target.size);
2981
3224
  this.activeId = target.id;
3225
+ if (previousSnapSize === 0 && target.size > 0) {
3226
+ this.opening = true;
3227
+ sheetStack.promote(this.id);
3228
+ }
2982
3229
  void this.animation.animateTo(target.size, velocity).then(() => {
3230
+ this.opening = false;
2983
3231
  if (signal.aborted) return;
2984
3232
  this.scrollCache.restore(target.id, previousSize, target.size);
2985
3233
  this.updateAriaSlider();
2986
- this.emit("snap", { id: target.id, size: target.size });
3234
+ this.emit("snap", {
3235
+ id: target.id,
3236
+ size: target.size,
3237
+ progress: this.computeProgress(target.size)
3238
+ });
2987
3239
  this.haptic();
2988
3240
  if (previousSnapSize === 0 && target.size > 0) {
2989
3241
  this.emit("open", { id: target.id });
2990
3242
  this.handleOpen();
2991
3243
  notifyLinkedSheets(this.linkedSheets, this);
3244
+ this.emit("opened", { id: target.id });
2992
3245
  }
2993
3246
  if (target.size === 0) {
2994
3247
  this.emit("close", void 0);
2995
3248
  this.handleClose();
3249
+ this.emit("closed", void 0);
2996
3250
  }
2997
3251
  });
2998
3252
  }
@@ -3017,6 +3271,9 @@ var BottomSheetEngine = class {
3017
3271
  if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3018
3272
  this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3019
3273
  }
3274
+ if (this.rootEl && this.rootEl !== this.scrimParent && this.rootEl !== this.anchorHost) {
3275
+ this.rootEl.style.setProperty("--bs-size", `${size}px`);
3276
+ }
3020
3277
  }
3021
3278
  const progress = this.computeProgress(size);
3022
3279
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -3028,6 +3285,9 @@ var BottomSheetEngine = class {
3028
3285
  if (this.anchorHost) {
3029
3286
  this.anchorHost.style.setProperty("--bs-progress", String(progress));
3030
3287
  }
3288
+ if (this.rootEl && this.rootEl !== this.anchorHost) {
3289
+ this.rootEl.style.setProperty("--bs-progress", String(progress));
3290
+ }
3031
3291
  }
3032
3292
  this.scrim.applyOpacity(progress, progressChanged);
3033
3293
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -3061,17 +3321,24 @@ var BottomSheetEngine = class {
3061
3321
  const target = this.snaps.findById(this.activeId);
3062
3322
  if (!target) return;
3063
3323
  this.updateAriaSlider();
3064
- this.emit("snap", { id: target.id, size: target.size });
3324
+ this.emit("snap", {
3325
+ id: target.id,
3326
+ size: target.size,
3327
+ progress: this.computeProgress(target.size)
3328
+ });
3065
3329
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3066
3330
  this.emit("open", { id: target.id });
3067
3331
  this.handleOpen();
3068
3332
  notifyLinkedSheets(this.linkedSheets, this);
3333
+ this.emit("opened", { id: target.id });
3069
3334
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3070
3335
  this.emit("close", void 0);
3071
3336
  this.handleClose();
3337
+ this.emit("closed", void 0);
3072
3338
  }
3073
3339
  }
3074
3340
  handleClose() {
3341
+ this.opening = false;
3075
3342
  this.lifecycle.release();
3076
3343
  sheetStack.update();
3077
3344
  }
@@ -3125,6 +3392,10 @@ function createBottomSheet(opts) {
3125
3392
  snapTo: (id) => engine?.snapTo(id) ?? Promise.resolve(),
3126
3393
  open: (id) => engine?.open(id) ?? Promise.resolve(),
3127
3394
  close: () => engine?.close() ?? Promise.resolve(),
3395
+ expand: () => engine?.expand() ?? Promise.resolve(),
3396
+ collapse: () => engine?.collapse() ?? Promise.resolve(),
3397
+ isTop: () => engine?.isTop() ?? false,
3398
+ depth: () => engine?.depth() ?? 0,
3128
3399
  setAllowed: (ids, snap) => engine?.setAllowed(ids, snap),
3129
3400
  setSnapPoints: (points, allowed) => engine?.setSnapPoints(points, allowed),
3130
3401
  setScrim: (scrimOpts) => engine?.setScrim(scrimOpts),
@@ -3134,6 +3405,7 @@ function createBottomSheet(opts) {
3134
3405
  }),
3135
3406
  setScrimStages: (stagesOpts) => engine?.setScrimStages(stagesOpts) ?? (() => {
3136
3407
  }),
3408
+ recompute: () => engine?.recompute(),
3137
3409
  destroy: () => {
3138
3410
  engine?.destroy();
3139
3411
  engine = null;