solarite 0.4.0 → 0.5.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 +1655 -1762
- package/dist/Solarite.js +1679 -1814
- package/dist/Solarite.min.js +2 -11
- package/package.json +11 -1
- package/src/Globals.js +14 -24
- package/src/HtmlParser.js +1 -1
- package/src/MultiValueMap.js +3 -3
- package/src/NodeGroup.js +248 -330
- package/src/Path.js +212 -0
- package/src/PathToAttribValue.js +259 -0
- package/src/PathToAttribs.js +77 -0
- package/src/PathToComment.js +8 -0
- package/src/PathToComponent.js +169 -0
- package/src/PathToEvent.js +57 -0
- package/src/PathToNodes.js +566 -0
- package/src/RootNodeGroup.js +2 -147
- package/src/Shell.js +83 -77
- package/src/Solarite.d.ts +71 -31
- package/src/Solarite.js +124 -14
- package/src/Template.js +39 -40
- package/src/Util.js +22 -38
- package/src/assert.js +5 -5
- package/src/getArg.js +26 -24
- package/src/h.js +23 -20
- package/src/hash.js +11 -9
- package/src/toEl.js +7 -8
- package/src/udomdiff.js +0 -2
- package/src/watch.js +76 -79
- package/src/ExprPath.js +0 -1117
- package/src/createSolarite.js +0 -154
package/src/Solarite.js
CHANGED
|
@@ -8,31 +8,141 @@ JavasCript UI library
|
|
|
8
8
|
https://vorticode.github.io/solarite/ */
|
|
9
9
|
import h from './h.js';
|
|
10
10
|
export default h;
|
|
11
|
-
export {default as
|
|
12
|
-
export {getArg, ArgType} from './getArg.js';
|
|
11
|
+
export {default as delve} from './delve.js';
|
|
13
12
|
export {default as Template} from './Template.js';
|
|
14
13
|
export {default as toEl} from './toEl.js';
|
|
15
|
-
|
|
14
|
+
import Template from './Template.js';
|
|
16
15
|
|
|
17
16
|
// Experimental:
|
|
18
17
|
//--------------
|
|
18
|
+
export {default as Globals} from './Globals.js';
|
|
19
|
+
export {default as SolariteUtil} from './Util.js';
|
|
20
|
+
|
|
21
|
+
// Deprecated:
|
|
22
|
+
//--------------
|
|
23
|
+
export {default as h} from './h.js'; // Named exports for h() are deprecated.
|
|
24
|
+
export {getArg, ArgType} from './getArg.js';
|
|
19
25
|
export {setArgs} from './getArg.js';
|
|
26
|
+
export {default as r} from './h.js';
|
|
27
|
+
export function t(html) {
|
|
28
|
+
return new Template([html], []);
|
|
29
|
+
}
|
|
30
|
+
//export {default as watch, renderWatched} from './watch.js'; // unfinished
|
|
31
|
+
|
|
32
|
+
// Only used for tests:
|
|
33
|
+
//--------------
|
|
34
|
+
export {default as HtmlParser} from './HtmlParser.js';
|
|
35
|
+
export {default as NodeGroup} from './NodeGroup.js';
|
|
36
|
+
export {default as Shell} from './Shell.js';
|
|
20
37
|
|
|
21
|
-
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// Solarite Class:
|
|
43
|
+
//--------------
|
|
44
|
+
import Util from "./Util.js";
|
|
45
|
+
import Globals from "./Globals.js";
|
|
22
46
|
|
|
23
47
|
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
* Intercept the construct call to auto-define the class before the constructor is called. */
|
|
49
|
+
let HTMLElementAutoDefine = new Proxy(HTMLElement, {
|
|
50
|
+
construct(Parent, args, Class) {
|
|
51
|
+
|
|
52
|
+
// 1. Call customElements.define() automatically.
|
|
53
|
+
Util.defineClass(Class);
|
|
54
|
+
|
|
55
|
+
// 2. This line is equivalent the to super() call to HTMLElement:
|
|
56
|
+
return Reflect.construct(Parent, args, Class);
|
|
29
57
|
}
|
|
30
58
|
});
|
|
31
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Solarite provides more features if your web component extends Solarite instead of HTMLElement.
|
|
62
|
+
*
|
|
63
|
+
* Reasons to inherit from Solarite instead of HTMLElement.
|
|
64
|
+
* 1. customElements.define() is called automatically when you create the first instance.
|
|
65
|
+
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
66
|
+
* 3. Populates the attribs argument to the constructor when instantiated from regular html outside a template string.
|
|
67
|
+
* It parses JSON from DOM attribute values surrouned with '${...}'
|
|
68
|
+
* 4. Shows an error if render() isn't defined.
|
|
69
|
+
*
|
|
70
|
+
* Advantages to inheriting from HTMLElement
|
|
71
|
+
* 1. We can inherit from things like HTMLTableRowElement directly.
|
|
72
|
+
* 2. There's less magic, since everyone is familiar with defining custom elements.
|
|
73
|
+
* 3. No confusion about how the class name becomes a tag name.
|
|
74
|
+
* @extends {HTMLElement} */
|
|
75
|
+
export class Solarite extends HTMLElementAutoDefine {
|
|
32
76
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
77
|
+
/**
|
|
78
|
+
* @param attribs {?Record<string, any>} */
|
|
79
|
+
constructor(attribs=null) {
|
|
80
|
+
super();
|
|
81
|
+
|
|
82
|
+
if (attribs) {
|
|
83
|
+
if (typeof attribs !== 'object')
|
|
84
|
+
throw new Error('First argument to custom element constructor must be an object.');
|
|
85
|
+
|
|
86
|
+
// 1. Populate attribs if it's an empty object.
|
|
87
|
+
if (attribs && !Object.keys(attribs).length) {
|
|
88
|
+
let attribs2 = Solarite.getAttribs(this);
|
|
89
|
+
for (let name in attribs2) {
|
|
90
|
+
attribs[name] = attribs2[name];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. Populate fields from attribs.
|
|
95
|
+
// This does nothing because the fields are overwritten by the child class after this super() constructor executes.
|
|
96
|
+
//for (let name in attribs || {}) {
|
|
97
|
+
// if (name in this) {
|
|
98
|
+
// const descriptor = Object.getOwnPropertyDescriptor(this, name);
|
|
99
|
+
// if (!descriptor || descriptor.writable || descriptor.set)
|
|
100
|
+
// this[name] = attribs[name];
|
|
101
|
+
// }
|
|
102
|
+
//}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Wrap render function so it always provides the attribs argument.
|
|
106
|
+
// Disabled because this gives us strings for attribute values when we call render manually.
|
|
107
|
+
// Instead of values given from ${...} expressions.
|
|
108
|
+
// let originalRender = this.render;
|
|
109
|
+
// this.render = (attribs, changed=true) => {
|
|
110
|
+
// if (!attribs) // If we have to look up the attribs, we don't know if they changed or not.
|
|
111
|
+
// attribs = Solarite.getAttribs(this);
|
|
112
|
+
// originalRender.call(this, attribs, changed);
|
|
113
|
+
// }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
'render'() {
|
|
117
|
+
throw new Error('render() is not defined for ' + this.constructor.name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Call render() only if it hasn't already been called. */
|
|
122
|
+
'renderFirstTime'() {
|
|
123
|
+
if (!Globals.rendered.has(this)) {
|
|
124
|
+
let attribs = Solarite.getAttribs(this);
|
|
125
|
+
this.render(attribs); // calls Globals.rendered.add(this); inside the call to h()'...'.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Called automatically by the browser. */
|
|
131
|
+
'connectedCallback'() { // quoted so terser doesn't remove it.
|
|
132
|
+
this.renderFirstTime();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static 'define'(tagName=null) {
|
|
136
|
+
Util.defineClass(this, tagName);
|
|
137
|
+
}
|
|
37
138
|
|
|
38
|
-
|
|
139
|
+
static 'getAttribs'(el) {
|
|
140
|
+
let result = Util.attribsToObject(el);
|
|
141
|
+
for (let name in result) {
|
|
142
|
+
let val = result[name];
|
|
143
|
+
if (val.startsWith('${') && val.endsWith('}'))
|
|
144
|
+
result[name] = JSON.parse(val.slice(2, -1));
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
}
|
package/src/Template.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from "./assert.js";
|
|
2
2
|
import {getObjectHash, getObjectId} from "./hash.js";
|
|
3
3
|
import Globals from "./Globals.js";
|
|
4
4
|
import RootNodeGroup from "./RootNodeGroup.js";
|
|
5
|
-
import Util from "./Util.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* The html strings and evaluated expressions from an html tagged template.
|
|
@@ -10,11 +9,11 @@ import Util from "./Util.js";
|
|
|
10
9
|
* Although the reference to the html strings is shared among templates. */
|
|
11
10
|
export default class Template {
|
|
12
11
|
|
|
13
|
-
/** @type {
|
|
14
|
-
exprs = []
|
|
12
|
+
/** @type {Expr[]} Evaulated expressions. */
|
|
13
|
+
'exprs' = []
|
|
15
14
|
|
|
16
15
|
/** @type {string[]} */
|
|
17
|
-
html = [];
|
|
16
|
+
'html' = [];
|
|
18
17
|
|
|
19
18
|
/** @type {Array} Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
|
|
20
19
|
hashedFields;
|
|
@@ -25,8 +24,9 @@ export default class Template {
|
|
|
25
24
|
*
|
|
26
25
|
* @param htmlStrings {string[]}
|
|
27
26
|
* @param exprs {*[]} */
|
|
28
|
-
constructor(htmlStrings, exprs) {
|
|
27
|
+
constructor(htmlStrings=[''], exprs=[]) {
|
|
29
28
|
this.html = htmlStrings;
|
|
29
|
+
|
|
30
30
|
this.exprs = exprs;
|
|
31
31
|
|
|
32
32
|
//this.trace = new Error().stack.split(/\n/g)
|
|
@@ -50,51 +50,50 @@ export default class Template {
|
|
|
50
50
|
* Called by JSON.serialize when it encounters a Template.
|
|
51
51
|
* This prevents the hashed version from being too large. */
|
|
52
52
|
toJSON() {
|
|
53
|
-
if (
|
|
53
|
+
if (this.hashedFields===undefined)
|
|
54
54
|
this.hashedFields = [getObjectId(this.html), this.exprs];
|
|
55
55
|
|
|
56
56
|
return this.hashedFields
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Render the main
|
|
61
|
-
* @param el {HTMLElement}
|
|
60
|
+
* Render the main (root) template.
|
|
61
|
+
* @param el {?HTMLElement} Null if we're rendering to a standalone element.
|
|
62
62
|
* @param options {RenderOptions}
|
|
63
63
|
* @return {?DocumentFragment|HTMLElement} */
|
|
64
|
-
render(el=null, options={}) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
el
|
|
74
|
-
Globals.nodeGroups.set(el, ng); // Why was this commented out?
|
|
75
|
-
firstTime = true;
|
|
64
|
+
'render'(el=null, options={}) {
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
let ng = el && Globals.rootNodeGroups.get(el);
|
|
69
|
+
if (!ng) {
|
|
70
|
+
ng = new RootNodeGroup(this, null, el, options);
|
|
71
|
+
if (!el) // null if it's a standalone elment.
|
|
72
|
+
el = ng.getRootNode();
|
|
73
|
+
Globals.rootNodeGroups.set(el, ng); // All tests still pass if this is commented out!
|
|
76
74
|
}
|
|
77
|
-
else {
|
|
78
|
-
ng = Globals.nodeGroups.get(el);
|
|
79
|
-
if (!ng) {
|
|
80
|
-
ng = new RootNodeGroup(this, el, options);
|
|
81
|
-
Globals.nodeGroups.set(el, ng); // Why was this commented out?
|
|
82
|
-
firstTime = true;
|
|
83
|
-
}
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
// Make sure the expresion count matches match the Path "hole" count.
|
|
77
|
+
// This can happen if we try manually rendering one template to a NodeGroup that was created expecting a different template.
|
|
78
|
+
// These don't always have the same length, for example if one attribute has multiple expressions.
|
|
79
|
+
// if (ng.paths.length === 0 && this.exprs.length || ng.paths.length > this.exprs.length)
|
|
80
|
+
// throw new Error(
|
|
81
|
+
// `Solarite Error: Parent HTMLElement ${ng.template.html.join('${...}')} and ${ng.paths.length} \${value} ` +
|
|
82
|
+
// `placeholders can't accomodate a Template with ${this.exprs.length} values.`);
|
|
89
83
|
|
|
90
84
|
// Creating the root nodegroup also renders it.
|
|
91
85
|
// If we didn't just create it, we need to render it.
|
|
92
|
-
if (!
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
if (this.html?.length === 1 && !this.html[0]) // An empty string.
|
|
87
|
+
el.innerHTML = ''; // Fast path for empty component.
|
|
88
|
+
else {
|
|
89
|
+
|
|
90
|
+
let oldKey = ng.exactKey;
|
|
91
|
+
let newKey = this.getExactKey();
|
|
92
|
+
ng.applyExprs(this.exprs, oldKey !== newKey);
|
|
93
|
+
ng.exactKey = newKey;
|
|
94
|
+
|
|
95
|
+
//if (firstTime)
|
|
96
|
+
// ng.instantiateStaticComponents(ng.staticComponents);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
ng.exprsToRender = new Map();
|
|
@@ -102,7 +101,7 @@ export default class Template {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
getExactKey() {
|
|
105
|
-
if (
|
|
104
|
+
if (this.exactKey===undefined) {
|
|
106
105
|
if (this.exprs.length)
|
|
107
106
|
this.exactKey = getObjectHash(this);// calls this.toJSON().
|
|
108
107
|
else // Don't hash plain html.
|
|
@@ -113,7 +112,7 @@ export default class Template {
|
|
|
113
112
|
|
|
114
113
|
getCloseKey() {
|
|
115
114
|
//console.log(this.exprs.length)
|
|
116
|
-
if (
|
|
115
|
+
if (this.closeKey===undefined) {
|
|
117
116
|
if (this.exprs.length)
|
|
118
117
|
this.closeKey = /*'@' + */this.toJSON()[0];
|
|
119
118
|
else
|
package/src/Util.js
CHANGED
|
@@ -20,6 +20,8 @@ let Util = {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Convert HTMLElement attributes to an object.
|
|
23
|
+
* Converts dash (kebob-case) attribute names to camelCase.
|
|
24
|
+
* See also Solarite.getAttribs()
|
|
23
25
|
* @param el {HTMLElement}
|
|
24
26
|
* @param ignore {?string} Optionally ignore this attribute.
|
|
25
27
|
* @return {Object} */
|
|
@@ -135,43 +137,14 @@ let Util = {
|
|
|
135
137
|
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
|
136
138
|
},
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*
|
|
147
|
-
* This function does not create a new array for the flattened values. Instead,
|
|
148
|
-
* it lazily yields each item as it is encountered. This can be more memory-efficient
|
|
149
|
-
* for large or deeply nested structures.
|
|
150
|
-
*
|
|
151
|
-
* @param {any} value - The value to flatten. Can be an array, object, function, or primitive.
|
|
152
|
-
* @yields {any} - The next item in the flattened structure.
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* const complexArray = [
|
|
156
|
-
* 1,
|
|
157
|
-
* [2, () => 3, [4, () => [5, 6]], { a: 'object' }],
|
|
158
|
-
* () => () => 7,
|
|
159
|
-
* () => [() => 8, 9],
|
|
160
|
-
* ]; *
|
|
161
|
-
* for (const item of flatten(complexArray))
|
|
162
|
-
* console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
|
|
163
|
-
*/
|
|
164
|
-
// *flatten(value) {
|
|
165
|
-
// if (Array.isArray(value)) {
|
|
166
|
-
// for (const item of value) {
|
|
167
|
-
// yield* Util.flatten(item); // Recursively flatten arrays
|
|
168
|
-
// }
|
|
169
|
-
// } else if (typeof value === 'function') {
|
|
170
|
-
// const result = value();
|
|
171
|
-
// yield* Util.flatten(result); // Recursively flatten the result of a function
|
|
172
|
-
// } else
|
|
173
|
-
// yield value; // Yield primitive values as is
|
|
174
|
-
// },
|
|
140
|
+
defineClass(Class, tagName) {
|
|
141
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
142
|
+
tagName = tagName || Util.camelToDashes(Class.name)
|
|
143
|
+
if (!tagName.includes('-')) // Browsers require that web components always have a dash in the name.
|
|
144
|
+
tagName += '-element';
|
|
145
|
+
customElements[define](tagName, Class)
|
|
146
|
+
}
|
|
147
|
+
},
|
|
175
148
|
|
|
176
149
|
/**
|
|
177
150
|
* Get the value of an input as the most appropriate JavaScript type.
|
|
@@ -261,7 +234,7 @@ let Util = {
|
|
|
261
234
|
|
|
262
235
|
/**
|
|
263
236
|
* Use an array as the value of a map, appending to it when we add.
|
|
264
|
-
* Used by watch.js.
|
|
237
|
+
* Used only by watch.js.
|
|
265
238
|
* @param map {Map|WeakMap|Object}
|
|
266
239
|
* @param key
|
|
267
240
|
* @param value */
|
|
@@ -275,6 +248,11 @@ let Util = {
|
|
|
275
248
|
result.push(value);
|
|
276
249
|
},
|
|
277
250
|
|
|
251
|
+
saveOrphans(nodes) {
|
|
252
|
+
let fragment = Globals.doc.createDocumentFragment();
|
|
253
|
+
fragment.append(...nodes);
|
|
254
|
+
},
|
|
255
|
+
|
|
278
256
|
/**
|
|
279
257
|
* Remove nodes from the beginning and end that are not:
|
|
280
258
|
* 1. Elements.
|
|
@@ -301,6 +279,12 @@ let Util = {
|
|
|
301
279
|
}
|
|
302
280
|
};
|
|
303
281
|
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
// Trick to prevent minifier from renaming these methods.
|
|
285
|
+
let define = 'define';
|
|
286
|
+
let getName = 'getName';
|
|
287
|
+
|
|
304
288
|
export default Util;
|
|
305
289
|
|
|
306
290
|
|
package/src/assert.js
CHANGED
package/src/getArg.js
CHANGED
|
@@ -2,10 +2,11 @@ import Util from "./Util.js";
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
+
* @deprecated Inherit from Solarite and pass arribs to super() instead.
|
|
5
6
|
* There are three ways to create an instance of a Solarite Component:
|
|
6
|
-
* 1. new ComponentName();
|
|
7
|
-
* 2. this
|
|
8
|
-
* 3. <body><component-name></component-name></body>
|
|
7
|
+
* 1. new ComponentName(3); // direct class instantiation
|
|
8
|
+
* 2. h(this)`<div><component-name user-id=${3}></component-name></div>; // as a child of another Component.
|
|
9
|
+
* 3. <body><component-name user-id="3"></component-name></body> // in the Document html.
|
|
9
10
|
*
|
|
10
11
|
* When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
|
|
11
12
|
* sure we get the correct value via all three paths, we write our constructors according to the following
|
|
@@ -13,40 +14,38 @@ import Util from "./Util.js";
|
|
|
13
14
|
* Browsers make all html attribute names lowercase.
|
|
14
15
|
*
|
|
15
16
|
* @example
|
|
16
|
-
* constructor({name,
|
|
17
|
+
* constructor({name, userId=1}={}) {
|
|
17
18
|
* super();
|
|
18
19
|
*
|
|
19
20
|
* // Get value from "name" attriute if persent, otherwise from name constructor arg.
|
|
20
21
|
* this.name = getArg(this, 'name', name);
|
|
21
22
|
*
|
|
22
23
|
* // Optionally convert the value to an integer.
|
|
23
|
-
* this.userId = getArg(this, '
|
|
24
|
+
* this.userId = getArg(this, 'user-id', userId, ArgType.Int);
|
|
24
25
|
* }
|
|
25
26
|
*
|
|
26
27
|
* @param el {HTMLElement}
|
|
27
28
|
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
28
|
-
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
29
|
+
* @param defaultValue {*} Default value to use if attribute doesn't exist. Typically the argument from the constructor.
|
|
29
30
|
* @param type {ArgType|function|Class|*[]}
|
|
30
31
|
* If an array, use the value if it's in the array, otherwise return undefined.
|
|
31
32
|
* If it's a function, pass the value to the function and return the result.
|
|
32
|
-
* @
|
|
33
|
-
|
|
34
|
-
* @return {*} Undefined if attribute isn't set. */
|
|
35
|
-
export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String, fallback=undefined) {
|
|
33
|
+
* @return {*} Undefined if attribute isn't set and there's no defaultValue, or if the value couldn't be parsed as the type. */
|
|
34
|
+
export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String) {
|
|
36
35
|
let val = defaultValue;
|
|
37
36
|
let attrVal = el.getAttribute(attributeName) || el.getAttribute(Util.camelToDashes(attributeName));
|
|
38
37
|
if (attrVal !== null) // If attribute doesn't exist.
|
|
39
38
|
val = attrVal;
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
if (Array.isArray(type))
|
|
42
|
-
return type.includes(val) ? val :
|
|
43
|
-
|
|
41
|
+
return type.includes(val) ? val : undefined;
|
|
42
|
+
|
|
44
43
|
if (typeof type === 'function') {
|
|
45
44
|
return type.constructor
|
|
46
45
|
? new type(val) // arg type is custom Class
|
|
47
46
|
: type(val); // arg type is custom function
|
|
48
47
|
}
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
51
50
|
if (type===ArgType.Bool) {
|
|
52
51
|
let lAttrVal = typeof val === 'string' ? val.toLowerCase() : val;
|
|
@@ -54,20 +53,17 @@ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.S
|
|
|
54
53
|
return false;
|
|
55
54
|
if (['true', true].includes(lAttrVal) || parseFloat(lAttrVal) !== 0)
|
|
56
55
|
return true;
|
|
57
|
-
return
|
|
56
|
+
return undefined;
|
|
58
57
|
}
|
|
59
|
-
|
|
58
|
+
|
|
60
59
|
// Attribute doesn't exist
|
|
61
|
-
let result;
|
|
62
60
|
switch (type) {
|
|
63
61
|
case ArgType.Int:
|
|
64
|
-
|
|
65
|
-
return isNaN(result) ? fallback : result;
|
|
62
|
+
return parseInt(val);
|
|
66
63
|
case ArgType.Float:
|
|
67
|
-
|
|
68
|
-
return isNaN(result) ? fallback : result;
|
|
64
|
+
return parseFloat(val);
|
|
69
65
|
case ArgType.String:
|
|
70
|
-
return [undefined, null, false].includes(val) ? '' : val+'';
|
|
66
|
+
return [undefined, null, false].includes(val) ? '' : (val+'');
|
|
71
67
|
case ArgType.Json:
|
|
72
68
|
case ArgType.Eval:
|
|
73
69
|
if (typeof val === 'string' && val.length)
|
|
@@ -89,6 +85,7 @@ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.S
|
|
|
89
85
|
|
|
90
86
|
|
|
91
87
|
/**
|
|
88
|
+
* @deprecated for Solarite.getAttribs()
|
|
92
89
|
* Experimental. Set multiple arguments/attributes all at once.
|
|
93
90
|
* @param el {HTMLElement}
|
|
94
91
|
* @param args {Record<string, any>}
|
|
@@ -97,6 +94,10 @@ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.S
|
|
|
97
94
|
* @example
|
|
98
95
|
* constructor({user, path}={}) {
|
|
99
96
|
* setArgs(this, arguments[0], {user: User, path: ArgType.String});
|
|
97
|
+
*
|
|
98
|
+
* // Equivalent to:
|
|
99
|
+
* this.user = getArg(this, user, 'user', User); // or new User(user);
|
|
100
|
+
* this.path = getArg(this, path, 'path', ArgType.String);
|
|
100
101
|
* }
|
|
101
102
|
*/
|
|
102
103
|
export function setArgs(el, args, types) {
|
|
@@ -106,15 +107,16 @@ export function setArgs(el, args, types) {
|
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
/**
|
|
110
|
+
* @deprecated
|
|
109
111
|
* @enum */
|
|
110
112
|
var ArgType = {
|
|
111
|
-
|
|
113
|
+
|
|
112
114
|
/**
|
|
113
115
|
* false, 0, null, undefined, '0', and 'false' (case-insensitive) become false.
|
|
114
116
|
* Anything else, including empty string becomes true.
|
|
115
117
|
* Empty string is true because attributes with no value should be evaulated as true. */
|
|
116
118
|
Bool: 'Bool',
|
|
117
|
-
|
|
119
|
+
|
|
118
120
|
Int: 'Int',
|
|
119
121
|
Float: 'Float',
|
|
120
122
|
String: 'String',
|
package/src/h.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Template from "./Template.js";
|
|
2
2
|
import Globals from "./Globals.js";
|
|
3
3
|
import toEl from "./toEl.js";
|
|
4
|
+
import Util from "./Util.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Convert strings to HTMLNodes.
|
|
@@ -8,7 +9,7 @@ import toEl from "./toEl.js";
|
|
|
8
9
|
* Using h() as a function() will always create a DOM element.
|
|
9
10
|
*
|
|
10
11
|
* Features beyond what standard js tagged template strings do:
|
|
11
|
-
* 1.
|
|
12
|
+
* 1. h`` sub-expressions
|
|
12
13
|
* 2. functions, nodes, and arrays of nodes as sub-expressions.
|
|
13
14
|
* 3. html-escape all expressions by default, unless wrapped in h()
|
|
14
15
|
* 4. event binding
|
|
@@ -26,7 +27,7 @@ import toEl from "./toEl.js";
|
|
|
26
27
|
*
|
|
27
28
|
* Add children to an element.
|
|
28
29
|
* 3. h(el, h`<b>${'Hi'}</b>`, ?options)
|
|
29
|
-
* 4. h(el, ?options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
|
|
30
|
+
* 4. h(el, ?options)`<b>${'Hi'}</b>` // typical path used in render(). Create template and render its nodes to el.
|
|
30
31
|
*
|
|
31
32
|
* Create top-level element
|
|
32
33
|
* 5. h()`Hello<b>${'World'}!</b>`
|
|
@@ -34,10 +35,10 @@ import toEl from "./toEl.js";
|
|
|
34
35
|
* 6. h(string, object, ...) // Used for JSX
|
|
35
36
|
* @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
|
|
36
37
|
* @param exprs {*[]|string|Template|Object}
|
|
37
|
-
* @return {Node|HTMLElement|Template} */
|
|
38
|
+
* @return {Node|HTMLElement|Template|Function} */
|
|
38
39
|
export default function h(htmlStrings=undefined, ...exprs) {
|
|
39
40
|
|
|
40
|
-
// 1. Tagged template
|
|
41
|
+
// 1. Tagged template: h`<div>...</div>`
|
|
41
42
|
if (Array.isArray(arguments[0])) {
|
|
42
43
|
return new Template(arguments[0], exprs);
|
|
43
44
|
}
|
|
@@ -55,11 +56,10 @@ export default function h(htmlStrings=undefined, ...exprs) {
|
|
|
55
56
|
return Template.fromJsx(tag, props, children);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
// 2b. Plain html string => template
|
|
59
|
+
// 2b. Plain html string => template: h('<div>...</div>')
|
|
59
60
|
else {
|
|
60
61
|
let html = tagOrHtml;
|
|
61
|
-
// If it starts with whitespace, trim
|
|
62
|
-
// TODO: Also trim if it ends with whitespace?
|
|
62
|
+
// If it starts with whitespace and then a tag, trim it.
|
|
63
63
|
if (html.match(/^\s^</))
|
|
64
64
|
html = html.trim();
|
|
65
65
|
return new Template([html], []);
|
|
@@ -68,44 +68,43 @@ export default function h(htmlStrings=undefined, ...exprs) {
|
|
|
68
68
|
|
|
69
69
|
else if (arguments[0] instanceof HTMLElement || arguments[0] instanceof DocumentFragment) {
|
|
70
70
|
|
|
71
|
-
// 3. Render template to element
|
|
71
|
+
// 3. Render template to element: h(el, template)
|
|
72
72
|
if (arguments[1] instanceof Template) {
|
|
73
73
|
|
|
74
74
|
/** @type Template */
|
|
75
75
|
let template = arguments[1];
|
|
76
76
|
let parent = arguments[0];
|
|
77
|
-
let options = arguments[2];
|
|
77
|
+
let options = arguments[2];
|
|
78
78
|
template.render(parent, options);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// 4. Render tagged template to element
|
|
81
|
+
// 4. Render tagged template to element: h(el)`<div>...</div>`
|
|
82
82
|
else {
|
|
83
83
|
let parent = arguments[0], options = arguments[1];
|
|
84
84
|
|
|
85
|
-
// Remove shadowroot. TODO: This could mess up paths?
|
|
85
|
+
// Remove shadowroot if present. TODO: This could mess up paths?
|
|
86
86
|
if (parent.shadowRoot)
|
|
87
87
|
parent.innerHTML = '';
|
|
88
88
|
|
|
89
89
|
// Return a tagged template function that applies the tagged template to parent.
|
|
90
|
-
let
|
|
90
|
+
let renderTemplate = (htmlStrings, ...exprs) => {
|
|
91
91
|
Globals.rendered.add(parent)
|
|
92
92
|
let template = new Template(htmlStrings, exprs);
|
|
93
93
|
return template.render(parent, options);
|
|
94
94
|
}
|
|
95
|
-
return
|
|
95
|
+
return renderTemplate;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// 5. Create a static element
|
|
99
|
+
// 5. Create a static element: h()`<div></div>`
|
|
100
100
|
else if (!arguments.length) {
|
|
101
101
|
return (htmlStrings, ...exprs) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
let template = h(htmlStrings, ...exprs);
|
|
103
|
+
return toEl(template);
|
|
104
|
+
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// 6. Help toEl() with objects
|
|
108
|
-
// Special rebound render path, called by normal path.
|
|
107
|
+
// 6. Help toEl() with objects: h(this)`<div>...</div>` inside an object's render()
|
|
109
108
|
// Intercepts the main h(this)`...` function call inside render().
|
|
110
109
|
// TODO: This path doesn't handle embeds like data-id="..."
|
|
111
110
|
else if (typeof arguments[0] === 'object' && Globals.objToEl.has(arguments[0])) {
|
|
@@ -121,7 +120,7 @@ export default function h(htmlStrings=undefined, ...exprs) {
|
|
|
121
120
|
Globals.objToEl.set(obj, el);
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
// h(this)`<div>...</div
|
|
123
|
+
// h(this)`<div>...</div>`
|
|
125
124
|
else
|
|
126
125
|
return function(...args) {
|
|
127
126
|
let template = h(...args);
|
|
@@ -129,6 +128,10 @@ export default function h(htmlStrings=undefined, ...exprs) {
|
|
|
129
128
|
Globals.objToEl.set(obj, el);
|
|
130
129
|
}.bind(obj);
|
|
131
130
|
}
|
|
131
|
+
// TODO: Handle other primitive types?
|
|
132
|
+
else if (Util.isFalsy(arguments[0]))
|
|
133
|
+
return new Template();
|
|
134
|
+
|
|
132
135
|
else
|
|
133
136
|
throw new Error('h() does not support argument of type: ' + (arguments[0] ? typeof arguments[0] : arguments[0]))
|
|
134
137
|
}
|