what-core 0.4.1 → 0.5.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 +213 -319
- package/dist/dom.js +730 -857
- package/dist/h.js +140 -191
- package/dist/head.js +42 -59
- package/dist/helpers.js +124 -187
- package/dist/hooks.js +186 -279
- package/dist/reactive.js +244 -317
- package/dist/store.js +73 -118
- package/dist/what.js +5 -3
- package/index.d.ts +391 -152
- package/package.json +4 -3
- package/render.d.ts +11 -0
- package/src/a11y.js +52 -6
- package/src/dom.js +137 -22
- package/src/form.js +85 -54
- package/src/helpers.js +1 -12
- package/src/hooks.js +11 -0
- package/src/index.js +1 -1
- package/src/render.js +114 -51
- package/src/store.js +6 -1
package/dist/dom.js
CHANGED
|
@@ -1,890 +1,763 @@
|
|
|
1
|
-
// What Framework - DOM Reconciler
|
|
2
|
-
// Surgical DOM updates. Diff props, diff children, patch only what changed.
|
|
3
|
-
// Components use <what-c> wrapper elements (display:contents) for clean reconciliation.
|
|
4
|
-
// No virtual DOM tree kept in memory — we diff against the live DOM.
|
|
5
|
-
|
|
6
1
|
import { effect, batch, untrack, signal } from './reactive.js';
|
|
7
2
|
import { reportError, _injectGetCurrentComponent } from './components.js';
|
|
8
3
|
import { _setComponentRef } from './helpers.js';
|
|
9
|
-
|
|
10
|
-
// Register <what-c> custom element to prevent flash of unstyled content
|
|
11
|
-
// Note: style is set in connectedCallback (not constructor) to comply with custom element spec
|
|
12
4
|
if (typeof customElements !== 'undefined' && !customElements.get('what-c')) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// SVG elements that need namespace
|
|
5
|
+
customElements.define('what-c', class extends HTMLElement {
|
|
6
|
+
connectedCallback() {
|
|
7
|
+
this.style.display = 'contents';
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
}
|
|
21
11
|
const SVG_ELEMENTS = new Set([
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse',
|
|
13
|
+
'g', 'defs', 'use', 'symbol', 'clipPath', 'mask', 'pattern', 'image',
|
|
14
|
+
'text', 'tspan', 'textPath', 'foreignObject', 'linearGradient', 'radialGradient', 'stop',
|
|
15
|
+
'marker', 'animate', 'animateTransform', 'animateMotion', 'set', 'filter',
|
|
16
|
+
'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix',
|
|
17
|
+
'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage',
|
|
18
|
+
'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'feSpecularLighting',
|
|
19
|
+
'feTile', 'feTurbulence',
|
|
30
20
|
]);
|
|
31
21
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
32
|
-
|
|
33
|
-
// Track all mounted component contexts for disposal
|
|
34
22
|
const mountedComponents = new Set();
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
function isDomNode(value) {
|
|
24
|
+
if (!value || typeof value !== 'object') return false;
|
|
25
|
+
if (typeof Node !== 'undefined' && value instanceof Node) return true;
|
|
26
|
+
return typeof value.nodeType === 'number' && typeof value.nodeName === 'string';
|
|
27
|
+
}
|
|
28
|
+
function isVNode(value) {
|
|
29
|
+
return !!value && typeof value === 'object' && (value._vnode === true || 'tag' in value);
|
|
30
|
+
}
|
|
37
31
|
function disposeComponent(ctx) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (node.childNodes) {
|
|
70
|
-
for (const child of node.childNodes) {
|
|
71
|
-
disposeTree(child);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Mount a component tree into a DOM container
|
|
32
|
+
if (ctx.disposed) return;
|
|
33
|
+
ctx.disposed = true;
|
|
34
|
+
for (const hook of ctx.hooks) {
|
|
35
|
+
if (hook && typeof hook === 'object' && 'cleanup' in hook && hook.cleanup) {
|
|
36
|
+
try { hook.cleanup(); } catch (e) { console.error('[what] cleanup error:', e); }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (ctx._cleanupCallbacks) {
|
|
40
|
+
for (const fn of ctx._cleanupCallbacks) {
|
|
41
|
+
try { fn(); } catch (e) { console.error('[what] onCleanup error:', e); }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const dispose of ctx.effects) {
|
|
45
|
+
try { dispose(); } catch (e) { }
|
|
46
|
+
}
|
|
47
|
+
mountedComponents.delete(ctx);
|
|
48
|
+
}
|
|
49
|
+
export function disposeTree(node) {
|
|
50
|
+
if (!node) return;
|
|
51
|
+
if (node._componentCtx) {
|
|
52
|
+
disposeComponent(node._componentCtx);
|
|
53
|
+
}
|
|
54
|
+
if (node._dispose) {
|
|
55
|
+
try { node._dispose(); } catch (e) { }
|
|
56
|
+
}
|
|
57
|
+
if (node.childNodes) {
|
|
58
|
+
for (const child of node.childNodes) {
|
|
59
|
+
disposeTree(child);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
77
63
|
export function mount(vnode, container) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
64
|
+
if (typeof container === 'string') {
|
|
65
|
+
container = document.querySelector(container);
|
|
66
|
+
}
|
|
67
|
+
disposeTree(container);
|
|
68
|
+
container.textContent = '';
|
|
69
|
+
const node = createDOM(vnode, container);
|
|
70
|
+
if (node) container.appendChild(node);
|
|
71
|
+
return () => {
|
|
72
|
+
disposeTree(container);
|
|
73
|
+
container.textContent = '';
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function createDOM(vnode, parent, isSvg) {
|
|
77
|
+
if (vnode == null || vnode === false || vnode === true) {
|
|
78
|
+
return document.createComment('');
|
|
79
|
+
}
|
|
80
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
81
|
+
return document.createTextNode(String(vnode));
|
|
82
|
+
}
|
|
83
|
+
if (isDomNode(vnode)) {
|
|
84
|
+
return vnode;
|
|
85
|
+
}
|
|
86
|
+
if (typeof vnode === 'function') {
|
|
87
|
+
const wrapper = document.createElement('what-c');
|
|
88
|
+
let mounted = false;
|
|
89
|
+
const dispose = effect(() => {
|
|
90
|
+
const val = vnode();
|
|
91
|
+
const vnodes = (val == null || val === false || val === true)
|
|
92
|
+
? []
|
|
93
|
+
: Array.isArray(val) ? val : [val];
|
|
94
|
+
if (!mounted) {
|
|
95
|
+
mounted = true;
|
|
96
|
+
for (const v of vnodes) {
|
|
97
|
+
const node = createDOM(v, wrapper, parent?._isSvg);
|
|
98
|
+
if (node) wrapper.appendChild(node);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
reconcileChildren(wrapper, vnodes);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
wrapper._dispose = dispose;
|
|
105
|
+
return wrapper;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(vnode)) {
|
|
108
|
+
const frag = document.createDocumentFragment();
|
|
109
|
+
for (const child of vnode) {
|
|
110
|
+
const node = createDOM(child, parent, isSvg);
|
|
111
|
+
if (node) frag.appendChild(node);
|
|
112
|
+
}
|
|
113
|
+
return frag;
|
|
114
|
+
}
|
|
115
|
+
if (!isVNode(vnode)) {
|
|
116
|
+
return document.createTextNode(String(vnode));
|
|
117
|
+
}
|
|
118
|
+
if (typeof vnode.tag === 'function') {
|
|
119
|
+
return createComponent(vnode, parent, isSvg);
|
|
120
|
+
}
|
|
121
|
+
const svgContext = isSvg || vnode.tag === 'svg' || SVG_ELEMENTS.has(vnode.tag);
|
|
122
|
+
const el = svgContext
|
|
123
|
+
? document.createElementNS(SVG_NS, vnode.tag)
|
|
124
|
+
: document.createElement(vnode.tag);
|
|
125
|
+
applyProps(el, vnode.props, {}, svgContext);
|
|
126
|
+
const hasRawHtml = vnode.props && (
|
|
127
|
+
Object.prototype.hasOwnProperty.call(vnode.props, 'dangerouslySetInnerHTML') ||
|
|
128
|
+
Object.prototype.hasOwnProperty.call(vnode.props, 'innerHTML')
|
|
129
|
+
);
|
|
130
|
+
if (!hasRawHtml) {
|
|
131
|
+
for (const child of vnode.children) {
|
|
132
|
+
const node = createDOM(child, el, svgContext && vnode.tag !== 'foreignObject');
|
|
133
|
+
if (node) el.appendChild(node);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
el._vnode = vnode;
|
|
137
|
+
return el;
|
|
138
|
+
}
|
|
140
139
|
const componentStack = [];
|
|
141
|
-
|
|
142
140
|
export function getCurrentComponent() {
|
|
143
|
-
|
|
141
|
+
return componentStack[componentStack.length - 1];
|
|
144
142
|
}
|
|
145
|
-
|
|
146
|
-
// Inject into components.js and helpers.js to avoid circular imports
|
|
147
143
|
_injectGetCurrentComponent(getCurrentComponent);
|
|
148
144
|
_setComponentRef(getCurrentComponent);
|
|
149
|
-
|
|
150
145
|
export function getComponentStack() {
|
|
151
|
-
|
|
146
|
+
return componentStack;
|
|
152
147
|
}
|
|
153
|
-
|
|
154
148
|
function createComponent(vnode, parent, isSvg) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Run onMount callbacks after DOM is ready
|
|
236
|
-
if (ctx._mountCallbacks) {
|
|
237
|
-
queueMicrotask(() => {
|
|
238
|
-
if (ctx.disposed) return;
|
|
239
|
-
for (const fn of ctx._mountCallbacks) {
|
|
240
|
-
try { fn(); } catch (e) { console.error('[what] onMount error:', e); }
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
for (const v of vnodes) {
|
|
246
|
-
const node = createDOM(v, wrapper, isSvg);
|
|
247
|
-
if (node) wrapper.appendChild(node);
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
// Update: reconcile children inside wrapper
|
|
251
|
-
reconcileChildren(wrapper, vnodes);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
ctx.effects.push(dispose);
|
|
256
|
-
return wrapper;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Error boundary component handler
|
|
149
|
+
const { tag: Component, props, children } = vnode;
|
|
150
|
+
if (Component === '__errorBoundary' || vnode.tag === '__errorBoundary') {
|
|
151
|
+
return createErrorBoundary(vnode, parent);
|
|
152
|
+
}
|
|
153
|
+
if (Component === '__suspense' || vnode.tag === '__suspense') {
|
|
154
|
+
return createSuspenseBoundary(vnode, parent);
|
|
155
|
+
}
|
|
156
|
+
if (Component === '__portal' || vnode.tag === '__portal') {
|
|
157
|
+
return createPortal(vnode, parent);
|
|
158
|
+
}
|
|
159
|
+
const ctx = {
|
|
160
|
+
hooks: [],
|
|
161
|
+
hookIndex: 0,
|
|
162
|
+
effects: [],
|
|
163
|
+
cleanups: [],
|
|
164
|
+
mounted: false,
|
|
165
|
+
disposed: false,
|
|
166
|
+
Component,
|
|
167
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
168
|
+
_errorBoundary: (() => {
|
|
169
|
+
let p = componentStack[componentStack.length - 1];
|
|
170
|
+
while (p) {
|
|
171
|
+
if (p._errorBoundary) return p._errorBoundary;
|
|
172
|
+
p = p._parentCtx;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
})()
|
|
176
|
+
};
|
|
177
|
+
let wrapper;
|
|
178
|
+
if (isSvg) {
|
|
179
|
+
wrapper = document.createElementNS(SVG_NS, 'g');
|
|
180
|
+
} else {
|
|
181
|
+
wrapper = document.createElement('what-c');
|
|
182
|
+
}
|
|
183
|
+
wrapper._componentCtx = ctx;
|
|
184
|
+
wrapper._isSvg = !!isSvg;
|
|
185
|
+
ctx._wrapper = wrapper;
|
|
186
|
+
mountedComponents.add(ctx);
|
|
187
|
+
const propsSignal = signal({ ...props, children });
|
|
188
|
+
ctx._propsSignal = propsSignal;
|
|
189
|
+
const dispose = effect(() => {
|
|
190
|
+
if (ctx.disposed) return;
|
|
191
|
+
ctx.hookIndex = 0;
|
|
192
|
+
componentStack.push(ctx);
|
|
193
|
+
let result;
|
|
194
|
+
try {
|
|
195
|
+
result = Component(propsSignal());
|
|
196
|
+
} catch (error) {
|
|
197
|
+
componentStack.pop();
|
|
198
|
+
if (!reportError(error, ctx)) {
|
|
199
|
+
console.error('[what] Uncaught error in component:', Component.name || 'Anonymous', error);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
componentStack.pop();
|
|
205
|
+
const vnodes = Array.isArray(result) ? result : [result];
|
|
206
|
+
if (!ctx.mounted) {
|
|
207
|
+
ctx.mounted = true;
|
|
208
|
+
if (ctx._mountCallbacks) {
|
|
209
|
+
queueMicrotask(() => {
|
|
210
|
+
if (ctx.disposed) return;
|
|
211
|
+
for (const fn of ctx._mountCallbacks) {
|
|
212
|
+
try { fn(); } catch (e) { console.error('[what] onMount error:', e); }
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
for (const v of vnodes) {
|
|
217
|
+
const node = createDOM(v, wrapper, isSvg);
|
|
218
|
+
if (node) wrapper.appendChild(node);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
reconcileChildren(wrapper, vnodes);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
ctx.effects.push(dispose);
|
|
225
|
+
wrapper._vnode = vnode;
|
|
226
|
+
return wrapper;
|
|
227
|
+
}
|
|
260
228
|
function createErrorBoundary(vnode, parent) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (node) wrapper.appendChild(node);
|
|
296
|
-
}
|
|
297
|
-
} else {
|
|
298
|
-
reconcileChildren(wrapper, vnodes);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
boundaryCtx.effects.push(dispose);
|
|
303
|
-
return wrapper;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Suspense boundary component handler
|
|
229
|
+
const { errorState, handleError, fallback, reset } = vnode.props;
|
|
230
|
+
const children = vnode.children;
|
|
231
|
+
const wrapper = document.createElement('what-c');
|
|
232
|
+
wrapper.style.display = 'contents';
|
|
233
|
+
const boundaryCtx = {
|
|
234
|
+
hooks: [], hookIndex: 0, effects: [], cleanups: [],
|
|
235
|
+
mounted: false, disposed: false,
|
|
236
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
237
|
+
_errorBoundary: handleError,
|
|
238
|
+
};
|
|
239
|
+
wrapper._componentCtx = boundaryCtx;
|
|
240
|
+
const dispose = effect(() => {
|
|
241
|
+
const error = errorState();
|
|
242
|
+
componentStack.push(boundaryCtx);
|
|
243
|
+
let vnodes;
|
|
244
|
+
if (error) {
|
|
245
|
+
vnodes = typeof fallback === 'function' ? [fallback({ error, reset })] : [fallback];
|
|
246
|
+
} else {
|
|
247
|
+
vnodes = children;
|
|
248
|
+
}
|
|
249
|
+
componentStack.pop();
|
|
250
|
+
vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
251
|
+
if (wrapper.childNodes.length === 0) {
|
|
252
|
+
for (const v of vnodes) {
|
|
253
|
+
const node = createDOM(v, wrapper);
|
|
254
|
+
if (node) wrapper.appendChild(node);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
reconcileChildren(wrapper, vnodes);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
boundaryCtx.effects.push(dispose);
|
|
261
|
+
return wrapper;
|
|
262
|
+
}
|
|
307
263
|
function createSuspenseBoundary(vnode, parent) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
boundaryCtx.effects.push(dispose);
|
|
338
|
-
return wrapper;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Portal component handler — renders children into a different DOM container
|
|
264
|
+
const { boundary, fallback, loading } = vnode.props;
|
|
265
|
+
const children = vnode.children;
|
|
266
|
+
const wrapper = document.createElement('what-c');
|
|
267
|
+
wrapper.style.display = 'contents';
|
|
268
|
+
const boundaryCtx = {
|
|
269
|
+
hooks: [], hookIndex: 0, effects: [], cleanups: [],
|
|
270
|
+
mounted: false, disposed: false,
|
|
271
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
272
|
+
};
|
|
273
|
+
wrapper._componentCtx = boundaryCtx;
|
|
274
|
+
const dispose = effect(() => {
|
|
275
|
+
const isLoading = loading();
|
|
276
|
+
const vnodes = isLoading ? [fallback] : children;
|
|
277
|
+
const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
278
|
+
if (wrapper.childNodes.length === 0) {
|
|
279
|
+
for (const v of normalized) {
|
|
280
|
+
const node = createDOM(v, wrapper);
|
|
281
|
+
if (node) wrapper.appendChild(node);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
reconcileChildren(wrapper, normalized);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
boundaryCtx.effects.push(dispose);
|
|
288
|
+
return wrapper;
|
|
289
|
+
}
|
|
342
290
|
function createPortal(vnode, parent) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
// Register cleanup to remove portal nodes when placeholder is disposed
|
|
373
|
-
portalCtx._cleanupCallbacks = [() => {
|
|
374
|
-
for (const node of portalNodes) {
|
|
375
|
-
disposeTree(node);
|
|
376
|
-
if (node.parentNode) node.parentNode.removeChild(node);
|
|
377
|
-
}
|
|
378
|
-
}];
|
|
379
|
-
|
|
380
|
-
return placeholder;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// --- Reconciliation ---
|
|
384
|
-
// Diff old DOM nodes against new VNodes, patch in place.
|
|
385
|
-
// Uses keyed reconciliation with LIS (Longest Increasing Subsequence) for minimal DOM moves.
|
|
386
|
-
|
|
291
|
+
const { container } = vnode.props;
|
|
292
|
+
const children = vnode.children;
|
|
293
|
+
if (!container) {
|
|
294
|
+
console.warn('[what] Portal: target container not found');
|
|
295
|
+
return document.createComment('portal:empty');
|
|
296
|
+
}
|
|
297
|
+
const portalCtx = {
|
|
298
|
+
hooks: [], hookIndex: 0, effects: [], cleanups: [],
|
|
299
|
+
mounted: false, disposed: false,
|
|
300
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
301
|
+
};
|
|
302
|
+
const placeholder = document.createComment('portal');
|
|
303
|
+
placeholder._componentCtx = portalCtx;
|
|
304
|
+
const portalNodes = [];
|
|
305
|
+
for (const child of children) {
|
|
306
|
+
const node = createDOM(child, container);
|
|
307
|
+
if (node) {
|
|
308
|
+
container.appendChild(node);
|
|
309
|
+
portalNodes.push(node);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
portalCtx._cleanupCallbacks = [() => {
|
|
313
|
+
for (const node of portalNodes) {
|
|
314
|
+
disposeTree(node);
|
|
315
|
+
if (node.parentNode) node.parentNode.removeChild(node);
|
|
316
|
+
}
|
|
317
|
+
}];
|
|
318
|
+
return placeholder;
|
|
319
|
+
}
|
|
387
320
|
function reconcile(parent, oldNodes, newVNodes, beforeMarker) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Unkeyed reconciliation (index-based, fast for static lists)
|
|
321
|
+
if (!parent) return;
|
|
322
|
+
const hasKeys = newVNodes.some(v => v && typeof v === 'object' && v.key != null);
|
|
323
|
+
if (hasKeys) {
|
|
324
|
+
reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker);
|
|
325
|
+
} else {
|
|
326
|
+
reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
400
329
|
function reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// Patch existing node
|
|
429
|
-
const patched = patchNode(parent, oldNode, newVNode);
|
|
430
|
-
newNodes.push(patched);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Update the reference array
|
|
434
|
-
oldNodes.length = 0;
|
|
435
|
-
oldNodes.push(...newNodes);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Keyed reconciliation with LIS algorithm for O(n log n) minimal moves
|
|
330
|
+
const maxLen = Math.max(oldNodes.length, newVNodes.length);
|
|
331
|
+
const newNodes = [];
|
|
332
|
+
for (let i = 0; i < maxLen; i++) {
|
|
333
|
+
const oldNode = oldNodes[i];
|
|
334
|
+
const newVNode = newVNodes[i];
|
|
335
|
+
if (i >= newVNodes.length) {
|
|
336
|
+
if (oldNode && oldNode.parentNode) {
|
|
337
|
+
disposeTree(oldNode);
|
|
338
|
+
oldNode.parentNode.removeChild(oldNode);
|
|
339
|
+
}
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (i >= oldNodes.length) {
|
|
343
|
+
const node = createDOM(newVNode, parent);
|
|
344
|
+
if (node) {
|
|
345
|
+
const ref = getInsertionRef(oldNodes, beforeMarker);
|
|
346
|
+
parent.insertBefore(node, ref);
|
|
347
|
+
newNodes.push(node);
|
|
348
|
+
}
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const patched = patchNode(parent, oldNode, newVNode);
|
|
352
|
+
newNodes.push(patched);
|
|
353
|
+
}
|
|
354
|
+
oldNodes.length = 0;
|
|
355
|
+
oldNodes.push(...newNodes);
|
|
356
|
+
}
|
|
439
357
|
function reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const key = vnode?.key;
|
|
503
|
-
const oldEntry = key != null ? oldKeyMap.get(key) : null;
|
|
504
|
-
|
|
505
|
-
if (oldEntry && sources[i] !== -1) {
|
|
506
|
-
// Reuse existing node
|
|
507
|
-
const oldNode = oldEntry.node;
|
|
508
|
-
// Patch props/children
|
|
509
|
-
const patched = patchNode(parent, oldNode, vnode);
|
|
510
|
-
newNodes[i] = patched;
|
|
511
|
-
|
|
512
|
-
// Move if not in LIS
|
|
513
|
-
if (!lisSet.has(i) && patched.parentNode) {
|
|
514
|
-
parent.insertBefore(patched, lastInserted);
|
|
515
|
-
}
|
|
516
|
-
lastInserted = patched;
|
|
517
|
-
} else {
|
|
518
|
-
// Create new node
|
|
519
|
-
const node = createDOM(vnode, parent);
|
|
520
|
-
if (node) {
|
|
521
|
-
parent.insertBefore(node, lastInserted);
|
|
522
|
-
lastInserted = node;
|
|
523
|
-
}
|
|
524
|
-
newNodes[i] = node;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Update the reference array
|
|
529
|
-
oldNodes.length = 0;
|
|
530
|
-
oldNodes.push(...newNodes.filter(Boolean));
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Longest Increasing Subsequence - O(n log n)
|
|
534
|
-
// Returns indices of elements that form the LIS
|
|
358
|
+
const oldKeyMap = new Map();
|
|
359
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
360
|
+
const node = oldNodes[i];
|
|
361
|
+
const key = node._vnode?.key;
|
|
362
|
+
if (key != null) {
|
|
363
|
+
oldKeyMap.set(key, { node, index: i });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const newNodes = [];
|
|
367
|
+
const newLen = newVNodes.length;
|
|
368
|
+
const sources = new Array(newLen).fill(-1);
|
|
369
|
+
const reused = new Set();
|
|
370
|
+
for (let i = 0; i < newLen; i++) {
|
|
371
|
+
const vnode = newVNodes[i];
|
|
372
|
+
const key = vnode?.key;
|
|
373
|
+
if (key != null && oldKeyMap.has(key)) {
|
|
374
|
+
const { node: oldNode, index: oldIndex } = oldKeyMap.get(key);
|
|
375
|
+
sources[i] = oldIndex;
|
|
376
|
+
reused.add(oldIndex);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
380
|
+
if (!reused.has(i) && oldNodes[i]?.parentNode) {
|
|
381
|
+
disposeTree(oldNodes[i]);
|
|
382
|
+
oldNodes[i].parentNode.removeChild(oldNodes[i]);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const filtered = [];
|
|
386
|
+
const filteredToOriginal = [];
|
|
387
|
+
for (let j = 0; j < sources.length; j++) {
|
|
388
|
+
if (sources[j] !== -1) {
|
|
389
|
+
filteredToOriginal.push(j);
|
|
390
|
+
filtered.push(sources[j]);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const lis = longestIncreasingSubsequence(filtered);
|
|
394
|
+
const lisSet = new Set(lis.map(i => filteredToOriginal[i]));
|
|
395
|
+
let lastInserted = beforeMarker?.nextSibling || null;
|
|
396
|
+
for (let i = newLen - 1; i >= 0; i--) {
|
|
397
|
+
const vnode = newVNodes[i];
|
|
398
|
+
const key = vnode?.key;
|
|
399
|
+
const oldEntry = key != null ? oldKeyMap.get(key) : null;
|
|
400
|
+
if (oldEntry && sources[i] !== -1) {
|
|
401
|
+
const oldNode = oldEntry.node;
|
|
402
|
+
const patched = patchNode(parent, oldNode, vnode);
|
|
403
|
+
newNodes[i] = patched;
|
|
404
|
+
if (!lisSet.has(i) && patched.parentNode) {
|
|
405
|
+
parent.insertBefore(patched, lastInserted);
|
|
406
|
+
}
|
|
407
|
+
lastInserted = patched;
|
|
408
|
+
} else {
|
|
409
|
+
const node = createDOM(vnode, parent);
|
|
410
|
+
if (node) {
|
|
411
|
+
parent.insertBefore(node, lastInserted);
|
|
412
|
+
lastInserted = node;
|
|
413
|
+
}
|
|
414
|
+
newNodes[i] = node;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
oldNodes.length = 0;
|
|
418
|
+
oldNodes.push(...newNodes.filter(Boolean));
|
|
419
|
+
}
|
|
535
420
|
function longestIncreasingSubsequence(arr) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
result.push(k);
|
|
567
|
-
k = parent[k];
|
|
568
|
-
}
|
|
569
|
-
return result.reverse();
|
|
570
|
-
}
|
|
571
|
-
|
|
421
|
+
if (arr.length === 0) return [];
|
|
422
|
+
const n = arr.length;
|
|
423
|
+
const dp = new Array(n).fill(1);
|
|
424
|
+
const parent = new Array(n).fill(-1);
|
|
425
|
+
const tails = [0];
|
|
426
|
+
for (let i = 1; i < n; i++) {
|
|
427
|
+
if (arr[i] > arr[tails[tails.length - 1]]) {
|
|
428
|
+
parent[i] = tails[tails.length - 1];
|
|
429
|
+
tails.push(i);
|
|
430
|
+
} else {
|
|
431
|
+
let lo = 0, hi = tails.length - 1;
|
|
432
|
+
while (lo < hi) {
|
|
433
|
+
const mid = (lo + hi) >> 1;
|
|
434
|
+
if (arr[tails[mid]] < arr[i]) lo = mid + 1;
|
|
435
|
+
else hi = mid;
|
|
436
|
+
}
|
|
437
|
+
if (arr[i] < arr[tails[lo]]) {
|
|
438
|
+
if (lo > 0) parent[i] = tails[lo - 1];
|
|
439
|
+
tails[lo] = i;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const result = [];
|
|
444
|
+
let k = tails[tails.length - 1];
|
|
445
|
+
while (k !== -1) {
|
|
446
|
+
result.push(k);
|
|
447
|
+
k = parent[k];
|
|
448
|
+
}
|
|
449
|
+
return result.reverse();
|
|
450
|
+
}
|
|
572
451
|
function getInsertionRef(nodes, marker) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Helper: clean up array marker range (startMarker .. endMarker) and return a clean replacement node
|
|
452
|
+
if (nodes.length > 0) {
|
|
453
|
+
const last = nodes[nodes.length - 1];
|
|
454
|
+
return last.nextSibling;
|
|
455
|
+
}
|
|
456
|
+
return marker ? marker.nextSibling : null;
|
|
457
|
+
}
|
|
581
458
|
function cleanupArrayMarkers(parent, startMarker) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
return startMarker;
|
|
595
|
-
}
|
|
596
|
-
|
|
459
|
+
const endMarker = startMarker._arrayEnd;
|
|
460
|
+
if (!endMarker) return null;
|
|
461
|
+
let node = startMarker.nextSibling;
|
|
462
|
+
while (node && node !== endMarker) {
|
|
463
|
+
const next = node.nextSibling;
|
|
464
|
+
disposeTree(node);
|
|
465
|
+
parent.removeChild(node);
|
|
466
|
+
node = next;
|
|
467
|
+
}
|
|
468
|
+
if (endMarker.parentNode) parent.removeChild(endMarker);
|
|
469
|
+
return startMarker;
|
|
470
|
+
}
|
|
597
471
|
function patchNode(parent, domNode, vnode) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
472
|
+
if (vnode == null || vnode === false || vnode === true) {
|
|
473
|
+
if (domNode && domNode.nodeType === 8 && domNode._arrayEnd) {
|
|
474
|
+
cleanupArrayMarkers(parent, domNode);
|
|
475
|
+
const placeholder = document.createComment('');
|
|
476
|
+
parent.replaceChild(placeholder, domNode);
|
|
477
|
+
return placeholder;
|
|
478
|
+
}
|
|
479
|
+
if (domNode && domNode.nodeType === 8 && !domNode._componentCtx) {
|
|
480
|
+
return domNode;
|
|
481
|
+
}
|
|
482
|
+
const placeholder = document.createComment('');
|
|
483
|
+
if (domNode && domNode.parentNode) {
|
|
484
|
+
disposeTree(domNode);
|
|
485
|
+
parent.replaceChild(placeholder, domNode);
|
|
486
|
+
}
|
|
487
|
+
return placeholder;
|
|
488
|
+
}
|
|
489
|
+
if (typeof vnode === 'function') {
|
|
490
|
+
const wrapper = document.createElement('what-c');
|
|
491
|
+
let mounted = false;
|
|
492
|
+
const dispose = effect(() => {
|
|
493
|
+
const val = vnode();
|
|
494
|
+
const vnodes = (val == null || val === false || val === true)
|
|
495
|
+
? []
|
|
496
|
+
: Array.isArray(val) ? val : [val];
|
|
497
|
+
if (!mounted) {
|
|
498
|
+
mounted = true;
|
|
499
|
+
for (const v of vnodes) {
|
|
500
|
+
const node = createDOM(v, wrapper);
|
|
501
|
+
if (node) wrapper.appendChild(node);
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
reconcileChildren(wrapper, vnodes);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
wrapper._dispose = dispose;
|
|
508
|
+
if (domNode && domNode.parentNode) {
|
|
509
|
+
disposeTree(domNode);
|
|
510
|
+
parent.replaceChild(wrapper, domNode);
|
|
511
|
+
}
|
|
512
|
+
return wrapper;
|
|
513
|
+
}
|
|
514
|
+
if (isDomNode(vnode)) {
|
|
515
|
+
if (domNode === vnode) return domNode;
|
|
516
|
+
if (domNode && domNode.parentNode) {
|
|
517
|
+
disposeTree(domNode);
|
|
518
|
+
parent.replaceChild(vnode, domNode);
|
|
519
|
+
}
|
|
520
|
+
return vnode;
|
|
521
|
+
}
|
|
522
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
523
|
+
const text = String(vnode);
|
|
524
|
+
if (domNode && domNode.nodeType === 8 && domNode._arrayEnd) {
|
|
525
|
+
cleanupArrayMarkers(parent, domNode);
|
|
526
|
+
const newNode = document.createTextNode(text);
|
|
527
|
+
parent.replaceChild(newNode, domNode);
|
|
528
|
+
return newNode;
|
|
529
|
+
}
|
|
530
|
+
if (domNode.nodeType === 3) {
|
|
531
|
+
if (domNode.textContent !== text) domNode.textContent = text;
|
|
532
|
+
return domNode;
|
|
533
|
+
}
|
|
534
|
+
const newNode = document.createTextNode(text);
|
|
535
|
+
disposeTree(domNode);
|
|
536
|
+
parent.replaceChild(newNode, domNode);
|
|
537
|
+
return newNode;
|
|
538
|
+
}
|
|
539
|
+
if (Array.isArray(vnode)) {
|
|
540
|
+
if (domNode && domNode.nodeType === 8 && domNode._arrayEnd) {
|
|
541
|
+
const endMarker = domNode._arrayEnd;
|
|
542
|
+
const oldChildren = [];
|
|
543
|
+
let node = domNode.nextSibling;
|
|
544
|
+
while (node && node !== endMarker) {
|
|
545
|
+
oldChildren.push(node);
|
|
546
|
+
node = node.nextSibling;
|
|
547
|
+
}
|
|
548
|
+
const maxLen = Math.max(oldChildren.length, vnode.length);
|
|
549
|
+
for (let i = 0; i < maxLen; i++) {
|
|
550
|
+
if (i >= vnode.length) {
|
|
551
|
+
if (oldChildren[i]?.parentNode) {
|
|
552
|
+
disposeTree(oldChildren[i]);
|
|
553
|
+
parent.removeChild(oldChildren[i]);
|
|
554
|
+
}
|
|
555
|
+
} else if (i >= oldChildren.length) {
|
|
556
|
+
const newNode = createDOM(vnode[i], parent);
|
|
557
|
+
if (newNode) parent.insertBefore(newNode, endMarker);
|
|
558
|
+
} else {
|
|
559
|
+
patchNode(parent, oldChildren[i], vnode[i]);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return domNode;
|
|
563
|
+
}
|
|
564
|
+
const startMarker = document.createComment('[');
|
|
565
|
+
const endMarker = document.createComment(']');
|
|
566
|
+
disposeTree(domNode);
|
|
567
|
+
parent.replaceChild(endMarker, domNode);
|
|
568
|
+
parent.insertBefore(startMarker, endMarker);
|
|
569
|
+
for (const v of vnode) {
|
|
570
|
+
const node = createDOM(v, parent);
|
|
571
|
+
if (node) parent.insertBefore(node, endMarker);
|
|
572
|
+
}
|
|
573
|
+
startMarker._arrayEnd = endMarker;
|
|
574
|
+
return startMarker;
|
|
575
|
+
}
|
|
576
|
+
if (!isVNode(vnode)) {
|
|
577
|
+
const text = String(vnode);
|
|
578
|
+
if (domNode.nodeType === 3) {
|
|
579
|
+
if (domNode.textContent !== text) domNode.textContent = text;
|
|
580
|
+
return domNode;
|
|
581
|
+
}
|
|
582
|
+
const newNode = document.createTextNode(text);
|
|
583
|
+
disposeTree(domNode);
|
|
584
|
+
parent.replaceChild(newNode, domNode);
|
|
585
|
+
return newNode;
|
|
586
|
+
}
|
|
587
|
+
if (typeof vnode.tag === 'function') {
|
|
588
|
+
if (domNode._componentCtx && !domNode._componentCtx.disposed
|
|
589
|
+
&& domNode._componentCtx.Component === vnode.tag) {
|
|
590
|
+
domNode._componentCtx._propsSignal.set({ ...vnode.props, children: vnode.children });
|
|
591
|
+
domNode._vnode = vnode;
|
|
592
|
+
return domNode;
|
|
593
|
+
}
|
|
594
|
+
disposeTree(domNode);
|
|
595
|
+
const node = createComponent(vnode, parent);
|
|
596
|
+
parent.replaceChild(node, domNode);
|
|
597
|
+
return node;
|
|
598
|
+
}
|
|
599
|
+
if (domNode.nodeType === 1 && domNode.tagName.toLowerCase() === vnode.tag) {
|
|
600
|
+
const oldProps = domNode._vnode?.props || {};
|
|
601
|
+
const nextProps = vnode.props || {};
|
|
602
|
+
const hadRawHtml = Object.prototype.hasOwnProperty.call(oldProps, 'dangerouslySetInnerHTML')
|
|
603
|
+
|| Object.prototype.hasOwnProperty.call(oldProps, 'innerHTML');
|
|
604
|
+
const hasRawHtml = Object.prototype.hasOwnProperty.call(nextProps, 'dangerouslySetInnerHTML')
|
|
605
|
+
|| Object.prototype.hasOwnProperty.call(nextProps, 'innerHTML');
|
|
606
|
+
if (hasRawHtml && !hadRawHtml) {
|
|
607
|
+
for (const child of Array.from(domNode.childNodes)) {
|
|
608
|
+
disposeTree(child);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
applyProps(domNode, nextProps, oldProps);
|
|
612
|
+
if (!hasRawHtml) {
|
|
613
|
+
reconcileChildren(domNode, vnode.children);
|
|
614
|
+
}
|
|
615
|
+
domNode._vnode = vnode;
|
|
616
|
+
return domNode;
|
|
617
|
+
}
|
|
618
|
+
const newNode = createDOM(vnode, parent);
|
|
619
|
+
disposeTree(domNode);
|
|
620
|
+
parent.replaceChild(newNode, domNode);
|
|
621
|
+
return newNode;
|
|
622
|
+
}
|
|
716
623
|
function reconcileChildren(parent, newChildVNodes) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
// Append new
|
|
741
|
-
const node = createDOM(newChildVNodes[i], parent);
|
|
742
|
-
if (node) parent.appendChild(node);
|
|
743
|
-
continue;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
patchNode(parent, oldChildren[i], newChildVNodes[i]);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// --- Prop Diffing ---
|
|
752
|
-
// Only touch DOM for props that actually changed.
|
|
753
|
-
|
|
624
|
+
const oldChildren = Array.from(parent.childNodes);
|
|
625
|
+
const hasKeys = newChildVNodes.some(v => v && typeof v === 'object' && v.key != null);
|
|
626
|
+
if (hasKeys) {
|
|
627
|
+
reconcileKeyed(parent, oldChildren, newChildVNodes, null);
|
|
628
|
+
} else {
|
|
629
|
+
const maxLen = Math.max(oldChildren.length, newChildVNodes.length);
|
|
630
|
+
for (let i = 0; i < maxLen; i++) {
|
|
631
|
+
if (i >= newChildVNodes.length) {
|
|
632
|
+
if (oldChildren[i]?.parentNode) {
|
|
633
|
+
disposeTree(oldChildren[i]);
|
|
634
|
+
parent.removeChild(oldChildren[i]);
|
|
635
|
+
}
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (i >= oldChildren.length) {
|
|
639
|
+
const node = createDOM(newChildVNodes[i], parent);
|
|
640
|
+
if (node) parent.appendChild(node);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
patchNode(parent, oldChildren[i], newChildVNodes[i]);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
754
647
|
function applyProps(el, newProps, oldProps, isSvg) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
// Handle ref
|
|
775
|
-
if (newProps.ref && newProps.ref !== oldProps.ref) {
|
|
776
|
-
if (typeof newProps.ref === 'function') newProps.ref(el);
|
|
777
|
-
else newProps.ref.current = el;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
648
|
+
newProps = newProps || {};
|
|
649
|
+
oldProps = oldProps || {};
|
|
650
|
+
for (const key in oldProps) {
|
|
651
|
+
if (key === 'key' || key === 'ref' || key === 'children') continue;
|
|
652
|
+
if (!(key in newProps)) {
|
|
653
|
+
removeProp(el, key, oldProps[key]);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const key in newProps) {
|
|
657
|
+
if (key === 'key' || key === 'ref' || key === 'children') continue;
|
|
658
|
+
if (newProps[key] !== oldProps[key]) {
|
|
659
|
+
setProp(el, key, newProps[key], isSvg);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (newProps.ref && newProps.ref !== oldProps.ref) {
|
|
663
|
+
if (typeof newProps.ref === 'function') newProps.ref(el);
|
|
664
|
+
else newProps.ref.current = el;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
781
667
|
function setProp(el, key, value, isSvg) {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
} else {
|
|
855
|
-
el.setAttribute(key, value === true ? '' : String(value));
|
|
856
|
-
}
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Default: set as property if it exists, otherwise attribute
|
|
861
|
-
if (key in el) {
|
|
862
|
-
el[key] = value;
|
|
863
|
-
} else {
|
|
864
|
-
el.setAttribute(key, value);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
668
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
669
|
+
const event = key.slice(2).toLowerCase();
|
|
670
|
+
const old = el._events?.[event];
|
|
671
|
+
if (old && old._original === value) return;
|
|
672
|
+
if (old) el.removeEventListener(event, old);
|
|
673
|
+
if (!el._events) el._events = {};
|
|
674
|
+
const wrappedHandler = (e) => untrack(() => value(e));
|
|
675
|
+
wrappedHandler._original = value;
|
|
676
|
+
el._events[event] = wrappedHandler;
|
|
677
|
+
const eventOpts = value._eventOpts;
|
|
678
|
+
el.addEventListener(event, wrappedHandler, eventOpts || undefined);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (key === 'className' || key === 'class') {
|
|
682
|
+
if (isSvg) {
|
|
683
|
+
el.setAttribute('class', value || '');
|
|
684
|
+
} else {
|
|
685
|
+
el.className = value || '';
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (key === 'style') {
|
|
690
|
+
if (typeof value === 'string') {
|
|
691
|
+
el.style.cssText = value;
|
|
692
|
+
el._prevStyle = null;
|
|
693
|
+
} else if (typeof value === 'object') {
|
|
694
|
+
const oldStyle = el._prevStyle || {};
|
|
695
|
+
for (const prop in oldStyle) {
|
|
696
|
+
if (!(prop in value)) el.style[prop] = '';
|
|
697
|
+
}
|
|
698
|
+
for (const prop in value) {
|
|
699
|
+
el.style[prop] = value[prop] ?? '';
|
|
700
|
+
}
|
|
701
|
+
el._prevStyle = { ...value };
|
|
702
|
+
}
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (key === 'dangerouslySetInnerHTML') {
|
|
706
|
+
el.innerHTML = value?.__html ?? '';
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (key === 'innerHTML') {
|
|
710
|
+
if (value && typeof value === 'object' && '__html' in value) {
|
|
711
|
+
el.innerHTML = value.__html ?? '';
|
|
712
|
+
} else {
|
|
713
|
+
el.innerHTML = value ?? '';
|
|
714
|
+
}
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (typeof value === 'boolean') {
|
|
718
|
+
if (value) el.setAttribute(key, '');
|
|
719
|
+
else el.removeAttribute(key);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
|
723
|
+
el.setAttribute(key, value);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (isSvg) {
|
|
727
|
+
if (value === false || value == null) {
|
|
728
|
+
el.removeAttribute(key);
|
|
729
|
+
} else {
|
|
730
|
+
el.setAttribute(key, value === true ? '' : String(value));
|
|
731
|
+
}
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (key in el) {
|
|
735
|
+
el[key] = value;
|
|
736
|
+
} else {
|
|
737
|
+
el.setAttribute(key, value);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
868
740
|
function removeProp(el, key, oldValue) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
el.removeAttribute(key);
|
|
741
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
742
|
+
const event = key.slice(2).toLowerCase();
|
|
743
|
+
if (el._events?.[event]) {
|
|
744
|
+
el.removeEventListener(event, el._events[event]);
|
|
745
|
+
delete el._events[event];
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (key === 'className' || key === 'class') {
|
|
750
|
+
el.className = '';
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (key === 'style') {
|
|
754
|
+
el.style.cssText = '';
|
|
755
|
+
el._prevStyle = null;
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (key === 'dangerouslySetInnerHTML' || key === 'innerHTML') {
|
|
759
|
+
el.innerHTML = '';
|
|
760
|
+
return;
|
|
890
761
|
}
|
|
762
|
+
el.removeAttribute(key);
|
|
763
|
+
}
|