what-core 0.6.2 → 0.8.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/index.js +321 -182
- package/dist/index.js.map +3 -3
- package/dist/index.min.js +5 -5
- package/dist/index.min.js.map +3 -3
- package/dist/jsx-dev-runtime.js +37 -16
- package/dist/jsx-dev-runtime.js.map +2 -2
- package/dist/jsx-dev-runtime.min.js +1 -1
- package/dist/jsx-dev-runtime.min.js.map +3 -3
- package/dist/jsx-runtime.js +37 -16
- package/dist/jsx-runtime.js.map +2 -2
- package/dist/jsx-runtime.min.js +1 -1
- package/dist/jsx-runtime.min.js.map +3 -3
- package/dist/render.js +263 -194
- package/dist/render.js.map +3 -3
- package/dist/render.min.js +1 -1
- package/dist/render.min.js.map +3 -3
- package/dist/testing.js +174 -149
- package/dist/testing.js.map +2 -2
- package/dist/testing.min.js +1 -1
- package/dist/testing.min.js.map +3 -3
- package/package.json +1 -1
- package/src/dom.js +78 -48
- package/src/guardrails.js +2 -0
- package/src/h.js +43 -18
- package/src/index.js +3 -0
- package/src/reactive.js +176 -101
- package/src/render.js +131 -35
package/package.json
CHANGED
package/src/dom.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import { effect, batch, untrack, signal, __DEV__, __devtools } from './reactive.js';
|
|
6
6
|
import { reportError, _injectGetCurrentComponent, shallowEqual } from './components.js';
|
|
7
7
|
import { _setComponentRef } from './helpers.js';
|
|
8
|
-
|
|
9
8
|
// SVG elements that need namespace
|
|
10
9
|
const SVG_ELEMENTS = new Set([
|
|
11
10
|
'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse',
|
|
@@ -74,16 +73,20 @@ function disposeComponent(ctx) {
|
|
|
74
73
|
mountedComponents.delete(ctx);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
// Dispose all components and reactive effects attached to a DOM subtree
|
|
76
|
+
// Dispose all components and reactive effects attached to a DOM subtree.
|
|
77
|
+
// Performance: checks _componentCtx / _dispose / _propEffects before walking
|
|
78
|
+
// children, and only checks _commentCtxMap for comment nodes (nodeType 8).
|
|
78
79
|
export function disposeTree(node) {
|
|
79
80
|
if (!node) return;
|
|
80
81
|
if (node._componentCtx) {
|
|
81
82
|
disposeComponent(node._componentCtx);
|
|
82
83
|
}
|
|
83
|
-
// Check comment node WeakMap for component context
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
// Check comment node WeakMap for component context — only for comment nodes
|
|
85
|
+
if (node.nodeType === 8) {
|
|
86
|
+
const commentCtx = _commentCtxMap.get(node);
|
|
87
|
+
if (commentCtx) {
|
|
88
|
+
disposeComponent(commentCtx);
|
|
89
|
+
}
|
|
87
90
|
}
|
|
88
91
|
// Dispose reactive function child effects ({() => ...} wrappers)
|
|
89
92
|
if (node._dispose) {
|
|
@@ -95,9 +98,11 @@ export function disposeTree(node) {
|
|
|
95
98
|
try { node._propEffects[key](); } catch (e) { /* already disposed */ }
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
// Recursively dispose children
|
|
102
|
+
const children = node.childNodes;
|
|
103
|
+
if (children && children.length > 0) {
|
|
104
|
+
for (let i = 0; i < children.length; i++) {
|
|
105
|
+
disposeTree(children[i]);
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
}
|
|
@@ -216,6 +221,31 @@ export function createDOM(vnode, parent, isSvg) {
|
|
|
216
221
|
// --- Component Rendering ---
|
|
217
222
|
// Components run ONCE. Props are passed as a signal for reactive access.
|
|
218
223
|
|
|
224
|
+
// Shared Proxy handler for reactive props — defined once, reused by all components.
|
|
225
|
+
// The Proxy target must be a plain object (not a function) so that ownKeys
|
|
226
|
+
// invariants are satisfied. The propsSignal is stored as target._sig.
|
|
227
|
+
const _propsProxyHandler = {
|
|
228
|
+
get(target, key) {
|
|
229
|
+
if (key === '_sig') return undefined; // hide internal property
|
|
230
|
+
return target._sig()[key];
|
|
231
|
+
},
|
|
232
|
+
has(target, key) {
|
|
233
|
+
if (key === '_sig') return false;
|
|
234
|
+
return key in target._sig();
|
|
235
|
+
},
|
|
236
|
+
ownKeys(target) {
|
|
237
|
+
return Reflect.ownKeys(target._sig());
|
|
238
|
+
},
|
|
239
|
+
getOwnPropertyDescriptor(target, key) {
|
|
240
|
+
if (key === '_sig') return undefined;
|
|
241
|
+
const current = target._sig();
|
|
242
|
+
if (key in current) {
|
|
243
|
+
return { value: current[key], writable: false, enumerable: true, configurable: true };
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
219
249
|
const componentStack = [];
|
|
220
250
|
|
|
221
251
|
export function getCurrentComponent() {
|
|
@@ -256,6 +286,20 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
256
286
|
}
|
|
257
287
|
|
|
258
288
|
// Component context for hooks
|
|
289
|
+
// Error boundary lookup: walk the parent chain once, cache the result.
|
|
290
|
+
const parentCtx = componentStack[componentStack.length - 1] || null;
|
|
291
|
+
let errorBoundary = null;
|
|
292
|
+
if (parentCtx) {
|
|
293
|
+
// Fast path: if parent has an error boundary, use it directly
|
|
294
|
+
errorBoundary = parentCtx._errorBoundary || null;
|
|
295
|
+
if (!errorBoundary) {
|
|
296
|
+
let p = parentCtx._parentCtx;
|
|
297
|
+
while (p) {
|
|
298
|
+
if (p._errorBoundary) { errorBoundary = p._errorBoundary; break; }
|
|
299
|
+
p = p._parentCtx;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
259
303
|
const ctx = {
|
|
260
304
|
hooks: [],
|
|
261
305
|
hookIndex: 0,
|
|
@@ -264,15 +308,8 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
264
308
|
mounted: false,
|
|
265
309
|
disposed: false,
|
|
266
310
|
Component,
|
|
267
|
-
_parentCtx:
|
|
268
|
-
_errorBoundary:
|
|
269
|
-
let p = componentStack[componentStack.length - 1];
|
|
270
|
-
while (p) {
|
|
271
|
-
if (p._errorBoundary) return p._errorBoundary;
|
|
272
|
-
p = p._parentCtx;
|
|
273
|
-
}
|
|
274
|
-
return null;
|
|
275
|
-
})()
|
|
311
|
+
_parentCtx: parentCtx,
|
|
312
|
+
_errorBoundary: errorBoundary,
|
|
276
313
|
};
|
|
277
314
|
|
|
278
315
|
// Component boundaries: use comment nodes instead of <span style="display:contents">
|
|
@@ -294,35 +331,24 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
294
331
|
|
|
295
332
|
// Props signal for reactive updates from parent
|
|
296
333
|
const propsChildren = children.length === 0 ? undefined : children.length === 1 ? children[0] : children;
|
|
297
|
-
|
|
334
|
+
// Merge children into props without spreading when possible
|
|
335
|
+
let mergedProps;
|
|
336
|
+
if (propsChildren !== undefined) {
|
|
337
|
+
mergedProps = props ? Object.assign({}, props, { children: propsChildren }) : { children: propsChildren };
|
|
338
|
+
} else {
|
|
339
|
+
mergedProps = props ? Object.assign({}, props) : {};
|
|
340
|
+
}
|
|
341
|
+
const propsSignal = signal(mergedProps);
|
|
298
342
|
ctx._propsSignal = propsSignal;
|
|
299
343
|
|
|
300
344
|
// Create a reactive props proxy: reading any prop inside an effect
|
|
301
345
|
// will auto-track the dependency on the propsSignal. This makes prop
|
|
302
346
|
// access reactive across re-renders without requiring the component
|
|
303
347
|
// to be re-executed.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return current[key];
|
|
309
|
-
},
|
|
310
|
-
has(_, key) {
|
|
311
|
-
const current = propsSignal();
|
|
312
|
-
return key in current;
|
|
313
|
-
},
|
|
314
|
-
ownKeys() {
|
|
315
|
-
const current = propsSignal();
|
|
316
|
-
return Reflect.ownKeys(current);
|
|
317
|
-
},
|
|
318
|
-
getOwnPropertyDescriptor(_, key) {
|
|
319
|
-
const current = propsSignal();
|
|
320
|
-
if (key in current) {
|
|
321
|
-
return { value: current[key], writable: false, enumerable: true, configurable: true };
|
|
322
|
-
}
|
|
323
|
-
return undefined;
|
|
324
|
-
},
|
|
325
|
-
});
|
|
348
|
+
// Reuse shared trap handlers to minimize per-component allocation.
|
|
349
|
+
// Store propsSignal on a plain object target (Proxy invariant: ownKeys must
|
|
350
|
+
// match non-configurable own properties of target; functions have 'prototype').
|
|
351
|
+
const reactiveProps = new Proxy({ _sig: propsSignal }, _propsProxyHandler);
|
|
326
352
|
|
|
327
353
|
// Component runs ONCE — not inside an effect
|
|
328
354
|
componentStack.push(ctx);
|
|
@@ -550,8 +576,9 @@ function createElementFromVNode(vnode, parent, isSvg) {
|
|
|
550
576
|
}
|
|
551
577
|
|
|
552
578
|
// Append children
|
|
553
|
-
|
|
554
|
-
|
|
579
|
+
const isSvgChildren = svgContext && tag !== 'foreignObject';
|
|
580
|
+
for (let i = 0; i < children.length; i++) {
|
|
581
|
+
const node = createDOM(children[i], el, isSvgChildren);
|
|
555
582
|
if (node) el.appendChild(node);
|
|
556
583
|
}
|
|
557
584
|
|
|
@@ -563,16 +590,16 @@ function createElementFromVNode(vnode, parent, isSvg) {
|
|
|
563
590
|
// Only applied once for fine-grained (no diffing). Reactive props use effects.
|
|
564
591
|
|
|
565
592
|
function applyProps(el, newProps, oldProps, isSvg) {
|
|
566
|
-
|
|
567
|
-
oldProps = oldProps || {};
|
|
593
|
+
if (!newProps) return;
|
|
568
594
|
|
|
569
595
|
for (const key in newProps) {
|
|
570
596
|
if (key === 'key' || key === 'children') continue;
|
|
571
597
|
|
|
572
598
|
// Handle ref
|
|
573
599
|
if (key === 'ref') {
|
|
574
|
-
|
|
575
|
-
|
|
600
|
+
const ref = newProps.ref;
|
|
601
|
+
if (typeof ref === 'function') ref(el);
|
|
602
|
+
else if (ref) ref.current = el;
|
|
576
603
|
continue;
|
|
577
604
|
}
|
|
578
605
|
|
|
@@ -609,10 +636,13 @@ function setProp(el, key, value, isSvg) {
|
|
|
609
636
|
if (old) el.removeEventListener(event, old, useCapture);
|
|
610
637
|
if (value == null) return;
|
|
611
638
|
if (!el._events) el._events = {};
|
|
639
|
+
// Single closure per event listener. Uses untrack to prevent accidental
|
|
640
|
+
// signal subscriptions inside event handlers.
|
|
612
641
|
const wrappedHandler = (e) => {
|
|
613
642
|
if (!e.nativeEvent) e.nativeEvent = e;
|
|
614
|
-
return untrack(() =>
|
|
643
|
+
return untrack(() => wrappedHandler._handler(e));
|
|
615
644
|
};
|
|
645
|
+
wrappedHandler._handler = value;
|
|
616
646
|
wrappedHandler._original = value;
|
|
617
647
|
el._events[storageKey] = wrappedHandler;
|
|
618
648
|
const eventOpts = value._eventOpts;
|
package/src/guardrails.js
CHANGED
|
@@ -150,6 +150,8 @@ const VALID_EXPORTS = new Set([
|
|
|
150
150
|
// Scheduler
|
|
151
151
|
'scheduleRead', 'scheduleWrite', 'flushScheduler', 'measure', 'mutate',
|
|
152
152
|
'useScheduledEffect', 'nextFrame', 'raf', 'onResize', 'onIntersect', 'smoothScrollTo',
|
|
153
|
+
// Text insertion hook (for external text engines)
|
|
154
|
+
'_setTextInsertHook',
|
|
153
155
|
// Animation
|
|
154
156
|
'spring', 'tween', 'easings', 'useTransition', 'useGesture', 'useAnimatedValue',
|
|
155
157
|
'createTransitionClasses', 'cssTransition',
|
package/src/h.js
CHANGED
|
@@ -8,9 +8,25 @@
|
|
|
8
8
|
const EMPTY_OBJ = Object.create(null);
|
|
9
9
|
const EMPTY_ARR = [];
|
|
10
10
|
|
|
11
|
-
export function h(tag, props
|
|
11
|
+
export function h(tag, props) {
|
|
12
12
|
props = props || EMPTY_OBJ;
|
|
13
|
-
|
|
13
|
+
// Collect children from arguments[2..n] without rest args — avoids array allocation
|
|
14
|
+
// when there are 0-1 children (common case: leaf elements, text nodes).
|
|
15
|
+
const argLen = arguments.length;
|
|
16
|
+
let flat;
|
|
17
|
+
if (argLen <= 2) {
|
|
18
|
+
flat = EMPTY_ARR;
|
|
19
|
+
} else if (argLen === 3) {
|
|
20
|
+
flat = _flattenSingle(arguments[2]);
|
|
21
|
+
} else {
|
|
22
|
+
// Multiple children: flatten all arguments from position 2 onward
|
|
23
|
+
const out = [];
|
|
24
|
+
for (let i = 2; i < argLen; i++) {
|
|
25
|
+
_flattenInto(arguments[i], out);
|
|
26
|
+
}
|
|
27
|
+
flat = out;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
const key = props.key ?? null;
|
|
15
31
|
|
|
16
32
|
// Strip key from props passed to component/element
|
|
@@ -27,24 +43,33 @@ export function Fragment({ children }) {
|
|
|
27
43
|
return children;
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
// Fast path for single child (most common case)
|
|
47
|
+
function _flattenSingle(child) {
|
|
48
|
+
if (child == null || child === false || child === true) return EMPTY_ARR;
|
|
49
|
+
if (Array.isArray(child)) {
|
|
50
|
+
const out = [];
|
|
51
|
+
_flattenInto(child, out);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
if (typeof child === 'object' && child._vnode) return [child];
|
|
55
|
+
if (typeof child === 'function') return [child];
|
|
56
|
+
return [String(child)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Flatten a child (or array of children) into the output array
|
|
60
|
+
function _flattenInto(child, out) {
|
|
61
|
+
if (child == null || child === false || child === true) return;
|
|
62
|
+
if (Array.isArray(child)) {
|
|
63
|
+
for (let i = 0; i < child.length; i++) {
|
|
64
|
+
_flattenInto(child[i], out);
|
|
45
65
|
}
|
|
66
|
+
} else if (typeof child === 'object' && child._vnode) {
|
|
67
|
+
out.push(child);
|
|
68
|
+
} else if (typeof child === 'function') {
|
|
69
|
+
out.push(child);
|
|
70
|
+
} else {
|
|
71
|
+
out.push(String(child));
|
|
46
72
|
}
|
|
47
|
-
return out;
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
// JSX-like tagged template alternative (no build step needed)
|
package/src/index.js
CHANGED
|
@@ -173,6 +173,9 @@ export {
|
|
|
173
173
|
validateImports,
|
|
174
174
|
} from './guardrails.js';
|
|
175
175
|
|
|
176
|
+
// Generic text insertion hook (used by external text engines like what-text)
|
|
177
|
+
export { _setTextInsertHook } from './render.js';
|
|
178
|
+
|
|
176
179
|
// Agent context (global inspection API)
|
|
177
180
|
export {
|
|
178
181
|
installAgentContext,
|