ajo 0.1.27 → 0.1.29
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/context.js +1 -1
- package/dist/html.cjs +1 -1
- package/dist/html.js +28 -26
- package/dist/index.cjs +1 -1
- package/dist/index.js +73 -74
- package/package.json +6 -6
- package/readme.md +307 -194
- package/types.ts +27 -16
package/dist/context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const e = Symbol.for("ajo.context"), r = (t, o = Symbol()) => function(...l) {
|
|
1
|
+
const e = /* @__PURE__ */ Symbol.for("ajo.context"), r = (t, o = /* @__PURE__ */ Symbol()) => function(...l) {
|
|
2
2
|
const n = this ?? c;
|
|
3
3
|
return n ? l.length ? n[e][o] = l[0] : o in n[e] ? n[e][o] : t : t;
|
|
4
4
|
};
|
package/dist/html.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("./context.cjs"),m=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),b=Symbol.for("ajo.args"),p=e=>e.replace(/[&<>"']/g,r=>`&#${r.charCodeAt(0)};`),o=()=>{},v=e=>[...y(e)].join(""),y=function*(e,{alloc:r=o,push:t=o,placeholder:n=o}={}){for(e of f(e,{alloc:r,push:t,placeholder:n}))typeof e=="string"?yield p(e):yield*w(e,{alloc:r,push:t,placeholder:n})},w=function*({nodeName:e,children:r,...t},n){let l="";for(const i in t)i.startsWith("set:")||t[i]==null||t[i]===!1||(t[i]===!0?l+=` ${i}`:l+=` ${i}="${p(String(t[i]))}"`);m.has(e)?yield`<${e}${l}>`:(yield`<${e}${l}>`,r!=null&&(yield*y(r,n)),yield`</${e}>`)},f=function*(e,r){if(e==null)return;const t=typeof e;if(t!="boolean")if(t=="string")yield e;else if(t=="number"||t=="bigint")yield String(e);else if(Symbol.iterator in e)for(e of e)yield*f(e,r);else"nodeName"in e?typeof e.nodeName=="function"?yield*$(e,r):yield a(e,r):yield String(e)},$=function*({nodeName:e,fallback:r=e.fallback,...t},n){const l=e.constructor.name;e.src?yield S(e.src,t,n):l=="GeneratorFunction"?yield x(e,t,n):l=="AsyncGeneratorFunction"?yield A(e,r,t,n):(t=e(t),typeof t?.then=="function"?yield j(r,t,n):yield*f(t,n))},S=(e,r,t)=>{const n=t.alloc();return t.push({id:n,src:e,h:a(r,t),done:!0}),t.placeholder(n)},x=(e,r,t)=>{const n={...e.attrs},l={...e.args};for(const c in r)c.startsWith("attr:")?n[c.slice(5)]=r[c]:c=="key"||c=="skip"||c=="memo"||c=="ref"||c.startsWith("set:")?n[c]=r[c]:l[c]=r[c];const i={[u.Context]:Object.create(u.current()?.[u.Context]??null),[b]:l,next:o,return:o,throw:c=>{throw c}},s=e.call(i,l),g=u.current();u.current(i);const d=c=>({...n,nodeName:e.is??"div",...a({children:c},t)});try{return d(s.next().value)}catch(c){return d(s.throw(c).value)}finally{s.return?.(),u.current(g)}},A=(e,r,t,n)=>{const l=n.alloc();return Promise.resolve().then(async()=>{const i=e(t);n={...n,alloc:(s=l)=>n.alloc(s)};try{for(t=await i.next();!t.done;)n.push({id:l,h:a(t.value,n),done:!1}),t=await i.next();n.push({id:l,h:a(t.value,n),done:!0})}catch(s){n.push({id:l,h:a(s,n),done:!0})}finally{i.return?.()}}),n.placeholder(l,r)},j=(e,r,t)=>{const n=t.alloc();return r.then(l=>t.push({id:n,h:a(l,{...t,alloc:(i=n)=>t.alloc(i)}),done:!0})),t.placeholder(n,e)},a=({key:e,skip:r,memo:t,ref:n,...l},i)=>{if("children"in l){const s=[...f(l.children,i)];s.length?l.children=s.length==1?s[0]:s:delete l.children}return l};exports.html=y;exports.render=v;
|
package/dist/html.js
CHANGED
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
import { Context as
|
|
2
|
-
const
|
|
3
|
-
},
|
|
4
|
-
for (e of
|
|
5
|
-
typeof e == "string" ? yield
|
|
1
|
+
import { Context as d, current as o } from "./context.js";
|
|
2
|
+
const b = /* @__PURE__ */ new Set(["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]), w = /* @__PURE__ */ Symbol.for("ajo.args"), p = (e) => e.replace(/[&<>"']/g, (r) => `&#${r.charCodeAt(0)};`), u = () => {
|
|
3
|
+
}, W = (e) => [...g(e)].join(""), g = function* (e, { alloc: r = u, push: t = u, placeholder: n = u } = {}) {
|
|
4
|
+
for (e of f(e, { alloc: r, push: t, placeholder: n }))
|
|
5
|
+
typeof e == "string" ? yield p(e) : yield* $(e, { alloc: r, push: t, placeholder: n });
|
|
6
6
|
}, $ = function* ({ nodeName: e, children: r, ...t }, n) {
|
|
7
7
|
let l = "";
|
|
8
8
|
for (const i in t)
|
|
9
|
-
i.startsWith("set:") || t[i] == null || t[i] === !1 || (t[i] === !0 ? l += ` ${i}` : l += ` ${i}="${
|
|
10
|
-
|
|
11
|
-
},
|
|
9
|
+
i.startsWith("set:") || t[i] == null || t[i] === !1 || (t[i] === !0 ? l += ` ${i}` : l += ` ${i}="${p(String(t[i]))}"`);
|
|
10
|
+
b.has(e) ? yield `<${e}${l}>` : (yield `<${e}${l}>`, r != null && (yield* g(r, n)), yield `</${e}>`);
|
|
11
|
+
}, f = function* (e, r) {
|
|
12
12
|
if (e == null) return;
|
|
13
13
|
const t = typeof e;
|
|
14
14
|
if (t != "boolean")
|
|
15
15
|
if (t == "string") yield e;
|
|
16
16
|
else if (t == "number" || t == "bigint") yield String(e);
|
|
17
|
-
else if (Symbol.iterator in e) for (e of e) yield*
|
|
18
|
-
else "nodeName" in e ? typeof e.nodeName == "function" ? yield*
|
|
19
|
-
},
|
|
17
|
+
else if (Symbol.iterator in e) for (e of e) yield* f(e, r);
|
|
18
|
+
else "nodeName" in e ? typeof e.nodeName == "function" ? yield* v(e, r) : yield a(e, r) : yield String(e);
|
|
19
|
+
}, v = function* ({ nodeName: e, fallback: r = e.fallback, ...t }, n) {
|
|
20
20
|
const l = e.constructor.name;
|
|
21
|
-
e.src ? yield
|
|
22
|
-
},
|
|
21
|
+
e.src ? yield x(e.src, t, n) : l == "GeneratorFunction" ? yield S(e, t, n) : l == "AsyncGeneratorFunction" ? yield A(e, r, t, n) : (t = e(t), typeof t?.then == "function" ? yield G(r, t, n) : yield* f(t, n));
|
|
22
|
+
}, x = (e, r, t) => {
|
|
23
23
|
const n = t.alloc();
|
|
24
24
|
return t.push({ id: n, src: e, h: a(r, t), done: !0 }), t.placeholder(n);
|
|
25
|
-
},
|
|
25
|
+
}, S = (e, r, t) => {
|
|
26
26
|
const n = { ...e.attrs }, l = { ...e.args };
|
|
27
27
|
for (const c in r)
|
|
28
28
|
c.startsWith("attr:") ? n[c.slice(5)] = r[c] : c == "key" || c == "skip" || c == "memo" || c == "ref" || c.startsWith("set:") ? n[c] = r[c] : l[c] = r[c];
|
|
29
29
|
const i = {
|
|
30
|
-
[
|
|
31
|
-
[
|
|
30
|
+
[d]: Object.create(o()?.[d] ?? null),
|
|
31
|
+
[w]: l,
|
|
32
32
|
next: u,
|
|
33
33
|
return: u,
|
|
34
34
|
throw: (c) => {
|
|
35
35
|
throw c;
|
|
36
36
|
}
|
|
37
|
-
}, s = e.call(i, l),
|
|
38
|
-
|
|
37
|
+
}, s = e.call(i, l), m = o();
|
|
38
|
+
o(i);
|
|
39
|
+
const y = (c) => ({ ...n, nodeName: e.is ?? "div", ...a({ children: c }, t) });
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
return y(s.next().value);
|
|
42
|
+
} catch (c) {
|
|
43
|
+
return y(s.throw(c).value);
|
|
42
44
|
} finally {
|
|
43
|
-
s.return?.(),
|
|
45
|
+
s.return?.(), o(m);
|
|
44
46
|
}
|
|
45
|
-
},
|
|
47
|
+
}, A = (e, r, t, n) => {
|
|
46
48
|
const l = n.alloc();
|
|
47
49
|
return Promise.resolve().then(async () => {
|
|
48
50
|
const i = e(t);
|
|
@@ -57,17 +59,17 @@ const m = /* @__PURE__ */ new Set(["area", "base", "br", "col", "command", "embe
|
|
|
57
59
|
i.return?.();
|
|
58
60
|
}
|
|
59
61
|
}), n.placeholder(l, r);
|
|
60
|
-
},
|
|
62
|
+
}, G = (e, r, t) => {
|
|
61
63
|
const n = t.alloc();
|
|
62
64
|
return r.then((l) => t.push({ id: n, h: a(l, { ...t, alloc: (i = n) => t.alloc(i) }), done: !0 })), t.placeholder(n, e);
|
|
63
65
|
}, a = ({ key: e, skip: r, memo: t, ref: n, ...l }, i) => {
|
|
64
66
|
if ("children" in l) {
|
|
65
|
-
const s = [...
|
|
67
|
+
const s = [...f(l.children, i)];
|
|
66
68
|
s.length ? l.children = s.length == 1 ? s[0] : s : delete l.children;
|
|
67
69
|
}
|
|
68
70
|
return l;
|
|
69
71
|
};
|
|
70
72
|
export {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
g as html,
|
|
74
|
+
W as render
|
|
73
75
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./context.cjs"),y=Symbol.for("ajo.key"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./context.cjs"),y=Symbol.for("ajo.key"),j=Symbol.for("ajo.memo"),g=Symbol.for("ajo.ref"),A=Symbol.for("ajo.cache"),c=Symbol.for("ajo.generator"),a=Symbol.for("ajo.iterator"),b=Symbol.for("ajo.render"),l=Symbol.for("ajo.args"),N=e=>e.children,k=(e,t,...r)=>((t??={}).nodeName=e,!("children"in t)&&r.length&&(t.children=r.length==1?r[0]:r),t),f=(e,t,r=t.firstChild,s=null)=>{for(e of S(e)){const n=E(e,t,r);r==null?m(t,n,s):n==r?r=n.nextSibling:n==r.nextSibling?(m(t,r,s),r=n.nextSibling):m(t,n,r)}for(;r!=s;){const n=r.nextSibling;r.nodeType==1&&p(r),t.removeChild(r),r=n}},S=function*(e){if(e==null)return;const t=typeof e;if(t!="boolean")if(t=="string")yield e;else if(t=="number"||t=="bigint")yield String(e);else if(Symbol.iterator in e)for(e of e)yield*S(e);else"nodeName"in e?typeof e.nodeName=="function"?yield*O(e):yield e:yield String(e)},O=function*({nodeName:e,...t}){e.constructor.name=="GeneratorFunction"?yield T(e,t):yield*S(e(t))},T=(e,t)=>{const r={...e.attrs},s={...e.args};for(const n in t)n.startsWith("attr:")?r[n.slice(5)]=t[n]:n=="key"||n=="skip"||n=="memo"||n=="ref"||n.startsWith("set:")?r[n]=t[n]:s[n]=t[n];return{...r,nodeName:e.is??"div",[c]:e,[l]:s}},E=(e,t,r)=>typeof e=="string"?F(e,r):G(e,t,r),F=(e,t)=>{for(;t&&t.nodeType!=3;)t=t.nextSibling;return t?t.data!=e&&(t.data=e):t=document.createTextNode(e),t},G=({nodeName:e,children:t,key:r,skip:s,memo:n,ref:x,[c]:u,[l]:v,...w},C,i)=>{for(;i&&(i.localName!=e||i[y]!=null&&i[y]!=r||i[c]&&i[c]!=u);)i=i.nextSibling;return i??=document.createElementNS(w.xmlns??C.namespaceURI,e),r!=null&&(i[y]=r),(n==null||W(i[j],i[j]=n))&&(R(i[A]??B(i),i[A]=w,i),s||(u?I(u,v,i):f(t,i)),typeof x=="function"&&(i[g]=x)(i)),i},R=(e,t,r)=>{for(const s in{...e,...t})e[s]!==t[s]&&(s.startsWith("set:")?r[s.slice(4)]=t[s]:t[s]==null||t[s]===!1?r.removeAttribute(s):r.setAttribute(s,t[s]===!0?"":t[s]))},W=(e,t)=>Array.isArray(e)&&Array.isArray(t)?e.some((r,s)=>r!==t[s]):e!==t,B=e=>Array.from(e.attributes).reduce((t,r)=>(t[r.name]=r.value,t),{}),m=(e,t,r)=>{if(t.contains(document.activeElement)){const s=t.nextSibling;for(;r&&r!=t;){const n=r.nextSibling;e.insertBefore(r,s),r=n}}else e.insertBefore(t,r)},p=e=>{for(const t of e.children)p(t);typeof e.return=="function"&&e.return(),e[g]?.(null)},I=(e,t,r)=>{r[c]??=(M(r),e),Object.assign(r[l]??={},t),r[b]()},M=e=>{Object.assign(e,h),e[o.Context]=Object.create(o.current()?.[o.Context]??null)},h={[b](){const e=o.current();o.current(this);try{const{value:t,done:r}=(this[a]??=this[c].call(this,this[l])).next();f(t,this),this[g]?.(this),r&&this.return()}catch(t){this.throw(t)}finally{o.current(e)}},next(e){try{e?.call(this,this[l])}catch(t){return this.throw(t)}o.current()?.contains(this)||this[b]()},throw(e){for(let t=this;t;t=t.parentNode)if(t[a]?.throw)try{return f(t[a].throw(e).value,t)}catch(r){e=new Error(r?.message??r,{cause:e})}throw e},return(){try{this[a]?.return()}catch(e){this.throw(e)}finally{this[a]=null}}};exports.Fragment=N;exports.h=k;exports.render=f;
|
package/dist/index.js
CHANGED
|
@@ -1,94 +1,93 @@
|
|
|
1
|
-
import { Context as
|
|
2
|
-
const u = Symbol.for("ajo.key"), p = Symbol.for("ajo.memo"),
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
r == null ? e.appendChild(i) : i == r ? r = i.nextSibling : i == r.nextSibling ? (e.appendChild(r), r = i.nextSibling) : W(e, i, r);
|
|
1
|
+
import { Context as j, current as c } from "./context.js";
|
|
2
|
+
const u = /* @__PURE__ */ Symbol.for("ajo.key"), p = /* @__PURE__ */ Symbol.for("ajo.memo"), g = /* @__PURE__ */ Symbol.for("ajo.ref"), A = /* @__PURE__ */ Symbol.for("ajo.cache"), o = /* @__PURE__ */ Symbol.for("ajo.generator"), a = /* @__PURE__ */ Symbol.for("ajo.iterator"), m = /* @__PURE__ */ Symbol.for("ajo.render"), l = /* @__PURE__ */ Symbol.for("ajo.args"), U = (e) => e.children, h = (e, t, ...r) => ((t ??= {}).nodeName = e, !("children" in t) && r.length && (t.children = r.length == 1 ? r[0] : r), t), b = (e, t, r = t.firstChild, s = null) => {
|
|
3
|
+
for (e of S(e)) {
|
|
4
|
+
const n = G(e, t, r);
|
|
5
|
+
r == null ? y(t, n, s) : n == r ? r = n.nextSibling : n == r.nextSibling ? (y(t, r, s), r = n.nextSibling) : y(t, n, r);
|
|
7
6
|
}
|
|
8
|
-
for (; r; ) {
|
|
9
|
-
const
|
|
10
|
-
r.nodeType == 1 &&
|
|
7
|
+
for (; r != s; ) {
|
|
8
|
+
const n = r.nextSibling;
|
|
9
|
+
r.nodeType == 1 && N(r), t.removeChild(r), r = n;
|
|
11
10
|
}
|
|
12
|
-
},
|
|
13
|
-
if (
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
if (
|
|
17
|
-
else if (
|
|
18
|
-
else if (Symbol.iterator in
|
|
19
|
-
else "nodeName" in
|
|
20
|
-
}, C = function* ({ nodeName:
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
const r = { ...
|
|
24
|
-
for (const
|
|
25
|
-
|
|
26
|
-
return { ...r, nodeName:
|
|
27
|
-
},
|
|
28
|
-
for (;
|
|
29
|
-
return
|
|
30
|
-
},
|
|
31
|
-
for (;
|
|
32
|
-
return
|
|
33
|
-
},
|
|
34
|
-
for (const
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
if (
|
|
38
|
-
const
|
|
39
|
-
for (; r && r !=
|
|
40
|
-
const
|
|
41
|
-
|
|
11
|
+
}, S = function* (e) {
|
|
12
|
+
if (e == null) return;
|
|
13
|
+
const t = typeof e;
|
|
14
|
+
if (t != "boolean")
|
|
15
|
+
if (t == "string") yield e;
|
|
16
|
+
else if (t == "number" || t == "bigint") yield String(e);
|
|
17
|
+
else if (Symbol.iterator in e) for (e of e) yield* S(e);
|
|
18
|
+
else "nodeName" in e ? typeof e.nodeName == "function" ? yield* C(e) : yield e : yield String(e);
|
|
19
|
+
}, C = function* ({ nodeName: e, ...t }) {
|
|
20
|
+
e.constructor.name == "GeneratorFunction" ? yield E(e, t) : yield* S(e(t));
|
|
21
|
+
}, E = (e, t) => {
|
|
22
|
+
const r = { ...e.attrs }, s = { ...e.args };
|
|
23
|
+
for (const n in t)
|
|
24
|
+
n.startsWith("attr:") ? r[n.slice(5)] = t[n] : n == "key" || n == "skip" || n == "memo" || n == "ref" || n.startsWith("set:") ? r[n] = t[n] : s[n] = t[n];
|
|
25
|
+
return { ...r, nodeName: e.is ?? "div", [o]: e, [l]: s };
|
|
26
|
+
}, G = (e, t, r) => typeof e == "string" ? O(e, r) : R(e, t, r), O = (e, t) => {
|
|
27
|
+
for (; t && t.nodeType != 3; ) t = t.nextSibling;
|
|
28
|
+
return t ? t.data != e && (t.data = e) : t = document.createTextNode(e), t;
|
|
29
|
+
}, R = ({ nodeName: e, children: t, key: r, skip: s, memo: n, ref: x, [o]: f, [l]: k, ...w }, v, i) => {
|
|
30
|
+
for (; i && (i.localName != e || i[u] != null && i[u] != r || i[o] && i[o] != f); ) i = i.nextSibling;
|
|
31
|
+
return i ??= document.createElementNS(w.xmlns ?? v.namespaceURI, e), r != null && (i[u] = r), (n == null || W(i[p], i[p] = n)) && (T(i[A] ?? B(i), i[A] = w, i), s || (f ? F(f, k, i) : b(t, i)), typeof x == "function" && (i[g] = x)(i)), i;
|
|
32
|
+
}, T = (e, t, r) => {
|
|
33
|
+
for (const s in { ...e, ...t })
|
|
34
|
+
e[s] !== t[s] && (s.startsWith("set:") ? r[s.slice(4)] = t[s] : t[s] == null || t[s] === !1 ? r.removeAttribute(s) : r.setAttribute(s, t[s] === !0 ? "" : t[s]));
|
|
35
|
+
}, W = (e, t) => Array.isArray(e) && Array.isArray(t) ? e.some((r, s) => r !== t[s]) : e !== t, B = (e) => Array.from(e.attributes).reduce((t, r) => (t[r.name] = r.value, t), {}), y = (e, t, r) => {
|
|
36
|
+
if (t.contains(document.activeElement)) {
|
|
37
|
+
const s = t.nextSibling;
|
|
38
|
+
for (; r && r != t; ) {
|
|
39
|
+
const n = r.nextSibling;
|
|
40
|
+
e.insertBefore(r, s), r = n;
|
|
42
41
|
}
|
|
43
|
-
} else
|
|
44
|
-
},
|
|
45
|
-
for (const
|
|
46
|
-
typeof
|
|
47
|
-
},
|
|
48
|
-
r[o] ??= (
|
|
49
|
-
},
|
|
50
|
-
Object.assign(
|
|
51
|
-
},
|
|
52
|
-
[
|
|
53
|
-
const
|
|
54
|
-
|
|
42
|
+
} else e.insertBefore(t, r);
|
|
43
|
+
}, N = (e) => {
|
|
44
|
+
for (const t of e.children) N(t);
|
|
45
|
+
typeof e.return == "function" && e.return(), e[g]?.(null);
|
|
46
|
+
}, F = (e, t, r) => {
|
|
47
|
+
r[o] ??= (I(r), e), Object.assign(r[l] ??= {}, t), r[m]();
|
|
48
|
+
}, I = (e) => {
|
|
49
|
+
Object.assign(e, K), e[j] = Object.create(c()?.[j] ?? null);
|
|
50
|
+
}, K = {
|
|
51
|
+
[m]() {
|
|
52
|
+
const e = c();
|
|
53
|
+
c(this);
|
|
55
54
|
try {
|
|
56
|
-
const { value:
|
|
57
|
-
|
|
58
|
-
} catch (
|
|
59
|
-
this.throw(
|
|
55
|
+
const { value: t, done: r } = (this[a] ??= this[o].call(this, this[l])).next();
|
|
56
|
+
b(t, this), this[g]?.(this), r && this.return();
|
|
57
|
+
} catch (t) {
|
|
58
|
+
this.throw(t);
|
|
60
59
|
} finally {
|
|
61
|
-
|
|
60
|
+
c(e);
|
|
62
61
|
}
|
|
63
62
|
},
|
|
64
|
-
next(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} catch (
|
|
68
|
-
this.throw(
|
|
63
|
+
next(e) {
|
|
64
|
+
try {
|
|
65
|
+
e?.call(this, this[l]);
|
|
66
|
+
} catch (t) {
|
|
67
|
+
return this.throw(t);
|
|
69
68
|
}
|
|
70
|
-
|
|
69
|
+
c()?.contains(this) || this[m]();
|
|
71
70
|
},
|
|
72
|
-
throw(
|
|
73
|
-
for (let
|
|
74
|
-
return
|
|
71
|
+
throw(e) {
|
|
72
|
+
for (let t = this; t; t = t.parentNode) if (t[a]?.throw) try {
|
|
73
|
+
return b(t[a].throw(e).value, t);
|
|
75
74
|
} catch (r) {
|
|
76
|
-
|
|
75
|
+
e = new Error(r?.message ?? r, { cause: e });
|
|
77
76
|
}
|
|
78
|
-
throw
|
|
77
|
+
throw e;
|
|
79
78
|
},
|
|
80
79
|
return() {
|
|
81
80
|
try {
|
|
82
|
-
this[
|
|
83
|
-
} catch (
|
|
84
|
-
this.throw(
|
|
81
|
+
this[a]?.return();
|
|
82
|
+
} catch (e) {
|
|
83
|
+
this.throw(e);
|
|
85
84
|
} finally {
|
|
86
|
-
this[
|
|
85
|
+
this[a] = null;
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
};
|
|
90
89
|
export {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
U as Fragment,
|
|
91
|
+
h,
|
|
92
|
+
b as render
|
|
94
93
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "ajo is a JavaScript view library for building user interfaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./types.ts",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"types.ts"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@types/node": "
|
|
37
|
-
"happy-dom": "
|
|
38
|
-
"vite": "7.1
|
|
39
|
-
"vite-tsconfig-paths": "
|
|
40
|
-
"vitest": "
|
|
36
|
+
"@types/node": "25.0.9",
|
|
37
|
+
"happy-dom": "20.3.1",
|
|
38
|
+
"vite": "7.3.1",
|
|
39
|
+
"vite-tsconfig-paths": "6.0.4",
|
|
40
|
+
"vitest": "4.0.17"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"ui",
|
package/readme.md
CHANGED
|
@@ -15,320 +15,433 @@
|
|
|
15
15
|
|
|
16
16
|
A modern JavaScript library for building user interfaces with generator-based state management, efficient DOM updates, and streaming server-side rendering.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **
|
|
21
|
-
- **Efficient DOM Updates**: In-place DOM reconciliation minimizes DOM manipulation and maximizes performance
|
|
22
|
-
- **Declarative JSX**: Write components using familiar JSX syntax with full TypeScript support
|
|
23
|
-
- **Performance Optimization**: Built-in `memo` attribute for fine-grained performance optimization
|
|
24
|
-
- **Context API**: Share state across component trees without prop drilling
|
|
25
|
-
- **Server-Side Rendering**: Complete SSR solution with streaming and hydration support
|
|
26
|
-
- **Islands Architecture**: Selective hydration for maximum performance with minimal client-side JavaScript
|
|
18
|
+
- **Generator-Based Components**: Use `function*` for stateful components with built-in lifecycle
|
|
19
|
+
- **Efficient DOM Updates**: In-place reconciliation minimizes DOM manipulation
|
|
20
|
+
- **Streaming SSR**: Progressive rendering with selective hydration (islands)
|
|
27
21
|
|
|
28
22
|
## Quick Start
|
|
29
23
|
|
|
30
|
-
Install Ajo using your preferred package manager:
|
|
31
|
-
|
|
32
24
|
```bash
|
|
33
25
|
npm install ajo
|
|
34
26
|
```
|
|
35
27
|
|
|
36
|
-
Create your first component:
|
|
37
|
-
|
|
38
28
|
```javascript
|
|
39
29
|
import { render } from 'ajo'
|
|
40
30
|
|
|
41
|
-
// Stateless component
|
|
42
|
-
const Greeting = ({ name }) => <p>Hello, {name}!</p>
|
|
43
|
-
|
|
44
|
-
// Stateful component
|
|
45
31
|
function* Counter() {
|
|
46
|
-
|
|
47
32
|
let count = 0
|
|
48
33
|
|
|
49
|
-
const increment = () => this.next(() => count++)
|
|
50
|
-
|
|
51
34
|
while (true) yield (
|
|
52
|
-
<button set:onclick={
|
|
35
|
+
<button set:onclick={() => this.next(() => count++)}>
|
|
53
36
|
Count: {count}
|
|
54
37
|
</button>
|
|
55
38
|
)
|
|
56
39
|
}
|
|
57
40
|
|
|
58
|
-
// Render to DOM
|
|
59
41
|
render(<Counter />, document.body)
|
|
60
42
|
```
|
|
61
43
|
|
|
62
|
-
|
|
44
|
+
### Build Configuration
|
|
63
45
|
|
|
64
|
-
|
|
46
|
+
Configure your build tool to use Ajo's JSX factory:
|
|
65
47
|
|
|
66
|
-
**
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
48
|
+
**Vite:**
|
|
49
|
+
```typescript
|
|
50
|
+
// vite.config.ts
|
|
51
|
+
import { defineConfig } from 'vite'
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
esbuild: {
|
|
55
|
+
jsxFactory: 'h',
|
|
56
|
+
jsxFragment: 'Fragment',
|
|
57
|
+
jsxInject: `import { h, Fragment } from 'ajo'`,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
74
60
|
```
|
|
75
61
|
|
|
76
|
-
**
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
while (true) yield (
|
|
85
|
-
<>
|
|
86
|
-
<input set:onkeydown={e => e.key === 'Enter' && addTodo(e.target.value)} />
|
|
87
|
-
<ul>
|
|
88
|
-
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
|
|
89
|
-
</ul>
|
|
90
|
-
</>
|
|
91
|
-
)
|
|
62
|
+
**TypeScript:**
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"compilerOptions": {
|
|
66
|
+
"jsx": "react",
|
|
67
|
+
"jsxFactory": "h",
|
|
68
|
+
"jsxFragmentFactory": "Fragment"
|
|
69
|
+
}
|
|
92
70
|
}
|
|
93
71
|
```
|
|
94
72
|
|
|
95
|
-
|
|
73
|
+
**Other build tools:** Set `jsxFactory: 'h'`, `jsxFragment: 'Fragment'`, and auto-import `{ h, Fragment }` from `'ajo'`.
|
|
96
74
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
75
|
+
## Core Concepts
|
|
76
|
+
|
|
77
|
+
### Stateless Components
|
|
78
|
+
|
|
79
|
+
Pure functions that receive props and return JSX:
|
|
100
80
|
|
|
101
81
|
```javascript
|
|
102
|
-
|
|
82
|
+
const Greeting = ({ name }) => <p>Hello, {name}!</p>
|
|
83
|
+
```
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
let items = []
|
|
85
|
+
### Stateful Components
|
|
106
86
|
|
|
107
|
-
|
|
108
|
-
const addItem = product => this.next(() => items.push(product))
|
|
87
|
+
Generator functions with automatic wrapper elements. The structure provides a natural mental model:
|
|
109
88
|
|
|
110
|
-
|
|
111
|
-
|
|
89
|
+
- **Before the loop**: Persistent state and handlers (survives re-renders)
|
|
90
|
+
- **Inside the loop**: Derived values (computed fresh each render)
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
function* TodoList() {
|
|
94
|
+
let todos = []
|
|
95
|
+
let text = ''
|
|
112
96
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
97
|
+
const add = () => this.next(() => {
|
|
98
|
+
if (text.trim()) {
|
|
99
|
+
todos.push({ id: Date.now(), text })
|
|
100
|
+
text = ''
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
while (true) {
|
|
105
|
+
const count = todos.length
|
|
116
106
|
|
|
117
107
|
yield (
|
|
118
108
|
<>
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
<input
|
|
110
|
+
set:value={text}
|
|
111
|
+
set:oninput={e => text = e.target.value}
|
|
112
|
+
set:onkeydown={e => e.key === 'Enter' && add()}
|
|
113
|
+
/>
|
|
114
|
+
<button set:onclick={add}>Add ({count})</button>
|
|
115
|
+
<ul>
|
|
116
|
+
{todos.map(t => <li key={t.id}>{t.text}</li>)}
|
|
117
|
+
</ul>
|
|
122
118
|
</>
|
|
123
119
|
)
|
|
124
120
|
}
|
|
125
121
|
}
|
|
126
122
|
```
|
|
127
123
|
|
|
128
|
-
###
|
|
124
|
+
### Re-rendering with `this.next()`
|
|
129
125
|
|
|
130
|
-
-
|
|
131
|
-
- **`ref`**: DOM element access
|
|
132
|
-
- **`memo`**: Performance optimization
|
|
133
|
-
- **`skip`**: Third-party DOM management
|
|
134
|
-
- **`set:`**: Direct property setting
|
|
135
|
-
- **`attr:`**: Force HTML attributes
|
|
126
|
+
Call `this.next()` to trigger a re-render. The optional callback receives current props, useful when props may have changed:
|
|
136
127
|
|
|
137
128
|
```javascript
|
|
138
|
-
function*
|
|
129
|
+
function* Stepper(args) {
|
|
130
|
+
let count = 0
|
|
139
131
|
|
|
140
|
-
|
|
132
|
+
// Access current props in callback
|
|
133
|
+
const inc = () => this.next(({ step = 1 }) => count += step)
|
|
141
134
|
|
|
142
135
|
while (true) yield (
|
|
143
|
-
<
|
|
144
|
-
ref={el => {
|
|
145
|
-
if (el && !mapRef) {
|
|
146
|
-
mapRef = el
|
|
147
|
-
// Third-party map library controls this DOM
|
|
148
|
-
new GoogleMap(el, args.config)
|
|
149
|
-
}
|
|
150
|
-
}}
|
|
151
|
-
skip={true}
|
|
152
|
-
>
|
|
153
|
-
{/* Google Maps API manages children elements */}
|
|
154
|
-
</div>
|
|
136
|
+
<button set:onclick={inc}>Count: {count} (+{args.step})</button>
|
|
155
137
|
)
|
|
156
138
|
}
|
|
157
139
|
```
|
|
158
140
|
|
|
159
|
-
|
|
141
|
+
**Never destructure args in the generator signature** - it locks values to initial props:
|
|
160
142
|
|
|
161
|
-
### Static SSR
|
|
162
143
|
```javascript
|
|
163
|
-
|
|
144
|
+
// DON'T - values frozen at mount
|
|
145
|
+
function* Bad({ step }) { let count = step }
|
|
164
146
|
|
|
165
|
-
|
|
147
|
+
// DO - use args directly, or destructure inside the loop
|
|
148
|
+
function* Good(args) {
|
|
149
|
+
while (true) {
|
|
150
|
+
const { step } = args // fresh each render
|
|
151
|
+
yield ...
|
|
152
|
+
}
|
|
153
|
+
}
|
|
166
154
|
```
|
|
167
155
|
|
|
168
|
-
###
|
|
169
|
-
|
|
170
|
-
|
|
156
|
+
### Lifecycle and Cleanup
|
|
157
|
+
|
|
158
|
+
Use `try...finally` for cleanup when the component unmounts:
|
|
171
159
|
|
|
172
|
-
|
|
160
|
+
```javascript
|
|
161
|
+
function* Clock() {
|
|
162
|
+
let time = new Date()
|
|
163
|
+
const interval = setInterval(() => this.next(() => time = new Date()), 1000)
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
while (true) yield <p>{time.toLocaleTimeString()}</p>
|
|
167
|
+
} finally {
|
|
168
|
+
clearInterval(interval)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
173
171
|
```
|
|
174
172
|
|
|
175
|
-
|
|
173
|
+
### Error Boundaries
|
|
176
174
|
|
|
177
|
-
|
|
178
|
-
2. **Don't destructure props in function signatures** - use `args` parameter or destructure inside the render loop
|
|
179
|
-
3. **Leverage the generator structure** for natural state and lifecycle management
|
|
180
|
-
4. **Use TypeScript** for enhanced developer experience
|
|
175
|
+
Use `try...catch` **inside** the loop to catch errors and recover:
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
```javascript
|
|
178
|
+
function* ErrorBoundary(args) {
|
|
179
|
+
while (true) {
|
|
180
|
+
try {
|
|
181
|
+
yield args.children
|
|
182
|
+
} catch (error) {
|
|
183
|
+
yield (
|
|
184
|
+
<>
|
|
185
|
+
<p>Error: {error.message}</p>
|
|
186
|
+
<button set:onclick={() => this.next()}>Retry</button>
|
|
187
|
+
</>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Special Attributes
|
|
183
195
|
|
|
184
|
-
|
|
196
|
+
| Attribute | Description |
|
|
197
|
+
|-----------|-------------|
|
|
198
|
+
| `key` | Unique identifier for list reconciliation |
|
|
199
|
+
| `ref` | Callback receiving DOM element (or `null` on unmount) |
|
|
200
|
+
| `memo` | Skip reconciliation: `memo={[deps]}`, `memo={value}`, or `memo` (render once) |
|
|
201
|
+
| `skip` | Exclude children from reconciliation (required with `set:innerHTML`) |
|
|
202
|
+
| `set:*` | Set DOM properties instead of HTML attributes |
|
|
203
|
+
| `attr:*` | Force HTML attributes on stateful component wrappers |
|
|
185
204
|
|
|
186
|
-
|
|
187
|
-
Renders JSX into a DOM container element.
|
|
205
|
+
### `set:` - DOM Properties vs HTML Attributes
|
|
188
206
|
|
|
189
207
|
```javascript
|
|
190
|
-
|
|
208
|
+
// Events (always use set:)
|
|
209
|
+
<button set:onclick={handleClick}>Click</button>
|
|
191
210
|
|
|
192
|
-
|
|
193
|
-
|
|
211
|
+
// Dynamic values that need to sync with state
|
|
212
|
+
<input set:value={text} /> // DOM property (syncs)
|
|
213
|
+
<input value="initial" /> // HTML attribute (initial only)
|
|
194
214
|
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
<input type="checkbox" set:checked={bool} />
|
|
216
|
+
<video set:currentTime={0} set:muted />
|
|
217
|
+
|
|
218
|
+
// innerHTML requires skip
|
|
219
|
+
<div set:innerHTML={html} skip />
|
|
220
|
+
```
|
|
197
221
|
|
|
198
|
-
|
|
199
|
-
JSX fragment component for rendering multiple elements without a wrapper.
|
|
222
|
+
### `ref` - DOM Access
|
|
200
223
|
|
|
201
224
|
```javascript
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<li>Item 1</li>
|
|
205
|
-
<li>Item 2</li>
|
|
206
|
-
</>
|
|
207
|
-
)
|
|
208
|
-
```
|
|
225
|
+
function* AutoFocus() {
|
|
226
|
+
let input = null
|
|
209
227
|
|
|
210
|
-
|
|
228
|
+
while (true) yield (
|
|
229
|
+
<>
|
|
230
|
+
<input ref={el => el?.focus()} />
|
|
231
|
+
<button set:onclick={() => input?.select()}>Select</button>
|
|
232
|
+
</>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
211
235
|
|
|
212
|
-
|
|
236
|
+
// Ref to stateful component includes control methods
|
|
237
|
+
let timer = null
|
|
238
|
+
<Clock ref={el => timer = el} />
|
|
239
|
+
timer?.next() // trigger re-render from outside
|
|
240
|
+
```
|
|
213
241
|
|
|
214
|
-
|
|
215
|
-
Triggers a re-render of the component by advancing to the next yield point. Optionally accepts a callback function that receives the component's current props/args as the first parameter.
|
|
242
|
+
### `memo` - Performance Optimization
|
|
216
243
|
|
|
217
244
|
```javascript
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
245
|
+
<div memo={[user.id]}>...</div> // re-render when user.id changes
|
|
246
|
+
<div memo={count}>...</div> // re-render when count changes
|
|
247
|
+
<footer memo>Static content</footer> // render once, never update
|
|
248
|
+
```
|
|
221
249
|
|
|
222
|
-
|
|
223
|
-
// Simple re-render
|
|
224
|
-
this.next(() => count++)
|
|
225
|
-
}
|
|
250
|
+
### `skip` - Third-Party DOM
|
|
226
251
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
252
|
+
```javascript
|
|
253
|
+
function* Chart(args) {
|
|
254
|
+
let chart = null
|
|
231
255
|
|
|
232
|
-
|
|
256
|
+
while (true) yield (
|
|
257
|
+
<div skip ref={el => el && (chart ??= new ChartLib(el, args.data))} />
|
|
258
|
+
)
|
|
233
259
|
}
|
|
234
260
|
```
|
|
235
261
|
|
|
236
|
-
|
|
237
|
-
Throws an error that can be caught by parent error boundaries.
|
|
262
|
+
### `attr:` - Wrapper Attributes
|
|
238
263
|
|
|
239
|
-
|
|
240
|
-
|
|
264
|
+
```javascript
|
|
265
|
+
<Counter
|
|
266
|
+
initial={0} // → args
|
|
267
|
+
attr:id="main" // → wrapper HTML attribute
|
|
268
|
+
attr:class="widget" // → wrapper HTML attribute
|
|
269
|
+
set:onclick={fn} // → wrapper DOM property
|
|
270
|
+
/>
|
|
271
|
+
```
|
|
241
272
|
|
|
242
|
-
|
|
273
|
+
## Context API
|
|
243
274
|
|
|
244
|
-
|
|
245
|
-
Creates a context for sharing data across component trees.
|
|
275
|
+
Share data across component trees without prop drilling:
|
|
246
276
|
|
|
247
277
|
```javascript
|
|
248
278
|
import { context } from 'ajo/context'
|
|
249
279
|
|
|
250
280
|
const ThemeContext = context('light')
|
|
251
|
-
|
|
252
|
-
// Set value
|
|
253
|
-
ThemeContext('dark')
|
|
254
|
-
|
|
255
|
-
// Get value
|
|
256
|
-
const theme = ThemeContext() // 'dark'
|
|
257
281
|
```
|
|
258
282
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
#### `render(children: Children): string`
|
|
262
|
-
Renders JSX to an HTML string for static site generation.
|
|
283
|
+
**Stateless**: read only.
|
|
284
|
+
**Stateful**: read/write. Write inside the loop to update each render, or outside for a one-time set.
|
|
263
285
|
|
|
264
286
|
```javascript
|
|
265
|
-
|
|
287
|
+
// Stateless - read only
|
|
288
|
+
const Card = ({ title }) => {
|
|
289
|
+
const theme = ThemeContext()
|
|
290
|
+
return <div class={`card theme-${theme}`}>{title}</div>
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Stateful - write inside loop (updates each render)
|
|
294
|
+
function* ThemeProvider(args) {
|
|
295
|
+
let theme = 'light'
|
|
266
296
|
|
|
267
|
-
|
|
297
|
+
while (true) {
|
|
298
|
+
ThemeContext(theme)
|
|
299
|
+
yield (
|
|
300
|
+
<>
|
|
301
|
+
<button set:onclick={() => this.next(() => theme = theme === 'light' ? 'dark' : 'light')}>
|
|
302
|
+
{theme}
|
|
303
|
+
</button>
|
|
304
|
+
{args.children}
|
|
305
|
+
</>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Stateful - write outside loop (one-time set)
|
|
311
|
+
function* FixedTheme(args) {
|
|
312
|
+
ThemeContext('dark') // set once at mount
|
|
313
|
+
while (true) yield args.children
|
|
314
|
+
}
|
|
268
315
|
```
|
|
269
316
|
|
|
270
|
-
|
|
271
|
-
|
|
317
|
+
## Async Operations
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
function* UserProfile(args) {
|
|
321
|
+
let data = null, error = null, loading = true
|
|
272
322
|
|
|
273
|
-
|
|
323
|
+
fetch(`/api/users/${args.id}`)
|
|
324
|
+
.then(r => r.json())
|
|
325
|
+
.then(d => this.next(() => { data = d; loading = false }))
|
|
326
|
+
.catch(e => this.next(() => { error = e; loading = false }))
|
|
274
327
|
|
|
275
|
-
|
|
276
|
-
|
|
328
|
+
while (true) {
|
|
329
|
+
if (loading) yield <p>Loading...</p>
|
|
330
|
+
else if (error) yield <p>Error: {error.message}</p>
|
|
331
|
+
else yield <h1>{data.name}</h1>
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Server-Side Rendering
|
|
277
337
|
|
|
278
338
|
```javascript
|
|
339
|
+
// Static
|
|
340
|
+
import { render } from 'ajo/html'
|
|
341
|
+
const html = render(<App />)
|
|
342
|
+
|
|
343
|
+
// Streaming
|
|
279
344
|
import { stream } from 'ajo/stream'
|
|
345
|
+
for await (const chunk of stream(<App />)) res.write(chunk)
|
|
280
346
|
|
|
281
|
-
|
|
347
|
+
// Hydration (client-side)
|
|
348
|
+
import { hydrate } from 'ajo/stream'
|
|
349
|
+
window.$stream = { push: hydrate }
|
|
282
350
|
```
|
|
283
351
|
|
|
284
|
-
|
|
285
|
-
Client-side function for applying streamed patches during hydration.
|
|
352
|
+
### Islands Architecture
|
|
286
353
|
|
|
287
354
|
```javascript
|
|
288
|
-
|
|
355
|
+
function* Interactive() {
|
|
356
|
+
let count = 0
|
|
357
|
+
while (true) yield (
|
|
358
|
+
<button set:onclick={() => this.next(() => count++)}>
|
|
359
|
+
{count}
|
|
360
|
+
</button>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
289
363
|
|
|
290
|
-
|
|
364
|
+
Interactive.src = '/islands/interactive.js' // hydrate on client
|
|
365
|
+
|
|
366
|
+
const Page = () => (
|
|
367
|
+
<html>
|
|
368
|
+
<body>
|
|
369
|
+
<p>Static content</p>
|
|
370
|
+
<Interactive fallback={<button>0</button>} />
|
|
371
|
+
</body>
|
|
372
|
+
</html>
|
|
373
|
+
)
|
|
291
374
|
```
|
|
292
375
|
|
|
293
|
-
|
|
376
|
+
## TypeScript
|
|
294
377
|
|
|
295
378
|
```typescript
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
379
|
+
import type { Stateless, Stateful, WithChildren } from 'ajo'
|
|
380
|
+
|
|
381
|
+
// Stateless
|
|
382
|
+
type CardProps = WithChildren<{ title: string }>
|
|
383
|
+
const Card: Stateless<CardProps> = ({ title, children }) => (
|
|
384
|
+
<div class="card"><h3>{title}</h3>{children}</div>
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
// Stateful with custom wrapper element
|
|
388
|
+
type CounterProps = { initial: number; step?: number }
|
|
389
|
+
|
|
390
|
+
const Counter: Stateful<CounterProps, 'section'> = function* (args) {
|
|
391
|
+
let count = args.initial
|
|
392
|
+
|
|
393
|
+
while (true) {
|
|
394
|
+
const { step = 1 } = args
|
|
395
|
+
yield <button set:onclick={() => this.next(() => count += step)}>+{step}</button>
|
|
396
|
+
}
|
|
303
397
|
}
|
|
304
398
|
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// Element types
|
|
313
|
-
type Children = unknown
|
|
314
|
-
type VNode<Type, Props> = Props & {
|
|
315
|
-
nodeName: Type
|
|
316
|
-
children?: Children
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
// Special attributes
|
|
320
|
-
type SpecialAttributes = {
|
|
321
|
-
key?: unknown
|
|
322
|
-
ref?: (element: Element | null) => void
|
|
323
|
-
memo?: unknown[]
|
|
324
|
-
skip?: boolean
|
|
325
|
-
};
|
|
399
|
+
Counter.is = 'section' // wrapper element (default: 'div')
|
|
400
|
+
Counter.attrs = { class: 'counter' } // default wrapper attributes
|
|
401
|
+
Counter.args = { step: 1 } // default args
|
|
402
|
+
|
|
403
|
+
// Ref typing
|
|
404
|
+
let ref: ThisParameterType<typeof Counter> | null = null
|
|
405
|
+
<Counter ref={el => ref = el} initial={0} />
|
|
326
406
|
```
|
|
327
407
|
|
|
328
|
-
##
|
|
408
|
+
## API Reference
|
|
409
|
+
|
|
410
|
+
### `ajo`
|
|
411
|
+
| Export | Description |
|
|
412
|
+
|--------|-------------|
|
|
413
|
+
| `render(children, container, start?, end?)` | Render to DOM. Optional `start`/`end` for targeted updates. |
|
|
414
|
+
| `h`, `Fragment` | JSX factory and fragment |
|
|
415
|
+
|
|
416
|
+
### `ajo/context`
|
|
417
|
+
| Export | Description |
|
|
418
|
+
|--------|-------------|
|
|
419
|
+
| `context<T>(fallback?)` | Create context. Call with value to write, without to read. |
|
|
420
|
+
|
|
421
|
+
### `ajo/html`
|
|
422
|
+
| Export | Description |
|
|
423
|
+
|--------|-------------|
|
|
424
|
+
| `render(children)` | Render to HTML string |
|
|
425
|
+
|
|
426
|
+
### `ajo/stream`
|
|
427
|
+
| Export | Description |
|
|
428
|
+
|--------|-------------|
|
|
429
|
+
| `stream(children)` | Async iterator for streaming SSR |
|
|
430
|
+
| `hydrate(patch)` | Apply streamed patch on client |
|
|
431
|
+
|
|
432
|
+
### Stateful `this`
|
|
433
|
+
| Method | Description |
|
|
434
|
+
|--------|-------------|
|
|
435
|
+
| `this.next(fn?)` | Re-render. Callback receives current args. |
|
|
436
|
+
| `this.throw(error)` | Throw to parent boundary |
|
|
437
|
+
| `this.return()` | Terminate generator |
|
|
438
|
+
|
|
439
|
+
`this` is also the wrapper element (`this.addEventListener()`, etc).
|
|
440
|
+
|
|
441
|
+
## For AI Assistants
|
|
329
442
|
|
|
330
|
-
|
|
443
|
+
See [LLMs.md](./LLMs.md) for a condensed reference.
|
|
331
444
|
|
|
332
445
|
## License
|
|
333
446
|
|
|
334
|
-
ISC © [Cristian Falcone](cristianfalcone.com)
|
|
447
|
+
ISC © [Cristian Falcone](https://cristianfalcone.com)
|
package/types.ts
CHANGED
|
@@ -2,13 +2,13 @@ declare module 'ajo' {
|
|
|
2
2
|
|
|
3
3
|
type Tag = keyof (HTMLElementTagNameMap & SVGElementTagNameMap)
|
|
4
4
|
|
|
5
|
-
type Type = Tag | Stateless | Stateful
|
|
5
|
+
type Type<TArguments extends Args = {}, TTag extends Tag = 'div'> = Tag | Stateless<TArguments> | Stateful<TArguments, TTag>
|
|
6
6
|
|
|
7
|
-
type Component<
|
|
7
|
+
type Component<TArguments extends Args = {}> = Stateless<TArguments> | Stateful<TArguments>
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type Args = Record<string, unknown>
|
|
10
10
|
|
|
11
|
-
type VNode<TTag extends Type,
|
|
11
|
+
type VNode<TTag extends Type, TArgs extends Args> = TArgs & {
|
|
12
12
|
nodeName: TTag,
|
|
13
13
|
children?: Children,
|
|
14
14
|
}
|
|
@@ -21,7 +21,7 @@ declare module 'ajo' {
|
|
|
21
21
|
? SVGElementTagNameMap[TTag]
|
|
22
22
|
: never
|
|
23
23
|
|
|
24
|
-
type
|
|
24
|
+
type SpecialAttrs<TElement> = {
|
|
25
25
|
key: unknown,
|
|
26
26
|
skip: boolean,
|
|
27
27
|
memo: unknown,
|
|
@@ -36,36 +36,47 @@ declare module 'ajo' {
|
|
|
36
36
|
[key: `attr:${string}`]: unknown
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
type Stateless<TArguments extends
|
|
39
|
+
type Stateless<TArguments extends Args = {}> = (args: TArguments) => Children
|
|
40
40
|
|
|
41
|
-
type Stateful<TArguments extends
|
|
42
|
-
(this: StatefulElement<TArguments, TTag>, args:
|
|
43
|
-
} & (TTag extends 'div' ? { is?: TTag } : { is: TTag }) & {
|
|
41
|
+
type Stateful<TArguments extends Args = {}, TTag extends Tag = 'div'> = {
|
|
42
|
+
(this: StatefulElement<TArguments, TTag>, args: StatefulArgs<TArguments, TTag>): Iterator<Children>
|
|
43
|
+
} & (TTag extends 'div' ? { is?: TTag } : { is: TTag }) & {
|
|
44
|
+
attrs?: Partial<PropSetter<TTag>> & Args,
|
|
45
|
+
args?: Partial<TArguments>,
|
|
46
|
+
src?: string,
|
|
47
|
+
fallback?: Children,
|
|
48
|
+
}
|
|
44
49
|
|
|
45
|
-
type
|
|
46
|
-
Partial<
|
|
50
|
+
type StatefulArgs<TArguments, TTag> =
|
|
51
|
+
Partial<SpecialAttrs<StatefulElement<TArguments, TTag>> & PropSetter<TTag>> &
|
|
47
52
|
AttrSetter &
|
|
48
53
|
TArguments
|
|
49
54
|
|
|
50
55
|
type StatefulElement<TArguments, TTag> = ElementType<TTag> & {
|
|
51
|
-
next: (fn?: (this: StatefulElement<TArguments, TTag>, args:
|
|
56
|
+
next: (fn?: (this: StatefulElement<TArguments, TTag>, args: StatefulArgs<TArguments, TTag>) => void) => void,
|
|
52
57
|
throw: (value?: unknown) => void,
|
|
53
58
|
return: () => void,
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
type IntrinsicElements = {
|
|
57
|
-
[TTag in Tag]: Partial<PropSetter<TTag> &
|
|
62
|
+
[TTag in Tag]: Partial<PropSetter<TTag> & SpecialAttrs<ElementType<TTag>>> & Args
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
type ElementChildrenAttribute = { children: Children }
|
|
61
66
|
|
|
67
|
+
type WithChildren<T extends Args = {}> = T & Partial<ElementChildrenAttribute>
|
|
68
|
+
|
|
62
69
|
function Fragment({ children }: ElementChildrenAttribute): typeof children
|
|
63
|
-
function h(tag: Type,
|
|
64
|
-
function render(h: Children, el:
|
|
70
|
+
function h(tag: Type, attrs?: Args | null, ...children: Children[]): VNode<Type, Args>
|
|
71
|
+
function render(h: Children, el: ParentNode, child?: ChildNode, ref?: ChildNode): void
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
declare module 'ajo/context' {
|
|
68
|
-
function context<T>(fallback?: T):
|
|
75
|
+
function context<T>(fallback?: T): {
|
|
76
|
+
(): T
|
|
77
|
+
<V extends T>(value: V): V
|
|
78
|
+
}
|
|
79
|
+
function current(): import('ajo').StatefulElement<any, any> | null
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
declare module 'ajo/html' {
|