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.
Files changed (3) hide show
  1. package/index.js +43 -33
  2. package/index.test.js +40 -0
  3. 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
- children = length == 0 ? null : length == 1 ? children[0] : children
10
- return { children, ...props, [Element]: name }
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 = Array.isArray(vnode) ? vnode : [vnode]
15
- let textContent = ''
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 == 'boolean') continue
21
- if (typeof vnode == 'string') textContent += vnode
22
- else if (Object.hasOwn(vnode, Element)) {
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 == 'function') {
28
+ if (typeof name === 'function') {
26
29
  vnodes.unshift(name(vnode, host))
27
30
  continue
28
31
  }
29
32
 
30
- if (textContent) {
31
- yield { [Element]: '#text', textContent }
32
- textContent = ''
33
+ if (data) {
34
+ yield { [Element]: '#text', data }
35
+ data = ''
33
36
  }
34
37
 
35
38
  yield vnode
36
- } else typeof vnode[Symbol.iterator] == 'function'
39
+ } else typeof vnode[Symbol.iterator] === 'function'
37
40
  ? vnodes.unshift(...vnode)
38
- : textContent += vnode
41
+ : data += vnode
39
42
  }
40
43
 
41
- if (textContent) yield { [Element]: '#text', textContent }
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 == Skip) {
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 (String(node.nodeName).toLowerCase() === name && node.getAttribute?.('key') == props.key) break
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 == '#text'
64
+ if (node == null) node = name === '#text'
62
65
  ? document.createTextNode('')
63
66
  : document.createElement(name)
64
67
 
65
- patch(props, node)
66
-
67
- if (typeof ref == 'function') ref(node)
68
-
69
- if (children != null) render(children, node)
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 == child) child = child.nextSibling
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 != node) {
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 == 1) dispose(child)
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([...(node.getAttributeNames?.() ?? []), ...Object.keys(props)])) {
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 == 'string' && typeof node[name] == 'boolean')) {
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 components = new WeakMap()
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
- if (typeof ref == 'function') ref(host)
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 == 'function' ? init : generate(init, this)
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, ctx) {
170
+ function* generate(init) {
161
171
  yield init
162
- for (const props of ctx) yield ctx.fn.call(ctx, props, ctx.host)
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 == 'function'
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.2",
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": "echo \"Error: no test specified\" && exit 1"
22
+ "test": "uvu"
19
23
  },
20
24
  "readme": "# ajo\najo is a JavaScript view library for building user interfaces"
21
25
  }