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 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.0",
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
- function read() {
21
- if (currentEffect) {
22
- subs.add(currentEffect);
23
- currentEffect.deps.push(subs); // Track reverse dep for cleanup
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
- return value;
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
- read.set = (next) => {
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
- read.peek = () => value;
44
+ sig.peek = () => value;
36
45
 
37
- read.subscribe = (fn) => {
38
- return effect(() => fn(read()));
46
+ sig.subscribe = (fn) => {
47
+ return effect(() => fn(sig()));
39
48
  };
40
49
 
41
- read._signal = true;
42
- return read;
50
+ sig._signal = true;
51
+ return sig;
43
52
  }
44
53
 
45
54
  // --- Computed ---