@ui-sdk/web 0.0.0-stage → 0.1.0-beta.1
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/LICENSE +202 -0
- package/README.md +10 -2
- package/browser/web/accordion.js +1 -0
- package/browser/web/alert-dialog.js +1 -0
- package/browser/web/autocomplete.js +1 -0
- package/browser/web/avatar.js +1 -0
- package/browser/web/button.js +1 -0
- package/browser/web/calendar.js +1 -0
- package/browser/web/carousel.js +1 -0
- package/browser/web/checkbox.js +1 -0
- package/browser/web/chunks/0eRGibL6.js +1 -0
- package/browser/web/chunks/2bIEUO6_.js +1 -0
- package/browser/web/chunks/5A9w2ULz.js +1 -0
- package/browser/web/chunks/5RSyydjV.js +1 -0
- package/browser/web/chunks/9pWTy2wl.js +1 -0
- package/browser/web/chunks/B0f2j6zz.js +1 -0
- package/browser/web/chunks/B1rux6br.js +1 -0
- package/browser/web/chunks/B3-APaNf.js +1 -0
- package/browser/web/chunks/B7EgSgjQ.js +1 -0
- package/browser/web/chunks/B8QcOzJU.js +1 -0
- package/browser/web/chunks/BFueEkwm.js +3 -0
- package/browser/web/chunks/BJj3oS29.js +1 -0
- package/browser/web/chunks/BNSvTCpr.js +1 -0
- package/browser/web/chunks/BPD0kAZB.js +1 -0
- package/browser/web/chunks/BQeduZqT.js +1 -0
- package/browser/web/chunks/B_CVFyiz.js +1 -0
- package/browser/web/chunks/B_ns1YHO.js +1 -0
- package/browser/web/chunks/BcwkVTsR.js +1 -0
- package/browser/web/chunks/BeaQ6TYM.js +1 -0
- package/browser/web/chunks/BgBB013u.js +1 -0
- package/browser/web/chunks/BgsKhHDm.js +1 -0
- package/browser/web/chunks/BjMcbuhi.js +9 -0
- package/browser/web/chunks/Bjiy_F2N.js +1 -0
- package/browser/web/chunks/BqScCLwv.js +1 -0
- package/browser/web/chunks/BqX3Utqx.js +1 -0
- package/browser/web/chunks/BtVkQf3z.js +1 -0
- package/browser/web/chunks/By30h-7B.js +1 -0
- package/browser/web/chunks/C1zqYpy2.js +1 -0
- package/browser/web/chunks/C436juFf.js +1 -0
- package/browser/web/chunks/C4_bULs1.js +1 -0
- package/browser/web/chunks/C4omqJEz.js +1 -0
- package/browser/web/chunks/C7tt0T3n.js +1 -0
- package/browser/web/chunks/C8w7s5dg.js +1 -0
- package/browser/web/chunks/CElY4erO.js +1 -0
- package/browser/web/chunks/CFODo-m2.js +1 -0
- package/browser/web/chunks/CL4wjam9.js +1 -0
- package/browser/web/chunks/CMg-8UVf.js +1 -0
- package/browser/web/chunks/COWKfakj.js +1 -0
- package/browser/web/chunks/CQBU0qZ4.js +1 -0
- package/browser/web/chunks/CV6uABgc.js +1 -0
- package/browser/web/chunks/C_SWFpWQ.js +1 -0
- package/browser/web/chunks/C_Uanbrl.js +1 -0
- package/browser/web/chunks/Ce_CSanC.js +1 -0
- package/browser/web/chunks/Cg-ZmRhS.js +1 -0
- package/browser/web/chunks/Cgf2wPI_.js +1 -0
- package/browser/web/chunks/Cuep_3Vf.js +1 -0
- package/browser/web/chunks/D02PaHn6.js +1 -0
- package/browser/web/chunks/D5LiaI5R.js +1 -0
- package/browser/web/chunks/D9UgVYvF.js +1 -0
- package/browser/web/chunks/DC08tnkE.js +2 -0
- package/browser/web/chunks/DG8YGSvu.js +1 -0
- package/browser/web/chunks/DIQd0FYD.js +1 -0
- package/browser/web/chunks/DMLqacE8.js +1 -0
- package/browser/web/chunks/DSUI3yI-.js +1 -0
- package/browser/web/chunks/DT3Hb76X.js +1 -0
- package/browser/web/chunks/DY75g3-W.js +1 -0
- package/browser/web/chunks/DZpNQKZ9.js +1 -0
- package/browser/web/chunks/Db-W1wrC.js +1 -0
- package/browser/web/chunks/Dc1xK3Ii.js +1 -0
- package/browser/web/chunks/DdF6awFs.js +1 -0
- package/browser/web/chunks/DdaCLml8.js +1 -0
- package/browser/web/chunks/Dh8MLjQO.js +1 -0
- package/browser/web/chunks/DiuslJRy.js +1 -0
- package/browser/web/chunks/DjRcw3Y0.js +1 -0
- package/browser/web/chunks/Djdv-16J.js +1 -0
- package/browser/web/chunks/DnFXe_lF.js +1 -0
- package/browser/web/chunks/DneGv1oC.js +1 -0
- package/browser/web/chunks/DsVxP-p-.js +1 -0
- package/browser/web/chunks/DwDilCo2.js +1 -0
- package/browser/web/chunks/DwV48AXv.js +1 -0
- package/browser/web/chunks/F37aF4-L.js +1 -0
- package/browser/web/chunks/LyB6XIbN.js +1 -0
- package/browser/web/chunks/NKnbYGjC.js +1 -0
- package/browser/web/chunks/Vk-rVi16.js +1 -0
- package/browser/web/chunks/XsM0J1Ex.js +1 -0
- package/browser/web/chunks/asSN9iEp.js +1 -0
- package/browser/web/chunks/c-AjHfdF.js +1 -0
- package/browser/web/chunks/dR-12TPY.js +1 -0
- package/browser/web/chunks/hR4xM0EC.js +1 -0
- package/browser/web/chunks/hqOaBxAg.js +1 -0
- package/browser/web/chunks/kMKqqeG4.js +1 -0
- package/browser/web/chunks/kXboBcfi.js +1 -0
- package/browser/web/chunks/l4aQIg22.js +1 -0
- package/browser/web/chunks/tJC0JSU4.js +1 -0
- package/browser/web/chunks/wHdS_QYM.js +1 -0
- package/browser/web/chunks/yv8dTvNI.js +1 -0
- package/browser/web/chunks/yw9M3nfi.js +1 -0
- package/browser/web/collapsible.js +1 -0
- package/browser/web/color-picker.js +1 -0
- package/browser/web/combobox.js +2 -0
- package/browser/web/command.js +1 -0
- package/browser/web/core.js +5 -0
- package/browser/web/date-input.js +1 -0
- package/browser/web/date-picker.js +1 -0
- package/browser/web/dialog.js +1 -0
- package/browser/web/drawer.js +1 -0
- package/browser/web/field.js +1 -0
- package/browser/web/file-input.js +1 -0
- package/browser/web/hover-card.js +1 -0
- package/browser/web/image-cropper.js +1 -0
- package/browser/web/input.js +1 -0
- package/browser/web/label.js +1 -0
- package/browser/web/listbox.js +1 -0
- package/browser/web/menu.js +1 -0
- package/browser/web/menubar.js +1 -0
- package/browser/web/meter.js +1 -0
- package/browser/web/navigation-menu.js +1 -0
- package/browser/web/number-input.js +1 -0
- package/browser/web/otp-input.js +2 -0
- package/browser/web/overlays.js +1 -0
- package/browser/web/pagination.js +1 -0
- package/browser/web/password-input.js +1 -0
- package/browser/web/popover.js +1 -0
- package/browser/web/preferences.js +1 -0
- package/browser/web/progress.js +1 -0
- package/browser/web/radio.js +1 -0
- package/browser/web/rating.js +1 -0
- package/browser/web/resizable.js +1 -0
- package/browser/web/scroll-area.js +1 -0
- package/browser/web/segmented-control.js +1 -0
- package/browser/web/select.js +1 -0
- package/browser/web/separator.js +1 -0
- package/browser/web/slider.js +1 -0
- package/browser/web/switch.js +1 -0
- package/browser/web/tabs.js +1 -0
- package/browser/web/textarea.js +1 -0
- package/browser/web/time-input.js +1 -0
- package/browser/web/timeline.js +1 -0
- package/browser/web/toast.js +1 -0
- package/browser/web/toggle.js +1 -0
- package/browser/web/token-input.js +8 -0
- package/browser/web/toolbar.js +1 -0
- package/browser/web/tooltip.js +1 -0
- package/browser/web/tree.js +1 -0
- package/browser/web/trigger-menu.js +4 -0
- package/browser/web/waterfall.js +1 -0
- package/browser/web.css +3378 -0
- package/browser/web.js +3 -0
- package/custom-elements.json +32315 -0
- package/dist/accordion.d.ts +2 -0
- package/dist/accordion.js +57 -0
- package/dist/action.d.ts +16 -0
- package/dist/action.js +39 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +7 -0
- package/dist/autocomplete.d.ts +2 -0
- package/dist/autocomplete.js +120 -0
- package/dist/autoload.d.ts +18 -0
- package/dist/autoload.js +63 -0
- package/dist/avatar.d.ts +2 -0
- package/dist/avatar.js +27 -0
- package/dist/browser-core.d.ts +2 -0
- package/dist/browser-core.js +8 -0
- package/dist/button.d.ts +2 -0
- package/dist/button.js +35 -0
- package/dist/calendar.d.ts +23 -0
- package/dist/calendar.js +429 -0
- package/dist/carousel.d.ts +2 -0
- package/dist/carousel.js +54 -0
- package/dist/checkbox.d.ts +4 -0
- package/dist/checkbox.js +75 -0
- package/dist/collapsible.d.ts +2 -0
- package/dist/collapsible.js +28 -0
- package/dist/color-picker.d.ts +2 -0
- package/dist/color-picker.js +64 -0
- package/dist/combobox.d.ts +2 -0
- package/dist/combobox.js +334 -0
- package/dist/command.d.ts +2 -0
- package/dist/command.js +122 -0
- package/dist/date-input.d.ts +3 -0
- package/dist/date-input.js +87 -0
- package/dist/date-picker.d.ts +2 -0
- package/dist/date-picker.js +174 -0
- package/dist/default-text.d.ts +3 -0
- package/dist/default-text.js +24 -0
- package/dist/define-element.d.ts +404 -0
- package/dist/define-element.js +1489 -0
- package/dist/dialog.d.ts +3 -0
- package/dist/dialog.js +94 -0
- package/dist/drawer.d.ts +2 -0
- package/dist/drawer.js +96 -0
- package/dist/entries/accordion.d.ts +3 -0
- package/dist/entries/accordion.js +7 -0
- package/dist/entries/alert-dialog.d.ts +3 -0
- package/dist/entries/alert-dialog.js +13 -0
- package/dist/entries/autocomplete.d.ts +3 -0
- package/dist/entries/autocomplete.js +7 -0
- package/dist/entries/avatar.d.ts +2 -0
- package/dist/entries/avatar.js +3 -0
- package/dist/entries/button.d.ts +2 -0
- package/dist/entries/button.js +3 -0
- package/dist/entries/calendar.d.ts +3 -0
- package/dist/entries/calendar.js +7 -0
- package/dist/entries/carousel.d.ts +3 -0
- package/dist/entries/carousel.js +7 -0
- package/dist/entries/checkbox.d.ts +3 -0
- package/dist/entries/checkbox.js +15 -0
- package/dist/entries/collapsible.d.ts +3 -0
- package/dist/entries/collapsible.js +7 -0
- package/dist/entries/color-picker.d.ts +3 -0
- package/dist/entries/color-picker.js +7 -0
- package/dist/entries/combobox.d.ts +3 -0
- package/dist/entries/combobox.js +7 -0
- package/dist/entries/command.d.ts +3 -0
- package/dist/entries/command.js +7 -0
- package/dist/entries/date-input.d.ts +3 -0
- package/dist/entries/date-input.js +7 -0
- package/dist/entries/date-picker.d.ts +3 -0
- package/dist/entries/date-picker.js +7 -0
- package/dist/entries/dialog.d.ts +3 -0
- package/dist/entries/dialog.js +7 -0
- package/dist/entries/drawer.d.ts +3 -0
- package/dist/entries/drawer.js +8 -0
- package/dist/entries/field.d.ts +2 -0
- package/dist/entries/field.js +3 -0
- package/dist/entries/file-input.d.ts +3 -0
- package/dist/entries/file-input.js +13 -0
- package/dist/entries/hover-card.d.ts +3 -0
- package/dist/entries/hover-card.js +7 -0
- package/dist/entries/image-cropper.d.ts +3 -0
- package/dist/entries/image-cropper.js +7 -0
- package/dist/entries/input.d.ts +3 -0
- package/dist/entries/input.js +8 -0
- package/dist/entries/label.d.ts +2 -0
- package/dist/entries/label.js +3 -0
- package/dist/entries/listbox.d.ts +3 -0
- package/dist/entries/listbox.js +7 -0
- package/dist/entries/menu.d.ts +3 -0
- package/dist/entries/menu.js +7 -0
- package/dist/entries/menubar.d.ts +3 -0
- package/dist/entries/menubar.js +7 -0
- package/dist/entries/meter.d.ts +2 -0
- package/dist/entries/meter.js +3 -0
- package/dist/entries/navigation-menu.d.ts +3 -0
- package/dist/entries/navigation-menu.js +7 -0
- package/dist/entries/number-input.d.ts +3 -0
- package/dist/entries/number-input.js +7 -0
- package/dist/entries/otp-input.d.ts +3 -0
- package/dist/entries/otp-input.js +7 -0
- package/dist/entries/overlays.d.ts +2 -0
- package/dist/entries/overlays.js +3 -0
- package/dist/entries/pagination.d.ts +3 -0
- package/dist/entries/pagination.js +11 -0
- package/dist/entries/password-input.d.ts +3 -0
- package/dist/entries/password-input.js +7 -0
- package/dist/entries/popover.d.ts +3 -0
- package/dist/entries/popover.js +7 -0
- package/dist/entries/preferences.d.ts +3 -0
- package/dist/entries/preferences.js +13 -0
- package/dist/entries/progress.d.ts +2 -0
- package/dist/entries/progress.js +3 -0
- package/dist/entries/radio.d.ts +3 -0
- package/dist/entries/radio.js +7 -0
- package/dist/entries/rating.d.ts +3 -0
- package/dist/entries/rating.js +8 -0
- package/dist/entries/resizable.d.ts +3 -0
- package/dist/entries/resizable.js +11 -0
- package/dist/entries/scroll-area.d.ts +3 -0
- package/dist/entries/scroll-area.js +7 -0
- package/dist/entries/segmented-control.d.ts +3 -0
- package/dist/entries/segmented-control.js +7 -0
- package/dist/entries/select.d.ts +3 -0
- package/dist/entries/select.js +7 -0
- package/dist/entries/separator.d.ts +2 -0
- package/dist/entries/separator.js +3 -0
- package/dist/entries/slider.d.ts +3 -0
- package/dist/entries/slider.js +7 -0
- package/dist/entries/switch.d.ts +3 -0
- package/dist/entries/switch.js +7 -0
- package/dist/entries/tabs.d.ts +3 -0
- package/dist/entries/tabs.js +7 -0
- package/dist/entries/textarea.d.ts +3 -0
- package/dist/entries/textarea.js +7 -0
- package/dist/entries/time-input.d.ts +3 -0
- package/dist/entries/time-input.js +7 -0
- package/dist/entries/timeline.d.ts +3 -0
- package/dist/entries/timeline.js +7 -0
- package/dist/entries/toast.d.ts +2 -0
- package/dist/entries/toast.js +3 -0
- package/dist/entries/toggle.d.ts +3 -0
- package/dist/entries/toggle.js +12 -0
- package/dist/entries/token-input.d.ts +3 -0
- package/dist/entries/token-input.js +17 -0
- package/dist/entries/toolbar.d.ts +3 -0
- package/dist/entries/toolbar.js +7 -0
- package/dist/entries/tooltip.d.ts +3 -0
- package/dist/entries/tooltip.js +7 -0
- package/dist/entries/tree.d.ts +3 -0
- package/dist/entries/tree.js +17 -0
- package/dist/entries/trigger-menu.d.ts +3 -0
- package/dist/entries/trigger-menu.js +11 -0
- package/dist/entries/waterfall.d.ts +3 -0
- package/dist/entries/waterfall.js +11 -0
- package/dist/events.d.ts +1658 -0
- package/dist/events.js +0 -0
- package/dist/field.d.ts +2 -0
- package/dist/field.js +101 -0
- package/dist/file-input.d.ts +2 -0
- package/dist/file-input.js +140 -0
- package/dist/forward-attributes.d.ts +2 -0
- package/dist/forward-attributes.js +22 -0
- package/dist/history.d.ts +2 -0
- package/dist/history.js +11 -0
- package/dist/hover-card.d.ts +2 -0
- package/dist/hover-card.js +31 -0
- package/dist/image-cropper.d.ts +2 -0
- package/dist/image-cropper.js +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +269 -0
- package/dist/input.d.ts +3 -0
- package/dist/input.js +69 -0
- package/dist/label.d.ts +2 -0
- package/dist/label.js +11 -0
- package/dist/list.d.ts +40 -0
- package/dist/list.js +192 -0
- package/dist/listbox.d.ts +2 -0
- package/dist/listbox.js +84 -0
- package/dist/menu.d.ts +4 -0
- package/dist/menu.js +295 -0
- package/dist/menubar.d.ts +4 -0
- package/dist/menubar.js +30 -0
- package/dist/meter.d.ts +2 -0
- package/dist/meter.js +22 -0
- package/dist/navigation-menu.d.ts +2 -0
- package/dist/navigation-menu.js +114 -0
- package/dist/number-input.d.ts +2 -0
- package/dist/number-input.js +73 -0
- package/dist/observe.d.ts +2 -0
- package/dist/observe.js +7 -0
- package/dist/otp-input.d.ts +2 -0
- package/dist/otp-input.js +72 -0
- package/dist/overlays.d.ts +13 -0
- package/dist/overlays.js +156 -0
- package/dist/pagination.d.ts +2 -0
- package/dist/pagination.js +127 -0
- package/dist/password-input.d.ts +2 -0
- package/dist/password-input.js +57 -0
- package/dist/popover.d.ts +2 -0
- package/dist/popover.js +64 -0
- package/dist/popup.d.ts +40 -0
- package/dist/popup.js +112 -0
- package/dist/preferences.d.ts +2 -0
- package/dist/preferences.js +56 -0
- package/dist/progress.d.ts +2 -0
- package/dist/progress.js +22 -0
- package/dist/radio.d.ts +2 -0
- package/dist/radio.js +33 -0
- package/dist/rating.d.ts +2 -0
- package/dist/rating.js +64 -0
- package/dist/resizable.d.ts +2 -0
- package/dist/resizable.js +42 -0
- package/dist/scroll-area.d.ts +2 -0
- package/dist/scroll-area.js +45 -0
- package/dist/segmented-control.d.ts +2 -0
- package/dist/segmented-control.js +28 -0
- package/dist/select.d.ts +2 -0
- package/dist/select.js +239 -0
- package/dist/separator.d.ts +2 -0
- package/dist/separator.js +11 -0
- package/dist/slider.d.ts +2 -0
- package/dist/slider.js +77 -0
- package/dist/tabs.d.ts +2 -0
- package/dist/tabs.js +37 -0
- package/dist/timeline.d.ts +2 -0
- package/dist/timeline.js +27 -0
- package/dist/toast.d.ts +20 -0
- package/dist/toast.js +177 -0
- package/dist/toggle.d.ts +3 -0
- package/dist/toggle.js +64 -0
- package/dist/token-input.d.ts +2 -0
- package/dist/token-input.js +103 -0
- package/dist/toolbar.d.ts +2 -0
- package/dist/toolbar.js +36 -0
- package/dist/tooltip.d.ts +2 -0
- package/dist/tooltip.js +35 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +147 -0
- package/dist/trigger-menu.d.ts +2 -0
- package/dist/trigger-menu.js +133 -0
- package/dist/waterfall.d.ts +2 -0
- package/dist/waterfall.js +140 -0
- package/docs/README.md +89 -0
- package/docs/agents.md +1523 -0
- package/docs/components/accordion.md +1401 -0
- package/docs/components/alert-dialog.md +788 -0
- package/docs/components/autocomplete.md +1692 -0
- package/docs/components/avatar.md +261 -0
- package/docs/components/button.md +441 -0
- package/docs/components/calendar.md +1536 -0
- package/docs/components/carousel.md +1055 -0
- package/docs/components/checkbox.md +1045 -0
- package/docs/components/collapsible.md +535 -0
- package/docs/components/color-picker.md +651 -0
- package/docs/components/combobox.md +3274 -0
- package/docs/components/command.md +1431 -0
- package/docs/components/date-input.md +563 -0
- package/docs/components/date-picker.md +1136 -0
- package/docs/components/dialog.md +1360 -0
- package/docs/components/drawer.md +1201 -0
- package/docs/components/field.md +519 -0
- package/docs/components/file-input.md +617 -0
- package/docs/components/hover-card.md +486 -0
- package/docs/components/image-cropper.md +489 -0
- package/docs/components/input.md +327 -0
- package/docs/components/label.md +208 -0
- package/docs/components/listbox.md +1015 -0
- package/docs/components/menu.md +1212 -0
- package/docs/components/menubar.md +460 -0
- package/docs/components/meter.md +298 -0
- package/docs/components/navigation-menu.md +1573 -0
- package/docs/components/number-input.md +1087 -0
- package/docs/components/otp-input.md +558 -0
- package/docs/components/pagination.md +639 -0
- package/docs/components/password-input.md +649 -0
- package/docs/components/popover.md +1433 -0
- package/docs/components/preferences.md +243 -0
- package/docs/components/progress.md +406 -0
- package/docs/components/radio.md +855 -0
- package/docs/components/rating.md +431 -0
- package/docs/components/resizable.md +866 -0
- package/docs/components/scroll-area.md +898 -0
- package/docs/components/segmented-control.md +421 -0
- package/docs/components/select.md +1957 -0
- package/docs/components/separator.md +238 -0
- package/docs/components/slider.md +1018 -0
- package/docs/components/switch.md +583 -0
- package/docs/components/tabs.md +948 -0
- package/docs/components/textarea.md +331 -0
- package/docs/components/time-input.md +235 -0
- package/docs/components/timeline.md +622 -0
- package/docs/components/toast.md +1223 -0
- package/docs/components/toggle.md +522 -0
- package/docs/components/token-input.md +854 -0
- package/docs/components/toolbar.md +815 -0
- package/docs/components/tooltip.md +860 -0
- package/docs/components/tree.md +1243 -0
- package/docs/components/trigger-menu.md +1110 -0
- package/docs/components/waterfall.md +709 -0
- package/docs/controllers.md +66 -0
- package/docs/core.md +8027 -0
- package/docs/custom-parts.md +91 -0
- package/docs/first-component.md +88 -0
- package/docs/forms.md +125 -0
- package/docs/installation.md +141 -0
- package/docs/introduction.md +32 -0
- package/docs/lists-and-menus.md +166 -0
- package/docs/llms.txt +1566 -0
- package/docs/parts-and-composition.md +57 -0
- package/docs/popups.md +92 -0
- package/docs/rendering.md +71 -0
- package/docs/server-rendering.md +118 -0
- package/docs/state-and-events.md +134 -0
- package/docs/styling-and-animation.md +104 -0
- package/docs/utilities/async-options.md +332 -0
- package/docs/utilities/shortcuts.md +184 -0
- package/html-custom-data.json +14792 -0
- package/package.json +302 -3
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
# Drawer
|
|
2
|
+
|
|
3
|
+
A sheet you can open, drag, and resize from an edge of the screen.
|
|
4
|
+
|
|
5
|
+
A drawer gives a task its own sheet without leaving the page. It shares [Dialog’s](./dialog.md) focus and dismissal behavior, then adds dragging, snap points, and a handle you can use with the keyboard.
|
|
6
|
+
|
|
7
|
+
```ts title="basic.ts"
|
|
8
|
+
import "@ui-sdk/web/drawer"
|
|
9
|
+
import { createElement, Minus, Plus } from "lucide"
|
|
10
|
+
const goal = { initial: 350, min: 200, max: 400, step: 10 }
|
|
11
|
+
|
|
12
|
+
function mount(root: HTMLElement) {
|
|
13
|
+
root.innerHTML = `
|
|
14
|
+
<ui-drawer>
|
|
15
|
+
<ui-drawer-trigger><button>Open Drawer</button></ui-drawer-trigger>
|
|
16
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
17
|
+
<ui-drawer-content hidden>
|
|
18
|
+
<div data-slot="grip" aria-hidden="true"></div>
|
|
19
|
+
|
|
20
|
+
<div data-slot="stack">
|
|
21
|
+
<ui-drawer-title>Move goal</ui-drawer-title>
|
|
22
|
+
<ui-drawer-description>Set your daily activity goal.</ui-drawer-description>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div data-slot="row">
|
|
26
|
+
<button type="button" aria-label="Decrease">${createElement(Minus, { "aria-hidden": "true" }).outerHTML}</button>
|
|
27
|
+
<div data-slot="stack">
|
|
28
|
+
<div aria-live="polite"></div>
|
|
29
|
+
<div data-slot="hint">Calories/day</div>
|
|
30
|
+
</div>
|
|
31
|
+
<button type="button" aria-label="Increase">${createElement(Plus, { "aria-hidden": "true" }).outerHTML}</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div data-slot="stack">
|
|
35
|
+
<ui-drawer-close value="submit"><button data-variant="solid">Submit</button></ui-drawer-close>
|
|
36
|
+
<ui-drawer-close><button>Cancel</button></ui-drawer-close>
|
|
37
|
+
</div>
|
|
38
|
+
</ui-drawer-content>
|
|
39
|
+
</ui-drawer>
|
|
40
|
+
`
|
|
41
|
+
const value = root.querySelector("[aria-live]")!
|
|
42
|
+
const decrease = root.querySelector<HTMLButtonElement>(
|
|
43
|
+
'[aria-label="Decrease"]'
|
|
44
|
+
)!
|
|
45
|
+
const increase = root.querySelector<HTMLButtonElement>(
|
|
46
|
+
'[aria-label="Increase"]'
|
|
47
|
+
)!
|
|
48
|
+
let calories = goal.initial
|
|
49
|
+
const show = () => {
|
|
50
|
+
value.textContent = String(calories)
|
|
51
|
+
decrease.disabled = calories <= goal.min
|
|
52
|
+
increase.disabled = calories >= goal.max
|
|
53
|
+
}
|
|
54
|
+
decrease.addEventListener("click", () => {
|
|
55
|
+
calories -= goal.step
|
|
56
|
+
show()
|
|
57
|
+
})
|
|
58
|
+
increase.addEventListener("click", () => {
|
|
59
|
+
calories += goal.step
|
|
60
|
+
show()
|
|
61
|
+
})
|
|
62
|
+
show()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { mount }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Open the drawer, adjust the daily goal, then Submit or Cancel. You can also press Escape, press outside, or drag the sheet toward its edge to close it.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install @ui-sdk/web @ui-sdk/core
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Import the component to register its elements. The import is safe on the server.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import "@ui-sdk/web/drawer"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Composition
|
|
83
|
+
|
|
84
|
+
Use the following composition to build a `ui-drawer`:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
ui-drawer
|
|
88
|
+
├── ui-drawer-trigger
|
|
89
|
+
│ └── button
|
|
90
|
+
├── ui-drawer-backdrop
|
|
91
|
+
└── ui-drawer-content
|
|
92
|
+
├── ui-drawer-title
|
|
93
|
+
├── ui-drawer-description
|
|
94
|
+
└── ui-drawer-close
|
|
95
|
+
└── button
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The content is the sheet. The drawer positions it inside a full-screen viewport and moves it with `translate`. Give it a size, a surface, and corners; leave positioning and translation to the drawer. Give the backdrop a color, such as `bg-black/30`; the drawer manages its opacity. Give the content and backdrop `hidden` to keep them hidden before the elements upgrade.
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
Import the parts and put them together:
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<script type="module">
|
|
106
|
+
import "@ui-sdk/web/drawer"
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<ui-drawer>
|
|
110
|
+
<ui-drawer-trigger><button>Filters</button></ui-drawer-trigger>
|
|
111
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
112
|
+
<ui-drawer-content hidden>
|
|
113
|
+
<ui-drawer-title>Filters</ui-drawer-title>
|
|
114
|
+
<ui-drawer-description>Narrow down the results.</ui-drawer-description>
|
|
115
|
+
<ui-drawer-close><button>Done</button></ui-drawer-close>
|
|
116
|
+
</ui-drawer-content>
|
|
117
|
+
</ui-drawer>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Scrollable Content
|
|
121
|
+
|
|
122
|
+
Put long content in an inner `min-h-0 flex-1 overflow-y-auto` area. A downward drag scrolls that area first; when it reaches the top, the same gesture can move the drawer. `initial-focus="content"` starts focus on the sheet itself.
|
|
123
|
+
|
|
124
|
+
```ts title="scrollable.ts"
|
|
125
|
+
import "@ui-sdk/web/drawer"
|
|
126
|
+
|
|
127
|
+
const paragraphs = [
|
|
128
|
+
"The drawer rises from any edge, rests on snap points and follows your finger with the same spring as a system sheet.",
|
|
129
|
+
"Snap points take fractions of the screen, pixels, rem, percentages or the content's own size. Points that land within a pixel of each other merge into one.",
|
|
130
|
+
"Dragging a list scrolls it until it reaches the top, and then the same gesture moves the drawer, so closing a long sheet never needs two tries.",
|
|
131
|
+
"The handle steps through the snap points with a press, the arrow keys, Home and End. Screen readers on phones adjust it like a slider.",
|
|
132
|
+
"Below a snap point you choose, the page behind stays usable and undimmed, like a map with a sheet of results.",
|
|
133
|
+
"Drawers opened from inside each other stack, and the one beneath steps back while the new one rises.",
|
|
134
|
+
"The on-screen keyboard pushes the drawer up, and the focused field scrolls into view inside it, never the page.",
|
|
135
|
+
"With reduced motion, drawers fade in and out instead of sliding.",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
function mount(root: HTMLElement) {
|
|
139
|
+
root.innerHTML = `
|
|
140
|
+
<ui-drawer>
|
|
141
|
+
<ui-drawer-trigger><button>Scrollable Content</button></ui-drawer-trigger>
|
|
142
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
143
|
+
<ui-drawer-content initial-focus="content" hidden>
|
|
144
|
+
<div data-slot="grip" aria-hidden="true"></div>
|
|
145
|
+
<div data-slot="stack">
|
|
146
|
+
<ui-drawer-title>Release notes</ui-drawer-title>
|
|
147
|
+
<ui-drawer-description>Everything new in the drawer.</ui-drawer-description>
|
|
148
|
+
</div>
|
|
149
|
+
<div data-slot="scroll">
|
|
150
|
+
${[...paragraphs, ...paragraphs].map((paragraph) => `<p data-slot="hint">${paragraph}</p>`).join("")}
|
|
151
|
+
</div>
|
|
152
|
+
<div data-slot="stack">
|
|
153
|
+
<ui-drawer-close><button data-variant="solid">Got It</button></ui-drawer-close>
|
|
154
|
+
</div>
|
|
155
|
+
</ui-drawer-content>
|
|
156
|
+
</ui-drawer>
|
|
157
|
+
`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { mount }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Open the long sheet and scroll its text. At the top, drag downward to move the sheet; on a keyboard, Tab reaches the controls and Escape closes it.
|
|
164
|
+
|
|
165
|
+
## Sides
|
|
166
|
+
|
|
167
|
+
`side` chooses bottom, top, left, or right. Logical `start` and `end` follow text direction. The content’s `data-side` tells you which physical edge to style.
|
|
168
|
+
|
|
169
|
+
Side drawers can opt into `prevent-edge-swipe` to keep an edge gesture from starting iOS Safari’s back or forward navigation. It also prevents clicks right at those edges, so use it only when you need that tradeoff.
|
|
170
|
+
|
|
171
|
+
```ts title="sides.ts"
|
|
172
|
+
import "@ui-sdk/web/drawer"
|
|
173
|
+
|
|
174
|
+
const sides = [
|
|
175
|
+
{
|
|
176
|
+
side: "top",
|
|
177
|
+
label: "Top",
|
|
178
|
+
title: "Notifications",
|
|
179
|
+
description: "You're all caught up.",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
side: "right",
|
|
183
|
+
label: "Right",
|
|
184
|
+
title: "Cart",
|
|
185
|
+
description: "2 items, $84.00.",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
side: "bottom",
|
|
189
|
+
label: "Bottom",
|
|
190
|
+
title: "Share",
|
|
191
|
+
description: "Send this page to your team.",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
side: "left",
|
|
195
|
+
label: "Left",
|
|
196
|
+
title: "Menu",
|
|
197
|
+
description: "Jump to a section of the docs.",
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
function mount(root: HTMLElement) {
|
|
202
|
+
root.innerHTML = `
|
|
203
|
+
<div data-slot="row">
|
|
204
|
+
${sides
|
|
205
|
+
.map(
|
|
206
|
+
({
|
|
207
|
+
side,
|
|
208
|
+
label,
|
|
209
|
+
title,
|
|
210
|
+
description,
|
|
211
|
+
}) => `<ui-drawer side="${side}" ${side === "left" || side === "right" ? "prevent-edge-swipe" : ""}>
|
|
212
|
+
<ui-drawer-trigger><button>${label}</button></ui-drawer-trigger>
|
|
213
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
214
|
+
<ui-drawer-content hidden>
|
|
215
|
+
<div data-slot="stack">
|
|
216
|
+
<ui-drawer-title>${title}</ui-drawer-title>
|
|
217
|
+
<ui-drawer-description>${description}</ui-drawer-description>
|
|
218
|
+
</div>
|
|
219
|
+
<ui-drawer-close><button data-variant="solid">Close</button></ui-drawer-close>
|
|
220
|
+
</ui-drawer-content>
|
|
221
|
+
</ui-drawer>`
|
|
222
|
+
)
|
|
223
|
+
.join("")}
|
|
224
|
+
</div>
|
|
225
|
+
`
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { mount }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Open a sheet from each edge. Drag it back toward that edge, press outside, or press Escape to close it.
|
|
232
|
+
|
|
233
|
+
## Snap Points
|
|
234
|
+
|
|
235
|
+
`snap-points` are the places the drawer can rest, as a comma-separated list. It opens at the first, or at `default-snap-point`, and settles at another after a drag. A fast fling can skip intermediate points.
|
|
236
|
+
|
|
237
|
+
| Value | Size |
|
|
238
|
+
| ------------------ | ------------------------------------------ |
|
|
239
|
+
| `0.5` | Half the viewport along the drawer’s axis. |
|
|
240
|
+
| `320` or `"320px"` | Pixels; numbers above 1 are pixel values. |
|
|
241
|
+
| `"20rem"` | A multiple of the root font size. |
|
|
242
|
+
| `"50%"` | A percentage of the viewport. |
|
|
243
|
+
| `"content"` | The sheet’s own size, the default. |
|
|
244
|
+
|
|
245
|
+
The sheet’s CSS size is the limit: `1` cannot make a short sheet fill the screen. Use a height such as `h-[calc(100dvh-3rem)]` when the largest point should nearly fill it. The drawer measures again when content or available space changes; a moved resting point reports `content-resize`.
|
|
246
|
+
|
|
247
|
+
A Handle wraps a button with an accessible name. Pressing it moves to the next larger snap point, then wraps to the smallest. Arrow keys along the drawer’s axis move one point without wrapping; Home and End move to the ends. `canExpand` and `canCollapse` let the example show which moves remain.
|
|
248
|
+
|
|
249
|
+
For a sheet with one snap point, use a decorative bar instead of an inactive button.
|
|
250
|
+
|
|
251
|
+
```ts title="snap-points.ts"
|
|
252
|
+
import "@ui-sdk/web/drawer"
|
|
253
|
+
|
|
254
|
+
const places = [
|
|
255
|
+
{ name: "Blue Bottle Coffee", detail: "Coffee · 0.2 mi" },
|
|
256
|
+
{ name: "Tartine Bakery", detail: "Bakery · 0.4 mi" },
|
|
257
|
+
{ name: "Dolores Park", detail: "Park · 0.5 mi" },
|
|
258
|
+
{ name: "Mission Dolores", detail: "Landmark · 0.6 mi" },
|
|
259
|
+
{ name: "Ritual Coffee Roasters", detail: "Coffee · 0.7 mi" },
|
|
260
|
+
{ name: "Bi-Rite Creamery", detail: "Ice cream · 0.7 mi" },
|
|
261
|
+
{ name: "Alamo Square", detail: "Park · 1.1 mi" },
|
|
262
|
+
{ name: "The Castro Theatre", detail: "Cinema · 1.2 mi" },
|
|
263
|
+
{ name: "Ferry Building", detail: "Market · 2.3 mi" },
|
|
264
|
+
{ name: "Coit Tower", detail: "Landmark · 2.9 mi" },
|
|
265
|
+
{ name: "Golden Gate Park", detail: "Park · 3.0 mi" },
|
|
266
|
+
{ name: "Lands End", detail: "Trail · 5.4 mi" },
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
function mount(root: HTMLElement) {
|
|
270
|
+
root.innerHTML = `
|
|
271
|
+
<ui-drawer snap-points="0.25,0.5,1" default-snap-point="0.5" expand-on-scroll>
|
|
272
|
+
<ui-drawer-trigger><button>Find a Place</button></ui-drawer-trigger>
|
|
273
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
274
|
+
<ui-drawer-content hidden>
|
|
275
|
+
<ui-drawer-handle><button aria-label="Resize"></button></ui-drawer-handle>
|
|
276
|
+
<div data-slot="stack">
|
|
277
|
+
<ui-drawer-title>Places nearby</ui-drawer-title>
|
|
278
|
+
<ui-drawer-description>Drag the drawer, or press the handle, to see more.</ui-drawer-description>
|
|
279
|
+
</div>
|
|
280
|
+
<div data-slot="scroll">
|
|
281
|
+
<ul data-slot="list">
|
|
282
|
+
${places
|
|
283
|
+
.map(
|
|
284
|
+
(place) => `<li>
|
|
285
|
+
<div>${place.name}</div>
|
|
286
|
+
<div data-slot="hint">${place.detail}</div>
|
|
287
|
+
</li>`
|
|
288
|
+
)
|
|
289
|
+
.join("")}
|
|
290
|
+
</ul>
|
|
291
|
+
</div>
|
|
292
|
+
</ui-drawer-content>
|
|
293
|
+
</ui-drawer>
|
|
294
|
+
`
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export { mount }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Open the sheet at its middle snap point. Drag it, press the handle, or Tab to the handle and use Up, Down, Home and End to try the other resting points.
|
|
301
|
+
|
|
302
|
+
## Controlled
|
|
303
|
+
|
|
304
|
+
Set the element’s `open` and `snapPoint` properties, and listen for `openchange` and `snappointchange`; the attributes set where it starts. You can control opening and snap points independently. Cancel a proposed change with `event.preventDefault()`; a canceled swipe springs back. `openchangecomplete` fires after the sheet settles.
|
|
305
|
+
|
|
306
|
+
```html
|
|
307
|
+
<ui-drawer snap-points="0.25,0.5,1" snap-point="0.5"></ui-drawer>
|
|
308
|
+
|
|
309
|
+
<script type="module">
|
|
310
|
+
const drawer = document.querySelector("ui-drawer")
|
|
311
|
+
drawer.addEventListener(
|
|
312
|
+
"openchange",
|
|
313
|
+
(event) => (open.checked = event.detail.value)
|
|
314
|
+
)
|
|
315
|
+
drawer.addEventListener(
|
|
316
|
+
"snappointchange",
|
|
317
|
+
(event) => (size.value = String(event.detail.value))
|
|
318
|
+
)
|
|
319
|
+
drawer.open = true
|
|
320
|
+
drawer.snapPoint = 1
|
|
321
|
+
</script>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Not Dismissible
|
|
325
|
+
|
|
326
|
+
With `dismissible="false"`, Escape, outside presses, back gestures, and swipes leave the drawer open. `dismissattempt` tells you what was tried, so you can offer a way to discard unsaved work. Close parts, triggers, and controllers can still close it deliberately.
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<ui-drawer dismissible="false">
|
|
330
|
+
<ui-drawer-trigger><button>Write a Post</button></ui-drawer-trigger>
|
|
331
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
332
|
+
<ui-drawer-content hidden>
|
|
333
|
+
<ui-drawer-close id="discard" hidden
|
|
334
|
+
><button>Discard</button></ui-drawer-close
|
|
335
|
+
>
|
|
336
|
+
<ui-drawer-close value="post"><button>Post</button></ui-drawer-close>
|
|
337
|
+
</ui-drawer-content>
|
|
338
|
+
</ui-drawer>
|
|
339
|
+
|
|
340
|
+
<script type="module">
|
|
341
|
+
const drawer = document.querySelector("ui-drawer")
|
|
342
|
+
drawer.addEventListener("dismissattempt", () => (discard.hidden = false))
|
|
343
|
+
</script>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Modal
|
|
347
|
+
|
|
348
|
+
Set `modal-above` when the page should stay usable beside a small sheet. At or below that point there is no dim, inert page, scroll lock, or `aria-modal`. Above it, `modal` applies. The switch happens when the drawer settles and does not move focus; `data-interactive` exposes the usable-page state.
|
|
349
|
+
|
|
350
|
+
`modal` has the same choices as [Dialog](./dialog.md#modal): `true` blocks the page, `"trap-focus"` keeps Tab inside while leaving the page usable, and `false` allows focus to leave. A click on the page also closes a `"trap-focus"` drawer, while a non-modal drawer closes on an outside press only when `close-on-outside-press` is enabled.
|
|
351
|
+
|
|
352
|
+
```ts title="non-modal.ts"
|
|
353
|
+
import "@ui-sdk/web/drawer"
|
|
354
|
+
|
|
355
|
+
const places = [
|
|
356
|
+
{ name: "Blue Bottle Coffee", detail: "Coffee · 0.2 mi" },
|
|
357
|
+
{ name: "Tartine Bakery", detail: "Bakery · 0.4 mi" },
|
|
358
|
+
{ name: "Dolores Park", detail: "Park · 0.5 mi" },
|
|
359
|
+
{ name: "Mission Dolores", detail: "Landmark · 0.6 mi" },
|
|
360
|
+
{ name: "Ritual Coffee Roasters", detail: "Coffee · 0.7 mi" },
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
function mount(root: HTMLElement) {
|
|
364
|
+
root.innerHTML = `
|
|
365
|
+
<div data-slot="stack">
|
|
366
|
+
<ul data-slot="list" aria-label="Places">
|
|
367
|
+
${places
|
|
368
|
+
.map(
|
|
369
|
+
(place) => `<li>
|
|
370
|
+
<button type="button" data-place="${place.name}">${place.name}<span data-slot="hint">${place.detail}</span></button>
|
|
371
|
+
</li>`
|
|
372
|
+
)
|
|
373
|
+
.join("")}
|
|
374
|
+
</ul>
|
|
375
|
+
<ui-drawer snap-points="0.2,0.6" modal-above="0.2">
|
|
376
|
+
<ui-drawer-trigger><button>Get Directions</button></ui-drawer-trigger>
|
|
377
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
378
|
+
<ui-drawer-content hidden>
|
|
379
|
+
<ui-drawer-handle><button aria-label="Resize"></button></ui-drawer-handle>
|
|
380
|
+
<div data-slot="row">
|
|
381
|
+
<div data-slot="stack">
|
|
382
|
+
<ui-drawer-title></ui-drawer-title>
|
|
383
|
+
<ui-drawer-description></ui-drawer-description>
|
|
384
|
+
</div>
|
|
385
|
+
<ui-drawer-close><button>Done</button></ui-drawer-close>
|
|
386
|
+
</div>
|
|
387
|
+
<ol data-slot="stack">
|
|
388
|
+
<li>Head north on Valencia St.</li>
|
|
389
|
+
<li>Turn left onto 18th St.</li>
|
|
390
|
+
<li></li>
|
|
391
|
+
</ol>
|
|
392
|
+
</ui-drawer-content>
|
|
393
|
+
</ui-drawer>
|
|
394
|
+
</div>
|
|
395
|
+
`
|
|
396
|
+
const content = root.querySelector("ui-drawer-content")!
|
|
397
|
+
const title = root.querySelector("ui-drawer-title")!
|
|
398
|
+
const description = root.querySelector("ui-drawer-description")!
|
|
399
|
+
const arrival = root.querySelector("ol li:last-child")!
|
|
400
|
+
const buttons = [...root.querySelectorAll<HTMLButtonElement>("[data-place]")]
|
|
401
|
+
const select = (destination: string) => {
|
|
402
|
+
for (const button of buttons) {
|
|
403
|
+
button.setAttribute(
|
|
404
|
+
"aria-pressed",
|
|
405
|
+
String(button.dataset.place === destination)
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
title.textContent = `To ${destination}`
|
|
409
|
+
arrival.textContent = `Arrive at ${destination}.`
|
|
410
|
+
}
|
|
411
|
+
const describe = () => {
|
|
412
|
+
description.textContent = content.hasAttribute("data-interactive")
|
|
413
|
+
? "12 min walk. Pick another place behind the sheet."
|
|
414
|
+
: "12 min walk. Drag the sheet down to see the places."
|
|
415
|
+
}
|
|
416
|
+
for (const button of buttons) {
|
|
417
|
+
button.addEventListener("click", () => select(button.dataset.place!))
|
|
418
|
+
}
|
|
419
|
+
new MutationObserver(describe).observe(content, {
|
|
420
|
+
attributes: true,
|
|
421
|
+
attributeFilter: ["data-interactive"],
|
|
422
|
+
})
|
|
423
|
+
select(places[0].name)
|
|
424
|
+
describe()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export { mount }
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Open directions at the small snap point and choose another place behind the sheet. Expand it to make the sheet modal, then collapse it to use the page again.
|
|
431
|
+
|
|
432
|
+
## Nested Drawers
|
|
433
|
+
|
|
434
|
+
A drawer opened inside another joins its stack. Escape and outside presses affect the topmost one; closing a parent closes its children. `--drawer-stack` follows the sheets above it as they open and drag, so a parent can scale back with `[scale:calc(1-var(--drawer-stack)*0.04)]`.
|
|
435
|
+
|
|
436
|
+
```ts title="nested.ts"
|
|
437
|
+
import "@ui-sdk/web/drawer"
|
|
438
|
+
import "@ui-sdk/web/field"
|
|
439
|
+
import "@ui-sdk/web/input"
|
|
440
|
+
|
|
441
|
+
function mount(root: HTMLElement) {
|
|
442
|
+
root.innerHTML = `
|
|
443
|
+
<ui-drawer>
|
|
444
|
+
<ui-drawer-trigger><button>Account</button></ui-drawer-trigger>
|
|
445
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
446
|
+
<ui-drawer-content hidden>
|
|
447
|
+
<div data-slot="grip" aria-hidden="true"></div>
|
|
448
|
+
<div data-slot="stack">
|
|
449
|
+
<ui-drawer-title>Account</ui-drawer-title>
|
|
450
|
+
<ui-drawer-description>Manage how you sign in.</ui-drawer-description>
|
|
451
|
+
</div>
|
|
452
|
+
<dl>
|
|
453
|
+
<div data-slot="row">
|
|
454
|
+
<dt>Email</dt>
|
|
455
|
+
<dd>hollis@example.com</dd>
|
|
456
|
+
</div>
|
|
457
|
+
<div data-slot="row">
|
|
458
|
+
<dt>Password</dt>
|
|
459
|
+
<dd>Changed 3 months ago</dd>
|
|
460
|
+
</div>
|
|
461
|
+
<div data-slot="row">
|
|
462
|
+
<dt>Two-factor</dt>
|
|
463
|
+
<dd>On</dd>
|
|
464
|
+
</div>
|
|
465
|
+
</dl>
|
|
466
|
+
<div data-slot="stack">
|
|
467
|
+
<ui-drawer>
|
|
468
|
+
<ui-drawer-trigger><button>Change Password</button></ui-drawer-trigger>
|
|
469
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
470
|
+
<ui-drawer-content hidden>
|
|
471
|
+
<div data-slot="grip" aria-hidden="true"></div>
|
|
472
|
+
<div data-slot="stack">
|
|
473
|
+
<ui-drawer-title>Change password</ui-drawer-title>
|
|
474
|
+
<ui-drawer-description>Use at least 12 characters.</ui-drawer-description>
|
|
475
|
+
</div>
|
|
476
|
+
<ui-field>
|
|
477
|
+
<ui-field-label>New password</ui-field-label>
|
|
478
|
+
<ui-input>
|
|
479
|
+
<input type="password" />
|
|
480
|
+
</ui-input>
|
|
481
|
+
</ui-field>
|
|
482
|
+
<div data-slot="stack">
|
|
483
|
+
<ui-drawer-close><button data-variant="solid">Update</button></ui-drawer-close>
|
|
484
|
+
</div>
|
|
485
|
+
</ui-drawer-content>
|
|
486
|
+
</ui-drawer>
|
|
487
|
+
<ui-drawer-close><button data-variant="solid">Done</button></ui-drawer-close>
|
|
488
|
+
</div>
|
|
489
|
+
</ui-drawer-content>
|
|
490
|
+
</ui-drawer>
|
|
491
|
+
`
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export { mount }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Open Account, then Change Password. The parent steps back as the child opens; Escape closes one sheet at a time.
|
|
498
|
+
|
|
499
|
+
## Forms
|
|
500
|
+
|
|
501
|
+
A Close part with the `action` attribute fires an `action` event; pass a Promise to `event.detail.waitUntil()`. The sheet stays open while it runs, including through Escape or a swipe. A rejection leaves the action’s error state available for a retry. See [Alert Dialog’s async action](./alert-dialog.md#async-action) for the shared pending and error contract.
|
|
502
|
+
|
|
503
|
+
`keep-mounted` preserves unfinished fields, and `final-focus` returns focus to the updated profile.
|
|
504
|
+
|
|
505
|
+
```html
|
|
506
|
+
<ui-drawer-content keep-mounted final-focus="#profile-summary" hidden>
|
|
507
|
+
<form><!-- fields --></form>
|
|
508
|
+
<ui-drawer-close action><button>Save Changes</button></ui-drawer-close>
|
|
509
|
+
</ui-drawer-content>
|
|
510
|
+
|
|
511
|
+
<script type="module">
|
|
512
|
+
const close = document.querySelector("ui-drawer-close[action]")
|
|
513
|
+
const button = close.querySelector("button")
|
|
514
|
+
close.addEventListener("action", (event) => {
|
|
515
|
+
button.textContent = "Saving…"
|
|
516
|
+
const saving = save()
|
|
517
|
+
event.detail.waitUntil(saving)
|
|
518
|
+
saving.then(
|
|
519
|
+
() => (button.textContent = "Save Changes"),
|
|
520
|
+
() => (button.textContent = "Try Again")
|
|
521
|
+
)
|
|
522
|
+
})
|
|
523
|
+
</script>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## Outlets
|
|
527
|
+
|
|
528
|
+
An Outlet makes another element follow the sheet’s progress. Its `keyframes` property runs from collapsed to fully expanded; this example scales and rounds the inbox behind a compose sheet. An outlet outside the drawer connects with `for="drawer-id"`.
|
|
529
|
+
|
|
530
|
+
```ts title="outlet.ts"
|
|
531
|
+
import "@ui-sdk/web/drawer"
|
|
532
|
+
import "@ui-sdk/web/field"
|
|
533
|
+
import "@ui-sdk/web/input"
|
|
534
|
+
import "@ui-sdk/web/textarea"
|
|
535
|
+
|
|
536
|
+
const messages = [
|
|
537
|
+
{ from: "Inès", text: "The proofs are back from the printer." },
|
|
538
|
+
{ from: "Kofi", text: "Lunch on Thursday?" },
|
|
539
|
+
{ from: "Wren", text: "Pushed the fix for the release build." },
|
|
540
|
+
{ from: "Tomasz", text: "Can you review the launch checklist?" },
|
|
541
|
+
]
|
|
542
|
+
|
|
543
|
+
function mount(root: HTMLElement) {
|
|
544
|
+
root.innerHTML = `
|
|
545
|
+
<ui-drawer>
|
|
546
|
+
<div>
|
|
547
|
+
<ui-drawer-outlet>
|
|
548
|
+
<div data-slot="row">
|
|
549
|
+
<span>Inbox</span>
|
|
550
|
+
<ui-drawer-trigger><button data-variant="solid">Compose</button></ui-drawer-trigger>
|
|
551
|
+
</div>
|
|
552
|
+
<ul data-slot="list">
|
|
553
|
+
${messages
|
|
554
|
+
.map(
|
|
555
|
+
(message) => `<li>
|
|
556
|
+
<div>${message.from}</div>
|
|
557
|
+
<div data-slot="hint">${message.text}</div>
|
|
558
|
+
</li>`
|
|
559
|
+
)
|
|
560
|
+
.join("")}
|
|
561
|
+
</ul>
|
|
562
|
+
</ui-drawer-outlet>
|
|
563
|
+
</div>
|
|
564
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
565
|
+
<ui-drawer-content hidden>
|
|
566
|
+
<div data-slot="grip" aria-hidden="true"></div>
|
|
567
|
+
<div data-slot="stack">
|
|
568
|
+
<ui-drawer-title>New message</ui-drawer-title>
|
|
569
|
+
<ui-drawer-description>Write a quick note to the team.</ui-drawer-description>
|
|
570
|
+
</div>
|
|
571
|
+
<ui-field>
|
|
572
|
+
<ui-field-label>To</ui-field-label>
|
|
573
|
+
<ui-input>
|
|
574
|
+
<input value="Design team" />
|
|
575
|
+
</ui-input>
|
|
576
|
+
</ui-field>
|
|
577
|
+
<ui-field>
|
|
578
|
+
<ui-field-label>Message</ui-field-label>
|
|
579
|
+
<ui-textarea>
|
|
580
|
+
<textarea placeholder="What's new?"></textarea>
|
|
581
|
+
</ui-textarea>
|
|
582
|
+
</ui-field>
|
|
583
|
+
<div data-slot="stack">
|
|
584
|
+
<ui-drawer-close value="send"><button data-variant="solid">Send</button></ui-drawer-close>
|
|
585
|
+
</div>
|
|
586
|
+
</ui-drawer-content>
|
|
587
|
+
</ui-drawer>
|
|
588
|
+
`
|
|
589
|
+
root.querySelector("ui-drawer-outlet")!.keyframes = {
|
|
590
|
+
scale: [1, 0.94],
|
|
591
|
+
borderRadius: ["0px", "24px"],
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export { mount }
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Open Compose, then drag the sheet slowly. The inbox behind it scales and rounds with the sheet’s progress.
|
|
599
|
+
|
|
600
|
+
## Opening from Anywhere
|
|
601
|
+
|
|
602
|
+
Give the drawer an `id`, connect external triggers with `for`, and assign each trigger’s `payload` property. The drawer’s `payloadchange` event carries it in `event.detail.value`. This lets one sheet serve every row, including disabled triggers for unavailable actions.
|
|
603
|
+
|
|
604
|
+
Calling `open({ payload })` on the drawer’s `api` returns a Promise with the closing `reason` and `returnValue`. `getSnapPoint()` and `setSnapPoint()` also let code read or move the sheet. The [controller guide](../controllers.md) covers the shared result and payload flow.
|
|
605
|
+
|
|
606
|
+
```html
|
|
607
|
+
<ui-drawer-trigger for="share" disabled
|
|
608
|
+
><button>Share</button></ui-drawer-trigger
|
|
609
|
+
>
|
|
610
|
+
|
|
611
|
+
<ui-drawer id="share">
|
|
612
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
613
|
+
<ui-drawer-content hidden>
|
|
614
|
+
<ui-drawer-title></ui-drawer-title>
|
|
615
|
+
</ui-drawer-content>
|
|
616
|
+
</ui-drawer>
|
|
617
|
+
|
|
618
|
+
<script type="module">
|
|
619
|
+
const drawer = document.querySelector("#share")
|
|
620
|
+
document.querySelector("ui-drawer-trigger").payload = file
|
|
621
|
+
drawer.addEventListener("payloadchange", (event) => {
|
|
622
|
+
drawer.querySelector("ui-drawer-title").textContent =
|
|
623
|
+
`Share ${event.detail.value?.name}`
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
const { reason, returnValue } = await drawer.api.open({ payload: roadmap })
|
|
627
|
+
</script>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
## Action Sheet
|
|
631
|
+
|
|
632
|
+
A sheet can offer choices instead of a form. Give each Close part a `value`; the open Promise returns the chosen action. Escape, an outside press, or a swipe returns no value, which this example treats as Cancel.
|
|
633
|
+
|
|
634
|
+
Put the least risky action first because a keyboard or code open starts there. Use an [Alert Dialog](./alert-dialog.md) when a choice needs confirmation.
|
|
635
|
+
|
|
636
|
+
```ts title="action-sheet.ts"
|
|
637
|
+
import "@ui-sdk/web/drawer"
|
|
638
|
+
import { log } from "./log"
|
|
639
|
+
|
|
640
|
+
function mount(root: HTMLElement) {
|
|
641
|
+
root.innerHTML = `
|
|
642
|
+
<div data-slot="row">
|
|
643
|
+
<button type="button">Photo Options</button>
|
|
644
|
+
<ui-drawer>
|
|
645
|
+
<ui-drawer-backdrop hidden></ui-drawer-backdrop>
|
|
646
|
+
<ui-drawer-content hidden>
|
|
647
|
+
<div data-slot="stack">
|
|
648
|
+
<ui-drawer-title>Beach.jpg</ui-drawer-title>
|
|
649
|
+
<ui-drawer-description>Taken today at 9:41.</ui-drawer-description>
|
|
650
|
+
</div>
|
|
651
|
+
<div data-slot="stack">
|
|
652
|
+
<ui-drawer-close value="share"><button>Share</button></ui-drawer-close>
|
|
653
|
+
<ui-drawer-close value="duplicate"><button>Duplicate</button></ui-drawer-close>
|
|
654
|
+
<ui-drawer-close value="delete"><button>Delete</button></ui-drawer-close>
|
|
655
|
+
<ui-drawer-close value="cancel"><button>Cancel</button></ui-drawer-close>
|
|
656
|
+
</div>
|
|
657
|
+
</ui-drawer-content>
|
|
658
|
+
</ui-drawer>
|
|
659
|
+
</div>
|
|
660
|
+
`
|
|
661
|
+
const drawer = root.querySelector("ui-drawer")!
|
|
662
|
+
root.querySelector("button")!.addEventListener("click", async () => {
|
|
663
|
+
const { returnValue = "cancel" } = await drawer.api.open()
|
|
664
|
+
log(root, returnValue === "cancel" ? "Canceled" : `Chose ${returnValue}`)
|
|
665
|
+
})
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export { mount }
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Open Photo Options and choose an action. The console logs the returned value; dismissing the sheet counts as Cancel.
|
|
672
|
+
|
|
673
|
+
## Responsive
|
|
674
|
+
|
|
675
|
+
This recipe uses a media query to choose a drawer on a narrow screen and a dialog on a wide screen. It replaces the markup at the breakpoint, so an open sheet closes and field edits reset. Keep the open state and field values outside that markup if they must survive the switch.
|
|
676
|
+
|
|
677
|
+
```html
|
|
678
|
+
<div id="profile"></div>
|
|
679
|
+
|
|
680
|
+
<script type="module">
|
|
681
|
+
import "@ui-sdk/web/dialog"
|
|
682
|
+
import "@ui-sdk/web/drawer"
|
|
683
|
+
|
|
684
|
+
const isDesktop = matchMedia("(min-width: 768px)")
|
|
685
|
+
const render = () => {
|
|
686
|
+
const name = isDesktop.matches ? "dialog" : "drawer"
|
|
687
|
+
document.querySelector("#profile").innerHTML = `
|
|
688
|
+
<ui-${name}>
|
|
689
|
+
<ui-${name}-trigger><button>Edit Profile</button></ui-${name}-trigger>
|
|
690
|
+
<ui-${name}-backdrop hidden></ui-${name}-backdrop>
|
|
691
|
+
<ui-${name}-content hidden>${profileForm}</ui-${name}-content>
|
|
692
|
+
</ui-${name}>
|
|
693
|
+
`
|
|
694
|
+
}
|
|
695
|
+
isDesktop.addEventListener("change", render)
|
|
696
|
+
render()
|
|
697
|
+
</script>
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## Driver and Physics
|
|
701
|
+
|
|
702
|
+
`driver` picks how the sheet moves. `"scroll-snap"` uses native scrolling and CSS snap points for a bottom sheet; `"pointer"` uses pointer dragging and spring motion on every side. The default `"auto"` chooses native scrolling for a bottom sheet on a touch screen, and pointer dragging for mouse and pen, other sides, `modal-above`, or `expand-on-scroll="false"`. `data-driver` reports the active choice.
|
|
703
|
+
|
|
704
|
+
Override part of `sheetPhysics.ios` with the `physics` property to tune the pointer driver: `response` changes spring timing, `dampingRatio` changes settling, `momentumDampingRatio` affects flings, and `rubberBand` changes resistance past the largest point.
|
|
705
|
+
|
|
706
|
+
```html
|
|
707
|
+
<ui-drawer driver="pointer"></ui-drawer>
|
|
708
|
+
|
|
709
|
+
<script type="module">
|
|
710
|
+
document.querySelector("ui-drawer").physics = {
|
|
711
|
+
response: 0.3,
|
|
712
|
+
dampingRatio: 0.9,
|
|
713
|
+
}
|
|
714
|
+
</script>
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
## Page Integration
|
|
718
|
+
|
|
719
|
+
`theme-color` dims an existing `<meta name="theme-color">` with the backdrop, so the browser's toolbars can follow it; pages without that tag are left alone. `persistent-elements` keeps selected elements usable while the drawer is modal: a comma-separated list of selectors, or assign the `persistentElements` property an array of elements or a function returning elements. Something that must stay above the backdrop also needs its own top-layer surface.
|
|
720
|
+
|
|
721
|
+
Drawers render in place and use the top layer. `container` takes a selector and moves the backdrop and viewport there, for example into a shadow root or out of a surrounding form. See the shared [popup guide](../popups.md#hidden-content-and-containers).
|
|
722
|
+
|
|
723
|
+
```html
|
|
724
|
+
<ui-drawer
|
|
725
|
+
theme-color
|
|
726
|
+
persistent-elements="#chat-launcher"
|
|
727
|
+
container="#drawer-host"
|
|
728
|
+
></ui-drawer>
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
## Custom Rendering
|
|
732
|
+
|
|
733
|
+
Elements have no render functions. Trigger, Close, and Handle parts wrap your native button or link, and `as-child` on the title binds your own heading. Content adds a full-screen `ui-drawer-viewport` around itself when the markup has none. Watch the drawer's `openchange` event and the content's data attributes, like `data-expanded`, to change what's inside.
|
|
734
|
+
|
|
735
|
+
```html
|
|
736
|
+
<ui-drawer>
|
|
737
|
+
<ui-drawer-trigger shortcut="mod+j"
|
|
738
|
+
><button>View Details</button></ui-drawer-trigger
|
|
739
|
+
>
|
|
740
|
+
<ui-drawer-content hidden>
|
|
741
|
+
<ui-drawer-title as-child><h3>Quarterly report</h3></ui-drawer-title>
|
|
742
|
+
<ui-drawer-description>Drag up to see more.</ui-drawer-description>
|
|
743
|
+
<ui-drawer-close
|
|
744
|
+
><a href="/reports/q3">Open the Full Page</a></ui-drawer-close
|
|
745
|
+
>
|
|
746
|
+
</ui-drawer-content>
|
|
747
|
+
</ui-drawer>
|
|
748
|
+
|
|
749
|
+
<script type="module">
|
|
750
|
+
const drawer = document.querySelector("ui-drawer")
|
|
751
|
+
const content = drawer.querySelector("ui-drawer-content")
|
|
752
|
+
drawer.addEventListener("openchange", (event) => {
|
|
753
|
+
drawer.querySelector("ui-drawer-trigger button").textContent = event.detail
|
|
754
|
+
.value
|
|
755
|
+
? "Viewing"
|
|
756
|
+
: "View Details"
|
|
757
|
+
})
|
|
758
|
+
new MutationObserver(() => {
|
|
759
|
+
content.querySelector("ui-drawer-description").textContent =
|
|
760
|
+
content.hasAttribute("data-expanded")
|
|
761
|
+
? "Showing all of it."
|
|
762
|
+
: "Drag up to see more."
|
|
763
|
+
}).observe(content, { attributeFilter: ["data-expanded"] })
|
|
764
|
+
</script>
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
## Styling
|
|
768
|
+
|
|
769
|
+
Each element has a `data-slot`, such as `drawer-content`, and shows its state through the attributes listed under [Data Attributes](#data-attributes).
|
|
770
|
+
|
|
771
|
+
Leave the content's `translate` and the backdrop's opacity to the drawer. You can style scale, corners, colors, and the inner scroll area. A bottom sheet can use an `after` element below its edge to cover the gap during overscroll, as the examples do. The drawer respects reduced motion, fading instead of sliding.
|
|
772
|
+
|
|
773
|
+
### `<ui-drawer-content>`
|
|
774
|
+
- `--drawer-progress`: How far open the sheet is, from 0 closed to 1 at its largest snap point. Never past 1.
|
|
775
|
+
- `--drawer-dim`: How dim the page behind is, from 0 to 1. The backdrop's opacity follows it.
|
|
776
|
+
- `--drawer-expand`: 0 at the second-largest snap point and below, 1 at the largest. With one snap point, the whole travel.
|
|
777
|
+
- `--drawer-stack`: How many sheets opened from inside this one are open above it, rising smoothly as each one opens. Push it back with it.
|
|
778
|
+
|
|
779
|
+
### `<ui-drawer-viewport>`
|
|
780
|
+
- `--keyboard-inset`: How many pixels of the page the on-screen keyboard covers, 0 without one.
|
|
781
|
+
- `--visual-viewport-width`: The width of the part of the page you can see.
|
|
782
|
+
- `--visual-viewport-height`: The height of the part of the page you can see, above the keyboard.
|
|
783
|
+
- `--visual-viewport-top`: How far the part of the page you can see is scrolled from the top of the layout, as when the keyboard pushes it up.
|
|
784
|
+
|
|
785
|
+
### `<ui-drawer-outlet>`
|
|
786
|
+
- `--drawer-progress`: How far open the sheet is, from 0 closed to 1 at its largest snap point. Never past 1.
|
|
787
|
+
- `--drawer-dim`: How dim the page behind is, from 0 to 1. The backdrop's opacity follows it.
|
|
788
|
+
- `--drawer-expand`: 0 at the second-largest snap point and below, 1 at the largest. With one snap point, the whole travel.
|
|
789
|
+
|
|
790
|
+
## Data Attributes
|
|
791
|
+
|
|
792
|
+
### `<ui-drawer-trigger>`
|
|
793
|
+
`[data-slot="drawer-trigger"]`
|
|
794
|
+
|
|
795
|
+
- `data-closed`: Present while the drawer is closed, including while it animates out.
|
|
796
|
+
- `data-disabled`: Present when the trigger is disabled.
|
|
797
|
+
- `data-open`: Present while the drawer is open. On a trigger, while it's open from that trigger.
|
|
798
|
+
|
|
799
|
+
### `<ui-drawer-backdrop>`
|
|
800
|
+
`[data-slot="drawer-backdrop"]`
|
|
801
|
+
|
|
802
|
+
- `data-closed`: Present while the drawer is closed, including while it animates out.
|
|
803
|
+
- `data-open`: Present while the drawer is open. On a trigger, while it's open from that trigger.
|
|
804
|
+
- `data-interactive`: Present while it rests at or below `modalAbove`, so the page behind is usable. The backdrop lets presses through.
|
|
805
|
+
- `data-starting-style`: Present for the first frame. The drawer slides itself, so leave the content's translate and the backdrop's opacity alone.
|
|
806
|
+
- `data-ending-style`: Present while the drawer animates out.
|
|
807
|
+
- `data-dragging`: Present while a finger, mouse or pen drags it.
|
|
808
|
+
|
|
809
|
+
### `<ui-drawer-viewport>`
|
|
810
|
+
`[data-slot="drawer-viewport"]`
|
|
811
|
+
|
|
812
|
+
- `data-side` (`top`, `right`, `bottom`, `left`): The edge it comes from, with `start` and `end` resolved for the writing direction.
|
|
813
|
+
- `data-closed`: Present while the drawer is closed, including while it animates out.
|
|
814
|
+
- `data-open`: Present while the drawer is open. On a trigger, while it's open from that trigger.
|
|
815
|
+
- `data-keyboard`: Present on the viewport while the on-screen keyboard covers part of the page.
|
|
816
|
+
|
|
817
|
+
### `<ui-drawer-content>`
|
|
818
|
+
`[data-slot="drawer-content"]`
|
|
819
|
+
|
|
820
|
+
- `data-closed`: Present while the drawer is closed, including while it animates out.
|
|
821
|
+
- `data-side` (`top`, `right`, `bottom`, `left`): The edge it comes from, with `start` and `end` resolved for the writing direction.
|
|
822
|
+
- `data-open`: Present while the drawer is open. On a trigger, while it's open from that trigger.
|
|
823
|
+
- `data-snap-point` (`content`, `0.2`): The snap point it rests on or is moving to, as written, like `0.5` or `content`.
|
|
824
|
+
- `data-interactive`: Present while it rests at or below `modalAbove`, so the page behind is usable. The backdrop lets presses through.
|
|
825
|
+
- `data-starting-style`: Present for the first frame. The drawer slides itself, so leave the content's translate and the backdrop's opacity alone.
|
|
826
|
+
- `data-ending-style`: Present while the drawer animates out.
|
|
827
|
+
- `data-driver` (`pointer`, `scroll-snap`): What moves it.
|
|
828
|
+
- `data-expanded`: Present while it rests at its largest snap point.
|
|
829
|
+
- `data-dragging`: Present while a finger, mouse or pen drags it.
|
|
830
|
+
|
|
831
|
+
### `<ui-drawer-handle>`
|
|
832
|
+
`[data-slot="drawer-handle"]`
|
|
833
|
+
|
|
834
|
+
- `data-snap-point` (`content`, `0.2`): The snap point it rests on or is moving to, as written, like `0.5` or `content`.
|
|
835
|
+
- `data-expanded`: Present while it rests at its largest snap point.
|
|
836
|
+
|
|
837
|
+
### `<ui-drawer-close>`
|
|
838
|
+
`[data-slot="drawer-close"]`
|
|
839
|
+
|
|
840
|
+
- `data-pending`: Present on a Close part while its action runs.
|
|
841
|
+
|
|
842
|
+
### `<ui-drawer-outlet>`
|
|
843
|
+
`[data-slot="drawer-outlet"]`
|
|
844
|
+
|
|
845
|
+
- `data-closed`: Present while the drawer is closed, including while it animates out.
|
|
846
|
+
- `data-open`: Present while the drawer is open. On a trigger, while it's open from that trigger.
|
|
847
|
+
|
|
848
|
+
## API Reference
|
|
849
|
+
|
|
850
|
+
Props are attributes in kebab case, like `modal-above`: `"false"` is false. `snap-points` takes a
|
|
851
|
+
comma-separated list, like `"0.5,1"` or `"240px,content"`. `container` takes a CSS selector, and
|
|
852
|
+
`persistent-elements` a comma-separated list of selectors. `physics` and `controller` are properties.
|
|
853
|
+
Callbacks are DOM events.
|
|
854
|
+
|
|
855
|
+
### Drawer
|
|
856
|
+
|
|
857
|
+
- `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
|
|
858
|
+
- `container?` (`DocumentFragment | Element | null`): Renders the popup into this element instead of in place, like a container inside a shadow root or an iframe. On web components, a CSS selector. Nested popups stay inside their parent.
|
|
859
|
+
- `open?` (`boolean`): Whether the dialog is open. Pass it to control the dialog.
|
|
860
|
+
- `defaultOpen?` (`boolean`): Whether the dialog starts open, when uncontrolled. Focus moves into it once it's on the page.
|
|
861
|
+
- `openchangecomplete event`: Runs once the dialog has finished opening or closing, after any animation.
|
|
862
|
+
- `dismissible?` (`boolean`): Whether Escape, an outside press and Android's back gesture can close the dialog. Close parts, triggers and the controller always can. Pass `false` while a form has unsaved changes, and ask in `dismissattempt`.
|
|
863
|
+
- `persistentElements?` (`(string | Element)[] | (() => Element[])`): Elements that stay usable while the dialog makes the page inert, like a chat launcher or a password manager's button. Elements, CSS selectors, or a function that returns elements, read when the dialog opens. Presses on them never close it.
|
|
864
|
+
- `pendingAnnouncement?` (`string`): What screen readers hear once a Close part's action has been running for a second, so a slow action doesn't pass in silence. Set it in the page's language. An empty string says nothing.
|
|
865
|
+
- `openchange event`: Runs when the drawer wants to open or close. `details.reason` says why, `"swipe"` included, `details.cancel()` stops it, and `details.returnValue` carries what closed it.
|
|
866
|
+
- `payloadchange event`: Runs when the payload the content shows changes: a trigger with another payload opens the drawer, or takes a non-modal drawer over while it's open, which fires no `openchange`, and so does a controller's `open({ payload })` on the open drawer. `details.cancel()` keeps the previous payload.
|
|
867
|
+
- `dismissattempt event`: Runs when a dismissal was refused because `dismissible` is false, including a swipe. The drawer resists and springs back. Ask "Discard changes?" here.
|
|
868
|
+
- `modal?` (`"trap-focus" | boolean`): `true`: the rest of the page is inert and doesn't scroll, focus is trapped, and an outside press only closes the drawer. `"trap-focus"`: traps focus and leaves the page usable. `false`: a drawer beside the page. See `modalAbove` to make it modal only above a snap point.
|
|
869
|
+
- `closeOnOutsidePress?` (`boolean`): Whether pressing outside the drawer, like on its backdrop, closes it.
|
|
870
|
+
- `controller?` (`DrawerController<any, any>`): Opens, closes and moves the drawer from anywhere, and connects triggers and outlets outside it. Create one with `createDrawerController()`.
|
|
871
|
+
- `snapPoints?` (`SnapPoint[]`): Where the drawer can rest, from the edge it comes from: a fraction of the screen up to 1, pixels above 1, `"…px"`, `"…rem"`, `"…%"`, or `"content"` for its own size. None can be larger than the drawer, which you size with CSS.
|
|
872
|
+
- `snapPoint?` (`SnapPoint | null`): The snap point it rests on. Pass it to control it.
|
|
873
|
+
- `defaultSnapPoint?` (`SnapPoint`): The snap point it opens at, when uncontrolled. It opens there again each time.
|
|
874
|
+
- `snappointchange event`: Runs when the drawer moves to another snap point. `details.reason` says why: a swipe, the handle, the keyboard, a resize or the controller.
|
|
875
|
+
- `side?` (`Side`): The edge it comes from. `"start"` and `"end"` follow the writing direction.
|
|
876
|
+
- `modalAbove?` (`SnapPoint`): At or below this snap point the page stays usable and undimmed, like Maps: no inert page, no scroll lock, no `aria-modal`, and presses beside the drawer reach the page. Above it, `modal` applies. Changes once the drawer comes to rest.
|
|
877
|
+
- `driver?` (`SheetDriverName`): How it moves. `"pointer"` follows touch, mouse and pen with a spring fitted to iOS. `"scroll-snap"` uses the browser's own scrolling for bottom drawers. `"auto"` picks scroll snap for a bottom drawer on a touch screen, unless `modalAbove` is set or `expandOnScroll` is false, and the pointer driver otherwise.
|
|
878
|
+
- `expandOnScroll?` (`boolean`): Whether dragging toward a larger snap point on scrollable content moves the drawer first, until it's at its largest.
|
|
879
|
+
- `physics?` (`Partial<SheetPhysics>`): Overrides the motion: spring, projection, rubber band and dim. Starts from `sheetPhysics.ios`.
|
|
880
|
+
- `themeColor?` (`boolean`): Whether `<meta name="theme-color">` dims with the backdrop, so Safari's toolbars darken too. Written where motion starts and ends, never per frame.
|
|
881
|
+
- `preventEdgeSwipe?` (`boolean`): Whether presses on the drawer near the screen's left and right edges are kept from starting iOS Safari's back and forward swipe. Taps right at those edges then don't click. Read when the content mounts.
|
|
882
|
+
|
|
883
|
+
### DrawerTrigger
|
|
884
|
+
|
|
885
|
+
A trigger outside the drawer names it with `for="drawer-id"`, and takes `payload` as a property.
|
|
886
|
+
|
|
887
|
+
- `disabled?` (`boolean`): Stops the trigger from opening the dialog. A native button can't be focused or pressed, unless `focusableWhenDisabled` is set.
|
|
888
|
+
- `focusableWhenDisabled?` (`boolean`): Keeps a disabled trigger in the tab order with `aria-disabled` instead of the native attribute, so it can host a tooltip that says why. Presses still do nothing.
|
|
889
|
+
- `payload?` (`unknown`): Data for the content to render when this trigger opens the dialog, like the row it belongs to.
|
|
890
|
+
- `shortcut?` (`string`): A keyboard shortcut that presses the trigger, like `"mod+k"`. `mod` is Command on Apple platforms and Control elsewhere. Sets `aria-keyshortcuts`, and a Shortcut part inside shows it. A modal dialog pauses the page's shortcuts while it's open.
|
|
891
|
+
- `controller?` (`PopupController<any, DialogOpenChangeReason, {}, any, DialogPayloadChangeReason>`): Connects a trigger outside the dialog, like one per table row, to the dialog with this controller.
|
|
892
|
+
|
|
893
|
+
### DrawerBackdrop
|
|
894
|
+
|
|
895
|
+
Dims the page behind a modal drawer, which manages its opacity. `<ui-drawer-backdrop>` takes no attributes of its own.
|
|
896
|
+
|
|
897
|
+
### DrawerContent
|
|
898
|
+
|
|
899
|
+
- `container?` (`DocumentFragment | Element | null`): Renders the popup into this element instead of in place, like a container inside a shadow root or an iframe. On web components, a CSS selector. Nested popups stay inside their parent.
|
|
900
|
+
- `initialFocus?` (`DialogInitialFocus`): Where focus goes when the dialog opens. Opening with touch focuses the dialog itself, so no on-screen keyboard appears.
|
|
901
|
+
- `finalFocus?` (`PopupFocusTarget`): Where focus goes when the dialog closes while focus is inside it.
|
|
902
|
+
- `keepMounted?` (`boolean`): Whether the dialog stays in the page, hidden, while closed. For animation libraries that drive the exit from state.
|
|
903
|
+
|
|
904
|
+
### DrawerHandle
|
|
905
|
+
|
|
906
|
+
Resizes the sheet: a press moves to the next snap point, and arrow keys, Home and End choose one. `<ui-drawer-handle>` wraps your native button.
|
|
907
|
+
|
|
908
|
+
### DrawerTitle
|
|
909
|
+
|
|
910
|
+
Names the drawer. `<ui-drawer-title>` renders a level 2 heading.
|
|
911
|
+
|
|
912
|
+
### DrawerDescription
|
|
913
|
+
|
|
914
|
+
Describes the drawer while it's rendered. `<ui-drawer-description>` is a paragraph.
|
|
915
|
+
|
|
916
|
+
### DrawerClose
|
|
917
|
+
|
|
918
|
+
- `value?` (`string`): Reported as `returnValue` in `openchange` and by the controller's `open()`. `createConfirm()` resolves `true` for `"confirm"`.
|
|
919
|
+
- `action?` (`((event: Event | undefined, details: ActionDetails) => unknown)`): Runs when the part is pressed, before the dialog closes. A returned Promise keeps the dialog open while it runs: the part gets `data-pending`, `aria-busy` and `aria-disabled`, screen readers hear the dialog's `pendingAnnouncement` after a second, and nothing but the controller closes the dialog. It closes with reason `"action"` once the Promise resolves. A rejection keeps it open and puts the reason in the part's `error` state until the next press, or until the dialog opens again. `details.source` says how the part was pressed, for changes made after an `await`.
|
|
920
|
+
|
|
921
|
+
### DrawerShortcut
|
|
922
|
+
|
|
923
|
+
- `shortcut?` (`string`): The keys to show. Inside a trigger, its `shortcut`.
|
|
924
|
+
|
|
925
|
+
### DrawerOutlet
|
|
926
|
+
|
|
927
|
+
An outlet outside the drawer names it with `for="drawer-id"`.
|
|
928
|
+
|
|
929
|
+
- `keyframes?` (`SheetKeyframes`): Keyframes it plays as the drawer expands, like `{ scale: [1, 0.94] }`: the first while collapsed, the last at the largest snap point. They run on the compositor when they're transforms or opacity.
|
|
930
|
+
- `controller?` (`DrawerController<any, any>`): Connects an outlet outside the drawer, like the app's shell, to the drawer with this controller.
|
|
931
|
+
|
|
932
|
+
### State
|
|
933
|
+
|
|
934
|
+
The same state shows as data attributes on each element.
|
|
935
|
+
|
|
936
|
+
`DrawerTrigger`:
|
|
937
|
+
|
|
938
|
+
- `open` (`boolean`): Whether the dialog is open from this trigger.
|
|
939
|
+
- `disabled` (`boolean`): Set by the trigger's `disabled` prop, which stops it opening the dialog.
|
|
940
|
+
|
|
941
|
+
`DrawerBackdrop`:
|
|
942
|
+
|
|
943
|
+
- `open` (`boolean`): Whether the dialog is open. False while it animates out.
|
|
944
|
+
- `nested` (`boolean`): Whether the dialog was opened from inside another dialog.
|
|
945
|
+
- `nestedOpen` (`boolean`): Whether a dialog opened from inside this one is open.
|
|
946
|
+
- `interactive` (`boolean`): Whether the page behind is usable, so the backdrop lets presses through.
|
|
947
|
+
|
|
948
|
+
`DrawerContent`:
|
|
949
|
+
|
|
950
|
+
- `open` (`boolean`): Whether the dialog is open. False while it animates out.
|
|
951
|
+
- `modal` (`boolean | "trap-focus"`): The `modal` setting. Always true for an alert dialog.
|
|
952
|
+
- `nested` (`boolean`): Whether the dialog was opened from inside another dialog.
|
|
953
|
+
- `nestedOpen` (`boolean`): Whether a dialog opened from inside this one is open.
|
|
954
|
+
- `stack` (`number`): How many dialogs are open above this one, nested inside it.
|
|
955
|
+
- `payload` (`Payload | undefined`): The payload of the last open, from the trigger that opened it or a controller, typed as the controller's `Payload`.
|
|
956
|
+
- `pending` (`boolean`): Whether a Close part's action is running.
|
|
957
|
+
- `snapPoint` (`SnapPoint | null`): The snap point it rests on or is moving to.
|
|
958
|
+
- `expanded` (`boolean`): Whether it's at its largest snap point.
|
|
959
|
+
- `dragging` (`boolean`): Whether a finger, mouse or pen is dragging it.
|
|
960
|
+
- `side` (`PhysicalSide`): The edge it comes from, with `start` and `end` resolved.
|
|
961
|
+
- `interactive` (`boolean`): Whether the page behind is usable, because it rests at or below `modalAbove`.
|
|
962
|
+
|
|
963
|
+
`DrawerHandle`:
|
|
964
|
+
|
|
965
|
+
- `snapPoint` (`SnapPoint | null`): The snap point it rests on.
|
|
966
|
+
- `canExpand` (`boolean`): Whether there's a larger snap point to move to.
|
|
967
|
+
- `canCollapse` (`boolean`): Whether there's a smaller snap point to move to.
|
|
968
|
+
|
|
969
|
+
`DrawerClose`:
|
|
970
|
+
|
|
971
|
+
- `pending` (`boolean`): Whether this part's action is running.
|
|
972
|
+
- `error` (`unknown`): Why this part's last action failed, until it's pressed again or the dialog opens again.
|
|
973
|
+
|
|
974
|
+
`DrawerOutlet`:
|
|
975
|
+
|
|
976
|
+
- `open` (`boolean`): Whether the drawer is open. False as soon as it starts closing, while it still animates out.
|
|
977
|
+
|
|
978
|
+
### Reasons
|
|
979
|
+
|
|
980
|
+
`event.detail.reason` says why the drawer opened or closed.
|
|
981
|
+
|
|
982
|
+
- `trigger-press` (`MouseEvent | KeyboardEvent | TouchEvent`): Click, tap, Enter or Space on the part that opens, closes or toggles. Arrow keys that open a menu or submenu count too. A tap on a combobox's input that opens a sheet reports its `touchend`.
|
|
983
|
+
- `close-press` (`MouseEvent | KeyboardEvent | SubmitEvent`): A Close part was pressed, or a form with `method="dialog"` was submitted.
|
|
984
|
+
- `outside-press` (`MouseEvent`): A press outside the layer.
|
|
985
|
+
- `escape-key` (`KeyboardEvent`): Escape, or a submenu's close key (ArrowLeft in left-to-right layouts).
|
|
986
|
+
- `parent-close` (`none`): The parent layer closed.
|
|
987
|
+
- `back` (`Event`): Going back: the Android back button or gesture, or Backspace in an empty command input, which returns to the previous page.
|
|
988
|
+
- `action` (`MouseEvent | KeyboardEvent`): A Close part's `action` finished.
|
|
989
|
+
- `shortcut` (`KeyboardEvent`): A keyboard shortcut, like a trigger's `shortcut`.
|
|
990
|
+
- `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
|
|
991
|
+
- `imperative` (`none`): A controller or `api` call, or a property set on a web component.
|
|
992
|
+
|
|
993
|
+
For a payload change, it says what opened the drawer or took it over.
|
|
994
|
+
|
|
995
|
+
- `trigger-press` (`MouseEvent | KeyboardEvent | TouchEvent`): Click, tap, Enter or Space on the part that opens, closes or toggles. Arrow keys that open a menu or submenu count too. A tap on a combobox's input that opens a sheet reports its `touchend`.
|
|
996
|
+
- `shortcut` (`KeyboardEvent`): A keyboard shortcut, like a trigger's `shortcut`.
|
|
997
|
+
- `imperative` (`none`): A controller or `api` call, or a property set on a web component.
|
|
998
|
+
|
|
999
|
+
Why it moved to another snap point:
|
|
1000
|
+
|
|
1001
|
+
- `back` (`Event`): Going back: the Android back button or gesture, or Backspace in an empty command input, which returns to the previous page.
|
|
1002
|
+
- `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
|
|
1003
|
+
- `imperative` (`none`): A controller or `api` call, or a property set on a web component.
|
|
1004
|
+
- `handle-press` (`MouseEvent | KeyboardEvent`): A drawer's handle was pressed, which moved it to its next snap point.
|
|
1005
|
+
- `handle-key` (`KeyboardEvent`): Arrow keys, Home or End on a drawer's handle moved it between snap points.
|
|
1006
|
+
- `content-resize` (`none`): The content or the screen changed size, and the drawer's snap point or the carousel's page went away, so it moved to the nearest one.
|
|
1007
|
+
|
|
1008
|
+
The dismissals `dismissible` can refuse:
|
|
1009
|
+
|
|
1010
|
+
- `outside-press` (`MouseEvent`): A press outside the layer.
|
|
1011
|
+
- `escape-key` (`KeyboardEvent`): Escape, or a submenu's close key (ArrowLeft in left-to-right layouts).
|
|
1012
|
+
- `back` (`Event`): Going back: the Android back button or gesture, or Backspace in an empty command input, which returns to the previous page.
|
|
1013
|
+
- `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
|
|
1014
|
+
|
|
1015
|
+
### Controller
|
|
1016
|
+
|
|
1017
|
+
Create one with `createDrawerController<Payload, Result>()` from `@ui-sdk/web/drawer` and assign it to the root
|
|
1018
|
+
element's `controller` property. The root element's `api` property has the same methods, and its `open` and
|
|
1019
|
+
`snapPoint` properties read and set whether it's open and where it rests.
|
|
1020
|
+
|
|
1021
|
+
Every write method takes an optional `{ reason, event }`, which the events carry. Closing with a controller always works, even when the drawer isn't dismissible or an action is running.
|
|
1022
|
+
|
|
1023
|
+
- `isOpen()` (`() => boolean`): Whether it's open. False as soon as it starts closing, while it still animates out.
|
|
1024
|
+
- `setOpen(open, options?)` (`(open: boolean, options?: PopupChangeOptions<DrawerOpenChangeReason> | undefined) => boolean`): Opens or closes it. Returns false when the change was canceled or it was already open or closed.
|
|
1025
|
+
- `open(options?)` (`(options?: (PopupChangeOptions<DialogPayloadChangeReason> & { payload?: Payload | undefined; anchor?: Anchor | undefined; signal?: AbortSignal | undefined; }) | undefined) => Promise<...>`): Opens it, and resolves once it closes, with why, how and any return value. Resolves exactly once and never rejects: on the next close from any source, when `signal` aborts, or with reason `"unmount"` when the component leaves the page.
|
|
1026
|
+
- `close(options?)` (`(options?: (PopupChangeOptions<DrawerOpenChangeReason> & { returnValue?: Result | undefined; }) | undefined) => boolean`): Closes it. `returnValue` is passed to the promise `open()` returned. Returns false when the close was canceled or it was already closed.
|
|
1027
|
+
- `toggle(options?)` (`(options?: PopupChangeOptions<DrawerOpenChangeReason> | undefined) => boolean`): Opens it when it's closed, and closes it when it's open. Returns false when the change was canceled.
|
|
1028
|
+
- `getPayload()` (`() => Payload | undefined`): The payload of the last open, from a controller or the trigger that opened it.
|
|
1029
|
+
- `focus()` (`() => void`): Moves focus to the trigger that opened it last, or the first trigger.
|
|
1030
|
+
- `getSnapPoint()` (`() => SnapPoint | null`): The snap point it rests on, or null before it first opens.
|
|
1031
|
+
- `setSnapPoint(snapPoint, options?)` (`(snapPoint: SnapPoint, options?: ChangeOptions<DrawerSnapPointChangeReason> | undefined) => boolean`): Moves it to a snap point. While closed, it opens there next. Returns false when the change was canceled or it was already there.
|
|
1032
|
+
|
|
1033
|
+
### Events
|
|
1034
|
+
|
|
1035
|
+
Events fire on their own element and don't bubble. Calling `preventDefault()` on `openchange` or `snappointchange` cancels the change.
|
|
1036
|
+
|
|
1037
|
+
| Member | On | Description |
|
|
1038
|
+
| ------------------------------- | ------------------- | --------------------------------------------------------------------------------------- |
|
|
1039
|
+
| `open` property, attribute | `ui-drawer` | Whether the drawer is open. The attribute sets where it starts, and is live |
|
|
1040
|
+
| `snapPoint` property, attribute | `ui-drawer` | The snap point it rests on. The `snap-point` attribute sets where it starts |
|
|
1041
|
+
| `api` property | `ui-drawer` | The [controller](#controller) |
|
|
1042
|
+
| `controller` property | `ui-drawer` | A controller from `createDrawerController()`, to drive it from code |
|
|
1043
|
+
| `physics` property | `ui-drawer` | Overrides for `sheetPhysics.ios` |
|
|
1044
|
+
| `openchange` event | `ui-drawer` | `detail: { value: open, reason, event, returnValue }` |
|
|
1045
|
+
| `openchangecomplete` event | `ui-drawer` | `detail: { value: open }`, once it has settled |
|
|
1046
|
+
| `payloadchange` event | `ui-drawer` | `detail: { value: payload, reason, event, source }`, when the content's payload changes |
|
|
1047
|
+
| `snappointchange` event | `ui-drawer` | `detail: { value: snapPoint, reason, event }` |
|
|
1048
|
+
| `dismissattempt` event | `ui-drawer` | `detail: { reason, event }`, when a dismissal was refused |
|
|
1049
|
+
| `payload` property | `ui-drawer-trigger` | Data for the content, read when the trigger connects |
|
|
1050
|
+
| `for` attribute | `ui-drawer-trigger` | The `id` of a drawer the trigger opens from outside it |
|
|
1051
|
+
| `for` attribute | `ui-drawer-outlet` | The `id` of a drawer the outlet follows from outside it |
|
|
1052
|
+
| `keyframes` property | `ui-drawer-outlet` | The keyframes it plays as the drawer expands |
|
|
1053
|
+
|
|
1054
|
+
## Accessibility
|
|
1055
|
+
|
|
1056
|
+
The trigger is a button that opens a dialog, and the drawer follows the [WAI-ARIA modal dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) while it's modal: `aria-modal`, focus trapped inside, the page inert, labelled by its title and described by its description. Escape closes it. The handle is a button that controls the drawer: a press moves to the next snap point, arrow keys along the drawer's axis step, and Home and End go to the ends. Every line below is an automated test.
|
|
1057
|
+
|
|
1058
|
+
### Keyboard
|
|
1059
|
+
|
|
1060
|
+
| Key | Does |
|
|
1061
|
+
| ------------------------- | ----------------------------------------------------------------------------- |
|
|
1062
|
+
| Enter, Space | On the trigger, opens the drawer; on the handle, moves to the next snap point |
|
|
1063
|
+
| Arrow keys along the axis | On the handle, moves one snap point without wrapping |
|
|
1064
|
+
| Home, End | On the handle, moves to the smallest or largest snap point |
|
|
1065
|
+
| Tab, Shift+Tab | Moves through the sheet's controls, staying inside while it's modal |
|
|
1066
|
+
| Escape | Closes the topmost drawer when it's dismissible |
|
|
1067
|
+
|
|
1068
|
+
It shares [Dialog’s focus behavior](./dialog.md#focus): focus moves inside on open and returns when it closes. A touch open focuses the content so a field does not summon the keyboard immediately. When the on-screen keyboard opens, the viewport shrinks and the focused field can scroll into view inside the sheet.
|
|
1069
|
+
|
|
1070
|
+
### Tested Behavior
|
|
1071
|
+
|
|
1072
|
+
**Dialog**
|
|
1073
|
+
|
|
1074
|
+
- The trigger advertises a dialog.
|
|
1075
|
+
- The trigger opens a modal dialog named by its title and described by its description.
|
|
1076
|
+
- Opening moves focus into the drawer and closing returns it to the trigger.
|
|
1077
|
+
- Tab stays inside the drawer.
|
|
1078
|
+
- Escape closes it with the escape-key reason.
|
|
1079
|
+
- Pressing the backdrop closes it.
|
|
1080
|
+
- A Close part closes it.
|
|
1081
|
+
- Buttons inside it work without closing it.
|
|
1082
|
+
- The page behind is hidden from assistive technology while it's open.
|
|
1083
|
+
- Opening and closing never scroll or restyle the page.
|
|
1084
|
+
- A controller opens it from outside.
|
|
1085
|
+
|
|
1086
|
+
**Sheet**
|
|
1087
|
+
|
|
1088
|
+
- It rises from the bottom and rests against it at its own size.
|
|
1089
|
+
- It opens at the first snap point, a fraction of the screen.
|
|
1090
|
+
- A slow drag settles on the nearest snap point and reports the swipe.
|
|
1091
|
+
- A drag down past the smallest snap point closes it with the swipe reason.
|
|
1092
|
+
- A short fast flick down closes it, though it moved less than half its height.
|
|
1093
|
+
- The same distance, slowly, settles back.
|
|
1094
|
+
- A fling up from the smallest snap point can pass the middle one.
|
|
1095
|
+
- A press while it rises catches it and holds it under the finger.
|
|
1096
|
+
- A short drag back up keeps it open.
|
|
1097
|
+
- A click on a button inside it isn't a drag.
|
|
1098
|
+
- With side="…" it rests against the … edge.
|
|
1099
|
+
- With side="left" a drag toward the left closes it.
|
|
1100
|
+
|
|
1101
|
+
**Touch**
|
|
1102
|
+
|
|
1103
|
+
- A touch drag down closes it.
|
|
1104
|
+
|
|
1105
|
+
**Handle**
|
|
1106
|
+
|
|
1107
|
+
- Pressing it moves to the next snap point and wraps.
|
|
1108
|
+
- Arrow keys step without wrapping, and Home and End go to the ends.
|
|
1109
|
+
- Moving it moves the drawer to the snap point's extent.
|
|
1110
|
+
- A snap point a controller sets while it animates closed is where it opens next.
|
|
1111
|
+
- A controller moves it with the imperative reason.
|
|
1112
|
+
|
|
1113
|
+
**Not dismissible**
|
|
1114
|
+
|
|
1115
|
+
- Escape reports an attempt and keeps it open.
|
|
1116
|
+
- A press on the backdrop reports an attempt and keeps it open.
|
|
1117
|
+
- A swipe down resists, springs back and reports an attempt.
|
|
1118
|
+
|
|
1119
|
+
**Modal above a snap point**
|
|
1120
|
+
|
|
1121
|
+
- At that snap point the page stays usable and the drawer isn't modal.
|
|
1122
|
+
- Above it the drawer becomes modal, and back down the page is usable again.
|
|
1123
|
+
- The switch doesn't move focus.
|
|
1124
|
+
|
|
1125
|
+
**Nested**
|
|
1126
|
+
|
|
1127
|
+
- Escape closes only the drawer on top.
|
|
1128
|
+
|
|
1129
|
+
Runs in React, Vue and web components.
|
|
1130
|
+
|
|
1131
|
+
Screen readers and layouts the drawer is tested against:
|
|
1132
|
+
|
|
1133
|
+
- No axe violations while closed.
|
|
1134
|
+
- No axe violations while open.
|
|
1135
|
+
- No axe violations while a nested drawer is open.
|
|
1136
|
+
- No axe violations while it rests where the page stays usable.
|
|
1137
|
+
- The drawer's accessibility tree is a dialog with its handle, title and actions.
|
|
1138
|
+
- The handle is a button that controls the drawer.
|
|
1139
|
+
|
|
1140
|
+
Runs in React, Vue and web components.
|
|
1141
|
+
|
|
1142
|
+
- Side="start" comes from the right in a right-to-left layout.
|
|
1143
|
+
- Side="end" comes from the left in a right-to-left layout.
|
|
1144
|
+
- A drag toward the start edge closes it in a right-to-left layout.
|
|
1145
|
+
|
|
1146
|
+
Runs in React, Vue and web components.
|
|
1147
|
+
|
|
1148
|
+
Also tested:
|
|
1149
|
+
|
|
1150
|
+
- Without a touch screen `auto` picks the pointer driver.
|
|
1151
|
+
|
|
1152
|
+
**Dialog**
|
|
1153
|
+
|
|
1154
|
+
- An open modal drawer is marked aria-modal.
|
|
1155
|
+
- A keyboard open focuses its first control, and Tab cycles through the controls in order.
|
|
1156
|
+
- A controller's open() resolves with how it closed.
|
|
1157
|
+
|
|
1158
|
+
**Shortcuts**
|
|
1159
|
+
|
|
1160
|
+
- A trigger connected through a controller shows its shortcut in a Shortcut part, and binds it.
|
|
1161
|
+
|
|
1162
|
+
**State attributes**
|
|
1163
|
+
|
|
1164
|
+
- The content names its side, its snap point and whether it's at the largest.
|
|
1165
|
+
- A drawer resting at its own size reports the content snap point and is expanded.
|
|
1166
|
+
- Data-dragging is set while a drag moves it.
|
|
1167
|
+
- A drawer opened from inside another marks both, and the one beneath reads its depth from --drawer-stack.
|
|
1168
|
+
|
|
1169
|
+
**Physics**
|
|
1170
|
+
|
|
1171
|
+
- Past the largest snap point it stretches less than 24px and springs back.
|
|
1172
|
+
|
|
1173
|
+
**Touch**
|
|
1174
|
+
|
|
1175
|
+
- Pulling down on scrolled content scrolls it to the top, then moves the drawer in the same gesture.
|
|
1176
|
+
- Pushing up on content below the largest snap point grows the drawer before the content scrolls.
|
|
1177
|
+
|
|
1178
|
+
**Compositor**
|
|
1179
|
+
|
|
1180
|
+
- A settle runs as a spring sampled into linear(), on translate and the backdrop's opacity.
|
|
1181
|
+
- While a finger drives it, the drawer writes no attributes, only styles.
|
|
1182
|
+
- Its variables are registered and don't inherit, so moving it restyles only the drawer.
|
|
1183
|
+
- With reduced motion it fades in and out instead of sliding.
|
|
1184
|
+
|
|
1185
|
+
**Page**
|
|
1186
|
+
|
|
1187
|
+
- An outlet outside the drawer follows it, and goes back when it closes.
|
|
1188
|
+
- The theme color dims while it's open and comes back when it closes.
|
|
1189
|
+
- Content that grows moves its content snap point, and the drawer follows.
|
|
1190
|
+
- Escape on a drawer that can't be dismissed nudges it toward closing.
|
|
1191
|
+
|
|
1192
|
+
**Scroll-snap driver**
|
|
1193
|
+
|
|
1194
|
+
- On a touch screen it's the driver `auto` picks.
|
|
1195
|
+
- On a touch screen `auto` keeps the pointer driver with …=….
|
|
1196
|
+
- It opens at its first snap point with the browser's scrolling.
|
|
1197
|
+
- A swipe up snaps to the next snap point and reports the swipe.
|
|
1198
|
+
- It comes to rest when the scroll ends before the page hears the finger lift.
|
|
1199
|
+
- The handle moves it with a spring, like the pointer driver.
|
|
1200
|
+
|
|
1201
|
+
Runs in React, Vue and web components.
|