@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/n.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { a } from "https://cdn.jsdelivr.net/gh/ikdao/hyp@main/a.js";
|
|
2
|
+
|
|
3
|
+
/* ---- Utilities -------------------------------- */
|
|
4
|
+
|
|
5
|
+
function parseQuery(search) {
|
|
6
|
+
return Object.fromEntries(new URLSearchParams(search || ""));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function compileMatcher(pattern) {
|
|
10
|
+
const keys = [];
|
|
11
|
+
const regex = new RegExp(
|
|
12
|
+
"^" +
|
|
13
|
+
pattern
|
|
14
|
+
.replace(/\/+$/, "")
|
|
15
|
+
.replace(/:([^/]+)/g, (_, k) => {
|
|
16
|
+
keys.push(k);
|
|
17
|
+
return "([^/]+)";
|
|
18
|
+
}) +
|
|
19
|
+
"$"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return (path) => {
|
|
23
|
+
const m = path.replace(/\/+$/, "").match(regex);
|
|
24
|
+
if (!m) return null;
|
|
25
|
+
const params = {};
|
|
26
|
+
keys.forEach((k, i) => (params[k] = decodeURIComponent(m[i + 1])));
|
|
27
|
+
return params;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ---- Navigator factory (SSR-Support) -------------- */
|
|
32
|
+
|
|
33
|
+
export function createNavigator({
|
|
34
|
+
url = null,
|
|
35
|
+
historyEnabled = true
|
|
36
|
+
} = {}) {
|
|
37
|
+
const initialURL = url
|
|
38
|
+
? new URL(url, "http://localhost")
|
|
39
|
+
: typeof window !== "undefined"
|
|
40
|
+
? window.location
|
|
41
|
+
: { pathname: "/", search: "" };
|
|
42
|
+
|
|
43
|
+
/* path is ALWAYS a string */
|
|
44
|
+
const path = a(String(initialURL.pathname));
|
|
45
|
+
const query = a(parseQuery(initialURL.search));
|
|
46
|
+
const params = a({});
|
|
47
|
+
|
|
48
|
+
/* async state */
|
|
49
|
+
const loading = a(false);
|
|
50
|
+
const error = a(null);
|
|
51
|
+
|
|
52
|
+
const routes = [];
|
|
53
|
+
let beforeEach = null;
|
|
54
|
+
|
|
55
|
+
/* canonical path setter */
|
|
56
|
+
function setPath(next) {
|
|
57
|
+
if (typeof next !== "string") {
|
|
58
|
+
console.warn("[navigator] path must be string, got:", next);
|
|
59
|
+
next = String(next);
|
|
60
|
+
}
|
|
61
|
+
path.set(next);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function syncFromLocation() {
|
|
65
|
+
if (typeof window === "undefined") return;
|
|
66
|
+
setPath(window.location.pathname);
|
|
67
|
+
query.set(parseQuery(window.location.search));
|
|
68
|
+
matchRoutes();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function matchRoutes() {
|
|
72
|
+
const p = path.get();
|
|
73
|
+
for (const r of routes) {
|
|
74
|
+
const res = r.match(p);
|
|
75
|
+
if (res) {
|
|
76
|
+
params.set(res);
|
|
77
|
+
return r;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
params.set({});
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function navigate(to, replace = false) {
|
|
85
|
+
const from = path.get();
|
|
86
|
+
const target = String(to);
|
|
87
|
+
|
|
88
|
+
/* Guards */
|
|
89
|
+
if (beforeEach) {
|
|
90
|
+
const result = await beforeEach(target, from);
|
|
91
|
+
if (result === false) return;
|
|
92
|
+
if (typeof result === "string") {
|
|
93
|
+
return navigate(result, true);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof window !== "undefined" && historyEnabled) {
|
|
98
|
+
if (replace) history.replaceState(null, "", target);
|
|
99
|
+
else history.pushState(null, "", target);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setPath(target);
|
|
103
|
+
matchRoutes();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof window !== "undefined" && historyEnabled) {
|
|
107
|
+
window.addEventListener("popstate", syncFromLocation);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* -------- 4: async resolve ---------------- */
|
|
111
|
+
|
|
112
|
+
async function resolveAsync() {
|
|
113
|
+
loading.set(true);
|
|
114
|
+
error.set(null);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const r = matchRoutes();
|
|
118
|
+
if (!r) {
|
|
119
|
+
loading.set(false);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let view = r.view;
|
|
124
|
+
|
|
125
|
+
// Allow lazy / async route views
|
|
126
|
+
if (typeof view === "function") {
|
|
127
|
+
const res = view();
|
|
128
|
+
view = res instanceof Promise ? await res : res;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
loading.set(false);
|
|
132
|
+
return view;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
error.set(err);
|
|
135
|
+
loading.set(false);
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
/* Reactive state */
|
|
142
|
+
path,
|
|
143
|
+
query,
|
|
144
|
+
params,
|
|
145
|
+
loading,
|
|
146
|
+
error,
|
|
147
|
+
|
|
148
|
+
/* Routing table */
|
|
149
|
+
route(pattern, view) {
|
|
150
|
+
routes.push({
|
|
151
|
+
pattern,
|
|
152
|
+
view,
|
|
153
|
+
match: compileMatcher(pattern)
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/* Navigation */
|
|
158
|
+
go(to) {
|
|
159
|
+
return navigate(to, false);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
replace(to) {
|
|
163
|
+
return navigate(to, true);
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
back() {
|
|
167
|
+
if (typeof window !== "undefined") history.back();
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
forward() {
|
|
171
|
+
if (typeof window !== "undefined") history.forward();
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/* Guards */
|
|
175
|
+
beforeEach(fn) {
|
|
176
|
+
beforeEach = fn;
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
/* Sync resolve (unchanged) */
|
|
180
|
+
resolve() {
|
|
181
|
+
const r = matchRoutes();
|
|
182
|
+
return r ? r.view : null;
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/* Async resolve (NEW) */
|
|
186
|
+
resolveAsync
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const n = createNavigator();
|
package/src/o.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// ORGANISER (o)
|
|
2
|
+
// Structural Layer — Organise Organs, keep identities and map
|
|
3
|
+
// Self License - 01SL
|
|
4
|
+
// HYP UI Framework
|
|
5
|
+
// Author: Hemang Tewari
|
|
6
|
+
|
|
7
|
+
export const o = (function () {
|
|
8
|
+
const organs = new Map();
|
|
9
|
+
let nextEi = 1;
|
|
10
|
+
|
|
11
|
+
function newEi() { return "ei_" + nextEi++; }
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
create(hi, body) {
|
|
15
|
+
const ei = newEi();
|
|
16
|
+
organs.set(ei, {
|
|
17
|
+
hi,
|
|
18
|
+
body,
|
|
19
|
+
ctx: new Map(),
|
|
20
|
+
mounted: true,
|
|
21
|
+
lifecycles: {
|
|
22
|
+
willMount: [], didMount: [],
|
|
23
|
+
willUpdate: [], didUpdate: [],
|
|
24
|
+
willUnmount: [], didUnmount: []
|
|
25
|
+
},
|
|
26
|
+
effects: new Set()
|
|
27
|
+
});
|
|
28
|
+
return ei;
|
|
29
|
+
},
|
|
30
|
+
addLifecycle(ei, phase, fn) {
|
|
31
|
+
const inst = organs.get(ei);
|
|
32
|
+
if (inst) inst.lifecycles[phase].push(fn);
|
|
33
|
+
},
|
|
34
|
+
runLifecycle(ei, phase, bodyRef) {
|
|
35
|
+
const inst = organs.get(ei);
|
|
36
|
+
if (!inst) return;
|
|
37
|
+
const list = inst.lifecycles[phase];
|
|
38
|
+
if (!list) return;
|
|
39
|
+
for (const fn of list)
|
|
40
|
+
s.add(() => fn(bodyRef), ei);
|
|
41
|
+
},
|
|
42
|
+
addEffect(ei, clear) {
|
|
43
|
+
const inst = organs.get(ei);
|
|
44
|
+
if (inst) inst.effects.add({ clear });
|
|
45
|
+
},
|
|
46
|
+
destroy(ei, { runLifecycle = true } = {}) {
|
|
47
|
+
const inst = organs.get(ei);
|
|
48
|
+
if (!inst) return;
|
|
49
|
+
|
|
50
|
+
inst.mounted = false;
|
|
51
|
+
|
|
52
|
+
if (runLifecycle) {
|
|
53
|
+
this.runLifecycle(ei, "willUnmount");
|
|
54
|
+
s.add(() => this.runLifecycle(ei, "didUnmount"), ei);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (inst.effects) {
|
|
58
|
+
for (const ef of inst.effects)
|
|
59
|
+
if (typeof ef.clear === "function") {
|
|
60
|
+
try { ef.clear(); }
|
|
61
|
+
catch (err) { console.error("Effect clear error:", err); }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
organs.delete(ei);
|
|
65
|
+
s.clear(ei);
|
|
66
|
+
},
|
|
67
|
+
get(ei) { return organs.get(ei); },
|
|
68
|
+
has(ei) { return organs.has(ei); },
|
|
69
|
+
isAlive(ei) {
|
|
70
|
+
const inst = organs.get(ei);
|
|
71
|
+
return inst ? inst.mounted : false;
|
|
72
|
+
},
|
|
73
|
+
all() { return organs; }
|
|
74
|
+
};
|
|
75
|
+
})();
|
package/src/r.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Reactor r()/Derived Act dA()
|
|
2
|
+
// Self License - 01SL
|
|
3
|
+
// HYP UI Framework
|
|
4
|
+
// Author: Hemang Tewari
|
|
5
|
+
|
|
6
|
+
export const r = (compute) => {
|
|
7
|
+
const sig = a();
|
|
8
|
+
const recompute = () => {
|
|
9
|
+
tr = recompute;
|
|
10
|
+
const val = compute();
|
|
11
|
+
tr = null;
|
|
12
|
+
sig.set(val);
|
|
13
|
+
};
|
|
14
|
+
recompute();
|
|
15
|
+
return sig;
|
|
16
|
+
};
|
package/src/s.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// SCHEDULER (s)
|
|
2
|
+
// Temporal Layer — queues & runs tasks efficiently
|
|
3
|
+
// Self License - 01SL
|
|
4
|
+
// HYP UI Framework
|
|
5
|
+
// Author: Hemang Tewari
|
|
6
|
+
|
|
7
|
+
export const s = (function () {
|
|
8
|
+
const left = new Set();
|
|
9
|
+
let flushing = false;
|
|
10
|
+
|
|
11
|
+
function flush() {
|
|
12
|
+
flushing = false;
|
|
13
|
+
const tasks = Array.from(left);
|
|
14
|
+
left.clear();
|
|
15
|
+
for (const task of tasks) {
|
|
16
|
+
try { task.fn(); }
|
|
17
|
+
catch (err) { console.error("Scheduler task error:", err); }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
add(fn, ei) {
|
|
23
|
+
if (ei && !o.isAlive(ei)) return;
|
|
24
|
+
left.add({ fn, ei });
|
|
25
|
+
if (!flushing) {
|
|
26
|
+
queueMicrotask(flush);
|
|
27
|
+
flushing = true;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
flush() { flush(); },
|
|
32
|
+
|
|
33
|
+
clear(ei) {
|
|
34
|
+
for (const task of [...left]) {
|
|
35
|
+
if (task.ei === ei) left.delete(task);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
})();
|
package/src/sA.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
// Side Act sA()
|
|
3
|
+
export const sA = (effect, depsFn = null, explicitEI = null) => {
|
|
4
|
+
const ei = explicitEI ?? e.currentEI(); // fallback to currentEI
|
|
5
|
+
if (!ei) return;
|
|
6
|
+
const inst = o.get(ei);
|
|
7
|
+
if (!inst) return;
|
|
8
|
+
|
|
9
|
+
if (!inst.ctx.has('sA-hooks')) inst.ctx.set('sA-hooks', new Map());
|
|
10
|
+
const hk = inst.ctx.get('sA-hooks');
|
|
11
|
+
const key = `sA-${hk.size}`;
|
|
12
|
+
|
|
13
|
+
const isEqualDeps = (a, b) => {
|
|
14
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
15
|
+
for (let i = 0; i < a.length; i++) {
|
|
16
|
+
if (!Object.is(a[i], b[i])) return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
20
|
+
let unsubscribers = [];
|
|
21
|
+
const run = () => {
|
|
22
|
+
tr = run;
|
|
23
|
+
const deps = depsFn ? depsFn() : [];
|
|
24
|
+
tr = null;
|
|
25
|
+
|
|
26
|
+
const prev = hk.get(key);
|
|
27
|
+
const changed = !prev || !isEqualDeps(deps, prev.deps);
|
|
28
|
+
|
|
29
|
+
if (changed) {
|
|
30
|
+
// cleanup previous effect and Actor subscriptions
|
|
31
|
+
prev?.clear?.();
|
|
32
|
+
unsubscribers.forEach(u => u());
|
|
33
|
+
unsubscribers = [];
|
|
34
|
+
|
|
35
|
+
// run effect
|
|
36
|
+
const clear = effect();
|
|
37
|
+
hk.set(key, { deps, clear });
|
|
38
|
+
if (clear) inst.effects.add({ clear });
|
|
39
|
+
|
|
40
|
+
// auto re-run if any Actor deps change
|
|
41
|
+
for (const d of deps) {
|
|
42
|
+
if (d instanceof Actor) {
|
|
43
|
+
const unsub = d.subscribe(() => s.add(run, ei));
|
|
44
|
+
unsubscribers.push(unsub);
|
|
45
|
+
if (ei) o.addEffect(ei, unsub);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
run();
|
|
51
|
+
s.add(run, ei);
|
|
52
|
+
};
|