what-core 0.1.1 → 0.3.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/a11y.js +425 -0
- package/dist/animation.js +540 -0
- package/dist/components.js +272 -115
- package/dist/data.js +444 -0
- package/dist/dom.js +702 -427
- package/dist/form.js +441 -0
- package/dist/h.js +191 -138
- package/dist/head.js +59 -42
- package/dist/helpers.js +125 -83
- package/dist/hooks.js +226 -124
- package/dist/index.js +2 -2
- package/dist/reactive.js +165 -108
- package/dist/scheduler.js +241 -0
- package/dist/skeleton.js +363 -0
- package/dist/store.js +114 -55
- package/dist/testing.js +367 -0
- package/dist/what.js +2 -2
- package/index.d.ts +15 -0
- package/package.json +1 -1
- package/src/animation.js +11 -2
- package/src/components.js +93 -0
- package/src/data.js +19 -9
- package/src/dom.js +181 -85
- package/src/hooks.js +22 -10
- package/src/index.js +2 -2
- package/src/reactive.js +15 -1
- package/src/store.js +24 -5
package/dist/dom.js
CHANGED
|
@@ -1,443 +1,718 @@
|
|
|
1
|
-
|
|
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
|
+
import { effect, batch, untrack, signal } from './reactive.js';
|
|
2
7
|
import { errorBoundaryStack, reportError } from './components.js';
|
|
8
|
+
|
|
9
|
+
// SVG elements that need namespace
|
|
10
|
+
const SVG_ELEMENTS = new Set([
|
|
11
|
+
'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse',
|
|
12
|
+
'g', 'defs', 'use', 'symbol', 'clipPath', 'mask', 'pattern', 'image',
|
|
13
|
+
'text', 'tspan', 'textPath', 'foreignObject', 'linearGradient', 'radialGradient', 'stop',
|
|
14
|
+
'marker', 'animate', 'animateTransform', 'animateMotion', 'set', 'filter',
|
|
15
|
+
'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix',
|
|
16
|
+
'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage',
|
|
17
|
+
'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'feSpecularLighting',
|
|
18
|
+
'feTile', 'feTurbulence',
|
|
19
|
+
]);
|
|
20
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
21
|
+
|
|
22
|
+
// Track all mounted component contexts for disposal
|
|
23
|
+
const mountedComponents = new Set();
|
|
24
|
+
|
|
25
|
+
// Dispose a component: run effect cleanups, hook cleanups, onCleanup callbacks
|
|
26
|
+
function disposeComponent(ctx) {
|
|
27
|
+
if (ctx.disposed) return;
|
|
28
|
+
ctx.disposed = true;
|
|
29
|
+
|
|
30
|
+
// Run useEffect cleanup functions
|
|
31
|
+
for (const hook of ctx.hooks) {
|
|
32
|
+
if (hook && typeof hook === 'object' && 'cleanup' in hook && hook.cleanup) {
|
|
33
|
+
try { hook.cleanup(); } catch (e) { console.error('[what] cleanup error:', e); }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Run onCleanup callbacks
|
|
38
|
+
if (ctx._cleanupCallbacks) {
|
|
39
|
+
for (const fn of ctx._cleanupCallbacks) {
|
|
40
|
+
try { fn(); } catch (e) { console.error('[what] onCleanup error:', e); }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Dispose reactive effects
|
|
45
|
+
for (const dispose of ctx.effects) {
|
|
46
|
+
try { dispose(); } catch (e) { /* effect already disposed */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mountedComponents.delete(ctx);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Dispose all components attached to a DOM subtree
|
|
53
|
+
function disposeTree(node) {
|
|
54
|
+
if (!node) return;
|
|
55
|
+
if (node._componentCtx) {
|
|
56
|
+
disposeComponent(node._componentCtx);
|
|
57
|
+
}
|
|
58
|
+
if (node.childNodes) {
|
|
59
|
+
for (const child of node.childNodes) {
|
|
60
|
+
disposeTree(child);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Mount a component tree into a DOM container
|
|
3
66
|
export function mount(vnode, container) {
|
|
4
|
-
if (typeof container === 'string') {
|
|
5
|
-
container = document.querySelector(container);
|
|
6
|
-
}
|
|
7
|
-
container
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return
|
|
38
|
-
}
|
|
67
|
+
if (typeof container === 'string') {
|
|
68
|
+
container = document.querySelector(container);
|
|
69
|
+
}
|
|
70
|
+
disposeTree(container); // Clean up any previous mount
|
|
71
|
+
container.textContent = '';
|
|
72
|
+
const node = createDOM(vnode, container);
|
|
73
|
+
if (node) container.appendChild(node);
|
|
74
|
+
return () => {
|
|
75
|
+
disposeTree(container);
|
|
76
|
+
container.textContent = '';
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Create DOM from VNode ---
|
|
81
|
+
|
|
82
|
+
function createDOM(vnode, parent, isSvg) {
|
|
83
|
+
// Null/false/true → placeholder comment (preserves child indices for reconciliation)
|
|
84
|
+
if (vnode == null || vnode === false || vnode === true) {
|
|
85
|
+
return document.createComment('');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Text
|
|
89
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
90
|
+
return document.createTextNode(String(vnode));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Array (fragment)
|
|
94
|
+
if (Array.isArray(vnode)) {
|
|
95
|
+
const frag = document.createDocumentFragment();
|
|
96
|
+
for (const child of vnode) {
|
|
97
|
+
const node = createDOM(child, parent, isSvg);
|
|
98
|
+
if (node) frag.appendChild(node);
|
|
99
|
+
}
|
|
100
|
+
return frag;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Component
|
|
104
|
+
if (typeof vnode.tag === 'function') {
|
|
105
|
+
return createComponent(vnode, parent, isSvg);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Detect SVG context: either we're already in SVG, or this tag is an SVG element
|
|
109
|
+
const svgContext = isSvg || vnode.tag === 'svg' || SVG_ELEMENTS.has(vnode.tag);
|
|
110
|
+
|
|
111
|
+
// HTML or SVG Element
|
|
112
|
+
const el = svgContext
|
|
113
|
+
? document.createElementNS(SVG_NS, vnode.tag)
|
|
114
|
+
: document.createElement(vnode.tag);
|
|
115
|
+
|
|
116
|
+
applyProps(el, vnode.props, {}, svgContext);
|
|
117
|
+
for (const child of vnode.children) {
|
|
118
|
+
const node = createDOM(child, el, svgContext && vnode.tag !== 'foreignObject');
|
|
119
|
+
if (node) el.appendChild(node);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Store vnode on element for diffing
|
|
123
|
+
el._vnode = vnode;
|
|
124
|
+
return el;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Component Rendering ---
|
|
128
|
+
|
|
39
129
|
const componentStack = [];
|
|
130
|
+
|
|
40
131
|
export function getCurrentComponent() {
|
|
41
|
-
return componentStack[componentStack.length - 1];
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
return componentStack[componentStack.length - 1];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getComponentStack() {
|
|
136
|
+
return componentStack;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createComponent(vnode, parent, isSvg) {
|
|
140
|
+
const { tag: Component, props, children } = vnode;
|
|
141
|
+
|
|
142
|
+
// Handle special boundary components
|
|
143
|
+
if (Component === '__errorBoundary' || vnode.tag === '__errorBoundary') {
|
|
144
|
+
return createErrorBoundary(vnode, parent);
|
|
145
|
+
}
|
|
146
|
+
if (Component === '__suspense' || vnode.tag === '__suspense') {
|
|
147
|
+
return createSuspenseBoundary(vnode, parent);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Component context for hooks
|
|
151
|
+
const ctx = {
|
|
152
|
+
hooks: [],
|
|
153
|
+
hookIndex: 0,
|
|
154
|
+
effects: [],
|
|
155
|
+
cleanups: [],
|
|
156
|
+
mounted: false,
|
|
157
|
+
disposed: false,
|
|
158
|
+
Component, // Store for identity check in patchNode
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Wrapper element: <what-c display:contents> for HTML, <g> for SVG
|
|
162
|
+
let wrapper;
|
|
163
|
+
if (isSvg) {
|
|
164
|
+
wrapper = document.createElementNS(SVG_NS, 'g');
|
|
165
|
+
} else {
|
|
166
|
+
wrapper = document.createElement('what-c');
|
|
167
|
+
wrapper.style.display = 'contents';
|
|
168
|
+
}
|
|
169
|
+
wrapper._componentCtx = ctx;
|
|
170
|
+
wrapper._isSvg = !!isSvg;
|
|
171
|
+
ctx._wrapper = wrapper;
|
|
172
|
+
|
|
173
|
+
// Track for disposal
|
|
174
|
+
mountedComponents.add(ctx);
|
|
175
|
+
|
|
176
|
+
// Props signal for reactive updates from parent
|
|
177
|
+
const propsSignal = signal({ ...props, children });
|
|
178
|
+
ctx._propsSignal = propsSignal;
|
|
179
|
+
|
|
180
|
+
// Reactive render: re-renders when signals used inside change
|
|
181
|
+
const dispose = effect(() => {
|
|
182
|
+
if (ctx.disposed) return;
|
|
183
|
+
ctx.hookIndex = 0;
|
|
184
|
+
|
|
185
|
+
componentStack.push(ctx);
|
|
186
|
+
|
|
187
|
+
let result;
|
|
188
|
+
try {
|
|
189
|
+
result = Component(propsSignal());
|
|
190
|
+
} catch (error) {
|
|
191
|
+
componentStack.pop();
|
|
192
|
+
if (!reportError(error)) {
|
|
193
|
+
console.error('[what] Uncaught error in component:', Component.name || 'Anonymous', error);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
componentStack.pop();
|
|
200
|
+
|
|
201
|
+
const vnodes = Array.isArray(result) ? result : [result];
|
|
202
|
+
|
|
203
|
+
if (!ctx.mounted) {
|
|
204
|
+
// Initial mount
|
|
205
|
+
ctx.mounted = true;
|
|
206
|
+
|
|
207
|
+
// Run onMount callbacks after DOM is ready
|
|
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
|
+
|
|
217
|
+
for (const v of vnodes) {
|
|
218
|
+
const node = createDOM(v, wrapper, isSvg);
|
|
219
|
+
if (node) wrapper.appendChild(node);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// Update: reconcile children inside wrapper
|
|
223
|
+
reconcileChildren(wrapper, vnodes);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
ctx.effects.push(dispose);
|
|
228
|
+
return wrapper;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Error boundary component handler
|
|
98
232
|
function createErrorBoundary(vnode, parent) {
|
|
99
|
-
const { errorState, handleError, fallback, reset } = vnode.props;
|
|
100
|
-
const children = vnode.children;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
vnodes
|
|
111
|
-
|
|
112
|
-
vnodes = [fallback];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
errorBoundaryStack.pop();
|
|
118
|
-
vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
233
|
+
const { errorState, handleError, fallback, reset } = vnode.props;
|
|
234
|
+
const children = vnode.children;
|
|
235
|
+
|
|
236
|
+
const wrapper = document.createElement('what-c');
|
|
237
|
+
wrapper.style.display = 'contents';
|
|
238
|
+
|
|
239
|
+
const dispose = effect(() => {
|
|
240
|
+
const error = errorState();
|
|
241
|
+
|
|
242
|
+
errorBoundaryStack.push({ handleError });
|
|
243
|
+
|
|
244
|
+
let vnodes;
|
|
245
|
+
if (error) {
|
|
246
|
+
vnodes = typeof fallback === 'function' ? [fallback({ error, reset })] : [fallback];
|
|
247
|
+
} else {
|
|
248
|
+
vnodes = children;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
errorBoundaryStack.pop();
|
|
252
|
+
vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
253
|
+
|
|
254
|
+
if (wrapper.childNodes.length === 0) {
|
|
255
|
+
for (const v of vnodes) {
|
|
256
|
+
const node = createDOM(v, wrapper);
|
|
257
|
+
if (node) wrapper.appendChild(node);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
reconcileChildren(wrapper, vnodes);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return wrapper;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Suspense boundary component handler
|
|
135
268
|
function createSuspenseBoundary(vnode, parent) {
|
|
136
|
-
const { boundary, fallback, loading } = vnode.props;
|
|
137
|
-
const children = vnode.children;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
269
|
+
const { boundary, fallback, loading } = vnode.props;
|
|
270
|
+
const children = vnode.children;
|
|
271
|
+
|
|
272
|
+
const wrapper = document.createElement('what-c');
|
|
273
|
+
wrapper.style.display = 'contents';
|
|
274
|
+
|
|
275
|
+
const dispose = effect(() => {
|
|
276
|
+
const isLoading = loading();
|
|
277
|
+
const vnodes = isLoading ? [fallback] : children;
|
|
278
|
+
const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
279
|
+
|
|
280
|
+
if (wrapper.childNodes.length === 0) {
|
|
281
|
+
for (const v of normalized) {
|
|
282
|
+
const node = createDOM(v, wrapper);
|
|
283
|
+
if (node) wrapper.appendChild(node);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
reconcileChildren(wrapper, normalized);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return wrapper;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// --- Reconciliation ---
|
|
294
|
+
// Diff old DOM nodes against new VNodes, patch in place.
|
|
295
|
+
// Uses keyed reconciliation with LIS (Longest Increasing Subsequence) for minimal DOM moves.
|
|
296
|
+
|
|
160
297
|
function reconcile(parent, oldNodes, newVNodes, beforeMarker) {
|
|
161
|
-
if (!parent) return;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
298
|
+
if (!parent) return;
|
|
299
|
+
|
|
300
|
+
const hasKeys = newVNodes.some(v => v && typeof v === 'object' && v.key != null);
|
|
301
|
+
|
|
302
|
+
if (hasKeys) {
|
|
303
|
+
reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker);
|
|
304
|
+
} else {
|
|
305
|
+
reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Unkeyed reconciliation (index-based, fast for static lists)
|
|
169
310
|
function reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
170
|
-
const maxLen = Math.max(oldNodes.length, newVNodes.length);
|
|
171
|
-
const newNodes = [];
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
311
|
+
const maxLen = Math.max(oldNodes.length, newVNodes.length);
|
|
312
|
+
const newNodes = [];
|
|
313
|
+
|
|
314
|
+
for (let i = 0; i < maxLen; i++) {
|
|
315
|
+
const oldNode = oldNodes[i];
|
|
316
|
+
const newVNode = newVNodes[i];
|
|
317
|
+
|
|
318
|
+
if (i >= newVNodes.length) {
|
|
319
|
+
// Remove extra old nodes
|
|
320
|
+
if (oldNode && oldNode.parentNode) {
|
|
321
|
+
disposeTree(oldNode);
|
|
322
|
+
oldNode.parentNode.removeChild(oldNode);
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (i >= oldNodes.length) {
|
|
328
|
+
// Append new nodes
|
|
329
|
+
const node = createDOM(newVNode, parent);
|
|
330
|
+
if (node) {
|
|
331
|
+
const ref = getInsertionRef(oldNodes, beforeMarker);
|
|
332
|
+
parent.insertBefore(node, ref);
|
|
333
|
+
newNodes.push(node);
|
|
334
|
+
}
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Patch existing node
|
|
339
|
+
const patched = patchNode(parent, oldNode, newVNode);
|
|
340
|
+
newNodes.push(patched);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Update the reference array
|
|
344
|
+
oldNodes.length = 0;
|
|
345
|
+
oldNodes.push(...newNodes);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Keyed reconciliation with LIS algorithm for O(n log n) minimal moves
|
|
196
349
|
function reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
let
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
lastInserted =
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
350
|
+
// Build old key -> { node, index } map
|
|
351
|
+
const oldKeyMap = new Map();
|
|
352
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
353
|
+
const node = oldNodes[i];
|
|
354
|
+
const key = node._vnode?.key;
|
|
355
|
+
if (key != null) {
|
|
356
|
+
oldKeyMap.set(key, { node, index: i });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const newNodes = [];
|
|
361
|
+
const newLen = newVNodes.length;
|
|
362
|
+
|
|
363
|
+
// First pass: match keys and find reusable nodes
|
|
364
|
+
const sources = new Array(newLen).fill(-1); // Maps new index to old index
|
|
365
|
+
const reused = new Set();
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < newLen; i++) {
|
|
368
|
+
const vnode = newVNodes[i];
|
|
369
|
+
const key = vnode?.key;
|
|
370
|
+
if (key != null && oldKeyMap.has(key)) {
|
|
371
|
+
const { node: oldNode, index: oldIndex } = oldKeyMap.get(key);
|
|
372
|
+
sources[i] = oldIndex;
|
|
373
|
+
reused.add(oldIndex);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Remove nodes that aren't reused
|
|
378
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
379
|
+
if (!reused.has(i) && oldNodes[i]?.parentNode) {
|
|
380
|
+
disposeTree(oldNodes[i]);
|
|
381
|
+
oldNodes[i].parentNode.removeChild(oldNodes[i]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Find LIS of old indices to determine which nodes don't need to move
|
|
386
|
+
const lis = longestIncreasingSubsequence(sources.filter(s => s !== -1));
|
|
387
|
+
const lisSet = new Set(lis.map((_, i) => {
|
|
388
|
+
let count = 0;
|
|
389
|
+
for (let j = 0; j < sources.length; j++) {
|
|
390
|
+
if (sources[j] !== -1) {
|
|
391
|
+
if (count === lis[i]) return j;
|
|
392
|
+
count++;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return -1;
|
|
396
|
+
}));
|
|
397
|
+
|
|
398
|
+
// Build new nodes array and move/create as needed
|
|
399
|
+
let lastInserted = beforeMarker?.nextSibling || null;
|
|
400
|
+
|
|
401
|
+
// Process in reverse order for correct insertion
|
|
402
|
+
for (let i = newLen - 1; i >= 0; i--) {
|
|
403
|
+
const vnode = newVNodes[i];
|
|
404
|
+
const key = vnode?.key;
|
|
405
|
+
const oldEntry = key != null ? oldKeyMap.get(key) : null;
|
|
406
|
+
|
|
407
|
+
if (oldEntry && sources[i] !== -1) {
|
|
408
|
+
// Reuse existing node
|
|
409
|
+
const oldNode = oldEntry.node;
|
|
410
|
+
// Patch props/children
|
|
411
|
+
const patched = patchNode(parent, oldNode, vnode);
|
|
412
|
+
newNodes[i] = patched;
|
|
413
|
+
|
|
414
|
+
// Move if not in LIS
|
|
415
|
+
if (!lisSet.has(i) && patched.parentNode) {
|
|
416
|
+
parent.insertBefore(patched, lastInserted);
|
|
417
|
+
}
|
|
418
|
+
lastInserted = patched;
|
|
419
|
+
} else {
|
|
420
|
+
// Create new node
|
|
421
|
+
const node = createDOM(vnode, parent);
|
|
422
|
+
if (node) {
|
|
423
|
+
parent.insertBefore(node, lastInserted);
|
|
424
|
+
lastInserted = node;
|
|
425
|
+
}
|
|
426
|
+
newNodes[i] = node;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Update the reference array
|
|
431
|
+
oldNodes.length = 0;
|
|
432
|
+
oldNodes.push(...newNodes.filter(Boolean));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Longest Increasing Subsequence - O(n log n)
|
|
436
|
+
// Returns indices of elements that form the LIS
|
|
259
437
|
function longestIncreasingSubsequence(arr) {
|
|
260
|
-
if (arr.length === 0) return [];
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
tails.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (arr[
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
438
|
+
if (arr.length === 0) return [];
|
|
439
|
+
|
|
440
|
+
const n = arr.length;
|
|
441
|
+
const dp = new Array(n).fill(1); // Length of LIS ending at i
|
|
442
|
+
const parent = new Array(n).fill(-1); // Parent index for reconstruction
|
|
443
|
+
const tails = [0]; // Indices of smallest tail elements
|
|
444
|
+
|
|
445
|
+
for (let i = 1; i < n; i++) {
|
|
446
|
+
if (arr[i] > arr[tails[tails.length - 1]]) {
|
|
447
|
+
parent[i] = tails[tails.length - 1];
|
|
448
|
+
tails.push(i);
|
|
449
|
+
} else {
|
|
450
|
+
// Binary search for the smallest element >= arr[i]
|
|
451
|
+
let lo = 0, hi = tails.length - 1;
|
|
452
|
+
while (lo < hi) {
|
|
453
|
+
const mid = (lo + hi) >> 1;
|
|
454
|
+
if (arr[tails[mid]] < arr[i]) lo = mid + 1;
|
|
455
|
+
else hi = mid;
|
|
456
|
+
}
|
|
457
|
+
if (arr[i] < arr[tails[lo]]) {
|
|
458
|
+
if (lo > 0) parent[i] = tails[lo - 1];
|
|
459
|
+
tails[lo] = i;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Reconstruct LIS
|
|
465
|
+
const result = [];
|
|
466
|
+
let k = tails[tails.length - 1];
|
|
467
|
+
while (k !== -1) {
|
|
468
|
+
result.push(k);
|
|
469
|
+
k = parent[k];
|
|
470
|
+
}
|
|
471
|
+
return result.reverse();
|
|
472
|
+
}
|
|
473
|
+
|
|
290
474
|
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;
|
|
475
|
+
if (nodes.length > 0) {
|
|
476
|
+
const last = nodes[nodes.length - 1];
|
|
477
|
+
return last.nextSibling;
|
|
478
|
+
}
|
|
479
|
+
return marker ? marker.nextSibling : null;
|
|
296
480
|
}
|
|
481
|
+
|
|
297
482
|
function patchNode(parent, domNode, vnode) {
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
if (domNode
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
483
|
+
// Null/removed → keep placeholder or replace with one
|
|
484
|
+
if (vnode == null || vnode === false || vnode === true) {
|
|
485
|
+
if (domNode && domNode.nodeType === 8 && !domNode._componentCtx) {
|
|
486
|
+
return domNode; // already a placeholder comment
|
|
487
|
+
}
|
|
488
|
+
const placeholder = document.createComment('');
|
|
489
|
+
if (domNode && domNode.parentNode) {
|
|
490
|
+
disposeTree(domNode);
|
|
491
|
+
parent.replaceChild(placeholder, domNode);
|
|
492
|
+
}
|
|
493
|
+
return placeholder;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Text
|
|
497
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
498
|
+
const text = String(vnode);
|
|
499
|
+
if (domNode.nodeType === 3) {
|
|
500
|
+
if (domNode.textContent !== text) domNode.textContent = text;
|
|
501
|
+
return domNode;
|
|
502
|
+
}
|
|
503
|
+
const newNode = document.createTextNode(text);
|
|
504
|
+
disposeTree(domNode);
|
|
505
|
+
parent.replaceChild(newNode, domNode);
|
|
506
|
+
return newNode;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Array
|
|
510
|
+
if (Array.isArray(vnode)) {
|
|
511
|
+
// For now, flatten and reconcile as children
|
|
512
|
+
const frag = document.createDocumentFragment();
|
|
513
|
+
for (const v of vnode) {
|
|
514
|
+
const node = createDOM(v, parent);
|
|
515
|
+
if (node) frag.appendChild(node);
|
|
516
|
+
}
|
|
517
|
+
disposeTree(domNode);
|
|
518
|
+
parent.replaceChild(frag, domNode);
|
|
519
|
+
return frag;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Component
|
|
523
|
+
if (typeof vnode.tag === 'function') {
|
|
524
|
+
// Check if old node is a component wrapper for the same component
|
|
525
|
+
if (domNode._componentCtx && !domNode._componentCtx.disposed
|
|
526
|
+
&& domNode._componentCtx.Component === vnode.tag) {
|
|
527
|
+
// Same component — update props reactively, let its effect re-render
|
|
528
|
+
domNode._componentCtx._propsSignal.set({ ...vnode.props, children: vnode.children });
|
|
529
|
+
return domNode;
|
|
530
|
+
}
|
|
531
|
+
// Different component or not a component — dispose old, create new
|
|
532
|
+
disposeTree(domNode);
|
|
533
|
+
const node = createComponent(vnode, parent);
|
|
534
|
+
parent.replaceChild(node, domNode);
|
|
535
|
+
return node;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Element: same tag? Patch props + children
|
|
539
|
+
if (domNode.nodeType === 1 && domNode.tagName.toLowerCase() === vnode.tag) {
|
|
540
|
+
const oldProps = domNode._vnode?.props || {};
|
|
541
|
+
applyProps(domNode, vnode.props, oldProps);
|
|
542
|
+
reconcileChildren(domNode, vnode.children);
|
|
543
|
+
domNode._vnode = vnode;
|
|
544
|
+
return domNode;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Different tag: replace entirely
|
|
548
|
+
const newNode = createDOM(vnode, parent);
|
|
549
|
+
disposeTree(domNode);
|
|
550
|
+
parent.replaceChild(newNode, domNode);
|
|
551
|
+
return newNode;
|
|
552
|
+
}
|
|
553
|
+
|
|
337
554
|
function reconcileChildren(parent, newChildVNodes) {
|
|
338
|
-
const oldChildren = Array.from(parent.childNodes);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (i >=
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (key
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
if (typeof
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
555
|
+
const oldChildren = Array.from(parent.childNodes);
|
|
556
|
+
|
|
557
|
+
// Check for keyed children
|
|
558
|
+
const hasKeys = newChildVNodes.some(v => v && typeof v === 'object' && v.key != null);
|
|
559
|
+
|
|
560
|
+
if (hasKeys) {
|
|
561
|
+
// Use keyed reconciliation
|
|
562
|
+
reconcileKeyed(parent, oldChildren, newChildVNodes, null);
|
|
563
|
+
} else {
|
|
564
|
+
// Unkeyed reconciliation
|
|
565
|
+
const maxLen = Math.max(oldChildren.length, newChildVNodes.length);
|
|
566
|
+
|
|
567
|
+
for (let i = 0; i < maxLen; i++) {
|
|
568
|
+
if (i >= newChildVNodes.length) {
|
|
569
|
+
// Remove extra
|
|
570
|
+
if (oldChildren[i]?.parentNode) {
|
|
571
|
+
disposeTree(oldChildren[i]);
|
|
572
|
+
parent.removeChild(oldChildren[i]);
|
|
573
|
+
}
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (i >= oldChildren.length) {
|
|
578
|
+
// Append new
|
|
579
|
+
const node = createDOM(newChildVNodes[i], parent);
|
|
580
|
+
if (node) parent.appendChild(node);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
patchNode(parent, oldChildren[i], newChildVNodes[i]);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// --- Prop Diffing ---
|
|
590
|
+
// Only touch DOM for props that actually changed.
|
|
591
|
+
|
|
592
|
+
function applyProps(el, newProps, oldProps, isSvg) {
|
|
593
|
+
newProps = newProps || {};
|
|
594
|
+
oldProps = oldProps || {};
|
|
595
|
+
|
|
596
|
+
// Remove old props not in new
|
|
597
|
+
for (const key in oldProps) {
|
|
598
|
+
if (key === 'key' || key === 'ref' || key === 'children') continue;
|
|
599
|
+
if (!(key in newProps)) {
|
|
600
|
+
removeProp(el, key, oldProps[key]);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Set new/changed props
|
|
605
|
+
for (const key in newProps) {
|
|
606
|
+
if (key === 'key' || key === 'ref' || key === 'children') continue;
|
|
607
|
+
if (newProps[key] !== oldProps[key]) {
|
|
608
|
+
setProp(el, key, newProps[key], isSvg);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Handle ref
|
|
613
|
+
if (newProps.ref && newProps.ref !== oldProps.ref) {
|
|
614
|
+
if (typeof newProps.ref === 'function') newProps.ref(el);
|
|
615
|
+
else newProps.ref.current = el;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function setProp(el, key, value, isSvg) {
|
|
620
|
+
// Event handlers: onClick -> click
|
|
621
|
+
// Wrap in untrack so signal reads in handlers don't create subscriptions
|
|
622
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
623
|
+
const event = key.slice(2).toLowerCase();
|
|
624
|
+
// Store handler for removal
|
|
625
|
+
const old = el._events?.[event];
|
|
626
|
+
if (old) el.removeEventListener(event, old);
|
|
627
|
+
if (!el._events) el._events = {};
|
|
628
|
+
// Wrap handler to untrack signal reads
|
|
629
|
+
const wrappedHandler = (e) => untrack(() => value(e));
|
|
630
|
+
wrappedHandler._original = value;
|
|
631
|
+
el._events[event] = wrappedHandler;
|
|
632
|
+
// Check for _eventOpts (once/capture/passive from compiler)
|
|
633
|
+
const eventOpts = value._eventOpts;
|
|
634
|
+
el.addEventListener(event, wrappedHandler, eventOpts || undefined);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// className / class
|
|
639
|
+
if (key === 'className' || key === 'class') {
|
|
640
|
+
if (isSvg) {
|
|
641
|
+
el.setAttribute('class', value || '');
|
|
642
|
+
} else {
|
|
643
|
+
el.className = value || '';
|
|
644
|
+
}
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Style object
|
|
649
|
+
if (key === 'style') {
|
|
650
|
+
if (typeof value === 'string') {
|
|
651
|
+
el.style.cssText = value;
|
|
652
|
+
} else if (typeof value === 'object') {
|
|
653
|
+
for (const prop in value) {
|
|
654
|
+
el.style[prop] = value[prop] ?? '';
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// dangerouslySetInnerHTML
|
|
661
|
+
if (key === 'dangerouslySetInnerHTML') {
|
|
662
|
+
el.innerHTML = value.__html;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Boolean attributes
|
|
667
|
+
if (typeof value === 'boolean') {
|
|
668
|
+
if (value) el.setAttribute(key, '');
|
|
669
|
+
else el.removeAttribute(key);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// data-* and aria-* as attributes
|
|
674
|
+
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
|
675
|
+
el.setAttribute(key, value);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// SVG: always use setAttribute (SVG properties don't work as DOM properties)
|
|
680
|
+
if (isSvg) {
|
|
681
|
+
if (value === false || value == null) {
|
|
682
|
+
el.removeAttribute(key);
|
|
683
|
+
} else {
|
|
684
|
+
el.setAttribute(key, value === true ? '' : String(value));
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Default: set as property if it exists, otherwise attribute
|
|
690
|
+
if (key in el) {
|
|
691
|
+
el[key] = value;
|
|
692
|
+
} else {
|
|
693
|
+
el.setAttribute(key, value);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
425
697
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
698
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
699
|
+
const event = key.slice(2).toLowerCase();
|
|
700
|
+
if (el._events?.[event]) {
|
|
701
|
+
el.removeEventListener(event, el._events[event]);
|
|
702
|
+
delete el._events[event];
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (key === 'className' || key === 'class') {
|
|
708
|
+
el.className = '';
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (key === 'style') {
|
|
713
|
+
el.style.cssText = '';
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
el.removeAttribute(key);
|
|
441
718
|
}
|
|
442
|
-
el.removeAttribute(key);
|
|
443
|
-
}
|