what-core 0.4.1 → 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.
Files changed (3) hide show
  1. package/dist/dom.js +59 -1
  2. package/package.json +1 -1
  3. package/src/dom.js +46 -14
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.1",
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",
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,16 +105,29 @@ function createDOM(vnode, parent, isSvg) {
101
105
  return document.createTextNode(String(vnode));
102
106
  }
103
107
 
104
- // Reactive function child — creates a text node that updates fine-grained
108
+ // Reactive function child — creates a wrapper that updates fine-grained
109
+ // Handles both primitives ({() => count()}) and vnodes ({() => items().map(...)})
105
110
  if (typeof vnode === 'function') {
106
- const textNode = document.createTextNode('');
107
- effect(() => {
111
+ const wrapper = document.createElement('what-c');
112
+ let mounted = false;
113
+ const dispose = effect(() => {
108
114
  const val = vnode();
109
- // If the function returns a vnode, we can't upgrade a text node to an element.
110
- // For now, stringify the result. Component-level re-render handles complex cases.
111
- textNode.textContent = (val == null || val === false || val === true) ? '' : String(val);
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
+ }
112
128
  });
113
- return textNode;
129
+ wrapper._dispose = dispose;
130
+ return wrapper;
114
131
  }
115
132
 
116
133
  // Array (fragment)
@@ -265,6 +282,7 @@ function createComponent(vnode, parent, isSvg) {
265
282
  });
266
283
 
267
284
  ctx.effects.push(dispose);
285
+ wrapper._vnode = vnode; // Store vnode for keyed reconciliation
268
286
  return wrapper;
269
287
  }
270
288
 
@@ -627,18 +645,31 @@ function patchNode(parent, domNode, vnode) {
627
645
  return placeholder;
628
646
  }
629
647
 
630
- // Reactive function child — replace whatever's there with a reactive text node
648
+ // Reactive function child — replace whatever's there with a reactive wrapper
631
649
  if (typeof vnode === 'function') {
632
- const textNode = document.createTextNode('');
633
- effect(() => {
650
+ const wrapper = document.createElement('what-c');
651
+ let mounted = false;
652
+ const dispose = effect(() => {
634
653
  const val = vnode();
635
- textNode.textContent = (val == null || val === false || val === true) ? '' : String(val);
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
+ }
636
666
  });
667
+ wrapper._dispose = dispose;
637
668
  if (domNode && domNode.parentNode) {
638
669
  disposeTree(domNode);
639
- parent.replaceChild(textNode, domNode);
670
+ parent.replaceChild(wrapper, domNode);
640
671
  }
641
- return textNode;
672
+ return wrapper;
642
673
  }
643
674
 
644
675
  // Text
@@ -714,6 +745,7 @@ function patchNode(parent, domNode, vnode) {
714
745
  && domNode._componentCtx.Component === vnode.tag) {
715
746
  // Same component — update props reactively, let its effect re-render
716
747
  domNode._componentCtx._propsSignal.set({ ...vnode.props, children: vnode.children });
748
+ domNode._vnode = vnode; // Keep vnode current for keyed reconciliation
717
749
  return domNode;
718
750
  }
719
751
  // Different component or not a component — dispose old, create new