@remix-run/ui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -164
- package/dist/accordion/index.d.ts +46 -0
- package/dist/accordion/index.js +142 -0
- package/dist/accordion/index.js.map +1 -0
- package/dist/accordion/primitives.d.ts +104 -0
- package/dist/accordion/primitives.js +312 -0
- package/dist/accordion/primitives.js.map +1 -0
- package/dist/{components/anchor/anchor.d.ts → anchor/index.d.ts} +8 -1
- package/dist/{components/anchor/anchor.js → anchor/index.js} +31 -12
- package/dist/anchor/index.js.map +1 -0
- package/dist/animation/demos/aspect-ratio.d.ts +2 -0
- package/dist/animation/demos/aspect-ratio.js +69 -0
- package/dist/animation/demos/aspect-ratio.js.map +1 -0
- package/dist/animation/demos/bouncy-switch.d.ts +2 -0
- package/dist/animation/demos/bouncy-switch.js +36 -0
- package/dist/animation/demos/bouncy-switch.js.map +1 -0
- package/dist/animation/demos/color-interpolation.d.ts +1 -0
- package/dist/animation/demos/color-interpolation.js +53 -0
- package/dist/animation/demos/color-interpolation.js.map +1 -0
- package/dist/animation/demos/cube.d.ts +2 -0
- package/dist/animation/demos/cube.js +86 -0
- package/dist/animation/demos/cube.js.map +1 -0
- package/dist/animation/demos/default-animate.d.ts +2 -0
- package/dist/animation/demos/default-animate.js +89 -0
- package/dist/animation/demos/default-animate.js.map +1 -0
- package/dist/animation/demos/drag-release.d.ts +24 -0
- package/dist/animation/demos/drag-release.js +96 -0
- package/dist/animation/demos/drag-release.js.map +1 -0
- package/dist/animation/demos/enter.d.ts +1 -0
- package/dist/animation/demos/enter.js +19 -0
- package/dist/animation/demos/enter.js.map +1 -0
- package/dist/animation/demos/exit.d.ts +2 -0
- package/dist/animation/demos/exit.js +58 -0
- package/dist/animation/demos/exit.js.map +1 -0
- package/dist/animation/demos/flip-toggle.d.ts +2 -0
- package/dist/animation/demos/flip-toggle.js +37 -0
- package/dist/animation/demos/flip-toggle.js.map +1 -0
- package/dist/animation/demos/hold-to-confirm.d.ts +13 -0
- package/dist/animation/demos/hold-to-confirm.js +211 -0
- package/dist/animation/demos/hold-to-confirm.js.map +1 -0
- package/dist/animation/demos/html-content.d.ts +2 -0
- package/dist/animation/demos/html-content.js +30 -0
- package/dist/animation/demos/html-content.js.map +1 -0
- package/dist/animation/demos/interruptible-keyframes.d.ts +1 -0
- package/dist/animation/demos/interruptible-keyframes.js +48 -0
- package/dist/animation/demos/interruptible-keyframes.js.map +1 -0
- package/dist/animation/demos/keyframes.d.ts +1 -0
- package/dist/animation/demos/keyframes.js +24 -0
- package/dist/animation/demos/keyframes.js.map +1 -0
- package/dist/animation/demos/material-ripple.d.ts +2 -0
- package/dist/animation/demos/material-ripple.js +127 -0
- package/dist/animation/demos/material-ripple.js.map +1 -0
- package/dist/animation/demos/mixin-presence-list.d.ts +2 -0
- package/dist/animation/demos/mixin-presence-list.js +88 -0
- package/dist/animation/demos/mixin-presence-list.js.map +1 -0
- package/dist/animation/demos/mixin-reclaim.d.ts +2 -0
- package/dist/animation/demos/mixin-reclaim.js +90 -0
- package/dist/animation/demos/mixin-reclaim.js.map +1 -0
- package/dist/animation/demos/multi-state-badge.d.ts +2 -0
- package/dist/animation/demos/multi-state-badge.js +231 -0
- package/dist/animation/demos/multi-state-badge.js.map +1 -0
- package/dist/animation/demos/press.d.ts +2 -0
- package/dist/animation/demos/press.js +63 -0
- package/dist/animation/demos/press.js.map +1 -0
- package/dist/animation/demos/reordering.d.ts +2 -0
- package/dist/animation/demos/reordering.js +53 -0
- package/dist/animation/demos/reordering.js.map +1 -0
- package/dist/animation/demos/rolling-square.d.ts +2 -0
- package/dist/animation/demos/rolling-square.js +42 -0
- package/dist/animation/demos/rolling-square.js.map +1 -0
- package/dist/animation/demos/rotate.d.ts +1 -0
- package/dist/animation/demos/rotate.js +18 -0
- package/dist/animation/demos/rotate.js.map +1 -0
- package/dist/animation/demos/shared-layout.d.ts +2 -0
- package/dist/animation/demos/shared-layout.js +114 -0
- package/dist/animation/demos/shared-layout.js.map +1 -0
- package/dist/animation/demos/transition-options.d.ts +1 -0
- package/dist/animation/demos/transition-options.js +21 -0
- package/dist/animation/demos/transition-options.js.map +1 -0
- package/dist/animation/spring.d.ts +22 -0
- package/dist/animation/spring.js +22 -0
- package/dist/animation/spring.js.map +1 -1
- package/dist/{components/breadcrumbs/breadcrumbs.js → breadcrumbs/index.js} +19 -19
- package/dist/breadcrumbs/index.js.map +1 -0
- package/dist/button/index.d.ts +15 -0
- package/dist/button/index.js +128 -0
- package/dist/button/index.js.map +1 -0
- package/dist/checkbox/index.d.ts +14 -0
- package/dist/checkbox/index.js +125 -0
- package/dist/checkbox/index.js.map +1 -0
- package/dist/combobox/index.d.ts +21 -0
- package/dist/combobox/index.js +58 -0
- package/dist/combobox/index.js.map +1 -0
- package/dist/{components/combobox/combobox.d.ts → combobox/primitives.d.ts} +3 -7
- package/dist/{components/combobox/combobox.js → combobox/primitives.js} +8 -61
- package/dist/combobox/primitives.js.map +1 -0
- package/dist/input/index.d.ts +13 -0
- package/dist/input/index.js +121 -0
- package/dist/input/index.js.map +1 -0
- package/dist/listbox/flash-attribute.js.map +1 -0
- package/dist/{components/listbox/listbox.d.ts → listbox/index.d.ts} +2 -6
- package/dist/{components/listbox/listbox.js → listbox/index.js} +4 -82
- package/dist/listbox/index.js.map +1 -0
- package/dist/menu/hover-aim.js.map +1 -0
- package/dist/menu/index.d.ts +42 -0
- package/dist/menu/index.js +250 -0
- package/dist/menu/index.js.map +1 -0
- package/dist/{components/menu/menu.d.ts → menu/primitives.d.ts} +6 -32
- package/dist/{components/menu/menu.js → menu/primitives.js} +117 -279
- package/dist/menu/primitives.js.map +1 -0
- package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +3 -5
- package/dist/{components/popover/popover.js → popover/index.js} +15 -51
- package/dist/popover/index.js.map +1 -0
- package/dist/{interactions/outside-click/outside-click-mixin.js → popover/outside-click.js} +1 -1
- package/dist/popover/outside-click.js.map +1 -0
- package/dist/popover/scroll-lock.d.ts +1 -0
- package/dist/{utils → popover}/scroll-lock.js +0 -18
- package/dist/popover/scroll-lock.js.map +1 -0
- package/dist/radio/index.d.ts +12 -0
- package/dist/radio/index.js +77 -0
- package/dist/radio/index.js.map +1 -0
- package/dist/runtime/client-entries.d.ts +1 -2
- package/dist/runtime/client-entries.js.map +1 -1
- package/dist/runtime/component.d.ts +2 -2
- package/dist/runtime/component.js +1 -1
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/diff-dom.js +194 -21
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/frame.d.ts +7 -0
- package/dist/runtime/frame.js +176 -41
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +2 -3
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/reconcile.js +11 -5
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/vdom.js +3 -0
- package/dist/runtime/vdom.js.map +1 -1
- package/dist/select/index.d.ts +19 -0
- package/dist/select/index.js +95 -0
- package/dist/select/index.js.map +1 -0
- package/dist/select/keydown.js.map +1 -0
- package/dist/{components/select/select.d.ts → select/primitives.d.ts} +4 -6
- package/dist/{components/select/select.js → select/primitives.js} +12 -64
- package/dist/select/primitives.js.map +1 -0
- package/dist/server/stream.js +5 -4
- package/dist/server/stream.js.map +1 -1
- package/dist/shared/focus-styles.d.ts +1 -0
- package/dist/shared/focus-styles.js +2 -0
- package/dist/shared/focus-styles.js.map +1 -0
- package/dist/shared/icons.d.ts +9 -0
- package/dist/shared/icons.js +42 -0
- package/dist/shared/icons.js.map +1 -0
- package/dist/shared/listbox-popover-styles.d.ts +6 -0
- package/dist/shared/listbox-popover-styles.js +137 -0
- package/dist/shared/listbox-popover-styles.js.map +1 -0
- package/dist/shared/style-values.d.ts +78 -0
- package/dist/{theme/presets/rmx-01/index.js → shared/style-values.js} +3 -34
- package/dist/shared/style-values.js.map +1 -0
- package/dist/{interactions/typeahead/typeahead-mixin.js → shared/typeahead.js} +1 -1
- package/dist/shared/typeahead.js.map +1 -0
- package/dist/shared/wait-for-css-transition.js.map +1 -0
- package/dist/shared/wait.js.map +1 -0
- package/dist/style/style.js +27 -10
- package/dist/style/style.js.map +1 -1
- package/dist/tabs/index.d.ts +32 -0
- package/dist/tabs/index.js +157 -0
- package/dist/tabs/index.js.map +1 -0
- package/dist/tabs/primitives.d.ts +57 -0
- package/dist/tabs/primitives.js +238 -0
- package/dist/tabs/primitives.js.map +1 -0
- package/dist/toggle/index.d.ts +9 -0
- package/dist/toggle/index.js +108 -0
- package/dist/toggle/index.js.map +1 -0
- package/dist/toggle/primitives.d.ts +29 -0
- package/dist/toggle/primitives.js +127 -0
- package/dist/toggle/primitives.js.map +1 -0
- package/package.json +74 -45
- package/src/{components/accordion → accordion}/README.md +50 -14
- package/src/accordion/index.tsx +285 -0
- package/src/accordion/primitives.tsx +512 -0
- package/src/{components/anchor → anchor}/README.md +48 -5
- package/src/{components/anchor/anchor.ts → anchor/index.ts} +53 -12
- package/src/animation/demos/aspect-ratio.tsx +120 -0
- package/src/animation/demos/bouncy-switch.tsx +47 -0
- package/src/animation/demos/color-interpolation.tsx +73 -0
- package/src/animation/demos/cube.tsx +116 -0
- package/src/animation/demos/default-animate.tsx +129 -0
- package/src/animation/demos/drag-release.ts +118 -0
- package/src/animation/demos/enter.tsx +22 -0
- package/src/animation/demos/exit.tsx +79 -0
- package/src/animation/demos/flip-toggle.tsx +46 -0
- package/src/animation/demos/hold-to-confirm.tsx +317 -0
- package/src/animation/demos/html-content.tsx +41 -0
- package/src/animation/demos/interruptible-keyframes.tsx +62 -0
- package/src/animation/demos/keyframes.tsx +29 -0
- package/src/animation/demos/material-ripple.tsx +158 -0
- package/src/animation/demos/mixin-presence-list.tsx +121 -0
- package/src/animation/demos/mixin-reclaim.tsx +125 -0
- package/src/animation/demos/multi-state-badge.tsx +426 -0
- package/src/animation/demos/press.tsx +67 -0
- package/src/animation/demos/reordering.tsx +68 -0
- package/src/animation/demos/rolling-square.tsx +57 -0
- package/src/animation/demos/rotate.tsx +21 -0
- package/src/animation/demos/shared-layout.tsx +211 -0
- package/src/animation/demos/transition-options.tsx +24 -0
- package/src/animation/spring.ts +22 -0
- package/src/{components/breadcrumbs → breadcrumbs}/README.md +4 -4
- package/src/{components/breadcrumbs/breadcrumbs.tsx → breadcrumbs/index.tsx} +18 -18
- package/src/button/README.md +44 -0
- package/src/button/index.ts +168 -0
- package/src/checkbox/README.md +59 -0
- package/src/checkbox/index.ts +162 -0
- package/src/{components/combobox → combobox}/README.md +58 -9
- package/src/combobox/index.tsx +112 -0
- package/src/{components/combobox/combobox.tsx → combobox/primitives.tsx} +5 -91
- package/src/input/README.md +52 -0
- package/src/input/index.ts +147 -0
- package/src/{components/listbox → listbox}/README.md +9 -41
- package/src/{components/listbox/listbox.ts → listbox/index.ts} +2 -87
- package/src/menu/README.md +161 -0
- package/src/menu/index.tsx +354 -0
- package/src/{components/menu/menu.tsx → menu/primitives.tsx} +117 -359
- package/src/{components/popover → popover}/README.md +20 -39
- package/src/{components/popover/popover.ts → popover/index.ts} +16 -61
- package/src/{utils → popover}/scroll-lock.ts +0 -28
- package/src/radio/README.md +53 -0
- package/src/radio/index.ts +100 -0
- package/src/runtime/client-entries.ts +1 -5
- package/src/runtime/component.ts +3 -3
- package/src/runtime/demos/readme.demo.tsx +704 -0
- package/src/runtime/diff-dom.ts +270 -20
- package/src/runtime/frame.ts +218 -41
- package/src/runtime/jsx.ts +6 -10
- package/src/runtime/reconcile.ts +11 -5
- package/src/runtime/vdom.ts +3 -0
- package/src/{components/select → select}/README.md +29 -19
- package/src/select/index.tsx +159 -0
- package/src/{components/select/select.tsx → select/primitives.tsx} +11 -99
- package/src/server/stream.ts +10 -5
- package/src/shared/focus-styles.ts +2 -0
- package/src/shared/icons.tsx +62 -0
- package/src/shared/listbox-popover-styles.ts +146 -0
- package/src/shared/style-values.ts +165 -0
- package/src/style/style.ts +29 -10
- package/src/tabs/README.md +141 -0
- package/src/tabs/index.tsx +244 -0
- package/src/tabs/primitives.ts +365 -0
- package/src/toggle/README.md +56 -0
- package/src/toggle/index.tsx +134 -0
- package/src/toggle/primitives.ts +192 -0
- package/dist/components/accordion/accordion.d.ts +0 -92
- package/dist/components/accordion/accordion.js +0 -335
- package/dist/components/accordion/accordion.js.map +0 -1
- package/dist/components/accordion/demos/card.demo.d.ts +0 -6
- package/dist/components/accordion/demos/card.demo.js +0 -58
- package/dist/components/accordion/demos/card.demo.js.map +0 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +0 -6
- package/dist/components/accordion/demos/multiple.demo.js +0 -19
- package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +0 -6
- package/dist/components/accordion/demos/overview.demo.js +0 -19
- package/dist/components/accordion/demos/overview.demo.js.map +0 -1
- package/dist/components/anchor/anchor.js.map +0 -1
- package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
- package/dist/components/breadcrumbs/demos/basic.demo.d.ts +0 -5
- package/dist/components/breadcrumbs/demos/basic.demo.js +0 -14
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/demos/separator.demo.d.ts +0 -5
- package/dist/components/breadcrumbs/demos/separator.demo.js +0 -14
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
- package/dist/components/button/button.d.ts +0 -105
- package/dist/components/button/button.js +0 -207
- package/dist/components/button/button.js.map +0 -1
- package/dist/components/button/demos/aliases.demo.d.ts +0 -6
- package/dist/components/button/demos/aliases.demo.js +0 -18
- package/dist/components/button/demos/aliases.demo.js.map +0 -1
- package/dist/components/button/demos/basic.demo.d.ts +0 -6
- package/dist/components/button/demos/basic.demo.js +0 -19
- package/dist/components/button/demos/basic.demo.js.map +0 -1
- package/dist/components/button/demos/states.demo.d.ts +0 -6
- package/dist/components/button/demos/states.demo.js +0 -29
- package/dist/components/button/demos/states.demo.js.map +0 -1
- package/dist/components/combobox/combobox.js.map +0 -1
- package/dist/components/combobox/demos/overview.demo.d.ts +0 -6
- package/dist/components/combobox/demos/overview.demo.js +0 -153
- package/dist/components/combobox/demos/overview.demo.js.map +0 -1
- package/dist/components/glyph/glyph.d.ts +0 -14
- package/dist/components/glyph/glyph.js +0 -65
- package/dist/components/glyph/glyph.js.map +0 -1
- package/dist/components/listbox/demos/overview.demo.d.ts +0 -6
- package/dist/components/listbox/demos/overview.demo.js +0 -64
- package/dist/components/listbox/demos/overview.demo.js.map +0 -1
- package/dist/components/listbox/listbox.js.map +0 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +0 -5
- package/dist/components/menu/demos/bubbling.demo.js +0 -15
- package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
- package/dist/components/menu/demos/overview.demo.d.ts +0 -6
- package/dist/components/menu/demos/overview.demo.js +0 -32
- package/dist/components/menu/demos/overview.demo.js.map +0 -1
- package/dist/components/menu/hover-aim.js.map +0 -1
- package/dist/components/menu/menu.js.map +0 -1
- package/dist/components/popover/demos/overview.demo.d.ts +0 -6
- package/dist/components/popover/demos/overview.demo.js +0 -64
- package/dist/components/popover/demos/overview.demo.js.map +0 -1
- package/dist/components/popover/popover.js.map +0 -1
- package/dist/components/select/demos/deconstructed.demo.d.ts +0 -6
- package/dist/components/select/demos/deconstructed.demo.js +0 -53
- package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
- package/dist/components/select/demos/overview.demo.d.ts +0 -5
- package/dist/components/select/demos/overview.demo.js +0 -27
- package/dist/components/select/demos/overview.demo.js.map +0 -1
- package/dist/components/select/select.js.map +0 -1
- package/dist/components/separator/separator.d.ts +0 -2
- package/dist/components/separator/separator.js +0 -15
- package/dist/components/separator/separator.js.map +0 -1
- package/dist/components/tabs/tabs.d.ts +0 -78
- package/dist/components/tabs/tabs.js +0 -339
- package/dist/components/tabs/tabs.js.map +0 -1
- package/dist/interactions/keydown/keydown.js.map +0 -1
- package/dist/interactions/outside-click/outside-click-mixin.js.map +0 -1
- package/dist/interactions/typeahead/typeahead-mixin.js.map +0 -1
- package/dist/theme/contract.d.ts +0 -61
- package/dist/theme/contract.js +0 -131
- package/dist/theme/contract.js.map +0 -1
- package/dist/theme/glyph-contract.d.ts +0 -11
- package/dist/theme/glyph-contract.js +0 -34
- package/dist/theme/glyph-contract.js.map +0 -1
- package/dist/theme/presets/rmx-01/glyphs.d.ts +0 -2
- package/dist/theme/presets/rmx-01/glyphs.js +0 -252
- package/dist/theme/presets/rmx-01/glyphs.js.map +0 -1
- package/dist/theme/presets/rmx-01/index.d.ts +0 -4
- package/dist/theme/presets/rmx-01/index.js.map +0 -1
- package/dist/theme/runtime.d.ts +0 -2
- package/dist/theme/runtime.js +0 -84
- package/dist/theme/runtime.js.map +0 -1
- package/dist/theme/theme.d.ts +0 -6
- package/dist/theme/theme.js +0 -5
- package/dist/theme/theme.js.map +0 -1
- package/dist/utils/flash-attribute.js.map +0 -1
- package/dist/utils/scroll-lock.d.ts +0 -3
- package/dist/utils/scroll-lock.js.map +0 -1
- package/dist/utils/wait-for-css-transition.js.map +0 -1
- package/dist/utils/wait.js.map +0 -1
- package/src/components/accordion/accordion.tsx +0 -565
- package/src/components/accordion/demos/card.demo.tsx +0 -113
- package/src/components/accordion/demos/multiple.demo.tsx +0 -56
- package/src/components/accordion/demos/overview.demo.tsx +0 -56
- package/src/components/breadcrumbs/demos/basic.demo.tsx +0 -17
- package/src/components/breadcrumbs/demos/separator.demo.tsx +0 -18
- package/src/components/button/README.md +0 -44
- package/src/components/button/button.tsx +0 -286
- package/src/components/button/demos/aliases.demo.tsx +0 -27
- package/src/components/button/demos/basic.demo.tsx +0 -30
- package/src/components/button/demos/states.demo.tsx +0 -52
- package/src/components/combobox/demos/overview.demo.tsx +0 -199
- package/src/components/glyph/README.md +0 -72
- package/src/components/glyph/glyph.tsx +0 -110
- package/src/components/listbox/demos/overview.demo.tsx +0 -101
- package/src/components/menu/README.md +0 -96
- package/src/components/menu/demos/bubbling.demo.tsx +0 -34
- package/src/components/menu/demos/overview.demo.tsx +0 -76
- package/src/components/popover/demos/overview.demo.tsx +0 -143
- package/src/components/select/demos/deconstructed.demo.tsx +0 -95
- package/src/components/select/demos/overview.demo.tsx +0 -51
- package/src/components/separator/separator.ts +0 -19
- package/src/components/tabs/README.md +0 -105
- package/src/components/tabs/tabs.tsx +0 -504
- package/src/theme/README.md +0 -103
- package/src/theme/contract.ts +0 -215
- package/src/theme/glyph-contract.ts +0 -59
- package/src/theme/presets/rmx-01/glyphs.tsx +0 -308
- package/src/theme/presets/rmx-01/index.ts +0 -123
- package/src/theme/runtime.ts +0 -119
- package/src/theme/theme.ts +0 -15
- package/src/utils/scroll-lock/README.md +0 -33
- /package/dist/{components/breadcrumbs/breadcrumbs.d.ts → breadcrumbs/index.d.ts} +0 -0
- /package/dist/{utils → listbox}/flash-attribute.d.ts +0 -0
- /package/dist/{utils → listbox}/flash-attribute.js +0 -0
- /package/dist/{components/menu → menu}/hover-aim.d.ts +0 -0
- /package/dist/{components/menu → menu}/hover-aim.js +0 -0
- /package/dist/{interactions/outside-click/outside-click-mixin.d.ts → popover/outside-click.d.ts} +0 -0
- /package/dist/{interactions/keydown → select}/keydown.d.ts +0 -0
- /package/dist/{interactions/keydown → select}/keydown.js +0 -0
- /package/dist/{interactions/typeahead/typeahead-mixin.d.ts → shared/typeahead.d.ts} +0 -0
- /package/dist/{utils → shared}/wait-for-css-transition.d.ts +0 -0
- /package/dist/{utils → shared}/wait-for-css-transition.js +0 -0
- /package/dist/{utils → shared}/wait.d.ts +0 -0
- /package/dist/{utils → shared}/wait.js +0 -0
- /package/src/{utils → listbox}/flash-attribute.ts +0 -0
- /package/src/{components/menu → menu}/hover-aim.ts +0 -0
- /package/src/{interactions/outside-click/outside-click-mixin.ts → popover/outside-click.ts} +0 -0
- /package/src/{interactions/keydown → select}/keydown.ts +0 -0
- /package/src/{interactions/typeahead/typeahead-mixin.ts → shared/typeahead.ts} +0 -0
- /package/src/{utils → shared}/wait-for-css-transition.ts +0 -0
- /package/src/{utils → shared}/wait.ts +0 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attrs,
|
|
3
|
+
createMixin,
|
|
4
|
+
on,
|
|
5
|
+
ref,
|
|
6
|
+
type Dispatched,
|
|
7
|
+
type ElementProps,
|
|
8
|
+
type Handle,
|
|
9
|
+
type MixinHandle,
|
|
10
|
+
type Props,
|
|
11
|
+
type RemixNode,
|
|
12
|
+
} from '@remix-run/ui'
|
|
13
|
+
|
|
14
|
+
const ACCORDION_CHANGE_EVENT = 'rmx:accordion-change' as const
|
|
15
|
+
|
|
16
|
+
type AccordionChangeHandler<target extends HTMLElement> = (
|
|
17
|
+
event: Dispatched<AccordionChangeEvent, target>,
|
|
18
|
+
signal: AbortSignal,
|
|
19
|
+
) => void | Promise<void>
|
|
20
|
+
|
|
21
|
+
declare global {
|
|
22
|
+
interface HTMLElementEventMap {
|
|
23
|
+
[ACCORDION_CHANGE_EVENT]: AccordionChangeEvent
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class AccordionChangeEvent extends Event {
|
|
28
|
+
readonly accordionType: AccordionType
|
|
29
|
+
readonly itemValue: string
|
|
30
|
+
readonly value: AccordionValue
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
value: AccordionValue,
|
|
34
|
+
init: {
|
|
35
|
+
accordionType: AccordionType
|
|
36
|
+
itemValue: string
|
|
37
|
+
},
|
|
38
|
+
) {
|
|
39
|
+
super(ACCORDION_CHANGE_EVENT, {
|
|
40
|
+
bubbles: true,
|
|
41
|
+
})
|
|
42
|
+
this.accordionType = init.accordionType
|
|
43
|
+
this.itemValue = init.itemValue
|
|
44
|
+
this.value = value
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type AccordionType = 'single' | 'multiple'
|
|
49
|
+
export type AccordionValue = string | null | string[]
|
|
50
|
+
export type AccordionSingleValue = string | null
|
|
51
|
+
export type AccordionMultipleValue = string[]
|
|
52
|
+
export type AccordionHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
|
|
53
|
+
|
|
54
|
+
type FocusDirection = 'first' | 'last' | 'next' | 'previous'
|
|
55
|
+
|
|
56
|
+
type RegisteredItem = {
|
|
57
|
+
disabled: boolean
|
|
58
|
+
getTriggerNode(): HTMLButtonElement | null
|
|
59
|
+
value: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface AccordionContextValue {
|
|
63
|
+
readonly collapsible: boolean
|
|
64
|
+
readonly disabled: boolean
|
|
65
|
+
readonly headingLevel: AccordionHeadingLevel
|
|
66
|
+
readonly type: AccordionType
|
|
67
|
+
focusItem(value: string, direction: FocusDirection): void
|
|
68
|
+
getPanelId(value: string): string
|
|
69
|
+
getTriggerId(value: string): string | undefined
|
|
70
|
+
isOpen(value: string): boolean
|
|
71
|
+
registerItem(item: RegisteredItem): void
|
|
72
|
+
registerRoot(node: HTMLElement): void
|
|
73
|
+
toggleItem(value: string): void
|
|
74
|
+
unregisterRoot(node: HTMLElement): void
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface AccordionItemContextValue {
|
|
78
|
+
readonly disabled: boolean
|
|
79
|
+
readonly headingLevel: AccordionHeadingLevel
|
|
80
|
+
readonly lockedOpen: boolean
|
|
81
|
+
readonly open: boolean
|
|
82
|
+
readonly panelId: string
|
|
83
|
+
readonly triggerId: string
|
|
84
|
+
readonly value: string
|
|
85
|
+
setTriggerNode(node: HTMLButtonElement | null): void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface AccordionBaseContextProps {
|
|
89
|
+
children?: RemixNode
|
|
90
|
+
disabled?: boolean
|
|
91
|
+
headingLevel?: AccordionHeadingLevel
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface AccordionSingleContextProps extends AccordionBaseContextProps {
|
|
95
|
+
type?: 'single'
|
|
96
|
+
value?: AccordionSingleValue
|
|
97
|
+
defaultValue?: AccordionSingleValue
|
|
98
|
+
onValueChange?: (value: AccordionSingleValue) => void
|
|
99
|
+
collapsible?: boolean
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface AccordionMultipleContextProps extends AccordionBaseContextProps {
|
|
103
|
+
type: 'multiple'
|
|
104
|
+
value?: AccordionMultipleValue
|
|
105
|
+
defaultValue?: AccordionMultipleValue
|
|
106
|
+
onValueChange?: (value: AccordionMultipleValue) => void
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type AccordionContextProps = AccordionSingleContextProps | AccordionMultipleContextProps
|
|
110
|
+
|
|
111
|
+
export interface AccordionRootOptions {}
|
|
112
|
+
|
|
113
|
+
export interface AccordionItemOptions {
|
|
114
|
+
disabled?: boolean
|
|
115
|
+
value: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface AccordionTriggerOptions {
|
|
119
|
+
disabled?: boolean
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface AccordionContentOptions {}
|
|
123
|
+
|
|
124
|
+
export type AccordionProps = Props<'div'> & AccordionContextProps
|
|
125
|
+
|
|
126
|
+
export type AccordionItemProps = Omit<Props<'div'>, 'children'> &
|
|
127
|
+
AccordionItemOptions & {
|
|
128
|
+
children?: RemixNode
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type AccordionTriggerProps = Omit<Props<'button'>, 'children' | 'type'> &
|
|
132
|
+
AccordionTriggerOptions & {
|
|
133
|
+
children?: RemixNode
|
|
134
|
+
type?: 'button' | 'submit' | 'reset'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type AccordionContentProps = Omit<Props<'div'>, 'children'> & {
|
|
138
|
+
children?: RemixNode
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isMultipleProps(
|
|
142
|
+
props: AccordionContextProps | null,
|
|
143
|
+
): props is AccordionMultipleContextProps {
|
|
144
|
+
return props?.type === 'multiple'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getAccordionContext(handle: Handle<unknown, unknown> | MixinHandle) {
|
|
148
|
+
return handle.context.get(AccordionProvider)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getAccordionItemContext(handle: Handle<unknown, unknown> | MixinHandle) {
|
|
152
|
+
return handle.context.get(AccordionItemProvider)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function AccordionProvider(
|
|
156
|
+
handle: Handle<AccordionContextProps, AccordionContextValue>,
|
|
157
|
+
): () => RemixNode {
|
|
158
|
+
let rootNode: HTMLElement | null = null
|
|
159
|
+
let registeredItems: RegisteredItem[] = []
|
|
160
|
+
let uncontrolledSingleValue: string | null = null
|
|
161
|
+
let uncontrolledMultipleValue: string[] = []
|
|
162
|
+
let hasInitializedSingle = false
|
|
163
|
+
let hasInitializedMultiple = false
|
|
164
|
+
|
|
165
|
+
function getType() {
|
|
166
|
+
return isMultipleProps(handle.props) ? 'multiple' : 'single'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getSingleValue() {
|
|
170
|
+
if (isMultipleProps(handle.props)) {
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (handle.props.value !== undefined) {
|
|
175
|
+
return handle.props.value
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!hasInitializedSingle) {
|
|
179
|
+
uncontrolledSingleValue = handle.props.defaultValue ?? null
|
|
180
|
+
hasInitializedSingle = true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return uncontrolledSingleValue
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getMultipleValue() {
|
|
187
|
+
if (!isMultipleProps(handle.props)) {
|
|
188
|
+
return []
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (handle.props.value !== undefined) {
|
|
192
|
+
return handle.props.value
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!hasInitializedMultiple) {
|
|
196
|
+
uncontrolledMultipleValue = [...(handle.props.defaultValue ?? [])]
|
|
197
|
+
hasInitializedMultiple = true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return uncontrolledMultipleValue
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isOpen(value: string) {
|
|
204
|
+
if (getType() === 'multiple') {
|
|
205
|
+
return getMultipleValue().includes(value)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return getSingleValue() === value
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function dispatchChange(itemValue: string, value: AccordionValue) {
|
|
212
|
+
rootNode?.dispatchEvent(
|
|
213
|
+
new AccordionChangeEvent(value, {
|
|
214
|
+
accordionType: getType(),
|
|
215
|
+
itemValue,
|
|
216
|
+
}),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toggleItem(itemValue: string) {
|
|
221
|
+
if (handle.props.disabled) {
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (isMultipleProps(handle.props)) {
|
|
226
|
+
let currentValue = getMultipleValue()
|
|
227
|
+
let nextValue = currentValue.includes(itemValue)
|
|
228
|
+
? currentValue.filter((value) => value !== itemValue)
|
|
229
|
+
: [...currentValue, itemValue]
|
|
230
|
+
|
|
231
|
+
if (handle.props.value === undefined) {
|
|
232
|
+
uncontrolledMultipleValue = nextValue
|
|
233
|
+
void handle.update()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
handle.props.onValueChange?.(nextValue)
|
|
237
|
+
dispatchChange(itemValue, nextValue)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let isCurrentItemOpen = getSingleValue() === itemValue
|
|
242
|
+
if (isCurrentItemOpen && !(handle.props.collapsible ?? true)) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let nextValue = isCurrentItemOpen ? null : itemValue
|
|
247
|
+
|
|
248
|
+
if (handle.props.value === undefined) {
|
|
249
|
+
uncontrolledSingleValue = nextValue
|
|
250
|
+
void handle.update()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
handle.props.onValueChange?.(nextValue)
|
|
254
|
+
dispatchChange(itemValue, nextValue)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function focusItem(itemValue: string, direction: FocusDirection) {
|
|
258
|
+
let items = registeredItems.filter((item) => !item.disabled && item.getTriggerNode() !== null)
|
|
259
|
+
if (items.length === 0) {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let currentIndex = items.findIndex((item) => item.value === itemValue)
|
|
264
|
+
let targetIndex = 0
|
|
265
|
+
|
|
266
|
+
switch (direction) {
|
|
267
|
+
case 'first':
|
|
268
|
+
targetIndex = 0
|
|
269
|
+
break
|
|
270
|
+
case 'last':
|
|
271
|
+
targetIndex = items.length - 1
|
|
272
|
+
break
|
|
273
|
+
case 'previous':
|
|
274
|
+
targetIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1
|
|
275
|
+
break
|
|
276
|
+
case 'next':
|
|
277
|
+
targetIndex =
|
|
278
|
+
currentIndex === -1 || currentIndex === items.length - 1 ? 0 : currentIndex + 1
|
|
279
|
+
break
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
items[targetIndex]?.getTriggerNode()?.focus()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function registerItem(item: RegisteredItem) {
|
|
286
|
+
registeredItems.push(item)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getTriggerId(value: string) {
|
|
290
|
+
return registeredItems.find((item) => item.value === value)?.getTriggerNode()?.id
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getPanelId(value: string) {
|
|
294
|
+
return `${handle.id}-${value}-panel`
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
handle.context.set({
|
|
298
|
+
get collapsible() {
|
|
299
|
+
return getType() === 'multiple'
|
|
300
|
+
? true
|
|
301
|
+
: ((handle.props as AccordionSingleContextProps).collapsible ?? true)
|
|
302
|
+
},
|
|
303
|
+
get disabled() {
|
|
304
|
+
return handle.props.disabled ?? false
|
|
305
|
+
},
|
|
306
|
+
focusItem,
|
|
307
|
+
getPanelId,
|
|
308
|
+
getTriggerId,
|
|
309
|
+
get headingLevel() {
|
|
310
|
+
return handle.props.headingLevel ?? 3
|
|
311
|
+
},
|
|
312
|
+
isOpen,
|
|
313
|
+
registerItem,
|
|
314
|
+
registerRoot(node) {
|
|
315
|
+
rootNode = node
|
|
316
|
+
},
|
|
317
|
+
toggleItem,
|
|
318
|
+
get type() {
|
|
319
|
+
return getType()
|
|
320
|
+
},
|
|
321
|
+
unregisterRoot(node) {
|
|
322
|
+
if (rootNode === node) {
|
|
323
|
+
rootNode = null
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
return () => {
|
|
329
|
+
registeredItems = []
|
|
330
|
+
return handle.props.children
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function AccordionItemProvider(
|
|
335
|
+
handle: Handle<AccordionItemOptions & { children?: RemixNode }, AccordionItemContextValue>,
|
|
336
|
+
): () => RemixNode {
|
|
337
|
+
let triggerNode: HTMLButtonElement | null = null
|
|
338
|
+
let triggerId = `${handle.id}-trigger`
|
|
339
|
+
let panelId = `${handle.id}-panel`
|
|
340
|
+
let disabled = false
|
|
341
|
+
let headingLevel: AccordionHeadingLevel = 3
|
|
342
|
+
let lockedOpen = false
|
|
343
|
+
let open = false
|
|
344
|
+
let value = handle.props.value
|
|
345
|
+
|
|
346
|
+
handle.context.set({
|
|
347
|
+
get disabled() {
|
|
348
|
+
return disabled
|
|
349
|
+
},
|
|
350
|
+
get headingLevel() {
|
|
351
|
+
return headingLevel
|
|
352
|
+
},
|
|
353
|
+
get lockedOpen() {
|
|
354
|
+
return lockedOpen
|
|
355
|
+
},
|
|
356
|
+
get open() {
|
|
357
|
+
return open
|
|
358
|
+
},
|
|
359
|
+
get panelId() {
|
|
360
|
+
return panelId
|
|
361
|
+
},
|
|
362
|
+
setTriggerNode(node) {
|
|
363
|
+
triggerNode = node
|
|
364
|
+
},
|
|
365
|
+
get triggerId() {
|
|
366
|
+
return triggerId
|
|
367
|
+
},
|
|
368
|
+
get value() {
|
|
369
|
+
return value
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
return () => {
|
|
374
|
+
let accordion = getAccordionContext(handle)
|
|
375
|
+
value = handle.props.value
|
|
376
|
+
disabled = accordion.disabled || handle.props.disabled === true
|
|
377
|
+
headingLevel = accordion.headingLevel
|
|
378
|
+
open = accordion.isOpen(handle.props.value)
|
|
379
|
+
lockedOpen = accordion.type === 'single' && !accordion.collapsible && open
|
|
380
|
+
|
|
381
|
+
accordion.registerItem({
|
|
382
|
+
disabled,
|
|
383
|
+
getTriggerNode: () => triggerNode,
|
|
384
|
+
value: handle.props.value,
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
return handle.props.children
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const rootMixin = createMixin<HTMLElement, [options?: AccordionRootOptions], ElementProps>(
|
|
392
|
+
(handle) => {
|
|
393
|
+
let context = getAccordionContext(handle)
|
|
394
|
+
let rootNode: HTMLElement | null = null
|
|
395
|
+
|
|
396
|
+
handle.queueTask((node, signal) => {
|
|
397
|
+
rootNode = node
|
|
398
|
+
context.registerRoot(node)
|
|
399
|
+
signal.addEventListener('abort', () => {
|
|
400
|
+
if (rootNode === node) {
|
|
401
|
+
rootNode = null
|
|
402
|
+
}
|
|
403
|
+
context.unregisterRoot(node)
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
return () =>
|
|
408
|
+
attrs({
|
|
409
|
+
'data-disabled': context.disabled ? '' : undefined,
|
|
410
|
+
'data-type': context.type,
|
|
411
|
+
})
|
|
412
|
+
},
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
const itemMixin = createMixin<HTMLElement, [options?: AccordionItemOptions], ElementProps>(
|
|
416
|
+
(handle) => {
|
|
417
|
+
let item = getAccordionItemContext(handle)
|
|
418
|
+
|
|
419
|
+
return () =>
|
|
420
|
+
attrs({
|
|
421
|
+
'data-disabled': item.disabled ? '' : undefined,
|
|
422
|
+
'data-state': item.open ? 'open' : 'closed',
|
|
423
|
+
})
|
|
424
|
+
},
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
const triggerMixin = createMixin<
|
|
428
|
+
HTMLButtonElement,
|
|
429
|
+
[options?: AccordionTriggerOptions],
|
|
430
|
+
ElementProps
|
|
431
|
+
>((handle) => {
|
|
432
|
+
let accordion = getAccordionContext(handle)
|
|
433
|
+
let item = getAccordionItemContext(handle)
|
|
434
|
+
|
|
435
|
+
return (options, props = options as ElementProps) => {
|
|
436
|
+
options = props === options ? undefined : options
|
|
437
|
+
let disabled = item.disabled || options?.disabled === true || props.disabled === true
|
|
438
|
+
let toggleItem = () => {
|
|
439
|
+
if (disabled || item.lockedOpen) {
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
accordion.toggleItem(item.value)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return [
|
|
447
|
+
attrs({
|
|
448
|
+
'aria-controls': item.panelId,
|
|
449
|
+
'aria-disabled': item.lockedOpen ? true : undefined,
|
|
450
|
+
'aria-expanded': item.open,
|
|
451
|
+
'data-state': item.open ? 'open' : 'closed',
|
|
452
|
+
disabled: disabled ? true : undefined,
|
|
453
|
+
id: item.triggerId,
|
|
454
|
+
}),
|
|
455
|
+
ref((node: HTMLButtonElement) => {
|
|
456
|
+
item.setTriggerNode(node)
|
|
457
|
+
}),
|
|
458
|
+
on('click', toggleItem),
|
|
459
|
+
on('keydown', (event) => {
|
|
460
|
+
switch (event.key) {
|
|
461
|
+
case 'ArrowDown':
|
|
462
|
+
event.preventDefault()
|
|
463
|
+
accordion.focusItem(item.value, 'next')
|
|
464
|
+
break
|
|
465
|
+
case 'ArrowUp':
|
|
466
|
+
event.preventDefault()
|
|
467
|
+
accordion.focusItem(item.value, 'previous')
|
|
468
|
+
break
|
|
469
|
+
case 'Home':
|
|
470
|
+
event.preventDefault()
|
|
471
|
+
accordion.focusItem(item.value, 'first')
|
|
472
|
+
break
|
|
473
|
+
case 'End':
|
|
474
|
+
event.preventDefault()
|
|
475
|
+
accordion.focusItem(item.value, 'last')
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
}),
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
const contentMixin = createMixin<HTMLElement, [options?: AccordionContentOptions], ElementProps>(
|
|
484
|
+
(handle) => {
|
|
485
|
+
let item = getAccordionItemContext(handle)
|
|
486
|
+
|
|
487
|
+
return () =>
|
|
488
|
+
attrs({
|
|
489
|
+
'aria-hidden': item.open ? undefined : true,
|
|
490
|
+
'aria-labelledby': item.triggerId,
|
|
491
|
+
'data-state': item.open ? 'open' : 'closed',
|
|
492
|
+
id: item.panelId,
|
|
493
|
+
inert: item.open ? undefined : true,
|
|
494
|
+
})
|
|
495
|
+
},
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
export const Context = AccordionProvider
|
|
499
|
+
export const ItemContext: (
|
|
500
|
+
handle: Handle<AccordionItemOptions & { children?: RemixNode }, AccordionItemContextValue>,
|
|
501
|
+
) => () => RemixNode = AccordionItemProvider
|
|
502
|
+
export const content = contentMixin
|
|
503
|
+
export const item = itemMixin
|
|
504
|
+
export const root = rootMixin
|
|
505
|
+
export const trigger = triggerMixin
|
|
506
|
+
|
|
507
|
+
export function onAccordionChange<target extends HTMLElement>(
|
|
508
|
+
handler: AccordionChangeHandler<target>,
|
|
509
|
+
captureBoolean?: boolean,
|
|
510
|
+
): ReturnType<typeof on<target, typeof ACCORDION_CHANGE_EVENT>> {
|
|
511
|
+
return on(ACCORDION_CHANGE_EVENT, handler, captureBoolean)
|
|
512
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# anchor
|
|
2
2
|
|
|
3
|
-
`anchor` positions a floating element against an anchor element and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
|
|
3
|
+
`anchor` positions a floating element against an anchor element or viewport coordinates and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Primitive Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
import { anchor } from 'remix/ui/anchor'
|
|
@@ -49,11 +49,54 @@ popover.addEventListener('beforetoggle', (event) => {
|
|
|
49
49
|
})
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Anchor to coordinates when the surface should open at a pointer location.
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
```tsx
|
|
55
|
+
let cleanup = anchor(popover, { x: event.clientX, y: event.clientY }, { placement: 'bottom-start' })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Keep presentation app-owned when the anchored element is rendered by your component:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { anchor } from 'remix/ui/anchor'
|
|
62
|
+
import { on, ref, type Handle } from 'remix/ui'
|
|
63
|
+
import { panelStyle } from './floating.styles'
|
|
64
|
+
|
|
65
|
+
export function AnchoredPanel(handle: Handle) {
|
|
66
|
+
let cleanup = () => {}
|
|
67
|
+
let trigger: HTMLElement | null = null
|
|
68
|
+
let panel: HTMLElement | null = null
|
|
69
|
+
|
|
70
|
+
function position() {
|
|
71
|
+
cleanup()
|
|
72
|
+
if (trigger && panel) {
|
|
73
|
+
cleanup = anchor(panel, trigger, { placement: 'bottom-start', offset: 8 })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return () => (
|
|
78
|
+
<>
|
|
79
|
+
<button
|
|
80
|
+
mix={[ref((node) => (trigger = node as HTMLElement)), on('click', position)]}
|
|
81
|
+
type="button"
|
|
82
|
+
>
|
|
83
|
+
Open
|
|
84
|
+
</button>
|
|
85
|
+
<div data-panel mix={[panelStyle, ref((node) => (panel = node as HTMLElement))]}>
|
|
86
|
+
Panel
|
|
87
|
+
</div>
|
|
88
|
+
</>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## `remix/ui/anchor`
|
|
94
|
+
|
|
95
|
+
- `anchor(floatingElement, anchorTarget, options)`: positions `floatingElement` against an element or coordinate target, starts animation-frame polling for geometry changes, and returns a cleanup function.
|
|
55
96
|
- `AnchorOptions`: placement, inset, relative alignment, and offset options.
|
|
97
|
+
- `AnchorPoint`: viewport coordinate target with `x`, `y`, and optional `width`/`height`.
|
|
56
98
|
- `AnchorPlacement`: exported placement names for the main sides and top/bottom start/end alignment.
|
|
99
|
+
- `AnchorTarget`: an `HTMLElement` or `AnchorPoint`.
|
|
57
100
|
|
|
58
101
|
## Placements
|
|
59
102
|
|
|
@@ -149,5 +192,5 @@ anchor(listbox, trigger, {
|
|
|
149
192
|
- Oversized inset surfaces with `relativeTo` preserve alignment by scrolling the nearest scrollable descendant when possible.
|
|
150
193
|
- `offset`, `offsetX`, and `offsetY` may be numbers or functions that receive the floating element.
|
|
151
194
|
- `relativeTo` lets a surface align to an inner element, which is useful for selected options inside popovers.
|
|
152
|
-
- `anchor` polls on animation frames for anchor or floating geometry changes and repositions when either changes.
|
|
195
|
+
- `anchor` polls on animation frames for anchor target or floating geometry changes and repositions when either changes.
|
|
153
196
|
- The returned cleanup function cancels animation-frame polling.
|
|
@@ -24,6 +24,15 @@ export type AnchorPlacement =
|
|
|
24
24
|
|
|
25
25
|
type AnchorOffsetValue = number | ((floating: HTMLElement) => number)
|
|
26
26
|
|
|
27
|
+
export interface AnchorPoint {
|
|
28
|
+
height?: number
|
|
29
|
+
width?: number
|
|
30
|
+
x: number
|
|
31
|
+
y: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AnchorTarget = HTMLElement | AnchorPoint
|
|
35
|
+
|
|
27
36
|
export type AnchorOptions = {
|
|
28
37
|
placement?: ExtendedAnchorPlacement
|
|
29
38
|
inset?: boolean
|
|
@@ -125,6 +134,39 @@ function resolveOffsetValue(offset: AnchorOffsetValue | undefined, floating: HTM
|
|
|
125
134
|
return offset ?? 0
|
|
126
135
|
}
|
|
127
136
|
|
|
137
|
+
function isAnchorPoint(value: unknown): value is AnchorPoint {
|
|
138
|
+
if (typeof value !== 'object' || value === null) {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!('x' in value) || !('y' in value)) {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let width = 'width' in value ? value.width : undefined
|
|
147
|
+
let height = 'height' in value ? value.height : undefined
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
typeof value.x === 'number' &&
|
|
151
|
+
typeof value.y === 'number' &&
|
|
152
|
+
(width === undefined || typeof width === 'number') &&
|
|
153
|
+
(height === undefined || typeof height === 'number')
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readAnchorRect(anchorTarget: AnchorTarget) {
|
|
158
|
+
if (anchorTarget instanceof HTMLElement) {
|
|
159
|
+
return anchorTarget.getBoundingClientRect()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return new DOMRect(
|
|
163
|
+
anchorTarget.x,
|
|
164
|
+
anchorTarget.y,
|
|
165
|
+
anchorTarget.width ?? 0,
|
|
166
|
+
anchorTarget.height ?? 0,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
128
170
|
function calculatePosition(
|
|
129
171
|
placement: ExtendedAnchorPlacement,
|
|
130
172
|
anchorRect: DOMRect,
|
|
@@ -468,7 +510,7 @@ function getFlippedPlacement(
|
|
|
468
510
|
placementHeight: number,
|
|
469
511
|
collisionWidth: number,
|
|
470
512
|
collisionHeight: number,
|
|
471
|
-
|
|
513
|
+
anchorIsAbsolutePositioned: boolean,
|
|
472
514
|
inset: boolean,
|
|
473
515
|
offset: number,
|
|
474
516
|
offsetX: number,
|
|
@@ -476,11 +518,9 @@ function getFlippedPlacement(
|
|
|
476
518
|
relativeOffsetX?: number | null,
|
|
477
519
|
relativeOffsetY?: number | null,
|
|
478
520
|
) {
|
|
479
|
-
let computedStyle = getComputedStyle(anchor)
|
|
480
|
-
let isAbsolutePositioned = computedStyle.position !== 'fixed'
|
|
481
521
|
let hasScroll = window.scrollY > 0
|
|
482
522
|
|
|
483
|
-
if (placement.startsWith('top') &&
|
|
523
|
+
if (placement.startsWith('top') && anchorIsAbsolutePositioned && hasScroll) {
|
|
484
524
|
if (anchorRect.top - placementHeight < 0) {
|
|
485
525
|
return placement
|
|
486
526
|
}
|
|
@@ -641,7 +681,7 @@ function solveRelativeAlignmentAxis(options: {
|
|
|
641
681
|
|
|
642
682
|
export function anchor(
|
|
643
683
|
floating: HTMLElement,
|
|
644
|
-
|
|
684
|
+
anchorTarget: AnchorTarget,
|
|
645
685
|
options: AnchorOptions = {},
|
|
646
686
|
): () => void {
|
|
647
687
|
let lastAnchorRect: DOMRect
|
|
@@ -651,8 +691,8 @@ export function anchor(
|
|
|
651
691
|
throw new TypeError('anchor() expected a floating HTMLElement')
|
|
652
692
|
}
|
|
653
693
|
|
|
654
|
-
if (!(
|
|
655
|
-
throw new TypeError('anchor() expected an anchor HTMLElement')
|
|
694
|
+
if (!(anchorTarget instanceof HTMLElement) && !isAnchorPoint(anchorTarget)) {
|
|
695
|
+
throw new TypeError('anchor() expected an anchor HTMLElement or coordinates')
|
|
656
696
|
}
|
|
657
697
|
|
|
658
698
|
let {
|
|
@@ -664,12 +704,13 @@ export function anchor(
|
|
|
664
704
|
offsetY: rawOffsetY = 0,
|
|
665
705
|
} = options
|
|
666
706
|
|
|
667
|
-
let
|
|
668
|
-
|
|
707
|
+
let anchorIsAbsolutePositioned =
|
|
708
|
+
anchorTarget instanceof HTMLElement && getComputedStyle(anchorTarget).position !== 'fixed'
|
|
709
|
+
let isFixed = floating.hasAttribute('popover') || !anchorIsAbsolutePositioned
|
|
669
710
|
let animationFrameId = 0
|
|
670
711
|
|
|
671
712
|
function updatePosition(
|
|
672
|
-
anchorRect =
|
|
713
|
+
anchorRect = readAnchorRect(anchorTarget),
|
|
673
714
|
naturalDimensions = getFloatingDimensions(floating, relativeTo, {
|
|
674
715
|
ignoreInlineMaxSize: true,
|
|
675
716
|
}),
|
|
@@ -695,7 +736,7 @@ export function anchor(
|
|
|
695
736
|
placementHeight,
|
|
696
737
|
naturalDimensions.width,
|
|
697
738
|
naturalDimensions.height,
|
|
698
|
-
|
|
739
|
+
anchorIsAbsolutePositioned,
|
|
699
740
|
inset,
|
|
700
741
|
offset,
|
|
701
742
|
offsetX,
|
|
@@ -872,7 +913,7 @@ export function anchor(
|
|
|
872
913
|
updatePosition()
|
|
873
914
|
|
|
874
915
|
function pollForPositionChanges() {
|
|
875
|
-
let currentRect =
|
|
916
|
+
let currentRect = readAnchorRect(anchorTarget)
|
|
876
917
|
let currentDimensions = getFloatingDimensions(floating, relativeTo)
|
|
877
918
|
if (
|
|
878
919
|
hasRectChanged(currentRect, lastAnchorRect) ||
|