pure-web-bottom-sheet 0.3.0 → 0.5.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/README.md CHANGED
@@ -61,9 +61,9 @@ npm install pure-web-bottom-sheet
61
61
  <body>
62
62
  <bottom-sheet tabindex="0">
63
63
  <!-- Snap points -->
64
- <div slot="snap" style="--snap: 25%"></div>
65
- <div slot="snap" style="--snap: 50%" class="initial"></div>
66
64
  <div slot="snap" style="--snap: 75%"></div>
65
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
66
+ <div slot="snap" style="--snap: 25%"></div>
67
67
 
68
68
  <div slot="header">
69
69
  <h2>Custom header</h2>
@@ -100,9 +100,9 @@ npm install pure-web-bottom-sheet
100
100
  <dialog id="bottom-sheet-dialog">
101
101
  <bottom-sheet swipe-to-dismiss tabindex="0">
102
102
  <!-- Snap points -->
103
- <div slot="snap" style="--snap: 25%"></div>
104
- <div slot="snap" style="--snap: 50%" class="initial"></div>
105
103
  <div slot="snap" style="--snap: 75%"></div>
104
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
105
+ <div slot="snap" style="--snap: 25%"></div>
106
106
 
107
107
  <div slot="header">
108
108
  <h2>Custom header</h2>
@@ -149,9 +149,9 @@ import { bottomSheetTemplate } from "pure-web-bottom-sheet/ssr";
149
149
  </template>
150
150
 
151
151
  {/* Snap points */}
152
- <div slot="snap" style="--snap: 25%"></div>
153
- <div slot="snap" style="--snap: 50%" class="initial"></div>
154
152
  <div slot="snap" style="--snap: 75%"></div>
153
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
154
+ <div slot="snap" style="--snap: 25%"></div>
155
155
 
156
156
  <div slot="header">
157
157
  <h2>Custom header</h2>
@@ -188,9 +188,9 @@ import { bottomSheetTemplate } from "pure-web-bottom-sheet/ssr";
188
188
  </template>
189
189
 
190
190
  <!-- Snap points -->
191
- <div slot="snap" style="--snap: 25%"></div>
192
- <div slot="snap" style="--snap: 50%" class="initial"></div>
193
191
  <div slot="snap" style="--snap: 75%"></div>
192
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
193
+ <div slot="snap" style="--snap: 25%"></div>
194
194
 
195
195
  <div slot="header">
196
196
  <h2>Custom header</h2>
@@ -232,9 +232,9 @@ import { BottomSheet } from "pure-web-bottom-sheet/react";
232
232
  function Example() {
233
233
  return (
234
234
  <BottomSheet tabIndex={0}>
235
- <div slot="snap" style={{ "--snap": "25%" }} />
236
- <div slot="snap" style={{ "--snap": "50%" }} className="initial" />
237
235
  <div slot="snap" style={{ "--snap": "75%" }} />
236
+ <div slot="snap" style={{ "--snap": "50%" }} className="initial" />
237
+ <div slot="snap" style={{ "--snap": "25%" }} />
238
238
 
239
239
  <div slot="header">
240
240
  <h2>Custom header</h2>
@@ -278,9 +278,9 @@ function Example() {
278
278
  <BottomSheetDialogManager>
279
279
  <dialog ref={dialog}>
280
280
  <BottomSheet swipe-to-dismiss tabIndex={0}>
281
- <div slot="snap" style={{ "--snap": "25%" }} />
282
- <div slot="snap" style={{ "--snap": "50%" }} className="initial" />
283
281
  <div slot="snap" style={{ "--snap": "75%" }} />
282
+ <div slot="snap" style={{ "--snap": "50%" }} className="initial" />
283
+ <div slot="snap" style={{ "--snap": "25%" }} />
284
284
  <div slot="header">
285
285
  <h2>Custom header</h2>
286
286
  </div>
@@ -309,9 +309,9 @@ use the component and provide SSR support out of the box.
309
309
  ```vue
310
310
  <template>
311
311
  <VBottomSheet tabindex="0">
312
- <div slot="snap" style="--snap: 25%"></div>
313
- <div slot="snap" style="--snap: 50%" class="initial"></div>
314
312
  <div slot="snap" style="--snap: 75%"></div>
313
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
314
+ <div slot="snap" style="--snap: 25%"></div>
315
315
 
316
316
  <div slot="header">
317
317
  <h2>Custom header</h2>
@@ -349,9 +349,9 @@ import { VBottomSheet } from "pure-web-bottom-sheet/vue";
349
349
  <div slot="footer">
350
350
  <h2>Custom footer</h2>
351
351
  </div>
352
- <div slot="snap" style="--snap: 25%"></div>
353
- <div slot="snap" style="--snap: 50%" class="initial"></div>
354
352
  <div slot="snap" style="--snap: 75%"></div>
353
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
354
+ <div slot="snap" style="--snap: 25%"></div>
355
355
  <DummyContent />
356
356
  </VBottomSheet>
357
357
  </dialog>
@@ -390,9 +390,9 @@ using the bottom sheet as an overlay that should always remain visible.
390
390
  ```html
391
391
  <bottom-sheet>
392
392
  <!-- Snap points -->
393
- <div slot="snap" style="--snap: 25%"></div>
394
- <div slot="snap" style="--snap: 50%" class="initial"></div>
395
393
  <div slot="snap" style="--snap: 75%"></div>
394
+ <div slot="snap" style="--snap: 50%" class="initial"></div>
395
+ <div slot="snap" style="--snap: 25%"></div>
396
396
 
397
397
  <!-- Custom header -->
398
398
  <div slot="header">
@@ -446,8 +446,13 @@ using the bottom sheet as an overlay that should always remain visible.
446
446
  Defines snap points for positioning the bottom sheet. If not specified, the bottom
447
447
  sheet will have a single snap point `--snap: 100%` (maximum
448
448
  height). Note that when the `<bottom-sheet>` has the `swipe-to-dismiss` attribute
449
- set, it also has a snap point at the bottom of the viewport to allow swiping down
450
- to dismiss it.
449
+ set, it also has an implicit snap point at the bottom of the viewport to allow
450
+ swiping down to dismiss it.
451
+
452
+ Note that the snap points should be placed in the DOM in a top-to-bottom order
453
+ due to the snap index calculation assuming this order. E.g., `<div slot="snap" style="--snap: 75vh"></div>`
454
+ should be placed before `<div slot="snap" style="--snap: 50vh"></div>`.
455
+
451
456
  Each snap point element should:
452
457
  - Be assigned to this slot
453
458
  - Specify the `--snap` custom property to set to the wanted offset from the viewport
@@ -459,6 +464,12 @@ using the bottom sheet as an overlay that should always remain visible.
459
464
  - Optionally specify the class `initial` to make the bottom sheet
460
465
  initially snap to that point each time it is opened. Note that
461
466
  only a single snap point should specify this class.
467
+ - Optionally specify the class `top` if the snap point represents the fully
468
+ expanded sheet position (i.e., `--snap: 100%`). This ensures the
469
+ `snap-position-change` event reports `sheetState: "expanded"` for this snap
470
+ point, and that its `snapIndex` matches the fully expanded sheet position.
471
+ Must be the first snap point in the DOM.
472
+
462
473
  - **`header`** (optional)
463
474
  Optional header content that is displayed at the top of the bottom sheet.
464
475
  - **`footer`** (optional)
@@ -477,10 +488,14 @@ using the bottom sheet as an overlay that should always remain visible.
477
488
 
478
489
  #### Events
479
490
 
480
- - **`snap-position-change`** - type: `CustomEvent<{ snapPosition: string; }>`
481
- Notifies that the sheet snap position has changed. Positions: `"0"` indicates
482
- a fully expanded position, `"2"` indicates a fully collapsed (closed) position,
483
- and `"1"` indicates an intermediate position.
491
+ - **`snap-position-change`** - type: `CustomEvent<{ sheetState: "collapsed" | "partially-expanded" | "expanded"; snapIndex: number; }>`
492
+ Notifies that the sheet snap position has changed. Snap index 0 corresponds to
493
+ the collapsed state. The `sheetState` is one of the following:
494
+ - `"collapsed"` - The bottom sheet is collapsed (i.e., snapped to the bottom).
495
+ - `"partially-expanded"` - The bottom sheet is snapped to one of the intermediate
496
+ snap points defined by the user.
497
+ - `"expanded"` - The bottom sheet is fully expanded (i.e., snapped to the full
498
+ height).
484
499
 
485
500
  ### `<bottom-sheet-dialog-manager>`: A utility element for the native `<dialog>` element to use the `<bottom-sheet>` element as a dialog
486
501
 
@@ -6,7 +6,7 @@ import Template from './ShadowRootTemplate.js';
6
6
  function BottomSheet({ children, ...props }) {
7
7
  return (jsxs(Fragment, { children: [jsxs("bottom-sheet", { ...props,
8
8
  // Need to use `suppressHydrationWarning` to avoid hydration mismatch
9
- // because the bottom-sheet component updates its `data-sheet-snap-position`
9
+ // because the bottom-sheet component updates its `data-sheet-state`
10
10
  // attribute during the initial render, which is not reflected in the
11
11
  // server-rendered HTML.
12
12
  suppressHydrationWarning: true, children: [jsx(Template, { html: template }), children] }), jsx(Client, {})] }));
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.js","sources":["../../../../src/react/BottomSheet.tsx"],"sourcesContent":["import { BottomSheetHTMLAttributes } from \"../web/bottom-sheet\";\nimport { BottomSheetEvents } from \"../web/index.client\";\nimport { bottomSheetTemplate } from \"../web/index.ssr\";\nimport Client from \"./Client\";\nimport { CustomElementProps } from \"./custom-element-props\";\nimport ShadowRootTemplate from \"./ShadowRootTemplate\";\n\ntype BottomSheetProps = CustomElementProps<\n BottomSheetHTMLAttributes,\n BottomSheetEvents\n>;\n\ndeclare module \"react/jsx-runtime\" {\n namespace JSX {\n interface IntrinsicElements {\n \"bottom-sheet\": BottomSheetProps;\n }\n }\n}\n\nexport default function BottomSheet({ children, ...props }: BottomSheetProps) {\n return (\n <>\n <bottom-sheet\n {...props}\n // Need to use `suppressHydrationWarning` to avoid hydration mismatch\n // because the bottom-sheet component updates its `data-sheet-snap-position`\n // attribute during the initial render, which is not reflected in the\n // server-rendered HTML.\n suppressHydrationWarning\n >\n {<ShadowRootTemplate html={bottomSheetTemplate} />}\n {children}\n </bottom-sheet>\n <Client />\n </>\n );\n}\n"],"names":["_jsxs","_Fragment","_jsx","ShadowRootTemplate","bottomSheetTemplate"],"mappings":";;;;;AAoBc,SAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAoB,EAAA;IAC1E,QACEA,IAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EAAA,CACED,IAAA,CAAA,cAAA,EAAA,EAAA,GACM,KAAK;;;;;AAKT,gBAAA,wBAAwB,mBAEvBE,GAAA,CAACC,QAAkB,EAAA,EAAC,IAAI,EAAEC,QAAmB,EAAA,CAAI,EACjD,QAAQ,IACI,EACfF,GAAA,CAAC,MAAM,EAAA,EAAA,CAAG,CAAA,EAAA,CACT;AAEP;;;;"}
1
+ {"version":3,"file":"BottomSheet.js","sources":["../../../../src/react/BottomSheet.tsx"],"sourcesContent":["import { BottomSheetHTMLAttributes } from \"../web/bottom-sheet\";\nimport { BottomSheetEvents } from \"../web/index.client\";\nimport { bottomSheetTemplate } from \"../web/index.ssr\";\nimport Client from \"./Client\";\nimport { CustomElementProps } from \"./custom-element-props\";\nimport ShadowRootTemplate from \"./ShadowRootTemplate\";\n\ntype BottomSheetProps = CustomElementProps<\n BottomSheetHTMLAttributes,\n BottomSheetEvents\n>;\n\ndeclare module \"react/jsx-runtime\" {\n namespace JSX {\n interface IntrinsicElements {\n \"bottom-sheet\": BottomSheetProps;\n }\n }\n}\n\nexport default function BottomSheet({ children, ...props }: BottomSheetProps) {\n return (\n <>\n <bottom-sheet\n {...props}\n // Need to use `suppressHydrationWarning` to avoid hydration mismatch\n // because the bottom-sheet component updates its `data-sheet-state`\n // attribute during the initial render, which is not reflected in the\n // server-rendered HTML.\n suppressHydrationWarning\n >\n {<ShadowRootTemplate html={bottomSheetTemplate} />}\n {children}\n </bottom-sheet>\n <Client />\n </>\n );\n}\n"],"names":["_jsxs","_Fragment","_jsx","ShadowRootTemplate","bottomSheetTemplate"],"mappings":";;;;;AAoBc,SAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAoB,EAAA;IAC1E,QACEA,IAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EAAA,CACED,IAAA,CAAA,cAAA,EAAA,EAAA,GACM,KAAK;;;;;AAKT,gBAAA,wBAAwB,mBAEvBE,GAAA,CAACC,QAAkB,EAAA,EAAC,IAAI,EAAEC,QAAmB,EAAA,CAAI,EACjD,QAAQ,IACI,EACfF,GAAA,CAAC,MAAM,EAAA,EAAA,CAAG,CAAA,EAAA,CACT;AAEP;;;;"}
@@ -19,9 +19,9 @@ class BottomSheetDialogManager extends HTMLElement {
19
19
  });
20
20
  this.addEventListener("snap-position-change", (event) => {
21
21
  if (event.detail) {
22
- this.dataset.sheetSnapPosition = event.detail.snapPosition;
22
+ this.dataset.sheetState = event.detail.sheetState;
23
23
  }
24
- if (event.detail?.snapPosition == "2" &&
24
+ if (event.detail?.sheetState === "collapsed" &&
25
25
  event.target instanceof HTMLElement &&
26
26
  event.target.hasAttribute("swipe-to-dismiss") &&
27
27
  event.target.checkVisibility()) {
@@ -1 +1 @@
1
- {"version":3,"file":"bottom-sheet-dialog-manager.js","sources":["../../../../src/web/bottom-sheet-dialog-manager.ts"],"sourcesContent":["import { SnapPositionChangeEventDetail } from \"./bottom-sheet\";\nimport { template } from \"./bottom-sheet-dialog-manager.template\";\n\nexport class BottomSheetDialogManager extends HTMLElement {\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\n this.addEventListener(\"click\", (event) => {\n if (\n event.target instanceof HTMLDialogElement &&\n event.target.matches(\":modal\")\n ) {\n event.target.close();\n }\n });\n this.addEventListener(\n \"snap-position-change\",\n (event: CustomEventInit<SnapPositionChangeEventDetail> & Event) => {\n if (event.detail) {\n this.dataset.sheetSnapPosition = event.detail.snapPosition;\n }\n if (\n event.detail?.snapPosition == \"2\" &&\n event.target instanceof HTMLElement &&\n event.target.hasAttribute(\"swipe-to-dismiss\") &&\n event.target.checkVisibility()\n ) {\n const parent = event.target.parentElement;\n if (\n parent instanceof HTMLDialogElement &&\n // Prevent Safari from closing the dialog immediately after opening\n // while the dialog open transition is still running.\n getComputedStyle(parent).getPropertyValue(\"translate\") === \"0px\"\n ) {\n parent.close();\n }\n }\n },\n );\n }\n}\n\n/**\n * Interface for the bottom-sheet-dialog-manager custom element.\n * Provides type definitions for its custom properties.\n *\n * @example\n * // Register in TypeScript for proper type checking:\n * declare global {\n * interface HTMLElementTagNameMap {\n * \"bottom-sheet-dialog-manager\": BottomSheetDialogManager;\n * }\n * }\n */\nexport interface BottomSheetDialogManager extends HTMLElement {}\n"],"names":[],"mappings":";;AAGM,MAAO,wBAAyB,SAAQ,WAAW,CAAA;AACvD,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;QAEA,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,KAAI;AACvC,YAAA,IACE,KAAK,CAAC,MAAM,YAAY,iBAAiB;gBACzC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAC9B;AACA,gBAAA,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE;YACtB;AACF,QAAA,CAAC,CAAC;QACF,IAAI,CAAC,gBAAgB,CACnB,sBAAsB,EACtB,CAAC,KAA6D,KAAI;AAChE,YAAA,IAAI,KAAK,CAAC,MAAM,EAAE;gBAChB,IAAI,CAAC,OAAO,CAAC,iBAAiB,GAAG,KAAK,CAAC,MAAM,CAAC,YAAY;YAC5D;AACA,YAAA,IACE,KAAK,CAAC,MAAM,EAAE,YAAY,IAAI,GAAG;gBACjC,KAAK,CAAC,MAAM,YAAY,WAAW;AACnC,gBAAA,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,kBAAkB,CAAC;AAC7C,gBAAA,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE,EAC9B;AACA,gBAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,aAAa;gBACzC,IACE,MAAM,YAAY,iBAAiB;;;oBAGnC,gBAAgB,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC,KAAK,KAAK,EAChE;oBACA,MAAM,CAAC,KAAK,EAAE;gBAChB;YACF;AACF,QAAA,CAAC,CACF;IACH;AACD;;;;"}
1
+ {"version":3,"file":"bottom-sheet-dialog-manager.js","sources":["../../../../src/web/bottom-sheet-dialog-manager.ts"],"sourcesContent":["import { SnapPositionChangeEventDetail } from \"./bottom-sheet\";\nimport { template } from \"./bottom-sheet-dialog-manager.template\";\n\nexport class BottomSheetDialogManager extends HTMLElement {\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\n this.addEventListener(\"click\", (event) => {\n if (\n event.target instanceof HTMLDialogElement &&\n event.target.matches(\":modal\")\n ) {\n event.target.close();\n }\n });\n this.addEventListener(\n \"snap-position-change\",\n (event: CustomEventInit<SnapPositionChangeEventDetail> & Event) => {\n if (event.detail) {\n this.dataset.sheetState = event.detail.sheetState;\n }\n if (\n event.detail?.sheetState === \"collapsed\" &&\n event.target instanceof HTMLElement &&\n event.target.hasAttribute(\"swipe-to-dismiss\") &&\n event.target.checkVisibility()\n ) {\n const parent = event.target.parentElement;\n if (\n parent instanceof HTMLDialogElement &&\n // Prevent Safari from closing the dialog immediately after opening\n // while the dialog open transition is still running.\n getComputedStyle(parent).getPropertyValue(\"translate\") === \"0px\"\n ) {\n parent.close();\n }\n }\n },\n );\n }\n}\n\n/**\n * Interface for the bottom-sheet-dialog-manager custom element.\n * Provides type definitions for its custom properties.\n *\n * @example\n * // Register in TypeScript for proper type checking:\n * declare global {\n * interface HTMLElementTagNameMap {\n * \"bottom-sheet-dialog-manager\": BottomSheetDialogManager;\n * }\n * }\n */\nexport interface BottomSheetDialogManager extends HTMLElement {}\n"],"names":[],"mappings":";;AAGM,MAAO,wBAAyB,SAAQ,WAAW,CAAA;AACvD,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;QAEA,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,KAAI;AACvC,YAAA,IACE,KAAK,CAAC,MAAM,YAAY,iBAAiB;gBACzC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAC9B;AACA,gBAAA,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE;YACtB;AACF,QAAA,CAAC,CAAC;QACF,IAAI,CAAC,gBAAgB,CACnB,sBAAsB,EACtB,CAAC,KAA6D,KAAI;AAChE,YAAA,IAAI,KAAK,CAAC,MAAM,EAAE;gBAChB,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU;YACnD;AACA,YAAA,IACE,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW;gBACxC,KAAK,CAAC,MAAM,YAAY,WAAW;AACnC,gBAAA,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,kBAAkB,CAAC;AAC7C,gBAAA,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE,EAC9B;AACA,gBAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,aAAa;gBACzC,IACE,MAAM,YAAY,iBAAiB;;;oBAGnC,gBAAgB,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC,KAAK,KAAK,EAChE;oBACA,MAAM,CAAC,KAAK,EAAE;gBAChB;YACF;AACF,QAAA,CAAC,CACF;IACH;AACD;;;;"}
@@ -1,5 +1,5 @@
1
1
  /* Needed until Prettier supports identifying embedded CSS by block comments */
2
- const styles = /*css*/`::slotted(dialog){background:unset;border:none;height:100%;inset:0;margin:0;max-height:none;max-width:none;padding:0;position:fixed;top:auto;width:100%}::slotted(dialog:not(:modal)){pointer-events:none}::slotted(dialog[open]){translate:0 0}@starting-style{::slotted(dialog[open]){translate:0 100vh}}::slotted(dialog){transition:translate .5s ease-out,overlay .5s ease-out allow-discrete,display var(--display-transition-duration,.5s) ease-out allow-discrete;translate:0 100vh}:host([data-sheet-snap-position="2"]) ::slotted(dialog:not([open])){transition:none}@supports (-webkit-touch-callout:none) or (-webkit-hyphens:none){::slotted(dialog){--display-transition-duration:0.1s}}`;
2
+ const styles = /*css*/`::slotted(dialog){background:unset;border:none;height:100%;inset:0;margin:0;max-height:none;max-width:none;padding:0;position:fixed;top:auto;width:100%}::slotted(dialog:not(:modal)){pointer-events:none}::slotted(dialog[open]){translate:0 0}@starting-style{::slotted(dialog[open]){translate:0 100vh}}::slotted(dialog){transition:translate .5s ease-out,overlay allow-discrete .5s ease-out,display allow-discrete .5s ease-out;translate:0 100vh}:host([data-sheet-state=collapsed]) ::slotted(dialog:not([open])){transition:none}`;
3
3
  const template = /* HTML */ `
4
4
  <style>
5
5
  ${styles}
@@ -1 +1 @@
1
- {"version":3,"file":"bottom-sheet-dialog-manager.template.js","sources":["../../../../src/web/bottom-sheet-dialog-manager.template.ts"],"sourcesContent":["/* Needed until Prettier supports identifying embedded CSS by block comments */\nconst css = String.raw;\n\nconst styles = css`\n ::slotted(dialog) {\n position: fixed;\n margin: 0;\n inset: 0;\n top: initial;\n border: none;\n background: unset;\n padding: 0;\n width: 100%;\n max-width: none;\n height: 100%;\n max-height: none;\n }\n\n ::slotted(dialog:not(:modal)) {\n pointer-events: none;\n }\n\n ::slotted(dialog[open]) {\n translate: 0 0;\n }\n\n @starting-style {\n ::slotted(dialog[open]) {\n translate: 0 100vh;\n }\n }\n\n ::slotted(dialog) {\n translate: 0 100vh;\n transition:\n translate 0.5s ease-out,\n overlay 0.5s ease-out allow-discrete,\n display var(--display-transition-duration, 0.5s) ease-out allow-discrete;\n }\n\n :host([data-sheet-snap-position=\"2\"]) ::slotted(dialog:not([open])) {\n transition: none;\n }\n\n /** Safari overrides */\n @supports (-webkit-touch-callout: none) or (-webkit-hyphens: none) {\n ::slotted(dialog) {\n /* \n For Safari we must user shorter duration for display property or otherwise\n the bottom sheet will not snap properly to the initial target on open.\n */\n --display-transition-duration: 0.1s;\n }\n }\n`;\n\nexport const template: string = /* HTML */ `\n <style>\n ${styles}\n </style>\n <slot></slot>\n`;\n"],"names":[],"mappings":"AAAA;AAGA,MAAM,MAAM,UAAM,CAAA,uqBAAA,CAAA;;;;;;;;;;"}
1
+ {"version":3,"file":"bottom-sheet-dialog-manager.template.js","sources":["../../../../src/web/bottom-sheet-dialog-manager.template.ts"],"sourcesContent":["/* Needed until Prettier supports identifying embedded CSS by block comments */\nconst css = String.raw;\n\nconst styles = css`\n ::slotted(dialog) {\n position: fixed;\n margin: 0;\n inset: 0;\n top: initial;\n border: none;\n background: unset;\n padding: 0;\n width: 100%;\n max-width: none;\n height: 100%;\n max-height: none;\n }\n\n ::slotted(dialog:not(:modal)) {\n pointer-events: none;\n }\n\n ::slotted(dialog[open]) {\n translate: 0 0;\n }\n\n @starting-style {\n ::slotted(dialog[open]) {\n translate: 0 100vh;\n }\n }\n\n ::slotted(dialog) {\n translate: 0 100vh;\n transition:\n translate 0.5s ease-out,\n overlay 0.5s ease-out allow-discrete,\n display 0.5s ease-out allow-discrete;\n }\n\n :host([data-sheet-state=\"collapsed\"]) ::slotted(dialog:not([open])) {\n transition: none;\n }\n`;\n\nexport const template: string = /* HTML */ `\n <style>\n ${styles}\n </style>\n <slot></slot>\n`;\n"],"names":[],"mappings":"AAAA;AAGA,MAAM,MAAM,UAAM,CAAA,2gBAAA,CAAA;;;;;;;;;;"}
@@ -44,8 +44,24 @@ export interface BottomSheetHTMLAttributes {
44
44
  */
45
45
  ["swipe-to-dismiss"]?: boolean;
46
46
  }
47
+ /**
48
+ * Represents the current state of the bottom sheet.
49
+ * - `collapsed`: Sheet is fully collapsed (closed/minimized)
50
+ * - `partially-expanded`: Sheet is at an intermediate snap point
51
+ * - `expanded`: Sheet is fully expanded to its maximum height
52
+ */
53
+ export type SheetState = "collapsed" | "partially-expanded" | "expanded";
54
+ /**
55
+ * Detail object for the `snap-position-change` custom event.
56
+ */
47
57
  export interface SnapPositionChangeEventDetail {
48
- snapPosition: string;
58
+ /** The semantic state of the sheet */
59
+ sheetState: SheetState;
60
+ /**
61
+ * The index of the current snap point (0 = collapsed,
62
+ * higher values = more expanded, with the highest being fully expanded)
63
+ */
64
+ snapIndex: number;
49
65
  }
50
66
  export type BottomSheetEvents = {
51
67
  "snap-position-change": CustomEvent<SnapPositionChangeEventDetail>;
@@ -13,12 +13,13 @@ import { template } from './bottom-sheet.template.js';
13
13
  */
14
14
  class BottomSheet extends HTMLElement {
15
15
  static observedAttributes = ["nested-scroll-optimization"];
16
- #observer = null;
17
16
  #handleViewportResize = () => {
18
17
  this.style.setProperty("--sw-keyboard-height", `${window.visualViewport?.offsetTop ?? 0}px`);
19
18
  };
20
19
  #shadow;
20
+ #cleanupIntersectionObserver = null;
21
21
  #cleanupNestedScrollResizeOptimization = null;
22
+ #currentSnapState = null;
22
23
  constructor() {
23
24
  super();
24
25
  const supportsDeclarative = HTMLElement.prototype.hasOwnProperty("attachInternals");
@@ -47,56 +48,126 @@ class BottomSheet extends HTMLElement {
47
48
  window.visualViewport?.addEventListener("resize", this.#handleViewportResize);
48
49
  }
49
50
  #setupIntersectionObserver() {
50
- this.#observer = new IntersectionObserver((entries) => {
51
- let lowestIntersectingSnap = Infinity;
52
- let highestNonIntersectingSnap = -Infinity;
53
- let hasIntersectingElement = false;
54
- for (const entry of entries) {
55
- if (!(entry.target instanceof HTMLElement) ||
56
- entry.target.dataset.snap == null) {
57
- continue;
58
- }
59
- const snap = Number.parseInt(entry.target.dataset.snap);
60
- if (entry.isIntersecting) {
61
- hasIntersectingElement = true;
62
- lowestIntersectingSnap = Math.min(lowestIntersectingSnap, snap);
63
- }
64
- else {
65
- highestNonIntersectingSnap = Math.max(highestNonIntersectingSnap, snap);
51
+ const snapSlot = this.#shadow.querySelector('slot[name="snap"]');
52
+ const bottomSnapTarget = this.#shadow.querySelector('.sentinel[data-snap="bottom"]');
53
+ if (!snapSlot || !bottomSnapTarget)
54
+ return;
55
+ const intersectingTargets = new Set();
56
+ let previousSnapTarget = null;
57
+ const getDistanceToSnapLine = (entry) => Math.abs(entry.intersectionRect.top - (entry.rootBounds?.bottom ?? 0));
58
+ 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);
73
+ });
74
+ // Skip when the root has no dimensions (e.g., inside a closed dialog)
75
+ if (!entries[0]?.rootBounds?.height) {
76
+ return;
77
+ }
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) {
81
+ return;
82
+ }
83
+ // Handle case where none of the targets intersect (fully collapsed state)
84
+ if (!currentTarget) {
85
+ if (intersectingTargets.has(bottomSnapTarget) ||
86
+ !this.hasAttribute("swipe-to-dismiss")) {
87
+ return;
66
88
  }
89
+ // Use bottom target when nothing intersects and swipe-to-dismiss is enabled
90
+ previousSnapTarget = bottomSnapTarget;
91
+ this.#updateSnapPosition(bottomSnapTarget);
92
+ return;
67
93
  }
68
- const newSnapPosition = hasIntersectingElement
69
- ? lowestIntersectingSnap
70
- : highestNonIntersectingSnap + 1;
71
- this.#updateSnapPosition(newSnapPosition.toString());
94
+ previousSnapTarget = currentTarget;
95
+ this.#updateSnapPosition(currentTarget);
72
96
  }, {
73
97
  root: this,
74
- threshold: 0,
75
98
  rootMargin: "1000% 0px -100% 0px",
76
99
  });
77
100
  const sentinels = this.#shadow.querySelectorAll(".sentinel");
78
101
  Array.from(sentinels).forEach((sentinel) => {
79
- this.#observer?.observe(sentinel);
102
+ observer.observe(sentinel);
80
103
  });
104
+ let observedSnapPoints = new Set();
105
+ const observeSnapPoints = () => {
106
+ const snapPoints = new Set(snapSlot.assignedElements());
107
+ // Unobserve elements no longer assigned to the slot
108
+ observedSnapPoints.forEach((el) => {
109
+ if (!snapPoints.has(el)) {
110
+ observer.unobserve(el);
111
+ }
112
+ });
113
+ snapPoints.forEach((el) => observer.observe(el));
114
+ observedSnapPoints = snapPoints;
115
+ };
116
+ snapSlot.addEventListener("slotchange", observeSnapPoints);
117
+ observeSnapPoints();
118
+ this.#cleanupIntersectionObserver = () => {
119
+ snapSlot.removeEventListener("slotchange", observeSnapPoints);
120
+ observer.disconnect();
121
+ this.#cleanupIntersectionObserver = null;
122
+ };
81
123
  }
82
124
  #handleScrollSnapChange(event) {
83
125
  const snapEvent = event;
84
126
  if (!(snapEvent.snapTargetBlock instanceof HTMLElement)) {
85
127
  return;
86
128
  }
87
- const newSnapPosition = snapEvent.snapTargetBlock.dataset.snap ?? "1";
88
- this.#updateSnapPosition(newSnapPosition);
129
+ this.#updateSnapPosition(snapEvent.snapTargetBlock);
89
130
  }
90
- #updateSnapPosition(position) {
91
- this.dataset.sheetSnapPosition = position;
131
+ #updateSnapPosition(newSnapTarget) {
132
+ const snapState = this.#calculateSnapState(newSnapTarget);
133
+ if (!snapState)
134
+ return;
135
+ const { snapIndex, sheetState } = snapState;
136
+ if (this.#currentSnapState?.snapIndex === snapIndex &&
137
+ this.#currentSnapState?.sheetState === sheetState) {
138
+ return;
139
+ }
140
+ this.#currentSnapState = snapState;
141
+ this.dataset.sheetState = sheetState;
92
142
  this.dispatchEvent(new CustomEvent("snap-position-change", {
93
- detail: {
94
- snapPosition: position,
95
- },
143
+ detail: snapState,
96
144
  bubbles: true,
97
145
  composed: true,
98
146
  }));
99
147
  }
148
+ #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
+ if (snapTarget instanceof HTMLElement &&
156
+ snapTarget.dataset.snap === "bottom") {
157
+ return { snapIndex: 0, sheetState: "collapsed" };
158
+ }
159
+ // Snapped on one of the snap points assigned to the "snap" slot
160
+ 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 };
166
+ }
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" };
170
+ }
100
171
  #handleScroll() {
101
172
  this.#shadow
102
173
  .querySelector(".sheet-wrapper")
@@ -140,9 +211,15 @@ class BottomSheet extends HTMLElement {
140
211
  const matrix = new DOMMatrixReadOnly(style.transform);
141
212
  const yOffset = -matrix.m42;
142
213
  cleanupStyleProperties();
143
- requestAnimationFrame(() => {
144
- content.scrollTop = yOffset + content.scrollTop;
145
- });
214
+ // Pin the sheet height inline so the content scrollTop update below is based
215
+ // on the correct scroll height. Removing [data-scrolling] reactivates the
216
+ // scroll-timeline-driven "expand-sheet-height" animation, which will not
217
+ // produce a current time until the next layout pass, leaving the sheet height
218
+ // (and content scroll height) stale for the scrollTop assignment that follows.
219
+ sheet.style.height = `${(this.scrollTop / (this.scrollHeight - this.offsetHeight)) * 100}%`;
220
+ content.scrollTop = yOffset + content.scrollTop;
221
+ // Clear the temporary inline override now that the scrollTop is set
222
+ sheet.style.height = "";
146
223
  };
147
224
  const handleScroll = () => {
148
225
  if (!("scrolling" in this.dataset)) {
@@ -235,7 +312,7 @@ class BottomSheet extends HTMLElement {
235
312
  }
236
313
  }
237
314
  disconnectedCallback() {
238
- this.#observer?.disconnect();
315
+ this.#cleanupIntersectionObserver?.();
239
316
  window.visualViewport?.removeEventListener("resize", this.#handleViewportResize);
240
317
  }
241
318
  }
@@ -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 #observer: IntersectionObserver | null = null;\n #handleViewportResize = () => {\n this.style.setProperty(\n \"--sw-keyboard-height\",\n `${window.visualViewport?.offsetTop ?? 0}px`,\n );\n };\n #shadow: ShadowRoot;\n #cleanupNestedScrollResizeOptimization: (() => void) | 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 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 this.#observer = new IntersectionObserver(\n (entries) => {\n let lowestIntersectingSnap = Infinity;\n let highestNonIntersectingSnap = -Infinity;\n let hasIntersectingElement = false;\n\n for (const entry of entries) {\n if (\n !(entry.target instanceof HTMLElement) ||\n entry.target.dataset.snap == null\n ) {\n continue;\n }\n\n const snap = Number.parseInt(entry.target.dataset.snap);\n\n if (entry.isIntersecting) {\n hasIntersectingElement = true;\n lowestIntersectingSnap = Math.min(lowestIntersectingSnap, snap);\n } else {\n highestNonIntersectingSnap = Math.max(\n highestNonIntersectingSnap,\n snap,\n );\n }\n }\n\n const newSnapPosition = hasIntersectingElement\n ? lowestIntersectingSnap\n : highestNonIntersectingSnap + 1;\n\n this.#updateSnapPosition(newSnapPosition.toString());\n },\n {\n root: this,\n threshold: 0,\n rootMargin: \"1000% 0px -100% 0px\",\n },\n );\n\n const sentinels = this.#shadow.querySelectorAll(\".sentinel\");\n Array.from(sentinels).forEach((sentinel) => {\n this.#observer?.observe(sentinel);\n });\n }\n\n #handleScrollSnapChange(event: Event) {\n const snapEvent = event as SnapEvent;\n if (!(snapEvent.snapTargetBlock instanceof HTMLElement)) {\n return;\n }\n const newSnapPosition = snapEvent.snapTargetBlock.dataset.snap ?? \"1\";\n this.#updateSnapPosition(newSnapPosition);\n }\n\n #updateSnapPosition(position: string) {\n this.dataset.sheetSnapPosition = position;\n this.dispatchEvent(\n new CustomEvent<SnapPositionChangeEventDetail>(\"snap-position-change\", {\n detail: {\n snapPosition: position,\n },\n bubbles: true,\n composed: true,\n }),\n );\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 requestAnimationFrame(() => {\n content.scrollTop = yOffset + content.scrollTop;\n });\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.#observer?.disconnect();\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\nexport interface SnapPositionChangeEventDetail {\n snapPosition: string;\n}\n\nexport type BottomSheetEvents = {\n \"snap-position-change\": CustomEvent<SnapPositionChangeEventDetail>;\n};\n"],"names":[],"mappings":"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"}
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":"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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]){position:relative;top:var(--snap)}.snap:before,::slotted([slot=snap]):before{content:"";height:1px;left:0;position:absolute;right:0;scroll-snap-align:var(--snap-point-align,start);top:0}.snap.initial,::slotted([slot=snap].initial){--snap-point-align:start}.snap.snap-bottom{height:auto;position:static;top:auto}.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="0"]{top:-1px}.sentinel[data-snap="1"]{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 .01s both}@supports (-webkit-touch-callout:none) or (-webkit-hyphens:none){:host{animation:initial-snap .1s both;display:inherit}}@keyframes initial-snap{0%{--snap-point-align:none}50%{scroll-snap-type:none;--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}: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;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{scroll-timeline:--sheet-timeline y}:host([nested-scroll]) .sheet{animation:expand-sheet-height linear forwards;animation-timeline:--sheet-timeline}@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:--sheet-timeline}@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;transform:translateY(var(--sheet-timeline-at-scroll-start,0));animation-timeline:--sheet-timeline}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-content{animation:translate-sheet-content linear forwards;animation-timeline:--sheet-timeline}:host([nested-scroll]:not([expand-to-scroll])[data-scrolling]) .sheet-footer{animation:translate-footer linear forwards;animation-timeline:--sheet-timeline}@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-snap-position="0"]) .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}.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)))}}`;
6
6
  const template = /* HTML */ `
7
7
  <style>
8
8
  ${styles}
@@ -10,11 +10,11 @@ const template = /* HTML */ `
10
10
  <slot name="snap">
11
11
  <div class="snap initial" style="--snap: 100%"></div>
12
12
  </slot>
13
- <div class="sentinel" data-snap="1"></div>
14
- <div class="snap snap-bottom" data-snap="2"></div>
15
- <div class="sentinel" data-snap="0"></div>
13
+ <div class="sentinel" data-snap="bottom"></div>
14
+ <div class="snap snap-bottom" data-snap="bottom"></div>
15
+ <div class="sentinel" data-snap="top"></div>
16
16
  <div class="sheet-wrapper">
17
- <aside class="sheet" part="sheet" data-snap="0">
17
+ <aside class="sheet" part="sheet" data-snap="top">
18
18
  <header class="sheet-header" part="header">
19
19
  <div class="handle" part="handle"></div>
20
20
  <slot name="header"></slot>