@rcarls/rc-bottom-sheet 0.6.0 → 0.7.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.
- package/CHANGELOG.md +148 -0
- package/README.md +23 -3
- package/dist/custom-elements.json +124 -24
- package/dist/rc-bottom-sheet-5mYgsRK-.js +257 -0
- package/dist/rc-bottom-sheet-5mYgsRK-.js.map +1 -0
- package/dist/rc-bottom-sheet-define.js +1 -1
- package/dist/rc-bottom-sheet.js +1 -1
- package/dist/types/packages/rc-bottom-sheet/src/rc-bottom-sheet.d.ts +43 -10
- package/package.json +3 -3
- package/dist/rc-bottom-sheet-BZg5AVUG.js +0 -248
- package/dist/rc-bottom-sheet-BZg5AVUG.js.map +0 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { property as p } from "lit/decorators.js";
|
|
2
|
+
import { RCDialog as y } from "@rcarls/rc-dialog";
|
|
3
|
+
import { DialogGeometry as _ } from "@rcarls/rc-dialog/geometry";
|
|
4
|
+
import { findExtremeSnapIndex as v, findNearestSnapIndex as x, cssZoom as m, parseCssTime as S, cssText as w } from "@rcarls/rc-common";
|
|
5
|
+
var z = Object.defineProperty, h = (r, t, i, s) => {
|
|
6
|
+
for (var n = void 0, e = r.length - 1, o; e >= 0; e--)
|
|
7
|
+
(o = r[e]) && (n = o(t, i, n) || n);
|
|
8
|
+
return n && z(t, i, n), n;
|
|
9
|
+
};
|
|
10
|
+
const k = 300, E = "cubic-bezier(0.4, 0, 0.2, 1)";
|
|
11
|
+
function F(r) {
|
|
12
|
+
const t = [];
|
|
13
|
+
let i = 0, s = "";
|
|
14
|
+
for (const n of r.trim())
|
|
15
|
+
n === "(" ? i += 1 : n === ")" && (i = Math.max(0, i - 1)), i === 0 && /\s/.test(n) ? s && (t.push(s), s = "") : s += n;
|
|
16
|
+
return s && t.push(s), t;
|
|
17
|
+
}
|
|
18
|
+
const T = w`@layer rc-base{rc-bottom-sheet>dialog{position:var(--rc-bottom-sheet-position,fixed);inset-block-start:auto;inset-block-end:var(--rc-bottom-sheet-inset-block-end,0px);inset-inline:var(--rc-bottom-sheet-inset-inline,0px);box-sizing:border-box;inline-size:100%;max-inline-size:var(--rc-bottom-sheet-max-inline-size,40rem);max-block-size:var(--rc-bottom-sheet-max-block-size,70dvh);margin-block:0;margin-inline:auto;overflow:auto}rc-bottom-sheet>dialog [data-rc-bottom-sheet-handle]{box-sizing:border-box;cursor:ns-resize;touch-action:none;-webkit-tap-highlight-color:transparent}rc-bottom-sheet>dialog>[data-rc-bottom-sheet-handle]{inline-size:var(--rc-bottom-sheet-handle-target-inline-size,100%);min-block-size:var(--rc-bottom-sheet-handle-target-block-size,3rem);margin:var(--rc-bottom-sheet-handle-target-margin,0);border-radius:inherit;appearance:none;background:0 0;border:0;padding:0;display:block;position:relative}rc-bottom-sheet>dialog>[data-rc-bottom-sheet-handle]:before{content:"";inline-size:var(--rc-bottom-sheet-handle-inline-size,2rem);block-size:var(--rc-bottom-sheet-handle-block-size,.25rem);border-radius:var(--rc-bottom-sheet-handle-radius,999px);background:var(--rc-bottom-sheet-handle-color,GrayText);position:absolute;inset-block-start:var(--rc-bottom-sheet-handle-offset,50%);inset-inline-start:50%;translate:-50% -50%}rc-bottom-sheet>dialog [data-rc-bottom-sheet-handle]:focus-visible{outline-offset:-.25rem;outline:auto}@media (prefers-reduced-motion:no-preference){rc-bottom-sheet>dialog[open]{opacity:1;transition:opacity var(--rc-motion-effects-duration-default,.2s) var(--rc-motion-effects-easing-enter,ease-out), overlay var(--rc-motion-effects-duration-default,.2s) allow-discrete, display var(--rc-motion-effects-duration-default,.2s) allow-discrete}rc-bottom-sheet>dialog:not([open]){opacity:0;transition:opacity var(--rc-motion-effects-duration-default,.2s) var(--rc-motion-effects-easing-exit,ease-in), overlay var(--rc-motion-effects-duration-default,.2s) allow-discrete, display var(--rc-motion-effects-duration-default,.2s) allow-discrete}@starting-style{rc-bottom-sheet>dialog[open]{opacity:0}}rc-bottom-sheet>dialog[open]::backdrop{opacity:1;transition:opacity var(--rc-motion-effects-duration-default,.2s) var(--rc-motion-effects-easing-enter,ease-out)}rc-bottom-sheet>dialog:not([open])::backdrop{opacity:0;transition:opacity var(--rc-motion-effects-duration-default,.2s) var(--rc-motion-effects-easing-exit,ease-in)}@starting-style{rc-bottom-sheet>dialog[open]::backdrop{opacity:0}}}}`, I = 24, P = 16, A = [
|
|
19
|
+
"inset",
|
|
20
|
+
"top",
|
|
21
|
+
"right",
|
|
22
|
+
"bottom",
|
|
23
|
+
"left",
|
|
24
|
+
"inset-block-start",
|
|
25
|
+
"inset-block-end",
|
|
26
|
+
"inset-inline-start",
|
|
27
|
+
"inset-inline-end",
|
|
28
|
+
"width",
|
|
29
|
+
"margin",
|
|
30
|
+
"translate"
|
|
31
|
+
];
|
|
32
|
+
function g(r) {
|
|
33
|
+
const t = b(r);
|
|
34
|
+
for (const i of A)
|
|
35
|
+
r.style.removeProperty(i);
|
|
36
|
+
return r.style.marginBlock = "0", r.style.boxSizing = "border-box", r.style.height = `${t}px`, t;
|
|
37
|
+
}
|
|
38
|
+
function b(r) {
|
|
39
|
+
return r.getBoundingClientRect().height / m(r);
|
|
40
|
+
}
|
|
41
|
+
var c;
|
|
42
|
+
const d = (c = class extends y {
|
|
43
|
+
constructor() {
|
|
44
|
+
super(...arguments), this.closedBy = "any", this.resize = "vertical", this.resizeOrigin = "top", this.resizeHandle = "[data-rc-bottom-sheet-handle]", this.snapPoints = "", this.swipeDismiss = !0, this.swipeVelocity = 500, this._reducedMotion = typeof window < "u" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null, this._activeSnapAnimation = null, this._lastSnapIndex = null, this._pendingSnap = null, this._extendedForFocus = !1, this._onWindowResize = () => {
|
|
45
|
+
!this.open || this._lastSnapIndex === null || (this._extendedForFocus = !1, this._$dialog?.style.removeProperty("max-block-size"), this._snapToIndex(this._lastSnapIndex, "instant", "api"), this._revealFocusedField());
|
|
46
|
+
}, this._onFocusIn = () => {
|
|
47
|
+
this._revealFocusedField();
|
|
48
|
+
}, this._onFocusOut = (t) => {
|
|
49
|
+
const i = this._$dialog;
|
|
50
|
+
!this._extendedForFocus || !i || t.relatedTarget instanceof Node && i.contains(t.relatedTarget) || (this._extendedForFocus = !1, i.style.removeProperty("max-block-size"), this._lastSnapIndex !== null && this._snapToIndex(this._lastSnapIndex, "animated", "api"));
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
static _ensureBaseStyles(t) {
|
|
54
|
+
if (c._styledRoots.has(t))
|
|
55
|
+
return;
|
|
56
|
+
c._styledRoots.add(t);
|
|
57
|
+
const i = document.createElement("style");
|
|
58
|
+
i.setAttribute("data-rc-light-dom-base", "rc-bottom-sheet"), i.textContent = T, t instanceof Document ? t.head.append(i) : t.append(i);
|
|
59
|
+
}
|
|
60
|
+
connectedCallback() {
|
|
61
|
+
super.connectedCallback(), c._ensureBaseStyles(this.getRootNode()), window.addEventListener("resize", this._onWindowResize), this.addEventListener("focusin", this._onFocusIn), this.addEventListener("focusout", this._onFocusOut);
|
|
62
|
+
}
|
|
63
|
+
disconnectedCallback() {
|
|
64
|
+
super.disconnectedCallback(), window.removeEventListener("resize", this._onWindowResize), this.removeEventListener("focusin", this._onFocusIn), this.removeEventListener("focusout", this._onFocusOut);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Snaps to a declared target by zero-based index.
|
|
68
|
+
*
|
|
69
|
+
* Finite fractional indices are truncated and out-of-range indices clamp to
|
|
70
|
+
* the nearest endpoint. Non-finite indices and calls without valid snap
|
|
71
|
+
* points have no effect.
|
|
72
|
+
*
|
|
73
|
+
* @param index - zero-based index into `snapPoints`
|
|
74
|
+
* @param behavior - whether to animate the movement
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* const sheet = document.querySelector('rc-bottom-sheet');
|
|
78
|
+
* sheet.snapTo(1);
|
|
79
|
+
*/
|
|
80
|
+
snapTo(t, i = "animated") {
|
|
81
|
+
if (Number.isFinite(t)) {
|
|
82
|
+
if (!this._$dialog) {
|
|
83
|
+
this._pendingSnap = { index: Math.trunc(t), behavior: i };
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this._snapToIndex(Math.trunc(t), i, "api");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
_applyOpen(t, i, s = !0, n = !0) {
|
|
90
|
+
const e = super._applyOpen(t, i, s, n), o = this._pendingSnap;
|
|
91
|
+
return e && t && o && (this._pendingSnap = null, this._snapToIndex(o.index, o.behavior, "api")), e;
|
|
92
|
+
}
|
|
93
|
+
_onResizeStart(t) {
|
|
94
|
+
this._activeSnapAnimation?.cancel(), this._activeSnapAnimation = null, this.dispatchEvent(
|
|
95
|
+
new CustomEvent("rc-bottom-sheet-resize-start", {
|
|
96
|
+
bubbles: !0,
|
|
97
|
+
composed: !0,
|
|
98
|
+
detail: { height: t.height, inputType: t.inputType }
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
_onResizeEnd(t) {
|
|
103
|
+
if (t.inputType === "pointer" && this.swipeDismiss && this._shouldSwipeDismiss(t)) {
|
|
104
|
+
this.requestClose();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const i = this._resolvedSnapPoints(), s = t.edge.includes("n") ? -t.velocityY : t.velocityY, n = Math.abs(t.height - t.startHeight);
|
|
108
|
+
if (t.inputType === "pointer" && i.length > 0 && n >= I && Math.abs(s) >= this.swipeVelocity) {
|
|
109
|
+
const e = v(i, s > 0 ? 1 : -1);
|
|
110
|
+
this._snapToIndex(e, "animated", "drag", i);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this._snapToNearestPoint("drag", i);
|
|
114
|
+
}
|
|
115
|
+
_shouldSwipeDismiss(t) {
|
|
116
|
+
return t.edge.includes("n") && t.startHeight - t.height >= c.swipeDismissThreshold;
|
|
117
|
+
}
|
|
118
|
+
_snapToNearestPoint(t = "api", i = this._resolvedSnapPoints()) {
|
|
119
|
+
const s = this._$dialog;
|
|
120
|
+
if (!s || i.length === 0)
|
|
121
|
+
return;
|
|
122
|
+
const n = b(s), e = x(i, n);
|
|
123
|
+
this._snapToIndex(e, "animated", t, i);
|
|
124
|
+
}
|
|
125
|
+
_snapToIndex(t, i, s, n = this._resolvedSnapPoints()) {
|
|
126
|
+
const e = this._$dialog;
|
|
127
|
+
if (!e || n.length === 0)
|
|
128
|
+
return;
|
|
129
|
+
const o = g(e), a = Math.max(0, Math.min(t, n.length - 1)), l = n[a];
|
|
130
|
+
this._lastSnapIndex = a, this._activeSnapAnimation?.cancel(), this._activeSnapAnimation = null, e.style.height = `${l}px`;
|
|
131
|
+
const u = b(e);
|
|
132
|
+
e.style.height = `${o}px`, this._applySnap(e, u, i, () => {
|
|
133
|
+
this.dispatchEvent(
|
|
134
|
+
new CustomEvent("rc-bottom-sheet-snap", {
|
|
135
|
+
bubbles: !0,
|
|
136
|
+
composed: !0,
|
|
137
|
+
detail: { index: a, height: u, trigger: s }
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
_applySnap(t, i, s, n) {
|
|
143
|
+
const e = b(t);
|
|
144
|
+
if (this._activeSnapAnimation?.cancel(), this._activeSnapAnimation = null, s === "instant" || this._reducedMotion?.matches || e === i) {
|
|
145
|
+
t.style.height = `${i}px`, n();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const o = t.animate([{ height: `${e}px` }, { height: `${i}px` }], {
|
|
149
|
+
duration: this._snapDuration(t),
|
|
150
|
+
easing: this._snapEasing(t),
|
|
151
|
+
fill: "forwards"
|
|
152
|
+
});
|
|
153
|
+
this._activeSnapAnimation = o, o.finished.then(
|
|
154
|
+
() => {
|
|
155
|
+
if (this._activeSnapAnimation !== o) {
|
|
156
|
+
o.cancel();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
t.style.height = `${i}px`, o.cancel(), this._activeSnapAnimation = null, n();
|
|
160
|
+
},
|
|
161
|
+
() => {
|
|
162
|
+
o.cancel(), this._activeSnapAnimation === o && (this._activeSnapAnimation = null);
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Keeps a focused field inside the sheet visible, as Android and Material
|
|
168
|
+
* sheets do above a software keyboard: scroll it into view within the
|
|
169
|
+
* sheet's own scroll containers, then grow the sheet past its snap height
|
|
170
|
+
* and CSS maximum (up to the space above its docked edge) only when
|
|
171
|
+
* scrolling cannot reveal it.
|
|
172
|
+
*/
|
|
173
|
+
_revealFocusedField() {
|
|
174
|
+
const t = this._$dialog, i = document.activeElement;
|
|
175
|
+
if (!this.open || !t || !(i instanceof HTMLElement) || !t.contains(i))
|
|
176
|
+
return;
|
|
177
|
+
for (let o = i.parentElement; o && t.contains(o); o = o.parentElement) {
|
|
178
|
+
if (o.scrollHeight <= o.clientHeight || !/auto|scroll/.test(getComputedStyle(o).overflowY))
|
|
179
|
+
continue;
|
|
180
|
+
const a = o.getBoundingClientRect(), l = i.getBoundingClientRect(), u = m(o);
|
|
181
|
+
if (l.bottom > a.bottom) {
|
|
182
|
+
const f = Math.min(l.bottom - a.bottom, l.top - a.top);
|
|
183
|
+
o.scrollTop += f / u;
|
|
184
|
+
} else l.top < a.top && (o.scrollTop -= (a.top - l.top) / u);
|
|
185
|
+
}
|
|
186
|
+
const s = t.getBoundingClientRect(), n = i.getBoundingClientRect();
|
|
187
|
+
if (n.bottom <= s.bottom)
|
|
188
|
+
return;
|
|
189
|
+
const e = Math.min(n.bottom - s.top + P, s.bottom) / m(t);
|
|
190
|
+
this._activeSnapAnimation?.cancel(), this._activeSnapAnimation = null, this._extendedForFocus = !0, g(t), t.style.maxBlockSize = "none", t.style.height = `${e}px`;
|
|
191
|
+
}
|
|
192
|
+
_snapDuration(t) {
|
|
193
|
+
const i = getComputedStyle(t).getPropertyValue("--rc-bottom-sheet-snap-duration");
|
|
194
|
+
return S(i) ?? k;
|
|
195
|
+
}
|
|
196
|
+
_snapEasing(t) {
|
|
197
|
+
return getComputedStyle(t).getPropertyValue("--rc-bottom-sheet-snap-easing").trim() || E;
|
|
198
|
+
}
|
|
199
|
+
_resolvedSnapPoints() {
|
|
200
|
+
if (!this.snapPoints.trim())
|
|
201
|
+
return [];
|
|
202
|
+
const t = window.innerHeight / m(this._$dialog ?? this), i = [], s = [];
|
|
203
|
+
for (const n of F(this.snapPoints)) {
|
|
204
|
+
const e = Number.parseFloat(n);
|
|
205
|
+
if (Number.isFinite(e) && n.endsWith("%"))
|
|
206
|
+
i.push(t * (e / 100));
|
|
207
|
+
else if (Number.isFinite(e) && (n.endsWith("px") || String(e) === n))
|
|
208
|
+
i.push(e);
|
|
209
|
+
else {
|
|
210
|
+
const o = document.createElement("div");
|
|
211
|
+
i.push(Number.NaN), s.push({ index: i.length - 1, point: n, probe: o });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return s.length > 0 && this._measureSnapPoints(s, i, t), i.filter((n) => Number.isFinite(n) && n > 0);
|
|
215
|
+
}
|
|
216
|
+
_measureSnapPoints(t, i, s) {
|
|
217
|
+
const n = document.createElement("div"), e = (o, a, l) => {
|
|
218
|
+
o.style.setProperty(a, l, "important");
|
|
219
|
+
};
|
|
220
|
+
e(n, "all", "initial"), e(n, "position", "fixed"), e(n, "display", "block"), e(n, "visibility", "hidden"), e(n, "pointer-events", "none"), e(n, "overflow", "hidden"), e(n, "box-sizing", "border-box"), e(n, "width", "0"), e(n, "height", `${s}px`), e(n, "margin", "0"), e(n, "padding", "0"), e(n, "border", "0"), e(n, "font", "inherit"), e(n, "writing-mode", "horizontal-tb"), e(n, "inset", "0 auto auto 0");
|
|
221
|
+
for (const { point: o, probe: a } of t)
|
|
222
|
+
e(a, "all", "initial"), e(a, "position", "absolute"), e(a, "display", "block"), e(a, "box-sizing", "border-box"), e(a, "width", "0"), e(a, "height", o), e(a, "min-height", "0"), e(a, "max-height", "none"), e(a, "margin", "0"), e(a, "padding", "0"), e(a, "border", "0"), e(a, "font", "inherit"), e(a, "writing-mode", "horizontal-tb"), e(a, "inset", "0 auto auto 0"), n.append(a);
|
|
223
|
+
this.append(n);
|
|
224
|
+
try {
|
|
225
|
+
for (const { index: o, probe: a } of t)
|
|
226
|
+
i[o] = Number.parseFloat(getComputedStyle(a).height);
|
|
227
|
+
} finally {
|
|
228
|
+
n.remove();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}, c.swipeDismissThreshold = 96, c._styledRoots = /* @__PURE__ */ new Set(), c._geometryClass = _, c);
|
|
232
|
+
h([
|
|
233
|
+
p({ type: String, attribute: "closed-by", reflect: !0 })
|
|
234
|
+
], d.prototype, "closedBy");
|
|
235
|
+
h([
|
|
236
|
+
p({ type: String, reflect: !0 })
|
|
237
|
+
], d.prototype, "resize");
|
|
238
|
+
h([
|
|
239
|
+
p({ type: String, attribute: "resize-origin" })
|
|
240
|
+
], d.prototype, "resizeOrigin");
|
|
241
|
+
h([
|
|
242
|
+
p({ type: String, attribute: "resize-handle" })
|
|
243
|
+
], d.prototype, "resizeHandle");
|
|
244
|
+
h([
|
|
245
|
+
p({ type: String, attribute: "snap-points" })
|
|
246
|
+
], d.prototype, "snapPoints");
|
|
247
|
+
h([
|
|
248
|
+
p({ type: Boolean, attribute: "swipe-dismiss" })
|
|
249
|
+
], d.prototype, "swipeDismiss");
|
|
250
|
+
h([
|
|
251
|
+
p({ type: Number, attribute: "swipe-velocity" })
|
|
252
|
+
], d.prototype, "swipeVelocity");
|
|
253
|
+
let $ = d;
|
|
254
|
+
export {
|
|
255
|
+
$ as R
|
|
256
|
+
};
|
|
257
|
+
//# sourceMappingURL=rc-bottom-sheet-5mYgsRK-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-bottom-sheet-5mYgsRK-.js","sources":["../src/rc-bottom-sheet.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { RCDialog } from '@rcarls/rc-dialog';\nimport { DialogGeometry } from '@rcarls/rc-dialog/geometry';\nimport {\n cssText,\n cssZoom,\n findExtremeSnapIndex,\n findNearestSnapIndex,\n parseCssTime,\n type ResizeDirection,\n type ResizeLifecycleDetail,\n type ResizeOrigin,\n} from '@rcarls/rc-common';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-bottom-sheet': RCBottomSheet;\n }\n\n interface HTMLElementEventMap {\n 'rc-bottom-sheet-resize-start': CustomEvent<RCBottomSheetResizeStartDetail>;\n 'rc-bottom-sheet-snap': CustomEvent<RCBottomSheetSnapDetail>;\n }\n}\n\n/** Detail shape for `rc-bottom-sheet-resize-start`. */\nexport interface RCBottomSheetResizeStartDetail {\n /** Rendered sheet height when the resize gesture begins. */\n height: number;\n\n /** Input mechanism initiating the resize. */\n inputType: 'pointer' | 'keyboard';\n}\n\n/** Detail shape for `rc-bottom-sheet-snap`. */\nexport interface RCBottomSheetSnapDetail {\n /** Index into the resolved `snap-points` list selected as the target. */\n index: number;\n\n /** Effective target height in pixels, after CSS size constraints. */\n height: number;\n\n /** Whether the snap came from a drag release or a `snapTo()` call. */\n trigger: 'drag' | 'api';\n}\n\nconst DEFAULT_SNAP_DURATION_MS = 300;\nconst DEFAULT_SNAP_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';\n\n/**\n * Splits a `snap-points` value on whitespace outside parentheses, so a\n * `calc()` or `var()` entry with inner spaces stays one snap point.\n */\nfunction splitSnapPoints(value: string): string[] {\n const points: string[] = [];\n let depth = 0;\n let current = '';\n\n for (const character of value.trim()) {\n if (character === '(') {\n depth += 1;\n } else if (character === ')') {\n depth = Math.max(0, depth - 1);\n }\n\n if (depth === 0 && /\\s/.test(character)) {\n if (current) {\n points.push(current);\n current = '';\n }\n } else {\n current += character;\n }\n }\n\n if (current) {\n points.push(current);\n }\n\n return points;\n}\n\nconst LIGHT_DOM_CSS = cssText`\n@layer rc-base {\n rc-bottom-sheet > dialog {\n position: var(--rc-bottom-sheet-position, fixed);\n inset-block-start: auto;\n inset-block-end: var(--rc-bottom-sheet-inset-block-end, 0px);\n inset-inline: var(--rc-bottom-sheet-inset-inline, 0px);\n box-sizing: border-box;\n inline-size: 100%;\n max-inline-size: var(--rc-bottom-sheet-max-inline-size, 40rem);\n max-block-size: var(--rc-bottom-sheet-max-block-size, 70dvh);\n margin-block: 0;\n margin-inline: auto;\n overflow: auto;\n }\n\n rc-bottom-sheet > dialog [data-rc-bottom-sheet-handle] {\n box-sizing: border-box;\n cursor: ns-resize;\n touch-action: none;\n -webkit-tap-highlight-color: transparent;\n }\n\n rc-bottom-sheet > dialog > [data-rc-bottom-sheet-handle] {\n position: relative;\n display: block;\n inline-size: var(--rc-bottom-sheet-handle-target-inline-size, 100%);\n min-block-size: var(--rc-bottom-sheet-handle-target-block-size, 3rem);\n margin: var(--rc-bottom-sheet-handle-target-margin, 0);\n padding: 0;\n border: 0;\n border-radius: inherit;\n background: transparent;\n appearance: none;\n }\n\n rc-bottom-sheet > dialog > [data-rc-bottom-sheet-handle]::before {\n content: '';\n position: absolute;\n inset-block-start: var(--rc-bottom-sheet-handle-offset, 50%);\n inset-inline-start: 50%;\n inline-size: var(--rc-bottom-sheet-handle-inline-size, 2rem);\n block-size: var(--rc-bottom-sheet-handle-block-size, 0.25rem);\n border-radius: var(--rc-bottom-sheet-handle-radius, 999px);\n background: var(--rc-bottom-sheet-handle-color, GrayText);\n translate: -50% -50%;\n }\n\n rc-bottom-sheet > dialog [data-rc-bottom-sheet-handle]:focus-visible {\n outline: auto;\n outline-offset: -0.25rem;\n }\n\n /*\n * Opacity only, deliberately not a slide transform: several tests (and a\n * consumer's own layout code) read getBoundingClientRect() immediately\n * after show()/showModal(), before any entrance transition would have\n * settled. A translate-based slide shifts that measured box for as long\n * as it's animating, the same conflict rc-dialog's scale had with its own\n * resize feature. showModal()/show()/close() stay synchronous either way;\n * nothing here is gated on the transition finishing.\n */\n @media (prefers-reduced-motion: no-preference) {\n rc-bottom-sheet > dialog[open] {\n opacity: 1;\n transition:\n opacity var(--rc-motion-effects-duration-default, 200ms)\n var(--rc-motion-effects-easing-enter, ease-out),\n overlay var(--rc-motion-effects-duration-default, 200ms) allow-discrete,\n display var(--rc-motion-effects-duration-default, 200ms) allow-discrete;\n }\n\n rc-bottom-sheet > dialog:not([open]) {\n opacity: 0;\n transition:\n opacity var(--rc-motion-effects-duration-default, 200ms)\n var(--rc-motion-effects-easing-exit, ease-in),\n overlay var(--rc-motion-effects-duration-default, 200ms) allow-discrete,\n display var(--rc-motion-effects-duration-default, 200ms) allow-discrete;\n }\n\n @starting-style {\n rc-bottom-sheet > dialog[open] {\n opacity: 0;\n }\n }\n\n rc-bottom-sheet > dialog[open]::backdrop {\n opacity: 1;\n transition: opacity var(--rc-motion-effects-duration-default, 200ms)\n var(--rc-motion-effects-easing-enter, ease-out);\n }\n\n rc-bottom-sheet > dialog:not([open])::backdrop {\n opacity: 0;\n transition: opacity var(--rc-motion-effects-duration-default, 200ms)\n var(--rc-motion-effects-easing-exit, ease-in);\n }\n\n @starting-style {\n rc-bottom-sheet > dialog[open]::backdrop {\n opacity: 0;\n }\n }\n }\n}\n`;\n\n// Floor so a noisy near-zero-distance release can't misread as a decisive\n// fling; a real swipe travels at least this far even if it's very quick.\nconst MIN_SWIPE_DISTANCE = 24;\n\n// Space kept below a field the sheet grows to reveal above a keyboard.\nconst FOCUS_REVEAL_MARGIN = 16;\n\n// Inline geometry a drag gesture (`pinElementBox`) or an earlier pin may\n// leave behind. A docked sheet hands all of it back to its stylesheet.\nconst DOCK_RELEASED_PROPERTIES = [\n 'inset',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'inset-block-start',\n 'inset-block-end',\n 'inset-inline-start',\n 'inset-inline-end',\n 'width',\n 'margin',\n 'translate',\n] as const;\n\n/**\n * Docks the sheet to its block-end edge and pins only its rendered height.\n *\n * A bottom sheet's position belongs to CSS (`inset-block-end` and the\n * inline insets), so it stays docked when the viewport, a software keyboard,\n * or a moving containing block changes the space around it. Only the height\n * is frozen inline, as the one value snapping controls. Inline edges that a\n * drag gesture pinned are released here: `top` measured against a containing\n * block that later moves, or a leftover `bottom: auto`, would otherwise strand\n * the sheet away from its edge, and a pinned `left`/`width` would override\n * the inline-axis confinement a host page sets through custom properties.\n */\nfunction dockBlockBox(target: HTMLElement): number {\n const height = localHeight(target);\n\n for (const property of DOCK_RELEASED_PROPERTIES) {\n target.style.removeProperty(property);\n }\n\n target.style.marginBlock = '0';\n target.style.boxSizing = 'border-box';\n target.style.height = `${height}px`;\n\n return height;\n}\n\n/**\n * The element's rendered height in its own CSS pixels, the unit its `height`\n * takes. A measured box is in viewport pixels, which differ from those when an\n * ancestor has a CSS zoom, such as an app-wide interface size.\n */\nfunction localHeight(target: HTMLElement): number {\n return target.getBoundingClientRect().height / cssZoom(target);\n}\n\n/**\n * Modal bottom-sheet wrapper for a native `<dialog>`, built on `rc-dialog`.\n *\n * The direct child `<dialog>` remains the semantic dialog surface and owns\n * content, labels, and form behavior. `rc-bottom-sheet` supplies the same\n * open/close lifecycle, focus restoration, cancel/request-close events, and\n * `closed-by` dismissal as `rc-dialog`. The component docks the sheet to\n * the viewport block-end by default, while themes provide its visual surface.\n *\n * Dragging the resize handle past a decisive velocity in either direction\n * (a \"swipe\" or \"fling,\" distinct from a slow deliberate drag) snaps\n * straight to the extreme `snap-points` entry in that direction — swipe up\n * to fully expand, swipe down to fully collapse — regardless of how close\n * the release position was to some other point. Below that velocity, the\n * sheet settles to whichever declared snap point is nearest the release\n * height. All settling, whether from a drag or from `snapTo()`, animates\n * over `--rc-bottom-sheet-snap-duration`, follows\n * `--rc-bottom-sheet-snap-easing`, and is skipped for\n * `prefers-reduced-motion: reduce`.\n *\n * Opening and closing themselves fade the surface and its backdrop through\n * CSS alone (`@starting-style` plus a discrete `display`/`overlay`\n * transition), using the shared `--rc-motion-effects-*` tokens.\n * `showModal()`/`show()`/`close()` stay synchronous; nothing waits on the\n * transition finishing. The entrance animates opacity only, not a slide\n * transform, because several call sites (and this component's own drag/snap\n * math) read `getBoundingClientRect()` right after opening, which reports\n * the transformed box for as long as a transform-based entrance is still\n * running.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-bottom-sheet rc-bottom-sheet docs}\n * @see {@link https://m3.material.io/components/bottom-sheets/overview Material Design bottom sheets}\n *\n * @slot - Place a `<dialog>` element with the sheet content and optional\n * `[data-rc-bottom-sheet-handle]` resize handle here.\n *\n * @fires rc-dialog-open - Inherited from `rc-dialog`; fired when the sheet opens.\n * @fires rc-dialog-toggle - Inherited from `rc-dialog`; fired when user/native interaction changes open state.\n * @fires rc-dialog-request-close - Inherited from `rc-dialog`; cancelable close request.\n * @fires rc-dialog-cancel - Inherited from `rc-dialog`; backward-compatible cancel alias.\n * @fires rc-dialog-close - Inherited from `rc-dialog`; fired after the sheet closes.\n * @fires rc-bottom-sheet-resize-start - Fires when an interactive resize begins.\n * `detail: { height, inputType }`\n * @fires rc-bottom-sheet-snap - Fires after a drag release or `snapTo()` call\n * settles at its snap target. `detail: { index, height, trigger }`, with `height` in the\n * sheet's own CSS pixels, the unit of its snap points.\n *\n * @attr closed-by - Proxied to the inner `<dialog closedby=\"...\">` attribute. Defaults to `any`, so\n * Escape or a backdrop click closes the sheet; inherited from `rc-dialog`.\n * @attr resize - Enables edge resizing, mirroring the CSS `resize` property values.\n * Defaults to `vertical`; inherited from `rc-dialog`.\n * @attr resize-origin - Origin for resize edge hit-testing or explicit handles. Defaults to\n * `top`; inherited from `rc-dialog`.\n * @attr resize-handle - CSS selector, scoped to the inner `<dialog>`, for explicit resize\n * handles. Defaults to `[data-rc-bottom-sheet-handle]`; inherited from `rc-dialog`.\n * @attr snap-points - Whitespace-separated CSS heights, in ascending order, addressable by\n * `snapTo()` and settled to on drag release. Accepts `calc()` and `var()`, resolved in the\n * sheet's context when each snap applies; percentages are of the viewport height.\n * @attr swipe-dismiss - Whether a decisive downward swipe of at least 96 pixels requests close.\n * @attr swipe-velocity - Minimum release velocity, in pixels per second, that counts as a\n * decisive swipe rather than a slow deliberate drag.\n *\n * @cssprop [--rc-bottom-sheet-bg=Canvas] - Sheet surface background.\n * @cssprop [--rc-bottom-sheet-color=CanvasText] - Sheet text color.\n * @cssprop [--rc-bottom-sheet-radius=1rem 1rem 0 0] - Sheet corner radius.\n * @cssprop [--rc-bottom-sheet-shadow=none] - Sheet elevation shadow.\n * @cssprop [--rc-bottom-sheet-position=fixed] - Positioning scheme. Use `absolute` with `show()` to dock a non-modal sheet to a positioned ancestor.\n * @cssprop [--rc-bottom-sheet-inset-block-end=0px] - Distance from the positioning container's block-end edge.\n * @cssprop [--rc-bottom-sheet-inset-inline=0px] - Inline-axis inset used with automatic margins.\n * @cssprop [--rc-bottom-sheet-max-inline-size=40rem] - Maximum sheet width.\n * @cssprop [--rc-bottom-sheet-max-block-size=70dvh] - Maximum sheet height.\n * @cssprop [--rc-bottom-sheet-padding=0 1rem 1rem] - Sheet padding.\n * @cssprop [--rc-bottom-sheet-scrim=var(--rc-dialog-scrim)] - Modal backdrop color.\n * @cssprop [--rc-bottom-sheet-handle-color=GrayText] - Resize handle color.\n * @cssprop [--rc-bottom-sheet-handle-inline-size=2rem] - Resize handle width.\n * @cssprop [--rc-bottom-sheet-handle-block-size=0.25rem] - Resize handle height.\n * @cssprop [--rc-bottom-sheet-handle-radius=999px] - Resize handle corner radius.\n * @cssprop [--rc-bottom-sheet-handle-offset=50%] - Block-start position of the handle indicator's\n * center within its target. Set a length to pin it above a title inside the handle.\n * @cssprop [--rc-bottom-sheet-handle-target-inline-size=100%] - Pointer target width for a direct-child handle.\n * @cssprop [--rc-bottom-sheet-handle-target-block-size=3rem] - Pointer target height for a direct-child handle.\n * @cssprop [--rc-bottom-sheet-handle-target-margin=0] - Margin around a direct-child handle target.\n * @cssprop [--rc-bottom-sheet-snap-duration=300ms] - Duration of the settle animation\n * after a drag release or `snapTo()` call.\n * @cssprop [--rc-bottom-sheet-snap-easing=cubic-bezier(0.4, 0, 0.2, 1)] - Easing\n * for the settle animation after a drag release or `snapTo()` call.\n */\nexport class RCBottomSheet extends RCDialog {\n private static readonly swipeDismissThreshold = 96;\n private static readonly _styledRoots = new Set<Document | ShadowRoot>();\n\n private static _ensureBaseStyles(root: Document | ShadowRoot): void {\n if (RCBottomSheet._styledRoots.has(root)) {\n return;\n }\n\n RCBottomSheet._styledRoots.add(root);\n\n const $style = document.createElement('style');\n\n $style.setAttribute('data-rc-light-dom-base', 'rc-bottom-sheet');\n $style.textContent = LIGHT_DOM_CSS;\n\n if (root instanceof Document) {\n root.head.append($style);\n } else {\n root.append($style);\n }\n }\n\n // Sheets always resize, so their move and resize behavior loads with them.\n protected static override _geometryClass = DialogGeometry;\n\n /**\n * Bottom sheets close on Escape or a backdrop click by default.\n *\n * Set `closed-by=\"closerequest\"` when a backdrop click must not close the\n * sheet, or `closed-by=\"none\"` when only explicit actions may close it.\n */\n @property({ type: String, attribute: 'closed-by', reflect: true })\n override closedBy: 'any' | 'closerequest' | 'none' | '' = 'any';\n\n /** Bottom sheets resize vertically from the top edge by default. */\n @property({ type: String, reflect: true })\n override resize: ResizeDirection = 'vertical';\n\n /** Top-origin resizing keeps the sheet docked to the viewport bottom. */\n @property({ type: String, attribute: 'resize-origin' })\n override resizeOrigin: ResizeOrigin = 'top';\n\n /** Default optional handle selector. Falls back to top-edge resize when absent. */\n @property({ type: String, attribute: 'resize-handle' })\n override resizeHandle = '[data-rc-bottom-sheet-handle]';\n\n /**\n * Whitespace-separated CSS heights in ascending order.\n *\n * Each height becomes an addressable snap target. Slow releases choose the\n * nearest target, while decisive swipes choose the first or last target.\n * Any CSS length works, including `calc()` and `var()`, and is resolved in\n * the sheet's own context when a snap is applied, so a custom property the\n * page animates (for example from a scroll timeline) is read at its current\n * value. Percentages are of the viewport height.\n */\n @property({ type: String, attribute: 'snap-points' })\n snapPoints = '';\n\n /**\n * Whether a downward pointer resize of at least 96 pixels requests close.\n *\n * When false, a decisive downward swipe settles at the first snap point.\n */\n @property({ type: Boolean, attribute: 'swipe-dismiss' })\n swipeDismiss = true;\n\n /**\n * Minimum release velocity, in pixels per second, that counts as a decisive\n * swipe rather than a slow deliberate drag. A swipe jumps straight to the\n * extreme `snap-points` entry in its direction; below this, the release\n * settles to the nearest point instead.\n */\n @property({ type: Number, attribute: 'swipe-velocity' })\n swipeVelocity = 500;\n\n private readonly _reducedMotion =\n typeof window !== 'undefined' ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;\n\n private _activeSnapAnimation: Animation | null = null;\n\n // Snap-point heights are resolved from the *current* viewport at the\n // moment a snap is applied (dvh units, or a probe-element measurement for\n // other CSS units) and then frozen as an inline height — nothing re-derives\n // them afterward. A window resize (maximize/restore, or a software keyboard\n // under `interactive-widget=resizes-content`) otherwise leaves the sheet at\n // a stale height. Track the last-applied index so a resize can re-run the\n // same snap against fresh geometry; the sheet's docked position needs no\n // re-derivation.\n private _lastSnapIndex: number | null = null;\n // A snapTo() that arrived before the inner <dialog> was attached, as from a\n // framework ref callback; applied when the sheet first opens.\n private _pendingSnap: { index: number; behavior: 'animated' | 'instant' } | null = null;\n // True while the sheet is grown past its snap height (and CSS maximum) to\n // keep a focused field visible above a software keyboard.\n private _extendedForFocus = false;\n private readonly _onWindowResize = (): void => {\n if (!this.open || this._lastSnapIndex === null) {\n return;\n }\n\n this._extendedForFocus = false;\n this._$dialog?.style.removeProperty('max-block-size');\n this._snapToIndex(this._lastSnapIndex, 'instant', 'api');\n this._revealFocusedField();\n };\n\n private readonly _onFocusIn = (): void => {\n this._revealFocusedField();\n };\n\n private readonly _onFocusOut = (event: FocusEvent): void => {\n const $dialog = this._$dialog;\n\n if (\n !this._extendedForFocus ||\n !$dialog ||\n (event.relatedTarget instanceof Node && $dialog.contains(event.relatedTarget))\n ) {\n return;\n }\n\n this._extendedForFocus = false;\n $dialog.style.removeProperty('max-block-size');\n\n if (this._lastSnapIndex !== null) {\n this._snapToIndex(this._lastSnapIndex, 'animated', 'api');\n }\n };\n\n override connectedCallback(): void {\n super.connectedCallback();\n RCBottomSheet._ensureBaseStyles(this.getRootNode() as Document | ShadowRoot);\n window.addEventListener('resize', this._onWindowResize);\n this.addEventListener('focusin', this._onFocusIn);\n this.addEventListener('focusout', this._onFocusOut);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener('resize', this._onWindowResize);\n this.removeEventListener('focusin', this._onFocusIn);\n this.removeEventListener('focusout', this._onFocusOut);\n }\n\n /**\n * Snaps to a declared target by zero-based index.\n *\n * Finite fractional indices are truncated and out-of-range indices clamp to\n * the nearest endpoint. Non-finite indices and calls without valid snap\n * points have no effect.\n *\n * @param index - zero-based index into `snapPoints`\n * @param behavior - whether to animate the movement\n *\n * @example\n * const sheet = document.querySelector('rc-bottom-sheet');\n * sheet.snapTo(1);\n */\n snapTo(index: number, behavior: 'animated' | 'instant' = 'animated'): void {\n if (!Number.isFinite(index)) {\n return;\n }\n\n if (!this._$dialog) {\n this._pendingSnap = { index: Math.trunc(index), behavior };\n\n return;\n }\n\n this._snapToIndex(Math.trunc(index), behavior, 'api');\n }\n\n protected override _applyOpen(\n open: boolean,\n silent: boolean,\n modal = true,\n moveFocus = true,\n ): boolean {\n const applied = super._applyOpen(open, silent, modal, moveFocus);\n const pending = this._pendingSnap;\n\n if (applied && open && pending) {\n this._pendingSnap = null;\n this._snapToIndex(pending.index, pending.behavior, 'api');\n }\n\n return applied;\n }\n\n protected override _onResizeStart(detail: ResizeLifecycleDetail): void {\n // A gesture interrupts a running snap. The resize controller has already\n // pinned the animated box inline, so dropping the animation's forwards\n // fill keeps the sheet where it is instead of letting the snap settle\n // over the gesture.\n this._activeSnapAnimation?.cancel();\n this._activeSnapAnimation = null;\n\n this.dispatchEvent(\n new CustomEvent<RCBottomSheetResizeStartDetail>('rc-bottom-sheet-resize-start', {\n bubbles: true,\n composed: true,\n detail: { height: detail.height, inputType: detail.inputType },\n }),\n );\n }\n\n protected override _onResizeEnd(detail: ResizeLifecycleDetail): void {\n if (detail.inputType === 'pointer' && this.swipeDismiss && this._shouldSwipeDismiss(detail)) {\n this.requestClose();\n\n return;\n }\n\n const points = this._resolvedSnapPoints();\n const velocity = detail.edge.includes('n') ? -detail.velocityY : detail.velocityY;\n const distance = Math.abs(detail.height - detail.startHeight);\n\n if (\n detail.inputType === 'pointer' &&\n points.length > 0 &&\n distance >= MIN_SWIPE_DISTANCE &&\n Math.abs(velocity) >= this.swipeVelocity\n ) {\n // Decisive fling: jump to the extreme point in the swipe direction,\n // regardless of proximity to some other point. Height grows as the\n // sheet is dragged upward, so a positive velocity is a swipe up.\n const targetIndex = findExtremeSnapIndex(points, velocity > 0 ? 1 : -1);\n\n this._snapToIndex(targetIndex, 'animated', 'drag', points);\n\n return;\n }\n\n this._snapToNearestPoint('drag', points);\n }\n\n private _shouldSwipeDismiss(detail: ResizeLifecycleDetail): boolean {\n return (\n detail.edge.includes('n') &&\n detail.startHeight - detail.height >= RCBottomSheet.swipeDismissThreshold\n );\n }\n\n private _snapToNearestPoint(\n trigger: 'drag' | 'api' = 'api',\n points = this._resolvedSnapPoints(),\n ): void {\n const $dialog = this._$dialog;\n\n if (!$dialog || points.length === 0) {\n return;\n }\n\n const height = localHeight($dialog);\n const nearestIndex = findNearestSnapIndex(points, height);\n\n this._snapToIndex(nearestIndex, 'animated', trigger, points);\n }\n\n private _snapToIndex(\n index: number,\n behavior: 'animated' | 'instant',\n trigger: 'drag' | 'api',\n points = this._resolvedSnapPoints(),\n ): void {\n const $dialog = this._$dialog;\n\n if (!$dialog || points.length === 0) {\n return;\n }\n\n // Dock before measuring: a drag gesture leaves its own edge pins inline,\n // and an earlier snap leaves an explicit height to animate from.\n const dockedHeight = dockBlockBox($dialog);\n const clampedIndex = Math.max(0, Math.min(index, points.length - 1));\n const requestedHeight = points[clampedIndex];\n\n this._lastSnapIndex = clampedIndex;\n\n // An in-flight animation wins over inline geometry in the cascade. Pin\n // its current visual box before cancelling so the constrained target can\n // be measured without jumping back to the prior snap point.\n this._activeSnapAnimation?.cancel();\n this._activeSnapAnimation = null;\n\n $dialog.style.height = `${requestedHeight}px`;\n\n // CSS min/max block-size can make the effective height differ from the\n // authored snap point; snap events report what actually renders.\n const targetHeight = localHeight($dialog);\n\n $dialog.style.height = `${dockedHeight}px`;\n\n this._applySnap($dialog, targetHeight, behavior, () => {\n this.dispatchEvent(\n new CustomEvent<RCBottomSheetSnapDetail>('rc-bottom-sheet-snap', {\n bubbles: true,\n composed: true,\n detail: { index: clampedIndex, height: targetHeight, trigger },\n }),\n );\n });\n }\n\n private _applySnap(\n $dialog: HTMLDialogElement,\n height: number,\n behavior: 'animated' | 'instant',\n onSettled: () => void,\n ): void {\n // Measure before cancelling any in-flight animation. `_snapToIndex()`\n // normally docks and cancels first, while this ordering also keeps direct\n // calls robust if applying a snap is refactored later.\n const fromHeight = localHeight($dialog);\n\n this._activeSnapAnimation?.cancel();\n this._activeSnapAnimation = null;\n\n if (behavior === 'instant' || this._reducedMotion?.matches || fromHeight === height) {\n $dialog.style.height = `${height}px`;\n\n onSettled();\n\n return;\n }\n\n // The sheet is docked at its block-end edge, so animating its height\n // alone moves the visible top edge.\n const animation = $dialog.animate([{ height: `${fromHeight}px` }, { height: `${height}px` }], {\n duration: this._snapDuration($dialog),\n easing: this._snapEasing($dialog),\n fill: 'forwards',\n });\n\n this._activeSnapAnimation = animation;\n\n animation.finished.then(\n () => {\n // A newer gesture or snapTo() call owns the final geometry and event.\n if (this._activeSnapAnimation !== animation) {\n animation.cancel();\n\n return;\n }\n\n // Persist the known target explicitly before removing the animation's\n // forwards fill. Unlike commitStyles(), this remains safe if a test or\n // consumer detaches the dialog as the finished promise resolves.\n $dialog.style.height = `${height}px`;\n animation.cancel();\n this._activeSnapAnimation = null;\n\n onSettled();\n },\n () => {\n // Interrupted by a newer gesture or snapTo() call, which owns applying\n // its own final styles and reporting its settled snap point.\n animation.cancel();\n\n if (this._activeSnapAnimation === animation) {\n this._activeSnapAnimation = null;\n }\n },\n );\n }\n\n /**\n * Keeps a focused field inside the sheet visible, as Android and Material\n * sheets do above a software keyboard: scroll it into view within the\n * sheet's own scroll containers, then grow the sheet past its snap height\n * and CSS maximum (up to the space above its docked edge) only when\n * scrolling cannot reveal it.\n */\n private _revealFocusedField(): void {\n const $dialog = this._$dialog;\n const $focused = document.activeElement;\n\n if (\n !this.open ||\n !$dialog ||\n !($focused instanceof HTMLElement) ||\n !$dialog.contains($focused)\n ) {\n return;\n }\n\n for (\n let $scroller = $focused.parentElement;\n $scroller && $dialog.contains($scroller);\n $scroller = $scroller.parentElement\n ) {\n if (\n $scroller.scrollHeight <= $scroller.clientHeight ||\n !/auto|scroll/.test(getComputedStyle($scroller).overflowY)\n ) {\n continue;\n }\n\n const box = $scroller.getBoundingClientRect();\n const field = $focused.getBoundingClientRect();\n const scrollerZoom = cssZoom($scroller);\n\n if (field.bottom > box.bottom) {\n const deltaViewportPx = Math.min(field.bottom - box.bottom, field.top - box.top);\n\n $scroller.scrollTop += deltaViewportPx / scrollerZoom;\n } else if (field.top < box.top) {\n $scroller.scrollTop -= (box.top - field.top) / scrollerZoom;\n }\n }\n\n const sheet = $dialog.getBoundingClientRect();\n const field = $focused.getBoundingClientRect();\n\n if (field.bottom <= sheet.bottom) {\n return;\n }\n\n // Growing the docked sheet lifts its content with its top edge, so the\n // field needs the sheet to be as tall as the field's offset from the\n // sheet's top, plus breathing room, bounded by the space above the\n // docked edge.\n const height =\n Math.min(field.bottom - sheet.top + FOCUS_REVEAL_MARGIN, sheet.bottom) / cssZoom($dialog);\n\n this._activeSnapAnimation?.cancel();\n this._activeSnapAnimation = null;\n this._extendedForFocus = true;\n dockBlockBox($dialog);\n $dialog.style.maxBlockSize = 'none';\n $dialog.style.height = `${height}px`;\n }\n\n private _snapDuration($dialog: HTMLDialogElement): number {\n const raw = getComputedStyle($dialog).getPropertyValue('--rc-bottom-sheet-snap-duration');\n\n // Themes may resolve motion tokens in seconds (`.3s`); Web Animations\n // durations are milliseconds.\n return parseCssTime(raw) ?? DEFAULT_SNAP_DURATION_MS;\n }\n\n private _snapEasing($dialog: HTMLDialogElement): string {\n return (\n getComputedStyle($dialog).getPropertyValue('--rc-bottom-sheet-snap-easing').trim() ||\n DEFAULT_SNAP_EASING\n );\n }\n\n private _resolvedSnapPoints(): number[] {\n if (!this.snapPoints.trim()) {\n return [];\n }\n\n // Snap heights are in the sheet's own CSS pixels, like its `height`; the\n // viewport height is not when an ancestor has a CSS zoom. The inner\n // <dialog> carries the zoom even when the host is `display: contents`.\n const viewportHeight = window.innerHeight / cssZoom(this._$dialog ?? this);\n const resolved: number[] = [];\n const measured: Array<{ index: number; point: string; probe: HTMLElement }> = [];\n\n for (const point of splitSnapPoints(this.snapPoints)) {\n const numeric = Number.parseFloat(point);\n\n if (Number.isFinite(numeric) && point.endsWith('%')) {\n resolved.push(viewportHeight * (numeric / 100));\n } else if (Number.isFinite(numeric) && (point.endsWith('px') || String(numeric) === point)) {\n resolved.push(numeric);\n } else {\n const probe = document.createElement('div');\n\n resolved.push(Number.NaN);\n measured.push({ index: resolved.length - 1, point, probe });\n }\n }\n\n if (measured.length > 0) {\n this._measureSnapPoints(measured, resolved, viewportHeight);\n }\n\n return resolved.filter((point) => Number.isFinite(point) && point > 0);\n }\n\n private _measureSnapPoints(\n entries: Array<{ index: number; point: string; probe: HTMLElement }>,\n resolved: number[],\n viewportHeight: number,\n ): void {\n const context = document.createElement('div');\n\n const important = (element: HTMLElement, property: string, value: string): void => {\n element.style.setProperty(property, value, 'important');\n };\n\n // Give percentage expressions an explicit viewport-height containing\n // block. This keeps `calc(50% - ...)` equivalent to a plain `50%` even\n // when a transformed ancestor establishes the fixed containing block.\n important(context, 'all', 'initial');\n important(context, 'position', 'fixed');\n important(context, 'display', 'block');\n important(context, 'visibility', 'hidden');\n important(context, 'pointer-events', 'none');\n important(context, 'overflow', 'hidden');\n important(context, 'box-sizing', 'border-box');\n important(context, 'width', '0');\n important(context, 'height', `${viewportHeight}px`);\n important(context, 'margin', '0');\n important(context, 'padding', '0');\n important(context, 'border', '0');\n important(context, 'font', 'inherit');\n important(context, 'writing-mode', 'horizontal-tb');\n important(context, 'inset', '0 auto auto 0');\n\n for (const { point, probe } of entries) {\n important(probe, 'all', 'initial');\n important(probe, 'position', 'absolute');\n important(probe, 'display', 'block');\n important(probe, 'box-sizing', 'border-box');\n important(probe, 'width', '0');\n important(probe, 'height', point);\n important(probe, 'min-height', '0');\n important(probe, 'max-height', 'none');\n important(probe, 'margin', '0');\n important(probe, 'padding', '0');\n important(probe, 'border', '0');\n important(probe, 'font', 'inherit');\n important(probe, 'writing-mode', 'horizontal-tb');\n important(probe, 'inset', '0 auto auto 0');\n context.append(probe);\n }\n\n this.append(context);\n\n try {\n // All probes are attached before the first style/layout read. Reading\n // computed physical height avoids transforms and writing mode changing\n // the CSS-pixel value that will be assigned to dialog.style.height.\n for (const { index, probe } of entries) {\n resolved[index] = Number.parseFloat(getComputedStyle(probe).height);\n }\n } finally {\n context.remove();\n }\n }\n}\n\nexport default RCBottomSheet;\n"],"names":["DEFAULT_SNAP_DURATION_MS","DEFAULT_SNAP_EASING","splitSnapPoints","value","points","depth","current","character","LIGHT_DOM_CSS","cssText","MIN_SWIPE_DISTANCE","FOCUS_REVEAL_MARGIN","DOCK_RELEASED_PROPERTIES","dockBlockBox","target","height","localHeight","property","cssZoom","_RCBottomSheet","_a","RCDialog","event","$dialog","root","$style","index","behavior","open","silent","modal","moveFocus","applied","pending","detail","velocity","distance","targetIndex","findExtremeSnapIndex","trigger","nearestIndex","findNearestSnapIndex","dockedHeight","clampedIndex","requestedHeight","targetHeight","onSettled","fromHeight","animation","$focused","$scroller","box","field","scrollerZoom","deltaViewportPx","sheet","raw","parseCssTime","viewportHeight","resolved","measured","point","numeric","probe","entries","context","important","element","DialogGeometry","__decorateClass","RCBottomSheet"],"mappings":";;;;;;;;;AA+CA,MAAMA,IAA2B,KAC3BC,IAAsB;AAM5B,SAASC,EAAgBC,GAAyB;AAChD,QAAMC,IAAmB,CAAA;AACzB,MAAIC,IAAQ,GACRC,IAAU;AAEd,aAAWC,KAAaJ,EAAM;AAC5B,IAAII,MAAc,MAChBF,KAAS,IACAE,MAAc,QACvBF,IAAQ,KAAK,IAAI,GAAGA,IAAQ,CAAC,IAG3BA,MAAU,KAAK,KAAK,KAAKE,CAAS,IAChCD,MACFF,EAAO,KAAKE,CAAO,GACnBA,IAAU,MAGZA,KAAWC;AAIf,SAAID,KACFF,EAAO,KAAKE,CAAO,GAGdF;AACT;AAEA,MAAMI,IAAgBC,q+EA8GhBC,IAAqB,IAGrBC,IAAsB,IAItBC,IAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAcA,SAASC,EAAaC,GAA6B;AACjD,QAAMC,IAASC,EAAYF,CAAM;AAEjC,aAAWG,KAAYL;AACrB,IAAAE,EAAO,MAAM,eAAeG,CAAQ;AAGtC,SAAAH,EAAO,MAAM,cAAc,KAC3BA,EAAO,MAAM,YAAY,cACzBA,EAAO,MAAM,SAAS,GAAGC,CAAM,MAExBA;AACT;AAOA,SAASC,EAAYF,GAA6B;AAChD,SAAOA,EAAO,sBAAA,EAAwB,SAASI,EAAQJ,CAAM;AAC/D;;AAyFO,MAAMK,KAANC,IAAA,cAA4BC,EAAS;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA,GAiCL,KAAS,WAAiD,OAI1D,KAAS,SAA0B,YAInC,KAAS,eAA6B,OAItC,KAAS,eAAe,iCAaxB,KAAA,aAAa,IAQb,KAAA,eAAe,IASf,KAAA,gBAAgB,KAEhB,KAAiB,iBACf,OAAO,SAAW,MAAc,OAAO,WAAW,kCAAkC,IAAI,MAE1F,KAAQ,uBAAyC,MAUjD,KAAQ,iBAAgC,MAGxC,KAAQ,eAA2E,MAGnF,KAAQ,oBAAoB,IAC5B,KAAiB,kBAAkB,MAAY;AAC7C,MAAI,CAAC,KAAK,QAAQ,KAAK,mBAAmB,SAI1C,KAAK,oBAAoB,IACzB,KAAK,UAAU,MAAM,eAAe,gBAAgB,GACpD,KAAK,aAAa,KAAK,gBAAgB,WAAW,KAAK,GACvD,KAAK,oBAAA;AAAA,IACP,GAEA,KAAiB,aAAa,MAAY;AACxC,WAAK,oBAAA;AAAA,IACP,GAEA,KAAiB,cAAc,CAACC,MAA4B;AAC1D,YAAMC,IAAU,KAAK;AAErB,MACE,CAAC,KAAK,qBACN,CAACA,KACAD,EAAM,yBAAyB,QAAQC,EAAQ,SAASD,EAAM,aAAa,MAK9E,KAAK,oBAAoB,IACzBC,EAAQ,MAAM,eAAe,gBAAgB,GAEzC,KAAK,mBAAmB,QAC1B,KAAK,aAAa,KAAK,gBAAgB,YAAY,KAAK;AAAA,IAE5D;AAAA,EAAA;AAAA,EA7HA,OAAe,kBAAkBC,GAAmC;AAClE,QAAIJ,EAAc,aAAa,IAAII,CAAI;AACrC;AAGF,IAAAJ,EAAc,aAAa,IAAII,CAAI;AAEnC,UAAMC,IAAS,SAAS,cAAc,OAAO;AAE7C,IAAAA,EAAO,aAAa,0BAA0B,iBAAiB,GAC/DA,EAAO,cAAcjB,GAEjBgB,aAAgB,WAClBA,EAAK,KAAK,OAAOC,CAAM,IAEvBD,EAAK,OAAOC,CAAM;AAAA,EAEtB;AAAA,EA8GS,oBAA0B;AACjC,UAAM,kBAAA,GACNL,EAAc,kBAAkB,KAAK,aAAsC,GAC3E,OAAO,iBAAiB,UAAU,KAAK,eAAe,GACtD,KAAK,iBAAiB,WAAW,KAAK,UAAU,GAChD,KAAK,iBAAiB,YAAY,KAAK,WAAW;AAAA,EACpD;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA,GACN,OAAO,oBAAoB,UAAU,KAAK,eAAe,GACzD,KAAK,oBAAoB,WAAW,KAAK,UAAU,GACnD,KAAK,oBAAoB,YAAY,KAAK,WAAW;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,OAAOM,GAAeC,IAAmC,YAAkB;AACzE,QAAK,OAAO,SAASD,CAAK,GAI1B;AAAA,UAAI,CAAC,KAAK,UAAU;AAClB,aAAK,eAAe,EAAE,OAAO,KAAK,MAAMA,CAAK,GAAG,UAAAC,EAAA;AAEhD;AAAA,MACF;AAEA,WAAK,aAAa,KAAK,MAAMD,CAAK,GAAGC,GAAU,KAAK;AAAA;AAAA,EACtD;AAAA,EAEmB,WACjBC,GACAC,GACAC,IAAQ,IACRC,IAAY,IACH;AACT,UAAMC,IAAU,MAAM,WAAWJ,GAAMC,GAAQC,GAAOC,CAAS,GACzDE,IAAU,KAAK;AAErB,WAAID,KAAWJ,KAAQK,MACrB,KAAK,eAAe,MACpB,KAAK,aAAaA,EAAQ,OAAOA,EAAQ,UAAU,KAAK,IAGnDD;AAAA,EACT;AAAA,EAEmB,eAAeE,GAAqC;AAKrE,SAAK,sBAAsB,OAAA,GAC3B,KAAK,uBAAuB,MAE5B,KAAK;AAAA,MACH,IAAI,YAA4C,gCAAgC;AAAA,QAC9E,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,QAAQA,EAAO,QAAQ,WAAWA,EAAO,UAAA;AAAA,MAAU,CAC9D;AAAA,IAAA;AAAA,EAEL;AAAA,EAEmB,aAAaA,GAAqC;AACnE,QAAIA,EAAO,cAAc,aAAa,KAAK,gBAAgB,KAAK,oBAAoBA,CAAM,GAAG;AAC3F,WAAK,aAAA;AAEL;AAAA,IACF;AAEA,UAAM9B,IAAS,KAAK,oBAAA,GACd+B,IAAWD,EAAO,KAAK,SAAS,GAAG,IAAI,CAACA,EAAO,YAAYA,EAAO,WAClEE,IAAW,KAAK,IAAIF,EAAO,SAASA,EAAO,WAAW;AAE5D,QACEA,EAAO,cAAc,aACrB9B,EAAO,SAAS,KAChBgC,KAAY1B,KACZ,KAAK,IAAIyB,CAAQ,KAAK,KAAK,eAC3B;AAIA,YAAME,IAAcC,EAAqBlC,GAAQ+B,IAAW,IAAI,IAAI,EAAE;AAEtE,WAAK,aAAaE,GAAa,YAAY,QAAQjC,CAAM;AAEzD;AAAA,IACF;AAEA,SAAK,oBAAoB,QAAQA,CAAM;AAAA,EACzC;AAAA,EAEQ,oBAAoB8B,GAAwC;AAClE,WACEA,EAAO,KAAK,SAAS,GAAG,KACxBA,EAAO,cAAcA,EAAO,UAAUd,EAAc;AAAA,EAExD;AAAA,EAEQ,oBACNmB,IAA0B,OAC1BnC,IAAS,KAAK,uBACR;AACN,UAAMmB,IAAU,KAAK;AAErB,QAAI,CAACA,KAAWnB,EAAO,WAAW;AAChC;AAGF,UAAMW,IAASC,EAAYO,CAAO,GAC5BiB,IAAeC,EAAqBrC,GAAQW,CAAM;AAExD,SAAK,aAAayB,GAAc,YAAYD,GAASnC,CAAM;AAAA,EAC7D;AAAA,EAEQ,aACNsB,GACAC,GACAY,GACAnC,IAAS,KAAK,uBACR;AACN,UAAMmB,IAAU,KAAK;AAErB,QAAI,CAACA,KAAWnB,EAAO,WAAW;AAChC;AAKF,UAAMsC,IAAe7B,EAAaU,CAAO,GACnCoB,IAAe,KAAK,IAAI,GAAG,KAAK,IAAIjB,GAAOtB,EAAO,SAAS,CAAC,CAAC,GAC7DwC,IAAkBxC,EAAOuC,CAAY;AAE3C,SAAK,iBAAiBA,GAKtB,KAAK,sBAAsB,OAAA,GAC3B,KAAK,uBAAuB,MAE5BpB,EAAQ,MAAM,SAAS,GAAGqB,CAAe;AAIzC,UAAMC,IAAe7B,EAAYO,CAAO;AAExC,IAAAA,EAAQ,MAAM,SAAS,GAAGmB,CAAY,MAEtC,KAAK,WAAWnB,GAASsB,GAAclB,GAAU,MAAM;AACrD,WAAK;AAAA,QACH,IAAI,YAAqC,wBAAwB;AAAA,UAC/D,SAAS;AAAA,UACT,UAAU;AAAA,UACV,QAAQ,EAAE,OAAOgB,GAAc,QAAQE,GAAc,SAAAN,EAAA;AAAA,QAAQ,CAC9D;AAAA,MAAA;AAAA,IAEL,CAAC;AAAA,EACH;AAAA,EAEQ,WACNhB,GACAR,GACAY,GACAmB,GACM;AAIN,UAAMC,IAAa/B,EAAYO,CAAO;AAKtC,QAHA,KAAK,sBAAsB,OAAA,GAC3B,KAAK,uBAAuB,MAExBI,MAAa,aAAa,KAAK,gBAAgB,WAAWoB,MAAehC,GAAQ;AACnF,MAAAQ,EAAQ,MAAM,SAAS,GAAGR,CAAM,MAEhC+B,EAAA;AAEA;AAAA,IACF;AAIA,UAAME,IAAYzB,EAAQ,QAAQ,CAAC,EAAE,QAAQ,GAAGwB,CAAU,KAAA,GAAQ,EAAE,QAAQ,GAAGhC,CAAM,KAAA,CAAM,GAAG;AAAA,MAC5F,UAAU,KAAK,cAAcQ,CAAO;AAAA,MACpC,QAAQ,KAAK,YAAYA,CAAO;AAAA,MAChC,MAAM;AAAA,IAAA,CACP;AAED,SAAK,uBAAuByB,GAE5BA,EAAU,SAAS;AAAA,MACjB,MAAM;AAEJ,YAAI,KAAK,yBAAyBA,GAAW;AAC3C,UAAAA,EAAU,OAAA;AAEV;AAAA,QACF;AAKA,QAAAzB,EAAQ,MAAM,SAAS,GAAGR,CAAM,MAChCiC,EAAU,OAAA,GACV,KAAK,uBAAuB,MAE5BF,EAAA;AAAA,MACF;AAAA,MACA,MAAM;AAGJ,QAAAE,EAAU,OAAA,GAEN,KAAK,yBAAyBA,MAChC,KAAK,uBAAuB;AAAA,MAEhC;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,sBAA4B;AAClC,UAAMzB,IAAU,KAAK,UACf0B,IAAW,SAAS;AAE1B,QACE,CAAC,KAAK,QACN,CAAC1B,KACD,EAAE0B,aAAoB,gBACtB,CAAC1B,EAAQ,SAAS0B,CAAQ;AAE1B;AAGF,aACMC,IAAYD,EAAS,eACzBC,KAAa3B,EAAQ,SAAS2B,CAAS,GACvCA,IAAYA,EAAU,eACtB;AACA,UACEA,EAAU,gBAAgBA,EAAU,gBACpC,CAAC,cAAc,KAAK,iBAAiBA,CAAS,EAAE,SAAS;AAEzD;AAGF,YAAMC,IAAMD,EAAU,sBAAA,GAChBE,IAAQH,EAAS,sBAAA,GACjBI,IAAenC,EAAQgC,CAAS;AAEtC,UAAIE,EAAM,SAASD,EAAI,QAAQ;AAC7B,cAAMG,IAAkB,KAAK,IAAIF,EAAM,SAASD,EAAI,QAAQC,EAAM,MAAMD,EAAI,GAAG;AAE/E,QAAAD,EAAU,aAAaI,IAAkBD;AAAA,MAC3C,MAAA,CAAWD,EAAM,MAAMD,EAAI,QACzBD,EAAU,cAAcC,EAAI,MAAMC,EAAM,OAAOC;AAAA,IAEnD;AAEA,UAAME,IAAQhC,EAAQ,sBAAA,GAChB6B,IAAQH,EAAS,sBAAA;AAEvB,QAAIG,EAAM,UAAUG,EAAM;AACxB;AAOF,UAAMxC,IACJ,KAAK,IAAIqC,EAAM,SAASG,EAAM,MAAM5C,GAAqB4C,EAAM,MAAM,IAAIrC,EAAQK,CAAO;AAE1F,SAAK,sBAAsB,OAAA,GAC3B,KAAK,uBAAuB,MAC5B,KAAK,oBAAoB,IACzBV,EAAaU,CAAO,GACpBA,EAAQ,MAAM,eAAe,QAC7BA,EAAQ,MAAM,SAAS,GAAGR,CAAM;AAAA,EAClC;AAAA,EAEQ,cAAcQ,GAAoC;AACxD,UAAMiC,IAAM,iBAAiBjC,CAAO,EAAE,iBAAiB,iCAAiC;AAIxF,WAAOkC,EAAaD,CAAG,KAAKxD;AAAA,EAC9B;AAAA,EAEQ,YAAYuB,GAAoC;AACtD,WACE,iBAAiBA,CAAO,EAAE,iBAAiB,+BAA+B,EAAE,UAC5EtB;AAAA,EAEJ;AAAA,EAEQ,sBAAgC;AACtC,QAAI,CAAC,KAAK,WAAW;AACnB,aAAO,CAAA;AAMT,UAAMyD,IAAiB,OAAO,cAAcxC,EAAQ,KAAK,YAAY,IAAI,GACnEyC,IAAqB,CAAA,GACrBC,IAAwE,CAAA;AAE9E,eAAWC,KAAS3D,EAAgB,KAAK,UAAU,GAAG;AACpD,YAAM4D,IAAU,OAAO,WAAWD,CAAK;AAEvC,UAAI,OAAO,SAASC,CAAO,KAAKD,EAAM,SAAS,GAAG;AAChD,QAAAF,EAAS,KAAKD,KAAkBI,IAAU,IAAI;AAAA,eACrC,OAAO,SAASA,CAAO,MAAMD,EAAM,SAAS,IAAI,KAAK,OAAOC,CAAO,MAAMD;AAClF,QAAAF,EAAS,KAAKG,CAAO;AAAA,WAChB;AACL,cAAMC,IAAQ,SAAS,cAAc,KAAK;AAE1C,QAAAJ,EAAS,KAAK,OAAO,GAAG,GACxBC,EAAS,KAAK,EAAE,OAAOD,EAAS,SAAS,GAAG,OAAAE,GAAO,OAAAE,GAAO;AAAA,MAC5D;AAAA,IACF;AAEA,WAAIH,EAAS,SAAS,KACpB,KAAK,mBAAmBA,GAAUD,GAAUD,CAAc,GAGrDC,EAAS,OAAO,CAACE,MAAU,OAAO,SAASA,CAAK,KAAKA,IAAQ,CAAC;AAAA,EACvE;AAAA,EAEQ,mBACNG,GACAL,GACAD,GACM;AACN,UAAMO,IAAU,SAAS,cAAc,KAAK,GAEtCC,IAAY,CAACC,GAAsBlD,GAAkBd,MAAwB;AACjF,MAAAgE,EAAQ,MAAM,YAAYlD,GAAUd,GAAO,WAAW;AAAA,IACxD;AAKA,IAAA+D,EAAUD,GAAS,OAAO,SAAS,GACnCC,EAAUD,GAAS,YAAY,OAAO,GACtCC,EAAUD,GAAS,WAAW,OAAO,GACrCC,EAAUD,GAAS,cAAc,QAAQ,GACzCC,EAAUD,GAAS,kBAAkB,MAAM,GAC3CC,EAAUD,GAAS,YAAY,QAAQ,GACvCC,EAAUD,GAAS,cAAc,YAAY,GAC7CC,EAAUD,GAAS,SAAS,GAAG,GAC/BC,EAAUD,GAAS,UAAU,GAAGP,CAAc,IAAI,GAClDQ,EAAUD,GAAS,UAAU,GAAG,GAChCC,EAAUD,GAAS,WAAW,GAAG,GACjCC,EAAUD,GAAS,UAAU,GAAG,GAChCC,EAAUD,GAAS,QAAQ,SAAS,GACpCC,EAAUD,GAAS,gBAAgB,eAAe,GAClDC,EAAUD,GAAS,SAAS,eAAe;AAE3C,eAAW,EAAE,OAAAJ,GAAO,OAAAE,EAAA,KAAWC;AAC7B,MAAAE,EAAUH,GAAO,OAAO,SAAS,GACjCG,EAAUH,GAAO,YAAY,UAAU,GACvCG,EAAUH,GAAO,WAAW,OAAO,GACnCG,EAAUH,GAAO,cAAc,YAAY,GAC3CG,EAAUH,GAAO,SAAS,GAAG,GAC7BG,EAAUH,GAAO,UAAUF,CAAK,GAChCK,EAAUH,GAAO,cAAc,GAAG,GAClCG,EAAUH,GAAO,cAAc,MAAM,GACrCG,EAAUH,GAAO,UAAU,GAAG,GAC9BG,EAAUH,GAAO,WAAW,GAAG,GAC/BG,EAAUH,GAAO,UAAU,GAAG,GAC9BG,EAAUH,GAAO,QAAQ,SAAS,GAClCG,EAAUH,GAAO,gBAAgB,eAAe,GAChDG,EAAUH,GAAO,SAAS,eAAe,GACzCE,EAAQ,OAAOF,CAAK;AAGtB,SAAK,OAAOE,CAAO;AAEnB,QAAI;AAIF,iBAAW,EAAE,OAAAvC,GAAO,OAAAqC,EAAA,KAAWC;AAC7B,QAAAL,EAASjC,CAAK,IAAI,OAAO,WAAW,iBAAiBqC,CAAK,EAAE,MAAM;AAAA,IAEtE,UAAA;AACE,MAAAE,EAAQ,OAAA;AAAA,IACV;AAAA,EACF;AACF,GAhiBE7C,EAAwB,wBAAwB,IAChDA,EAAwB,mCAAmB,IAAA,GAsB3CA,EAA0B,iBAAiBgD,GAxBtChD;AAiCIiD,EAAA;AAAA,EADRpD,EAAS,EAAE,MAAM,QAAQ,WAAW,aAAa,SAAS,IAAM;AAAA,GAhCtDE,EAiCF,WAAA,UAAA;AAIAkD,EAAA;AAAA,EADRpD,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GApC9BE,EAqCF,WAAA,QAAA;AAIAkD,EAAA;AAAA,EADRpD,EAAS,EAAE,MAAM,QAAQ,WAAW,iBAAiB;AAAA,GAxC3CE,EAyCF,WAAA,cAAA;AAIAkD,EAAA;AAAA,EADRpD,EAAS,EAAE,MAAM,QAAQ,WAAW,iBAAiB;AAAA,GA5C3CE,EA6CF,WAAA,cAAA;AAaTkD,EAAA;AAAA,EADCpD,EAAS,EAAE,MAAM,QAAQ,WAAW,eAAe;AAAA,GAzDzCE,EA0DX,WAAA,YAAA;AAQAkD,EAAA;AAAA,EADCpD,EAAS,EAAE,MAAM,SAAS,WAAW,iBAAiB;AAAA,GAjE5CE,EAkEX,WAAA,cAAA;AASAkD,EAAA;AAAA,EADCpD,EAAS,EAAE,MAAM,QAAQ,WAAW,kBAAkB;AAAA,GA1E5CE,EA2EX,WAAA,eAAA;AA3EK,IAAMmD,IAANnD;"}
|
package/dist/rc-bottom-sheet.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RCDialog } from '@rcarls/rc-dialog';
|
|
2
|
+
import { DialogGeometry } from '@rcarls/rc-dialog/geometry';
|
|
2
3
|
import { ResizeDirection, ResizeLifecycleDetail, ResizeOrigin } from '@rcarls/rc-common';
|
|
3
4
|
declare global {
|
|
4
5
|
interface HTMLElementTagNameMap {
|
|
@@ -31,7 +32,7 @@ export interface RCBottomSheetSnapDetail {
|
|
|
31
32
|
* The direct child `<dialog>` remains the semantic dialog surface and owns
|
|
32
33
|
* content, labels, and form behavior. `rc-bottom-sheet` supplies the same
|
|
33
34
|
* open/close lifecycle, focus restoration, cancel/request-close events, and
|
|
34
|
-
*
|
|
35
|
+
* `closed-by` dismissal as `rc-dialog`. The component docks the sheet to
|
|
35
36
|
* the viewport block-end by default, while themes provide its visual surface.
|
|
36
37
|
*
|
|
37
38
|
* Dragging the resize handle past a decisive velocity in either direction
|
|
@@ -45,6 +46,16 @@ export interface RCBottomSheetSnapDetail {
|
|
|
45
46
|
* `--rc-bottom-sheet-snap-easing`, and is skipped for
|
|
46
47
|
* `prefers-reduced-motion: reduce`.
|
|
47
48
|
*
|
|
49
|
+
* Opening and closing themselves fade the surface and its backdrop through
|
|
50
|
+
* CSS alone (`@starting-style` plus a discrete `display`/`overlay`
|
|
51
|
+
* transition), using the shared `--rc-motion-effects-*` tokens.
|
|
52
|
+
* `showModal()`/`show()`/`close()` stay synchronous; nothing waits on the
|
|
53
|
+
* transition finishing. The entrance animates opacity only, not a slide
|
|
54
|
+
* transform, because several call sites (and this component's own drag/snap
|
|
55
|
+
* math) read `getBoundingClientRect()` right after opening, which reports
|
|
56
|
+
* the transformed box for as long as a transform-based entrance is still
|
|
57
|
+
* running.
|
|
58
|
+
*
|
|
48
59
|
* @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-bottom-sheet rc-bottom-sheet docs}
|
|
49
60
|
* @see {@link https://m3.material.io/components/bottom-sheets/overview Material Design bottom sheets}
|
|
50
61
|
*
|
|
@@ -59,10 +70,11 @@ export interface RCBottomSheetSnapDetail {
|
|
|
59
70
|
* @fires rc-bottom-sheet-resize-start - Fires when an interactive resize begins.
|
|
60
71
|
* `detail: { height, inputType }`
|
|
61
72
|
* @fires rc-bottom-sheet-snap - Fires after a drag release or `snapTo()` call
|
|
62
|
-
* settles at its snap target. `detail: { index, height, trigger }`
|
|
73
|
+
* settles at its snap target. `detail: { index, height, trigger }`, with `height` in the
|
|
74
|
+
* sheet's own CSS pixels, the unit of its snap points.
|
|
63
75
|
*
|
|
64
|
-
* @attr
|
|
65
|
-
*
|
|
76
|
+
* @attr closed-by - Proxied to the inner `<dialog closedby="...">` attribute. Defaults to `any`, so
|
|
77
|
+
* Escape or a backdrop click closes the sheet; inherited from `rc-dialog`.
|
|
66
78
|
* @attr resize - Enables edge resizing, mirroring the CSS `resize` property values.
|
|
67
79
|
* Defaults to `vertical`; inherited from `rc-dialog`.
|
|
68
80
|
* @attr resize-origin - Origin for resize edge hit-testing or explicit handles. Defaults to
|
|
@@ -70,7 +82,8 @@ export interface RCBottomSheetSnapDetail {
|
|
|
70
82
|
* @attr resize-handle - CSS selector, scoped to the inner `<dialog>`, for explicit resize
|
|
71
83
|
* handles. Defaults to `[data-rc-bottom-sheet-handle]`; inherited from `rc-dialog`.
|
|
72
84
|
* @attr snap-points - Whitespace-separated CSS heights, in ascending order, addressable by
|
|
73
|
-
* `snapTo()` and settled to on drag release.
|
|
85
|
+
* `snapTo()` and settled to on drag release. Accepts `calc()` and `var()`, resolved in the
|
|
86
|
+
* sheet's context when each snap applies; percentages are of the viewport height.
|
|
74
87
|
* @attr swipe-dismiss - Whether a decisive downward swipe of at least 96 pixels requests close.
|
|
75
88
|
* @attr swipe-velocity - Minimum release velocity, in pixels per second, that counts as a
|
|
76
89
|
* decisive swipe rather than a slow deliberate drag.
|
|
@@ -90,6 +103,8 @@ export interface RCBottomSheetSnapDetail {
|
|
|
90
103
|
* @cssprop [--rc-bottom-sheet-handle-inline-size=2rem] - Resize handle width.
|
|
91
104
|
* @cssprop [--rc-bottom-sheet-handle-block-size=0.25rem] - Resize handle height.
|
|
92
105
|
* @cssprop [--rc-bottom-sheet-handle-radius=999px] - Resize handle corner radius.
|
|
106
|
+
* @cssprop [--rc-bottom-sheet-handle-offset=50%] - Block-start position of the handle indicator's
|
|
107
|
+
* center within its target. Set a length to pin it above a title inside the handle.
|
|
93
108
|
* @cssprop [--rc-bottom-sheet-handle-target-inline-size=100%] - Pointer target width for a direct-child handle.
|
|
94
109
|
* @cssprop [--rc-bottom-sheet-handle-target-block-size=3rem] - Pointer target height for a direct-child handle.
|
|
95
110
|
* @cssprop [--rc-bottom-sheet-handle-target-margin=0] - Margin around a direct-child handle target.
|
|
@@ -102,13 +117,14 @@ export declare class RCBottomSheet extends RCDialog {
|
|
|
102
117
|
private static readonly swipeDismissThreshold;
|
|
103
118
|
private static readonly _styledRoots;
|
|
104
119
|
private static _ensureBaseStyles;
|
|
120
|
+
protected static _geometryClass: typeof DialogGeometry;
|
|
105
121
|
/**
|
|
106
|
-
* Bottom sheets
|
|
122
|
+
* Bottom sheets close on Escape or a backdrop click by default.
|
|
107
123
|
*
|
|
108
|
-
* Set `
|
|
109
|
-
*
|
|
124
|
+
* Set `closed-by="closerequest"` when a backdrop click must not close the
|
|
125
|
+
* sheet, or `closed-by="none"` when only explicit actions may close it.
|
|
110
126
|
*/
|
|
111
|
-
|
|
127
|
+
closedBy: 'any' | 'closerequest' | 'none' | '';
|
|
112
128
|
/** Bottom sheets resize vertically from the top edge by default. */
|
|
113
129
|
resize: ResizeDirection;
|
|
114
130
|
/** Top-origin resizing keeps the sheet docked to the viewport bottom. */
|
|
@@ -120,6 +136,10 @@ export declare class RCBottomSheet extends RCDialog {
|
|
|
120
136
|
*
|
|
121
137
|
* Each height becomes an addressable snap target. Slow releases choose the
|
|
122
138
|
* nearest target, while decisive swipes choose the first or last target.
|
|
139
|
+
* Any CSS length works, including `calc()` and `var()`, and is resolved in
|
|
140
|
+
* the sheet's own context when a snap is applied, so a custom property the
|
|
141
|
+
* page animates (for example from a scroll timeline) is read at its current
|
|
142
|
+
* value. Percentages are of the viewport height.
|
|
123
143
|
*/
|
|
124
144
|
snapPoints: string;
|
|
125
145
|
/**
|
|
@@ -138,7 +158,11 @@ export declare class RCBottomSheet extends RCDialog {
|
|
|
138
158
|
private readonly _reducedMotion;
|
|
139
159
|
private _activeSnapAnimation;
|
|
140
160
|
private _lastSnapIndex;
|
|
161
|
+
private _pendingSnap;
|
|
162
|
+
private _extendedForFocus;
|
|
141
163
|
private readonly _onWindowResize;
|
|
164
|
+
private readonly _onFocusIn;
|
|
165
|
+
private readonly _onFocusOut;
|
|
142
166
|
connectedCallback(): void;
|
|
143
167
|
disconnectedCallback(): void;
|
|
144
168
|
/**
|
|
@@ -156,15 +180,24 @@ export declare class RCBottomSheet extends RCDialog {
|
|
|
156
180
|
* sheet.snapTo(1);
|
|
157
181
|
*/
|
|
158
182
|
snapTo(index: number, behavior?: 'animated' | 'instant'): void;
|
|
183
|
+
protected _applyOpen(open: boolean, silent: boolean, modal?: boolean, moveFocus?: boolean): boolean;
|
|
159
184
|
protected _onResizeStart(detail: ResizeLifecycleDetail): void;
|
|
160
185
|
protected _onResizeEnd(detail: ResizeLifecycleDetail): void;
|
|
161
186
|
private _shouldSwipeDismiss;
|
|
162
187
|
private _snapToNearestPoint;
|
|
163
188
|
private _snapToIndex;
|
|
164
189
|
private _applySnap;
|
|
190
|
+
/**
|
|
191
|
+
* Keeps a focused field inside the sheet visible, as Android and Material
|
|
192
|
+
* sheets do above a software keyboard: scroll it into view within the
|
|
193
|
+
* sheet's own scroll containers, then grow the sheet past its snap height
|
|
194
|
+
* and CSS maximum (up to the space above its docked edge) only when
|
|
195
|
+
* scrolling cannot reveal it.
|
|
196
|
+
*/
|
|
197
|
+
private _revealFocusedField;
|
|
165
198
|
private _snapDuration;
|
|
166
199
|
private _snapEasing;
|
|
167
200
|
private _resolvedSnapPoints;
|
|
168
|
-
private
|
|
201
|
+
private _measureSnapPoints;
|
|
169
202
|
}
|
|
170
203
|
export default RCBottomSheet;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.7.0",
|
|
7
7
|
"description": "Modal bottom-sheet wrapper for a native <dialog>.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"test:browser:firefox": "vitest --run --project=firefox"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@rcarls/rc-common": "0.
|
|
49
|
-
"@rcarls/rc-dialog": "0.
|
|
48
|
+
"@rcarls/rc-common": "0.7.0",
|
|
49
|
+
"@rcarls/rc-dialog": "0.7.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@custom-elements-manifest/analyzer": "0.11.0",
|