@webtides/element-js 1.1.7 → 1.2.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/docs/features/styles.md +29 -0
- package/package.json +1 -1
- package/src/BaseElement.js +6 -0
- package/src/dom-parts/ChildNodePart.js +193 -227
- package/src/dom-parts/PartMarkers.js +62 -0
- package/src/dom-parts/TemplatePart.js +59 -84
- package/src/util/GlobalStylesStore.js +72 -36
package/docs/features/styles.md
CHANGED
|
@@ -87,6 +87,35 @@ It is also possible to combine the `:host` selector with other matching selector
|
|
|
87
87
|
}
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
#### CSS :state pseudo-class
|
|
91
|
+
|
|
92
|
+
All _element-js_ elements automatically have the "connected" state added when they are connected and rendered to the DOM. This uses the [ElementInternals.states](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states) API, which is a standard web API.
|
|
93
|
+
|
|
94
|
+
You can target elements with specific states using the `:state()` pseudo-class in CSS:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
/* Style the element when it's connected to the DOM */
|
|
98
|
+
:state(connected) {
|
|
99
|
+
opacity: 1;
|
|
100
|
+
transition: opacity 0.3s ease-in-out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Style the element when it's not yet connected */
|
|
104
|
+
:not(:state(connected)) {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
You can combine the `:state()` pseudo-class with the `:host` selector in Shadow DOM:
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
:host(:state(connected)) {
|
|
113
|
+
border: 1px solid green;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> Note: The `:state()` pseudo-class is part of the standard CSS Selectors Level 4 specification, but it might not be recognized by all CSS linters or editors yet.
|
|
118
|
+
|
|
90
119
|
#### CSS custom properties
|
|
91
120
|
|
|
92
121
|
Custom properties allow for reusing CSS values throughout a document. To read all about it, see [Using CSS custom properties (variables) - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
|
package/package.json
CHANGED
package/src/BaseElement.js
CHANGED
|
@@ -52,6 +52,9 @@ export { toString } from './util/toString.js';
|
|
|
52
52
|
class BaseElement extends HTMLElement {
|
|
53
53
|
_serializationKey;
|
|
54
54
|
|
|
55
|
+
/** @type {ElementInternals} */
|
|
56
|
+
_internals;
|
|
57
|
+
|
|
55
58
|
/**
|
|
56
59
|
* @param {BaseElementOptions} options
|
|
57
60
|
*/
|
|
@@ -76,6 +79,7 @@ class BaseElement extends HTMLElement {
|
|
|
76
79
|
propertyOptions: {},
|
|
77
80
|
...options,
|
|
78
81
|
};
|
|
82
|
+
this._internals = this.attachInternals();
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
/**
|
|
@@ -121,9 +125,11 @@ class BaseElement extends HTMLElement {
|
|
|
121
125
|
this.registerEventsAndRefs();
|
|
122
126
|
|
|
123
127
|
this.triggerHook('connected');
|
|
128
|
+
this._internals.states.add('connected');
|
|
124
129
|
} else {
|
|
125
130
|
this.requestUpdate({ notify: false }).then(() => {
|
|
126
131
|
this.triggerHook('connected');
|
|
132
|
+
this._internals.states.add('connected');
|
|
127
133
|
this.triggerHook('afterUpdate');
|
|
128
134
|
});
|
|
129
135
|
}
|
|
@@ -2,211 +2,204 @@ import { COMMENT_NODE, ELEMENT_NODE } from '../util/DOMHelper.js';
|
|
|
2
2
|
import { Part } from './Part.js';
|
|
3
3
|
import { TemplateResult } from './TemplateResult.js';
|
|
4
4
|
import { TemplatePart } from './TemplatePart.js';
|
|
5
|
+
import { PartMarkers } from './PartMarkers.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {Comment} commentNode
|
|
9
|
+
* @param {Node[]} nodes
|
|
9
10
|
*/
|
|
10
|
-
const insertNodesBetweenComments = (commentNode, nodes) => {
|
|
11
|
+
export const insertNodesBetweenComments = (commentNode, nodes) => {
|
|
11
12
|
for (const node of nodes) {
|
|
12
13
|
commentNode.parentNode.insertBefore(node, commentNode);
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* @param {
|
|
18
|
+
* @param {Comment} commentNode
|
|
19
|
+
* @param {Node[]} nodes
|
|
18
20
|
*/
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
commentNode.previousSibling.data === commentNode.data.replace('/', '')
|
|
24
|
-
) {
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
commentNode.parentNode.removeChild(commentNode.previousSibling);
|
|
21
|
+
export const replaceNodesBetweenComments = (commentNode, nodes) => {
|
|
22
|
+
removeNodesBetweenComments(commentNode);
|
|
23
|
+
for (const node of nodes) {
|
|
24
|
+
commentNode.parentNode.insertBefore(node, commentNode);
|
|
28
25
|
}
|
|
29
26
|
};
|
|
30
27
|
|
|
31
28
|
/**
|
|
32
|
-
* @param {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
* @param {Comment} commentNode
|
|
30
|
+
*/
|
|
31
|
+
export const removeNodesBetweenComments = (commentNode) => {
|
|
32
|
+
const nodes = getNodesBetweenComments(commentNode);
|
|
33
|
+
for (const node of nodes) {
|
|
34
|
+
commentNode.parentNode.removeChild(node);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Comment} commentNode
|
|
40
|
+
* @param {boolean} includeComments
|
|
36
41
|
* @return {Node[]}
|
|
37
42
|
*/
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// closing comment marker (/template-part) aka the anchorNode. So we push it to the end by inserting undefined items.
|
|
49
|
-
if (clonedTemplateChildNodes.length > clonedDomChildNodes.length) {
|
|
50
|
-
const nodesToFill = clonedTemplateChildNodes.length - clonedDomChildNodes.length;
|
|
51
|
-
for (let i = 0; i < nodesToFill; i++) {
|
|
52
|
-
clonedDomChildNodes.splice(clonedDomChildNodes.length - offset, 0, undefined);
|
|
43
|
+
export const getNodesBetweenComments = (commentNode, includeComments = false) => {
|
|
44
|
+
const nodes = [];
|
|
45
|
+
let currentNode = includeComments ? commentNode : commentNode.previousSibling;
|
|
46
|
+
let nestingLevel = 0;
|
|
47
|
+
|
|
48
|
+
while (currentNode) {
|
|
49
|
+
if (includeComments) nodes.unshift(currentNode);
|
|
50
|
+
|
|
51
|
+
if (currentNode.nodeType === COMMENT_NODE && currentNode.data === commentNode.data) {
|
|
52
|
+
nestingLevel++;
|
|
53
53
|
}
|
|
54
|
-
}
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
if (currentNode.nodeType === COMMENT_NODE && currentNode.data === commentNode.data.replace('/', '')) {
|
|
56
|
+
if (nestingLevel === 0) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
nestingLevel--;
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
if (!includeComments) nodes.unshift(currentNode);
|
|
63
|
+
currentNode = currentNode.previousSibling;
|
|
61
64
|
}
|
|
65
|
+
return nodes;
|
|
66
|
+
};
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
/**
|
|
69
|
+
* @param {Node[]} newChildNodes
|
|
70
|
+
* @param {Comment} anchorNode
|
|
71
|
+
*/
|
|
72
|
+
const diffChildNodes = function (newChildNodes, anchorNode) {
|
|
73
|
+
const parentNode = anchorNode.parentNode;
|
|
74
|
+
const oldChildNodes = getNodesBetweenComments(anchorNode);
|
|
75
|
+
|
|
76
|
+
const length = Math.max(oldChildNodes.length, newChildNodes.length);
|
|
64
77
|
// Diff each node in the child node lists
|
|
65
78
|
for (let index = 0; index < length; index++) {
|
|
66
|
-
let
|
|
67
|
-
let
|
|
79
|
+
let oldChildNode = oldChildNodes[index];
|
|
80
|
+
let newChildNode = newChildNodes[index];
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
parentNode.insertBefore(document.createTextNode(templateChildNode), anchorNode);
|
|
77
|
-
}
|
|
82
|
+
if (newChildNode && !newChildNode.nodeType) {
|
|
83
|
+
throw new Error('ChildNodePart: newChildNode is not a node');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If the DOM node doesn't exist, append/copy the new node
|
|
87
|
+
if (oldChildNode === undefined) {
|
|
88
|
+
parentNode.insertBefore(newChildNode, anchorNode);
|
|
78
89
|
continue;
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// remove dom node
|
|
85
|
-
parentNode.removeChild(domChildNode);
|
|
86
|
-
} else if (Array.isArray(domChildNode) || domChildNode instanceof TemplatePart) {
|
|
87
|
-
// remove childNodes
|
|
88
|
-
const childNodes = Array.isArray(domChildNode) ? domChildNode : domChildNode.childNodes;
|
|
89
|
-
for (const childNode of childNodes) {
|
|
90
|
-
childNode.remove();
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
console.log('This should not happen... what do we do about this node?!', { domChildNode });
|
|
94
|
-
}
|
|
92
|
+
// If the new node doesn't exist, remove the node in the DOM
|
|
93
|
+
if (newChildNode === undefined) {
|
|
94
|
+
parentNode.removeChild(oldChildNode);
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// If DOM node is equal to the
|
|
99
|
-
if (
|
|
100
|
-
domChildNode === templateChildNode ||
|
|
101
|
-
(domChildNode.nodeType && templateChildNode.nodeType && domChildNode.isEqualNode(templateChildNode))
|
|
102
|
-
) {
|
|
103
|
-
// but we need to assign the current DOM node into the templateChildNodes
|
|
104
|
-
// as they will be returned here at the end and become the domNodes for the next diffing.
|
|
105
|
-
const childIndex = templateChildNodes.indexOf(templateChildNode);
|
|
106
|
-
templateChildNodes[childIndex] = domChildNode;
|
|
98
|
+
// If DOM node is equal to the new node, don't do anything in the DOM
|
|
99
|
+
if (oldChildNode === newChildNode || oldChildNode.isEqualNode(newChildNode)) {
|
|
107
100
|
continue;
|
|
108
101
|
}
|
|
109
102
|
|
|
110
103
|
// Everything else is somehow different and can be replaced
|
|
111
|
-
|
|
112
|
-
parentNode.replaceChild(templateChildNode, domChildNode);
|
|
113
|
-
} else {
|
|
114
|
-
console.warn('This should not happen', {
|
|
115
|
-
parentNode,
|
|
116
|
-
'parentNode.contains(domChildNode)': parentNode.contains(domChildNode),
|
|
117
|
-
templateChildNode,
|
|
118
|
-
domChildNode,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
104
|
+
parentNode.replaceChild(newChildNode, oldChildNode);
|
|
121
105
|
}
|
|
122
|
-
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function getType(value) {
|
|
109
|
+
if (value instanceof TemplatePart) return 'templatePart';
|
|
110
|
+
if (value === null || value === undefined) return 'empty';
|
|
111
|
+
if (Array.isArray(value)) return value.length === 0 ? 'empty' : 'array';
|
|
112
|
+
if (value?.nodeType) return 'node';
|
|
113
|
+
return 'text';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// TODO: handle funcitons?! Should be handled in parseArray!!!
|
|
117
|
+
const unwrapArray = (nodes) => {
|
|
118
|
+
// we have to unwrap any complex objects inside the array so that we can diff arrays of childNodes
|
|
119
|
+
return nodes.flatMap((value) => {
|
|
120
|
+
if (value instanceof TemplatePart) {
|
|
121
|
+
return value.childNodes;
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
return unwrapArray(value);
|
|
125
|
+
}
|
|
126
|
+
if (typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
|
|
127
|
+
return document.createTextNode(value);
|
|
128
|
+
}
|
|
129
|
+
return value;
|
|
130
|
+
});
|
|
123
131
|
};
|
|
124
132
|
|
|
125
133
|
/**
|
|
126
|
-
* @param {
|
|
134
|
+
* @param {Comment} comment
|
|
127
135
|
* @param {TemplatePart | any[] | any} initialValue
|
|
136
|
+
* @param {boolean} serverSideRendered
|
|
128
137
|
* @return {(function(*): void)|*}
|
|
129
138
|
*/
|
|
130
|
-
export const processNodePart = (comment, initialValue) => {
|
|
131
|
-
let
|
|
132
|
-
let oldValue =
|
|
139
|
+
export const processNodePart = (comment, initialValue, serverSideRendered = false) => {
|
|
140
|
+
let oldValueType = 'empty';
|
|
141
|
+
let oldValue = undefined;
|
|
133
142
|
// this is for string values to be inserted into the DOM. A cached TextNode will be used so that we don't have to constantly create new DOM nodes.
|
|
134
|
-
|
|
143
|
+
// TODO: when server side rendered, we need to get the text node from the DOM no?!
|
|
144
|
+
let cachedTextNode = globalThis.document?.createTextNode('');
|
|
145
|
+
|
|
146
|
+
if (serverSideRendered) {
|
|
147
|
+
oldValueType = getType(initialValue);
|
|
148
|
+
oldValue = initialValue instanceof TemplatePart ? initialValue.strings : initialValue;
|
|
149
|
+
}
|
|
135
150
|
|
|
136
151
|
const processNodeValue = (newValue) => {
|
|
137
|
-
|
|
138
|
-
// primitives are handled as text content
|
|
139
|
-
case 'string':
|
|
140
|
-
case 'number':
|
|
141
|
-
case 'boolean':
|
|
142
|
-
if (oldValue !== newValue) {
|
|
143
|
-
oldValue = newValue;
|
|
144
|
-
if (!cachedTextNode) cachedTextNode = globalThis.document?.createTextNode('');
|
|
145
|
-
cachedTextNode.data = newValue;
|
|
146
|
-
|
|
147
|
-
if (comment.previousSibling?.data === comment.data.replace('/', '')) {
|
|
148
|
-
// the part is empty - we haven't rendered it yet
|
|
149
|
-
comment.parentNode.insertBefore(cachedTextNode, comment);
|
|
150
|
-
} else {
|
|
151
|
-
// the part was already rendered (either server side or on the client)
|
|
152
|
-
comment.previousSibling.data = newValue;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
nodes = [cachedTextNode];
|
|
156
|
-
}
|
|
157
|
-
break;
|
|
158
|
-
// null (= typeof "object") and undefined are used to clean up previous content
|
|
159
|
-
case 'object':
|
|
160
|
-
case 'undefined':
|
|
161
|
-
if (newValue === null || newValue === undefined) {
|
|
162
|
-
if (oldValue !== newValue) {
|
|
163
|
-
oldValue = newValue;
|
|
164
|
-
removeNodesBetweenComments(comment);
|
|
165
|
-
nodes = [];
|
|
166
|
-
}
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
if (Array.isArray(newValue)) {
|
|
170
|
-
if (newValue.length === 0) {
|
|
171
|
-
removeNodesBetweenComments(comment);
|
|
172
|
-
nodes = [];
|
|
173
|
-
} else {
|
|
174
|
-
// TODO: this will always diff the arrays although they might not have changed...
|
|
175
|
-
const unwrapArray = (nodes) => {
|
|
176
|
-
// we have to unwrap any complex objects inside the array so that we can diff arrays of childNodes
|
|
177
|
-
return nodes.flatMap((value) => {
|
|
178
|
-
if (value instanceof TemplatePart) {
|
|
179
|
-
return value.childNodes;
|
|
180
|
-
}
|
|
181
|
-
if (Array.isArray(value)) {
|
|
182
|
-
return unwrapArray(value);
|
|
183
|
-
}
|
|
184
|
-
return value;
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
nodes = diffChildNodes(comment.parentNode, nodes, unwrapArray(newValue), comment);
|
|
188
|
-
}
|
|
189
|
-
oldValue = newValue;
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
152
|
+
let newValueType = getType(newValue);
|
|
192
153
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
154
|
+
if (oldValueType === newValueType && oldValue === newValue) return;
|
|
155
|
+
|
|
156
|
+
if (oldValueType !== newValueType) {
|
|
157
|
+
if (newValueType === 'empty') {
|
|
158
|
+
removeNodesBetweenComments(comment);
|
|
159
|
+
} else {
|
|
160
|
+
const getNodesForType = (value, type) => {
|
|
161
|
+
if (type === 'templatePart') return value.childNodes;
|
|
162
|
+
if (type === 'array') return unwrapArray(value);
|
|
163
|
+
if (type === 'node') return [value];
|
|
164
|
+
|
|
165
|
+
// Every else falls back to rendering a text node
|
|
166
|
+
cachedTextNode.data = value;
|
|
167
|
+
return [cachedTextNode];
|
|
168
|
+
};
|
|
169
|
+
replaceNodesBetweenComments(comment, getNodesForType(newValue, newValueType));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
oldValueType = newValueType;
|
|
173
|
+
oldValue = newValue;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (newValueType === 'text') {
|
|
178
|
+
cachedTextNode.data = newValue;
|
|
179
|
+
|
|
180
|
+
oldValue = newValue;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (newValueType === 'array') {
|
|
185
|
+
diffChildNodes(unwrapArray(newValue), comment);
|
|
186
|
+
|
|
187
|
+
oldValue = newValue;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (newValueType === 'node') {
|
|
192
|
+
diffChildNodes([newValue], comment);
|
|
193
|
+
|
|
194
|
+
oldValue = newValue;
|
|
207
195
|
}
|
|
208
196
|
};
|
|
209
197
|
|
|
198
|
+
if (!serverSideRendered) {
|
|
199
|
+
// initial rendering
|
|
200
|
+
processNodeValue(initialValue);
|
|
201
|
+
}
|
|
202
|
+
|
|
210
203
|
return processNodeValue;
|
|
211
204
|
};
|
|
212
205
|
|
|
@@ -214,69 +207,35 @@ export const processNodePart = (comment, initialValue) => {
|
|
|
214
207
|
* For updating a text node, a sequence of child nodes or an array of `any` values
|
|
215
208
|
*/
|
|
216
209
|
export class ChildNodePart extends Part {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
parts = [];
|
|
210
|
+
/** @type {PartMarkers|undefined} */
|
|
211
|
+
markers;
|
|
220
212
|
|
|
221
|
-
|
|
222
|
-
templatePart
|
|
213
|
+
/** @type {TemplatePart|undefined} */
|
|
214
|
+
templatePart;
|
|
223
215
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
/** @type {Node[]} */
|
|
227
|
-
childNodes = [];
|
|
216
|
+
/** @type {Part[]} */
|
|
217
|
+
arrayParts = [];
|
|
228
218
|
|
|
229
219
|
/**
|
|
230
220
|
* @param {Node} startNode - the start comment marker node
|
|
231
221
|
* @param {any | any[]} value
|
|
232
222
|
*/
|
|
233
223
|
constructor(startNode, value) {
|
|
234
|
-
if (startNode
|
|
235
|
-
throw new Error(
|
|
224
|
+
if (!startNode || startNode?.nodeType !== COMMENT_NODE) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
startNode ? 'ChildNodePart: startNode is undefined' : 'ChildNodePart: startNode is not a comment node',
|
|
227
|
+
);
|
|
236
228
|
}
|
|
237
229
|
|
|
238
230
|
super();
|
|
239
231
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (startNode) {
|
|
243
|
-
startNode.__part = this; // add Part to comment node for debugging in the browser
|
|
244
|
-
|
|
245
|
-
const placeholder = startNode.data;
|
|
246
|
-
const childNodes = [startNode];
|
|
247
|
-
let childNode = startNode.nextSibling;
|
|
248
|
-
while (childNode && childNode.data !== `/${placeholder}`) {
|
|
249
|
-
childNodes.push(childNode);
|
|
250
|
-
childNode = childNode.nextSibling;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
endNode = childNode;
|
|
254
|
-
childNodes.push(endNode);
|
|
255
|
-
|
|
256
|
-
// if not SSRed, childNodes will only ever have two comment nodes, the start and the end marker
|
|
257
|
-
if (childNodes.length > 2) {
|
|
258
|
-
serverSideRendered = true;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this.childNodes = childNodes;
|
|
262
|
-
}
|
|
232
|
+
startNode.__part = this; // add Part to comment node for debugging in the browser
|
|
233
|
+
this.markers = PartMarkers.createFromStartNode(startNode);
|
|
263
234
|
|
|
264
235
|
// value can become array | TemplatePart | any
|
|
265
236
|
const initialValue = this.parseValue(value);
|
|
266
237
|
|
|
267
|
-
|
|
268
|
-
this.processor = processNodePart(endNode, serverSideRendered ? initialValue : undefined);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (!serverSideRendered) {
|
|
272
|
-
if (value instanceof TemplateResult || Array.isArray(value)) {
|
|
273
|
-
this.updateParts(value);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!(value instanceof TemplateResult)) {
|
|
277
|
-
this.processor?.(initialValue);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
238
|
+
this.processor = processNodePart(this.markers?.endNode, initialValue, this.markers?.serverSideRendered);
|
|
280
239
|
}
|
|
281
240
|
|
|
282
241
|
/**
|
|
@@ -290,9 +249,7 @@ export class ChildNodePart extends Part {
|
|
|
290
249
|
this.updateParts(value);
|
|
291
250
|
}
|
|
292
251
|
|
|
293
|
-
|
|
294
|
-
this.processor?.(parsedValue);
|
|
295
|
-
}
|
|
252
|
+
this.processor?.(parsedValue);
|
|
296
253
|
}
|
|
297
254
|
|
|
298
255
|
/**
|
|
@@ -304,38 +261,45 @@ export class ChildNodePart extends Part {
|
|
|
304
261
|
}
|
|
305
262
|
if (Array.isArray(value)) {
|
|
306
263
|
for (let index = 0; index < value.length; index++) {
|
|
307
|
-
this.
|
|
264
|
+
this.arrayParts[index]?.update(value[index]);
|
|
308
265
|
}
|
|
309
266
|
}
|
|
310
267
|
}
|
|
311
268
|
|
|
312
269
|
/**
|
|
313
270
|
* @param {TemplateResult | any[] | any} value
|
|
314
|
-
* @return {TemplatePart |
|
|
271
|
+
* @return {TemplatePart | Node | string | Array<TemplatePart | Node | string>}
|
|
315
272
|
*/
|
|
316
273
|
parseValue(value) {
|
|
317
274
|
if (value instanceof TemplateResult) {
|
|
318
275
|
if (!this.templatePart) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
.find((node) => node.data === 'template-part');
|
|
322
|
-
this.templatePart = new TemplatePart(startNode, value);
|
|
323
|
-
const serverSideRendered = startNode !== undefined;
|
|
324
|
-
if (!serverSideRendered) {
|
|
325
|
-
// TODO: we should already have the "endNode" from the constructor...
|
|
326
|
-
const endNode = Array.from(this.childNodes)
|
|
327
|
-
.filter((node) => node.nodeType === COMMENT_NODE)
|
|
328
|
-
.find((node) => node.data.includes('/dom-part-'));
|
|
276
|
+
this.templatePart = new TemplatePart(this.markers?.findNestedStartNode('template-part'), value);
|
|
277
|
+
if (!this.markers?.serverSideRendered) {
|
|
329
278
|
// INFO: this is similar to TemplateResult#237
|
|
330
|
-
|
|
279
|
+
replaceNodesBetweenComments(this.markers?.endNode, this.templatePart.childNodes);
|
|
331
280
|
}
|
|
281
|
+
} else if (this.templatePart.strings !== value.strings) {
|
|
282
|
+
// this means we have a different TemplateResult here with maybe different parts also
|
|
283
|
+
this.templatePart = new TemplatePart(undefined, value);
|
|
284
|
+
// INFO: this is similar to TemplateResult#237
|
|
285
|
+
replaceNodesBetweenComments(this.markers?.endNode, this.templatePart.childNodes);
|
|
332
286
|
}
|
|
333
287
|
return this.templatePart;
|
|
334
288
|
}
|
|
335
289
|
if (Array.isArray(value)) {
|
|
336
290
|
return this.parseArray(value);
|
|
337
291
|
}
|
|
338
|
-
|
|
292
|
+
if (value?.nodeType) {
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
295
|
+
if (typeof value === 'function') {
|
|
296
|
+
return this.parseValue(value());
|
|
297
|
+
}
|
|
298
|
+
if (value === null || value === undefined) {
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
// Everything else will be rendered as TextNode
|
|
302
|
+
return value.toString();
|
|
339
303
|
}
|
|
340
304
|
|
|
341
305
|
/**
|
|
@@ -348,28 +312,30 @@ export class ChildNodePart extends Part {
|
|
|
348
312
|
for (let index = 0; index < values.length; index++) {
|
|
349
313
|
let value = values[index];
|
|
350
314
|
|
|
315
|
+
// TODO: it would be better to use this.parseValue() here!!!
|
|
316
|
+
|
|
351
317
|
if (value instanceof TemplateResult) {
|
|
352
318
|
// TODO this is probably wrong. Should only be taken from parts cache if it has the same type (eg. template vs string, mixed types arrays)
|
|
353
|
-
let templatePart = this.
|
|
319
|
+
let templatePart = this.arrayParts[index];
|
|
354
320
|
if (!templatePart) {
|
|
355
|
-
const templatePartCommentNodes = this.childNodes?.filter(
|
|
321
|
+
const templatePartCommentNodes = this.markers?.childNodes?.filter(
|
|
356
322
|
(node) => node && node.nodeType === COMMENT_NODE && node.data === 'template-part',
|
|
357
323
|
);
|
|
358
324
|
const startNode = templatePartCommentNodes[index];
|
|
359
325
|
templatePart = new TemplatePart(startNode, value);
|
|
360
|
-
this.
|
|
326
|
+
this.arrayParts[index] = templatePart;
|
|
361
327
|
}
|
|
362
328
|
parsedValues[index] = templatePart;
|
|
363
329
|
} else if (Array.isArray(value)) {
|
|
364
|
-
let childNodePart = this.
|
|
330
|
+
let childNodePart = this.arrayParts[index];
|
|
365
331
|
if (!childNodePart) {
|
|
366
332
|
// TODO: this seems not correct :( (maybe Template vs DOM Parts !?)
|
|
367
|
-
const templatePartCommentNodes = this.childNodes?.filter(
|
|
333
|
+
const templatePartCommentNodes = this.markers?.childNodes?.filter(
|
|
368
334
|
(node) => node && node.nodeType === COMMENT_NODE && node.data === 'template-part',
|
|
369
335
|
);
|
|
370
336
|
const startNode = templatePartCommentNodes[index];
|
|
371
337
|
childNodePart = new ChildNodePart(startNode, value);
|
|
372
|
-
this.
|
|
338
|
+
this.arrayParts[index] = childNodePart;
|
|
373
339
|
}
|
|
374
340
|
parsedValues[index] = childNodePart;
|
|
375
341
|
} else {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { COMMENT_NODE } from '../util/DOMHelper.js';
|
|
2
|
+
|
|
3
|
+
export class PartMarkers {
|
|
4
|
+
/** @type {Comment} */
|
|
5
|
+
startNode;
|
|
6
|
+
|
|
7
|
+
/** @type {Comment} */
|
|
8
|
+
endNode;
|
|
9
|
+
|
|
10
|
+
/** @type {Node[]} */
|
|
11
|
+
childNodes = [];
|
|
12
|
+
|
|
13
|
+
/** @type {boolean} */
|
|
14
|
+
serverSideRendered = false;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Comment} start
|
|
18
|
+
* @param {Comment} end
|
|
19
|
+
* @param {Node[]} childNodes
|
|
20
|
+
* @return {void}
|
|
21
|
+
*/
|
|
22
|
+
constructor(start, end, childNodes) {
|
|
23
|
+
this.startNode = start;
|
|
24
|
+
this.endNode = end;
|
|
25
|
+
this.childNodes = childNodes;
|
|
26
|
+
|
|
27
|
+
// if not SSRed, childNodes will only ever have two comment nodes, the start and the end marker
|
|
28
|
+
if (childNodes.length > 2) {
|
|
29
|
+
this.serverSideRendered = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} partType The type of the node to search for. Defaults to 'template-part'.
|
|
35
|
+
* @return {Node|undefined} The first comment node matching the specified type, or undefined if no match is found.
|
|
36
|
+
*/
|
|
37
|
+
findNestedStartNode(partType = 'template-part') {
|
|
38
|
+
return this.childNodes.filter((node) => node.nodeType === COMMENT_NODE).find((node) => node.data === partType);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new instance of PartMarkers from the given start node.
|
|
43
|
+
* @param {Comment} startNode - The starting comment node that marks the beginning of the section.
|
|
44
|
+
* @return {PartMarkers} A new instance of the PartMarkers class encapsulating the start and end nodes.
|
|
45
|
+
*/
|
|
46
|
+
static createFromStartNode(startNode) {
|
|
47
|
+
let endNode;
|
|
48
|
+
|
|
49
|
+
const placeholder = startNode.data;
|
|
50
|
+
const childNodes = [startNode];
|
|
51
|
+
let childNode = startNode.nextSibling;
|
|
52
|
+
while (childNode && childNode.data !== `/${placeholder}`) {
|
|
53
|
+
childNodes.push(childNode);
|
|
54
|
+
childNode = childNode.nextSibling;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
endNode = childNode;
|
|
58
|
+
childNodes.push(endNode);
|
|
59
|
+
|
|
60
|
+
return new PartMarkers(startNode, endNode, childNodes);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -2,17 +2,21 @@ import { COMMENT_NODE, convertStringToTemplate } from '../util/DOMHelper.js';
|
|
|
2
2
|
import { Part } from './Part.js';
|
|
3
3
|
import { TemplateResult } from './TemplateResult.js';
|
|
4
4
|
import { AttributePart } from './AttributePart.js';
|
|
5
|
-
import { ChildNodePart,
|
|
5
|
+
import { ChildNodePart, getNodesBetweenComments, replaceNodesBetweenComments } from './ChildNodePart.js';
|
|
6
6
|
import { NodePart } from './NodePart.js';
|
|
7
7
|
import { RawTextNodePart } from './RawTextNodePart.js';
|
|
8
|
+
import { PartMarkers } from './PartMarkers.js';
|
|
8
9
|
|
|
9
|
-
/** @type {
|
|
10
|
+
/** @type {WeakMap<TemplateStringsArray, Part[]>} */
|
|
10
11
|
const partsCache = new WeakMap();
|
|
11
12
|
|
|
12
|
-
/** @type {
|
|
13
|
+
/** @type {WeakMap<TemplateStringsArray, DocumentFragment>} */
|
|
13
14
|
const fragmentsCache = new WeakMap();
|
|
14
15
|
|
|
15
16
|
export class TemplatePart extends Part {
|
|
17
|
+
/** @type {PartMarkers|undefined} */
|
|
18
|
+
markers;
|
|
19
|
+
|
|
16
20
|
/** @type {Part[]} */
|
|
17
21
|
parts = [];
|
|
18
22
|
|
|
@@ -21,9 +25,10 @@ export class TemplatePart extends Part {
|
|
|
21
25
|
|
|
22
26
|
/** @type {Node[]} */
|
|
23
27
|
childNodes = [];
|
|
28
|
+
// TODO: there is childNodes here and also in this.markers now...
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
|
-
* @param {Node} startNode - the start comment marker node
|
|
31
|
+
* @param {Node|undefined} startNode - the start comment marker node
|
|
27
32
|
* @param {TemplateResult} value
|
|
28
33
|
*/
|
|
29
34
|
constructor(startNode, value) {
|
|
@@ -33,53 +38,37 @@ export class TemplatePart extends Part {
|
|
|
33
38
|
|
|
34
39
|
super();
|
|
35
40
|
|
|
36
|
-
let endNode = undefined;
|
|
37
|
-
let serverSideRendered = false;
|
|
38
41
|
if (startNode) {
|
|
39
42
|
startNode.__part = this; // add Part to comment node for debugging in the browser
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const childNodes = [startNode];
|
|
43
|
-
let childNode = startNode.nextSibling;
|
|
44
|
-
while (childNode && childNode.data !== `/${placeholder}`) {
|
|
45
|
-
childNodes.push(childNode);
|
|
46
|
-
childNode = childNode.nextSibling;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
endNode = childNode;
|
|
50
|
-
childNodes.push(endNode);
|
|
51
|
-
|
|
52
|
-
// if not SSRed, childNodes will only ever have two comment nodes, the start and the end marker
|
|
53
|
-
if (childNodes.length > 2) {
|
|
54
|
-
serverSideRendered = true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
this.childNodes = childNodes;
|
|
43
|
+
this.markers = PartMarkers.createFromStartNode(startNode);
|
|
44
|
+
this.childNodes = this.markers.childNodes;
|
|
58
45
|
}
|
|
59
46
|
|
|
60
47
|
this.parseValue(value);
|
|
61
48
|
|
|
62
|
-
if (!serverSideRendered) {
|
|
49
|
+
if (!this.markers?.serverSideRendered) {
|
|
63
50
|
// TODO: this is causing double diffing because of the this.parseValue() before, ChildNode parts will be constructed + processed
|
|
51
|
+
// TODO: I think that maybe ChildNode Parts work, but Other Parts like Attributes are not set initially otherwise...
|
|
64
52
|
this.updateParts(value.values);
|
|
65
53
|
}
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (endNode) {
|
|
70
|
-
this.processor = processNodePart(endNode, this);
|
|
71
|
-
}
|
|
55
|
+
// TODO: this is somewhat strange that we are creating this twice...
|
|
56
|
+
this.markers = PartMarkers.createFromStartNode(this.childNodes[0]);
|
|
72
57
|
}
|
|
73
58
|
|
|
74
59
|
/**
|
|
75
|
-
* @param {TemplateResult}
|
|
60
|
+
* @param {TemplateResult} templateResult
|
|
76
61
|
*/
|
|
77
|
-
update(
|
|
78
|
-
const shouldUpdateDOM = this.
|
|
62
|
+
update(templateResult) {
|
|
63
|
+
const shouldUpdateDOM = this.strings !== templateResult.strings;
|
|
79
64
|
if (shouldUpdateDOM) {
|
|
80
|
-
this.
|
|
65
|
+
this.parseValue(templateResult);
|
|
66
|
+
// TODO: this is a strange way of replacing the childnodes...
|
|
67
|
+
const endNode = this.childNodes[this.childNodes.length - 1];
|
|
68
|
+
// INFO: this is similar to TemplateResult#237
|
|
69
|
+
replaceNodesBetweenComments(this.markers?.endNode, getNodesBetweenComments(endNode));
|
|
81
70
|
}
|
|
82
|
-
this.updateParts(
|
|
71
|
+
this.updateParts(templateResult.values);
|
|
83
72
|
}
|
|
84
73
|
|
|
85
74
|
/**
|
|
@@ -93,58 +82,44 @@ export class TemplatePart extends Part {
|
|
|
93
82
|
|
|
94
83
|
/**
|
|
95
84
|
* @param {TemplateResult} templateResult
|
|
96
|
-
* @return {boolean} whether the part needs to be updated in the DOM
|
|
97
85
|
*/
|
|
98
86
|
parseValue(templateResult) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
fragmentsCache.set(templateResult.strings, fragment);
|
|
104
|
-
}
|
|
105
|
-
const importedFragment = globalThis.document?.importNode(fragment, true);
|
|
106
|
-
this.childNodes = importedFragment.childNodes;
|
|
107
|
-
|
|
108
|
-
let parts = partsCache.get(templateResult.strings);
|
|
109
|
-
if (!parts) {
|
|
110
|
-
parts = templateResult.parseParts(this.childNodes);
|
|
111
|
-
partsCache.set(templateResult.strings, parts);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this.parts = parts
|
|
115
|
-
.map((part) => {
|
|
116
|
-
// We currently need the path because the fragment will be cloned via importNode and therefore the node will be a different one
|
|
117
|
-
part.node = part.path.reduceRight(({ childNodes }, i) => childNodes[i], this);
|
|
118
|
-
return part;
|
|
119
|
-
})
|
|
120
|
-
.map((part, index) => {
|
|
121
|
-
if (part.type === 'node') {
|
|
122
|
-
return new ChildNodePart(part.node, templateResult.values[index]);
|
|
123
|
-
}
|
|
124
|
-
if (part.type === 'attribute') {
|
|
125
|
-
return new AttributePart(part.node, part.name, part.initialValue);
|
|
126
|
-
}
|
|
127
|
-
if (part.type === 'raw-text-node') {
|
|
128
|
-
return new RawTextNodePart(part.node, part.initialValue);
|
|
129
|
-
}
|
|
130
|
-
if (part.type === 'directive') {
|
|
131
|
-
return new NodePart(part.node, templateResult.values[index]);
|
|
132
|
-
}
|
|
133
|
-
throw `cannot map part: ${part}`;
|
|
134
|
-
});
|
|
135
|
-
this.strings = templateResult.strings;
|
|
136
|
-
this.childNodes = [...this.childNodes];
|
|
137
|
-
return true;
|
|
87
|
+
let fragment = fragmentsCache.get(templateResult.strings);
|
|
88
|
+
if (!fragment) {
|
|
89
|
+
fragment = convertStringToTemplate(templateResult.templateString);
|
|
90
|
+
fragmentsCache.set(templateResult.strings, fragment);
|
|
138
91
|
}
|
|
139
|
-
|
|
140
|
-
|
|
92
|
+
const importedFragment = globalThis.document?.importNode(fragment, true);
|
|
93
|
+
this.childNodes = importedFragment.childNodes;
|
|
141
94
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
95
|
+
let parts = partsCache.get(templateResult.strings);
|
|
96
|
+
if (!parts) {
|
|
97
|
+
parts = templateResult.parseParts(this.childNodes);
|
|
98
|
+
partsCache.set(templateResult.strings, parts);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.parts = parts
|
|
102
|
+
.map((part) => {
|
|
103
|
+
// We currently need the path because the fragment will be cloned via importNode and therefore the node will be a different one
|
|
104
|
+
part.node = part.path.reduceRight(({ childNodes }, i) => childNodes[i], this);
|
|
105
|
+
return part;
|
|
106
|
+
})
|
|
107
|
+
.map((part, index) => {
|
|
108
|
+
if (part.type === 'node') {
|
|
109
|
+
return new ChildNodePart(part.node, templateResult.values[index]);
|
|
110
|
+
}
|
|
111
|
+
if (part.type === 'attribute') {
|
|
112
|
+
return new AttributePart(part.node, part.name, part.initialValue);
|
|
113
|
+
}
|
|
114
|
+
if (part.type === 'raw-text-node') {
|
|
115
|
+
return new RawTextNodePart(part.node, part.initialValue);
|
|
116
|
+
}
|
|
117
|
+
if (part.type === 'directive') {
|
|
118
|
+
return new NodePart(part.node, templateResult.values[index]);
|
|
119
|
+
}
|
|
120
|
+
throw `cannot map part: ${part}`;
|
|
121
|
+
});
|
|
122
|
+
this.strings = templateResult.strings;
|
|
123
|
+
this.childNodes = [...this.childNodes];
|
|
149
124
|
}
|
|
150
125
|
}
|
|
@@ -4,26 +4,83 @@ import { Store } from './Store.js';
|
|
|
4
4
|
* @property {CSSStyleSheet[]} cssStyleSheets
|
|
5
5
|
*/
|
|
6
6
|
class GlobalStylesStore extends Store {
|
|
7
|
-
/** @type {
|
|
7
|
+
/** @type {WeakMap<Node, CSSStyleSheet>} */
|
|
8
8
|
globalStyleSheetsCache = new WeakMap();
|
|
9
9
|
|
|
10
10
|
constructor() {
|
|
11
11
|
super();
|
|
12
|
+
this.createGlobalStyleSheetsCache();
|
|
12
13
|
if (globalThis.elementJsConfig?.observeGlobalStyles) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
this.initMutationObserver();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initMutationObserver() {
|
|
19
|
+
const mutationObserver = new MutationObserver(async (mutationRecord) => {
|
|
20
|
+
if (!mutationRecord[0]) return;
|
|
21
|
+
|
|
22
|
+
const filteredNodes = Array.from(mutationRecord[0].addedNodes).filter(
|
|
23
|
+
(node) => node.tagName === 'STYLE' || node.tagName === 'LINK',
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
filteredNodes.forEach((styleNode) => {
|
|
27
|
+
if (styleNode.tagName === 'LINK') {
|
|
28
|
+
if (styleNode.sheet) {
|
|
29
|
+
// already loaded
|
|
30
|
+
this.processStylesheet(styleNode.sheet);
|
|
31
|
+
} else {
|
|
32
|
+
// wait for download
|
|
33
|
+
styleNode.onload = () => {
|
|
34
|
+
this.processStylesheet(styleNode.sheet);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
} else if (styleNode.tagName === 'STYLE') {
|
|
38
|
+
this.processStylesheet(styleNode.sheet);
|
|
24
39
|
}
|
|
25
40
|
});
|
|
26
|
-
|
|
41
|
+
});
|
|
42
|
+
mutationObserver.observe(globalThis.document, { subtree: true, childList: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
createGlobalStyleSheetsCache() {
|
|
46
|
+
Array.from(globalThis.document?.styleSheets).map((styleSheet) => this.processStylesheet(styleSheet));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
processStylesheet(styleSheet) {
|
|
50
|
+
let cssStyleSheet = this.globalStyleSheetsCache.get(styleSheet.ownerNode);
|
|
51
|
+
|
|
52
|
+
if (cssStyleSheet) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// if it does not exist yet -> build it
|
|
57
|
+
if (styleSheet.ownerNode.tagName === 'STYLE') {
|
|
58
|
+
const cssStyleSheet = new CSSStyleSheet({ media: styleSheet.media, disabled: styleSheet.disabled });
|
|
59
|
+
cssStyleSheet.replaceSync(styleSheet.ownerNode.textContent);
|
|
60
|
+
|
|
61
|
+
this.globalStyleSheetsCache.set(styleSheet.ownerNode, cssStyleSheet);
|
|
62
|
+
this.requestUpdate();
|
|
63
|
+
} else if (styleSheet.ownerNode.tagName === 'LINK') {
|
|
64
|
+
const cssStyleSheet = new CSSStyleSheet({
|
|
65
|
+
baseURL: styleSheet.href,
|
|
66
|
+
media: styleSheet.media,
|
|
67
|
+
disabled: styleSheet.disabled,
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
Array.from(styleSheet?.cssRules ?? []).map((rule, index) =>
|
|
71
|
+
cssStyleSheet.insertRule(rule.cssText, index),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
this.globalStyleSheetsCache.set(styleSheet.ownerNode, cssStyleSheet);
|
|
75
|
+
|
|
76
|
+
this.requestUpdate();
|
|
77
|
+
// update observer that there might be new styles to adopt
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error(
|
|
80
|
+
'GlobalStylesStore: cannot read cssRules. Maybe add crossorigin="anonymous" to your style link?',
|
|
81
|
+
e,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
27
84
|
}
|
|
28
85
|
}
|
|
29
86
|
|
|
@@ -46,31 +103,10 @@ class GlobalStylesStore extends Store {
|
|
|
46
103
|
return;
|
|
47
104
|
}
|
|
48
105
|
|
|
49
|
-
// TODO: this will always be null as we never set anything to the cache...
|
|
50
106
|
let cssStyleSheet = this.globalStyleSheetsCache.get(styleSheet.ownerNode);
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
cssStyleSheet = new CSSStyleSheet({ media: styleSheet.media, disabled: styleSheet.disabled });
|
|
54
|
-
cssStyleSheet.replaceSync(styleSheet.ownerNode.textContent);
|
|
55
|
-
} else if (styleSheet.ownerNode.tagName === 'LINK') {
|
|
56
|
-
cssStyleSheet = new CSSStyleSheet({
|
|
57
|
-
baseURL: styleSheet.href,
|
|
58
|
-
media: styleSheet.media,
|
|
59
|
-
disabled: styleSheet.disabled,
|
|
60
|
-
});
|
|
61
|
-
try {
|
|
62
|
-
Array.from(styleSheet?.cssRules ?? []).map((rule, index) =>
|
|
63
|
-
cssStyleSheet.insertRule(rule.cssText, index),
|
|
64
|
-
);
|
|
65
|
-
} catch (e) {
|
|
66
|
-
console.error(
|
|
67
|
-
'GlobalStylesStore: cannot read cssRules. Maybe add crossorigin="anonymous" to your style link?',
|
|
68
|
-
e,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
107
|
+
if (cssStyleSheet) {
|
|
108
|
+
cssStyleSheets.push(cssStyleSheet);
|
|
72
109
|
}
|
|
73
|
-
cssStyleSheets.push(cssStyleSheet);
|
|
74
110
|
});
|
|
75
111
|
|
|
76
112
|
return cssStyleSheets;
|