solarite 0.4.0 → 0.5.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 +1992 -1945
- package/dist/Solarite.js +1825 -1806
- 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 +65 -0
- package/src/PathToNodes.js +566 -0
- package/src/RootNodeGroup.js +2 -147
- package/src/Shell.js +89 -78
- package/src/Solarite.d.ts +78 -31
- package/src/Solarite.js +262 -14
- package/src/Template.js +42 -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/RootNodeGroup.js
CHANGED
|
@@ -1,153 +1,8 @@
|
|
|
1
|
-
import Globals from './Globals.js';
|
|
2
1
|
import NodeGroup from './NodeGroup.js';
|
|
3
2
|
|
|
4
|
-
|
|
5
3
|
export default class RootNodeGroup extends NodeGroup {
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @type {HTMLElement} */
|
|
10
|
-
root;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* When we call renerWatched() we re-render these expressions, then clear this to a new Map()
|
|
14
|
-
* @type {Map<ExprPath, ValueOp|WholeArrayOp|ArraySpliceOp[]>} */
|
|
15
|
-
exprsToRender = new Map();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param template {Template}
|
|
19
|
-
* @param el {?HTMLElement} Optional, pre-existing htmlElement tat will be the root.
|
|
20
|
-
* @param options {?object} */
|
|
21
|
-
constructor(template, el, options) {
|
|
22
|
-
super(template);
|
|
23
|
-
|
|
24
|
-
this.options = options;
|
|
25
|
-
|
|
26
|
-
let [fragment, shell] = this.populateFromTemplate(template);
|
|
27
|
-
|
|
28
|
-
let startingPathDepth = 0;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (fragment instanceof Text) {
|
|
32
|
-
|
|
33
|
-
if (el) {
|
|
34
|
-
this.startNode = el;
|
|
35
|
-
this.endNode = el;
|
|
36
|
-
if (fragment.nodeValue.length)
|
|
37
|
-
el.append(fragment);
|
|
38
|
-
this.root = el;
|
|
39
|
-
}
|
|
40
|
-
else
|
|
41
|
-
throw new Error('Cannot create a standalone text node');
|
|
42
|
-
Globals.nodeGroups.set(this.root, this);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else {
|
|
47
|
-
|
|
48
|
-
// If adding NodeGroup to an element.
|
|
49
|
-
if (el) {
|
|
50
|
-
this.root = el;
|
|
51
|
-
|
|
52
|
-
// Save slot children
|
|
53
|
-
let slotChildren;
|
|
54
|
-
if (el.childNodes.length) {
|
|
55
|
-
slotChildren = Globals.doc.createDocumentFragment();
|
|
56
|
-
slotChildren.append(...el.childNodes);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// If el should replace the root node of the fragment.
|
|
60
|
-
if (isReplaceEl(fragment, el)) {
|
|
61
|
-
el.append(...fragment.children[0].childNodes);
|
|
62
|
-
|
|
63
|
-
// Copy attributes
|
|
64
|
-
for (let attrib of fragment.children[0].attributes)
|
|
65
|
-
if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
|
|
66
|
-
el.setAttribute(attrib.name, attrib.value);
|
|
67
|
-
|
|
68
|
-
// Go one level deeper into all of shell's paths.
|
|
69
|
-
startingPathDepth = 1;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
else {
|
|
73
|
-
let isEmpty = fragment.childNodes.length === 1 && fragment.childNodes[0].nodeType === 3 && fragment.childNodes[0].textContent === '';
|
|
74
|
-
if (!isEmpty)
|
|
75
|
-
el.append(...fragment.childNodes);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Setup children
|
|
79
|
-
if (slotChildren) {
|
|
80
|
-
|
|
81
|
-
// Named slots
|
|
82
|
-
for (let slot of el.querySelectorAll('slot[name]')) {
|
|
83
|
-
let name = slot.getAttribute('name')
|
|
84
|
-
if (name) {
|
|
85
|
-
let slotChildren2 = slotChildren.querySelectorAll(`[slot='${name}']`);
|
|
86
|
-
slot.append(...slotChildren2);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Unnamed slots
|
|
91
|
-
let unamedSlot = el.querySelector('slot:not([name])')
|
|
92
|
-
if (unamedSlot)
|
|
93
|
-
unamedSlot.append(slotChildren);
|
|
94
|
-
|
|
95
|
-
// No slots
|
|
96
|
-
else
|
|
97
|
-
el.append(slotChildren);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.startNode = el;
|
|
101
|
-
this.endNode = el;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Instantiate as a standalone element.
|
|
105
|
-
else {
|
|
106
|
-
let singleEl = getSingleEl(fragment);
|
|
107
|
-
this.root = singleEl || fragment; // We return the whole fragment when calling h() with a collection of nodes.
|
|
108
|
-
|
|
109
|
-
if (singleEl)
|
|
110
|
-
startingPathDepth = 1;
|
|
111
|
-
}
|
|
112
|
-
Globals.nodeGroups.set(this.root, this);
|
|
113
|
-
this.updatePaths(this.root, shell.paths, startingPathDepth);
|
|
114
|
-
|
|
115
|
-
// Static web components can sometimes have children created via expressions.
|
|
116
|
-
// But calling applyExprs() will mess up the shell's path to them.
|
|
117
|
-
// So we find them first, then call activateStaticComponents() after their children have been created.
|
|
118
|
-
this.staticComponents = this.findStaticComponents(this.root, shell, startingPathDepth);
|
|
119
|
-
|
|
120
|
-
this.activateEmbeds(this.root, shell, startingPathDepth);
|
|
121
|
-
|
|
122
|
-
// Apply exprs
|
|
123
|
-
this.applyExprs(template.exprs);
|
|
124
|
-
|
|
125
|
-
this.instantiateStaticComponents(this.staticComponents);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getSingleEl(fragment) {
|
|
133
|
-
let nonempty = [];
|
|
134
|
-
for (let n of fragment.childNodes) {
|
|
135
|
-
if (n.nodeType === 1 || n.nodeType === 3 && n.textContent.trim().length) {
|
|
136
|
-
if (nonempty.length)
|
|
137
|
-
return null;
|
|
138
|
-
nonempty.push(n);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return nonempty[0];
|
|
142
|
-
}
|
|
5
|
+
// Used only by watch.js
|
|
6
|
+
exprsToRender;
|
|
143
7
|
|
|
144
|
-
/**
|
|
145
|
-
* Does the fragment have one child that's an element matching the tagname of el?
|
|
146
|
-
* @param fragment {DocumentFragment}
|
|
147
|
-
* @param el {HTMLElement}
|
|
148
|
-
* @returns {boolean} */
|
|
149
|
-
function isReplaceEl(fragment, el) {
|
|
150
|
-
return fragment.children.length===1
|
|
151
|
-
&& el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
|
|
152
|
-
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
|
|
153
8
|
}
|
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,6 +49,7 @@ 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
54
|
this.fragment = Globals.doc.createTextNode(html[0]);
|
|
57
55
|
return;
|
|
@@ -59,11 +57,11 @@ export default class Shell {
|
|
|
59
57
|
|
|
60
58
|
|
|
61
59
|
// 1. Add placeholders
|
|
62
|
-
let
|
|
60
|
+
let htmlWithPlaceholders = Shell.addPlaceholders(html);
|
|
63
61
|
|
|
64
62
|
let template = Globals.doc.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
|
|
65
|
-
if (
|
|
66
|
-
template.innerHTML =
|
|
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
66
|
template.content.append(Globals.doc.createTextNode(''))
|
|
69
67
|
this.fragment = template.content;
|
|
@@ -75,20 +73,28 @@ export default class Shell {
|
|
|
75
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,15 +102,41 @@ 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
|
+
try {
|
|
118
|
+
node.setAttribute(attr.name, parts.join(''));
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
throw new Error(`Error setting attribute "${attr.name}" on node <${node.tagName}>: ${e.message}`);
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
124
|
}
|
|
106
125
|
}
|
|
126
|
+
|
|
127
|
+
// Web components
|
|
128
|
+
if (isComponent) {
|
|
129
|
+
let path = new PathToComponent(null, node);
|
|
130
|
+
path.attribPaths = componentAttribPaths;
|
|
131
|
+
this.paths.splice(this.paths.length - componentAttribPaths.length, 0, path); // Insert before its componentAttribPaths
|
|
132
|
+
|
|
133
|
+
if (hasIs) {
|
|
134
|
+
node.setAttribute('_is', node.getAttribute('is'));
|
|
135
|
+
node.removeAttribute('is');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
107
138
|
}
|
|
139
|
+
|
|
108
140
|
// Replace comment placeholders
|
|
109
141
|
else if (node.nodeType === 8 && node.nodeValue === '!✨!') {
|
|
110
142
|
|
|
@@ -114,7 +146,7 @@ export default class Shell {
|
|
|
114
146
|
// Get or create nodeBefore.
|
|
115
147
|
let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
|
|
116
148
|
if (!nodeBefore) {
|
|
117
|
-
nodeBefore = Globals.doc.createComment('
|
|
149
|
+
nodeBefore = Globals.doc.createComment('Path:'+this.paths.length);
|
|
118
150
|
node.parentNode.insertBefore(nodeBefore, node)
|
|
119
151
|
}
|
|
120
152
|
/*#IFDEV*/assert(nodeBefore);/*#ENDIF*/
|
|
@@ -130,11 +162,11 @@ export default class Shell {
|
|
|
130
162
|
// Re-use existing comment placeholder.
|
|
131
163
|
else {
|
|
132
164
|
nodeMarker = node;
|
|
133
|
-
nodeMarker.textContent = '
|
|
165
|
+
nodeMarker.textContent = 'PathEnd:'+ this.paths.length;
|
|
134
166
|
}
|
|
135
167
|
/*#IFDEV*/assert(nodeMarker);/*#ENDIF*/
|
|
136
168
|
|
|
137
|
-
let path = new
|
|
169
|
+
let path = new PathToNodes(nodeBefore, nodeMarker);
|
|
138
170
|
this.paths.push(path);
|
|
139
171
|
placeholdersUsed ++;
|
|
140
172
|
}
|
|
@@ -148,18 +180,17 @@ export default class Shell {
|
|
|
148
180
|
// Here we look for expressions in comments.
|
|
149
181
|
// We don't actually update them dynamically, but we still add paths for them.
|
|
150
182
|
// That way the expression count still matches.
|
|
151
|
-
else if (node.nodeType === Node.COMMENT_NODE
|
|
183
|
+
else if (node.nodeType === 8) { // Node.COMMENT_NODE
|
|
152
184
|
let parts = node.textContent.split(/[\ue000-\uf8ff]/g);
|
|
153
185
|
for (let i=0; i<parts.length-1; i++) {
|
|
154
|
-
let path = new
|
|
155
|
-
path.type = ExprPathType.Comment;
|
|
186
|
+
let path = new Path(node.previousSibling, node)
|
|
156
187
|
this.paths.push(path);
|
|
157
188
|
placeholdersUsed ++;
|
|
158
189
|
}
|
|
159
190
|
}
|
|
160
191
|
|
|
161
192
|
// Replace comment placeholders inside script and style tags, which have become text nodes.
|
|
162
|
-
else if (node.nodeType ===
|
|
193
|
+
else if (node.nodeType === 3 && ['SCRIPT', 'STYLE'].includes(node.parentNode?.nodeName)) { // Node.TEXT_NODE
|
|
163
194
|
let parts = node.textContent.split(commentPlaceholder);
|
|
164
195
|
if (parts.length > 1) {
|
|
165
196
|
|
|
@@ -172,7 +203,7 @@ export default class Shell {
|
|
|
172
203
|
}
|
|
173
204
|
|
|
174
205
|
for (let i=0, node; node=placeholders[i]; i++) {
|
|
175
|
-
let path = new
|
|
206
|
+
let path = new PathToNodes(node.previousSibling, node);
|
|
176
207
|
this.paths.push(path);
|
|
177
208
|
placeholdersUsed ++;
|
|
178
209
|
|
|
@@ -191,51 +222,31 @@ export default class Shell {
|
|
|
191
222
|
if (placeholdersUsed !== html.length-1)
|
|
192
223
|
throw new Error(`Could not parse expressions in template. Check for duplicate attributes or malformed html: ${html.join('${...}')}`);
|
|
193
224
|
|
|
194
|
-
// Handle solarite-placeholder's.
|
|
195
|
-
|
|
196
|
-
// 3. Rename "is" attributes so the Web Components don't instantiate until we have the values of their PathExpr arguments.
|
|
197
|
-
// that happens in NodeGroup.applyComponentExprs()
|
|
198
|
-
for (let el of this.fragment.querySelectorAll('[is]'))
|
|
199
|
-
el.setAttribute('_is', el.getAttribute('is'));
|
|
200
|
-
|
|
201
225
|
for (let path of this.paths) {
|
|
202
226
|
if (path.nodeBefore)
|
|
203
227
|
path.nodeBeforeIndex = Array.prototype.indexOf.call(path.nodeBefore.parentNode.childNodes, path.nodeBefore)
|
|
204
|
-
path.nodeMarkerPath = getNodePath(path.nodeMarker)
|
|
205
228
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
229
|
+
// Must be calculated after we remove the toRemove nodes:
|
|
230
|
+
path.nodeMarkerPath = Path.get(path.nodeMarker)
|
|
231
|
+
|
|
232
|
+
|
|
211
233
|
}
|
|
212
234
|
|
|
213
235
|
this.findEmbeds();
|
|
214
236
|
|
|
237
|
+
|
|
215
238
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
216
239
|
}
|
|
217
240
|
|
|
218
241
|
/**
|
|
219
242
|
* 1. Add a Unicode placeholder char for where expressions go within attributes.
|
|
220
243
|
* 2. Add a comment placeholder for where expressions are children of other nodes.
|
|
221
|
-
* 3. Append -solarite-placeholder to the tag names of custom components so that we can
|
|
244
|
+
* 3. Append -solarite-placeholder to the tag names of custom components so that we can instantiate them later
|
|
245
|
+
* when we can manually call their constructors with the proper attribute and children arguments from evaluated expressions.
|
|
222
246
|
* @param htmlChunks {string[]}
|
|
223
|
-
* @returns {string} */
|
|
247
|
+
* @returns {string} Html with the placeholders in place. */
|
|
224
248
|
static addPlaceholders(htmlChunks) {
|
|
225
|
-
let
|
|
226
|
-
|
|
227
|
-
function addToken(token, context) {
|
|
228
|
-
|
|
229
|
-
if (context === HtmlParser.Tag) {
|
|
230
|
-
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
231
|
-
// and give them a solarite-placeholder attribute so we can easily find them later.
|
|
232
|
-
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
233
|
-
// Later, NodeGroup.instantiateComponent() will replace them with the real components.
|
|
234
|
-
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
235
|
-
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder solarite-placeholder');
|
|
236
|
-
}
|
|
237
|
-
tokens.push(token)
|
|
238
|
-
}
|
|
249
|
+
let result = [];
|
|
239
250
|
|
|
240
251
|
let htmlParser = new HtmlParser(); // Reset the context.
|
|
241
252
|
for (let i = 0; i < htmlChunks.length; i++) {
|
|
@@ -243,10 +254,20 @@ export default class Shell {
|
|
|
243
254
|
|
|
244
255
|
// Append -solarite-placholder to web component tags, so we can pass args to them when they're instantiated.
|
|
245
256
|
let lastIndex = 0;
|
|
246
|
-
let context = htmlParser.parse(lastHtml, (html, index,
|
|
257
|
+
let context = htmlParser.parse(lastHtml, (html, index, prevContext/*, nextContext*/) => { // This function is called every time the html context changes.
|
|
247
258
|
if (lastIndex !== index) {
|
|
248
259
|
let token = html.slice(lastIndex, index);
|
|
249
|
-
|
|
260
|
+
|
|
261
|
+
if (prevContext === HtmlParser.Tag) {
|
|
262
|
+
// Find Web Component tags and append -solarite-placeholder to their tag names
|
|
263
|
+
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
264
|
+
// Later, PathToComponent.apply() will replace them with the real components.
|
|
265
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
266
|
+
const isWebComponentTagName = /^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i; // a dash in the middle
|
|
267
|
+
token = token.replace(isWebComponentTagName, match => match + '-SOLARITE-PLACEHOLDER'); // caps to match other instances of this string, for better compression.
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
result.push(token);
|
|
250
271
|
}
|
|
251
272
|
lastIndex = index;
|
|
252
273
|
});
|
|
@@ -254,13 +275,13 @@ export default class Shell {
|
|
|
254
275
|
// Insert placeholders
|
|
255
276
|
if (i < htmlChunks.length - 1) {
|
|
256
277
|
if (context === HtmlParser.Text)
|
|
257
|
-
|
|
278
|
+
result.push(commentPlaceholder) // Comment Placeholder. because we can't put text in between <tr> tags for example.
|
|
258
279
|
else
|
|
259
|
-
|
|
280
|
+
result.push(String.fromCharCode(attribPlaceholder + i));
|
|
260
281
|
}
|
|
261
282
|
}
|
|
262
283
|
|
|
263
|
-
return
|
|
284
|
+
return result.join('');
|
|
264
285
|
}
|
|
265
286
|
|
|
266
287
|
/**
|
|
@@ -272,10 +293,10 @@ export default class Shell {
|
|
|
272
293
|
* this.ids
|
|
273
294
|
* this.staticComponents */
|
|
274
295
|
findEmbeds() {
|
|
275
|
-
this.scripts = Array.prototype.map.call(this.fragment.querySelectorAll('scripts'), el =>
|
|
296
|
+
this.scripts = Array.prototype.map.call(this.fragment.querySelectorAll('scripts'), el => Path.get(el))
|
|
276
297
|
|
|
277
|
-
// TODO: only find styles that have
|
|
278
|
-
this.styles = Array.prototype.map.call(this.fragment.querySelectorAll('style'), el =>
|
|
298
|
+
// TODO: only find styles that have Paths in them?
|
|
299
|
+
this.styles = Array.prototype.map.call(this.fragment.querySelectorAll('style'), el => Path.get(el))
|
|
279
300
|
|
|
280
301
|
let idEls = this.fragment.querySelectorAll('[id],[data-id]');
|
|
281
302
|
|
|
@@ -286,17 +307,7 @@ export default class Shell {
|
|
|
286
307
|
throw new Error(`<${el.tagName.toLowerCase()} id="${id}"> can't override existing HTMLElement id property.`)
|
|
287
308
|
}
|
|
288
309
|
|
|
289
|
-
this.ids = Array.prototype.map.call(idEls, el =>
|
|
290
|
-
|
|
291
|
-
for (let el of this.fragment.querySelectorAll('*')) {
|
|
292
|
-
if (el.tagName.includes('-') || el.hasAttribute('_is'))
|
|
293
|
-
|
|
294
|
-
// Dynamic components are components that have attributes with expression values.
|
|
295
|
-
// They are created from applyExprs()
|
|
296
|
-
// But static components are created in a separate path inside the NodeGroup constructor.
|
|
297
|
-
if (!this.paths.find(path => path.nodeMarker === el))
|
|
298
|
-
this.staticComponents.push(getNodePath(el));
|
|
299
|
-
}
|
|
310
|
+
this.ids = Array.prototype.map.call(idEls, el => Path.get(el))
|
|
300
311
|
}
|
|
301
312
|
|
|
302
313
|
/**
|
package/src/Solarite.d.ts
CHANGED
|
@@ -4,63 +4,110 @@
|
|
|
4
4
|
* https://vorticode.github.io/solarite/
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
7
|
+
export interface RenderOptions {
|
|
8
|
+
styles?: boolean;
|
|
9
|
+
scripts?: boolean;
|
|
10
|
+
ids?: boolean;
|
|
11
|
+
render?: boolean;
|
|
12
|
+
}
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
declare function h(): (htmlStrings: TemplateStringsArray, ...exprs: any[]) => Node|DocumentFragment;
|
|
12
23
|
|
|
24
|
+
export default h;
|
|
25
|
+
export {h};
|
|
26
|
+
export {h as r}; // deprecated
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Solarite provides more features if your web component extends Solarite instead of HTMLElement. */
|
|
30
|
+
export class Solarite extends HTMLElement {
|
|
31
|
+
constructor(attribs?: Record<string, any> | null);
|
|
32
|
+
render(attribs?: Record<string, any>, changed?:boolean): void;
|
|
33
|
+
renderFirstTime(): void;
|
|
34
|
+
connectedCallback(): void;
|
|
35
|
+
static define(tagName?: string | null): void;
|
|
36
|
+
static getAttribs(el: HTMLElement): Record<string, any>;
|
|
21
37
|
}
|
|
22
38
|
|
|
23
|
-
export function getArg(el:HTMLElement, attributeName:string, defaultValue?:any,
|
|
24
|
-
type?:typeof ArgType[keyof typeof ArgType] | Function | any[], fallback?:any): any;
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Convert a template, string, or object into a DOM Node or Element. */
|
|
42
|
+
export function toEl(arg: string | Template | {render: () => void}): Node | HTMLElement | DocumentFragment;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Assign fields from `src` to `dest` if they exist in `dest` and don't exist in `ignore`.
|
|
46
|
+
* When a value in `src` is a string and the existing value in `dest` is a boolean, number, or Date,
|
|
47
|
+
* it will be converted to that type. */
|
|
48
|
+
export function assignFields(dest: object, src: object|null, ignore?: string[]): void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated
|
|
52
|
+
* Retrieve and cast an attribute value from an HTMLElement. */
|
|
53
|
+
export function getArg(el: HTMLElement, attributeName: string, defaultValue?: any,
|
|
54
|
+
type?: typeof ArgType[keyof typeof ArgType] | Function | any[]): any;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated
|
|
58
|
+
* Update attributes on an element from an object. */
|
|
59
|
+
export function setArgs(el: HTMLElement, args: object): void;
|
|
60
|
+
|
|
61
|
+
/** @deprecated */
|
|
62
|
+
export const ArgType: {
|
|
63
|
+
Bool: string;
|
|
64
|
+
Int: string;
|
|
65
|
+
Float: string;
|
|
66
|
+
String: string;
|
|
67
|
+
Json: string;
|
|
68
|
+
Eval: string;
|
|
31
69
|
}
|
|
32
70
|
|
|
33
71
|
export class Template {
|
|
34
|
-
exprs:
|
|
72
|
+
exprs: any[];
|
|
35
73
|
html: string[];
|
|
36
74
|
constructor(htmlStrings: string[], exprs: any[]);
|
|
37
|
-
render(el?: HTMLElement, options?: RenderOptions):
|
|
75
|
+
render(el?: HTMLElement | null, options?: RenderOptions): HTMLElement | DocumentFragment;
|
|
38
76
|
getExactKey(): string;
|
|
39
77
|
getCloseKey(): string;
|
|
78
|
+
static fromJsx(tag: string, props: Record<string, any> | null, children: any[]): Template;
|
|
40
79
|
}
|
|
41
80
|
|
|
42
81
|
export function delve(obj: object, path: string[], createVal?: any): any;
|
|
43
82
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Experimental:
|
|
47
|
-
//--------------
|
|
48
|
-
|
|
49
|
-
// Globals object
|
|
83
|
+
/**
|
|
84
|
+
* Internal utilities and state. */
|
|
50
85
|
export const Globals: {
|
|
51
|
-
//componentArgsHash: WeakMap<any, any>;
|
|
52
86
|
connected: WeakSet<HTMLElement>;
|
|
53
|
-
|
|
87
|
+
currentPath: any;
|
|
88
|
+
currentSlotChildren: any[] | null;
|
|
54
89
|
div: HTMLDivElement;
|
|
90
|
+
doc: Document;
|
|
55
91
|
elementClasses: {[key: string]: typeof Node};
|
|
56
92
|
htmlProps: {[key: string]: boolean};
|
|
57
93
|
nodeEvents: WeakMap<Node, {[eventName: string]: [Function, Function, any[]]}>;
|
|
58
|
-
|
|
94
|
+
rootNodeGroups: WeakMap<HTMLElement, any>;
|
|
59
95
|
objToEl: WeakMap<any, any>;
|
|
60
96
|
rendered: WeakSet<HTMLElement>;
|
|
61
|
-
rendering: WeakSet<HTMLElement>;
|
|
62
97
|
shells: WeakMap<string[], any>;
|
|
63
98
|
reset: Function;
|
|
64
|
-
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const SolariteUtil: {
|
|
102
|
+
arraySame(a: any[], b: any[]): boolean;
|
|
103
|
+
attribsToObject(el: HTMLElement, ignore?: string | null): Record<string, any>;
|
|
104
|
+
bindId(root: any, el: HTMLElement): void;
|
|
105
|
+
bindStyles(style: HTMLStyleElement, root: HTMLElement): void;
|
|
106
|
+
camelToDashes(str: string): string;
|
|
107
|
+
dashesToCamel(str: string): string;
|
|
108
|
+
defineClass(Class: typeof HTMLElement, tagName?: string | null): void;
|
|
109
|
+
isIterable(obj: any): boolean;
|
|
110
|
+
trimEmptyNodes(nodes: NodeList | Node[]): Node[];
|
|
111
|
+
[key: string]: any;
|
|
65
112
|
};
|
|
66
113
|
|