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/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,36 @@ 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
|
|
|
@@ -114,7 +141,7 @@ export default class Shell {
|
|
|
114
141
|
// Get or create nodeBefore.
|
|
115
142
|
let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
|
|
116
143
|
if (!nodeBefore) {
|
|
117
|
-
nodeBefore = Globals.doc.createComment('
|
|
144
|
+
nodeBefore = Globals.doc.createComment('Path:'+this.paths.length);
|
|
118
145
|
node.parentNode.insertBefore(nodeBefore, node)
|
|
119
146
|
}
|
|
120
147
|
/*#IFDEV*/assert(nodeBefore);/*#ENDIF*/
|
|
@@ -130,11 +157,11 @@ export default class Shell {
|
|
|
130
157
|
// Re-use existing comment placeholder.
|
|
131
158
|
else {
|
|
132
159
|
nodeMarker = node;
|
|
133
|
-
nodeMarker.textContent = '
|
|
160
|
+
nodeMarker.textContent = 'PathEnd:'+ this.paths.length;
|
|
134
161
|
}
|
|
135
162
|
/*#IFDEV*/assert(nodeMarker);/*#ENDIF*/
|
|
136
163
|
|
|
137
|
-
let path = new
|
|
164
|
+
let path = new PathToNodes(nodeBefore, nodeMarker);
|
|
138
165
|
this.paths.push(path);
|
|
139
166
|
placeholdersUsed ++;
|
|
140
167
|
}
|
|
@@ -148,18 +175,17 @@ export default class Shell {
|
|
|
148
175
|
// Here we look for expressions in comments.
|
|
149
176
|
// We don't actually update them dynamically, but we still add paths for them.
|
|
150
177
|
// That way the expression count still matches.
|
|
151
|
-
else if (node.nodeType === Node.COMMENT_NODE
|
|
178
|
+
else if (node.nodeType === 8) { // Node.COMMENT_NODE
|
|
152
179
|
let parts = node.textContent.split(/[\ue000-\uf8ff]/g);
|
|
153
180
|
for (let i=0; i<parts.length-1; i++) {
|
|
154
|
-
let path = new
|
|
155
|
-
path.type = ExprPathType.Comment;
|
|
181
|
+
let path = new Path(node.previousSibling, node)
|
|
156
182
|
this.paths.push(path);
|
|
157
183
|
placeholdersUsed ++;
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
186
|
|
|
161
187
|
// Replace comment placeholders inside script and style tags, which have become text nodes.
|
|
162
|
-
else if (node.nodeType ===
|
|
188
|
+
else if (node.nodeType === 3 && ['SCRIPT', 'STYLE'].includes(node.parentNode?.nodeName)) { // Node.TEXT_NODE
|
|
163
189
|
let parts = node.textContent.split(commentPlaceholder);
|
|
164
190
|
if (parts.length > 1) {
|
|
165
191
|
|
|
@@ -172,7 +198,7 @@ export default class Shell {
|
|
|
172
198
|
}
|
|
173
199
|
|
|
174
200
|
for (let i=0, node; node=placeholders[i]; i++) {
|
|
175
|
-
let path = new
|
|
201
|
+
let path = new PathToNodes(node.previousSibling, node);
|
|
176
202
|
this.paths.push(path);
|
|
177
203
|
placeholdersUsed ++;
|
|
178
204
|
|
|
@@ -191,51 +217,31 @@ export default class Shell {
|
|
|
191
217
|
if (placeholdersUsed !== html.length-1)
|
|
192
218
|
throw new Error(`Could not parse expressions in template. Check for duplicate attributes or malformed html: ${html.join('${...}')}`);
|
|
193
219
|
|
|
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
220
|
for (let path of this.paths) {
|
|
202
221
|
if (path.nodeBefore)
|
|
203
222
|
path.nodeBeforeIndex = Array.prototype.indexOf.call(path.nodeBefore.parentNode.childNodes, path.nodeBefore)
|
|
204
|
-
path.nodeMarkerPath = getNodePath(path.nodeMarker)
|
|
205
223
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
224
|
+
// Must be calculated after we remove the toRemove nodes:
|
|
225
|
+
path.nodeMarkerPath = Path.get(path.nodeMarker)
|
|
226
|
+
|
|
227
|
+
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
this.findEmbeds();
|
|
214
231
|
|
|
232
|
+
|
|
215
233
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
216
234
|
}
|
|
217
235
|
|
|
218
236
|
/**
|
|
219
237
|
* 1. Add a Unicode placeholder char for where expressions go within attributes.
|
|
220
238
|
* 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
|
|
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.
|
|
222
241
|
* @param htmlChunks {string[]}
|
|
223
|
-
* @returns {string} */
|
|
242
|
+
* @returns {string} Html with the placeholders in place. */
|
|
224
243
|
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
|
-
}
|
|
244
|
+
let result = [];
|
|
239
245
|
|
|
240
246
|
let htmlParser = new HtmlParser(); // Reset the context.
|
|
241
247
|
for (let i = 0; i < htmlChunks.length; i++) {
|
|
@@ -243,10 +249,20 @@ export default class Shell {
|
|
|
243
249
|
|
|
244
250
|
// Append -solarite-placholder to web component tags, so we can pass args to them when they're instantiated.
|
|
245
251
|
let lastIndex = 0;
|
|
246
|
-
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.
|
|
247
253
|
if (lastIndex !== index) {
|
|
248
254
|
let token = html.slice(lastIndex, index);
|
|
249
|
-
|
|
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);
|
|
250
266
|
}
|
|
251
267
|
lastIndex = index;
|
|
252
268
|
});
|
|
@@ -254,13 +270,13 @@ export default class Shell {
|
|
|
254
270
|
// Insert placeholders
|
|
255
271
|
if (i < htmlChunks.length - 1) {
|
|
256
272
|
if (context === HtmlParser.Text)
|
|
257
|
-
|
|
273
|
+
result.push(commentPlaceholder) // Comment Placeholder. because we can't put text in between <tr> tags for example.
|
|
258
274
|
else
|
|
259
|
-
|
|
275
|
+
result.push(String.fromCharCode(attribPlaceholder + i));
|
|
260
276
|
}
|
|
261
277
|
}
|
|
262
278
|
|
|
263
|
-
return
|
|
279
|
+
return result.join('');
|
|
264
280
|
}
|
|
265
281
|
|
|
266
282
|
/**
|
|
@@ -272,10 +288,10 @@ export default class Shell {
|
|
|
272
288
|
* this.ids
|
|
273
289
|
* this.staticComponents */
|
|
274
290
|
findEmbeds() {
|
|
275
|
-
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))
|
|
276
292
|
|
|
277
|
-
// TODO: only find styles that have
|
|
278
|
-
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))
|
|
279
295
|
|
|
280
296
|
let idEls = this.fragment.querySelectorAll('[id],[data-id]');
|
|
281
297
|
|
|
@@ -286,17 +302,7 @@ export default class Shell {
|
|
|
286
302
|
throw new Error(`<${el.tagName.toLowerCase()} id="${id}"> can't override existing HTMLElement id property.`)
|
|
287
303
|
}
|
|
288
304
|
|
|
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
|
-
}
|
|
305
|
+
this.ids = Array.prototype.map.call(idEls, el => Path.get(el))
|
|
300
306
|
}
|
|
301
307
|
|
|
302
308
|
/**
|
package/src/Solarite.d.ts
CHANGED
|
@@ -4,63 +4,103 @@
|
|
|
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
|
|
12
22
|
|
|
23
|
+
export default h;
|
|
24
|
+
export {h};
|
|
25
|
+
export {h as r};
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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>;
|
|
21
36
|
}
|
|
22
37
|
|
|
23
|
-
export function getArg(el:HTMLElement, attributeName:string, defaultValue?:any,
|
|
24
|
-
type?:typeof ArgType[keyof typeof ArgType] | Function | any[], fallback?:any): any;
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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;
|
|
31
62
|
}
|
|
32
63
|
|
|
33
64
|
export class Template {
|
|
34
|
-
exprs:
|
|
65
|
+
exprs: any[];
|
|
35
66
|
html: string[];
|
|
36
67
|
constructor(htmlStrings: string[], exprs: any[]);
|
|
37
|
-
render(el?: HTMLElement, options?: RenderOptions):
|
|
68
|
+
render(el?: HTMLElement | null, options?: RenderOptions): HTMLElement | DocumentFragment;
|
|
38
69
|
getExactKey(): string;
|
|
39
70
|
getCloseKey(): string;
|
|
71
|
+
static fromJsx(tag: string, props: Record<string, any> | null, children: any[]): Template;
|
|
40
72
|
}
|
|
41
73
|
|
|
42
74
|
export function delve(obj: object, path: string[], createVal?: any): any;
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Experimental:
|
|
47
|
-
//--------------
|
|
48
|
-
|
|
49
|
-
// Globals object
|
|
76
|
+
/**
|
|
77
|
+
* Internal utilities and state. */
|
|
50
78
|
export const Globals: {
|
|
51
|
-
//componentArgsHash: WeakMap<any, any>;
|
|
52
79
|
connected: WeakSet<HTMLElement>;
|
|
53
|
-
|
|
80
|
+
currentPath: any;
|
|
81
|
+
currentSlotChildren: any[] | null;
|
|
54
82
|
div: HTMLDivElement;
|
|
83
|
+
doc: Document;
|
|
55
84
|
elementClasses: {[key: string]: typeof Node};
|
|
56
85
|
htmlProps: {[key: string]: boolean};
|
|
57
86
|
nodeEvents: WeakMap<Node, {[eventName: string]: [Function, Function, any[]]}>;
|
|
58
|
-
|
|
87
|
+
rootNodeGroups: WeakMap<HTMLElement, any>;
|
|
59
88
|
objToEl: WeakMap<any, any>;
|
|
60
89
|
rendered: WeakSet<HTMLElement>;
|
|
61
|
-
rendering: WeakSet<HTMLElement>;
|
|
62
90
|
shells: WeakMap<string[], any>;
|
|
63
91
|
reset: Function;
|
|
64
|
-
|
|
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;
|
|
65
105
|
};
|
|
66
106
|
|