@ikdao/hyp 0.1.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/src/e.js ADDED
@@ -0,0 +1,255 @@
1
+ // executor e()
2
+ // EI execution identity/instance
3
+ // render/update/unmount
4
+ // Self License - 01SL
5
+ // HYP UI Framework
6
+ // Author: Hemang Tewari
7
+
8
+ export const e = (function () {
9
+ const execStack = [];
10
+
11
+ function pushEI(ei) { execStack.push(ei); }
12
+ function popEI() { execStack.pop(); }
13
+ function currentEI() { return execStack[execStack.length - 1] || null; }
14
+
15
+ function render(vnode, body) {
16
+ const hi = vnode?.ty?.name || vnode?.ty || "anonymous";
17
+ const ei = o.create(hi, body);
18
+ pushEI(ei);
19
+ o.runLifecycle(ei, "willMount");
20
+ const dom = createDom(vnode, ei);
21
+ if (body) body.appendChild(dom);
22
+ s.add(() => o.runLifecycle(ei, "didMount"), ei);
23
+ popEI();
24
+ return ei;
25
+ }
26
+
27
+ function patch(dom, oldVNode, newVNode, ei) {
28
+ // Guard: invalid DOM or dead instance
29
+ if (!dom || !o.isAlive(ei)) return dom;
30
+
31
+ if (oldVNode == null) {
32
+ const newDom = createDom(newVNode, ei);
33
+ dom.replaceWith(newDom);
34
+ s.add(() => o.runLifecycle(ei, "didUpdate"), ei);
35
+ return newDom;
36
+ }
37
+
38
+ if (newVNode == null) {
39
+ dom.remove();
40
+ return null;
41
+ }
42
+
43
+ pushEI(ei);
44
+ o.runLifecycle(ei, "willUpdate");
45
+
46
+ // Type or key changed → full replace
47
+ if (oldVNode.ty !== newVNode.ty || oldVNode.key !== newVNode.key) {
48
+ const newDom = createDom(newVNode, ei);
49
+ dom.replaceWith(newDom);
50
+ s.add(() => o.runLifecycle(ei, "didUpdate"), ei);
51
+ popEI();
52
+ return newDom;
53
+ }
54
+
55
+ // Handle reactive text nodes (Actor)
56
+ if (oldVNode instanceof Actor && newVNode instanceof Actor) {
57
+ dom.data = newVNode.get();
58
+ s.add(() => o.runLifecycle(ei, "didUpdate"), ei);
59
+ popEI();
60
+ return dom;
61
+ }
62
+
63
+ // Handle primitive text nodes
64
+ if ((typeof oldVNode === "string" || typeof oldVNode === "number") &&
65
+ (typeof newVNode === "string" || typeof newVNode === "number")) {
66
+ const newVal = String(newVNode);
67
+ if (dom.data !== newVal) dom.data = newVal;
68
+ s.add(() => o.runLifecycle(ei, "didUpdate"), ei);
69
+ popEI();
70
+ return dom;
71
+ }
72
+
73
+ // Update props and children
74
+ updateprps(dom, oldVNode.prp || {}, newVNode.prp || {});
75
+ patchChildren(dom, oldVNode.chd || [], newVNode.chd || [], ei);
76
+
77
+ // Handle ref
78
+ if (newVNode.ref) newVNode.ref(dom);
79
+
80
+ s.add(() => o.runLifecycle(ei, "didUpdate"), ei);
81
+ popEI();
82
+
83
+ return dom;
84
+ }
85
+
86
+ function unmount(vnode = null, ei) {
87
+ const inst = o.get(ei);
88
+ if (!inst) return;
89
+ pushEI(ei);
90
+ const bodyRef = inst.body;
91
+
92
+ o.runLifecycle(ei, "willUnmount");
93
+ if (bodyRef?.parentNode)
94
+ bodyRef.parentNode.removeChild(bodyRef);
95
+
96
+ s.add(() => o.runLifecycle(ei, "didUnmount", bodyRef), ei);
97
+
98
+ o.destroy(ei, { runLifecycle: false });
99
+ popEI();
100
+ }
101
+
102
+
103
+ function createDom(v, ei) {
104
+ // null or primitive → text node
105
+ if (v == null) return document.createTextNode("");
106
+ if (typeof v === "string" || typeof v === "number")
107
+ return document.createTextNode(String(v));
108
+
109
+ // Reactive text node (Actor or dA)
110
+ if (v instanceof Actor) {
111
+ const textNode = document.createTextNode(v.get());
112
+ const update = () => { textNode.data = v.get(); };
113
+ const unsub = v.subscribe(update);
114
+ // tie cleanup to organiser (o)
115
+ if (ei) o.addEffect(ei, unsub);
116
+ return textNode;
117
+ }
118
+
119
+ const el = document.createElement(v.ty);
120
+
121
+ for (const [k, val] of Object.entries(v.prp || {})) {
122
+ if (k.startsWith("on") && typeof val === "function") {
123
+ el.addEventListener(k.slice(2).toLowerCase(), val);
124
+ continue;
125
+ }
126
+ if (k === "style" && typeof val === "object") {
127
+ for (const [sk, sv] of Object.entries(val)) {
128
+ if (sv instanceof Actor) {
129
+ const updateStyle = () => { el.style[sk] = sv.get(); };
130
+ updateStyle();
131
+ const unsub = sv.subscribe(updateStyle);
132
+ if (ei) o.addEffect(ei, unsub);
133
+ } else {
134
+ el.style[sk] = sv;
135
+ }
136
+ }
137
+ continue;
138
+ }
139
+ if (val instanceof Actor) {
140
+ const updateAttr = () => {
141
+ const next = val.get();
142
+ if (k in el) el[k] = next;
143
+ else el.setAttribute(k, next);
144
+ };
145
+ updateAttr();
146
+ const unsub = val.subscribe(updateAttr);
147
+ if (ei) o.addEffect(ei, unsub);
148
+ continue;
149
+ }
150
+
151
+ if (k in el) el[k] = val;
152
+ else el.setAttribute(k, val);
153
+ }
154
+
155
+ (v.chd || []).forEach(ch => {
156
+ el.appendChild(createDom(ch, ei));
157
+ });
158
+
159
+ if (v.ref) v.ref(el);
160
+
161
+ return el;
162
+ }
163
+
164
+ function updateprps(dom, oldprps, newprps) {
165
+ for (const k in oldprps) {
166
+ if (!(k in newprps)) {
167
+ if (k.startsWith("on") && typeof oldprps[k] === "function")
168
+ dom.removeEventListener(k.slice(2).toLowerCase(), oldprps[k]);
169
+ else
170
+ dom.removeAttribute(k);
171
+ }
172
+ }
173
+
174
+ for (const [k, v] of Object.entries(newprps)) {
175
+ if (oldprps[k] !== v) {
176
+ if (k.startsWith("on") && typeof v === "function") {
177
+ if (oldprps[k]) dom.removeEventListener(k.slice(2).toLowerCase(), oldprps[k]);
178
+ dom.addEventListener(k.slice(2).toLowerCase(), v);
179
+ } else {
180
+ dom.setAttribute(k, v);
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ function patchChildren(dom, oldCh, newCh, ei) {
187
+ const oldKeyed = new Map();
188
+ const usedKeys = new Set();
189
+
190
+ // Index old children by key (skip non-keyed)
191
+ oldCh.forEach((c, i) => {
192
+ if (c && c.key != null) {
193
+ oldKeyed.set(c.key, { vnode: c, dom: dom.childNodes[i], index: i });
194
+ }
195
+ });
196
+
197
+ const newDoms = [];
198
+
199
+ // Process each new child
200
+ for (let i = 0; i < newCh.length; i++) {
201
+ const newV = newCh[i];
202
+ let newDom;
203
+
204
+ if (newV && newV.key != null) {
205
+ // Keyed node: try to reuse
206
+ const oldEntry = oldKeyed.get(newV.key);
207
+ if (oldEntry && oldEntry.dom) {
208
+ newDom = patch(oldEntry.dom, oldEntry.vnode, newV, ei);
209
+ usedKeys.add(newV.key);
210
+ } else {
211
+ // Create new
212
+ newDom = createDom(newV, ei);
213
+ }
214
+ } else {
215
+ // Non-keyed: patch by index if possible
216
+ const oldV = oldCh[i];
217
+ const oldDom = dom.childNodes[i];
218
+ if (oldDom && oldV != null) {
219
+ newDom = patch(oldDom, oldV, newV, ei);
220
+ } else if (oldDom) {
221
+ // Replace with new content
222
+ newDom = createDom(newV, ei);
223
+ oldDom.replaceWith(newDom);
224
+ } else {
225
+ // Append new
226
+ newDom = createDom(newV, ei);
227
+ }
228
+ }
229
+
230
+ newDoms.push(newDom);
231
+ }
232
+
233
+ // Update DOM order to match newDoms
234
+ for (let i = 0; i < newDoms.length; i++) {
235
+ const nextDom = dom.childNodes[i];
236
+ if (nextDom !== newDoms[i]) {
237
+ dom.insertBefore(newDoms[i], nextDom || null);
238
+ }
239
+ }
240
+
241
+ // Remove unused keyed nodes
242
+ for (const [key, entry] of oldKeyed) {
243
+ if (!usedKeys.has(key) && entry.dom) {
244
+ entry.dom.remove();
245
+ }
246
+ }
247
+
248
+ // Remove extra non-keyed nodes at the end
249
+ while (dom.childNodes.length > newCh.length) {
250
+ dom.lastChild.remove();
251
+ }
252
+ }
253
+
254
+ return { render, patch, unmount, pushEI, popEI, currentEI };
255
+ })();
package/src/h.js ADDED
@@ -0,0 +1,56 @@
1
+ // HYP Organ Factory h()
2
+ // Self License - 01SL
3
+ // HYP UI Framework
4
+ // Author: Hemang Tewari
5
+
6
+ // --- 1. Hyperscript h() ---
7
+
8
+ export const h = (ty, prp, ...chd) => {
9
+ // Normalize props & children
10
+ if (prp == null || typeof prp !== "object" || Array.isArray(prp)) {
11
+ chd.unshift(prp);
12
+ prp = {};
13
+ }
14
+
15
+ // 🔁 Iterative (stack-safe) flattening
16
+ const stack = [...chd];
17
+ const flatChildren = [];
18
+
19
+ while (stack.length > 0) {
20
+ const item = stack.pop();
21
+ if (item == null || item === false) continue;
22
+
23
+ if (Array.isArray(item)) {
24
+ // Push in reverse to preserve order
25
+ for (let i = item.length - 1; i >= 0; i--) {
26
+ stack.push(item[i]);
27
+ }
28
+ } else if (item instanceof Actor) {
29
+ flatChildren.push(item);
30
+ } else if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
31
+ flatChildren.push(String(item));
32
+ } else if (typeof item === "object" && item.ty) {
33
+ flatChildren.push(item);
34
+ } else if (typeof item === "function") {
35
+ flatChildren.push(item());
36
+ } else {
37
+ flatChildren.push(String(item));
38
+ }
39
+ }
40
+
41
+ flatChildren.reverse(); // because we popped in reverse
42
+
43
+ // Handle component (function as type)
44
+ if (typeof ty === "function") {
45
+ return ty({ ...prp, children: flatChildren });
46
+ }
47
+
48
+ // Handle element
49
+ return {
50
+ ty,
51
+ prp,
52
+ chd: flatChildren,
53
+ key: prp.key ?? null,
54
+ ref: prp.ref ?? null,
55
+ };
56
+ };