what-core 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/dist/components.js +140 -0
- package/dist/dom.js +443 -0
- package/dist/h.js +150 -0
- package/dist/head.js +51 -0
- package/dist/helpers.js +98 -0
- package/dist/hooks.js +156 -0
- package/dist/index.js +147 -0
- package/dist/reactive.js +124 -0
- package/dist/store.js +59 -0
- package/dist/what.js +114 -0
- package/index.d.ts +252 -0
- package/package.json +47 -0
- package/src/a11y.js +425 -0
- package/src/animation.js +531 -0
- package/src/components.js +204 -0
- package/src/data.js +434 -0
- package/src/dom.js +619 -0
- package/src/form.js +441 -0
- package/src/h.js +203 -0
- package/src/head.js +68 -0
- package/src/helpers.js +140 -0
- package/src/hooks.js +246 -0
- package/src/index.js +147 -0
- package/src/reactive.js +167 -0
- package/src/scheduler.js +241 -0
- package/src/skeleton.js +363 -0
- package/src/store.js +99 -0
- package/src/testing.js +367 -0
- package/testing.d.ts +103 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { h } from './h.js';
|
|
2
|
+
import { signal, effect, untrack } from './reactive.js';
|
|
3
|
+
export const errorBoundaryStack = [];
|
|
4
|
+
export function memo(Component, areEqual) {
|
|
5
|
+
const compare = areEqual || shallowEqual;
|
|
6
|
+
let prevProps = null;
|
|
7
|
+
let prevResult = null;
|
|
8
|
+
function MemoWrapper(props) {
|
|
9
|
+
if (prevProps && compare(prevProps, props)) {
|
|
10
|
+
return prevResult;
|
|
11
|
+
}
|
|
12
|
+
prevProps = { ...props };
|
|
13
|
+
prevResult = Component(props);
|
|
14
|
+
return prevResult;
|
|
15
|
+
}
|
|
16
|
+
MemoWrapper.displayName = `Memo(${Component.name || 'Anonymous'})`;
|
|
17
|
+
return MemoWrapper;
|
|
18
|
+
}
|
|
19
|
+
function shallowEqual(a, b) {
|
|
20
|
+
if (a === b) return true;
|
|
21
|
+
const keysA = Object.keys(a);
|
|
22
|
+
const keysB = Object.keys(b);
|
|
23
|
+
if (keysA.length !== keysB.length) return false;
|
|
24
|
+
for (const key of keysA) {
|
|
25
|
+
if (!Object.is(a[key], b[key])) return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
export function lazy(loader) {
|
|
30
|
+
let Component = null;
|
|
31
|
+
let loadPromise = null;
|
|
32
|
+
let loadError = null;
|
|
33
|
+
const listeners = new Set();
|
|
34
|
+
function LazyWrapper(props) {
|
|
35
|
+
if (loadError) throw loadError;
|
|
36
|
+
if (Component) return h(Component, props);
|
|
37
|
+
if (!loadPromise) {
|
|
38
|
+
loadPromise = loader()
|
|
39
|
+
.then(mod => {
|
|
40
|
+
Component = mod.default || mod;
|
|
41
|
+
listeners.forEach(fn => fn());
|
|
42
|
+
listeners.clear();
|
|
43
|
+
})
|
|
44
|
+
.catch(err => { loadError = err; });
|
|
45
|
+
}
|
|
46
|
+
throw loadPromise;
|
|
47
|
+
}
|
|
48
|
+
LazyWrapper.displayName = 'Lazy';
|
|
49
|
+
LazyWrapper._lazy = true;
|
|
50
|
+
LazyWrapper._onLoad = (fn) => {
|
|
51
|
+
if (Component) fn();
|
|
52
|
+
else listeners.add(fn);
|
|
53
|
+
};
|
|
54
|
+
return LazyWrapper;
|
|
55
|
+
}
|
|
56
|
+
export function Suspense({ fallback, children }) {
|
|
57
|
+
const loading = signal(false);
|
|
58
|
+
const pendingPromises = new Set();
|
|
59
|
+
const boundary = {
|
|
60
|
+
_suspense: true,
|
|
61
|
+
onSuspend(promise) {
|
|
62
|
+
loading.set(true);
|
|
63
|
+
pendingPromises.add(promise);
|
|
64
|
+
promise.finally(() => {
|
|
65
|
+
pendingPromises.delete(promise);
|
|
66
|
+
if (pendingPromises.size === 0) {
|
|
67
|
+
loading.set(false);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
tag: '__suspense',
|
|
74
|
+
props: { boundary, fallback, loading },
|
|
75
|
+
children: Array.isArray(children) ? children : [children],
|
|
76
|
+
_vnode: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function ErrorBoundary({ fallback, children, onError }) {
|
|
80
|
+
const errorState = signal(null);
|
|
81
|
+
const handleError = (error) => {
|
|
82
|
+
errorState.set(error);
|
|
83
|
+
if (onError) {
|
|
84
|
+
try {
|
|
85
|
+
onError(error);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('Error in onError handler:', e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const reset = () => errorState.set(null);
|
|
92
|
+
return {
|
|
93
|
+
tag: '__errorBoundary',
|
|
94
|
+
props: { errorState, handleError, fallback, reset },
|
|
95
|
+
children: Array.isArray(children) ? children : [children],
|
|
96
|
+
_vnode: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function getCurrentErrorBoundary() {
|
|
100
|
+
return errorBoundaryStack[errorBoundaryStack.length - 1] || null;
|
|
101
|
+
}
|
|
102
|
+
export function reportError(error) {
|
|
103
|
+
const boundary = getCurrentErrorBoundary();
|
|
104
|
+
if (boundary) {
|
|
105
|
+
boundary.handleError(error);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
export function Show({ when, fallback = null, children }) {
|
|
111
|
+
const condition = typeof when === 'function' ? when() : when;
|
|
112
|
+
return condition ? children : fallback;
|
|
113
|
+
}
|
|
114
|
+
export function For({ each, fallback = null, children }) {
|
|
115
|
+
const list = typeof each === 'function' ? each() : each;
|
|
116
|
+
if (!list || list.length === 0) return fallback;
|
|
117
|
+
const renderFn = Array.isArray(children) ? children[0] : children;
|
|
118
|
+
if (typeof renderFn !== 'function') {
|
|
119
|
+
console.warn('For: children must be a function');
|
|
120
|
+
return fallback;
|
|
121
|
+
}
|
|
122
|
+
return list.map((item, index) => renderFn(item, index));
|
|
123
|
+
}
|
|
124
|
+
export function Switch({ fallback = null, children }) {
|
|
125
|
+
const kids = Array.isArray(children) ? children : [children];
|
|
126
|
+
for (const child of kids) {
|
|
127
|
+
if (child && child.tag === Match) {
|
|
128
|
+
const condition = typeof child.props.when === 'function'
|
|
129
|
+
? child.props.when()
|
|
130
|
+
: child.props.when;
|
|
131
|
+
if (condition) {
|
|
132
|
+
return child.children;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
export function Match({ when, children }) {
|
|
139
|
+
return { tag: Match, props: { when }, children, _vnode: true };
|
|
140
|
+
}
|
package/dist/dom.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { effect, batch, untrack } from './reactive.js';
|
|
2
|
+
import { errorBoundaryStack, reportError } from './components.js';
|
|
3
|
+
export function mount(vnode, container) {
|
|
4
|
+
if (typeof container === 'string') {
|
|
5
|
+
container = document.querySelector(container);
|
|
6
|
+
}
|
|
7
|
+
container.textContent = '';
|
|
8
|
+
const node = createDOM(vnode, container);
|
|
9
|
+
if (node) container.appendChild(node);
|
|
10
|
+
return () => {
|
|
11
|
+
container.textContent = '';
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function createDOM(vnode, parent) {
|
|
15
|
+
if (vnode == null || vnode === false || vnode === true) return null;
|
|
16
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
17
|
+
return document.createTextNode(String(vnode));
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(vnode)) {
|
|
20
|
+
const frag = document.createDocumentFragment();
|
|
21
|
+
for (const child of vnode) {
|
|
22
|
+
const node = createDOM(child, parent);
|
|
23
|
+
if (node) frag.appendChild(node);
|
|
24
|
+
}
|
|
25
|
+
return frag;
|
|
26
|
+
}
|
|
27
|
+
if (typeof vnode.tag === 'function') {
|
|
28
|
+
return createComponent(vnode, parent);
|
|
29
|
+
}
|
|
30
|
+
const el = document.createElement(vnode.tag);
|
|
31
|
+
applyProps(el, vnode.props, {});
|
|
32
|
+
for (const child of vnode.children) {
|
|
33
|
+
const node = createDOM(child, el);
|
|
34
|
+
if (node) el.appendChild(node);
|
|
35
|
+
}
|
|
36
|
+
el._vnode = vnode;
|
|
37
|
+
return el;
|
|
38
|
+
}
|
|
39
|
+
const componentStack = [];
|
|
40
|
+
export function getCurrentComponent() {
|
|
41
|
+
return componentStack[componentStack.length - 1];
|
|
42
|
+
}
|
|
43
|
+
function createComponent(vnode, parent) {
|
|
44
|
+
const { tag: Component, props, children } = vnode;
|
|
45
|
+
if (Component === '__errorBoundary' || vnode.tag === '__errorBoundary') {
|
|
46
|
+
return createErrorBoundary(vnode, parent);
|
|
47
|
+
}
|
|
48
|
+
if (Component === '__suspense' || vnode.tag === '__suspense') {
|
|
49
|
+
return createSuspenseBoundary(vnode, parent);
|
|
50
|
+
}
|
|
51
|
+
const ctx = {
|
|
52
|
+
hooks: [],
|
|
53
|
+
hookIndex: 0,
|
|
54
|
+
effects: [],
|
|
55
|
+
cleanups: [],
|
|
56
|
+
mounted: false,
|
|
57
|
+
disposed: false,
|
|
58
|
+
};
|
|
59
|
+
const marker = document.createComment(`w:${Component.name || 'anon'}`);
|
|
60
|
+
let currentNodes = [];
|
|
61
|
+
const dispose = effect(() => {
|
|
62
|
+
if (ctx.disposed) return;
|
|
63
|
+
ctx.hookIndex = 0;
|
|
64
|
+
componentStack.push(ctx);
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
result = Component({ ...props, children });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
componentStack.pop();
|
|
70
|
+
if (!reportError(error)) {
|
|
71
|
+
console.error('[what] Uncaught error in component:', Component.name || 'Anonymous', error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
componentStack.pop();
|
|
77
|
+
const vnodes = Array.isArray(result) ? result : [result];
|
|
78
|
+
if (!ctx.mounted) {
|
|
79
|
+
ctx.mounted = true;
|
|
80
|
+
for (const v of vnodes) {
|
|
81
|
+
const node = createDOM(v, parent);
|
|
82
|
+
if (node) {
|
|
83
|
+
currentNodes.push(node);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
reconcile(marker.parentNode, currentNodes, vnodes, marker);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
ctx.effects.push(dispose);
|
|
91
|
+
const frag = document.createDocumentFragment();
|
|
92
|
+
frag.appendChild(marker);
|
|
93
|
+
for (const node of currentNodes) {
|
|
94
|
+
frag.appendChild(node);
|
|
95
|
+
}
|
|
96
|
+
return frag;
|
|
97
|
+
}
|
|
98
|
+
function createErrorBoundary(vnode, parent) {
|
|
99
|
+
const { errorState, handleError, fallback, reset } = vnode.props;
|
|
100
|
+
const children = vnode.children;
|
|
101
|
+
const marker = document.createComment('w:errorBoundary');
|
|
102
|
+
let currentNodes = [];
|
|
103
|
+
const boundary = { handleError };
|
|
104
|
+
const dispose = effect(() => {
|
|
105
|
+
const error = errorState();
|
|
106
|
+
errorBoundaryStack.push(boundary);
|
|
107
|
+
let vnodes;
|
|
108
|
+
if (error) {
|
|
109
|
+
if (typeof fallback === 'function') {
|
|
110
|
+
vnodes = [fallback({ error, reset })];
|
|
111
|
+
} else {
|
|
112
|
+
vnodes = [fallback];
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
vnodes = children;
|
|
116
|
+
}
|
|
117
|
+
errorBoundaryStack.pop();
|
|
118
|
+
vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
119
|
+
if (currentNodes.length === 0) {
|
|
120
|
+
for (const v of vnodes) {
|
|
121
|
+
const node = createDOM(v, parent);
|
|
122
|
+
if (node) currentNodes.push(node);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
reconcile(marker.parentNode, currentNodes, vnodes, marker);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const frag = document.createDocumentFragment();
|
|
129
|
+
frag.appendChild(marker);
|
|
130
|
+
for (const node of currentNodes) {
|
|
131
|
+
frag.appendChild(node);
|
|
132
|
+
}
|
|
133
|
+
return frag;
|
|
134
|
+
}
|
|
135
|
+
function createSuspenseBoundary(vnode, parent) {
|
|
136
|
+
const { boundary, fallback, loading } = vnode.props;
|
|
137
|
+
const children = vnode.children;
|
|
138
|
+
const marker = document.createComment('w:suspense');
|
|
139
|
+
let currentNodes = [];
|
|
140
|
+
const dispose = effect(() => {
|
|
141
|
+
const isLoading = loading();
|
|
142
|
+
const vnodes = isLoading ? [fallback] : children;
|
|
143
|
+
const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
144
|
+
if (currentNodes.length === 0) {
|
|
145
|
+
for (const v of normalized) {
|
|
146
|
+
const node = createDOM(v, parent);
|
|
147
|
+
if (node) currentNodes.push(node);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
reconcile(marker.parentNode, currentNodes, normalized, marker);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const frag = document.createDocumentFragment();
|
|
154
|
+
frag.appendChild(marker);
|
|
155
|
+
for (const node of currentNodes) {
|
|
156
|
+
frag.appendChild(node);
|
|
157
|
+
}
|
|
158
|
+
return frag;
|
|
159
|
+
}
|
|
160
|
+
function reconcile(parent, oldNodes, newVNodes, beforeMarker) {
|
|
161
|
+
if (!parent) return;
|
|
162
|
+
const hasKeys = newVNodes.some(v => v && typeof v === 'object' && v.key != null);
|
|
163
|
+
if (hasKeys) {
|
|
164
|
+
reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker);
|
|
165
|
+
} else {
|
|
166
|
+
reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
170
|
+
const maxLen = Math.max(oldNodes.length, newVNodes.length);
|
|
171
|
+
const newNodes = [];
|
|
172
|
+
for (let i = 0; i < maxLen; i++) {
|
|
173
|
+
const oldNode = oldNodes[i];
|
|
174
|
+
const newVNode = newVNodes[i];
|
|
175
|
+
if (i >= newVNodes.length) {
|
|
176
|
+
if (oldNode && oldNode.parentNode) {
|
|
177
|
+
oldNode.parentNode.removeChild(oldNode);
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (i >= oldNodes.length) {
|
|
182
|
+
const node = createDOM(newVNode, parent);
|
|
183
|
+
if (node) {
|
|
184
|
+
const ref = getInsertionRef(oldNodes, beforeMarker);
|
|
185
|
+
parent.insertBefore(node, ref);
|
|
186
|
+
newNodes.push(node);
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const patched = patchNode(parent, oldNode, newVNode);
|
|
191
|
+
newNodes.push(patched);
|
|
192
|
+
}
|
|
193
|
+
oldNodes.length = 0;
|
|
194
|
+
oldNodes.push(...newNodes);
|
|
195
|
+
}
|
|
196
|
+
function reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
197
|
+
const oldKeyMap = new Map();
|
|
198
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
199
|
+
const node = oldNodes[i];
|
|
200
|
+
const key = node._vnode?.key;
|
|
201
|
+
if (key != null) {
|
|
202
|
+
oldKeyMap.set(key, { node, index: i });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const newNodes = [];
|
|
206
|
+
const newLen = newVNodes.length;
|
|
207
|
+
const sources = new Array(newLen).fill(-1);
|
|
208
|
+
const reused = new Set();
|
|
209
|
+
for (let i = 0; i < newLen; i++) {
|
|
210
|
+
const vnode = newVNodes[i];
|
|
211
|
+
const key = vnode?.key;
|
|
212
|
+
if (key != null && oldKeyMap.has(key)) {
|
|
213
|
+
const { node: oldNode, index: oldIndex } = oldKeyMap.get(key);
|
|
214
|
+
sources[i] = oldIndex;
|
|
215
|
+
reused.add(oldIndex);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
219
|
+
if (!reused.has(i) && oldNodes[i]?.parentNode) {
|
|
220
|
+
oldNodes[i].parentNode.removeChild(oldNodes[i]);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const lis = longestIncreasingSubsequence(sources.filter(s => s !== -1));
|
|
224
|
+
const lisSet = new Set(lis.map((_, i) => {
|
|
225
|
+
let count = 0;
|
|
226
|
+
for (let j = 0; j < sources.length; j++) {
|
|
227
|
+
if (sources[j] !== -1) {
|
|
228
|
+
if (count === lis[i]) return j;
|
|
229
|
+
count++;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return -1;
|
|
233
|
+
}));
|
|
234
|
+
let lastInserted = beforeMarker?.nextSibling || null;
|
|
235
|
+
for (let i = newLen - 1; i >= 0; i--) {
|
|
236
|
+
const vnode = newVNodes[i];
|
|
237
|
+
const key = vnode?.key;
|
|
238
|
+
const oldEntry = key != null ? oldKeyMap.get(key) : null;
|
|
239
|
+
if (oldEntry && sources[i] !== -1) {
|
|
240
|
+
const oldNode = oldEntry.node;
|
|
241
|
+
const patched = patchNode(parent, oldNode, vnode);
|
|
242
|
+
newNodes[i] = patched;
|
|
243
|
+
if (!lisSet.has(i) && patched.parentNode) {
|
|
244
|
+
parent.insertBefore(patched, lastInserted);
|
|
245
|
+
}
|
|
246
|
+
lastInserted = patched;
|
|
247
|
+
} else {
|
|
248
|
+
const node = createDOM(vnode, parent);
|
|
249
|
+
if (node) {
|
|
250
|
+
parent.insertBefore(node, lastInserted);
|
|
251
|
+
lastInserted = node;
|
|
252
|
+
}
|
|
253
|
+
newNodes[i] = node;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
oldNodes.length = 0;
|
|
257
|
+
oldNodes.push(...newNodes.filter(Boolean));
|
|
258
|
+
}
|
|
259
|
+
function longestIncreasingSubsequence(arr) {
|
|
260
|
+
if (arr.length === 0) return [];
|
|
261
|
+
const n = arr.length;
|
|
262
|
+
const dp = new Array(n).fill(1);
|
|
263
|
+
const parent = new Array(n).fill(-1);
|
|
264
|
+
const tails = [0];
|
|
265
|
+
for (let i = 1; i < n; i++) {
|
|
266
|
+
if (arr[i] > arr[tails[tails.length - 1]]) {
|
|
267
|
+
parent[i] = tails[tails.length - 1];
|
|
268
|
+
tails.push(i);
|
|
269
|
+
} else {
|
|
270
|
+
let lo = 0, hi = tails.length - 1;
|
|
271
|
+
while (lo < hi) {
|
|
272
|
+
const mid = (lo + hi) >> 1;
|
|
273
|
+
if (arr[tails[mid]] < arr[i]) lo = mid + 1;
|
|
274
|
+
else hi = mid;
|
|
275
|
+
}
|
|
276
|
+
if (arr[i] < arr[tails[lo]]) {
|
|
277
|
+
if (lo > 0) parent[i] = tails[lo - 1];
|
|
278
|
+
tails[lo] = i;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const result = [];
|
|
283
|
+
let k = tails[tails.length - 1];
|
|
284
|
+
while (k !== -1) {
|
|
285
|
+
result.push(k);
|
|
286
|
+
k = parent[k];
|
|
287
|
+
}
|
|
288
|
+
return result.reverse();
|
|
289
|
+
}
|
|
290
|
+
function getInsertionRef(nodes, marker) {
|
|
291
|
+
if (nodes.length > 0) {
|
|
292
|
+
const last = nodes[nodes.length - 1];
|
|
293
|
+
return last.nextSibling;
|
|
294
|
+
}
|
|
295
|
+
return marker ? marker.nextSibling : null;
|
|
296
|
+
}
|
|
297
|
+
function patchNode(parent, domNode, vnode) {
|
|
298
|
+
if (vnode == null || vnode === false || vnode === true) {
|
|
299
|
+
if (domNode && domNode.parentNode) domNode.parentNode.removeChild(domNode);
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
303
|
+
const text = String(vnode);
|
|
304
|
+
if (domNode.nodeType === 3) {
|
|
305
|
+
if (domNode.textContent !== text) domNode.textContent = text;
|
|
306
|
+
return domNode;
|
|
307
|
+
}
|
|
308
|
+
const newNode = document.createTextNode(text);
|
|
309
|
+
parent.replaceChild(newNode, domNode);
|
|
310
|
+
return newNode;
|
|
311
|
+
}
|
|
312
|
+
if (Array.isArray(vnode)) {
|
|
313
|
+
const frag = document.createDocumentFragment();
|
|
314
|
+
for (const v of vnode) {
|
|
315
|
+
const node = createDOM(v, parent);
|
|
316
|
+
if (node) frag.appendChild(node);
|
|
317
|
+
}
|
|
318
|
+
parent.replaceChild(frag, domNode);
|
|
319
|
+
return frag;
|
|
320
|
+
}
|
|
321
|
+
if (typeof vnode.tag === 'function') {
|
|
322
|
+
const node = createComponent(vnode, parent);
|
|
323
|
+
parent.replaceChild(node, domNode);
|
|
324
|
+
return node;
|
|
325
|
+
}
|
|
326
|
+
if (domNode.nodeType === 1 && domNode.tagName.toLowerCase() === vnode.tag) {
|
|
327
|
+
const oldProps = domNode._vnode?.props || {};
|
|
328
|
+
applyProps(domNode, vnode.props, oldProps);
|
|
329
|
+
reconcileChildren(domNode, vnode.children);
|
|
330
|
+
domNode._vnode = vnode;
|
|
331
|
+
return domNode;
|
|
332
|
+
}
|
|
333
|
+
const newNode = createDOM(vnode, parent);
|
|
334
|
+
parent.replaceChild(newNode, domNode);
|
|
335
|
+
return newNode;
|
|
336
|
+
}
|
|
337
|
+
function reconcileChildren(parent, newChildVNodes) {
|
|
338
|
+
const oldChildren = Array.from(parent.childNodes);
|
|
339
|
+
const hasKeys = newChildVNodes.some(v => v && typeof v === 'object' && v.key != null);
|
|
340
|
+
if (hasKeys) {
|
|
341
|
+
reconcileKeyed(parent, oldChildren, newChildVNodes, null);
|
|
342
|
+
} else {
|
|
343
|
+
const maxLen = Math.max(oldChildren.length, newChildVNodes.length);
|
|
344
|
+
for (let i = 0; i < maxLen; i++) {
|
|
345
|
+
if (i >= newChildVNodes.length) {
|
|
346
|
+
if (oldChildren[i]?.parentNode) {
|
|
347
|
+
parent.removeChild(oldChildren[i]);
|
|
348
|
+
}
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (i >= oldChildren.length) {
|
|
352
|
+
const node = createDOM(newChildVNodes[i], parent);
|
|
353
|
+
if (node) parent.appendChild(node);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
patchNode(parent, oldChildren[i], newChildVNodes[i]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function applyProps(el, newProps, oldProps) {
|
|
361
|
+
newProps = newProps || {};
|
|
362
|
+
oldProps = oldProps || {};
|
|
363
|
+
for (const key in oldProps) {
|
|
364
|
+
if (key === 'key' || key === 'ref' || key === 'children') continue;
|
|
365
|
+
if (!(key in newProps)) {
|
|
366
|
+
removeProp(el, key, oldProps[key]);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const key in newProps) {
|
|
370
|
+
if (key === 'key' || key === 'ref' || key === 'children') continue;
|
|
371
|
+
if (newProps[key] !== oldProps[key]) {
|
|
372
|
+
setProp(el, key, newProps[key]);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (newProps.ref && newProps.ref !== oldProps.ref) {
|
|
376
|
+
if (typeof newProps.ref === 'function') newProps.ref(el);
|
|
377
|
+
else newProps.ref.current = el;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function setProp(el, key, value) {
|
|
381
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
382
|
+
const event = key.slice(2).toLowerCase();
|
|
383
|
+
const old = el._events?.[event];
|
|
384
|
+
if (old) el.removeEventListener(event, old);
|
|
385
|
+
if (!el._events) el._events = {};
|
|
386
|
+
const wrappedHandler = (e) => untrack(() => value(e));
|
|
387
|
+
wrappedHandler._original = value;
|
|
388
|
+
el._events[event] = wrappedHandler;
|
|
389
|
+
el.addEventListener(event, wrappedHandler);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (key === 'className' || key === 'class') {
|
|
393
|
+
el.className = value || '';
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (key === 'style') {
|
|
397
|
+
if (typeof value === 'string') {
|
|
398
|
+
el.style.cssText = value;
|
|
399
|
+
} else if (typeof value === 'object') {
|
|
400
|
+
for (const prop in value) {
|
|
401
|
+
el.style[prop] = value[prop] ?? '';
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (key === 'dangerouslySetInnerHTML') {
|
|
407
|
+
el.innerHTML = value.__html;
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (typeof value === 'boolean') {
|
|
411
|
+
if (value) el.setAttribute(key, '');
|
|
412
|
+
else el.removeAttribute(key);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
|
416
|
+
el.setAttribute(key, value);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (key in el) {
|
|
420
|
+
el[key] = value;
|
|
421
|
+
} else {
|
|
422
|
+
el.setAttribute(key, value);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function removeProp(el, key, oldValue) {
|
|
426
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
427
|
+
const event = key.slice(2).toLowerCase();
|
|
428
|
+
if (el._events?.[event]) {
|
|
429
|
+
el.removeEventListener(event, el._events[event]);
|
|
430
|
+
delete el._events[event];
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (key === 'className' || key === 'class') {
|
|
435
|
+
el.className = '';
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (key === 'style') {
|
|
439
|
+
el.style.cssText = '';
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
el.removeAttribute(key);
|
|
443
|
+
}
|