@remix-run/ui 0.1.1 → 0.2.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 +33 -10
- package/dist/animation/animate-layout-mixin.js +66 -66
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/animation/spring.d.ts +6 -35
- package/dist/animation/spring.js +9 -8
- package/dist/animation/spring.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +7 -7
- package/dist/components/accordion/accordion.js +0 -2
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/accordion/demos/card.demo.d.ts +6 -0
- package/dist/components/accordion/demos/card.demo.js +58 -0
- package/dist/components/accordion/demos/card.demo.js.map +1 -0
- package/dist/components/accordion/demos/multiple.demo.d.ts +6 -0
- package/dist/components/accordion/demos/multiple.demo.js +19 -0
- package/dist/components/accordion/demos/multiple.demo.js.map +1 -0
- package/dist/components/accordion/demos/overview.demo.d.ts +6 -0
- package/dist/components/accordion/demos/overview.demo.js +19 -0
- package/dist/components/accordion/demos/overview.demo.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
- package/dist/components/breadcrumbs/breadcrumbs.js +0 -2
- package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/components/breadcrumbs/demos/basic.demo.d.ts +5 -0
- package/dist/components/breadcrumbs/demos/basic.demo.js +14 -0
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +1 -0
- package/dist/components/breadcrumbs/demos/separator.demo.d.ts +5 -0
- package/dist/components/breadcrumbs/demos/separator.demo.js +14 -0
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +1 -0
- package/dist/components/button/button.d.ts +86 -4
- package/dist/components/button/button.js +63 -3
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/demos/aliases.demo.d.ts +6 -0
- package/dist/components/button/demos/aliases.demo.js +18 -0
- package/dist/components/button/demos/aliases.demo.js.map +1 -0
- package/dist/components/button/demos/basic.demo.d.ts +6 -0
- package/dist/components/button/demos/basic.demo.js +19 -0
- package/dist/components/button/demos/basic.demo.js.map +1 -0
- package/dist/components/button/demos/states.demo.d.ts +6 -0
- package/dist/components/button/demos/states.demo.js +29 -0
- package/dist/components/button/demos/states.demo.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +11 -11
- package/dist/components/combobox/combobox.js +0 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/combobox/demos/overview.demo.d.ts +6 -0
- package/dist/components/combobox/demos/overview.demo.js +153 -0
- package/dist/components/combobox/demos/overview.demo.js.map +1 -0
- package/dist/components/listbox/demos/overview.demo.d.ts +6 -0
- package/dist/components/listbox/demos/overview.demo.js +64 -0
- package/dist/components/listbox/demos/overview.demo.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +4 -9
- package/dist/components/listbox/listbox.js +21 -33
- package/dist/components/listbox/listbox.js.map +1 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +5 -0
- package/dist/components/menu/demos/bubbling.demo.js +15 -0
- package/dist/components/menu/demos/bubbling.demo.js.map +1 -0
- package/dist/components/menu/demos/overview.demo.d.ts +6 -0
- package/dist/components/menu/demos/overview.demo.js +32 -0
- package/dist/components/menu/demos/overview.demo.js.map +1 -0
- package/dist/components/menu/menu.d.ts +15 -24
- package/dist/components/menu/menu.js +23 -38
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/demos/overview.demo.d.ts +6 -0
- package/dist/components/popover/demos/overview.demo.js +64 -0
- package/dist/components/popover/demos/overview.demo.js.map +1 -0
- package/dist/components/popover/popover.d.ts +5 -5
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/select/demos/deconstructed.demo.d.ts +6 -0
- package/dist/components/select/demos/deconstructed.demo.js +53 -0
- package/dist/components/select/demos/deconstructed.demo.js.map +1 -0
- package/dist/components/select/demos/overview.demo.d.ts +5 -0
- package/dist/components/select/demos/overview.demo.js +27 -0
- package/dist/components/select/demos/overview.demo.js.map +1 -0
- package/dist/components/select/select.d.ts +11 -11
- package/dist/components/select/select.js +17 -26
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +6 -6
- package/dist/components/tabs/tabs.js +0 -2
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- 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/component.d.ts +18 -12
- package/dist/runtime/component.js +93 -80
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.d.ts +14 -0
- package/dist/runtime/core/attributes.js +103 -0
- package/dist/runtime/core/attributes.js.map +1 -0
- package/dist/runtime/core/children.d.ts +7 -0
- package/dist/runtime/core/children.js +30 -0
- package/dist/runtime/core/children.js.map +1 -0
- package/dist/runtime/core/props.d.ts +9 -0
- package/dist/runtime/core/props.js +170 -0
- package/dist/runtime/core/props.js.map +1 -0
- package/dist/runtime/core/vnode.d.ts +3 -0
- package/dist/runtime/core/vnode.js +39 -0
- package/dist/runtime/core/vnode.js.map +1 -0
- package/dist/runtime/create-element.js +2 -1
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +47 -14
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.d.ts +1 -2
- package/dist/runtime/diff-props.js +2 -181
- package/dist/runtime/diff-props.js.map +1 -1
- package/dist/runtime/dom.d.ts +20 -9
- package/dist/runtime/frame.d.ts +23 -2
- package/dist/runtime/frame.js +41 -47
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +1 -1
- package/dist/runtime/jsx.js +2 -28
- 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 +9 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.d.ts +15 -4
- package/dist/runtime/mixins/on-mixin.js +9 -2
- package/dist/runtime/mixins/on-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/navigation.js +9 -1
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +1 -1
- package/dist/runtime/reconcile.js +452 -197
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/run.d.ts +17 -2
- package/dist/runtime/run.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/stream-protocol.d.ts +8 -0
- package/dist/runtime/stream-protocol.js +20 -0
- package/dist/runtime/stream-protocol.js.map +1 -0
- package/dist/runtime/svg-attributes.js +1 -0
- package/dist/runtime/svg-attributes.js.map +1 -1
- package/dist/runtime/to-vnode.js +7 -11
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vnode.d.ts +11 -5
- package/dist/runtime/vnode.js +6 -3
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +154 -114
- 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/layers.d.ts +2 -0
- package/dist/style/layers.js +3 -0
- package/dist/style/layers.js.map +1 -0
- package/dist/style/stylesheet.d.ts +13 -11
- package/dist/style/stylesheet.js +69 -8
- 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/theme/runtime.js +4 -1
- package/dist/theme/runtime.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 +3 -3
- package/src/animation/README.md +316 -0
- package/src/animation/animate-layout-mixin.ts +79 -71
- package/src/animation/spring.ts +21 -9
- package/src/components/accordion/README.md +166 -0
- package/src/components/accordion/accordion.tsx +14 -14
- package/src/components/accordion/demos/card.demo.tsx +113 -0
- package/src/components/accordion/demos/multiple.demo.tsx +56 -0
- package/src/components/accordion/demos/overview.demo.tsx +56 -0
- package/src/components/anchor/README.md +153 -0
- package/src/components/anchor/anchor.ts +1 -1
- package/src/components/breadcrumbs/README.md +55 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +1 -3
- package/src/components/breadcrumbs/demos/basic.demo.tsx +17 -0
- package/src/components/breadcrumbs/demos/separator.demo.tsx +18 -0
- package/src/components/button/README.md +4 -4
- package/src/components/button/button.tsx +118 -20
- package/src/components/button/demos/aliases.demo.tsx +27 -0
- package/src/components/button/demos/basic.demo.tsx +30 -0
- package/src/components/button/demos/states.demo.tsx +52 -0
- package/src/components/combobox/README.md +1 -1
- package/src/components/combobox/combobox.tsx +37 -21
- package/src/components/combobox/demos/overview.demo.tsx +199 -0
- package/src/components/glyph/README.md +72 -0
- package/src/components/glyph/glyph.tsx +2 -2
- package/src/components/listbox/README.md +115 -0
- package/src/components/listbox/demos/overview.demo.tsx +101 -0
- package/src/components/listbox/listbox.ts +106 -110
- package/src/components/menu/README.md +96 -0
- package/src/components/menu/demos/bubbling.demo.tsx +34 -0
- package/src/components/menu/demos/overview.demo.tsx +76 -0
- package/src/components/menu/menu.tsx +61 -55
- package/src/components/popover/README.md +3 -3
- package/src/components/popover/demos/overview.demo.tsx +143 -0
- package/src/components/popover/popover.ts +90 -79
- package/src/components/select/README.md +107 -0
- package/src/components/select/demos/deconstructed.demo.tsx +95 -0
- package/src/components/select/demos/overview.demo.tsx +51 -0
- package/src/components/select/select.tsx +53 -36
- package/src/components/tabs/tabs.tsx +10 -5
- package/src/index.ts +2 -1
- package/src/runtime/component.ts +122 -88
- package/src/runtime/core/attributes.ts +118 -0
- package/src/runtime/core/children.ts +42 -0
- package/src/runtime/core/props.ts +187 -0
- package/src/runtime/core/vnode.ts +48 -0
- package/src/runtime/create-element.ts +2 -1
- package/src/runtime/diff-dom.ts +59 -12
- package/src/runtime/diff-props.ts +2 -187
- package/src/runtime/dom.ts +20 -9
- package/src/runtime/frame.ts +71 -55
- package/src/runtime/jsx.ts +3 -30
- package/src/runtime/mixins/link-mixin.ts +88 -86
- package/src/runtime/mixins/mixin.ts +18 -5
- package/src/runtime/mixins/on-mixin.ts +20 -12
- package/src/runtime/mixins/ref-mixin.ts +4 -5
- package/src/runtime/navigation.ts +10 -1
- package/src/runtime/reconcile.ts +563 -223
- package/src/runtime/run.ts +17 -1
- package/src/runtime/scheduler.ts +20 -20
- package/src/runtime/stream-protocol.ts +27 -0
- package/src/runtime/svg-attributes.ts +1 -0
- package/src/runtime/to-vnode.ts +7 -10
- package/src/runtime/vnode.ts +16 -9
- package/src/server/README.md +90 -0
- package/src/server/stream.ts +176 -124
- package/src/style/css-mixin.ts +6 -1
- package/src/style/index.ts +1 -2
- package/src/style/layers.ts +2 -0
- package/src/style/stylesheet.ts +88 -11
- package/src/test/README.md +107 -0
- package/src/test/utils.ts +3 -1
- package/src/theme/README.md +103 -0
- 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/theme/runtime.ts +4 -1
- package/src/utils/scroll-lock/README.md +33 -0
- package/src/utils/scroll-lock.ts +7 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { normalizeCssValue } from '../../style/style.ts'
|
|
2
|
+
import { normalizeSvgAttribute } from '../svg-attributes.ts'
|
|
3
|
+
import type { ElementProps } from '../jsx.ts'
|
|
4
|
+
|
|
5
|
+
const ATTRIBUTE_FALLBACK_NAMES = new Set([
|
|
6
|
+
'width',
|
|
7
|
+
'height',
|
|
8
|
+
'href',
|
|
9
|
+
'list',
|
|
10
|
+
'form',
|
|
11
|
+
'tabIndex',
|
|
12
|
+
'download',
|
|
13
|
+
'rowSpan',
|
|
14
|
+
'colSpan',
|
|
15
|
+
'role',
|
|
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',
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
export const FRAMEWORK_PROPS = new Set(['children', 'mix', 'key', 'animate', 'innerHTML', 'on'])
|
|
31
|
+
|
|
32
|
+
export const SELF_CLOSING_TAGS = new Set([
|
|
33
|
+
'area',
|
|
34
|
+
'base',
|
|
35
|
+
'br',
|
|
36
|
+
'col',
|
|
37
|
+
'embed',
|
|
38
|
+
'hr',
|
|
39
|
+
'img',
|
|
40
|
+
'input',
|
|
41
|
+
'link',
|
|
42
|
+
'meta',
|
|
43
|
+
'param',
|
|
44
|
+
'source',
|
|
45
|
+
'track',
|
|
46
|
+
'wbr',
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
export function isChildlessElement(name: string): boolean {
|
|
50
|
+
return SELF_CLOSING_TAGS.has(name)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function canUseProperty(
|
|
54
|
+
element: Element,
|
|
55
|
+
name: string,
|
|
56
|
+
isSvg: boolean,
|
|
57
|
+
attr: string,
|
|
58
|
+
): element is Element & Record<string, unknown> {
|
|
59
|
+
if (isSvg) return false
|
|
60
|
+
if (ATTRIBUTE_FALLBACK_NAMES.has(name)) return false
|
|
61
|
+
if (isBooleanishStringAttribute(attr)) return false
|
|
62
|
+
return name in element
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function normalizeAttributeName(
|
|
66
|
+
name: string,
|
|
67
|
+
isSvg: boolean,
|
|
68
|
+
): { ns?: string; attr: string } {
|
|
69
|
+
if (name.startsWith('aria-') || name.startsWith('data-')) return { attr: name }
|
|
70
|
+
|
|
71
|
+
if (name === 'className') return { attr: 'class' }
|
|
72
|
+
|
|
73
|
+
if (!isSvg) {
|
|
74
|
+
if (name === 'htmlFor') return { attr: 'for' }
|
|
75
|
+
if (name === 'tabIndex') return { attr: 'tabindex' }
|
|
76
|
+
if (name === 'acceptCharset') return { attr: 'accept-charset' }
|
|
77
|
+
if (name === 'httpEquiv') return { attr: 'http-equiv' }
|
|
78
|
+
return { attr: name.toLowerCase() }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return normalizeSvgAttribute(name)
|
|
82
|
+
}
|
|
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
|
+
|
|
92
|
+
export function serializeStyleObject(style: Record<string, unknown>): string {
|
|
93
|
+
let parts: string[] = []
|
|
94
|
+
|
|
95
|
+
for (let [key, value] of Object.entries(style)) {
|
|
96
|
+
if (value == null) continue
|
|
97
|
+
if (typeof value === 'boolean') continue
|
|
98
|
+
if (typeof value === 'number' && !Number.isFinite(value)) continue
|
|
99
|
+
|
|
100
|
+
let cssKey = toKebabCase(key)
|
|
101
|
+
let cssValue = Array.isArray(value) ? value.join(', ') : normalizeCssValue(key, value)
|
|
102
|
+
|
|
103
|
+
parts.push(`${cssKey}: ${cssValue};`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return parts.join(' ')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getMergedClassName(props: ElementProps): string | undefined {
|
|
110
|
+
let classAttr = typeof props.class === 'string' ? props.class : ''
|
|
111
|
+
let className = typeof props.className === 'string' ? props.className : ''
|
|
112
|
+
let merged = classAttr && className ? `${classAttr} ${className}` : classAttr || className
|
|
113
|
+
return merged || undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function toKebabCase(value: string): string {
|
|
117
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
|
|
118
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RemixNode } from '../jsx.ts'
|
|
2
|
+
|
|
3
|
+
export type EmptyChild = boolean | null | undefined
|
|
4
|
+
export type PrimitiveChild = string | number | bigint
|
|
5
|
+
|
|
6
|
+
export function isEmptyChild(value: RemixNode): value is EmptyChild {
|
|
7
|
+
return value == null || typeof value === 'boolean'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isPrimitiveChild(value: RemixNode): value is PrimitiveChild {
|
|
11
|
+
let type = typeof value
|
|
12
|
+
return type === 'string' || type === 'number' || type === 'bigint'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeChildren(children: readonly RemixNode[]): RemixNode[] {
|
|
16
|
+
for (let child of children) {
|
|
17
|
+
if (Array.isArray(child)) {
|
|
18
|
+
return (children as unknown[]).flat(Infinity) as RemixNode[]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return children as RemixNode[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function packChildren(children: readonly RemixNode[]): RemixNode | undefined {
|
|
26
|
+
if (children.length === 0) {
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (children.length === 1) {
|
|
31
|
+
let child = children[0]
|
|
32
|
+
|
|
33
|
+
if (child === undefined || isEmptyChild(child)) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return Array.isArray(child) ? normalizeChildren(child) : child
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let normalized = normalizeChildren(children)
|
|
41
|
+
return normalized.length === 0 ? undefined : normalized
|
|
42
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { ElementProps } from '../jsx.ts'
|
|
2
|
+
import {
|
|
3
|
+
canUseProperty,
|
|
4
|
+
getMergedClassName,
|
|
5
|
+
isBooleanishStringAttribute,
|
|
6
|
+
normalizeAttributeName,
|
|
7
|
+
serializeStyleObject,
|
|
8
|
+
toKebabCase,
|
|
9
|
+
} from './attributes.ts'
|
|
10
|
+
import { normalizeCssValue } from '../../style/style.ts'
|
|
11
|
+
|
|
12
|
+
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
13
|
+
|
|
14
|
+
function isFrameworkProp(name: string): boolean {
|
|
15
|
+
return (
|
|
16
|
+
name === 'children' ||
|
|
17
|
+
name === 'mix' ||
|
|
18
|
+
name === 'key' ||
|
|
19
|
+
name === 'animate' ||
|
|
20
|
+
name === 'innerHTML' ||
|
|
21
|
+
name === 'on'
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toLocalName(attrName: string): string {
|
|
26
|
+
let separatorIndex = attrName.indexOf(':')
|
|
27
|
+
if (separatorIndex === -1) return attrName
|
|
28
|
+
return attrName.slice(separatorIndex + 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function clearRuntimePropertyOnRemoval(dom: Element & Record<string, unknown>, name: string): void {
|
|
32
|
+
try {
|
|
33
|
+
if (name === 'value' || name === 'defaultValue') {
|
|
34
|
+
dom[name] = ''
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
if (name === 'checked' || name === 'defaultChecked' || name === 'selected') {
|
|
38
|
+
dom[name] = false
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (name === 'selectedIndex') {
|
|
42
|
+
dom[name] = -1
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Patches DOM attributes and selected properties from old props to new props.
|
|
49
|
+
*
|
|
50
|
+
* @param curr Previous host props.
|
|
51
|
+
* @param next Next host props.
|
|
52
|
+
* @param dom Element to patch.
|
|
53
|
+
*/
|
|
54
|
+
export function patchHostProps(curr: ElementProps, next: ElementProps, dom: Element): void {
|
|
55
|
+
let isSvg = dom.namespaceURI === SVG_NS
|
|
56
|
+
let currClassName = getMergedClassName(curr)
|
|
57
|
+
let nextClassName = getMergedClassName(next)
|
|
58
|
+
|
|
59
|
+
if (currClassName !== nextClassName) {
|
|
60
|
+
if (nextClassName) {
|
|
61
|
+
dom.setAttribute('class', nextClassName)
|
|
62
|
+
} else {
|
|
63
|
+
dom.removeAttribute('class')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (let name in curr) {
|
|
68
|
+
if (isFrameworkProp(name)) continue
|
|
69
|
+
if (name === 'class' || name === 'className') continue
|
|
70
|
+
if (name in next && next[name] != null) continue
|
|
71
|
+
|
|
72
|
+
let { ns, attr } = normalizeAttributeName(name, isSvg)
|
|
73
|
+
if (canUseProperty(dom, name, isSvg, attr)) {
|
|
74
|
+
clearRuntimePropertyOnRemoval(dom, name)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
78
|
+
else dom.removeAttribute(attr)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (let name in next) {
|
|
82
|
+
if (isFrameworkProp(name)) continue
|
|
83
|
+
if (name === 'class' || name === 'className') continue
|
|
84
|
+
|
|
85
|
+
let nextValue = next[name]
|
|
86
|
+
if (nextValue == null) continue
|
|
87
|
+
|
|
88
|
+
let prevValue = curr[name]
|
|
89
|
+
if (prevValue === nextValue) continue
|
|
90
|
+
|
|
91
|
+
if (name === 'style' && isStyleObject(nextValue)) {
|
|
92
|
+
if (isStyleObject(prevValue)) {
|
|
93
|
+
patchStyleObject(dom, prevValue, nextValue)
|
|
94
|
+
} else {
|
|
95
|
+
dom.removeAttribute('style')
|
|
96
|
+
patchStyleObject(dom, undefined, nextValue)
|
|
97
|
+
}
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
patchHostProp(dom, name, nextValue, isSvg)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function patchHostProp(dom: Element, name: string, value: unknown, isSvg: boolean): void {
|
|
106
|
+
let { ns, attr } = normalizeAttributeName(name, isSvg)
|
|
107
|
+
|
|
108
|
+
if (attr === 'style' && isStyleObject(value)) {
|
|
109
|
+
patchStyleObject(dom, undefined, value)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (attr === 'style' && typeof value === 'string') {
|
|
114
|
+
dom.setAttribute('style', value)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (canUseProperty(dom, name, isSvg, attr)) {
|
|
119
|
+
try {
|
|
120
|
+
dom[name] = value == null ? '' : value
|
|
121
|
+
return
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof value === 'function') return
|
|
126
|
+
|
|
127
|
+
let isAriaOrData = name.startsWith('aria-') || name.startsWith('data-')
|
|
128
|
+
let isBooleanishString = isBooleanishStringAttribute(attr)
|
|
129
|
+
if (value != null && (value !== false || isAriaOrData || isBooleanishString)) {
|
|
130
|
+
let attrValue = name === 'popover' && value === true ? '' : String(value)
|
|
131
|
+
if (ns) dom.setAttributeNS(ns, attr, attrValue)
|
|
132
|
+
else dom.setAttribute(attr, attrValue)
|
|
133
|
+
} else {
|
|
134
|
+
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
135
|
+
else dom.removeAttribute(attr)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isStyleObject(value: unknown): value is Record<string, unknown> {
|
|
140
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function patchStyleObject(
|
|
144
|
+
dom: Element,
|
|
145
|
+
curr: Record<string, unknown> | undefined,
|
|
146
|
+
next: Record<string, unknown>,
|
|
147
|
+
): void {
|
|
148
|
+
if (!(dom instanceof HTMLElement || dom instanceof SVGElement)) {
|
|
149
|
+
dom.setAttribute('style', serializeStyleObject(next))
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let style = dom.style
|
|
154
|
+
if (curr) {
|
|
155
|
+
for (let name in curr) {
|
|
156
|
+
let nextCssValue = styleValueToCss(name, next[name])
|
|
157
|
+
if (nextCssValue !== undefined) continue
|
|
158
|
+
|
|
159
|
+
let prevCssValue = styleValueToCss(name, curr[name])
|
|
160
|
+
if (prevCssValue === undefined) continue
|
|
161
|
+
|
|
162
|
+
style.removeProperty(toKebabCase(name))
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (let name in next) {
|
|
167
|
+
let nextCssValue = styleValueToCss(name, next[name])
|
|
168
|
+
if (nextCssValue === undefined) continue
|
|
169
|
+
|
|
170
|
+
let prevCssValue = curr ? styleValueToCss(name, curr[name]) : undefined
|
|
171
|
+
if (prevCssValue === nextCssValue) continue
|
|
172
|
+
|
|
173
|
+
style.setProperty(toKebabCase(name), nextCssValue)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function styleValueToCss(name: string, value: unknown): string | undefined {
|
|
178
|
+
if (value == null) return undefined
|
|
179
|
+
if (typeof value === 'boolean') return undefined
|
|
180
|
+
if (typeof value === 'number' && !Number.isFinite(value)) return undefined
|
|
181
|
+
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
return value.join(', ')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return normalizeCssValue(name, value)
|
|
187
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ElementProps, ElementType, RemixElement, RemixNode } from '../jsx.ts'
|
|
2
|
+
|
|
3
|
+
export function createRemixElement(
|
|
4
|
+
type: ElementType,
|
|
5
|
+
props: ElementProps | null | undefined,
|
|
6
|
+
key?: string,
|
|
7
|
+
): RemixElement {
|
|
8
|
+
return {
|
|
9
|
+
$rmx: true,
|
|
10
|
+
key,
|
|
11
|
+
props: normalizeElementProps(props),
|
|
12
|
+
type,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isRemixElement(node: RemixNode): node is RemixElement {
|
|
17
|
+
return typeof node === 'object' && node !== null && '$rmx' in node
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeElementProps(props: ElementProps | null | undefined): ElementProps {
|
|
21
|
+
if (!props) return {}
|
|
22
|
+
if (!('mix' in props)) return props
|
|
23
|
+
|
|
24
|
+
let { mix, ...rest } = props
|
|
25
|
+
let normalizedMix = normalizeMixValue(mix)
|
|
26
|
+
return normalizedMix === undefined ? rest : { ...rest, mix: normalizedMix }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeMixValue(mix: unknown): unknown[] | undefined {
|
|
30
|
+
if (!mix) return undefined
|
|
31
|
+
|
|
32
|
+
let normalizedMix: unknown[] = []
|
|
33
|
+
flattenMixValue(mix, normalizedMix)
|
|
34
|
+
return normalizedMix.length === 0 ? undefined : normalizedMix
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function flattenMixValue(mix: unknown, out: unknown[]): void {
|
|
38
|
+
if (!mix) return
|
|
39
|
+
|
|
40
|
+
if (!Array.isArray(mix)) {
|
|
41
|
+
out.push(mix)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let item of mix) {
|
|
46
|
+
flattenMixValue(item, out)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from './jsx.ts'
|
|
2
2
|
import type { ElementType, RemixElement } from './jsx.ts'
|
|
3
|
+
import { normalizeChildren } from './core/children.ts'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Creates a Remix virtual element from a JSX-like call signature.
|
|
@@ -20,7 +21,7 @@ export function createElement(
|
|
|
20
21
|
console.error(new Error('mixin elements must not receive children'))
|
|
21
22
|
}
|
|
22
23
|
} else {
|
|
23
|
-
nextProps.children = children
|
|
24
|
+
nextProps.children = normalizeChildren(children)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
if (nextProps.key != null) {
|
package/src/runtime/diff-dom.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { invariant } from './invariant.ts'
|
|
2
2
|
import type { FrameContext } from './frame.ts'
|
|
3
3
|
|
|
4
|
+
type HydratedVirtualRootStartMarker = Comment & {
|
|
5
|
+
$rmx: {
|
|
6
|
+
dispose(): void
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
|
|
5
11
|
let parent = curr[0]?.parentNode ?? context.regionParent ?? null
|
|
6
12
|
invariant(parent, 'Parent node not found')
|
|
@@ -23,8 +29,7 @@ export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
|
|
|
23
29
|
parent.appendChild(n)
|
|
24
30
|
}
|
|
25
31
|
} else if (c && !n) {
|
|
26
|
-
|
|
27
|
-
parent.removeChild(c)
|
|
32
|
+
removeNode(c, parent, context)
|
|
28
33
|
} else if (c && n) {
|
|
29
34
|
// Skip hydrated client-entry boundary ranges; hydration pass re-renders
|
|
30
35
|
// roots with new props from incoming payload
|
|
@@ -71,7 +76,10 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
|
|
|
71
76
|
// Comment -> Comment
|
|
72
77
|
if (isCommentNode(current) && isCommentNode(next)) {
|
|
73
78
|
let newData = next.data
|
|
74
|
-
if (current.data !== newData)
|
|
79
|
+
if (current.data !== newData) {
|
|
80
|
+
if (isFrameStartMarker(current)) disposeFrameStartMarker(current, context)
|
|
81
|
+
current.data = newData
|
|
82
|
+
}
|
|
75
83
|
return
|
|
76
84
|
}
|
|
77
85
|
|
|
@@ -80,7 +88,10 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
|
|
|
80
88
|
// Different tags: replace
|
|
81
89
|
if (current.tagName !== next.tagName) {
|
|
82
90
|
let parent = current.parentNode
|
|
83
|
-
if (parent)
|
|
91
|
+
if (parent) {
|
|
92
|
+
parent.insertBefore(next, current)
|
|
93
|
+
removeNode(current, parent, context)
|
|
94
|
+
}
|
|
84
95
|
return
|
|
85
96
|
}
|
|
86
97
|
|
|
@@ -93,7 +104,10 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
|
|
|
93
104
|
|
|
94
105
|
// Type mismatch: replace
|
|
95
106
|
let parent = current.parentNode
|
|
96
|
-
if (parent)
|
|
107
|
+
if (parent) {
|
|
108
|
+
parent.insertBefore(next, current)
|
|
109
|
+
removeNode(current, parent, context)
|
|
110
|
+
}
|
|
97
111
|
}
|
|
98
112
|
|
|
99
113
|
function diffElementAttributes(current: Element, next: Element): void {
|
|
@@ -315,8 +329,7 @@ function diffElementChildren(current: Element, next: Element, context: FrameCont
|
|
|
315
329
|
for (let i = 0; i < currentChildren.length; i++) {
|
|
316
330
|
if (!used[i]) {
|
|
317
331
|
let nodeToRemove = currentChildren[i]
|
|
318
|
-
|
|
319
|
-
current.removeChild(currentChildren[i])
|
|
332
|
+
removeNode(nodeToRemove, current, context)
|
|
320
333
|
}
|
|
321
334
|
}
|
|
322
335
|
}
|
|
@@ -375,6 +388,32 @@ function isFrameStartMarker(node: Node): node is Comment {
|
|
|
375
388
|
return node instanceof Comment && node.data.trim().startsWith('rmx:f:')
|
|
376
389
|
}
|
|
377
390
|
|
|
391
|
+
function removeNode(node: Node, parent: ParentNode, context: FrameContext): void {
|
|
392
|
+
disposeRemovedVirtualRoots(node)
|
|
393
|
+
disposeRemovedSubFrames(node, context)
|
|
394
|
+
|
|
395
|
+
if (node.parentNode === parent) {
|
|
396
|
+
parent.removeChild(node)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function disposeRemovedVirtualRoots(node: Node): void {
|
|
401
|
+
let stack: Node[] = [node]
|
|
402
|
+
while (stack.length > 0) {
|
|
403
|
+
let next = stack.pop()
|
|
404
|
+
if (!next) continue
|
|
405
|
+
|
|
406
|
+
if (isHydratedVirtualRootStartMarker(next)) {
|
|
407
|
+
next.$rmx.dispose()
|
|
408
|
+
continue
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (let child of Array.from(next.childNodes)) {
|
|
412
|
+
stack.push(child)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
378
417
|
function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
379
418
|
let stack: Node[] = [node]
|
|
380
419
|
while (stack.length > 0) {
|
|
@@ -382,11 +421,7 @@ function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
|
382
421
|
if (!next) continue
|
|
383
422
|
|
|
384
423
|
if (isFrameStartMarker(next)) {
|
|
385
|
-
|
|
386
|
-
if (subFrame) {
|
|
387
|
-
subFrame.dispose()
|
|
388
|
-
context.frameInstances.delete(next)
|
|
389
|
-
}
|
|
424
|
+
disposeFrameStartMarker(next, context)
|
|
390
425
|
}
|
|
391
426
|
|
|
392
427
|
for (let child of Array.from(next.childNodes)) {
|
|
@@ -395,10 +430,22 @@ function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
|
395
430
|
}
|
|
396
431
|
}
|
|
397
432
|
|
|
433
|
+
function disposeFrameStartMarker(marker: Comment, context: FrameContext): void {
|
|
434
|
+
let subFrame = context.frameInstances.get(marker)
|
|
435
|
+
if (subFrame) {
|
|
436
|
+
subFrame.dispose()
|
|
437
|
+
context.frameInstances.delete(marker)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
398
441
|
function isVirtualRootStartMarker(node: Node): node is Comment {
|
|
399
442
|
return isCommentNode(node) && node.data.trim().startsWith('rmx:h:')
|
|
400
443
|
}
|
|
401
444
|
|
|
445
|
+
function isHydratedVirtualRootStartMarker(node: Node): node is HydratedVirtualRootStartMarker {
|
|
446
|
+
return isVirtualRootStartMarker(node) && '$rmx' in node
|
|
447
|
+
}
|
|
448
|
+
|
|
402
449
|
function isVirtualRootEndMarker(node: Node): node is Comment {
|
|
403
450
|
return isCommentNode(node) && node.data.trim() === '/rmx:h'
|
|
404
451
|
}
|