pure-web-bottom-sheet 0.5.0 → 0.6.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.
@@ -12,12 +12,13 @@ import { template } from './bottom-sheet.template.js';
12
12
  * }
13
13
  */
14
14
  class BottomSheet extends HTMLElement {
15
- static observedAttributes = ["nested-scroll-optimization"];
15
+ static observedAttributes = ["nested-scroll-optimization", "content-height"];
16
16
  #handleViewportResize = () => {
17
17
  this.style.setProperty("--sw-keyboard-height", `${window.visualViewport?.offsetTop ?? 0}px`);
18
18
  };
19
19
  #shadow;
20
20
  #cleanupIntersectionObserver = null;
21
+ #cleanupSheetSizeObserver = null;
21
22
  #cleanupNestedScrollResizeOptimization = null;
22
23
  #currentSnapState = null;
23
24
  constructor() {
@@ -31,20 +32,13 @@ class BottomSheet extends HTMLElement {
31
32
  shadow.innerHTML = template;
32
33
  }
33
34
  this.#shadow = shadow;
34
- const supportsScrollSnapChange = "onscrollsnapchange" in window;
35
- if (supportsScrollSnapChange) {
36
- this.addEventListener("scrollsnapchange", this.#handleScrollSnapChange);
37
- }
38
35
  if (!CSS.supports("(animation-timeline: scroll()) and (animation-range: 0% 100%)")) {
39
36
  this.addEventListener("scroll", this.#handleScroll);
40
37
  this.#handleScroll();
41
38
  }
42
39
  }
43
40
  connectedCallback() {
44
- const supportsScrollSnapChange = "onscrollsnapchange" in window;
45
- if (!supportsScrollSnapChange) {
46
- this.#setupIntersectionObserver();
47
- }
41
+ this.#setupIntersectionObserver();
48
42
  window.visualViewport?.addEventListener("resize", this.#handleViewportResize);
49
43
  }
50
44
  #setupIntersectionObserver() {
@@ -52,41 +46,43 @@ class BottomSheet extends HTMLElement {
52
46
  const bottomSnapTarget = this.#shadow.querySelector('.sentinel[data-snap="bottom"]');
53
47
  if (!snapSlot || !bottomSnapTarget)
54
48
  return;
49
+ const contentHeightTarget = this.#shadow.querySelector('.sentinel[data-snap="content-height"]');
55
50
  const intersectingTargets = new Set();
56
51
  let previousSnapTarget = null;
57
- const getDistanceToSnapLine = (entry) => Math.abs(entry.intersectionRect.top - (entry.rootBounds?.bottom ?? 0));
58
52
  const observer = new IntersectionObserver((entries) => {
59
- // Add intersecting entries to the set sorted by proximity to the host's top
60
- // which is the point where snapping occurs because we use `scroll-snap-align: start`
61
- entries
62
- .filter((entry) => entry.isIntersecting)
63
- .sort((a, b) => {
64
- return getDistanceToSnapLine(b) - getDistanceToSnapLine(a);
65
- })
66
- .forEach((entry) => {
67
- intersectingTargets.add(entry.target);
68
- });
69
- entries
70
- .filter((entry) => !entry.isIntersecting)
71
- .forEach((entry) => {
72
- intersectingTargets.delete(entry.target);
53
+ entries.forEach((entry) => {
54
+ if (entry.isIntersecting) {
55
+ intersectingTargets.add(entry.target);
56
+ }
57
+ else {
58
+ intersectingTargets.delete(entry.target);
59
+ }
73
60
  });
74
61
  // Skip when the root has no dimensions (e.g., inside a closed dialog)
75
62
  if (!entries[0]?.rootBounds?.height) {
76
63
  return;
77
64
  }
78
- // Skip bottom snap target (handled separately for collapsed state detection)
79
- const currentTarget = Array.from(intersectingTargets).findLast((target) => target !== bottomSnapTarget);
80
- if (currentTarget === previousSnapTarget) {
65
+ // Pick the intersecting target closest to the snap line (host's top edge).
66
+ // Skip bottom snap target (handled separately for collapsed state detection).
67
+ const currentTarget = Array.from(intersectingTargets)
68
+ .filter((target) => target !== bottomSnapTarget)
69
+ .sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top)
70
+ .at(-1);
71
+ if (currentTarget === previousSnapTarget &&
72
+ // Never skip the "content-height" target even if it was the previous snap
73
+ // target, because the computed snap index may be different if the content
74
+ // height has changed since the last intersection update.
75
+ currentTarget !== contentHeightTarget) {
81
76
  return;
82
77
  }
83
- // Handle case where none of the targets intersect (fully collapsed state)
78
+ // No snap target within the bounds -> collapsed if the bottom sentinel has
79
+ // also exited (scrollTop at 0) and swipe-to-dismiss is enabled.
84
80
  if (!currentTarget) {
85
81
  if (intersectingTargets.has(bottomSnapTarget) ||
82
+ this.scrollTop > 1 ||
86
83
  !this.hasAttribute("swipe-to-dismiss")) {
87
84
  return;
88
85
  }
89
- // Use bottom target when nothing intersects and swipe-to-dismiss is enabled
90
86
  previousSnapTarget = bottomSnapTarget;
91
87
  this.#updateSnapPosition(bottomSnapTarget);
92
88
  return;
@@ -95,7 +91,7 @@ class BottomSheet extends HTMLElement {
95
91
  this.#updateSnapPosition(currentTarget);
96
92
  }, {
97
93
  root: this,
98
- rootMargin: "1000% 0px -100% 0px",
94
+ rootMargin: "100% 0px -100% 0px",
99
95
  });
100
96
  const sentinels = this.#shadow.querySelectorAll(".sentinel");
101
97
  Array.from(sentinels).forEach((sentinel) => {
@@ -108,6 +104,7 @@ class BottomSheet extends HTMLElement {
108
104
  observedSnapPoints.forEach((el) => {
109
105
  if (!snapPoints.has(el)) {
110
106
  observer.unobserve(el);
107
+ intersectingTargets.delete(el);
111
108
  }
112
109
  });
113
110
  snapPoints.forEach((el) => observer.observe(el));
@@ -121,13 +118,6 @@ class BottomSheet extends HTMLElement {
121
118
  this.#cleanupIntersectionObserver = null;
122
119
  };
123
120
  }
124
- #handleScrollSnapChange(event) {
125
- const snapEvent = event;
126
- if (!(snapEvent.snapTargetBlock instanceof HTMLElement)) {
127
- return;
128
- }
129
- this.#updateSnapPosition(snapEvent.snapTargetBlock);
130
- }
131
121
  #updateSnapPosition(newSnapTarget) {
132
122
  const snapState = this.#calculateSnapState(newSnapTarget);
133
123
  if (!snapState)
@@ -137,7 +127,7 @@ class BottomSheet extends HTMLElement {
137
127
  this.#currentSnapState?.sheetState === sheetState) {
138
128
  return;
139
129
  }
140
- this.#currentSnapState = snapState;
130
+ this.#currentSnapState = { ...snapState, snapTarget: newSnapTarget };
141
131
  this.dataset.sheetState = sheetState;
142
132
  this.dispatchEvent(new CustomEvent("snap-position-change", {
143
133
  detail: snapState,
@@ -146,27 +136,75 @@ class BottomSheet extends HTMLElement {
146
136
  }));
147
137
  }
148
138
  #calculateSnapState(snapTarget) {
149
- const snapSlot = this.#shadow.querySelector('slot[name="snap"]');
150
- if (!snapSlot)
151
- return null;
152
- const assignedSnapPoints = snapSlot.assignedElements();
153
- const hasTopSnapPoint = assignedSnapPoints.at(0)?.classList.contains("top") ?? false;
154
- // Snapped on the .snap.snap-bottom element
155
139
  if (snapTarget instanceof HTMLElement &&
156
140
  snapTarget.dataset.snap === "bottom") {
157
141
  return { snapIndex: 0, sheetState: "collapsed" };
158
142
  }
143
+ const snapSlot = this.#shadow.querySelector('slot[name="snap"]');
144
+ if (!snapSlot)
145
+ return null;
146
+ // Reverse to bottom-to-top order so array index maps to snap index (i + 1)
147
+ const assignedSnapPoints = snapSlot.assignedElements().reverse();
148
+ const hasTopSnapPoint = assignedSnapPoints.at(-1)?.classList.contains("top") ?? false;
149
+ const maxExpandedIndex = assignedSnapPoints.length + (hasTopSnapPoint ? 0 : 1);
150
+ // When content-height is set, the topmost reachable snap index may be
151
+ // lower than maxExpandedIndex (limited by how tall the content is).
152
+ let topmostReachableIndex = maxExpandedIndex;
153
+ if (this.hasAttribute("content-height")) {
154
+ const maxSheetHeight = Math.min(this.offsetHeight, this.scrollHeight - this.offsetHeight);
155
+ let minHeightGap = Infinity;
156
+ for (let i = 0; i < assignedSnapPoints.length; i++) {
157
+ const el = assignedSnapPoints[i];
158
+ if (!(el instanceof HTMLElement))
159
+ continue;
160
+ // Add 1px to account for the -1px offset in CSS
161
+ const snapOffset = el.offsetTop + 1;
162
+ const heightGap = snapOffset - maxSheetHeight;
163
+ if (heightGap >= 0 && heightGap < minHeightGap) {
164
+ minHeightGap = heightGap;
165
+ topmostReachableIndex = i + 1;
166
+ }
167
+ }
168
+ // When snap target is the content-height sentinel, find the snap index from
169
+ // the current scroll position since the sentinel itself is not a snap point.
170
+ if (snapTarget instanceof HTMLElement &&
171
+ snapTarget.dataset.snap === "content-height") {
172
+ let closestSnapIndex = -1;
173
+ let minScrollGap = Infinity;
174
+ for (let i = 0; i < assignedSnapPoints.length; i++) {
175
+ const el = assignedSnapPoints[i];
176
+ if (!(el instanceof HTMLElement))
177
+ continue;
178
+ // Add 1px to account for the -1px offset in CSS
179
+ const snapOffset = el.offsetTop + 1;
180
+ const scrollGap = snapOffset - this.scrollTop;
181
+ if (scrollGap >= 0 && scrollGap < minScrollGap) {
182
+ minScrollGap = scrollGap;
183
+ closestSnapIndex = i + 1;
184
+ }
185
+ }
186
+ if (closestSnapIndex !== -1) {
187
+ return {
188
+ snapIndex: closestSnapIndex,
189
+ sheetState: closestSnapIndex >= topmostReachableIndex
190
+ ? "expanded"
191
+ : "partially-expanded",
192
+ };
193
+ }
194
+ }
195
+ }
159
196
  // Snapped on one of the snap points assigned to the "snap" slot
160
197
  if (snapTarget.matches('[slot="snap"]')) {
161
- const position = assignedSnapPoints.indexOf(snapTarget);
162
- const snapIndex = assignedSnapPoints.length - position;
163
- const isTopSnapPoint = snapTarget.classList.contains("top");
164
- const sheetState = isTopSnapPoint ? "expanded" : "partially-expanded";
165
- return { snapIndex, sheetState };
198
+ const snapIndex = assignedSnapPoints.indexOf(snapTarget) + 1;
199
+ return {
200
+ snapIndex,
201
+ sheetState: snapIndex >= topmostReachableIndex
202
+ ? "expanded"
203
+ : "partially-expanded",
204
+ };
166
205
  }
167
- // Snapped either on the .sheet element or on the "snap" slot fallback element
168
- const snapIndex = assignedSnapPoints.length + (hasTopSnapPoint ? 0 : 1);
169
- return { snapIndex, sheetState: "expanded" };
206
+ // Snapped on the .sheet element or the "snap" slot fallback element
207
+ return { snapIndex: maxExpandedIndex, sheetState: "expanded" };
170
208
  }
171
209
  #handleScroll() {
172
210
  this.#shadow
@@ -292,6 +330,29 @@ class BottomSheet extends HTMLElement {
292
330
  this.#cleanupNestedScrollResizeOptimization = null;
293
331
  };
294
332
  }
333
+ #setupSheetSizeObserver() {
334
+ // Observe sheet size changes to re-dispatch the snap-position-change event
335
+ // in case the height change results in a different snap point being the closest
336
+ // one to the sheet top.
337
+ let previousBlockSize = 0;
338
+ const resizeObserver = new ResizeObserver((e) => {
339
+ const blockSize = e.at(0)?.contentBoxSize.at(0)?.blockSize ?? 0;
340
+ if (!blockSize || blockSize === previousBlockSize)
341
+ return;
342
+ previousBlockSize = blockSize;
343
+ if (this.#currentSnapState) {
344
+ this.#updateSnapPosition(this.#currentSnapState.snapTarget);
345
+ }
346
+ });
347
+ const sheet = this.#shadow.querySelector(".sheet");
348
+ if (sheet) {
349
+ resizeObserver.observe(sheet);
350
+ }
351
+ this.#cleanupSheetSizeObserver = () => {
352
+ resizeObserver.disconnect();
353
+ this.#cleanupSheetSizeObserver = null;
354
+ };
355
+ }
295
356
  attributeChangedCallback(name, oldValue, newValue) {
296
357
  if (oldValue === newValue)
297
358
  return;
@@ -307,6 +368,17 @@ class BottomSheet extends HTMLElement {
307
368
  this.#cleanupNestedScrollResizeOptimization();
308
369
  }
309
370
  break;
371
+ case "content-height":
372
+ if (newValue !== null) {
373
+ if (!this.#cleanupSheetSizeObserver) {
374
+ // Only setup if not already setup
375
+ this.#setupSheetSizeObserver();
376
+ }
377
+ }
378
+ else if (this.#cleanupSheetSizeObserver) {
379
+ this.#cleanupSheetSizeObserver();
380
+ }
381
+ break;
310
382
  default:
311
383
  console.warn(`Unhandled attribute: ${name}`);
312
384
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bottom-sheet.js","sources":["../../../../src/web/bottom-sheet.ts"],"sourcesContent":["import { template } from \"./bottom-sheet.template\";\n\n/**\n * @see https://drafts.csswg.org/css-scroll-snap-2/#snapevent-interface\n */\ninterface SnapEvent extends Event {\n snapTargetBlock: Element;\n}\n\n/**\n * @see https://drafts.csswg.org/scroll-animations/#scrolltimeline-interface\n */\ninterface ScrollTimeline extends AnimationTimeline {\n readonly source: Element | null;\n readonly axis: string;\n}\ndeclare var ScrollTimeline: {\n prototype: ScrollTimeline;\n new ({ source, axis }: { source: Element; axis: string }): ScrollTimeline;\n};\n\n/**\n * BottomSheet custom element.\n *\n * @example\n * // Register in TypeScript for proper type checking:\n * declare global {\n * interface HTMLElementTagNameMap {\n * \"bottom-sheet\": BottomSheet;\n * }\n * }\n */\nexport class BottomSheet extends HTMLElement {\n static observedAttributes = [\"nested-scroll-optimization\"];\n #handleViewportResize = () => {\n this.style.setProperty(\n \"--sw-keyboard-height\",\n `${window.visualViewport?.offsetTop ?? 0}px`,\n );\n };\n #shadow: ShadowRoot;\n #cleanupIntersectionObserver: (() => void) | null = null;\n #cleanupNestedScrollResizeOptimization: (() => void) | null = null;\n #currentSnapState: { snapIndex: number; sheetState: SheetState } | null =\n null;\n\n constructor() {\n super();\n\n const supportsDeclarative =\n HTMLElement.prototype.hasOwnProperty(\"attachInternals\");\n const internals = supportsDeclarative ? this.attachInternals() : undefined;\n\n // Use existing declarative shadow root if present, otherwise create one\n let shadow = internals?.shadowRoot;\n if (!shadow) {\n shadow = this.attachShadow({ mode: \"open\" });\n shadow.innerHTML = template;\n }\n this.#shadow = shadow;\n\n const supportsScrollSnapChange = \"onscrollsnapchange\" in window;\n if (supportsScrollSnapChange) {\n this.addEventListener(\"scrollsnapchange\", this.#handleScrollSnapChange);\n }\n\n if (\n !CSS.supports(\n \"(animation-timeline: scroll()) and (animation-range: 0% 100%)\",\n )\n ) {\n this.addEventListener(\"scroll\", this.#handleScroll);\n this.#handleScroll();\n }\n }\n\n connectedCallback() {\n const supportsScrollSnapChange = \"onscrollsnapchange\" in window;\n if (!supportsScrollSnapChange) {\n this.#setupIntersectionObserver();\n }\n\n window.visualViewport?.addEventListener(\n \"resize\",\n this.#handleViewportResize,\n );\n }\n\n #setupIntersectionObserver() {\n const snapSlot =\n this.#shadow.querySelector<HTMLSlotElement>('slot[name=\"snap\"]');\n const bottomSnapTarget = this.#shadow.querySelector(\n '.sentinel[data-snap=\"bottom\"]',\n );\n\n if (!snapSlot || !bottomSnapTarget) return;\n\n const intersectingTargets = new Set<Element>();\n let previousSnapTarget: Element | null = null;\n\n const getDistanceToSnapLine = (entry: IntersectionObserverEntry) =>\n Math.abs(entry.intersectionRect.top - (entry.rootBounds?.bottom ?? 0));\n\n const observer = new IntersectionObserver(\n (entries) => {\n // Add intersecting entries to the set sorted by proximity to the host's top\n // which is the point where snapping occurs because we use `scroll-snap-align: start`\n entries\n .filter((entry) => entry.isIntersecting)\n .sort((a, b) => {\n return getDistanceToSnapLine(b) - getDistanceToSnapLine(a);\n })\n .forEach((entry) => {\n intersectingTargets.add(entry.target);\n });\n\n entries\n .filter((entry) => !entry.isIntersecting)\n .forEach((entry) => {\n intersectingTargets.delete(entry.target);\n });\n\n // Skip when the root has no dimensions (e.g., inside a closed dialog)\n if (!entries[0]?.rootBounds?.height) {\n return;\n }\n\n // Skip bottom snap target (handled separately for collapsed state detection)\n const currentTarget = Array.from(intersectingTargets).findLast(\n (target) => target !== bottomSnapTarget,\n );\n\n if (currentTarget === previousSnapTarget) {\n return;\n }\n\n // Handle case where none of the targets intersect (fully collapsed state)\n if (!currentTarget) {\n if (\n intersectingTargets.has(bottomSnapTarget) ||\n !this.hasAttribute(\"swipe-to-dismiss\")\n ) {\n return;\n }\n // Use bottom target when nothing intersects and swipe-to-dismiss is enabled\n previousSnapTarget = bottomSnapTarget;\n this.#updateSnapPosition(bottomSnapTarget);\n return;\n }\n\n previousSnapTarget = currentTarget;\n this.#updateSnapPosition(currentTarget);\n },\n {\n root: this,\n rootMargin: \"1000% 0px -100% 0px\",\n },\n );\n\n const sentinels = this.#shadow.querySelectorAll(\".sentinel\");\n Array.from(sentinels).forEach((sentinel) => {\n observer.observe(sentinel);\n });\n\n let observedSnapPoints = new Set<Element>();\n const observeSnapPoints = () => {\n const snapPoints = new Set(snapSlot.assignedElements());\n\n // Unobserve elements no longer assigned to the slot\n observedSnapPoints.forEach((el) => {\n if (!snapPoints.has(el)) {\n observer.unobserve(el);\n }\n });\n\n snapPoints.forEach((el) => observer.observe(el));\n\n observedSnapPoints = snapPoints;\n };\n snapSlot.addEventListener(\"slotchange\", observeSnapPoints);\n observeSnapPoints();\n\n this.#cleanupIntersectionObserver = () => {\n snapSlot.removeEventListener(\"slotchange\", observeSnapPoints);\n observer.disconnect();\n this.#cleanupIntersectionObserver = null;\n };\n }\n\n #handleScrollSnapChange(event: Event) {\n const snapEvent = event as SnapEvent;\n if (!(snapEvent.snapTargetBlock instanceof HTMLElement)) {\n return;\n }\n this.#updateSnapPosition(snapEvent.snapTargetBlock);\n }\n\n #updateSnapPosition(newSnapTarget: Element) {\n const snapState = this.#calculateSnapState(newSnapTarget);\n if (!snapState) return;\n\n const { snapIndex, sheetState } = snapState;\n\n if (\n this.#currentSnapState?.snapIndex === snapIndex &&\n this.#currentSnapState?.sheetState === sheetState\n ) {\n return;\n }\n\n this.#currentSnapState = snapState;\n this.dataset.sheetState = sheetState;\n\n this.dispatchEvent(\n new CustomEvent<SnapPositionChangeEventDetail>(\"snap-position-change\", {\n detail: snapState,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n #calculateSnapState(\n snapTarget: Element,\n ): { snapIndex: number; sheetState: SheetState } | null {\n const snapSlot =\n this.#shadow.querySelector<HTMLSlotElement>('slot[name=\"snap\"]');\n if (!snapSlot) return null;\n\n const assignedSnapPoints = snapSlot.assignedElements();\n\n const hasTopSnapPoint =\n assignedSnapPoints.at(0)?.classList.contains(\"top\") ?? false;\n\n // Snapped on the .snap.snap-bottom element\n if (\n snapTarget instanceof HTMLElement &&\n snapTarget.dataset.snap === \"bottom\"\n ) {\n return { snapIndex: 0, sheetState: \"collapsed\" };\n }\n\n // Snapped on one of the snap points assigned to the \"snap\" slot\n if (snapTarget.matches('[slot=\"snap\"]')) {\n const position = assignedSnapPoints.indexOf(snapTarget);\n const snapIndex = assignedSnapPoints.length - position;\n const isTopSnapPoint = snapTarget.classList.contains(\"top\");\n const sheetState = isTopSnapPoint ? \"expanded\" : \"partially-expanded\";\n return { snapIndex, sheetState };\n }\n\n // Snapped either on the .sheet element or on the \"snap\" slot fallback element\n const snapIndex = assignedSnapPoints.length + (hasTopSnapPoint ? 0 : 1);\n return { snapIndex, sheetState: \"expanded\" };\n }\n\n #handleScroll() {\n this.#shadow\n .querySelector<HTMLElement>(\".sheet-wrapper\")\n ?.style.setProperty(\"--sheet-position\", `${this.scrollTop}px`);\n }\n\n #setupNestedScrollResizeOptimization() {\n const wrapper = this.#shadow.querySelector<HTMLElement>(\".sheet-wrapper\");\n const sheet = this.#shadow.querySelector<HTMLElement>(\".sheet\");\n const content = this.#shadow.querySelector<HTMLElement>(\".sheet-content\");\n\n if (!wrapper || !sheet || !content) {\n return;\n }\n\n const SCROLL_END_TIMEOUT_MS = 100;\n const supportsScrollAnimations = CSS.supports(\n \"scroll-timeline: --sheet-timeline y\",\n );\n // Use scrollend event if available to detect end of scrolling\n // (exclude Firefox for now which has inconsistencies with scrollend\n // implementation with scroll chaining from nested scrollable elements)\n const supportScrollEnd =\n \"onscrollend\" in window && !CSS.supports(\"-moz-appearance\", \"none\");\n let contentYOffsetStart: number,\n contentYOffsetEnd: number,\n scrollTimeout: number;\n\n // If CSS scroll-timeline is not supported, we need to manually update\n // the y offset of the sheet content during scrolling\n const updateContentYOffset = () => {\n const t = this.scrollTop / (this.scrollHeight - this.offsetHeight);\n const contentTranslateY =\n (1 - t) * contentYOffsetStart + t * contentYOffsetEnd;\n wrapper.style.setProperty(\n \"--sheet-content-offset\",\n `${contentTranslateY}px`,\n );\n };\n\n const cleanupStyleProperties = () => {\n delete this.dataset.scrolling;\n [\n \"--sheet-content-offset\",\n \"--sheet-content-offset-start\",\n \"--sheet-content-offset-end\",\n ].forEach((p) => wrapper.style.removeProperty(p));\n };\n\n const updateOffsetProperties = () => {\n wrapper.style.setProperty(\n \"--sheet-content-offset-start\",\n `${contentYOffsetStart}px`,\n );\n wrapper.style.setProperty(\n \"--sheet-content-offset-end\",\n `${contentYOffsetEnd}px`,\n );\n };\n\n const handleScrollEnd = () => {\n const style = getComputedStyle(content);\n const matrix = new DOMMatrixReadOnly(style.transform);\n const yOffset = -matrix.m42;\n cleanupStyleProperties();\n // Pin the sheet height inline so the content scrollTop update below is based\n // on the correct scroll height. Removing [data-scrolling] reactivates the\n // scroll-timeline-driven \"expand-sheet-height\" animation, which will not\n // produce a current time until the next layout pass, leaving the sheet height\n // (and content scroll height) stale for the scrollTop assignment that follows.\n sheet.style.height = `${(this.scrollTop / (this.scrollHeight - this.offsetHeight)) * 100}%`;\n content.scrollTop = yOffset + content.scrollTop;\n // Clear the temporary inline override now that the scrollTop is set\n sheet.style.height = \"\";\n };\n\n const handleScroll = () => {\n if (!(\"scrolling\" in this.dataset)) {\n const contentMaxOffsetHeight =\n wrapper.offsetHeight - (sheet.offsetHeight - content.offsetHeight);\n // Threshold after which resizing sheet content anchors the inner\n // content to the bottom of the scrollport instead of top\n const scrollBottomAnchorThreshold =\n content.scrollHeight - contentMaxOffsetHeight;\n if (Math.floor(content.scrollTop) > scrollBottomAnchorThreshold) {\n const contentScrollBottom =\n content.scrollHeight - content.offsetHeight - content.scrollTop;\n\n // Size factor based on the current height of the bottom sheet\n const sheetSizeFactor =\n wrapper.offsetHeight / (wrapper.offsetHeight - sheet.offsetHeight);\n\n contentYOffsetStart =\n -(\n content.scrollHeight +\n (sheet.offsetHeight - content.offsetHeight)\n ) +\n contentScrollBottom * sheetSizeFactor;\n contentYOffsetEnd = -scrollBottomAnchorThreshold;\n\n // Toggle scrolling state on\n this.dataset.scrolling = \"\";\n\n // Readjust sheet content offset range based on current scroll position\n // of the sheet content after the layout change caused by toggling the\n // data-scrolling attribute\n contentYOffsetStart += content.scrollTop;\n contentYOffsetEnd += content.scrollTop;\n\n updateOffsetProperties();\n } else {\n contentYOffsetStart = 0;\n contentYOffsetEnd = 0;\n updateOffsetProperties();\n this.dataset.scrolling = \"\";\n }\n\n if (!supportsScrollAnimations) {\n updateContentYOffset();\n }\n\n if (\"ScrollTimeline\" in window) {\n // Needed for Safari 26+ to prevent flash of sheet position when toggling data-scrolling\n // before the scroll timeline is applied by the browser\n const timeline = new ScrollTimeline({\n source: this,\n axis: \"y\",\n });\n wrapper.style.setProperty(\n \"--sheet-timeline-at-scroll-start\",\n `${timeline.currentTime}`,\n );\n }\n } else if (!supportsScrollAnimations) {\n updateContentYOffset();\n }\n };\n\n const handleFallbackScrollEnd = () => {\n window.clearTimeout(scrollTimeout);\n scrollTimeout = window.setTimeout(handleScrollEnd, SCROLL_END_TIMEOUT_MS);\n };\n\n this.addEventListener(\"scroll\", handleScroll);\n if (supportScrollEnd) {\n this.addEventListener(\"scrollend\", handleScrollEnd);\n } else {\n this.addEventListener(\"scroll\", handleFallbackScrollEnd);\n }\n\n this.#cleanupNestedScrollResizeOptimization = () => {\n this.removeEventListener(\"scroll\", handleScroll);\n if (supportScrollEnd) {\n this.removeEventListener(\"scrollend\", handleScrollEnd);\n } else {\n this.removeEventListener(\"scroll\", handleFallbackScrollEnd);\n window.clearTimeout(scrollTimeout);\n }\n\n cleanupStyleProperties();\n this.#cleanupNestedScrollResizeOptimization = null;\n };\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) return;\n\n switch (name) {\n case \"nested-scroll-optimization\":\n if (newValue !== null) {\n if (!this.#cleanupNestedScrollResizeOptimization) {\n // Only setup if not already setup\n this.#setupNestedScrollResizeOptimization();\n }\n } else if (this.#cleanupNestedScrollResizeOptimization) {\n this.#cleanupNestedScrollResizeOptimization();\n }\n break;\n default:\n console.warn(`Unhandled attribute: ${name}`);\n }\n }\n\n disconnectedCallback() {\n this.#cleanupIntersectionObserver?.();\n window.visualViewport?.removeEventListener(\n \"resize\",\n this.#handleViewportResize,\n );\n }\n}\n\nexport interface BottomSheetHTMLAttributes {\n /**\n * When set, the bottom sheet maximum height is based on the the height of its\n * contents.\n */\n [\"content-height\"]?: boolean;\n /**\n * When set, enables scrolling the sheet inner content independently of the sheet.\n */\n [\"nested-scroll\"]?: boolean;\n /**\n * When set, enables resize optimization for the nested scroll mode to avoid reflows\n * during sheet resizing. Only relevant when `nested-scroll` is also true. Not relevant\n * for `expand-to-scroll` mode since it already avoids reflows.\n */\n [\"nested-scroll-optimization\"]?: boolean;\n /**\n * When set, content becomes scrollable only after full expansion. Only relevant\n * when `nested-scroll` is also true.\n */\n [\"expand-to-scroll\"]?: boolean;\n /**\n * When set, allows swiping down to dismiss the bottom sheet when used together\n * with the `bottom-sheet-dialog-manager` or with the Popover API.\n */\n [\"swipe-to-dismiss\"]?: boolean;\n}\n\n/**\n * Represents the current state of the bottom sheet.\n * - `collapsed`: Sheet is fully collapsed (closed/minimized)\n * - `partially-expanded`: Sheet is at an intermediate snap point\n * - `expanded`: Sheet is fully expanded to its maximum height\n */\nexport type SheetState = \"collapsed\" | \"partially-expanded\" | \"expanded\";\n\n/**\n * Detail object for the `snap-position-change` custom event.\n */\nexport interface SnapPositionChangeEventDetail {\n /** The semantic state of the sheet */\n sheetState: SheetState;\n /**\n * The index of the current snap point (0 = collapsed,\n * higher values = more expanded, with the highest being fully expanded)\n */\n snapIndex: number;\n}\n\nexport type BottomSheetEvents = {\n \"snap-position-change\": CustomEvent<SnapPositionChangeEventDetail>;\n};\n"],"names":[],"mappings":";;AAqBA;;;;;;;;;;AAUG;AACG,MAAO,WAAY,SAAQ,WAAW,CAAA;AAC1C,IAAA,OAAO,kBAAkB,GAAG,CAAC,4BAA4B,CAAC;IAC1D,qBAAqB,GAAG,MAAK;AAC3B,QAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CACpB,sBAAsB,EACtB,CAAA,EAAG,MAAM,CAAC,cAAc,EAAE,SAAS,IAAI,CAAC,CAAA,EAAA,CAAI,CAC7C;AACH,IAAA,CAAC;AACD,IAAA,OAAO;IACP,4BAA4B,GAAwB,IAAI;IACxD,sCAAsC,GAAwB,IAAI;IAClE,iBAAiB,GACf,IAAI;AAEN,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;QAEP,MAAM,mBAAmB,GACvB,WAAW,CAAC,SAAS,CAAC,cAAc,CAAC,iBAAiB,CAAC;AACzD,QAAA,MAAM,SAAS,GAAG,mBAAmB,GAAG,IAAI,CAAC,eAAe,EAAE,GAAG,SAAS;;AAG1E,QAAA,IAAI,MAAM,GAAG,SAAS,EAAE,UAAU;QAClC,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5C,YAAA,MAAM,CAAC,SAAS,GAAG,QAAQ;QAC7B;AACA,QAAA,IAAI,CAAC,OAAO,GAAG,MAAM;AAErB,QAAA,MAAM,wBAAwB,GAAG,oBAAoB,IAAI,MAAM;QAC/D,IAAI,wBAAwB,EAAE;YAC5B,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,uBAAuB,CAAC;QACzE;QAEA,IACE,CAAC,GAAG,CAAC,QAAQ,CACX,+DAA+D,CAChE,EACD;YACA,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC;YACnD,IAAI,CAAC,aAAa,EAAE;QACtB;IACF;IAEA,iBAAiB,GAAA;AACf,QAAA,MAAM,wBAAwB,GAAG,oBAAoB,IAAI,MAAM;QAC/D,IAAI,CAAC,wBAAwB,EAAE;YAC7B,IAAI,CAAC,0BAA0B,EAAE;QACnC;QAEA,MAAM,CAAC,cAAc,EAAE,gBAAgB,CACrC,QAAQ,EACR,IAAI,CAAC,qBAAqB,CAC3B;IACH;IAEA,0BAA0B,GAAA;QACxB,MAAM,QAAQ,GACZ,IAAI,CAAC,OAAO,CAAC,aAAa,CAAkB,mBAAmB,CAAC;QAClE,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CACjD,+BAA+B,CAChC;AAED,QAAA,IAAI,CAAC,QAAQ,IAAI,CAAC,gBAAgB;YAAE;AAEpC,QAAA,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAW;QAC9C,IAAI,kBAAkB,GAAmB,IAAI;QAE7C,MAAM,qBAAqB,GAAG,CAAC,KAAgC,KAC7D,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;QAExE,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CACvC,CAAC,OAAO,KAAI;;;YAGV;iBACG,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,cAAc;AACtC,iBAAA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;gBACb,OAAO,qBAAqB,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;AAC5D,YAAA,CAAC;AACA,iBAAA,OAAO,CAAC,CAAC,KAAK,KAAI;AACjB,gBAAA,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;AACvC,YAAA,CAAC,CAAC;YAEJ;iBACG,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,cAAc;AACvC,iBAAA,OAAO,CAAC,CAAC,KAAK,KAAI;AACjB,gBAAA,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;AAC1C,YAAA,CAAC,CAAC;;YAGJ,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE;gBACnC;YACF;;YAGA,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAC5D,CAAC,MAAM,KAAK,MAAM,KAAK,gBAAgB,CACxC;AAED,YAAA,IAAI,aAAa,KAAK,kBAAkB,EAAE;gBACxC;YACF;;YAGA,IAAI,CAAC,aAAa,EAAE;AAClB,gBAAA,IACE,mBAAmB,CAAC,GAAG,CAAC,gBAAgB,CAAC;AACzC,oBAAA,CAAC,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,EACtC;oBACA;gBACF;;gBAEA,kBAAkB,GAAG,gBAAgB;AACrC,gBAAA,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC;gBAC1C;YACF;YAEA,kBAAkB,GAAG,aAAa;AAClC,YAAA,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC;AACzC,QAAA,CAAC,EACD;AACE,YAAA,IAAI,EAAE,IAAI;AACV,YAAA,UAAU,EAAE,qBAAqB;AAClC,SAAA,CACF;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,WAAW,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAI;AACzC,YAAA,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;AAC5B,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,kBAAkB,GAAG,IAAI,GAAG,EAAW;QAC3C,MAAM,iBAAiB,GAAG,MAAK;YAC7B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,gBAAgB,EAAE,CAAC;;AAGvD,YAAA,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,KAAI;gBAChC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;AACvB,oBAAA,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACxB;AACF,YAAA,CAAC,CAAC;AAEF,YAAA,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAEhD,kBAAkB,GAAG,UAAU;AACjC,QAAA,CAAC;AACD,QAAA,QAAQ,CAAC,gBAAgB,CAAC,YAAY,EAAE,iBAAiB,CAAC;AAC1D,QAAA,iBAAiB,EAAE;AAEnB,QAAA,IAAI,CAAC,4BAA4B,GAAG,MAAK;AACvC,YAAA,QAAQ,CAAC,mBAAmB,CAAC,YAAY,EAAE,iBAAiB,CAAC;YAC7D,QAAQ,CAAC,UAAU,EAAE;AACrB,YAAA,IAAI,CAAC,4BAA4B,GAAG,IAAI;AAC1C,QAAA,CAAC;IACH;AAEA,IAAA,uBAAuB,CAAC,KAAY,EAAA;QAClC,MAAM,SAAS,GAAG,KAAkB;QACpC,IAAI,EAAE,SAAS,CAAC,eAAe,YAAY,WAAW,CAAC,EAAE;YACvD;QACF;AACA,QAAA,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,eAAe,CAAC;IACrD;AAEA,IAAA,mBAAmB,CAAC,aAAsB,EAAA;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC;AACzD,QAAA,IAAI,CAAC,SAAS;YAAE;AAEhB,QAAA,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,SAAS;AAE3C,QAAA,IACE,IAAI,CAAC,iBAAiB,EAAE,SAAS,KAAK,SAAS;AAC/C,YAAA,IAAI,CAAC,iBAAiB,EAAE,UAAU,KAAK,UAAU,EACjD;YACA;QACF;AAEA,QAAA,IAAI,CAAC,iBAAiB,GAAG,SAAS;AAClC,QAAA,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,UAAU;AAEpC,QAAA,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAgC,sBAAsB,EAAE;AACrE,YAAA,MAAM,EAAE,SAAS;AACjB,YAAA,OAAO,EAAE,IAAI;AACb,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA,CAAC,CACH;IACH;AAEA,IAAA,mBAAmB,CACjB,UAAmB,EAAA;QAEnB,MAAM,QAAQ,GACZ,IAAI,CAAC,OAAO,CAAC,aAAa,CAAkB,mBAAmB,CAAC;AAClE,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,IAAI;AAE1B,QAAA,MAAM,kBAAkB,GAAG,QAAQ,CAAC,gBAAgB,EAAE;AAEtD,QAAA,MAAM,eAAe,GACnB,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK;;QAG9D,IACE,UAAU,YAAY,WAAW;AACjC,YAAA,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EACpC;YACA,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE;QAClD;;AAGA,QAAA,IAAI,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE;YACvC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC;AACvD,YAAA,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,GAAG,QAAQ;YACtD,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;YAC3D,MAAM,UAAU,GAAG,cAAc,GAAG,UAAU,GAAG,oBAAoB;AACrE,YAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE;QAClC;;AAGA,QAAA,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,IAAI,eAAe,GAAG,CAAC,GAAG,CAAC,CAAC;AACvE,QAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE;IAC9C;IAEA,aAAa,GAAA;AACX,QAAA,IAAI,CAAC;aACF,aAAa,CAAc,gBAAgB;AAC5C,cAAE,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAA,EAAG,IAAI,CAAC,SAAS,CAAA,EAAA,CAAI,CAAC;IAClE;IAEA,oCAAoC,GAAA;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAc,gBAAgB,CAAC;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAc,QAAQ,CAAC;QAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAc,gBAAgB,CAAC;QAEzE,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE;YAClC;QACF;QAEA,MAAM,qBAAqB,GAAG,GAAG;QACjC,MAAM,wBAAwB,GAAG,GAAG,CAAC,QAAQ,CAC3C,qCAAqC,CACtC;;;;AAID,QAAA,MAAM,gBAAgB,GACpB,aAAa,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;AACrE,QAAA,IAAI,mBAA2B,EAC7B,iBAAyB,EACzB,aAAqB;;;QAIvB,MAAM,oBAAoB,GAAG,MAAK;AAChC,YAAA,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;AAClE,YAAA,MAAM,iBAAiB,GACrB,CAAC,CAAC,GAAG,CAAC,IAAI,mBAAmB,GAAG,CAAC,GAAG,iBAAiB;YACvD,OAAO,CAAC,KAAK,CAAC,WAAW,CACvB,wBAAwB,EACxB,CAAA,EAAG,iBAAiB,CAAA,EAAA,CAAI,CACzB;AACH,QAAA,CAAC;QAED,MAAM,sBAAsB,GAAG,MAAK;AAClC,YAAA,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS;AAC7B,YAAA;gBACE,wBAAwB;gBACxB,8BAA8B;gBAC9B,4BAA4B;AAC7B,aAAA,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;AACnD,QAAA,CAAC;QAED,MAAM,sBAAsB,GAAG,MAAK;YAClC,OAAO,CAAC,KAAK,CAAC,WAAW,CACvB,8BAA8B,EAC9B,CAAA,EAAG,mBAAmB,CAAA,EAAA,CAAI,CAC3B;YACD,OAAO,CAAC,KAAK,CAAC,WAAW,CACvB,4BAA4B,EAC5B,CAAA,EAAG,iBAAiB,CAAA,EAAA,CAAI,CACzB;AACH,QAAA,CAAC;QAED,MAAM,eAAe,GAAG,MAAK;AAC3B,YAAA,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC;AACrD,YAAA,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG;AAC3B,YAAA,sBAAsB,EAAE;;;;;;YAMxB,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,CAAA,CAAA,CAAG;YAC3F,OAAO,CAAC,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,SAAS;;AAE/C,YAAA,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE;AACzB,QAAA,CAAC;QAED,MAAM,YAAY,GAAG,MAAK;YACxB,IAAI,EAAE,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE;AAClC,gBAAA,MAAM,sBAAsB,GAC1B,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;;;AAGpE,gBAAA,MAAM,2BAA2B,GAC/B,OAAO,CAAC,YAAY,GAAG,sBAAsB;gBAC/C,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,2BAA2B,EAAE;AAC/D,oBAAA,MAAM,mBAAmB,GACvB,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,SAAS;;AAGjE,oBAAA,MAAM,eAAe,GACnB,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;oBAEpE,mBAAmB;wBACjB,EACE,OAAO,CAAC,YAAY;6BACnB,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAC5C;4BACD,mBAAmB,GAAG,eAAe;oBACvC,iBAAiB,GAAG,CAAC,2BAA2B;;AAGhD,oBAAA,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,EAAE;;;;AAK3B,oBAAA,mBAAmB,IAAI,OAAO,CAAC,SAAS;AACxC,oBAAA,iBAAiB,IAAI,OAAO,CAAC,SAAS;AAEtC,oBAAA,sBAAsB,EAAE;gBAC1B;qBAAO;oBACL,mBAAmB,GAAG,CAAC;oBACvB,iBAAiB,GAAG,CAAC;AACrB,oBAAA,sBAAsB,EAAE;AACxB,oBAAA,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,EAAE;gBAC7B;gBAEA,IAAI,CAAC,wBAAwB,EAAE;AAC7B,oBAAA,oBAAoB,EAAE;gBACxB;AAEA,gBAAA,IAAI,gBAAgB,IAAI,MAAM,EAAE;;;AAG9B,oBAAA,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC;AAClC,wBAAA,MAAM,EAAE,IAAI;AACZ,wBAAA,IAAI,EAAE,GAAG;AACV,qBAAA,CAAC;AACF,oBAAA,OAAO,CAAC,KAAK,CAAC,WAAW,CACvB,kCAAkC,EAClC,CAAA,EAAG,QAAQ,CAAC,WAAW,CAAA,CAAE,CAC1B;gBACH;YACF;iBAAO,IAAI,CAAC,wBAAwB,EAAE;AACpC,gBAAA,oBAAoB,EAAE;YACxB;AACF,QAAA,CAAC;QAED,MAAM,uBAAuB,GAAG,MAAK;AACnC,YAAA,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC;YAClC,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,eAAe,EAAE,qBAAqB,CAAC;AAC3E,QAAA,CAAC;AAED,QAAA,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC;QAC7C,IAAI,gBAAgB,EAAE;AACpB,YAAA,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,eAAe,CAAC;QACrD;aAAO;AACL,YAAA,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,uBAAuB,CAAC;QAC1D;AAEA,QAAA,IAAI,CAAC,sCAAsC,GAAG,MAAK;AACjD,YAAA,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC;YAChD,IAAI,gBAAgB,EAAE;AACpB,gBAAA,IAAI,CAAC,mBAAmB,CAAC,WAAW,EAAE,eAAe,CAAC;YACxD;iBAAO;AACL,gBAAA,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,uBAAuB,CAAC;AAC3D,gBAAA,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC;YACpC;AAEA,YAAA,sBAAsB,EAAE;AACxB,YAAA,IAAI,CAAC,sCAAsC,GAAG,IAAI;AACpD,QAAA,CAAC;IACH;AAEA,IAAA,wBAAwB,CACtB,IAAY,EACZ,QAAuB,EACvB,QAAuB,EAAA;QAEvB,IAAI,QAAQ,KAAK,QAAQ;YAAE;QAE3B,QAAQ,IAAI;AACV,YAAA,KAAK,4BAA4B;AAC/B,gBAAA,IAAI,QAAQ,KAAK,IAAI,EAAE;AACrB,oBAAA,IAAI,CAAC,IAAI,CAAC,sCAAsC,EAAE;;wBAEhD,IAAI,CAAC,oCAAoC,EAAE;oBAC7C;gBACF;AAAO,qBAAA,IAAI,IAAI,CAAC,sCAAsC,EAAE;oBACtD,IAAI,CAAC,sCAAsC,EAAE;gBAC/C;gBACA;AACF,YAAA;AACE,gBAAA,OAAO,CAAC,IAAI,CAAC,wBAAwB,IAAI,CAAA,CAAE,CAAC;;IAElD;IAEA,oBAAoB,GAAA;AAClB,QAAA,IAAI,CAAC,4BAA4B,IAAI;QACrC,MAAM,CAAC,cAAc,EAAE,mBAAmB,CACxC,QAAQ,EACR,IAAI,CAAC,qBAAqB,CAC3B;IACH;;;;;"}
1
+ {"version":3,"file":"bottom-sheet.js","sources":["../../../../src/web/bottom-sheet.ts"],"sourcesContent":["import { template } from \"./bottom-sheet.template\";\n\n/**\n * @see https://drafts.csswg.org/scroll-animations/#scrolltimeline-interface\n */\ninterface ScrollTimeline extends AnimationTimeline {\n readonly source: Element | null;\n readonly axis: string;\n}\ndeclare var ScrollTimeline: {\n prototype: ScrollTimeline;\n new ({ source, axis }: { source: Element; axis: string }): ScrollTimeline;\n};\n\n/**\n * BottomSheet custom element.\n *\n * @example\n * // Register in TypeScript for proper type checking:\n * declare global {\n * interface HTMLElementTagNameMap {\n * \"bottom-sheet\": BottomSheet;\n * }\n * }\n */\nexport class BottomSheet extends HTMLElement {\n static observedAttributes = [\"nested-scroll-optimization\", \"content-height\"];\n #handleViewportResize = () => {\n this.style.setProperty(\n \"--sw-keyboard-height\",\n `${window.visualViewport?.offsetTop ?? 0}px`,\n );\n };\n #shadow: ShadowRoot;\n #cleanupIntersectionObserver: (() => void) | null = null;\n #cleanupSheetSizeObserver: (() => void) | null = null;\n #cleanupNestedScrollResizeOptimization: (() => void) | null = null;\n #currentSnapState: {\n snapIndex: number;\n sheetState: SheetState;\n snapTarget: Element;\n } | null = null;\n\n constructor() {\n super();\n\n const supportsDeclarative =\n HTMLElement.prototype.hasOwnProperty(\"attachInternals\");\n const internals = supportsDeclarative ? this.attachInternals() : undefined;\n\n // Use existing declarative shadow root if present, otherwise create one\n let shadow = internals?.shadowRoot;\n if (!shadow) {\n shadow = this.attachShadow({ mode: \"open\" });\n shadow.innerHTML = template;\n }\n this.#shadow = shadow;\n\n if (\n !CSS.supports(\n \"(animation-timeline: scroll()) and (animation-range: 0% 100%)\",\n )\n ) {\n this.addEventListener(\"scroll\", this.#handleScroll);\n this.#handleScroll();\n }\n }\n\n connectedCallback() {\n this.#setupIntersectionObserver();\n\n window.visualViewport?.addEventListener(\n \"resize\",\n this.#handleViewportResize,\n );\n }\n\n #setupIntersectionObserver() {\n const snapSlot =\n this.#shadow.querySelector<HTMLSlotElement>('slot[name=\"snap\"]');\n const bottomSnapTarget = this.#shadow.querySelector(\n '.sentinel[data-snap=\"bottom\"]',\n );\n\n if (!snapSlot || !bottomSnapTarget) return;\n\n const contentHeightTarget = this.#shadow.querySelector(\n '.sentinel[data-snap=\"content-height\"]',\n );\n\n const intersectingTargets = new Set<Element>();\n let previousSnapTarget: Element | null = null;\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n intersectingTargets.add(entry.target);\n } else {\n intersectingTargets.delete(entry.target);\n }\n });\n\n // Skip when the root has no dimensions (e.g., inside a closed dialog)\n if (!entries[0]?.rootBounds?.height) {\n return;\n }\n\n // Pick the intersecting target closest to the snap line (host's top edge).\n // Skip bottom snap target (handled separately for collapsed state detection).\n const currentTarget = Array.from(intersectingTargets)\n .filter((target) => target !== bottomSnapTarget)\n .sort(\n (a, b) =>\n a.getBoundingClientRect().top - b.getBoundingClientRect().top,\n )\n .at(-1);\n\n if (\n currentTarget === previousSnapTarget &&\n // Never skip the \"content-height\" target even if it was the previous snap\n // target, because the computed snap index may be different if the content\n // height has changed since the last intersection update.\n currentTarget !== contentHeightTarget\n ) {\n return;\n }\n\n // No snap target within the bounds -> collapsed if the bottom sentinel has\n // also exited (scrollTop at 0) and swipe-to-dismiss is enabled.\n if (!currentTarget) {\n if (\n intersectingTargets.has(bottomSnapTarget) ||\n this.scrollTop > 1 ||\n !this.hasAttribute(\"swipe-to-dismiss\")\n ) {\n return;\n }\n previousSnapTarget = bottomSnapTarget;\n this.#updateSnapPosition(bottomSnapTarget);\n return;\n }\n\n previousSnapTarget = currentTarget;\n this.#updateSnapPosition(currentTarget);\n },\n {\n root: this,\n rootMargin: \"100% 0px -100% 0px\",\n },\n );\n\n const sentinels = this.#shadow.querySelectorAll(\".sentinel\");\n Array.from(sentinels).forEach((sentinel) => {\n observer.observe(sentinel);\n });\n\n let observedSnapPoints = new Set<Element>();\n const observeSnapPoints = () => {\n const snapPoints = new Set(snapSlot.assignedElements());\n\n // Unobserve elements no longer assigned to the slot\n observedSnapPoints.forEach((el) => {\n if (!snapPoints.has(el)) {\n observer.unobserve(el);\n intersectingTargets.delete(el);\n }\n });\n\n snapPoints.forEach((el) => observer.observe(el));\n\n observedSnapPoints = snapPoints;\n };\n snapSlot.addEventListener(\"slotchange\", observeSnapPoints);\n observeSnapPoints();\n\n this.#cleanupIntersectionObserver = () => {\n snapSlot.removeEventListener(\"slotchange\", observeSnapPoints);\n observer.disconnect();\n this.#cleanupIntersectionObserver = null;\n };\n }\n\n #updateSnapPosition(newSnapTarget: Element) {\n const snapState = this.#calculateSnapState(newSnapTarget);\n if (!snapState) return;\n\n const { snapIndex, sheetState } = snapState;\n\n if (\n this.#currentSnapState?.snapIndex === snapIndex &&\n this.#currentSnapState?.sheetState === sheetState\n ) {\n return;\n }\n\n this.#currentSnapState = { ...snapState, snapTarget: newSnapTarget };\n this.dataset.sheetState = sheetState;\n\n this.dispatchEvent(\n new CustomEvent<SnapPositionChangeEventDetail>(\"snap-position-change\", {\n detail: snapState,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n #calculateSnapState(\n snapTarget: Element,\n ): { snapIndex: number; sheetState: SheetState } | null {\n if (\n snapTarget instanceof HTMLElement &&\n snapTarget.dataset.snap === \"bottom\"\n ) {\n return { snapIndex: 0, sheetState: \"collapsed\" };\n }\n\n const snapSlot =\n this.#shadow.querySelector<HTMLSlotElement>('slot[name=\"snap\"]');\n if (!snapSlot) return null;\n\n // Reverse to bottom-to-top order so array index maps to snap index (i + 1)\n const assignedSnapPoints = snapSlot.assignedElements().reverse();\n\n const hasTopSnapPoint =\n assignedSnapPoints.at(-1)?.classList.contains(\"top\") ?? false;\n\n const maxExpandedIndex =\n assignedSnapPoints.length + (hasTopSnapPoint ? 0 : 1);\n\n // When content-height is set, the topmost reachable snap index may be\n // lower than maxExpandedIndex (limited by how tall the content is).\n let topmostReachableIndex = maxExpandedIndex;\n if (this.hasAttribute(\"content-height\")) {\n const maxSheetHeight = Math.min(\n this.offsetHeight,\n this.scrollHeight - this.offsetHeight,\n );\n let minHeightGap = Infinity;\n\n for (let i = 0; i < assignedSnapPoints.length; i++) {\n const el = assignedSnapPoints[i];\n if (!(el instanceof HTMLElement)) continue;\n // Add 1px to account for the -1px offset in CSS\n const snapOffset = el.offsetTop + 1;\n const heightGap = snapOffset - maxSheetHeight;\n if (heightGap >= 0 && heightGap < minHeightGap) {\n minHeightGap = heightGap;\n topmostReachableIndex = i + 1;\n }\n }\n\n // When snap target is the content-height sentinel, find the snap index from\n // the current scroll position since the sentinel itself is not a snap point.\n if (\n snapTarget instanceof HTMLElement &&\n snapTarget.dataset.snap === \"content-height\"\n ) {\n let closestSnapIndex = -1;\n let minScrollGap = Infinity;\n for (let i = 0; i < assignedSnapPoints.length; i++) {\n const el = assignedSnapPoints[i];\n if (!(el instanceof HTMLElement)) continue;\n // Add 1px to account for the -1px offset in CSS\n const snapOffset = el.offsetTop + 1;\n const scrollGap = snapOffset - this.scrollTop;\n if (scrollGap >= 0 && scrollGap < minScrollGap) {\n minScrollGap = scrollGap;\n closestSnapIndex = i + 1;\n }\n }\n if (closestSnapIndex !== -1) {\n return {\n snapIndex: closestSnapIndex,\n sheetState:\n closestSnapIndex >= topmostReachableIndex\n ? \"expanded\"\n : \"partially-expanded\",\n };\n }\n }\n }\n\n // Snapped on one of the snap points assigned to the \"snap\" slot\n if (snapTarget.matches('[slot=\"snap\"]')) {\n const snapIndex = assignedSnapPoints.indexOf(snapTarget) + 1;\n return {\n snapIndex,\n sheetState:\n snapIndex >= topmostReachableIndex\n ? \"expanded\"\n : \"partially-expanded\",\n };\n }\n\n // Snapped on the .sheet element or the \"snap\" slot fallback element\n return { snapIndex: maxExpandedIndex, sheetState: \"expanded\" };\n }\n\n #handleScroll() {\n this.#shadow\n .querySelector<HTMLElement>(\".sheet-wrapper\")\n ?.style.setProperty(\"--sheet-position\", `${this.scrollTop}px`);\n }\n\n #setupNestedScrollResizeOptimization() {\n const wrapper = this.#shadow.querySelector<HTMLElement>(\".sheet-wrapper\");\n const sheet = this.#shadow.querySelector<HTMLElement>(\".sheet\");\n const content = this.#shadow.querySelector<HTMLElement>(\".sheet-content\");\n\n if (!wrapper || !sheet || !content) {\n return;\n }\n\n const SCROLL_END_TIMEOUT_MS = 100;\n const supportsScrollAnimations = CSS.supports(\n \"scroll-timeline: --sheet-timeline y\",\n );\n // Use scrollend event if available to detect end of scrolling\n // (exclude Firefox for now which has inconsistencies with scrollend\n // implementation with scroll chaining from nested scrollable elements)\n const supportScrollEnd =\n \"onscrollend\" in window && !CSS.supports(\"-moz-appearance\", \"none\");\n let contentYOffsetStart: number,\n contentYOffsetEnd: number,\n scrollTimeout: number;\n\n // If CSS scroll-timeline is not supported, we need to manually update\n // the y offset of the sheet content during scrolling\n const updateContentYOffset = () => {\n const t = this.scrollTop / (this.scrollHeight - this.offsetHeight);\n const contentTranslateY =\n (1 - t) * contentYOffsetStart + t * contentYOffsetEnd;\n wrapper.style.setProperty(\n \"--sheet-content-offset\",\n `${contentTranslateY}px`,\n );\n };\n\n const cleanupStyleProperties = () => {\n delete this.dataset.scrolling;\n [\n \"--sheet-content-offset\",\n \"--sheet-content-offset-start\",\n \"--sheet-content-offset-end\",\n ].forEach((p) => wrapper.style.removeProperty(p));\n };\n\n const updateOffsetProperties = () => {\n wrapper.style.setProperty(\n \"--sheet-content-offset-start\",\n `${contentYOffsetStart}px`,\n );\n wrapper.style.setProperty(\n \"--sheet-content-offset-end\",\n `${contentYOffsetEnd}px`,\n );\n };\n\n const handleScrollEnd = () => {\n const style = getComputedStyle(content);\n const matrix = new DOMMatrixReadOnly(style.transform);\n const yOffset = -matrix.m42;\n cleanupStyleProperties();\n // Pin the sheet height inline so the content scrollTop update below is based\n // on the correct scroll height. Removing [data-scrolling] reactivates the\n // scroll-timeline-driven \"expand-sheet-height\" animation, which will not\n // produce a current time until the next layout pass, leaving the sheet height\n // (and content scroll height) stale for the scrollTop assignment that follows.\n sheet.style.height = `${(this.scrollTop / (this.scrollHeight - this.offsetHeight)) * 100}%`;\n content.scrollTop = yOffset + content.scrollTop;\n // Clear the temporary inline override now that the scrollTop is set\n sheet.style.height = \"\";\n };\n\n const handleScroll = () => {\n if (!(\"scrolling\" in this.dataset)) {\n const contentMaxOffsetHeight =\n wrapper.offsetHeight - (sheet.offsetHeight - content.offsetHeight);\n // Threshold after which resizing sheet content anchors the inner\n // content to the bottom of the scrollport instead of top\n const scrollBottomAnchorThreshold =\n content.scrollHeight - contentMaxOffsetHeight;\n if (Math.floor(content.scrollTop) > scrollBottomAnchorThreshold) {\n const contentScrollBottom =\n content.scrollHeight - content.offsetHeight - content.scrollTop;\n\n // Size factor based on the current height of the bottom sheet\n const sheetSizeFactor =\n wrapper.offsetHeight / (wrapper.offsetHeight - sheet.offsetHeight);\n\n contentYOffsetStart =\n -(\n content.scrollHeight +\n (sheet.offsetHeight - content.offsetHeight)\n ) +\n contentScrollBottom * sheetSizeFactor;\n contentYOffsetEnd = -scrollBottomAnchorThreshold;\n\n // Toggle scrolling state on\n this.dataset.scrolling = \"\";\n\n // Readjust sheet content offset range based on current scroll position\n // of the sheet content after the layout change caused by toggling the\n // data-scrolling attribute\n contentYOffsetStart += content.scrollTop;\n contentYOffsetEnd += content.scrollTop;\n\n updateOffsetProperties();\n } else {\n contentYOffsetStart = 0;\n contentYOffsetEnd = 0;\n updateOffsetProperties();\n this.dataset.scrolling = \"\";\n }\n\n if (!supportsScrollAnimations) {\n updateContentYOffset();\n }\n\n if (\"ScrollTimeline\" in window) {\n // Needed for Safari 26+ to prevent flash of sheet position when toggling data-scrolling\n // before the scroll timeline is applied by the browser\n const timeline = new ScrollTimeline({\n source: this,\n axis: \"y\",\n });\n wrapper.style.setProperty(\n \"--sheet-timeline-at-scroll-start\",\n `${timeline.currentTime}`,\n );\n }\n } else if (!supportsScrollAnimations) {\n updateContentYOffset();\n }\n };\n\n const handleFallbackScrollEnd = () => {\n window.clearTimeout(scrollTimeout);\n scrollTimeout = window.setTimeout(handleScrollEnd, SCROLL_END_TIMEOUT_MS);\n };\n\n this.addEventListener(\"scroll\", handleScroll);\n if (supportScrollEnd) {\n this.addEventListener(\"scrollend\", handleScrollEnd);\n } else {\n this.addEventListener(\"scroll\", handleFallbackScrollEnd);\n }\n\n this.#cleanupNestedScrollResizeOptimization = () => {\n this.removeEventListener(\"scroll\", handleScroll);\n if (supportScrollEnd) {\n this.removeEventListener(\"scrollend\", handleScrollEnd);\n } else {\n this.removeEventListener(\"scroll\", handleFallbackScrollEnd);\n window.clearTimeout(scrollTimeout);\n }\n\n cleanupStyleProperties();\n this.#cleanupNestedScrollResizeOptimization = null;\n };\n }\n\n #setupSheetSizeObserver() {\n // Observe sheet size changes to re-dispatch the snap-position-change event\n // in case the height change results in a different snap point being the closest\n // one to the sheet top.\n let previousBlockSize = 0;\n const resizeObserver = new ResizeObserver((e) => {\n const blockSize = e.at(0)?.contentBoxSize.at(0)?.blockSize ?? 0;\n if (!blockSize || blockSize === previousBlockSize) return;\n previousBlockSize = blockSize;\n if (this.#currentSnapState) {\n this.#updateSnapPosition(this.#currentSnapState.snapTarget);\n }\n });\n\n const sheet = this.#shadow.querySelector<HTMLElement>(\".sheet\");\n if (sheet) {\n resizeObserver.observe(sheet);\n }\n\n this.#cleanupSheetSizeObserver = () => {\n resizeObserver.disconnect();\n this.#cleanupSheetSizeObserver = null;\n };\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) return;\n\n switch (name) {\n case \"nested-scroll-optimization\":\n if (newValue !== null) {\n if (!this.#cleanupNestedScrollResizeOptimization) {\n // Only setup if not already setup\n this.#setupNestedScrollResizeOptimization();\n }\n } else if (this.#cleanupNestedScrollResizeOptimization) {\n this.#cleanupNestedScrollResizeOptimization();\n }\n break;\n case \"content-height\":\n if (newValue !== null) {\n if (!this.#cleanupSheetSizeObserver) {\n // Only setup if not already setup\n this.#setupSheetSizeObserver();\n }\n } else if (this.#cleanupSheetSizeObserver) {\n this.#cleanupSheetSizeObserver();\n }\n break;\n default:\n console.warn(`Unhandled attribute: ${name}`);\n }\n }\n\n disconnectedCallback() {\n this.#cleanupIntersectionObserver?.();\n window.visualViewport?.removeEventListener(\n \"resize\",\n this.#handleViewportResize,\n );\n }\n}\n\nexport interface BottomSheetHTMLAttributes {\n /**\n * When set, the bottom sheet maximum height is based on the the height of its\n * contents.\n */\n [\"content-height\"]?: boolean;\n /**\n * When set, enables scrolling the sheet inner content independently of the sheet.\n */\n [\"nested-scroll\"]?: boolean;\n /**\n * When set, enables resize optimization for the nested scroll mode to avoid reflows\n * during sheet resizing. Only relevant when `nested-scroll` is also true. Not relevant\n * for `expand-to-scroll` mode since it already avoids reflows.\n */\n [\"nested-scroll-optimization\"]?: boolean;\n /**\n * When set, content becomes scrollable only after full expansion. Only relevant\n * when `nested-scroll` is also true.\n */\n [\"expand-to-scroll\"]?: boolean;\n /**\n * When set, allows swiping down to dismiss the bottom sheet when used together\n * with the `bottom-sheet-dialog-manager` or with the Popover API.\n */\n [\"swipe-to-dismiss\"]?: boolean;\n}\n\n/**\n * Represents the current state of the bottom sheet.\n * - `collapsed`: Sheet is fully collapsed (closed/minimized)\n * - `partially-expanded`: Sheet is at an intermediate snap point\n * - `expanded`: Sheet is fully expanded to its maximum height\n */\nexport type SheetState = \"collapsed\" | \"partially-expanded\" | \"expanded\";\n\n/**\n * Detail object for the `snap-position-change` custom event.\n */\nexport interface SnapPositionChangeEventDetail {\n /** The semantic state of the sheet */\n sheetState: SheetState;\n /**\n * The index of the current snap point (0 = collapsed,\n * higher values = more expanded, with the highest being fully expanded)\n */\n snapIndex: number;\n}\n\nexport type BottomSheetEvents = {\n \"snap-position-change\": CustomEvent<SnapPositionChangeEventDetail>;\n};\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  const styles = /*css*/`:host{--sheet-max-height:calc(100dvh - 24px);--sheet-background:#f2f2f2;--sheet-border-radius:12px;--sheet-safe-max-height:calc(var(--sheet-max-height) - env(
3
3
  keyboard-inset-height,
4
4
  var(--sw-keyboard-height, 0px)
5
- ));border-top-left-radius:var(--sheet-border-radius);border-top-right-radius:var(--sheet-border-radius);bottom:env(keyboard-inset-height,0);contain:strict;display:block;height:var(--sheet-max-height);left:0;max-height:var(--sheet-safe-max-height);overflow-y:scroll;overscroll-behavior-y:none;pointer-events:none;position:fixed;right:0;scroll-snap-type:y mandatory;scrollbar-width:none;will-change:scroll-position}:host(:focus-visible){outline:none}:host(:focus-visible) .handle{outline:auto;outline-offset:4px}.snap,::slotted([slot=snap]){height:1px;margin-bottom:-1px;position:relative;top:calc(var(--snap) - 1px)}.snap:before,::slotted([slot=snap]):before{content:"";height:1px;left:0;position:absolute;right:0;scroll-snap-align:var(--snap-point-align,start);top:1px}.snap.initial,::slotted([slot=snap].initial){--snap-point-align:start}.snap.snap-bottom{height:auto;margin-bottom:0;position:static;top:auto}.snap.snap-bottom:before{top:0}.snap.snap-bottom:after{content:"";display:block;height:var(--sheet-max-height);max-height:var(--sheet-safe-max-height);position:static}:host(:not([swipe-to-dismiss])) .snap.snap-bottom:before{scroll-snap-align:none}.sentinel{position:relative}.sentinel[data-snap=top]{top:-1px}.sentinel[data-snap=bottom]{top:1px}.sheet,.sheet-wrapper{border-radius:inherit}.sheet{background:var(--sheet-background);cursor:row-resize;display:flex;flex-direction:column;overflow:clip;pointer-events:all;scroll-snap-align:var(--snap-point-align,start)}:host(:not([nested-scroll])) .sheet:after{content:"";display:block;position:static;scroll-snap-align:var(--snap-point-align,end)}.sheet-header{top:0}.sheet-footer,.sheet-header{background:inherit;position:sticky;width:100%}.sheet-footer{bottom:0}.sheet-content{flex:1 1 auto;padding:0 .5rem}.handle{background:#ccc;border-radius:5px;height:5px;margin:.5rem auto;width:40px}:host{animation:initial-snap var(--initial-snap-duration,.5s) backwards}@supports (-webkit-touch-callout:none) or (-webkit-hyphens:none){:host{display:inherit;--snap-type-initial-reset:none}}@keyframes initial-snap{0%,49.99%{scroll-snap-type:y mandatory;--snap-point-align:none}50%,to{scroll-snap-type:var(--snap-type-initial-reset,y mandatory);--snap-point-align:none}}@supports (-webkit-touch-callout:none){.sheet-content,.sheet-footer,.sheet-header{overflow-x:scroll;overscroll-behavior-x:none;scrollbar-width:none;touch-action:pan-y pinch-zoom}:is(.sheet-content,.sheet-header,.sheet-footer):after{box-sizing:content-box;content:"";display:block;height:1px;padding:inherit;padding-left:0;width:calc(100% + 1px)}.sheet-content{scrollbar-width:auto}}:host(:not([nested-scroll]):not([content-height])) .sheet-wrapper{height:100%}:host(:not([nested-scroll]):not([content-height])) .sheet{min-height:100%}:host([nested-scroll]) .sheet-wrapper{bottom:0;contain:strict;content-visibility:auto;display:flex;flex-direction:column;height:100%;justify-content:end;position:sticky}:host([nested-scroll]) .sheet{bottom:0;display:flex;flex-direction:column;height:100%;max-height:100%;position:sticky}:host([nested-scroll]) .sheet-content{overflow-y:auto;scrollbar-gutter:stable;will-change:scroll-position}:host([nested-scroll]) .sheet-footer,:host([nested-scroll]) .sheet-header{flex:0 0 auto}:host([nested-scroll][expand-to-scroll]) .sheet-wrapper{position:static}:host([nested-scroll][expand-to-scroll]) .sheet{animation:none;height:100%;position:static}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-header{z-index:1}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{scrollbar-color:transparent transparent}@supports ((animation-timeline:scroll()) and (animation-range:0% 100%)){:host([nested-scroll]) .sheet{animation:expand-sheet-height linear forwards;animation-timeline:scroll()}@keyframes expand-sheet-height{0%{height:0}to{height:100%}}:host([nested-scroll][expand-to-scroll]) .sheet-content{animation:overflow-y-toggle linear forwards;animation-timeline:scroll()}@keyframes overflow-y-toggle{0%,99.99%{overflow-y:hidden}to{overflow-y:auto}}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet{animation:translate-sheet linear forwards;animation-timeline:scroll();transform:translateY(var(--sheet-timeline-at-scroll-start,0))}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{animation:translate-sheet-content linear forwards;animation-timeline:scroll()}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-footer{animation:translate-footer linear forwards;animation-timeline:scroll()}@keyframes translate-sheet{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes translate-sheet-content{0%{transform:translateY(var(--sheet-content-offset-start,0))}to{transform:translateY(var(--sheet-content-offset-end,0))}}@keyframes translate-footer{0%{transform:translateY(calc(var(--sheet-safe-max-height)*-1))}to{transform:translateY(0)}}}@supports (not ((animation-timeline:scroll()) and (animation-range:0% 100%))){:host([nested-scroll]) .sheet{height:var(--sheet-position)}:host([nested-scroll][expand-to-scroll]) .sheet-content{overflow-y:hidden}:host([nested-scroll][expand-to-scroll][data-sheet-state=expanded]) .sheet-content{overflow-y:auto}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet{height:100%;transform:translateY(calc(100% - var(--sheet-position, 0)))}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{transform:translateY(var(--sheet-content-offset,0))}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-footer{transform:translateY(calc(var(--sheet-safe-max-height)*-1 + var(--sheet-position, 0)))}}`;
5
+ ));border-top-left-radius:var(--sheet-border-radius);border-top-right-radius:var(--sheet-border-radius);bottom:env(keyboard-inset-height,0);contain:strict;display:block;height:var(--sheet-max-height);left:0;max-height:var(--sheet-safe-max-height);overflow-y:scroll;overscroll-behavior-y:none;pointer-events:none;position:fixed;right:0;scroll-snap-type:y mandatory;scrollbar-width:none;will-change:scroll-position}:host(:focus-visible){outline:none}:host(:focus-visible) .handle{outline:auto;outline-offset:4px}.snap,::slotted([slot=snap]){height:1px;margin-bottom:-1px;position:relative;top:calc(var(--snap) - 1px)}.snap:before,::slotted([slot=snap]):before{content:"";height:1px;left:0;position:absolute;right:0;scroll-snap-align:var(--snap-point-align,start);top:1px}.snap.initial,::slotted([slot=snap].initial){--snap-point-align:start}.snap.snap-bottom{height:auto;margin-bottom:0;position:static;top:auto}.snap.snap-bottom:before{top:0}.snap.snap-bottom:after{content:"";display:block;height:var(--sheet-max-height);max-height:var(--sheet-safe-max-height);position:static}:host(:not([swipe-to-dismiss])) .snap.snap-bottom:before{scroll-snap-align:none}.sentinel{position:relative}.sentinel[data-snap=top]{top:-1px}.sentinel[data-snap=bottom]{top:1px}.sentinel[data-snap=content-height]{position:absolute;top:calc((var(--sheet-max-height) - min(100%, var(--sheet-max-height)))*-1 - 1px)}:host(:not([content-height])) .sentinel[data-snap=content-height]{display:none}.sheet,.sheet-wrapper{border-radius:inherit}.sheet{background:var(--sheet-background);cursor:row-resize;display:flex;flex-direction:column;overflow:clip;pointer-events:all;scroll-snap-align:var(--snap-point-align,start)}:host(:not([nested-scroll])) .sheet:after{content:"";display:block;position:static;scroll-snap-align:var(--snap-point-align,end)}.sheet-header{top:0}.sheet-footer,.sheet-header{background:inherit;position:sticky;width:100%}.sheet-footer{bottom:0}.sheet-content{flex:1 1 auto;padding:0 .5rem}.handle{background:#ccc;border-radius:5px;height:5px;margin:.5rem auto;width:40px}:host{animation:initial-snap var(--initial-snap-duration,.5s) backwards}@supports (-webkit-touch-callout:none) or (-webkit-hyphens:none){:host{display:inherit;--snap-type-initial-reset:none}}@keyframes initial-snap{0%,49.99%{scroll-snap-type:y mandatory;--snap-point-align:none}50%,to{scroll-snap-type:var(--snap-type-initial-reset,y mandatory);--snap-point-align:none}}@supports (-webkit-touch-callout:none){.sheet-content,.sheet-footer,.sheet-header{overflow-x:scroll;overscroll-behavior-x:none;scrollbar-width:none;touch-action:pan-y pinch-zoom}:is(.sheet-content,.sheet-header,.sheet-footer):after{box-sizing:content-box;content:"";display:block;height:1px;padding:inherit;padding-left:0;width:calc(100% + 1px)}.sheet-content{scrollbar-width:auto}}:host(:not([nested-scroll]):not([content-height])) .sheet-wrapper{height:100%}:host(:not([nested-scroll]):not([content-height])) .sheet{min-height:100%}:host([content-height]) .sheet-wrapper{position:relative}:host([nested-scroll]) .sheet-wrapper{bottom:0;contain:strict;content-visibility:auto;display:flex;flex-direction:column;height:100%;justify-content:end;position:sticky}:host([nested-scroll]) .sheet{bottom:0;display:flex;flex-direction:column;height:100%;max-height:100%;position:sticky}:host([nested-scroll]) .sheet-content{overflow-y:auto;scrollbar-gutter:stable;will-change:scroll-position}:host([nested-scroll]) .sheet-footer,:host([nested-scroll]) .sheet-header{flex:0 0 auto}:host([nested-scroll][expand-to-scroll]) .sheet-wrapper{position:static}:host([nested-scroll][expand-to-scroll]) .sheet{animation:none;height:100%;position:static}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-header{z-index:1}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{scrollbar-color:transparent transparent}@supports ((animation-timeline:scroll()) and (animation-range:0% 100%)){:host([nested-scroll]) .sheet{animation:expand-sheet-height linear forwards;animation-timeline:scroll()}@keyframes expand-sheet-height{0%{height:0}to{height:100%}}:host([nested-scroll][expand-to-scroll]) .sheet-content{animation:overflow-y-toggle linear forwards;animation-timeline:scroll()}@keyframes overflow-y-toggle{0%,99.99%{overflow-y:hidden}to{overflow-y:auto}}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet{animation:translate-sheet linear forwards;animation-timeline:scroll();transform:translateY(var(--sheet-timeline-at-scroll-start,0))}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{animation:translate-sheet-content linear forwards;animation-timeline:scroll()}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-footer{animation:translate-footer linear forwards;animation-timeline:scroll()}@keyframes translate-sheet{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes translate-sheet-content{0%{transform:translateY(var(--sheet-content-offset-start,0))}to{transform:translateY(var(--sheet-content-offset-end,0))}}@keyframes translate-footer{0%{transform:translateY(calc(var(--sheet-safe-max-height)*-1))}to{transform:translateY(0)}}}@supports (not ((animation-timeline:scroll()) and (animation-range:0% 100%))){:host([nested-scroll]) .sheet{height:var(--sheet-position)}:host([nested-scroll][expand-to-scroll]) .sheet-content{overflow-y:hidden}:host([nested-scroll][expand-to-scroll][data-sheet-state=expanded]) .sheet-content{overflow-y:auto}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet{height:100%;transform:translateY(calc(100% - var(--sheet-position, 0)))}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{transform:translateY(var(--sheet-content-offset,0))}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-footer{transform:translateY(calc(var(--sheet-safe-max-height)*-1 + var(--sheet-position, 0)))}}`;
6
6
  const template = /* HTML */ `
7
7
  <style>
8
8
  ${styles}
@@ -14,6 +14,7 @@ const template = /* HTML */ `
14
14
  <div class="snap snap-bottom" data-snap="bottom"></div>
15
15
  <div class="sentinel" data-snap="top"></div>
16
16
  <div class="sheet-wrapper">
17
+ <div class="sentinel" data-snap="content-height"></div>
17
18
  <aside class="sheet" part="sheet" data-snap="top">
18
19
  <header class="sheet-header" part="header">
19
20
  <div class="handle" part="handle"></div>
@@ -1 +1 @@
1
- {"version":3,"file":"bottom-sheet.template.js","sources":["../../../../src/web/bottom-sheet.template.ts"],"sourcesContent":["/* Needed until Prettier supports identifying embedded CSS by block comments */\nconst css = String.raw;\n\nconst styles = css`\n :host {\n --sheet-max-height: calc(100dvh - 24px);\n --sheet-background: rgb(242, 242, 242);\n --sheet-border-radius: 12px;\n /* \n The --sw-keyboard-height is needed to handle iOS Safari on-screen keyboard\n since iOS Safari pushes content offscreen by keyboard height when it is opened\n */\n --sheet-safe-max-height: calc(\n var(--sheet-max-height) - env(\n keyboard-inset-height,\n var(--sw-keyboard-height, 0px)\n )\n );\n display: block;\n position: fixed;\n right: 0;\n bottom: env(keyboard-inset-height, 0);\n left: 0;\n will-change: scroll-position;\n contain: strict;\n border-top-right-radius: var(--sheet-border-radius);\n border-top-left-radius: var(--sheet-border-radius);\n height: var(--sheet-max-height);\n max-height: var(--sheet-safe-max-height);\n overflow-y: scroll;\n /* Prevent rubberband effect when scrolling to the end of the sheet */\n overscroll-behavior-y: none;\n scroll-snap-type: y mandatory;\n scrollbar-width: none;\n pointer-events: none;\n }\n\n :host(:focus-visible) {\n outline: none;\n\n .handle {\n outline: auto;\n outline-offset: 4px;\n }\n }\n\n .snap,\n ::slotted([slot=\"snap\"]) {\n position: relative;\n top: calc(var(--snap) - 1px);\n margin-bottom: -1px; /* Compensate height so it does not affect layout */\n /*\n The bottom sheet uses an IntersectionObserver as a fallback for browsers\n that do not support the native scrollsnapchange event. The snap element\n needs a bounding box that extends above the observer boundary (the scroll\n container's top edge) so that these browsers reliably detect it as\n intersecting when snapped. Without this, e.g., WebKit may report the element\n as not-intersecting when it sits exactly at the boundary.\n */\n height: 1px;\n }\n\n .snap::before,\n ::slotted([slot=\"snap\"])::before {\n position: absolute;\n /* Compensate for the -1px top offset on the parent to keep exact snap position */\n top: 1px;\n right: 0;\n left: 0;\n height: 1px; /* Height required for Safari to snap */\n scroll-snap-align: var(--snap-point-align, start);\n content: \"\";\n }\n\n .snap.initial,\n ::slotted([slot=\"snap\"].initial) {\n --snap-point-align: start;\n }\n\n .snap.snap-bottom {\n position: static;\n top: initial;\n margin-bottom: 0;\n height: auto;\n\n &::before {\n top: 0;\n }\n\n &::after {\n display: block;\n position: static;\n height: var(--sheet-max-height);\n max-height: var(--sheet-safe-max-height);\n content: \"\";\n }\n }\n\n :host(:not([swipe-to-dismiss])) .snap.snap-bottom::before {\n scroll-snap-align: none;\n }\n\n .sentinel {\n position: relative;\n\n &[data-snap=\"top\"] {\n top: -1px; /** Extra -1px needed for Safari */\n }\n &[data-snap=\"bottom\"] {\n top: 1px;\n }\n }\n\n .sheet-wrapper {\n border-radius: inherit;\n }\n\n .sheet {\n display: flex;\n flex-direction: column;\n cursor: row-resize;\n border-radius: inherit;\n background: var(--sheet-background);\n overflow: clip;\n scroll-snap-align: var(--snap-point-align, start);\n pointer-events: all;\n }\n\n /* \n Needed for Safari/Firefox to prevent abrupt scroll re-snapping in case the\n sheet DOM content is dynamically changed. Without this, these browsers would\n re-snap to the start of the .sheet element.\n See related spec: https://drafts.csswg.org/css-scroll-snap-1/#re-snap\n */\n :host(:not([nested-scroll])) .sheet::after {\n display: block;\n position: static;\n scroll-snap-align: var(--snap-point-align, end);\n content: \"\";\n }\n\n .sheet-header {\n position: sticky;\n top: 0;\n background: inherit;\n width: 100%;\n }\n\n .sheet-footer {\n position: sticky;\n bottom: 0;\n background: inherit;\n width: 100%;\n }\n\n .sheet-content {\n flex: 1 1 auto;\n padding: 0 0.5rem;\n }\n\n .handle {\n margin: 0.5rem auto;\n border-radius: 5px;\n background: #ccc;\n width: 40px;\n height: 5px;\n }\n\n :host {\n animation: initial-snap var(--initial-snap-duration, 0.5s) backwards;\n }\n\n /* Safari overrides */\n @supports (-webkit-touch-callout: none) or (-webkit-hyphens: none) {\n :host {\n /* \n On Safari, when displaying in a dialog, we must inherit display property\n so that the animation runs each time the dialog is-reopened\n (display toggles between none and block).\n */\n display: inherit;\n /*\n On Safari, we need to briefly reset scroll-snap-type after the initial snap\n before re-enabling scroll-snap-align on other snap points to prevent scroll\n position from being reset to the initial snap point.\n */\n --snap-type-initial-reset: none;\n }\n }\n\n /*\n Temporarily disables scroll snapping for all snap points\n except the explicitly marked initial snap point (which overrides\n --snap-point-align) so that the sheet snaps to\n the initial snap point.\n */\n @keyframes initial-snap {\n /*\n Phase 1 (0%-49.99%): Disable scroll snapping for all snap points except \n the initial snap point. Keep the host scroll-snap-type at the base value\n (y mandatory) so the browser can snap to the initial snap point during\n this window.\n */\n 0%,\n 49.99% {\n scroll-snap-type: y mandatory;\n --snap-point-align: none;\n }\n /*\n Phase 2 (50%-100%): On Safari, set the host scroll-snap-type temporarily\n to none (via --snap-type-initial-reset) to prevent the scroll position resetting\n when the scroll-snap-align is re-enabled on all snap points after the animation ends.\n See https://stackoverflow.com/q/65653679\n */\n 50%,\n 100% {\n scroll-snap-type: var(--snap-type-initial-reset, y mandatory);\n --snap-point-align: none;\n }\n }\n\n /* Temporary workaround for iOS Safari bug https://bugs.webkit.org/show_bug.cgi?id=183870 */\n @supports (-webkit-touch-callout: none) {\n .sheet-content,\n .sheet-header,\n .sheet-footer {\n overflow-x: scroll;\n /* Prevent rubberband effect */\n overscroll-behavior-x: none;\n scrollbar-width: none;\n /* Prevent horizontal scrolling using touch gestures */\n touch-action: pan-y pinch-zoom;\n\n &::after {\n display: block;\n box-sizing: content-box;\n padding: inherit;\n padding-left: 0;\n width: calc(100% + 1px);\n height: 1px;\n content: \"\";\n }\n }\n .sheet-content {\n scrollbar-width: auto;\n }\n }\n\n :host(:not([nested-scroll]):not([content-height])) {\n .sheet-wrapper {\n height: 100%;\n }\n\n .sheet {\n min-height: 100%;\n }\n }\n\n :host([nested-scroll]) {\n .sheet-wrapper {\n display: flex;\n position: sticky;\n bottom: 0;\n flex-direction: column;\n justify-content: end;\n /* Needed for Firefox to properly render sticky items due to using contain: strict */\n content-visibility: auto;\n /* Fixes scroll-chaining issue on Firefox when sheet content is scrollable */\n contain: strict;\n height: 100%;\n }\n\n .sheet {\n display: flex;\n position: sticky;\n bottom: 0;\n flex-direction: column;\n height: 100%;\n max-height: 100%;\n }\n\n .sheet-content {\n will-change: scroll-position;\n overflow-y: auto;\n scrollbar-gutter: stable;\n }\n\n .sheet-header,\n .sheet-footer {\n /* Prevent shrinking the header and footer */\n flex: 0 0 auto;\n }\n }\n\n /*\n When expand-to-scroll is enabled, we do not need dynamic sheet height animation\n since sheet content is always fixed size and only scrollable at full height\n so we can simplify the layout and improve performance by disabling height animation.\n */\n :host([nested-scroll][expand-to-scroll]) {\n .sheet-wrapper {\n position: static;\n }\n\n .sheet {\n position: static;\n animation: none;\n height: 100%;\n }\n }\n\n /*\n Performance optimization for the nested-scroll mode:\n When the sheet is being actively scrolled, disable sheet height animation and\n switch to transform-based animation for the sheet content to improve performance\n and avoid jank because animating height would cause continuous reflow (layout).\n\n Follow use of [data-scrolling] for the specific CSS rules applied in this case.\n */\n :host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) {\n .sheet-header {\n z-index: 1;\n }\n\n .sheet-content {\n /* Hide the scrollbar visually during scrolling */\n scrollbar-color: transparent transparent;\n }\n }\n\n @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {\n :host([nested-scroll]) .sheet {\n animation: expand-sheet-height linear forwards;\n animation-timeline: scroll();\n }\n\n @keyframes expand-sheet-height {\n from {\n height: 0;\n }\n to {\n height: 100%;\n }\n }\n\n :host([nested-scroll][expand-to-scroll]) .sheet-content {\n animation: overflow-y-toggle linear forwards;\n animation-timeline: scroll();\n }\n\n @keyframes overflow-y-toggle {\n 0%,\n 99.99% {\n overflow-y: hidden;\n }\n 100% {\n overflow-y: auto;\n }\n }\n\n :host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) {\n .sheet {\n /* \n Safari bug fix: Pre-apply transform to prevent flickering. Safari 26+ has\n a one-frame delay when switching from height-based to transform-based animation,\n causing brief position shift. Setting an initial transform value ensures\n smooth transition.\n */\n transform: translateY(var(--sheet-timeline-at-scroll-start, 0));\n animation: translate-sheet linear forwards;\n animation-timeline: scroll();\n }\n\n .sheet-content {\n animation: translate-sheet-content linear forwards;\n animation-timeline: scroll();\n }\n\n .sheet-footer {\n animation: translate-footer linear forwards;\n animation-timeline: scroll();\n }\n }\n\n @keyframes translate-sheet {\n from {\n transform: translateY(100%);\n }\n to {\n transform: translateY(0);\n }\n }\n\n @keyframes translate-sheet-content {\n from {\n transform: translateY(var(--sheet-content-offset-start, 0));\n }\n to {\n transform: translateY(var(--sheet-content-offset-end, 0));\n }\n }\n\n @keyframes translate-footer {\n from {\n transform: translateY(calc(-1 * var(--sheet-safe-max-height)));\n }\n to {\n transform: translateY(0);\n }\n }\n }\n\n /* Fallback for browsers that do not yet support scroll-driven animations */\n @supports (\n not ((animation-timeline: scroll()) and (animation-range: 0% 100%))\n ) {\n :host([nested-scroll]) .sheet {\n height: var(--sheet-position);\n }\n\n :host([nested-scroll][expand-to-scroll]) .sheet-content {\n overflow-y: hidden;\n }\n\n :host([nested-scroll][expand-to-scroll][data-sheet-state=\"expanded\"])\n .sheet-content {\n overflow-y: auto;\n }\n\n :host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) {\n .sheet {\n transform: translateY(calc(100% - var(--sheet-position, 0)));\n height: 100%;\n }\n\n .sheet-content {\n transform: translateY(var(--sheet-content-offset, 0));\n }\n\n .sheet-footer {\n transform: translateY(\n calc(-1 * var(--sheet-safe-max-height) + var(--sheet-position, 0))\n );\n }\n }\n }\n`;\n\nexport const template: string = /* HTML */ `\n <style>\n ${styles}\n </style>\n <slot name=\"snap\">\n <div class=\"snap initial\" style=\"--snap: 100%\"></div>\n </slot>\n <div class=\"sentinel\" data-snap=\"bottom\"></div>\n <div class=\"snap snap-bottom\" data-snap=\"bottom\"></div>\n <div class=\"sentinel\" data-snap=\"top\"></div>\n <div class=\"sheet-wrapper\">\n <aside class=\"sheet\" part=\"sheet\" data-snap=\"top\">\n <header class=\"sheet-header\" part=\"header\">\n <div class=\"handle\" part=\"handle\"></div>\n <slot name=\"header\"></slot>\n </header>\n <section class=\"sheet-content\" part=\"content\">\n <slot></slot>\n </section>\n <footer class=\"sheet-footer\" part=\"footer\">\n <slot name=\"footer\"></slot>\n </footer>\n </aside>\n </div>\n`;\n"],"names":[],"mappings":"AAAA;AAGA,MAAM,MAAM,UAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"bottom-sheet.template.js","sources":["../../../../src/web/bottom-sheet.template.ts"],"sourcesContent":["/* Needed until Prettier supports identifying embedded CSS by block comments */\nconst css = String.raw;\n\nconst styles = css`\n :host {\n --sheet-max-height: calc(100dvh - 24px);\n --sheet-background: rgb(242, 242, 242);\n --sheet-border-radius: 12px;\n /* \n The --sw-keyboard-height is needed to handle iOS Safari on-screen keyboard\n since iOS Safari pushes content offscreen by keyboard height when it is opened\n */\n --sheet-safe-max-height: calc(\n var(--sheet-max-height) - env(\n keyboard-inset-height,\n var(--sw-keyboard-height, 0px)\n )\n );\n display: block;\n position: fixed;\n right: 0;\n bottom: env(keyboard-inset-height, 0);\n left: 0;\n will-change: scroll-position;\n contain: strict;\n border-top-right-radius: var(--sheet-border-radius);\n border-top-left-radius: var(--sheet-border-radius);\n height: var(--sheet-max-height);\n max-height: var(--sheet-safe-max-height);\n overflow-y: scroll;\n /* Prevent rubberband effect when scrolling to the end of the sheet */\n overscroll-behavior-y: none;\n scroll-snap-type: y mandatory;\n scrollbar-width: none;\n pointer-events: none;\n }\n\n :host(:focus-visible) {\n outline: none;\n\n .handle {\n outline: auto;\n outline-offset: 4px;\n }\n }\n\n .snap,\n ::slotted([slot=\"snap\"]) {\n position: relative;\n top: calc(var(--snap) - 1px);\n margin-bottom: -1px; /* Compensate height so it does not affect layout */\n /*\n The bottom sheet uses an IntersectionObserver to dispatch custom snap-position-change\n events. The snap element needs a bounding box that extends above the observer\n boundary (the scroll container's top edge) so that these browsers reliably\n detect it as intersecting when snapped. Without this, e.g., WebKit may report\n the element as not-intersecting when it sits exactly at the boundary.\n */\n height: 1px;\n }\n\n .snap::before,\n ::slotted([slot=\"snap\"])::before {\n position: absolute;\n /* Compensate for the -1px top offset on the parent to keep exact snap position */\n top: 1px;\n right: 0;\n left: 0;\n height: 1px; /* Height required for Safari to snap */\n scroll-snap-align: var(--snap-point-align, start);\n content: \"\";\n }\n\n .snap.initial,\n ::slotted([slot=\"snap\"].initial) {\n --snap-point-align: start;\n }\n\n .snap.snap-bottom {\n position: static;\n top: initial;\n margin-bottom: 0;\n height: auto;\n\n &::before {\n top: 0;\n }\n\n &::after {\n display: block;\n position: static;\n height: var(--sheet-max-height);\n max-height: var(--sheet-safe-max-height);\n content: \"\";\n }\n }\n\n :host(:not([swipe-to-dismiss])) .snap.snap-bottom::before {\n scroll-snap-align: none;\n }\n\n .sentinel {\n position: relative;\n\n &[data-snap=\"top\"] {\n top: -1px; /** Extra -1px needed for Safari */\n }\n &[data-snap=\"bottom\"] {\n top: 1px;\n }\n &[data-snap=\"content-height\"] {\n position: absolute;\n top: calc(\n (var(--sheet-max-height) - min(100%, var(--sheet-max-height))) * -1 -\n 1px\n );\n\n :host(:not([content-height])) & {\n display: none;\n }\n }\n }\n\n .sheet-wrapper {\n border-radius: inherit;\n }\n\n .sheet {\n display: flex;\n flex-direction: column;\n cursor: row-resize;\n border-radius: inherit;\n background: var(--sheet-background);\n overflow: clip;\n scroll-snap-align: var(--snap-point-align, start);\n pointer-events: all;\n }\n\n /* \n Needed for Safari/Firefox to prevent abrupt scroll re-snapping in case the\n sheet DOM content is dynamically changed. Without this, these browsers would\n re-snap to the start of the .sheet element.\n See related spec: https://drafts.csswg.org/css-scroll-snap-1/#re-snap\n */\n :host(:not([nested-scroll])) .sheet::after {\n display: block;\n position: static;\n scroll-snap-align: var(--snap-point-align, end);\n content: \"\";\n }\n\n .sheet-header {\n position: sticky;\n top: 0;\n background: inherit;\n width: 100%;\n }\n\n .sheet-footer {\n position: sticky;\n bottom: 0;\n background: inherit;\n width: 100%;\n }\n\n .sheet-content {\n flex: 1 1 auto;\n padding: 0 0.5rem;\n }\n\n .handle {\n margin: 0.5rem auto;\n border-radius: 5px;\n background: #ccc;\n width: 40px;\n height: 5px;\n }\n\n :host {\n animation: initial-snap var(--initial-snap-duration, 0.5s) backwards;\n }\n\n /* Safari overrides */\n @supports (-webkit-touch-callout: none) or (-webkit-hyphens: none) {\n :host {\n /* \n On Safari, when displaying in a dialog, we must inherit display property\n so that the animation runs each time the dialog is-reopened\n (display toggles between none and block).\n */\n display: inherit;\n /*\n On Safari, we need to briefly reset scroll-snap-type after the initial snap\n before re-enabling scroll-snap-align on other snap points to prevent scroll\n position from being reset to the initial snap point.\n */\n --snap-type-initial-reset: none;\n }\n }\n\n /*\n Temporarily disables scroll snapping for all snap points\n except the explicitly marked initial snap point (which overrides\n --snap-point-align) so that the sheet snaps to\n the initial snap point.\n */\n @keyframes initial-snap {\n /*\n Phase 1 (0%-49.99%): Disable scroll snapping for all snap points except \n the initial snap point. Keep the host scroll-snap-type at the base value\n (y mandatory) so the browser can snap to the initial snap point during\n this window.\n */\n 0%,\n 49.99% {\n scroll-snap-type: y mandatory;\n --snap-point-align: none;\n }\n /*\n Phase 2 (50%-100%): On Safari, set the host scroll-snap-type temporarily\n to none (via --snap-type-initial-reset) to prevent the scroll position resetting\n when the scroll-snap-align is re-enabled on all snap points after the animation ends.\n See https://stackoverflow.com/q/65653679\n */\n 50%,\n 100% {\n scroll-snap-type: var(--snap-type-initial-reset, y mandatory);\n --snap-point-align: none;\n }\n }\n\n /* Temporary workaround for iOS Safari bug https://bugs.webkit.org/show_bug.cgi?id=183870 */\n @supports (-webkit-touch-callout: none) {\n .sheet-content,\n .sheet-header,\n .sheet-footer {\n overflow-x: scroll;\n /* Prevent rubberband effect */\n overscroll-behavior-x: none;\n scrollbar-width: none;\n /* Prevent horizontal scrolling using touch gestures */\n touch-action: pan-y pinch-zoom;\n\n &::after {\n display: block;\n box-sizing: content-box;\n padding: inherit;\n padding-left: 0;\n width: calc(100% + 1px);\n height: 1px;\n content: \"\";\n }\n }\n .sheet-content {\n scrollbar-width: auto;\n }\n }\n\n :host(:not([nested-scroll]):not([content-height])) {\n .sheet-wrapper {\n height: 100%;\n }\n\n .sheet {\n min-height: 100%;\n }\n }\n\n :host([content-height]) {\n .sheet-wrapper {\n /*\n Needed to position the \"content-height\" sentinel at the correct position\n relative to the sheet for accurate intersection observation.\n */\n position: relative;\n }\n }\n\n :host([nested-scroll]) {\n .sheet-wrapper {\n display: flex;\n position: sticky;\n bottom: 0;\n flex-direction: column;\n justify-content: end;\n /* Needed for Firefox to properly render sticky items due to using contain: strict */\n content-visibility: auto;\n /* Fixes scroll-chaining issue on Firefox when sheet content is scrollable */\n contain: strict;\n height: 100%;\n }\n\n .sheet {\n display: flex;\n position: sticky;\n bottom: 0;\n flex-direction: column;\n height: 100%;\n max-height: 100%;\n }\n\n .sheet-content {\n will-change: scroll-position;\n overflow-y: auto;\n scrollbar-gutter: stable;\n }\n\n .sheet-header,\n .sheet-footer {\n /* Prevent shrinking the header and footer */\n flex: 0 0 auto;\n }\n }\n\n /*\n When expand-to-scroll is enabled, we do not need dynamic sheet height animation\n since sheet content is always fixed size and only scrollable at full height\n so we can simplify the layout and improve performance by disabling height animation.\n */\n :host([nested-scroll][expand-to-scroll]) {\n .sheet-wrapper {\n position: static;\n }\n\n .sheet {\n position: static;\n animation: none;\n height: 100%;\n }\n }\n\n /*\n Performance optimization for the nested-scroll mode:\n When the sheet is being actively scrolled, disable sheet height animation and\n switch to transform-based animation for the sheet content to improve performance\n and avoid jank because animating height would cause continuous reflow (layout).\n\n Follow use of [data-scrolling] for the specific CSS rules applied in this case.\n */\n :host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) {\n .sheet-header {\n z-index: 1;\n }\n\n .sheet-content {\n /* Hide the scrollbar visually during scrolling */\n scrollbar-color: transparent transparent;\n }\n }\n\n @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {\n :host([nested-scroll]) .sheet {\n animation: expand-sheet-height linear forwards;\n animation-timeline: scroll();\n }\n\n @keyframes expand-sheet-height {\n from {\n height: 0;\n }\n to {\n height: 100%;\n }\n }\n\n :host([nested-scroll][expand-to-scroll]) .sheet-content {\n animation: overflow-y-toggle linear forwards;\n animation-timeline: scroll();\n }\n\n @keyframes overflow-y-toggle {\n 0%,\n 99.99% {\n overflow-y: hidden;\n }\n 100% {\n overflow-y: auto;\n }\n }\n\n :host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) {\n .sheet {\n /* \n Safari bug fix: Pre-apply transform to prevent flickering. Safari 26+ has\n a one-frame delay when switching from height-based to transform-based animation,\n causing brief position shift. Setting an initial transform value ensures\n smooth transition.\n */\n transform: translateY(var(--sheet-timeline-at-scroll-start, 0));\n animation: translate-sheet linear forwards;\n animation-timeline: scroll();\n }\n\n .sheet-content {\n animation: translate-sheet-content linear forwards;\n animation-timeline: scroll();\n }\n\n .sheet-footer {\n animation: translate-footer linear forwards;\n animation-timeline: scroll();\n }\n }\n\n @keyframes translate-sheet {\n from {\n transform: translateY(100%);\n }\n to {\n transform: translateY(0);\n }\n }\n\n @keyframes translate-sheet-content {\n from {\n transform: translateY(var(--sheet-content-offset-start, 0));\n }\n to {\n transform: translateY(var(--sheet-content-offset-end, 0));\n }\n }\n\n @keyframes translate-footer {\n from {\n transform: translateY(calc(-1 * var(--sheet-safe-max-height)));\n }\n to {\n transform: translateY(0);\n }\n }\n }\n\n /* Fallback for browsers that do not yet support scroll-driven animations */\n @supports (\n not ((animation-timeline: scroll()) and (animation-range: 0% 100%))\n ) {\n :host([nested-scroll]) .sheet {\n height: var(--sheet-position);\n }\n\n :host([nested-scroll][expand-to-scroll]) .sheet-content {\n overflow-y: hidden;\n }\n\n :host([nested-scroll][expand-to-scroll][data-sheet-state=\"expanded\"])\n .sheet-content {\n overflow-y: auto;\n }\n\n :host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) {\n .sheet {\n transform: translateY(calc(100% - var(--sheet-position, 0)));\n height: 100%;\n }\n\n .sheet-content {\n transform: translateY(var(--sheet-content-offset, 0));\n }\n\n .sheet-footer {\n transform: translateY(\n calc(-1 * var(--sheet-safe-max-height) + var(--sheet-position, 0))\n );\n }\n }\n }\n`;\n\nexport const template: string = /* HTML */ `\n <style>\n ${styles}\n </style>\n <slot name=\"snap\">\n <div class=\"snap initial\" style=\"--snap: 100%\"></div>\n </slot>\n <div class=\"sentinel\" data-snap=\"bottom\"></div>\n <div class=\"snap snap-bottom\" data-snap=\"bottom\"></div>\n <div class=\"sentinel\" data-snap=\"top\"></div>\n <div class=\"sheet-wrapper\">\n <div class=\"sentinel\" data-snap=\"content-height\"></div>\n <aside class=\"sheet\" part=\"sheet\" data-snap=\"top\">\n <header class=\"sheet-header\" part=\"header\">\n <div class=\"handle\" part=\"handle\"></div>\n <slot name=\"header\"></slot>\n </header>\n <section class=\"sheet-content\" part=\"content\">\n <slot></slot>\n </section>\n <footer class=\"sheet-footer\" part=\"footer\">\n <slot name=\"footer\"></slot>\n </footer>\n </aside>\n </div>\n`;\n"],"names":[],"mappings":"AAAA;AAGA,MAAM,MAAM,UAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}