what-core 0.4.0 → 0.4.2
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/dom.js +59 -1
- package/package.json +7 -1
- package/src/dom.js +59 -1
- package/src/h.js +3 -0
- package/src/jsx-dev-runtime.js +19 -0
- package/src/jsx-runtime.js +21 -0
- package/src/reactive.js +20 -11
package/dist/dom.js
CHANGED
|
@@ -60,12 +60,16 @@ function disposeComponent(ctx) {
|
|
|
60
60
|
mountedComponents.delete(ctx);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// Dispose all components attached to a DOM subtree
|
|
63
|
+
// Dispose all components and reactive effects attached to a DOM subtree
|
|
64
64
|
function disposeTree(node) {
|
|
65
65
|
if (!node) return;
|
|
66
66
|
if (node._componentCtx) {
|
|
67
67
|
disposeComponent(node._componentCtx);
|
|
68
68
|
}
|
|
69
|
+
// Dispose reactive function child effects ({() => ...} wrappers)
|
|
70
|
+
if (node._dispose) {
|
|
71
|
+
try { node._dispose(); } catch (e) { /* already disposed */ }
|
|
72
|
+
}
|
|
69
73
|
if (node.childNodes) {
|
|
70
74
|
for (const child of node.childNodes) {
|
|
71
75
|
disposeTree(child);
|
|
@@ -101,6 +105,31 @@ function createDOM(vnode, parent, isSvg) {
|
|
|
101
105
|
return document.createTextNode(String(vnode));
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
// Reactive function child — creates a wrapper that updates fine-grained
|
|
109
|
+
// Handles both primitives ({() => count()}) and vnodes ({() => items().map(...)})
|
|
110
|
+
if (typeof vnode === 'function') {
|
|
111
|
+
const wrapper = document.createElement('what-c');
|
|
112
|
+
let mounted = false;
|
|
113
|
+
const dispose = effect(() => {
|
|
114
|
+
const val = vnode();
|
|
115
|
+
// Normalize: null/false/true → empty, primitives and vnodes → array
|
|
116
|
+
const vnodes = (val == null || val === false || val === true)
|
|
117
|
+
? []
|
|
118
|
+
: Array.isArray(val) ? val : [val];
|
|
119
|
+
if (!mounted) {
|
|
120
|
+
mounted = true;
|
|
121
|
+
for (const v of vnodes) {
|
|
122
|
+
const node = createDOM(v, wrapper, parent?._isSvg);
|
|
123
|
+
if (node) wrapper.appendChild(node);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
reconcileChildren(wrapper, vnodes);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
wrapper._dispose = dispose;
|
|
130
|
+
return wrapper;
|
|
131
|
+
}
|
|
132
|
+
|
|
104
133
|
// Array (fragment)
|
|
105
134
|
if (Array.isArray(vnode)) {
|
|
106
135
|
const frag = document.createDocumentFragment();
|
|
@@ -253,6 +282,7 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
253
282
|
});
|
|
254
283
|
|
|
255
284
|
ctx.effects.push(dispose);
|
|
285
|
+
wrapper._vnode = vnode; // Store vnode for keyed reconciliation
|
|
256
286
|
return wrapper;
|
|
257
287
|
}
|
|
258
288
|
|
|
@@ -615,6 +645,33 @@ function patchNode(parent, domNode, vnode) {
|
|
|
615
645
|
return placeholder;
|
|
616
646
|
}
|
|
617
647
|
|
|
648
|
+
// Reactive function child — replace whatever's there with a reactive wrapper
|
|
649
|
+
if (typeof vnode === 'function') {
|
|
650
|
+
const wrapper = document.createElement('what-c');
|
|
651
|
+
let mounted = false;
|
|
652
|
+
const dispose = effect(() => {
|
|
653
|
+
const val = vnode();
|
|
654
|
+
const vnodes = (val == null || val === false || val === true)
|
|
655
|
+
? []
|
|
656
|
+
: Array.isArray(val) ? val : [val];
|
|
657
|
+
if (!mounted) {
|
|
658
|
+
mounted = true;
|
|
659
|
+
for (const v of vnodes) {
|
|
660
|
+
const node = createDOM(v, wrapper);
|
|
661
|
+
if (node) wrapper.appendChild(node);
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
reconcileChildren(wrapper, vnodes);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
wrapper._dispose = dispose;
|
|
668
|
+
if (domNode && domNode.parentNode) {
|
|
669
|
+
disposeTree(domNode);
|
|
670
|
+
parent.replaceChild(wrapper, domNode);
|
|
671
|
+
}
|
|
672
|
+
return wrapper;
|
|
673
|
+
}
|
|
674
|
+
|
|
618
675
|
// Text
|
|
619
676
|
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
620
677
|
const text = String(vnode);
|
|
@@ -688,6 +745,7 @@ function patchNode(parent, domNode, vnode) {
|
|
|
688
745
|
&& domNode._componentCtx.Component === vnode.tag) {
|
|
689
746
|
// Same component — update props reactively, let its effect re-render
|
|
690
747
|
domNode._componentCtx._propsSignal.set({ ...vnode.props, children: vnode.children });
|
|
748
|
+
domNode._vnode = vnode; // Keep vnode current for keyed reconciliation
|
|
691
749
|
return domNode;
|
|
692
750
|
}
|
|
693
751
|
// Different component or not a component — dispose old, create new
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "What Framework - The closest framework to vanilla JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/what.js",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"import": "./src/index.js",
|
|
13
13
|
"require": "./dist/what.cjs"
|
|
14
14
|
},
|
|
15
|
+
"./jsx-runtime": {
|
|
16
|
+
"import": "./src/jsx-runtime.js"
|
|
17
|
+
},
|
|
18
|
+
"./jsx-dev-runtime": {
|
|
19
|
+
"import": "./src/jsx-dev-runtime.js"
|
|
20
|
+
},
|
|
15
21
|
"./render": {
|
|
16
22
|
"import": "./src/render.js"
|
|
17
23
|
},
|
package/src/dom.js
CHANGED
|
@@ -60,12 +60,16 @@ function disposeComponent(ctx) {
|
|
|
60
60
|
mountedComponents.delete(ctx);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// Dispose all components attached to a DOM subtree
|
|
63
|
+
// Dispose all components and reactive effects attached to a DOM subtree
|
|
64
64
|
function disposeTree(node) {
|
|
65
65
|
if (!node) return;
|
|
66
66
|
if (node._componentCtx) {
|
|
67
67
|
disposeComponent(node._componentCtx);
|
|
68
68
|
}
|
|
69
|
+
// Dispose reactive function child effects ({() => ...} wrappers)
|
|
70
|
+
if (node._dispose) {
|
|
71
|
+
try { node._dispose(); } catch (e) { /* already disposed */ }
|
|
72
|
+
}
|
|
69
73
|
if (node.childNodes) {
|
|
70
74
|
for (const child of node.childNodes) {
|
|
71
75
|
disposeTree(child);
|
|
@@ -101,6 +105,31 @@ function createDOM(vnode, parent, isSvg) {
|
|
|
101
105
|
return document.createTextNode(String(vnode));
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
// Reactive function child — creates a wrapper that updates fine-grained
|
|
109
|
+
// Handles both primitives ({() => count()}) and vnodes ({() => items().map(...)})
|
|
110
|
+
if (typeof vnode === 'function') {
|
|
111
|
+
const wrapper = document.createElement('what-c');
|
|
112
|
+
let mounted = false;
|
|
113
|
+
const dispose = effect(() => {
|
|
114
|
+
const val = vnode();
|
|
115
|
+
// Normalize: null/false/true → empty, primitives and vnodes → array
|
|
116
|
+
const vnodes = (val == null || val === false || val === true)
|
|
117
|
+
? []
|
|
118
|
+
: Array.isArray(val) ? val : [val];
|
|
119
|
+
if (!mounted) {
|
|
120
|
+
mounted = true;
|
|
121
|
+
for (const v of vnodes) {
|
|
122
|
+
const node = createDOM(v, wrapper, parent?._isSvg);
|
|
123
|
+
if (node) wrapper.appendChild(node);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
reconcileChildren(wrapper, vnodes);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
wrapper._dispose = dispose;
|
|
130
|
+
return wrapper;
|
|
131
|
+
}
|
|
132
|
+
|
|
104
133
|
// Array (fragment)
|
|
105
134
|
if (Array.isArray(vnode)) {
|
|
106
135
|
const frag = document.createDocumentFragment();
|
|
@@ -253,6 +282,7 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
253
282
|
});
|
|
254
283
|
|
|
255
284
|
ctx.effects.push(dispose);
|
|
285
|
+
wrapper._vnode = vnode; // Store vnode for keyed reconciliation
|
|
256
286
|
return wrapper;
|
|
257
287
|
}
|
|
258
288
|
|
|
@@ -615,6 +645,33 @@ function patchNode(parent, domNode, vnode) {
|
|
|
615
645
|
return placeholder;
|
|
616
646
|
}
|
|
617
647
|
|
|
648
|
+
// Reactive function child — replace whatever's there with a reactive wrapper
|
|
649
|
+
if (typeof vnode === 'function') {
|
|
650
|
+
const wrapper = document.createElement('what-c');
|
|
651
|
+
let mounted = false;
|
|
652
|
+
const dispose = effect(() => {
|
|
653
|
+
const val = vnode();
|
|
654
|
+
const vnodes = (val == null || val === false || val === true)
|
|
655
|
+
? []
|
|
656
|
+
: Array.isArray(val) ? val : [val];
|
|
657
|
+
if (!mounted) {
|
|
658
|
+
mounted = true;
|
|
659
|
+
for (const v of vnodes) {
|
|
660
|
+
const node = createDOM(v, wrapper);
|
|
661
|
+
if (node) wrapper.appendChild(node);
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
reconcileChildren(wrapper, vnodes);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
wrapper._dispose = dispose;
|
|
668
|
+
if (domNode && domNode.parentNode) {
|
|
669
|
+
disposeTree(domNode);
|
|
670
|
+
parent.replaceChild(wrapper, domNode);
|
|
671
|
+
}
|
|
672
|
+
return wrapper;
|
|
673
|
+
}
|
|
674
|
+
|
|
618
675
|
// Text
|
|
619
676
|
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
620
677
|
const text = String(vnode);
|
|
@@ -688,6 +745,7 @@ function patchNode(parent, domNode, vnode) {
|
|
|
688
745
|
&& domNode._componentCtx.Component === vnode.tag) {
|
|
689
746
|
// Same component — update props reactively, let its effect re-render
|
|
690
747
|
domNode._componentCtx._propsSignal.set({ ...vnode.props, children: vnode.children });
|
|
748
|
+
domNode._vnode = vnode; // Keep vnode current for keyed reconciliation
|
|
691
749
|
return domNode;
|
|
692
750
|
}
|
|
693
751
|
// Different component or not a component — dispose old, create new
|
package/src/h.js
CHANGED
|
@@ -36,6 +36,9 @@ function flattenChildren(children) {
|
|
|
36
36
|
out.push(...flattenChildren(child));
|
|
37
37
|
} else if (typeof child === 'object' && child._vnode) {
|
|
38
38
|
out.push(child);
|
|
39
|
+
} else if (typeof child === 'function') {
|
|
40
|
+
// Reactive child — preserve function for fine-grained DOM updates
|
|
41
|
+
out.push(child);
|
|
39
42
|
} else {
|
|
40
43
|
// Text node
|
|
41
44
|
out.push(String(child));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// What Framework — JSX Dev Runtime
|
|
2
|
+
// Same as jsx-runtime but used in development mode by Vite.
|
|
3
|
+
|
|
4
|
+
import { h, Fragment } from './h.js';
|
|
5
|
+
|
|
6
|
+
export { Fragment };
|
|
7
|
+
|
|
8
|
+
export function jsxDEV(type, props, key) {
|
|
9
|
+
if (props == null) return h(type, null);
|
|
10
|
+
const { children, ...rest } = props;
|
|
11
|
+
if (key !== undefined) rest.key = key;
|
|
12
|
+
if (children === undefined) return h(type, rest);
|
|
13
|
+
if (Array.isArray(children)) return h(type, rest, ...children);
|
|
14
|
+
return h(type, rest, children);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Also export jsx/jsxs for compatibility — some bundlers use these even in dev
|
|
18
|
+
export const jsx = jsxDEV;
|
|
19
|
+
export const jsxs = jsxDEV;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// What Framework — JSX Automatic Runtime
|
|
2
|
+
// Used by: jsxImportSource: "what-framework" (or "what-core")
|
|
3
|
+
// Vite/esbuild import this automatically when using the "react-jsx" transform.
|
|
4
|
+
|
|
5
|
+
import { h, Fragment } from './h.js';
|
|
6
|
+
|
|
7
|
+
export { Fragment };
|
|
8
|
+
|
|
9
|
+
// Automatic JSX transform signature: jsx(type, { children, ...props }, key)
|
|
10
|
+
// What's h() signature: h(type, props, ...children)
|
|
11
|
+
export function jsx(type, props, key) {
|
|
12
|
+
if (props == null) return h(type, null);
|
|
13
|
+
const { children, ...rest } = props;
|
|
14
|
+
if (key !== undefined) rest.key = key;
|
|
15
|
+
if (children === undefined) return h(type, rest);
|
|
16
|
+
if (Array.isArray(children)) return h(type, rest, ...children);
|
|
17
|
+
return h(type, rest, children);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// jsxs = jsx for static children (multiple). Same behavior for What.
|
|
21
|
+
export const jsxs = jsx;
|
package/src/reactive.js
CHANGED
|
@@ -17,29 +17,38 @@ export function signal(initial) {
|
|
|
17
17
|
let value = initial;
|
|
18
18
|
const subs = new Set();
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// Unified getter/setter: sig() reads, sig(newVal) writes
|
|
21
|
+
function sig(...args) {
|
|
22
|
+
if (args.length === 0) {
|
|
23
|
+
// Read
|
|
24
|
+
if (currentEffect) {
|
|
25
|
+
subs.add(currentEffect);
|
|
26
|
+
currentEffect.deps.push(subs);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
24
29
|
}
|
|
25
|
-
|
|
30
|
+
// Write
|
|
31
|
+
const nextVal = typeof args[0] === 'function' ? args[0](value) : args[0];
|
|
32
|
+
if (Object.is(value, nextVal)) return;
|
|
33
|
+
value = nextVal;
|
|
34
|
+
notify(subs);
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
|
|
37
|
+
sig.set = (next) => {
|
|
29
38
|
const nextVal = typeof next === 'function' ? next(value) : next;
|
|
30
39
|
if (Object.is(value, nextVal)) return;
|
|
31
40
|
value = nextVal;
|
|
32
41
|
notify(subs);
|
|
33
42
|
};
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
sig.peek = () => value;
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
return effect(() => fn(
|
|
46
|
+
sig.subscribe = (fn) => {
|
|
47
|
+
return effect(() => fn(sig()));
|
|
39
48
|
};
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
return
|
|
50
|
+
sig._signal = true;
|
|
51
|
+
return sig;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
// --- Computed ---
|