@webtides/element-js 1.1.8 → 1.2.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/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
|
}
|
|
@@ -295,6 +295,9 @@ export class ChildNodePart extends Part {
|
|
|
295
295
|
if (typeof value === 'function') {
|
|
296
296
|
return this.parseValue(value());
|
|
297
297
|
}
|
|
298
|
+
if (value === null || value === undefined) {
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
298
301
|
// Everything else will be rendered as TextNode
|
|
299
302
|
return value.toString();
|
|
300
303
|
}
|
|
@@ -6,7 +6,7 @@ const voidElements = /^(?:area|base|br|col|embed|hr|img|input|link|meta|source|t
|
|
|
6
6
|
const rawTextElements = /<(script|style|textarea|title)([^>]*)>(.*?)<\/\1>/i;
|
|
7
7
|
const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
|
|
8
8
|
// TODO: v this will not match any values with escaped quotes like onClick='console.log("\'test")'
|
|
9
|
-
const attributes = /([^\s]*)=((?:")[^"]*(?:")|(?:')[^']*(?:')|[^\s\/>]*)|([^\s\/>]*)/g;
|
|
9
|
+
const attributes = /([^\s=]*)=((?:")[^"]*(?:")|(?:')[^']*(?:')|[^\s\/>]*)|([^\s\/>]*)/g;
|
|
10
10
|
const partPositions = /[\x01\x02]/g;
|
|
11
11
|
const ssrPlaceholder = /{{dom-part\?(.*?)}}/g;
|
|
12
12
|
// \x01 Node.ELEMENT_NODE
|
|
@@ -125,16 +125,20 @@ export function camelToDash(string) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Decodes an attribute according to {@link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2}
|
|
128
|
+
* Decodes an attribute according to {@link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2} & {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}
|
|
129
129
|
* @param {string} attribute
|
|
130
130
|
* @returns {string}
|
|
131
131
|
*/
|
|
132
132
|
export function decodeAttribute(attribute) {
|
|
133
|
-
return `${attribute}
|
|
133
|
+
return `${attribute}`
|
|
134
|
+
.replace(/</g, '<')
|
|
135
|
+
.replace(/>/g, '>')
|
|
136
|
+
.replace(/"/g, '"')
|
|
137
|
+
.replace(/&/g, '&');
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
/**
|
|
137
|
-
* Encodes an attribute according to {@link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2}
|
|
141
|
+
* Encodes an attribute according to {@link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2} & {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}
|
|
138
142
|
* @param {string} attribute
|
|
139
143
|
* @returns {string}
|
|
140
144
|
*/
|
|
@@ -142,6 +146,8 @@ export function encodeAttribute(attribute) {
|
|
|
142
146
|
return `${attribute}`
|
|
143
147
|
.replace(/&/g, '&')
|
|
144
148
|
.replace(/"/g, '"')
|
|
149
|
+
.replace(/</g, '<')
|
|
150
|
+
.replace(/>/g, '>')
|
|
145
151
|
.replace(/\r\n/g, '\n')
|
|
146
152
|
.replace(/[\r\n]/g, '\n');
|
|
147
153
|
}
|
|
@@ -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;
|