@vaadin/vaadin-themable-mixin 25.0.0-alpha6 → 25.0.0-alpha8

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.
@@ -61,6 +61,12 @@ export const LumoInjectionMixin = (superClass) =>
61
61
  return `--${this.is}-lumo-inject`;
62
62
  }
63
63
 
64
+ static get lumoInjector() {
65
+ return {
66
+ includeBaseStyles: false,
67
+ };
68
+ }
69
+
64
70
  /** @protected */
65
71
  connectedCallback() {
66
72
  super.connectedCallback();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/vaadin-themable-mixin",
3
- "version": "25.0.0-alpha6",
3
+ "version": "25.0.0-alpha8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -37,10 +37,10 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@polymer/polymer": "^3.0.0",
40
- "@vaadin/chai-plugins": "25.0.0-alpha6",
41
- "@vaadin/test-runner-commands": "25.0.0-alpha6",
40
+ "@vaadin/chai-plugins": "25.0.0-alpha8",
41
+ "@vaadin/test-runner-commands": "25.0.0-alpha8",
42
42
  "@vaadin/testing-helpers": "^2.0.0",
43
43
  "sinon": "^18.0.0"
44
44
  },
45
- "gitHead": "cd1d084198d2b326c58d44bb39fa4845b71ce551"
45
+ "gitHead": "ebf53673d5f639d2b1b6f2b31f640f530643ee2f"
46
46
  }
@@ -14,14 +14,36 @@ export class CSSPropertyObserver {
14
14
  #name;
15
15
  #callback;
16
16
  #properties = new Set();
17
+ #styleSheet;
18
+ #isConnected = false;
17
19
 
18
20
  constructor(root, name, callback) {
19
21
  this.#root = root;
20
22
  this.#name = name;
21
23
  this.#callback = callback;
24
+ }
25
+
26
+ #handleTransitionEvent(event) {
27
+ const { propertyName } = event;
28
+ if (this.#properties.has(propertyName)) {
29
+ this.#callback(propertyName);
30
+ }
31
+ }
32
+
33
+ observe(property) {
34
+ this.connect();
35
+
36
+ this.#properties.add(property);
37
+ this.#rootHost.style.setProperty(`--${this.#name}-props`, [...this.#properties].join(', '));
38
+ }
22
39
 
23
- const styleSheet = new CSSStyleSheet();
24
- styleSheet.replaceSync(`
40
+ connect() {
41
+ if (this.#isConnected) {
42
+ return;
43
+ }
44
+
45
+ this.#styleSheet = new CSSStyleSheet();
46
+ this.#styleSheet.replaceSync(`
25
47
  :is(:root, :host)::before {
26
48
  content: '' !important;
27
49
  position: absolute !important;
@@ -32,22 +54,24 @@ export class CSSPropertyObserver {
32
54
  transition-property: var(--${this.#name}-props) !important;
33
55
  }
34
56
  `);
35
- this.#root.adoptedStyleSheets.unshift(styleSheet);
57
+ this.#root.adoptedStyleSheets.unshift(this.#styleSheet);
36
58
 
37
59
  this.#rootHost.addEventListener('transitionstart', (event) => this.#handleTransitionEvent(event));
38
60
  this.#rootHost.addEventListener('transitionend', (event) => this.#handleTransitionEvent(event));
39
- }
40
61
 
41
- #handleTransitionEvent(event) {
42
- const { propertyName } = event;
43
- if (this.#properties.has(propertyName)) {
44
- this.#callback(propertyName);
45
- }
62
+ this.#isConnected = true;
46
63
  }
47
64
 
48
- observe(property) {
49
- this.#properties.add(property);
50
- this.#rootHost.style.setProperty(`--${this.#name}-props`, [...this.#properties].join(', '));
65
+ disconnect() {
66
+ this.#properties.clear();
67
+
68
+ this.#root.adoptedStyleSheets = this.#root.adoptedStyleSheets.filter((s) => s !== this.#styleSheet);
69
+
70
+ this.#rootHost.removeEventListener('transitionstart', this.#handleTransitionEvent);
71
+ this.#rootHost.removeEventListener('transitionend', this.#handleTransitionEvent);
72
+ this.#rootHost.style.removeProperty(`--${this.#name}-props`);
73
+
74
+ this.#isConnected = false;
51
75
  }
52
76
 
53
77
  get #rootHost() {
package/src/css-utils.js CHANGED
@@ -15,16 +15,14 @@ import { adoptStyles } from 'lit';
15
15
  * @return {CSSStyleSheet[]}
16
16
  */
17
17
  function getEffectiveStyles(component) {
18
- const componentClass = component.constructor;
18
+ const { baseStyles, themeStyles, elementStyles, lumoInjector } = component.constructor;
19
+ const lumoStyleSheet = component.__lumoStyleSheet;
19
20
 
20
- const styleSheet = component.__lumoInjectorStyleSheet;
21
- if (styleSheet) {
22
- return (componentClass.baseStyles ?? componentClass.themeStyles)
23
- ? [...componentClass.baseStyles, styleSheet, ...componentClass.themeStyles]
24
- : [styleSheet, ...componentClass.elementStyles];
21
+ if (lumoStyleSheet && (baseStyles || themeStyles)) {
22
+ return [...(lumoInjector.includeBaseStyles ? baseStyles : []), lumoStyleSheet, ...themeStyles];
25
23
  }
26
24
 
27
- return componentClass.elementStyles;
25
+ return [lumoStyleSheet, ...elementStyles].filter(Boolean);
28
26
  }
29
27
 
30
28
  /**
@@ -47,7 +45,7 @@ export function applyInstanceStyles(component) {
47
45
  */
48
46
  export function injectLumoStyleSheet(component, styleSheet) {
49
47
  // Store the new stylesheet so that it can be removed later.
50
- component.__lumoInjectorStyleSheet = styleSheet;
48
+ component.__lumoStyleSheet = styleSheet;
51
49
  applyInstanceStyles(component);
52
50
  }
53
51
 
@@ -57,11 +55,7 @@ export function injectLumoStyleSheet(component, styleSheet) {
57
55
  *
58
56
  * @param {HTMLElement} component
59
57
  */
60
- export function cleanupLumoStyleSheet(component) {
61
- const adoptedStyleSheets = component.shadowRoot.adoptedStyleSheets.filter(
62
- (s) => s !== component.__lumoInjectorStyleSheet,
63
- );
64
-
65
- component.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
66
- component.__lumoInjectorStyleSheet = undefined;
58
+ export function removeLumoStyleSheet(component) {
59
+ component.__lumoStyleSheet = undefined;
60
+ applyInstanceStyles(component);
67
61
  }
@@ -4,7 +4,7 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { CSSPropertyObserver } from './css-property-observer.js';
7
- import { cleanupLumoStyleSheet, injectLumoStyleSheet } from './css-utils.js';
7
+ import { injectLumoStyleSheet, removeLumoStyleSheet } from './css-utils.js';
8
8
  import { parseStyleSheets } from './lumo-modules.js';
9
9
 
10
10
  /**
@@ -77,14 +77,23 @@ export class LumoInjector {
77
77
  /** @type {Map<string, CSSStyleSheet>} */
78
78
  #styleSheetsByTag = new Map();
79
79
 
80
+ /** @type {Map<string, Set<HTMLElement>>} */
81
+ #componentsByTag = new Map();
82
+
80
83
  constructor(root = document) {
81
84
  this.#root = root;
82
85
  this.#cssPropertyObserver = new CSSPropertyObserver(this.#root, 'vaadin-lumo-injector', (propertyName) => {
83
86
  const tagName = propertyName.slice(2).replace('-lumo-inject', '');
84
- this.#updateComponentStyleSheet(tagName);
87
+ this.#updateStyleSheet(tagName);
85
88
  });
86
89
  }
87
90
 
91
+ disconnect() {
92
+ this.#cssPropertyObserver.disconnect();
93
+ this.#styleSheetsByTag.clear();
94
+ this.#componentsByTag.values().forEach((components) => components.forEach(removeLumoStyleSheet));
95
+ }
96
+
88
97
  /**
89
98
  * Adds a component to the list of elements monitored for style injection.
90
99
  * If the styles have already been detected, they are injected into the
@@ -97,12 +106,18 @@ export class LumoInjector {
97
106
  componentConnected(component) {
98
107
  const { is: tagName, lumoInjectPropName } = component.constructor;
99
108
 
100
- const stylesheet = this.#styleSheetsByTag.get(tagName) ?? new CSSStyleSheet();
101
- injectLumoStyleSheet(component, stylesheet);
102
- this.#styleSheetsByTag.set(tagName, stylesheet);
109
+ this.#componentsByTag.set(tagName, this.#componentsByTag.get(tagName) ?? new Set());
110
+ this.#componentsByTag.get(tagName).add(component);
103
111
 
104
- this.#updateComponentStyleSheet(tagName);
112
+ const stylesheet = this.#styleSheetsByTag.get(tagName);
113
+ if (stylesheet) {
114
+ if (stylesheet.cssRules.length > 0) {
115
+ injectLumoStyleSheet(component, stylesheet);
116
+ }
117
+ return;
118
+ }
105
119
 
120
+ this.#initStyleSheet(tagName);
106
121
  this.#cssPropertyObserver.observe(lumoInjectPropName);
107
122
  }
108
123
 
@@ -113,10 +128,18 @@ export class LumoInjector {
113
128
  * @param {HTMLElement} component
114
129
  */
115
130
  componentDisconnected(component) {
116
- cleanupLumoStyleSheet(component);
131
+ const { is: tagName } = component.constructor;
132
+ this.#componentsByTag.get(tagName)?.delete(component);
133
+
134
+ removeLumoStyleSheet(component);
117
135
  }
118
136
 
119
- #updateComponentStyleSheet(tagName) {
137
+ #initStyleSheet(tagName) {
138
+ this.#styleSheetsByTag.set(tagName, new CSSStyleSheet());
139
+ this.#updateStyleSheet(tagName);
140
+ }
141
+
142
+ #updateStyleSheet(tagName) {
120
143
  const { tags, modules } = parseStyleSheets(this.#rootStyleSheets);
121
144
 
122
145
  const cssText = (tags.get(tagName) ?? [])
@@ -124,9 +147,16 @@ export class LumoInjector {
124
147
  .map((rule) => rule.cssText)
125
148
  .join('\n');
126
149
 
127
- const stylesheet = this.#styleSheetsByTag.get(tagName) ?? new CSSStyleSheet();
150
+ const stylesheet = this.#styleSheetsByTag.get(tagName);
128
151
  stylesheet.replaceSync(cssText);
129
- this.#styleSheetsByTag.set(tagName, stylesheet);
152
+
153
+ this.#componentsByTag.get(tagName)?.forEach((component) => {
154
+ if (cssText) {
155
+ injectLumoStyleSheet(component, stylesheet);
156
+ } else {
157
+ removeLumoStyleSheet(component);
158
+ }
159
+ });
130
160
  }
131
161
 
132
162
  get #rootStyleSheets() {