ajo 0.0.1

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.
Files changed (3) hide show
  1. package/index.js +193 -0
  2. package/package.json +20 -0
  3. package/readme.md +2 -0
package/index.js ADDED
@@ -0,0 +1,193 @@
1
+ export const Skip = Symbol()
2
+ export const Fragment = ({ children }) => children
3
+ export const Portal = ({ host, children }) => render(host, children)
4
+
5
+ const Element = Symbol()
6
+
7
+ export const createElement = (name, props, ...children) => {
8
+ const { length } = children
9
+ children = length == 0 ? null : length == 1 ? children[0] : children
10
+ return { children, ...props, [Element]: name }
11
+ }
12
+
13
+ function* g(vnode, host) {
14
+ const vnodes = Array.isArray(vnode) ? vnode : [vnode]
15
+ let textContent = ''
16
+
17
+ while (vnodes.length > 0) {
18
+ vnode = vnodes.shift()
19
+
20
+ if (vnode == null || typeof vnode == 'boolean') continue
21
+ if (typeof vnode == 'string') textContent += vnode
22
+ else if (Object.hasOwn(vnode, Element)) {
23
+ const { [Element]: name } = vnode
24
+
25
+ if (typeof name == 'function') {
26
+ vnodes.unshift(name(vnode, host))
27
+ continue
28
+ }
29
+
30
+ if (textContent) {
31
+ yield { [Element]: '#text', textContent }
32
+ textContent = ''
33
+ }
34
+
35
+ yield vnode
36
+ } else typeof vnode[Symbol.iterator] == 'function'
37
+ ? vnodes.unshift(...vnode)
38
+ : textContent += vnode
39
+ }
40
+
41
+ if (textContent) yield { [Element]: '#text', textContent }
42
+ }
43
+
44
+ export const render = (vnode, host) => {
45
+ let child = host.firstChild
46
+
47
+ for (const { [Element]: name, ref, children, ...props } of g(vnode, host)) {
48
+
49
+ if (name == Skip) {
50
+ child = props.end ? null : child?.nextSibling ?? null
51
+ continue
52
+ }
53
+
54
+ let node = child
55
+
56
+ while (node != null) {
57
+ if (String(node.nodeName).toLowerCase() === name && node.getAttribute?.('key') == props.key) break
58
+ node = node.nextSibling
59
+ }
60
+
61
+ if (node == null) node = name == '#text'
62
+ ? document.createTextNode('')
63
+ : document.createElement(name)
64
+
65
+ patch(props, node)
66
+
67
+ if (typeof ref == 'function') ref(node)
68
+
69
+ if (children != null) render(children, node)
70
+
71
+ if (node == child) child = child.nextSibling
72
+ else if (node.contains?.(document.activeElement)) {
73
+ const { nextSibling } = node
74
+ let ref = child
75
+
76
+ while (ref != null && ref != node) {
77
+ const next = ref.nextSibling
78
+ host.insertBefore(ref, nextSibling)
79
+ ref = next
80
+ }
81
+ } else host.insertBefore(node, child)
82
+ }
83
+
84
+ while (child != null) {
85
+ const { nodeType, nextSibling } = child
86
+ if (nodeType == 1) dispose(child)
87
+ host.removeChild(child)
88
+ child = nextSibling
89
+ }
90
+ }
91
+
92
+ const patch = (props, node) => {
93
+ for (const name of new Set([...(node.getAttributeNames?.() ?? []), ...Object.keys(props)])) {
94
+ let value = props[name]
95
+
96
+ if (name in node && !(typeof value == 'string' && typeof node[name] == 'boolean')) {
97
+ try {
98
+ if (node[name] !== value) node[name] = value
99
+ continue
100
+ } catch (err) { }
101
+ }
102
+
103
+ if (value === true) value = ''
104
+ else if (value == null || value === false) {
105
+ node.removeAttribute(name)
106
+ continue
107
+ }
108
+
109
+ if (node.getAttribute(name) !== value) node.setAttribute(name, value)
110
+ }
111
+ }
112
+
113
+ const components = new WeakMap()
114
+
115
+ export const createComponent = fn => ({ is, host, key, ref, ...props }) =>
116
+ createElement(is ?? fn.is ?? 'div', {
117
+ ...fn.host, ...host, key, ['ref']: host => {
118
+ let cmp = components.get(host)
119
+ if (cmp == null) components.set(host, cmp = new Component(host, fn))
120
+ cmp.props = { ...fn.props, ...props }
121
+ cmp.update()
122
+ if (typeof ref == 'function') ref(host)
123
+ }
124
+ })
125
+
126
+ class Component {
127
+ constructor(host, fn) {
128
+ this.host = host
129
+ this.fn = fn
130
+ this.props = null
131
+ this.it = null
132
+ this.err = false
133
+ }
134
+
135
+ update({ method = 'next', arg } = {}) {
136
+ if (this.host == null) return
137
+ if (this.err) try { this.it.return() } catch (e) { this.host = null } finally { return }
138
+
139
+ try {
140
+ if (this.it == null) {
141
+ const init = this.fn.call(this, this.props, this.host)
142
+ this.it = typeof init?.next == 'function' ? init : generate(init, this)
143
+ }
144
+
145
+ const { value, done } = this.it[method](arg)
146
+
147
+ render(value, this.host)
148
+ if (done) this.host = null
149
+ } catch (err) {
150
+ this.err = true
151
+ propagate(this.host.parentNode, err)
152
+ }
153
+ }
154
+
155
+ *[Symbol.iterator]() {
156
+ while (true) yield this.props
157
+ }
158
+ }
159
+
160
+ function* generate(init, ctx) {
161
+ yield init
162
+ for (const props of ctx) yield ctx.fn.call(ctx, props, ctx.host)
163
+ }
164
+
165
+ const propagate = (el, err) => {
166
+ if (el == null) throw err
167
+ const cmp = components.get(el)
168
+ typeof cmp?.it?.throw == 'function'
169
+ ? cmp.update({ method: 'throw', arg: err })
170
+ : propagate(el.parentNode, err)
171
+ }
172
+
173
+ const dispose = el => {
174
+ for (const child of el.children) dispose(child)
175
+ components.get(el)?.update({ method: 'return' })
176
+ }
177
+
178
+ const provisions = new WeakMap
179
+
180
+ export const provide = (host, key, value) => {
181
+ let map = provisions.get(host)
182
+ if (map == null) provisions.set(host, map = new Map)
183
+ map.set(key, value)
184
+ }
185
+
186
+ export const consume = (host, key) => {
187
+ for (let node = host; node != null; node = node.parentNode) {
188
+ if (provisions.has(node)) {
189
+ const map = provisions.get(node)
190
+ if (map.has(key)) return map.get(key)
191
+ }
192
+ }
193
+ }
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "ajo",
3
+ "version": "0.0.1",
4
+ "description": "ajo is a JavaScript view library for building user interfaces",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/cristianfalcone/ajo.git"
9
+ },
10
+ "author": "Cristian Falcone",
11
+ "license": "ISC",
12
+ "bugs": {
13
+ "url": "https://github.com/cristianfalcone/ajo/issues"
14
+ },
15
+ "homepage": "https://github.com/cristianfalcone/ajo#readme",
16
+ "scripts": {
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ },
19
+ "readme": "# ajo\najo is a JavaScript view library for building user interfaces"
20
+ }
package/readme.md ADDED
@@ -0,0 +1,2 @@
1
+ # ajo
2
+ ajo is a JavaScript view library for building user interfaces