@surdeddd/bottom-sheet 0.9.1 → 0.10.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 (50) hide show
  1. package/README.md +108 -66
  2. package/dist/{BottomSheetEngine-CBRcBwV-.d.ts → BottomSheetEngine-DRjCU73e.d.ts} +6 -5
  3. package/dist/{BottomSheetEngine-BcyruNP1.d.cts → BottomSheetEngine-DzsIwD28.d.cts} +6 -5
  4. package/dist/element.cjs +161 -99
  5. package/dist/element.d.cts +1 -1
  6. package/dist/element.d.ts +1 -1
  7. package/dist/element.iife.global.js +2 -2
  8. package/dist/element.js +161 -99
  9. package/dist/index.cjs +155 -115
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +155 -115
  13. package/dist/integrations/formik.d.cts +1 -1
  14. package/dist/integrations/formik.d.ts +1 -1
  15. package/dist/integrations/react-hook-form.d.cts +1 -1
  16. package/dist/integrations/react-hook-form.d.ts +1 -1
  17. package/dist/overlay.cjs +34 -25
  18. package/dist/overlay.d.cts +3 -0
  19. package/dist/overlay.d.ts +3 -0
  20. package/dist/overlay.js +34 -25
  21. package/dist/preact.d.cts +2 -1
  22. package/dist/preact.d.ts +2 -1
  23. package/dist/qwik.cjs +179 -93
  24. package/dist/qwik.d.cts +11 -2
  25. package/dist/qwik.d.ts +11 -2
  26. package/dist/qwik.js +179 -93
  27. package/dist/react.cjs +266 -181
  28. package/dist/react.d.cts +26 -4
  29. package/dist/react.d.ts +26 -4
  30. package/dist/react.js +266 -181
  31. package/dist/solid.cjs +145 -92
  32. package/dist/solid.d.cts +14 -4
  33. package/dist/solid.d.ts +14 -4
  34. package/dist/solid.js +145 -92
  35. package/dist/solid.source.jsx +145 -92
  36. package/dist/styles.css +17 -1
  37. package/dist/svelte-core.cjs +128 -90
  38. package/dist/svelte-core.js +128 -90
  39. package/dist/svelte-src/BottomSheet.svelte +6 -0
  40. package/dist/svelte.cjs +130 -90
  41. package/dist/svelte.d.cts +2 -0
  42. package/dist/svelte.d.ts +2 -0
  43. package/dist/svelte.js +130 -90
  44. package/dist/teleport-7uYNiEAT.d.cts +3 -0
  45. package/dist/teleport-7uYNiEAT.d.ts +3 -0
  46. package/dist/vue.cjs +136 -91
  47. package/dist/vue.d.cts +3 -1
  48. package/dist/vue.d.ts +3 -1
  49. package/dist/vue.js +136 -91
  50. package/package.json +6 -6
@@ -186,11 +186,18 @@ var resolveSnap = (point, mode, measureFit) => {
186
186
  return Math.max(0, el.getBoundingClientRect().width);
187
187
  };
188
188
 
189
+ // src/core/primitives/devWarn.ts
190
+ function devWarn(...args) {
191
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
192
+ console.warn(...args);
193
+ }
194
+ }
195
+
189
196
  // src/core/primitives/snap-points.ts
190
197
  var resolveSnapList = (points, mode, measureFit) => points.map((p) => {
191
198
  const raw = resolveSnap(p.size, mode, measureFit);
192
199
  if (!Number.isFinite(raw) || raw < 0) {
193
- console.warn(
200
+ devWarn(
194
201
  `[BottomSheet] snap "${p.id}" resolved to invalid size (${String(p.size)} \u2192 ${raw}); clamped to 0.`
195
202
  );
196
203
  return { id: p.id, size: 0 };
@@ -202,7 +209,7 @@ var auditVhUsage = (points) => {
202
209
  for (const p of points) {
203
210
  if (typeof p.size !== "string") continue;
204
211
  if (!VH_NOT_DVH.test(p.size)) continue;
205
- console.warn(
212
+ devWarn(
206
213
  `[BottomSheet] snap "${p.id}" uses "vh" \u2014 prefer "dvh" on mobile (iOS Safari URL bar makes vh unstable). See https://web.dev/blog/viewport-units`
207
214
  );
208
215
  }
@@ -256,6 +263,26 @@ var findDragSettleTarget = (input) => {
256
263
  );
257
264
  };
258
265
 
266
+ // src/core/primitives/cancelable-emit.ts
267
+ function emitCancelable(emit, base, warnLabel) {
268
+ let cancelled = false;
269
+ let frozen = false;
270
+ emit({
271
+ ...base,
272
+ cancel: () => {
273
+ if (frozen) {
274
+ devWarn(
275
+ `[BottomSheet] ${warnLabel}.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener.`
276
+ );
277
+ return;
278
+ }
279
+ cancelled = true;
280
+ }
281
+ });
282
+ frozen = true;
283
+ return cancelled;
284
+ }
285
+
259
286
  // src/core/primitives/snap-resolver.ts
260
287
  var SnapResolver = class {
261
288
  constructor(raw, allowed, mode, measureFit, onMaxAxisSizeChange) {
@@ -403,7 +430,9 @@ function notifyLinkedSheets(sheets, self) {
403
430
  try {
404
431
  if (linked.state.size === 0) continue;
405
432
  const ids = linked.getAllowedIds();
406
- const target = ids.find((id) => id !== "closed");
433
+ const resolved = linked.getResolvedSnaps();
434
+ const sizeOf = (id) => resolved.find((s) => s.id === id)?.size ?? 0;
435
+ const target = ids.find((id) => sizeOf(id) > 0);
407
436
  if (target && target !== linked.state.activeId) {
408
437
  void linked.snapTo(target);
409
438
  }
@@ -620,11 +649,15 @@ function installVisualViewport(deps) {
620
649
  if (clamp < deps.getMaxAxisSize()) {
621
650
  deps.setMaxAxisSize(clamp);
622
651
  }
652
+ const wasAnimating = deps.isAnimating?.() ?? false;
653
+ deps.cancelInFlight();
654
+ deps.newCycle();
623
655
  const max = deps.getMaxAxisSize();
624
656
  deps.setSize(Math.min(deps.getSize(), max));
625
657
  const current = deps.resolveActiveSnap();
626
658
  if (current && !deps.isDragging()) {
627
659
  deps.applySize(Math.min(current.size, max));
660
+ if (wasAnimating) deps.resyncAfterCancel?.();
628
661
  } else {
629
662
  deps.applySize(deps.getSize());
630
663
  }
@@ -765,6 +798,54 @@ function installFitObserver(deps) {
765
798
  };
766
799
  }
767
800
 
801
+ // src/core/features/max-height-controller.ts
802
+ function createMaxHeightController(deps) {
803
+ let maxHeightCap;
804
+ let maxHeightRaw;
805
+ const resolveCap = () => {
806
+ const raw = maxHeightRaw;
807
+ if (raw === void 0) {
808
+ maxHeightCap = void 0;
809
+ return;
810
+ }
811
+ if (typeof raw === "number") {
812
+ maxHeightCap = raw;
813
+ return;
814
+ }
815
+ if (typeof window === "undefined") return;
816
+ const measured = resolveSnap(raw, deps.mode);
817
+ maxHeightCap = measured > 0 ? measured : void 0;
818
+ };
819
+ const clampTo = () => {
820
+ if (maxHeightCap === void 0) return;
821
+ if (deps.getMaxAxisSize() > maxHeightCap) {
822
+ deps.setMaxAxisSize(maxHeightCap);
823
+ }
824
+ };
825
+ return {
826
+ setRadius(r) {
827
+ deps.element.style.setProperty(
828
+ "--bs-radius",
829
+ typeof r === "number" ? `${r}px` : r
830
+ );
831
+ },
832
+ setMaxHeight(h) {
833
+ const value = typeof h === "number" ? `${h}px` : h;
834
+ const axis = layoutAxis(deps.mode);
835
+ deps.element.style.setProperty(
836
+ axis === "height" ? "max-height" : "max-width",
837
+ value
838
+ );
839
+ maxHeightRaw = h;
840
+ resolveCap();
841
+ deps.recompute();
842
+ },
843
+ resolveCap,
844
+ clampTo,
845
+ getCap: () => maxHeightCap
846
+ };
847
+ }
848
+
768
849
  // src/core/features/fit-measurement.ts
769
850
  var measureSheetNatural = (element, content, vertical) => {
770
851
  if (!content) {
@@ -894,13 +975,6 @@ var WriteSentinel = class {
894
975
  }
895
976
  };
896
977
 
897
- // src/core/primitives/devWarn.ts
898
- function devWarn(...args) {
899
- if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
900
- console.warn(...args);
901
- }
902
- }
903
-
904
978
  // src/core/primitives/overlay-position.ts
905
979
  function resolveSheetAnchoredStyle(mode, position, inset) {
906
980
  const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
@@ -2544,8 +2618,7 @@ var BottomSheetEngine = class {
2544
2618
  disableCloseFlag;
2545
2619
  disableDragFlag;
2546
2620
  closeOnRouteChange;
2547
- maxHeightCap;
2548
- maxHeightRaw;
2621
+ maxHeight;
2549
2622
  snapPointsRaw;
2550
2623
  snaps;
2551
2624
  activeId;
@@ -2657,11 +2730,20 @@ var BottomSheetEngine = class {
2657
2730
  auditVhUsage(this.snapPointsRaw);
2658
2731
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2659
2732
  this.rootEl = this.element.closest(".bs-root");
2733
+ this.maxHeight = createMaxHeightController({
2734
+ element: this.element,
2735
+ mode: this.mode,
2736
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2737
+ setMaxAxisSize: (size) => {
2738
+ this.snaps.setMaxAxisSize(size);
2739
+ },
2740
+ recompute: () => this.recompute()
2741
+ });
2660
2742
  this.fitDeps = {
2661
2743
  element: this.element,
2662
2744
  scrollContainer: this.scrollContainer,
2663
2745
  mode: this.mode,
2664
- getMaxHeightCap: () => this.maxHeightCap
2746
+ getMaxHeightCap: () => this.maxHeight.getCap()
2665
2747
  };
2666
2748
  this.snaps = new SnapResolver(
2667
2749
  this.snapPointsRaw,
@@ -2772,7 +2854,7 @@ var BottomSheetEngine = class {
2772
2854
  }
2773
2855
  use(plugin) {
2774
2856
  if (this.destroyed) {
2775
- console.warn(
2857
+ devWarn(
2776
2858
  `[BottomSheet] use("${plugin.name}") called on destroyed engine \u2014 ignored.`
2777
2859
  );
2778
2860
  return this;
@@ -2814,6 +2896,9 @@ var BottomSheetEngine = class {
2814
2896
  getAllowedIds() {
2815
2897
  return this.snaps.getAllowedIds().slice();
2816
2898
  }
2899
+ getResolvedSnaps() {
2900
+ return this.snaps.getResolvedSnaps();
2901
+ }
2817
2902
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2818
2903
  if (this.destroyed) return;
2819
2904
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2822,11 +2907,11 @@ var BottomSheetEngine = class {
2822
2907
  if (externalSignal?.aborted) return;
2823
2908
  const target = this.snaps.findById(id);
2824
2909
  if (!target) {
2825
- console.warn(`[BottomSheet] unknown snap id: ${id}`);
2910
+ devWarn(`[BottomSheet] unknown snap id: ${id}`);
2826
2911
  return;
2827
2912
  }
2828
2913
  if (!this.snaps.getAllowedIds().includes(id)) {
2829
- console.warn(`[BottomSheet] snap "${id}" is not in allowed list`);
2914
+ devWarn(`[BottomSheet] snap "${id}" is not in allowed list`);
2830
2915
  return;
2831
2916
  }
2832
2917
  if (!_skipBeforeSnap && this.emitBeforeSnap(target, this.activeId)) {
@@ -2958,42 +3043,11 @@ var BottomSheetEngine = class {
2958
3043
  }
2959
3044
  setRadius(r) {
2960
3045
  if (this.destroyed) return;
2961
- this.element.style.setProperty(
2962
- "--bs-radius",
2963
- typeof r === "number" ? `${r}px` : r
2964
- );
3046
+ this.maxHeight.setRadius(r);
2965
3047
  }
2966
3048
  setMaxHeight(h) {
2967
3049
  if (this.destroyed) return;
2968
- const value = typeof h === "number" ? `${h}px` : h;
2969
- const axis = layoutAxis(this.mode);
2970
- this.element.style.setProperty(
2971
- axis === "height" ? "max-height" : "max-width",
2972
- value
2973
- );
2974
- this.maxHeightRaw = h;
2975
- this.resolveMaxHeightCap();
2976
- this.recompute();
2977
- }
2978
- resolveMaxHeightCap() {
2979
- const raw = this.maxHeightRaw;
2980
- if (raw === void 0) {
2981
- this.maxHeightCap = void 0;
2982
- return;
2983
- }
2984
- if (typeof raw === "number") {
2985
- this.maxHeightCap = raw;
2986
- return;
2987
- }
2988
- if (typeof window === "undefined") return;
2989
- const measured = resolveSnap(raw, this.mode);
2990
- this.maxHeightCap = measured > 0 ? measured : void 0;
2991
- }
2992
- clampToMaxHeight() {
2993
- if (this.maxHeightCap === void 0) return;
2994
- if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2995
- this.snaps.setMaxAxisSize(this.maxHeightCap);
2996
- }
3050
+ this.maxHeight.setMaxHeight(h);
2997
3051
  }
2998
3052
  setAllowed(ids, snap) {
2999
3053
  if (this.destroyed) return;
@@ -3081,7 +3135,7 @@ var BottomSheetEngine = class {
3081
3135
  };
3082
3136
  const host = this.screenComponent?.parentElement;
3083
3137
  if (!host) {
3084
- console.warn(
3138
+ devWarn(
3085
3139
  "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
3086
3140
  );
3087
3141
  return () => {
@@ -3131,9 +3185,9 @@ var BottomSheetEngine = class {
3131
3185
  }
3132
3186
  recompute() {
3133
3187
  if (this.destroyed) return;
3134
- this.resolveMaxHeightCap();
3188
+ this.maxHeight.resolveCap();
3135
3189
  this.snaps.recompute();
3136
- this.clampToMaxHeight();
3190
+ this.maxHeight.clampTo();
3137
3191
  this.scrim.invalidateOpacityCache();
3138
3192
  if (this.gesture?.isDragging) return;
3139
3193
  const current = this.snaps.findById(this.activeId);
@@ -3183,42 +3237,18 @@ var BottomSheetEngine = class {
3183
3237
  return this.currentAbort.signal;
3184
3238
  }
3185
3239
  emitBeforeSnap(target, previousId) {
3186
- let cancelled = false;
3187
- let frozen = false;
3188
- this.emit("before-snap", {
3189
- id: target.id,
3190
- size: target.size,
3191
- previousId,
3192
- cancel: () => {
3193
- if (frozen) {
3194
- console.warn(
3195
- "[BottomSheet] before-snap.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3196
- );
3197
- return;
3198
- }
3199
- cancelled = true;
3200
- }
3201
- });
3202
- frozen = true;
3203
- return cancelled;
3240
+ return emitCancelable(
3241
+ (payload) => this.emit("before-snap", payload),
3242
+ { id: target.id, size: target.size, previousId },
3243
+ "before-snap"
3244
+ );
3204
3245
  }
3205
3246
  emitBeforeClose(reason) {
3206
- let cancelled = false;
3207
- let frozen = false;
3208
- this.emit("before-close", {
3209
- reason,
3210
- cancel: () => {
3211
- if (frozen) {
3212
- console.warn(
3213
- "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3214
- );
3215
- return;
3216
- }
3217
- cancelled = true;
3218
- }
3219
- });
3220
- frozen = true;
3221
- return cancelled;
3247
+ return emitCancelable(
3248
+ (payload) => this.emit("before-close", payload),
3249
+ { reason },
3250
+ "before-close"
3251
+ );
3222
3252
  }
3223
3253
  applyAutoAriaLabelledBy() {
3224
3254
  if (typeof document === "undefined") return;
@@ -3233,16 +3263,16 @@ var BottomSheetEngine = class {
3233
3263
  this.element.setAttribute("aria-labelledby", titled.id);
3234
3264
  }
3235
3265
  recomputeSnaps() {
3236
- this.resolveMaxHeightCap();
3266
+ this.maxHeight.resolveCap();
3237
3267
  this.snaps.setRaw(this.snapPointsRaw);
3238
- this.clampToMaxHeight();
3268
+ this.maxHeight.clampTo();
3239
3269
  this.scrim.invalidateOpacityCache();
3240
3270
  }
3241
3271
  auditDuplicateSnapIds(points) {
3242
3272
  const seen = /* @__PURE__ */ new Set();
3243
3273
  for (const p of points) {
3244
3274
  if (seen.has(p.id)) {
3245
- console.warn(
3275
+ devWarn(
3246
3276
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
3247
3277
  );
3248
3278
  }
@@ -3409,7 +3439,15 @@ var BottomSheetEngine = class {
3409
3439
  setSize: (size) => {
3410
3440
  this.size = size;
3411
3441
  },
3412
- applySize: (size) => this.applySize(size)
3442
+ applySize: (size) => this.applySize(size),
3443
+ cancelInFlight: () => {
3444
+ this.animation.cancel();
3445
+ },
3446
+ newCycle: () => {
3447
+ this.newCycle();
3448
+ },
3449
+ isAnimating: () => this.animation.isAnimating,
3450
+ resyncAfterCancel: () => this.resyncAfterResize()
3413
3451
  }));
3414
3452
  }
3415
3453
  this.teardowns.add(installSliderKeyboard({
@@ -3670,8 +3708,23 @@ var BottomSheet = (props) => {
3670
3708
  props.onSnap?.(payload.id);
3671
3709
  });
3672
3710
  engine.on("before-snap", (payload) => props.onBeforeSnap?.(payload));
3673
- engine.on("dragstart", sync);
3674
- engine.on("dragend", sync);
3711
+ engine.on("before-close", (payload) => props.onBeforeClose?.(payload));
3712
+ engine.on("open", (payload) => props.onOpen?.(payload.id));
3713
+ engine.on("close", () => props.onClose?.());
3714
+ engine.on("opened", (payload) => props.onOpened?.(payload.id));
3715
+ engine.on("closed", () => props.onClosed?.());
3716
+ engine.on("dragstart", (payload) => {
3717
+ sync();
3718
+ props.onDragStart?.(payload);
3719
+ });
3720
+ engine.on("dragend", (payload) => {
3721
+ sync();
3722
+ props.onDragEnd?.(payload);
3723
+ });
3724
+ if (props.onDrag) engine.on("drag", (payload) => props.onDrag?.(payload));
3725
+ if (props.onProgress) {
3726
+ engine.on("progress", (payload) => props.onProgress?.(payload));
3727
+ }
3675
3728
  if (props.backdrop !== false && props.closeOnBackdrop !== false && backdropEl) {
3676
3729
  const onBackdropClick = () => {
3677
3730
  if (engine?.canDismiss()) void engine.close("backdrop");
package/dist/styles.css CHANGED
@@ -10,7 +10,6 @@
10
10
 
11
11
  --bs-z-backdrop: 90;
12
12
  --bs-z-sheet: 100;
13
- --bs-header-min-height: 80px;
14
13
 
15
14
  --bs-backdrop-color-d: rgba(0, 0, 0, 0.5);
16
15
 
@@ -246,6 +245,7 @@
246
245
 
247
246
  .bs-header {
248
247
  flex: 0 0 auto;
248
+ min-height: var(--bs-header-min-height, 0px);
249
249
  padding: var(--bs-header-padding, 0 16px 8px);
250
250
  background: var(--bs-surface, var(--bs-surface-d));
251
251
  }
@@ -285,6 +285,8 @@
285
285
  z-index: var(--bs-z-scrim);
286
286
 
287
287
  background: var(--bs-scrim-color, var(--bs-scrim-color-d));
288
+ backdrop-filter: blur(var(--bs-scrim-blur, 0px));
289
+ -webkit-backdrop-filter: blur(var(--bs-scrim-blur, 0px));
288
290
  transition: opacity 0.24s cubic-bezier(0.22, 1, 0.36, 1);
289
291
  will-change: opacity;
290
292
  }
@@ -347,6 +349,20 @@
347
349
  }
348
350
  }
349
351
 
352
+ @media (forced-colors: active) {
353
+ .bs-sheet {
354
+ border: 1px solid CanvasText;
355
+ }
356
+ .bs-handle::before {
357
+ background: CanvasText;
358
+ forced-color-adjust: none;
359
+ }
360
+ .bs-backdrop,
361
+ .bs-screen {
362
+ forced-color-adjust: none;
363
+ }
364
+ }
365
+
350
366
  .bs-sr-only {
351
367
  position: absolute;
352
368
  width: 1px;