ajo 0.0.6 → 0.0.7

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.cjs ADDED
@@ -0,0 +1,167 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from2, except, desc) => {
10
+ if (from2 && typeof from2 === "object" || typeof from2 === "function") {
11
+ for (let key of __getOwnPropNames(from2))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var ajo_exports = {};
19
+ __export(ajo_exports, {
20
+ Fragment: () => Fragment,
21
+ cleanup: () => cleanup,
22
+ clx: () => clx,
23
+ component: () => component,
24
+ consume: () => consume,
25
+ h: () => h,
26
+ intercept: () => intercept,
27
+ keb: () => keb,
28
+ propagate: () => propagate,
29
+ provide: () => provide,
30
+ refresh: () => refresh,
31
+ render: () => render,
32
+ stx: () => stx
33
+ });
34
+ module.exports = __toCommonJS(ajo_exports);
35
+ const Fragment = ({ children }) => children, h = (tagName, props, ...children) => {
36
+ children = children.length == 0 ? null : children.length == 1 ? children[0] : children;
37
+ return { children, ...props, tagName };
38
+ }, component = (setup) => ({ is, key, ref, host, ...props }) => h(is ?? setup.is ?? "div", {
39
+ key,
40
+ ...setup.host,
41
+ ...host,
42
+ skip: true,
43
+ ref: (host2) => (refresh(host2, props, setup), isFn(ref) && ref(host2))
44
+ }), render = (h2, host) => {
45
+ let child = host.firstChild, node, byKey = keyed.get(host);
46
+ for (h2 of normalize(h2, host)) {
47
+ if (typeof h2 == str) {
48
+ for (node = child; node; node = node.nextSibling)
49
+ if (node.nodeType == 3)
50
+ break;
51
+ node ? node.data !== h2 && (node.data = h2) : node = document.createTextNode(h2);
52
+ } else if (hasOwn(h2, "tagName")) {
53
+ const { tagName, key, skip, ref, children, block, ...props } = h2;
54
+ if (key != null && (node = byKey?.get(key)))
55
+ ;
56
+ else
57
+ for (node = child; node; node = node.nextSibling)
58
+ if (node.localName == tagName)
59
+ break;
60
+ node ||= document.createElement(tagName);
61
+ key != null && (byKey ||= keyed.set(host, createMap(Map))).set(key, node);
62
+ update(props, node);
63
+ !(skip || block != null && every(deps.get(node), deps.set(node, block))) && render(children, node);
64
+ isFn(ref) && ref(node);
65
+ } else
66
+ node = h2;
67
+ node === child ? child = child.nextSibling : before(host, child, node);
68
+ }
69
+ while (child) {
70
+ const next = child.nextSibling;
71
+ host.removeChild(child).nodeType == 1 && dispose(child);
72
+ child = next;
73
+ }
74
+ }, refresh = (host, props, setup) => {
75
+ try {
76
+ if (setup && !isFn(setup))
77
+ throwTypeError("Setup", setup, fn);
78
+ props = props ? memo.set(host, { ...setup?.props, ...props }) : memo.get(host) ?? {};
79
+ render((renders.get(host) ?? renders.set(host, setup(props, host)))(props, host), host);
80
+ } catch (error) {
81
+ propagate(host, error);
82
+ }
83
+ }, provide = (host, key, value) => (provisions.get(host) ?? provisions.set(host, createMap(Map))).set(key, value), consume = (host, key, fallback) => {
84
+ let map;
85
+ while (host) {
86
+ if ((map = provisions.get(host)) && map.has(key))
87
+ return map.get(key);
88
+ host = host.parentNode;
89
+ }
90
+ return fallback;
91
+ }, intercept = (host, interceptor) => {
92
+ if (!isFn(interceptor))
93
+ throwTypeError("Interceptor", interceptor, fn);
94
+ interceptors.set(host, interceptor);
95
+ }, propagate = (host, error) => {
96
+ for (let interceptor; host; host = host.parentNode)
97
+ if (interceptor = interceptors.get(host))
98
+ return render(host, interceptor(error));
99
+ throw error;
100
+ }, cleanup = (host, cleaner) => {
101
+ if (!isFn(cleaner))
102
+ throwTypeError("Cleaner", cleaner, fn);
103
+ (cleaners.get(host) ?? cleaners.set(host, /* @__PURE__ */ new Set())).add(cleaner);
104
+ }, clx = (o) => keys(o).filter((k) => o[k]).join(" ") || null, stx = (o) => entries(o).map((t) => t.join(":")).join(";") || null, keb = (o) => keys(o).reduce((r, k) => (r[k.replace(search, replace).toLowerCase()] = o[k], r), {});
105
+ const createMap = (constructor, ...args) => {
106
+ const instance = new constructor(...args);
107
+ const { set } = instance;
108
+ instance.set = (key, value) => (set.call(instance, key, value), value);
109
+ return instance;
110
+ }, throwTypeError = (name, value, expected) => {
111
+ throw new TypeError(`Expected ${name} to be of type ${expected}, got ${typeof value} instead`);
112
+ }, every = (a, b) => a === b || isArray(a) && isArray(b) && a.length == b.length && a.every((v, i) => v === b[i]), apply = (o, { name, value }) => (o[name] = value, o), reduce = (list) => from(list).reduce(apply, {}), isFn = (v) => typeof v == fn, search = /([a-z0-9])([A-Z])/g, replace = "$1-$2", str = "string", fn = "function", { keys, entries, hasOwn } = Object, { isArray, from } = Array, wm = WeakMap, deps = createMap(wm), memo = createMap(wm), keyed = createMap(wm), cache = createMap(wm), renders = createMap(wm), cleaners = createMap(wm), provisions = createMap(wm), interceptors = createMap(wm), normalize = function* (h2, host) {
113
+ let type, buffer = "";
114
+ for (h2 of isFn(h2?.[Symbol.iterator]) ? h2 : [h2]) {
115
+ if (h2 == null || (type = typeof h2) == "boolean")
116
+ continue;
117
+ if (h2 instanceof Node || hasOwn(h2, "tagName")) {
118
+ if (isFn(h2.tagName)) {
119
+ yield* normalize(h2.tagName(h2, host), host);
120
+ continue;
121
+ }
122
+ if (buffer) {
123
+ yield buffer;
124
+ buffer = "";
125
+ }
126
+ yield h2;
127
+ continue;
128
+ }
129
+ type == str || !isFn(h2[Symbol.iterator]) ? buffer += h2 : yield* normalize(h2, host);
130
+ }
131
+ if (buffer)
132
+ yield buffer;
133
+ }, update = (props, host) => {
134
+ const prev = cache.get(host) ?? (host.hasAttributes() ? reduce(host.attributes) : {});
135
+ for (const name in { ...prev, ...props }) {
136
+ let value = props[name];
137
+ if (value === prev[name])
138
+ continue;
139
+ if (name.startsWith("set:")) {
140
+ host[name.slice(4)] = value;
141
+ continue;
142
+ }
143
+ if (value === true)
144
+ value = "";
145
+ else if (value == null || value === false) {
146
+ host.removeAttribute(name);
147
+ continue;
148
+ }
149
+ host.setAttribute(name, value);
150
+ }
151
+ cache.set(host, props);
152
+ }, before = (host, child, node) => {
153
+ if (node.contains?.(document.activeElement)) {
154
+ const ref = node.nextSibling;
155
+ while (child && child !== node) {
156
+ const next = child.nextSibling;
157
+ host.insertBefore(child, ref);
158
+ child = next;
159
+ }
160
+ } else
161
+ host.insertBefore(node, child);
162
+ }, dispose = (host) => {
163
+ for (const child of host.children)
164
+ dispose(child);
165
+ for (const cleaner of cleaners.get(host) ?? [])
166
+ cleaner(host);
167
+ };
package/index.js CHANGED
@@ -1,206 +1,203 @@
1
- const { isArray } = Array
2
- const { hasOwn, keys } = Object
1
+ export const
2
+ Fragment = ({ children }) => children,
3
3
 
4
- export const Skip = Symbol()
5
- export const Fragment = ({ children }) => children
6
- export const Portal = ({ host, children }) => render(host, children)
4
+ h = (tagName, props, ...children) => {
5
+ children = children.length == 0 ? null : children.length == 1 ? children[0] : children
6
+ return { children, ...props, tagName }
7
+ },
7
8
 
8
- const Element = Symbol()
9
+ component = setup => ({ is, key, ref, host, ...props }) => h(is ?? setup.is ?? 'div', {
10
+ key, ...setup.host, ...host, skip: true, ref: host => (refresh(host, props, setup), isFn(ref) && ref(host))
11
+ }),
9
12
 
10
- export const createTextNode = data => ({ [Element]: '#text', data, skip: true })
13
+ render = (h, host) => {
11
14
 
12
- export const createElement = (name, props, ...children) => {
13
- props = { ...props, [Element]: name }
14
- children = props.children ?? children
15
- if (children.length > 0) props.children = children.length === 1 ? children[0] : children
16
- return props
17
- }
15
+ let child = host.firstChild, node, byKey = keyed.get(host)
18
16
 
19
- export const isElement = el => hasOwn(el ?? {}, Element)
17
+ for (h of normalize(h, host)) {
20
18
 
21
- function* g(vnode, host) {
22
- const vnodes = isArray(vnode) ? vnode : [vnode]
23
- let data = ''
19
+ if (typeof h == str) {
24
20
 
25
- while (vnodes.length > 0) {
26
- vnode = vnodes.shift()
21
+ for (node = child; node; node = node.nextSibling) if (node.nodeType == 3) break
22
+ node ? node.data !== h && (node.data = h) : node = document.createTextNode(h)
27
23
 
28
- if (vnode == null || typeof vnode === 'boolean') continue
29
- if (typeof vnode === 'string') data += vnode
30
- else if (isElement(vnode)) {
31
- const { [Element]: name } = vnode
24
+ } else if (hasOwn(h, 'tagName')) {
32
25
 
33
- if (typeof name === 'function') {
34
- vnodes.unshift(name(vnode, host))
35
- continue
36
- }
26
+ const { tagName, key, skip, ref, children, block, ...props } = h
37
27
 
38
- if (data) {
39
- yield createTextNode(data)
40
- data = ''
41
- }
28
+ if (key != null && (node = byKey?.get(key)));
29
+ else for (node = child; node; node = node.nextSibling) if (node.localName == tagName) break
42
30
 
43
- yield vnode
44
- } else typeof vnode[Symbol.iterator] === 'function'
45
- ? vnodes.unshift(...vnode)
46
- : data += vnode
47
- }
31
+ node ||= document.createElement(tagName)
32
+
33
+ key != null && (byKey ||= keyed.set(host, createMap(Map))).set(key, node)
48
34
 
49
- if (data) yield createTextNode(data)
50
- }
35
+ update(props, node)
51
36
 
52
- export const render = (vnode, host) => {
53
- let child = host.firstChild
37
+ !(skip || block != null && every(deps.get(node), deps.set(node, block))) && render(children, node)
54
38
 
55
- for (const { [Element]: name, ref, skip, children, ...props } of g(vnode, host)) {
39
+ isFn(ref) && ref(node)
56
40
 
57
- if (name === Skip) {
58
- child = props.end ? null : child?.nextSibling ?? null
59
- continue
41
+ } else node = h
42
+
43
+ node === child ? child = child.nextSibling : before(host, child, node)
60
44
  }
61
45
 
62
- let node = child
46
+ while (child) {
47
+ const next = child.nextSibling
48
+ host.removeChild(child).nodeType == 1 && dispose(child)
49
+ child = next
50
+ }
51
+ },
63
52
 
64
- while (node != null) {
65
- if (node.nodeName.toLowerCase() === name && node.getAttribute?.('key') == props.key) break
66
- node = node.nextSibling
53
+ refresh = (host, props, setup) => {
54
+ try {
55
+ if (setup && !isFn(setup)) throwTypeError('Setup', setup, fn)
56
+ props = props ? memo.set(host, { ...setup?.props, ...props }) : memo.get(host) ?? {}
57
+ render((renders.get(host) ?? renders.set(host, setup(props, host)))(props, host), host)
58
+ } catch (error) {
59
+ propagate(host, error)
67
60
  }
61
+ },
68
62
 
69
- if (node == null) node = name === '#text'
70
- ? document.createTextNode('')
71
- : document.createElement(name)
63
+ provide = (host, key, value) => (provisions.get(host) ?? provisions.set(host, createMap(Map))).set(key, value),
72
64
 
73
- patch(props, node)
65
+ consume = (host, key, fallback) => {
66
+ let map
67
+ while (host) {
68
+ if ((map = provisions.get(host)) && map.has(key)) return map.get(key)
69
+ host = host.parentNode
70
+ }
71
+ return fallback
72
+ },
73
+
74
+ intercept = (host, interceptor) => {
75
+ if (!isFn(interceptor)) throwTypeError('Interceptor', interceptor, fn)
76
+ interceptors.set(host, interceptor)
77
+ },
78
+
79
+ propagate = (host, error) => {
80
+ for (let interceptor; host; host = host.parentNode)
81
+ if (interceptor = interceptors.get(host)) return render(host, interceptor(error))
82
+ throw error
83
+ },
84
+
85
+ cleanup = (host, cleaner) => {
86
+ if (!isFn(cleaner)) throwTypeError('Cleaner', cleaner, fn);
87
+ (cleaners.get(host) ?? cleaners.set(host, new Set)).add(cleaner)
88
+ },
89
+
90
+ clx = o => keys(o).filter(k => o[k]).join(' ') || null,
91
+ stx = o => entries(o).map(t => t.join(':')).join(';') || null,
92
+ keb = o => keys(o).reduce((r, k) => ((r[k.replace(search, replace).toLowerCase()] = o[k]), r), {})
93
+
94
+ const
95
+ createMap = (constructor, ...args) => {
96
+ const instance = new constructor(...args)
97
+ const { set } = instance
98
+ instance.set = (key, value) => (set.call(instance, key, value), value)
99
+ return instance
100
+ },
101
+
102
+ throwTypeError = (name, value, expected) => {
103
+ throw new TypeError(`Expected ${name} to be of type ${expected}, got ${typeof value} instead`)
104
+ },
105
+
106
+ every = (a, b) => a === b || isArray(a) && isArray(b) && a.length == b.length && a.every((v, i) => v === b[i]),
107
+ apply = (o, { name, value }) => ((o[name] = value), o),
108
+ reduce = list => from(list).reduce(apply, {}),
109
+ isFn = v => typeof v == fn,
110
+
111
+ search = /([a-z0-9])([A-Z])/g,
112
+ replace = '$1-$2',
113
+ str = 'string',
114
+ fn = 'function',
115
+
116
+ { keys, entries, hasOwn } = Object,
117
+ { isArray, from } = Array,
118
+ wm = WeakMap,
119
+
120
+ deps = createMap(wm),
121
+ memo = createMap(wm),
122
+ keyed = createMap(wm),
123
+ cache = createMap(wm),
124
+ renders = createMap(wm),
125
+ cleaners = createMap(wm),
126
+ provisions = createMap(wm),
127
+ interceptors = createMap(wm),
128
+
129
+ normalize = function* (h, host) {
130
+
131
+ let type, buffer = ''
132
+
133
+ for (h of isFn(h?.[Symbol.iterator]) ? h : [h]) {
134
+
135
+ if (h == null || (type = typeof h) == 'boolean') continue
136
+
137
+ if (h instanceof Node || hasOwn(h, 'tagName')) {
138
+
139
+ if (isFn(h.tagName)) {
140
+ yield* normalize(h.tagName(h, host), host)
141
+ continue
142
+ }
143
+
144
+ if (buffer) {
145
+ yield buffer
146
+ buffer = ''
147
+ }
148
+
149
+ yield h
150
+ continue
151
+ }
74
152
 
75
- if (typeof ref === 'function') ref(node)
76
- else if (typeof ref === 'object' && ref !== null) ref.current = node
153
+ (type == str || !isFn(h[Symbol.iterator])) ? buffer += h : yield* normalize(h, host)
154
+ }
77
155
 
78
- skip || render(children, node)
156
+ if (buffer) yield buffer
157
+ },
79
158
 
80
- if (node === child) child = child.nextSibling
81
- else if (node.contains?.(document.activeElement)) {
82
- const { nextSibling } = node
83
- let ref = child
159
+ update = (props, host) => {
84
160
 
85
- while (ref != null && ref !== node) {
86
- const next = ref.nextSibling
87
- host.insertBefore(ref, nextSibling)
88
- ref = next
89
- }
90
- } else host.insertBefore(node, child)
91
- }
161
+ const prev = cache.get(host) ?? (host.hasAttributes() ? reduce(host.attributes) : {})
92
162
 
93
- while (child != null) {
94
- const { nextSibling } = child
95
- if (child.nodeType === Node.ELEMENT_NODE) dispose(child)
96
- host.removeChild(child)
97
- child = nextSibling
98
- }
99
- }
163
+ for (const name in { ...prev, ...props }) {
100
164
 
101
- const patch = (props, node) => {
102
- if (node.nodeType === Node.TEXT_NODE) {
103
- if (node.data !== props.data) node.data = props.data
104
- return
105
- }
165
+ let value = props[name]
106
166
 
107
- for (const name of new Set([...node.getAttributeNames(), ...keys(props)])) {
108
- let value = props[name]
167
+ if (value === prev[name]) continue
109
168
 
110
- if (name in node && !(typeof value === 'string' && typeof node[name] === 'boolean')) {
111
- try {
112
- if (node[name] !== value) node[name] = value
169
+ if (name.startsWith('set:')) {
170
+ host[name.slice(4)] = value
113
171
  continue
114
- } catch (err) { }
115
- }
172
+ }
116
173
 
117
- if (value === true) value = ''
118
- else if (value == null || value === false) {
119
- node.removeAttribute(name)
120
- continue
121
- }
174
+ if (value === true) value = ''
175
+ else if (value == null || value === false) {
176
+ host.removeAttribute(name)
177
+ continue
178
+ }
122
179
 
123
- if (node.getAttribute(name) !== value) node.setAttribute(name, value)
124
- }
125
- }
180
+ host.setAttribute(name, value)
181
+ }
126
182
 
127
- const components = new WeakMap
183
+ cache.set(host, props)
184
+ },
128
185
 
129
- export const createComponent = fn => ({ is, host, key, ...props }) =>
130
- createElement(is ?? fn.is ?? 'div', {
131
- ...fn.host, ...host, key, skip: true, ref: host => {
132
- let cmp = components.get(host)
133
- if (cmp == null) components.set(host, cmp = new Component(host, fn))
134
- cmp.props = { ...fn.props, ...props }
135
- cmp.update()
136
- }
137
- })
138
-
139
- class Component {
140
- constructor(host, fn) {
141
- this.host = host
142
- this.fn = fn
143
- this.props = null
144
- this.it = null
145
- this.err = false
146
- }
186
+ before = (host, child, node) => {
187
+ if (node.contains?.(document.activeElement)) {
147
188
 
148
- update({ method = 'next', arg } = {}) {
149
- if (this.host == null) return
150
- if (this.err) try { this.it.return() } catch (e) { this.host = null } finally { return }
189
+ const ref = node.nextSibling
151
190
 
152
- try {
153
- if (this.it == null) {
154
- const init = this.fn.call(this, this.props, this.host)
155
- this.it = typeof init?.next === 'function' ? init : generate.call(this, init)
191
+ while (child && child !== node) {
192
+ const next = child.nextSibling
193
+ host.insertBefore(child, ref)
194
+ child = next
156
195
  }
157
196
 
158
- const { value, done } = this.it[method](arg)
159
-
160
- render(value, this.host)
161
- if (done) this.host = null
162
- } catch (err) {
163
- this.err = true
164
- propagate(this.host.parentNode, err)
165
- }
166
- }
197
+ } else host.insertBefore(node, child)
198
+ },
167
199
 
168
- *[Symbol.iterator]() {
169
- while (this.host) yield this.props
170
- }
171
- }
172
-
173
- function* generate(init) {
174
- yield init
175
- for (const props of this) yield this.fn.call(this, props, this.host)
176
- }
177
-
178
- const propagate = (el, err) => {
179
- if (el == null) throw err
180
- const cmp = components.get(el)
181
- typeof cmp?.it?.throw === 'function'
182
- ? cmp.update({ method: 'throw', arg: err })
183
- : propagate(el.parentNode, err)
184
- }
185
-
186
- const dispose = el => {
187
- for (const child of el.children) dispose(child)
188
- components.get(el)?.update({ method: 'return' })
189
- }
190
-
191
- const provisions = new WeakMap
192
-
193
- export const provide = (host, key, value) => {
194
- let map = provisions.get(host)
195
- if (map == null) provisions.set(host, map = new Map)
196
- map.set(key, value)
197
- }
198
-
199
- export const consume = (host, key) => {
200
- for (let node = host; node != null; node = node.parentNode) {
201
- if (provisions.has(node)) {
202
- const map = provisions.get(node)
203
- if (map.has(key)) return map.get(key)
204
- }
200
+ dispose = host => {
201
+ for (const child of host.children) dispose(child)
202
+ for (const cleaner of cleaners.get(host) ?? []) cleaner(host)
205
203
  }
206
- }
package/index.test.js CHANGED
@@ -1,38 +1,37 @@
1
1
  import 'backdom/register'
2
2
  import { suite } from 'uvu'
3
3
  import * as assert from 'uvu/assert'
4
- import { createComponent, createElement, isElement, render, } from './index.js'
4
+ import { component, h, render } from './index.js'
5
5
 
6
6
  let it
7
7
 
8
8
  // ----------------------------------------------------------------------------
9
9
 
10
- it = suite('createElement')
10
+ it = suite('h')
11
11
 
12
12
  it('should create empty vnode', () => {
13
- const vnode = createElement('div')
14
- assert.ok(isElement(vnode))
15
- assert.equal(vnode, {})
13
+ const vnode = h('div')
14
+ assert.equal(vnode, { tagName: 'div', children: null })
16
15
  })
17
16
 
18
17
  it('should create vnode with props', () => {
19
- assert.equal(createElement('div', { id: 'app' }), { id: 'app' })
18
+ assert.equal(h('div', { id: 'app' }), { tagName: 'div', children: null, id: 'app' })
20
19
  })
21
20
 
22
21
  it('should create vnode with one string child', () => {
23
- assert.equal(createElement('div', null, 'foo'), { children: 'foo' })
22
+ assert.equal(h('div', null, 'foo'), { tagName: 'div', children: 'foo' })
24
23
  })
25
24
 
26
25
  it('should create vnode with one vnode child', () => {
27
- const child = createElement('span')
28
- const vnode = createElement('div', null, child)
29
- assert.ok(isElement(vnode.children))
26
+ const child = h('span')
27
+ const vnode = h('div', null, child)
28
+ assert.equal(vnode.children, { tagName: 'span', children: null })
30
29
  })
31
30
 
32
31
  it('should allow children as prop', () => {
33
- const child = createElement('span')
34
- const vnode = createElement('div', { children: child })
35
- assert.ok(isElement(vnode.children))
32
+ const child = h('span')
33
+ const vnode = h('div', { children: child })
34
+ assert.equal(vnode.children, { tagName: 'span', children: null })
36
35
  })
37
36
 
38
37
  it.run()
@@ -43,22 +42,21 @@ it = suite('render')
43
42
 
44
43
  it('should render a vnode', () => {
45
44
  const host = document.createElement('div')
46
- render(createElement('div', { foo: 'bar' }, 'foobar'), host)
45
+ render(h('div', { foo: 'bar' }, 'foobar'), host)
47
46
  assert.snapshot(host.innerHTML, '<div foo="bar">foobar</div>')
48
47
  })
49
48
 
50
49
  it('should render a stateless component', () => {
51
50
  const host = document.createElement('div')
52
- const Foo = ({ foo, children }) => createElement('div', { foo }, children)
53
- debugger
54
- render(createElement(Foo, { foo: 'bar' }, createElement('span', null, 'foobar')), host)
51
+ const Foo = ({ foo, children }) => h('div', { foo }, children)
52
+ render(h(Foo, { foo: 'bar' }, h('span', null, 'foobar')), host)
55
53
  assert.snapshot(host.innerHTML, '<div foo="bar"><span>foobar</span></div>')
56
54
  })
57
55
 
58
56
  it('should render a stateful component', () => {
59
57
  const host = document.createElement('div')
60
- const Foo = createComponent(({ foo, children }) => createElement('div', { foo }, children))
61
- render(createElement(Foo, { is: 'foo-component', foo: 'bar' }, createElement('span', null, 'foobar')), host)
58
+ const Foo = component(() => ({ foo, children }) => h('div', { foo }, children))
59
+ render(h(Foo, { is: 'foo-component', foo: 'bar' }, h('span', null, 'foobar')), host)
62
60
  assert.snapshot(host.innerHTML, '<foo-component><div foo="bar"><span>foobar</span></div></foo-component>')
63
61
  })
64
62
 
package/package.json CHANGED
@@ -1,33 +1,29 @@
1
1
  {
2
2
  "name": "ajo",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "ajo is a JavaScript view library for building user interfaces",
5
5
  "type": "module",
6
- "main": "cjs/index.js",
6
+ "module": "index.js",
7
+ "main": "index.cjs",
7
8
  "exports": {
8
9
  ".": {
9
10
  "import": "./index.js",
10
- "require": "./cjs/index.js"
11
+ "require": "./index.cjs"
11
12
  }
12
13
  },
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/cristianfalcone/ajo.git"
14
+ "scripts": {
15
+ "build": "esbuild --format=cjs --out-extension:.js=.cjs --outdir=. index.js",
16
+ "test": "uvu",
17
+ "release": "npm run build && npm t && git commit -am \"$npm_package_version\" && git tag $npm_package_version && git push && git push --tags && npm publish"
16
18
  },
19
+ "repository": "cristianfalcone/ajo",
17
20
  "author": "Cristian Falcone",
18
21
  "license": "ISC",
19
- "bugs": {
20
- "url": "https://github.com/cristianfalcone/ajo/issues"
21
- },
22
+ "bugs": "https://github.com/cristianfalcone/ajo/issues",
22
23
  "homepage": "https://github.com/cristianfalcone/ajo#readme",
23
24
  "devDependencies": {
24
- "backdom": "^0.0.3",
25
- "esbuild": "^0.14.31",
26
- "uvu": "^0.5.3"
27
- },
28
- "scripts": {
29
- "build": "esbuild --outdir=cjs --format=cjs index.js",
30
- "test": "uvu"
31
- },
32
- "readme": "# ajo\najo is a JavaScript view library for building user interfaces\n\n```bash\nnpm install ajo\n```\n\n## Incremental DOM\nTo keep the UI in sync, ajo uses a technique called incremental DOM.\nIt is a way to build UI components without the need to keep previous virtual DOM in memory.\nInstead, generated virtual DOM is diffed against the actual DOM, and changes are applied along the way.\nThis reduces memory usage and makes ajo code more simple and concise. As a result, ajo is easy to read and maintain, but lacks perfomance oportunities that diffing two virtual DOM trees can provide.\n\n```jsx\n/** @jsx createElement */\nimport { render, createElement } from 'ajo'\n\ndocument.body.innerHTML = '<div>Hello World</div>'\n\nrender(<div>Goodbye World</div>, document.body)\n```\n\n## Stateless components\nAs a way to reuse markup snipets, ajo uses simple synchroneous functions that return virtual DOM.\nThis type of components are ment to be \"consumers\" of data.\nNo state is preserved between invocations, so generated virtual DOM should rely exclusively on function's arguments. \n\n```jsx\n/** @jsx createElement */\nimport { render, createElement } from 'ajo'\n\nconst Greet = ({ name }) => <div>Hello {name}</div>\n\nrender(<Greet name=\"World\" />, document.body)\n```\n\n## Stateful components\nSince ajo does not store previous virtual DOM, stateful components rely on a DOM node to preserve its state between UI updates.\nThis DOM node is called a host node (similar to a Web Component host node).\nState is declared in a generator function local scope.\nThen ajo asociates the returned iterator with the host, and updates host children nodes each time, retrieving iterator's next value. Lifecycle of these components are closely related to its host nodes, and generator function provides a way to manage them.\n\n```jsx\n/** @jsx createElement */\nimport { render, createElement, createComponent } from 'ajo'\n\nconst Counter = createComponent(function* () {\n\tlet count = 1\n\t\n\tconst increment = () => {\n\t\tcount++\n\t\tthis.update()\n\t}\n\t\n\tfor ({} of this) yield (\n\t\t<button onclick={increment}>\n\t\t\tCurrent: {count}\n\t\t</button>\n\t)\n})\n\nrender(<Counter />, document.body)\n```\n"
33
- }
25
+ "backdom": "^0.0.4",
26
+ "esbuild": "^0.14.51",
27
+ "uvu": "^0.5.6"
28
+ }
29
+ }
package/readme.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ajo
2
2
  ajo is a JavaScript view library for building user interfaces
3
3
 
4
- ```bash
4
+ ```sh
5
5
  npm install ajo
6
6
  ```
7
7
 
@@ -12,12 +12,12 @@ Instead, generated virtual DOM is diffed against the actual DOM, and changes are
12
12
  This reduces memory usage and makes ajo code more simple and concise. As a result, ajo is easy to read and maintain, but lacks perfomance oportunities that diffing two virtual DOM trees can provide.
13
13
 
14
14
  ```jsx
15
- /** @jsx createElement */
16
- import { render, createElement } from 'ajo'
15
+ /** @jsx h */
16
+ import { h, render } from 'ajo'
17
17
 
18
18
  document.body.innerHTML = '<div>Hello World</div>'
19
19
 
20
- render(<div>Goodbye World</div>, document.body)
20
+ render(document.body, <div>Goodbye World</div>)
21
21
  ```
22
22
 
23
23
  ## Stateless components
@@ -26,12 +26,12 @@ This type of components are ment to be "consumers" of data.
26
26
  No state is preserved between invocations, so generated virtual DOM should rely exclusively on function's arguments.
27
27
 
28
28
  ```jsx
29
- /** @jsx createElement */
30
- import { render, createElement } from 'ajo'
29
+ /** @jsx h */
30
+ import { h, render } from 'ajo'
31
31
 
32
32
  const Greet = ({ name }) => <div>Hello {name}</div>
33
33
 
34
- render(<Greet name="World" />, document.body)
34
+ render(document.body, <Greet name="World" />)
35
35
  ```
36
36
 
37
37
  ## Stateful components
@@ -41,23 +41,22 @@ State is declared in a generator function local scope.
41
41
  Then ajo asociates the returned iterator with the host, and updates host children nodes each time, retrieving iterator's next value. Lifecycle of these components are closely related to its host nodes, and generator function provides a way to manage them.
42
42
 
43
43
  ```jsx
44
- /** @jsx createElement */
45
- import { render, createElement, createComponent } from 'ajo'
46
-
47
- const Counter = createComponent(function* () {
48
- let count = 1
49
-
50
- const increment = () => {
51
- count++
52
- this.update()
53
- }
54
-
55
- for ({} of this) yield (
56
- <button onclick={increment}>
57
- Current: {count}
58
- </button>
59
- )
44
+ /** @jsx h */
45
+ import { h, component, render, refresh } from 'ajo'
46
+
47
+ const Counter = component(({ start = 0 }, host) => {
48
+ let count = start
49
+
50
+ const increment = () => {
51
+ count++
52
+ refresh(host)
53
+ }
54
+
55
+ return () =>
56
+ <button onclick={increment}>
57
+ Current: {count}
58
+ </button>
60
59
  })
61
60
 
62
- render(<Counter />, document.body)
61
+ render(document.body, <Counter start={1} />)
63
62
  ```
package/cjs/index.js DELETED
@@ -1,224 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __export = (target, all) => {
6
- for (var name in all)
7
- __defProp(target, name, { get: all[name], enumerable: true });
8
- };
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
- var ajo_exports = {};
19
- __export(ajo_exports, {
20
- Fragment: () => Fragment,
21
- Portal: () => Portal,
22
- Skip: () => Skip,
23
- consume: () => consume,
24
- createComponent: () => createComponent,
25
- createElement: () => createElement,
26
- createTextNode: () => createTextNode,
27
- isElement: () => isElement,
28
- provide: () => provide,
29
- render: () => render
30
- });
31
- module.exports = __toCommonJS(ajo_exports);
32
- const { isArray } = Array;
33
- const { hasOwn, keys } = Object;
34
- const Skip = Symbol();
35
- const Fragment = ({ children }) => children;
36
- const Portal = ({ host, children }) => render(host, children);
37
- const Element = Symbol();
38
- const createTextNode = (data) => ({ [Element]: "#text", data, skip: true });
39
- const createElement = (name, props, ...children) => {
40
- props = { ...props, [Element]: name };
41
- children = props.children ?? children;
42
- if (children.length > 0)
43
- props.children = children.length === 1 ? children[0] : children;
44
- return props;
45
- };
46
- const isElement = (el) => hasOwn(el ?? {}, Element);
47
- function* g(vnode, host) {
48
- const vnodes = isArray(vnode) ? vnode : [vnode];
49
- let data = "";
50
- while (vnodes.length > 0) {
51
- vnode = vnodes.shift();
52
- if (vnode == null || typeof vnode === "boolean")
53
- continue;
54
- if (typeof vnode === "string")
55
- data += vnode;
56
- else if (isElement(vnode)) {
57
- const { [Element]: name } = vnode;
58
- if (typeof name === "function") {
59
- vnodes.unshift(name(vnode, host));
60
- continue;
61
- }
62
- if (data) {
63
- yield createTextNode(data);
64
- data = "";
65
- }
66
- yield vnode;
67
- } else
68
- typeof vnode[Symbol.iterator] === "function" ? vnodes.unshift(...vnode) : data += vnode;
69
- }
70
- if (data)
71
- yield createTextNode(data);
72
- }
73
- const render = (vnode, host) => {
74
- let child = host.firstChild;
75
- for (const { [Element]: name, ref, skip, children, ...props } of g(vnode, host)) {
76
- if (name === Skip) {
77
- child = props.end ? null : child?.nextSibling ?? null;
78
- continue;
79
- }
80
- let node = child;
81
- while (node != null) {
82
- if (node.nodeName.toLowerCase() === name && node.getAttribute?.("key") == props.key)
83
- break;
84
- node = node.nextSibling;
85
- }
86
- if (node == null)
87
- node = name === "#text" ? document.createTextNode("") : document.createElement(name);
88
- patch(props, node);
89
- if (typeof ref === "function")
90
- ref(node);
91
- else if (typeof ref === "object" && ref !== null)
92
- ref.current = node;
93
- skip || render(children, node);
94
- if (node === child)
95
- child = child.nextSibling;
96
- else if (node.contains?.(document.activeElement)) {
97
- const { nextSibling } = node;
98
- let ref2 = child;
99
- while (ref2 != null && ref2 !== node) {
100
- const next = ref2.nextSibling;
101
- host.insertBefore(ref2, nextSibling);
102
- ref2 = next;
103
- }
104
- } else
105
- host.insertBefore(node, child);
106
- }
107
- while (child != null) {
108
- const { nextSibling } = child;
109
- if (child.nodeType === Node.ELEMENT_NODE)
110
- dispose(child);
111
- host.removeChild(child);
112
- child = nextSibling;
113
- }
114
- };
115
- const patch = (props, node) => {
116
- if (node.nodeType === Node.TEXT_NODE) {
117
- if (node.data !== props.data)
118
- node.data = props.data;
119
- return;
120
- }
121
- for (const name of /* @__PURE__ */ new Set([...node.getAttributeNames(), ...keys(props)])) {
122
- let value = props[name];
123
- if (name in node && !(typeof value === "string" && typeof node[name] === "boolean")) {
124
- try {
125
- if (node[name] !== value)
126
- node[name] = value;
127
- continue;
128
- } catch (err) {
129
- }
130
- }
131
- if (value === true)
132
- value = "";
133
- else if (value == null || value === false) {
134
- node.removeAttribute(name);
135
- continue;
136
- }
137
- if (node.getAttribute(name) !== value)
138
- node.setAttribute(name, value);
139
- }
140
- };
141
- const components = /* @__PURE__ */ new WeakMap();
142
- const createComponent = (fn) => ({ is, host, key, ...props }) => createElement(is ?? fn.is ?? "div", {
143
- ...fn.host,
144
- ...host,
145
- key,
146
- skip: true,
147
- ref: (host2) => {
148
- let cmp = components.get(host2);
149
- if (cmp == null)
150
- components.set(host2, cmp = new Component(host2, fn));
151
- cmp.props = { ...fn.props, ...props };
152
- cmp.update();
153
- }
154
- });
155
- class Component {
156
- constructor(host, fn) {
157
- this.host = host;
158
- this.fn = fn;
159
- this.props = null;
160
- this.it = null;
161
- this.err = false;
162
- }
163
- update({ method = "next", arg } = {}) {
164
- if (this.host == null)
165
- return;
166
- if (this.err)
167
- try {
168
- this.it.return();
169
- } catch (e) {
170
- this.host = null;
171
- } finally {
172
- return;
173
- }
174
- try {
175
- if (this.it == null) {
176
- const init = this.fn.call(this, this.props, this.host);
177
- this.it = typeof init?.next === "function" ? init : generate.call(this, init);
178
- }
179
- const { value, done } = this.it[method](arg);
180
- render(value, this.host);
181
- if (done)
182
- this.host = null;
183
- } catch (err) {
184
- this.err = true;
185
- propagate(this.host.parentNode, err);
186
- }
187
- }
188
- *[Symbol.iterator]() {
189
- while (this.host)
190
- yield this.props;
191
- }
192
- }
193
- function* generate(init) {
194
- yield init;
195
- for (const props of this)
196
- yield this.fn.call(this, props, this.host);
197
- }
198
- const propagate = (el, err) => {
199
- if (el == null)
200
- throw err;
201
- const cmp = components.get(el);
202
- typeof cmp?.it?.throw === "function" ? cmp.update({ method: "throw", arg: err }) : propagate(el.parentNode, err);
203
- };
204
- const dispose = (el) => {
205
- for (const child of el.children)
206
- dispose(child);
207
- components.get(el)?.update({ method: "return" });
208
- };
209
- const provisions = /* @__PURE__ */ new WeakMap();
210
- const provide = (host, key, value) => {
211
- let map = provisions.get(host);
212
- if (map == null)
213
- provisions.set(host, map = /* @__PURE__ */ new Map());
214
- map.set(key, value);
215
- };
216
- const consume = (host, key) => {
217
- for (let node = host; node != null; node = node.parentNode) {
218
- if (provisions.has(node)) {
219
- const map = provisions.get(node);
220
- if (map.has(key))
221
- return map.get(key);
222
- }
223
- }
224
- };