ajo 0.1.32 → 0.1.34

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/LLMs.md CHANGED
@@ -58,7 +58,7 @@ const Counter: Stateful<Args, 'section'> = function* ({ initial }) {
58
58
  <>
59
59
  <input
60
60
  ref={el => inputRef = el}
61
- value={count}
61
+ set:value={count}
62
62
  set:oninput={e => this.next(() => count = +(e.target as HTMLInputElement).value)}
63
63
  />
64
64
  <button set:onclick={inc}>+{step}</button>
@@ -117,9 +117,10 @@ ref?.next() // trigger re-render from outside
117
117
  | **Re-render** | `this.next(fn?)` — returns `fn`'s result. Safe after unmount (no-op). Use `this.throw(e)` for explicit error routing |
118
118
  | **Context** | `context<T>(fallback)` creates context. Stateless: read only. Stateful: read/write. Reads go inside loop (fresh each render). Constant writes can go before loop; dynamic writes go inside loop |
119
119
  | **Lists** | Always provide unique `key` on elements |
120
+ | **SVG** | Set `xmlns="http://www.w3.org/2000/svg"` on the root `<svg>` element — children inherit the namespace |
120
121
  | **Refs** | `ref={el => ...}` on elements. Receives `null` on unmount. Stateful ref type: `ThisParameterType<typeof Component>` |
121
122
  | **Memo** | `memo={[deps]}` array, `memo={value}` single, or just `memo` (never re-render). Skips subtree if unchanged |
122
- | **Skip** | `skip` excludes children from reconciliation. Use for `set:textContent`/`set:innerHTML` or third-party managed DOM |
123
+ | **Skip** | `skip` excludes children from reconciliation; it does not sanitize `set:innerHTML` |
123
124
  | **Custom wrapper** | `stateful(fn, 'tagname')` sets `.is` and infers `this` type. Or manually: `Stateful<Args, 'tagname'>` + `.is = 'tagname'`. Default is `div` (no need to set) |
124
125
  | **Default attrs** | `.attrs = { class: '...' }` on stateful component generator function |
125
126
  | **Default args** | `.args = { prop: value }` on stateful component generator function |
@@ -224,7 +225,7 @@ counterRef?.next() // trigger re-render from outside
224
225
  <input type="checkbox" set:checked={bool} /> // DOM property: syncs with state
225
226
  <video set:currentTime={0} set:muted /> // DOM properties
226
227
  <div set:textContent={str} skip /> // DOM property + skip (required!)
227
- <div set:innerHTML={html} skip /> // DOM property + skip (required!)
228
+ <div set:innerHTML={trustedHtml} skip /> // trusted/sanitized HTML + skip
228
229
 
229
230
  // Post-render work (DOM is updated by the time the microtask runs)
230
231
  const ScrollList: Stateful<{ items: Item[] }> = function* () {
@@ -261,7 +262,7 @@ className="..."
261
262
  onClick={...}
262
263
  useState, useEffect, useCallback
263
264
 
264
- // ❌ class/style as object or array: no special handling in Ajo
265
+ // ❌ class/style as object or array: no special handling in Ajo (compile error)
265
266
  <div class={{ active: isActive }} /> // won't work
266
267
  <div class={['btn', 'primary']} /> // won't work
267
268
  <div style={{ color: 'red' }} /> // won't work
@@ -321,9 +322,12 @@ const inc = () => count++ // won't re-render
321
322
  // ❌ set:textContent/innerHTML without skip: content gets cleared
322
323
  <div set:innerHTML={html} />
323
324
 
325
+ // ❌ skip is not sanitization
326
+ <div set:innerHTML={userHtml} skip />
327
+
324
328
  // ✅ Correct
325
329
  const inc = () => this.next(() => count++)
326
- <div set:innerHTML={html} skip />
330
+ <div set:innerHTML={trustedHtml} skip />
327
331
  ```
328
332
 
329
333
  ## Setup
@@ -334,10 +338,17 @@ pnpm add ajo
334
338
  yarn add ajo
335
339
  ```
336
340
 
337
- Configure JSX automatic runtime (Vite example):
341
+ Configure the JSX automatic runtime in the build tool:
338
342
 
339
343
  ```ts
340
- // vite.config.ts
344
+ // vite.config.ts (Vite 8+, rolldown/oxc)
345
+ export default defineConfig({
346
+ oxc: {
347
+ jsx: { importSource: 'ajo' },
348
+ },
349
+ })
350
+
351
+ // vite.config.ts (Vite 7 and below, esbuild)
341
352
  export default defineConfig({
342
353
  esbuild: {
343
354
  jsx: 'automatic',
@@ -346,4 +357,30 @@ export default defineConfig({
346
357
  })
347
358
  ```
348
359
 
349
- For other build systems: `jsx: 'react-jsx'` (or `'automatic'`), `jsxImportSource: 'ajo'`. No manual imports needed — the build tool auto-imports from `ajo/jsx-runtime`.
360
+ And in tsconfig, so JSX types resolve in the editor:
361
+
362
+ ```jsonc
363
+ // tsconfig.json
364
+ {
365
+ "compilerOptions": {
366
+ "jsx": "react-jsx",
367
+ "jsxImportSource": "ajo"
368
+ }
369
+ }
370
+ ```
371
+
372
+ For other build systems: `jsx: 'react-jsx'` (or `'automatic'`), `jsxImportSource: 'ajo'`. No manual imports needed — the build tool auto-imports from `ajo/jsx-runtime`.
373
+
374
+ Mount and server-render:
375
+
376
+ ```tsx
377
+ import { render } from 'ajo'
378
+
379
+ render(<App />, document.getElementById('root')!)
380
+ // render(h, el, child?, ref?) — optional child/ref bound the reconciled
381
+ // range to [child, ref) for hydration or partial updates inside el
382
+
383
+ import { render as toString } from 'ajo/html'
384
+
385
+ const html = toString(<App />) // SSR: stateful components run one iteration, then finalize
386
+ ```
package/context.js CHANGED
@@ -1,18 +1,18 @@
1
- export const Context = Symbol.for('ajo.context')
2
-
3
- export const context = (fallback, key = Symbol()) => function (...args) {
4
-
5
- const self = this ?? component
6
-
7
- return self
8
- ? args.length
9
- ? self[Context][key] = args[0]
10
- : key in self[Context]
11
- ? self[Context][key]
12
- : fallback
13
- : fallback
14
- }
15
-
16
- let component = null
17
-
18
- export const current = (...args) => args.length ? (component = args[0]) : component
1
+ export const Context = Symbol.for('ajo.context')
2
+
3
+ export const context = (fallback, key = Symbol()) => function (...args) {
4
+
5
+ const self = this ?? component
6
+
7
+ return self
8
+ ? args.length
9
+ ? self[Context][key] = args[0]
10
+ : key in self[Context]
11
+ ? self[Context][key]
12
+ : fallback
13
+ : fallback
14
+ }
15
+
16
+ let component = null
17
+
18
+ export const current = (...args) => args.length ? (component = args[0]) : component
package/dist/context.js CHANGED
@@ -1,11 +1 @@
1
- const e = /* @__PURE__ */ Symbol.for("ajo.context"), r = (t, o = /* @__PURE__ */ Symbol()) => function(...l) {
2
- const n = this ?? c;
3
- return n ? l.length ? n[e][o] = l[0] : o in n[e] ? n[e][o] : t : t;
4
- };
5
- let c = null;
6
- const s = (...t) => t.length ? c = t[0] : c;
7
- export {
8
- e as Context,
9
- r as context,
10
- s as current
11
- };
1
+ var t=Symbol.for("ajo.context"),n=(n,o=Symbol())=>function(...l){let r=this??e;return r?l.length?r[t][o]=l[0]:o in r[t]?r[t][o]:n:n},e=null,o=(...t)=>t.length?e=t[0]:e;export{t as Context,n as context,o as current};
package/dist/html.js CHANGED
@@ -1,55 +1 @@
1
- import { Context as d, current as c } from "./context.js";
2
- const w = /* @__PURE__ */ new Set(["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]), p = /* @__PURE__ */ Symbol.for("ajo.args"), i = (t) => t.replace(/[&<>"']/g, (e) => `&#${e.charCodeAt(0)};`), m = () => {
3
- }, S = { tag: "div" }, C = (t) => {
4
- let e = "";
5
- return f(t, (n) => e += n), e;
6
- }, f = (t, e) => {
7
- if (t == null) return;
8
- const n = typeof t;
9
- if (n != "boolean")
10
- if (n == "string") e(i(t));
11
- else if (n == "number" || n == "bigint") e(i(String(t)));
12
- else if (Symbol.iterator in t) for (t of t) f(t, e);
13
- else "nodeName" in t ? typeof t.nodeName == "function" ? v(t, e) : g(t, e) : e(i(String(t)));
14
- }, g = (t, e) => {
15
- const { nodeName: n, children: s } = t;
16
- let l = "";
17
- for (const o in t)
18
- o == "nodeName" || o == "children" || o == "key" || o == "skip" || o == "memo" || o == "ref" || o.startsWith("set:") || t[o] == null || t[o] === !1 || (t[o] === !0 ? l += ` ${o}` : l += ` ${o}="${i(String(t[o]))}"`);
19
- w.has(n) ? e(`<${n}${l}>`) : (e(`<${n}${l}>`), s != null && f(s, e), e(`</${n}>`));
20
- }, v = ({ nodeName: t, fallback: e = t.fallback, ...n }, s) => {
21
- t.constructor.name == "GeneratorFunction" ? x(t, n, s) : f(t(n), s);
22
- }, x = (t, e, n) => {
23
- const s = { ...t.attrs }, l = { ...t.args };
24
- for (const r in e)
25
- r.startsWith("attr:") ? s[r.slice(5)] = e[r] : r == "key" || r == "skip" || r == "memo" || r == "ref" || r.startsWith("set:") ? s[r] = e[r] : l[r] = e[r];
26
- const o = new AbortController(), b = {
27
- *[Symbol.iterator]() {
28
- for (; ; ) yield this[p];
29
- },
30
- [d]: Object.create(c()?.[d] ?? null),
31
- [p]: l,
32
- signal: o.signal,
33
- next: m,
34
- return: m,
35
- throw: (r) => {
36
- throw r;
37
- }
38
- }, u = t.call(b, l), k = c();
39
- c(b);
40
- const y = (r) => ({ ...s, nodeName: t.is ?? S.tag, children: r });
41
- let a = "";
42
- try {
43
- g(y(u.next().value), (r) => a += r);
44
- } catch (r) {
45
- a = "", g(y(u.throw(r).value), ($) => a += $);
46
- } finally {
47
- u.return(), o.abort(), c(k);
48
- }
49
- n(a);
50
- };
51
- export {
52
- S as defaults,
53
- f as html,
54
- C as render
55
- };
1
+ import{isVNode as t}from"./jsx.js";import{Context as n,current as e}from"./context.js";var{keys:o}=Object,r=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),a=Symbol.for("ajo.args"),l=e=>e.replace(/[&<>"']/g,e=>`&#${e.charCodeAt(0)};`),s=/^[A-Za-z][\w:.-]*$/,i=/^[^\s"'/>=\0-\x1F\x7F]+$/,m=()=>{},c={tag:"div"},f=e=>{let t="";return u(e,e=>t+=e),t},u=(e,r)=>{if(null==e)return;let a=typeof e;if("boolean"!=a)if("string"==a)r(l(e));else if("number"==a||"bigint"==a)r(l(String(e)));else if(t(e))"function"==typeof e.nodeName?g(e,r):d(e,r);else if(Symbol.iterator in Object(e))for(e of e)u(e,r);else r(l(String(e)))},d=(e,t)=>{let{nodeName:a,children:n}=e;a="string"==typeof a&&s.test(a)?a:c.tag;let m="";for(let t of o(e))"nodeName"==t||"children"==t||"key"==t||"skip"==t||"memo"==t||"ref"==t||t.startsWith("set:")||!i.test(t)||null==e[t]||!1===e[t]||(!0===e[t]?m+=` ${t}`:m+=` ${t}="${l(String(e[t]))}"`);t(`<${a}${m}>`),r.has(a)||(null!=n&&u(n,t),t(`</${a}>`))},g=({nodeName:e,...t},r)=>{"GeneratorFunction"==e.constructor.name?h(e,t,r):u(e(t),r)},h=(t,r,l)=>{let s={...t.attrs},i={...t.args};for(let e of o(r))e.startsWith("attr:")?s[e.slice(5)]=r[e]:"key"==e||"skip"==e||"memo"==e||"ref"==e||e.startsWith("set:")?s[e]=r[e]:i[e]=r[e];s.nodeName=t.is??c.tag;let f=new AbortController,u={*[Symbol.iterator](){for(;;)yield this[a]},[n]:Object.create(e()?.[n]??null),[a]:i,signal:f.signal,next:m,return:m,throw:e=>{throw e}},g=t.call(u,i),h=e();e(u);let b="",y=e=>b+=e;try{s.children=g.next().value,d(s,y)}catch(t){b="",s.children=g.throw(t).value,d(s,y)}finally{g.return(),f.abort(),e(h)}l(b)};export{c as defaults,u as html,f as render};
package/dist/index.js CHANGED
@@ -1,110 +1 @@
1
- import { Context as x, current as c } from "./context.js";
2
- const { isArray: h } = Array, { assign: N, create: A } = Object, l = /* @__PURE__ */ Symbol.for("ajo.key"), b = /* @__PURE__ */ Symbol.for("ajo.keyed"), C = /* @__PURE__ */ Symbol.for("ajo.memo"), u = /* @__PURE__ */ Symbol.for("ajo.cache"), a = /* @__PURE__ */ Symbol.for("ajo.generator"), o = /* @__PURE__ */ Symbol.for("ajo.iterator"), g = /* @__PURE__ */ Symbol.for("ajo.render"), f = /* @__PURE__ */ Symbol.for("ajo.args"), E = /* @__PURE__ */ Symbol.for("ajo.controller"), v = { tag: "div" }, q = (r, t) => (t && (r.is = t), r), S = (r, t, e = t.firstChild, i = null) => {
3
- for (p(r, (n) => {
4
- const s = typeof n == "string" ? W(n, e) : B(n, t, e);
5
- e == null ? y(t, s, i) : s == e ? e = s.nextSibling : s == e.nextSibling ? (y(t, e, i), e = s.nextSibling) : y(t, s, e);
6
- }); e != i; ) {
7
- const n = e.nextSibling;
8
- e.nodeType == 1 && R(e), t.removeChild(e), e = n;
9
- }
10
- }, p = (r, t) => {
11
- if (r == null) return;
12
- const e = typeof r;
13
- if (e != "boolean")
14
- if (e == "string") t(r);
15
- else if (e == "number" || e == "bigint") t(String(r));
16
- else if (Symbol.iterator in r) for (r of r) p(r, t);
17
- else "nodeName" in r ? typeof r.nodeName == "function" ? G(r, t) : t(r) : t(String(r));
18
- }, G = ({ nodeName: r, ...t }, e) => {
19
- r.constructor.name == "GeneratorFunction" ? e(T(r, t)) : p(r(t), e);
20
- }, T = (r, t) => {
21
- const e = { ...r.attrs }, i = { ...r.args };
22
- for (const n in t)
23
- n.startsWith("attr:") ? e[n.slice(5)] = t[n] : n == "key" || n == "skip" || n == "memo" || n == "ref" || n.startsWith("set:") ? e[n] = t[n] : i[n] = t[n];
24
- return { ...e, nodeName: r.is ?? v.tag, [a]: r, [f]: i };
25
- }, W = (r, t) => {
26
- for (; t && t.nodeType != 3; ) t = t.nextSibling;
27
- return t ? t.data != r && (t.data = r) : t = document.createTextNode(r), t;
28
- }, B = (r, t, e) => {
29
- const { nodeName: i, children: n, key: s, skip: k, memo: w, [a]: m, [f]: j } = r;
30
- for (s != null && (e = (t[b] ??= /* @__PURE__ */ new Map()).get(s) ?? e); e && (e.localName != i || e[l] != null && e[l] != s || e[a] && e[a] != m); ) e = e[l] != null ? null : e.nextElementSibling;
31
- return e ??= document.createElementNS(r.xmlns ?? t.namespaceURI, i), s != null && t[b].set(e[l] = s, e), (w == null || K(e[C], e[C] = w)) && (I(e[u], e[u] = r, e), k || (m ? F(m, j, e) : S(n, e))), e;
32
- }, I = (r, t, e) => {
33
- for (const i in { ...r, ...t })
34
- i == "nodeName" || i == "children" || i == "key" || i == "skip" || i == "memo" || r?.[i] === t[i] || (i == "ref" && typeof t[i] == "function" ? t[i](e) : i.startsWith("set:") ? e[i.slice(4)] = t[i] : t[i] == null || t[i] === !1 ? e.removeAttribute(i) : e.setAttribute(i, t[i] === !0 ? "" : t[i]));
35
- }, K = (r, t) => h(r) && h(t) ? r.some((e, i) => e !== t[i]) : r !== t, M = (r, t) => {
36
- let e = t.firstElementChild;
37
- for (; e; )
38
- if (r(e) && e.firstElementChild) e = e.firstElementChild;
39
- else {
40
- for (; e != t && !e.nextElementSibling; ) e = e.parentNode ?? t;
41
- e = e != t && e.nextElementSibling;
42
- }
43
- }, y = (r, t, e) => {
44
- if (t.isConnected && t.contains(document.activeElement)) {
45
- const i = t.nextSibling;
46
- for (; e && e != t; ) {
47
- const n = e.nextSibling;
48
- r.insertBefore(e, i), e = n;
49
- }
50
- } else r.insertBefore(t, e);
51
- }, R = (r) => {
52
- let t = r;
53
- for (; t.firstElementChild; ) t = t.firstElementChild;
54
- for (; ; ) {
55
- const { nextElementSibling: e, parentNode: i } = t;
56
- if (t[l] != null && i?.[b]?.delete(t[l]), typeof t.return == "function" && t.return(!1), typeof t[u]?.ref == "function" && t[u].ref(null), t === r) break;
57
- if (t = e ?? i ?? r, e) for (; t.firstElementChild; ) t = t.firstElementChild;
58
- }
59
- }, F = (r, t, e) => {
60
- e[a] ??= (N(e, O)[x] = A(c()?.[x] ?? null), r), e[f] = t, e[g]();
61
- }, O = {
62
- *[Symbol.iterator]() {
63
- for (; ; ) yield this[f];
64
- },
65
- [g]() {
66
- const r = c();
67
- c(this);
68
- try {
69
- this[o] || (this.signal = (this[E] = new AbortController()).signal, this[o] = this[a].call(this, this[f]));
70
- const { value: t, done: e } = this[o].next();
71
- S(t, this), e && this.return();
72
- } catch (t) {
73
- this.throw(t);
74
- } finally {
75
- c(r);
76
- }
77
- },
78
- next(r, t) {
79
- if (!this.isConnected) return t;
80
- try {
81
- typeof r == "function" && (t = r.call(this, this[f]));
82
- } catch (e) {
83
- return this.throw(e);
84
- }
85
- return c()?.contains(this) || this[g](), t;
86
- },
87
- throw(r) {
88
- for (let t = this; t; t = t.parentNode) if (t[o]?.throw) try {
89
- return S(t[o].throw(r).value, t);
90
- } catch (e) {
91
- r = new Error(e?.message ?? e, { cause: r });
92
- }
93
- throw r;
94
- },
95
- return(r = !0) {
96
- r && M((t) => typeof t.return == "function" ? t.return() : !0, this);
97
- try {
98
- this[o]?.return();
99
- } catch (t) {
100
- this.throw(t);
101
- } finally {
102
- this[o] = null, this[E]?.abort();
103
- }
104
- }
105
- };
106
- export {
107
- v as defaults,
108
- S as render,
109
- q as stateful
110
- };
1
+ import{isVNode as t}from"./jsx.js";import{Context as n,current as e}from"./context.js";var b,{isArray:o}=Array,{assign:r,hasOwn:a,keys:l}=Object,s=Symbol.for("ajo.key"),i=Symbol.for("ajo.keyed"),m=Symbol.for("ajo.memo"),c=Symbol.for("ajo.cache"),f=Symbol.for("ajo.generator"),u=Symbol.for("ajo.iterator"),d=Symbol.for("ajo.render"),g=Symbol.for("ajo.args"),h=Symbol.for("ajo.controller"),y={tag:"div"},p=(e,t)=>(t&&(e.is=t),e),S=(e,t,r=t.firstChild,n=null)=>{if(x(e,e=>{let i="string"==typeof e?E(e,r,n):N(e,t,r,n);null==r?A(t,i,n):i==r?r=i.nextSibling:i==r.nextSibling?(r.contains(document.activeElement)?t.insertBefore(i,r):t.insertBefore(r,n),r=i.nextSibling):A(t,i,r)}),r&&!n&&r==t.firstChild){let e=[...t.children];t[i]?.clear(),t.textContent="";for(let t of e)O(t)}else for(;r&&r!=n;){let e=r.nextSibling;t.removeChild(r),1==r.nodeType&&O(r,t),r=e}},x=(e,r)=>{let n=typeof e;if(null!=e&&"boolean"!=n)if("string"==n)r(e);else if("number"==n||"bigint"==n)r(String(e));else if(t(e))"function"==typeof e.nodeName?C(e,r):r(e);else if(Symbol.iterator in Object(e))for(e of e)x(e,r);else r(String(e))},C=({nodeName:e,...t},r)=>{"GeneratorFunction"==e.constructor.name?r(j(e,t)):x(e(t),r)},j=(e,t)=>{let r={...e.attrs},n={...e.args};for(let e of l(t))e.startsWith("attr:")?r[e.slice(5)]=t[e]:"key"==e||"skip"==e||"memo"==e||"ref"==e||e.startsWith("set:")?r[e]=t[e]:n[e]=t[e];return r.nodeName=e.is??y.tag,r[f]=e,r[g]=n,r},E=(e,t,r)=>{for(;t&&t!=r&&3!=t.nodeType;)t=t.nextSibling;return null==t||t==r?t=document.createTextNode(e):t.data!=e&&(t.data=e),t},N=(e,t,r,n)=>{let l=e.nodeName,o=e.key,a=e[f];for(r&&null!=o&&null==n&&(r=t[i]?.get(o)??r);r&&r!=n;){let e=r[s],t=r[f];if(!(r.localName!=l||null!=e&&e!=o||t&&t!=a))break;r=null==e?r.nextElementSibling:null}return(null==r||r==n)&&(r=document.createElementNS(e.xmlns??t.namespaceURI,l)),null!=o&&(t[i]??=new Map).set(r[s]=o,r),(!r[c]||null==e.memo||w(r[m],e.memo))&&(k(r[c],e,r),e.skip||(a?B(a,e[g],r):r[c]||"string"!=typeof e.children&&"number"!=typeof e.children?S(e.children,r):r.textContent=e.children+""),r[m]=e.memo,r[c]=e),r},v=e=>"nodeName"==e||"children"==e||"key"==e||"skip"==e||"memo"==e,k=(e,t,r)=>{if(!e)for(let e=r.attributes.length;e--;){let{name:n}=r.attributes[e];a(t,n)||r.removeAttribute(n)}for(let n in t)if(a(t,n)){let i=t[n];if(e&&e[n]===i||v(n))continue;"ref"==n&&"function"==typeof i?i(r):n.startsWith("set:")?r[n.slice(4)]=i:null==i||!1===i?r.removeAttribute(n):"class"==n&&"string"==typeof r.className?r.className=!0===i?"":i:r.setAttribute(n,!0===i?"":i)}if(e)for(let n in e)if(a(e,n)){if(a(t,n)||v(n))continue;n.startsWith("set:")?r[n.slice(4)]=void 0:r.removeAttribute(n)}},w=(e,t)=>{if(o(e)&&o(t)){if(e.length!=t.length)return!0;for(let r=e.length;r--;)if(e[r]!==t[r])return!0;return!1}return e!==t},A=(e,t,r)=>{if(t.isConnected&&t.contains(document.activeElement)){let n=t.nextSibling;for(;r&&r!=t;){let t=r.nextSibling;e.insertBefore(r,n),r=t}}else e.insertBefore(t,r)},O=(e,t)=>{if(e[m]==O)return;e[m]=O;let r=e;for(;r.firstElementChild;)r=r.firstElementChild;for(;;){let{nextElementSibling:n,parentNode:l}=r;if(null!=r[s]&&(l??t)?.[i]?.delete(r[s]),"function"==typeof r.return&&r.return(!1),"function"==typeof r[c]?.ref&&r[c].ref(null),r==e)break;if(r=n??l??e,n)for(;r.firstElementChild;)r=r.firstElementChild}},B=(t,i,o)=>{o[f]||(T(),r(o,W)[n]=Object.create(e()?.[n]??null),o[f]=t);let s=o[g]??=i;for(let e of l(s))a(i,e)||delete s[e];r(s,i),o[d]()},T=()=>b||(b=new MutationObserver(e=>{for(let t of e)for(let e of t.removedNodes)1==e.nodeType&&!e.isConnected&&O(e,t.target)})).observe(document,{childList:!0,subtree:!0}),W={*[Symbol.iterator](){for(;;)yield this[g]},[d](){let t=e();e(this);try{this[u]||(this.signal=(this[h]=new AbortController).signal,this[u]=this[f].call(this,this[g]));let{value:e,done:t}=this[u].next();S(e,this),t&&this.return()}catch(t){this.throw(t)}finally{e(t)}},next(t,r){if(!1===this[u]||!this.isConnected)return r;try{"function"==typeof t&&(r=t.call(this,this[g]))}catch(t){return this.throw(t)}return e()?.contains(this)||this[d](),r},throw(e){for(let t=this;t;t=t.parentNode)if(t[u]?.throw)try{return S(t[u].throw(e).value,t)}catch(t){e=Error(t?.message??t,{cause:e})}throw e},return(e=!0){let t=this[u];if(!1!==t){this[u]=!1,e&&((e,t)=>{let r=t.firstElementChild;for(;r;)if(e(r)&&r.firstElementChild)r=r.firstElementChild;else{for(;r!=t&&!r.nextElementSibling;)r=r.parentNode??t;r=r!=t&&r.nextElementSibling}})(e=>"function"!=typeof e.return||e.return(),this);try{t?.return()}catch(e){this.throw(e)}finally{this[u]=null,this[h]?.abort()}}}};export{y as defaults,S as render,p as stateful};
package/dist/jsx.js CHANGED
@@ -1,12 +1 @@
1
- const l = (e) => e.children, i = (e, n, ...t) => ((n ??= {}).nodeName = e, !("children" in n) && t.length && (n.children = t.length == 1 ? t[0] : t), n);
2
- function c(e, n, t) {
3
- return t != null && ((n ??= {}).key = t), i(e, n);
4
- }
5
- const s = c, u = c;
6
- export {
7
- l as Fragment,
8
- i as h,
9
- c as jsx,
10
- u as jsxDEV,
11
- s as jsxs
12
- };
1
+ var t=Symbol(),n=a=>(a[t]=a,a),e=a=>a?.[t]===a,o=a=>a.children;function r(a,s,e){return(s??={}).nodeName=a,null!=e&&(s.key=e),n(s)}var a=r,l=r;export{o as Fragment,e as isVNode,r as jsx,l as jsxDEV,a as jsxs,n as mark};
package/html.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { Context, current } from './context.js'
2
+ import { isVNode } from './jsx.js'
3
+
4
+ const { keys } = Object
2
5
 
3
6
  const Void = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
4
7
 
@@ -6,6 +9,8 @@ const Args = Symbol.for('ajo.args')
6
9
 
7
10
  const escape = s => s.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`)
8
11
 
12
+ const Tag = /^[A-Za-z][\w:.-]*$/, Attr = /^[^\s"'/>=\0-\x1F\x7F]+$/
13
+
9
14
  const noop = () => { }
10
15
 
11
16
  export const defaults = { tag: 'div' }
@@ -31,33 +36,33 @@ export const html = (h, emit) => {
31
36
 
32
37
  else if (type == 'number' || type == 'bigint') emit(escape(String(h)))
33
38
 
34
- else if (Symbol.iterator in h) for (h of h) html(h, emit)
39
+ else if (isVNode(h)) typeof h.nodeName == 'function' ? run(h, emit) : element(h, emit)
35
40
 
36
- else if ('nodeName' in h) typeof h.nodeName == 'function' ? run(h, emit) : element(h, emit)
41
+ else if (Symbol.iterator in Object(h)) for (h of h) html(h, emit)
37
42
 
38
43
  else emit(escape(String(h)))
39
44
  }
40
45
 
41
46
  const element = (h, emit) => {
42
47
 
43
- const { nodeName, children } = h
48
+ let { nodeName, children } = h
49
+
50
+ nodeName = typeof nodeName == 'string' && Tag.test(nodeName) ? nodeName : defaults.tag
44
51
 
45
52
  let a = ''
46
53
 
47
- for (const key in h) {
54
+ for (const key of keys(h)) {
48
55
 
49
- if (key == 'nodeName' || key == 'children' || key == 'key' || key == 'skip' || key == 'memo' || key == 'ref' || key.startsWith('set:') || h[key] == null || h[key] === false) continue
56
+ if (key == 'nodeName' || key == 'children' || key == 'key' || key == 'skip' || key == 'memo' || key == 'ref' || key.startsWith('set:') || !Attr.test(key) || h[key] == null || h[key] === false) continue
50
57
 
51
58
  if (h[key] === true) a += ` ${key}`
52
59
 
53
60
  else a += ` ${key}="${escape(String(h[key]))}"`
54
61
  }
55
62
 
56
- if (Void.has(nodeName)) emit(`<${nodeName}${a}>`)
63
+ emit(`<${nodeName}${a}>`)
57
64
 
58
- else {
59
-
60
- emit(`<${nodeName}${a}>`)
65
+ if (!Void.has(nodeName)) {
61
66
 
62
67
  if (children != null) html(children, emit)
63
68
 
@@ -65,7 +70,7 @@ const element = (h, emit) => {
65
70
  }
66
71
  }
67
72
 
68
- const run = ({ nodeName, fallback = nodeName.fallback, ...h }, emit) => {
73
+ const run = ({ nodeName, ...h }, emit) => {
69
74
 
70
75
  if (nodeName.constructor.name == 'GeneratorFunction') runGenerator(nodeName, h, emit)
71
76
 
@@ -76,7 +81,7 @@ const runGenerator = (fn, h, emit) => {
76
81
 
77
82
  const attrs = { ...fn.attrs }, args = { ...fn.args }
78
83
 
79
- for (const key in h) {
84
+ for (const key of keys(h)) {
80
85
 
81
86
  if (key.startsWith('attr:')) attrs[key.slice(5)] = h[key]
82
87
 
@@ -85,6 +90,8 @@ const runGenerator = (fn, h, emit) => {
85
90
  else args[key] = h[key]
86
91
  }
87
92
 
93
+ attrs.nodeName = fn.is ?? defaults.tag
94
+
88
95
  const controller = new AbortController()
89
96
 
90
97
  const instance = {
@@ -110,19 +117,23 @@ const runGenerator = (fn, h, emit) => {
110
117
 
111
118
  current(instance)
112
119
 
113
- const vnode = children => ({ ...attrs, nodeName: fn.is ?? defaults.tag, children })
114
-
115
120
  let out = ''
116
121
 
122
+ const write = chunk => out += chunk
123
+
117
124
  try {
118
125
 
119
- element(vnode(iterator.next().value), chunk => out += chunk)
126
+ attrs.children = iterator.next().value
127
+
128
+ element(attrs, write)
120
129
 
121
130
  } catch (error) {
122
131
 
123
132
  out = ''
124
133
 
125
- element(vnode(iterator.throw(error).value), chunk => out += chunk)
134
+ attrs.children = iterator.throw(error).value
135
+
136
+ element(attrs, write)
126
137
 
127
138
  } finally {
128
139
 
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Context, current } from './context.js'
2
+ import { isVNode } from './jsx.js'
2
3
 
3
- const { isArray } = Array, { assign, create } = Object
4
+ const { isArray } = Array, { assign, hasOwn, keys } = Object
4
5
 
5
6
  const Key = Symbol.for('ajo.key')
6
7
  const Keyed = Symbol.for('ajo.keyed')
@@ -20,7 +21,7 @@ export const render = (h, el, child = el.firstChild, ref = null) => {
20
21
 
21
22
  walk(h, h => {
22
23
 
23
- const node = typeof h == 'string' ? text(h, child) : element(h, el, child)
24
+ const node = typeof h == 'string' ? text(h, child, ref) : element(h, el, child, ref)
24
25
 
25
26
  if (child == null) {
26
27
 
@@ -32,7 +33,9 @@ export const render = (h, el, child = el.firstChild, ref = null) => {
32
33
 
33
34
  } else if (node == child.nextSibling) {
34
35
 
35
- before(el, child, ref)
36
+ if (child.contains(document.activeElement)) el.insertBefore(node, child)
37
+
38
+ else el.insertBefore(child, ref)
36
39
 
37
40
  child = node.nextSibling
38
41
 
@@ -42,33 +45,41 @@ export const render = (h, el, child = el.firstChild, ref = null) => {
42
45
  }
43
46
  })
44
47
 
45
- while (child != ref) {
48
+ if (child && !ref && child == el.firstChild) {
46
49
 
47
- const node = child.nextSibling
50
+ const gone = [...el.children]
51
+
52
+ el[Keyed]?.clear()
53
+
54
+ el.textContent = ''
48
55
 
49
- if (child.nodeType == 1) unref(child)
56
+ for (const node of gone) unref(node)
57
+
58
+ } else while (child && child != ref) {
59
+
60
+ const node = child.nextSibling
50
61
 
51
62
  el.removeChild(child)
52
63
 
64
+ if (child.nodeType == 1) unref(child, el)
65
+
53
66
  child = node
54
67
  }
55
68
  }
56
69
 
57
70
  const walk = (h, fn) => {
58
71
 
59
- if (h == null) return
60
-
61
72
  const type = typeof h
62
73
 
63
- if (type == 'boolean') return
74
+ if (h == null || type == 'boolean') return
64
75
 
65
76
  if (type == 'string') fn(h)
66
77
 
67
78
  else if (type == 'number' || type == 'bigint') fn(String(h))
68
79
 
69
- else if (Symbol.iterator in h) for (h of h) walk(h, fn)
80
+ else if (isVNode(h)) typeof h.nodeName == 'function' ? run(h, fn) : fn(h)
70
81
 
71
- else if ('nodeName' in h) typeof h.nodeName == 'function' ? run(h, fn) : fn(h)
82
+ else if (Symbol.iterator in Object(h)) for (h of h) walk(h, fn)
72
83
 
73
84
  else fn(String(h))
74
85
  }
@@ -84,7 +95,7 @@ const runGenerator = (fn, h) => {
84
95
 
85
96
  const attrs = { ...fn.attrs }, args = { ...fn.args }
86
97
 
87
- for (const key in h) {
98
+ for (const key of keys(h)) {
88
99
 
89
100
  if (key.startsWith('attr:')) attrs[key.slice(5)] = h[key]
90
101
 
@@ -93,65 +104,117 @@ const runGenerator = (fn, h) => {
93
104
  else args[key] = h[key]
94
105
  }
95
106
 
96
- return { ...attrs, nodeName: fn.is ?? defaults.tag, [Generator]: fn, [Args]: args }
107
+ attrs.nodeName = fn.is ?? defaults.tag
108
+
109
+ attrs[Generator] = fn
110
+
111
+ attrs[Args] = args
112
+
113
+ return attrs
97
114
  }
98
115
 
99
- const text = (h, node) => {
116
+ const text = (h, node, ref) => {
117
+
118
+ while (node && node != ref && node.nodeType != 3) node = node.nextSibling
100
119
 
101
- while (node && node.nodeType != 3) node = node.nextSibling
120
+ if (node == null || node == ref) node = document.createTextNode(h)
102
121
 
103
- node ? node.data != h && (node.data = h) : node = document.createTextNode(h)
122
+ else if (node.data != h) node.data = h
104
123
 
105
124
  return node
106
125
  }
107
126
 
108
- const element = (h, el, node) => {
127
+ const element = (h, el, node, ref) => {
109
128
 
110
- const { nodeName, children, key, skip, memo, [Generator]: gen, [Args]: args } = h
129
+ const nodeName = h.nodeName, key = h.key, gen = h[Generator]
111
130
 
112
- if (key != null) node = (el[Keyed] ??= new Map()).get(key) ?? node
131
+ if (node && key != null && ref == null) node = el[Keyed]?.get(key) ?? node
113
132
 
114
- while (node && (
133
+ while (node && node != ref) {
115
134
 
116
- (node.localName != nodeName) ||
135
+ const k = node[Key], g = node[Generator]
117
136
 
118
- (node[Key] != null && node[Key] != key) ||
137
+ if (node.localName == nodeName && (k == null || k == key) && (!g || g == gen)) break
119
138
 
120
- (node[Generator] && node[Generator] != gen)
139
+ node = k != null ? null : node.nextElementSibling
140
+ }
121
141
 
122
- )) node = node[Key] != null ? null : node.nextElementSibling
142
+ if (node == null || node == ref) node = document.createElementNS(h.xmlns ?? el.namespaceURI, nodeName)
123
143
 
124
- node ??= document.createElementNS(h.xmlns ?? el.namespaceURI, nodeName)
144
+ if (key != null) (el[Keyed] ??= new Map()).set(node[Key] = key, node)
125
145
 
126
- if (key != null) el[Keyed].set(node[Key] = key, node)
146
+ if (!node[Cache] || h.memo == null || some(node[Memo], h.memo)) {
127
147
 
128
- if (memo == null || some(node[Memo], node[Memo] = memo)) {
148
+ attrs(node[Cache], h, node)
129
149
 
130
- attrs(node[Cache], node[Cache] = h, node)
150
+ if (!h.skip) {
131
151
 
132
- if (!skip) gen ? next(gen, args, node) : render(children, node)
152
+ if (gen) next(gen, h[Args], node)
153
+
154
+ else if (!node[Cache] && (typeof h.children == 'string' || typeof h.children == 'number')) node.textContent = h.children + ''
155
+
156
+ else render(h.children, node)
157
+ }
158
+
159
+ node[Memo] = h.memo
160
+
161
+ node[Cache] = h
133
162
  }
134
163
 
135
164
  return node
136
165
  }
137
166
 
167
+ const special = key => key == 'nodeName' || key == 'children' || key == 'key' || key == 'skip' || key == 'memo'
168
+
138
169
  const attrs = (cache, h, node) => {
139
170
 
140
- for (const key in { ...cache, ...h }) {
171
+ if (!cache) for (let i = node.attributes.length; i--;) {
141
172
 
142
- if (key == 'nodeName' || key == 'children' || key == 'key' || key == 'skip' || key == 'memo' || cache?.[key] === h[key]) continue
173
+ const { name } = node.attributes[i]
143
174
 
144
- if (key == 'ref' && typeof h[key] == 'function') h[key](node)
175
+ if (!hasOwn(h, name)) node.removeAttribute(name)
176
+ }
145
177
 
146
- else if (key.startsWith('set:')) node[key.slice(4)] = h[key]
178
+ for (const key in h) if (hasOwn(h, key)) {
147
179
 
148
- else if (h[key] == null || h[key] === false) node.removeAttribute(key)
180
+ const value = h[key]
149
181
 
150
- else node.setAttribute(key, h[key] === true ? '' : h[key])
182
+ if (cache && cache[key] === value || special(key)) continue
183
+
184
+ if (key == 'ref' && typeof value == 'function') value(node)
185
+
186
+ else if (key.startsWith('set:')) node[key.slice(4)] = value
187
+
188
+ else if (value == null || value === false) node.removeAttribute(key)
189
+
190
+ else if (key == 'class' && typeof node.className == 'string') node.className = value === true ? '' : value
191
+
192
+ else node.setAttribute(key, value === true ? '' : value)
193
+ }
194
+
195
+ if (cache) for (const key in cache) if (hasOwn(cache, key)) {
196
+
197
+ if (hasOwn(h, key) || special(key)) continue
198
+
199
+ if (key.startsWith('set:')) node[key.slice(4)] = undefined
200
+
201
+ else node.removeAttribute(key)
151
202
  }
152
203
  }
153
204
 
154
- const some = (a, b) => isArray(a) && isArray(b) ? a.some((v, i) => v !== b[i]) : a !== b
205
+ const some = (a, b) => {
206
+
207
+ if (isArray(a) && isArray(b)) {
208
+
209
+ if (a.length != b.length) return true
210
+
211
+ for (let i = a.length; i--;) if (a[i] !== b[i]) return true
212
+
213
+ return false
214
+ }
215
+
216
+ return a !== b
217
+ }
155
218
 
156
219
  const each = (fn, node) => {
157
220
 
@@ -187,7 +250,11 @@ const before = (el, node, child) => {
187
250
  } else el.insertBefore(node, child)
188
251
  }
189
252
 
190
- const unref = root => {
253
+ const unref = (root, parent) => {
254
+
255
+ if (root[Memo] == unref) return
256
+
257
+ root[Memo] = unref
191
258
 
192
259
  let node = root
193
260
 
@@ -197,13 +264,13 @@ const unref = root => {
197
264
 
198
265
  const { nextElementSibling, parentNode } = node
199
266
 
200
- if (node[Key] != null) parentNode?.[Keyed]?.delete(node[Key])
267
+ if (node[Key] != null) (parentNode ?? parent)?.[Keyed]?.delete(node[Key])
201
268
 
202
269
  if (typeof node.return == 'function') node.return(false)
203
270
 
204
271
  if (typeof node[Cache]?.ref == 'function') node[Cache].ref(null)
205
272
 
206
- if (node === root) break
273
+ if (node == root) break
207
274
 
208
275
  node = nextElementSibling ?? parentNode ?? root
209
276
 
@@ -213,13 +280,32 @@ const unref = root => {
213
280
 
214
281
  const next = (fn, args, el) => {
215
282
 
216
- el[Generator] ??= (assign(el, methods)[Context] = create(current()?.[Context] ?? null), fn)
283
+ if (!el[Generator]) {
217
284
 
218
- el[Args] = args
285
+ watch()
286
+
287
+ assign(el, methods)[Context] = Object.create(current()?.[Context] ?? null)
288
+
289
+ el[Generator] = fn
290
+ }
291
+
292
+ const prev = el[Args] ??= args
293
+
294
+ for (const key of keys(prev)) if (!hasOwn(args, key)) delete prev[key]
295
+
296
+ assign(prev, args)
219
297
 
220
298
  el[Render]()
221
299
  }
222
300
 
301
+ let observer
302
+
303
+ const watch = () => observer || (observer = new MutationObserver(records => {
304
+
305
+ for (const record of records) for (const node of record.removedNodes) if (node.nodeType == 1 && !node.isConnected) unref(node, record.target)
306
+
307
+ })).observe(document, { childList: true, subtree: true })
308
+
223
309
  const methods = {
224
310
 
225
311
  *[Symbol.iterator]() { while (true) yield this[Args] },
@@ -257,7 +343,7 @@ const methods = {
257
343
 
258
344
  next(fn, result) {
259
345
 
260
- if (!this.isConnected) return result
346
+ if (this[Iterator] === false || !this.isConnected) return result
261
347
 
262
348
  try {
263
349
 
@@ -289,11 +375,17 @@ const methods = {
289
375
 
290
376
  return(deep = true) {
291
377
 
378
+ const iterator = this[Iterator]
379
+
380
+ if (iterator === false) return
381
+
382
+ this[Iterator] = false
383
+
292
384
  if (deep) each(el => typeof el.return == 'function' ? el.return() : true, this)
293
385
 
294
386
  try {
295
387
 
296
- this[Iterator]?.return()
388
+ iterator?.return()
297
389
 
298
390
  } catch (e) {
299
391
 
package/jsx.js CHANGED
@@ -1,21 +1,20 @@
1
- export const Fragment = props => props.children
2
-
3
- export const h = (type, props, ...children) => {
4
-
5
- (props ??= {}).nodeName = type
6
-
7
- if (!('children' in props) && children.length) props.children = children.length == 1 ? children[0] : children
8
-
9
- return props
10
- }
11
-
12
- export function jsx(type, props, key) {
13
-
14
- if (key != null) (props ??= {}).key = key
15
-
16
- return h(type, props)
17
- }
18
-
19
- export const jsxs = jsx
20
-
21
- export const jsxDEV = jsx
1
+ const VNode = Symbol()
2
+
3
+ export const mark = v => (v[VNode] = v, v)
4
+
5
+ export const isVNode = v => v?.[VNode] === v
6
+
7
+ export const Fragment = props => props.children
8
+
9
+ export function jsx(type, props, key) {
10
+
11
+ (props ??= {}).nodeName = type
12
+
13
+ if (key != null) props.key = key
14
+
15
+ return mark(props)
16
+ }
17
+
18
+ export const jsxs = jsx
19
+
20
+ export const jsxDEV = jsx
package/license CHANGED
@@ -1,15 +1,15 @@
1
- ISC License
2
-
3
- Copyright (c) 2022-2026, Cristian Falcone
4
-
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
1
+ ISC License
2
+
3
+ Copyright (c) 2022-2026, Cristian Falcone
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
+ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/package.json CHANGED
@@ -1,40 +1,34 @@
1
1
  {
2
2
  "name": "ajo",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "ajo is a JavaScript view library for building user interfaces",
5
5
  "type": "module",
6
6
  "types": "./types.ts",
7
7
  "module": "./dist/index.js",
8
- "main": "./dist/index.cjs",
9
8
  "exports": {
10
9
  ".": {
11
10
  "react-native": "./index.js",
12
11
  "types": "./types.ts",
13
- "import": "./dist/index.js",
14
- "require": "./dist/index.cjs"
12
+ "import": "./dist/index.js"
15
13
  },
16
14
  "./context": {
17
15
  "react-native": "./context.js",
18
16
  "types": "./types.ts",
19
- "import": "./dist/context.js",
20
- "require": "./dist/context.cjs"
17
+ "import": "./dist/context.js"
21
18
  },
22
19
  "./html": {
23
20
  "types": "./types.ts",
24
- "import": "./dist/html.js",
25
- "require": "./dist/html.cjs"
21
+ "import": "./dist/html.js"
26
22
  },
27
23
  "./jsx-runtime": {
28
24
  "react-native": "./jsx.js",
29
25
  "types": "./types.ts",
30
- "import": "./dist/jsx.js",
31
- "require": "./dist/jsx.cjs"
26
+ "import": "./dist/jsx.js"
32
27
  },
33
28
  "./jsx-dev-runtime": {
34
29
  "react-native": "./jsx.js",
35
30
  "types": "./types.ts",
36
- "import": "./dist/jsx.js",
37
- "require": "./dist/jsx.cjs"
31
+ "import": "./dist/jsx.js"
38
32
  }
39
33
  },
40
34
  "files": [
@@ -47,12 +41,12 @@
47
41
  "types.ts"
48
42
  ],
49
43
  "devDependencies": {
50
- "@types/node": "25.3.1",
51
- "happy-dom": "20.7.0",
52
- "typescript": "5.9.3",
53
- "vite": "7.3.1",
54
- "vite-tsconfig-paths": "6.1.1",
55
- "vitest": "4.0.18"
44
+ "@types/node": "26.0.0",
45
+ "happy-dom": "20.10.6",
46
+ "terser": "5.48.0",
47
+ "typescript": "6.0.3",
48
+ "vite": "8.0.16",
49
+ "vitest": "4.1.9"
56
50
  },
57
51
  "keywords": [
58
52
  "ui",
@@ -61,13 +55,19 @@
61
55
  "dom",
62
56
  "jsx"
63
57
  ],
64
- "repository": "cristianfalcone/ajo",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "cristianfalcone/ajo"
61
+ },
65
62
  "author": "Cristian Falcone",
66
63
  "license": "ISC",
67
64
  "bugs": "https://github.com/cristianfalcone/ajo/issues",
68
65
  "homepage": "https://github.com/cristianfalcone/ajo#readme",
69
66
  "scripts": {
70
67
  "test": "vitest --run",
68
+ "bench": "pnpm --dir bench bench",
69
+ "bench:quick": "pnpm --dir bench bench:quick",
70
+ "bench:ajo": "pnpm --dir bench bench:ajo",
71
71
  "build": "vite build",
72
72
  "bump": "pnpm version patch && git push && git push --tags",
73
73
  "release": "pnpm test && pnpm build && pnpm bump && pnpm publish"
package/readme.md CHANGED
@@ -226,7 +226,7 @@ function* ErrorBoundary() {
226
226
  | `key` | Unique identifier for list reconciliation |
227
227
  | `ref` | Callback receiving DOM element (or `null` on unmount) |
228
228
  | `memo` | Skip reconciliation: `memo={[deps]}`, `memo={value}`, or `memo` (render once) |
229
- | `skip` | Exclude children from reconciliation (required with `set:innerHTML`) |
229
+ | `skip` | Exclude children from reconciliation; not a sanitizer |
230
230
  | `set:*` | Set DOM properties instead of HTML attributes |
231
231
  | `attr:*` | Force HTML attributes on stateful component wrappers |
232
232
 
@@ -243,8 +243,8 @@ function* ErrorBoundary() {
243
243
  <input type="checkbox" set:checked={bool} />
244
244
  <video set:currentTime={0} set:muted />
245
245
 
246
- // innerHTML requires skip
247
- <div set:innerHTML={html} skip />
246
+ // innerHTML is only for trusted/sanitized HTML; skip preserves assigned children
247
+ <div set:innerHTML={trustedHtml} skip />
248
248
  ```
249
249
 
250
250
  ### `ref` - DOM Access
@@ -254,13 +254,13 @@ function* AutoFocus() {
254
254
 
255
255
  let input = null
256
256
 
257
- while (true) yield (
258
- <>
259
- <input ref={el => el?.focus()} />
260
- <button set:onclick={() => input?.select()}>Select</button>
261
- </>
262
- )
263
- }
257
+ while (true) yield (
258
+ <>
259
+ <input ref={el => { input = el; el?.focus() }} />
260
+ <button set:onclick={() => input?.select()}>Select</button>
261
+ </>
262
+ )
263
+ }
264
264
 
265
265
  // Ref to stateful component includes control methods
266
266
  let timer = null
@@ -424,8 +424,9 @@ let ref: ThisParameterType<typeof Counter> | null = null
424
424
  ### `ajo/html`
425
425
  | Export | Description |
426
426
  |--------|-------------|
427
- | `render(children)` | Render to HTML string. |
428
- | `html(children)` | Render to HTML generator (yields strings). |
427
+ | `render(children)` | Render to HTML string. |
428
+ | `html(children, emit)` | Render to HTML chunks by calling `emit(chunk)`. |
429
+ | `defaults` | Default SSR wrapper tag config (`defaults.tag`). |
429
430
 
430
431
  ### Stateful `this`
431
432
  | Property | Description |
@@ -434,7 +435,7 @@ let ref: ThisParameterType<typeof Counter> | null = null
434
435
  | `this.signal` | AbortSignal that aborts on unmount. Pass to `fetch()`, `addEventListener()`, etc. |
435
436
  | `this.next(fn?)` | Re-render. Callback receives current args. Returns callback's result. |
436
437
  | `this.throw(error)` | Throw to parent boundary. |
437
- | `this.return(deep?)` | Terminate generator. Pass `true` to also terminate child generators. |
438
+ | `this.return(deep?)` | Terminate generator. Deep cleanup is the default; pass `false` to skip child generators. |
438
439
 
439
440
  `this` is also the wrapper element (`this.addEventListener()`, etc).
440
441
 
package/types.ts CHANGED
@@ -2,7 +2,7 @@ declare module 'ajo' {
2
2
 
3
3
  type Tag = keyof (HTMLElementTagNameMap & SVGElementTagNameMap)
4
4
 
5
- type Type = Tag | (string & {}) | Stateless | Stateful
5
+ type Type = Tag | (string & {}) | Stateless<any> | Stateful<any, any>
6
6
 
7
7
  type Children = unknown
8
8
 
@@ -35,6 +35,12 @@ declare module 'ajo' {
35
35
  [key: `attr:${string}`]: unknown
36
36
  }
37
37
 
38
+ type CommonAttrs = {
39
+ class: string | boolean | null,
40
+ style: string | boolean | null,
41
+ xmlns: string,
42
+ }
43
+
38
44
  interface Defaults { }
39
45
 
40
46
  type DefaultTag = Defaults extends { tag: infer T extends string } ? T : 'div'
@@ -46,7 +52,7 @@ declare module 'ajo' {
46
52
  type Stateful<TArgs extends Args = {}, TTag extends string = DefaultTag> = {
47
53
  (this: StatefulElement<TArgs, TTag>, args: TArgs): Iterator<Children>
48
54
  } & (TTag extends DefaultTag ? { is?: TTag } : { is: TTag }) & {
49
- attrs?: Partial<PropSetter<TTag>> & Args,
55
+ attrs?: Partial<PropSetter<TTag> & CommonAttrs> & Args,
50
56
  args?: Partial<TArgs>,
51
57
  }
52
58
 
@@ -60,13 +66,16 @@ declare module 'ajo' {
60
66
  type StatefulElement<TArgs, TTag> = ElementType<TTag> & {
61
67
  [Symbol.iterator](): Iterator<TArgs>,
62
68
  signal: AbortSignal,
63
- next: <R>(fn?: (this: StatefulElement<TArgs, TTag>, args: TArgs) => R) => R,
69
+ next: {
70
+ (): undefined,
71
+ <R>(fn: (this: StatefulElement<TArgs, TTag>, args: TArgs) => R): R,
72
+ },
64
73
  throw: (value?: unknown) => void,
65
74
  return: (deep?: boolean) => void,
66
75
  }
67
76
 
68
77
  type HTMLIntrinsicElements = {
69
- [TTag in Tag]: Partial<PropSetter<TTag> & SpecialAttrs<ElementType<TTag>>> & Args
78
+ [TTag in Tag]: Partial<PropSetter<TTag> & SpecialAttrs<ElementType<TTag>> & CommonAttrs> & Args
70
79
  }
71
80
 
72
81
  interface IntrinsicElements extends HTMLIntrinsicElements { }
@@ -82,7 +91,7 @@ declare module 'ajo' {
82
91
  : P
83
92
  : P
84
93
 
85
- function render(h: Children, el: ParentNode, child?: ChildNode, ref?: ChildNode): void
94
+ function render(h: Children, el: ParentNode, child?: ChildNode | null, ref?: ChildNode | null): void
86
95
 
87
96
  function stateful<TArgs extends Args, TTag extends string = DefaultTag>(
88
97
  fn: (this: StatefulElement<TArgs, TTag>, args: TArgs) => Iterator<Children>,
@@ -105,9 +114,8 @@ declare module 'ajo/html' {
105
114
  }
106
115
 
107
116
  declare module 'ajo/jsx-runtime' {
108
- import { Type, Args, VNode, Children, ElementChildrenAttribute, IntrinsicElements as _IE } from 'ajo'
117
+ import { Type, Args, VNode, ElementChildrenAttribute, IntrinsicElements as _IE } from 'ajo'
109
118
  export function Fragment({ children }: ElementChildrenAttribute): typeof children
110
- export function h(tag: Type, attrs?: Args | null, ...children: Children[]): VNode
111
119
  export function jsx(type: Type, props: Args | null, key?: string | number): VNode
112
120
  export function jsxs(type: Type, props: Args | null, key?: string | number): VNode
113
121
  export function jsxDEV(type: Type, props: Args | null, key?: string | number): VNode
@@ -120,7 +128,7 @@ declare module 'ajo/jsx-runtime' {
120
128
 
121
129
  declare module 'ajo/jsx-dev-runtime' {
122
130
  import { IntrinsicElements as _IE } from 'ajo'
123
- export { Fragment, h, jsx, jsxs, jsxDEV } from 'ajo/jsx-runtime'
131
+ export { Fragment, jsx, jsxs, jsxDEV } from 'ajo/jsx-runtime'
124
132
  export namespace JSX {
125
133
  type ElementChildrenAttribute = import('ajo').ElementChildrenAttribute
126
134
  type LibraryManagedAttributes<C, P> = import('ajo').ManagedAttributes<C, P>
package/dist/context.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=Symbol.for("ajo.context"),r=(t,e=Symbol())=>function(...l){const n=this??c;return n?l.length?n[o][e]=l[0]:e in n[o]?n[o][e]:t:t};let c=null;const u=(...t)=>t.length?c=t[0]:c;exports.Context=o;exports.context=r;exports.current=u;
package/dist/html.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./context.cjs"),$=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),y=Symbol.for("ajo.args"),u=e=>e.replace(/[&<>"']/g,t=>`&#${t.charCodeAt(0)};`),m=()=>{},p={tag:"div"},w=e=>{let t="";return c(e,n=>t+=n),t},c=(e,t)=>{if(e==null)return;const n=typeof e;if(n!="boolean")if(n=="string")t(u(e));else if(n=="number"||n=="bigint")t(u(String(e)));else if(Symbol.iterator in e)for(e of e)c(e,t);else"nodeName"in e?typeof e.nodeName=="function"?v(e,t):g(e,t):t(u(String(e)))},g=(e,t)=>{const{nodeName:n,children:s}=e;let l="";for(const o in e)o=="nodeName"||o=="children"||o=="key"||o=="skip"||o=="memo"||o=="ref"||o.startsWith("set:")||e[o]==null||e[o]===!1||(e[o]===!0?l+=` ${o}`:l+=` ${o}="${u(String(e[o]))}"`);$.has(n)?t(`<${n}${l}>`):(t(`<${n}${l}>`),s!=null&&c(s,t),t(`</${n}>`))},v=({nodeName:e,fallback:t=e.fallback,...n},s)=>{e.constructor.name=="GeneratorFunction"?x(e,n,s):c(e(n),s)},x=(e,t,n)=>{const s={...e.attrs},l={...e.args};for(const r in t)r.startsWith("attr:")?s[r.slice(5)]=t[r]:r=="key"||r=="skip"||r=="memo"||r=="ref"||r.startsWith("set:")?s[r]=t[r]:l[r]=t[r];const o=new AbortController,b={*[Symbol.iterator](){for(;;)yield this[y]},[a.Context]:Object.create(a.current()?.[a.Context]??null),[y]:l,signal:o.signal,next:m,return:m,throw:r=>{throw r}},f=e.call(b,l),k=a.current();a.current(b);const d=r=>({...s,nodeName:e.is??p.tag,children:r});let i="";try{g(d(f.next().value),r=>i+=r)}catch(r){i="",g(d(f.throw(r).value),S=>i+=S)}finally{f.return(),o.abort(),a.current(k)}n(i)};exports.defaults=p;exports.html=c;exports.render=w;
package/dist/index.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("./context.cjs"),{isArray:x}=Array,{assign:N,create:A}=Object,a=Symbol.for("ajo.key"),g=Symbol.for("ajo.keyed"),C=Symbol.for("ajo.memo"),u=Symbol.for("ajo.cache"),f=Symbol.for("ajo.generator"),o=Symbol.for("ajo.iterator"),S=Symbol.for("ajo.render"),c=Symbol.for("ajo.args"),h=Symbol.for("ajo.controller"),E={tag:"div"},v=(r,t)=>(t&&(r.is=t),r),m=(r,t,e=t.firstChild,n=null)=>{for(p(r,i=>{const s=typeof i=="string"?M(i,e):W(i,t,e);e==null?b(t,s,n):s==e?e=s.nextSibling:s==e.nextSibling?(b(t,e,n),e=s.nextSibling):b(t,s,e)});e!=n;){const i=e.nextSibling;e.nodeType==1&&O(e),t.removeChild(e),e=i}},p=(r,t)=>{if(r==null)return;const e=typeof r;if(e!="boolean")if(e=="string")t(r);else if(e=="number"||e=="bigint")t(String(r));else if(Symbol.iterator in r)for(r of r)p(r,t);else"nodeName"in r?typeof r.nodeName=="function"?T(r,t):t(r):t(String(r))},T=({nodeName:r,...t},e)=>{r.constructor.name=="GeneratorFunction"?e(G(r,t)):p(r(t),e)},G=(r,t)=>{const e={...r.attrs},n={...r.args};for(const i in t)i.startsWith("attr:")?e[i.slice(5)]=t[i]:i=="key"||i=="skip"||i=="memo"||i=="ref"||i.startsWith("set:")?e[i]=t[i]:n[i]=t[i];return{...e,nodeName:r.is??E.tag,[f]:r,[c]:n}},M=(r,t)=>{for(;t&&t.nodeType!=3;)t=t.nextSibling;return t?t.data!=r&&(t.data=r):t=document.createTextNode(r),t},W=(r,t,e)=>{const{nodeName:n,children:i,key:s,skip:k,memo:w,[f]:y,[c]:j}=r;for(s!=null&&(e=(t[g]??=new Map).get(s)??e);e&&(e.localName!=n||e[a]!=null&&e[a]!=s||e[f]&&e[f]!=y);)e=e[a]!=null?null:e.nextElementSibling;return e??=document.createElementNS(r.xmlns??t.namespaceURI,n),s!=null&&t[g].set(e[a]=s,e),(w==null||I(e[C],e[C]=w))&&(B(e[u],e[u]=r,e),k||(y?R(y,j,e):m(i,e))),e},B=(r,t,e)=>{for(const n in{...r,...t})n=="nodeName"||n=="children"||n=="key"||n=="skip"||n=="memo"||r?.[n]===t[n]||(n=="ref"&&typeof t[n]=="function"?t[n](e):n.startsWith("set:")?e[n.slice(4)]=t[n]:t[n]==null||t[n]===!1?e.removeAttribute(n):e.setAttribute(n,t[n]===!0?"":t[n]))},I=(r,t)=>x(r)&&x(t)?r.some((e,n)=>e!==t[n]):r!==t,K=(r,t)=>{let e=t.firstElementChild;for(;e;)if(r(e)&&e.firstElementChild)e=e.firstElementChild;else{for(;e!=t&&!e.nextElementSibling;)e=e.parentNode??t;e=e!=t&&e.nextElementSibling}},b=(r,t,e)=>{if(t.isConnected&&t.contains(document.activeElement)){const n=t.nextSibling;for(;e&&e!=t;){const i=e.nextSibling;r.insertBefore(e,n),e=i}}else r.insertBefore(t,e)},O=r=>{let t=r;for(;t.firstElementChild;)t=t.firstElementChild;for(;;){const{nextElementSibling:e,parentNode:n}=t;if(t[a]!=null&&n?.[g]?.delete(t[a]),typeof t.return=="function"&&t.return(!1),typeof t[u]?.ref=="function"&&t[u].ref(null),t===r)break;if(t=e??n??r,e)for(;t.firstElementChild;)t=t.firstElementChild}},R=(r,t,e)=>{e[f]??=(N(e,q)[l.Context]=A(l.current()?.[l.Context]??null),r),e[c]=t,e[S]()},q={*[Symbol.iterator](){for(;;)yield this[c]},[S](){const r=l.current();l.current(this);try{this[o]||(this.signal=(this[h]=new AbortController).signal,this[o]=this[f].call(this,this[c]));const{value:t,done:e}=this[o].next();m(t,this),e&&this.return()}catch(t){this.throw(t)}finally{l.current(r)}},next(r,t){if(!this.isConnected)return t;try{typeof r=="function"&&(t=r.call(this,this[c]))}catch(e){return this.throw(e)}return l.current()?.contains(this)||this[S](),t},throw(r){for(let t=this;t;t=t.parentNode)if(t[o]?.throw)try{return m(t[o].throw(r).value,t)}catch(e){r=new Error(e?.message??e,{cause:r})}throw r},return(r=!0){r&&K(t=>typeof t.return=="function"?t.return():!0,this);try{this[o]?.return()}catch(t){this.throw(t)}finally{this[o]=null,this[h]?.abort()}}};exports.defaults=E;exports.render=m;exports.stateful=v;
package/dist/jsx.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=e=>e.children,c=(e,n,...t)=>((n??={}).nodeName=e,!("children"in n)&&t.length&&(n.children=t.length==1?t[0]:t),n);function s(e,n,t){return t!=null&&((n??={}).key=t),c(e,n)}const j=s,l=s;exports.Fragment=i;exports.h=c;exports.jsx=s;exports.jsxDEV=l;exports.jsxs=j;