@remix-run/ui 0.3.0 → 0.5.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 +116 -157
- 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.js → anchor/index.js} +1 -1
- package/dist/anchor/index.js.map +1 -0
- package/dist/animation/animate-layout-mixin.js +1 -1
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/animation/animate-mixins.js +2 -2
- package/dist/animation/animate-mixins.js.map +1 -1
- package/dist/animation/demos/color-interpolation.js +1 -1
- package/dist/animation/demos/color-interpolation.js.map +1 -1
- package/dist/animation/demos/drag-release.d.ts +1 -1
- package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
- package/dist/animation/demos/material-ripple.js +1 -3
- package/dist/animation/demos/material-ripple.js.map +1 -1
- package/dist/animation/demos/reordering.js +9 -6
- package/dist/animation/demos/reordering.js.map +1 -1
- package/dist/animation/index.js +4 -4
- 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 +127 -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} +4 -8
- package/dist/{components/combobox/combobox.js → combobox/primitives.js} +8 -61
- package/dist/combobox/primitives.js.map +1 -0
- package/dist/dev/refresh.d.ts +1 -0
- package/dist/dev/refresh.js +2 -0
- package/dist/dev/refresh.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -14
- 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/jsx-runtime.js +2 -2
- 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} +4 -33
- package/dist/{components/menu/menu.js → menu/primitives.js} +98 -315
- package/dist/menu/primitives.js.map +1 -0
- package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +2 -4
- package/dist/{components/popover/popover.js → popover/index.js} +5 -48
- 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-entry-boundary.d.ts +14 -0
- package/dist/runtime/client-entry-boundary.js +29 -0
- package/dist/runtime/client-entry-boundary.js.map +1 -0
- package/dist/runtime/component.d.ts +19 -4
- package/dist/runtime/component.js +39 -9
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.js +36 -3
- package/dist/runtime/core/attributes.js.map +1 -1
- package/dist/runtime/core/children.d.ts +2 -0
- package/dist/runtime/core/children.js +26 -2
- package/dist/runtime/core/children.js.map +1 -1
- package/dist/runtime/core/props.js +2 -2
- package/dist/runtime/core/vnode.d.ts +3 -3
- package/dist/runtime/core/vnode.js +22 -1
- package/dist/runtime/core/vnode.js.map +1 -1
- package/dist/runtime/create-element.d.ts +2 -2
- package/dist/runtime/create-element.js +3 -2
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +294 -219
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.js +3 -3
- package/dist/runtime/dom.d.ts +12 -0
- package/dist/runtime/element-function.d.ts +3 -0
- package/dist/runtime/element-function.js +2 -0
- package/dist/runtime/element-function.js.map +1 -0
- package/dist/runtime/form-navigation.d.ts +25 -0
- package/dist/runtime/form-navigation.js +132 -0
- package/dist/runtime/form-navigation.js.map +1 -0
- package/dist/runtime/frame-resolution.d.ts +5 -0
- package/dist/runtime/frame-resolution.js +10 -0
- package/dist/runtime/frame-resolution.js.map +1 -0
- package/dist/runtime/frame.d.ts +58 -15
- package/dist/runtime/frame.js +367 -114
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +1 -1
- package/dist/runtime/jsx.js +1 -1
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/key.d.ts +1 -0
- package/dist/runtime/key.js +2 -0
- package/dist/runtime/key.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.js +2 -2
- package/dist/runtime/mixins/link-mixin.js +4 -3
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +15 -10
- package/dist/runtime/mixins/mixin.js +4 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.js +1 -1
- package/dist/runtime/mixins/ref-mixin.js +1 -1
- package/dist/runtime/navigation.d.ts +4 -1
- package/dist/runtime/navigation.js +157 -32
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +11 -10
- package/dist/runtime/reconcile.js +557 -423
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/refresh.d.ts +28 -0
- package/dist/runtime/refresh.js +31 -0
- package/dist/runtime/refresh.js.map +1 -0
- package/dist/runtime/render.js +1 -1
- package/dist/runtime/render.js.map +1 -1
- package/dist/runtime/run.d.ts +3 -1
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +8 -16
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/to-vnode.d.ts +2 -2
- package/dist/runtime/to-vnode.js +68 -20
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vdom.d.ts +4 -2
- package/dist/runtime/vdom.js +89 -41
- package/dist/runtime/vdom.js.map +1 -1
- package/dist/runtime/vnode.d.ts +138 -67
- package/dist/runtime/vnode.js +17 -12
- package/dist/runtime/vnode.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} +5 -7
- package/dist/{components/select/select.js → select/primitives.js} +11 -63
- package/dist/select/primitives.js.map +1 -0
- package/dist/server/stream.js +22 -29
- 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/shared/style-values.js +88 -0
- 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/css-mixin.js +3 -3
- package/dist/style/index.js +2 -2
- package/dist/style/style.js +25 -6
- package/dist/style/style.js.map +1 -1
- package/dist/style/stylesheet.d.ts +0 -1
- package/dist/style/stylesheet.js +13 -54
- package/dist/style/stylesheet.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/test.js +1 -1
- 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 +81 -49
- 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 +37 -2
- package/src/animation/animate-mixins.ts +3 -3
- package/src/animation/demos/color-interpolation.tsx +1 -1
- package/src/animation/demos/reordering.tsx +9 -6
- 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 +164 -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/dev/refresh.ts +1 -0
- package/src/index.ts +1 -1
- 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/{components/menu → menu}/README.md +55 -14
- package/src/menu/index.tsx +354 -0
- package/src/{components/menu/menu.tsx → menu/primitives.tsx} +99 -413
- package/src/{components/popover → popover}/README.md +20 -39
- package/src/{components/popover/popover.ts → popover/index.ts} +3 -57
- package/src/{utils → popover}/scroll-lock.ts +0 -28
- package/src/radio/README.md +53 -0
- package/src/radio/index.ts +101 -0
- package/src/runtime/client-entry-boundary.ts +54 -0
- package/src/runtime/component.ts +65 -16
- package/src/runtime/core/attributes.ts +35 -1
- package/src/runtime/core/children.ts +24 -2
- package/src/runtime/core/vnode.ts +25 -4
- package/src/runtime/create-element.ts +6 -5
- package/src/runtime/demos/readme.demo.tsx +20 -18
- package/src/runtime/diff-dom.ts +358 -243
- package/src/runtime/dom.ts +331 -214
- package/src/runtime/element-function.ts +1 -0
- package/src/runtime/event-listeners.ts +1 -1
- package/src/runtime/form-navigation.ts +184 -0
- package/src/runtime/frame-resolution.ts +13 -0
- package/src/runtime/frame.ts +482 -124
- package/src/runtime/jsx.ts +3 -5
- package/src/runtime/key.ts +1 -0
- package/src/runtime/mixins/link-mixin.ts +1 -0
- package/src/runtime/mixins/mixin.ts +21 -18
- package/src/runtime/navigation.ts +204 -30
- package/src/runtime/reconcile.ts +769 -760
- package/src/runtime/refresh.ts +46 -0
- package/src/runtime/run.ts +12 -2
- package/src/runtime/scheduler.ts +9 -29
- package/src/runtime/to-vnode.ts +85 -23
- package/src/runtime/vdom.ts +91 -69
- package/src/runtime/vnode.ts +192 -88
- 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} +10 -98
- package/src/server/stream.ts +25 -31
- 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 +25 -6
- package/src/style/stylesheet.ts +32 -57
- package/src/tabs/README.md +141 -0
- package/src/tabs/index.tsx +245 -0
- package/src/tabs/primitives.ts +365 -0
- package/src/test/README.md +151 -60
- package/src/toggle/README.md +56 -0
- package/src/toggle/index.tsx +136 -0
- package/src/toggle/primitives.ts +192 -0
- package/dist/animation/demos/animation.demo.d.ts +0 -5
- package/dist/animation/demos/animation.demo.js +0 -92
- package/dist/animation/demos/animation.demo.js.map +0 -1
- package/dist/animation/demos/spring.demo.d.ts +0 -6
- package/dist/animation/demos/spring.demo.js +0 -304
- package/dist/animation/demos/spring.demo.js.map +0 -1
- 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 -7
- package/dist/components/accordion/demos/card.demo.js +0 -59
- package/dist/components/accordion/demos/card.demo.js.map +0 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +0 -7
- package/dist/components/accordion/demos/multiple.demo.js +0 -20
- package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +0 -7
- package/dist/components/accordion/demos/overview.demo.js +0 -20
- package/dist/components/accordion/demos/overview.demo.js.map +0 -1
- package/dist/components/anchor/anchor.js.map +0 -1
- package/dist/components/breadcrumbs/basic.demo.d.ts +0 -6
- package/dist/components/breadcrumbs/basic.demo.js +0 -15
- package/dist/components/breadcrumbs/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
- package/dist/components/breadcrumbs/separator.demo.d.ts +0 -6
- package/dist/components/breadcrumbs/separator.demo.js +0 -15
- package/dist/components/breadcrumbs/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 -7
- package/dist/components/button/demos/aliases.demo.js +0 -19
- package/dist/components/button/demos/aliases.demo.js.map +0 -1
- package/dist/components/button/demos/basic.demo.d.ts +0 -7
- package/dist/components/button/demos/basic.demo.js +0 -20
- package/dist/components/button/demos/basic.demo.js.map +0 -1
- package/dist/components/button/demos/states.demo.d.ts +0 -7
- package/dist/components/button/demos/states.demo.js +0 -30
- package/dist/components/button/demos/states.demo.js.map +0 -1
- package/dist/components/combobox/combobox.js.map +0 -1
- package/dist/components/combobox/overview.demo.d.ts +0 -7
- package/dist/components/combobox/overview.demo.js +0 -154
- package/dist/components/combobox/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/listbox.js.map +0 -1
- package/dist/components/listbox/overview.demo.d.ts +0 -7
- package/dist/components/listbox/overview.demo.js +0 -65
- package/dist/components/listbox/overview.demo.js.map +0 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +0 -6
- package/dist/components/menu/demos/bubbling.demo.js +0 -16
- package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
- package/dist/components/menu/demos/context-trigger.demo.d.ts +0 -7
- package/dist/components/menu/demos/context-trigger.demo.js +0 -87
- package/dist/components/menu/demos/context-trigger.demo.js.map +0 -1
- package/dist/components/menu/demos/overview.demo.d.ts +0 -7
- package/dist/components/menu/demos/overview.demo.js +0 -33
- 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/overview.demo.d.ts +0 -7
- package/dist/components/popover/overview.demo.js +0 -65
- package/dist/components/popover/overview.demo.js.map +0 -1
- package/dist/components/popover/popover.js.map +0 -1
- package/dist/components/select/deconstructed.demo.d.ts +0 -7
- package/dist/components/select/deconstructed.demo.js +0 -54
- package/dist/components/select/deconstructed.demo.js.map +0 -1
- package/dist/components/select/overview.demo.d.ts +0 -6
- package/dist/components/select/overview.demo.js +0 -28
- package/dist/components/select/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/runtime/demos/basic.demo.d.ts +0 -6
- package/dist/runtime/demos/basic.demo.js +0 -16
- package/dist/runtime/demos/basic.demo.js.map +0 -1
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +0 -6
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +0 -77
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +0 -1
- package/dist/runtime/demos/keyed-list.demo.d.ts +0 -6
- package/dist/runtime/demos/keyed-list.demo.js +0 -62
- package/dist/runtime/demos/keyed-list.demo.js.map +0 -1
- package/dist/runtime/demos/readme.demo.d.ts +0 -5
- package/dist/runtime/demos/readme.demo.js +0 -396
- package/dist/runtime/demos/readme.demo.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 +0 -119
- 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/animation/demos/animation.demo.tsx +0 -194
- package/src/animation/demos/spring.demo.tsx +0 -385
- package/src/components/accordion/accordion.tsx +0 -565
- package/src/components/accordion/demos/card.demo.tsx +0 -114
- package/src/components/accordion/demos/multiple.demo.tsx +0 -57
- package/src/components/accordion/demos/overview.demo.tsx +0 -57
- package/src/components/breadcrumbs/basic.demo.tsx +0 -18
- package/src/components/breadcrumbs/separator.demo.tsx +0 -19
- 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 -28
- package/src/components/button/demos/basic.demo.tsx +0 -31
- package/src/components/button/demos/states.demo.tsx +0 -53
- package/src/components/combobox/overview.demo.tsx +0 -200
- package/src/components/glyph/README.md +0 -72
- package/src/components/glyph/glyph.tsx +0 -110
- package/src/components/listbox/overview.demo.tsx +0 -102
- package/src/components/menu/demos/bubbling.demo.tsx +0 -35
- package/src/components/menu/demos/context-trigger.demo.tsx +0 -133
- package/src/components/menu/demos/overview.demo.tsx +0 -77
- package/src/components/popover/overview.demo.tsx +0 -144
- package/src/components/select/deconstructed.demo.tsx +0 -96
- package/src/components/select/overview.demo.tsx +0 -52
- 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/runtime/demos/basic.demo.tsx +0 -21
- package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +0 -177
- package/src/runtime/demos/keyed-list.demo.tsx +0 -94
- 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/anchor/anchor.d.ts → anchor/index.d.ts} +0 -0
- /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/{components/anchor/anchor.ts → anchor/index.ts} +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,365 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attrs,
|
|
3
|
+
createMixin,
|
|
4
|
+
on,
|
|
5
|
+
ref,
|
|
6
|
+
type Dispatched,
|
|
7
|
+
type ElementProps,
|
|
8
|
+
type Handle,
|
|
9
|
+
type MixinHandle,
|
|
10
|
+
type MixinFactory,
|
|
11
|
+
type RemixNode,
|
|
12
|
+
} from '@remix-run/ui'
|
|
13
|
+
|
|
14
|
+
const TABS_CHANGE_EVENT = 'rmx:tabs-change' as const
|
|
15
|
+
|
|
16
|
+
type TabsChangeHandler<target extends HTMLElement> = (
|
|
17
|
+
event: Dispatched<TabsChangeEvent, target>,
|
|
18
|
+
signal: AbortSignal,
|
|
19
|
+
) => void | Promise<void>
|
|
20
|
+
|
|
21
|
+
export type TabsActivationDirection = 'first' | 'last' | 'next' | 'previous'
|
|
22
|
+
|
|
23
|
+
export interface TabsRegisteredTab {
|
|
24
|
+
disabled: boolean
|
|
25
|
+
getTabNode(): HTMLButtonElement | null
|
|
26
|
+
name: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TabsContextValue {
|
|
30
|
+
readonly activeTab: string | null
|
|
31
|
+
readonly disabled: boolean
|
|
32
|
+
activateTab(name: string): void
|
|
33
|
+
activateTabInDirection(name: string, direction: TabsActivationDirection): void
|
|
34
|
+
getPanelId(name: string): string
|
|
35
|
+
getTabId(name: string): string
|
|
36
|
+
isActiveTab(name: string): boolean
|
|
37
|
+
registerRoot(node: HTMLElement): void
|
|
38
|
+
registerTab(tab: TabsRegisteredTab): void
|
|
39
|
+
unregisterRoot(node: HTMLElement): void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TabsContextProps {
|
|
43
|
+
activeTab?: string
|
|
44
|
+
children?: RemixNode
|
|
45
|
+
defaultActiveTab?: string
|
|
46
|
+
disabled?: boolean
|
|
47
|
+
onActiveTabChange?: (activeTab: string) => void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TabsRootOptions {}
|
|
51
|
+
|
|
52
|
+
export interface TabListOptions {}
|
|
53
|
+
|
|
54
|
+
export interface TabOptions {
|
|
55
|
+
disabled?: boolean
|
|
56
|
+
name: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TabPanelOptions {
|
|
60
|
+
name: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class TabsChangeEvent extends Event {
|
|
64
|
+
readonly activeTab: string
|
|
65
|
+
readonly previousActiveTab: string | null
|
|
66
|
+
|
|
67
|
+
constructor(activeTab: string, previousActiveTab: string | null) {
|
|
68
|
+
super(TABS_CHANGE_EVENT, {
|
|
69
|
+
bubbles: true,
|
|
70
|
+
})
|
|
71
|
+
this.activeTab = activeTab
|
|
72
|
+
this.previousActiveTab = previousActiveTab
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare global {
|
|
77
|
+
interface HTMLElementEventMap {
|
|
78
|
+
[TABS_CHANGE_EVENT]: TabsChangeEvent
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function encodeIdPart(value: string): string {
|
|
83
|
+
return encodeURIComponent(value).replace(/%/g, '-')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getTabState(active: boolean): 'active' | 'inactive' {
|
|
87
|
+
return active ? 'active' : 'inactive'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getTabsContext(handle: Handle<unknown, unknown> | MixinHandle) {
|
|
91
|
+
return handle.context.get(TabsProvider)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function TabsProvider(handle: Handle<TabsContextProps, TabsContextValue>): () => RemixNode {
|
|
95
|
+
let rootNode: HTMLElement | null = null
|
|
96
|
+
let renderedTabs: TabsRegisteredTab[] = []
|
|
97
|
+
let uncontrolledActiveTab: string | null = null
|
|
98
|
+
let hasInitialized = false
|
|
99
|
+
|
|
100
|
+
function getActiveTab() {
|
|
101
|
+
if (handle.props.activeTab !== undefined) {
|
|
102
|
+
return handle.props.activeTab
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!hasInitialized) {
|
|
106
|
+
uncontrolledActiveTab = handle.props.defaultActiveTab ?? null
|
|
107
|
+
hasInitialized = true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return uncontrolledActiveTab
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getRegisteredTab(name: string) {
|
|
114
|
+
return renderedTabs.find((tab) => tab.name === name)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function dispatchChange(activeTab: string, previousActiveTab: string | null) {
|
|
118
|
+
rootNode?.dispatchEvent(new TabsChangeEvent(activeTab, previousActiveTab))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function activateTab(name: string) {
|
|
122
|
+
if (handle.props.disabled) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let tab = getRegisteredTab(name)
|
|
127
|
+
if (tab?.disabled) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let previousActiveTab = getActiveTab()
|
|
132
|
+
if (previousActiveTab === name) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (handle.props.activeTab === undefined) {
|
|
137
|
+
uncontrolledActiveTab = name
|
|
138
|
+
void handle.update()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
handle.props.onActiveTabChange?.(name)
|
|
142
|
+
dispatchChange(name, previousActiveTab)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getFocusableTabs() {
|
|
146
|
+
return renderedTabs.filter((tab) => !tab.disabled && tab.getTabNode() !== null)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function activateTabInDirection(name: string, direction: TabsActivationDirection) {
|
|
150
|
+
let tabs = getFocusableTabs()
|
|
151
|
+
if (tabs.length === 0) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let currentIndex = tabs.findIndex((tab) => tab.name === name)
|
|
156
|
+
let targetIndex = 0
|
|
157
|
+
|
|
158
|
+
switch (direction) {
|
|
159
|
+
case 'first':
|
|
160
|
+
targetIndex = 0
|
|
161
|
+
break
|
|
162
|
+
case 'last':
|
|
163
|
+
targetIndex = tabs.length - 1
|
|
164
|
+
break
|
|
165
|
+
case 'previous':
|
|
166
|
+
targetIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1
|
|
167
|
+
break
|
|
168
|
+
case 'next':
|
|
169
|
+
targetIndex = currentIndex === -1 || currentIndex === tabs.length - 1 ? 0 : currentIndex + 1
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let targetTab = tabs[targetIndex]
|
|
174
|
+
targetTab?.getTabNode()?.focus()
|
|
175
|
+
if (targetTab) {
|
|
176
|
+
activateTab(targetTab.name)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getTabId(name: string) {
|
|
181
|
+
return `${handle.id}-${encodeIdPart(name)}-tab`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getPanelId(name: string) {
|
|
185
|
+
return `${handle.id}-${encodeIdPart(name)}-panel`
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function registerTab(tab: TabsRegisteredTab) {
|
|
189
|
+
renderedTabs.push(tab)
|
|
190
|
+
|
|
191
|
+
if (handle.props.activeTab === undefined && getActiveTab() === null && !tab.disabled) {
|
|
192
|
+
uncontrolledActiveTab = tab.name
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
handle.context.set({
|
|
197
|
+
get activeTab() {
|
|
198
|
+
return getActiveTab()
|
|
199
|
+
},
|
|
200
|
+
get disabled() {
|
|
201
|
+
return handle.props.disabled ?? false
|
|
202
|
+
},
|
|
203
|
+
activateTab,
|
|
204
|
+
activateTabInDirection,
|
|
205
|
+
getPanelId,
|
|
206
|
+
getTabId,
|
|
207
|
+
isActiveTab(name) {
|
|
208
|
+
return getActiveTab() === name
|
|
209
|
+
},
|
|
210
|
+
registerRoot(node) {
|
|
211
|
+
rootNode = node
|
|
212
|
+
},
|
|
213
|
+
registerTab,
|
|
214
|
+
unregisterRoot(node) {
|
|
215
|
+
if (rootNode === node) {
|
|
216
|
+
rootNode = null
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
return () => {
|
|
222
|
+
renderedTabs = []
|
|
223
|
+
return handle.props.children
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const rootMixin: MixinFactory<HTMLElement, [options?: TabsRootOptions], ElementProps> = createMixin<
|
|
228
|
+
HTMLElement,
|
|
229
|
+
[options?: TabsRootOptions],
|
|
230
|
+
ElementProps
|
|
231
|
+
>((handle) => {
|
|
232
|
+
let context = getTabsContext(handle)
|
|
233
|
+
|
|
234
|
+
handle.queueTask((node, signal) => {
|
|
235
|
+
context.registerRoot(node)
|
|
236
|
+
signal.addEventListener('abort', () => {
|
|
237
|
+
context.unregisterRoot(node)
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
return () =>
|
|
242
|
+
attrs({
|
|
243
|
+
'data-disabled': context.disabled ? '' : undefined,
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const listMixin: MixinFactory<HTMLElement, [options?: TabListOptions], ElementProps> = createMixin<
|
|
248
|
+
HTMLElement,
|
|
249
|
+
[options?: TabListOptions],
|
|
250
|
+
ElementProps
|
|
251
|
+
>((handle) => {
|
|
252
|
+
let context = getTabsContext(handle)
|
|
253
|
+
|
|
254
|
+
return () =>
|
|
255
|
+
attrs({
|
|
256
|
+
'aria-disabled': context.disabled ? true : undefined,
|
|
257
|
+
role: 'tablist',
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const tabMixin: MixinFactory<HTMLButtonElement, [options: TabOptions], ElementProps> = createMixin<
|
|
262
|
+
HTMLButtonElement,
|
|
263
|
+
[options: TabOptions],
|
|
264
|
+
ElementProps
|
|
265
|
+
>((handle, hostType) => {
|
|
266
|
+
let context = getTabsContext(handle)
|
|
267
|
+
let tabNode: HTMLButtonElement | null = null
|
|
268
|
+
|
|
269
|
+
return (options, props) => {
|
|
270
|
+
let disabled = context.disabled || options.disabled === true || props.disabled === true
|
|
271
|
+
|
|
272
|
+
context.registerTab({
|
|
273
|
+
disabled,
|
|
274
|
+
getTabNode: () => tabNode,
|
|
275
|
+
name: options.name,
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
let active = context.isActiveTab(options.name)
|
|
279
|
+
|
|
280
|
+
return [
|
|
281
|
+
attrs({
|
|
282
|
+
'aria-controls': context.getPanelId(options.name),
|
|
283
|
+
'aria-selected': active ? 'true' : 'false',
|
|
284
|
+
'data-state': getTabState(active),
|
|
285
|
+
disabled: disabled ? true : undefined,
|
|
286
|
+
id: context.getTabId(options.name),
|
|
287
|
+
role: 'tab',
|
|
288
|
+
tabIndex: active && !disabled ? 0 : -1,
|
|
289
|
+
type: hostType === 'button' ? 'button' : undefined,
|
|
290
|
+
}),
|
|
291
|
+
ref((node: HTMLButtonElement, signal) => {
|
|
292
|
+
tabNode = node
|
|
293
|
+
signal.addEventListener('abort', () => {
|
|
294
|
+
if (tabNode === node) {
|
|
295
|
+
tabNode = null
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}),
|
|
299
|
+
on('click', () => {
|
|
300
|
+
context.activateTab(options.name)
|
|
301
|
+
}),
|
|
302
|
+
on('keydown', (event) => {
|
|
303
|
+
switch (event.key) {
|
|
304
|
+
case 'ArrowDown':
|
|
305
|
+
case 'ArrowRight':
|
|
306
|
+
event.preventDefault()
|
|
307
|
+
context.activateTabInDirection(options.name, 'next')
|
|
308
|
+
break
|
|
309
|
+
case 'ArrowUp':
|
|
310
|
+
case 'ArrowLeft':
|
|
311
|
+
event.preventDefault()
|
|
312
|
+
context.activateTabInDirection(options.name, 'previous')
|
|
313
|
+
break
|
|
314
|
+
case 'Home':
|
|
315
|
+
event.preventDefault()
|
|
316
|
+
context.activateTabInDirection(options.name, 'first')
|
|
317
|
+
break
|
|
318
|
+
case 'End':
|
|
319
|
+
event.preventDefault()
|
|
320
|
+
context.activateTabInDirection(options.name, 'last')
|
|
321
|
+
break
|
|
322
|
+
case 'Enter':
|
|
323
|
+
case ' ':
|
|
324
|
+
event.preventDefault()
|
|
325
|
+
context.activateTab(options.name)
|
|
326
|
+
break
|
|
327
|
+
}
|
|
328
|
+
}),
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const panelMixin: MixinFactory<HTMLElement, [options: TabPanelOptions], ElementProps> = createMixin<
|
|
334
|
+
HTMLElement,
|
|
335
|
+
[options: TabPanelOptions],
|
|
336
|
+
ElementProps
|
|
337
|
+
>((handle) => {
|
|
338
|
+
let context = getTabsContext(handle)
|
|
339
|
+
|
|
340
|
+
return (options) => {
|
|
341
|
+
let active = context.isActiveTab(options.name)
|
|
342
|
+
|
|
343
|
+
return attrs({
|
|
344
|
+
'aria-labelledby': context.getTabId(options.name),
|
|
345
|
+
'data-state': getTabState(active),
|
|
346
|
+
hidden: active ? undefined : true,
|
|
347
|
+
id: context.getPanelId(options.name),
|
|
348
|
+
inert: active ? undefined : true,
|
|
349
|
+
role: 'tabpanel',
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
export const Context = TabsProvider
|
|
355
|
+
export const list = listMixin
|
|
356
|
+
export const panel = panelMixin
|
|
357
|
+
export const root = rootMixin
|
|
358
|
+
export const tab = tabMixin
|
|
359
|
+
|
|
360
|
+
export function onTabsChange<target extends HTMLElement>(
|
|
361
|
+
handler: TabsChangeHandler<target>,
|
|
362
|
+
captureBoolean?: boolean,
|
|
363
|
+
): ReturnType<typeof on<target, typeof TABS_CHANGE_EVENT>> {
|
|
364
|
+
return on(TABS_CHANGE_EVENT, handler, captureBoolean)
|
|
365
|
+
}
|
package/src/test/README.md
CHANGED
|
@@ -1,104 +1,195 @@
|
|
|
1
1
|
# Test
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `render` from `remix/ui/test` as the primary way to test components in a live browser through a `remix/test` "browser" test (`*.test.browser.tsx`). It creates a DOM container, renders and flushes the initial component tree, and returns helpers for querying, interacting with, and cleaning up the rendered output.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
The main use case is flushing after events that call `handle.update()`. Since updates are asynchronous, you need to flush to ensure the DOM reflects the changes:
|
|
5
|
+
## Rendering Components
|
|
8
6
|
|
|
9
7
|
```tsx
|
|
8
|
+
import { expect } from 'remix/assert'
|
|
9
|
+
import { describe, it } from 'remix/test'
|
|
10
|
+
import { on, type Handle } from 'remix/ui'
|
|
11
|
+
import { render } from 'remix/ui/test'
|
|
12
|
+
|
|
10
13
|
function Counter(handle: Handle) {
|
|
11
14
|
let count = 0
|
|
12
15
|
|
|
13
16
|
return () => (
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
<div>
|
|
18
|
+
<button
|
|
19
|
+
data-action="increment"
|
|
20
|
+
mix={[
|
|
21
|
+
on('click', () => {
|
|
22
|
+
count++
|
|
23
|
+
handle.update()
|
|
24
|
+
}),
|
|
25
|
+
]}
|
|
26
|
+
>
|
|
27
|
+
Increment
|
|
28
|
+
</button>
|
|
29
|
+
<output data-testid="count">{count}</output>
|
|
30
|
+
</div>
|
|
24
31
|
)
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
34
|
+
describe('Counter', () => {
|
|
35
|
+
it('increments the count', async (t) => {
|
|
36
|
+
let { $, act, cleanup } = render(<Counter />)
|
|
37
|
+
t.after(cleanup)
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
root.flush() // Ensure initial render completes
|
|
39
|
+
expect($('[data-testid="count"]')?.textContent).toBe('0')
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
button.click() // Triggers handle.update()
|
|
36
|
-
root.flush() // Flush to apply the update
|
|
41
|
+
await act(() => $('[data-action="increment"]')?.click())
|
|
37
42
|
|
|
38
|
-
expect(
|
|
43
|
+
expect($('[data-testid="count"]')?.textContent).toBe('1')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
39
46
|
```
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
The initial render is already flushed, so the DOM and event listeners are ready when `render`
|
|
49
|
+
returns. Wrap interactions that may update component state in `act` and always await it. `act`
|
|
50
|
+
waits for the callback and then flushes pending component updates before the next assertion.
|
|
51
|
+
|
|
52
|
+
### Testing Async Operations
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
When an interaction starts an async operation, await that operation inside `act` so updates queued
|
|
55
|
+
after it resolves are flushed before making assertions:
|
|
44
56
|
|
|
45
57
|
```tsx
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
root.flush() // Event listeners now attached
|
|
58
|
+
function UserLoader(handle: Handle<{ loadUser(): Promise<{ name: string }> }>) {
|
|
59
|
+
let name = 'No user loaded'
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
return () => (
|
|
62
|
+
<div>
|
|
63
|
+
<button
|
|
64
|
+
mix={[
|
|
65
|
+
on('click', async () => {
|
|
66
|
+
let user = await handle.props.loadUser()
|
|
67
|
+
name = user.name
|
|
68
|
+
handle.update()
|
|
69
|
+
}),
|
|
70
|
+
]}
|
|
71
|
+
>
|
|
72
|
+
Load user
|
|
73
|
+
</button>
|
|
74
|
+
<output>{name}</output>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
it('loads a user', async (t) => {
|
|
80
|
+
let userPromise = Promise.resolve({ name: 'Ada' })
|
|
81
|
+
let { $, act, cleanup } = render(<UserLoader loadUser={() => userPromise} />)
|
|
82
|
+
t.after(cleanup)
|
|
83
|
+
|
|
84
|
+
await act(async () => {
|
|
85
|
+
$('button')?.click()
|
|
86
|
+
await userPromise
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect($('output')?.textContent).toBe('Ada')
|
|
90
|
+
})
|
|
52
91
|
```
|
|
53
92
|
|
|
54
|
-
|
|
93
|
+
### Querying Rendered Output
|
|
55
94
|
|
|
56
|
-
|
|
95
|
+
The returned query helpers are scoped to the rendered container so you don't have to worry about false positives outside of the rendered component:
|
|
57
96
|
|
|
58
97
|
```tsx
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (signal.aborted) return
|
|
66
|
-
data = json.value
|
|
67
|
-
handle.update()
|
|
68
|
-
})
|
|
98
|
+
let { $, $$, container, cleanup } = render(
|
|
99
|
+
<nav>
|
|
100
|
+
<a href="/one">One</a>
|
|
101
|
+
<a href="/two">Two</a>
|
|
102
|
+
</nav>,
|
|
103
|
+
)
|
|
69
104
|
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
let nav = $('nav') // container.querySelector('nav')
|
|
106
|
+
let links = $$('a') // container.querySelectorAll('a')
|
|
72
107
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
root.flush()
|
|
108
|
+
expect(nav).toBeTruthy()
|
|
109
|
+
expect(links).toHaveLength(2)
|
|
110
|
+
expect(container.textContent).toContain('One')
|
|
77
111
|
|
|
78
|
-
|
|
112
|
+
cleanup()
|
|
113
|
+
```
|
|
79
114
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
root.flush()
|
|
115
|
+
Use `$` for the first matching element, `$$` for all matching elements, and `container` when a
|
|
116
|
+
DOM assertion does not fit a selector.
|
|
83
117
|
|
|
84
|
-
|
|
118
|
+
### Cleaning Up
|
|
119
|
+
|
|
120
|
+
Register `cleanup` with the test context so it always runs, including when an assertion fails:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
it('renders a dialog', (t) => {
|
|
124
|
+
let { $, cleanup } = render(<Dialog />)
|
|
125
|
+
t.after(cleanup)
|
|
126
|
+
|
|
127
|
+
expect($('[role="dialog"]')).toBeTruthy()
|
|
128
|
+
})
|
|
85
129
|
```
|
|
86
130
|
|
|
87
|
-
|
|
131
|
+
`cleanup` disposes the root and removes its container from the document. You can call it directly
|
|
132
|
+
when a test needs to assert cleanup behavior.
|
|
133
|
+
|
|
134
|
+
### Rendering Into an Existing Container
|
|
88
135
|
|
|
89
|
-
|
|
136
|
+
Pass a container when the test needs a specific DOM context:
|
|
90
137
|
|
|
91
138
|
```tsx
|
|
139
|
+
let container = document.createElement('section')
|
|
140
|
+
document.body.appendChild(container)
|
|
141
|
+
|
|
142
|
+
let result = render(<MyComponent />, { container })
|
|
143
|
+
|
|
144
|
+
expect(result.container).toBe(container)
|
|
145
|
+
result.cleanup()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Advanced: Flushing Manually
|
|
149
|
+
|
|
150
|
+
Most component tests should use `render` and `act`. For lower-level runtime tests that need direct
|
|
151
|
+
control over rendering or scheduling, use the returned `root` and call `root.flush()` after an
|
|
152
|
+
operation that queues work:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
let { root, container, cleanup } = render(<MyComponent value="first" />)
|
|
156
|
+
|
|
157
|
+
root.render(<MyComponent value="second" />)
|
|
158
|
+
root.flush()
|
|
159
|
+
|
|
160
|
+
expect(container.textContent).toContain('second')
|
|
161
|
+
cleanup()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
You can also create and manage a root directly:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { createRoot } from 'remix/ui'
|
|
168
|
+
|
|
169
|
+
let container = document.createElement('div')
|
|
92
170
|
let root = createRoot(container)
|
|
171
|
+
|
|
93
172
|
root.render(<MyComponent />)
|
|
94
|
-
root.flush()
|
|
173
|
+
root.flush() // Complete the initial render and attach event listeners
|
|
95
174
|
|
|
96
|
-
|
|
97
|
-
|
|
175
|
+
container.querySelector('button')?.click()
|
|
176
|
+
root.flush() // Apply updates queued by the interaction
|
|
98
177
|
|
|
99
|
-
// Remove and verify cleanup
|
|
100
178
|
root.dispose()
|
|
101
|
-
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Manual flushing is also useful after an async operation resolves outside an `act` callback:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
root.render(<AsyncLoader />)
|
|
185
|
+
root.flush()
|
|
186
|
+
|
|
187
|
+
expect(container.textContent).toBe('Loading...')
|
|
188
|
+
|
|
189
|
+
await waitForFetch()
|
|
190
|
+
root.flush()
|
|
191
|
+
|
|
192
|
+
expect(container.textContent).toBe('Expected data')
|
|
102
193
|
```
|
|
103
194
|
|
|
104
195
|
## See Also
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# toggle
|
|
2
|
+
|
|
3
|
+
`toggle` is a style mixin for checkbox inputs rendered as switches. Use native input props for normal form behavior, and compose it with `remix/ui/toggle/primitives` when you need the headless toggle state helpers or custom hosts.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import toggle from 'remix/ui/toggle'
|
|
9
|
+
|
|
10
|
+
function NotificationSetting() {
|
|
11
|
+
return () => (
|
|
12
|
+
<label>
|
|
13
|
+
<input mix={toggle({ size: 'lg' })} defaultChecked />
|
|
14
|
+
Email notifications
|
|
15
|
+
</label>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Primitive Usage
|
|
21
|
+
|
|
22
|
+
Use the lower-level primitive when app code needs normalized checked state, toggle change events, or a non-input host:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import toggle from 'remix/ui/toggle'
|
|
26
|
+
import * as togglePrimitive from 'remix/ui/toggle/primitives'
|
|
27
|
+
|
|
28
|
+
function CustomToggle() {
|
|
29
|
+
return () => (
|
|
30
|
+
<button
|
|
31
|
+
aria-label="Notifications"
|
|
32
|
+
mix={[...toggle(), togglePrimitive.control({ defaultChecked: true })]}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## `remix/ui/toggle`
|
|
39
|
+
|
|
40
|
+
- `toggle(options)`: styles a native checkbox input as a switch and supplies `type="checkbox"` plus `role="switch"` when the host is an `<input>` without an explicit non-checkbox `type`. Supports `size: 'md' | 'lg'` and defaults to `'md'`.
|
|
41
|
+
- `onToggleChange(handler)`: re-exported primitive event mixin.
|
|
42
|
+
- `ToggleChangeEvent`: re-exported primitive event class.
|
|
43
|
+
- `ToggleSize` and `ToggleOptions`: public TypeScript types for the style mixin.
|
|
44
|
+
|
|
45
|
+
## `remix/ui/toggle/primitives`
|
|
46
|
+
|
|
47
|
+
- `control(options)`: wires a boolean switch control with checked state, ARIA, keyboard, and native input behavior.
|
|
48
|
+
- `onToggleChange(handler)`: event mixin for bubbling toggle changes.
|
|
49
|
+
- `ToggleChangeEvent`: event class dispatched when a toggle changes.
|
|
50
|
+
- `ToggleControlOptions`: primitive options for controlled and uncontrolled state.
|
|
51
|
+
|
|
52
|
+
## Behavior Notes
|
|
53
|
+
|
|
54
|
+
- Checked styles apply through `:checked`, `[aria-checked="true"]`, or `[data-state="checked"]`.
|
|
55
|
+
- Native checkbox switches use the native `checked` attribute for state. Custom hosts use `role="switch"` and `aria-checked="true"` or `aria-checked="false"`.
|
|
56
|
+
- Every switch needs an accessible name, usually from visible label text, `aria-label`, or `aria-labelledby`.
|