ajo 0.0.2 → 0.0.3
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 +43 -33
- package/index.test.js +40 -0
- package/package.json +7 -3
package/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const { isArray } = Array
|
|
2
|
+
const { hasOwn, keys } = Object
|
|
3
|
+
|
|
1
4
|
export const Skip = Symbol()
|
|
2
5
|
export const Fragment = ({ children }) => children
|
|
3
6
|
export const Portal = ({ host, children }) => render(host, children)
|
|
@@ -6,39 +9,39 @@ const Element = Symbol()
|
|
|
6
9
|
|
|
7
10
|
export const createElement = (name, props, ...children) => {
|
|
8
11
|
const { length } = children
|
|
9
|
-
|
|
10
|
-
return {
|
|
12
|
+
if (length > 0) (props ?? (props = {})).children = length === 1 ? children[0] : children
|
|
13
|
+
return { ...props, [Element]: name }
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
function* g(vnode, host) {
|
|
14
|
-
const vnodes =
|
|
15
|
-
let
|
|
17
|
+
const vnodes = isArray(vnode) ? vnode : [vnode]
|
|
18
|
+
let data = ''
|
|
16
19
|
|
|
17
20
|
while (vnodes.length > 0) {
|
|
18
21
|
vnode = vnodes.shift()
|
|
19
22
|
|
|
20
|
-
if (vnode == null || typeof vnode
|
|
21
|
-
if (typeof vnode
|
|
22
|
-
else if (
|
|
23
|
+
if (vnode == null || typeof vnode === 'boolean') continue
|
|
24
|
+
if (typeof vnode === 'string') data += vnode
|
|
25
|
+
else if (hasOwn(vnode, Element)) {
|
|
23
26
|
const { [Element]: name } = vnode
|
|
24
27
|
|
|
25
|
-
if (typeof name
|
|
28
|
+
if (typeof name === 'function') {
|
|
26
29
|
vnodes.unshift(name(vnode, host))
|
|
27
30
|
continue
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
if (
|
|
31
|
-
yield { [Element]: '#text',
|
|
32
|
-
|
|
33
|
+
if (data) {
|
|
34
|
+
yield { [Element]: '#text', data }
|
|
35
|
+
data = ''
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
yield vnode
|
|
36
|
-
} else typeof vnode[Symbol.iterator]
|
|
39
|
+
} else typeof vnode[Symbol.iterator] === 'function'
|
|
37
40
|
? vnodes.unshift(...vnode)
|
|
38
|
-
:
|
|
41
|
+
: data += vnode
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
if (
|
|
44
|
+
if (data) yield { [Element]: '#text', data }
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export const render = (vnode, host) => {
|
|
@@ -46,7 +49,7 @@ export const render = (vnode, host) => {
|
|
|
46
49
|
|
|
47
50
|
for (const { [Element]: name, ref, children, ...props } of g(vnode, host)) {
|
|
48
51
|
|
|
49
|
-
if (name
|
|
52
|
+
if (name === Skip) {
|
|
50
53
|
child = props.end ? null : child?.nextSibling ?? null
|
|
51
54
|
continue
|
|
52
55
|
}
|
|
@@ -54,26 +57,28 @@ export const render = (vnode, host) => {
|
|
|
54
57
|
let node = child
|
|
55
58
|
|
|
56
59
|
while (node != null) {
|
|
57
|
-
if (
|
|
60
|
+
if (node.nodeName.toLowerCase() === name && node.getAttribute?.('key') == props.key) break
|
|
58
61
|
node = node.nextSibling
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
if (node == null) node = name
|
|
64
|
+
if (node == null) node = name === '#text'
|
|
62
65
|
? document.createTextNode('')
|
|
63
66
|
: document.createElement(name)
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
if (name === '#text') {
|
|
69
|
+
if (node.data !== props.data) node.data = props.data
|
|
70
|
+
} else {
|
|
71
|
+
patch(props, node)
|
|
72
|
+
setRef(ref, node)
|
|
73
|
+
render(children, node)
|
|
74
|
+
}
|
|
70
75
|
|
|
71
|
-
if (node
|
|
76
|
+
if (node === child) child = child.nextSibling
|
|
72
77
|
else if (node.contains?.(document.activeElement)) {
|
|
73
78
|
const { nextSibling } = node
|
|
74
79
|
let ref = child
|
|
75
80
|
|
|
76
|
-
while (ref != null && ref
|
|
81
|
+
while (ref != null && ref !== node) {
|
|
77
82
|
const next = ref.nextSibling
|
|
78
83
|
host.insertBefore(ref, nextSibling)
|
|
79
84
|
ref = next
|
|
@@ -83,17 +88,17 @@ export const render = (vnode, host) => {
|
|
|
83
88
|
|
|
84
89
|
while (child != null) {
|
|
85
90
|
const { nodeType, nextSibling } = child
|
|
86
|
-
if (nodeType
|
|
91
|
+
if (nodeType === 1) dispose(child)
|
|
87
92
|
host.removeChild(child)
|
|
88
93
|
child = nextSibling
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
const patch = (props, node) => {
|
|
93
|
-
for (const name of new Set([...
|
|
98
|
+
for (const name of new Set([...node.getAttributeNames(), ...keys(props)])) {
|
|
94
99
|
let value = props[name]
|
|
95
100
|
|
|
96
|
-
if (name in node && !(typeof value
|
|
101
|
+
if (name in node && !(typeof value === 'string' && typeof node[name] === 'boolean')) {
|
|
97
102
|
try {
|
|
98
103
|
if (node[name] !== value) node[name] = value
|
|
99
104
|
continue
|
|
@@ -110,7 +115,12 @@ const patch = (props, node) => {
|
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
const
|
|
118
|
+
const setRef = (ref, node) => {
|
|
119
|
+
if (typeof ref === 'function') ref(node)
|
|
120
|
+
else if (typeof ref === 'object' && ref !== null) ref.current = node
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const components = new WeakMap
|
|
114
124
|
|
|
115
125
|
export const createComponent = fn => ({ is, host, key, ref, ...props }) =>
|
|
116
126
|
createElement(is ?? fn.is ?? 'div', {
|
|
@@ -119,7 +129,7 @@ export const createComponent = fn => ({ is, host, key, ref, ...props }) =>
|
|
|
119
129
|
if (cmp == null) components.set(host, cmp = new Component(host, fn))
|
|
120
130
|
cmp.props = { ...fn.props, ...props }
|
|
121
131
|
cmp.update()
|
|
122
|
-
|
|
132
|
+
setRef(ref, host)
|
|
123
133
|
}
|
|
124
134
|
})
|
|
125
135
|
|
|
@@ -139,7 +149,7 @@ class Component {
|
|
|
139
149
|
try {
|
|
140
150
|
if (this.it == null) {
|
|
141
151
|
const init = this.fn.call(this, this.props, this.host)
|
|
142
|
-
this.it = typeof init?.next
|
|
152
|
+
this.it = typeof init?.next === 'function' ? init : generate.call(this, init)
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
const { value, done } = this.it[method](arg)
|
|
@@ -157,15 +167,15 @@ class Component {
|
|
|
157
167
|
}
|
|
158
168
|
}
|
|
159
169
|
|
|
160
|
-
function* generate(init
|
|
170
|
+
function* generate(init) {
|
|
161
171
|
yield init
|
|
162
|
-
for (const props of
|
|
172
|
+
for (const props of this) yield this.fn.call(this, props, this.host)
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
const propagate = (el, err) => {
|
|
166
176
|
if (el == null) throw err
|
|
167
177
|
const cmp = components.get(el)
|
|
168
|
-
typeof cmp?.it?.throw
|
|
178
|
+
typeof cmp?.it?.throw === 'function'
|
|
169
179
|
? cmp.update({ method: 'throw', arg: err })
|
|
170
180
|
: propagate(el.parentNode, err)
|
|
171
181
|
}
|
package/index.test.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import 'backdom/register.js'
|
|
2
|
+
import { suite } from 'uvu'
|
|
3
|
+
import * as assert from 'uvu/assert'
|
|
4
|
+
import { createElement, render, } from './index.js'
|
|
5
|
+
|
|
6
|
+
let it
|
|
7
|
+
|
|
8
|
+
// ----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
it = suite('createElement')
|
|
11
|
+
|
|
12
|
+
it('should create empty vnode', () => {
|
|
13
|
+
assert.equal(createElement('div'), {})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should create vnode with props', () => {
|
|
17
|
+
assert.equal(createElement('div', { id: 'app' }), { id: 'app' })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should create vnode with one string child', () => {
|
|
21
|
+
assert.equal(createElement('div', null, 'foo'), { children: 'foo' })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should create vnode with one vnode child', () => {
|
|
25
|
+
assert.equal(createElement('div', null, createElement('span')), { children: {} })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it.run()
|
|
29
|
+
|
|
30
|
+
// ----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
it = suite('render')
|
|
33
|
+
|
|
34
|
+
it('should render a vnode', () => {
|
|
35
|
+
const host = document.createElement('div')
|
|
36
|
+
render(createElement('div', { foo: 'bar' }, 'foobar'), host)
|
|
37
|
+
assert.snapshot(host.innerHTML, '<div foo="bar">foobar</div>')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it.run()
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "ajo is a JavaScript view library for building user interfaces",
|
|
5
|
-
"main": "index.js",
|
|
6
5
|
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/cristianfalcone/ajo.git"
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
"url": "https://github.com/cristianfalcone/ajo/issues"
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://github.com/cristianfalcone/ajo#readme",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"backdom": "^0.0.2",
|
|
19
|
+
"uvu": "^0.5.3"
|
|
20
|
+
},
|
|
17
21
|
"scripts": {
|
|
18
|
-
"test": "
|
|
22
|
+
"test": "uvu"
|
|
19
23
|
},
|
|
20
24
|
"readme": "# ajo\najo is a JavaScript view library for building user interfaces"
|
|
21
25
|
}
|