ajo 0.1.31 → 0.1.33
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 +352 -329
- package/dist/context.js +1 -11
- package/dist/html.js +1 -54
- package/dist/index.js +1 -94
- package/dist/jsx.js +1 -0
- package/html.js +112 -112
- package/index.js +152 -63
- package/jsx.js +20 -0
- package/package.json +30 -14
- package/readme.md +442 -428
- package/types.ts +134 -91
- package/dist/context.cjs +0 -1
- package/dist/html.cjs +0 -1
- package/dist/index.cjs +0 -1
package/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { Context, current } from '
|
|
1
|
+
import { Context, current } from './context.js'
|
|
2
|
+
import { isVNode, mark } from './jsx.js'
|
|
3
|
+
|
|
4
|
+
const { isArray } = Array, { assign, hasOwn, keys } = Object
|
|
2
5
|
|
|
3
6
|
const Key = Symbol.for('ajo.key')
|
|
7
|
+
const Keyed = Symbol.for('ajo.keyed')
|
|
4
8
|
const Memo = Symbol.for('ajo.memo')
|
|
5
|
-
const Ref = Symbol.for('ajo.ref')
|
|
6
9
|
const Cache = Symbol.for('ajo.cache')
|
|
7
10
|
const Generator = Symbol.for('ajo.generator')
|
|
8
11
|
const Iterator = Symbol.for('ajo.iterator')
|
|
@@ -10,22 +13,15 @@ const Render = Symbol.for('ajo.render')
|
|
|
10
13
|
const Args = Symbol.for('ajo.args')
|
|
11
14
|
const Controller = Symbol.for('ajo.controller')
|
|
12
15
|
|
|
13
|
-
export const
|
|
14
|
-
|
|
15
|
-
export const h = (type, props, ...children) => {
|
|
16
|
-
|
|
17
|
-
(props ??= {}).nodeName = type
|
|
18
|
-
|
|
19
|
-
if (!('children' in props) && children.length) props.children = children.length == 1 ? children[0] : children
|
|
16
|
+
export const defaults = { tag: 'div' }
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
}
|
|
18
|
+
export const stateful = (fn, is) => (is && (fn.is = is), fn)
|
|
23
19
|
|
|
24
20
|
export const render = (h, el, child = el.firstChild, ref = null) => {
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
walk(h, h => {
|
|
27
23
|
|
|
28
|
-
const node =
|
|
24
|
+
const node = typeof h == 'string' ? text(h, child, ref) : element(h, el, child, ref)
|
|
29
25
|
|
|
30
26
|
if (child == null) {
|
|
31
27
|
|
|
@@ -45,21 +41,33 @@ export const render = (h, el, child = el.firstChild, ref = null) => {
|
|
|
45
41
|
|
|
46
42
|
before(el, node, child)
|
|
47
43
|
}
|
|
48
|
-
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (child && !ref && child == el.firstChild) {
|
|
47
|
+
|
|
48
|
+
const gone = []
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
el[Keyed]?.clear()
|
|
51
|
+
|
|
52
|
+
while (child) gone.push(child), child = child.nextSibling
|
|
53
|
+
|
|
54
|
+
el.textContent = ''
|
|
55
|
+
|
|
56
|
+
for (child of gone) if (child.nodeType == 1) unref(child)
|
|
57
|
+
|
|
58
|
+
} else while (child && child != ref) {
|
|
51
59
|
|
|
52
60
|
const node = child.nextSibling
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
const gone = el.removeChild(child)
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
if (gone.nodeType == 1) unref(gone, el)
|
|
57
65
|
|
|
58
66
|
child = node
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
const walk =
|
|
70
|
+
const walk = (h, fn) => {
|
|
63
71
|
|
|
64
72
|
if (h == null) return
|
|
65
73
|
|
|
@@ -67,29 +75,29 @@ const walk = function* (h) {
|
|
|
67
75
|
|
|
68
76
|
if (type == 'boolean') return
|
|
69
77
|
|
|
70
|
-
if (type == 'string')
|
|
78
|
+
if (type == 'string') fn(h)
|
|
71
79
|
|
|
72
|
-
else if (type == 'number' || type == 'bigint')
|
|
80
|
+
else if (type == 'number' || type == 'bigint') fn(String(h))
|
|
73
81
|
|
|
74
|
-
else if (
|
|
82
|
+
else if (isVNode(h)) typeof h.nodeName == 'function' ? run(h, fn) : fn(h)
|
|
75
83
|
|
|
76
|
-
else if (
|
|
84
|
+
else if (Symbol.iterator in Object(h)) for (h of h) walk(h, fn)
|
|
77
85
|
|
|
78
|
-
else
|
|
86
|
+
else fn(String(h))
|
|
79
87
|
}
|
|
80
88
|
|
|
81
|
-
const run =
|
|
89
|
+
const run = ({ nodeName, ...h }, fn) => {
|
|
82
90
|
|
|
83
|
-
if (nodeName.constructor.name == 'GeneratorFunction')
|
|
91
|
+
if (nodeName.constructor.name == 'GeneratorFunction') fn(runGenerator(nodeName, h))
|
|
84
92
|
|
|
85
|
-
else
|
|
93
|
+
else walk(nodeName(h), fn)
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
const runGenerator = (fn, h) => {
|
|
89
97
|
|
|
90
98
|
const attrs = { ...fn.attrs }, args = { ...fn.args }
|
|
91
99
|
|
|
92
|
-
for (const key
|
|
100
|
+
for (const key of keys(h)) {
|
|
93
101
|
|
|
94
102
|
if (key.startsWith('attr:')) attrs[key.slice(5)] = h[key]
|
|
95
103
|
|
|
@@ -98,23 +106,25 @@ const runGenerator = (fn, h) => {
|
|
|
98
106
|
else args[key] = h[key]
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
return { ...attrs, nodeName: fn.is ??
|
|
109
|
+
return mark({ ...attrs, nodeName: fn.is ?? defaults.tag, [Generator]: fn, [Args]: args })
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
const
|
|
112
|
+
const text = (h, node, ref) => {
|
|
105
113
|
|
|
106
|
-
|
|
114
|
+
while (node && node != ref && node.nodeType != 3) node = node.nextSibling
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
node ? node.data != h && (node.data = h) : node = document.createTextNode(h)
|
|
116
|
+
node && node != ref ? node.data != h && (node.data = h) : node = document.createTextNode(h)
|
|
111
117
|
|
|
112
118
|
return node
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
const element = (
|
|
121
|
+
const element = (h, el, node, ref) => {
|
|
122
|
+
|
|
123
|
+
const nodeName = h.nodeName, key = h.key, gen = h[Generator]
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
if (node && key != null && ref == null) node = (el[Keyed] ??= new Map()).get(key) ?? node
|
|
126
|
+
|
|
127
|
+
while (node && node != ref && (
|
|
118
128
|
|
|
119
129
|
(node.localName != nodeName) ||
|
|
120
130
|
|
|
@@ -122,19 +132,23 @@ const element = ({ nodeName, children, key, skip, memo, ref, [Generator]: gen, [
|
|
|
122
132
|
|
|
123
133
|
(node[Generator] && node[Generator] != gen)
|
|
124
134
|
|
|
125
|
-
)) node = node.
|
|
135
|
+
)) node = node[Key] != null ? null : node.nextElementSibling
|
|
136
|
+
|
|
137
|
+
if (node == ref) node = null
|
|
126
138
|
|
|
127
139
|
node ??= document.createElementNS(h.xmlns ?? el.namespaceURI, nodeName)
|
|
128
140
|
|
|
129
|
-
if (key != null) node[Key] = key
|
|
141
|
+
if (key != null) (el[Keyed] ??= new Map()).set(node[Key] = key, node)
|
|
130
142
|
|
|
131
|
-
if (memo == null || some(node[Memo],
|
|
143
|
+
if (!node[Cache] || h.memo == null || some(node[Memo], h.memo)) {
|
|
132
144
|
|
|
133
|
-
attrs(node[Cache]
|
|
145
|
+
attrs(node[Cache], h, node)
|
|
134
146
|
|
|
135
|
-
if (!skip) gen ? next(gen,
|
|
147
|
+
if (!h.skip) gen ? next(gen, h[Args], node) : !node[Cache] && (typeof h.children == 'string' || typeof h.children == 'number') ? node.textContent = h.children + '' : render(h.children, node)
|
|
136
148
|
|
|
137
|
-
|
|
149
|
+
node[Memo] = h.memo
|
|
150
|
+
|
|
151
|
+
node[Cache] = h
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
return node
|
|
@@ -142,25 +156,68 @@ const element = ({ nodeName, children, key, skip, memo, ref, [Generator]: gen, [
|
|
|
142
156
|
|
|
143
157
|
const attrs = (cache, h, node) => {
|
|
144
158
|
|
|
145
|
-
for (
|
|
159
|
+
if (!cache) for (let i = node.attributes.length; i--;) hasOwn(h, node.attributes[i].name) || node.removeAttribute(node.attributes[i].name)
|
|
160
|
+
|
|
161
|
+
for (const key in h) if (hasOwn(h, key)) {
|
|
162
|
+
|
|
163
|
+
const value = h[key]
|
|
164
|
+
|
|
165
|
+
if (cache && cache[key] === value || key == 'nodeName' || key == 'children' || key == 'key' || key == 'skip' || key == 'memo') continue
|
|
146
166
|
|
|
147
|
-
if (
|
|
167
|
+
if (key == 'ref' && typeof value == 'function') value(node)
|
|
148
168
|
|
|
149
|
-
if (key.startsWith('set:')) node[key.slice(4)] =
|
|
169
|
+
else if (key.startsWith('set:')) node[key.slice(4)] = value
|
|
150
170
|
|
|
151
|
-
else if (
|
|
171
|
+
else if (value == null || value === false) node.removeAttribute(key)
|
|
152
172
|
|
|
153
|
-
else
|
|
173
|
+
else if (key == 'class' && typeof node.className == 'string') node.className = value === true ? '' : value
|
|
174
|
+
|
|
175
|
+
else node.setAttribute(key, value === true ? '' : value)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (cache) for (const key in cache) if (hasOwn(cache, key)) {
|
|
179
|
+
|
|
180
|
+
if (hasOwn(h, key) || key == 'nodeName' || key == 'children' || key == 'key' || key == 'skip' || key == 'memo') continue
|
|
181
|
+
|
|
182
|
+
if (key.startsWith('set:')) node[key.slice(4)] = undefined
|
|
183
|
+
|
|
184
|
+
else node.removeAttribute(key)
|
|
154
185
|
}
|
|
155
186
|
}
|
|
156
187
|
|
|
157
|
-
const some = (a, b) =>
|
|
188
|
+
const some = (a, b) => {
|
|
189
|
+
|
|
190
|
+
if (isArray(a) && isArray(b)) {
|
|
191
|
+
|
|
192
|
+
if (a.length != b.length) return true
|
|
158
193
|
|
|
159
|
-
|
|
194
|
+
for (let i = a.length; i--;) if (a[i] !== b[i]) return true
|
|
195
|
+
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return a !== b
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const each = (fn, node) => {
|
|
203
|
+
|
|
204
|
+
let child = node.firstElementChild
|
|
205
|
+
|
|
206
|
+
while (child)
|
|
207
|
+
|
|
208
|
+
if (fn(child) && child.firstElementChild) child = child.firstElementChild
|
|
209
|
+
|
|
210
|
+
else {
|
|
211
|
+
|
|
212
|
+
while (child != node && !child.nextElementSibling) child = child.parentNode ?? node
|
|
213
|
+
|
|
214
|
+
child = child != node && child.nextElementSibling
|
|
215
|
+
}
|
|
216
|
+
}
|
|
160
217
|
|
|
161
218
|
const before = (el, node, child) => {
|
|
162
219
|
|
|
163
|
-
if (node.contains(document.activeElement)) {
|
|
220
|
+
if (node.isConnected && node.contains(document.activeElement)) {
|
|
164
221
|
|
|
165
222
|
const ref = node.nextSibling
|
|
166
223
|
|
|
@@ -176,33 +233,57 @@ const before = (el, node, child) => {
|
|
|
176
233
|
} else el.insertBefore(node, child)
|
|
177
234
|
}
|
|
178
235
|
|
|
179
|
-
const unref =
|
|
236
|
+
const unref = (root, parent) => {
|
|
237
|
+
|
|
238
|
+
if (root[Memo] == unref) return
|
|
239
|
+
|
|
240
|
+
root[Memo] = unref
|
|
241
|
+
|
|
242
|
+
let node = root
|
|
243
|
+
|
|
244
|
+
while (node.firstElementChild) node = node.firstElementChild
|
|
245
|
+
|
|
246
|
+
while (true) {
|
|
180
247
|
|
|
181
|
-
|
|
248
|
+
const { nextElementSibling, parentNode } = node
|
|
182
249
|
|
|
183
|
-
|
|
250
|
+
if (node[Key] != null) (parentNode ?? parent)?.[Keyed]?.delete(node[Key])
|
|
184
251
|
|
|
185
|
-
|
|
252
|
+
if (typeof node.return == 'function') node.return(false)
|
|
253
|
+
|
|
254
|
+
if (typeof node[Cache]?.ref == 'function') node[Cache].ref(null)
|
|
255
|
+
|
|
256
|
+
if (node === root) break
|
|
257
|
+
|
|
258
|
+
node = nextElementSibling ?? parentNode ?? root
|
|
259
|
+
|
|
260
|
+
if (nextElementSibling) while (node.firstElementChild) node = node.firstElementChild
|
|
261
|
+
}
|
|
186
262
|
}
|
|
187
263
|
|
|
188
264
|
const next = (fn, args, el) => {
|
|
189
265
|
|
|
190
|
-
el[Generator] ??= (
|
|
266
|
+
el[Generator] ??= (watch(), assign(el, methods)[Context] = Object.create(current()?.[Context] ?? null), fn)
|
|
267
|
+
|
|
268
|
+
for (const key of keys(el[Args] ??= args)) hasOwn(args, key) || delete el[Args][key]
|
|
191
269
|
|
|
192
|
-
|
|
270
|
+
assign(el[Args], args)
|
|
193
271
|
|
|
194
272
|
el[Render]()
|
|
195
273
|
}
|
|
196
274
|
|
|
197
|
-
|
|
275
|
+
let observer
|
|
198
276
|
|
|
199
|
-
|
|
277
|
+
const watch = () => observer || (observer = new MutationObserver(records => {
|
|
200
278
|
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
for (const record of records) for (const node of record.removedNodes) if (node.nodeType == 1 && !node.isConnected) unref(node, record.target)
|
|
280
|
+
|
|
281
|
+
})).observe(document, { childList: true, subtree: true })
|
|
203
282
|
|
|
204
283
|
const methods = {
|
|
205
284
|
|
|
285
|
+
*[Symbol.iterator]() { while (true) yield this[Args] },
|
|
286
|
+
|
|
206
287
|
[Render]() {
|
|
207
288
|
|
|
208
289
|
const parent = current()
|
|
@@ -222,8 +303,6 @@ const methods = {
|
|
|
222
303
|
|
|
223
304
|
render(value, this)
|
|
224
305
|
|
|
225
|
-
this[Ref]?.(this)
|
|
226
|
-
|
|
227
306
|
if (done) this.return()
|
|
228
307
|
|
|
229
308
|
} catch (e) {
|
|
@@ -238,6 +317,8 @@ const methods = {
|
|
|
238
317
|
|
|
239
318
|
next(fn, result) {
|
|
240
319
|
|
|
320
|
+
if (this[Iterator] === false || !this.isConnected) return result
|
|
321
|
+
|
|
241
322
|
try {
|
|
242
323
|
|
|
243
324
|
if (typeof fn == 'function') result = fn.call(this, this[Args])
|
|
@@ -266,11 +347,19 @@ const methods = {
|
|
|
266
347
|
throw value
|
|
267
348
|
},
|
|
268
349
|
|
|
269
|
-
return() {
|
|
350
|
+
return(deep = true) {
|
|
351
|
+
|
|
352
|
+
if (this[Iterator] === false) return
|
|
353
|
+
|
|
354
|
+
const iterator = this[Iterator]
|
|
355
|
+
|
|
356
|
+
this[Iterator] = false
|
|
357
|
+
|
|
358
|
+
if (deep) each(el => typeof el.return == 'function' ? el.return() : true, this)
|
|
270
359
|
|
|
271
360
|
try {
|
|
272
361
|
|
|
273
|
-
|
|
362
|
+
iterator?.return()
|
|
274
363
|
|
|
275
364
|
} catch (e) {
|
|
276
365
|
|
package/jsx.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const VNode = Symbol.for('ajo.vnode')
|
|
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/package.json
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
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
|
".": {
|
|
10
|
+
"react-native": "./index.js",
|
|
11
11
|
"types": "./types.ts",
|
|
12
|
-
"import": "./dist/index.js"
|
|
13
|
-
"require": "./dist/index.cjs"
|
|
12
|
+
"import": "./dist/index.js"
|
|
14
13
|
},
|
|
15
14
|
"./context": {
|
|
15
|
+
"react-native": "./context.js",
|
|
16
16
|
"types": "./types.ts",
|
|
17
|
-
"import": "./dist/context.js"
|
|
18
|
-
"require": "./dist/context.cjs"
|
|
17
|
+
"import": "./dist/context.js"
|
|
19
18
|
},
|
|
20
19
|
"./html": {
|
|
21
20
|
"types": "./types.ts",
|
|
22
|
-
"import": "./dist/html.js"
|
|
23
|
-
|
|
21
|
+
"import": "./dist/html.js"
|
|
22
|
+
},
|
|
23
|
+
"./jsx-runtime": {
|
|
24
|
+
"react-native": "./jsx.js",
|
|
25
|
+
"types": "./types.ts",
|
|
26
|
+
"import": "./dist/jsx.js"
|
|
27
|
+
},
|
|
28
|
+
"./jsx-dev-runtime": {
|
|
29
|
+
"react-native": "./jsx.js",
|
|
30
|
+
"types": "./types.ts",
|
|
31
|
+
"import": "./dist/jsx.js"
|
|
24
32
|
}
|
|
25
33
|
},
|
|
26
34
|
"files": [
|
|
@@ -28,15 +36,17 @@
|
|
|
28
36
|
"context.js",
|
|
29
37
|
"html.js",
|
|
30
38
|
"index.js",
|
|
39
|
+
"jsx.js",
|
|
31
40
|
"LLMs.md",
|
|
32
41
|
"types.ts"
|
|
33
42
|
],
|
|
34
43
|
"devDependencies": {
|
|
35
|
-
"@types/node": "
|
|
36
|
-
"happy-dom": "20.6
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
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"
|
|
40
50
|
},
|
|
41
51
|
"keywords": [
|
|
42
52
|
"ui",
|
|
@@ -45,13 +55,19 @@
|
|
|
45
55
|
"dom",
|
|
46
56
|
"jsx"
|
|
47
57
|
],
|
|
48
|
-
"repository":
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "cristianfalcone/ajo"
|
|
61
|
+
},
|
|
49
62
|
"author": "Cristian Falcone",
|
|
50
63
|
"license": "ISC",
|
|
51
64
|
"bugs": "https://github.com/cristianfalcone/ajo/issues",
|
|
52
65
|
"homepage": "https://github.com/cristianfalcone/ajo#readme",
|
|
53
66
|
"scripts": {
|
|
54
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",
|
|
55
71
|
"build": "vite build",
|
|
56
72
|
"bump": "pnpm version patch && git push && git push --tags",
|
|
57
73
|
"release": "pnpm test && pnpm build && pnpm bump && pnpm publish"
|