@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/README.md +119 -0
- package/doc.md +223 -0
- package/example/counter/index.html +84 -0
- package/example/hello-world/index.html +37 -0
- package/helper/file-loader.js +19 -0
- package/index.js +12 -0
- package/license.md +4 -0
- package/package.json +31 -0
- package/src/a.js +26 -0
- package/src/dA.js +12 -0
- package/src/e.js +255 -0
- package/src/h.js +56 -0
- package/src/hyp.js +671 -0
- package/src/i.js +20 -0
- package/src/n.js +190 -0
- package/src/o.js +75 -0
- package/src/r.js +16 -0
- package/src/s.js +39 -0
- package/src/sA.js +52 -0
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
|
+
};
|