@surdeddd/bottom-sheet 0.7.0 → 0.8.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.
package/dist/svelte.js CHANGED
@@ -2359,6 +2359,7 @@ var BottomSheetEngine = class {
2359
2359
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2360
2360
  __publicField(this, "isTopSheet", true);
2361
2361
  __publicField(this, "opening", false);
2362
+ __publicField(this, "allowOvershoot", false);
2362
2363
  __publicField(this, "progressPayload", {
2363
2364
  value: 0,
2364
2365
  size: 0
@@ -2911,45 +2912,52 @@ var BottomSheetEngine = class {
2911
2912
  }
2912
2913
  return out;
2913
2914
  }
2914
- measureContentExtent(vertical) {
2915
+ measureSheetNatural(vertical) {
2916
+ const el = this.element;
2915
2917
  const content = this.scrollContainer;
2916
- if (!content) return 0;
2917
- const targets = this.contentTargets();
2918
- if (targets.length === 0) {
2919
- return vertical ? content.scrollHeight : content.scrollWidth;
2920
- }
2921
- let min = Infinity;
2922
- let max = -Infinity;
2923
- for (const el of targets) {
2924
- const r = el.getBoundingClientRect();
2925
- if (vertical) {
2926
- if (r.top < min) min = r.top;
2927
- if (r.bottom > max) max = r.bottom;
2928
- } else {
2929
- if (r.left < min) min = r.left;
2930
- if (r.right > max) max = r.right;
2931
- }
2932
- }
2933
- if (!isFinite(min) || !isFinite(max) || max <= min) {
2934
- return vertical ? content.scrollHeight : content.scrollWidth;
2935
- }
2936
- let pad = 0;
2937
- if (typeof getComputedStyle !== "undefined") {
2938
- const cs = getComputedStyle(content);
2939
- pad = vertical ? (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0) : (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0);
2940
- }
2941
- return max - min + pad;
2918
+ if (!content) {
2919
+ return vertical ? el.offsetHeight : el.offsetWidth;
2920
+ }
2921
+ const axis = vertical ? "height" : "width";
2922
+ const sheetStyle = el.style;
2923
+ const cs = content.style;
2924
+ const prevSheet = sheetStyle[axis];
2925
+ const prevFlex = cs.flex;
2926
+ const prevContent = vertical ? cs.height : cs.width;
2927
+ sheetStyle[axis] = "auto";
2928
+ cs.flex = "none";
2929
+ if (vertical) cs.height = "auto";
2930
+ else cs.width = "auto";
2931
+ const poked = vertical ? el.offsetHeight : el.offsetWidth;
2932
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
2933
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
2934
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
2935
+ sheetStyle[axis] = prevSheet;
2936
+ cs.flex = prevFlex;
2937
+ if (vertical) cs.height = prevContent;
2938
+ else cs.width = prevContent;
2939
+ return natural;
2942
2940
  }
2943
2941
  measureFitSize() {
2944
2942
  const vertical = layoutAxis(this.mode) === "height";
2945
- const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2946
- const content = this.scrollContainer;
2947
- const natural = content ? handleSize + this.measureContentExtent(vertical) : handleSize;
2943
+ const natural = this.measureSheetNatural(vertical);
2948
2944
  const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
2949
2945
  if (typeof window === "undefined") return capped;
2950
- const viewport = vertical ? window.innerHeight : window.innerWidth;
2946
+ const viewport = this.containingExtent(vertical);
2951
2947
  return viewport > 0 ? Math.min(capped, viewport) : capped;
2952
2948
  }
2949
+ // The fit cap is the sheet's containing block, not always the window: a
2950
+ // position:fixed sheet nested under a transform/filter/will-change ancestor
2951
+ // is clipped to that ancestor, so capping by window.innerHeight oversizes the
2952
+ // sheet and the content/footer get clipped. offsetParent resolves to that
2953
+ // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
2954
+ containingExtent(vertical) {
2955
+ const op = this.element.offsetParent;
2956
+ if (op && op !== document.body && op !== document.documentElement) {
2957
+ return vertical ? op.clientHeight : op.clientWidth;
2958
+ }
2959
+ return vertical ? window.innerHeight : window.innerWidth;
2960
+ }
2953
2961
  installFitObserver() {
2954
2962
  if (typeof ResizeObserver === "undefined") return;
2955
2963
  if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
@@ -3030,6 +3038,10 @@ var BottomSheetEngine = class {
3030
3038
  this.backdrop.style.opacity = "";
3031
3039
  this.backdrop.style.pointerEvents = "";
3032
3040
  }
3041
+ if (this.anchorHost) {
3042
+ this.anchorHost.style.removeProperty("--bs-size");
3043
+ this.anchorHost.style.removeProperty("--bs-progress");
3044
+ }
3033
3045
  if (this.screenComponent) {
3034
3046
  this.screenComponent.style.opacity = "";
3035
3047
  this.screenComponent.style.display = "";
@@ -3041,6 +3053,7 @@ var BottomSheetEngine = class {
3041
3053
  this.bus.emit(event, payload);
3042
3054
  }
3043
3055
  newCycle() {
3056
+ this.allowOvershoot = false;
3044
3057
  this.currentAbort.abort();
3045
3058
  this.currentAbort = new AbortController();
3046
3059
  return this.currentAbort.signal;
@@ -3106,6 +3119,7 @@ var BottomSheetEngine = class {
3106
3119
  seen.add(p.id);
3107
3120
  }
3108
3121
  this.snaps.setRaw(this.snapPointsRaw);
3122
+ this.clampToMaxHeight();
3109
3123
  this.scrim.invalidateOpacityCache();
3110
3124
  }
3111
3125
  getAllowedRange() {
@@ -3312,9 +3326,13 @@ var BottomSheetEngine = class {
3312
3326
  this.opening = true;
3313
3327
  sheetStack.promote(this.id);
3314
3328
  }
3315
- void this.animation.animateTo(target.size, velocity).then(() => {
3316
- this.opening = false;
3329
+ const settleCap = this.snaps.getMaxAxisSize();
3330
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3331
+ this.allowOvershoot = true;
3332
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3317
3333
  if (signal.aborted) return;
3334
+ this.allowOvershoot = false;
3335
+ this.opening = false;
3318
3336
  this.scrollCache.restore(target.id, previousSize, target.size);
3319
3337
  this.updateAriaSlider();
3320
3338
  this.emit("snap", {
@@ -3346,7 +3364,7 @@ var BottomSheetEngine = class {
3346
3364
  }
3347
3365
  applySize(size) {
3348
3366
  const cap = this.snaps.getMaxAxisSize();
3349
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging ? cap : size;
3367
+ const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3350
3368
  this.size = clamped;
3351
3369
  const offset = cap - clamped;
3352
3370
  const style = this.element.style;
@@ -3552,7 +3570,7 @@ var root_3 = $2.from_html(`<div class="bs-header"><!></div>`);
3552
3570
  var root_5 = $2.from_html(`<div class="bs-footer"><!></div>`);
3553
3571
  var root_6 = $2.from_html(`<div class="bs-button-slot" data-side="left"><!></div>`);
3554
3572
  var root_7 = $2.from_html(`<div class="bs-button-slot" data-side="right"><!></div>`);
3555
- var root = $2.from_html(`<div class="bs-root"><!> <div class="bs-screen"><!></div> <section class="bs-sheet" role="dialog"><div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet"></div> <!> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <!> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section> <!> <!></div>`);
3573
+ var root = $2.from_html(`<div class="bs-root"><!> <div class="bs-screen"><!></div> <section class="bs-sheet" role="dialog"><div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0"></div> <!> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <!> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section> <!> <!></div>`);
3556
3574
  function BottomSheet($$anchor, $$props) {
3557
3575
  $2.push($$props, true);
3558
3576
  let mode = $2.prop($$props, "mode", 3, "bottom"), animation = $2.prop($$props, "animation", 3, "spring"), focusTrap = $2.prop($$props, "focusTrap", 3, false), closeOnEscape = $2.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2.prop($$props, "rubberBand", 3, true), backdrop = $2.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2.prop($$props, "closeOnBackdrop", 3, true), persistent = $2.prop($$props, "persistent", 3, false), disableClose = $2.prop($$props, "disableClose", 3, false), disableDrag = $2.prop($$props, "disableDrag", 3, false), closeOnRouteChange = $2.prop($$props, "closeOnRouteChange", 3, false), openProp = $2.prop($$props, "open", 15), snap = $2.prop($$props, "snap", 15), ariaLabel = $2.prop($$props, "ariaLabel", 3, "Bottom sheet");
@@ -3677,8 +3695,6 @@ function BottomSheet($$anchor, $$props) {
3677
3695
  if (next && !isOpen) void engine.open();
3678
3696
  else if (!next && isOpen) void engine.close();
3679
3697
  });
3680
- let allowedIds = $2.derived(() => $$props.allowed && $$props.allowed.length > 0 ? $$props.allowed : $$props.snapPoints.map((s) => s.id));
3681
- let activeIdx = $2.derived(() => $2.get(allowedIds).indexOf($2.get(viewState).activeId));
3682
3698
  let isVerticalAxis = $2.derived(() => mode() === "bottom" || mode() === "top");
3683
3699
  var $$exports = {
3684
3700
  snapTo,
@@ -3732,7 +3748,6 @@ function BottomSheet($$anchor, $$props) {
3732
3748
  $2.bind_this(div_2, ($$value) => $2.set(screenEl, $$value), () => $2.get(screenEl));
3733
3749
  var section = $2.sibling(div_2, 2);
3734
3750
  var div_3 = $2.child(section);
3735
- $2.set_attribute(div_3, "aria-valuemin", 0);
3736
3751
  $2.bind_this(div_3, ($$value) => $2.set(handleEl, $$value), () => $2.get(handleEl));
3737
3752
  var node_3 = $2.sibling(div_3, 2);
3738
3753
  {
@@ -3809,23 +3824,14 @@ function BottomSheet($$anchor, $$props) {
3809
3824
  });
3810
3825
  }
3811
3826
  $2.reset(div);
3812
- $2.template_effect(
3813
- ($0, $1) => {
3814
- $2.set_attribute(section, "data-mode", mode());
3815
- $2.set_attribute(section, "data-active", $2.get(viewState).activeId);
3816
- $2.set_attribute(section, "aria-modal", focusTrap() ? "true" : void 0);
3817
- $2.set_attribute(section, "aria-label", ariaLabel());
3818
- $2.set_attribute(div_3, "aria-orientation", $2.get(isVerticalAxis) ? "vertical" : "horizontal");
3819
- $2.set_attribute(div_3, "aria-valuemax", $0);
3820
- $2.set_attribute(div_3, "aria-valuenow", $1);
3821
- $2.set_attribute(div_3, "aria-valuetext", $2.get(viewState).activeId);
3822
- $2.set_text(text, $2.get(viewState).activeId);
3823
- },
3824
- [
3825
- () => Math.max(0, $2.get(allowedIds).length - 1),
3826
- () => Math.max(0, $2.get(activeIdx))
3827
- ]
3828
- );
3827
+ $2.template_effect(() => {
3828
+ $2.set_attribute(section, "data-mode", mode());
3829
+ $2.set_attribute(section, "data-active", $2.get(viewState).activeId);
3830
+ $2.set_attribute(section, "aria-modal", focusTrap() ? "true" : void 0);
3831
+ $2.set_attribute(section, "aria-label", ariaLabel());
3832
+ $2.set_attribute(div_3, "aria-orientation", $2.get(isVerticalAxis) ? "vertical" : "horizontal");
3833
+ $2.set_text(text, $2.get(viewState).activeId);
3834
+ });
3829
3835
  $2.append($$anchor, div);
3830
3836
  return $2.pop($$exports);
3831
3837
  }
package/dist/vue.cjs CHANGED
@@ -2359,6 +2359,7 @@ var BottomSheetEngine = class {
2359
2359
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2360
2360
  __publicField(this, "isTopSheet", true);
2361
2361
  __publicField(this, "opening", false);
2362
+ __publicField(this, "allowOvershoot", false);
2362
2363
  __publicField(this, "progressPayload", {
2363
2364
  value: 0,
2364
2365
  size: 0
@@ -2911,45 +2912,52 @@ var BottomSheetEngine = class {
2911
2912
  }
2912
2913
  return out;
2913
2914
  }
2914
- measureContentExtent(vertical) {
2915
+ measureSheetNatural(vertical) {
2916
+ const el = this.element;
2915
2917
  const content = this.scrollContainer;
2916
- if (!content) return 0;
2917
- const targets = this.contentTargets();
2918
- if (targets.length === 0) {
2919
- return vertical ? content.scrollHeight : content.scrollWidth;
2918
+ if (!content) {
2919
+ return vertical ? el.offsetHeight : el.offsetWidth;
2920
2920
  }
2921
- let min = Infinity;
2922
- let max = -Infinity;
2923
- for (const el of targets) {
2924
- const r = el.getBoundingClientRect();
2925
- if (vertical) {
2926
- if (r.top < min) min = r.top;
2927
- if (r.bottom > max) max = r.bottom;
2928
- } else {
2929
- if (r.left < min) min = r.left;
2930
- if (r.right > max) max = r.right;
2931
- }
2932
- }
2933
- if (!isFinite(min) || !isFinite(max) || max <= min) {
2934
- return vertical ? content.scrollHeight : content.scrollWidth;
2935
- }
2936
- let pad = 0;
2937
- if (typeof getComputedStyle !== "undefined") {
2938
- const cs = getComputedStyle(content);
2939
- pad = vertical ? (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0) : (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0);
2940
- }
2941
- return max - min + pad;
2921
+ const axis = vertical ? "height" : "width";
2922
+ const sheetStyle = el.style;
2923
+ const cs = content.style;
2924
+ const prevSheet = sheetStyle[axis];
2925
+ const prevFlex = cs.flex;
2926
+ const prevContent = vertical ? cs.height : cs.width;
2927
+ sheetStyle[axis] = "auto";
2928
+ cs.flex = "none";
2929
+ if (vertical) cs.height = "auto";
2930
+ else cs.width = "auto";
2931
+ const poked = vertical ? el.offsetHeight : el.offsetWidth;
2932
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
2933
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
2934
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
2935
+ sheetStyle[axis] = prevSheet;
2936
+ cs.flex = prevFlex;
2937
+ if (vertical) cs.height = prevContent;
2938
+ else cs.width = prevContent;
2939
+ return natural;
2942
2940
  }
2943
2941
  measureFitSize() {
2944
2942
  const vertical = layoutAxis(this.mode) === "height";
2945
- const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2946
- const content = this.scrollContainer;
2947
- const natural = content ? handleSize + this.measureContentExtent(vertical) : handleSize;
2943
+ const natural = this.measureSheetNatural(vertical);
2948
2944
  const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
2949
2945
  if (typeof window === "undefined") return capped;
2950
- const viewport = vertical ? window.innerHeight : window.innerWidth;
2946
+ const viewport = this.containingExtent(vertical);
2951
2947
  return viewport > 0 ? Math.min(capped, viewport) : capped;
2952
2948
  }
2949
+ // The fit cap is the sheet's containing block, not always the window: a
2950
+ // position:fixed sheet nested under a transform/filter/will-change ancestor
2951
+ // is clipped to that ancestor, so capping by window.innerHeight oversizes the
2952
+ // sheet and the content/footer get clipped. offsetParent resolves to that
2953
+ // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
2954
+ containingExtent(vertical) {
2955
+ const op = this.element.offsetParent;
2956
+ if (op && op !== document.body && op !== document.documentElement) {
2957
+ return vertical ? op.clientHeight : op.clientWidth;
2958
+ }
2959
+ return vertical ? window.innerHeight : window.innerWidth;
2960
+ }
2953
2961
  installFitObserver() {
2954
2962
  if (typeof ResizeObserver === "undefined") return;
2955
2963
  if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
@@ -3030,6 +3038,10 @@ var BottomSheetEngine = class {
3030
3038
  this.backdrop.style.opacity = "";
3031
3039
  this.backdrop.style.pointerEvents = "";
3032
3040
  }
3041
+ if (this.anchorHost) {
3042
+ this.anchorHost.style.removeProperty("--bs-size");
3043
+ this.anchorHost.style.removeProperty("--bs-progress");
3044
+ }
3033
3045
  if (this.screenComponent) {
3034
3046
  this.screenComponent.style.opacity = "";
3035
3047
  this.screenComponent.style.display = "";
@@ -3041,6 +3053,7 @@ var BottomSheetEngine = class {
3041
3053
  this.bus.emit(event, payload);
3042
3054
  }
3043
3055
  newCycle() {
3056
+ this.allowOvershoot = false;
3044
3057
  this.currentAbort.abort();
3045
3058
  this.currentAbort = new AbortController();
3046
3059
  return this.currentAbort.signal;
@@ -3106,6 +3119,7 @@ var BottomSheetEngine = class {
3106
3119
  seen.add(p.id);
3107
3120
  }
3108
3121
  this.snaps.setRaw(this.snapPointsRaw);
3122
+ this.clampToMaxHeight();
3109
3123
  this.scrim.invalidateOpacityCache();
3110
3124
  }
3111
3125
  getAllowedRange() {
@@ -3312,9 +3326,13 @@ var BottomSheetEngine = class {
3312
3326
  this.opening = true;
3313
3327
  sheetStack.promote(this.id);
3314
3328
  }
3315
- void this.animation.animateTo(target.size, velocity).then(() => {
3316
- this.opening = false;
3329
+ const settleCap = this.snaps.getMaxAxisSize();
3330
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3331
+ this.allowOvershoot = true;
3332
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3317
3333
  if (signal.aborted) return;
3334
+ this.allowOvershoot = false;
3335
+ this.opening = false;
3318
3336
  this.scrollCache.restore(target.id, previousSize, target.size);
3319
3337
  this.updateAriaSlider();
3320
3338
  this.emit("snap", {
@@ -3346,7 +3364,7 @@ var BottomSheetEngine = class {
3346
3364
  }
3347
3365
  applySize(size) {
3348
3366
  const cap = this.snaps.getMaxAxisSize();
3349
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging ? cap : size;
3367
+ const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3350
3368
  this.size = clamped;
3351
3369
  const offset = cap - clamped;
3352
3370
  const style = this.element.style;
@@ -3939,11 +3957,6 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
3939
3957
  const mode = props.mode ?? "bottom";
3940
3958
  return mode === "bottom" || mode === "top";
3941
3959
  });
3942
- const allowedIds = vue.computed(() => props.allowed ?? props.snapPoints.map((p) => p.id));
3943
- const activeIdx = vue.computed(() => {
3944
- const idx = allowedIds.value.indexOf(state.activeId);
3945
- return idx === -1 ? 0 : idx;
3946
- });
3947
3960
  const ariaModal = vue.computed(() => props.focusTrap === true);
3948
3961
  __expose({
3949
3962
  snapTo,
@@ -4013,8 +4026,6 @@ var _sfc_main = /* @__PURE__ */ vue.defineComponent({
4013
4026
  anchorsKey,
4014
4027
  applyStages,
4015
4028
  isVerticalAxis,
4016
- allowedIds,
4017
- activeIdx,
4018
4029
  ariaModal
4019
4030
  };
4020
4031
  Object.defineProperty(__returned__, "__isScriptSetup", {
@@ -4036,12 +4047,7 @@ var _hoisted_3 = [
4036
4047
  "aria-modal",
4037
4048
  "aria-label"
4038
4049
  ];
4039
- var _hoisted_4 = [
4040
- "aria-orientation",
4041
- "aria-valuemax",
4042
- "aria-valuenow",
4043
- "aria-valuetext"
4044
- ];
4050
+ var _hoisted_4 = ["aria-orientation"];
4045
4051
  var _hoisted_5 = {
4046
4052
  key: 0,
4047
4053
  class: "bs-header"
@@ -4104,10 +4110,6 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
4104
4110
  role: "slider",
4105
4111
  tabindex: "0",
4106
4112
  "aria-orientation": $setup.isVerticalAxis ? "vertical" : "horizontal",
4107
- "aria-valuemin": "0",
4108
- "aria-valuemax": Math.max(0, $setup.allowedIds.length - 1),
4109
- "aria-valuenow": $setup.activeIdx,
4110
- "aria-valuetext": $setup.state.activeId,
4111
4113
  "aria-label": "Resize sheet"
4112
4114
  }, null, 8, _hoisted_4),
4113
4115
  _ctx.$slots.header ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5, [vue.renderSlot(_ctx.$slots, "header", { state: $setup.state })])) : vue.createCommentVNode("v-if", true),
package/dist/vue.js CHANGED
@@ -2357,6 +2357,7 @@ var BottomSheetEngine = class {
2357
2357
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2358
2358
  __publicField(this, "isTopSheet", true);
2359
2359
  __publicField(this, "opening", false);
2360
+ __publicField(this, "allowOvershoot", false);
2360
2361
  __publicField(this, "progressPayload", {
2361
2362
  value: 0,
2362
2363
  size: 0
@@ -2909,45 +2910,52 @@ var BottomSheetEngine = class {
2909
2910
  }
2910
2911
  return out;
2911
2912
  }
2912
- measureContentExtent(vertical) {
2913
+ measureSheetNatural(vertical) {
2914
+ const el = this.element;
2913
2915
  const content = this.scrollContainer;
2914
- if (!content) return 0;
2915
- const targets = this.contentTargets();
2916
- if (targets.length === 0) {
2917
- return vertical ? content.scrollHeight : content.scrollWidth;
2916
+ if (!content) {
2917
+ return vertical ? el.offsetHeight : el.offsetWidth;
2918
2918
  }
2919
- let min = Infinity;
2920
- let max = -Infinity;
2921
- for (const el of targets) {
2922
- const r = el.getBoundingClientRect();
2923
- if (vertical) {
2924
- if (r.top < min) min = r.top;
2925
- if (r.bottom > max) max = r.bottom;
2926
- } else {
2927
- if (r.left < min) min = r.left;
2928
- if (r.right > max) max = r.right;
2929
- }
2930
- }
2931
- if (!isFinite(min) || !isFinite(max) || max <= min) {
2932
- return vertical ? content.scrollHeight : content.scrollWidth;
2933
- }
2934
- let pad = 0;
2935
- if (typeof getComputedStyle !== "undefined") {
2936
- const cs = getComputedStyle(content);
2937
- pad = vertical ? (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0) : (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0);
2938
- }
2939
- return max - min + pad;
2919
+ const axis = vertical ? "height" : "width";
2920
+ const sheetStyle = el.style;
2921
+ const cs = content.style;
2922
+ const prevSheet = sheetStyle[axis];
2923
+ const prevFlex = cs.flex;
2924
+ const prevContent = vertical ? cs.height : cs.width;
2925
+ sheetStyle[axis] = "auto";
2926
+ cs.flex = "none";
2927
+ if (vertical) cs.height = "auto";
2928
+ else cs.width = "auto";
2929
+ const poked = vertical ? el.offsetHeight : el.offsetWidth;
2930
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
2931
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
2932
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
2933
+ sheetStyle[axis] = prevSheet;
2934
+ cs.flex = prevFlex;
2935
+ if (vertical) cs.height = prevContent;
2936
+ else cs.width = prevContent;
2937
+ return natural;
2940
2938
  }
2941
2939
  measureFitSize() {
2942
2940
  const vertical = layoutAxis(this.mode) === "height";
2943
- const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2944
- const content = this.scrollContainer;
2945
- const natural = content ? handleSize + this.measureContentExtent(vertical) : handleSize;
2941
+ const natural = this.measureSheetNatural(vertical);
2946
2942
  const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
2947
2943
  if (typeof window === "undefined") return capped;
2948
- const viewport = vertical ? window.innerHeight : window.innerWidth;
2944
+ const viewport = this.containingExtent(vertical);
2949
2945
  return viewport > 0 ? Math.min(capped, viewport) : capped;
2950
2946
  }
2947
+ // The fit cap is the sheet's containing block, not always the window: a
2948
+ // position:fixed sheet nested under a transform/filter/will-change ancestor
2949
+ // is clipped to that ancestor, so capping by window.innerHeight oversizes the
2950
+ // sheet and the content/footer get clipped. offsetParent resolves to that
2951
+ // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
2952
+ containingExtent(vertical) {
2953
+ const op = this.element.offsetParent;
2954
+ if (op && op !== document.body && op !== document.documentElement) {
2955
+ return vertical ? op.clientHeight : op.clientWidth;
2956
+ }
2957
+ return vertical ? window.innerHeight : window.innerWidth;
2958
+ }
2951
2959
  installFitObserver() {
2952
2960
  if (typeof ResizeObserver === "undefined") return;
2953
2961
  if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
@@ -3028,6 +3036,10 @@ var BottomSheetEngine = class {
3028
3036
  this.backdrop.style.opacity = "";
3029
3037
  this.backdrop.style.pointerEvents = "";
3030
3038
  }
3039
+ if (this.anchorHost) {
3040
+ this.anchorHost.style.removeProperty("--bs-size");
3041
+ this.anchorHost.style.removeProperty("--bs-progress");
3042
+ }
3031
3043
  if (this.screenComponent) {
3032
3044
  this.screenComponent.style.opacity = "";
3033
3045
  this.screenComponent.style.display = "";
@@ -3039,6 +3051,7 @@ var BottomSheetEngine = class {
3039
3051
  this.bus.emit(event, payload);
3040
3052
  }
3041
3053
  newCycle() {
3054
+ this.allowOvershoot = false;
3042
3055
  this.currentAbort.abort();
3043
3056
  this.currentAbort = new AbortController();
3044
3057
  return this.currentAbort.signal;
@@ -3104,6 +3117,7 @@ var BottomSheetEngine = class {
3104
3117
  seen.add(p.id);
3105
3118
  }
3106
3119
  this.snaps.setRaw(this.snapPointsRaw);
3120
+ this.clampToMaxHeight();
3107
3121
  this.scrim.invalidateOpacityCache();
3108
3122
  }
3109
3123
  getAllowedRange() {
@@ -3310,9 +3324,13 @@ var BottomSheetEngine = class {
3310
3324
  this.opening = true;
3311
3325
  sheetStack.promote(this.id);
3312
3326
  }
3313
- void this.animation.animateTo(target.size, velocity).then(() => {
3314
- this.opening = false;
3327
+ const settleCap = this.snaps.getMaxAxisSize();
3328
+ const settleSize = settleCap > 0 ? Math.min(target.size, settleCap) : target.size;
3329
+ this.allowOvershoot = true;
3330
+ void this.animation.animateTo(settleSize, velocity).then(() => {
3315
3331
  if (signal.aborted) return;
3332
+ this.allowOvershoot = false;
3333
+ this.opening = false;
3316
3334
  this.scrollCache.restore(target.id, previousSize, target.size);
3317
3335
  this.updateAriaSlider();
3318
3336
  this.emit("snap", {
@@ -3344,7 +3362,7 @@ var BottomSheetEngine = class {
3344
3362
  }
3345
3363
  applySize(size) {
3346
3364
  const cap = this.snaps.getMaxAxisSize();
3347
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging ? cap : size;
3365
+ const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3348
3366
  this.size = clamped;
3349
3367
  const offset = cap - clamped;
3350
3368
  const style = this.element.style;
@@ -3937,11 +3955,6 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
3937
3955
  const mode = props.mode ?? "bottom";
3938
3956
  return mode === "bottom" || mode === "top";
3939
3957
  });
3940
- const allowedIds = computed(() => props.allowed ?? props.snapPoints.map((p) => p.id));
3941
- const activeIdx = computed(() => {
3942
- const idx = allowedIds.value.indexOf(state.activeId);
3943
- return idx === -1 ? 0 : idx;
3944
- });
3945
3958
  const ariaModal = computed(() => props.focusTrap === true);
3946
3959
  __expose({
3947
3960
  snapTo,
@@ -4011,8 +4024,6 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
4011
4024
  anchorsKey,
4012
4025
  applyStages,
4013
4026
  isVerticalAxis,
4014
- allowedIds,
4015
- activeIdx,
4016
4027
  ariaModal
4017
4028
  };
4018
4029
  Object.defineProperty(__returned__, "__isScriptSetup", {
@@ -4034,12 +4045,7 @@ var _hoisted_3 = [
4034
4045
  "aria-modal",
4035
4046
  "aria-label"
4036
4047
  ];
4037
- var _hoisted_4 = [
4038
- "aria-orientation",
4039
- "aria-valuemax",
4040
- "aria-valuenow",
4041
- "aria-valuetext"
4042
- ];
4048
+ var _hoisted_4 = ["aria-orientation"];
4043
4049
  var _hoisted_5 = {
4044
4050
  key: 0,
4045
4051
  class: "bs-header"
@@ -4102,10 +4108,6 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
4102
4108
  role: "slider",
4103
4109
  tabindex: "0",
4104
4110
  "aria-orientation": $setup.isVerticalAxis ? "vertical" : "horizontal",
4105
- "aria-valuemin": "0",
4106
- "aria-valuemax": Math.max(0, $setup.allowedIds.length - 1),
4107
- "aria-valuenow": $setup.activeIdx,
4108
- "aria-valuetext": $setup.state.activeId,
4109
4111
  "aria-label": "Resize sheet"
4110
4112
  }, null, 8, _hoisted_4),
4111
4113
  _ctx.$slots.header ? (openBlock(), createElementBlock("div", _hoisted_5, [renderSlot(_ctx.$slots, "header", { state: $setup.state })])) : createCommentVNode("v-if", true),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surdeddd/bottom-sheet",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Universal, headless bottom sheet — framework-agnostic core with React, Vue 3, Svelte, Solid, Qwik, Preact, Lit and Custom Element adapters. Pointer Events, snap points, momentum, a11y, SSR-safe.",
5
5
  "type": "module",
6
6
  "license": "MIT",