ajo 0.1.28 → 0.1.30

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