ajo 0.0.14 → 0.0.17

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 CHANGED
@@ -17,6 +17,7 @@ var __copyProps = (to, from2, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
  var ajo_exports = {};
19
19
  __export(ajo_exports, {
20
+ For: () => For,
20
21
  Fragment: () => Fragment,
21
22
  cleanup: () => cleanup,
22
23
  clx: () => clx,
@@ -32,140 +33,156 @@ __export(ajo_exports, {
32
33
  stx: () => stx
33
34
  });
34
35
  module.exports = __toCommonJS(ajo_exports);
35
- const Fragment = ({ children }) => children, h = (nodeName, props, ...children) => {
36
- children = children.length == 0 ? null : children.length == 1 ? children[0] : children;
37
- return { children, ...props, nodeName };
38
- }, component = (setup) => ({ is, key, host, ref, ...props }) => h(is ?? setup.is ?? "div", {
39
- key,
40
- ...setup.host,
41
- ...host,
36
+ var import_element_notifier = require("element-notifier");
37
+ const Fragment = ({ children }) => children, For = ({ is, each, by, children, ref: ref2, ...props }) => h(is ?? "div", {
38
+ ...props,
42
39
  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 == "string") {
48
- for (node = child; node; node = node.nextSibling)
40
+ ref: (host, root) => iterate(host, root, each, by, children, ref2)
41
+ }), h = (nodeName, props, ...children) => ({ children: children.length == 0 ? null : children.length == 1 ? children[0] : children, ...props, nodeName }), render = (h2, host, to, root = host) => {
42
+ let child = host.firstChild, node;
43
+ root == host && (root.$mo ??= (0, import_element_notifier.notify)((host2, connected) => connected || root.contains(host2) || dispose(host2), root));
44
+ for (h2 of normalize(h2)) {
45
+ if (h2 instanceof Node)
46
+ node = h2;
47
+ else if (typeof h2 == "string") {
48
+ for (node = child; node != to; node = node.nextSibling)
49
49
  if (node.nodeType == 3)
50
50
  break;
51
- node ? node.data !== h2 && (node.data = h2) : node = document.createTextNode(h2);
52
- } else if (h2 instanceof Node) {
53
- node = h2;
51
+ node == to ? node = document.createTextNode(h2) : node.data != h2 && (node.data = h2);
54
52
  } else {
55
- const { key, nodeName, skip, block, children, ref, ...props } = h2;
56
- if (key != null && (node = byKey?.get(key)))
57
- ;
58
- else
59
- for (node = child; node; node = node.nextSibling)
60
- if (node.localName == nodeName)
61
- break;
62
- node ||= document.createElement(nodeName);
63
- key != null && (byKey ||= keyed.set(host, /* @__PURE__ */ new Map())).set(key, node);
64
- update(props, node);
65
- !(skip || block != null && every(deps.get(node), deps.set(node, block))) && render(children, node);
66
- isFn(ref) && ref(node);
53
+ const { nodeName, block, skip, children, ref: ref2, ...props } = h2;
54
+ for (node = child; node != to; node = node.nextSibling)
55
+ if (node.localName == nodeName)
56
+ break;
57
+ node == to && (node = document.createElement(nodeName));
58
+ if (block == null || some(node.$deps, node.$deps = block)) {
59
+ update(props, node);
60
+ skip || render(children, node, null, root);
61
+ isFunction(ref2) && ref2(node, root);
62
+ }
67
63
  }
68
- node === child ? child = child.nextSibling : before(host, child, node);
64
+ node == child ? child = child.nextSibling : before(host, node, child);
69
65
  }
70
- while (child) {
66
+ while (child != to) {
71
67
  const next = child.nextSibling;
72
- host.removeChild(child).nodeType == 1 && dispose(child);
68
+ host.removeChild(child);
73
69
  child = next;
74
70
  }
75
- }, refresh = (host, props, setup) => {
71
+ }, component = (setup) => ({ is, props, ref: ref2, ...params }) => h(is ?? setup.is ?? "div", {
72
+ ...setup.props,
73
+ ...props,
74
+ skip: true,
75
+ ref: (host, root) => run(host, root, setup, params, ref2)
76
+ }), refresh = (host) => {
76
77
  try {
77
- if (setup && !isFn(setup))
78
- throwTypeError("Setup", setup, fn);
79
- props = props ? memo.set(host, { ...setup?.props, ...props }) : memo.get(host) ?? {};
80
- render((renders.get(host) ?? renders.set(host, setup(props, host)))(props, host), host);
78
+ render((host.$render ??= host.$setup(host))(host.$params), host, null, host.$root);
81
79
  } catch (error) {
82
80
  propagate(host, error);
83
81
  }
84
- }, provide = (host, key, value) => (provisions.get(host) ?? provisions.set(host, /* @__PURE__ */ new Map())).set(key, value), consume = (host, key, fallback) => {
85
- let map;
86
- while (host) {
87
- if ((map = provisions.get(host)) && map.has(key))
88
- return map.get(key);
89
- host = host.parentNode;
90
- }
82
+ }, provide = (host, key, value) => (host.$provisions ??= /* @__PURE__ */ new Map()).set(key, value), consume = (host, key, fallback) => {
83
+ for (let map2; host; host = host.parentNode)
84
+ if ((map2 = host.$provisions) && map2.has(key))
85
+ return map2.get(key);
91
86
  return fallback;
92
- }, intercept = (host, interceptor) => {
93
- if (!isFn(interceptor))
94
- throwTypeError("Interceptor", interceptor, fn);
95
- interceptors.set(host, interceptor);
96
- }, propagate = (host, error) => {
97
- for (let interceptor; host; host = host.parentNode)
98
- if (interceptor = interceptors.get(host))
99
- return render(interceptor(error), host);
87
+ }, intercept = (host, fn) => isFunction(fn) && (host.$interceptor = fn), propagate = (host, error) => {
88
+ for (let fn; host; host = host.parentNode)
89
+ if (isFunction(fn = host.$interceptor))
90
+ return render(fn(error));
100
91
  throw error;
101
- }, cleanup = (host, cleaner) => {
102
- if (!isFn(cleaner))
103
- throwTypeError("Cleaner", cleaner, fn);
104
- (cleaners.get(host) ?? cleaners.set(host, /* @__PURE__ */ new Set())).add(cleaner);
105
- }, 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), {});
106
- const wm = () => {
107
- const instance = /* @__PURE__ */ new WeakMap(), { 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, { keys, entries } = Object, { isArray, from } = Array, fn = "function", search = /([a-z0-9])([A-Z])/g, replace = "$1-$2", keyed = wm(), deps = wm(), memo = wm(), renders = wm(), provisions = wm(), interceptors = wm(), cleaners = wm(), cache = 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 (type == "string" || type == "number") {
118
- buffer += h2;
119
- continue;
120
- }
121
- if ("nodeName" in Object(h2)) {
122
- if (isFn(h2.nodeName)) {
123
- yield* normalize(h2.nodeName(h2, host), host);
124
- continue;
125
- }
126
- if (buffer) {
127
- yield buffer;
128
- buffer = "";
129
- }
130
- yield h2;
92
+ }, cleanup = (host, fn) => isFunction(fn) && (host.$cleanups ??= /* @__PURE__ */ new Set()).add(fn), 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), {});
93
+ const { isArray, from } = Array, { keys, entries } = Object, isFunction = (v) => typeof v == "function", noop = () => {
94
+ }, ref = (v) => (...args) => args.length ? v = args[0] : v, map = (list) => list.reduce(set, /* @__PURE__ */ new Map()), set = (m, v, i) => (m.set(v, i), m), some = (a, b) => isArray(a) && isArray(b) ? a.some((v, i) => v !== b[i]) : a !== b, reduce = (v) => from(v).reduce(assign, {}), assign = (v, { name, value }) => (v[name] = value, v), proxy = { firstChild: null, insertBefore: (node) => proxy.firstChild ??= node }, search = /([a-z0-9])([A-Z])/g, replace = "$1-$2", normalize = function* (h2, buffer = ref(""), root = true) {
95
+ let text;
96
+ for (h2 of isArray(h2) ? h2 : [h2]) {
97
+ if (h2 == null || typeof h2 == "boolean")
131
98
  continue;
132
- }
133
- isFn(h2[Symbol.iterator]) ? yield* normalize(h2, host) : buffer += h2;
99
+ if (typeof h2.nodeName == "string")
100
+ (text = buffer()) && (buffer(""), yield text), yield h2;
101
+ else if (isFunction(h2.nodeName))
102
+ yield* normalize(h2.nodeName(h2), buffer, false);
103
+ else
104
+ isArray(h2) ? yield* normalize(h2, buffer, false) : buffer(buffer() + h2);
134
105
  }
135
- if (buffer)
136
- yield buffer;
106
+ root && (text = buffer()) && (yield text);
137
107
  }, update = (props, host) => {
138
- const prev = cache.get(host) ?? (host.hasAttributes() ? reduce(host.attributes) : {});
108
+ const prev = host.$props ?? (host.hasAttributes() ? reduce(host.attributes) : {});
139
109
  for (const name in { ...prev, ...props }) {
140
110
  let value = props[name];
141
- if (value === prev[name])
142
- continue;
143
- if (name.startsWith("set:")) {
144
- host[name.slice(4)] = value;
145
- continue;
146
- }
147
- if (value === true)
148
- value = "";
149
- else if (value == null || value === false) {
150
- host.removeAttribute(name);
151
- continue;
152
- }
153
- host.setAttribute(name, value);
111
+ if (value !== prev[name])
112
+ if (name.startsWith("set:"))
113
+ host[name.slice(4)] = value;
114
+ else if (value == null || value === false)
115
+ host.removeAttribute(name);
116
+ else
117
+ host.setAttribute(name, value === true ? "" : value);
154
118
  }
155
- cache.set(host, props);
156
- }, before = (host, child, node) => {
119
+ host.$props = props;
120
+ }, before = (host, node, child) => {
157
121
  if (node.contains?.(document.activeElement)) {
158
- const ref = node.nextSibling;
122
+ const ref2 = node.nextSibling;
159
123
  while (child && child !== node) {
160
124
  const next = child.nextSibling;
161
- host.insertBefore(child, ref);
125
+ host.insertBefore(child, ref2);
162
126
  child = next;
163
127
  }
164
128
  } else
165
129
  host.insertBefore(node, child);
130
+ }, iterate = (host, root, each, by, fn, ref2) => {
131
+ each = isArray(each) ? each : [];
132
+ by = isFunction(by) ? by : (v) => v;
133
+ fn = isFunction(fn) ? fn : noop;
134
+ const map2 = host.$for ??= /* @__PURE__ */ new Map(), del = (node) => map2.delete(node.$key), clr = each !== host.$each, len = (host.$each = each).length, a = from(host.childNodes), b = new Array(len);
135
+ clr && map2.clear();
136
+ for (let last, index = 0; index < len; index++) {
137
+ const item = each[index], key = by(item, index);
138
+ proxy.firstChild = (clr ? a[index] : map2.get(key)) ?? last?.cloneNode(true);
139
+ render(fn(item), proxy, proxy.firstChild?.nextSibling, root);
140
+ last = proxy.firstChild;
141
+ proxy.firstChild = null;
142
+ map2.set(last.$key = key, b[index] = last);
143
+ }
144
+ arrange(host, a, b, del);
145
+ isFunction(ref2) && ref2(host, root);
146
+ }, arrange = (host, a, b, dispose2 = noop) => {
147
+ const aLen = a.length, bLen = b.length;
148
+ let aIndex = 0, bIndex = 0, aValue, bValue, aMap, bMap, i;
149
+ while (aIndex !== aLen || bIndex !== bLen) {
150
+ aValue = a[aIndex];
151
+ bValue = b[bIndex];
152
+ if (aValue === null)
153
+ aIndex++;
154
+ else if (bLen <= bIndex)
155
+ aIndex++, dispose2(host.removeChild(aValue));
156
+ else if (aLen <= aIndex)
157
+ bIndex++, host.appendChild(bValue);
158
+ else if (aValue === bValue)
159
+ aIndex++, bIndex++;
160
+ else {
161
+ aMap ??= map(a);
162
+ bMap ??= map(b);
163
+ if (bMap.get(aValue) == null)
164
+ aIndex++, dispose2(host.removeChild(aValue));
165
+ else {
166
+ host.insertBefore(bValue, aValue), bIndex++;
167
+ if ((i = aMap.get(bValue)) != null) {
168
+ if (i > aIndex + 1)
169
+ aIndex++;
170
+ a[i] = null;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }, run = (host, root, setup, params, ref2) => {
176
+ host.$root = root;
177
+ host.$setup ??= isFunction(setup) ? setup : noop;
178
+ host.$params = { ...setup.params, ...params };
179
+ refresh(host);
180
+ isFunction(ref2) && ref2(host, root);
166
181
  }, dispose = (host) => {
167
- for (const child of host.children)
168
- dispose(child);
169
- for (const cleaner of cleaners.get(host) ?? [])
170
- cleaner(host);
182
+ const { $cleanups } = host;
183
+ if ($cleanups) {
184
+ for (let fn of $cleanups)
185
+ fn(host);
186
+ $cleanups.clear();
187
+ }
171
188
  };
package/index.js CHANGED
@@ -1,198 +1,230 @@
1
- export const
2
- Fragment = ({ children }) => children,
1
+ import { notify } from 'element-notifier'
3
2
 
4
- h = (nodeName, props, ...children) => {
5
- children = children.length == 0 ? null : children.length == 1 ? children[0] : children
6
- return { children, ...props, nodeName }
7
- },
3
+ export const
4
+ Fragment = ({ children }) => children,
8
5
 
9
- component = setup => ({ is, key, host, ref, ...props }) => h(is ?? setup.is ?? 'div', {
10
- key, ...setup.host, ...host, skip: true, ref: host => (refresh(host, props, setup), isFn(ref) && ref(host))
11
- }),
6
+ For = ({ is, each, by, children, ref, ...props }) => h(is ?? 'div', {
7
+ ...props, skip: true, ref: (host, root) => iterate(host, root, each, by, children, ref)
8
+ }),
12
9
 
13
- render = (h, host) => {
10
+ h = (nodeName, props, ...children) => ({ children: children.length == 0 ? null : children.length == 1 ? children[0] : children, ...props, nodeName }),
14
11
 
15
- let child = host.firstChild, node, byKey = keyed.get(host)
12
+ render = (h, host, to, root = host) => {
16
13
 
17
- for (h of normalize(h, host)) {
14
+ let child = host.firstChild, node
18
15
 
19
- if (typeof h == 'string') {
16
+ root == host && (root.$mo ??= notify((host, connected) => connected || root.contains(host) || dispose(host), root))
20
17
 
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)
18
+ for (h of normalize(h)) {
23
19
 
24
- } else if (h instanceof Node) {
20
+ if (h instanceof Node) node = h
25
21
 
26
- node = h
22
+ else if (typeof h == 'string') {
27
23
 
28
- } else {
24
+ for (node = child; node != to; node = node.nextSibling) if (node.nodeType == 3) break
25
+ node == to ? node = document.createTextNode(h) : node.data != h && (node.data = h)
29
26
 
30
- const { key, nodeName, skip, block, children, ref, ...props } = h
27
+ } else {
31
28
 
32
- if (key != null && (node = byKey?.get(key)));
33
- else for (node = child; node; node = node.nextSibling) if (node.localName == nodeName) break
29
+ const { nodeName, block, skip, children, ref, ...props } = h
34
30
 
35
- node ||= document.createElement(nodeName)
31
+ for (node = child; node != to; node = node.nextSibling) if (node.localName == nodeName) break
32
+ node == to && (node = document.createElement(nodeName))
36
33
 
37
- key != null && (byKey ||= keyed.set(host, new Map)).set(key, node)
34
+ if (block == null || some(node.$deps, node.$deps = block)) {
35
+ update(props, node)
36
+ skip || render(children, node, null, root)
37
+ isFunction(ref) && ref(node, root)
38
+ }
39
+ }
38
40
 
39
- update(props, node)
41
+ node == child ? child = child.nextSibling : before(host, node, child)
42
+ }
40
43
 
41
- !(skip || block != null && every(deps.get(node), deps.set(node, block))) && render(children, node)
44
+ while (child != to) {
45
+ const next = child.nextSibling
46
+ host.removeChild(child)
47
+ child = next
48
+ }
49
+ },
42
50
 
43
- isFn(ref) && ref(node)
44
- }
51
+ component = setup => ({ is, props, ref, ...params }) => h(is ?? setup.is ?? 'div', {
52
+ ...setup.props, ...props, skip: true, ref: (host, root) => run(host, root, setup, params, ref)
53
+ }),
45
54
 
46
- node === child ? child = child.nextSibling : before(host, child, node)
47
- }
55
+ refresh = host => {
56
+ try {
57
+ render((host.$render ??= host.$setup(host))(host.$params), host, null, host.$root)
58
+ } catch (error) {
59
+ propagate(host, error)
60
+ }
61
+ },
48
62
 
49
- while (child) {
50
- const next = child.nextSibling
51
- host.removeChild(child).nodeType == 1 && dispose(child)
52
- child = next
53
- }
54
- },
63
+ provide = (host, key, value) => (host.$provisions ??= new Map).set(key, value),
55
64
 
56
- refresh = (host, props, setup) => {
57
- try {
58
- if (setup && !isFn(setup)) throwTypeError('Setup', setup, fn)
59
- props = props ? memo.set(host, { ...setup?.props, ...props }) : memo.get(host) ?? {}
60
- render((renders.get(host) ?? renders.set(host, setup(props, host)))(props, host), host)
61
- } catch (error) {
62
- propagate(host, error)
63
- }
64
- },
65
+ consume = (host, key, fallback) => {
66
+ for (let map; host; host = host.parentNode) if ((map = host.$provisions) && map.has(key)) return map.get(key)
67
+ return fallback
68
+ },
65
69
 
66
- provide = (host, key, value) => (provisions.get(host) ?? provisions.set(host, new Map)).set(key, value),
70
+ intercept = (host, fn) => isFunction(fn) && (host.$interceptor = fn),
67
71
 
68
- consume = (host, key, fallback) => {
69
- let map
70
- while (host) {
71
- if ((map = provisions.get(host)) && map.has(key)) return map.get(key)
72
- host = host.parentNode
73
- }
74
- return fallback
75
- },
72
+ propagate = (host, error) => {
73
+ for (let fn; host; host = host.parentNode) if (isFunction(fn = host.$interceptor)) return render(fn(error))
74
+ throw error
75
+ },
76
76
 
77
- intercept = (host, interceptor) => {
78
- if (!isFn(interceptor)) throwTypeError('Interceptor', interceptor, fn)
79
- interceptors.set(host, interceptor)
80
- },
77
+ cleanup = (host, fn) => isFunction(fn) && (host.$cleanups ??= new Set).add(fn),
81
78
 
82
- propagate = (host, error) => {
83
- for (let interceptor; host; host = host.parentNode)
84
- if (interceptor = interceptors.get(host)) return render(interceptor(error), host)
85
- throw error
86
- },
79
+ clx = o => keys(o).filter(k => o[k]).join(' ') || null,
87
80
 
88
- cleanup = (host, cleaner) => {
89
- if (!isFn(cleaner)) throwTypeError('Cleaner', cleaner, fn);
90
- (cleaners.get(host) ?? cleaners.set(host, new Set)).add(cleaner)
91
- },
81
+ stx = o => entries(o).map(t => t.join(':')).join(';') || null,
92
82
 
93
- clx = o => keys(o).filter(k => o[k]).join(' ') || null,
94
- stx = o => entries(o).map(t => t.join(':')).join(';') || null,
95
- keb = o => keys(o).reduce((r, k) => ((r[k.replace(search, replace).toLowerCase()] = o[k]), r), {})
83
+ keb = o => keys(o).reduce((r, k) => ((r[k.replace(search, replace).toLowerCase()] = o[k]), r), {})
96
84
 
97
85
  const
98
- wm = () => {
99
- const instance = new WeakMap, { set } = instance
100
- instance.set = (key, value) => (set.call(instance, key, value), value)
101
- return instance
102
- },
86
+ { isArray, from } = Array, { keys, entries } = Object, isFunction = v => typeof v == 'function',
87
+
88
+ noop = () => { }, ref = v => (...args) => args.length ? v = args[0] : v,
89
+
90
+ map = list => list.reduce(set, new Map), set = (m, v, i) => (m.set(v, i), m),
91
+
92
+ some = (a, b) => (isArray(a) && isArray(b)) ? a.some((v, i) => v !== b[i]) : a !== b,
93
+
94
+ reduce = v => from(v).reduce(assign, {}), assign = (v, { name, value }) => ((v[name] = value), v),
95
+
96
+ proxy = { firstChild: null, insertBefore: node => proxy.firstChild ??= node },
97
+
98
+ search = /([a-z0-9])([A-Z])/g, replace = '$1-$2',
99
+
100
+ normalize = function* (h, buffer = ref(''), root = true) {
101
+
102
+ let text
103
+
104
+ for (h of isArray(h) ? h : [h]) {
105
+ if (h == null || typeof h == 'boolean') continue
106
+ if (typeof h.nodeName == 'string') ((text = buffer()) && (buffer(''), yield text)), yield h
107
+ else if (isFunction(h.nodeName)) yield* normalize(h.nodeName(h), buffer, false)
108
+ else isArray(h) ? yield* normalize(h, buffer, false) : buffer(buffer() + h)
109
+ }
110
+
111
+ root && (text = buffer()) && (yield text)
112
+ },
113
+
114
+ update = (props, host) => {
115
+
116
+ const prev = host.$props ?? (host.hasAttributes() ? reduce(host.attributes) : {})
117
+
118
+ for (const name in { ...prev, ...props }) {
119
+
120
+ let value = props[name]
121
+
122
+ if (value !== prev[name])
123
+ if (name.startsWith('set:')) host[name.slice(4)] = value
124
+ else if (value == null || value === false) host.removeAttribute(name)
125
+ else host.setAttribute(name, value === true ? '' : value)
126
+ }
127
+
128
+ host.$props = props
129
+ },
103
130
 
104
- throwTypeError = (name, value, expected) => {
105
- throw new TypeError(`Expected ${name} to be of type ${expected}, got ${typeof value} instead`)
106
- },
131
+ before = (host, node, child) => {
132
+ if (node.contains?.(document.activeElement)) {
107
133
 
108
- every = (a, b) => a === b || isArray(a) && isArray(b) && a.length == b.length && a.every((v, i) => v === b[i]),
109
- apply = (o, { name, value }) => ((o[name] = value), o),
110
- reduce = list => from(list).reduce(apply, {}),
111
- isFn = v => typeof v == fn,
134
+ const ref = node.nextSibling
112
135
 
113
- { keys, entries } = Object, { isArray, from } = Array,
136
+ while (child && child !== node) {
137
+ const next = child.nextSibling
138
+ host.insertBefore(child, ref)
139
+ child = next
140
+ }
114
141
 
115
- fn = 'function', search = /([a-z0-9])([A-Z])/g, replace = '$1-$2',
142
+ } else host.insertBefore(node, child)
143
+ },
116
144
 
117
- keyed = wm(), deps = wm(), memo = wm(), renders = wm(), provisions = wm(), interceptors = wm(), cleaners = wm(), cache = wm(),
145
+ iterate = (host, root, each, by, fn, ref) => {
118
146
 
119
- normalize = function* (h, host) {
147
+ each = isArray(each) ? each : []
148
+ by = isFunction(by) ? by : v => v
149
+ fn = isFunction(fn) ? fn : noop
120
150
 
121
- let type, buffer = ''
151
+ const
152
+ map = host.$for ??= new Map,
153
+ del = node => map.delete(node.$key),
154
+ clr = each !== host.$each,
155
+ len = (host.$each = each).length,
156
+ a = from(host.childNodes),
157
+ b = new Array(len)
122
158
 
123
- for (h of isFn(h?.[Symbol.iterator]) ? h : [h]) {
159
+ clr && map.clear()
124
160
 
125
- if (h == null || (type = typeof h) == 'boolean') continue
161
+ for (let last, index = 0; index < len; index++) {
126
162
 
127
- if (type == 'string' || type == 'number') {
128
- buffer += h
129
- continue
130
- }
163
+ const item = each[index], key = by(item, index)
131
164
 
132
- if ('nodeName' in Object(h)) {
165
+ proxy.firstChild = (clr ? a[index] : map.get(key)) ?? last?.cloneNode(true)
166
+ render(fn(item), proxy, proxy.firstChild?.nextSibling, root)
133
167
 
134
- if (isFn(h.nodeName)) {
135
- yield* normalize(h.nodeName(h, host), host)
136
- continue
137
- }
168
+ last = proxy.firstChild
169
+ proxy.firstChild = null
138
170
 
139
- if (buffer) {
140
- yield buffer
141
- buffer = ''
142
- }
171
+ map.set(last.$key = key, b[index] = last)
172
+ }
143
173
 
144
- yield h
145
- continue
146
- }
174
+ arrange(host, a, b, del)
175
+ isFunction(ref) && ref(host, root)
176
+ },
147
177
 
148
- isFn(h[Symbol.iterator]) ? yield* normalize(h, host) : buffer += h
149
- }
178
+ arrange = (host, a, b, dispose = noop) => {
150
179
 
151
- if (buffer) yield buffer
152
- },
180
+ const aLen = a.length, bLen = b.length
153
181
 
154
- update = (props, host) => {
182
+ let aIndex = 0, bIndex = 0, aValue, bValue, aMap, bMap, i
155
183
 
156
- const prev = cache.get(host) ?? (host.hasAttributes() ? reduce(host.attributes) : {})
184
+ while (aIndex !== aLen || bIndex !== bLen) {
157
185
 
158
- for (const name in { ...prev, ...props }) {
186
+ aValue = a[aIndex]
187
+ bValue = b[bIndex]
159
188
 
160
- let value = props[name]
189
+ if (aValue === null) aIndex++
190
+ else if (bLen <= bIndex) aIndex++, dispose(host.removeChild(aValue))
191
+ else if (aLen <= aIndex) bIndex++, host.appendChild(bValue)
192
+ else if (aValue === bValue) aIndex++, bIndex++
193
+ else {
161
194
 
162
- if (value === prev[name]) continue
195
+ aMap ??= map(a)
196
+ bMap ??= map(b)
163
197
 
164
- if (name.startsWith('set:')) {
165
- host[name.slice(4)] = value
166
- continue
167
- }
198
+ if (bMap.get(aValue) == null) aIndex++, dispose(host.removeChild(aValue))
199
+ else {
168
200
 
169
- if (value === true) value = ''
170
- else if (value == null || value === false) {
171
- host.removeAttribute(name)
172
- continue
173
- }
201
+ host.insertBefore(bValue, aValue), bIndex++
174
202
 
175
- host.setAttribute(name, value)
176
- }
203
+ if ((i = aMap.get(bValue)) != null) {
204
+ if (i > aIndex + 1) aIndex++
205
+ a[i] = null
206
+ }
207
+ }
208
+ }
209
+ }
210
+ },
177
211
 
178
- cache.set(host, props)
179
- },
212
+ run = (host, root, setup, params, ref) => {
180
213
 
181
- before = (host, child, node) => {
182
- if (node.contains?.(document.activeElement)) {
214
+ host.$root = root
215
+ host.$setup ??= isFunction(setup) ? setup : noop
216
+ host.$params = { ...setup.params, ...params }
183
217
 
184
- const ref = node.nextSibling
218
+ refresh(host)
219
+ isFunction(ref) && ref(host, root)
220
+ },
185
221
 
186
- while (child && child !== node) {
187
- const next = child.nextSibling
188
- host.insertBefore(child, ref)
189
- child = next
190
- }
222
+ dispose = host => {
191
223
 
192
- } else host.insertBefore(node, child)
193
- },
224
+ const { $cleanups } = host
194
225
 
195
- dispose = host => {
196
- for (const child of host.children) dispose(child)
197
- for (const cleaner of cleaners.get(host) ?? []) cleaner(host)
198
- }
226
+ if ($cleanups) {
227
+ for (let fn of $cleanups) fn(host)
228
+ $cleanups.clear()
229
+ }
230
+ }
package/index.min.js CHANGED
@@ -1 +1 @@
1
- var ajo=(()=>{var d=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var I=(e,n)=>{for(var t in n)d(e,t,{get:n[t],enumerable:!0})},K=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of O(n))!W.call(e,i)&&i!==t&&d(e,i,{get:()=>n[i],enumerable:!(r=F(n,i))||r.enumerable});return e};var L=e=>K(d({},"__esModule",{value:!0}),e);var re={};I(re,{Fragment:()=>Z,cleanup:()=>J,clx:()=>P,component:()=>q,consume:()=>G,h:()=>A,intercept:()=>H,keb:()=>R,propagate:()=>E,provide:()=>D,refresh:()=>j,render:()=>c,stx:()=>Q});const Z=({children:e})=>e,A=(e,n,...t)=>(t=t.length==0?null:t.length==1?t[0]:t,{children:t,...n,nodeName:e}),q=e=>({is:n,key:t,host:r,ref:i,...l})=>A(n??e.is??"div",{key:t,...e.host,...r,skip:!0,ref:o=>(j(o,l,e),f(i)&&i(o))}),c=(e,n)=>{let t=n.firstChild,r,i=x.get(n);for(e of m(e,n)){if(typeof e=="string"){for(r=t;r&&r.nodeType!=3;r=r.nextSibling);r?r.data!==e&&(r.data=e):r=document.createTextNode(e)}else if(e instanceof Node)r=e;else{const{key:l,nodeName:o,skip:M,block:p,children:z,ref:b,...B}=e;if(!(l!=null&&(r=i?.get(l))))for(r=t;r&&r.localName!=o;r=r.nextSibling);r||=document.createElement(o),l!=null&&(i||=x.set(n,new Map)).set(l,r),ne(B,r),!(M||p!=null&&U(S.get(r),S.set(r,p)))&&c(z,r),f(b)&&b(r)}r===t?t=t.nextSibling:te(n,t,r)}for(;t;){const l=t.nextSibling;n.removeChild(t).nodeType==1&&C(t),t=l}},j=(e,n,t)=>{try{t&&!f(t)&&s("Setup",t,u),n=n?v.set(e,{...t?.props,...n}):v.get(e)??{},c((N.get(e)??N.set(e,t(n,e)))(n,e),e)}catch(r){E(e,r)}},D=(e,n,t)=>(g.get(e)??g.set(e,new Map)).set(n,t),G=(e,n,t)=>{let r;for(;e;){if((r=g.get(e))&&r.has(n))return r.get(n);e=e.parentNode}return t},H=(e,n)=>{f(n)||s("Interceptor",n,u),$.set(e,n)},E=(e,n)=>{for(let t;e;e=e.parentNode)if(t=$.get(e))return c(t(n),e);throw n},J=(e,n)=>{f(n)||s("Cleaner",n,u),(y.get(e)??y.set(e,new Set)).add(n)},P=e=>T(e).filter(n=>e[n]).join(" ")||null,Q=e=>Y(e).map(n=>n.join(":")).join(";")||null,R=e=>T(e).reduce((n,t)=>(n[t.replace(h,ee).toLowerCase()]=e[t],n),{}),a=()=>{const e=new WeakMap,{set:n}=e;return e.set=(t,r)=>(n.call(e,t,r),r),e},s=(e,n,t)=>{throw new TypeError(`Expected ${e} to be of type ${t}, got ${typeof n} instead`)},U=(e,n)=>e===n||w(e)&&w(n)&&e.length==n.length&&e.every((t,r)=>t===n[r]),V=(e,{name:n,value:t})=>(e[n]=t,e),X=e=>_(e).reduce(V,{}),f=e=>typeof e==u,{keys:T,entries:Y}=Object,{isArray:w,from:_}=Array,u="function",h=/([a-z0-9])([A-Z])/g,ee="$1-$2",x=a(),S=a(),v=a(),N=a(),g=a(),$=a(),y=a(),k=a(),m=function*(e,n){let t,r="";for(e of f(e?.[Symbol.iterator])?e:[e])if(!(e==null||(t=typeof e)=="boolean")){if(t=="string"||t=="number"){r+=e;continue}if("nodeName"in Object(e)){if(f(e.nodeName)){yield*m(e.nodeName(e,n),n);continue}r&&(yield r,r=""),yield e;continue}f(e[Symbol.iterator])?yield*m(e,n):r+=e}r&&(yield r)},ne=(e,n)=>{const t=k.get(n)??(n.hasAttributes()?X(n.attributes):{});for(const r in{...t,...e}){let i=e[r];if(i!==t[r]){if(r.startsWith("set:")){n[r.slice(4)]=i;continue}if(i===!0)i="";else if(i==null||i===!1){n.removeAttribute(r);continue}n.setAttribute(r,i)}}k.set(n,e)},te=(e,n,t)=>{if(t.contains?.(document.activeElement)){const r=t.nextSibling;for(;n&&n!==t;){const i=n.nextSibling;e.insertBefore(n,r),n=i}}else e.insertBefore(t,n)},C=e=>{for(const n of e.children)C(n);for(const n of y.get(e)??[])n(e)};return L(re);})();
1
+ var ajo=(()=>{var y=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var E=(e,n)=>{for(var i in n)y(e,i,{get:n[i],enumerable:!0})},I=(e,n,i,l)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of L(n))!z.call(e,r)&&r!==i&&y(e,r,{get:()=>n[r],enumerable:!(l=F(n,r))||l.enumerable});return e};var T=e=>I(y({},"__esModule",{value:!0}),e);var ae={};E(ae,{For:()=>O,Fragment:()=>V,cleanup:()=>G,clx:()=>H,component:()=>W,consume:()=>q,h:()=>N,intercept:()=>D,keb:()=>K,propagate:()=>M,provide:()=>Z,refresh:()=>A,render:()=>x,stx:()=>J});var S=require("element-notifier");const V=({children:e})=>e,O=({is:e,each:n,by:i,children:l,ref:r,...t})=>N(e??"div",{...t,skip:!0,ref:(a,f)=>ie(a,f,n,i,l,r)}),N=(e,n,...i)=>({children:i.length==0?null:i.length==1?i[0]:i,...n,nodeName:e}),x=(e,n,i,l=n)=>{let r=n.firstChild,t;l==n&&(l.$mo??=(0,S.notify)((a,f)=>f||l.contains(a)||te(a),l));for(e of C(e)){if(e instanceof Node)t=e;else if(typeof e=="string"){for(t=r;t!=i&&t.nodeType!=3;t=t.nextSibling);t==i?t=document.createTextNode(e):t.data!=e&&(t.data=e)}else{const{nodeName:a,block:f,skip:p,children:d,ref:o,...u}=e;for(t=r;t!=i&&t.localName!=a;t=t.nextSibling);t==i&&(t=document.createElement(a)),(f==null||U(t.$deps,t.$deps=f))&&(ee(u,t),p||x(d,t,null,l),c(o)&&o(t,l))}t==r?r=r.nextSibling:ne(n,t,r)}for(;r!=i;){const a=r.nextSibling;n.removeChild(r),r=a}},W=e=>({is:n,props:i,ref:l,...r})=>N(n??e.is??"div",{...e.props,...i,skip:!0,ref:(t,a)=>re(t,a,e,r,l)}),A=e=>{try{x((e.$render??=e.$setup(e))(e.$params),e,null,e.$root)}catch(n){M(e,n)}},Z=(e,n,i)=>(e.$provisions??=new Map).set(n,i),q=(e,n,i)=>{for(let l;e;e=e.parentNode)if((l=e.$provisions)&&l.has(n))return l.get(n);return i},D=(e,n)=>c(n)&&(e.$interceptor=n),M=(e,n)=>{for(let i;e;e=e.parentNode)if(c(i=e.$interceptor))return x(i(n));throw n},G=(e,n)=>c(n)&&(e.$cleanups??=new Set).add(n),H=e=>B(e).filter(n=>e[n]).join(" ")||null,J=e=>P(e).map(n=>n.join(":")).join(";")||null,K=e=>B(e).reduce((n,i)=>(n[i.replace(_,h).toLowerCase()]=e[i],n),{}),{isArray:g,from:j}=Array,{keys:B,entries:P}=Object,c=e=>typeof e=="function",b=()=>{},Q=e=>(...n)=>n.length?e=n[0]:e,k=e=>e.reduce(R,new Map),R=(e,n,i)=>(e.set(n,i),e),U=(e,n)=>g(e)&&g(n)?e.some((i,l)=>i!==n[l]):e!==n,X=e=>j(e).reduce(Y,{}),Y=(e,{name:n,value:i})=>(e[n]=i,e),$={firstChild:null,insertBefore:e=>$.firstChild??=e},_=/([a-z0-9])([A-Z])/g,h="$1-$2",C=function*(e,n=Q(""),i=!0){let l;for(e of g(e)?e:[e])e==null||typeof e=="boolean"||(typeof e.nodeName=="string"?((l=n())&&(n(""),yield l),yield e):c(e.nodeName)?yield*C(e.nodeName(e),n,!1):g(e)?yield*C(e,n,!1):n(n()+e));i&&(l=n())&&(yield l)},ee=(e,n)=>{const i=n.$props??(n.hasAttributes()?X(n.attributes):{});for(const l in{...i,...e}){let r=e[l];r!==i[l]&&(l.startsWith("set:")?n[l.slice(4)]=r:r==null||r===!1?n.removeAttribute(l):n.setAttribute(l,r===!0?"":r))}n.$props=e},ne=(e,n,i)=>{if(n.contains?.(document.activeElement)){const l=n.nextSibling;for(;i&&i!==n;){const r=i.nextSibling;e.insertBefore(i,l),i=r}}else e.insertBefore(n,i)},ie=(e,n,i,l,r,t)=>{i=g(i)?i:[],l=c(l)?l:s=>s,r=c(r)?r:b;const a=e.$for??=new Map,f=s=>a.delete(s.$key),p=i!==e.$each,d=(e.$each=i).length,o=j(e.childNodes),u=new Array(d);p&&a.clear();for(let s,m=0;m<d;m++){const v=i[m],w=l(v,m);$.firstChild=(p?o[m]:a.get(w))??s?.cloneNode(!0),x(r(v),$,$.firstChild?.nextSibling,n),s=$.firstChild,$.firstChild=null,a.set(s.$key=w,u[m]=s)}le(e,o,u,f),c(t)&&t(e,n)},le=(e,n,i,l=b)=>{const r=n.length,t=i.length;let a=0,f=0,p,d,o,u,s;for(;a!==r||f!==t;)p=n[a],d=i[f],p===null?a++:t<=f?(a++,l(e.removeChild(p))):r<=a?(f++,e.appendChild(d)):p===d?(a++,f++):(o??=k(n),u??=k(i),u.get(p)==null?(a++,l(e.removeChild(p))):(e.insertBefore(d,p),f++,(s=o.get(d))!=null&&(s>a+1&&a++,n[s]=null)))},re=(e,n,i,l,r)=>{e.$root=n,e.$setup??=c(i)?i:b,e.$params={...i.params,...l},A(e),c(r)&&r(e,n)},te=e=>{const{$cleanups:n}=e;if(n){for(let i of n)i(e);n.clear()}};return T(ae);})();
package/license ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2022, Cristian Falcone, @cristianfalcone
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
+ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajo",
3
- "version": "0.0.14",
3
+ "version": "0.0.17",
4
4
  "description": "ajo is a JavaScript view library for building user interfaces",
5
5
  "type": "module",
6
6
  "module": "index.js",
@@ -17,6 +17,13 @@
17
17
  "index.js",
18
18
  "index.min.js"
19
19
  ],
20
+ "keywords": [
21
+ "ui",
22
+ "frontend",
23
+ "web",
24
+ "dom",
25
+ "jsx"
26
+ ],
20
27
  "scripts": {
21
28
  "build": "npm run build:require && npm run build:browser",
22
29
  "build:require": "esbuild --format=cjs --out-extension:.js=.cjs --outdir=. index.js",
@@ -30,8 +37,11 @@
30
37
  "bugs": "https://github.com/cristianfalcone/ajo/issues",
31
38
  "homepage": "https://github.com/cristianfalcone/ajo#readme",
32
39
  "devDependencies": {
33
- "backdom": "^0.0.4",
34
- "esbuild": "^0.14.51",
40
+ "backdom": "^0.0.6",
41
+ "esbuild": "^0.15.6",
35
42
  "uvu": "^0.5.6"
43
+ },
44
+ "dependencies": {
45
+ "element-notifier": "^1.1.2"
36
46
  }
37
47
  }
package/readme.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # ajo
2
2
  ajo is a JavaScript view library for building user interfaces
3
3
 
4
- ## Install
4
+ ## install
5
5
 
6
6
  ```sh
7
7
  npm install ajo
8
8
  ```
9
9
 
10
- ## Render JSX to a DOM element
10
+ ## render JSX to a DOM element
11
11
 
12
12
  ```jsx
13
13
  /** @jsx h */
@@ -18,7 +18,7 @@ document.body.innerHTML = '<div>Hello World</div>'
18
18
  render(<div>Goodbye World</div>, document.body)
19
19
  ```
20
20
 
21
- ## Stateless Component
21
+ ## stateless component
22
22
 
23
23
  ```jsx
24
24
  /** @jsx h */
@@ -29,26 +29,53 @@ const Greet = ({ name }) => <div>Hello {name}</div>
29
29
  render(<Greet name="World" />, document.body)
30
30
  ```
31
31
 
32
- ## Stateful Component
32
+ ## stateful component
33
33
 
34
34
  ```jsx
35
35
  /** @jsx h */
36
36
  import { h, component, refresh, render } from 'ajo'
37
37
 
38
- const Counter = component(({ start = 0 }, host) => {
39
-
40
- let count = start
41
-
38
+ const Counter = component(host => {
39
+
40
+ let count = 0
41
+
42
42
  const increment = () => {
43
43
  count++
44
44
  refresh(host)
45
45
  }
46
-
46
+
47
47
  return () =>
48
48
  <button set:onclick={increment}>
49
49
  Current: {count}
50
50
  </button>
51
51
  })
52
52
 
53
- render(<Counter start={1} />, document.body)
53
+ render(<Counter />, document.body)
54
+ ```
55
+
56
+ ## rendering lists
57
+
58
+ ```jsx
59
+ /** @jsx h */
60
+ import { h, render, component, For } from 'ajo'
61
+
62
+ const products = [
63
+ { title: 'Cabbage', isFruit: false, id: 1 },
64
+ { title: 'Garlic', isFruit: false, id: 2 },
65
+ { title: 'Apple', isFruit: true, id: 3 },
66
+ ];
67
+
68
+ const ShoppingList = component(() => {
69
+ const listItem = product =>
70
+ <li style={stx({ color: product.isFruit ? 'magenta' : 'darkgreen' })}>
71
+ {product.title}
72
+ </li>
73
+
74
+ return ({ products }) => <For each={products} is="ul">{listItem}</For>
75
+ })
76
+
77
+ render(<ShoppingList producst={products} />, document.body)
54
78
  ```
79
+
80
+ ## acknowledgments
81
+ ajo takes heavy inspiration from [Incremental DOM](https://github.com/google/incremental-dom) and [Crank.js](https://github.com/bikeshaving/crank)