@remix-run/ui 0.0.0 → 0.1.1
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 +920 -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 +32 -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 +28 -0
- package/dist/runtime/navigation.js +107 -0
- package/dist/runtime/navigation.js.map +1 -0
- package/dist/runtime/reconcile.d.ts +11 -0
- package/dist/runtime/reconcile.js +1262 -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 +195 -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 +46 -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 +1011 -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 +124 -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 +1218 -0
- package/src/runtime/invariant.ts +31 -0
- package/src/runtime/jsx.ts +397 -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 +148 -0
- package/src/runtime/reconcile.ts +1757 -0
- package/src/runtime/render.ts +101 -0
- package/src/runtime/run.ts +100 -0
- package/src/runtime/scheduler.ts +269 -0
- package/src/runtime/svg-attributes.ts +133 -0
- package/src/runtime/to-vnode.ts +51 -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 +1347 -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/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,1262 @@
|
|
|
1
|
+
import { createComponent, Frame } from "./component.js";
|
|
2
|
+
import { createFrame } from "./frame.js";
|
|
3
|
+
import { createRangeRoot } from "./vdom.js";
|
|
4
|
+
import { isCommittedComponentNode, isComponentNode, isCommittedHostNode, isCommittedTextNode, isFragmentNode, isHostNode, isTextNode, findContextFromAncestry, } from "./vnode.js";
|
|
5
|
+
import { invariant } from "./invariant.js";
|
|
6
|
+
import { diffHostProps } from "./diff-props.js";
|
|
7
|
+
import { skipComments, logHydrationMismatch } from "./client-entries.js";
|
|
8
|
+
import { toVNode } from "./to-vnode.js";
|
|
9
|
+
import { bindMixinRuntime, cancelPendingMixinRemoval, dispatchMixinBeforeUpdate, dispatchMixinCommit, getMixinRuntimeSignal, prepareMixinRemoval, resolveMixedProps, teardownMixins, } from "./mixins/mixin.js";
|
|
10
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
11
|
+
// Internal diffing flags (modeled after Preact)
|
|
12
|
+
const INSERT_VNODE = 1 << 0;
|
|
13
|
+
const MATCHED = 1 << 1;
|
|
14
|
+
let idCounter = 0;
|
|
15
|
+
let persistedRemovalToken = 0;
|
|
16
|
+
const persistedMixinNodes = new Set();
|
|
17
|
+
let activeSchedulerUpdateParents;
|
|
18
|
+
// Compute SVG context for a node based on its parent and type.
|
|
19
|
+
// Returns true if the node is within an SVG subtree, false otherwise.
|
|
20
|
+
function getSvgContext(vParent, nodeType) {
|
|
21
|
+
// Only host elements (strings) can affect SVG context
|
|
22
|
+
if (typeof nodeType === 'string') {
|
|
23
|
+
// svg element creates SVG context
|
|
24
|
+
if (nodeType === 'svg')
|
|
25
|
+
return true;
|
|
26
|
+
// foreignObject switches back to HTML context
|
|
27
|
+
if (nodeType === 'foreignObject')
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// Otherwise inherit from parent
|
|
31
|
+
return vParent._svg ?? false;
|
|
32
|
+
}
|
|
33
|
+
function getHostProps(node) {
|
|
34
|
+
return node._mixedProps ?? node.props;
|
|
35
|
+
}
|
|
36
|
+
function markNodePersistedByMixins(node, domParent, token) {
|
|
37
|
+
node._persistedByMixins = true;
|
|
38
|
+
node._persistedParentByMixins = domParent;
|
|
39
|
+
node._persistedRemovalToken = token;
|
|
40
|
+
persistedMixinNodes.add(node);
|
|
41
|
+
bindMixinRuntime(node._mixState, undefined);
|
|
42
|
+
}
|
|
43
|
+
function unmarkNodePersistedByMixins(node) {
|
|
44
|
+
node._persistedByMixins = false;
|
|
45
|
+
node._persistedParentByMixins = undefined;
|
|
46
|
+
node._persistedRemovalToken = undefined;
|
|
47
|
+
persistedMixinNodes.delete(node);
|
|
48
|
+
}
|
|
49
|
+
function findMatchingPersistedMixinNode(type, key, domParent) {
|
|
50
|
+
if (key == null)
|
|
51
|
+
return null;
|
|
52
|
+
for (let node of persistedMixinNodes) {
|
|
53
|
+
if (node._persistedParentByMixins !== domParent)
|
|
54
|
+
continue;
|
|
55
|
+
if (node.type !== type)
|
|
56
|
+
continue;
|
|
57
|
+
if (node.key !== key)
|
|
58
|
+
continue;
|
|
59
|
+
return node;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function shouldRestoreControlledReflectionOnInput(node, state) {
|
|
64
|
+
// Some controls dispatch `input` before `change` for the same interaction.
|
|
65
|
+
// When checked/value state is typically handled on `change`, restoring on the
|
|
66
|
+
// earlier `input` can race and clobber the value observed by app handlers.
|
|
67
|
+
if (state.hasControlledChecked)
|
|
68
|
+
return false;
|
|
69
|
+
if (node.type === 'select')
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function ensureControlledReflection(node, scheduler) {
|
|
74
|
+
let existing = node._controlledState;
|
|
75
|
+
if (existing)
|
|
76
|
+
return existing;
|
|
77
|
+
let state = {
|
|
78
|
+
disposed: false,
|
|
79
|
+
listenersAttached: false,
|
|
80
|
+
pendingRestoreVersion: 0,
|
|
81
|
+
managesValue: false,
|
|
82
|
+
managesChecked: false,
|
|
83
|
+
hasControlledValue: false,
|
|
84
|
+
controlledValue: undefined,
|
|
85
|
+
hasControlledChecked: false,
|
|
86
|
+
controlledChecked: undefined,
|
|
87
|
+
onInput: () => {
|
|
88
|
+
if (!shouldRestoreControlledReflectionOnInput(node, state))
|
|
89
|
+
return;
|
|
90
|
+
scheduleControlledRestore(node, state);
|
|
91
|
+
},
|
|
92
|
+
onChange: () => {
|
|
93
|
+
scheduleControlledRestore(node, state);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
node._controlledState = state;
|
|
97
|
+
scheduler.enqueueTasks([
|
|
98
|
+
() => {
|
|
99
|
+
if (state.disposed)
|
|
100
|
+
return;
|
|
101
|
+
node._dom.addEventListener('input', state.onInput);
|
|
102
|
+
node._dom.addEventListener('change', state.onChange);
|
|
103
|
+
state.listenersAttached = true;
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
function syncControlledReflection(node, props) {
|
|
109
|
+
let state = node._controlledState;
|
|
110
|
+
if (!state || state.disposed)
|
|
111
|
+
return;
|
|
112
|
+
state.managesValue = canManageValue(node.type, node._dom);
|
|
113
|
+
state.managesChecked = canReflectProperty(node._dom, 'checked');
|
|
114
|
+
state.hasControlledValue = state.managesValue && hasControlledValueProp(props);
|
|
115
|
+
state.controlledValue = props.value;
|
|
116
|
+
state.hasControlledChecked = state.managesChecked && hasControlledCheckedProp(props);
|
|
117
|
+
state.controlledChecked = props.checked;
|
|
118
|
+
state.pendingRestoreVersion++;
|
|
119
|
+
}
|
|
120
|
+
function shouldTrackControlledReflection(props) {
|
|
121
|
+
return hasControlledValueProp(props) || hasControlledCheckedProp(props);
|
|
122
|
+
}
|
|
123
|
+
function scheduleControlledRestore(node, state) {
|
|
124
|
+
if (state.disposed)
|
|
125
|
+
return;
|
|
126
|
+
let version = ++state.pendingRestoreVersion;
|
|
127
|
+
queueMicrotask(() => {
|
|
128
|
+
if (state.disposed)
|
|
129
|
+
return;
|
|
130
|
+
if (state.pendingRestoreVersion !== version)
|
|
131
|
+
return;
|
|
132
|
+
restoreControlledReflections(node, state);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function restoreControlledReflections(node, state) {
|
|
136
|
+
let element = node._dom;
|
|
137
|
+
if (state.hasControlledValue && readDomProp(element, 'value') !== state.controlledValue) {
|
|
138
|
+
setPropertyReflection(element, 'value', state.controlledValue);
|
|
139
|
+
}
|
|
140
|
+
if (state.hasControlledChecked && readDomProp(element, 'checked') !== state.controlledChecked) {
|
|
141
|
+
setPropertyReflection(element, 'checked', state.controlledChecked);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function teardownControlledReflection(node) {
|
|
145
|
+
let state = node._controlledState;
|
|
146
|
+
if (!state)
|
|
147
|
+
return;
|
|
148
|
+
state.disposed = true;
|
|
149
|
+
state.pendingRestoreVersion++;
|
|
150
|
+
if (state.listenersAttached) {
|
|
151
|
+
node._dom.removeEventListener('input', state.onInput);
|
|
152
|
+
node._dom.removeEventListener('change', state.onChange);
|
|
153
|
+
state.listenersAttached = false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function canManageValue(type, element) {
|
|
157
|
+
if (type === 'progress')
|
|
158
|
+
return false;
|
|
159
|
+
return canReflectProperty(element, 'value');
|
|
160
|
+
}
|
|
161
|
+
function hasControlledValueProp(props) {
|
|
162
|
+
return 'value' in props && props.value !== undefined;
|
|
163
|
+
}
|
|
164
|
+
function hasControlledCheckedProp(props) {
|
|
165
|
+
return 'checked' in props && props.checked !== undefined;
|
|
166
|
+
}
|
|
167
|
+
function canReflectProperty(element, key) {
|
|
168
|
+
return key in element && !key.includes('-');
|
|
169
|
+
}
|
|
170
|
+
function readDomProp(element, key) {
|
|
171
|
+
if (!canReflectProperty(element, key))
|
|
172
|
+
return undefined;
|
|
173
|
+
return element[key];
|
|
174
|
+
}
|
|
175
|
+
function setPropertyReflection(element, key, value) {
|
|
176
|
+
if (!canReflectProperty(element, key))
|
|
177
|
+
return;
|
|
178
|
+
element[key] = value == null ? '' : value;
|
|
179
|
+
}
|
|
180
|
+
function resolveNodeMixProps(node, frame, scheduler, state) {
|
|
181
|
+
let mix = node.props.mix;
|
|
182
|
+
if (state == null && (mix == null || (Array.isArray(mix) && mix.length === 0))) {
|
|
183
|
+
node._mixState = undefined;
|
|
184
|
+
node._mixedProps = node.props;
|
|
185
|
+
return node.props;
|
|
186
|
+
}
|
|
187
|
+
let resolved = resolveMixedProps({
|
|
188
|
+
hostType: node.type,
|
|
189
|
+
frame,
|
|
190
|
+
scheduler,
|
|
191
|
+
getContext: (type) => {
|
|
192
|
+
if (typeof type !== 'function') {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return findContextFromAncestry(node, type);
|
|
196
|
+
},
|
|
197
|
+
props: node.props,
|
|
198
|
+
state,
|
|
199
|
+
});
|
|
200
|
+
node._mixState = resolved.state;
|
|
201
|
+
node._mixedProps = resolved.props;
|
|
202
|
+
return resolved.props;
|
|
203
|
+
}
|
|
204
|
+
function enqueueMixinBindingUpdate(done) {
|
|
205
|
+
let node = this.target;
|
|
206
|
+
let state = node._mixState;
|
|
207
|
+
this.scheduler.enqueueWork([
|
|
208
|
+
() => {
|
|
209
|
+
if (state?.aborted) {
|
|
210
|
+
done(getMixinRuntimeSignal(state));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
dispatchMixinBeforeUpdate(state);
|
|
214
|
+
let prevProps = getHostProps(node);
|
|
215
|
+
let nextProps = resolveNodeMixProps(node, this.frame, this.scheduler, state);
|
|
216
|
+
diffHostProps(prevProps, nextProps, this.node);
|
|
217
|
+
dispatchMixinCommit(state);
|
|
218
|
+
done(state ? getMixinRuntimeSignal(state) : AbortSignal.abort());
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
}
|
|
222
|
+
function bindNodeMixRuntime(node, frame, scheduler, styles, reclaimed = false, parent) {
|
|
223
|
+
let state = node._mixState;
|
|
224
|
+
bindMixinRuntime(state, {
|
|
225
|
+
node: node._dom,
|
|
226
|
+
parent: parent ?? node._dom.parentNode,
|
|
227
|
+
key: node.key,
|
|
228
|
+
target: node,
|
|
229
|
+
frame,
|
|
230
|
+
scheduler,
|
|
231
|
+
enqueueUpdate: enqueueMixinBindingUpdate,
|
|
232
|
+
}, { dispatchReclaimed: reclaimed });
|
|
233
|
+
}
|
|
234
|
+
function isHeadHostNode(node) {
|
|
235
|
+
if (node.type === 'head')
|
|
236
|
+
return true;
|
|
237
|
+
if (node.type.length !== 4)
|
|
238
|
+
return false;
|
|
239
|
+
return node.type.toLowerCase() === 'head';
|
|
240
|
+
}
|
|
241
|
+
function getDocumentHead(domParent) {
|
|
242
|
+
if (domParent instanceof Document) {
|
|
243
|
+
return domParent.head;
|
|
244
|
+
}
|
|
245
|
+
if (domParent instanceof Node) {
|
|
246
|
+
return domParent.ownerDocument?.head ?? null;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
export function diffVNodes(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, rootCursor) {
|
|
251
|
+
next._parent = vParent; // set parent for initial render context lookups
|
|
252
|
+
next._svg = getSvgContext(vParent, next.type);
|
|
253
|
+
// new
|
|
254
|
+
if (curr === null) {
|
|
255
|
+
return insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, rootCursor);
|
|
256
|
+
}
|
|
257
|
+
if (curr.type !== next.type) {
|
|
258
|
+
replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor);
|
|
259
|
+
return rootCursor;
|
|
260
|
+
}
|
|
261
|
+
if (isCommittedTextNode(curr) && isTextNode(next)) {
|
|
262
|
+
diffText(curr, next, vParent);
|
|
263
|
+
return rootCursor;
|
|
264
|
+
}
|
|
265
|
+
if (isCommittedHostNode(curr) && isHostNode(next)) {
|
|
266
|
+
diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget);
|
|
267
|
+
return rootCursor;
|
|
268
|
+
}
|
|
269
|
+
if (isCommittedComponentNode(curr) && isComponentNode(next)) {
|
|
270
|
+
diffComponent(curr, next, frame, scheduler, styles, domParent, vParent, rootTarget);
|
|
271
|
+
return rootCursor;
|
|
272
|
+
}
|
|
273
|
+
if (isFragmentNode(curr) && isFragmentNode(next)) {
|
|
274
|
+
diffChildren(curr._children, next._children, domParent, frame, scheduler, styles, vParent, rootTarget, undefined, anchor);
|
|
275
|
+
return rootCursor;
|
|
276
|
+
}
|
|
277
|
+
if (curr.type === Frame && next.type === Frame) {
|
|
278
|
+
diffFrame(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor);
|
|
279
|
+
return rootCursor;
|
|
280
|
+
}
|
|
281
|
+
invariant(false, 'Unexpected diff case');
|
|
282
|
+
}
|
|
283
|
+
function replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor) {
|
|
284
|
+
let currAnchor = findFirstDomAnchor(curr);
|
|
285
|
+
if (currAnchor && currAnchor.parentNode === domParent) {
|
|
286
|
+
let replacementAnchor = document.createComment('rmx:replace');
|
|
287
|
+
domParent.insertBefore(replacementAnchor, currAnchor);
|
|
288
|
+
try {
|
|
289
|
+
remove(curr, domParent, scheduler, styles);
|
|
290
|
+
insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor);
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
replacementAnchor.parentNode?.removeChild(replacementAnchor);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
let replacementAnchor = findNextSiblingDomAnchor(curr, vParent) ?? anchor;
|
|
298
|
+
remove(curr, domParent, scheduler, styles);
|
|
299
|
+
insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor);
|
|
300
|
+
}
|
|
301
|
+
function diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget) {
|
|
302
|
+
let mixState = curr._mixState;
|
|
303
|
+
let currProps = getHostProps(curr);
|
|
304
|
+
let nextProps = resolveNodeMixProps(next, frame, scheduler, mixState);
|
|
305
|
+
let nextMixState = next._mixState;
|
|
306
|
+
let shouldDispatchMixinLifecycle = (nextMixState?.runners.length ?? 0) > 0 && shouldDispatchInlineMixinLifecycle(curr._dom);
|
|
307
|
+
if (shouldDispatchMixinLifecycle) {
|
|
308
|
+
dispatchMixinBeforeUpdate(nextMixState);
|
|
309
|
+
}
|
|
310
|
+
// Handle innerHTML prop BEFORE diffChildren to avoid clearing children
|
|
311
|
+
if (nextProps.innerHTML != null) {
|
|
312
|
+
// innerHTML is set, update it if changed
|
|
313
|
+
if (currProps.innerHTML !== nextProps.innerHTML) {
|
|
314
|
+
curr._dom.innerHTML = nextProps.innerHTML;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else if (currProps.innerHTML != null) {
|
|
318
|
+
// innerHTML was removed, clear it before adding children
|
|
319
|
+
curr._dom.innerHTML = '';
|
|
320
|
+
}
|
|
321
|
+
diffChildren(curr._children, next._children, curr._dom, frame, scheduler, styles, next, rootTarget);
|
|
322
|
+
diffHostProps(currProps, nextProps, curr._dom);
|
|
323
|
+
next._dom = curr._dom;
|
|
324
|
+
next._parent = vParent;
|
|
325
|
+
next._controller = curr._controller;
|
|
326
|
+
next._controlledState = curr._controlledState;
|
|
327
|
+
if (next._controlledState || shouldTrackControlledReflection(nextProps)) {
|
|
328
|
+
ensureControlledReflection(next, scheduler);
|
|
329
|
+
syncControlledReflection(next, nextProps);
|
|
330
|
+
}
|
|
331
|
+
bindNodeMixRuntime(next, frame, scheduler, styles);
|
|
332
|
+
if (shouldDispatchMixinLifecycle) {
|
|
333
|
+
scheduler.enqueueCommitPhase([() => dispatchMixinCommit(nextMixState)]);
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
function setupHostNode(node, dom, scheduler) {
|
|
338
|
+
node._dom = dom;
|
|
339
|
+
let props = getHostProps(node);
|
|
340
|
+
let committedNode = node;
|
|
341
|
+
if (shouldTrackControlledReflection(props)) {
|
|
342
|
+
ensureControlledReflection(committedNode, scheduler);
|
|
343
|
+
syncControlledReflection(committedNode, props);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function diffText(curr, next, vParent) {
|
|
347
|
+
if (curr._text !== next._text) {
|
|
348
|
+
curr._dom.textContent = next._text;
|
|
349
|
+
}
|
|
350
|
+
next._dom = curr._dom;
|
|
351
|
+
next._parent = vParent;
|
|
352
|
+
}
|
|
353
|
+
function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor) {
|
|
354
|
+
node._parent = vParent; // set parent for initial render context lookups
|
|
355
|
+
node._svg = getSvgContext(vParent, node.type);
|
|
356
|
+
// Stop hydration if cursor has reached the anchor (end boundary)
|
|
357
|
+
// Check BEFORE skipComments to prevent escaping range root markers
|
|
358
|
+
if (cursor && anchor && cursor === anchor) {
|
|
359
|
+
cursor = null;
|
|
360
|
+
}
|
|
361
|
+
cursor =
|
|
362
|
+
node.type === Frame
|
|
363
|
+
? skipCommentsExceptFrameStart(cursor ?? null)
|
|
364
|
+
: skipComments(cursor ?? null);
|
|
365
|
+
// Also check after skipComments in case we skipped past the anchor
|
|
366
|
+
if (cursor && anchor && cursor === anchor) {
|
|
367
|
+
cursor = null;
|
|
368
|
+
}
|
|
369
|
+
let doInsert = anchor
|
|
370
|
+
? (dom) => domParent.insertBefore(dom, anchor)
|
|
371
|
+
: (dom) => domParent.appendChild(dom);
|
|
372
|
+
if (isTextNode(node)) {
|
|
373
|
+
if (cursor instanceof Text) {
|
|
374
|
+
node._parent = vParent;
|
|
375
|
+
// Handle text node consolidation: server renders adjacent text as single node
|
|
376
|
+
// e.g., <span>Hello {world}</span> → server: "Hello world", client: ["Hello ", "world"]
|
|
377
|
+
if (cursor.data !== node._text) {
|
|
378
|
+
if (cursor.data.startsWith(node._text) && node._text.length < cursor.data.length) {
|
|
379
|
+
// Consolidation case: split the text node at the boundary
|
|
380
|
+
// cursor becomes the first part (node._text), remainder is returned for next vnode
|
|
381
|
+
let remainder = cursor.splitText(node._text.length);
|
|
382
|
+
node._dom = cursor;
|
|
383
|
+
return remainder;
|
|
384
|
+
}
|
|
385
|
+
// Genuine mismatch - correct it
|
|
386
|
+
logHydrationMismatch('text mismatch', cursor.data, node._text);
|
|
387
|
+
cursor.data = node._text;
|
|
388
|
+
}
|
|
389
|
+
node._dom = cursor;
|
|
390
|
+
return cursor.nextSibling;
|
|
391
|
+
}
|
|
392
|
+
let dom = document.createTextNode(node._text);
|
|
393
|
+
node._dom = dom;
|
|
394
|
+
node._parent = vParent;
|
|
395
|
+
doInsert(dom);
|
|
396
|
+
return cursor;
|
|
397
|
+
}
|
|
398
|
+
if (isHostNode(node)) {
|
|
399
|
+
let hostProps = resolveNodeMixProps(node, frame, scheduler);
|
|
400
|
+
if (isHeadHostNode(node)) {
|
|
401
|
+
let targetHead = getDocumentHead(domParent);
|
|
402
|
+
if (targetHead) {
|
|
403
|
+
let childCursor = cursor;
|
|
404
|
+
if (cursor instanceof Element && cursor.tagName.toLowerCase() === 'head') {
|
|
405
|
+
childCursor = cursor.firstChild;
|
|
406
|
+
let nextCursor = cursor.nextSibling;
|
|
407
|
+
if (cursor !== targetHead) {
|
|
408
|
+
while (cursor.firstChild) {
|
|
409
|
+
targetHead.appendChild(cursor.firstChild);
|
|
410
|
+
}
|
|
411
|
+
cursor.remove();
|
|
412
|
+
}
|
|
413
|
+
cursor = nextCursor;
|
|
414
|
+
}
|
|
415
|
+
// Render explicit <head> children directly into document.head.
|
|
416
|
+
diffChildren(null, node._children, targetHead, frame, scheduler, styles, node, rootTarget, childCursor);
|
|
417
|
+
diffHostProps({}, hostProps, targetHead);
|
|
418
|
+
setupHostNode(node, targetHead, scheduler);
|
|
419
|
+
bindNodeMixRuntime(node, frame, scheduler, styles);
|
|
420
|
+
return cursor;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Check for matching mixin-persisted node that can be reclaimed
|
|
424
|
+
let persistedNode = findMatchingPersistedMixinNode(node.type, node.key, domParent);
|
|
425
|
+
if (persistedNode) {
|
|
426
|
+
reclaimPersistedMixinNode(persistedNode, node, frame, scheduler, styles, vParent, rootTarget);
|
|
427
|
+
return cursor;
|
|
428
|
+
}
|
|
429
|
+
if (cursor instanceof Element) {
|
|
430
|
+
// SVG elements have case-sensitive tag names (e.g. linearGradient, clipPath)
|
|
431
|
+
// HTML elements are case-insensitive, so we lowercase for comparison
|
|
432
|
+
let cursorTag = node._svg ? cursor.tagName : cursor.tagName.toLowerCase();
|
|
433
|
+
if (cursorTag === node.type) {
|
|
434
|
+
let nextCursor = cursor.nextSibling;
|
|
435
|
+
diffHostProps({}, hostProps, cursor);
|
|
436
|
+
// Handle innerHTML prop
|
|
437
|
+
if (hostProps.innerHTML != null) {
|
|
438
|
+
cursor.innerHTML = hostProps.innerHTML;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
let childCursor = cursor.firstChild;
|
|
442
|
+
// Ignore excess nodes - browser extensions may inject content
|
|
443
|
+
diffChildren(null, node._children, cursor, frame, scheduler, styles, node, rootTarget, childCursor);
|
|
444
|
+
}
|
|
445
|
+
setupHostNode(node, cursor, scheduler);
|
|
446
|
+
bindNodeMixRuntime(node, frame, scheduler, styles);
|
|
447
|
+
return nextCursor;
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
// Type mismatch - try single-advance retry to handle browser extension injections
|
|
451
|
+
// at the start of containers. Skip this node and try the next sibling once.
|
|
452
|
+
let nextSibling = skipComments(cursor.nextSibling);
|
|
453
|
+
if (nextSibling instanceof Element) {
|
|
454
|
+
let nextTag = node._svg ? nextSibling.tagName : nextSibling.tagName.toLowerCase();
|
|
455
|
+
if (nextTag === node.type) {
|
|
456
|
+
let nextCursor = nextSibling.nextSibling;
|
|
457
|
+
// Found a match after skipping - adopt it and leave skipped node in place
|
|
458
|
+
diffHostProps({}, hostProps, nextSibling);
|
|
459
|
+
if (hostProps.innerHTML != null) {
|
|
460
|
+
nextSibling.innerHTML = hostProps.innerHTML;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
let childCursor = nextSibling.firstChild;
|
|
464
|
+
diffChildren(null, node._children, nextSibling, frame, scheduler, styles, node, rootTarget, childCursor);
|
|
465
|
+
}
|
|
466
|
+
setupHostNode(node, nextSibling, scheduler);
|
|
467
|
+
bindNodeMixRuntime(node, frame, scheduler, styles);
|
|
468
|
+
return nextCursor;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Retry failed - log mismatch and create new element (don't remove mismatched nodes)
|
|
472
|
+
logHydrationMismatch('tag', cursorTag, node.type);
|
|
473
|
+
cursor = undefined; // stop hydration for this tree
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
let dom = node._svg
|
|
477
|
+
? document.createElementNS(SVG_NS, node.type)
|
|
478
|
+
: document.createElement(node.type);
|
|
479
|
+
diffHostProps({}, hostProps, dom);
|
|
480
|
+
// Handle innerHTML prop
|
|
481
|
+
if (hostProps.innerHTML != null) {
|
|
482
|
+
dom.innerHTML = hostProps.innerHTML;
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
diffChildren(null, node._children, dom, frame, scheduler, styles, node, rootTarget);
|
|
486
|
+
}
|
|
487
|
+
setupHostNode(node, dom, scheduler);
|
|
488
|
+
bindNodeMixRuntime(node, frame, scheduler, styles, false, domParent);
|
|
489
|
+
doInsert(dom);
|
|
490
|
+
return cursor;
|
|
491
|
+
}
|
|
492
|
+
if (isFragmentNode(node)) {
|
|
493
|
+
// Insert fragment children in order before the same anchor
|
|
494
|
+
for (let child of node._children) {
|
|
495
|
+
cursor = insert(child, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
|
|
496
|
+
}
|
|
497
|
+
return cursor;
|
|
498
|
+
}
|
|
499
|
+
if (isComponentNode(node)) {
|
|
500
|
+
return diffComponent(null, node, frame, scheduler, styles, domParent, vParent, rootTarget, anchor, cursor);
|
|
501
|
+
}
|
|
502
|
+
if (node.type === Frame) {
|
|
503
|
+
return insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
|
|
504
|
+
}
|
|
505
|
+
invariant(false, 'Unexpected node type');
|
|
506
|
+
}
|
|
507
|
+
function diffFrame(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor) {
|
|
508
|
+
let currSrc = getFrameSrc(curr);
|
|
509
|
+
let nextSrc = getFrameSrc(next);
|
|
510
|
+
let currName = getFrameName(curr);
|
|
511
|
+
let nextName = getFrameName(next);
|
|
512
|
+
if (currName !== nextName) {
|
|
513
|
+
let replaceAnchor = curr._rangeEnd?.nextSibling ?? anchor;
|
|
514
|
+
remove(curr, domParent, scheduler, styles);
|
|
515
|
+
insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replaceAnchor);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// If the frame hasn't resolved yet, preserve existing cancel/remount behavior
|
|
519
|
+
// so pending streams from the old src cannot take over the new src.
|
|
520
|
+
if (currSrc !== nextSrc && !curr._frameResolved) {
|
|
521
|
+
let replaceAnchor = curr._rangeEnd?.nextSibling ?? anchor;
|
|
522
|
+
remove(curr, domParent, scheduler, styles);
|
|
523
|
+
insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replaceAnchor);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
next._rangeStart = curr._rangeStart;
|
|
527
|
+
next._rangeEnd = curr._rangeEnd;
|
|
528
|
+
next._frameInstance = curr._frameInstance;
|
|
529
|
+
next._frameFallbackRoot = curr._frameFallbackRoot;
|
|
530
|
+
next._frameResolveToken = curr._frameResolveToken;
|
|
531
|
+
next._frameResolved = curr._frameResolved;
|
|
532
|
+
next._parent = vParent;
|
|
533
|
+
if (currSrc !== nextSrc) {
|
|
534
|
+
let frameInstance = next._frameInstance;
|
|
535
|
+
if (frameInstance) {
|
|
536
|
+
frameInstance.handle.src = nextSrc;
|
|
537
|
+
}
|
|
538
|
+
let runtime = getFrameRuntime(frame);
|
|
539
|
+
if (runtime) {
|
|
540
|
+
resolveClientFrame(next, runtime);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (!next._frameResolved && next._frameFallbackRoot) {
|
|
544
|
+
next._frameFallbackRoot.render(next.props?.fallback ?? null);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor) {
|
|
548
|
+
let runtime = getFrameRuntime(frame);
|
|
549
|
+
if (!runtime || runtime.canResolveFrames === false) {
|
|
550
|
+
throw new Error('Cannot render <Frame /> without frame runtime. Use run() or pass frameInit to createRoot/createRangeRoot.');
|
|
551
|
+
}
|
|
552
|
+
// Hydration path: adopt server-rendered frame markers and reuse the existing
|
|
553
|
+
// frame instance created during createSubFrames().
|
|
554
|
+
if (isFrameStartComment(cursor)) {
|
|
555
|
+
let start = cursor;
|
|
556
|
+
let end = findFrameEndComment(start);
|
|
557
|
+
if (end) {
|
|
558
|
+
node._rangeStart = start;
|
|
559
|
+
node._rangeEnd = end;
|
|
560
|
+
node._parent = vParent;
|
|
561
|
+
node._frameResolveToken = 0;
|
|
562
|
+
node._frameResolveController = undefined;
|
|
563
|
+
node._frameFallbackRoot = undefined;
|
|
564
|
+
node._frameResolved = true;
|
|
565
|
+
let frameId = getFrameIdFromComment(start);
|
|
566
|
+
let marker = frameId ? runtime.data.f?.[frameId] : undefined;
|
|
567
|
+
let src = marker?.src ?? getFrameSrc(node);
|
|
568
|
+
let instance = runtime.frameInstances.get(start);
|
|
569
|
+
if (!instance) {
|
|
570
|
+
instance = createFrame([start, end], {
|
|
571
|
+
name: getFrameName(node),
|
|
572
|
+
src,
|
|
573
|
+
marker: frameId && marker ? { ...marker, id: frameId } : undefined,
|
|
574
|
+
errorTarget: runtime.errorTarget,
|
|
575
|
+
loadModule: runtime.loadModule,
|
|
576
|
+
resolveFrame: runtime.resolveFrame,
|
|
577
|
+
pendingClientEntries: runtime.pendingClientEntries,
|
|
578
|
+
scheduler: runtime.scheduler,
|
|
579
|
+
data: runtime.data,
|
|
580
|
+
moduleCache: runtime.moduleCache,
|
|
581
|
+
moduleLoads: runtime.moduleLoads,
|
|
582
|
+
frameInstances: runtime.frameInstances,
|
|
583
|
+
namedFrames: runtime.namedFrames,
|
|
584
|
+
});
|
|
585
|
+
runtime.frameInstances.set(start, instance);
|
|
586
|
+
}
|
|
587
|
+
node._frameInstance = instance;
|
|
588
|
+
return end.nextSibling;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
let start = document.createComment(` rmx:f:${randomFrameId()} `);
|
|
592
|
+
let end = document.createComment(' /rmx:f ');
|
|
593
|
+
let doInsert = anchor
|
|
594
|
+
? (dom) => domParent.insertBefore(dom, anchor)
|
|
595
|
+
: (dom) => domParent.appendChild(dom);
|
|
596
|
+
doInsert(start);
|
|
597
|
+
doInsert(end);
|
|
598
|
+
node._rangeStart = start;
|
|
599
|
+
node._rangeEnd = end;
|
|
600
|
+
node._parent = vParent;
|
|
601
|
+
let fallbackRoot = createRangeRoot([start, end], {
|
|
602
|
+
frame,
|
|
603
|
+
styleManager: styles,
|
|
604
|
+
});
|
|
605
|
+
fallbackRoot.render(node.props?.fallback ?? null);
|
|
606
|
+
node._frameFallbackRoot = fallbackRoot;
|
|
607
|
+
node._frameResolved = false;
|
|
608
|
+
node._frameResolveToken = 0;
|
|
609
|
+
let instance = createFrame([start, end], {
|
|
610
|
+
name: getFrameName(node),
|
|
611
|
+
src: getFrameSrc(node),
|
|
612
|
+
errorTarget: runtime.errorTarget,
|
|
613
|
+
loadModule: runtime.loadModule,
|
|
614
|
+
resolveFrame: runtime.resolveFrame,
|
|
615
|
+
pendingClientEntries: runtime.pendingClientEntries,
|
|
616
|
+
scheduler: runtime.scheduler,
|
|
617
|
+
data: runtime.data,
|
|
618
|
+
moduleCache: runtime.moduleCache,
|
|
619
|
+
moduleLoads: runtime.moduleLoads,
|
|
620
|
+
frameInstances: runtime.frameInstances,
|
|
621
|
+
namedFrames: runtime.namedFrames,
|
|
622
|
+
});
|
|
623
|
+
node._frameInstance = instance;
|
|
624
|
+
runtime.frameInstances.set(start, instance);
|
|
625
|
+
resolveClientFrame(node, runtime);
|
|
626
|
+
return cursor;
|
|
627
|
+
}
|
|
628
|
+
function resolveClientFrame(node, runtime) {
|
|
629
|
+
let frameSrc = getFrameSrc(node);
|
|
630
|
+
let instance = node._frameInstance;
|
|
631
|
+
if (!instance)
|
|
632
|
+
return;
|
|
633
|
+
let token = (node._frameResolveToken ?? 0) + 1;
|
|
634
|
+
node._frameResolveToken = token;
|
|
635
|
+
node._frameResolveController?.abort();
|
|
636
|
+
let resolveController = new AbortController();
|
|
637
|
+
node._frameResolveController = resolveController;
|
|
638
|
+
Promise.resolve(runtime.resolveFrame(frameSrc, resolveController.signal))
|
|
639
|
+
.then(async (content) => {
|
|
640
|
+
if (node._frameResolveToken !== token || resolveController.signal.aborted)
|
|
641
|
+
return;
|
|
642
|
+
node._frameFallbackRoot?.dispose();
|
|
643
|
+
node._frameFallbackRoot = undefined;
|
|
644
|
+
let nextContent = asAbortableFrameContent(content, resolveController.signal);
|
|
645
|
+
await instance.render(nextContent, { signal: resolveController.signal });
|
|
646
|
+
if (node._frameResolveToken !== token || resolveController.signal.aborted)
|
|
647
|
+
return;
|
|
648
|
+
node._frameResolved = true;
|
|
649
|
+
})
|
|
650
|
+
.catch(() => { })
|
|
651
|
+
.finally(() => {
|
|
652
|
+
if (node._frameResolveController === resolveController) {
|
|
653
|
+
node._frameResolveController = undefined;
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function disposeFrameResources(node) {
|
|
658
|
+
node._frameResolveToken = (node._frameResolveToken ?? 0) + 1;
|
|
659
|
+
node._frameResolveController?.abort();
|
|
660
|
+
node._frameResolveController = undefined;
|
|
661
|
+
node._frameFallbackRoot?.dispose();
|
|
662
|
+
node._frameFallbackRoot = undefined;
|
|
663
|
+
let frameInstance = node._frameInstance;
|
|
664
|
+
if (frameInstance) {
|
|
665
|
+
frameInstance.dispose();
|
|
666
|
+
node._frameInstance = undefined;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function asAbortableFrameContent(content, signal) {
|
|
670
|
+
if (!(content instanceof ReadableStream))
|
|
671
|
+
return content;
|
|
672
|
+
return createAbortableReadableStream(content, signal);
|
|
673
|
+
}
|
|
674
|
+
function createAbortableReadableStream(source, signal) {
|
|
675
|
+
let reader = source.getReader();
|
|
676
|
+
let aborted = false;
|
|
677
|
+
let onAbort = () => {
|
|
678
|
+
aborted = true;
|
|
679
|
+
void reader.cancel(signal.reason);
|
|
680
|
+
};
|
|
681
|
+
if (signal.aborted)
|
|
682
|
+
onAbort();
|
|
683
|
+
else
|
|
684
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
685
|
+
return new ReadableStream({
|
|
686
|
+
async pull(controller) {
|
|
687
|
+
if (aborted) {
|
|
688
|
+
controller.close();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
let removeAbortReadListener;
|
|
692
|
+
let abortRead = new Promise((resolve) => {
|
|
693
|
+
if (signal.aborted) {
|
|
694
|
+
resolve({ done: true, value: undefined });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
let onAbortRead = () => {
|
|
698
|
+
resolve({ done: true, value: undefined });
|
|
699
|
+
};
|
|
700
|
+
removeAbortReadListener = () => signal.removeEventListener('abort', onAbortRead);
|
|
701
|
+
signal.addEventListener('abort', onAbortRead, { once: true });
|
|
702
|
+
});
|
|
703
|
+
let { done, value } = await Promise.race([reader.read(), abortRead]);
|
|
704
|
+
removeAbortReadListener?.();
|
|
705
|
+
if (done) {
|
|
706
|
+
controller.close();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
controller.enqueue(value);
|
|
710
|
+
},
|
|
711
|
+
cancel(reason) {
|
|
712
|
+
signal.removeEventListener('abort', onAbort);
|
|
713
|
+
return reader.cancel(reason);
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
function removeFrameDomRange(node, domParent) {
|
|
718
|
+
let start = node._rangeStart;
|
|
719
|
+
let end = node._rangeEnd;
|
|
720
|
+
if (!(start instanceof Comment) || !(end instanceof Comment))
|
|
721
|
+
return;
|
|
722
|
+
let cursor = start;
|
|
723
|
+
while (cursor) {
|
|
724
|
+
let nextSibling = cursor.nextSibling;
|
|
725
|
+
if (cursor.parentNode === domParent) {
|
|
726
|
+
domParent.removeChild(cursor);
|
|
727
|
+
}
|
|
728
|
+
if (cursor === end)
|
|
729
|
+
break;
|
|
730
|
+
cursor = nextSibling;
|
|
731
|
+
}
|
|
732
|
+
node._rangeStart = undefined;
|
|
733
|
+
node._rangeEnd = undefined;
|
|
734
|
+
}
|
|
735
|
+
function getFrameRuntime(frame) {
|
|
736
|
+
return frame.$runtime;
|
|
737
|
+
}
|
|
738
|
+
function getFrameSrc(node) {
|
|
739
|
+
let src = node.props?.src;
|
|
740
|
+
invariant(typeof src === 'string' && src.length > 0, '<Frame /> requires a src prop');
|
|
741
|
+
return src;
|
|
742
|
+
}
|
|
743
|
+
function getFrameName(node) {
|
|
744
|
+
let name = node.props?.name;
|
|
745
|
+
return typeof name === 'string' && name.length > 0 ? name : undefined;
|
|
746
|
+
}
|
|
747
|
+
function randomFrameId() {
|
|
748
|
+
return `f${crypto.randomUUID().slice(0, 8)}`;
|
|
749
|
+
}
|
|
750
|
+
function skipCommentsExceptFrameStart(cursor) {
|
|
751
|
+
while (cursor && cursor.nodeType === Node.COMMENT_NODE) {
|
|
752
|
+
if (isFrameStartComment(cursor))
|
|
753
|
+
return cursor;
|
|
754
|
+
cursor = cursor.nextSibling;
|
|
755
|
+
}
|
|
756
|
+
return cursor;
|
|
757
|
+
}
|
|
758
|
+
function isFrameStartComment(node) {
|
|
759
|
+
return node instanceof Comment && node.data.trim().startsWith('rmx:f:');
|
|
760
|
+
}
|
|
761
|
+
function isFrameEndComment(node) {
|
|
762
|
+
return node instanceof Comment && node.data.trim() === '/rmx:f';
|
|
763
|
+
}
|
|
764
|
+
function getFrameIdFromComment(comment) {
|
|
765
|
+
let text = comment.data.trim();
|
|
766
|
+
if (!text.startsWith('rmx:f:'))
|
|
767
|
+
return undefined;
|
|
768
|
+
return text.slice('rmx:f:'.length);
|
|
769
|
+
}
|
|
770
|
+
function findFrameEndComment(start) {
|
|
771
|
+
let depth = 1;
|
|
772
|
+
let node = start.nextSibling;
|
|
773
|
+
while (node) {
|
|
774
|
+
if (isFrameStartComment(node))
|
|
775
|
+
depth++;
|
|
776
|
+
else if (isFrameEndComment(node)) {
|
|
777
|
+
depth--;
|
|
778
|
+
if (depth === 0)
|
|
779
|
+
return node;
|
|
780
|
+
}
|
|
781
|
+
node = node.nextSibling;
|
|
782
|
+
}
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
export function renderComponent(handle, currContent, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor) {
|
|
786
|
+
let [element, tasks] = handle.render(next.props);
|
|
787
|
+
let content = toVNode(element);
|
|
788
|
+
let newCursor = diffVNodes(currContent, content, domParent, frame, scheduler, styles, next, rootTarget, anchor, cursor);
|
|
789
|
+
next._content = content;
|
|
790
|
+
next._handle = handle;
|
|
791
|
+
next._parent = vParent;
|
|
792
|
+
let committed = next;
|
|
793
|
+
handle.setScheduleUpdate(() => {
|
|
794
|
+
scheduler.enqueue(committed, domParent);
|
|
795
|
+
});
|
|
796
|
+
scheduler.enqueueTasks(tasks);
|
|
797
|
+
return newCursor;
|
|
798
|
+
}
|
|
799
|
+
function diffComponent(curr, next, frame, scheduler, styles, domParent, vParent, rootTarget, anchor, cursor) {
|
|
800
|
+
if (curr === null) {
|
|
801
|
+
let componentId = vParent._pendingHydrationComponentId;
|
|
802
|
+
if (componentId) {
|
|
803
|
+
vParent._pendingHydrationComponentId = undefined;
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
componentId = `c${++idCounter}`;
|
|
807
|
+
}
|
|
808
|
+
next._handle = createComponent({
|
|
809
|
+
id: componentId,
|
|
810
|
+
frame,
|
|
811
|
+
type: next.type,
|
|
812
|
+
getContext: (type) => findContextFromAncestry(vParent, type),
|
|
813
|
+
getFrameByName(name) {
|
|
814
|
+
let runtime = getFrameRuntime(frame);
|
|
815
|
+
return runtime?.namedFrames.get(name);
|
|
816
|
+
},
|
|
817
|
+
getTopFrame() {
|
|
818
|
+
let runtime = getFrameRuntime(frame);
|
|
819
|
+
return runtime?.topFrame;
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
return renderComponent(next._handle, null, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor);
|
|
823
|
+
}
|
|
824
|
+
next._handle = curr._handle;
|
|
825
|
+
let { _content, _handle } = curr;
|
|
826
|
+
return renderComponent(_handle, _content, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor);
|
|
827
|
+
}
|
|
828
|
+
// Cleanup without DOM removal - used for descendants when parent DOM node is removed
|
|
829
|
+
function cleanupDescendants(node, scheduler, styles) {
|
|
830
|
+
if (isCommittedTextNode(node)) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (isCommittedHostNode(node)) {
|
|
834
|
+
for (let child of node._children) {
|
|
835
|
+
cleanupDescendants(child, scheduler, styles);
|
|
836
|
+
}
|
|
837
|
+
teardownMixins(node._mixState);
|
|
838
|
+
teardownControlledReflection(node);
|
|
839
|
+
if (node._controller)
|
|
840
|
+
node._controller.abort();
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (isFragmentNode(node)) {
|
|
844
|
+
for (let child of node._children) {
|
|
845
|
+
cleanupDescendants(child, scheduler, styles);
|
|
846
|
+
}
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (isCommittedComponentNode(node)) {
|
|
850
|
+
cleanupDescendants(node._content, scheduler, styles);
|
|
851
|
+
let tasks = node._handle.remove();
|
|
852
|
+
scheduler.enqueueTasks(tasks);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (node.type === Frame) {
|
|
856
|
+
disposeFrameResources(node);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
export function remove(node, domParent, scheduler, styles) {
|
|
861
|
+
if (isCommittedTextNode(node)) {
|
|
862
|
+
node._dom.parentNode?.removeChild(node._dom);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (isCommittedHostNode(node)) {
|
|
866
|
+
if (node._persistedByMixins)
|
|
867
|
+
return;
|
|
868
|
+
let persistedRemoval = prepareMixinRemoval(node._mixState);
|
|
869
|
+
if (persistedRemoval) {
|
|
870
|
+
let token = ++persistedRemovalToken;
|
|
871
|
+
markNodePersistedByMixins(node, domParent, token);
|
|
872
|
+
void persistedRemoval
|
|
873
|
+
.catch(() => { })
|
|
874
|
+
.finally(() => {
|
|
875
|
+
if (!node._persistedByMixins)
|
|
876
|
+
return;
|
|
877
|
+
if (node._persistedRemovalToken !== token)
|
|
878
|
+
return;
|
|
879
|
+
unmarkNodePersistedByMixins(node);
|
|
880
|
+
performHostNodeRemoval(node, domParent, scheduler, styles);
|
|
881
|
+
});
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
performHostNodeRemoval(node, domParent, scheduler, styles);
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (isFragmentNode(node)) {
|
|
888
|
+
for (let child of node._children) {
|
|
889
|
+
remove(child, domParent, scheduler, styles);
|
|
890
|
+
}
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (isCommittedComponentNode(node)) {
|
|
894
|
+
remove(node._content, domParent, scheduler, styles);
|
|
895
|
+
let tasks = node._handle.remove();
|
|
896
|
+
scheduler.enqueueTasks(tasks);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (node.type === Frame) {
|
|
900
|
+
disposeFrameResources(node);
|
|
901
|
+
removeFrameDomRange(node, domParent);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
// Actually remove a host node from DOM and clean up
|
|
906
|
+
function performHostNodeRemoval(node, domParent, scheduler, styles) {
|
|
907
|
+
if (isHeadHostNode(node)) {
|
|
908
|
+
for (let child of node._children) {
|
|
909
|
+
remove(child, node._dom, scheduler, styles);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
// Clean up all descendants first (before removing DOM subtree)
|
|
914
|
+
for (let child of node._children) {
|
|
915
|
+
cleanupDescendants(child, scheduler, styles);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
teardownMixins(node._mixState);
|
|
919
|
+
teardownControlledReflection(node);
|
|
920
|
+
// Never remove the real document.head node when reconciling a <head> vnode.
|
|
921
|
+
if (!isHeadHostNode(node)) {
|
|
922
|
+
node._dom.parentNode?.removeChild(node._dom);
|
|
923
|
+
}
|
|
924
|
+
if (node._controller)
|
|
925
|
+
node._controller.abort();
|
|
926
|
+
}
|
|
927
|
+
function diffChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor) {
|
|
928
|
+
let nextLength = next.length;
|
|
929
|
+
// Warn when duplicate keys are present among siblings. Duplicate keys are
|
|
930
|
+
// still processed (last one wins), but they make keyed diffing ambiguous.
|
|
931
|
+
let hasKeys = false;
|
|
932
|
+
let seenKeys;
|
|
933
|
+
let duplicateKeys;
|
|
934
|
+
for (let i = 0; i < nextLength; i++) {
|
|
935
|
+
let node = next[i];
|
|
936
|
+
if (node && node.key != null) {
|
|
937
|
+
hasKeys = true;
|
|
938
|
+
if (!seenKeys) {
|
|
939
|
+
seenKeys = new Set([node.key]);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (seenKeys.has(node.key)) {
|
|
943
|
+
if (!duplicateKeys) {
|
|
944
|
+
duplicateKeys = new Set();
|
|
945
|
+
}
|
|
946
|
+
duplicateKeys.add(node.key);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
seenKeys.add(node.key);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (duplicateKeys?.size) {
|
|
954
|
+
let quotedKeys = Array.from(duplicateKeys, (key) => `"${key}"`);
|
|
955
|
+
console.warn(`Duplicate keys detected in siblings: ${quotedKeys.join(', ')}. Keys should be unique.`);
|
|
956
|
+
}
|
|
957
|
+
// Initial mount / hydration: delegate to insert() for each child so that
|
|
958
|
+
// hydration cursors and creation logic remain centralized there.
|
|
959
|
+
if (curr === null) {
|
|
960
|
+
for (let node of next) {
|
|
961
|
+
cursor = insert(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
|
|
962
|
+
}
|
|
963
|
+
vParent._children = next;
|
|
964
|
+
return cursor;
|
|
965
|
+
}
|
|
966
|
+
let currLength = curr.length;
|
|
967
|
+
// Detect if any keys are present in the new children. If not, we can fall
|
|
968
|
+
// back to the simpler index-based diff which is cheaper and matches
|
|
969
|
+
// pre-existing behavior.
|
|
970
|
+
if (!hasKeys) {
|
|
971
|
+
for (let i = 0; i < nextLength; i++) {
|
|
972
|
+
let currentNode = i < currLength ? curr[i] : null;
|
|
973
|
+
diffVNodes(currentNode, next[i], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
|
|
974
|
+
}
|
|
975
|
+
if (currLength > nextLength) {
|
|
976
|
+
for (let i = nextLength; i < currLength; i++) {
|
|
977
|
+
let node = curr[i];
|
|
978
|
+
if (node)
|
|
979
|
+
remove(node, domParent, scheduler, styles);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
vParent._children = next;
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (currLength === nextLength) {
|
|
986
|
+
let canDiffByPosition = true;
|
|
987
|
+
for (let i = 0; i < nextLength; i++) {
|
|
988
|
+
let currentNode = curr[i];
|
|
989
|
+
let nextNode = next[i];
|
|
990
|
+
if (!currentNode || currentNode.key !== nextNode.key || currentNode.type !== nextNode.type) {
|
|
991
|
+
canDiffByPosition = false;
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (canDiffByPosition) {
|
|
996
|
+
for (let i = 0; i < nextLength; i++) {
|
|
997
|
+
diffVNodes(curr[i], next[i], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
|
|
998
|
+
}
|
|
999
|
+
vParent._children = next;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
// --- O(n + m) keyed diff with Map-based lookup ------------------------------
|
|
1004
|
+
let oldChildren = curr;
|
|
1005
|
+
let oldChildrenLength = currLength;
|
|
1006
|
+
let remainingOldChildren = oldChildrenLength;
|
|
1007
|
+
// Build key → index map for O(1) lookup: O(m)
|
|
1008
|
+
let oldKeyMap = new Map();
|
|
1009
|
+
for (let i = 0; i < oldChildrenLength; i++) {
|
|
1010
|
+
let c = oldChildren[i];
|
|
1011
|
+
if (c) {
|
|
1012
|
+
c._flags = 0;
|
|
1013
|
+
if (c.key != null) {
|
|
1014
|
+
oldKeyMap.set(c.key, i);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
let skew = 0;
|
|
1019
|
+
let newChildren = new Array(nextLength);
|
|
1020
|
+
// First pass: match new children to old ones using Map lookup: O(n)
|
|
1021
|
+
for (let i = 0; i < nextLength; i++) {
|
|
1022
|
+
let childVNode = next[i];
|
|
1023
|
+
if (!childVNode) {
|
|
1024
|
+
newChildren[i] = childVNode;
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
newChildren[i] = childVNode;
|
|
1028
|
+
childVNode._parent = vParent;
|
|
1029
|
+
let skewedIndex = i + skew;
|
|
1030
|
+
let matchingIndex = -1;
|
|
1031
|
+
let key = childVNode.key;
|
|
1032
|
+
let type = childVNode.type;
|
|
1033
|
+
if (key != null) {
|
|
1034
|
+
// O(1) Map lookup for keyed children
|
|
1035
|
+
let mapIndex = oldKeyMap.get(key);
|
|
1036
|
+
if (mapIndex !== undefined) {
|
|
1037
|
+
let candidate = oldChildren[mapIndex];
|
|
1038
|
+
let candidateFlags = candidate?._flags ?? 0;
|
|
1039
|
+
if (candidate && (candidateFlags & MATCHED) === 0 && candidate.type === type) {
|
|
1040
|
+
matchingIndex = mapIndex;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
// Non-keyed children use positional identity only - no searching
|
|
1046
|
+
let searchVNode = oldChildren[skewedIndex];
|
|
1047
|
+
let searchFlags = searchVNode?._flags ?? 0;
|
|
1048
|
+
let available = searchVNode != null && (searchFlags & MATCHED) === 0;
|
|
1049
|
+
if (available && searchVNode.key == null && type === searchVNode.type) {
|
|
1050
|
+
matchingIndex = skewedIndex;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
childVNode._index = matchingIndex;
|
|
1054
|
+
let matchedOldVNode = null;
|
|
1055
|
+
if (matchingIndex !== -1) {
|
|
1056
|
+
matchedOldVNode = oldChildren[matchingIndex];
|
|
1057
|
+
remainingOldChildren--;
|
|
1058
|
+
if (matchedOldVNode) {
|
|
1059
|
+
matchedOldVNode._flags = (matchedOldVNode._flags ?? 0) | MATCHED;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
// Determine whether this is a mount vs move and mark INSERT_VNODE
|
|
1063
|
+
let oldDom = matchedOldVNode && findFirstDomAnchor(matchedOldVNode);
|
|
1064
|
+
let isMounting = !matchedOldVNode || !oldDom;
|
|
1065
|
+
if (isMounting) {
|
|
1066
|
+
if (matchingIndex === -1) {
|
|
1067
|
+
// Adjust skew similar to Preact when lengths differ
|
|
1068
|
+
if (nextLength > oldChildrenLength) {
|
|
1069
|
+
skew--;
|
|
1070
|
+
}
|
|
1071
|
+
else if (nextLength < oldChildrenLength) {
|
|
1072
|
+
skew++;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
childVNode._flags = (childVNode._flags ?? 0) | INSERT_VNODE;
|
|
1076
|
+
}
|
|
1077
|
+
else if (matchingIndex !== i + skew) {
|
|
1078
|
+
if (matchingIndex === i + skew - 1) {
|
|
1079
|
+
skew--;
|
|
1080
|
+
}
|
|
1081
|
+
else if (matchingIndex === i + skew + 1) {
|
|
1082
|
+
skew++;
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
if (matchingIndex > i + skew)
|
|
1086
|
+
skew--;
|
|
1087
|
+
else
|
|
1088
|
+
skew++;
|
|
1089
|
+
childVNode._flags = (childVNode._flags ?? 0) | INSERT_VNODE;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
// Unmount any old children that weren't matched
|
|
1094
|
+
if (remainingOldChildren) {
|
|
1095
|
+
for (let i = 0; i < oldChildrenLength; i++) {
|
|
1096
|
+
let oldVNode = oldChildren[i];
|
|
1097
|
+
if (oldVNode && ((oldVNode._flags ?? 0) & MATCHED) === 0) {
|
|
1098
|
+
remove(oldVNode, domParent, scheduler, styles);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Second pass: diff matched pairs and place/move DOM nodes in the correct
|
|
1103
|
+
// order, similar to Preact's diffChildren + insert.
|
|
1104
|
+
vParent._children = newChildren;
|
|
1105
|
+
let lastPlaced = null;
|
|
1106
|
+
for (let i = 0; i < nextLength; i++) {
|
|
1107
|
+
let childVNode = newChildren[i];
|
|
1108
|
+
if (!childVNode)
|
|
1109
|
+
continue;
|
|
1110
|
+
let idx = childVNode._index ?? -1;
|
|
1111
|
+
let oldVNode = idx >= 0 ? oldChildren[idx] : null;
|
|
1112
|
+
diffVNodes(oldVNode, childVNode, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
|
|
1113
|
+
let shouldPlace = (childVNode._flags ?? 0) & INSERT_VNODE;
|
|
1114
|
+
let firstDom = findFirstDomAnchor(childVNode);
|
|
1115
|
+
let lastDom = firstDom ? findLastDomAnchor(childVNode) : null;
|
|
1116
|
+
if (shouldPlace && firstDom && lastDom && firstDom.parentNode === domParent) {
|
|
1117
|
+
let target;
|
|
1118
|
+
if (lastPlaced === null) {
|
|
1119
|
+
if (vParent._rangeStart && vParent._rangeStart.parentNode === domParent) {
|
|
1120
|
+
target = vParent._rangeStart.nextSibling;
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
target = domParent.firstChild;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
target = lastPlaced.nextSibling;
|
|
1128
|
+
}
|
|
1129
|
+
if (target === null && anchor)
|
|
1130
|
+
target = anchor;
|
|
1131
|
+
// If target lies within the range we're moving, skip the move.
|
|
1132
|
+
if (target && domRangeContainsNode(firstDom, lastDom, target)) {
|
|
1133
|
+
// no-op
|
|
1134
|
+
}
|
|
1135
|
+
else if (firstDom !== target) {
|
|
1136
|
+
moveDomRange(domParent, firstDom, lastDom, target);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (lastDom)
|
|
1140
|
+
lastPlaced = lastDom;
|
|
1141
|
+
// Clear internal flags for next diff
|
|
1142
|
+
childVNode._flags = 0;
|
|
1143
|
+
childVNode._index = undefined;
|
|
1144
|
+
}
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
export function findFirstDomAnchor(node) {
|
|
1148
|
+
if (!node)
|
|
1149
|
+
return null;
|
|
1150
|
+
if (isCommittedTextNode(node))
|
|
1151
|
+
return node._dom;
|
|
1152
|
+
if (isCommittedHostNode(node))
|
|
1153
|
+
return node._dom;
|
|
1154
|
+
if (isCommittedComponentNode(node))
|
|
1155
|
+
return findFirstDomAnchor(node._content);
|
|
1156
|
+
if (node.type === Frame)
|
|
1157
|
+
return node._rangeStart ?? null;
|
|
1158
|
+
if (isFragmentNode(node)) {
|
|
1159
|
+
for (let child of node._children) {
|
|
1160
|
+
let dom = findFirstDomAnchor(child);
|
|
1161
|
+
if (dom)
|
|
1162
|
+
return dom;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
export function findLastDomAnchor(node) {
|
|
1168
|
+
if (!node)
|
|
1169
|
+
return null;
|
|
1170
|
+
if (isCommittedTextNode(node))
|
|
1171
|
+
return node._dom;
|
|
1172
|
+
if (isCommittedHostNode(node))
|
|
1173
|
+
return node._dom;
|
|
1174
|
+
if (isCommittedComponentNode(node))
|
|
1175
|
+
return findLastDomAnchor(node._content);
|
|
1176
|
+
if (node.type === Frame)
|
|
1177
|
+
return node._rangeEnd ?? null;
|
|
1178
|
+
if (isFragmentNode(node)) {
|
|
1179
|
+
for (let i = node._children.length - 1; i >= 0; i--) {
|
|
1180
|
+
let dom = findLastDomAnchor(node._children[i]);
|
|
1181
|
+
if (dom)
|
|
1182
|
+
return dom;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
function domRangeContainsNode(first, last, node) {
|
|
1188
|
+
let current = first;
|
|
1189
|
+
while (current) {
|
|
1190
|
+
if (current === node)
|
|
1191
|
+
return true;
|
|
1192
|
+
if (current === last)
|
|
1193
|
+
break;
|
|
1194
|
+
current = current.nextSibling;
|
|
1195
|
+
}
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
function moveDomRange(domParent, first, last, before) {
|
|
1199
|
+
let current = first;
|
|
1200
|
+
while (current) {
|
|
1201
|
+
let next = current === last ? null : current.nextSibling;
|
|
1202
|
+
domParent.insertBefore(current, before);
|
|
1203
|
+
if (current === last)
|
|
1204
|
+
break;
|
|
1205
|
+
current = next;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
export function setActiveSchedulerUpdateParents(parents) {
|
|
1209
|
+
activeSchedulerUpdateParents = parents;
|
|
1210
|
+
}
|
|
1211
|
+
function shouldDispatchInlineMixinLifecycle(node) {
|
|
1212
|
+
let parents = activeSchedulerUpdateParents;
|
|
1213
|
+
if (!parents?.length)
|
|
1214
|
+
return true;
|
|
1215
|
+
for (let parent of parents) {
|
|
1216
|
+
let parentNode = parent;
|
|
1217
|
+
if (parentNode === node)
|
|
1218
|
+
return false;
|
|
1219
|
+
if (parentNode.contains(node))
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
1224
|
+
export function findNextSiblingDomAnchor(curr, vParent) {
|
|
1225
|
+
if (!vParent || !Array.isArray(vParent._children))
|
|
1226
|
+
return null;
|
|
1227
|
+
let children = vParent._children;
|
|
1228
|
+
let idx = children.indexOf(curr);
|
|
1229
|
+
if (idx === -1)
|
|
1230
|
+
return null;
|
|
1231
|
+
for (let i = idx + 1; i < children.length; i++) {
|
|
1232
|
+
let dom = findFirstDomAnchor(children[i]);
|
|
1233
|
+
if (dom)
|
|
1234
|
+
return dom;
|
|
1235
|
+
}
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
function reclaimPersistedMixinNode(persistedNode, newNode, frame, scheduler, styles, vParent, rootTarget) {
|
|
1239
|
+
cancelPendingMixinRemoval(persistedNode._mixState);
|
|
1240
|
+
unmarkNodePersistedByMixins(persistedNode);
|
|
1241
|
+
newNode._dom = persistedNode._dom;
|
|
1242
|
+
newNode._parent = vParent;
|
|
1243
|
+
newNode._controller = persistedNode._controller;
|
|
1244
|
+
newNode._mixState = persistedNode._mixState;
|
|
1245
|
+
newNode._controlledState = persistedNode._controlledState;
|
|
1246
|
+
let prevProps = getHostProps(persistedNode);
|
|
1247
|
+
let nextProps = resolveNodeMixProps(newNode, frame, scheduler, newNode._mixState);
|
|
1248
|
+
if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
|
|
1249
|
+
dispatchMixinBeforeUpdate(newNode._mixState);
|
|
1250
|
+
}
|
|
1251
|
+
diffHostProps(prevProps, nextProps, persistedNode._dom);
|
|
1252
|
+
ensureControlledReflection(newNode, scheduler);
|
|
1253
|
+
syncControlledReflection(newNode, nextProps);
|
|
1254
|
+
diffChildren(persistedNode._children, newNode._children, persistedNode._dom, frame, scheduler, styles, newNode, rootTarget);
|
|
1255
|
+
bindNodeMixRuntime(newNode, frame, scheduler, styles, true);
|
|
1256
|
+
if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
|
|
1257
|
+
scheduler.enqueueCommitPhase([
|
|
1258
|
+
() => dispatchMixinCommit(newNode._mixState),
|
|
1259
|
+
]);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
//# sourceMappingURL=reconcile.js.map
|