@remix-run/ui 0.1.2 → 0.3.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 +9 -7
- package/dist/animation/demos/animation.demo.d.ts +5 -0
- package/dist/animation/demos/animation.demo.js +92 -0
- package/dist/animation/demos/animation.demo.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 +50 -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/spring.demo.d.ts +6 -0
- package/dist/animation/demos/spring.demo.js +304 -0
- package/dist/animation/demos/spring.demo.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 +24 -31
- package/dist/animation/spring.js +31 -8
- package/dist/animation/spring.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +6 -6
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/accordion/demos/card.demo.d.ts +1 -0
- package/dist/components/accordion/demos/card.demo.js +1 -0
- package/dist/components/accordion/demos/card.demo.js.map +1 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +1 -0
- package/dist/components/accordion/demos/multiple.demo.js +1 -0
- package/dist/components/accordion/demos/multiple.demo.js.map +1 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +1 -0
- package/dist/components/accordion/demos/overview.demo.js +1 -0
- package/dist/components/accordion/demos/overview.demo.js.map +1 -1
- package/dist/components/anchor/anchor.d.ts +8 -1
- package/dist/components/anchor/anchor.js +30 -11
- package/dist/components/anchor/anchor.js.map +1 -1
- package/dist/components/breadcrumbs/{demos/basic.demo.d.ts → basic.demo.d.ts} +1 -0
- package/dist/components/breadcrumbs/{demos/basic.demo.js → basic.demo.js} +1 -0
- package/dist/components/breadcrumbs/basic.demo.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
- package/dist/components/breadcrumbs/{demos/separator.demo.d.ts → separator.demo.d.ts} +1 -0
- package/dist/components/breadcrumbs/{demos/separator.demo.js → separator.demo.js} +1 -0
- package/dist/components/breadcrumbs/separator.demo.js.map +1 -0
- package/dist/components/button/button.d.ts +4 -4
- package/dist/components/button/button.js +3 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/demos/aliases.demo.d.ts +1 -0
- package/dist/components/button/demos/aliases.demo.js +1 -0
- package/dist/components/button/demos/aliases.demo.js.map +1 -1
- package/dist/components/button/demos/basic.demo.d.ts +1 -0
- package/dist/components/button/demos/basic.demo.js +1 -0
- package/dist/components/button/demos/basic.demo.js.map +1 -1
- package/dist/components/button/demos/states.demo.d.ts +1 -0
- package/dist/components/button/demos/states.demo.js +1 -0
- package/dist/components/button/demos/states.demo.js.map +1 -1
- package/dist/components/combobox/combobox.d.ts +10 -10
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/combobox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/combobox/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/combobox/overview.demo.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +3 -3
- package/dist/components/listbox/listbox.js.map +1 -1
- package/dist/components/listbox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/listbox/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/listbox/overview.demo.js.map +1 -0
- package/dist/components/menu/demos/bubbling.demo.d.ts +1 -0
- package/dist/components/menu/demos/bubbling.demo.js +1 -0
- package/dist/components/menu/demos/bubbling.demo.js.map +1 -1
- package/dist/components/menu/demos/context-trigger.demo.d.ts +7 -0
- package/dist/components/menu/demos/context-trigger.demo.js +87 -0
- package/dist/components/menu/demos/context-trigger.demo.js.map +1 -0
- package/dist/components/menu/demos/overview.demo.d.ts +1 -0
- package/dist/components/menu/demos/overview.demo.js +1 -0
- package/dist/components/menu/demos/overview.demo.js.map +1 -1
- package/dist/components/menu/menu.d.ts +15 -12
- package/dist/components/menu/menu.js +55 -0
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/popover/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/popover/overview.demo.js.map +1 -0
- package/dist/components/popover/popover.d.ts +7 -7
- package/dist/components/popover/popover.js +11 -4
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/select/{demos/deconstructed.demo.d.ts → deconstructed.demo.d.ts} +1 -0
- package/dist/components/select/{demos/deconstructed.demo.js → deconstructed.demo.js} +1 -0
- package/dist/components/select/deconstructed.demo.js.map +1 -0
- package/dist/components/select/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/select/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/select/overview.demo.js.map +1 -0
- package/dist/components/select/select.d.ts +10 -10
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactions/keydown/keydown.d.ts +1 -1
- package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -1
- package/dist/interactions/typeahead/typeahead-mixin.d.ts +3 -1
- package/dist/runtime/client-entries.d.ts +1 -2
- package/dist/runtime/client-entries.js.map +1 -1
- package/dist/runtime/component.d.ts +20 -17
- package/dist/runtime/component.js +15 -6
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.d.ts +3 -1
- package/dist/runtime/core/attributes.js +19 -1
- package/dist/runtime/core/attributes.js.map +1 -1
- package/dist/runtime/core/props.js +6 -5
- package/dist/runtime/core/props.js.map +1 -1
- package/dist/runtime/demos/basic.demo.d.ts +6 -0
- package/dist/runtime/demos/basic.demo.js +16 -0
- package/dist/runtime/demos/basic.demo.js.map +1 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +6 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +77 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +1 -0
- package/dist/runtime/demos/keyed-list.demo.d.ts +6 -0
- package/dist/runtime/demos/keyed-list.demo.js +62 -0
- package/dist/runtime/demos/keyed-list.demo.js.map +1 -0
- package/dist/runtime/demos/readme.demo.d.ts +5 -0
- package/dist/runtime/demos/readme.demo.js +396 -0
- package/dist/runtime/demos/readme.demo.js.map +1 -0
- package/dist/runtime/diff-dom.js +194 -21
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/dom.d.ts +16 -9
- 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 +3 -4
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/mixins/link-mixin.d.ts +5 -1
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +2 -1
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/ref-mixin.d.ts +2 -1
- package/dist/runtime/mixins/ref-mixin.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +1 -1
- package/dist/runtime/reconcile.js +19 -5
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/scheduler.d.ts +10 -10
- package/dist/runtime/scheduler.js +1 -1
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/svg-attributes.js +1 -0
- package/dist/runtime/svg-attributes.js.map +1 -1
- package/dist/runtime/to-vnode.js +2 -2
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vnode.d.ts +6 -1
- package/dist/runtime/vnode.js +4 -0
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +82 -11
- package/dist/server/stream.js.map +1 -1
- package/dist/style/css-mixin.d.ts +2 -1
- package/dist/style/css-mixin.js.map +1 -1
- package/dist/style/index.d.ts +1 -1
- package/dist/style/style.js +27 -10
- package/dist/style/style.js.map +1 -1
- package/dist/style/stylesheet.d.ts +13 -13
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/theme/contract.d.ts +24 -222
- package/dist/theme/contract.js +2 -2
- package/dist/theme/contract.js.map +1 -1
- package/dist/theme/glyph-contract.d.ts +1 -3
- package/dist/theme/glyph-contract.js.map +1 -1
- package/dist/theme/presets/rmx-01/index.d.ts +4 -2
- package/dist/theme/presets/rmx-01/index.js.map +1 -1
- package/dist/utils/scroll-lock.d.ts +3 -3
- package/dist/utils/scroll-lock.js.map +1 -1
- package/package.json +4 -4
- package/src/animation/README.md +7 -5
- package/src/animation/demos/animation.demo.tsx +194 -0
- 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 +65 -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/spring.demo.tsx +385 -0
- package/src/animation/demos/transition-options.tsx +24 -0
- package/src/animation/spring.ts +43 -9
- package/src/components/accordion/accordion.tsx +7 -5
- package/src/components/accordion/demos/card.demo.tsx +1 -0
- package/src/components/accordion/demos/multiple.demo.tsx +1 -0
- package/src/components/accordion/demos/overview.demo.tsx +1 -0
- package/src/components/anchor/README.md +11 -3
- package/src/components/anchor/anchor.ts +54 -13
- package/src/components/breadcrumbs/{demos/basic.demo.tsx → basic.demo.tsx} +1 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +1 -1
- package/src/components/breadcrumbs/{demos/separator.demo.tsx → separator.demo.tsx} +1 -0
- package/src/components/button/button.tsx +30 -18
- package/src/components/button/demos/aliases.demo.tsx +1 -0
- package/src/components/button/demos/basic.demo.tsx +1 -0
- package/src/components/button/demos/states.demo.tsx +1 -0
- package/src/components/combobox/README.md +1 -1
- package/src/components/combobox/combobox.tsx +30 -12
- package/src/components/combobox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/glyph/glyph.tsx +2 -2
- package/src/components/listbox/listbox.ts +88 -83
- package/src/components/listbox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/menu/README.md +26 -2
- package/src/components/menu/demos/bubbling.demo.tsx +1 -0
- package/src/components/menu/demos/context-trigger.demo.tsx +133 -0
- package/src/components/menu/demos/overview.demo.tsx +1 -0
- package/src/components/menu/menu.tsx +100 -16
- package/src/components/popover/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/popover/popover.ts +101 -81
- package/src/components/select/{demos/deconstructed.demo.tsx → deconstructed.demo.tsx} +1 -0
- package/src/components/select/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/select/select.tsx +27 -10
- package/src/index.ts +1 -0
- package/src/runtime/client-entries.ts +1 -5
- package/src/runtime/component.ts +37 -15
- package/src/runtime/core/attributes.ts +21 -0
- package/src/runtime/core/props.ts +6 -4
- package/src/runtime/demos/basic.demo.tsx +21 -0
- package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +177 -0
- package/src/runtime/demos/keyed-list.demo.tsx +94 -0
- package/src/runtime/demos/readme.demo.tsx +702 -0
- package/src/runtime/diff-dom.ts +270 -20
- package/src/runtime/dom.ts +16 -9
- package/src/runtime/frame.ts +218 -41
- package/src/runtime/jsx.ts +7 -11
- package/src/runtime/mixins/link-mixin.ts +88 -86
- package/src/runtime/mixins/mixin.ts +9 -1
- package/src/runtime/mixins/ref-mixin.ts +4 -5
- package/src/runtime/reconcile.ts +23 -14
- package/src/runtime/scheduler.ts +20 -20
- package/src/runtime/svg-attributes.ts +1 -0
- package/src/runtime/to-vnode.ts +2 -2
- package/src/runtime/vnode.ts +10 -0
- package/src/server/README.md +4 -2
- package/src/server/stream.ts +84 -10
- package/src/style/css-mixin.ts +6 -1
- package/src/style/index.ts +1 -2
- package/src/style/style.ts +29 -10
- package/src/style/stylesheet.ts +14 -2
- package/src/theme/contract.ts +37 -5
- package/src/theme/glyph-contract.ts +3 -1
- package/src/theme/presets/rmx-01/index.ts +4 -2
- package/src/utils/scroll-lock.ts +7 -3
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
- package/dist/components/combobox/demos/overview.demo.js.map +0 -1
- package/dist/components/listbox/demos/overview.demo.js.map +0 -1
- package/dist/components/popover/demos/overview.demo.js.map +0 -1
- package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
- package/dist/components/select/demos/overview.demo.js.map +0 -1
|
@@ -4,10 +4,16 @@ import {
|
|
|
4
4
|
css,
|
|
5
5
|
on,
|
|
6
6
|
type CSSMixinDescriptor,
|
|
7
|
+
type ElementProps,
|
|
7
8
|
type Handle,
|
|
9
|
+
type MixinFactory,
|
|
8
10
|
type RemixNode,
|
|
9
11
|
} from '@remix-run/ui'
|
|
10
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
anchor as positionAnchor,
|
|
14
|
+
type AnchorOptions,
|
|
15
|
+
type AnchorTarget,
|
|
16
|
+
} from '../anchor/anchor.ts'
|
|
11
17
|
import { onOutsideClick } from '../../interactions/outside-click/outside-click-mixin.ts'
|
|
12
18
|
import { theme } from '../../theme/theme.ts'
|
|
13
19
|
import { lockScroll } from '../../utils/scroll-lock.ts'
|
|
@@ -24,7 +30,7 @@ export interface PopoverProps {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
interface AnchorRef {
|
|
27
|
-
|
|
33
|
+
target: AnchorTarget
|
|
28
34
|
options: AnchorOptions
|
|
29
35
|
}
|
|
30
36
|
|
|
@@ -75,7 +81,7 @@ const popoverSurfaceCss: CSSMixinDescriptor = css({
|
|
|
75
81
|
export const contentStyle = popoverContentCss
|
|
76
82
|
export const surfaceStyle = [popoverSurfaceCss, popoverSurfaceTransitionCss] as const
|
|
77
83
|
|
|
78
|
-
function PopoverProvider(handle: Handle<PopoverProps, PopoverContext>) {
|
|
84
|
+
function PopoverProvider(handle: Handle<PopoverProps, PopoverContext>): () => RemixNode {
|
|
79
85
|
handle.context.set({
|
|
80
86
|
hideFocusTarget: null,
|
|
81
87
|
showFocusTarget: null,
|
|
@@ -99,100 +105,114 @@ export interface PopoverHideRequest {
|
|
|
99
105
|
target?: Node | null
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
const anchorMixin
|
|
108
|
+
const anchorMixin: MixinFactory<HTMLElement, [options: AnchorOptions], ElementProps> = createMixin<
|
|
109
|
+
HTMLElement,
|
|
110
|
+
[options: AnchorOptions]
|
|
111
|
+
>((handle) => {
|
|
103
112
|
let context = handle.context.get(PopoverProvider)
|
|
104
113
|
return (options) => {
|
|
105
114
|
handle.queueTask((node) => {
|
|
106
|
-
context.anchor = { node, options }
|
|
115
|
+
context.anchor = { target: node, options }
|
|
107
116
|
})
|
|
108
117
|
}
|
|
109
118
|
})
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let unlockScroll = () => {}
|
|
115
|
-
let context = handle.context.get(PopoverProvider)
|
|
116
|
-
|
|
117
|
-
handle.queueTask((node, signal) => {
|
|
118
|
-
context.surface = node
|
|
120
|
+
function anchorContains(anchor: AnchorRef | null, target: Node) {
|
|
121
|
+
return anchor?.target instanceof HTMLElement && anchor.target.contains(target)
|
|
122
|
+
}
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
124
|
+
const surfaceMixin: MixinFactory<HTMLElement, [options: PopoverSurfaceOptions], ElementProps> =
|
|
125
|
+
createMixin<HTMLElement, [options: PopoverSurfaceOptions]>((handle) => {
|
|
126
|
+
let openProp = false
|
|
127
|
+
let cleanupAnchor = () => {}
|
|
128
|
+
let unlockScroll = () => {}
|
|
129
|
+
let context = handle.context.get(PopoverProvider)
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
openProp = options.open
|
|
130
|
-
|
|
131
|
-
handle.queueTask(async (node) => {
|
|
132
|
-
if (openProp && !wasOpen) {
|
|
133
|
-
node.showPopover()
|
|
134
|
-
} else if (!openProp && wasOpen) {
|
|
135
|
-
node.hidePopover()
|
|
136
|
-
}
|
|
137
|
-
})
|
|
131
|
+
handle.queueTask((node, signal) => {
|
|
132
|
+
context.surface = node
|
|
138
133
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
on('beforetoggle', (event) => {
|
|
143
|
-
if (event.newState === 'open') {
|
|
144
|
-
cleanupAnchor = positionAnchor(
|
|
145
|
-
event.currentTarget,
|
|
146
|
-
context.anchor!.node,
|
|
147
|
-
context.anchor!.options,
|
|
148
|
-
)
|
|
149
|
-
unlockScroll = lockScroll()
|
|
150
|
-
} else if (event.newState === 'closed') {
|
|
151
|
-
cleanupAnchor()
|
|
152
|
-
unlockScroll()
|
|
134
|
+
signal.addEventListener('abort', () => {
|
|
135
|
+
if (context.surface === node) {
|
|
136
|
+
context.surface = null
|
|
153
137
|
}
|
|
154
|
-
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
155
140
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} else if (event.newState === 'closed' && options.restoreFocusOnHide !== false) {
|
|
160
|
-
context.hideFocusTarget?.focus()
|
|
161
|
-
}
|
|
162
|
-
}),
|
|
141
|
+
return (options) => {
|
|
142
|
+
let wasOpen = openProp
|
|
143
|
+
openProp = options.open
|
|
163
144
|
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
145
|
+
handle.queueTask(async (node) => {
|
|
146
|
+
if (openProp && !wasOpen) {
|
|
147
|
+
node.showPopover()
|
|
148
|
+
} else if (!openProp && wasOpen) {
|
|
149
|
+
node.hidePopover()
|
|
168
150
|
}
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return [
|
|
154
|
+
attrs({ popover: 'manual' }),
|
|
155
|
+
|
|
156
|
+
on('beforetoggle', (event) => {
|
|
157
|
+
if (event.newState === 'open') {
|
|
158
|
+
let anchor = context.anchor
|
|
159
|
+
if (!anchor) {
|
|
160
|
+
throw new Error('popover.surface() requires a registered anchor before opening')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
cleanupAnchor = positionAnchor(event.currentTarget, anchor.target, anchor.options)
|
|
164
|
+
unlockScroll = lockScroll()
|
|
165
|
+
} else if (event.newState === 'closed') {
|
|
166
|
+
cleanupAnchor()
|
|
167
|
+
unlockScroll()
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
|
|
171
|
+
on('toggle', async (event) => {
|
|
172
|
+
if (event.newState === 'open') {
|
|
173
|
+
context.showFocusTarget?.focus()
|
|
174
|
+
} else if (event.newState === 'closed' && options.restoreFocusOnHide !== false) {
|
|
175
|
+
context.hideFocusTarget?.focus()
|
|
176
|
+
}
|
|
177
|
+
}),
|
|
178
|
+
|
|
179
|
+
on('keydown', (event) => {
|
|
180
|
+
if (event.key === 'Escape') {
|
|
181
|
+
event.preventDefault()
|
|
182
|
+
options.onHide({ reason: 'escape-key' })
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
onOutsideClick(
|
|
187
|
+
openProp,
|
|
188
|
+
(target) => {
|
|
189
|
+
options.onHide({ reason: 'outside-click', target })
|
|
190
|
+
},
|
|
191
|
+
(target) =>
|
|
192
|
+
options.closeOnAnchorClick === false && anchorContains(context.anchor, target),
|
|
193
|
+
options.stopOutsideClickPropagation ?? true,
|
|
194
|
+
),
|
|
195
|
+
]
|
|
196
|
+
}
|
|
187
197
|
})
|
|
188
|
-
})
|
|
189
198
|
|
|
190
|
-
const
|
|
191
|
-
handle
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
})
|
|
199
|
+
const focusOnShowMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<HTMLElement, []>(
|
|
200
|
+
(handle) => {
|
|
201
|
+
handle.addEventListener('insert', (event) => {
|
|
202
|
+
let context = handle.context.get(PopoverProvider)
|
|
203
|
+
context.showFocusTarget = event.node
|
|
204
|
+
})
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
const focusOnHideMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<HTMLElement, []>(
|
|
209
|
+
(handle) => {
|
|
210
|
+
handle.addEventListener('insert', (event) => {
|
|
211
|
+
let context = handle.context.get(PopoverProvider)
|
|
212
|
+
context.hideFocusTarget = event.node
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
)
|
|
196
216
|
|
|
197
217
|
export const Context = PopoverProvider
|
|
198
218
|
export const anchor = anchorMixin
|
|
@@ -9,6 +9,7 @@ import { theme } from '@remix-run/ui/theme'
|
|
|
9
9
|
/**
|
|
10
10
|
* @name Select Deconstructed
|
|
11
11
|
* @description Build a fully custom select by composing the select, popover, and listbox primitives directly.
|
|
12
|
+
* @layout center
|
|
12
13
|
*/
|
|
13
14
|
export default function Example(handle: Handle) {
|
|
14
15
|
let label = 'Local'
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ElementProps,
|
|
11
11
|
type Handle,
|
|
12
12
|
type MixinHandle,
|
|
13
|
+
type MixinFactory,
|
|
13
14
|
type Props,
|
|
14
15
|
type RemixNode,
|
|
15
16
|
} from '@remix-run/ui'
|
|
@@ -106,7 +107,7 @@ interface SelectContextValue {
|
|
|
106
107
|
unregisterTrigger: (node: HTMLButtonElement) => void
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>) {
|
|
110
|
+
function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>): () => RemixNode {
|
|
110
111
|
let triggerRef: HTMLButtonElement | undefined
|
|
111
112
|
let listboxRef: listbox.ListboxRef | undefined
|
|
112
113
|
let surfaceRef: HTMLElement | undefined
|
|
@@ -155,7 +156,7 @@ function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>)
|
|
|
155
156
|
popoverContextRef.hideFocusTarget = triggerRef ?? null
|
|
156
157
|
popoverContextRef.anchor = triggerRef
|
|
157
158
|
? {
|
|
158
|
-
|
|
159
|
+
target: triggerRef,
|
|
159
160
|
options: getPopoverAnchorOptions(),
|
|
160
161
|
}
|
|
161
162
|
: null
|
|
@@ -396,7 +397,11 @@ function getSelectContext(handle: Handle | MixinHandle) {
|
|
|
396
397
|
return handle.context.get(SelectProvider)
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
const triggerMixin
|
|
400
|
+
const triggerMixin: MixinFactory<HTMLButtonElement, [], ElementProps> = createMixin<
|
|
401
|
+
HTMLButtonElement,
|
|
402
|
+
[],
|
|
403
|
+
ElementProps
|
|
404
|
+
>((handle) => {
|
|
400
405
|
let context = getSelectContext(handle)
|
|
401
406
|
|
|
402
407
|
return (props) => [
|
|
@@ -428,7 +433,11 @@ const triggerMixin = createMixin<HTMLButtonElement, [], ElementProps>((handle) =
|
|
|
428
433
|
]
|
|
429
434
|
})
|
|
430
435
|
|
|
431
|
-
const popoverMixin
|
|
436
|
+
const popoverMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
437
|
+
HTMLElement,
|
|
438
|
+
[],
|
|
439
|
+
ElementProps
|
|
440
|
+
>((handle) => {
|
|
432
441
|
let context = getSelectContext(handle)
|
|
433
442
|
let popoverState = handle.context.get(popover.Context)
|
|
434
443
|
|
|
@@ -453,7 +462,11 @@ const popoverMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
453
462
|
]
|
|
454
463
|
})
|
|
455
464
|
|
|
456
|
-
const listMixin
|
|
465
|
+
const listMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
466
|
+
HTMLElement,
|
|
467
|
+
[],
|
|
468
|
+
ElementProps
|
|
469
|
+
>((handle) => {
|
|
457
470
|
let context = getSelectContext(handle)
|
|
458
471
|
|
|
459
472
|
return () => [
|
|
@@ -466,7 +479,11 @@ const listMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
466
479
|
]
|
|
467
480
|
})
|
|
468
481
|
|
|
469
|
-
const hiddenInputMixin
|
|
482
|
+
const hiddenInputMixin: MixinFactory<HTMLInputElement, [], ElementProps> = createMixin<
|
|
483
|
+
HTMLInputElement,
|
|
484
|
+
[],
|
|
485
|
+
ElementProps
|
|
486
|
+
>((handle) => {
|
|
470
487
|
let context = getSelectContext(handle)
|
|
471
488
|
|
|
472
489
|
return () =>
|
|
@@ -532,17 +549,17 @@ const select = {
|
|
|
532
549
|
export function onSelectChange<target extends HTMLElement>(
|
|
533
550
|
handler: SelectChangeHandler<target>,
|
|
534
551
|
captureBoolean?: boolean,
|
|
535
|
-
) {
|
|
552
|
+
): ReturnType<typeof on<target, typeof SELECT_CHANGE_EVENT>> {
|
|
536
553
|
return on(SELECT_CHANGE_EVENT, handler, captureBoolean)
|
|
537
554
|
}
|
|
538
555
|
|
|
539
|
-
function SelectLabel(handle: Handle) {
|
|
556
|
+
function SelectLabel(handle: Handle): () => RemixNode {
|
|
540
557
|
let context = getSelectContext(handle)
|
|
541
558
|
|
|
542
559
|
return () => <span mix={button.labelStyle}>{context.displayedLabel}</span>
|
|
543
560
|
}
|
|
544
561
|
|
|
545
|
-
export function Select(handle: Handle<SelectProps>) {
|
|
562
|
+
export function Select(handle: Handle<SelectProps>): () => RemixNode {
|
|
546
563
|
return () => {
|
|
547
564
|
let { children, defaultLabel, defaultValue, disabled, name, mix, ...buttonProps } = handle.props
|
|
548
565
|
|
|
@@ -568,7 +585,7 @@ export function Select(handle: Handle<SelectProps>) {
|
|
|
568
585
|
}
|
|
569
586
|
}
|
|
570
587
|
|
|
571
|
-
export function Option(handle: Handle<SelectOptionProps>) {
|
|
588
|
+
export function Option(handle: Handle<SelectOptionProps>): () => RemixNode {
|
|
572
589
|
return () => {
|
|
573
590
|
let { label, value, disabled, textValue, children, mix, ...divProps } = handle.props
|
|
574
591
|
|
package/src/index.ts
CHANGED
|
@@ -47,7 +47,7 @@ export type EntryMetadata = {
|
|
|
47
47
|
*/
|
|
48
48
|
export type EntryComponent<props extends SerializableProps = {}, context = NoContext> = ((
|
|
49
49
|
handle: Handle<props, context>,
|
|
50
|
-
) => RenderFn
|
|
50
|
+
) => RenderFn) &
|
|
51
51
|
EntryMetadata
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -86,10 +86,6 @@ export function clientEntry<props extends SerializableProps = {}, context = NoCo
|
|
|
86
86
|
entryId: string,
|
|
87
87
|
component: (handle: Handle<props, context>) => RenderFn,
|
|
88
88
|
): EntryComponent<props, context>
|
|
89
|
-
export function clientEntry<props extends SerializableProps = {}, context = NoContext>(
|
|
90
|
-
entryId: string,
|
|
91
|
-
component: (handle: Handle<Record<string, never>, context>) => RenderFn<props>,
|
|
92
|
-
): EntryComponent<props, context>
|
|
93
89
|
|
|
94
90
|
// Implementation
|
|
95
91
|
export function clientEntry(entryId: string, component: any): any {
|
package/src/runtime/component.ts
CHANGED
|
@@ -118,13 +118,18 @@ export type ContextFrom<ComponentType> =
|
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
120
|
* Context storage API exposed on component handles.
|
|
121
|
+
*
|
|
122
|
+
* Context values are keyed by provider component identity. `get(Component)`
|
|
123
|
+
* reads the nearest ancestor instance whose component function is exactly
|
|
124
|
+
* `Component`, so nested instances of the same provider shadow outer instances
|
|
125
|
+
* while different component types remain independent.
|
|
121
126
|
*/
|
|
122
127
|
export interface Context<C> {
|
|
123
128
|
/** Replaces the current context value for this component instance. */
|
|
124
129
|
set(values: C): void
|
|
125
|
-
/** Reads the context value
|
|
130
|
+
/** Reads the context value from the nearest ancestor instance of the given component type. */
|
|
126
131
|
get<ComponentType>(component: ComponentType): ContextFrom<ComponentType>
|
|
127
|
-
/** Reads
|
|
132
|
+
/** Reads an unknown context value for an untyped lookup. */
|
|
128
133
|
get(component: ElementType | symbol): unknown | undefined
|
|
129
134
|
}
|
|
130
135
|
|
|
@@ -174,9 +179,9 @@ export type ComponentFn<Props = Record<string, never>, ContextValue = NoContext>
|
|
|
174
179
|
) => RenderFn
|
|
175
180
|
|
|
176
181
|
/**
|
|
177
|
-
*
|
|
182
|
+
* Zero-argument render function returned by a component factory.
|
|
178
183
|
*/
|
|
179
|
-
export type RenderFn
|
|
184
|
+
export type RenderFn = () => RemixNode
|
|
180
185
|
|
|
181
186
|
export type { RemixNode } from './jsx.ts'
|
|
182
187
|
|
|
@@ -218,7 +223,14 @@ type ComponentConfig = {
|
|
|
218
223
|
/**
|
|
219
224
|
* Runtime handle returned by {@link createComponent}.
|
|
220
225
|
*/
|
|
221
|
-
export
|
|
226
|
+
export interface ComponentHandle<C = NoContext> {
|
|
227
|
+
frame: FrameHandle
|
|
228
|
+
render(nextProps: ElementProps): [RemixNode, Array<() => void>]
|
|
229
|
+
remove(): Array<() => void>
|
|
230
|
+
setScheduleUpdate(nextScheduleUpdate: () => void): void
|
|
231
|
+
getContextValue(): C | undefined
|
|
232
|
+
isRemoved(): boolean
|
|
233
|
+
}
|
|
222
234
|
|
|
223
235
|
/**
|
|
224
236
|
* Creates the internal runtime wrapper for a component instance.
|
|
@@ -226,11 +238,11 @@ export type ComponentHandle = ReturnType<typeof createComponent>
|
|
|
226
238
|
* @param config Component runtime configuration.
|
|
227
239
|
* @returns Component runtime helpers used by the reconciler.
|
|
228
240
|
*/
|
|
229
|
-
export function createComponent<C = NoContext>(config: ComponentConfig) {
|
|
241
|
+
export function createComponent<C = NoContext>(config: ComponentConfig): ComponentHandle<C> {
|
|
230
242
|
return new ComponentRuntime<C>(config)
|
|
231
243
|
}
|
|
232
244
|
|
|
233
|
-
class ComponentRuntime<C = NoContext> {
|
|
245
|
+
class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
|
|
234
246
|
frame: FrameHandle
|
|
235
247
|
|
|
236
248
|
#config: ComponentConfig
|
|
@@ -240,6 +252,7 @@ class ComponentRuntime<C = NoContext> {
|
|
|
240
252
|
#props = {} as ElementProps
|
|
241
253
|
#renderController: AbortController | undefined
|
|
242
254
|
#renderFn: RenderFn | undefined
|
|
255
|
+
#removed = false
|
|
243
256
|
#scheduleUpdate: () => void = () => {
|
|
244
257
|
throw new Error('scheduleUpdate not implemented')
|
|
245
258
|
}
|
|
@@ -252,7 +265,7 @@ class ComponentRuntime<C = NoContext> {
|
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
render = (nextProps: ElementProps): [RemixNode, Array<() => void>] => {
|
|
255
|
-
if (this.#
|
|
268
|
+
if (this.#removed) {
|
|
256
269
|
console.warn('render called after component was removed, potential application memory leak')
|
|
257
270
|
return [null, []]
|
|
258
271
|
}
|
|
@@ -274,13 +287,15 @@ class ComponentRuntime<C = NoContext> {
|
|
|
274
287
|
this.#renderFn = renderFn
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
return [renderFn(
|
|
290
|
+
return [renderFn(), this.#dequeueTasks()]
|
|
278
291
|
}
|
|
279
292
|
|
|
280
293
|
remove = (): Array<() => void> => {
|
|
294
|
+
if (this.#removed) return []
|
|
295
|
+
this.#removed = true
|
|
281
296
|
this.#connectedController?.abort()
|
|
282
297
|
this.#abortRenderSignal()
|
|
283
|
-
return this.#dequeueTasks()
|
|
298
|
+
return this.#dequeueTasks(AbortSignal.abort())
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
setScheduleUpdate = (nextScheduleUpdate: () => void): void => {
|
|
@@ -289,6 +304,8 @@ class ComponentRuntime<C = NoContext> {
|
|
|
289
304
|
|
|
290
305
|
getContextValue = (): C | undefined => this.#contextValue
|
|
291
306
|
|
|
307
|
+
isRemoved = (): boolean => this.#removed
|
|
308
|
+
|
|
292
309
|
#createHandle(): Handle<ElementProps, C> {
|
|
293
310
|
let component = this
|
|
294
311
|
let context: Context<C> = {
|
|
@@ -303,6 +320,11 @@ class ComponentRuntime<C = NoContext> {
|
|
|
303
320
|
props: this.#props,
|
|
304
321
|
update: () =>
|
|
305
322
|
new Promise((resolve) => {
|
|
323
|
+
if (component.#removed) {
|
|
324
|
+
resolve(AbortSignal.abort())
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
306
328
|
this.#tasks.push((signal) => resolve(signal))
|
|
307
329
|
this.#scheduleUpdate()
|
|
308
330
|
}),
|
|
@@ -335,14 +357,14 @@ class ComponentRuntime<C = NoContext> {
|
|
|
335
357
|
this.#renderController = undefined
|
|
336
358
|
}
|
|
337
359
|
|
|
338
|
-
#dequeueTasks(): Array<() => void> {
|
|
339
|
-
let needsSignal = this.#tasks.some((task) => task.length >= 1)
|
|
360
|
+
#dequeueTasks(signal?: AbortSignal): Array<() => void> {
|
|
361
|
+
let needsSignal = signal === undefined && this.#tasks.some((task) => task.length >= 1)
|
|
340
362
|
|
|
341
363
|
if (needsSignal) {
|
|
342
364
|
this.#renderController ??= new AbortController()
|
|
343
365
|
}
|
|
344
366
|
|
|
345
|
-
|
|
367
|
+
signal ??= this.#renderController?.signal
|
|
346
368
|
let tasks = this.#tasks.splice(0, this.#tasks.length)
|
|
347
369
|
return tasks.map((task) => () => task(signal!))
|
|
348
370
|
}
|
|
@@ -366,7 +388,7 @@ function syncProps(target: ElementProps, next: ElementProps): void {
|
|
|
366
388
|
* @param handle Component handle for the frame instance.
|
|
367
389
|
* @returns A placeholder render function handled by the reconciler.
|
|
368
390
|
*/
|
|
369
|
-
export function Frame(handle: Handle<FrameProps, FrameHandle>) {
|
|
391
|
+
export function Frame(handle: Handle<FrameProps, FrameHandle>): RenderFn {
|
|
370
392
|
void handle
|
|
371
393
|
return () => null // reconciler renders
|
|
372
394
|
}
|
|
@@ -377,7 +399,7 @@ export function Frame(handle: Handle<FrameProps, FrameHandle>) {
|
|
|
377
399
|
* @param handle Component handle for the fragment instance.
|
|
378
400
|
* @returns A placeholder render function handled by the reconciler.
|
|
379
401
|
*/
|
|
380
|
-
export function Fragment(handle: Handle<FragmentProps>) {
|
|
402
|
+
export function Fragment(handle: Handle<FragmentProps>): RenderFn {
|
|
381
403
|
void handle
|
|
382
404
|
return () => null // reconciler renders
|
|
383
405
|
}
|
|
@@ -14,6 +14,17 @@ const ATTRIBUTE_FALLBACK_NAMES = new Set([
|
|
|
14
14
|
'colSpan',
|
|
15
15
|
'role',
|
|
16
16
|
'popover',
|
|
17
|
+
'translate',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const BOOLEANISH_STRING_ATTRIBUTES = new Set([
|
|
21
|
+
'autoReverse',
|
|
22
|
+
'contenteditable',
|
|
23
|
+
'draggable',
|
|
24
|
+
'externalResourcesRequired',
|
|
25
|
+
'focusable',
|
|
26
|
+
'preserveAlpha',
|
|
27
|
+
'spellcheck',
|
|
17
28
|
])
|
|
18
29
|
|
|
19
30
|
export const FRAMEWORK_PROPS = new Set(['children', 'mix', 'key', 'animate', 'innerHTML', 'on'])
|
|
@@ -43,9 +54,11 @@ export function canUseProperty(
|
|
|
43
54
|
element: Element,
|
|
44
55
|
name: string,
|
|
45
56
|
isSvg: boolean,
|
|
57
|
+
attr: string,
|
|
46
58
|
): element is Element & Record<string, unknown> {
|
|
47
59
|
if (isSvg) return false
|
|
48
60
|
if (ATTRIBUTE_FALLBACK_NAMES.has(name)) return false
|
|
61
|
+
if (isBooleanishStringAttribute(attr)) return false
|
|
49
62
|
return name in element
|
|
50
63
|
}
|
|
51
64
|
|
|
@@ -68,6 +81,14 @@ export function normalizeAttributeName(
|
|
|
68
81
|
return normalizeSvgAttribute(name)
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
export function isBooleanishStringAttribute(name: string): boolean {
|
|
85
|
+
return BOOLEANISH_STRING_ATTRIBUTES.has(name)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function shouldStringifyBooleanAttribute(name: string): boolean {
|
|
89
|
+
return isBooleanishStringAttribute(name) || name === 'value'
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
export function serializeStyleObject(style: Record<string, unknown>): string {
|
|
72
93
|
let parts: string[] = []
|
|
73
94
|
|
|
@@ -2,6 +2,7 @@ import type { ElementProps } from '../jsx.ts'
|
|
|
2
2
|
import {
|
|
3
3
|
canUseProperty,
|
|
4
4
|
getMergedClassName,
|
|
5
|
+
isBooleanishStringAttribute,
|
|
5
6
|
normalizeAttributeName,
|
|
6
7
|
serializeStyleObject,
|
|
7
8
|
toKebabCase,
|
|
@@ -68,11 +69,11 @@ export function patchHostProps(curr: ElementProps, next: ElementProps, dom: Elem
|
|
|
68
69
|
if (name === 'class' || name === 'className') continue
|
|
69
70
|
if (name in next && next[name] != null) continue
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
let { ns, attr } = normalizeAttributeName(name, isSvg)
|
|
73
|
+
if (canUseProperty(dom, name, isSvg, attr)) {
|
|
72
74
|
clearRuntimePropertyOnRemoval(dom, name)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
let { ns, attr } = normalizeAttributeName(name, isSvg)
|
|
76
77
|
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
77
78
|
else dom.removeAttribute(attr)
|
|
78
79
|
}
|
|
@@ -114,7 +115,7 @@ function patchHostProp(dom: Element, name: string, value: unknown, isSvg: boolea
|
|
|
114
115
|
return
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
if (canUseProperty(dom, name, isSvg)) {
|
|
118
|
+
if (canUseProperty(dom, name, isSvg, attr)) {
|
|
118
119
|
try {
|
|
119
120
|
dom[name] = value == null ? '' : value
|
|
120
121
|
return
|
|
@@ -124,7 +125,8 @@ function patchHostProp(dom: Element, name: string, value: unknown, isSvg: boolea
|
|
|
124
125
|
if (typeof value === 'function') return
|
|
125
126
|
|
|
126
127
|
let isAriaOrData = name.startsWith('aria-') || name.startsWith('data-')
|
|
127
|
-
|
|
128
|
+
let isBooleanishString = isBooleanishStringAttribute(attr)
|
|
129
|
+
if (value != null && (value !== false || isAriaOrData || isBooleanishString)) {
|
|
128
130
|
let attrValue = name === 'popover' && value === true ? '' : String(value)
|
|
129
131
|
if (ns) dom.setAttributeNS(ns, attr, attrValue)
|
|
130
132
|
else dom.setAttribute(attr, attrValue)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { on, type Handle } from '@remix-run/ui'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name Basic Counter
|
|
5
|
+
* @description The smallest interactive Remix UI counter from the standalone demos.
|
|
6
|
+
*/
|
|
7
|
+
export default function App(handle: Handle) {
|
|
8
|
+
let count = 0
|
|
9
|
+
return () => (
|
|
10
|
+
<button
|
|
11
|
+
mix={[
|
|
12
|
+
on('click', () => {
|
|
13
|
+
count++
|
|
14
|
+
handle.update()
|
|
15
|
+
}),
|
|
16
|
+
]}
|
|
17
|
+
>
|
|
18
|
+
Ye ol' counter: {count}
|
|
19
|
+
</button>
|
|
20
|
+
)
|
|
21
|
+
}
|