@vaadin/vaadin-themable-mixin 24.4.0-dev.b3e1d14600 → 24.5.0-alpha1
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/README.md
CHANGED
|
@@ -5,7 +5,6 @@ A mixin to enable customization of Shadow DOM used by Vaadin components.
|
|
|
5
5
|
[Documentation ↗](https://vaadin.com/docs/latest/styling/styling-components)
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@vaadin/vaadin-themable-mixin)
|
|
8
|
-
[](https://discord.gg/PHmkCKC)
|
|
9
8
|
|
|
10
9
|
## License
|
|
11
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/vaadin-themable-mixin",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.5.0-alpha1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"@vaadin/testing-helpers": "^0.6.0",
|
|
41
41
|
"sinon": "^13.0.2"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "57806caac5468532a3b4e3dbdda730cd0fca193a"
|
|
44
44
|
}
|
package/register-styles.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { CSSResultGroup } from 'lit';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @license
|
|
5
|
-
* Copyright (c) 2017 -
|
|
5
|
+
* Copyright (c) 2017 - 2024 Vaadin Ltd.
|
|
6
6
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
7
7
|
*/
|
|
8
8
|
export { registerStyles, css, unsafeCSS } from './vaadin-themable-mixin.js';
|
package/register-styles.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2017 -
|
|
3
|
+
* Copyright (c) 2017 - 2024 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
export { registerStyles, css, unsafeCSS } from './vaadin-themable-mixin.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2017 -
|
|
3
|
+
* Copyright (c) 2017 - 2024 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import type { Constructor } from '@open-wc/dedupe-mixin';
|
package/vaadin-themable-mixin.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2017 -
|
|
3
|
+
* Copyright (c) 2017 - 2024 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { css, CSSResult, unsafeCSS } from 'lit';
|
|
6
|
+
import { adoptStyles, css, CSSResult, LitElement, unsafeCSS } from 'lit';
|
|
7
7
|
import { ThemePropertyMixin } from './vaadin-theme-property-mixin.js';
|
|
8
8
|
|
|
9
9
|
export { css, unsafeCSS };
|
|
@@ -23,6 +23,16 @@ export { css, unsafeCSS };
|
|
|
23
23
|
*/
|
|
24
24
|
const themeRegistry = [];
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @type {WeakRef<HTMLElement>[]}
|
|
28
|
+
*/
|
|
29
|
+
const themableInstances = new Set();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @type {string[]}
|
|
33
|
+
*/
|
|
34
|
+
const themableTagNames = new Set();
|
|
35
|
+
|
|
26
36
|
/**
|
|
27
37
|
* Check if the custom element type has themes applied.
|
|
28
38
|
* @param {Function} elementClass
|
|
@@ -57,6 +67,129 @@ function flattenStyles(styles = []) {
|
|
|
57
67
|
});
|
|
58
68
|
}
|
|
59
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Returns true if the themeFor string matches the tag name
|
|
72
|
+
* @param {string} themeFor
|
|
73
|
+
* @param {string} tagName
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function matchesThemeFor(themeFor, tagName) {
|
|
77
|
+
return (themeFor || '').split(' ').some((themeForToken) => {
|
|
78
|
+
return new RegExp(`^${themeForToken.split('*').join('.*')}$`, 'u').test(tagName);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns the CSS text content from an array of CSSResults
|
|
84
|
+
* @param {CSSResult[]} styles
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
function getCssText(styles) {
|
|
88
|
+
return styles.map((style) => style.cssText).join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const STYLE_ID = 'vaadin-themable-mixin-style';
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Includes the styles to the template.
|
|
95
|
+
* @param {CSSResult[]} styles
|
|
96
|
+
* @param {HTMLTemplateElement} template
|
|
97
|
+
*/
|
|
98
|
+
function addStylesToTemplate(styles, template) {
|
|
99
|
+
const styleEl = document.createElement('style');
|
|
100
|
+
styleEl.id = STYLE_ID;
|
|
101
|
+
styleEl.textContent = getCssText(styles);
|
|
102
|
+
template.content.appendChild(styleEl);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Dynamically updates the styles of the given component instance.
|
|
107
|
+
* @param {HTMLElement} instance
|
|
108
|
+
*/
|
|
109
|
+
function updateInstanceStyles(instance) {
|
|
110
|
+
if (!instance.shadowRoot) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const componentClass = instance.constructor;
|
|
115
|
+
|
|
116
|
+
if (instance instanceof LitElement) {
|
|
117
|
+
// LitElement
|
|
118
|
+
|
|
119
|
+
// The adoptStyles function may fall back to appending style elements to shadow root.
|
|
120
|
+
// Remove them first to avoid duplicates.
|
|
121
|
+
[...instance.shadowRoot.querySelectorAll('style')].forEach((style) => style.remove());
|
|
122
|
+
|
|
123
|
+
// Adopt the updated styles
|
|
124
|
+
adoptStyles(instance.shadowRoot, componentClass.elementStyles);
|
|
125
|
+
} else {
|
|
126
|
+
// PolymerElement
|
|
127
|
+
|
|
128
|
+
// Update style element content in the shadow root
|
|
129
|
+
const style = instance.shadowRoot.getElementById(STYLE_ID);
|
|
130
|
+
const template = componentClass.prototype._template;
|
|
131
|
+
style.textContent = template.content.getElementById(STYLE_ID).textContent;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Dynamically updates the styles of the instances matching the given component type.
|
|
137
|
+
* @param {Function} componentClass
|
|
138
|
+
*/
|
|
139
|
+
function updateInstanceStylesOfType(componentClass) {
|
|
140
|
+
// Iterate over component instances and update their styles if needed
|
|
141
|
+
themableInstances.forEach((ref) => {
|
|
142
|
+
const instance = ref.deref();
|
|
143
|
+
if (instance instanceof componentClass) {
|
|
144
|
+
updateInstanceStyles(instance);
|
|
145
|
+
} else if (!instance) {
|
|
146
|
+
// Clean up the weak reference to a GC'd instance
|
|
147
|
+
themableInstances.delete(ref);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Dynamically updates the styles of the given component type.
|
|
154
|
+
* @param {Function} componentClass
|
|
155
|
+
*/
|
|
156
|
+
function updateComponentStyles(componentClass) {
|
|
157
|
+
if (componentClass.prototype instanceof LitElement) {
|
|
158
|
+
// Update LitElement-based component's elementStyles
|
|
159
|
+
componentClass.elementStyles = componentClass.finalizeStyles(componentClass.styles);
|
|
160
|
+
} else {
|
|
161
|
+
// Update Polymer-based component's template
|
|
162
|
+
const template = componentClass.prototype._template;
|
|
163
|
+
template.content.getElementById(STYLE_ID).textContent = getCssText(componentClass.getStylesForThis());
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Update the styles of inheriting types
|
|
167
|
+
themableTagNames.forEach((inheritingTagName) => {
|
|
168
|
+
const inheritingClass = customElements.get(inheritingTagName);
|
|
169
|
+
if (inheritingClass !== componentClass && inheritingClass.prototype instanceof componentClass) {
|
|
170
|
+
updateComponentStyles(inheritingClass);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if the component type already has a style matching the given styles.
|
|
177
|
+
*
|
|
178
|
+
* @param {Function} componentClass
|
|
179
|
+
* @param {CSSResultGroup} styles
|
|
180
|
+
* @returns {boolean}
|
|
181
|
+
*/
|
|
182
|
+
function hasMatchingStyle(componentClass, styles) {
|
|
183
|
+
const themes = componentClass.__themes;
|
|
184
|
+
if (!themes || !styles) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return themes.some((theme) =>
|
|
189
|
+
theme.styles.some((themeStyle) => styles.some((style) => style.cssText === themeStyle.cssText)),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
60
193
|
/**
|
|
61
194
|
* Registers CSS styles for a component type. Make sure to register the styles before
|
|
62
195
|
* the first instance of a component of the type is attached to DOM.
|
|
@@ -68,15 +201,6 @@ function flattenStyles(styles = []) {
|
|
|
68
201
|
* @return {void}
|
|
69
202
|
*/
|
|
70
203
|
export function registerStyles(themeFor, styles, options = {}) {
|
|
71
|
-
if (themeFor) {
|
|
72
|
-
if (hasThemes(themeFor)) {
|
|
73
|
-
console.warn(`The custom element definition for "${themeFor}"
|
|
74
|
-
was finalized before a style module was registered.
|
|
75
|
-
Make sure to add component specific style modules before
|
|
76
|
-
importing the corresponding custom element.`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
204
|
styles = flattenStyles(styles);
|
|
81
205
|
|
|
82
206
|
if (window.Vaadin && window.Vaadin.styleModules) {
|
|
@@ -89,6 +213,34 @@ export function registerStyles(themeFor, styles, options = {}) {
|
|
|
89
213
|
moduleId: options.moduleId,
|
|
90
214
|
});
|
|
91
215
|
}
|
|
216
|
+
|
|
217
|
+
if (themeFor) {
|
|
218
|
+
// Update styles of the component types that match themeFor and have already been finalized
|
|
219
|
+
themableTagNames.forEach((tagName) => {
|
|
220
|
+
if (matchesThemeFor(themeFor, tagName) && hasThemes(tagName)) {
|
|
221
|
+
const componentClass = customElements.get(tagName);
|
|
222
|
+
|
|
223
|
+
if (hasMatchingStyle(componentClass, styles)) {
|
|
224
|
+
// Show a warning if the component type already has some of the given styles
|
|
225
|
+
console.warn(`Registering styles that already exist for ${tagName}`);
|
|
226
|
+
} else if (!window.Vaadin || !window.Vaadin.suppressPostFinalizeStylesWarning) {
|
|
227
|
+
// Show a warning if the component type has already been finalized
|
|
228
|
+
console.warn(
|
|
229
|
+
`The custom element definition for "${tagName}" ` +
|
|
230
|
+
`was finalized before a style module was registered. ` +
|
|
231
|
+
`Ideally, import component specific style modules before ` +
|
|
232
|
+
`importing the corresponding custom element. ` +
|
|
233
|
+
`This warning can be suppressed by setting "window.Vaadin.suppressPostFinalizeStylesWarning = true".`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Update the styles of the component type
|
|
238
|
+
updateComponentStyles(componentClass);
|
|
239
|
+
// Update the styles of the component instances matching the component type
|
|
240
|
+
updateInstanceStylesOfType(componentClass);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
92
244
|
}
|
|
93
245
|
|
|
94
246
|
/**
|
|
@@ -103,18 +255,6 @@ function getAllThemes() {
|
|
|
103
255
|
return themeRegistry;
|
|
104
256
|
}
|
|
105
257
|
|
|
106
|
-
/**
|
|
107
|
-
* Returns true if the themeFor string matches the tag name
|
|
108
|
-
* @param {string} themeFor
|
|
109
|
-
* @param {string} tagName
|
|
110
|
-
* @returns {boolean}
|
|
111
|
-
*/
|
|
112
|
-
function matchesThemeFor(themeFor, tagName) {
|
|
113
|
-
return (themeFor || '').split(' ').some((themeForToken) => {
|
|
114
|
-
return new RegExp(`^${themeForToken.split('*').join('.*')}$`, 'u').test(tagName);
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
258
|
/**
|
|
119
259
|
* Maps the moduleName to an include priority number which is used for
|
|
120
260
|
* determining the order in which styles are applied.
|
|
@@ -151,17 +291,6 @@ function getIncludedStyles(theme) {
|
|
|
151
291
|
return includedStyles;
|
|
152
292
|
}
|
|
153
293
|
|
|
154
|
-
/**
|
|
155
|
-
* Includes the styles to the template.
|
|
156
|
-
* @param {CSSResult[]} styles
|
|
157
|
-
* @param {HTMLTemplateElement} template
|
|
158
|
-
*/
|
|
159
|
-
function addStylesToTemplate(styles, template) {
|
|
160
|
-
const styleEl = document.createElement('style');
|
|
161
|
-
styleEl.innerHTML = styles.map((style) => style.cssText).join('\n');
|
|
162
|
-
template.content.appendChild(styleEl);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
294
|
/**
|
|
166
295
|
* Returns an array of themes that should be used for styling a component matching
|
|
167
296
|
* the tag name. The array is sorted by the include order.
|
|
@@ -197,6 +326,12 @@ function getThemes(tagName) {
|
|
|
197
326
|
*/
|
|
198
327
|
export const ThemableMixin = (superClass) =>
|
|
199
328
|
class VaadinThemableMixin extends ThemePropertyMixin(superClass) {
|
|
329
|
+
constructor() {
|
|
330
|
+
super();
|
|
331
|
+
// Store a weak reference to the instance
|
|
332
|
+
themableInstances.add(new WeakRef(this));
|
|
333
|
+
}
|
|
334
|
+
|
|
200
335
|
/**
|
|
201
336
|
* Covers PolymerElement based component styling
|
|
202
337
|
* @protected
|
|
@@ -204,6 +339,10 @@ export const ThemableMixin = (superClass) =>
|
|
|
204
339
|
static finalize() {
|
|
205
340
|
super.finalize();
|
|
206
341
|
|
|
342
|
+
if (this.is) {
|
|
343
|
+
themableTagNames.add(this.is);
|
|
344
|
+
}
|
|
345
|
+
|
|
207
346
|
// Make sure not to run the logic intended for PolymerElement when LitElement is used.
|
|
208
347
|
if (this.elementStyles) {
|
|
209
348
|
return;
|
|
@@ -227,7 +366,7 @@ export const ThemableMixin = (superClass) =>
|
|
|
227
366
|
// a LitElement based component. The theme styles are added after it
|
|
228
367
|
// so that they can override the component styles.
|
|
229
368
|
const themeStyles = this.getStylesForThis();
|
|
230
|
-
return styles ? [...
|
|
369
|
+
return styles ? [...[styles].flat(Infinity), ...themeStyles] : themeStyles;
|
|
231
370
|
}
|
|
232
371
|
|
|
233
372
|
/**
|
|
@@ -236,9 +375,10 @@ export const ThemableMixin = (superClass) =>
|
|
|
236
375
|
* @private
|
|
237
376
|
*/
|
|
238
377
|
static getStylesForThis() {
|
|
378
|
+
const superClassThemes = superClass.__themes || [];
|
|
239
379
|
const parent = Object.getPrototypeOf(this.prototype);
|
|
240
380
|
const inheritedThemes = (parent ? parent.constructor.__themes : []) || [];
|
|
241
|
-
this.__themes = [...inheritedThemes, ...getThemes(this.is)];
|
|
381
|
+
this.__themes = [...superClassThemes, ...inheritedThemes, ...getThemes(this.is)];
|
|
242
382
|
const themeStyles = this.__themes.flatMap((theme) => theme.styles);
|
|
243
383
|
// Remove duplicates
|
|
244
384
|
return themeStyles.filter((style, index) => index === themeStyles.lastIndexOf(style));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2017 -
|
|
3
|
+
* Copyright (c) 2017 - 2024 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import type { Constructor } from '@open-wc/dedupe-mixin';
|