solarite 0.2.4 → 0.3.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/dist/Solarite-debug.js +1457 -1402
- package/dist/Solarite.js +1425 -1272
- package/dist/Solarite.min.js +2 -2
- package/package.json +5 -6
- package/readme.md +2 -4
- package/src/{solarite/ExprPath.js → ExprPath.js} +421 -231
- package/src/Globals.js +79 -0
- package/src/HtmlParser.js +91 -0
- package/src/{util/MultiValueMap.js → MultiValueMap.js} +22 -26
- package/src/{solarite/NodeGroup.js → NodeGroup.js} +286 -226
- package/src/{solarite/Shell.js → Shell.js} +119 -92
- package/src/Solarite.d.ts +62 -0
- package/src/{solarite/Solarite.js → Solarite.js} +15 -13
- package/src/{solarite/Template.js → Template.js} +22 -19
- package/src/Util.js +330 -0
- package/src/{util/Errors.js → assert.js} +1 -0
- package/src/createSolarite.js +154 -0
- package/src/{util/delve.js → delve.js} +5 -4
- package/src/{solarite/getArg.js → getArg.js} +41 -15
- package/src/{solarite/r.js → h.js} +59 -29
- package/src/{solarite/hash.js → hash.js} +12 -9
- package/src/unused/FastLookupArray.js +54 -0
- package/src/unused/Hashes.js +339 -0
- package/src/unused/InUse.test.js +92 -0
- package/src/unused/InUseMap.js +98 -0
- package/src/unused/LinkedList.js +117 -0
- package/src/unused/LinkedList.test.js +115 -0
- package/src/unused/Misc.js +13 -0
- package/src/unused/Perf.js +47 -0
- package/src/unused/TrackedArray.js +54 -0
- package/src/watch.js +546 -0
- package/src/solarite/Globals.js +0 -54
- package/src/solarite/Util.js +0 -388
- package/src/solarite/createSolarite.js +0 -274
- package/src/solarite/watch3.js +0 -189
- package/src/util/Util.js +0 -113
- /package/src/{solarite/udomdiff.js → udomdiff.js} +0 -0
- /package/src/{util → unused}/WeakArray.js +0 -0
package/src/Util.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import Globals from "./Globals.js";
|
|
2
|
+
import delve from "./delve.js";
|
|
3
|
+
|
|
4
|
+
let Util = {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns true if they're the same.
|
|
8
|
+
* @param a
|
|
9
|
+
* @param b
|
|
10
|
+
* @returns {boolean} */
|
|
11
|
+
arraySame(a, b) {
|
|
12
|
+
let aLength = a.length;
|
|
13
|
+
if (aLength !== b.length)
|
|
14
|
+
return false;
|
|
15
|
+
for (let i=0; i<aLength; i++)
|
|
16
|
+
if (a[i] !== b[i])
|
|
17
|
+
return false;
|
|
18
|
+
return true; // the same.
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
bindId(root, el) {
|
|
22
|
+
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
23
|
+
if (id) { // If something hasn't removed the id.
|
|
24
|
+
|
|
25
|
+
// Don't allow overwriting existing class properties if they already have a non-Node value.
|
|
26
|
+
if (root[id] && !(root[id] instanceof Node))
|
|
27
|
+
throw new Error(`${root.constructor.name}.${id} already has a value. ` +
|
|
28
|
+
`Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
|
|
29
|
+
|
|
30
|
+
delve(root, id.split(/\./g), el);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param style {HTMLStyleElement}
|
|
36
|
+
* @param root {HTMLElement} */
|
|
37
|
+
bindStyles(style, root) {
|
|
38
|
+
let styleId = root.getAttribute('data-style');
|
|
39
|
+
if (!styleId) {
|
|
40
|
+
// Keep track of one style id for each class.
|
|
41
|
+
// TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
|
|
42
|
+
if (!root.constructor.styleId)
|
|
43
|
+
root.constructor.styleId = 1;
|
|
44
|
+
styleId = root.constructor.styleId++;
|
|
45
|
+
|
|
46
|
+
root.setAttribute('data-style', styleId)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Replace ":host" with "tagName[data-style=...]" in the css.
|
|
50
|
+
let tagName = root.tagName.toLowerCase();
|
|
51
|
+
for (let child of style.childNodes) {
|
|
52
|
+
if (child.nodeType === 3) {
|
|
53
|
+
let oldText = child.textContent;
|
|
54
|
+
let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}[data-style="${styleId}"]`)
|
|
55
|
+
if (oldText !== newText)
|
|
56
|
+
child.textContent = newText;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convert a Proper Case name to a name with dashes.
|
|
63
|
+
* Dashes will be placed between letters and numbers.
|
|
64
|
+
* If there are multiple consecutive capital letters followed by another chracater, a dash will be placed before the last capital letter.
|
|
65
|
+
* @param str {string}
|
|
66
|
+
* @return {string}
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* 'ProperName' => 'proper-name'
|
|
70
|
+
* 'HTMLElement' => 'html-element'
|
|
71
|
+
* 'BigUI' => 'big-ui'
|
|
72
|
+
* 'UIForm' => 'ui-form'
|
|
73
|
+
* 'A100' => 'a-100' */
|
|
74
|
+
camelToDashes(str) {
|
|
75
|
+
// Convert any capital letter that is preceded by a lowercase letter or number to lowercase and precede with a dash.
|
|
76
|
+
str = str.replace(/([a-z0-9])([A-Z])/g, '$1-$2');
|
|
77
|
+
|
|
78
|
+
// Convert any capital letter that is followed by a lowercase letter or number to lowercase and precede with a dash.
|
|
79
|
+
str = str.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2');
|
|
80
|
+
|
|
81
|
+
// Convert any number that is preceded by a lowercase or uppercase letter to be preceded by a dash.
|
|
82
|
+
str = str.replace(/([a-zA-Z])([0-9])/g, '$1-$2');
|
|
83
|
+
|
|
84
|
+
// Convert all the remaining capital letters to lowercase.
|
|
85
|
+
return str.toLowerCase();
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Converts a string written in kebab-case to camelCase.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} str - The input string written in kebab-case.
|
|
92
|
+
* @return {string} - The resulting camelCase string.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* dashesToCamel('example-string') // Returns 'exampleString'
|
|
96
|
+
* dashesToCamel('another-example-test') // Returns 'anotherExampleTest' */
|
|
97
|
+
dashesToCamel(str) {
|
|
98
|
+
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A generator function that recursively traverses and flattens a value.
|
|
104
|
+
*
|
|
105
|
+
* - If the input is an array, it recursively traverses and flattens the array.
|
|
106
|
+
* - If the input is a function, it calls the function, replaces the function
|
|
107
|
+
* with its result, and flattens the result if necessary. It will recursively
|
|
108
|
+
* call functions that return other functions.
|
|
109
|
+
* - Otherwise it yields the value as is.
|
|
110
|
+
*
|
|
111
|
+
* This function does not create a new array for the flattened values. Instead,
|
|
112
|
+
* it lazily yields each item as it is encountered. This can be more memory-efficient
|
|
113
|
+
* for large or deeply nested structures.
|
|
114
|
+
*
|
|
115
|
+
* @param {any} value - The value to flatten. Can be an array, object, function, or primitive.
|
|
116
|
+
* @yields {any} - The next item in the flattened structure.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const complexArray = [
|
|
120
|
+
* 1,
|
|
121
|
+
* [2, () => 3, [4, () => [5, 6]], { a: 'object' }],
|
|
122
|
+
* () => () => 7,
|
|
123
|
+
* () => [() => 8, 9],
|
|
124
|
+
* ]; *
|
|
125
|
+
* for (const item of flatten(complexArray))
|
|
126
|
+
* console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
|
|
127
|
+
*/
|
|
128
|
+
// *flatten(value) {
|
|
129
|
+
// if (Array.isArray(value)) {
|
|
130
|
+
// for (const item of value) {
|
|
131
|
+
// yield* Util.flatten(item); // Recursively flatten arrays
|
|
132
|
+
// }
|
|
133
|
+
// } else if (typeof value === 'function') {
|
|
134
|
+
// const result = value();
|
|
135
|
+
// yield* Util.flatten(result); // Recursively flatten the result of a function
|
|
136
|
+
// } else
|
|
137
|
+
// yield value; // Yield primitive values as is
|
|
138
|
+
// },
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get the value of an input as the most appropriate JavaScript type.
|
|
142
|
+
* @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLDivElement}
|
|
143
|
+
* @return {string|string[]|number|[]|File[]|Date|boolean} */
|
|
144
|
+
getInputValue(node) {
|
|
145
|
+
// .type is a built-in DOM property
|
|
146
|
+
if (node.type === 'checkbox' || node.type === 'radio')
|
|
147
|
+
return node.checked; // Boolean
|
|
148
|
+
if (node.type === 'file')
|
|
149
|
+
return [...node.files]; // FileList
|
|
150
|
+
if (node.type === 'number' || node.type === 'range')
|
|
151
|
+
return node.valueAsNumber; // Number
|
|
152
|
+
if (node.type === 'date' || node.type === 'time' || node.type === 'datetime-local')
|
|
153
|
+
return node.valueAsDate; // Date Object
|
|
154
|
+
if (node.type === 'select-multiple') // <select multiple>
|
|
155
|
+
return [...node.selectedOptions].map(option => option.value); // Array of Strings
|
|
156
|
+
|
|
157
|
+
return node.value; // String
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
isEvent(attrName) {
|
|
161
|
+
return attrName.startsWith('on') && attrName in Globals.div;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param el {HTMLElement}
|
|
166
|
+
* @param prop {string}
|
|
167
|
+
* @returns {boolean} */
|
|
168
|
+
isHtmlProp(el, prop) {
|
|
169
|
+
let key = el.tagName + '.' + prop;
|
|
170
|
+
let result = Globals.htmlProps[key];
|
|
171
|
+
if (result === undefined) { // Caching just barely makes this slightly faster.
|
|
172
|
+
let proto = Object.getPrototypeOf(el);
|
|
173
|
+
|
|
174
|
+
// Find the first HTMLElement that we inherit from (not our own classes)
|
|
175
|
+
while (proto) {
|
|
176
|
+
const ctorName = proto.constructor.name;
|
|
177
|
+
if (ctorName.startsWith('HTML') && ctorName.endsWith('Element'))
|
|
178
|
+
break
|
|
179
|
+
proto = Object.getPrototypeOf(proto);
|
|
180
|
+
}
|
|
181
|
+
Globals.htmlProps[key] = result = (proto
|
|
182
|
+
? !!Object.getOwnPropertyDescriptor(proto, prop)?.set
|
|
183
|
+
: false);
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Is it an array and a path that can be evaluated by delve() ?
|
|
190
|
+
* We allow the first element to be null/undefined so binding can report errors.
|
|
191
|
+
* @param arr {Array|*}
|
|
192
|
+
* @returns {boolean} */
|
|
193
|
+
isPath(arr) {
|
|
194
|
+
return Array.isArray(arr) && arr.length >=2 // An array of at least two elements.
|
|
195
|
+
&& (typeof arr[0] === 'object' || arr[0] === undefined) // Where the first element is an object, null, or undefined.
|
|
196
|
+
&& !arr.slice(1).find(p => typeof p !== 'string' && typeof p !== 'number'); // Path 1..x is only numbers and strings.
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
isFalsy(val) {
|
|
200
|
+
return val === undefined || val === false || val === null;
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
isPrimitive(val) {
|
|
205
|
+
return typeof val === 'string' || typeof val === 'number'
|
|
206
|
+
},*/
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* If val is a function, evaluate it recursively until the result is not a function.
|
|
210
|
+
* If it's an array or an object, convert it to Json.
|
|
211
|
+
* If it's a Date, format it as Y-m-d H:i:s
|
|
212
|
+
* @param val
|
|
213
|
+
* @returns {string|number|boolean} */
|
|
214
|
+
makePrimitive(val) {
|
|
215
|
+
if (typeof val === 'function')
|
|
216
|
+
return Util.makePrimitive(val());
|
|
217
|
+
else if (val instanceof Date)
|
|
218
|
+
return val.toISOString().replace(/T/, ' ');
|
|
219
|
+
else if (Array.isArray(val) || typeof val === 'object')
|
|
220
|
+
return ''; // JSON.stringify(val);
|
|
221
|
+
return val;
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Use an array as the value of a map, appending to it when we add.
|
|
226
|
+
* Used by watch.js.
|
|
227
|
+
* @param map {Map|WeakMap|Object}
|
|
228
|
+
* @param key
|
|
229
|
+
* @param value */
|
|
230
|
+
mapArrayAdd(map, key, value) {
|
|
231
|
+
let result = map.get(key);
|
|
232
|
+
if (!result) {
|
|
233
|
+
result = [value];
|
|
234
|
+
map.set(key, result);
|
|
235
|
+
}
|
|
236
|
+
else
|
|
237
|
+
result.push(value);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Remove nodes from the beginning and end that are not:
|
|
242
|
+
* 1. Elements.
|
|
243
|
+
* 2. Non-whitespace text nodes.
|
|
244
|
+
* @param nodes {Node[]|NodeList}
|
|
245
|
+
* @returns {Node[]} */
|
|
246
|
+
trimEmptyNodes(nodes) {
|
|
247
|
+
const shouldTrimNode = node =>
|
|
248
|
+
node.nodeType !== Node.ELEMENT_NODE &&
|
|
249
|
+
(node.nodeType !== Node.TEXT_NODE || node.textContent.trim() === '');
|
|
250
|
+
|
|
251
|
+
// Convert nodeList to an array for easier manipulation
|
|
252
|
+
const result = [...nodes]
|
|
253
|
+
|
|
254
|
+
// Trim from the start
|
|
255
|
+
while (result.length > 0 && shouldTrimNode(result[0]))
|
|
256
|
+
result.shift();
|
|
257
|
+
|
|
258
|
+
// Trim from the end
|
|
259
|
+
while (result.length > 0 && shouldTrimNode(result[result.length - 1]))
|
|
260
|
+
result.pop();
|
|
261
|
+
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export default Util;
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
// For debugging only
|
|
271
|
+
//#IFDEV
|
|
272
|
+
export function setIndent(items, level=1) {
|
|
273
|
+
if (typeof items === 'string')
|
|
274
|
+
items = items.split(/\r?\n/g)
|
|
275
|
+
|
|
276
|
+
return items.map(str => {
|
|
277
|
+
if (level > 0)
|
|
278
|
+
return ' '.repeat(level) + str;
|
|
279
|
+
else if (level < 0)
|
|
280
|
+
return str.replace(new RegExp(`^ {0,${Math.abs(level)}}`), '');
|
|
281
|
+
return str;
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function nodeToArrayTree(node, callback=null) {
|
|
286
|
+
if (!node) return [];
|
|
287
|
+
|
|
288
|
+
let result = [];
|
|
289
|
+
|
|
290
|
+
if (callback)
|
|
291
|
+
result.push(...callback(node))
|
|
292
|
+
|
|
293
|
+
if (node.nodeType === 1) {
|
|
294
|
+
let attrs = Array.from(node.attributes).map(attr => `${attr.name}="${attr.value}"`).join(' ');
|
|
295
|
+
let openingTag = `<${node.nodeName.toLowerCase()}${attrs ? ' ' + attrs : ''}>`;
|
|
296
|
+
|
|
297
|
+
let childrenArray = [];
|
|
298
|
+
for (let child of node.childNodes) {
|
|
299
|
+
let childResult = nodeToArrayTree(child, callback);
|
|
300
|
+
if (childResult.length > 0) {
|
|
301
|
+
childrenArray.push(childResult);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//let closingTag = `</${node.nodeName.toLowerCase()}>`;
|
|
306
|
+
|
|
307
|
+
result.push(openingTag, ...childrenArray);
|
|
308
|
+
} else if (node.nodeType === 3) {
|
|
309
|
+
result.push("'"+node.nodeValue+"'");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
export function flattenAndIndent(inputArray, indent = "") {
|
|
317
|
+
let result = [];
|
|
318
|
+
|
|
319
|
+
for (let item of inputArray) {
|
|
320
|
+
if (Array.isArray(item)) {
|
|
321
|
+
// Recursively handle nested arrays with increased indentation
|
|
322
|
+
result = result.concat(flattenAndIndent(item, indent + " "));
|
|
323
|
+
} else {
|
|
324
|
+
result.push(indent + item);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
//#ENDIF
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import Util from './Util.js';
|
|
2
|
+
import Globals from "./Globals.js";
|
|
3
|
+
|
|
4
|
+
function defineClass(Class, tagName, extendsTag) {
|
|
5
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
6
|
+
tagName = tagName || Util.camelToDashes(Class.name)
|
|
7
|
+
if (!tagName.includes('-'))
|
|
8
|
+
tagName += '-element';
|
|
9
|
+
|
|
10
|
+
let options = null;
|
|
11
|
+
if (extendsTag)
|
|
12
|
+
options = {extends: extendsTag}
|
|
13
|
+
|
|
14
|
+
customElements[define](tagName, Class, options)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a version of the Solarite class that extends from the given tag name.
|
|
20
|
+
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
21
|
+
* 1. customElements.define() is called automatically when you create the first instance.
|
|
22
|
+
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
23
|
+
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
24
|
+
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
25
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
26
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
27
|
+
* 6. Can we extend from other element types like TR?
|
|
28
|
+
* 7. Shows default text if render() function isn't defined.
|
|
29
|
+
*
|
|
30
|
+
* Advantages to inheriting from HTMLElement
|
|
31
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
32
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
33
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
34
|
+
*
|
|
35
|
+
* @param extendsTag {?string}
|
|
36
|
+
* @return {Class} */
|
|
37
|
+
export default function createSolarite(extendsTag=null) {
|
|
38
|
+
|
|
39
|
+
let BaseClass = HTMLElement;
|
|
40
|
+
if (extendsTag && !extendsTag.includes('-')) {
|
|
41
|
+
extendsTag = extendsTag.toLowerCase();
|
|
42
|
+
|
|
43
|
+
BaseClass = Globals.elementClasses[extendsTag];
|
|
44
|
+
if (!BaseClass) { // TODO: Use Cache
|
|
45
|
+
BaseClass = document.createElement(extendsTag).constructor;
|
|
46
|
+
Globals.elementClasses[extendsTag] = BaseClass
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
52
|
+
* @type {HTMLElement} */
|
|
53
|
+
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
54
|
+
construct(Parent, args, Class) {
|
|
55
|
+
defineClass(Class, null, extendsTag)
|
|
56
|
+
|
|
57
|
+
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
58
|
+
// Such as loading them from attributes, if I could find a way to do so.
|
|
59
|
+
|
|
60
|
+
// This line is equivalent the to super() call.
|
|
61
|
+
return Reflect.construct(Parent, args, Class);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return class Solarite extends HTMLElementAutoDefine {
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* TODO: Make these standalone functions.
|
|
70
|
+
* Callbacks.
|
|
71
|
+
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
72
|
+
onConnect;
|
|
73
|
+
|
|
74
|
+
onFirstConnect;
|
|
75
|
+
onDisconnect;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param options {RenderOptions} */
|
|
79
|
+
constructor(options={}) {
|
|
80
|
+
super();
|
|
81
|
+
|
|
82
|
+
// TODO: Is options.render ever used?
|
|
83
|
+
if (options.render===true)
|
|
84
|
+
this.render();
|
|
85
|
+
|
|
86
|
+
else if (options.render===false)
|
|
87
|
+
Globals.rendered.add(this); // Don't render on connectedCallback()
|
|
88
|
+
|
|
89
|
+
// Add slot children before constructor code executes.
|
|
90
|
+
// This breaks the styleStaticNested test.
|
|
91
|
+
// PendingChildren is setup in NodeGroup.instantiateComponent()
|
|
92
|
+
// TODO: Match named slots.
|
|
93
|
+
//let ch = Globals.pendingChildren.pop();
|
|
94
|
+
//if (ch) // TODO: how could there be a slot before render is called?
|
|
95
|
+
// (this.querySelector('slot') || this).append(...ch);
|
|
96
|
+
|
|
97
|
+
/** @deprecated
|
|
98
|
+
Object.defineProperty(this, 'html', {
|
|
99
|
+
set(html) {
|
|
100
|
+
Globals.rendered.add(this);
|
|
101
|
+
if (typeof html === 'string') {
|
|
102
|
+
console.warn("Assigning to this.html without the r template prefix.")
|
|
103
|
+
this.innerHTML = html;
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
this.modifications = r(this, html, options);
|
|
107
|
+
}
|
|
108
|
+
})*/
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
let pthis = new Proxy(this, {
|
|
112
|
+
get(obj, prop) {
|
|
113
|
+
return Reflect.get(obj, prop)
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
this.render = this.render.bind(pthis);
|
|
117
|
+
*/
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Call render() only if it hasn't already been called. */
|
|
122
|
+
renderFirstTime() {
|
|
123
|
+
if (!Globals.rendered.has(this) && this.render)
|
|
124
|
+
this.render();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Called automatically by the browser. */
|
|
129
|
+
connectedCallback() {
|
|
130
|
+
this.renderFirstTime();
|
|
131
|
+
if (!Globals.connected.has(this)) {
|
|
132
|
+
Globals.connected.add(this);
|
|
133
|
+
if (this.onFirstConnect)
|
|
134
|
+
this.onFirstConnect();
|
|
135
|
+
}
|
|
136
|
+
if (this.onConnect)
|
|
137
|
+
this.onConnect();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
disconnectedCallback() {
|
|
141
|
+
if (this.onDisconnect)
|
|
142
|
+
this.onDisconnect();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
static define(tagName=null) {
|
|
147
|
+
defineClass(this, tagName, extendsTag)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Trick to prevent minifier from renaming this method.
|
|
153
|
+
let define = 'define';
|
|
154
|
+
let getName = 'getName';
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Follow a path into an object.
|
|
3
3
|
* @param obj {object}
|
|
4
4
|
* @param path {string[]}
|
|
5
|
-
* @param createVal {*} If set, non-
|
|
5
|
+
* @param createVal {*} If set, non-existent paths will be created and value at path will be set to createVal.
|
|
6
6
|
* @return {*} The value, or undefined if it can't be reached. */
|
|
7
|
-
export default function delve(obj, path, createVal =
|
|
8
|
-
let isCreate = createVal !==
|
|
7
|
+
export default function delve(obj, path, createVal = d) {
|
|
8
|
+
let isCreate = createVal !== d;
|
|
9
9
|
|
|
10
10
|
let len = path.length;
|
|
11
11
|
if (!obj && !isCreate && len)
|
|
@@ -40,4 +40,5 @@ export default function delve(obj, path, createVal = delveDontCreate) {
|
|
|
40
40
|
return obj;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// d means "don't create"
|
|
44
|
+
let d = {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import Util from "./Util.js";
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -24,23 +24,28 @@
|
|
|
24
24
|
* }
|
|
25
25
|
*
|
|
26
26
|
* @param el {HTMLElement}
|
|
27
|
-
* @param
|
|
28
|
-
* @param
|
|
29
|
-
* @param type {ArgType|function|*[]}
|
|
27
|
+
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
28
|
+
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
29
|
+
* @param type {ArgType|function|Class|*[]}
|
|
30
30
|
* If an array, use the value if it's in the array, otherwise return undefined.
|
|
31
31
|
* If it's a function, pass the value to the function and return the result.
|
|
32
|
-
* @param fallback {*} If the type can't be parsed as the given type, use this value.
|
|
32
|
+
* @param fallback {*} If the defaultValue is undefiend and type can't be parsed as the given type, use this value.
|
|
33
|
+
* TODO: Should this be merged with the defaultValue argument?
|
|
33
34
|
* @return {*} Undefined if attribute isn't set. */
|
|
34
|
-
export function getArg(el,
|
|
35
|
-
let
|
|
35
|
+
export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String, fallback=undefined) {
|
|
36
|
+
let val = defaultValue;
|
|
37
|
+
let attrVal = el.getAttribute(attributeName) || el.getAttribute(Util.camelToDashes(attributeName));
|
|
36
38
|
if (attrVal !== null) // If attribute doesn't exist.
|
|
37
39
|
val = attrVal;
|
|
38
40
|
|
|
39
41
|
if (Array.isArray(type))
|
|
40
42
|
return type.includes(val) ? val : fallback;
|
|
41
43
|
|
|
42
|
-
if (typeof type === 'function')
|
|
43
|
-
return type
|
|
44
|
+
if (typeof type === 'function') {
|
|
45
|
+
return type.constructor
|
|
46
|
+
? new type(val) // arg type is custom Class
|
|
47
|
+
: type(val); // arg type is custom function
|
|
48
|
+
}
|
|
44
49
|
|
|
45
50
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
46
51
|
if (type===ArgType.Bool) {
|
|
@@ -63,18 +68,18 @@ export function getArg(el, name, val=undefined, type=ArgType.String, fallback=un
|
|
|
63
68
|
return isNaN(result) ? fallback : result;
|
|
64
69
|
case ArgType.String:
|
|
65
70
|
return [undefined, null, false].includes(val) ? '' : val+'';
|
|
66
|
-
case ArgType.
|
|
71
|
+
case ArgType.Json:
|
|
67
72
|
case ArgType.Eval:
|
|
68
73
|
if (typeof val === 'string' && val.length)
|
|
69
74
|
try {
|
|
70
|
-
if (type === ArgType.
|
|
75
|
+
if (type === ArgType.Json)
|
|
71
76
|
return JSON.parse(val);
|
|
72
77
|
else
|
|
73
78
|
return eval(`(${val})`);
|
|
74
79
|
} catch (e) {
|
|
75
80
|
return val;
|
|
76
81
|
}
|
|
77
|
-
else return
|
|
82
|
+
else return val;
|
|
78
83
|
|
|
79
84
|
// type not provided
|
|
80
85
|
default:
|
|
@@ -82,6 +87,24 @@ export function getArg(el, name, val=undefined, type=ArgType.String, fallback=un
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Experimental. Set multiple arguments/attributes all at once.
|
|
93
|
+
* @param el {HTMLElement}
|
|
94
|
+
* @param args {Record<string, any>}
|
|
95
|
+
* @param types {Record<string, ArgType|function|Class>}
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* constructor({user, path}={}) {
|
|
99
|
+
* setArgs(this, arguments[0], {user: User, path: ArgType.String});
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
export function setArgs(el, args, types) {
|
|
103
|
+
for (let name in args)
|
|
104
|
+
this[name] = getArg(el, name, args[name], types[name] || ArgType.String);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
85
108
|
/**
|
|
86
109
|
* @enum */
|
|
87
110
|
var ArgType = {
|
|
@@ -95,12 +118,15 @@ var ArgType = {
|
|
|
95
118
|
Int: 'Int',
|
|
96
119
|
Float: 'Float',
|
|
97
120
|
String: 'String',
|
|
98
|
-
|
|
121
|
+
|
|
122
|
+
/** @deprecated for Json */
|
|
123
|
+
JSON: 'Json',
|
|
124
|
+
|
|
99
125
|
/**
|
|
100
126
|
* Parse the string value as JSON.
|
|
101
127
|
* If it's not parsable, return the value as a string. */
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
Json: 'Json',
|
|
129
|
+
|
|
104
130
|
/**
|
|
105
131
|
* Evaluate the string as JavaScript using the eval() function.
|
|
106
132
|
* If it can't be evaluated, return the original string. */
|