@webtides/element-js 1.1.8 → 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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtides/element-js",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "module": "index.js",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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
  }
@@ -4,26 +4,83 @@ import { Store } from './Store.js';
4
4
  * @property {CSSStyleSheet[]} cssStyleSheets
5
5
  */
6
6
  class GlobalStylesStore extends Store {
7
- /** @type {Map<Node, CSSStyleSheet>} */
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
- const mutationObserver = new MutationObserver((mutationRecord) => {
14
- if (!mutationRecord[0]) return;
15
- const filteredNodes = Array.from(mutationRecord[0].addedNodes).filter(
16
- (node) => node.tagName === 'STYLE' || node.tagName === 'LINK',
17
- );
18
- if (filteredNodes && filteredNodes[0] && filteredNodes[0].tagName === 'LINK') {
19
- filteredNodes[0].onload = () => {
20
- this.requestUpdate();
21
- };
22
- } else if (filteredNodes && filteredNodes[0] && filteredNodes[0].tagName === 'STYLE') {
23
- this.requestUpdate();
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
- mutationObserver.observe(globalThis.document, { subtree: true, childList: true });
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 (!cssStyleSheet) {
52
- if (styleSheet.ownerNode.tagName === 'STYLE') {
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;