@thi.ng/hdom 9.3.30 → 9.4.0
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/CHANGELOG.md +7 -1
- package/README.md +1 -1
- package/api.js +0 -1
- package/default.js +39 -28
- package/diff.js +220 -257
- package/dom.js +216 -255
- package/logger.js +6 -2
- package/normalize.js +110 -131
- package/package.json +15 -13
- package/render-once.js +11 -20
- package/resolve.js +4 -1
- package/start.js +25 -65
package/dom.js
CHANGED
|
@@ -1,277 +1,238 @@
|
|
|
1
1
|
import { implementsFunction } from "@thi.ng/checks/implements-function";
|
|
2
|
-
import { isArray
|
|
3
|
-
import { isNotStringAndIterable
|
|
4
|
-
import { isString
|
|
5
|
-
import { SVG_TAGS } from "@thi.ng/hiccup/api";
|
|
2
|
+
import { isArray } from "@thi.ng/checks/is-array";
|
|
3
|
+
import { isNotStringAndIterable } from "@thi.ng/checks/is-not-string-iterable";
|
|
4
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
5
|
+
import { ATTRIB_JOIN_DELIMS, SVG_TAGS } from "@thi.ng/hiccup/api";
|
|
6
6
|
import { css } from "@thi.ng/hiccup/css";
|
|
7
7
|
import { formatPrefixes } from "@thi.ng/hiccup/prefix";
|
|
8
8
|
import { XML_SVG } from "@thi.ng/prefixes/xml";
|
|
9
|
-
const isArray = isa;
|
|
10
|
-
const isNotStringAndIterable = isi;
|
|
11
|
-
const isString = iss;
|
|
12
9
|
const maybeInitElement = (el, tree) => tree.__init && tree.__init.apply(tree.__this, [el, ...tree.__args]);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (typeof tag === "function") {
|
|
25
|
-
return createTree(opts, impl, parent, tag.apply(null, [opts.ctx, ...tree.slice(1)]), insert);
|
|
26
|
-
}
|
|
27
|
-
const attribs = tree[1];
|
|
28
|
-
if (attribs.__impl) {
|
|
29
|
-
return attribs.__impl.createTree(opts, parent, tree, insert, init);
|
|
30
|
-
}
|
|
31
|
-
const el = impl.createElement(parent, tag, attribs, insert);
|
|
32
|
-
if (tree.length > 2) {
|
|
33
|
-
const n = tree.length;
|
|
34
|
-
for (let i = 2; i < n; i++) {
|
|
35
|
-
createTree(opts, impl, el, tree[i], undefined, init);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
init && maybeInitElement(el, tree);
|
|
39
|
-
return el;
|
|
10
|
+
const createTree = (opts, impl, parent, tree, insert, init = true) => {
|
|
11
|
+
if (isArray(tree)) {
|
|
12
|
+
const tag = tree[0];
|
|
13
|
+
if (typeof tag === "function") {
|
|
14
|
+
return createTree(
|
|
15
|
+
opts,
|
|
16
|
+
impl,
|
|
17
|
+
parent,
|
|
18
|
+
tag.apply(null, [opts.ctx, ...tree.slice(1)]),
|
|
19
|
+
insert
|
|
20
|
+
);
|
|
40
21
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
22
|
+
const attribs = tree[1];
|
|
23
|
+
if (attribs.__impl) {
|
|
24
|
+
return attribs.__impl.createTree(
|
|
25
|
+
opts,
|
|
26
|
+
parent,
|
|
27
|
+
tree,
|
|
28
|
+
insert,
|
|
29
|
+
init
|
|
30
|
+
);
|
|
47
31
|
}
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
const el = impl.createElement(parent, tag, attribs, insert);
|
|
33
|
+
if (tree.length > 2) {
|
|
34
|
+
const n = tree.length;
|
|
35
|
+
for (let i = 2; i < n; i++) {
|
|
36
|
+
createTree(opts, impl, el, tree[i], void 0, init);
|
|
37
|
+
}
|
|
50
38
|
}
|
|
51
|
-
|
|
39
|
+
init && maybeInitElement(el, tree);
|
|
40
|
+
return el;
|
|
41
|
+
}
|
|
42
|
+
if (isNotStringAndIterable(tree)) {
|
|
43
|
+
const res = [];
|
|
44
|
+
for (let t of tree) {
|
|
45
|
+
res.push(createTree(opts, impl, parent, t, insert, init));
|
|
46
|
+
}
|
|
47
|
+
return res;
|
|
48
|
+
}
|
|
49
|
+
if (tree == null) {
|
|
50
|
+
return parent;
|
|
51
|
+
}
|
|
52
|
+
return impl.createTextElement(parent, tree);
|
|
52
53
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
a[0] === "o" && a[1] === "n" && impl.setAttrib(el, a, attribs[a]);
|
|
74
|
-
}
|
|
75
|
-
for (let n = tree.length, i = 2; i < n; i++) {
|
|
76
|
-
hydrateTree(opts, impl, el, tree[i], i - 2);
|
|
77
|
-
}
|
|
54
|
+
const hydrateTree = (opts, impl, parent, tree, index = 0) => {
|
|
55
|
+
if (isArray(tree)) {
|
|
56
|
+
const el = impl.getChild(parent, index);
|
|
57
|
+
if (typeof tree[0] === "function") {
|
|
58
|
+
hydrateTree(
|
|
59
|
+
opts,
|
|
60
|
+
impl,
|
|
61
|
+
parent,
|
|
62
|
+
tree[0].apply(null, [opts.ctx, ...tree.slice(1)]),
|
|
63
|
+
index
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const attribs = tree[1];
|
|
67
|
+
if (attribs.__impl) {
|
|
68
|
+
return attribs.__impl.hydrateTree(
|
|
69
|
+
opts,
|
|
70
|
+
parent,
|
|
71
|
+
tree,
|
|
72
|
+
index
|
|
73
|
+
);
|
|
78
74
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
index++;
|
|
83
|
-
}
|
|
75
|
+
maybeInitElement(el, tree);
|
|
76
|
+
for (let a in attribs) {
|
|
77
|
+
a[0] === "o" && a[1] === "n" && impl.setAttrib(el, a, attribs[a]);
|
|
84
78
|
}
|
|
79
|
+
for (let n = tree.length, i = 2; i < n; i++) {
|
|
80
|
+
hydrateTree(opts, impl, el, tree[i], i - 2);
|
|
81
|
+
}
|
|
82
|
+
} else if (isNotStringAndIterable(tree)) {
|
|
83
|
+
for (let t of tree) {
|
|
84
|
+
hydrateTree(opts, impl, parent, t, index);
|
|
85
|
+
index++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
85
88
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* appended to the `parent`'s list of children. Returns new DOM node.
|
|
91
|
-
*
|
|
92
|
-
* If `tag` is a known SVG element name, the new element will be created
|
|
93
|
-
* with the proper SVG XML namespace.
|
|
94
|
-
*
|
|
95
|
-
* @param parent - DOM element
|
|
96
|
-
* @param tag - component tree
|
|
97
|
-
* @param attribs - attributes
|
|
98
|
-
* @param insert - child index
|
|
99
|
-
*/
|
|
100
|
-
export const createElement = (parent, tag, attribs, insert) => {
|
|
101
|
-
const el = SVG_TAGS[tag]
|
|
102
|
-
? document.createElementNS(XML_SVG, tag)
|
|
103
|
-
: document.createElement(tag);
|
|
104
|
-
attribs && setAttribs(el, attribs);
|
|
105
|
-
return addChild(parent, el, insert);
|
|
89
|
+
const createElement = (parent, tag, attribs, insert) => {
|
|
90
|
+
const el = SVG_TAGS[tag] ? document.createElementNS(XML_SVG, tag) : document.createElement(tag);
|
|
91
|
+
attribs && setAttribs(el, attribs);
|
|
92
|
+
return addChild(parent, el, insert);
|
|
106
93
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
export const cloneWithNewAttribs = (el, attribs) => {
|
|
117
|
-
const res = el.cloneNode(true);
|
|
118
|
-
setAttribs(res, attribs);
|
|
119
|
-
el.parentNode.replaceChild(res, el);
|
|
120
|
-
return res;
|
|
94
|
+
const createTextElement = (parent, content, insert) => addChild(parent, document.createTextNode(content), insert);
|
|
95
|
+
const addChild = (parent, child, insert) => parent ? insert === void 0 ? parent.appendChild(child) : parent.insertBefore(child, parent.children[insert]) : child;
|
|
96
|
+
const getChild = (parent, child) => parent.children[child];
|
|
97
|
+
const replaceChild = (opts, impl, parent, child, tree, init = true) => (impl.removeChild(parent, child), impl.createTree(opts, parent, tree, child, init));
|
|
98
|
+
const cloneWithNewAttribs = (el, attribs) => {
|
|
99
|
+
const res = el.cloneNode(true);
|
|
100
|
+
setAttribs(res, attribs);
|
|
101
|
+
el.parentNode.replaceChild(res, el);
|
|
102
|
+
return res;
|
|
121
103
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
104
|
+
const setContent = (el, body) => el.textContent = body;
|
|
105
|
+
const setAttribs = (el, attribs) => {
|
|
106
|
+
for (let k in attribs) {
|
|
107
|
+
setAttrib(el, k, attribs[k], attribs);
|
|
108
|
+
}
|
|
109
|
+
return el;
|
|
128
110
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
* - attrib IDs starting with "on" are treated as event listeners
|
|
141
|
-
*
|
|
142
|
-
* If the given (or computed) attrib value is `false` or `undefined` the
|
|
143
|
-
* attrib is removed from the element.
|
|
144
|
-
*
|
|
145
|
-
* @param el - DOM element
|
|
146
|
-
* @param id - attribute name
|
|
147
|
-
* @param val - attribute value
|
|
148
|
-
* @param attribs - object of all attribs
|
|
149
|
-
*/
|
|
150
|
-
export const setAttrib = (el, id, val, attribs) => {
|
|
151
|
-
implementsFunction(val, "deref") && (val = val.deref());
|
|
152
|
-
if (id.startsWith("__"))
|
|
153
|
-
return;
|
|
154
|
-
const isListener = id[0] === "o" && id[1] === "n";
|
|
155
|
-
if (!isListener && typeof val === "function") {
|
|
156
|
-
val = val(attribs);
|
|
157
|
-
}
|
|
158
|
-
if (val !== undefined && val !== false) {
|
|
159
|
-
switch (id) {
|
|
160
|
-
case "style":
|
|
161
|
-
setStyle(el, val);
|
|
162
|
-
break;
|
|
163
|
-
case "value":
|
|
164
|
-
updateValueAttrib(el, val);
|
|
165
|
-
break;
|
|
166
|
-
case "prefix":
|
|
167
|
-
el.setAttribute(id, isString(val) ? val : formatPrefixes(val));
|
|
168
|
-
break;
|
|
169
|
-
case "accesskey":
|
|
170
|
-
el.accessKey = val;
|
|
171
|
-
break;
|
|
172
|
-
case "contenteditable":
|
|
173
|
-
el.contentEditable = val;
|
|
174
|
-
break;
|
|
175
|
-
case "tabindex":
|
|
176
|
-
el.tabIndex = val;
|
|
177
|
-
break;
|
|
178
|
-
case "align":
|
|
179
|
-
case "autocapitalize":
|
|
180
|
-
case "checked":
|
|
181
|
-
case "dir":
|
|
182
|
-
case "draggable":
|
|
183
|
-
case "hidden":
|
|
184
|
-
case "id":
|
|
185
|
-
case "lang":
|
|
186
|
-
case "namespaceURI":
|
|
187
|
-
case "scrollTop":
|
|
188
|
-
case "scrollLeft":
|
|
189
|
-
case "title":
|
|
190
|
-
// TODO add more properties / enumerated attribs?
|
|
191
|
-
el[id] = val;
|
|
192
|
-
break;
|
|
193
|
-
default:
|
|
194
|
-
isListener
|
|
195
|
-
? setListener(el, id.substring(2), val)
|
|
196
|
-
: el.setAttribute(id, val === true ? "" : val);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
el.hasAttribute(id)
|
|
201
|
-
? el.removeAttribute("title")
|
|
202
|
-
: el[id] && (el[id] = null);
|
|
111
|
+
const setAttrib = (el, id, val, attribs) => {
|
|
112
|
+
implementsFunction(val, "deref") && (val = val.deref());
|
|
113
|
+
if (id.startsWith("__"))
|
|
114
|
+
return;
|
|
115
|
+
const isListener = id[0] === "o" && id[1] === "n";
|
|
116
|
+
if (isListener) {
|
|
117
|
+
if (isString(val)) {
|
|
118
|
+
el.setAttribute(id, val);
|
|
119
|
+
} else {
|
|
120
|
+
id = id.substring(2);
|
|
121
|
+
isArray(val) ? el.addEventListener(id, val[0], val[1]) : el.addEventListener(id, val);
|
|
203
122
|
}
|
|
204
123
|
return el;
|
|
124
|
+
}
|
|
125
|
+
if (typeof val === "function")
|
|
126
|
+
val = val(attribs);
|
|
127
|
+
if (isArray(val))
|
|
128
|
+
val = val.join(ATTRIB_JOIN_DELIMS[id] || " ");
|
|
129
|
+
switch (id) {
|
|
130
|
+
case "style":
|
|
131
|
+
setStyle(el, val);
|
|
132
|
+
break;
|
|
133
|
+
case "value":
|
|
134
|
+
updateValueAttrib(el, val);
|
|
135
|
+
break;
|
|
136
|
+
case "prefix":
|
|
137
|
+
el.setAttribute(id, isString(val) ? val : formatPrefixes(val));
|
|
138
|
+
break;
|
|
139
|
+
case "accesskey":
|
|
140
|
+
case "accessKey":
|
|
141
|
+
el.accessKey = val;
|
|
142
|
+
break;
|
|
143
|
+
case "contenteditable":
|
|
144
|
+
case "contentEditable":
|
|
145
|
+
el.contentEditable = val;
|
|
146
|
+
break;
|
|
147
|
+
case "tabindex":
|
|
148
|
+
case "tabIndex":
|
|
149
|
+
el.tabIndex = val;
|
|
150
|
+
break;
|
|
151
|
+
case "align":
|
|
152
|
+
case "autocapitalize":
|
|
153
|
+
case "checked":
|
|
154
|
+
case "dir":
|
|
155
|
+
case "draggable":
|
|
156
|
+
case "hidden":
|
|
157
|
+
case "id":
|
|
158
|
+
case "indeterminate":
|
|
159
|
+
case "lang":
|
|
160
|
+
case "namespaceURI":
|
|
161
|
+
case "scrollLeft":
|
|
162
|
+
case "scrollTop":
|
|
163
|
+
case "selectionEnd":
|
|
164
|
+
case "selectionStart":
|
|
165
|
+
case "slot":
|
|
166
|
+
case "spellcheck":
|
|
167
|
+
case "title":
|
|
168
|
+
el[id] = val;
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
val === false || val == null ? el.removeAttribute(id) : el.setAttribute(id, val === true ? id : val);
|
|
172
|
+
}
|
|
173
|
+
return el;
|
|
205
174
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
el.value = value;
|
|
232
|
-
el.selectionStart = el.selectionEnd = off;
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
default:
|
|
236
|
-
el.value = value;
|
|
237
|
-
}
|
|
175
|
+
const updateValueAttrib = (el, value) => {
|
|
176
|
+
let ev;
|
|
177
|
+
switch (el.type) {
|
|
178
|
+
case "text":
|
|
179
|
+
case "textarea":
|
|
180
|
+
case "password":
|
|
181
|
+
case "search":
|
|
182
|
+
case "number":
|
|
183
|
+
case "email":
|
|
184
|
+
case "url":
|
|
185
|
+
case "tel":
|
|
186
|
+
case "date":
|
|
187
|
+
case "datetime-local":
|
|
188
|
+
case "time":
|
|
189
|
+
case "week":
|
|
190
|
+
case "month":
|
|
191
|
+
if ((ev = el.value) !== void 0 && typeof value === "string") {
|
|
192
|
+
const off = value.length - (ev.length - (el.selectionStart || 0));
|
|
193
|
+
el.value = value;
|
|
194
|
+
el.selectionStart = el.selectionEnd = off;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
default:
|
|
198
|
+
el.value = value;
|
|
199
|
+
}
|
|
238
200
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
el.hasAttribute(a) ? el.removeAttribute(a) : (el[a] = null);
|
|
247
|
-
}
|
|
201
|
+
const removeAttribs = (el, attribs, prev) => {
|
|
202
|
+
for (let i = attribs.length; i-- > 0; ) {
|
|
203
|
+
const a = attribs[i];
|
|
204
|
+
if (a[0] === "o" && a[1] === "n") {
|
|
205
|
+
removeListener(el, a.substring(2), prev[a]);
|
|
206
|
+
} else {
|
|
207
|
+
el.hasAttribute(a) ? el.removeAttribute(a) : el[a] = null;
|
|
248
208
|
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const setStyle = (el, styles) => (el.setAttribute("style", css(styles)), el);
|
|
212
|
+
const setListener = (el, id, listener) => isString(listener) ? el.setAttribute("on" + id, listener) : isArray(listener) ? el.addEventListener(id, ...listener) : el.addEventListener(id, listener);
|
|
213
|
+
const removeListener = (el, id, listener) => isArray(listener) ? el.removeEventListener(id, ...listener) : el.removeEventListener(id, listener);
|
|
214
|
+
const clearDOM = (el) => el.innerHTML = "";
|
|
215
|
+
const removeChild = (parent, childIdx) => {
|
|
216
|
+
const n = parent.children[childIdx];
|
|
217
|
+
n !== void 0 && parent.removeChild(n);
|
|
249
218
|
};
|
|
250
|
-
export
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
*/
|
|
270
|
-
export const removeListener = (el, id, listener) => isArray(listener)
|
|
271
|
-
? el.removeEventListener(id, ...listener)
|
|
272
|
-
: el.removeEventListener(id, listener);
|
|
273
|
-
export const clearDOM = (el) => (el.innerHTML = "");
|
|
274
|
-
export const removeChild = (parent, childIdx) => {
|
|
275
|
-
const n = parent.children[childIdx];
|
|
276
|
-
n !== undefined && parent.removeChild(n);
|
|
219
|
+
export {
|
|
220
|
+
addChild,
|
|
221
|
+
clearDOM,
|
|
222
|
+
cloneWithNewAttribs,
|
|
223
|
+
createElement,
|
|
224
|
+
createTextElement,
|
|
225
|
+
createTree,
|
|
226
|
+
getChild,
|
|
227
|
+
hydrateTree,
|
|
228
|
+
removeAttribs,
|
|
229
|
+
removeChild,
|
|
230
|
+
removeListener,
|
|
231
|
+
replaceChild,
|
|
232
|
+
setAttrib,
|
|
233
|
+
setAttribs,
|
|
234
|
+
setContent,
|
|
235
|
+
setListener,
|
|
236
|
+
setStyle,
|
|
237
|
+
updateValueAttrib
|
|
277
238
|
};
|
package/logger.js
CHANGED