@remix-run/ui 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/animation/animate-layout-mixin.d.ts +12 -0
- package/dist/animation/animate-layout-mixin.js +214 -0
- package/dist/animation/animate-layout-mixin.js.map +1 -0
- package/dist/animation/animate-mixins.d.ts +29 -0
- package/dist/animation/animate-mixins.js +235 -0
- package/dist/animation/animate-mixins.js.map +1 -0
- package/dist/animation/index.d.ts +6 -0
- package/dist/animation/index.js +5 -0
- package/dist/animation/index.js.map +1 -0
- package/dist/animation/spring.d.ts +75 -0
- package/dist/animation/spring.js +183 -0
- package/dist/animation/spring.js.map +1 -0
- package/dist/animation/tween.d.ts +70 -0
- package/dist/animation/tween.js +93 -0
- package/dist/animation/tween.js.map +1 -0
- package/dist/components/accordion/accordion.d.ts +92 -0
- package/dist/components/accordion/accordion.js +337 -0
- package/dist/components/accordion/accordion.js.map +1 -0
- package/dist/components/anchor/anchor.d.ts +13 -0
- package/dist/components/anchor/anchor.js +558 -0
- package/dist/components/anchor/anchor.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +11 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +78 -0
- package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/components/button/button.d.ts +23 -0
- package/dist/components/button/button.js +147 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +101 -0
- package/dist/components/combobox/combobox.js +708 -0
- package/dist/components/combobox/combobox.js.map +1 -0
- package/dist/components/glyph/glyph.d.ts +14 -0
- package/dist/components/glyph/glyph.js +65 -0
- package/dist/components/glyph/glyph.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +67 -0
- package/dist/components/listbox/listbox.js +340 -0
- package/dist/components/listbox/listbox.js.map +1 -0
- package/dist/components/menu/hover-aim.d.ts +5 -0
- package/dist/components/menu/hover-aim.js +308 -0
- package/dist/components/menu/hover-aim.js.map +1 -0
- package/dist/components/menu/menu.d.ts +164 -0
- package/dist/components/menu/menu.js +1106 -0
- package/dist/components/menu/menu.js.map +1 -0
- package/dist/components/popover/popover.d.ts +35 -0
- package/dist/components/popover/popover.js +138 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/select/select.d.ts +67 -0
- package/dist/components/select/select.js +397 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/separator/separator.d.ts +2 -0
- package/dist/components/separator/separator.js +15 -0
- package/dist/components/separator/separator.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +78 -0
- package/dist/components/tabs/tabs.js +341 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions/keydown/keydown.d.ts +1 -0
- package/dist/interactions/keydown/keydown.js +8 -0
- package/dist/interactions/keydown/keydown.js.map +1 -0
- package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -0
- package/dist/interactions/outside-click/outside-click-mixin.js +29 -0
- package/dist/interactions/outside-click/outside-click-mixin.js.map +1 -0
- package/dist/interactions/typeahead/typeahead-mixin.d.ts +8 -0
- package/dist/interactions/typeahead/typeahead-mixin.js +59 -0
- package/dist/interactions/typeahead/typeahead-mixin.js.map +1 -0
- package/dist/jsx-runtime.d.ts +2 -0
- package/dist/jsx-runtime.js +3 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/runtime/client-entries.d.ts +91 -0
- package/dist/runtime/client-entries.js +40 -0
- package/dist/runtime/client-entries.js.map +1 -0
- package/dist/runtime/component.d.ts +223 -0
- package/dist/runtime/component.js +152 -0
- package/dist/runtime/component.js.map +1 -0
- package/dist/runtime/create-element.d.ts +10 -0
- package/dist/runtime/create-element.js +29 -0
- package/dist/runtime/create-element.js.map +1 -0
- package/dist/runtime/diff-dom.d.ts +2 -0
- package/dist/runtime/diff-dom.js +364 -0
- package/dist/runtime/diff-dom.js.map +1 -0
- package/dist/runtime/diff-props.d.ts +9 -0
- package/dist/runtime/diff-props.js +195 -0
- package/dist/runtime/diff-props.js.map +1 -0
- package/dist/runtime/document-state.d.ts +11 -0
- package/dist/runtime/document-state.js +106 -0
- package/dist/runtime/document-state.js.map +1 -0
- package/dist/runtime/dom.d.ts +3189 -0
- package/dist/runtime/dom.js +2 -0
- package/dist/runtime/dom.js.map +1 -0
- package/dist/runtime/error-event.d.ts +20 -0
- package/dist/runtime/error-event.js +19 -0
- package/dist/runtime/error-event.js.map +1 -0
- package/dist/runtime/event-listeners.d.ts +50 -0
- package/dist/runtime/event-listeners.js +31 -0
- package/dist/runtime/event-listeners.js.map +1 -0
- package/dist/runtime/frame.d.ts +119 -0
- package/dist/runtime/frame.js +915 -0
- package/dist/runtime/frame.js.map +1 -0
- package/dist/runtime/invariant.d.ts +23 -0
- package/dist/runtime/invariant.js +33 -0
- package/dist/runtime/invariant.js.map +1 -0
- package/dist/runtime/jsx.d.ts +320 -0
- package/dist/runtime/jsx.js +31 -0
- package/dist/runtime/jsx.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.d.ts +9 -0
- package/dist/runtime/mixins/attrs-mixin.js +23 -0
- package/dist/runtime/mixins/attrs-mixin.js.map +1 -0
- package/dist/runtime/mixins/link-mixin.d.ts +17 -0
- package/dist/runtime/mixins/link-mixin.js +101 -0
- package/dist/runtime/mixins/link-mixin.js.map +1 -0
- package/dist/runtime/mixins/mixin.d.ts +145 -0
- package/dist/runtime/mixins/mixin.js +570 -0
- package/dist/runtime/mixins/mixin.js.map +1 -0
- package/dist/runtime/mixins/on-mixin.d.ts +16 -0
- package/dist/runtime/mixins/on-mixin.js +49 -0
- package/dist/runtime/mixins/on-mixin.js.map +1 -0
- package/dist/runtime/mixins/ref-mixin.d.ts +9 -0
- package/dist/runtime/mixins/ref-mixin.js +21 -0
- package/dist/runtime/mixins/ref-mixin.js.map +1 -0
- package/dist/runtime/navigation.d.ts +22 -0
- package/dist/runtime/navigation.js +102 -0
- package/dist/runtime/navigation.js.map +1 -0
- package/dist/runtime/reconcile.d.ts +11 -0
- package/dist/runtime/reconcile.js +1240 -0
- package/dist/runtime/reconcile.js.map +1 -0
- package/dist/runtime/render.d.ts +54 -0
- package/dist/runtime/render.js +50 -0
- package/dist/runtime/render.js.map +1 -0
- package/dist/runtime/run.d.ts +45 -0
- package/dist/runtime/run.js +68 -0
- package/dist/runtime/run.js.map +1 -0
- package/dist/runtime/scheduler.d.ts +30 -0
- package/dist/runtime/scheduler.js +185 -0
- package/dist/runtime/scheduler.js.map +1 -0
- package/dist/runtime/svg-attributes.d.ts +5 -0
- package/dist/runtime/svg-attributes.js +124 -0
- package/dist/runtime/svg-attributes.js.map +1 -0
- package/dist/runtime/to-vnode.d.ts +3 -0
- package/dist/runtime/to-vnode.js +43 -0
- package/dist/runtime/to-vnode.js.map +1 -0
- package/dist/runtime/typed-event-target.d.ts +50 -0
- package/dist/runtime/typed-event-target.js +6 -0
- package/dist/runtime/typed-event-target.js.map +1 -0
- package/dist/runtime/vdom.d.ts +55 -0
- package/dist/runtime/vdom.js +198 -0
- package/dist/runtime/vdom.js.map +1 -0
- package/dist/runtime/vnode.d.ts +79 -0
- package/dist/runtime/vnode.js +38 -0
- package/dist/runtime/vnode.js.map +1 -0
- package/dist/server/stream.d.ts +57 -0
- package/dist/server/stream.js +1007 -0
- package/dist/server/stream.js.map +1 -0
- package/dist/style/css-mixin.d.ts +8 -0
- package/dist/style/css-mixin.js +54 -0
- package/dist/style/css-mixin.js.map +1 -0
- package/dist/style/index.d.ts +5 -0
- package/dist/style/index.js +4 -0
- package/dist/style/index.js.map +1 -0
- package/dist/style/style.d.ts +24 -0
- package/dist/style/style.js +269 -0
- package/dist/style/style.js.map +1 -0
- package/dist/style/stylesheet.d.ts +11 -0
- package/dist/style/stylesheet.js +174 -0
- package/dist/style/stylesheet.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.js +2 -0
- package/dist/test.js.map +1 -0
- package/dist/theme/contract.d.ts +259 -0
- package/dist/theme/contract.js +131 -0
- package/dist/theme/contract.js.map +1 -0
- package/dist/theme/glyph-contract.d.ts +13 -0
- package/dist/theme/glyph-contract.js +34 -0
- package/dist/theme/glyph-contract.js.map +1 -0
- package/dist/theme/presets/rmx-01/glyphs.d.ts +2 -0
- package/dist/theme/presets/rmx-01/glyphs.js +252 -0
- package/dist/theme/presets/rmx-01/glyphs.js.map +1 -0
- package/dist/theme/presets/rmx-01/index.d.ts +2 -0
- package/dist/theme/presets/rmx-01/index.js +119 -0
- package/dist/theme/presets/rmx-01/index.js.map +1 -0
- package/dist/theme/runtime.d.ts +2 -0
- package/dist/theme/runtime.js +81 -0
- package/dist/theme/runtime.js.map +1 -0
- package/dist/theme/theme.d.ts +6 -0
- package/dist/theme/theme.js +5 -0
- package/dist/theme/theme.js.map +1 -0
- package/dist/utils/flash-attribute.d.ts +1 -0
- package/dist/utils/flash-attribute.js +11 -0
- package/dist/utils/flash-attribute.js.map +1 -0
- package/dist/utils/scroll-lock.d.ts +3 -0
- package/dist/utils/scroll-lock.js +69 -0
- package/dist/utils/scroll-lock.js.map +1 -0
- package/dist/utils/wait-for-css-transition.d.ts +1 -0
- package/dist/utils/wait-for-css-transition.js +58 -0
- package/dist/utils/wait-for-css-transition.js.map +1 -0
- package/dist/utils/wait.d.ts +1 -0
- package/dist/utils/wait.js +6 -0
- package/dist/utils/wait.js.map +1 -0
- package/package.json +125 -5
- package/src/animation/animate-layout-mixin.ts +266 -0
- package/src/animation/animate-mixins.ts +292 -0
- package/src/animation/index.ts +6 -0
- package/src/animation/spring.ts +299 -0
- package/src/animation/tween.ts +134 -0
- package/src/components/accordion/accordion.tsx +565 -0
- package/src/components/anchor/anchor.ts +904 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +124 -0
- package/src/components/button/README.md +44 -0
- package/src/components/button/button.tsx +188 -0
- package/src/components/combobox/README.md +145 -0
- package/src/components/combobox/combobox.tsx +1014 -0
- package/src/components/glyph/glyph.tsx +110 -0
- package/src/components/listbox/listbox.ts +447 -0
- package/src/components/menu/hover-aim.ts +428 -0
- package/src/components/menu/menu.tsx +1547 -0
- package/src/components/popover/README.md +122 -0
- package/src/components/popover/popover.ts +201 -0
- package/src/components/select/select.tsx +585 -0
- package/src/components/separator/separator.ts +19 -0
- package/src/components/tabs/README.md +105 -0
- package/src/components/tabs/tabs.tsx +499 -0
- package/src/index.ts +72 -0
- package/src/interactions/keydown/keydown.ts +14 -0
- package/src/interactions/outside-click/outside-click-mixin.ts +55 -0
- package/src/interactions/typeahead/typeahead-mixin.ts +89 -0
- package/src/jsx-runtime.ts +2 -0
- package/src/runtime/client-entries.ts +137 -0
- package/src/runtime/component.ts +402 -0
- package/src/runtime/create-element.ts +38 -0
- package/src/runtime/diff-dom.ts +404 -0
- package/src/runtime/diff-props.ts +209 -0
- package/src/runtime/document-state.ts +130 -0
- package/src/runtime/dom.ts +3971 -0
- package/src/runtime/error-event.ts +26 -0
- package/src/runtime/event-listeners.ts +171 -0
- package/src/runtime/frame.ts +1211 -0
- package/src/runtime/invariant.ts +31 -0
- package/src/runtime/jsx.ts +398 -0
- package/src/runtime/mixins/attrs-mixin.ts +35 -0
- package/src/runtime/mixins/link-mixin.ts +131 -0
- package/src/runtime/mixins/mixin.ts +908 -0
- package/src/runtime/mixins/on-mixin.ts +89 -0
- package/src/runtime/mixins/ref-mixin.ts +32 -0
- package/src/runtime/navigation.ts +136 -0
- package/src/runtime/reconcile.ts +1722 -0
- package/src/runtime/render.ts +101 -0
- package/src/runtime/run.ts +100 -0
- package/src/runtime/scheduler.ts +262 -0
- package/src/runtime/svg-attributes.ts +133 -0
- package/src/runtime/to-vnode.ts +50 -0
- package/src/runtime/typed-event-target.ts +67 -0
- package/src/runtime/vdom.ts +295 -0
- package/src/runtime/vnode.ts +137 -0
- package/src/server/stream.ts +1342 -0
- package/src/style/css-mixin.ts +82 -0
- package/src/style/index.ts +10 -0
- package/src/style/style.ts +312 -0
- package/src/style/stylesheet.ts +209 -0
- package/src/test/setup.ts +7 -0
- package/src/test/utils.ts +45 -0
- package/src/test.ts +2 -0
- package/src/theme/contract.ts +183 -0
- package/src/theme/glyph-contract.ts +57 -0
- package/src/theme/presets/rmx-01/glyphs.tsx +308 -0
- package/src/theme/presets/rmx-01/index.ts +121 -0
- package/src/theme/runtime.ts +116 -0
- package/src/theme/theme.ts +15 -0
- package/src/utils/flash-attribute.ts +11 -0
- package/src/utils/scroll-lock.ts +97 -0
- package/src/utils/wait-for-css-transition.ts +66 -0
- package/src/utils/wait.ts +5 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { invariant } from './invariant.ts'
|
|
2
|
+
import type { FrameContext } from './frame.ts'
|
|
3
|
+
|
|
4
|
+
export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
|
|
5
|
+
let parent = curr[0]?.parentNode ?? context.regionParent ?? null
|
|
6
|
+
invariant(parent, 'Parent node not found')
|
|
7
|
+
|
|
8
|
+
// When diffing a bounded region (e.g. between frame comments), we should insert new
|
|
9
|
+
// nodes before the region tail ref rather than appending to the parent.
|
|
10
|
+
let regionTailRef: ChildNode | null =
|
|
11
|
+
context.regionTailRef ??
|
|
12
|
+
(curr.length > 0 ? (curr[curr.length - 1].nextSibling as ChildNode | null) : null)
|
|
13
|
+
|
|
14
|
+
let max = Math.max(curr.length, next.length)
|
|
15
|
+
for (let i = 0; i < max; i++) {
|
|
16
|
+
let c = curr[i]
|
|
17
|
+
let n = next[i]
|
|
18
|
+
|
|
19
|
+
if (!c && n) {
|
|
20
|
+
if (regionTailRef) {
|
|
21
|
+
parent.insertBefore(n, regionTailRef)
|
|
22
|
+
} else {
|
|
23
|
+
parent.appendChild(n)
|
|
24
|
+
}
|
|
25
|
+
} else if (c && !n) {
|
|
26
|
+
disposeRemovedSubFrames(c, context)
|
|
27
|
+
parent.removeChild(c)
|
|
28
|
+
} else if (c && n) {
|
|
29
|
+
// Skip hydrated client-entry boundary ranges; hydration pass re-renders
|
|
30
|
+
// roots with new props from incoming payload
|
|
31
|
+
if (isVirtualRootStartMarker(c) && isVirtualRootStartMarker(n)) {
|
|
32
|
+
let currentEnd = findHydrationEndMarker(c)
|
|
33
|
+
let nextEnd = findHydrationEndMarker(n)
|
|
34
|
+
let nextData = n.data
|
|
35
|
+
if (c.data !== nextData) c.data = nextData
|
|
36
|
+
|
|
37
|
+
let currentEndIndex = curr.indexOf(currentEnd)
|
|
38
|
+
let nextEndIndex = next.indexOf(nextEnd)
|
|
39
|
+
i = Math.max(currentEndIndex, nextEndIndex)
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let cursor = diffNode(c, n, context)
|
|
44
|
+
if (cursor) {
|
|
45
|
+
i = next.indexOf(cursor)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function diffNode(current: Node, next: Node, context: FrameContext): ChildNode | undefined {
|
|
52
|
+
// Text -> Text
|
|
53
|
+
if (isTextNode(current) && isTextNode(next)) {
|
|
54
|
+
let newText = next.textContent || ''
|
|
55
|
+
if (current.textContent !== newText) current.textContent = newText
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Hydration boundary -> Hydration boundary
|
|
60
|
+
if (isVirtualRootStartMarker(current) && isVirtualRootStartMarker(next)) {
|
|
61
|
+
let nextData = next.data
|
|
62
|
+
if (current.data !== nextData) {
|
|
63
|
+
current.data = nextData
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let end = findHydrationEndMarker(next)
|
|
67
|
+
// Fast-forward across this hydrated region.
|
|
68
|
+
return end
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Comment -> Comment
|
|
72
|
+
if (isCommentNode(current) && isCommentNode(next)) {
|
|
73
|
+
let newData = next.data
|
|
74
|
+
if (current.data !== newData) current.data = newData
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Element -> Element
|
|
79
|
+
if (isElement(current) && isElement(next)) {
|
|
80
|
+
// Different tags: replace
|
|
81
|
+
if (current.tagName !== next.tagName) {
|
|
82
|
+
let parent = current.parentNode
|
|
83
|
+
if (parent) parent.replaceChild(next, current)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Same tag: update attributes then children
|
|
88
|
+
diffElementAttributes(current, next)
|
|
89
|
+
if (shouldPreserveElementChildren(current, next)) return
|
|
90
|
+
diffElementChildren(current, next, context)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Type mismatch: replace
|
|
95
|
+
let parent = current.parentNode
|
|
96
|
+
if (parent) parent.replaceChild(next, current)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function diffElementAttributes(current: Element, next: Element): void {
|
|
100
|
+
let prevAttrNames = current.getAttributeNames()
|
|
101
|
+
let nextAttrNames = next.getAttributeNames()
|
|
102
|
+
|
|
103
|
+
let nextNameSet = new Set(nextAttrNames)
|
|
104
|
+
|
|
105
|
+
// Removals
|
|
106
|
+
for (let name of prevAttrNames) {
|
|
107
|
+
if (!nextNameSet.has(name)) {
|
|
108
|
+
if (shouldPreserveLiveAttribute(current, next, name)) continue
|
|
109
|
+
current.removeAttribute(name)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Additions/updates
|
|
114
|
+
for (let name of nextAttrNames) {
|
|
115
|
+
let prevVal = current.getAttribute(name)
|
|
116
|
+
let nextVal = next.getAttribute(name)
|
|
117
|
+
if (prevVal !== nextVal) {
|
|
118
|
+
if (shouldPreserveLiveAttribute(current, next, name)) continue
|
|
119
|
+
current.setAttribute(name, nextVal == null ? '' : String(nextVal))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function shouldPreserveLiveAttribute(current: Element, next: Element, name: string): boolean {
|
|
125
|
+
if (name === 'open') {
|
|
126
|
+
if (current instanceof HTMLDetailsElement && next instanceof HTMLDetailsElement) {
|
|
127
|
+
return current.open !== next.open
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (current instanceof HTMLDialogElement && next instanceof HTMLDialogElement) {
|
|
131
|
+
return current.open !== next.open
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (name === 'checked') {
|
|
136
|
+
if (current instanceof HTMLInputElement && next instanceof HTMLInputElement) {
|
|
137
|
+
return current.checked !== next.checked
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (name === 'value') {
|
|
142
|
+
if (
|
|
143
|
+
current instanceof HTMLInputElement &&
|
|
144
|
+
next instanceof HTMLInputElement &&
|
|
145
|
+
shouldPreserveInputValue(current)
|
|
146
|
+
) {
|
|
147
|
+
return current.value !== next.value
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (name === 'selected') {
|
|
152
|
+
if (current instanceof HTMLOptionElement && next instanceof HTMLOptionElement) {
|
|
153
|
+
return current.selected !== next.selected
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (name === 'popover') {
|
|
158
|
+
return isPopoverOpen(current) !== isPopoverOpen(next)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function shouldPreserveElementChildren(current: Element, next: Element): boolean {
|
|
165
|
+
if (current instanceof HTMLTextAreaElement && next instanceof HTMLTextAreaElement) {
|
|
166
|
+
return current.value !== next.value
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function shouldPreserveInputValue(input: HTMLInputElement): boolean {
|
|
173
|
+
return (
|
|
174
|
+
input.type !== 'button' &&
|
|
175
|
+
input.type !== 'checkbox' &&
|
|
176
|
+
input.type !== 'hidden' &&
|
|
177
|
+
input.type !== 'image' &&
|
|
178
|
+
input.type !== 'radio' &&
|
|
179
|
+
input.type !== 'reset' &&
|
|
180
|
+
input.type !== 'submit'
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isPopoverOpen(element: Element): boolean {
|
|
185
|
+
try {
|
|
186
|
+
return element.matches(':popover-open')
|
|
187
|
+
} catch {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function diffElementChildren(current: Element, next: Element, context: FrameContext): void {
|
|
193
|
+
let currentChildren = Array.from(current.childNodes)
|
|
194
|
+
let nextChildren = Array.from(next.childNodes)
|
|
195
|
+
|
|
196
|
+
// Keyed map by data-key for current children
|
|
197
|
+
let keyToIndex = new Map<string, number>()
|
|
198
|
+
for (let i = 0; i < currentChildren.length; i++) {
|
|
199
|
+
let node = currentChildren[i]
|
|
200
|
+
if (isElement(node)) {
|
|
201
|
+
let key = node.getAttribute('data-key')
|
|
202
|
+
if (key != null) keyToIndex.set(key, i)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let used = new Array<boolean>(currentChildren.length).fill(false)
|
|
207
|
+
let matchIndexForNext = new Array<number>(nextChildren.length).fill(-1)
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < nextChildren.length; i++) {
|
|
210
|
+
let nextChild = nextChildren[i]
|
|
211
|
+
let matchIndex = -1
|
|
212
|
+
|
|
213
|
+
if (isElement(nextChild)) {
|
|
214
|
+
let key = nextChild.getAttribute('data-key')
|
|
215
|
+
if (key != null && keyToIndex.has(key)) {
|
|
216
|
+
let idx = keyToIndex.get(key)!
|
|
217
|
+
if (!used[idx]) matchIndex = idx
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (matchIndex === -1) {
|
|
222
|
+
let candidateIndex = i
|
|
223
|
+
if (
|
|
224
|
+
candidateIndex < currentChildren.length &&
|
|
225
|
+
!used[candidateIndex] &&
|
|
226
|
+
nodeTypesComparable(currentChildren[candidateIndex], nextChild)
|
|
227
|
+
) {
|
|
228
|
+
matchIndex = candidateIndex
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (matchIndex !== -1) used[matchIndex] = true
|
|
233
|
+
matchIndexForNext[i] = matchIndex
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Forward pass: update matched, collect committed
|
|
237
|
+
let committed: Array<Node | undefined> = new Array(nextChildren.length)
|
|
238
|
+
for (let i = 0; i < nextChildren.length; i++) {
|
|
239
|
+
let mi = matchIndexForNext[i]
|
|
240
|
+
if (mi !== -1) {
|
|
241
|
+
let curChild = currentChildren[mi]
|
|
242
|
+
let cursor = diffNode(curChild, nextChildren[i], context)
|
|
243
|
+
if (cursor) {
|
|
244
|
+
// Fast-forward across a hydrated virtual root region.
|
|
245
|
+
let nextEndIdx = nextChildren.indexOf(cursor)
|
|
246
|
+
let currEndIdx = findHydrationEndIndex(currentChildren, mi)
|
|
247
|
+
|
|
248
|
+
// Adjacent hydration regions can pre-match into the next region and leave
|
|
249
|
+
// an orphaned `<!-- /rmx:h -->` behind. Clear those stale matches first.
|
|
250
|
+
for (let j = i + 1; j <= nextEndIdx; j++) {
|
|
251
|
+
let matchedIndex = matchIndexForNext[j]
|
|
252
|
+
if (matchedIndex > currEndIdx) {
|
|
253
|
+
used[matchedIndex] = false
|
|
254
|
+
}
|
|
255
|
+
matchIndexForNext[j] = -1
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Mark the entire current region as used to avoid removals.
|
|
259
|
+
for (let k = mi; k <= currEndIdx; k++) used[k] = true
|
|
260
|
+
|
|
261
|
+
// Preserve both boundary markers in committed; skip interior in reorder pass.
|
|
262
|
+
committed[i] = curChild // start marker
|
|
263
|
+
committed[nextEndIdx] = currentChildren[currEndIdx] // end marker
|
|
264
|
+
for (let j = i + 1; j < nextEndIdx; j++) committed[j] = undefined
|
|
265
|
+
|
|
266
|
+
// Jump to end of region.
|
|
267
|
+
i = nextEndIdx
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
committed[i] = curChild
|
|
271
|
+
} else {
|
|
272
|
+
committed[i] = nextChildren[i]
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Backward pass: reorder via inserts while avoiding redundant moves
|
|
277
|
+
let anchor: Node | undefined = undefined
|
|
278
|
+
for (let i = committed.length - 1; i >= 0; i--) {
|
|
279
|
+
let node = committed[i]
|
|
280
|
+
if (!node) continue
|
|
281
|
+
|
|
282
|
+
// Use only an anchor that is actually a child of the current parent
|
|
283
|
+
let ref = anchor && anchor.parentNode === current ? anchor : null
|
|
284
|
+
|
|
285
|
+
// Do not move hydration boundary markers; keep region stable.
|
|
286
|
+
// If a boundary marker is new, ensure it is inserted before using it as an anchor.
|
|
287
|
+
if (isVirtualRootStartMarker(node) || isVirtualRootEndMarker(node)) {
|
|
288
|
+
if (node.parentNode !== current) {
|
|
289
|
+
current.insertBefore(node, ref)
|
|
290
|
+
}
|
|
291
|
+
anchor = node
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (node.parentNode === current) {
|
|
296
|
+
// Node already in parent: move only if its nextSibling is not the desired ref.
|
|
297
|
+
let targetNext = ref
|
|
298
|
+
let alreadyInPlace =
|
|
299
|
+
(targetNext === null && node.nextSibling === null) || node.nextSibling === targetNext
|
|
300
|
+
if (!alreadyInPlace) {
|
|
301
|
+
current.insertBefore(node, targetNext)
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
// New node: insert relative to a valid ref or append
|
|
305
|
+
current.insertBefore(node, ref)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Advance anchor only after the node is placed in the correct parent
|
|
309
|
+
if (node.parentNode === current) {
|
|
310
|
+
anchor = node
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Remove any current children not used
|
|
315
|
+
for (let i = 0; i < currentChildren.length; i++) {
|
|
316
|
+
if (!used[i]) {
|
|
317
|
+
let nodeToRemove = currentChildren[i]
|
|
318
|
+
disposeRemovedSubFrames(nodeToRemove, context)
|
|
319
|
+
current.removeChild(currentChildren[i])
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function nodeTypesComparable(a: Node, b: Node): boolean {
|
|
325
|
+
if (isTextNode(a) && isTextNode(b)) return true
|
|
326
|
+
if (isElement(a) && isElement(b)) return a.tagName === b.tagName
|
|
327
|
+
if (isVirtualRootStartMarker(a) && isVirtualRootStartMarker(b)) return true
|
|
328
|
+
if (isVirtualRootEndMarker(a) && isVirtualRootEndMarker(b)) return true
|
|
329
|
+
if (isCommentNode(a) && isCommentNode(b)) return true
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isHydrationEndComment(node: Node): node is Comment {
|
|
334
|
+
return isCommentNode(node) && node.data.trim() === '/rmx:h'
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function findHydrationEndMarker(start: Comment): Comment {
|
|
338
|
+
let node: Node | null = start.nextSibling
|
|
339
|
+
let depth = 1
|
|
340
|
+
|
|
341
|
+
while (node) {
|
|
342
|
+
if (isCommentNode(node)) {
|
|
343
|
+
if (isVirtualRootStartMarker(node)) depth++
|
|
344
|
+
if (isVirtualRootEndMarker(node)) {
|
|
345
|
+
depth--
|
|
346
|
+
if (depth === 0) return node
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
node = node.nextSibling
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
throw new Error('Hydration end marker not found')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function findHydrationEndIndex(nodes: Node[], startIdx: number): number {
|
|
356
|
+
for (let j = startIdx + 1; j < nodes.length; j++) {
|
|
357
|
+
if (isHydrationEndComment(nodes[j])) return j
|
|
358
|
+
}
|
|
359
|
+
return startIdx
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function isTextNode(node: Node): node is Text {
|
|
363
|
+
return node.nodeType === Node.TEXT_NODE
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function isElement(node: Node): node is Element {
|
|
367
|
+
return node.nodeType === Node.ELEMENT_NODE
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function isCommentNode(node: Node): node is Comment {
|
|
371
|
+
return node.nodeType === Node.COMMENT_NODE
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function isFrameStartMarker(node: Node): node is Comment {
|
|
375
|
+
return node instanceof Comment && node.data.trim().startsWith('rmx:f:')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
379
|
+
let stack: Node[] = [node]
|
|
380
|
+
while (stack.length > 0) {
|
|
381
|
+
let next = stack.pop()
|
|
382
|
+
if (!next) continue
|
|
383
|
+
|
|
384
|
+
if (isFrameStartMarker(next)) {
|
|
385
|
+
let subFrame = context.frameInstances.get(next)
|
|
386
|
+
if (subFrame) {
|
|
387
|
+
subFrame.dispose()
|
|
388
|
+
context.frameInstances.delete(next)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
for (let child of Array.from(next.childNodes)) {
|
|
393
|
+
stack.push(child)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isVirtualRootStartMarker(node: Node): node is Comment {
|
|
399
|
+
return isCommentNode(node) && node.data.trim().startsWith('rmx:h:')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function isVirtualRootEndMarker(node: Node): node is Comment {
|
|
403
|
+
return isCommentNode(node) && node.data.trim() === '/rmx:h'
|
|
404
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { invariant } from './invariant.ts'
|
|
2
|
+
import { createStyleManager, normalizeCssValue } from '../style/index.ts'
|
|
3
|
+
import type { StyleManager } from '../style/index.ts'
|
|
4
|
+
import type { ElementProps } from './jsx.ts'
|
|
5
|
+
import { normalizeSvgAttribute } from './svg-attributes.ts'
|
|
6
|
+
|
|
7
|
+
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
8
|
+
|
|
9
|
+
let globalStyleManager =
|
|
10
|
+
typeof window !== 'undefined' ? createStyleManager() : (null as unknown as StyleManager)
|
|
11
|
+
|
|
12
|
+
export { type StyleManager }
|
|
13
|
+
|
|
14
|
+
export let defaultStyleManager: StyleManager = globalStyleManager
|
|
15
|
+
|
|
16
|
+
// Preact excludes certain attributes from the property path due to browser quirks
|
|
17
|
+
const ATTRIBUTE_FALLBACK_NAMES = new Set([
|
|
18
|
+
'width',
|
|
19
|
+
'height',
|
|
20
|
+
'href',
|
|
21
|
+
'list',
|
|
22
|
+
'form',
|
|
23
|
+
'tabIndex',
|
|
24
|
+
'download',
|
|
25
|
+
'rowSpan',
|
|
26
|
+
'colSpan',
|
|
27
|
+
'role',
|
|
28
|
+
'popover',
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
// Determine if we should use the property path for a given name.
|
|
32
|
+
// Also acts as a type guard to allow bracket assignment without casts.
|
|
33
|
+
function canUseProperty(
|
|
34
|
+
dom: Element,
|
|
35
|
+
name: string,
|
|
36
|
+
isSvg: boolean,
|
|
37
|
+
): dom is Element & Record<string, unknown> {
|
|
38
|
+
if (isSvg) return false
|
|
39
|
+
if (ATTRIBUTE_FALLBACK_NAMES.has(name)) return false
|
|
40
|
+
return name in dom
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isFrameworkProp(name: string): boolean {
|
|
44
|
+
return (
|
|
45
|
+
name === 'children' ||
|
|
46
|
+
name === 'mix' ||
|
|
47
|
+
name === 'key' ||
|
|
48
|
+
name === 'animate' ||
|
|
49
|
+
name === 'innerHTML'
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// TODO: would rather actually diff el.style object directly instead of writing
|
|
54
|
+
// to the style attribute
|
|
55
|
+
function serializeStyleObject(style: Record<string, unknown>): string {
|
|
56
|
+
let parts: string[] = []
|
|
57
|
+
for (let [key, value] of Object.entries(style)) {
|
|
58
|
+
if (value == null) continue
|
|
59
|
+
if (typeof value === 'boolean') continue
|
|
60
|
+
if (typeof value === 'number' && !Number.isFinite(value)) continue
|
|
61
|
+
|
|
62
|
+
let cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
|
|
63
|
+
|
|
64
|
+
let cssValue = Array.isArray(value)
|
|
65
|
+
? (value as unknown[]).join(', ')
|
|
66
|
+
: normalizeCssValue(key, value)
|
|
67
|
+
|
|
68
|
+
parts.push(`${cssKey}: ${cssValue};`)
|
|
69
|
+
}
|
|
70
|
+
return parts.join(' ')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizePropName(name: string, isSvg: boolean): { ns?: string; attr: string } {
|
|
74
|
+
// aria-/data- pass through
|
|
75
|
+
if (name.startsWith('aria-') || name.startsWith('data-')) return { attr: name }
|
|
76
|
+
|
|
77
|
+
// DOM property -> HTML mappings
|
|
78
|
+
if (name === 'className') return { attr: 'class' }
|
|
79
|
+
if (!isSvg) {
|
|
80
|
+
if (name === 'htmlFor') return { attr: 'for' }
|
|
81
|
+
if (name === 'tabIndex') return { attr: 'tabindex' }
|
|
82
|
+
if (name === 'acceptCharset') return { attr: 'accept-charset' }
|
|
83
|
+
if (name === 'httpEquiv') return { attr: 'http-equiv' }
|
|
84
|
+
return { attr: name.toLowerCase() }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return normalizeSvgAttribute(name)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toLocalName(attrName: string): string {
|
|
91
|
+
let separatorIndex = attrName.indexOf(':')
|
|
92
|
+
if (separatorIndex === -1) return attrName
|
|
93
|
+
return attrName.slice(separatorIndex + 1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function clearRuntimePropertyOnRemoval(dom: Element & Record<string, unknown>, name: string): void {
|
|
97
|
+
try {
|
|
98
|
+
if (name === 'value' || name === 'defaultValue') {
|
|
99
|
+
dom[name] = ''
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (name === 'checked' || name === 'defaultChecked' || name === 'selected') {
|
|
103
|
+
dom[name] = false
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
if (name === 'selectedIndex') {
|
|
107
|
+
dom[name] = -1
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getMergedClassName(props: ElementProps): string | undefined {
|
|
113
|
+
let classAttr = typeof props.class === 'string' ? props.class : ''
|
|
114
|
+
let className = typeof props.className === 'string' ? props.className : ''
|
|
115
|
+
let mergedClassName =
|
|
116
|
+
classAttr && className ? `${classAttr} ${className}` : classAttr || className
|
|
117
|
+
return mergedClassName || undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function diffHostProps(curr: ElementProps, next: ElementProps, dom: Element) {
|
|
121
|
+
let isSvg = dom.namespaceURI === SVG_NS
|
|
122
|
+
let currClassName = getMergedClassName(curr)
|
|
123
|
+
let nextClassName = getMergedClassName(next)
|
|
124
|
+
|
|
125
|
+
if (currClassName !== nextClassName) {
|
|
126
|
+
if (nextClassName) {
|
|
127
|
+
dom.setAttribute('class', nextClassName)
|
|
128
|
+
} else {
|
|
129
|
+
dom.removeAttribute('class')
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Removals
|
|
134
|
+
for (let name in curr) {
|
|
135
|
+
if (isFrameworkProp(name)) continue
|
|
136
|
+
if (name === 'class' || name === 'className') continue
|
|
137
|
+
if (!(name in next) || next[name] == null) {
|
|
138
|
+
// Clear runtime state for form-like props where removing the attribute is not enough.
|
|
139
|
+
if (canUseProperty(dom, name, isSvg)) {
|
|
140
|
+
clearRuntimePropertyOnRemoval(dom, name)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let { ns, attr } = normalizePropName(name, isSvg)
|
|
144
|
+
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
145
|
+
else dom.removeAttribute(attr)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Additions/updates
|
|
150
|
+
for (let name in next) {
|
|
151
|
+
if (isFrameworkProp(name)) continue
|
|
152
|
+
if (name === 'class' || name === 'className') continue
|
|
153
|
+
let nextValue = next[name]
|
|
154
|
+
if (nextValue == null) continue
|
|
155
|
+
let prevValue = curr[name]
|
|
156
|
+
if (prevValue !== nextValue) {
|
|
157
|
+
let { ns, attr } = normalizePropName(name, isSvg)
|
|
158
|
+
|
|
159
|
+
// Object style: serialize to attribute for now
|
|
160
|
+
if (
|
|
161
|
+
attr === 'style' &&
|
|
162
|
+
typeof nextValue === 'object' &&
|
|
163
|
+
nextValue &&
|
|
164
|
+
!Array.isArray(nextValue)
|
|
165
|
+
) {
|
|
166
|
+
dom.setAttribute('style', serializeStyleObject(nextValue))
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Prefer property assignment when possible (HTML only, not SVG)
|
|
171
|
+
if (canUseProperty(dom, name, isSvg)) {
|
|
172
|
+
try {
|
|
173
|
+
dom[name] = nextValue == null ? '' : nextValue
|
|
174
|
+
continue
|
|
175
|
+
} catch {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Attribute path
|
|
179
|
+
if (typeof nextValue === 'function') {
|
|
180
|
+
// Never serialize functions as attribute values
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let isAriaOrData = name.startsWith('aria-') || name.startsWith('data-')
|
|
185
|
+
if (nextValue != null && (nextValue !== false || isAriaOrData)) {
|
|
186
|
+
// Special-case popover: true => presence only
|
|
187
|
+
let attrValue = name === 'popover' && nextValue === true ? '' : String(nextValue)
|
|
188
|
+
if (ns) dom.setAttributeNS(ns, attr, attrValue)
|
|
189
|
+
else dom.setAttribute(attr, attrValue)
|
|
190
|
+
} else {
|
|
191
|
+
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
192
|
+
else dom.removeAttribute(attr)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Reset the global style state. For testing only - not exported from index.ts.
|
|
200
|
+
*/
|
|
201
|
+
export function resetStyleState() {
|
|
202
|
+
invariant(
|
|
203
|
+
typeof window !== 'undefined',
|
|
204
|
+
'resetStyleState() is only available in a browser environment',
|
|
205
|
+
)
|
|
206
|
+
globalStyleManager.dispose()
|
|
207
|
+
globalStyleManager = createStyleManager()
|
|
208
|
+
defaultStyleManager = globalStyleManager
|
|
209
|
+
}
|