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.
- package/README.md +13 -17
- package/dist/README.md +13 -17
- package/dist/components/bottom-sheet/config.js +11 -4
- package/dist/components/bottom-sheet/constants.d.ts +0 -2
- package/dist/components/bottom-sheet/constants.js +0 -1
- package/dist/components/bottom-sheet/features/state.js +39 -2
- package/dist/components/bottom-sheet/features/structure.js +5 -1
- package/dist/components/bottom-sheet/types.d.ts +15 -2
- package/dist/components/chips/features/controller.js +2 -1
- package/dist/components/datepicker/datepicker.js +10 -5
- package/dist/components/dialog/api.js +5 -0
- package/dist/components/dialog/config.js +8 -2
- package/dist/components/dialog/features.js +73 -14
- package/dist/components/dialog/types.d.ts +11 -0
- package/dist/components/drawer/config.js +9 -2
- package/dist/components/drawer/features/items.js +2 -1
- package/dist/components/drawer/features/state.js +50 -9
- package/dist/components/drawer/types.d.ts +12 -0
- package/dist/components/list/features/renderer.js +2 -1
- package/dist/components/menu/config.js +6 -1
- package/dist/components/menu/features/controller.js +23 -8
- package/dist/components/menu/features/keyboard.js +3 -2
- package/dist/components/menu/features/layer.d.ts +15 -0
- package/dist/components/menu/features/layer.js +16 -0
- package/dist/components/menu/features/opener.js +2 -1
- package/dist/components/menu/features/position.js +8 -6
- package/dist/components/menu/features/submenu.js +12 -4
- package/dist/components/menu/types.d.ts +12 -0
- package/dist/components/navigation-rail/navigation-rail.js +4 -3
- package/dist/components/search/api.d.ts +9 -1
- package/dist/components/search/api.js +16 -8
- package/dist/components/search/config.d.ts +17 -1
- package/dist/components/search/config.js +10 -1
- package/dist/components/search/constants.d.ts +11 -0
- package/dist/components/search/constants.js +9 -0
- package/dist/components/search/features/input.js +12 -3
- package/dist/components/search/features/states.d.ts +4 -2
- package/dist/components/search/features/states.js +98 -16
- package/dist/components/search/features/structure.d.ts +7 -20
- package/dist/components/search/features/structure.js +79 -29
- package/dist/components/search/features/suggestions.js +22 -6
- package/dist/components/search/index.d.ts +1 -1
- package/dist/components/search/types.d.ts +23 -0
- package/dist/components/select/features.js +6 -3
- package/dist/components/select/types.d.ts +7 -0
- package/dist/components/side-sheet/config.js +11 -3
- package/dist/components/side-sheet/features/state.js +38 -2
- package/dist/components/side-sheet/features/structure.js +1 -1
- package/dist/components/side-sheet/types.d.ts +12 -0
- package/dist/components/slider/features/handlers.js +2 -1
- package/dist/components/snackbar/api.js +28 -4
- package/dist/components/snackbar/config.d.ts +1 -0
- package/dist/components/snackbar/config.js +8 -1
- package/dist/components/snackbar/layer.d.ts +23 -0
- package/dist/components/snackbar/layer.js +85 -0
- package/dist/components/snackbar/types.d.ts +12 -0
- package/dist/components/split-button/features/menu.js +1 -0
- package/dist/components/split-button/types.d.ts +6 -0
- package/dist/components/timepicker/api.js +68 -18
- package/dist/components/timepicker/config.d.ts +0 -1082
- package/dist/components/timepicker/config.js +3 -35
- package/dist/components/timepicker/constants.d.ts +21 -11
- package/dist/components/timepicker/constants.js +4 -3
- package/dist/components/timepicker/dial.d.ts +5 -0
- package/dist/components/timepicker/dial.js +8 -2
- package/dist/components/timepicker/render.js +91 -103
- package/dist/components/timepicker/timepicker.js +2 -2
- package/dist/components/timepicker/types.d.ts +38 -9
- package/dist/components/timepicker/utils.d.ts +18 -51
- package/dist/components/timepicker/utils.js +36 -86
- package/dist/components/tooltip/api.js +34 -3
- package/dist/components/tooltip/config.js +8 -1
- package/dist/components/tooltip/types.d.ts +12 -0
- package/dist/core/dom/focus.d.ts +36 -0
- package/dist/core/dom/focus.js +47 -0
- package/dist/core/dom/index.d.ts +3 -0
- package/dist/core/dom/index.js +2 -0
- package/dist/core/dom/layer.d.ts +58 -0
- package/dist/core/dom/layer.js +52 -0
- package/dist/elements/bottom-app-bar.d.ts +46 -0
- package/dist/elements/bottom-app-bar.js +42 -0
- package/dist/elements/bottom-sheet.d.ts +121 -0
- package/dist/elements/bottom-sheet.js +95 -0
- package/dist/elements/button-group.d.ts +153 -0
- package/dist/elements/button-group.js +174 -0
- package/dist/elements/card.d.ts +68 -0
- package/dist/elements/card.js +112 -0
- package/dist/elements/carousel.d.ts +123 -0
- package/dist/elements/carousel.js +117 -0
- package/dist/elements/chips.d.ts +136 -0
- package/dist/elements/chips.js +214 -0
- package/dist/elements/css/bottom-app-bar.d.ts +1 -0
- package/dist/elements/css/bottom-app-bar.js +3 -0
- package/dist/elements/css/bottom-sheet.d.ts +1 -0
- package/dist/elements/css/bottom-sheet.js +3 -0
- package/dist/elements/css/button-group.d.ts +1 -0
- package/dist/elements/css/button-group.js +5 -0
- package/dist/elements/css/card.d.ts +1 -0
- package/dist/elements/css/card.js +4 -0
- package/dist/elements/css/carousel.d.ts +1 -0
- package/dist/elements/css/carousel.js +3 -0
- package/dist/elements/css/chips.d.ts +1 -0
- package/dist/elements/css/chips.js +3 -0
- package/dist/elements/css/datepicker.d.ts +1 -0
- package/dist/elements/css/datepicker.js +3 -0
- package/dist/elements/css/dialog.d.ts +1 -0
- package/dist/elements/css/dialog.js +5 -0
- package/dist/elements/css/drawer.d.ts +1 -0
- package/dist/elements/css/drawer.js +3 -0
- package/dist/elements/css/index.js +20 -0
- package/dist/elements/css/list.d.ts +1 -0
- package/dist/elements/css/list.js +3 -0
- package/dist/elements/css/menu.d.ts +1 -0
- package/dist/elements/css/menu.js +3 -0
- package/dist/elements/css/navigation-rail.d.ts +1 -0
- package/dist/elements/css/navigation-rail.js +3 -0
- package/dist/elements/css/search.d.ts +1 -0
- package/dist/elements/css/search.js +3 -0
- package/dist/elements/css/select.d.ts +1 -0
- package/dist/elements/css/select.js +5 -0
- package/dist/elements/css/side-sheet.d.ts +1 -0
- package/dist/elements/css/side-sheet.js +3 -0
- package/dist/elements/css/snackbar.d.ts +1 -0
- package/dist/elements/css/snackbar.js +5 -0
- package/dist/elements/css/split-button.d.ts +1 -0
- package/dist/elements/css/split-button.js +5 -0
- package/dist/elements/css/timepicker.d.ts +1 -0
- package/dist/elements/css/timepicker.js +3 -0
- package/dist/elements/css/tooltip.d.ts +1 -0
- package/dist/elements/css/tooltip.js +3 -0
- package/dist/elements/css/top-app-bar.d.ts +1 -0
- package/dist/elements/css/top-app-bar.js +3 -0
- package/dist/elements/datepicker.d.ts +155 -0
- package/dist/elements/datepicker.js +150 -0
- package/dist/elements/define.d.ts +7 -0
- package/dist/elements/define.js +21 -9
- package/dist/elements/dialog.d.ts +118 -0
- package/dist/elements/dialog.js +155 -0
- package/dist/elements/drawer.d.ts +137 -0
- package/dist/elements/drawer.js +136 -0
- package/dist/elements/index.d.ts +306 -0
- package/dist/elements/index.js +89 -0
- package/dist/elements/list.d.ts +135 -0
- package/dist/elements/list.js +191 -0
- package/dist/elements/menu.d.ts +165 -0
- package/dist/elements/menu.js +178 -0
- package/dist/elements/navigation-rail.d.ts +153 -0
- package/dist/elements/navigation-rail.js +129 -0
- package/dist/elements/search.d.ts +215 -0
- package/dist/elements/search.js +215 -0
- package/dist/elements/select.d.ts +134 -0
- package/dist/elements/select.js +149 -0
- package/dist/elements/sheet.d.ts +44 -0
- package/dist/elements/sheet.js +60 -0
- package/dist/elements/side-sheet.d.ts +91 -0
- package/dist/elements/side-sheet.js +63 -0
- package/dist/elements/snackbar.d.ts +99 -0
- package/dist/elements/snackbar.js +54 -0
- package/dist/elements/split-button.d.ts +82 -0
- package/dist/elements/split-button.js +77 -0
- package/dist/elements/timepicker.d.ts +158 -0
- package/dist/elements/timepicker.js +159 -0
- package/dist/elements/tooltip.d.ts +89 -0
- package/dist/elements/tooltip.js +132 -0
- package/dist/elements/top-app-bar.d.ts +63 -0
- package/dist/elements/top-app-bar.js +94 -0
- package/dist/index.cjs +0 -0
- package/dist/package.json +1 -1
- package/dist/react/index.d.ts +50 -1
- package/dist/react/index.js +50 -1
- package/dist/solid/index.d.ts +50 -1
- package/dist/solid/index.js +50 -1
- package/dist/styles/badge.css +1 -1
- package/dist/styles/base.css +1 -1
- package/dist/styles/bottom-app-bar.css +1 -1
- package/dist/styles/bottom-sheet.css +2 -2
- package/dist/styles/button-group.css +1 -1
- package/dist/styles/button.css +1 -1
- package/dist/styles/card.css +1 -1
- package/dist/styles/carousel.css +1 -1
- package/dist/styles/checkbox.css +1 -1
- package/dist/styles/chips.css +1 -1
- package/dist/styles/datepicker.css +1 -1
- package/dist/styles/dialog.css +2 -2
- package/dist/styles/divider.css +1 -1
- package/dist/styles/drawer.css +2 -2
- package/dist/styles/extended-fab.css +1 -1
- package/dist/styles/fab.css +1 -1
- package/dist/styles/icon-button.css +1 -1
- package/dist/styles/list.css +1 -1
- package/dist/styles/loading-indicator.css +1 -1
- package/dist/styles/menu.css +2 -2
- package/dist/styles/navigation-rail.css +1 -1
- package/dist/styles/progress.css +1 -1
- package/dist/styles/radios.css +1 -1
- package/dist/styles/search.css +2 -2
- package/dist/styles/segmented-button.css +1 -1
- package/dist/styles/select.css +1 -1
- package/dist/styles/side-sheet.css +2 -2
- package/dist/styles/slider.css +1 -1
- package/dist/styles/snackbar.css +2 -2
- package/dist/styles/split-button.css +1 -1
- package/dist/styles/switch.css +1 -1
- package/dist/styles/tabs.css +1 -1
- package/dist/styles/textfield.css +1 -1
- package/dist/styles/timepicker.css +2 -2
- package/dist/styles/tooltip.css +2 -2
- package/dist/styles/top-app-bar.css +1 -1
- package/dist/styles/utilities.css +1 -1
- package/dist/styles.css +2 -2
- package/dist/svelte/BottomAppBar.svelte +19 -0
- package/dist/svelte/BottomAppBar.svelte.d.ts +5 -0
- package/dist/svelte/BottomSheet.svelte +19 -0
- package/dist/svelte/BottomSheet.svelte.d.ts +5 -0
- package/dist/svelte/ButtonGroup.svelte +20 -0
- package/dist/svelte/ButtonGroup.svelte.d.ts +5 -0
- package/dist/svelte/ButtonGroupItem.svelte +11 -0
- package/dist/svelte/ButtonGroupItem.svelte.d.ts +5 -0
- package/dist/svelte/Card.svelte +19 -0
- package/dist/svelte/Card.svelte.d.ts +5 -0
- package/dist/svelte/Carousel.svelte +20 -0
- package/dist/svelte/Carousel.svelte.d.ts +5 -0
- package/dist/svelte/CarouselItem.svelte +11 -0
- package/dist/svelte/CarouselItem.svelte.d.ts +5 -0
- package/dist/svelte/Chip.svelte +11 -0
- package/dist/svelte/Chip.svelte.d.ts +5 -0
- package/dist/svelte/Chips.svelte +20 -0
- package/dist/svelte/Chips.svelte.d.ts +5 -0
- package/dist/svelte/Datepicker.svelte +20 -0
- package/dist/svelte/Datepicker.svelte.d.ts +5 -0
- package/dist/svelte/Dialog.svelte +19 -0
- package/dist/svelte/Dialog.svelte.d.ts +5 -0
- package/dist/svelte/Drawer.svelte +20 -0
- package/dist/svelte/Drawer.svelte.d.ts +5 -0
- package/dist/svelte/DrawerItem.svelte +11 -0
- package/dist/svelte/DrawerItem.svelte.d.ts +5 -0
- package/dist/svelte/List.svelte +21 -0
- package/dist/svelte/List.svelte.d.ts +5 -0
- package/dist/svelte/ListItem.svelte +11 -0
- package/dist/svelte/ListItem.svelte.d.ts +5 -0
- package/dist/svelte/Menu.svelte +20 -0
- package/dist/svelte/Menu.svelte.d.ts +5 -0
- package/dist/svelte/MenuItem.svelte +11 -0
- package/dist/svelte/MenuItem.svelte.d.ts +5 -0
- package/dist/svelte/NavigationRail.svelte +20 -0
- package/dist/svelte/NavigationRail.svelte.d.ts +5 -0
- package/dist/svelte/NavigationRailItem.svelte +11 -0
- package/dist/svelte/NavigationRailItem.svelte.d.ts +5 -0
- package/dist/svelte/Search.svelte +20 -0
- package/dist/svelte/Search.svelte.d.ts +5 -0
- package/dist/svelte/SearchSuggestion.svelte +11 -0
- package/dist/svelte/SearchSuggestion.svelte.d.ts +5 -0
- package/dist/svelte/Select.svelte +20 -0
- package/dist/svelte/Select.svelte.d.ts +5 -0
- package/dist/svelte/SelectOption.svelte +11 -0
- package/dist/svelte/SelectOption.svelte.d.ts +5 -0
- package/dist/svelte/SideSheet.svelte +19 -0
- package/dist/svelte/SideSheet.svelte.d.ts +5 -0
- package/dist/svelte/Snackbar.svelte +20 -0
- package/dist/svelte/Snackbar.svelte.d.ts +5 -0
- package/dist/svelte/SplitButton.svelte +19 -0
- package/dist/svelte/SplitButton.svelte.d.ts +5 -0
- package/dist/svelte/Timepicker.svelte +20 -0
- package/dist/svelte/Timepicker.svelte.d.ts +5 -0
- package/dist/svelte/Tooltip.svelte +20 -0
- package/dist/svelte/Tooltip.svelte.d.ts +5 -0
- package/dist/svelte/TopAppBar.svelte +19 -0
- package/dist/svelte/TopAppBar.svelte.d.ts +5 -0
- package/dist/svelte/index.d.ts +29 -0
- package/dist/svelte/index.js +49 -0
- package/dist/themes/autumn.css +1 -1
- package/dist/themes/baseline.css +1 -1
- package/dist/themes/brownbeige.css +1 -1
- package/dist/themes/browngreen.css +1 -1
- package/dist/themes/desert.css +1 -1
- package/dist/themes/forest.css +1 -1
- package/dist/themes/highcontrast.css +1 -1
- package/dist/themes/legacy.css +1 -1
- package/dist/themes/material.css +1 -1
- package/dist/themes/ocean.css +1 -1
- package/dist/themes/sageivory.css +1 -1
- package/dist/themes/spring.css +1 -1
- package/dist/themes/summer.css +1 -1
- package/dist/themes/sunset.css +1 -1
- package/dist/themes/tealcaramel.css +1 -1
- package/dist/themes/winter.css +1 -1
- package/dist/vue/index.d.ts +50 -1
- package/dist/vue/index.js +50 -1
- package/package.json +1 -1
- package/src/styles/components/_bottom-sheet.scss +42 -2
- package/src/styles/components/_dialog.scss +52 -0
- package/src/styles/components/_drawer.scss +42 -0
- package/src/styles/components/_menu.scss +9 -0
- package/src/styles/components/_search.scss +278 -273
- package/src/styles/components/_side-sheet.scss +41 -0
- package/src/styles/components/_snackbar.scss +12 -0
- package/src/styles/components/_timepicker.scss +116 -139
- 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`),
|
|
140
|
-
|
|
141
|
-
|
|
|
142
|
-
|
|
143
|
-
| `<m-button>` | `Button`
|
|
144
|
-
| `<m-
|
|
145
|
-
| `<m-
|
|
146
|
-
| `<m-
|
|
147
|
-
| `<m-
|
|
148
|
-
| `<m-
|
|
149
|
-
| `<m-
|
|
150
|
-
|
|
151
|
-
|
|
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`),
|
|
140
|
-
|
|
141
|
-
|
|
|
142
|
-
|
|
143
|
-
| `<m-button>` | `Button`
|
|
144
|
-
| `<m-
|
|
145
|
-
| `<m-
|
|
146
|
-
| `<m-
|
|
147
|
-
| `<m-
|
|
148
|
-
| `<m-
|
|
149
|
-
| `<m-
|
|
150
|
-
|
|
151
|
-
|
|
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 = {}) =>
|
|
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 */
|
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
*
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
|
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 = {}) =>
|
|
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
|
-
|
|
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 ===
|
|
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 =
|
|
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
|
-
|
|
333
|
+
scrim.addEventListener("mousedown", handleOverlayMouseDown);
|
|
323
334
|
document.addEventListener("mouseup", handleOverlayMouseUp);
|
|
324
335
|
}
|
|
325
|
-
if (
|
|
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
|
-
|
|
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
|
|
358
|
+
mouseDownOnOverlay = onScrim(e);
|
|
336
359
|
}
|
|
337
360
|
function handleOverlayMouseUp(e) {
|
|
338
|
-
if (mouseDownOnOverlay && e
|
|
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 =
|
|
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
|