solarite 0.2.3 → 0.3.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/dist/Solarite-debug.js +1708 -1211
- package/dist/Solarite.js +1642 -1047
- package/dist/Solarite.min.js +2 -2
- package/package.json +1 -1
- package/readme.md +1 -3
- package/src/solarite/ExprPath.js +479 -196
- package/src/solarite/Globals.js +77 -51
- package/src/solarite/HtmlParser.js +91 -0
- package/src/solarite/NodeGroup.js +288 -195
- package/src/solarite/Shell.js +117 -91
- package/src/solarite/Solarite.js +7 -3
- package/src/solarite/Template.js +23 -18
- package/src/solarite/Util.js +117 -179
- package/src/solarite/createSolarite.js +16 -138
- package/src/solarite/getArg.js +31 -16
- package/src/solarite/{r.js → h.js} +56 -18
- package/src/solarite/hash.js +12 -9
- package/src/solarite/watch.js +546 -0
- package/src/util/Errors.js +1 -0
- package/src/util/MultiValueMap.js +73 -13
- package/src/util/Util.js +15 -2
- package/src/util/delve.js +5 -4
- package/src/solarite/watch3.js +0 -98
- package/src/util/WeakArray.js +0 -33
package/src/solarite/Util.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
|
+
import Globals from "./Globals.js";
|
|
2
|
+
import delve from "../util/delve.js";
|
|
3
|
+
|
|
1
4
|
let Util = {
|
|
2
5
|
|
|
6
|
+
bindId(root, el) {
|
|
7
|
+
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
8
|
+
if (id) { // If something hasn't removed the id.
|
|
9
|
+
|
|
10
|
+
// Don't allow overwriting existing class properties if they already have a non-Node value.
|
|
11
|
+
if (root[id] && !(root[id] instanceof Node))
|
|
12
|
+
throw new Error(`${root.constructor.name}.${id} already has a value. ` +
|
|
13
|
+
`Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
|
|
14
|
+
|
|
15
|
+
delve(root, id.split(/\./g), el);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param style {HTMLStyleElement}
|
|
21
|
+
* @param root {HTMLElement} */
|
|
3
22
|
bindStyles(style, root) {
|
|
4
23
|
let styleId = root.getAttribute('data-style');
|
|
5
24
|
if (!styleId) {
|
|
@@ -12,17 +31,60 @@ let Util = {
|
|
|
12
31
|
root.setAttribute('data-style', styleId)
|
|
13
32
|
}
|
|
14
33
|
|
|
34
|
+
// Replace ":host" with "tagName[data-style=...]" in the css.
|
|
15
35
|
let tagName = root.tagName.toLowerCase();
|
|
16
36
|
for (let child of style.childNodes) {
|
|
17
37
|
if (child.nodeType === 3) {
|
|
18
38
|
let oldText = child.textContent;
|
|
19
|
-
let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, tagName
|
|
39
|
+
let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}[data-style="${styleId}"]`)
|
|
20
40
|
if (oldText !== newText)
|
|
21
41
|
child.textContent = newText;
|
|
22
42
|
}
|
|
23
43
|
}
|
|
24
44
|
},
|
|
25
45
|
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convert a Proper Case name to a name with dashes.
|
|
49
|
+
* Dashes will be placed between letters and numbers.
|
|
50
|
+
* If there are multiple consecutive capital letters followed by another chracater, a dash will be placed before the last capital letter.
|
|
51
|
+
* @param str {string}
|
|
52
|
+
* @return {string}
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* 'ProperName' => 'proper-name'
|
|
56
|
+
* 'HTMLElement' => 'html-element'
|
|
57
|
+
* 'BigUI' => 'big-ui'
|
|
58
|
+
* 'UIForm' => 'ui-form'
|
|
59
|
+
* 'A100' => 'a-100' */
|
|
60
|
+
camelToDashes(str) {
|
|
61
|
+
// Convert any capital letter that is preceded by a lowercase letter or number to lowercase and precede with a dash.
|
|
62
|
+
str = str.replace(/([a-z0-9])([A-Z])/g, '$1-$2');
|
|
63
|
+
|
|
64
|
+
// Convert any capital letter that is followed by a lowercase letter or number to lowercase and precede with a dash.
|
|
65
|
+
str = str.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2');
|
|
66
|
+
|
|
67
|
+
// Convert any number that is preceded by a lowercase or uppercase letter to be preceded by a dash.
|
|
68
|
+
str = str.replace(/([a-zA-Z])([0-9])/g, '$1-$2');
|
|
69
|
+
|
|
70
|
+
// Convert all the remaining capital letters to lowercase.
|
|
71
|
+
return str.toLowerCase();
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Converts a string written in kebab-case to camelCase.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} str - The input string written in kebab-case.
|
|
78
|
+
* @return {string} - The resulting camelCase string.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* dashesToCamel('example-string') // Returns 'exampleString'
|
|
82
|
+
* dashesToCamel('another-example-test') // Returns 'anotherExampleTest' */
|
|
83
|
+
dashesToCamel(str) {
|
|
84
|
+
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
|
|
26
88
|
/**
|
|
27
89
|
* A generator function that recursively traverses and flattens a value.
|
|
28
90
|
*
|
|
@@ -66,6 +128,7 @@ let Util = {
|
|
|
66
128
|
* @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLDivElement}
|
|
67
129
|
* @return {string|string[]|number|[]|File[]|Date|boolean} */
|
|
68
130
|
getInputValue(node) {
|
|
131
|
+
// .type is a built-in DOM property
|
|
69
132
|
if (node.type === 'checkbox' || node.type === 'radio')
|
|
70
133
|
return node.checked; // Boolean
|
|
71
134
|
if (node.type === 'file')
|
|
@@ -80,48 +143,63 @@ let Util = {
|
|
|
80
143
|
return node.value; // String
|
|
81
144
|
},
|
|
82
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @param el {HTMLElement}
|
|
148
|
+
* @param prop {string}
|
|
149
|
+
* @returns {boolean} */
|
|
150
|
+
isHtmlProp(el, prop) {
|
|
151
|
+
let key = el.tagName + '.' + prop;
|
|
152
|
+
let result = Globals.htmlProps[key];
|
|
153
|
+
if (result === undefined) { // Caching just barely makes this slightly faster.
|
|
154
|
+
let proto = Object.getPrototypeOf(el);
|
|
155
|
+
|
|
156
|
+
// Find the first HTMLElement that we inherit from (not our own classes)
|
|
157
|
+
while (proto) {
|
|
158
|
+
const ctorName = proto.constructor.name;
|
|
159
|
+
if (ctorName.startsWith('HTML') && ctorName.endsWith('Element'))
|
|
160
|
+
break
|
|
161
|
+
proto = Object.getPrototypeOf(proto);
|
|
162
|
+
}
|
|
163
|
+
Globals.htmlProps[key] = result = (proto
|
|
164
|
+
? !!Object.getOwnPropertyDescriptor(proto, prop)?.set
|
|
165
|
+
: false);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
},
|
|
169
|
+
|
|
83
170
|
/**
|
|
84
171
|
* Is it an array and a path that can be evaluated by delve() ?
|
|
172
|
+
* We allow the first element to be null/undefined so binding can report errors.
|
|
85
173
|
* @param arr {Array|*}
|
|
86
174
|
* @returns {boolean} */
|
|
87
175
|
isPath(arr) {
|
|
88
|
-
return Array.isArray(arr) &&
|
|
176
|
+
return Array.isArray(arr) && arr.length >=2 // An array of at least two elements.
|
|
177
|
+
&& (typeof arr[0] === 'object' || arr[0] === undefined) // Where the first element is an object, null, or undefined.
|
|
178
|
+
&& !arr.slice(1).find(p => typeof p !== 'string' && typeof p !== 'number'); // Path 1..x is only numbers and strings.
|
|
89
179
|
},
|
|
90
180
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
}
|
|
181
|
+
isFalsy(val) {
|
|
182
|
+
return val === undefined || val === false || val === null;
|
|
183
|
+
},
|
|
108
184
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
185
|
+
isPrimitive(val) {
|
|
186
|
+
return typeof val === 'string' || typeof val === 'number'
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* If val is a function, evaluate it recursively until the result is not a function.
|
|
191
|
+
* If it's an array or an object, convert it to Json.
|
|
192
|
+
* If it's a Date, format it as Y-m-d H:i:s
|
|
193
|
+
* @param val
|
|
194
|
+
* @returns {string|number|boolean} */
|
|
195
|
+
makePrimitive(val) {
|
|
196
|
+
if (typeof val === 'function')
|
|
197
|
+
return Util.makePrimitive(val());
|
|
198
|
+
else if (val instanceof Date)
|
|
199
|
+
return val.toISOString().replace(/T/, ' ');
|
|
200
|
+
else if (Array.isArray(val) || typeof val === 'object')
|
|
201
|
+
return ''; // JSON.stringify(val);
|
|
202
|
+
return val;
|
|
125
203
|
},
|
|
126
204
|
|
|
127
205
|
/**
|
|
@@ -154,38 +232,12 @@ export default Util;
|
|
|
154
232
|
|
|
155
233
|
|
|
156
234
|
|
|
157
|
-
let
|
|
158
|
-
export {div}
|
|
159
|
-
|
|
160
|
-
let isEvent = attrName => attrName.startsWith('on') && attrName in div;
|
|
235
|
+
let isEvent = attrName => attrName.startsWith('on') && attrName in Globals.div;
|
|
161
236
|
export {isEvent};
|
|
162
237
|
|
|
163
238
|
|
|
164
|
-
|
|
165
|
-
|
|
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();
|
|
239
|
+
export function dashesToCamel(str) {
|
|
240
|
+
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
|
189
241
|
}
|
|
190
242
|
|
|
191
243
|
|
|
@@ -193,7 +245,7 @@ export function camelToDashes(str) {
|
|
|
193
245
|
|
|
194
246
|
|
|
195
247
|
/**
|
|
196
|
-
* Returns
|
|
248
|
+
* Returns true if they're the same.
|
|
197
249
|
* @param a
|
|
198
250
|
* @param b
|
|
199
251
|
* @returns {boolean} */
|
|
@@ -208,121 +260,7 @@ export function arraySame(a, b) {
|
|
|
208
260
|
}
|
|
209
261
|
|
|
210
262
|
|
|
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
263
|
|
|
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
264
|
|
|
327
265
|
|
|
328
266
|
// For debugging only
|
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
import Util from "../util/Util.js";
|
|
2
|
-
|
|
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";
|
|
2
|
+
import SolariteUtil from './Util.js';
|
|
9
3
|
import Globals from "./Globals.js";
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
//import {watchGet, watchSet} from "./watch.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
5
|
function defineClass(Class, tagName, extendsTag) {
|
|
17
|
-
if (!customElements
|
|
18
|
-
tagName = tagName || camelToDashes(Class.name)
|
|
6
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
7
|
+
tagName = tagName || SolariteUtil.camelToDashes(Class.name)
|
|
19
8
|
if (!tagName.includes('-'))
|
|
20
9
|
tagName += '-element';
|
|
21
10
|
|
|
@@ -23,21 +12,17 @@ function defineClass(Class, tagName, extendsTag) {
|
|
|
23
12
|
if (extendsTag)
|
|
24
13
|
options = {extends: extendsTag}
|
|
25
14
|
|
|
26
|
-
customElements
|
|
15
|
+
customElements[define](tagName, Class, options)
|
|
27
16
|
}
|
|
28
17
|
}
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
19
|
/**
|
|
35
20
|
* Create a version of the Solarite class that extends from the given tag name.
|
|
36
21
|
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
37
22
|
* 1. customElements.define() is called automatically when you create the first instance.
|
|
38
23
|
* 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.
|
|
24
|
+
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
25
|
+
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
41
26
|
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
42
27
|
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
43
28
|
* 6. Can we extend from other element types like TR?
|
|
@@ -102,14 +87,15 @@ export default function createSolarite(extendsTag=null) {
|
|
|
102
87
|
else if (options.render===false)
|
|
103
88
|
Globals.rendered.add(this); // Don't render on connectedCallback()
|
|
104
89
|
|
|
105
|
-
// Add children before constructor code executes.
|
|
90
|
+
// Add slot children before constructor code executes.
|
|
91
|
+
// This breaks the styleStaticNested test.
|
|
106
92
|
// PendingChildren is setup in NodeGroup.createNewComponent()
|
|
107
93
|
// TODO: Match named slots.
|
|
108
|
-
let ch = Globals.pendingChildren.pop();
|
|
109
|
-
if (ch)
|
|
110
|
-
|
|
94
|
+
//let ch = Globals.pendingChildren.pop();
|
|
95
|
+
//if (ch) // TODO: how could there be a slot before render is called?
|
|
96
|
+
// (this.querySelector('slot') || this).append(...ch);
|
|
111
97
|
|
|
112
|
-
/** @deprecated
|
|
98
|
+
/** @deprecated
|
|
113
99
|
Object.defineProperty(this, 'html', {
|
|
114
100
|
set(html) {
|
|
115
101
|
Globals.rendered.add(this);
|
|
@@ -120,7 +106,7 @@ export default function createSolarite(extendsTag=null) {
|
|
|
120
106
|
else
|
|
121
107
|
this.modifications = r(this, html, options);
|
|
122
108
|
}
|
|
123
|
-
})
|
|
109
|
+
})*/
|
|
124
110
|
|
|
125
111
|
/*
|
|
126
112
|
let pthis = new Proxy(this, {
|
|
@@ -158,117 +144,9 @@ export default function createSolarite(extendsTag=null) {
|
|
|
158
144
|
static define(tagName=null) {
|
|
159
145
|
defineClass(this, tagName, extendsTag)
|
|
160
146
|
}
|
|
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
147
|
}
|
|
273
148
|
}
|
|
274
149
|
|
|
150
|
+
// Trick to prevent minifier from renaming this method.
|
|
151
|
+
let define = 'define';
|
|
152
|
+
let getName = 'getName';
|
package/src/solarite/getArg.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import Util from "./Util.js";
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* There are three ways to create an instance of a Solarite Component:
|
|
6
6
|
* 1. new ComponentName(); // direct class instantiation
|
|
7
|
-
* 2. this.html = r`<div><component-name></component-name></div>; // as a child of another
|
|
7
|
+
* 2. this.html = r`<div><component-name></component-name></div>; // as a child of another RedComponent.
|
|
8
8
|
* 3. <body><component-name></component-name></body> // in the Document html.
|
|
9
9
|
*
|
|
10
10
|
* When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
|
|
@@ -24,19 +24,22 @@
|
|
|
24
24
|
* }
|
|
25
25
|
*
|
|
26
26
|
* @param el {HTMLElement}
|
|
27
|
-
* @param
|
|
28
|
-
* @param
|
|
27
|
+
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
28
|
+
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
29
29
|
* @param type {ArgType|function|*[]}
|
|
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
|
-
* @
|
|
33
|
-
|
|
34
|
-
|
|
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?
|
|
34
|
+
* @return {*} Undefined if attribute isn't set. */
|
|
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));
|
|
35
38
|
if (attrVal !== null) // If attribute doesn't exist.
|
|
36
39
|
val = attrVal;
|
|
37
40
|
|
|
38
41
|
if (Array.isArray(type))
|
|
39
|
-
return type.includes(val) ? val :
|
|
42
|
+
return type.includes(val) ? val : fallback;
|
|
40
43
|
|
|
41
44
|
if (typeof type === 'function')
|
|
42
45
|
return type(val);
|
|
@@ -44,22 +47,29 @@ export function getArg(el, name, val=null, type=ArgType.String) {
|
|
|
44
47
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
45
48
|
if (type===ArgType.Bool) {
|
|
46
49
|
let lAttrVal = typeof val === 'string' ? val.toLowerCase() : val;
|
|
47
|
-
|
|
50
|
+
if (['false', '0', false, 0, null, undefined, NaN].includes(lAttrVal))
|
|
51
|
+
return false;
|
|
52
|
+
if (['true', true].includes(lAttrVal) || parseFloat(lAttrVal) !== 0)
|
|
53
|
+
return true;
|
|
54
|
+
return fallback;
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
// Attribute doesn't exist
|
|
58
|
+
let result;
|
|
51
59
|
switch (type) {
|
|
52
60
|
case ArgType.Int:
|
|
53
|
-
|
|
61
|
+
result = parseInt(val);
|
|
62
|
+
return isNaN(result) ? fallback : result;
|
|
54
63
|
case ArgType.Float:
|
|
55
|
-
|
|
64
|
+
result = parseFloat(val);
|
|
65
|
+
return isNaN(result) ? fallback : result;
|
|
56
66
|
case ArgType.String:
|
|
57
67
|
return [undefined, null, false].includes(val) ? '' : val+'';
|
|
58
|
-
case ArgType.
|
|
68
|
+
case ArgType.Json:
|
|
59
69
|
case ArgType.Eval:
|
|
60
70
|
if (typeof val === 'string' && val.length)
|
|
61
71
|
try {
|
|
62
|
-
if (type === ArgType.
|
|
72
|
+
if (type === ArgType.Json)
|
|
63
73
|
return JSON.parse(val);
|
|
64
74
|
else
|
|
65
75
|
return eval(`(${val})`);
|
|
@@ -67,6 +77,8 @@ export function getArg(el, name, val=null, type=ArgType.String) {
|
|
|
67
77
|
return val;
|
|
68
78
|
}
|
|
69
79
|
else return val;
|
|
80
|
+
|
|
81
|
+
// type not provided
|
|
70
82
|
default:
|
|
71
83
|
return val;
|
|
72
84
|
}
|
|
@@ -85,12 +97,15 @@ var ArgType = {
|
|
|
85
97
|
Int: 'Int',
|
|
86
98
|
Float: 'Float',
|
|
87
99
|
String: 'String',
|
|
88
|
-
|
|
100
|
+
|
|
101
|
+
/** @deprecated for Json */
|
|
102
|
+
JSON: 'Json',
|
|
103
|
+
|
|
89
104
|
/**
|
|
90
105
|
* Parse the string value as JSON.
|
|
91
106
|
* If it's not parsable, return the value as a string. */
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
Json: 'Json',
|
|
108
|
+
|
|
94
109
|
/**
|
|
95
110
|
* Evaluate the string as JavaScript using the eval() function.
|
|
96
111
|
* If it can't be evaluated, return the original string. */
|