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 +111 -19
- package/dist/html.js +1 -1
- package/dist/index.js +1 -1
- package/html.js +5 -3
- package/index.js +363 -401
- package/jsx.js +5 -5
- package/package.json +1 -1
- package/readme.md +93 -34
- package/types.ts +12 -12
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' //
|
|
78
|
-
Counter.attrs = { class: 'counter-wrap' } // default
|
|
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
|
|
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" // →
|
|
93
|
-
set:onclick={fn} // →
|
|
94
|
-
key={id} // →
|
|
95
|
-
memo={[id]} // →
|
|
96
|
-
memo={id} // →
|
|
97
|
-
memo // →
|
|
98
|
-
ref={el => ref = el} // →
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 = {
|
|
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** |
|
|
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))
|
|
149
|
-
.catch(e => this.next(() => error = e)) //
|
|
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)};`),
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
90
|
+
else if (special(key)) attrs[key] = h[key]
|
|
89
91
|
|
|
90
92
|
else args[key] = h[key]
|
|
91
93
|
}
|