@vaadin/vaadin-themable-mixin 22.0.0-alpha6 → 22.0.0-beta1

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/package.json CHANGED
@@ -1,38 +1,42 @@
1
1
  {
2
2
  "name": "@vaadin/vaadin-themable-mixin",
3
- "version": "22.0.0-alpha6",
3
+ "version": "22.0.0-beta1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
4
7
  "description": "vaadin-themable-mixin",
8
+ "license": "Apache-2.0",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/vaadin/web-components.git",
12
+ "directory": "packages/vaadin-themable-mixin"
13
+ },
14
+ "author": "Vaadin Ltd",
15
+ "homepage": "https://vaadin.com/elements",
16
+ "bugs": {
17
+ "url": "https://github.com/vaadin/vaadin-themable-mixin/issues"
18
+ },
5
19
  "main": "vaadin-themable-mixin.js",
6
20
  "module": "vaadin-themable-mixin.js",
7
- "repository": "vaadin/vaadin-themable-mixin",
21
+ "files": [
22
+ "*.d.ts",
23
+ "register-styles.js",
24
+ "vaadin-*.js"
25
+ ],
8
26
  "keywords": [
9
27
  "Vaadin",
10
28
  "web-components",
11
29
  "web-component",
12
30
  "polymer"
13
31
  ],
14
- "author": "Vaadin Ltd",
15
- "license": "Apache-2.0",
16
- "bugs": {
17
- "url": "https://github.com/vaadin/vaadin-themable-mixin/issues"
18
- },
19
- "homepage": "https://vaadin.com/elements",
20
- "files": [
21
- "*.d.ts",
22
- "vaadin-*.js",
23
- "register-styles.js"
24
- ],
25
32
  "dependencies": {
26
- "@polymer/polymer": "^3.0.0",
27
- "lit": "^2.0.0-rc.1"
33
+ "lit": "^2.0.0"
28
34
  },
29
35
  "devDependencies": {
30
36
  "@esm-bundle/chai": "^4.3.4",
37
+ "@polymer/polymer": "^3.0.0",
31
38
  "@vaadin/testing-helpers": "^0.3.0",
32
39
  "sinon": "^9.2.4"
33
40
  },
34
- "publishConfig": {
35
- "access": "public"
36
- },
37
- "gitHead": "4b136b1c7da8942960e7255f40c27859125b3a45"
41
+ "gitHead": "4cf8a9d0504994200c610e44b3676114fef49c1e"
38
42
  }
@@ -1,11 +1,6 @@
1
- import { CSSResultGroup } from 'lit';
2
-
3
- export { css, unsafeCSS } from 'lit';
4
-
5
1
  /**
6
- * Registers CSS styles for a component type. Make sure to register the styles before
7
- * the first instance of a component of the type is attached to DOM.
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8
5
  */
9
- declare function registerStyles(themeFor: string | null, styles: CSSResultGroup, options?: object | null): void;
10
-
11
- export { registerStyles };
6
+ export { registerStyles, css, unsafeCSS } from './vaadin-themable-mixin.js';
@@ -1,80 +1,6 @@
1
- import '@polymer/polymer/lib/elements/dom-module.js';
2
- import { CSSResult } from 'lit';
3
- import { stylesFromTemplate } from '@polymer/polymer/lib/utils/style-gather.js';
4
- export { css, unsafeCSS } from 'lit';
5
-
6
- let moduleIdIndex = 0;
7
- // Map of <CSSResult, Polymer.DomModule> pairs.
8
- const styleMap = {};
9
-
10
- function recursiveFlattenStyles(styles, result = []) {
11
- if (styles instanceof CSSResult) {
12
- result.push(styles);
13
- } else if (Array.isArray(styles)) {
14
- styles.forEach((style) => recursiveFlattenStyles(style, result));
15
- }
16
- return result;
17
- }
18
-
19
1
  /**
20
- * Registers CSS styles for a component type. Make sure to register the styles before
21
- * the first instance of a component of the type is attached to DOM.
22
- *
23
- * @param {String} themeFor The local/tag name of the component type to register the styles for
24
- * @param {CSSResultGroup} styles The CSS style rules to be registered for the component type
25
- * matching themeFor and included in the local scope of each component instance
26
- * @param {Object=} options Additional options
27
- * @return {void}
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
28
5
  */
29
- export const registerStyles = (themeFor, styles, options) => {
30
- if (options && options.include && !options.suppressDeprecationWarning) {
31
- console.warn(
32
- `The "include" option in registerStyles is deprecated. Instead, include an imported CSSResult in the styles array.`
33
- );
34
- }
35
-
36
- const themeId = (options && options.moduleId) || `custom-style-module-${moduleIdIndex++}`;
37
-
38
- if (!Array.isArray(styles)) {
39
- styles = styles ? [styles] : [];
40
- }
41
-
42
- styles = recursiveFlattenStyles(styles);
43
-
44
- const processedStyles = styles.map((cssResult) => {
45
- if (!(cssResult instanceof CSSResult)) {
46
- throw new Error('An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`.');
47
- }
48
- if (!styleMap[cssResult]) {
49
- const template = document.createElement('template');
50
- template.innerHTML = `<style>${cssResult.toString()}</style>`;
51
-
52
- styleMap[cssResult] = stylesFromTemplate(template)[0];
53
- }
54
-
55
- return styleMap[cssResult].textContent;
56
- });
57
-
58
- const themeModuleElement = document.createElement('dom-module');
59
- if (themeFor) {
60
- const elementClass = customElements.get(themeFor);
61
- if (elementClass && Object.prototype.hasOwnProperty.call(elementClass, '__finalized')) {
62
- console.warn(`The custom element definition for "${themeFor}"
63
- was finalized before a style module was registered.
64
- Make sure to add component specific style modules before
65
- importing the corresponding custom element.`);
66
- }
67
- themeModuleElement.setAttribute('theme-for', themeFor);
68
- }
69
-
70
- const moduleIncludes = (options && options.include) || [];
71
-
72
- themeModuleElement.innerHTML = `
73
- <template>
74
- ${moduleIncludes.map((include) => `<style include=${include}></style>`)}
75
- ${processedStyles.length ? `<style>${processedStyles.join('\n')}</style>` : ''}
76
- </template>
77
- `;
78
-
79
- themeModuleElement.register(themeId);
80
- };
6
+ export { registerStyles, css, unsafeCSS } from './vaadin-themable-mixin.js';
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { CSSResult, CSSResultGroup } from 'lit';
1
7
  import { ThemePropertyMixin, ThemePropertyMixinConstructor } from './vaadin-theme-property-mixin.js';
2
8
 
3
9
  declare function ThemableMixin<T extends new (...args: any[]) => {}>(
@@ -11,4 +17,24 @@ interface ThemableMixinConstructor {
11
17
 
12
18
  interface ThemableMixin extends ThemePropertyMixin {}
13
19
 
14
- export { ThemableMixin, ThemableMixinConstructor };
20
+ /**
21
+ * Registers CSS styles for a component type. Make sure to register the styles before
22
+ * the first instance of a component of the type is attached to DOM.
23
+ */
24
+ declare function registerStyles(themeFor: string | null, styles: CSSResultGroup, options?: object | null): void;
25
+
26
+ type Theme = {
27
+ themeFor: string;
28
+ styles: CSSResult[];
29
+ moduleId?: string;
30
+ include?: string | string[];
31
+ };
32
+
33
+ /**
34
+ * For internal purposes only.
35
+ */
36
+ declare const __themeRegistry: Theme[];
37
+
38
+ export { css, unsafeCSS } from 'lit';
39
+
40
+ export { ThemableMixin, ThemableMixinConstructor, registerStyles, __themeRegistry };
@@ -1,83 +1,230 @@
1
- import { DomModule } from '@polymer/polymer/lib/elements/dom-module.js';
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { CSSResult, css, unsafeCSS } from 'lit';
2
7
  import { ThemePropertyMixin } from './vaadin-theme-property-mixin.js';
3
8
 
9
+ export { css, unsafeCSS };
10
+
11
+ /**
12
+ * @typedef {Object} Theme
13
+ * @property {string} themeFor
14
+ * @property {CSSResult[]} styles
15
+ * @property {string | string[]} [include]
16
+ * @property {string} [moduleId]
17
+ *
18
+ * @typedef {CSSResult[] | CSSResult} CSSResultGroup
19
+ */
20
+
21
+ /**
22
+ * @type {Theme[]}
23
+ */
24
+ const themeRegistry = [];
25
+
26
+ /**
27
+ * Registers CSS styles for a component type. Make sure to register the styles before
28
+ * the first instance of a component of the type is attached to DOM.
29
+ *
30
+ * @param {string} themeFor The local/tag name of the component type to register the styles for
31
+ * @param {CSSResultGroup} styles The CSS style rules to be registered for the component type
32
+ * matching themeFor and included in the local scope of each component instance
33
+ * @param {{moduleId?: string, include?: string | string[]}} options Additional options
34
+ * @return {void}
35
+ */
36
+ export function registerStyles(themeFor, styles, options = {}) {
37
+ if (themeFor) {
38
+ const elementClass = customElements.get(themeFor);
39
+ if (elementClass && Object.prototype.hasOwnProperty.call(elementClass, '__finalized')) {
40
+ console.warn(`The custom element definition for "${themeFor}"
41
+ was finalized before a style module was registered.
42
+ Make sure to add component specific style modules before
43
+ importing the corresponding custom element.`);
44
+ }
45
+ }
46
+
47
+ styles = recursiveFlattenStyles(styles);
48
+
49
+ if (window.Vaadin && window.Vaadin.styleModules) {
50
+ window.Vaadin.styleModules.registerStyles(themeFor, styles, options);
51
+ } else {
52
+ themeRegistry.push({
53
+ themeFor,
54
+ styles,
55
+ include: options.include,
56
+ moduleId: options.moduleId
57
+ });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Returns all registered themes. By default the themeRegistry is returend as is.
63
+ * In case the style-modules adapter is imported, the themes are obtained from there instead
64
+ * @returns {Theme[]}
65
+ */
66
+ function getAllThemes() {
67
+ if (window.Vaadin && window.Vaadin.styleModules) {
68
+ return window.Vaadin.styleModules.getAllThemes();
69
+ } else {
70
+ return themeRegistry;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Returns true if the themeFor string matches the tag name
76
+ * @param {string} themeFor
77
+ * @param {string} tagName
78
+ * @returns {boolean}
79
+ */
80
+ function matchesThemeFor(themeFor, tagName) {
81
+ return (themeFor || '').split(' ').some((themeForToken) => {
82
+ return new RegExp('^' + themeForToken.split('*').join('.*') + '$').test(tagName);
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Maps the moduleName to an include priority number which is used for
88
+ * determining the order in which styles are applied.
89
+ * @param {string} moduleName
90
+ * @returns {number}
91
+ */
92
+ function getIncludePriority(moduleName = '') {
93
+ let includePriority = 0;
94
+ if (moduleName.indexOf('lumo-') === 0 || moduleName.indexOf('material-') === 0) {
95
+ includePriority = 1;
96
+ } else if (moduleName.indexOf('vaadin-') === 0) {
97
+ includePriority = 2;
98
+ }
99
+ return includePriority;
100
+ }
101
+
102
+ /**
103
+ * Flattens the styles into a single array of styles.
104
+ * @param {CSSResultGroup} styles
105
+ * @param {CSSResult[]} result
106
+ * @returns {CSSResult[]}
107
+ */
108
+ function recursiveFlattenStyles(styles = [], result = []) {
109
+ if (styles instanceof CSSResult) {
110
+ result.push(styles);
111
+ } else if (Array.isArray(styles)) {
112
+ styles.forEach((style) => recursiveFlattenStyles(style, result));
113
+ } else {
114
+ console.warn('An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`.');
115
+ }
116
+ return result;
117
+ }
118
+
119
+ /**
120
+ * Gets an array of CSSResults matching the include property of the theme.
121
+ * @param {Theme} theme
122
+ * @returns {CSSResult[]}
123
+ */
124
+ function getIncludedStyles(theme) {
125
+ const includedStyles = [];
126
+ if (theme.include) {
127
+ [].concat(theme.include).forEach((includeModuleId) => {
128
+ const includedTheme = getAllThemes().find((s) => s.moduleId === includeModuleId);
129
+ if (includedTheme) {
130
+ includedStyles.push(...getIncludedStyles(includedTheme), ...includedTheme.styles);
131
+ } else {
132
+ console.warn(`Included moduleId ${includeModuleId} not found in style registry`);
133
+ }
134
+ }, theme.styles);
135
+ }
136
+ return includedStyles;
137
+ }
138
+
139
+ /**
140
+ * Includes the styles to the template.
141
+ * @param {CSSResult[]} styles
142
+ * @param {HTMLTemplateElement} template
143
+ */
144
+ function addStylesToTemplate(styles, template) {
145
+ const styleEl = document.createElement('style');
146
+ styleEl.innerHTML = styles
147
+ // Remove duplicates so that the last occurrence remains
148
+ .filter((style, index) => index === styles.lastIndexOf(style))
149
+ .map((style) => style.cssText)
150
+ .join('\n');
151
+ template.content.appendChild(styleEl);
152
+ }
153
+
154
+ /**
155
+ * Returns an array of themes that should be used for styling a component matching
156
+ * the tag name. The array is sorted by the include order.
157
+ * @param {string} tagName
158
+ * @returns {Theme[]}
159
+ */
160
+ function getThemes(tagName) {
161
+ const defaultModuleName = tagName + '-default-theme';
162
+
163
+ const themes = getAllThemes()
164
+ // Filter by matching themeFor properties
165
+ .filter((theme) => theme.moduleId !== defaultModuleName && matchesThemeFor(theme.themeFor, tagName))
166
+ .map((theme) => ({
167
+ ...theme,
168
+ // Prepend styles from included themes
169
+ styles: [...getIncludedStyles(theme), ...theme.styles],
170
+ // Map moduleId to includePriority
171
+ includePriority: getIncludePriority(theme.moduleId)
172
+ }))
173
+ // Sort by includePriority
174
+ .sort((themeA, themeB) => themeB.includePriority - themeA.includePriority);
175
+
176
+ if (themes.length > 0) {
177
+ return themes;
178
+ } else {
179
+ // No theme modules found, return the default module if it exists
180
+ return getAllThemes().filter((theme) => theme.moduleId === defaultModuleName);
181
+ }
182
+ }
183
+
4
184
  /**
5
185
  * @polymerMixin
6
186
  * @mixes ThemePropertyMixin
7
187
  */
8
188
  export const ThemableMixin = (superClass) =>
9
189
  class VaadinThemableMixin extends ThemePropertyMixin(superClass) {
10
- /** @protected */
190
+ /**
191
+ * Covers PolymerElement based component styling
192
+ * @protected
193
+ */
11
194
  static finalize() {
12
195
  super.finalize();
13
196
 
14
197
  const template = this.prototype._template;
198
+ if (!template || template.__themes) {
199
+ return;
200
+ }
15
201
 
16
202
  const inheritedTemplate = Object.getPrototypeOf(this.prototype)._template;
17
- if (inheritedTemplate) {
18
- // Include the theme modules from the inherited template
19
- Array.from(inheritedTemplate.content.querySelectorAll('style[include]')).forEach((s) => {
20
- this._includeStyle(s.getAttribute('include'), template);
21
- });
22
- }
203
+ const inheritedThemes = (inheritedTemplate ? inheritedTemplate.__themes : []) || [];
23
204
 
24
- this._includeMatchingThemes(template);
25
- }
205
+ template.__themes = [...inheritedThemes, ...getThemes(this.is)];
26
206
 
27
- /** @private */
28
- static _includeMatchingThemes(template) {
29
- const domModule = DomModule;
30
- const modules = domModule.prototype.modules;
31
-
32
- let hasThemes = false;
33
- const defaultModuleName = this.is + '-default-theme';
34
-
35
- Object.keys(modules)
36
- .sort((moduleNameA, moduleNameB) => {
37
- const vaadinA = moduleNameA.indexOf('vaadin-') === 0;
38
- const vaadinB = moduleNameB.indexOf('vaadin-') === 0;
39
-
40
- const vaadinThemePrefixes = ['lumo-', 'material-'];
41
- const vaadinThemeA = vaadinThemePrefixes.filter((prefix) => moduleNameA.indexOf(prefix) === 0).length > 0;
42
- const vaadinThemeB = vaadinThemePrefixes.filter((prefix) => moduleNameB.indexOf(prefix) === 0).length > 0;
43
-
44
- if (vaadinA !== vaadinB) {
45
- // Include vaadin core styles first
46
- return vaadinA ? -1 : 1;
47
- } else if (vaadinThemeA !== vaadinThemeB) {
48
- // Include vaadin theme styles after that
49
- return vaadinThemeA ? -1 : 1;
50
- } else {
51
- // Lastly include custom styles so they override all vaadin styles
52
- return 0;
53
- }
54
- })
55
- .forEach((moduleName) => {
56
- if (moduleName !== defaultModuleName) {
57
- const themeFor = modules[moduleName].getAttribute('theme-for');
58
- if (themeFor) {
59
- themeFor.split(' ').forEach((themeForToken) => {
60
- if (new RegExp('^' + themeForToken.split('*').join('.*') + '$').test(this.is)) {
61
- hasThemes = true;
62
- this._includeStyle(moduleName, template);
63
- }
64
- });
65
- }
66
- }
67
- });
68
-
69
- if (!hasThemes && modules[defaultModuleName]) {
70
- // No theme modules found, include the default module if it exists
71
- this._includeStyle(defaultModuleName, template);
72
- }
207
+ // Get flattened styles array
208
+ const styles = template.__themes.reduce((styles, theme) => [...styles, ...theme.styles], []);
209
+ addStylesToTemplate(styles, template);
73
210
  }
74
211
 
75
- /** @private */
76
- static _includeStyle(moduleName, template) {
77
- if (template && !template.content.querySelector(`style[include="${moduleName}"]`)) {
78
- const styleEl = document.createElement('style');
79
- styleEl.setAttribute('include', moduleName);
80
- template.content.appendChild(styleEl);
81
- }
212
+ /**
213
+ * Covers LitElement based component styling
214
+ *
215
+ * NOTE: This is not yet an offically supported API!
216
+ *
217
+ * TODO: Add tests (run a variation of themable-mixin.test.js where the components get created as LitElements)
218
+ * @protected
219
+ */
220
+ static finalizeStyles(styles) {
221
+ return (
222
+ getThemes(this.is)
223
+ // Get flattened styles array
224
+ .reduce((styles, theme) => [...styles, ...theme.styles], [])
225
+ .concat(styles)
226
+ );
82
227
  }
83
228
  };
229
+
230
+ export { themeRegistry as __themeRegistry };
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
1
6
  declare function ThemePropertyMixin<T extends new (...args: any[]) => {}>(base: T): T & ThemePropertyMixinConstructor;
2
7
 
3
8
  interface ThemePropertyMixinConstructor {
@@ -10,14 +15,14 @@ interface ThemePropertyMixin {
10
15
  * in shadow DOM.
11
16
  *
12
17
  * Enables the component implementation to propagate the `theme`
13
- * attribute value to the subcomponents in Shadow DOM by binding
14
- * the subcomponent’s "theme" attribute to the `theme` property of
18
+ * attribute value to the sub-components in Shadow DOM by binding
19
+ * the sub-component’s "theme" attribute to the `theme` property of
15
20
  * the host.
16
21
  *
17
22
  * **NOTE:** Extending the mixin only provides the property for binding,
18
23
  * and does not make the propagation alone.
19
24
  *
20
- * See [Theme Attribute and Subcomponents](https://github.com/vaadin/vaadin-themable-mixin/wiki/5.-Theme-Attribute-and-Subcomponents).
25
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/ds/customization/styling-components/#sub-components).
21
26
  * page for more information.
22
27
  */
23
28
  readonly theme: string | null | undefined;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
1
6
  /**
2
7
  * @polymerMixin
3
8
  */
@@ -10,14 +15,14 @@ export const ThemePropertyMixin = (superClass) =>
10
15
  * in shadow DOM.
11
16
  *
12
17
  * Enables the component implementation to propagate the `theme`
13
- * attribute value to the subcomponents in Shadow DOM by binding
14
- * the subcomponent’s "theme" attribute to the `theme` property of
18
+ * attribute value to the sub-components in Shadow DOM by binding
19
+ * the sub-component’s "theme" attribute to the `theme` property of
15
20
  * the host.
16
21
  *
17
22
  * **NOTE:** Extending the mixin only provides the property for binding,
18
23
  * and does not make the propagation alone.
19
24
  *
20
- * See [Theme Attribute and Subcomponents](https://github.com/vaadin/vaadin-themable-mixin/wiki/5.-Theme-Attribute-and-Subcomponents).
25
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/ds/customization/styling-components/#sub-components).
21
26
  * page for more information.
22
27
  *
23
28
  * @protected