@vaadin/vaadin-themable-mixin 25.0.0-beta1 → 25.0.0-beta3
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/lumo-injection-mixin.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copyright (c) 2021 - 2025 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { LumoInjector } from './src/lumo-injector.js';
|
|
6
|
+
import { getLumoInjectorPropName, LumoInjector } from './src/lumo-injector.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @type {Set<string>}
|
|
@@ -16,7 +16,7 @@ const registeredProperties = new Set();
|
|
|
16
16
|
* @param {HTMLElement} element
|
|
17
17
|
* @return {DocumentOrShadowRoot}
|
|
18
18
|
*/
|
|
19
|
-
function findRoot(element) {
|
|
19
|
+
export function findRoot(element) {
|
|
20
20
|
const root = element.getRootNode();
|
|
21
21
|
|
|
22
22
|
if (root.host && root.host.constructor.version) {
|
|
@@ -36,7 +36,7 @@ export const LumoInjectionMixin = (superClass) =>
|
|
|
36
36
|
static finalize() {
|
|
37
37
|
super.finalize();
|
|
38
38
|
|
|
39
|
-
const propName = this.
|
|
39
|
+
const propName = getLumoInjectorPropName(this.lumoInjector);
|
|
40
40
|
|
|
41
41
|
// Prevent registering same property twice when a class extends
|
|
42
42
|
// another class using this mixin, since `finalize()` is called
|
|
@@ -57,12 +57,9 @@ export const LumoInjectionMixin = (superClass) =>
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
static get lumoInjectPropName() {
|
|
61
|
-
return `--_lumo-${this.is}-inject`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
60
|
static get lumoInjector() {
|
|
65
61
|
return {
|
|
62
|
+
is: this.is,
|
|
66
63
|
includeBaseStyles: false,
|
|
67
64
|
};
|
|
68
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/vaadin-themable-mixin",
|
|
3
|
-
"version": "25.0.0-
|
|
3
|
+
"version": "25.0.0-beta3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
36
|
-
"@vaadin/component-base": "25.0.0-
|
|
36
|
+
"@vaadin/component-base": "25.0.0-beta3",
|
|
37
37
|
"lit": "^3.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@polymer/polymer": "^3.0.0",
|
|
41
|
-
"@vaadin/chai-plugins": "25.0.0-
|
|
42
|
-
"@vaadin/test-runner-commands": "25.0.0-
|
|
41
|
+
"@vaadin/chai-plugins": "25.0.0-beta3",
|
|
42
|
+
"@vaadin/test-runner-commands": "25.0.0-beta3",
|
|
43
43
|
"@vaadin/testing-helpers": "^2.0.0",
|
|
44
44
|
"sinon": "^21.0.0"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "4b2006b0e2f4fc131f5483223b852d34224e7b9a"
|
|
47
47
|
}
|
|
@@ -9,23 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @private
|
|
11
11
|
*/
|
|
12
|
-
export class CSSPropertyObserver {
|
|
12
|
+
export class CSSPropertyObserver extends EventTarget {
|
|
13
13
|
#root;
|
|
14
|
-
#callback;
|
|
15
14
|
#properties = new Set();
|
|
16
15
|
#styleSheet;
|
|
17
16
|
#isConnected = false;
|
|
18
17
|
|
|
19
|
-
constructor(root
|
|
18
|
+
constructor(root) {
|
|
19
|
+
super();
|
|
20
20
|
this.#root = root;
|
|
21
|
-
this.#callback = callback;
|
|
22
21
|
this.#styleSheet = new CSSStyleSheet();
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
#handleTransitionEvent(event) {
|
|
26
25
|
const { propertyName } = event;
|
|
27
26
|
if (this.#properties.has(propertyName)) {
|
|
28
|
-
this
|
|
27
|
+
this.dispatchEvent(new CustomEvent('property-changed', { detail: { propertyName } }));
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -78,4 +77,14 @@ export class CSSPropertyObserver {
|
|
|
78
77
|
get #rootHost() {
|
|
79
78
|
return this.#root.documentElement ?? this.#root.host;
|
|
80
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Gets or creates the CSSPropertyObserver for the given root.
|
|
83
|
+
* @param {DocumentOrShadowRoot} root
|
|
84
|
+
* @returns {CSSPropertyObserver}
|
|
85
|
+
*/
|
|
86
|
+
static for(root) {
|
|
87
|
+
root.__cssPropertyObserver ||= new CSSPropertyObserver(root);
|
|
88
|
+
return root.__cssPropertyObserver;
|
|
89
|
+
}
|
|
81
90
|
}
|
package/src/lumo-injector.js
CHANGED
|
@@ -7,6 +7,10 @@ import { CSSPropertyObserver } from './css-property-observer.js';
|
|
|
7
7
|
import { injectLumoStyleSheet, removeLumoStyleSheet } from './css-utils.js';
|
|
8
8
|
import { parseStyleSheets } from './lumo-modules.js';
|
|
9
9
|
|
|
10
|
+
export function getLumoInjectorPropName(lumoInjector) {
|
|
11
|
+
return `--_lumo-${lumoInjector.is}-inject`;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Implements auto-injection of CSS styles from document style sheets
|
|
12
16
|
* into the Shadow DOM of corresponding Vaadin components.
|
|
@@ -82,14 +86,13 @@ export class LumoInjector {
|
|
|
82
86
|
|
|
83
87
|
constructor(root = document) {
|
|
84
88
|
this.#root = root;
|
|
85
|
-
this
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
+
this.handlePropertyChange = this.handlePropertyChange.bind(this);
|
|
90
|
+
this.#cssPropertyObserver = CSSPropertyObserver.for(root);
|
|
91
|
+
this.#cssPropertyObserver.addEventListener('property-changed', this.handlePropertyChange);
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
disconnect() {
|
|
92
|
-
this.#cssPropertyObserver.
|
|
95
|
+
this.#cssPropertyObserver.removeEventListener('property-changed', this.handlePropertyChange);
|
|
93
96
|
this.#styleSheetsByTag.clear();
|
|
94
97
|
this.#componentsByTag.values().forEach((components) => components.forEach(removeLumoStyleSheet));
|
|
95
98
|
}
|
|
@@ -104,7 +107,8 @@ export class LumoInjector {
|
|
|
104
107
|
* @param {HTMLElement} component
|
|
105
108
|
*/
|
|
106
109
|
componentConnected(component) {
|
|
107
|
-
const {
|
|
110
|
+
const { lumoInjector } = component.constructor;
|
|
111
|
+
const { is: tagName } = lumoInjector;
|
|
108
112
|
|
|
109
113
|
this.#componentsByTag.set(tagName, this.#componentsByTag.get(tagName) ?? new Set());
|
|
110
114
|
this.#componentsByTag.get(tagName).add(component);
|
|
@@ -118,7 +122,9 @@ export class LumoInjector {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
this.#initStyleSheet(tagName);
|
|
121
|
-
|
|
125
|
+
|
|
126
|
+
const propName = getLumoInjectorPropName(lumoInjector);
|
|
127
|
+
this.#cssPropertyObserver.observe(propName);
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
/**
|
|
@@ -128,12 +134,20 @@ export class LumoInjector {
|
|
|
128
134
|
* @param {HTMLElement} component
|
|
129
135
|
*/
|
|
130
136
|
componentDisconnected(component) {
|
|
131
|
-
const { is: tagName } = component.constructor;
|
|
137
|
+
const { is: tagName } = component.constructor.lumoInjector;
|
|
132
138
|
this.#componentsByTag.get(tagName)?.delete(component);
|
|
133
139
|
|
|
134
140
|
removeLumoStyleSheet(component);
|
|
135
141
|
}
|
|
136
142
|
|
|
143
|
+
handlePropertyChange(event) {
|
|
144
|
+
const { propertyName } = event.detail;
|
|
145
|
+
const tagName = propertyName.match(/^--_lumo-(.*)-inject$/u)?.[1];
|
|
146
|
+
if (tagName) {
|
|
147
|
+
this.#updateStyleSheet(tagName);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
137
151
|
#initStyleSheet(tagName) {
|
|
138
152
|
this.#styleSheetsByTag.set(tagName, new CSSStyleSheet());
|
|
139
153
|
this.#updateStyleSheet(tagName);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2000 - 2025 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { CSSPropertyObserver } from './css-property-observer.js';
|
|
7
|
+
|
|
8
|
+
// Register CSS custom properties for observing theme changes
|
|
9
|
+
CSS.registerProperty({
|
|
10
|
+
name: '--vaadin-aura-theme',
|
|
11
|
+
syntax: '<number>',
|
|
12
|
+
inherits: true,
|
|
13
|
+
initialValue: '0',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
CSS.registerProperty({
|
|
17
|
+
name: '--vaadin-lumo-theme',
|
|
18
|
+
syntax: '<number>',
|
|
19
|
+
inherits: true,
|
|
20
|
+
initialValue: '0',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Observes a root (Document or ShadowRoot) for which Vaadin theme is currently applied.
|
|
25
|
+
* Notifies about theme changes by firing a `theme-changed` event.
|
|
26
|
+
*
|
|
27
|
+
* WARNING: For internal use only. Do not use this class in custom components.
|
|
28
|
+
*
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
export class ThemeDetector extends EventTarget {
|
|
32
|
+
/** @type {DocumentOrShadowRoot} */
|
|
33
|
+
#root;
|
|
34
|
+
/** @type {CSSPropertyObserver} */
|
|
35
|
+
#observer;
|
|
36
|
+
/** @type {{ aura: boolean; lumo: boolean }} */
|
|
37
|
+
#themes = { aura: false, lumo: false };
|
|
38
|
+
/** @type {(event: CustomEvent) => void} */
|
|
39
|
+
#boundHandleThemeChange = this.#handleThemeChange.bind(this);
|
|
40
|
+
|
|
41
|
+
constructor(root) {
|
|
42
|
+
super();
|
|
43
|
+
this.#root = root;
|
|
44
|
+
this.#detectTheme();
|
|
45
|
+
|
|
46
|
+
this.#observer = CSSPropertyObserver.for(this.#root);
|
|
47
|
+
this.#observer.observe('--vaadin-aura-theme');
|
|
48
|
+
this.#observer.observe('--vaadin-lumo-theme');
|
|
49
|
+
this.#observer.addEventListener('property-changed', this.#boundHandleThemeChange);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get themes() {
|
|
53
|
+
return { ...this.#themes };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#handleThemeChange(event) {
|
|
57
|
+
const { propertyName } = event.detail;
|
|
58
|
+
if (!['--vaadin-aura-theme', '--vaadin-lumo-theme'].includes(propertyName)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.#detectTheme();
|
|
63
|
+
this.dispatchEvent(new CustomEvent('theme-changed'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#detectTheme() {
|
|
67
|
+
const rootElement = this.#root.documentElement ?? this.#root.host;
|
|
68
|
+
const style = getComputedStyle(rootElement);
|
|
69
|
+
this.#themes = {
|
|
70
|
+
aura: style.getPropertyValue('--vaadin-aura-theme').trim() === '1',
|
|
71
|
+
lumo: style.getPropertyValue('--vaadin-lumo-theme').trim() === '1',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
disconnect() {
|
|
76
|
+
this.#observer.removeEventListener('property-changed', this.#boundHandleThemeChange);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2025 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
+
|
|
8
|
+
export declare function ThemeDetectionMixin<T extends Constructor<HTMLElement>>(
|
|
9
|
+
base: T,
|
|
10
|
+
): Constructor<ThemeDetectionMixinClass> & T;
|
|
11
|
+
|
|
12
|
+
export declare class ThemeDetectionMixinClass {}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { findRoot } from './lumo-injection-mixin.js';
|
|
2
|
+
import { ThemeDetector } from './src/theme-detector.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mixin for detecting which Vaadin theme is applied to the application.
|
|
6
|
+
* Automatically adds a `data-application-theme` attribute to the host
|
|
7
|
+
* element with the name of the detected theme (`lumo` or `aura`), which
|
|
8
|
+
* can be used in component styles to apply theme-specific styling.
|
|
9
|
+
*
|
|
10
|
+
* @polymerMixin
|
|
11
|
+
*/
|
|
12
|
+
export const ThemeDetectionMixin = (superClass) =>
|
|
13
|
+
class ThemeDetectionMixinClass extends superClass {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this.__applyDetectedTheme = this.__applyDetectedTheme.bind(this);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @protected */
|
|
21
|
+
connectedCallback() {
|
|
22
|
+
super.connectedCallback();
|
|
23
|
+
|
|
24
|
+
if (this.isConnected) {
|
|
25
|
+
const root = findRoot(this);
|
|
26
|
+
root.__themeDetector = root.__themeDetector || new ThemeDetector(root);
|
|
27
|
+
this.__themeDetector = root.__themeDetector;
|
|
28
|
+
this.__themeDetector.addEventListener('theme-changed', this.__applyDetectedTheme);
|
|
29
|
+
this.__applyDetectedTheme();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @protected */
|
|
34
|
+
disconnectedCallback() {
|
|
35
|
+
super.disconnectedCallback();
|
|
36
|
+
|
|
37
|
+
if (this.__themeDetector) {
|
|
38
|
+
this.__themeDetector.removeEventListener('theme-changed', this.__applyDetectedTheme);
|
|
39
|
+
this.__themeDetector = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @private */
|
|
44
|
+
__applyDetectedTheme() {
|
|
45
|
+
if (!this.__themeDetector) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const themes = this.__themeDetector.themes;
|
|
50
|
+
if (themes.aura) {
|
|
51
|
+
this.dataset.applicationTheme = 'aura';
|
|
52
|
+
} else if (themes.lumo) {
|
|
53
|
+
this.dataset.applicationTheme = 'lumo';
|
|
54
|
+
} else {
|
|
55
|
+
delete this.dataset.applicationTheme;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|