ajo 0.1.34 → 0.1.35

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
@@ -74,8 +74,8 @@ const Counter: Stateful<Args, 'section'> = function* ({ initial }) {
74
74
  }
75
75
  }
76
76
 
77
- Counter.is = 'section' // wrapper element (default: div)
78
- Counter.attrs = { class: 'counter-wrap' } // default wrapper attributes
77
+ Counter.is = 'section' // host element (default: div)
78
+ Counter.attrs = { class: 'counter-wrap' } // default host attributes
79
79
  Counter.args = { step: 1 } // default args
80
80
 
81
81
  // Or use stateful() to avoid duplicating 'section':
@@ -83,19 +83,19 @@ Counter.args = { step: 1 } // default args
83
83
  // Counter.attrs = { class: 'counter-wrap' }
84
84
  // Counter.args = { step: 1 }
85
85
 
86
- // Example usage - special attrs apply to wrapper, rest goes to args:
86
+ // Example usage - special attrs apply to the host, rest goes to args:
87
87
 
88
88
  let ref: ThisParameterType<typeof Counter> | null = null
89
89
 
90
90
  <Counter
91
91
  initial={0} step={5} // → args
92
- attr:id="main" attr:class="my-counter" // → wrapper attributes (HTML attrs)
93
- set:onclick={fn} // → wrapper properties (DOM props)
94
- key={id} // → wrapper key
95
- memo={[id]} // → wrapper memo (array)
96
- memo={id} // → wrapper memo (single value)
97
- memo // → wrapper memo (render once)
98
- ref={el => ref = el} // → wrapper ref (el is <section> + .next()/.throw()/.return())
92
+ attr:id="main" attr:class="my-counter" // → host attributes (HTML attrs)
93
+ set:onclick={fn} // → host properties (DOM properties)
94
+ key={id} // → host key
95
+ memo={[id]} // → host memo (array)
96
+ memo={id} // → host memo (single value)
97
+ memo // → host memo (render once)
98
+ ref={el => ref = el} // → host ref (el is <section> + .next()/.throw()/.return())
99
99
  />
100
100
 
101
101
  ref?.next() // trigger re-render from outside
@@ -105,15 +105,15 @@ ref?.next() // trigger re-render from outside
105
105
 
106
106
  | Topic | Rule |
107
107
  |-------|------|
108
- | **Elements** | Everything becomes HTML attributes. `set:prop` assigns DOM properties instead (`node[prop] = value`) |
108
+ | **Elements** | Everything becomes HTML attributes. `set:property` assigns DOM properties instead (`node[property] = value`) |
109
109
  | **Stateless** | Everything goes to `args`. Special attrs like `memo` must be applied to elements inside |
110
- | **Stateful** | `key`, `memo`, `skip`, `ref`, `set:*` apply to implicit wrapper element. `attr:*` sets wrapper attributes. Rest goes to `args` |
110
+ | **Stateful** | `key`, `memo`, `skip`, `ref`, `set:*` apply to the host (the implicit wrapper DOM element every stateful component gets). `attr:*` sets host attributes. Rest goes to `args` |
111
111
  | **Events** | `set:onclick`, `set:oninput`, etc. Never `onClick` |
112
112
  | **Classes** | `class`, never `className`. Must be string, no object/array syntax. Use `clsx()` or template literals |
113
113
  | **Styles** | `style` must be string (`style="color: red"`), not object. No special handling |
114
- | **Args** | Destructure in parameter for init code. Render loops: `for (const { prop } of this)` for fresh args each cycle (new bindings), or `for ({ prop } of this)` without `const` to reassign variables from the outer scope. `while (true)` when you don't need fresh args |
114
+ | **Args** | Destructure in parameter for init code. Render loops: `for (const { step } of this)` for fresh args each cycle (new bindings), or `for ({ step } of this)` without `const` to reassign variables from the outer scope. `while (true)` when you don't need fresh args |
115
115
  | **Generators** | Stateful components are JS generators with a main render loop (`while`/`for...of`). Since they're generators, yields can also appear outside the loop — sequential phases, conditional blocks, etc. are all valid |
116
- | **Root JSX** | Use `<>...</>` in stateful to avoid double wrapper |
116
+ | **Root JSX** | Use `<>...</>` in stateful to avoid an extra element inside the host |
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 |
@@ -121,12 +121,13 @@ ref?.next() // trigger re-render from outside
121
121
  | **Refs** | `ref={el => ...}` on elements. Receives `null` on unmount. Stateful ref type: `ThisParameterType<typeof Component>` |
122
122
  | **Memo** | `memo={[deps]}` array, `memo={value}` single, or just `memo` (never re-render). Skips subtree if unchanged |
123
123
  | **Skip** | `skip` excludes children from reconciliation; it does not sanitize `set:innerHTML` |
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
+ | **Custom host** | `stateful(fn, 'tagname')` sets `.is` and infers `this` type. Or manually: `Stateful<Args, 'tagname'>` + `.is = 'tagname'`. Default is `div` (no need to set) |
125
125
  | **Default attrs** | `.attrs = { class: '...' }` on stateful component generator function |
126
- | **Default args** | `.args = { prop: value }` on stateful component generator function |
126
+ | **Default args** | `.args = { step: 1 }` on stateful component generator function |
127
127
  | **Cleanup** | `this.signal` aborts on lifecycle end (unmount, `this.return()`, generator done). Use for fetch, addEventListener, etc. `try/finally` for the rest |
128
128
  | **Error recovery** | `try { ... } catch { yield error UI }` inside loop |
129
- | **this** | Stateful wrapper element with `.signal`, `.next(fn?)` (returns `fn`'s result), `.throw()`, `.return(deep?)`. Iterable: `for...of this` yields args. Type: `ThisParameterType<typeof Component>` |
129
+ | **this** | The host element with `.signal`, `.next(fn?)` (returns `fn`'s result), `.throw()`, `.return(deep?)`. Iterable: `for...of this` yields args. Type: `ThisParameterType<typeof Component>` or `Host` |
130
+ | **Cloves** | Shared logic = plain function `(host: Host, options?) => live view`. Pass `this` as host. See Cloves section |
130
131
 
131
132
  ## Common Patterns
132
133
 
@@ -145,8 +146,8 @@ const DataLoader: Stateful<LoaderArgs> = function* ({ url }) {
145
146
 
146
147
  fetch(url, { signal: this.signal }) // signal auto-aborts on lifecycle end
147
148
  .then(r => r.json())
148
- .then(d => this.next(() => data = d)) // no-op if unmounted
149
- .catch(e => this.next(() => error = e)) // no-op if unmounted
149
+ .then(d => this.next(() => data = d)) // no-op if unmounted
150
+ .catch(e => e.name == 'AbortError' || this.next(() => error = e)) // skip AbortError: the signal already fired because the component ended
150
151
 
151
152
  while (true) yield (
152
153
  <>
@@ -253,6 +254,97 @@ let map: MapLibrary | null = null
253
254
  <div skip ref={el => el ? (map ??= new MapLibrary(el)) : map?.destroy()} /> // skip lets library control children
254
255
  ```
255
256
 
257
+ ## Cloves — Reusable Logic
258
+
259
+ A clove is Ajo's unit of shared component logic (what hooks/composables are in other frameworks): a plain function that takes the component host (`this`) and returns a live view of one stateful concern. No runtime, no registry, no ordering rules — generators already provide persistent closures, so the instance protocol (`signal` + `next`) is the whole mechanism.
260
+
261
+ ```tsx
262
+ import type { Host } from 'ajo'
263
+
264
+ /** Tracks the pointer position. */
265
+ const pointer = (host: Host) => {
266
+
267
+ const pos = { x: 0, y: 0 }
268
+
269
+ if (typeof document != 'undefined' && host.nodeType == 1) // SSR guard: inert view on the server
270
+ document.addEventListener('pointermove',
271
+ e => host.next(() => { pos.x = e.clientX; pos.y = e.clientY }),
272
+ { signal: host.signal }) // teardown on unmount AND on reset, automatically
273
+
274
+ return pos
275
+ }
276
+
277
+ function* Cursor() {
278
+ const pos = pointer(this) // setup phase: call before the render loop
279
+ while (true) yield <p>{pos.x}, {pos.y}</p>
280
+ }
281
+ ```
282
+
283
+ **The contract — all six rules are mandatory when writing a clove:**
284
+
285
+ 1. **Signature**: `(host, options?) => view`. Host first. Options in a single object with defaults, never positional flags.
286
+ 2. **Cleanup goes through `host.signal`, exclusively.** Pass `{ signal: host.signal }` to `addEventListener`; use `host.signal.addEventListener('abort', () => ...)` for timers, observers, sockets. Never return a dispose function. This makes teardown compose with unmount and with explicit reset (`el.return()` + `el.next()` restarts the generator with a fresh signal, so setup re-runs and the clove re-subscribes with zero extra code).
287
+ 3. **Invalidation goes through `host.next(fn)`**, mutating state inside `fn`. Already safe: no-op after unmount, no reentrant render while rendering.
288
+ 4. **The view is a stable reference.** Mutate its fields; never reassign or spread it (that breaks liveness — same contract as Ajo's own live `args`). Use getters for derived values. Name state as nouns (`x`, `open`, `data`), methods as verbs (`load`, `start`).
289
+ 5. **Per-render input is an explicit method call inside the render loop**, internally a no-op when input is unchanged. No hidden dependency tracking.
290
+ 6. **SSR-inert by shape.** During `ajo/html` SSR the host is protocol-only (`next` is a noop, no DOM). Guard DOM access (`typeof document != 'undefined' && host.nodeType == 1`) and always return the correctly shaped view so universal components can call cloves unconditionally.
291
+
292
+ Per-render input + async, the canonical shape:
293
+
294
+ ```tsx
295
+ /** Keyed async loader: chains host.signal, re-loads only when the key changes. */
296
+ const loader = <T,>(host: Host) => {
297
+
298
+ let key: string | undefined
299
+
300
+ const q = {
301
+ data: null as T | null, error: null as Error | null, loading: false,
302
+ load(url: string) {
303
+ if (url == key) return // per-render no-op: rule 5
304
+ key = url
305
+ q.loading = true
306
+ q.data = q.error = null
307
+ fetch(url, { signal: host.signal })
308
+ .then(r => r.json())
309
+ .then(d => host.next(() => { q.data = d; q.loading = false }))
310
+ .catch(e => e.name == 'AbortError' || host.next(() => { q.error = e; q.loading = false }))
311
+ },
312
+ }
313
+
314
+ return q
315
+ }
316
+
317
+ function* User() {
318
+ const q = loader<{ name: string }>(this)
319
+ for (const { id } of this) {
320
+ q.load(`/api/users/${id}`) // reacts to args because it runs every render
321
+ yield q.loading ? <p>Loading...</p> : q.error ? <p>{q.error.message}</p> : <h1>{q.data!.name}</h1>
322
+ }
323
+ }
324
+ ```
325
+
326
+ Composition is function calls sharing the host — a clove may call other cloves, and conditional calls are legal (there are no hook rules):
327
+
328
+ ```tsx
329
+ const idle = (host: Host, ms = 60000) => { /* uses timer(host) + pointer(host) internally */ }
330
+
331
+ function* App(args: { track?: boolean }) {
332
+ const t = args.track ? idle(this) : { idle: false } // conditional: fine
333
+ while (true) yield <p>{t.idle ? 'away' : 'here'}</p>
334
+ }
335
+ ```
336
+
337
+ Shared state across components is a clove factory: module-level state + a `Set` of hosts, `next()` on all hosts when mutating, each host auto-unsubscribed via its signal.
338
+
339
+ ```tsx
340
+ // ❌ Clove anti-patterns
341
+ const bad1 = (host: Host) => { /* ... */ return () => cleanup() } // dispose fn: use host.signal
342
+ const bad2 = (host: Host) => ({ ...state }) // copy: breaks liveness, return the live object
343
+ const useBad3 = (host: Host) => { } // use* prefix: signals React rules that don't exist
344
+ setInterval(() => host.next(), 1000) // leak: no abort wiring
345
+ fetch(url).catch(e => host.next(() => error = e)) // missing host.signal + AbortError filter
346
+ ```
347
+
256
348
  ## Anti-patterns
257
349
 
258
350
  ```tsx
package/dist/html.js CHANGED
@@ -1 +1 @@
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};
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)};`),i=/^[A-Za-z][\w:.-]*$/,s=/^[^\s"'/>=\0-\x1F\x7F]+$/,f=()=>{},m=e=>"key"==e||"skip"==e||"memo"==e||"ref"==e||e.startsWith("set:"),c={tag:"div"},d=e=>{let t="";return g(e,e=>t+=e),t},g=(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?b(e,r):u(e,r);else if(Symbol.iterator in Object(e))for(e of e)g(e,r);else r(l(String(e)))},u=(e,t)=>{let{nodeName:a,children:n}=e;a="string"==typeof a&&i.test(a)?a:c.tag;let f="";for(let t of o(e))"nodeName"==t||"children"==t||m(t)||!s.test(t)||null==e[t]||!1===e[t]||(!0===e[t]?f+=` ${t}`:f+=` ${t}="${l(String(e[t]))}"`);t(`<${a}${f}>`),r.has(a)||(null!=n&&g(n,t),t(`</${a}>`))},b=({nodeName:e,...t},r)=>{e.prototype?.next?h(e,t,r):g(e(t),r)},h=(t,r,l)=>{let i={...t.attrs},s={...t.args};for(let e of o(r))e.startsWith("attr:")?i[e.slice(5)]=r[e]:m(e)?i[e]=r[e]:s[e]=r[e];i.nodeName=t.is??c.tag;let d=new AbortController,g={*[Symbol.iterator](){for(;;)yield this[a]},[n]:Object.create(e()?.[n]??null),[a]:s,signal:d.signal,next:f,return:f,throw:e=>{throw e}},b=t.call(g,s),h=e();e(g);let y="",p=e=>y+=e;try{i.children=b.next().value,u(i,p)}catch(t){y="",i.children=b.throw(t).value,u(i,p)}finally{b.return(),d.abort(),e(h)}l(y)};export{c as defaults,g as html,d as render};
package/dist/index.js CHANGED
@@ -1 +1 @@
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};
1
+ import{isVNode as t}from"./jsx.js";import{Context as n,current as e}from"./context.js";var y,{isArray:o}=Array,{assign:r,hasOwn:a,keys:l}=Object,i=Symbol.for("ajo.key"),s=Symbol.for("ajo.keyed"),f=Symbol.for("ajo.memo"),m=Symbol.for("ajo.cache"),c=Symbol.for("ajo.generator"),d=Symbol.for("ajo.iterator"),g=Symbol.for("ajo.render"),u=Symbol.for("ajo.args"),b=Symbol.for("ajo.controller"),h={tag:"div"},p=(e,t)=>(t&&(e.is=t),e),S=(e,t,r=t.firstChild,n=null)=>{if(x(e,e=>{let o="string"==typeof e?N(e,r,n):v(e,t,r,n);null==r?A(t,o,n):o==r?r=o.nextSibling:o==r.nextSibling?(r.contains(document.activeElement)?t.insertBefore(o,r):t.insertBefore(r,n),r=o.nextSibling):A(t,o,r)}),r&&!n&&r==t.firstChild){let e=[...t.children];t[s]?.clear(),t.textContent="";for(let t of e)B(t)}else for(;r&&r!=n;){let e=r.nextSibling;t.removeChild(r),B(r,t),r=e}},x=(e,r)=>{if(null!=e&&"boolean"!=typeof e)if(t(e)){if("function"!=typeof e.nodeName)return r(e);let{nodeName:t,...n}=e;t.prototype?.next?r(j(t,n)):x(t(n),r)}else if("object"==typeof e&&Symbol.iterator in e)for(e of e)x(e,r);else r(String(e))},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??h.tag,r[c]=e,r[u]=n,r},N=(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},v=(e,t,r,n)=>{let o=e.nodeName,l=e.key,a=e[c];for(r&&null!=l&&null==n&&(r=t[s]?.get(l)??r);r&&r!=n;){let e=r[i],t=r[c];if(!(r.localName!=o||null!=e&&e!=l||t&&t!=a))break;r=null==e?r.nextElementSibling:null}return(null==r||r==n)&&(r=document.createElementNS(e.xmlns??t.namespaceURI,o)),null!=l&&(t[s]??=new Map).set(r[i]=l,r),(!r[m]||null==e.memo||k(r[f],e.memo))&&(w(r[m],e,r),e.skip||(a?O(a,e[u],r):r[m]||"string"!=typeof e.children&&"number"!=typeof e.children?S(e.children,r):r.textContent=e.children+""),r[f]=e.memo,r[m]=e),r},C=e=>"nodeName"==e||"children"==e||"key"==e||"skip"==e||"memo"==e,w=(e,t,r)=>{for(let n of e?l(e):r.getAttributeNames())a(t,n)||C(n)||(n.startsWith("set:")?r[n.slice(4)]=void 0:r.removeAttribute(n));for(let n of l(t)){let o=t[n];e&&e[n]===o||C(n)||("ref"==n&&"function"==typeof o?o(r):n.startsWith("set:")?r[n.slice(4)]=o:null==o||!1===o?r.removeAttribute(n):"class"==n&&"string"==typeof r.className?r.className=!0===o?"":o:r.setAttribute(n,!0===o?"":o))}},k=(e,t)=>o(e)&&o(t)?e.length!=t.length||e.some((e,r)=>e!==t[r]):e!==t,E=(e,t)=>{for(let r=t.firstElementChild;r;r=r.nextElementSibling)e(r)&&E(e,r)},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)},B=(e,t)=>{if(e[f]==B)return;e[f]=B;let r=e=>{for(let t,n=e.firstElementChild;n;n=t)t=n.nextElementSibling,r(n);null!=e[i]&&(e.parentNode??t)?.[s]?.delete(e[i]),"function"==typeof e.return&&e.return(!1),"function"==typeof e[m]?.ref&&e[m].ref(null)};r(e)},O=(t,o,i)=>{i[c]||(y||(y=new MutationObserver(e=>{for(let t of e)for(let e of t.removedNodes)1==e.nodeType&&!e.isConnected&&B(e,t.target)})).observe(document,{childList:!0,subtree:!0}),r(i,W)[n]=Object.create(e()?.[n]??null),i[c]=t);let s=i[u]??=o;for(let e of l(s))a(o,e)||delete s[e];r(s,o),i[g]()},W={*[Symbol.iterator](){for(;;)yield this[u]},[g](){let t=e();e(this);try{this[d]||(this.signal=(this[b]=new AbortController).signal,this[d]=this[c].call(this,this[u]));let{value:e,done:t}=this[d].next();S(e,this),t&&this.return()}catch(t){this.throw(t)}finally{e(t)}},next(t,r){if(!1===this[d]||!this.isConnected)return r;try{"function"==typeof t&&(r=t.call(this,this[u]))}catch(t){return this.throw(t)}return e()?.contains(this)||this[g](),r},throw(e){for(let t=this;t;t=t.parentNode)if(t[d]?.throw)try{return S(t[d].throw(e).value,t)}catch(t){e=Error(t?.message??t,{cause:e})}throw e},return(e=!0){let t=this[d];if(!1!==t){this[d]=!1,e&&E(e=>"function"!=typeof e.return||e.return(),this);try{t?.return()}catch(e){this.throw(e)}finally{this[d]=null,this[b]?.abort()}}}};export{h as defaults,S as render,p as stateful};
package/html.js CHANGED
@@ -13,6 +13,8 @@ const Tag = /^[A-Za-z][\w:.-]*$/, Attr = /^[^\s"'/>=\0-\x1F\x7F]+$/
13
13
 
14
14
  const noop = () => { }
15
15
 
16
+ const special = key => key == 'key' || key == 'skip' || key == 'memo' || key == 'ref' || key.startsWith('set:')
17
+
16
18
  export const defaults = { tag: 'div' }
17
19
 
18
20
  export const render = h => {
@@ -53,7 +55,7 @@ const element = (h, emit) => {
53
55
 
54
56
  for (const key of keys(h)) {
55
57
 
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
58
+ if (key == 'nodeName' || key == 'children' || special(key) || !Attr.test(key) || h[key] == null || h[key] === false) continue
57
59
 
58
60
  if (h[key] === true) a += ` ${key}`
59
61
 
@@ -72,7 +74,7 @@ const element = (h, emit) => {
72
74
 
73
75
  const run = ({ nodeName, ...h }, emit) => {
74
76
 
75
- if (nodeName.constructor.name == 'GeneratorFunction') runGenerator(nodeName, h, emit)
77
+ if (nodeName.prototype?.next) runGenerator(nodeName, h, emit)
76
78
 
77
79
  else html(nodeName(h), emit)
78
80
  }
@@ -85,7 +87,7 @@ const runGenerator = (fn, h, emit) => {
85
87
 
86
88
  if (key.startsWith('attr:')) attrs[key.slice(5)] = h[key]
87
89
 
88
- else if (key == 'key' || key == 'skip' || key == 'memo' || key == 'ref' || key.startsWith('set:')) attrs[key] = h[key]
90
+ else if (special(key)) attrs[key] = h[key]
89
91
 
90
92
  else args[key] = h[key]
91
93
  }