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/index.js CHANGED
@@ -1,8 +1,11 @@
1
- import { Context, current } from 'ajo/context'
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 Fragment = props => props.children
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
- return props
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
- for (h of walk(h)) {
22
+ walk(h, h => {
27
23
 
28
- const node = reconcile(h, el, child)
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
- while (child != ref) {
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
- if (child.nodeType == 1) unref(child)
62
+ const gone = el.removeChild(child)
55
63
 
56
- el.removeChild(child)
64
+ if (gone.nodeType == 1) unref(gone, el)
57
65
 
58
66
  child = node
59
67
  }
60
68
  }
61
69
 
62
- const walk = function* (h) {
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') yield h
78
+ if (type == 'string') fn(h)
71
79
 
72
- else if (type == 'number' || type == 'bigint') yield String(h)
80
+ else if (type == 'number' || type == 'bigint') fn(String(h))
73
81
 
74
- else if (Symbol.iterator in h) for (h of h) yield* walk(h)
82
+ else if (isVNode(h)) typeof h.nodeName == 'function' ? run(h, fn) : fn(h)
75
83
 
76
- else if ('nodeName' in h) typeof h.nodeName == 'function' ? yield* run(h) : yield h
84
+ else if (Symbol.iterator in Object(h)) for (h of h) walk(h, fn)
77
85
 
78
- else yield String(h)
86
+ else fn(String(h))
79
87
  }
80
88
 
81
- const run = function* ({ nodeName, ...h }) {
89
+ const run = ({ nodeName, ...h }, fn) => {
82
90
 
83
- if (nodeName.constructor.name == 'GeneratorFunction') yield runGenerator(nodeName, h)
91
+ if (nodeName.constructor.name == 'GeneratorFunction') fn(runGenerator(nodeName, h))
84
92
 
85
- else yield* walk(nodeName(h))
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 in h) {
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 ?? 'div', [Generator]: fn, [Args]: args }
109
+ return mark({ ...attrs, nodeName: fn.is ?? defaults.tag, [Generator]: fn, [Args]: args })
102
110
  }
103
111
 
104
- const reconcile = (h, el, node) => typeof h == 'string' ? text(h, node) : element(h, el, node)
112
+ const text = (h, node, ref) => {
105
113
 
106
- const text = (h, node) => {
114
+ while (node && node != ref && node.nodeType != 3) node = node.nextSibling
107
115
 
108
- while (node && node.nodeType != 3) node = node.nextSibling
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 = ({ nodeName, children, key, skip, memo, ref, [Generator]: gen, [Args]: args, ...h }, el, node) => {
121
+ const element = (h, el, node, ref) => {
122
+
123
+ const nodeName = h.nodeName, key = h.key, gen = h[Generator]
116
124
 
117
- while (node && (
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.nextSibling
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], node[Memo] = memo)) {
143
+ if (!node[Cache] || h.memo == null || some(node[Memo], h.memo)) {
132
144
 
133
- attrs(node[Cache] ?? extract(node), node[Cache] = h, node)
145
+ attrs(node[Cache], h, node)
134
146
 
135
- if (!skip) gen ? next(gen, args, node) : render(children, node)
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
- if (typeof ref == 'function') (node[Ref] = ref)(node)
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 (const key in { ...cache, ...h }) {
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 (cache[key] === h[key]) continue
167
+ if (key == 'ref' && typeof value == 'function') value(node)
148
168
 
149
- if (key.startsWith('set:')) node[key.slice(4)] = h[key]
169
+ else if (key.startsWith('set:')) node[key.slice(4)] = value
150
170
 
151
- else if (h[key] == null || h[key] === false) node.removeAttribute(key)
171
+ else if (value == null || value === false) node.removeAttribute(key)
152
172
 
153
- else node.setAttribute(key, h[key] === true ? '' : h[key])
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) => Array.isArray(a) && Array.isArray(b) ? a.some((v, i) => v !== b[i]) : 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
- const extract = el => Array.from(el.attributes).reduce((out, attr) => (out[attr.name] = attr.value, out), {})
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 = node => {
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
- for (const child of node.children) unref(child)
248
+ const { nextElementSibling, parentNode } = node
182
249
 
183
- if (typeof node.return == 'function') node.return()
250
+ if (node[Key] != null) (parentNode ?? parent)?.[Keyed]?.delete(node[Key])
184
251
 
185
- node[Ref]?.(null)
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] ??= (attach(el), fn)
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
- Object.assign(el[Args] ??= {}, args)
270
+ assign(el[Args], args)
193
271
 
194
272
  el[Render]()
195
273
  }
196
274
 
197
- const attach = el => {
275
+ let observer
198
276
 
199
- Object.assign(el, methods)
277
+ const watch = () => observer || (observer = new MutationObserver(records => {
200
278
 
201
- el[Context] = Object.create(current()?.[Context] ?? null)
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
- this[Iterator]?.return()
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.31",
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
- "require": "./dist/html.cjs"
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": "25.2.3",
36
- "happy-dom": "20.6.1",
37
- "vite": "7.3.1",
38
- "vite-tsconfig-paths": "6.1.1",
39
- "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"
40
50
  },
41
51
  "keywords": [
42
52
  "ui",
@@ -45,13 +55,19 @@
45
55
  "dom",
46
56
  "jsx"
47
57
  ],
48
- "repository": "cristianfalcone/ajo",
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"