mtrl 0.10.0-next.2 → 0.10.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +13 -17
  2. package/dist/README.md +13 -17
  3. package/dist/components/bottom-sheet/config.js +11 -4
  4. package/dist/components/bottom-sheet/constants.d.ts +0 -2
  5. package/dist/components/bottom-sheet/constants.js +0 -1
  6. package/dist/components/bottom-sheet/features/state.js +39 -2
  7. package/dist/components/bottom-sheet/features/structure.js +5 -1
  8. package/dist/components/bottom-sheet/types.d.ts +15 -2
  9. package/dist/components/chips/features/controller.js +2 -1
  10. package/dist/components/datepicker/datepicker.js +10 -5
  11. package/dist/components/dialog/api.js +5 -0
  12. package/dist/components/dialog/config.js +8 -2
  13. package/dist/components/dialog/features.js +73 -14
  14. package/dist/components/dialog/types.d.ts +11 -0
  15. package/dist/components/drawer/config.js +9 -2
  16. package/dist/components/drawer/features/items.js +2 -1
  17. package/dist/components/drawer/features/state.js +50 -9
  18. package/dist/components/drawer/types.d.ts +12 -0
  19. package/dist/components/list/features/renderer.js +2 -1
  20. package/dist/components/menu/config.js +6 -1
  21. package/dist/components/menu/features/controller.js +23 -8
  22. package/dist/components/menu/features/keyboard.js +3 -2
  23. package/dist/components/menu/features/layer.d.ts +15 -0
  24. package/dist/components/menu/features/layer.js +16 -0
  25. package/dist/components/menu/features/opener.js +2 -1
  26. package/dist/components/menu/features/position.js +8 -6
  27. package/dist/components/menu/features/submenu.js +12 -4
  28. package/dist/components/menu/types.d.ts +12 -0
  29. package/dist/components/navigation-rail/navigation-rail.js +4 -3
  30. package/dist/components/search/api.d.ts +9 -1
  31. package/dist/components/search/api.js +16 -8
  32. package/dist/components/search/config.d.ts +17 -1
  33. package/dist/components/search/config.js +10 -1
  34. package/dist/components/search/constants.d.ts +11 -0
  35. package/dist/components/search/constants.js +9 -0
  36. package/dist/components/search/features/input.js +12 -3
  37. package/dist/components/search/features/states.d.ts +4 -2
  38. package/dist/components/search/features/states.js +98 -16
  39. package/dist/components/search/features/structure.d.ts +7 -20
  40. package/dist/components/search/features/structure.js +79 -29
  41. package/dist/components/search/features/suggestions.js +22 -6
  42. package/dist/components/search/index.d.ts +1 -1
  43. package/dist/components/search/types.d.ts +23 -0
  44. package/dist/components/select/features.js +6 -3
  45. package/dist/components/select/types.d.ts +7 -0
  46. package/dist/components/side-sheet/config.js +11 -3
  47. package/dist/components/side-sheet/features/state.js +38 -2
  48. package/dist/components/side-sheet/features/structure.js +1 -1
  49. package/dist/components/side-sheet/types.d.ts +12 -0
  50. package/dist/components/slider/features/handlers.js +2 -1
  51. package/dist/components/snackbar/api.js +28 -4
  52. package/dist/components/snackbar/config.d.ts +1 -0
  53. package/dist/components/snackbar/config.js +8 -1
  54. package/dist/components/snackbar/layer.d.ts +23 -0
  55. package/dist/components/snackbar/layer.js +85 -0
  56. package/dist/components/snackbar/types.d.ts +12 -0
  57. package/dist/components/split-button/features/menu.js +1 -0
  58. package/dist/components/split-button/types.d.ts +6 -0
  59. package/dist/components/timepicker/api.js +68 -18
  60. package/dist/components/timepicker/config.d.ts +0 -1082
  61. package/dist/components/timepicker/config.js +3 -35
  62. package/dist/components/timepicker/constants.d.ts +21 -11
  63. package/dist/components/timepicker/constants.js +4 -3
  64. package/dist/components/timepicker/dial.d.ts +5 -0
  65. package/dist/components/timepicker/dial.js +8 -2
  66. package/dist/components/timepicker/render.js +91 -103
  67. package/dist/components/timepicker/timepicker.js +2 -2
  68. package/dist/components/timepicker/types.d.ts +38 -9
  69. package/dist/components/timepicker/utils.d.ts +18 -51
  70. package/dist/components/timepicker/utils.js +36 -86
  71. package/dist/components/tooltip/api.js +34 -3
  72. package/dist/components/tooltip/config.js +8 -1
  73. package/dist/components/tooltip/types.d.ts +12 -0
  74. package/dist/core/dom/focus.d.ts +36 -0
  75. package/dist/core/dom/focus.js +47 -0
  76. package/dist/core/dom/index.d.ts +3 -0
  77. package/dist/core/dom/index.js +2 -0
  78. package/dist/core/dom/layer.d.ts +58 -0
  79. package/dist/core/dom/layer.js +52 -0
  80. package/dist/elements/bottom-app-bar.d.ts +46 -0
  81. package/dist/elements/bottom-app-bar.js +42 -0
  82. package/dist/elements/bottom-sheet.d.ts +121 -0
  83. package/dist/elements/bottom-sheet.js +95 -0
  84. package/dist/elements/button-group.d.ts +153 -0
  85. package/dist/elements/button-group.js +174 -0
  86. package/dist/elements/card.d.ts +68 -0
  87. package/dist/elements/card.js +112 -0
  88. package/dist/elements/carousel.d.ts +123 -0
  89. package/dist/elements/carousel.js +117 -0
  90. package/dist/elements/chips.d.ts +136 -0
  91. package/dist/elements/chips.js +214 -0
  92. package/dist/elements/css/bottom-app-bar.d.ts +1 -0
  93. package/dist/elements/css/bottom-app-bar.js +3 -0
  94. package/dist/elements/css/bottom-sheet.d.ts +1 -0
  95. package/dist/elements/css/bottom-sheet.js +3 -0
  96. package/dist/elements/css/button-group.d.ts +1 -0
  97. package/dist/elements/css/button-group.js +5 -0
  98. package/dist/elements/css/card.d.ts +1 -0
  99. package/dist/elements/css/card.js +4 -0
  100. package/dist/elements/css/carousel.d.ts +1 -0
  101. package/dist/elements/css/carousel.js +3 -0
  102. package/dist/elements/css/chips.d.ts +1 -0
  103. package/dist/elements/css/chips.js +3 -0
  104. package/dist/elements/css/datepicker.d.ts +1 -0
  105. package/dist/elements/css/datepicker.js +3 -0
  106. package/dist/elements/css/dialog.d.ts +1 -0
  107. package/dist/elements/css/dialog.js +5 -0
  108. package/dist/elements/css/drawer.d.ts +1 -0
  109. package/dist/elements/css/drawer.js +3 -0
  110. package/dist/elements/css/index.js +20 -0
  111. package/dist/elements/css/list.d.ts +1 -0
  112. package/dist/elements/css/list.js +3 -0
  113. package/dist/elements/css/menu.d.ts +1 -0
  114. package/dist/elements/css/menu.js +3 -0
  115. package/dist/elements/css/navigation-rail.d.ts +1 -0
  116. package/dist/elements/css/navigation-rail.js +3 -0
  117. package/dist/elements/css/search.d.ts +1 -0
  118. package/dist/elements/css/search.js +3 -0
  119. package/dist/elements/css/select.d.ts +1 -0
  120. package/dist/elements/css/select.js +5 -0
  121. package/dist/elements/css/side-sheet.d.ts +1 -0
  122. package/dist/elements/css/side-sheet.js +3 -0
  123. package/dist/elements/css/snackbar.d.ts +1 -0
  124. package/dist/elements/css/snackbar.js +5 -0
  125. package/dist/elements/css/split-button.d.ts +1 -0
  126. package/dist/elements/css/split-button.js +5 -0
  127. package/dist/elements/css/timepicker.d.ts +1 -0
  128. package/dist/elements/css/timepicker.js +3 -0
  129. package/dist/elements/css/tooltip.d.ts +1 -0
  130. package/dist/elements/css/tooltip.js +3 -0
  131. package/dist/elements/css/top-app-bar.d.ts +1 -0
  132. package/dist/elements/css/top-app-bar.js +3 -0
  133. package/dist/elements/datepicker.d.ts +155 -0
  134. package/dist/elements/datepicker.js +150 -0
  135. package/dist/elements/define.d.ts +7 -0
  136. package/dist/elements/define.js +21 -9
  137. package/dist/elements/dialog.d.ts +118 -0
  138. package/dist/elements/dialog.js +155 -0
  139. package/dist/elements/drawer.d.ts +137 -0
  140. package/dist/elements/drawer.js +136 -0
  141. package/dist/elements/index.d.ts +306 -0
  142. package/dist/elements/index.js +89 -0
  143. package/dist/elements/list.d.ts +135 -0
  144. package/dist/elements/list.js +191 -0
  145. package/dist/elements/menu.d.ts +165 -0
  146. package/dist/elements/menu.js +178 -0
  147. package/dist/elements/navigation-rail.d.ts +153 -0
  148. package/dist/elements/navigation-rail.js +129 -0
  149. package/dist/elements/search.d.ts +215 -0
  150. package/dist/elements/search.js +215 -0
  151. package/dist/elements/select.d.ts +134 -0
  152. package/dist/elements/select.js +149 -0
  153. package/dist/elements/sheet.d.ts +44 -0
  154. package/dist/elements/sheet.js +60 -0
  155. package/dist/elements/side-sheet.d.ts +91 -0
  156. package/dist/elements/side-sheet.js +63 -0
  157. package/dist/elements/snackbar.d.ts +99 -0
  158. package/dist/elements/snackbar.js +54 -0
  159. package/dist/elements/split-button.d.ts +82 -0
  160. package/dist/elements/split-button.js +77 -0
  161. package/dist/elements/timepicker.d.ts +158 -0
  162. package/dist/elements/timepicker.js +159 -0
  163. package/dist/elements/tooltip.d.ts +89 -0
  164. package/dist/elements/tooltip.js +132 -0
  165. package/dist/elements/top-app-bar.d.ts +63 -0
  166. package/dist/elements/top-app-bar.js +94 -0
  167. package/dist/index.cjs +0 -0
  168. package/dist/package.json +1 -1
  169. package/dist/react/index.d.ts +50 -1
  170. package/dist/react/index.js +50 -1
  171. package/dist/solid/index.d.ts +50 -1
  172. package/dist/solid/index.js +50 -1
  173. package/dist/styles/badge.css +1 -1
  174. package/dist/styles/base.css +1 -1
  175. package/dist/styles/bottom-app-bar.css +1 -1
  176. package/dist/styles/bottom-sheet.css +2 -2
  177. package/dist/styles/button-group.css +1 -1
  178. package/dist/styles/button.css +1 -1
  179. package/dist/styles/card.css +1 -1
  180. package/dist/styles/carousel.css +1 -1
  181. package/dist/styles/checkbox.css +1 -1
  182. package/dist/styles/chips.css +1 -1
  183. package/dist/styles/datepicker.css +1 -1
  184. package/dist/styles/dialog.css +2 -2
  185. package/dist/styles/divider.css +1 -1
  186. package/dist/styles/drawer.css +2 -2
  187. package/dist/styles/extended-fab.css +1 -1
  188. package/dist/styles/fab.css +1 -1
  189. package/dist/styles/icon-button.css +1 -1
  190. package/dist/styles/list.css +1 -1
  191. package/dist/styles/loading-indicator.css +1 -1
  192. package/dist/styles/menu.css +2 -2
  193. package/dist/styles/navigation-rail.css +1 -1
  194. package/dist/styles/progress.css +1 -1
  195. package/dist/styles/radios.css +1 -1
  196. package/dist/styles/search.css +2 -2
  197. package/dist/styles/segmented-button.css +1 -1
  198. package/dist/styles/select.css +1 -1
  199. package/dist/styles/side-sheet.css +2 -2
  200. package/dist/styles/slider.css +1 -1
  201. package/dist/styles/snackbar.css +2 -2
  202. package/dist/styles/split-button.css +1 -1
  203. package/dist/styles/switch.css +1 -1
  204. package/dist/styles/tabs.css +1 -1
  205. package/dist/styles/textfield.css +1 -1
  206. package/dist/styles/timepicker.css +2 -2
  207. package/dist/styles/tooltip.css +2 -2
  208. package/dist/styles/top-app-bar.css +1 -1
  209. package/dist/styles/utilities.css +1 -1
  210. package/dist/styles.css +2 -2
  211. package/dist/svelte/BottomAppBar.svelte +19 -0
  212. package/dist/svelte/BottomAppBar.svelte.d.ts +5 -0
  213. package/dist/svelte/BottomSheet.svelte +19 -0
  214. package/dist/svelte/BottomSheet.svelte.d.ts +5 -0
  215. package/dist/svelte/ButtonGroup.svelte +20 -0
  216. package/dist/svelte/ButtonGroup.svelte.d.ts +5 -0
  217. package/dist/svelte/ButtonGroupItem.svelte +11 -0
  218. package/dist/svelte/ButtonGroupItem.svelte.d.ts +5 -0
  219. package/dist/svelte/Card.svelte +19 -0
  220. package/dist/svelte/Card.svelte.d.ts +5 -0
  221. package/dist/svelte/Carousel.svelte +20 -0
  222. package/dist/svelte/Carousel.svelte.d.ts +5 -0
  223. package/dist/svelte/CarouselItem.svelte +11 -0
  224. package/dist/svelte/CarouselItem.svelte.d.ts +5 -0
  225. package/dist/svelte/Chip.svelte +11 -0
  226. package/dist/svelte/Chip.svelte.d.ts +5 -0
  227. package/dist/svelte/Chips.svelte +20 -0
  228. package/dist/svelte/Chips.svelte.d.ts +5 -0
  229. package/dist/svelte/Datepicker.svelte +20 -0
  230. package/dist/svelte/Datepicker.svelte.d.ts +5 -0
  231. package/dist/svelte/Dialog.svelte +19 -0
  232. package/dist/svelte/Dialog.svelte.d.ts +5 -0
  233. package/dist/svelte/Drawer.svelte +20 -0
  234. package/dist/svelte/Drawer.svelte.d.ts +5 -0
  235. package/dist/svelte/DrawerItem.svelte +11 -0
  236. package/dist/svelte/DrawerItem.svelte.d.ts +5 -0
  237. package/dist/svelte/List.svelte +21 -0
  238. package/dist/svelte/List.svelte.d.ts +5 -0
  239. package/dist/svelte/ListItem.svelte +11 -0
  240. package/dist/svelte/ListItem.svelte.d.ts +5 -0
  241. package/dist/svelte/Menu.svelte +20 -0
  242. package/dist/svelte/Menu.svelte.d.ts +5 -0
  243. package/dist/svelte/MenuItem.svelte +11 -0
  244. package/dist/svelte/MenuItem.svelte.d.ts +5 -0
  245. package/dist/svelte/NavigationRail.svelte +20 -0
  246. package/dist/svelte/NavigationRail.svelte.d.ts +5 -0
  247. package/dist/svelte/NavigationRailItem.svelte +11 -0
  248. package/dist/svelte/NavigationRailItem.svelte.d.ts +5 -0
  249. package/dist/svelte/Search.svelte +20 -0
  250. package/dist/svelte/Search.svelte.d.ts +5 -0
  251. package/dist/svelte/SearchSuggestion.svelte +11 -0
  252. package/dist/svelte/SearchSuggestion.svelte.d.ts +5 -0
  253. package/dist/svelte/Select.svelte +20 -0
  254. package/dist/svelte/Select.svelte.d.ts +5 -0
  255. package/dist/svelte/SelectOption.svelte +11 -0
  256. package/dist/svelte/SelectOption.svelte.d.ts +5 -0
  257. package/dist/svelte/SideSheet.svelte +19 -0
  258. package/dist/svelte/SideSheet.svelte.d.ts +5 -0
  259. package/dist/svelte/Snackbar.svelte +20 -0
  260. package/dist/svelte/Snackbar.svelte.d.ts +5 -0
  261. package/dist/svelte/SplitButton.svelte +19 -0
  262. package/dist/svelte/SplitButton.svelte.d.ts +5 -0
  263. package/dist/svelte/Timepicker.svelte +20 -0
  264. package/dist/svelte/Timepicker.svelte.d.ts +5 -0
  265. package/dist/svelte/Tooltip.svelte +20 -0
  266. package/dist/svelte/Tooltip.svelte.d.ts +5 -0
  267. package/dist/svelte/TopAppBar.svelte +19 -0
  268. package/dist/svelte/TopAppBar.svelte.d.ts +5 -0
  269. package/dist/svelte/index.d.ts +29 -0
  270. package/dist/svelte/index.js +49 -0
  271. package/dist/themes/autumn.css +1 -1
  272. package/dist/themes/baseline.css +1 -1
  273. package/dist/themes/brownbeige.css +1 -1
  274. package/dist/themes/browngreen.css +1 -1
  275. package/dist/themes/desert.css +1 -1
  276. package/dist/themes/forest.css +1 -1
  277. package/dist/themes/highcontrast.css +1 -1
  278. package/dist/themes/legacy.css +1 -1
  279. package/dist/themes/material.css +1 -1
  280. package/dist/themes/ocean.css +1 -1
  281. package/dist/themes/sageivory.css +1 -1
  282. package/dist/themes/spring.css +1 -1
  283. package/dist/themes/summer.css +1 -1
  284. package/dist/themes/sunset.css +1 -1
  285. package/dist/themes/tealcaramel.css +1 -1
  286. package/dist/themes/winter.css +1 -1
  287. package/dist/vue/index.d.ts +50 -1
  288. package/dist/vue/index.js +50 -1
  289. package/package.json +1 -1
  290. package/src/styles/components/_bottom-sheet.scss +42 -2
  291. package/src/styles/components/_dialog.scss +52 -0
  292. package/src/styles/components/_drawer.scss +42 -0
  293. package/src/styles/components/_menu.scss +9 -0
  294. package/src/styles/components/_search.scss +278 -273
  295. package/src/styles/components/_side-sheet.scss +41 -0
  296. package/src/styles/components/_snackbar.scss +12 -0
  297. package/src/styles/components/_timepicker.scss +116 -139
  298. package/src/styles/components/_tooltip.scss +11 -0
package/README.md CHANGED
@@ -136,23 +136,19 @@ Button progress and card actions load on demand; enable code splitting in your b
136
136
 
137
137
  ## Web components and frameworks (experimental)
138
138
 
139
- From the 0.10.0 prereleases (`npm install mtrl@next`), components also ship as custom elements, with thin adapters for React, Vue, Svelte and Solid. The API may still change.
140
-
141
- | Element | Framework component (Vue: `M` prefix, e.g. `MTextfield`) | Kind |
142
- |---------|---------------------|------|
143
- | `<m-button>` | `Button` | action, submits or resets its form |
144
- | `<m-icon-button>` | `IconButton` | action or toggle (`selected`) |
145
- | `<m-fab>`, `<m-extended-fab>` | `Fab`, `ExtendedFab` | action |
146
- | `<m-checkbox>` | `Checkbox` | form control (`checked`, `indeterminate`) |
147
- | `<m-switch>` | `Switch` | form control (`checked`) |
148
- | `<m-radios>` with `<m-radio>` | `Radios`, `Radio` | form control (`value`) |
149
- | `<m-slider>` | `Slider` | form control (`value`, `secondValue` with `range`) |
150
- | `<m-textfield>` | `Textfield` | form control (`value`) |
151
- | `<m-tabs>` with `<m-tab>` | `Tabs`, `Tab` | selection (`value`) |
152
- | `<m-progress>`, `<m-loading-indicator>` | `Progress`, `LoadingIndicator` | indicator |
153
- | `<m-badge>`, `<m-divider>` | `Badge`, `Divider` | display |
154
-
155
- More components follow through 0.10.x; the others are available as factories today.
139
+ From the 0.10.0 prereleases (`npm install mtrl@next`), every component also ships as a custom element, with thin adapters for React, Vue, Svelte and Solid. The API may still change before 1.0.
140
+
141
+ | Kind | Elements | Framework components (Vue: `M` prefix, e.g. `MDialog`) |
142
+ |------|----------|--------------------------------------------------------|
143
+ | Actions | `<m-button>`, `<m-icon-button>`, `<m-fab>`, `<m-extended-fab>`, `<m-split-button>`, `<m-button-group>` + `<m-button-group-item>` | `Button`, `IconButton`, `Fab`, `ExtendedFab`, `SplitButton`, `ButtonGroup`, `ButtonGroupItem` |
144
+ | Form controls | `<m-checkbox>`, `<m-switch>`, `<m-radios>` + `<m-radio>`, `<m-slider>`, `<m-textfield>`, `<m-select>` + `<m-select-option>`, `<m-search>` + `<m-search-suggestion>`, `<m-datepicker>`, `<m-timepicker>` | `Checkbox`, `Switch`, `Radios`, `Radio`, `Slider`, `Textfield`, `Select`, `SelectOption`, `Search`, `SearchSuggestion`, `Datepicker`, `Timepicker` |
145
+ | Selection | `<m-chips>` + `<m-chip>`, `<m-tabs>` + `<m-tab>`, `<m-list>` + `<m-list-item>` | `Chips`, `Chip`, `Tabs`, `Tab`, `List`, `ListItem` |
146
+ | Navigation | `<m-navigation-rail>` + `<m-navigation-rail-item>`, `<m-drawer>` + `<m-drawer-item>`, `<m-top-app-bar>`, `<m-bottom-app-bar>` | `NavigationRail`, `NavigationRailItem`, `Drawer`, `DrawerItem`, `TopAppBar`, `BottomAppBar` |
147
+ | Overlays (top layer) | `<m-menu>` + `<m-menu-item>`, `<m-dialog>`, `<m-bottom-sheet>`, `<m-side-sheet>`, `<m-tooltip>`, `<m-snackbar>` | `Menu`, `MenuItem`, `Dialog`, `BottomSheet`, `SideSheet`, `Tooltip`, `Snackbar` |
148
+ | Containment | `<m-card>`, `<m-carousel>` + `<m-carousel-item>` | `Card`, `Carousel`, `CarouselItem` |
149
+ | Indicators | `<m-progress>`, `<m-loading-indicator>`, `<m-badge>`, `<m-divider>` | `Progress`, `LoadingIndicator`, `Badge`, `Divider` |
150
+
151
+ Overlays open in the browser's top layer (`showModal()` or `popover`) from inside their own shadow root, so they keep their styles, trap focus and make the page inert where M3 says so. Form controls are form-associated: `name`, the form value, reset, validation, `<label for>` and back-navigation restore work as on native controls.
156
152
 
157
153
  ```html
158
154
  <script type="module">
package/dist/README.md CHANGED
@@ -136,23 +136,19 @@ Button progress and card actions load on demand; enable code splitting in your b
136
136
 
137
137
  ## Web components and frameworks (experimental)
138
138
 
139
- From the 0.10.0 prereleases (`npm install mtrl@next`), components also ship as custom elements, with thin adapters for React, Vue, Svelte and Solid. The API may still change.
140
-
141
- | Element | Framework component (Vue: `M` prefix, e.g. `MTextfield`) | Kind |
142
- |---------|---------------------|------|
143
- | `<m-button>` | `Button` | action, submits or resets its form |
144
- | `<m-icon-button>` | `IconButton` | action or toggle (`selected`) |
145
- | `<m-fab>`, `<m-extended-fab>` | `Fab`, `ExtendedFab` | action |
146
- | `<m-checkbox>` | `Checkbox` | form control (`checked`, `indeterminate`) |
147
- | `<m-switch>` | `Switch` | form control (`checked`) |
148
- | `<m-radios>` with `<m-radio>` | `Radios`, `Radio` | form control (`value`) |
149
- | `<m-slider>` | `Slider` | form control (`value`, `secondValue` with `range`) |
150
- | `<m-textfield>` | `Textfield` | form control (`value`) |
151
- | `<m-tabs>` with `<m-tab>` | `Tabs`, `Tab` | selection (`value`) |
152
- | `<m-progress>`, `<m-loading-indicator>` | `Progress`, `LoadingIndicator` | indicator |
153
- | `<m-badge>`, `<m-divider>` | `Badge`, `Divider` | display |
154
-
155
- More components follow through 0.10.x; the others are available as factories today.
139
+ From the 0.10.0 prereleases (`npm install mtrl@next`), every component also ships as a custom element, with thin adapters for React, Vue, Svelte and Solid. The API may still change before 1.0.
140
+
141
+ | Kind | Elements | Framework components (Vue: `M` prefix, e.g. `MDialog`) |
142
+ |------|----------|--------------------------------------------------------|
143
+ | Actions | `<m-button>`, `<m-icon-button>`, `<m-fab>`, `<m-extended-fab>`, `<m-split-button>`, `<m-button-group>` + `<m-button-group-item>` | `Button`, `IconButton`, `Fab`, `ExtendedFab`, `SplitButton`, `ButtonGroup`, `ButtonGroupItem` |
144
+ | Form controls | `<m-checkbox>`, `<m-switch>`, `<m-radios>` + `<m-radio>`, `<m-slider>`, `<m-textfield>`, `<m-select>` + `<m-select-option>`, `<m-search>` + `<m-search-suggestion>`, `<m-datepicker>`, `<m-timepicker>` | `Checkbox`, `Switch`, `Radios`, `Radio`, `Slider`, `Textfield`, `Select`, `SelectOption`, `Search`, `SearchSuggestion`, `Datepicker`, `Timepicker` |
145
+ | Selection | `<m-chips>` + `<m-chip>`, `<m-tabs>` + `<m-tab>`, `<m-list>` + `<m-list-item>` | `Chips`, `Chip`, `Tabs`, `Tab`, `List`, `ListItem` |
146
+ | Navigation | `<m-navigation-rail>` + `<m-navigation-rail-item>`, `<m-drawer>` + `<m-drawer-item>`, `<m-top-app-bar>`, `<m-bottom-app-bar>` | `NavigationRail`, `NavigationRailItem`, `Drawer`, `DrawerItem`, `TopAppBar`, `BottomAppBar` |
147
+ | Overlays (top layer) | `<m-menu>` + `<m-menu-item>`, `<m-dialog>`, `<m-bottom-sheet>`, `<m-side-sheet>`, `<m-tooltip>`, `<m-snackbar>` | `Menu`, `MenuItem`, `Dialog`, `BottomSheet`, `SideSheet`, `Tooltip`, `Snackbar` |
148
+ | Containment | `<m-card>`, `<m-carousel>` + `<m-carousel-item>` | `Card`, `Carousel`, `CarouselItem` |
149
+ | Indicators | `<m-progress>`, `<m-loading-indicator>`, `<m-badge>`, `<m-divider>` | `Progress`, `LoadingIndicator`, `Badge`, `Divider` |
150
+
151
+ Overlays open in the browser's top layer (`showModal()` or `popover`) from inside their own shadow root, so they keep their styles, trap focus and make the page inert where M3 says so. Form controls are form-associated: `name`, the form value, reset, validation, `<label for>` and back-navigation restore work as on native controls.
156
152
 
157
153
  ```html
158
154
  <script type="module">
@@ -1,17 +1,24 @@
1
1
  import { createComponentConfig, createElementConfig, } from "../../core/config/component.js";
2
- import { BOTTOM_SHEET_DEFAULTS, BOTTOM_SHEET_STATES, } from "./constants.js";
2
+ import { BOTTOM_SHEET_DEFAULTS, BOTTOM_SHEET_STATES, BOTTOM_SHEET_VARIANTS, } from "./constants.js";
3
+ import { supportsTopLayer } from "../../core/dom/layer.js";
3
4
  export const defaultConfig = {
4
5
  variant: BOTTOM_SHEET_DEFAULTS.VARIANT,
5
6
  dragHandle: BOTTOM_SHEET_DEFAULTS.DRAG_HANDLE,
6
- peekHeight: BOTTOM_SHEET_DEFAULTS.PEEK_HEIGHT,
7
7
  maxWidth: BOTTOM_SHEET_DEFAULTS.MAX_WIDTH,
8
8
  initialState: BOTTOM_SHEET_STATES.HIDDEN,
9
9
  closeOnScrimClick: BOTTOM_SHEET_DEFAULTS.CLOSE_ON_SCRIM_CLICK,
10
10
  closeOnEscape: BOTTOM_SHEET_DEFAULTS.CLOSE_ON_ESCAPE,
11
11
  };
12
- export const createBaseConfig = (config = {}) => createComponentConfig(defaultConfig, config, "bottom-sheet");
12
+ export const createBaseConfig = (config = {}) => {
13
+ const merged = createComponentConfig(defaultConfig, config, "bottom-sheet");
14
+ if (merged.layer === "top" &&
15
+ (merged.variant !== BOTTOM_SHEET_VARIANTS.MODAL || !supportsTopLayer("modal"))) {
16
+ merged.layer = undefined;
17
+ }
18
+ return merged;
19
+ };
13
20
  export const getElementConfig = (config) => createElementConfig(config, {
14
- tag: "div",
21
+ tag: config.layer === "top" ? "dialog" : "div",
15
22
  className: [config.class].filter(Boolean),
16
23
  attributes: {
17
24
  "aria-hidden": "true",
@@ -25,8 +25,6 @@ export declare const BOTTOM_SHEET_STATES: {
25
25
  */
26
26
  export declare const BOTTOM_SHEET_DEFAULTS: {
27
27
  readonly VARIANT: "modal";
28
- /** BottomSheetDefaults.SheetPeekHeight */
29
- readonly PEEK_HEIGHT: 56;
30
28
  /** BottomSheetDefaults.SheetMaxWidth; wider viewports centre the sheet */
31
29
  readonly MAX_WIDTH: 640;
32
30
  /** A drag handle is part of the anatomy, so it is on unless asked otherwise */
@@ -9,7 +9,6 @@ export const BOTTOM_SHEET_STATES = {
9
9
  };
10
10
  export const BOTTOM_SHEET_DEFAULTS = {
11
11
  VARIANT: BOTTOM_SHEET_VARIANTS.MODAL,
12
- PEEK_HEIGHT: 56,
13
12
  MAX_WIDTH: 640,
14
13
  DRAG_HANDLE: true,
15
14
  CLOSE_ON_SCRIM_CLICK: true,
@@ -1,8 +1,11 @@
1
1
  import { BOTTOM_SHEET_CLASSES, BOTTOM_SHEET_EVENTS, BOTTOM_SHEET_STATES, BOTTOM_SHEET_VARIANTS, } from "../constants.js";
2
+ import { deepActiveElement, wrapTab } from "../../../core/dom/focus.js";
3
+ import { hideFromTopLayer, onTopLayerClose, showInTopLayer, } from "../../../core/dom/layer.js";
2
4
  export const withState = (config) => (component) => {
3
5
  const { element, getClass, structure } = component;
4
6
  const root = getClass(BOTTOM_SHEET_CLASSES.ROOT);
5
7
  const isModal = config.variant === BOTTOM_SHEET_VARIANTS.MODAL;
8
+ const top = config.layer === "top";
6
9
  let state = config.initialState ?? BOTTOM_SHEET_STATES.HIDDEN;
7
10
  let previouslyFocused = null;
8
11
  const applyState = (next) => {
@@ -35,12 +38,27 @@ export const withState = (config) => (component) => {
35
38
  if (config.closeOnScrimClick)
36
39
  close();
37
40
  };
41
+ const handleBackdropClick = (event) => {
42
+ if (event.target === element)
43
+ handleScrimClick();
44
+ };
45
+ const handleTab = (event) => wrapTab(element, event);
46
+ const handleCancel = (event) => {
47
+ event.preventDefault();
48
+ if (config.closeOnEscape)
49
+ close();
50
+ };
38
51
  function open(to = BOTTOM_SHEET_STATES.PARTIAL) {
39
52
  if (state === to)
40
53
  return;
41
54
  const wasHidden = state === BOTTOM_SHEET_STATES.HIDDEN;
42
55
  if (wasHidden && isModal) {
43
- previouslyFocused = document.activeElement;
56
+ previouslyFocused = deepActiveElement();
57
+ }
58
+ if (wasHidden && top) {
59
+ if (!showInTopLayer(element, { kind: "modal" }))
60
+ element.setAttribute("open", "");
61
+ void element.offsetWidth;
44
62
  }
45
63
  setState(to);
46
64
  if (wasHidden) {
@@ -53,6 +71,8 @@ export const withState = (config) => (component) => {
53
71
  if (state === BOTTOM_SHEET_STATES.HIDDEN)
54
72
  return;
55
73
  setState(BOTTOM_SHEET_STATES.HIDDEN);
74
+ if (top)
75
+ hideFromTopLayer(element);
56
76
  if (isModal && previouslyFocused?.isConnected) {
57
77
  previouslyFocused.focus();
58
78
  previouslyFocused = null;
@@ -60,8 +80,19 @@ export const withState = (config) => (component) => {
60
80
  component.emit(BOTTOM_SHEET_EVENTS.CLOSE);
61
81
  }
62
82
  applyState(state);
63
- if (config.closeOnEscape)
83
+ let stopCloses = null;
84
+ if (top) {
85
+ element.addEventListener("cancel", handleCancel);
86
+ element.addEventListener("click", handleBackdropClick);
87
+ element.addEventListener("keydown", handleTab);
88
+ stopCloses = onTopLayerClose(element, close);
89
+ if (state !== BOTTOM_SHEET_STATES.HIDDEN && element.isConnected) {
90
+ showInTopLayer(element, { kind: "modal" });
91
+ }
92
+ }
93
+ else if (config.closeOnEscape) {
64
94
  document.addEventListener("keydown", handleKeydown);
95
+ }
65
96
  structure.scrim?.addEventListener("click", handleScrimClick);
66
97
  return {
67
98
  ...component,
@@ -76,6 +107,12 @@ export const withState = (config) => (component) => {
76
107
  release: () => {
77
108
  document.removeEventListener("keydown", handleKeydown);
78
109
  structure.scrim?.removeEventListener("click", handleScrimClick);
110
+ element.removeEventListener("cancel", handleCancel);
111
+ element.removeEventListener("click", handleBackdropClick);
112
+ element.removeEventListener("keydown", handleTab);
113
+ stopCloses?.();
114
+ if (top)
115
+ hideFromTopLayer(element);
79
116
  },
80
117
  },
81
118
  };
@@ -1,4 +1,5 @@
1
1
  import { BOTTOM_SHEET_CLASSES, BOTTOM_SHEET_VARIANTS } from "../constants.js";
2
+ import { PREFIX } from "../../../core/config.js";
2
3
  import { setHTML } from "../../../core/dom/html.js";
3
4
  export const withStructure = (config) => (component) => {
4
5
  const { element, getClass } = component;
@@ -6,7 +7,7 @@ export const withStructure = (config) => (component) => {
6
7
  element.classList.add(getClass(BOTTOM_SHEET_CLASSES.ROOT));
7
8
  element.classList.add(`${getClass(BOTTOM_SHEET_CLASSES.ROOT)}--${config.variant}`);
8
9
  let scrim = null;
9
- if (isModal) {
10
+ if (isModal && config.layer !== "top") {
10
11
  scrim = document.createElement("div");
11
12
  scrim.className = getClass(BOTTOM_SHEET_CLASSES.SCRIM);
12
13
  element.appendChild(scrim);
@@ -20,6 +21,9 @@ export const withStructure = (config) => (component) => {
20
21
  if (config.maxWidth) {
21
22
  container.style.maxWidth = `${config.maxWidth}px`;
22
23
  }
24
+ if (config.peekHeight) {
25
+ container.style.setProperty(`--${PREFIX}-bottom-sheet-peek-height`, `${config.peekHeight}px`);
26
+ }
23
27
  let handle = null;
24
28
  if (config.dragHandle) {
25
29
  handle = document.createElement("div");
@@ -44,8 +44,9 @@ export interface BottomSheetConfig {
44
44
  */
45
45
  dragHandle?: boolean;
46
46
  /**
47
- * Height of the partially expanded state, in pixels.
48
- * @default 56
47
+ * Height of the partially expanded state, in pixels: a sheet with more
48
+ * content shows this much of it until it is expanded. Unset, the partial
49
+ * sheet shows its content up to half the screen.
49
50
  */
50
51
  peekHeight?: number;
51
52
  /**
@@ -72,6 +73,18 @@ export interface BottomSheetConfig {
72
73
  closeOnEscape?: boolean;
73
74
  /** Where to mount the sheet. Defaults to `document.body` */
74
75
  container?: HTMLElement;
76
+ /**
77
+ * `"top"` makes a modal sheet's root a `<dialog>` element, rendered in
78
+ * `container` like any sheet, and opens it with `showModal()`: in the
79
+ * browser's top layer, above every z-index and outside any clipping
80
+ * ancestor, with the rest of the page inert. The scrim is its `::backdrop`,
81
+ * and Escape reaches the sheet as the dialog's `cancel` event. The sheet
82
+ * slides as without it.
83
+ *
84
+ * A standard sheet stays in the page, and where the browser has no
85
+ * `showModal()` the sheet behaves as without it.
86
+ */
87
+ layer?: "top";
75
88
  /** Handlers registered at creation, equivalent to calling `on` for each */
76
89
  on?: BottomSheetEventHandlers;
77
90
  /** Extra classes for the root element */
@@ -1,6 +1,7 @@
1
1
  import { getCleanup } from "../../../core/compose/cleanup.js";
2
2
  import createChip from "../chip/chip.js";
3
3
  import { CHIPS_EVENTS } from "../constants.js";
4
+ import { activeElementOf } from "../../../core/dom/focus.js";
4
5
  export const withController = (config) => (component) => {
5
6
  const eventListeners = {
6
7
  change: [],
@@ -157,7 +158,7 @@ export const withController = (config) => (component) => {
157
158
  : component.chipInstances.indexOf(chipOrIndex);
158
159
  if (index >= 0 && index < component.chipInstances.length) {
159
160
  const chip = component.chipInstances[index];
160
- const hadFocus = chip.element.contains(document.activeElement);
161
+ const hadFocus = chip.element.contains(activeElementOf(chip.element));
161
162
  dispatchEvent(CHIPS_EVENTS.REMOVE, chip);
162
163
  chip.element.removeEventListener("keydown", handleKeyboardNavigation);
163
164
  chip.destroy();
@@ -3,6 +3,7 @@ import { createBase, withElement } from "../../core/compose/component.js";
3
3
  import { withEvents, withDisabled, withLifecycle } from "../../core/compose/features/index.js";
4
4
  import { createElement } from "../../core/dom/create.js";
5
5
  import { setHTML } from "../../core/dom/html.js";
6
+ import { activeElementOf, deepActiveElement } from "../../core/dom/focus.js";
6
7
  import { DATEPICKER_ICONS } from "./constants.js";
7
8
  import { PREFIX } from "../../core/config.js";
8
9
  import { createBaseConfig, getContainerConfig } from "./config.js";
@@ -144,7 +145,8 @@ const createDatePicker = (config = {}) => {
144
145
  }
145
146
  else
146
147
  return;
147
- const active = doc.activeElement instanceof HTMLElement ? doc.activeElement.dataset.date : undefined;
148
+ const focused = activeElementOf(dialog);
149
+ const active = focused instanceof HTMLElement ? focused.dataset.date : undefined;
148
150
  render();
149
151
  if (active)
150
152
  dialog.querySelector(`[data-date="${active}"]`)?.focus({ preventScroll: true });
@@ -215,7 +217,8 @@ const createDatePicker = (config = {}) => {
215
217
  if (!navigationRequested)
216
218
  setDisplayDate(initial);
217
219
  navigationRequested = false;
218
- returnFocus = doc.activeElement instanceof HTMLElement && doc.activeElement !== doc.body ? doc.activeElement : trigger;
220
+ const opener = deepActiveElement();
221
+ returnFocus = opener instanceof HTMLElement && opener !== doc.body ? opener : trigger;
219
222
  render();
220
223
  if (modal && typeof dialog.showModal === "function" && dialog.isConnected)
221
224
  dialog.showModal();
@@ -407,11 +410,12 @@ const createDatePicker = (config = {}) => {
407
410
  if (event.key === "Tab" && modal) {
408
411
  const elements = Array.from(dialog.querySelectorAll('button:not(:disabled):not([tabindex="-1"]), input:not(:disabled)'));
409
412
  const first = elements[0], last = elements.at(-1);
410
- if (event.shiftKey && doc.activeElement === first) {
413
+ const focused = activeElementOf(dialog);
414
+ if (event.shiftKey && focused === first) {
411
415
  event.preventDefault();
412
416
  last?.focus();
413
417
  }
414
- else if (!event.shiftKey && doc.activeElement === last) {
418
+ else if (!event.shiftKey && focused === last) {
415
419
  event.preventDefault();
416
420
  first?.focus();
417
421
  }
@@ -485,7 +489,8 @@ const createDatePicker = (config = {}) => {
485
489
  const page = Math.round(Math.abs(element.scrollLeft) / element.clientWidth) - 1;
486
490
  if (!page)
487
491
  return;
488
- const active = doc.activeElement instanceof HTMLElement && dialog.contains(doc.activeElement) ? doc.activeElement : null;
492
+ const focused = activeElementOf(dialog);
493
+ const active = focused instanceof HTMLElement && dialog.contains(focused) ? focused : null;
489
494
  navigate(page);
490
495
  render(!!active?.dataset.date, active?.dataset.action);
491
496
  };
@@ -1,4 +1,5 @@
1
1
  import { removeClass } from "../../core/dom/classes.js";
2
+ import { hideFromTopLayer } from "../../core/dom/layer.js";
2
3
  export const withAPI = (options) => (component) => ({
3
4
  ...component,
4
5
  open() {
@@ -92,6 +93,7 @@ export const withAPI = (options) => (component) => ({
92
93
  const overlayVisibleClass = `${component.getClass("dialog__overlay")}--visible`;
93
94
  removeClass(component.element, dialogVisibleClass);
94
95
  removeClass(component.overlay, overlayVisibleClass);
96
+ hideFromTopLayer(component.element);
95
97
  if (options.focus && options.focus.releaseFocus) {
96
98
  options.focus.releaseFocus();
97
99
  }
@@ -100,6 +102,9 @@ export const withAPI = (options) => (component) => ({
100
102
  if (component.overlay && component.overlay.parentNode) {
101
103
  component.overlay.parentNode.removeChild(component.overlay);
102
104
  }
105
+ if (component.element.parentNode !== component.overlay) {
106
+ component.element.remove();
107
+ }
103
108
  },
104
109
  element: component.element,
105
110
  overlay: component.overlay,
@@ -1,4 +1,5 @@
1
1
  import { createComponentConfig, createElementConfig } from "../../core/config/component.js";
2
+ import { supportsTopLayer } from "../../core/dom/layer.js";
2
3
  export const defaultConfig = {
3
4
  size: 'medium',
4
5
  animation: 'scale',
@@ -12,7 +13,12 @@ export const defaultConfig = {
12
13
  divider: false,
13
14
  buttons: []
14
15
  };
15
- export const createBaseConfig = (config = {}) => createComponentConfig(defaultConfig, config, 'dialog');
16
+ export const createBaseConfig = (config = {}) => {
17
+ const merged = createComponentConfig(defaultConfig, config, 'dialog');
18
+ if (merged.layer === 'top' && !supportsTopLayer('modal'))
19
+ merged.layer = undefined;
20
+ return merged;
21
+ };
16
22
  export const getElementConfig = (config) => {
17
23
  const role = config.role ||
18
24
  (config.size === 'fullscreen' ? 'dialog' : 'alertdialog');
@@ -24,7 +30,7 @@ export const getElementConfig = (config) => {
24
30
  attributes['aria-modal'] = 'true';
25
31
  }
26
32
  return createElementConfig(config, {
27
- tag: 'div',
33
+ tag: config.layer === 'top' ? 'dialog' : 'div',
28
34
  attributes,
29
35
  className: config.class
30
36
  });
@@ -3,6 +3,8 @@ import createButton from "../button/index.js";
3
3
  import { createDivider } from "../divider/index.js";
4
4
  import { addClass, removeClass } from "../../core/dom/classes.js";
5
5
  import { setHTML } from "../../core/dom/html.js";
6
+ import { activeElementOf, deepActiveElement, tabStops, wrapTab } from "../../core/dom/focus.js";
7
+ import { hideFromTopLayer, onTopLayerClose, showInTopLayer } from "../../core/dom/layer.js";
6
8
  const DIALOG_EVENTS = {
7
9
  OPEN: "open",
8
10
  CLOSE: "close",
@@ -141,9 +143,14 @@ export const withStructure = (config, getComponent) => (component) => {
141
143
  if (footer) {
142
144
  component.element.appendChild(footer);
143
145
  }
144
- overlay.appendChild(component.element);
145
146
  const container = config.container || document.body;
146
- container.appendChild(overlay);
147
+ if (config.layer === "top") {
148
+ container.appendChild(component.element);
149
+ }
150
+ else {
151
+ overlay.appendChild(component.element);
152
+ container.appendChild(overlay);
153
+ }
147
154
  return {
148
155
  ...component,
149
156
  overlay,
@@ -239,6 +246,8 @@ export const withVisibility = (getComponent) => (component) => {
239
246
  const isOpen = component.config.open === true;
240
247
  const openDuration = component.config.animationDuration ?? 500;
241
248
  const closeDuration = component.config.animationDuration ?? 150;
249
+ const top = component.config.layer === "top";
250
+ const scrim = top ? component.element : component.overlay;
242
251
  const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
243
252
  let previouslyFocusedElement = null;
244
253
  let mouseDownOnOverlay = false;
@@ -246,7 +255,7 @@ export const withVisibility = (getComponent) => (component) => {
246
255
  let scrollLock = null;
247
256
  const focusable = () => Array.from(component.element.querySelectorAll(focusableElements)).filter((el) => !el.hasAttribute("disabled") &&
248
257
  el.getAttribute("aria-hidden") !== "true" &&
249
- (el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement));
258
+ (el.offsetWidth > 0 || el.offsetHeight > 0 || el === activeElementOf(el)));
250
259
  function handleTabKey(e) {
251
260
  if (e.key !== "Tab")
252
261
  return;
@@ -258,7 +267,7 @@ export const withVisibility = (getComponent) => (component) => {
258
267
  }
259
268
  const first = elements[0];
260
269
  const last = elements[elements.length - 1];
261
- const active = document.activeElement;
270
+ const active = activeElementOf(component.element);
262
271
  if (e.shiftKey && (active === first || !component.element.contains(active))) {
263
272
  last.focus();
264
273
  e.preventDefault();
@@ -271,7 +280,7 @@ export const withVisibility = (getComponent) => (component) => {
271
280
  const inertBackground = () => {
272
281
  if (component.config.modal === false)
273
282
  return;
274
- const parent = component.overlay.parentElement;
283
+ const parent = top ? null : component.overlay.parentElement;
275
284
  if (parent) {
276
285
  Array.from(parent.children).forEach((sibling) => {
277
286
  if (sibling === component.overlay)
@@ -297,19 +306,21 @@ export const withVisibility = (getComponent) => (component) => {
297
306
  scrollLock = null;
298
307
  }
299
308
  };
309
+ const handleWrap = (e) => wrapTab(component.element, e);
300
310
  const trapFocus = () => {
301
311
  inertBackground();
302
312
  if (component.config.autofocus !== false) {
303
313
  const requested = component.element.querySelector("[autofocus]");
304
- const target = requested || focusable()[0] || component.element;
314
+ const target = requested || (top ? tabStops(component.element) : focusable())[0] || component.element;
305
315
  target.focus();
306
316
  }
307
317
  if (component.config.trapFocus !== false) {
308
- component.element.addEventListener("keydown", handleTabKey);
318
+ component.element.addEventListener("keydown", top ? handleWrap : handleTabKey);
309
319
  }
310
320
  };
311
321
  const releaseFocus = () => {
312
322
  component.element.removeEventListener("keydown", handleTabKey);
323
+ component.element.removeEventListener("keydown", handleWrap);
313
324
  releaseBackground();
314
325
  if (previouslyFocusedElement) {
315
326
  if (previouslyFocusedElement.isConnected)
@@ -319,33 +330,61 @@ export const withVisibility = (getComponent) => (component) => {
319
330
  };
320
331
  const setupEvents = () => {
321
332
  if (component.config.closeOnOverlayClick !== false) {
322
- component.overlay.addEventListener("mousedown", handleOverlayMouseDown);
333
+ scrim.addEventListener("mousedown", handleOverlayMouseDown);
323
334
  document.addEventListener("mouseup", handleOverlayMouseUp);
324
335
  }
325
- if (component.config.closeOnEscape !== false) {
336
+ if (top) {
337
+ component.element.addEventListener("cancel", handleCancel);
338
+ }
339
+ else if (component.config.closeOnEscape !== false) {
326
340
  document.addEventListener("keydown", handleEscKey);
327
341
  }
328
342
  };
329
343
  const cleanupEvents = () => {
330
- component.overlay.removeEventListener("mousedown", handleOverlayMouseDown);
344
+ scrim.removeEventListener("mousedown", handleOverlayMouseDown);
331
345
  document.removeEventListener("mouseup", handleOverlayMouseUp);
332
346
  document.removeEventListener("keydown", handleEscKey);
347
+ component.element.removeEventListener("cancel", handleCancel);
348
+ };
349
+ const onScrim = (e) => {
350
+ if (!top)
351
+ return e.target === component.overlay;
352
+ if (e.composedPath()[0] !== scrim)
353
+ return false;
354
+ const box = scrim.getBoundingClientRect();
355
+ return e.clientX < box.left || e.clientX > box.right || e.clientY < box.top || e.clientY > box.bottom;
333
356
  };
334
357
  function handleOverlayMouseDown(e) {
335
- mouseDownOnOverlay = e.target === component.overlay;
358
+ mouseDownOnOverlay = onScrim(e);
336
359
  }
337
360
  function handleOverlayMouseUp(e) {
338
- if (mouseDownOnOverlay && e.target === component.overlay) {
361
+ if (mouseDownOnOverlay && onScrim(e)) {
339
362
  visibility.close();
340
363
  }
341
364
  mouseDownOnOverlay = false;
342
365
  }
366
+ function handleCancel(e) {
367
+ e.preventDefault();
368
+ if (component.config.closeOnEscape !== false && visibility.isOpen()) {
369
+ visibility.close();
370
+ }
371
+ }
372
+ const showModal = () => {
373
+ if (!component.element.isConnected) {
374
+ component.structure.container.appendChild(component.element);
375
+ }
376
+ if (!component.element.isConnected || !showInTopLayer(component.element, { kind: "modal" })) {
377
+ component.element.setAttribute("open", "");
378
+ }
379
+ };
343
380
  function handleEscKey(e) {
344
381
  if (e.key === "Escape" && visibility.isOpen()) {
345
382
  visibility.close();
346
383
  }
347
384
  }
348
385
  if (isOpen) {
386
+ if (top)
387
+ showModal();
349
388
  addClass(component.overlay, `${component.getClass("dialog__overlay")}--visible`);
350
389
  addClass(component.element, `${component.getClass("dialog")}--visible`);
351
390
  trapFocus();
@@ -355,7 +394,7 @@ export const withVisibility = (getComponent) => (component) => {
355
394
  open() {
356
395
  if (this.isOpen())
357
396
  return;
358
- previouslyFocusedElement = document.activeElement;
397
+ previouslyFocusedElement = deepActiveElement();
359
398
  const beforeOpenEvent = {
360
399
  dialog: getComponent(),
361
400
  defaultPrevented: false,
@@ -368,6 +407,18 @@ export const withVisibility = (getComponent) => (component) => {
368
407
  }
369
408
  if (beforeOpenEvent.defaultPrevented)
370
409
  return;
410
+ if (top) {
411
+ showModal();
412
+ void component.element.offsetWidth;
413
+ addClass(component.element, `${component.getClass("dialog")}--visible`);
414
+ trapFocus();
415
+ setupEvents();
416
+ component.emit(DIALOG_EVENTS.OPEN, { dialog: getComponent() });
417
+ setTimeout(() => {
418
+ component.emit(DIALOG_EVENTS.AFTER_OPEN, { dialog: getComponent() });
419
+ }, openDuration);
420
+ return;
421
+ }
371
422
  if (component.overlay && !component.overlay.parentNode) {
372
423
  const container = component.structure.container || document.body;
373
424
  container.appendChild(component.overlay);
@@ -403,13 +454,15 @@ export const withVisibility = (getComponent) => (component) => {
403
454
  const overlayVisibleClass = `${component.getClass("dialog__overlay")}--visible`;
404
455
  removeClass(component.element, dialogVisibleClass);
405
456
  removeClass(component.overlay, overlayVisibleClass);
457
+ if (top)
458
+ hideFromTopLayer(component.element);
406
459
  releaseFocus();
407
460
  cleanupEvents();
408
461
  if (typeof component.emit === "function") {
409
462
  component.emit(DIALOG_EVENTS.CLOSE, { dialog: getComponent() });
410
463
  }
411
464
  setTimeout(() => {
412
- if (component.overlay && component.overlay.parentNode) {
465
+ if (!top && component.overlay && component.overlay.parentNode) {
413
466
  component.overlay.parentNode.removeChild(component.overlay);
414
467
  }
415
468
  if (typeof component.emit === "function") {
@@ -437,6 +490,12 @@ export const withVisibility = (getComponent) => (component) => {
437
490
  visibility.close();
438
491
  });
439
492
  }
493
+ if (top) {
494
+ onTopLayerClose(component.element, () => {
495
+ if (visibility.isOpen())
496
+ visibility.close();
497
+ });
498
+ }
440
499
  return {
441
500
  ...component,
442
501
  visibility,
@@ -151,6 +151,17 @@ export interface DialogConfig {
151
151
  * @default document.body
152
152
  */
153
153
  container?: HTMLElement;
154
+ /**
155
+ * `"top"` makes the dialog itself a `<dialog>` element, rendered in place in
156
+ * `container` (or `document.body`) and kept there when it closes, and opens
157
+ * it with `showModal()`: in the browser's top layer, above every z-index and
158
+ * outside any clipping ancestor, with the rest of the page inert. The scrim
159
+ * is its `::backdrop`, and the overlay element is not used. Escape reaches
160
+ * the dialog as its `cancel` event.
161
+ *
162
+ * Where the browser has no `showModal()`, the dialog behaves as without it.
163
+ */
164
+ layer?: "top";
154
165
  /**
155
166
  * Footer buttons configuration
156
167
  * Array of button objects to display in the footer