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/solarite/Util.js
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
let Util = {
|
|
2
|
-
|
|
3
|
-
bindStyles(style, root) {
|
|
4
|
-
let styleId = root.getAttribute('data-style');
|
|
5
|
-
if (!styleId) {
|
|
6
|
-
// Keep track of one style id for each class.
|
|
7
|
-
// TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
|
|
8
|
-
if (!root.constructor.styleId)
|
|
9
|
-
root.constructor.styleId = 1;
|
|
10
|
-
styleId = root.constructor.styleId++;
|
|
11
|
-
|
|
12
|
-
root.setAttribute('data-style', styleId)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let tagName = root.tagName.toLowerCase();
|
|
16
|
-
for (let child of style.childNodes) {
|
|
17
|
-
if (child.nodeType === 3) {
|
|
18
|
-
let oldText = child.textContent;
|
|
19
|
-
let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, tagName + '[data-style="' + styleId + '"]')
|
|
20
|
-
if (oldText !== newText)
|
|
21
|
-
child.textContent = newText;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A generator function that recursively traverses and flattens a value.
|
|
28
|
-
*
|
|
29
|
-
* - If the input is an array, it recursively traverses and flattens the array.
|
|
30
|
-
* - If the input is a function, it calls the function, replaces the function
|
|
31
|
-
* with its result, and flattens the result if necessary. It will recursively
|
|
32
|
-
* call functions that return other functions.
|
|
33
|
-
* - Otherwise it yields the value as is.
|
|
34
|
-
*
|
|
35
|
-
* This function does not create a new array for the flattened values. Instead,
|
|
36
|
-
* it lazily yields each item as it is encountered. This can be more memory-efficient
|
|
37
|
-
* for large or deeply nested structures.
|
|
38
|
-
*
|
|
39
|
-
* @param {any} value - The value to flatten. Can be an array, object, function, or primitive.
|
|
40
|
-
* @yields {any} - The next item in the flattened structure.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* const complexArray = [
|
|
44
|
-
* 1,
|
|
45
|
-
* [2, () => 3, [4, () => [5, 6]], { a: 'object' }],
|
|
46
|
-
* () => () => 7,
|
|
47
|
-
* () => [() => 8, 9],
|
|
48
|
-
* ]; *
|
|
49
|
-
* for (const item of flatten(complexArray))
|
|
50
|
-
* console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
|
|
51
|
-
*/
|
|
52
|
-
*flatten(value) {
|
|
53
|
-
if (Array.isArray(value)) {
|
|
54
|
-
for (const item of value) {
|
|
55
|
-
yield* Util.flatten(item); // Recursively flatten arrays
|
|
56
|
-
}
|
|
57
|
-
} else if (typeof value === 'function') {
|
|
58
|
-
const result = value();
|
|
59
|
-
yield* Util.flatten(result); // Recursively flatten the result of a function
|
|
60
|
-
} else
|
|
61
|
-
yield value; // Yield primitive values as is
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get the value of an input as the most appropriate JavaScript type.
|
|
66
|
-
* @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLDivElement}
|
|
67
|
-
* @return {string|string[]|number|[]|File[]|Date|boolean} */
|
|
68
|
-
getInputValue(node) {
|
|
69
|
-
if (node.type === 'checkbox' || node.type === 'radio')
|
|
70
|
-
return node.checked; // Boolean
|
|
71
|
-
if (node.type === 'file')
|
|
72
|
-
return [...node.files]; // FileList
|
|
73
|
-
if (node.type === 'number' || node.type === 'range')
|
|
74
|
-
return node.valueAsNumber; // Number
|
|
75
|
-
if (node.type === 'date' || node.type === 'time' || node.type === 'datetime-local')
|
|
76
|
-
return node.valueAsDate; // Date Object
|
|
77
|
-
if (node.type === 'select-multiple') // <select multiple>
|
|
78
|
-
return [...node.selectedOptions].map(option => option.value); // Array of Strings
|
|
79
|
-
|
|
80
|
-
return node.value; // String
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Is it an array and a path that can be evaluated by delve() ?
|
|
85
|
-
* @param arr {Array|*}
|
|
86
|
-
* @returns {boolean} */
|
|
87
|
-
isPath(arr) {
|
|
88
|
-
return Array.isArray(arr) && typeof arr[0] === 'object' && !arr.slice(1).find(p => typeof p !== 'string' && typeof p !== 'number');
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Find NodeGroups that had their nodes removed and add those nodes to a Fragment so
|
|
93
|
-
* they're not lost forever and the NodeGroup's internal structure is still consistent.
|
|
94
|
-
* This saves all of a NodeGroup's nodes in order, so that nextChildNode still works.
|
|
95
|
-
* This is necessary because a NodeGroup normally only stores the first and last node.
|
|
96
|
-
* Called from ExprPath.apply().
|
|
97
|
-
* @param oldNodeGroups {NodeGroup[]}
|
|
98
|
-
* @param oldNodes {Node[]} */
|
|
99
|
-
saveOrphans(oldNodeGroups, oldNodes) {
|
|
100
|
-
let oldNgMap = new Map();
|
|
101
|
-
for (let ng of oldNodeGroups) {
|
|
102
|
-
oldNgMap.set(ng.startNode, ng)
|
|
103
|
-
|
|
104
|
-
// TODO: Is this necessary?
|
|
105
|
-
// if (ng.parentPath)
|
|
106
|
-
// ng.parentPath.clearNodesCache();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
for (let i=0, node; node = oldNodes[i]; i++) {
|
|
110
|
-
let ng;
|
|
111
|
-
if (!node.parentNode && (ng = oldNgMap.get(node))) {
|
|
112
|
-
//ng.nodesCache = [];
|
|
113
|
-
let fragment = document.createDocumentFragment();
|
|
114
|
-
let endNode = ng.endNode;
|
|
115
|
-
while (node !== endNode) {
|
|
116
|
-
fragment.append(node);
|
|
117
|
-
//ng.nodesCache.push(node);
|
|
118
|
-
i++;
|
|
119
|
-
node = oldNodes[i];
|
|
120
|
-
}
|
|
121
|
-
fragment.append(endNode);
|
|
122
|
-
//ng.nodesCache.push(endNode);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Remove nodes from the beginning and end that are not:
|
|
129
|
-
* 1. Elements.
|
|
130
|
-
* 2. Non-whitespace text nodes.
|
|
131
|
-
* @param nodes {Node[]|NodeList}
|
|
132
|
-
* @returns {Node[]} */
|
|
133
|
-
trimEmptyNodes(nodes) {
|
|
134
|
-
const shouldTrimNode = node =>
|
|
135
|
-
node.nodeType !== Node.ELEMENT_NODE &&
|
|
136
|
-
(node.nodeType !== Node.TEXT_NODE || node.textContent.trim() === '');
|
|
137
|
-
|
|
138
|
-
// Convert nodeList to an array for easier manipulation
|
|
139
|
-
const result = [...nodes]
|
|
140
|
-
|
|
141
|
-
// Trim from the start
|
|
142
|
-
while (result.length > 0 && shouldTrimNode(result[0]))
|
|
143
|
-
result.shift();
|
|
144
|
-
|
|
145
|
-
// Trim from the end
|
|
146
|
-
while (result.length > 0 && shouldTrimNode(result[result.length - 1]))
|
|
147
|
-
result.pop();
|
|
148
|
-
|
|
149
|
-
return result;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
export default Util;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let div = document.createElement('div');
|
|
158
|
-
export {div}
|
|
159
|
-
|
|
160
|
-
let isEvent = attrName => attrName.startsWith('on') && attrName in div;
|
|
161
|
-
export {isEvent};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Convert a Proper Case name to a name with dashes.
|
|
166
|
-
* Dashes will be placed between letters and numbers.
|
|
167
|
-
* If there are multiple consecutive capital letters followed by another chracater, a dash will be placed before the last capital letter.
|
|
168
|
-
* @param str {string}
|
|
169
|
-
* @return {string}
|
|
170
|
-
*
|
|
171
|
-
* @example
|
|
172
|
-
* 'ProperName' => 'proper-name'
|
|
173
|
-
* 'HTMLElement' => 'html-element'
|
|
174
|
-
* 'BigUI' => 'big-ui'
|
|
175
|
-
* 'UIForm' => 'ui-form'
|
|
176
|
-
* 'A100' => 'a-100' */
|
|
177
|
-
export function camelToDashes(str) {
|
|
178
|
-
// Convert any capital letter that is preceded by a lowercase letter or number to lowercase and precede with a dash.
|
|
179
|
-
str = str.replace(/([a-z0-9])([A-Z])/g, '$1-$2');
|
|
180
|
-
|
|
181
|
-
// Convert any capital letter that is followed by a lowercase letter or number to lowercase and precede with a dash.
|
|
182
|
-
str = str.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2');
|
|
183
|
-
|
|
184
|
-
// Convert any number that is preceded by a lowercase or uppercase letter to be preceded by a dash.
|
|
185
|
-
str = str.replace(/([a-zA-Z])([0-9])/g, '$1-$2');
|
|
186
|
-
|
|
187
|
-
// Convert all the remaining capital letters to lowercase.
|
|
188
|
-
return str.toLowerCase();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Returns false if they're the same. Or the first index where they differ.
|
|
197
|
-
* @param a
|
|
198
|
-
* @param b
|
|
199
|
-
* @returns {boolean} */
|
|
200
|
-
export function arraySame(a, b) {
|
|
201
|
-
let aLength = a.length;
|
|
202
|
-
if (aLength !== b.length)
|
|
203
|
-
return false;
|
|
204
|
-
for (let i=0; i<aLength; i++)
|
|
205
|
-
if (a[i] !== b[i])
|
|
206
|
-
return false;
|
|
207
|
-
return true; // the same.
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* TODO: Turn this into a class because it has internal state.
|
|
213
|
-
* TODO: Don't break on 3<a inside a <script> or <style> tag.
|
|
214
|
-
* @param html {?string} Pass null to reset context.
|
|
215
|
-
* @returns {string} */
|
|
216
|
-
export function htmlContext(html) {
|
|
217
|
-
if (html === null) {
|
|
218
|
-
state = {...defaultState};
|
|
219
|
-
return state.context;
|
|
220
|
-
}
|
|
221
|
-
for (let i = 0; i < html.length; i++) {
|
|
222
|
-
const char = html[i];
|
|
223
|
-
switch (state.context) {
|
|
224
|
-
case htmlContext.Text:
|
|
225
|
-
if (char === '<' && html[i+1].match(/[a-z!]/i)) { // Start of a tag or comment.
|
|
226
|
-
// if (html.slice(i, i+4) === '<!--')
|
|
227
|
-
// state.context = htmlContext.Comment;
|
|
228
|
-
// else
|
|
229
|
-
state.context = htmlContext.Tag;
|
|
230
|
-
state.buffer = '';
|
|
231
|
-
}
|
|
232
|
-
break;
|
|
233
|
-
case htmlContext.Tag:
|
|
234
|
-
if (char === '>') {
|
|
235
|
-
state.context = htmlContext.Text;
|
|
236
|
-
state.quote = null;
|
|
237
|
-
state.buffer = '';
|
|
238
|
-
} else if (char === ' ' && !state.buffer) {
|
|
239
|
-
// No attribute name is present. Skipping the space.
|
|
240
|
-
continue;
|
|
241
|
-
} else if (char === ' ' || char === '/' || char === '?') {
|
|
242
|
-
state.buffer = ''; // Reset the buffer when a delimiter or potential self-closing sign is found.
|
|
243
|
-
} else if (char === '"' || char === "'" || char === '=') {
|
|
244
|
-
state.context = htmlContext.Attribute;
|
|
245
|
-
state.quote = char === '=' ? null : char;
|
|
246
|
-
state.buffer = '';
|
|
247
|
-
} else {
|
|
248
|
-
state.buffer += char;
|
|
249
|
-
}
|
|
250
|
-
break;
|
|
251
|
-
case htmlContext.Attribute:
|
|
252
|
-
if (!state.quote && !state.buffer.length && (char === '"' || char === "'"))
|
|
253
|
-
state.quote = char;
|
|
254
|
-
|
|
255
|
-
else if (char === state.quote || (!state.quote && state.buffer.length)) {
|
|
256
|
-
state.context = htmlContext.Tag;
|
|
257
|
-
state.quote = null;
|
|
258
|
-
state.buffer = '';
|
|
259
|
-
} else if (!state.quote && char === '>') {
|
|
260
|
-
state.context = htmlContext.Text;
|
|
261
|
-
state.quote = null;
|
|
262
|
-
state.buffer = '';
|
|
263
|
-
} else if (char !== ' ') {
|
|
264
|
-
state.buffer += char;
|
|
265
|
-
}
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
return state.context;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
htmlContext.Attribute = 'Attribute';
|
|
275
|
-
htmlContext.Text = 'Text';
|
|
276
|
-
htmlContext.Tag = 'Tag';
|
|
277
|
-
//htmlContext.Comment = 'Comment';
|
|
278
|
-
let defaultState = {
|
|
279
|
-
context: htmlContext.Text, // possible values: 'TEXT', 'TAG', 'ATTRIBUTE'
|
|
280
|
-
quote: null, // possible values: null, '"', "'"
|
|
281
|
-
buffer: '',
|
|
282
|
-
lastChar: null
|
|
283
|
-
};
|
|
284
|
-
let state = {...defaultState};
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
let cacheItems = {};
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* @param item {string}
|
|
295
|
-
* @param initial {*}
|
|
296
|
-
* @returns {*} */
|
|
297
|
-
export function cache(item, initial) {
|
|
298
|
-
let result = cacheItems[item];
|
|
299
|
-
if (!result) {
|
|
300
|
-
cacheItems[item] = initial
|
|
301
|
-
result = initial;
|
|
302
|
-
}
|
|
303
|
-
return result;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
export class WeakCache {
|
|
309
|
-
|
|
310
|
-
items = new WeakMap();
|
|
311
|
-
|
|
312
|
-
constructor(initial) {
|
|
313
|
-
this.initial = initial;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
get(item) {
|
|
317
|
-
let result = this.items.get(item);
|
|
318
|
-
if (!result) {
|
|
319
|
-
let value = typeof this.initial === 'function' ? this.initial() : this.initial;
|
|
320
|
-
this.items.set(item, value)
|
|
321
|
-
result = this.initial;
|
|
322
|
-
}
|
|
323
|
-
return result;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// For debugging only
|
|
329
|
-
//#IFDEV
|
|
330
|
-
export function setIndent(items, level=1) {
|
|
331
|
-
if (typeof items === 'string')
|
|
332
|
-
items = items.split(/\r?\n/g)
|
|
333
|
-
|
|
334
|
-
return items.map(str => {
|
|
335
|
-
if (level > 0)
|
|
336
|
-
return ' '.repeat(level) + str;
|
|
337
|
-
else if (level < 0)
|
|
338
|
-
return str.replace(new RegExp(`^ {0,${Math.abs(level)}}`), '');
|
|
339
|
-
return str;
|
|
340
|
-
})
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export function nodeToArrayTree(node, callback=null) {
|
|
344
|
-
if (!node) return [];
|
|
345
|
-
|
|
346
|
-
let result = [];
|
|
347
|
-
|
|
348
|
-
if (callback)
|
|
349
|
-
result.push(...callback(node))
|
|
350
|
-
|
|
351
|
-
if (node.nodeType === 1) {
|
|
352
|
-
let attrs = Array.from(node.attributes).map(attr => `${attr.name}="${attr.value}"`).join(' ');
|
|
353
|
-
let openingTag = `<${node.nodeName.toLowerCase()}${attrs ? ' ' + attrs : ''}>`;
|
|
354
|
-
|
|
355
|
-
let childrenArray = [];
|
|
356
|
-
for (let child of node.childNodes) {
|
|
357
|
-
let childResult = nodeToArrayTree(child, callback);
|
|
358
|
-
if (childResult.length > 0) {
|
|
359
|
-
childrenArray.push(childResult);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
//let closingTag = `</${node.nodeName.toLowerCase()}>`;
|
|
364
|
-
|
|
365
|
-
result.push(openingTag, ...childrenArray);
|
|
366
|
-
} else if (node.nodeType === 3) {
|
|
367
|
-
result.push("'"+node.nodeValue+"'");
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return result;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
export function flattenAndIndent(inputArray, indent = "") {
|
|
375
|
-
let result = [];
|
|
376
|
-
|
|
377
|
-
for (let item of inputArray) {
|
|
378
|
-
if (Array.isArray(item)) {
|
|
379
|
-
// Recursively handle nested arrays with increased indentation
|
|
380
|
-
result = result.concat(flattenAndIndent(item, indent + " "));
|
|
381
|
-
} else {
|
|
382
|
-
result.push(indent + item);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return result;
|
|
387
|
-
}
|
|
388
|
-
//#ENDIF
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import Util from "../util/Util.js";
|
|
2
|
-
//import {assert} from "../util/Errors.js";
|
|
3
|
-
import delve from "../util/delve.js";
|
|
4
|
-
import {getArg, ArgType} from "./getArg.js";
|
|
5
|
-
import {getObjectHash} from "./hash.js";
|
|
6
|
-
//import NodeGroupManager from "./NodeGroupManager.js";
|
|
7
|
-
import r from "./r.js";
|
|
8
|
-
import {camelToDashes} from "./Util.js";
|
|
9
|
-
import Globals from "./Globals.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//import {watchGet, watchSet} from "./watch.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function defineClass(Class, tagName, extendsTag) {
|
|
17
|
-
if (!customElements.getName(Class)) { // If not previously defined.
|
|
18
|
-
tagName = tagName || camelToDashes(Class.name)
|
|
19
|
-
if (!tagName.includes('-'))
|
|
20
|
-
tagName += '-element';
|
|
21
|
-
|
|
22
|
-
let options = null;
|
|
23
|
-
if (extendsTag)
|
|
24
|
-
options = {extends: extendsTag}
|
|
25
|
-
|
|
26
|
-
customElements.define(tagName, Class, options)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Create a version of the Solarite class that extends from the given tag name.
|
|
36
|
-
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
37
|
-
* 1. customElements.define() is called automatically when you create the first instance.
|
|
38
|
-
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
39
|
-
* 3. Child elements are added before constructor is called. But they're also passed to the constructor.
|
|
40
|
-
* 4. We can use this.html = r`...` to set html.
|
|
41
|
-
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
42
|
-
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
43
|
-
* 6. Can we extend from other element types like TR?
|
|
44
|
-
* 7. Shows default text if render() function isn't defined.
|
|
45
|
-
*
|
|
46
|
-
* Advantages to inheriting from HTMLElement
|
|
47
|
-
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
48
|
-
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
49
|
-
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
50
|
-
*
|
|
51
|
-
* @param extendsTag {?string}
|
|
52
|
-
* @return {Class} */
|
|
53
|
-
export default function createSolarite(extendsTag=null) {
|
|
54
|
-
|
|
55
|
-
let BaseClass = HTMLElement;
|
|
56
|
-
if (extendsTag && !extendsTag.includes('-')) {
|
|
57
|
-
extendsTag = extendsTag.toLowerCase();
|
|
58
|
-
|
|
59
|
-
BaseClass = Globals.elementClasses[extendsTag];
|
|
60
|
-
if (!BaseClass) { // TODO: Use Cache
|
|
61
|
-
BaseClass = document.createElement(extendsTag).constructor;
|
|
62
|
-
Globals.elementClasses[extendsTag] = BaseClass
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
68
|
-
* @type {HTMLElement} */
|
|
69
|
-
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
70
|
-
construct(Parent, args, Class) {
|
|
71
|
-
defineClass(Class, null, extendsTag)
|
|
72
|
-
|
|
73
|
-
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
74
|
-
// Such as loading them from attributes, if I could find a way to do so.
|
|
75
|
-
|
|
76
|
-
// This line is equivalent the to super() call.
|
|
77
|
-
return Reflect.construct(Parent, args, Class);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
return class Solarite extends HTMLElementAutoDefine {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* TODO: Make these standalone functions.
|
|
86
|
-
* Callbacks.
|
|
87
|
-
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
88
|
-
onConnect = Util.callback();
|
|
89
|
-
|
|
90
|
-
onFirstConnect = Util.callback();
|
|
91
|
-
onDisconnect = Util.callback();
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param options {RenderOptions} */
|
|
95
|
-
constructor(options={}) {
|
|
96
|
-
super();
|
|
97
|
-
|
|
98
|
-
// TODO: Is options.render ever used?
|
|
99
|
-
if (options.render===true)
|
|
100
|
-
this.render();
|
|
101
|
-
|
|
102
|
-
else if (options.render===false)
|
|
103
|
-
Globals.rendered.add(this); // Don't render on connectedCallback()
|
|
104
|
-
|
|
105
|
-
// Add children before constructor code executes.
|
|
106
|
-
// PendingChildren is setup in NodeGroup.createNewComponent()
|
|
107
|
-
// TODO: Match named slots.
|
|
108
|
-
let ch = Globals.pendingChildren.pop();
|
|
109
|
-
if (ch)
|
|
110
|
-
(this.querySelector('slot') || this).append(...ch);
|
|
111
|
-
|
|
112
|
-
/** @deprecated */
|
|
113
|
-
Object.defineProperty(this, 'html', {
|
|
114
|
-
set(html) {
|
|
115
|
-
Globals.rendered.add(this);
|
|
116
|
-
if (typeof html === 'string') {
|
|
117
|
-
console.warn("Assigning to this.html without the r template prefix.")
|
|
118
|
-
this.innerHTML = html;
|
|
119
|
-
}
|
|
120
|
-
else
|
|
121
|
-
this.modifications = r(this, html, options);
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
/*
|
|
126
|
-
let pthis = new Proxy(this, {
|
|
127
|
-
get(obj, prop) {
|
|
128
|
-
return Reflect.get(obj, prop)
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
this.render = this.render.bind(pthis);
|
|
132
|
-
*/
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Call render() only if it hasn't already been called. */
|
|
137
|
-
renderFirstTime() {
|
|
138
|
-
if (!Globals.rendered.has(this) && this.render)
|
|
139
|
-
this.render();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Called automatically by the browser. */
|
|
144
|
-
connectedCallback() {
|
|
145
|
-
this.renderFirstTime();
|
|
146
|
-
if (!Globals.connected.has(this)) {
|
|
147
|
-
Globals.connected.add(this);
|
|
148
|
-
this.onFirstConnect();
|
|
149
|
-
}
|
|
150
|
-
this.onConnect();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
disconnectedCallback() {
|
|
154
|
-
this.onDisconnect();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
static define(tagName=null) {
|
|
159
|
-
defineClass(this, tagName, extendsTag)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
//#IFDEV
|
|
163
|
-
|
|
164
|
-
/** @deprecated */
|
|
165
|
-
renderWatched() {
|
|
166
|
-
let ngm = NodeGroupManager.get(this);
|
|
167
|
-
|
|
168
|
-
let nodeGroupUpdates = [];
|
|
169
|
-
|
|
170
|
-
for (let change of ngm.changes) {
|
|
171
|
-
if (change.action === 'set') {
|
|
172
|
-
for (let transformerInfo of change.transformerInfo) {
|
|
173
|
-
|
|
174
|
-
let oldHash = transformerInfo.hash;
|
|
175
|
-
|
|
176
|
-
let newObj = delve(watchSet(transformerInfo.path[0]), transformerInfo.path.slice(1));
|
|
177
|
-
let newTemplate = transformerInfo.transformer(newObj);
|
|
178
|
-
let newHash = getObjectHash(newTemplate);
|
|
179
|
-
let ngs = [...ngm.nodeGroupsAvailable.data[oldHash]];
|
|
180
|
-
for (let ng of ngs) {
|
|
181
|
-
nodeGroupUpdates.push([ng, oldHash, newHash, newTemplate.exprs, transformerInfo]);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
else if (change.action === 'delete') {
|
|
187
|
-
for (let hash of change.value) {
|
|
188
|
-
let ngs = [...ngm.nodeGroupsAvailable.getAll(hash)]; // deletes from nodeGroupsAvailable.
|
|
189
|
-
|
|
190
|
-
for (let ng of ngs) {
|
|
191
|
-
if (ng.parentPath)
|
|
192
|
-
ng.parentPath.clearNodesCache();
|
|
193
|
-
|
|
194
|
-
for (let node of ng.getNodes())
|
|
195
|
-
node.remove();
|
|
196
|
-
|
|
197
|
-
// TODO: Update ancestor NodeGroup exactKeys
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
else if (change.action === 'insert') {
|
|
202
|
-
|
|
203
|
-
let beforeNg = change.beforeTemplate ? ngm.getNodeGroup(change.beforeTemplate, true) : null;
|
|
204
|
-
let arrayPath = [change.root, ...change.path];
|
|
205
|
-
|
|
206
|
-
// Get anchor so we can use it to get the parent
|
|
207
|
-
// TODO: Should this be watchGet(change.root) ?
|
|
208
|
-
for (let loopInfo of ngm.getLoopInfo([change.root, ...change.path.slice(0, -1)])) {
|
|
209
|
-
|
|
210
|
-
// Change.extra is aTemplate telling us where to insert before.
|
|
211
|
-
let beforeNode = beforeNg?.startNode || loopInfo.template.parentPath.nodeMarker;
|
|
212
|
-
|
|
213
|
-
// Loop over every item added to the array.
|
|
214
|
-
let i = 0; // TODO: How to get real insert index.
|
|
215
|
-
for (let obj of change.value) {
|
|
216
|
-
|
|
217
|
-
// Same logic as forEach() function.
|
|
218
|
-
|
|
219
|
-
let callback = loopInfo.itemTransformer;
|
|
220
|
-
let path = [...arrayPath.slice(0, -1), (arrayPath.at(-1) * 1 + i) + ''];
|
|
221
|
-
|
|
222
|
-
// Shortened logic found in watchGet(), but not any faster?
|
|
223
|
-
// the watchSet() is what makes this slower!
|
|
224
|
-
// let obj = delve(watchSet(path[0]), path.slice(1));
|
|
225
|
-
// let template = callback(obj);
|
|
226
|
-
// let serializedPath = serializePath(path);
|
|
227
|
-
// pathToTransformer.add(serializedPath, new TransformerInfo(path, callback, template)); // Uses a Set() to ensure no duplicates.
|
|
228
|
-
|
|
229
|
-
let template = watchGet(path, callback);
|
|
230
|
-
i++;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
//let template = loopInfo.itemTransformer(obj); // What if it takes more than one obj argument?
|
|
234
|
-
|
|
235
|
-
// Create new NodeGroup
|
|
236
|
-
let ng = ngm.getNodeGroup(template, false, true);
|
|
237
|
-
ng.parentPath = beforeNg?.parentPath || loopInfo.template.parentPath;
|
|
238
|
-
|
|
239
|
-
for (let node of ng.getNodes())
|
|
240
|
-
beforeNode.parentNode.insertBefore(node, beforeNode);
|
|
241
|
-
|
|
242
|
-
if (ng.parentPath) // This check is needed for the forEachSpliceInsert test, but why?
|
|
243
|
-
ng.parentPath.clearNodesCache();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// TODO: Update ancestor NodeGroup exactKeys
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Update them all at once, that way we can reassign the same value twice.
|
|
252
|
-
for (let [ng, oldHash, newHash, exprs, ti] of nodeGroupUpdates) {
|
|
253
|
-
ng.applyExprs(exprs);
|
|
254
|
-
ngm.nodeGroupsAvailable.data[oldHash].delete(ng);
|
|
255
|
-
ng.exactKey = ti.hash = newHash;
|
|
256
|
-
ngm.nodeGroupsAvailable.add(ng.exactKey, ng); // Add back to Map with new key.
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
ngm.changes = [];
|
|
261
|
-
|
|
262
|
-
return []; // TODO
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* @deprecated Use the getArg() function instead. */
|
|
267
|
-
getArg(name, val=null, type=ArgType.String) {
|
|
268
|
-
throw new Error('deprecated');
|
|
269
|
-
return getArg(this, name, val, type);
|
|
270
|
-
}
|
|
271
|
-
//#ENDIF
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|