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.
- package/index.js +193 -0
- package/package.json +20 -0
- 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