solarite 0.3.2 → 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 +2149 -2090
- package/dist/Solarite.js +1895 -1821
- package/dist/Solarite.min.js +2 -2
- package/package.json +12 -2
- package/readme.md +2 -2
- package/src/Globals.js +18 -24
- package/src/HtmlParser.js +1 -1
- package/src/MultiValueMap.js +3 -3
- package/src/NodeGroup.js +238 -483
- 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 +8 -0
- package/src/Shell.js +92 -83
- package/src/Solarite.d.ts +73 -29
- package/src/Solarite.js +133 -21
- package/src/Template.js +173 -42
- package/src/Util.js +74 -52
- package/src/assert.js +5 -5
- package/src/getArg.js +26 -24
- package/src/h.js +83 -128
- package/src/hash.js +11 -9
- package/src/toEl.js +83 -0
- package/src/udomdiff.js +0 -57
- package/src/watch.js +78 -81
- package/src/ExprPath.js +0 -1109
- package/src/createSolarite.js +0 -154
package/src/Shell.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import assert from "./assert.js";
|
|
2
|
+
import Path from "./Path.js";
|
|
3
3
|
import Util from "./Util.js";
|
|
4
4
|
import Globals from "./Globals.js";
|
|
5
5
|
import HtmlParser from "./HtmlParser.js";
|
|
6
|
+
import PathToEvent from "./PathToEvent.js";
|
|
7
|
+
import PathToAttribValue from "./PathToAttribValue.js";
|
|
8
|
+
import PathToAttribs from "./PathToAttribs.js";
|
|
9
|
+
import PathToNodes from "./PathToNodes.js";
|
|
10
|
+
import PathToComponent from "./PathToComponent.js";
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* A Shell is created from a tagged template expression instantiated as Nodes,
|
|
@@ -17,10 +22,10 @@ export default class Shell {
|
|
|
17
22
|
* @type {DocumentFragment|Text} DOM parent of the shell's nodes. */
|
|
18
23
|
fragment;
|
|
19
24
|
|
|
20
|
-
/** @type {
|
|
25
|
+
/** @type {Path[]} Paths to where expressions should go. */
|
|
21
26
|
paths = [];
|
|
22
27
|
|
|
23
|
-
// Elements with events.
|
|
28
|
+
// Elements with events. Is there a reason to use this? We already mark event Exprs in Shell.js.
|
|
24
29
|
// events = [];
|
|
25
30
|
|
|
26
31
|
/** @type {int[][]} Array of paths */
|
|
@@ -32,14 +37,6 @@ export default class Shell {
|
|
|
32
37
|
/** @type {int[][]} Array of paths */
|
|
33
38
|
styles = [];
|
|
34
39
|
|
|
35
|
-
/** @type {int[][]} Array of paths. Used by activateEmbeds() to quickly find components. */
|
|
36
|
-
staticComponents = [];
|
|
37
|
-
|
|
38
|
-
/** @type {{path:int[], attribs:Record<string, string>}[]} */
|
|
39
|
-
//componentAttribs = [];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
40
|
/**
|
|
44
41
|
* Create the nodes but without filling in the expressions.
|
|
45
42
|
* This is useful because the expression-less nodes created by a template can be cached.
|
|
@@ -52,43 +49,52 @@ export default class Shell {
|
|
|
52
49
|
this._html = html.join('');
|
|
53
50
|
//#ENDIF
|
|
54
51
|
|
|
52
|
+
// If no html tags or entities, just create a text node.
|
|
55
53
|
if (html.length === 1 && !html[0].match(/[<&]/)) {
|
|
56
|
-
this.fragment =
|
|
54
|
+
this.fragment = Globals.doc.createTextNode(html[0]);
|
|
57
55
|
return;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
|
|
61
59
|
// 1. Add placeholders
|
|
62
|
-
let
|
|
60
|
+
let htmlWithPlaceholders = Shell.addPlaceholders(html);
|
|
63
61
|
|
|
64
|
-
let template =
|
|
65
|
-
if (
|
|
66
|
-
template.innerHTML =
|
|
62
|
+
let template = Globals.doc.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
|
|
63
|
+
if (htmlWithPlaceholders)
|
|
64
|
+
template.innerHTML = htmlWithPlaceholders;
|
|
67
65
|
else // Create one text node, so shell isn't empty and NodeGroups created from it have something to point the startNode and endNode at.
|
|
68
|
-
template.content.append(
|
|
66
|
+
template.content.append(Globals.doc.createTextNode(''))
|
|
69
67
|
this.fragment = template.content;
|
|
70
68
|
|
|
71
69
|
// 2. Find placeholders
|
|
72
70
|
let node;
|
|
73
71
|
let toRemove = [];
|
|
74
72
|
let placeholdersUsed = 0;
|
|
75
|
-
const walker =
|
|
73
|
+
const walker = Globals.doc.createTreeWalker(this.fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
|
|
76
74
|
while (node = walker.nextNode()) {
|
|
77
75
|
|
|
78
|
-
// Remove previous after each iteration, so paths will still be calculated correctly.
|
|
76
|
+
// Remove previous elements after each iteration, so paths will still be calculated correctly.
|
|
79
77
|
toRemove.map(el => el.remove());
|
|
80
78
|
toRemove = [];
|
|
81
79
|
|
|
82
80
|
// Replace attributes
|
|
83
81
|
if (node.nodeType === 1) {
|
|
84
|
-
|
|
82
|
+
const hasIs = node.hasAttribute('is');
|
|
83
|
+
const isComponent = (hasIs || node.tagName.includes('-'));
|
|
84
|
+
const componentAttribPaths = [];
|
|
85
|
+
|
|
86
|
+
for (let attr of [...node.attributes]) { // Copy the attributes array b/c we remove attributes with placeholders as we go.
|
|
85
87
|
|
|
86
88
|
// Whole attribute
|
|
87
89
|
let matches = attr.name.match(/^[\ue000-\uf8ff]$/)
|
|
88
90
|
if (matches) {
|
|
89
|
-
|
|
91
|
+
let path = new PathToAttribs(null, node);
|
|
92
|
+
this.paths.push(path);
|
|
93
|
+
if (isComponent)
|
|
94
|
+
componentAttribPaths.push(path);
|
|
95
|
+
|
|
90
96
|
placeholdersUsed ++;
|
|
91
|
-
node.removeAttribute(matches[0]);
|
|
97
|
+
node.removeAttribute(matches[0]); // TODO: Is this necessary?
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
// Just the attribute value.
|
|
@@ -96,22 +102,46 @@ export default class Shell {
|
|
|
96
102
|
let parts = attr.value.split(/[\ue000-\uf8ff]/g);
|
|
97
103
|
if (parts.length > 1) {
|
|
98
104
|
let nonEmptyParts = (parts.length === 2 && !parts[0].length && !parts[1].length) ? null : parts;
|
|
99
|
-
let type = Util.isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
let path = Util.isEvent(attr.name)
|
|
107
|
+
? new PathToEvent(null, node, attr.name, nonEmptyParts)
|
|
108
|
+
: new PathToAttribValue(null, node, attr.name, nonEmptyParts);
|
|
109
|
+
path.isHtmlProperty = Util.isHtmlProp(node, attr.name);
|
|
110
|
+
this.paths.push(path);
|
|
111
|
+
if (isComponent) {
|
|
112
|
+
path.isComponentAttrib = true;
|
|
113
|
+
componentAttribPaths.push(path);
|
|
114
|
+
}
|
|
115
|
+
|
|
102
116
|
placeholdersUsed += parts.length - 1;
|
|
103
117
|
node.setAttribute(attr.name, parts.join(''));
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
}
|
|
121
|
+
|
|
122
|
+
// Web components
|
|
123
|
+
if (isComponent) {
|
|
124
|
+
let path = new PathToComponent(null, node);
|
|
125
|
+
path.attribPaths = componentAttribPaths;
|
|
126
|
+
this.paths.splice(this.paths.length - componentAttribPaths.length, 0, path); // Insert before its componentAttribPaths
|
|
127
|
+
|
|
128
|
+
if (hasIs) {
|
|
129
|
+
node.setAttribute('_is', node.getAttribute('is'));
|
|
130
|
+
node.removeAttribute('is');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
107
133
|
}
|
|
134
|
+
|
|
108
135
|
// Replace comment placeholders
|
|
109
136
|
else if (node.nodeType === 8 && node.nodeValue === '!✨!') {
|
|
110
137
|
|
|
138
|
+
if (node?.parentNode?.closest && node?.parentNode?.closest('[contenteditable]'))
|
|
139
|
+
throw new Error(`Contenteditable can't have expressions inside them. Use <div contenteditable value="\${...}"> instead.`);
|
|
140
|
+
|
|
111
141
|
// Get or create nodeBefore.
|
|
112
142
|
let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
|
|
113
143
|
if (!nodeBefore) {
|
|
114
|
-
nodeBefore =
|
|
144
|
+
nodeBefore = Globals.doc.createComment('Path:'+this.paths.length);
|
|
115
145
|
node.parentNode.insertBefore(nodeBefore, node)
|
|
116
146
|
}
|
|
117
147
|
/*#IFDEV*/assert(nodeBefore);/*#ENDIF*/
|
|
@@ -127,49 +157,48 @@ export default class Shell {
|
|
|
127
157
|
// Re-use existing comment placeholder.
|
|
128
158
|
else {
|
|
129
159
|
nodeMarker = node;
|
|
130
|
-
nodeMarker.textContent = '
|
|
160
|
+
nodeMarker.textContent = 'PathEnd:'+ this.paths.length;
|
|
131
161
|
}
|
|
132
162
|
/*#IFDEV*/assert(nodeMarker);/*#ENDIF*/
|
|
133
163
|
|
|
134
|
-
let path = new
|
|
164
|
+
let path = new PathToNodes(nodeBefore, nodeMarker);
|
|
135
165
|
this.paths.push(path);
|
|
136
166
|
placeholdersUsed ++;
|
|
137
167
|
}
|
|
138
168
|
|
|
169
|
+
// Comments become text nodes when inside textareas.
|
|
139
170
|
else if (node.nodeType === 3 && node.parentNode?.tagName === 'TEXTAREA' && node.textContent.includes('<!--!✨!-->'))
|
|
140
171
|
throw new Error(`Textarea can't have expressions inside them. Use <textarea value="\${...}"> instead.`);
|
|
141
|
-
|
|
142
172
|
|
|
143
173
|
|
|
144
174
|
// Sometimes users will comment out a block of html code that has expressions.
|
|
145
175
|
// Here we look for expressions in comments.
|
|
146
176
|
// We don't actually update them dynamically, but we still add paths for them.
|
|
147
177
|
// That way the expression count still matches.
|
|
148
|
-
else if (node.nodeType === Node.COMMENT_NODE
|
|
178
|
+
else if (node.nodeType === 8) { // Node.COMMENT_NODE
|
|
149
179
|
let parts = node.textContent.split(/[\ue000-\uf8ff]/g);
|
|
150
180
|
for (let i=0; i<parts.length-1; i++) {
|
|
151
|
-
let path = new
|
|
152
|
-
path.type = ExprPathType.Comment;
|
|
181
|
+
let path = new Path(node.previousSibling, node)
|
|
153
182
|
this.paths.push(path);
|
|
154
183
|
placeholdersUsed ++;
|
|
155
184
|
}
|
|
156
185
|
}
|
|
157
186
|
|
|
158
187
|
// Replace comment placeholders inside script and style tags, which have become text nodes.
|
|
159
|
-
else if (node.nodeType ===
|
|
188
|
+
else if (node.nodeType === 3 && ['SCRIPT', 'STYLE'].includes(node.parentNode?.nodeName)) { // Node.TEXT_NODE
|
|
160
189
|
let parts = node.textContent.split(commentPlaceholder);
|
|
161
190
|
if (parts.length > 1) {
|
|
162
191
|
|
|
163
192
|
let placeholders = [];
|
|
164
193
|
for (let i = 0; i<parts.length; i++) {
|
|
165
|
-
let current =
|
|
194
|
+
let current = Globals.doc.createTextNode(parts[i]);
|
|
166
195
|
node.parentNode.insertBefore(current, node);
|
|
167
196
|
if (i > 0)
|
|
168
197
|
placeholders.push(current)
|
|
169
198
|
}
|
|
170
199
|
|
|
171
200
|
for (let i=0, node; node=placeholders[i]; i++) {
|
|
172
|
-
let path = new
|
|
201
|
+
let path = new PathToNodes(node.previousSibling, node);
|
|
173
202
|
this.paths.push(path);
|
|
174
203
|
placeholdersUsed ++;
|
|
175
204
|
|
|
@@ -188,51 +217,31 @@ export default class Shell {
|
|
|
188
217
|
if (placeholdersUsed !== html.length-1)
|
|
189
218
|
throw new Error(`Could not parse expressions in template. Check for duplicate attributes or malformed html: ${html.join('${...}')}`);
|
|
190
219
|
|
|
191
|
-
// Handle solarite-placeholder's.
|
|
192
|
-
|
|
193
|
-
// 3. Rename "is" attributes so the Web Components don't instantiate until we have the values of their PathExpr arguments.
|
|
194
|
-
// that happens in NodeGroup.applyComponentExprs()
|
|
195
|
-
for (let el of this.fragment.querySelectorAll('[is]'))
|
|
196
|
-
el.setAttribute('_is', el.getAttribute('is'));
|
|
197
|
-
|
|
198
220
|
for (let path of this.paths) {
|
|
199
221
|
if (path.nodeBefore)
|
|
200
222
|
path.nodeBeforeIndex = Array.prototype.indexOf.call(path.nodeBefore.parentNode.childNodes, path.nodeBefore)
|
|
201
|
-
path.nodeMarkerPath = getNodePath(path.nodeMarker)
|
|
202
223
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
224
|
+
// Must be calculated after we remove the toRemove nodes:
|
|
225
|
+
path.nodeMarkerPath = Path.get(path.nodeMarker)
|
|
226
|
+
|
|
227
|
+
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
this.findEmbeds();
|
|
211
231
|
|
|
232
|
+
|
|
212
233
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
213
234
|
}
|
|
214
235
|
|
|
215
236
|
/**
|
|
216
237
|
* 1. Add a Unicode placeholder char for where expressions go within attributes.
|
|
217
238
|
* 2. Add a comment placeholder for where expressions are children of other nodes.
|
|
218
|
-
* 3. Append -solarite-placeholder to the tag names of custom components so that we can
|
|
239
|
+
* 3. Append -solarite-placeholder to the tag names of custom components so that we can instantiate them later
|
|
240
|
+
* when we can manually call their constructors with the proper attribute and children arguments from evaluated expressions.
|
|
219
241
|
* @param htmlChunks {string[]}
|
|
220
|
-
* @returns {string} */
|
|
242
|
+
* @returns {string} Html with the placeholders in place. */
|
|
221
243
|
static addPlaceholders(htmlChunks) {
|
|
222
|
-
let
|
|
223
|
-
|
|
224
|
-
function addToken(token, context) {
|
|
225
|
-
|
|
226
|
-
if (context === HtmlParser.Tag) {
|
|
227
|
-
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
228
|
-
// and give them a solarite-placeholder attribute so we can easily find them later.
|
|
229
|
-
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
230
|
-
// Later, NodeGroup.instantiateComponent() will replace them with the real components.
|
|
231
|
-
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
232
|
-
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder solarite-placeholder');
|
|
233
|
-
}
|
|
234
|
-
tokens.push(token)
|
|
235
|
-
}
|
|
244
|
+
let result = [];
|
|
236
245
|
|
|
237
246
|
let htmlParser = new HtmlParser(); // Reset the context.
|
|
238
247
|
for (let i = 0; i < htmlChunks.length; i++) {
|
|
@@ -240,10 +249,20 @@ export default class Shell {
|
|
|
240
249
|
|
|
241
250
|
// Append -solarite-placholder to web component tags, so we can pass args to them when they're instantiated.
|
|
242
251
|
let lastIndex = 0;
|
|
243
|
-
let context = htmlParser.parse(lastHtml, (html, index,
|
|
252
|
+
let context = htmlParser.parse(lastHtml, (html, index, prevContext/*, nextContext*/) => { // This function is called every time the html context changes.
|
|
244
253
|
if (lastIndex !== index) {
|
|
245
254
|
let token = html.slice(lastIndex, index);
|
|
246
|
-
|
|
255
|
+
|
|
256
|
+
if (prevContext === HtmlParser.Tag) {
|
|
257
|
+
// Find Web Component tags and append -solarite-placeholder to their tag names
|
|
258
|
+
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
259
|
+
// Later, PathToComponent.apply() will replace them with the real components.
|
|
260
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
261
|
+
const isWebComponentTagName = /^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i; // a dash in the middle
|
|
262
|
+
token = token.replace(isWebComponentTagName, match => match + '-SOLARITE-PLACEHOLDER'); // caps to match other instances of this string, for better compression.
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
result.push(token);
|
|
247
266
|
}
|
|
248
267
|
lastIndex = index;
|
|
249
268
|
});
|
|
@@ -251,13 +270,13 @@ export default class Shell {
|
|
|
251
270
|
// Insert placeholders
|
|
252
271
|
if (i < htmlChunks.length - 1) {
|
|
253
272
|
if (context === HtmlParser.Text)
|
|
254
|
-
|
|
273
|
+
result.push(commentPlaceholder) // Comment Placeholder. because we can't put text in between <tr> tags for example.
|
|
255
274
|
else
|
|
256
|
-
|
|
275
|
+
result.push(String.fromCharCode(attribPlaceholder + i));
|
|
257
276
|
}
|
|
258
277
|
}
|
|
259
278
|
|
|
260
|
-
return
|
|
279
|
+
return result.join('');
|
|
261
280
|
}
|
|
262
281
|
|
|
263
282
|
/**
|
|
@@ -269,10 +288,10 @@ export default class Shell {
|
|
|
269
288
|
* this.ids
|
|
270
289
|
* this.staticComponents */
|
|
271
290
|
findEmbeds() {
|
|
272
|
-
this.scripts = Array.prototype.map.call(this.fragment.querySelectorAll('scripts'), el =>
|
|
291
|
+
this.scripts = Array.prototype.map.call(this.fragment.querySelectorAll('scripts'), el => Path.get(el))
|
|
273
292
|
|
|
274
|
-
// TODO: only find styles that have
|
|
275
|
-
this.styles = Array.prototype.map.call(this.fragment.querySelectorAll('style'), el =>
|
|
293
|
+
// TODO: only find styles that have Paths in them?
|
|
294
|
+
this.styles = Array.prototype.map.call(this.fragment.querySelectorAll('style'), el => Path.get(el))
|
|
276
295
|
|
|
277
296
|
let idEls = this.fragment.querySelectorAll('[id],[data-id]');
|
|
278
297
|
|
|
@@ -283,17 +302,7 @@ export default class Shell {
|
|
|
283
302
|
throw new Error(`<${el.tagName.toLowerCase()} id="${id}"> can't override existing HTMLElement id property.`)
|
|
284
303
|
}
|
|
285
304
|
|
|
286
|
-
this.ids = Array.prototype.map.call(idEls, el =>
|
|
287
|
-
|
|
288
|
-
for (let el of this.fragment.querySelectorAll('*')) {
|
|
289
|
-
if (el.tagName.includes('-') || el.hasAttribute('_is'))
|
|
290
|
-
|
|
291
|
-
// Dynamic components are components that have attributes with expression values.
|
|
292
|
-
// They are created from applyExprs()
|
|
293
|
-
// But static components are created in a separate path inside the NodeGroup constructor.
|
|
294
|
-
if (!this.paths.find(path => path.nodeMarker === el))
|
|
295
|
-
this.staticComponents.push(getNodePath(el));
|
|
296
|
-
}
|
|
305
|
+
this.ids = Array.prototype.map.call(idEls, el => Path.get(el))
|
|
297
306
|
}
|
|
298
307
|
|
|
299
308
|
/**
|
package/src/Solarite.d.ts
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Solarite
|
|
2
|
+
* Solarite JavaScript UI library.
|
|
3
3
|
* MIT License
|
|
4
4
|
* https://vorticode.github.io/solarite/
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export default function h(htmlStrings?: HTMLElement | string | string[] | Function | {render: Function}, ...exprs: any[]): Node | HTMLElement | Template | Function;
|
|
8
|
-
export { default as h, default as r } from './h.js';
|
|
9
|
-
|
|
10
|
-
export const ArgType: {
|
|
11
|
-
Bool: string;
|
|
12
|
-
Int: string;
|
|
13
|
-
Float: string;
|
|
14
|
-
String: string;
|
|
15
|
-
Json: string;
|
|
16
|
-
Eval: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getArg(el:HTMLElement, attributeName:string, defaultValue?:any,
|
|
20
|
-
type?:typeof ArgType[keyof typeof ArgType] | Function | any[], fallback?:any): any;
|
|
21
|
-
|
|
22
7
|
export interface RenderOptions {
|
|
23
8
|
styles?: boolean;
|
|
24
9
|
scripts?: boolean;
|
|
@@ -26,37 +11,96 @@ export interface RenderOptions {
|
|
|
26
11
|
render?: boolean;
|
|
27
12
|
}
|
|
28
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Tagged template literal or function for creating Templates and rendering to the DOM. */
|
|
16
|
+
declare function h(htmlStrings: TemplateStringsArray, ...exprs: any[]): Template;
|
|
17
|
+
declare function h(htmlStrings: string | string[], ...exprs: any[]): Template;
|
|
18
|
+
declare function h(el: HTMLElement | DocumentFragment, options?: RenderOptions): (htmlStrings: TemplateStringsArray, ...exprs: any[]) => HTMLElement | DocumentFragment;
|
|
19
|
+
declare function h(el: HTMLElement | DocumentFragment, template: Template, options?: RenderOptions): void;
|
|
20
|
+
declare function h(tag: string, props: object, ...children: any[]): Template; // JSX
|
|
21
|
+
declare function h(obj: {render: Function}): (htmlStrings: TemplateStringsArray, ...exprs: any[]) => void; // Rebound render
|
|
22
|
+
|
|
23
|
+
export default h;
|
|
24
|
+
export {h};
|
|
25
|
+
export {h as r};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Solarite provides more features if your web component extends Solarite instead of HTMLElement. */
|
|
29
|
+
export class Solarite extends HTMLElement {
|
|
30
|
+
constructor(attribs?: Record<string, any> | null);
|
|
31
|
+
render(attribs?: Record<string, any>, changed?:boolean): void;
|
|
32
|
+
renderFirstTime(): void;
|
|
33
|
+
connectedCallback(): void;
|
|
34
|
+
static define(tagName?: string | null): void;
|
|
35
|
+
static getAttribs(el: HTMLElement): Record<string, any>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert a template, string, or object into a DOM Node or Element. */
|
|
41
|
+
export function toEl(arg: string | Template | {render: () => void}): Node | HTMLElement | DocumentFragment;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @deprecated
|
|
45
|
+
* Retrieve and cast an attribute value from an HTMLElement. */
|
|
46
|
+
export function getArg(el: HTMLElement, attributeName: string, defaultValue?: any,
|
|
47
|
+
type?: typeof ArgType[keyof typeof ArgType] | Function | any[]): any;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated
|
|
51
|
+
* Update attributes on an element from an object. */
|
|
52
|
+
export function setArgs(el: HTMLElement, args: object): void;
|
|
53
|
+
|
|
54
|
+
/** @deprecated */
|
|
55
|
+
export const ArgType: {
|
|
56
|
+
Bool: string;
|
|
57
|
+
Int: string;
|
|
58
|
+
Float: string;
|
|
59
|
+
String: string;
|
|
60
|
+
Json: string;
|
|
61
|
+
Eval: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
29
64
|
export class Template {
|
|
30
|
-
exprs:
|
|
65
|
+
exprs: any[];
|
|
31
66
|
html: string[];
|
|
32
67
|
constructor(htmlStrings: string[], exprs: any[]);
|
|
33
|
-
render(el?: HTMLElement, options?: RenderOptions):
|
|
68
|
+
render(el?: HTMLElement | null, options?: RenderOptions): HTMLElement | DocumentFragment;
|
|
34
69
|
getExactKey(): string;
|
|
35
70
|
getCloseKey(): string;
|
|
71
|
+
static fromJsx(tag: string, props: Record<string, any> | null, children: any[]): Template;
|
|
36
72
|
}
|
|
37
73
|
|
|
38
74
|
export function delve(obj: object, path: string[], createVal?: any): any;
|
|
39
75
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Experimental:
|
|
43
|
-
//--------------
|
|
44
|
-
|
|
45
|
-
// Globals object
|
|
76
|
+
/**
|
|
77
|
+
* Internal utilities and state. */
|
|
46
78
|
export const Globals: {
|
|
47
|
-
componentArgsHash: WeakMap<any, any>;
|
|
48
79
|
connected: WeakSet<HTMLElement>;
|
|
49
|
-
|
|
80
|
+
currentPath: any;
|
|
81
|
+
currentSlotChildren: any[] | null;
|
|
50
82
|
div: HTMLDivElement;
|
|
83
|
+
doc: Document;
|
|
51
84
|
elementClasses: {[key: string]: typeof Node};
|
|
52
85
|
htmlProps: {[key: string]: boolean};
|
|
53
86
|
nodeEvents: WeakMap<Node, {[eventName: string]: [Function, Function, any[]]}>;
|
|
54
|
-
|
|
87
|
+
rootNodeGroups: WeakMap<HTMLElement, any>;
|
|
55
88
|
objToEl: WeakMap<any, any>;
|
|
56
89
|
rendered: WeakSet<HTMLElement>;
|
|
57
|
-
rendering: WeakSet<HTMLElement>;
|
|
58
90
|
shells: WeakMap<string[], any>;
|
|
59
91
|
reset: Function;
|
|
60
|
-
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const SolariteUtil: {
|
|
95
|
+
arraySame(a: any[], b: any[]): boolean;
|
|
96
|
+
attribsToObject(el: HTMLElement, ignore?: string | null): Record<string, any>;
|
|
97
|
+
bindId(root: any, el: HTMLElement): void;
|
|
98
|
+
bindStyles(style: HTMLStyleElement, root: HTMLElement): void;
|
|
99
|
+
camelToDashes(str: string): string;
|
|
100
|
+
dashesToCamel(str: string): string;
|
|
101
|
+
defineClass(Class: typeof HTMLElement, tagName?: string | null): void;
|
|
102
|
+
isIterable(obj: any): boolean;
|
|
103
|
+
trimEmptyNodes(nodes: NodeList | Node[]): Node[];
|
|
104
|
+
[key: string]: any;
|
|
61
105
|
};
|
|
62
106
|
|
package/src/Solarite.js
CHANGED
|
@@ -1,36 +1,148 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/*
|
|
2
|
+
┏┓ ┓ •
|
|
3
|
+
┗┓┏┓┃┏┓┏┓┓╋▗▖
|
|
4
|
+
┗┛┗┛┗┗┻╹ ╹╹┗
|
|
5
|
+
JavasCript UI library
|
|
6
|
+
@license MIT
|
|
7
|
+
@copyright Vorticode LLC
|
|
8
|
+
https://vorticode.github.io/solarite/ */
|
|
7
9
|
import h from './h.js';
|
|
8
10
|
export default h;
|
|
9
|
-
export {default as h, default as r} from './h.js'; //Named exports for h() are deprecated.
|
|
10
11
|
export {default as delve} from './delve.js';
|
|
11
|
-
export {getArg, ArgType} from './getArg.js';
|
|
12
12
|
export {default as Template} from './Template.js';
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
export {default as toEl} from './toEl.js';
|
|
14
|
+
import Template from './Template.js';
|
|
15
15
|
|
|
16
16
|
// Experimental:
|
|
17
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';
|
|
18
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';
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
19
40
|
|
|
20
|
-
|
|
41
|
+
|
|
42
|
+
// Solarite Class:
|
|
43
|
+
//--------------
|
|
44
|
+
import Util from "./Util.js";
|
|
45
|
+
import Globals from "./Globals.js";
|
|
21
46
|
|
|
22
47
|
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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);
|
|
28
57
|
}
|
|
29
58
|
});
|
|
30
59
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 {
|
|
76
|
+
|
|
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
|
+
}
|
|
35
115
|
|
|
36
|
-
|
|
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
|
+
}
|
|
138
|
+
|
|
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
|
+
}
|