@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,1055 @@
|
|
|
1
|
+
# Carousel
|
|
2
|
+
|
|
3
|
+
Page through slides with native scrolling, keyboard controls, and optional rotation.
|
|
4
|
+
|
|
5
|
+
Use a Carousel to show a set of slides one view at a time, like product photos or featured stories. It scrolls natively, so swipes and trackpads work, and Previous and Next buttons page through the slides.
|
|
6
|
+
|
|
7
|
+
```ts title="basic.ts"
|
|
8
|
+
import "@ui-sdk/web/carousel"
|
|
9
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
10
|
+
|
|
11
|
+
const slides = [1, 2, 3, 4, 5]
|
|
12
|
+
|
|
13
|
+
function mount(root: HTMLElement) {
|
|
14
|
+
root.innerHTML = `
|
|
15
|
+
<ui-carousel aria-label="Featured">
|
|
16
|
+
<div data-slot="row">
|
|
17
|
+
<ui-carousel-previous>
|
|
18
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
19
|
+
</ui-carousel-previous>
|
|
20
|
+
<ui-carousel-viewport>
|
|
21
|
+
<ui-carousel-content>
|
|
22
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
23
|
+
</ui-carousel-content>
|
|
24
|
+
</ui-carousel-viewport>
|
|
25
|
+
<ui-carousel-next>
|
|
26
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
27
|
+
</ui-carousel-next>
|
|
28
|
+
</div>
|
|
29
|
+
</ui-carousel>
|
|
30
|
+
`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { mount }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use Previous and Next, swipe the slides, or focus the viewport and use the arrow keys.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @ui-sdk/web @ui-sdk/core
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Importing the component defines its elements. On the server the import does nothing.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import "@ui-sdk/web/carousel"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Composition
|
|
51
|
+
|
|
52
|
+
Use the following composition to build a `ui-carousel`:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
ui-carousel
|
|
56
|
+
├── ui-carousel-viewport
|
|
57
|
+
│ └── ui-carousel-content
|
|
58
|
+
│ ├── ui-carousel-item
|
|
59
|
+
│ └── ui-carousel-item
|
|
60
|
+
├── ui-carousel-previous
|
|
61
|
+
│ └── button
|
|
62
|
+
└── ui-carousel-next
|
|
63
|
+
└── button
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Put the slides inside a viewport, then add the controls you need.
|
|
67
|
+
|
|
68
|
+
The viewport is the scrolling window, and the content is the row of slides inside it, a flex row (a column when vertical). Name the carousel with `aria-label`, like "Featured products". Previous, Next, the indicators and the play toggle wrap your own `<button>`: give it text, or an `aria-label` when it holds an icon.
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
Import the parts and put them together:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<script type="module">
|
|
76
|
+
import "@ui-sdk/web/carousel"
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<ui-carousel aria-label="Featured">
|
|
80
|
+
<ui-carousel-viewport>
|
|
81
|
+
<ui-carousel-content>
|
|
82
|
+
<ui-carousel-item>First slide</ui-carousel-item>
|
|
83
|
+
<ui-carousel-item>Second slide</ui-carousel-item>
|
|
84
|
+
</ui-carousel-content>
|
|
85
|
+
</ui-carousel-viewport>
|
|
86
|
+
<ui-carousel-previous><button>Previous</button></ui-carousel-previous>
|
|
87
|
+
<ui-carousel-next><button>Next</button></ui-carousel-next>
|
|
88
|
+
</ui-carousel>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Vertical
|
|
92
|
+
|
|
93
|
+
`orientation="vertical"` scrolls up and down. Give the viewport a height. Up and down arrows page it, not left and right.
|
|
94
|
+
|
|
95
|
+
```ts title="vertical.ts"
|
|
96
|
+
import "@ui-sdk/web/carousel"
|
|
97
|
+
import { ChevronDown, ChevronUp, createElement } from "lucide"
|
|
98
|
+
|
|
99
|
+
const slides = [1, 2, 3, 4, 5]
|
|
100
|
+
|
|
101
|
+
function mount(root: HTMLElement) {
|
|
102
|
+
root.innerHTML = `
|
|
103
|
+
<ui-carousel aria-label="Featured" orientation="vertical">
|
|
104
|
+
<ui-carousel-viewport>
|
|
105
|
+
<ui-carousel-content>
|
|
106
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
107
|
+
</ui-carousel-content>
|
|
108
|
+
</ui-carousel-viewport>
|
|
109
|
+
<div data-slot="row">
|
|
110
|
+
<ui-carousel-previous>
|
|
111
|
+
<button aria-label="Previous slide">${createElement(ChevronUp, { "aria-hidden": "true" }).outerHTML}</button>
|
|
112
|
+
</ui-carousel-previous>
|
|
113
|
+
<ui-carousel-next>
|
|
114
|
+
<button aria-label="Next slide">${createElement(ChevronDown, { "aria-hidden": "true" }).outerHTML}</button>
|
|
115
|
+
</ui-carousel-next>
|
|
116
|
+
</div>
|
|
117
|
+
</ui-carousel>
|
|
118
|
+
`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { mount }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Scroll vertically, or use the up and down controls.
|
|
125
|
+
|
|
126
|
+
## Loop
|
|
127
|
+
|
|
128
|
+
`loop` makes the slides go round. Next on the last page goes to the first, which comes in after the last, and a swipe, a drag or a wheel back from the first page reaches the last, with native scrolling all the way. Rotation always wraps.
|
|
129
|
+
|
|
130
|
+
The slides take turns at the ends of the content with CSS `order` while the carousel rests, so the current page always has slides on both sides. They keep their place in the document, so screen readers and Tab follow the slides' own order. The content needs to lay its slides out in a row with even gaps, as flex and grid do, and hold a page of slides to either side of the current one. With fewer, like two slides one to a view, Next on the last page scrolls back to the first instead.
|
|
131
|
+
|
|
132
|
+
```ts title="loop.ts"
|
|
133
|
+
import "@ui-sdk/web/carousel"
|
|
134
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
135
|
+
|
|
136
|
+
const slides = [1, 2, 3, 4, 5]
|
|
137
|
+
|
|
138
|
+
function mount(root: HTMLElement) {
|
|
139
|
+
root.innerHTML = `
|
|
140
|
+
<ui-carousel aria-label="Featured" loop>
|
|
141
|
+
<div data-slot="row">
|
|
142
|
+
<ui-carousel-previous>
|
|
143
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
144
|
+
</ui-carousel-previous>
|
|
145
|
+
<ui-carousel-viewport>
|
|
146
|
+
<ui-carousel-content>
|
|
147
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
148
|
+
</ui-carousel-content>
|
|
149
|
+
</ui-carousel-viewport>
|
|
150
|
+
<ui-carousel-next>
|
|
151
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
152
|
+
</ui-carousel-next>
|
|
153
|
+
</div>
|
|
154
|
+
</ui-carousel>
|
|
155
|
+
`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { mount }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Keep moving forward or backward to wrap around the slides.
|
|
162
|
+
|
|
163
|
+
## Align
|
|
164
|
+
|
|
165
|
+
`align` lines pages up at the viewport's `start` (the default), `center` or `end`. Centered slides narrower than the viewport let their neighbors peek in. The first and last pages stay at the ends of the content, so there's no empty space before the first slide.
|
|
166
|
+
|
|
167
|
+
```ts title="align.ts"
|
|
168
|
+
import "@ui-sdk/web/carousel"
|
|
169
|
+
import "@ui-sdk/web/select"
|
|
170
|
+
import {
|
|
171
|
+
Check,
|
|
172
|
+
ChevronDown,
|
|
173
|
+
ChevronLeft,
|
|
174
|
+
ChevronRight,
|
|
175
|
+
createElement,
|
|
176
|
+
} from "lucide"
|
|
177
|
+
|
|
178
|
+
const photos = [
|
|
179
|
+
{ name: "Dunes" },
|
|
180
|
+
{ name: "Forest" },
|
|
181
|
+
{ name: "Glacier" },
|
|
182
|
+
{ name: "Canyon" },
|
|
183
|
+
{ name: "Meadow" },
|
|
184
|
+
{ name: "Harbor" },
|
|
185
|
+
]
|
|
186
|
+
const alignments = [
|
|
187
|
+
{ value: "start", label: "Start" },
|
|
188
|
+
{ value: "center", label: "Center" },
|
|
189
|
+
{ value: "end", label: "End" },
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
function mount(root: HTMLElement) {
|
|
193
|
+
root.innerHTML = `
|
|
194
|
+
<div data-slot="stack">
|
|
195
|
+
<div data-slot="controls">
|
|
196
|
+
<ui-select default-value="center">
|
|
197
|
+
<ui-select-label>Align</ui-select-label>
|
|
198
|
+
<ui-select-trigger>
|
|
199
|
+
<button>
|
|
200
|
+
<ui-select-value></ui-select-value>
|
|
201
|
+
${createElement(ChevronDown, { "aria-hidden": "true" }).outerHTML}
|
|
202
|
+
</button>
|
|
203
|
+
</ui-select-trigger>
|
|
204
|
+
<ui-select-content hidden>
|
|
205
|
+
${alignments
|
|
206
|
+
.map(
|
|
207
|
+
(option) => `
|
|
208
|
+
<ui-select-item value="${option.value}">
|
|
209
|
+
${option.label}
|
|
210
|
+
<ui-select-item-indicator>${createElement(Check, { "aria-hidden": "true" }).outerHTML}</ui-select-item-indicator>
|
|
211
|
+
</ui-select-item>
|
|
212
|
+
`
|
|
213
|
+
)
|
|
214
|
+
.join("")}
|
|
215
|
+
</ui-select-content>
|
|
216
|
+
</ui-select>
|
|
217
|
+
</div>
|
|
218
|
+
<ui-carousel aria-label="Photos" align="center" default-page="2">
|
|
219
|
+
<div data-slot="row">
|
|
220
|
+
<ui-carousel-previous>
|
|
221
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
222
|
+
</ui-carousel-previous>
|
|
223
|
+
<ui-carousel-viewport>
|
|
224
|
+
<ui-carousel-content>
|
|
225
|
+
${photos.map((photo) => `<ui-carousel-item><div data-slot="tile">${photo.name}</div></ui-carousel-item>`).join("")}
|
|
226
|
+
</ui-carousel-content>
|
|
227
|
+
</ui-carousel-viewport>
|
|
228
|
+
<ui-carousel-next>
|
|
229
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
230
|
+
</ui-carousel-next>
|
|
231
|
+
</div>
|
|
232
|
+
</ui-carousel>
|
|
233
|
+
</div>
|
|
234
|
+
`
|
|
235
|
+
const carousel = root.querySelector("ui-carousel")!
|
|
236
|
+
root.querySelector("ui-select")!.addEventListener("valuechange", (event) => {
|
|
237
|
+
if (event.detail.value) {
|
|
238
|
+
carousel.setAttribute("align", String(event.detail.value))
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export { mount }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Choose Start, Center or End, then page through the slides. Centered slides let their neighbors peek in on both sides.
|
|
247
|
+
|
|
248
|
+
## Default Page
|
|
249
|
+
|
|
250
|
+
`default-page` is the page it starts on, when uncontrolled. It's 1 by default.
|
|
251
|
+
|
|
252
|
+
```ts title="default-page.ts"
|
|
253
|
+
import "@ui-sdk/web/carousel"
|
|
254
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
255
|
+
|
|
256
|
+
const slides = [1, 2, 3, 4, 5]
|
|
257
|
+
|
|
258
|
+
function mount(root: HTMLElement) {
|
|
259
|
+
root.innerHTML = `
|
|
260
|
+
<ui-carousel aria-label="Featured" default-page="3">
|
|
261
|
+
<div data-slot="row">
|
|
262
|
+
<ui-carousel-previous>
|
|
263
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
264
|
+
</ui-carousel-previous>
|
|
265
|
+
<ui-carousel-viewport>
|
|
266
|
+
<ui-carousel-content>
|
|
267
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
268
|
+
</ui-carousel-content>
|
|
269
|
+
</ui-carousel-viewport>
|
|
270
|
+
<ui-carousel-next>
|
|
271
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
272
|
+
</ui-carousel-next>
|
|
273
|
+
</div>
|
|
274
|
+
</ui-carousel>
|
|
275
|
+
`
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export { mount }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Start on the chosen page, then move forward or backward.
|
|
282
|
+
|
|
283
|
+
## Indicators
|
|
284
|
+
|
|
285
|
+
Indicators are a tablist: one Tab stop on the current page, arrows to move between them and show their page, Home and End for the ends. Each controls the slides of its page.
|
|
286
|
+
|
|
287
|
+
Write one per slide. Those past the page count hide themselves, so the same markup works however many slides
|
|
288
|
+
fit. An indicator's `page` is inferred from their order; pass it to render the selected one on the server.
|
|
289
|
+
|
|
290
|
+
```ts title="indicators.ts"
|
|
291
|
+
import "@ui-sdk/web/carousel"
|
|
292
|
+
|
|
293
|
+
const photos = [
|
|
294
|
+
{ name: "Dunes" },
|
|
295
|
+
{ name: "Forest" },
|
|
296
|
+
{ name: "Glacier" },
|
|
297
|
+
{ name: "Canyon" },
|
|
298
|
+
{ name: "Meadow" },
|
|
299
|
+
{ name: "Harbor" },
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
function mount(root: HTMLElement) {
|
|
303
|
+
root.innerHTML = `
|
|
304
|
+
<ui-carousel aria-label="Photos">
|
|
305
|
+
<ui-carousel-viewport>
|
|
306
|
+
<ui-carousel-content>
|
|
307
|
+
${photos.map((photo) => `<ui-carousel-item><div data-slot="tile">${photo.name}</div></ui-carousel-item>`).join("")}
|
|
308
|
+
</ui-carousel-content>
|
|
309
|
+
</ui-carousel-viewport>
|
|
310
|
+
<ui-carousel-indicator-group aria-label="Choose a photo">
|
|
311
|
+
${photos.map((photo) => `<ui-carousel-indicator><button aria-label="${photo.name}"></button></ui-carousel-indicator>`).join("")}
|
|
312
|
+
</ui-carousel-indicator-group>
|
|
313
|
+
</ui-carousel>
|
|
314
|
+
`
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export { mount }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Choose a named indicator, then use arrows, Home, and End to move between them.
|
|
321
|
+
|
|
322
|
+
## Pages
|
|
323
|
+
|
|
324
|
+
`slides-per-page` is how many slides a page holds: Next moves that many, and each indicator stands for that
|
|
325
|
+
many. `"auto"` fits as many as show. Pages that can't reach the start, like the last few slides, merge into
|
|
326
|
+
the last page. With three slides in view, five slides make three pages at one slide per page, or two pages at three per page.
|
|
327
|
+
|
|
328
|
+
```ts title="pages.ts"
|
|
329
|
+
import "@ui-sdk/web/carousel"
|
|
330
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
331
|
+
|
|
332
|
+
const products = [1, 2, 3, 4, 5, 6, 7, 8]
|
|
333
|
+
|
|
334
|
+
function mount(root: HTMLElement) {
|
|
335
|
+
root.innerHTML = `
|
|
336
|
+
<ui-carousel aria-label="Products" slides-per-page="3">
|
|
337
|
+
<div data-slot="row">
|
|
338
|
+
<ui-carousel-previous>
|
|
339
|
+
<button aria-label="Previous page">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
340
|
+
</ui-carousel-previous>
|
|
341
|
+
<ui-carousel-viewport>
|
|
342
|
+
<ui-carousel-content>
|
|
343
|
+
${products.map((product) => `<ui-carousel-item><div data-slot="tile">${product}</div></ui-carousel-item>`).join("")}
|
|
344
|
+
</ui-carousel-content>
|
|
345
|
+
</ui-carousel-viewport>
|
|
346
|
+
<ui-carousel-next>
|
|
347
|
+
<button aria-label="Next page">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
348
|
+
</ui-carousel-next>
|
|
349
|
+
</div>
|
|
350
|
+
<div data-slot="row">
|
|
351
|
+
<ui-carousel-indicator-group aria-label="Choose a page">
|
|
352
|
+
${products.map((product) => `<ui-carousel-indicator><button aria-label="Page ${product}"></button></ui-carousel-indicator>`).join("")}
|
|
353
|
+
</ui-carousel-indicator-group>
|
|
354
|
+
</div>
|
|
355
|
+
</ui-carousel>
|
|
356
|
+
`
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { mount }
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Move through groups of slides using the page controls.
|
|
363
|
+
|
|
364
|
+
## Thumbnails
|
|
365
|
+
|
|
366
|
+
A gallery and a strip of thumbnails share the selected page. The thumbnail carousel scrolls freely with `drag-free` and fits as many as show. Pressing a thumbnail selects its slide.
|
|
367
|
+
|
|
368
|
+
```ts title="thumbnails.ts"
|
|
369
|
+
import "@ui-sdk/web/carousel"
|
|
370
|
+
|
|
371
|
+
const photos = [
|
|
372
|
+
{ name: "Dunes" },
|
|
373
|
+
{ name: "Forest" },
|
|
374
|
+
{ name: "Glacier" },
|
|
375
|
+
{ name: "Canyon" },
|
|
376
|
+
{ name: "Meadow" },
|
|
377
|
+
{ name: "Harbor" },
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
function mount(root: HTMLElement) {
|
|
381
|
+
root.innerHTML = `
|
|
382
|
+
<div data-slot="stack">
|
|
383
|
+
<ui-carousel aria-label="Photos">
|
|
384
|
+
<ui-carousel-viewport>
|
|
385
|
+
<ui-carousel-content>
|
|
386
|
+
${photos.map((photo) => `<ui-carousel-item><div data-slot="tile">${photo.name}</div></ui-carousel-item>`).join("")}
|
|
387
|
+
</ui-carousel-content>
|
|
388
|
+
</ui-carousel-viewport>
|
|
389
|
+
</ui-carousel>
|
|
390
|
+
<ui-carousel aria-label="Thumbnails" drag-free slides-per-page="auto">
|
|
391
|
+
<ui-carousel-viewport>
|
|
392
|
+
<ui-carousel-content>
|
|
393
|
+
${photos.map((photo, index) => `<ui-carousel-item aria-label="${photo.name}"><button type="button" data-page="${index + 1}" aria-pressed="${index === 0}">${photo.name}</button></ui-carousel-item>`).join("")}
|
|
394
|
+
</ui-carousel-content>
|
|
395
|
+
</ui-carousel-viewport>
|
|
396
|
+
</ui-carousel>
|
|
397
|
+
</div>
|
|
398
|
+
`
|
|
399
|
+
const gallery = root.querySelector("ui-carousel")!
|
|
400
|
+
const thumbnails = [
|
|
401
|
+
...root.querySelectorAll<HTMLButtonElement>("button[data-page]"),
|
|
402
|
+
]
|
|
403
|
+
const show = (page: number) => {
|
|
404
|
+
for (const thumbnail of thumbnails) {
|
|
405
|
+
thumbnail.setAttribute(
|
|
406
|
+
"aria-pressed",
|
|
407
|
+
String(thumbnail.dataset.page === String(page))
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
thumbnails[page - 1]?.scrollIntoView({
|
|
411
|
+
block: "nearest",
|
|
412
|
+
inline: "nearest",
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
for (const thumbnail of thumbnails) {
|
|
416
|
+
thumbnail.addEventListener(
|
|
417
|
+
"click",
|
|
418
|
+
() => (gallery.page = Number(thumbnail.dataset.page))
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
gallery.addEventListener("pagechange", (event) => show(event.detail.value))
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export { mount }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Choose a thumbnail to change the gallery, or scroll either strip.
|
|
428
|
+
|
|
429
|
+
## Autoplay
|
|
430
|
+
|
|
431
|
+
`default-playing` starts rotating, every `interval` milliseconds (5000 by default). `ui-carousel-play-toggle`
|
|
432
|
+
starts and stops it. Its button has `data-playing` while it rotates, so its label can switch with CSS.
|
|
433
|
+
|
|
434
|
+
As the carousel pattern asks, rotation stops when keyboard focus moves in and doesn't restart until the user asks, and pauses while the mouse is over it. It also pauses while the tab is hidden, while the carousel is scrolled out of view and while a finger is down. Changing slides yourself stops it. The slides are a polite live region only while it isn't rotating, so screen readers aren't interrupted every few seconds. It doesn't start on its own when the user prefers reduced motion.
|
|
435
|
+
|
|
436
|
+
`--carousel-interval` on the root is the interval, and `data-paused` is there while it's paused, so a progress bar can time itself with CSS.
|
|
437
|
+
|
|
438
|
+
```ts title="autoplay.ts"
|
|
439
|
+
import "@ui-sdk/web/carousel"
|
|
440
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
441
|
+
import { log } from "./log"
|
|
442
|
+
|
|
443
|
+
const photos = [
|
|
444
|
+
{ name: "Dunes" },
|
|
445
|
+
{ name: "Forest" },
|
|
446
|
+
{ name: "Glacier" },
|
|
447
|
+
{ name: "Canyon" },
|
|
448
|
+
{ name: "Meadow" },
|
|
449
|
+
{ name: "Harbor" },
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
function mount(root: HTMLElement) {
|
|
453
|
+
root.innerHTML = `
|
|
454
|
+
<ui-carousel aria-label="Photos" default-playing interval="4000">
|
|
455
|
+
<div data-slot="controls">
|
|
456
|
+
<ui-carousel-play-toggle>
|
|
457
|
+
<button><span data-show="playing">Pause</span><span data-show="stopped">Play</span></button>
|
|
458
|
+
</ui-carousel-play-toggle>
|
|
459
|
+
</div>
|
|
460
|
+
<div data-slot="row">
|
|
461
|
+
<ui-carousel-previous>
|
|
462
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
463
|
+
</ui-carousel-previous>
|
|
464
|
+
<ui-carousel-viewport>
|
|
465
|
+
<ui-carousel-content>
|
|
466
|
+
${photos.map((photo) => `<ui-carousel-item><div data-slot="tile">${photo.name}</div></ui-carousel-item>`).join("")}
|
|
467
|
+
</ui-carousel-content>
|
|
468
|
+
</ui-carousel-viewport>
|
|
469
|
+
<ui-carousel-next>
|
|
470
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
471
|
+
</ui-carousel-next>
|
|
472
|
+
</div>
|
|
473
|
+
</ui-carousel>
|
|
474
|
+
`
|
|
475
|
+
root
|
|
476
|
+
.querySelector("ui-carousel")!
|
|
477
|
+
.addEventListener("playingchange", (event) => {
|
|
478
|
+
log(
|
|
479
|
+
root,
|
|
480
|
+
`${event.detail.value ? "Playing" : "Stopped"} (${event.detail.reason})`
|
|
481
|
+
)
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export { mount }
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Start or stop rotation. Hover to pause it, or move keyboard focus inside to stop it. The console logs each change and why.
|
|
489
|
+
|
|
490
|
+
## Progress
|
|
491
|
+
|
|
492
|
+
`--carousel-progress` on the root says how far the slides have scrolled, from 0 at the first page to 1 at the last, on every frame of a scroll or a spring, with no re-renders, so a progress bar can follow it in CSS. With `loop`, it goes round from 0 to 1 and back to 0. The controller's `getProgress()` reads the same number.
|
|
493
|
+
|
|
494
|
+
`pagechangecomplete` fires once the slides come to rest on a page, after a spring, a drag or a scroll, with
|
|
495
|
+
the page in `event.detail.value`. Start work there that would stutter the motion, like loading the next slides' images.
|
|
496
|
+
|
|
497
|
+
```ts title="progress.ts"
|
|
498
|
+
import "@ui-sdk/web/carousel"
|
|
499
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
500
|
+
import { log } from "./log"
|
|
501
|
+
|
|
502
|
+
const slides = [1, 2, 3, 4, 5]
|
|
503
|
+
|
|
504
|
+
function mount(root: HTMLElement) {
|
|
505
|
+
root.innerHTML = `
|
|
506
|
+
<ui-carousel aria-label="Featured">
|
|
507
|
+
<div data-slot="row">
|
|
508
|
+
<ui-carousel-previous>
|
|
509
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
510
|
+
</ui-carousel-previous>
|
|
511
|
+
<ui-carousel-viewport>
|
|
512
|
+
<ui-carousel-content>
|
|
513
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
514
|
+
</ui-carousel-content>
|
|
515
|
+
</ui-carousel-viewport>
|
|
516
|
+
<ui-carousel-next>
|
|
517
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
518
|
+
</ui-carousel-next>
|
|
519
|
+
</div>
|
|
520
|
+
<div data-slot="row">
|
|
521
|
+
<div data-slot="bar">
|
|
522
|
+
<div></div>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
</ui-carousel>
|
|
526
|
+
`
|
|
527
|
+
root
|
|
528
|
+
.querySelector("ui-carousel")!
|
|
529
|
+
.addEventListener("pagechangecomplete", (event) => {
|
|
530
|
+
log(root, `Rested on slide ${event.detail.value}`)
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export { mount }
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Move through the slides. The progress bar follows the scroll position, and the console logs the slide it rests on.
|
|
538
|
+
|
|
539
|
+
## Controlled
|
|
540
|
+
|
|
541
|
+
The element owns its page, from 1. The `page` property reads it, and setting it goes there. Listen for
|
|
542
|
+
`pagechange`, with the new page in `event.detail.value`. `api.getPageCount()` is null until the slides are measured.
|
|
543
|
+
|
|
544
|
+
Every change carries a [reason](#reasons): `next-press` and `previous-press` for the parts, `item-press` for an indicator, `list-navigation` for arrow keys, `swipe` for a finger or a mouse drag, `scroll` for a wheel or trackpad, `timeout` for rotation and `imperative` from a [controller](#controller). Call `event.preventDefault()` to stay on the page: a swipe or drag springs back.
|
|
545
|
+
|
|
546
|
+
```ts title="controlled.ts"
|
|
547
|
+
import "@ui-sdk/web/carousel"
|
|
548
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
549
|
+
import { log } from "./log"
|
|
550
|
+
|
|
551
|
+
const slides = [1, 2, 3, 4, 5]
|
|
552
|
+
|
|
553
|
+
function mount(root: HTMLElement) {
|
|
554
|
+
root.innerHTML = `
|
|
555
|
+
<ui-carousel aria-label="Featured">
|
|
556
|
+
<div data-slot="row">
|
|
557
|
+
<ui-carousel-previous>
|
|
558
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
559
|
+
</ui-carousel-previous>
|
|
560
|
+
<ui-carousel-viewport>
|
|
561
|
+
<ui-carousel-content>
|
|
562
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
563
|
+
</ui-carousel-content>
|
|
564
|
+
</ui-carousel-viewport>
|
|
565
|
+
<ui-carousel-next>
|
|
566
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
567
|
+
</ui-carousel-next>
|
|
568
|
+
</div>
|
|
569
|
+
</ui-carousel>
|
|
570
|
+
`
|
|
571
|
+
root.querySelector("ui-carousel")!.addEventListener("pagechange", (event) => {
|
|
572
|
+
log(root, `Page ${event.detail.value} (${event.detail.reason})`)
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export { mount }
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Change the page. The console logs each page and the reason for the change.
|
|
580
|
+
|
|
581
|
+
## Paging from Anywhere
|
|
582
|
+
|
|
583
|
+
The element's `api` property is its [controller](#controller). Previous, Next, the indicator group and the
|
|
584
|
+
play toggle work anywhere on the page with `for="<carousel id>"`, like in a section header.
|
|
585
|
+
|
|
586
|
+
`drag="false"` turns off swiping and dragging, so only the parts, keys and controllers move it.
|
|
587
|
+
|
|
588
|
+
```ts title="controller.ts"
|
|
589
|
+
import "@ui-sdk/web/carousel"
|
|
590
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
591
|
+
|
|
592
|
+
const slides = [1, 2, 3, 4, 5]
|
|
593
|
+
|
|
594
|
+
function mount(root: HTMLElement) {
|
|
595
|
+
root.innerHTML = `
|
|
596
|
+
<div data-slot="stack">
|
|
597
|
+
<div data-slot="controls">
|
|
598
|
+
<ui-carousel-previous for="featured-carousel">
|
|
599
|
+
<button aria-label="Previous slide">${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}</button>
|
|
600
|
+
</ui-carousel-previous>
|
|
601
|
+
<ui-carousel-next for="featured-carousel">
|
|
602
|
+
<button aria-label="Next slide">${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
603
|
+
</ui-carousel-next>
|
|
604
|
+
<button type="button" data-show-last>Show the Last Slide</button>
|
|
605
|
+
</div>
|
|
606
|
+
<ui-carousel id="featured-carousel" aria-label="Featured" drag="false">
|
|
607
|
+
<ui-carousel-viewport>
|
|
608
|
+
<ui-carousel-content>
|
|
609
|
+
${slides.map((slide) => `<ui-carousel-item><div data-slot="tile">${slide}</div></ui-carousel-item>`).join("")}
|
|
610
|
+
</ui-carousel-content>
|
|
611
|
+
</ui-carousel-viewport>
|
|
612
|
+
</ui-carousel>
|
|
613
|
+
</div>
|
|
614
|
+
`
|
|
615
|
+
const carousel = root.querySelector("ui-carousel")!
|
|
616
|
+
root
|
|
617
|
+
.querySelector("[data-show-last]")!
|
|
618
|
+
.addEventListener("click", () => carousel.api.setPage(5))
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export { mount }
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Use the controls outside the carousel to change its page.
|
|
625
|
+
|
|
626
|
+
## Swipes and Drags
|
|
627
|
+
|
|
628
|
+
Touch, trackpads and wheels scroll the viewport natively, with CSS scroll snap doing the paging. Native scrolling and snapping also work before JavaScript loads. A mouse or pen drags the slides too: they follow the pointer, stretch past the ends, and spring from the release velocity to the page a fling would reach. A drag that moved never clicks the link it started on, and fields inside slides keep their pointer.
|
|
629
|
+
|
|
630
|
+
Without `skip-snaps`, one swipe moves at most one page. `drag-free` lets swipes coast to wherever they stop,
|
|
631
|
+
and the nearest page becomes the current one.
|
|
632
|
+
|
|
633
|
+
Buttons, keys, indicators, rotation, and controllers animate the scroll position. A second press updates the destination while it is moving. Reduced motion makes those changes immediate.
|
|
634
|
+
|
|
635
|
+
## Custom Rendering
|
|
636
|
+
|
|
637
|
+
Elements have no render functions. Style them from their attributes, and watch those attributes to change
|
|
638
|
+
what's inside them: a slide's `data-active` and `data-in-view`, a step's `data-disabled`, an indicator's
|
|
639
|
+
`data-selected`, the play toggle's `data-playing` and `data-paused`. Here each slide says when it's showing,
|
|
640
|
+
and a gallery link takes Next's place once Next's button has `data-disabled`.
|
|
641
|
+
|
|
642
|
+
```ts title="render.ts"
|
|
643
|
+
import "@ui-sdk/web/carousel"
|
|
644
|
+
import { ChevronLeft, ChevronRight, createElement } from "lucide"
|
|
645
|
+
|
|
646
|
+
const photos = [
|
|
647
|
+
{ name: "Dunes" },
|
|
648
|
+
{ name: "Forest" },
|
|
649
|
+
{ name: "Glacier" },
|
|
650
|
+
{ name: "Canyon" },
|
|
651
|
+
{ name: "Meadow" },
|
|
652
|
+
{ name: "Harbor" },
|
|
653
|
+
]
|
|
654
|
+
|
|
655
|
+
function mount(root: HTMLElement) {
|
|
656
|
+
root.innerHTML = `
|
|
657
|
+
<ui-carousel aria-label="Photos">
|
|
658
|
+
<ui-carousel-viewport>
|
|
659
|
+
<ui-carousel-content>
|
|
660
|
+
${photos.map((photo, index) => `<ui-carousel-item><div data-slot="tile">${photo.name}<span data-slot="hint">${index + 1} / ${photos.length}<span hidden> · showing</span></span></div></ui-carousel-item>`).join("")}
|
|
661
|
+
</ui-carousel-content>
|
|
662
|
+
</ui-carousel-viewport>
|
|
663
|
+
<div data-slot="row">
|
|
664
|
+
<ui-carousel-previous>
|
|
665
|
+
<button>${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML} Previous</button>
|
|
666
|
+
</ui-carousel-previous>
|
|
667
|
+
<ui-carousel-next>
|
|
668
|
+
<button>Next ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}</button>
|
|
669
|
+
</ui-carousel-next>
|
|
670
|
+
<a href="https://example.com/gallery" hidden>See all photos</a>
|
|
671
|
+
</div>
|
|
672
|
+
</ui-carousel>
|
|
673
|
+
`
|
|
674
|
+
const carousel = root.querySelector("ui-carousel")!
|
|
675
|
+
const next = root.querySelector<HTMLElement>("ui-carousel-next button")!
|
|
676
|
+
const link = root.querySelector("a")!
|
|
677
|
+
const update = () => {
|
|
678
|
+
for (const item of carousel.querySelectorAll("ui-carousel-item")) {
|
|
679
|
+
item.querySelector<HTMLElement>("[data-slot='hint'] > span")!.hidden =
|
|
680
|
+
!item.hasAttribute("data-active")
|
|
681
|
+
}
|
|
682
|
+
const end = next.hasAttribute("data-disabled")
|
|
683
|
+
const focused = document.activeElement === next
|
|
684
|
+
link.hidden = !end
|
|
685
|
+
if (end && focused) {
|
|
686
|
+
link.focus()
|
|
687
|
+
}
|
|
688
|
+
next.hidden = end
|
|
689
|
+
}
|
|
690
|
+
new MutationObserver(update).observe(carousel, {
|
|
691
|
+
subtree: true,
|
|
692
|
+
attributeFilter: ["data-active", "data-disabled"],
|
|
693
|
+
})
|
|
694
|
+
update()
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export { mount }
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
Change slides to see each slide and control describe its current state.
|
|
701
|
+
|
|
702
|
+
## Styling
|
|
703
|
+
|
|
704
|
+
Use `data-disabled` on the Previous and Next buttons at their end, `data-selected` on the current indicator's button, `data-active` on the current page's slides, `data-in-view` on slides in view, and `data-playing` and `data-paused` on the root.
|
|
705
|
+
|
|
706
|
+
Slides size themselves: give them a `flex-basis` of 100% for one to a view or a third for three, and put the gap on the content, taking it out of each basis. The viewport writes the few styles scrolling needs inline: its overflow, `scroll-snap-type` and `overscroll-behavior`. Slides get `flex-shrink: 0` and their snap alignment. Hide the scrollbar with `scrollbar-width: none` if you like.
|
|
707
|
+
|
|
708
|
+
Each element has a `data-slot`, such as `carousel-item`, and its state attributes are listed under [Data Attributes](#data-attributes).
|
|
709
|
+
|
|
710
|
+
### `<ui-carousel>`
|
|
711
|
+
- `--carousel-interval`: How long each page shows while rotating, like `500ms`. Time a progress bar's animation with it.
|
|
712
|
+
- `--carousel-progress`: How far the slides have scrolled, from 0 at the first page to 1 at the last, or round the loop. It follows every frame of a scroll or spring.
|
|
713
|
+
|
|
714
|
+
## Data Attributes
|
|
715
|
+
|
|
716
|
+
### `<ui-carousel>`
|
|
717
|
+
`[data-slot="carousel"]`
|
|
718
|
+
|
|
719
|
+
- `data-orientation` (`horizontal`, `vertical`): Which way the slides scroll, and which arrow keys page.
|
|
720
|
+
- `data-playing`: Present while it rotates on its own.
|
|
721
|
+
- `data-paused`: Present while rotation is paused for now: hovered, the tab hidden, scrolled out of view or held. Pause a progress animation with it.
|
|
722
|
+
|
|
723
|
+
### `<ui-carousel-viewport>`
|
|
724
|
+
`[data-slot="carousel-viewport"]`
|
|
725
|
+
|
|
726
|
+
- `data-orientation` (`horizontal`, `vertical`): Which way the slides scroll, and which arrow keys page.
|
|
727
|
+
- `data-dragging`: Present while a mouse or pen drags the slides.
|
|
728
|
+
|
|
729
|
+
### `<ui-carousel-content>`
|
|
730
|
+
`[data-slot="carousel-content"]`
|
|
731
|
+
|
|
732
|
+
- `data-orientation` (`horizontal`, `vertical`): Which way the slides scroll, and which arrow keys page.
|
|
733
|
+
|
|
734
|
+
### `<ui-carousel-item>`
|
|
735
|
+
`[data-slot="carousel-item"]`
|
|
736
|
+
|
|
737
|
+
- `data-orientation` (`horizontal`, `vertical`): Which way the slides scroll, and which arrow keys page.
|
|
738
|
+
- `data-active`: Present on the slides of the current page.
|
|
739
|
+
- `data-in-view`: Present on slides at least half in view. Slides without it are inert and hidden from screen readers.
|
|
740
|
+
|
|
741
|
+
### `<ui-carousel-previous>`
|
|
742
|
+
`[data-slot="carousel-previous"]`
|
|
743
|
+
|
|
744
|
+
- `data-disabled`: Present on Previous or Next when there's no page that way, beside `aria-disabled`. It stays focusable and does nothing.
|
|
745
|
+
|
|
746
|
+
### `<ui-carousel-indicator>`
|
|
747
|
+
`[data-slot="carousel-indicator"]`
|
|
748
|
+
|
|
749
|
+
- `data-selected`: Present on the current page's indicator, beside `aria-selected`.
|
|
750
|
+
|
|
751
|
+
### `<ui-carousel-play-toggle>`
|
|
752
|
+
`[data-slot="carousel-play-toggle"]`
|
|
753
|
+
|
|
754
|
+
- `data-playing`: Present while it rotates on its own.
|
|
755
|
+
- `data-paused`: Present while rotation is paused for now: hovered, the tab hidden, scrolled out of view or held. Pause a progress animation with it.
|
|
756
|
+
|
|
757
|
+
## API Reference
|
|
758
|
+
|
|
759
|
+
Props are attributes in kebab case, like `default-page`, `slides-per-page` and `default-playing`, and
|
|
760
|
+
numbers are numbers. `controller` is a property. `page` and `playing` are live properties. Callbacks are DOM events.
|
|
761
|
+
|
|
762
|
+
### Carousel
|
|
763
|
+
|
|
764
|
+
- `page?` (`number`): The current page, from 1. Pass it to control the carousel.
|
|
765
|
+
- `defaultPage?` (`number`): The page it starts on, from 1, when uncontrolled.
|
|
766
|
+
- `pagechange event`: Runs when the page wants to change. `details.reason` says why, and `details.cancel()` stays on the page.
|
|
767
|
+
- `pagechangecomplete event`: Runs once the slides come to rest on a page, after a spring, a drag or a scroll, with that page. Start work there that would stutter the motion.
|
|
768
|
+
- `playing?` (`boolean`): Whether it rotates through the pages on its own. Pass it to control rotation.
|
|
769
|
+
- `defaultPlaying?` (`boolean`): Whether it starts rotating, when uncontrolled. It doesn't when the user prefers reduced motion; the play toggle still starts it.
|
|
770
|
+
- `playingchange event`: Runs when rotation wants to start or stop. Keyboard focus moving in and the user paging stop it, as the carousel pattern asks.
|
|
771
|
+
- `interval?` (`number`): How long each page shows while rotating, in ms. Hovering, a hidden tab and scrolling the carousel out of view pause it.
|
|
772
|
+
- `orientation?` (`CarouselOrientation`): Which way the slides scroll, and which arrow keys page.
|
|
773
|
+
- `align?` (`CarouselAlign`): Where a page lines up in the viewport.
|
|
774
|
+
- `slidesPerPage?` (`CarouselSlidesPerPage`): How many slides one page holds: Next moves this many, and each indicator stands for this many. `"auto"` fits as many as the viewport shows.
|
|
775
|
+
- `loop?` (`boolean`): Whether the slides go round: Next on the last page goes to the first, which comes in after the last, and a swipe back from the first page reaches the last. With fewer than a page of slides to either side, Next on the last page scrolls back to the first instead.
|
|
776
|
+
- `skipSnaps?` (`boolean`): Whether a fast swipe can pass over pages. Without it, one swipe moves one page.
|
|
777
|
+
- `dragFree?` (`boolean`): Whether swipes and drags coast to wherever they stop instead of a page. The nearest page becomes the current one.
|
|
778
|
+
- `drag?` (`boolean`): Whether swiping, dragging and scrolling move the slides. Without it, only the parts, keys and controllers do.
|
|
779
|
+
- `controller?` (`object`): Pages the carousel from anywhere. Create one with `createCarouselController()`.
|
|
780
|
+
- `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
|
|
781
|
+
|
|
782
|
+
### CarouselViewport
|
|
783
|
+
|
|
784
|
+
The scrolling area. `<ui-carousel-viewport>` scrolls and snaps natively, and takes focus for the arrow keys.
|
|
785
|
+
|
|
786
|
+
### CarouselContent
|
|
787
|
+
|
|
788
|
+
Holds the slides. `<ui-carousel-content>` announces slides coming into view, except while rotating.
|
|
789
|
+
|
|
790
|
+
### CarouselItem
|
|
791
|
+
|
|
792
|
+
One slide. `<ui-carousel-item>` has the group role, named by its position, like "2 of 5".
|
|
793
|
+
|
|
794
|
+
### CarouselPrevious
|
|
795
|
+
|
|
796
|
+
- `controller?` (`object`): The carousel this part drives when it isn't inside one, like buttons in a page header.
|
|
797
|
+
|
|
798
|
+
### CarouselNext
|
|
799
|
+
|
|
800
|
+
Shows the next page. `<ui-carousel-next>` takes the same attributes as `<ui-carousel-previous>`.
|
|
801
|
+
|
|
802
|
+
### CarouselIndicatorGroup
|
|
803
|
+
|
|
804
|
+
Groups the indicators. `<ui-carousel-indicator-group>` has the tablist role, and takes the same attributes as `<ui-carousel-previous>`.
|
|
805
|
+
|
|
806
|
+
### CarouselIndicator
|
|
807
|
+
|
|
808
|
+
- `page?` (`number`): The page it shows and selects, from 1. Inferred from the indicators' order when left out.
|
|
809
|
+
|
|
810
|
+
### CarouselPlayToggle
|
|
811
|
+
|
|
812
|
+
Starts and stops rotation. `<ui-carousel-play-toggle>` wraps a `button`, and takes the same attributes as `<ui-carousel-previous>`.
|
|
813
|
+
|
|
814
|
+
### State
|
|
815
|
+
|
|
816
|
+
The same state shows as attributes on each part.
|
|
817
|
+
|
|
818
|
+
`Carousel`:
|
|
819
|
+
|
|
820
|
+
- `page` (`number`): The current page, from 1.
|
|
821
|
+
- `pageCount` (`number | null`): How many pages there are, or null before the slides are measured, like on the server.
|
|
822
|
+
- `playing` (`boolean`): Whether it rotates on its own.
|
|
823
|
+
- `orientation` (`CarouselOrientation`): Which way the slides scroll.
|
|
824
|
+
|
|
825
|
+
`CarouselViewport`:
|
|
826
|
+
|
|
827
|
+
- `orientation` (`CarouselOrientation`): Which way the slides scroll.
|
|
828
|
+
- `dragging` (`boolean`): Whether a mouse or pen is dragging the slides.
|
|
829
|
+
|
|
830
|
+
`CarouselContent`:
|
|
831
|
+
|
|
832
|
+
- `orientation` (`CarouselOrientation`): Which way the slides scroll.
|
|
833
|
+
|
|
834
|
+
`CarouselItem`:
|
|
835
|
+
|
|
836
|
+
- `index` (`number`): The slide's position among the slides, from 0. -1 until it's measured.
|
|
837
|
+
- `active` (`boolean`): Whether the slide belongs to the current page.
|
|
838
|
+
- `inView` (`boolean`): Whether at least half of the slide shows. Slides out of view are inert.
|
|
839
|
+
|
|
840
|
+
`CarouselPrevious` and `CarouselNext`:
|
|
841
|
+
|
|
842
|
+
- `disabled` (`boolean`): Whether there's no page that way, so pressing does nothing. It stays focusable.
|
|
843
|
+
|
|
844
|
+
`CarouselIndicatorGroup`:
|
|
845
|
+
|
|
846
|
+
- `page` (`number`): The current page, from 1.
|
|
847
|
+
- `pageCount` (`number | null`): How many pages there are, or null before the slides are measured.
|
|
848
|
+
|
|
849
|
+
`CarouselIndicator`:
|
|
850
|
+
|
|
851
|
+
- `page` (`number`): The page it stands for, from 1, or 0 before it's known.
|
|
852
|
+
- `selected` (`boolean`): Whether its page is the current one.
|
|
853
|
+
|
|
854
|
+
`CarouselPlayToggle`:
|
|
855
|
+
|
|
856
|
+
- `playing` (`boolean`): Whether it rotates on its own.
|
|
857
|
+
- `paused` (`boolean`): Whether rotation is paused for now: hovered, the tab hidden, scrolled out of view or held.
|
|
858
|
+
|
|
859
|
+
### Reasons
|
|
860
|
+
|
|
861
|
+
`event.detail.reason` says why the page changed.
|
|
862
|
+
|
|
863
|
+
- `previous-press` (`MouseEvent | KeyboardEvent`): A pagination's or a carousel's Previous part was pressed.
|
|
864
|
+
- `next-press` (`MouseEvent | KeyboardEvent`): A pagination's or a carousel's Next part was pressed.
|
|
865
|
+
- `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
|
|
866
|
+
- `list-navigation` (`KeyboardEvent`): Arrow, Home or End keys moved the selection, typing picked an item, or Ctrl or ⌘ with A selected every item of a listbox.
|
|
867
|
+
- `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
|
|
868
|
+
- `scroll` (`Event | undefined`): The user scrolled a carousel some other way: a wheel, a trackpad, a scrollbar, or the browser bringing focus or a find in page into view.
|
|
869
|
+
- `timeout` (`none`): A duration ran out, like a toast's, or a rotating carousel's interval.
|
|
870
|
+
- `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.
|
|
871
|
+
- `imperative` (`none`): A controller or `api` call, or a property set on a web component.
|
|
872
|
+
|
|
873
|
+
And why rotation started or stopped.
|
|
874
|
+
|
|
875
|
+
- `previous-press` (`MouseEvent | KeyboardEvent`): A pagination's or a carousel's Previous part was pressed.
|
|
876
|
+
- `next-press` (`MouseEvent | KeyboardEvent`): A pagination's or a carousel's Next part was pressed.
|
|
877
|
+
- `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
|
|
878
|
+
- `list-navigation` (`KeyboardEvent`): Arrow, Home or End keys moved the selection, typing picked an item, or Ctrl or ⌘ with A selected every item of a listbox.
|
|
879
|
+
- `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
|
|
880
|
+
- `scroll` (`Event | undefined`): The user scrolled a carousel some other way: a wheel, a trackpad, a scrollbar, or the browser bringing focus or a find in page into view.
|
|
881
|
+
- `imperative` (`none`): A controller or `api` call, or a property set on a web component.
|
|
882
|
+
- `toggle-press` (`MouseEvent | KeyboardEvent`): A toggle was pressed: a password input's, which shows or hides the password, or a carousel's, which starts or stops rotation.
|
|
883
|
+
- `focus-in` (`FocusEvent`): Keyboard focus moved into the component, which stops a rotating carousel, as the carousel pattern asks, or onto a canvas item, which brought it into view, or onto a calendar's day another way, like a screen reader's.
|
|
884
|
+
|
|
885
|
+
### Controller
|
|
886
|
+
|
|
887
|
+
The element's `api` property has the same methods.
|
|
888
|
+
|
|
889
|
+
Every write method takes an optional `{ reason, event }`, which the change event carries, and returns `false` when the change is canceled or there's nowhere to go.
|
|
890
|
+
|
|
891
|
+
- `getPage()` (`() => number`): The current page, from 1.
|
|
892
|
+
- `getPageCount()` (`() => number | null`): How many pages there are, or null before the carousel has measured its slides.
|
|
893
|
+
- `setPage(page, options?)` (`(page: number, options?: CarouselPageOptions | undefined) => boolean`): Goes to a page, from 1, clamped to the pages there are. Returns false when canceled or already there.
|
|
894
|
+
- `next(options?)` (`(options?: CarouselPageOptions | undefined) => boolean`): Goes to the next page, or the first one after the last with `loop`. Returns false at the end.
|
|
895
|
+
- `previous(options?)` (`(options?: CarouselPageOptions | undefined) => boolean`): Goes to the previous page, or the last one before the first with `loop`. Returns false at the start.
|
|
896
|
+
- `canNext()` (`() => boolean`): Whether there's a next page, always with `loop`.
|
|
897
|
+
- `canPrevious()` (`() => boolean`): Whether there's a previous page, always with `loop`.
|
|
898
|
+
- `getProgress()` (`() => number`): How far the slides have scrolled, from 0 at the first page to 1 at the last, or round the loop. Also `--carousel-progress` on the root.
|
|
899
|
+
- `isPlaying()` (`() => boolean`): Whether it rotates on its own.
|
|
900
|
+
- `setPlaying(playing, options?)` (`(playing: boolean, options?: CarouselPlayingOptions | undefined) => boolean`): Starts or stops rotation. Returns false when canceled or unchanged.
|
|
901
|
+
- `play(options?)` (`(options?: CarouselPlayingOptions | undefined) => boolean`): Starts rotation.
|
|
902
|
+
- `pause(options?)` (`(options?: CarouselPlayingOptions | undefined) => boolean`): Stops rotation.
|
|
903
|
+
|
|
904
|
+
### Events
|
|
905
|
+
|
|
906
|
+
Events fire on their own element and don't bubble, with the new value in `event.detail.value` and the `reason` and `event` beside it. Calling `preventDefault()` cancels the change. Type the element as `UiCarouselElement` for typed listeners.
|
|
907
|
+
|
|
908
|
+
| Member | On | Description |
|
|
909
|
+
| -------------------------- | ------------- | ------------------------------------------------------- |
|
|
910
|
+
| `page` property | `ui-carousel` | The current page. Setting it goes there |
|
|
911
|
+
| `playing` property | `ui-carousel` | Whether it rotates |
|
|
912
|
+
| `api` property | `ui-carousel` | The [controller](#controller) |
|
|
913
|
+
| `controller` property | `ui-carousel` | A controller from `createCarouselController()` |
|
|
914
|
+
| `pagechange` event | `ui-carousel` | `detail: { value: page, reason, event, source }` |
|
|
915
|
+
| `pagechangecomplete` event | `ui-carousel` | `detail: { value: page }`, once the slides come to rest |
|
|
916
|
+
| `playingchange` event | `ui-carousel` | `detail: { value: playing, reason, event, source }` |
|
|
917
|
+
|
|
918
|
+
## Accessibility
|
|
919
|
+
|
|
920
|
+
It follows the [WAI-ARIA carousel pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/): a region described as a carousel, slides as groups described as slides and labelled by position, Previous and Next that control the slides, a rotation control, and indicators as a tablist. Every line below is an automated test.
|
|
921
|
+
|
|
922
|
+
Each slide is labelled by its position, like "1 of 5", unless it has an `aria-label` of its own, which is also how you translate it. Slides less than half in view are `inert` and hidden from screen readers, so Tab and the virtual cursor only reach what shows.
|
|
923
|
+
|
|
924
|
+
### Keyboard
|
|
925
|
+
|
|
926
|
+
Arrow keys follow the orientation, and swap left and right in right-to-left text.
|
|
927
|
+
|
|
928
|
+
| Key | Does |
|
|
929
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
930
|
+
| Tab | Reaches the play toggle, links and controls in slides in view, Previous, Next and the current indicator |
|
|
931
|
+
| ArrowRight, ArrowLeft | In the viewport, the next and previous page. ArrowDown and ArrowUp when vertical |
|
|
932
|
+
| Home, End | In the viewport or the indicators, the first and last page |
|
|
933
|
+
| ArrowRight, ArrowLeft, ArrowDown, ArrowUp | In the indicators, the next and previous indicator, which shows its page |
|
|
934
|
+
| Enter, Space | Presses Previous, Next, an indicator or the play toggle |
|
|
935
|
+
|
|
936
|
+
Fields and widgets inside slides keep their arrow keys: a text field's caret moves, and the carousel stays put. When arrow keys page away from a slide that has focus, focus moves to the slide that comes in, so it never stays on something out of view. Previous and Next stay focusable at the ends, with `aria-disabled`, so focus never drops to the page when Next reaches the last slide.
|
|
937
|
+
|
|
938
|
+
### Tested Behavior
|
|
939
|
+
|
|
940
|
+
**Structure**
|
|
941
|
+
|
|
942
|
+
- The carousel is a named region described as a carousel.
|
|
943
|
+
- Each slide is a group described as a slide.
|
|
944
|
+
- The first slide shows first.
|
|
945
|
+
|
|
946
|
+
**Previous and next**
|
|
947
|
+
|
|
948
|
+
- Next shows the next slide.
|
|
949
|
+
- Previous shows the previous slide.
|
|
950
|
+
- Previous is disabled on the first slide and Next on the last.
|
|
951
|
+
- Next on the last slide does nothing.
|
|
952
|
+
- Next and Previous are buttons that don't submit a form.
|
|
953
|
+
|
|
954
|
+
**Keyboard**
|
|
955
|
+
|
|
956
|
+
- ArrowRight in a slide shows the next slide, and ArrowLeft the previous.
|
|
957
|
+
- ArrowLeft on the first slide stays there.
|
|
958
|
+
|
|
959
|
+
**Pages**
|
|
960
|
+
|
|
961
|
+
- With three slides to a view, Next moves one slide, and the last page shows the last three.
|
|
962
|
+
- Three slides to a page move three at a time.
|
|
963
|
+
- Auto pages fit as many slides as show.
|
|
964
|
+
- It can start on another page.
|
|
965
|
+
- A controller pages it from outside.
|
|
966
|
+
|
|
967
|
+
**Rest**
|
|
968
|
+
|
|
969
|
+
- It reports the page once the slides come to rest.
|
|
970
|
+
|
|
971
|
+
**Loop**
|
|
972
|
+
|
|
973
|
+
- Next on the last slide shows the first.
|
|
974
|
+
- Previous on the first slide shows the last.
|
|
975
|
+
- Previous and Next are never disabled.
|
|
976
|
+
- Next on the last slide carries on the same way to the first.
|
|
977
|
+
- Pressing Next quickly keeps going forward, round past the last slide.
|
|
978
|
+
- A mouse drag back from the first slide shows the last.
|
|
979
|
+
- With three slides to a view, the last slide shows before the first.
|
|
980
|
+
- A vertical carousel drags down from the first slide to the last.
|
|
981
|
+
|
|
982
|
+
**Dragging**
|
|
983
|
+
|
|
984
|
+
- Dragging the slides with a mouse shows the next one.
|
|
985
|
+
- A short, slow drag settles back on the same slide.
|
|
986
|
+
- Dragging back shows the previous slide.
|
|
987
|
+
- A drag that starts on a link doesn't follow it.
|
|
988
|
+
- Without dragging, a drag moves nothing.
|
|
989
|
+
|
|
990
|
+
**More**
|
|
991
|
+
|
|
992
|
+
- A vertical carousel drags up and down.
|
|
993
|
+
- Pressing keys with no waits lands on the right slide.
|
|
994
|
+
- A fast flick moves one page, not more.
|
|
995
|
+
- The current slide stays in view when the slides change size.
|
|
996
|
+
|
|
997
|
+
Runs in React, Vue and web components.
|
|
998
|
+
|
|
999
|
+
Beyond the baseline:
|
|
1000
|
+
|
|
1001
|
+
**Focus**
|
|
1002
|
+
|
|
1003
|
+
- Next keeps focus when it reaches the last slide.
|
|
1004
|
+
- Tab reaches only the slides in view.
|
|
1005
|
+
- Slides out of view are hidden from assistive technology.
|
|
1006
|
+
- Arrow keys bring the next slide in and move focus to it.
|
|
1007
|
+
- A text field in a slide keeps its arrow keys.
|
|
1008
|
+
- Home and End go to the first and last slide.
|
|
1009
|
+
|
|
1010
|
+
**Orientation**
|
|
1011
|
+
|
|
1012
|
+
- A vertical carousel pages with ArrowDown and ArrowUp.
|
|
1013
|
+
|
|
1014
|
+
**Indicators**
|
|
1015
|
+
|
|
1016
|
+
- Indicators are a tablist with the current page selected.
|
|
1017
|
+
- Pressing an indicator shows its page.
|
|
1018
|
+
- Arrow keys move between indicators and show their page.
|
|
1019
|
+
- The indicators are one tab stop, on the current page.
|
|
1020
|
+
- An indicator controls the slides of its page.
|
|
1021
|
+
- Indicators past the page count hide themselves.
|
|
1022
|
+
|
|
1023
|
+
**Changes**
|
|
1024
|
+
|
|
1025
|
+
- A change can be canceled.
|
|
1026
|
+
- A canceled drag springs back.
|
|
1027
|
+
- Every change says why.
|
|
1028
|
+
- A horizontal wheel scrolls to the next slide.
|
|
1029
|
+
- With loop, a wheel back from the first slide scrolls natively to the last.
|
|
1030
|
+
- --carousel-progress on the root follows the slides, for progress bars in CSS.
|
|
1031
|
+
- With loop, --carousel-progress goes round, whatever the track's rotation.
|
|
1032
|
+
- Next outside the carousel works through its controller.
|
|
1033
|
+
|
|
1034
|
+
**Rotation**
|
|
1035
|
+
|
|
1036
|
+
- It rotates on its own, past the last slide to the first.
|
|
1037
|
+
- The play toggle stops and starts rotation, and its name says which.
|
|
1038
|
+
- Keyboard focus moving in stops rotation, as the pattern asks.
|
|
1039
|
+
- Hovering pauses rotation, and leaving resumes it.
|
|
1040
|
+
- Pressing Next stops rotation.
|
|
1041
|
+
- The slides are a live region only while not rotating.
|
|
1042
|
+
- It doesn't start rotating when the user prefers reduced motion.
|
|
1043
|
+
- Reduced motion pages without animating.
|
|
1044
|
+
|
|
1045
|
+
**Pages**
|
|
1046
|
+
|
|
1047
|
+
- Indicators follow a change in size.
|
|
1048
|
+
|
|
1049
|
+
Runs in React, Vue and web components.
|
|
1050
|
+
|
|
1051
|
+
Screen readers hear the same thing in each tested framework, recorded by a virtual screen reader:
|
|
1052
|
+
|
|
1053
|
+
- Announcements while using the carousel with a keyboard.
|
|
1054
|
+
|
|
1055
|
+
Runs in React, Vue and web components.
|