diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/package.json b/flow-server/src/main/resources/plugins/application-theme-plugin/package.json index d875478e378..380baea46a4 100644 --- a/flow-server/src/main/resources/plugins/application-theme-plugin/package.json +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/package.json @@ -6,7 +6,7 @@ ], "repository": "vaadin/flow", "name": "@vaadin/application-theme-plugin", - "version": "0.1.3", + "version": "0.1.4", "main": "application-theme-plugin.js", "author": "Vaadin Ltd", "license": "Apache-2.0", diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js index c7ba5deb782..a502b205376 100644 --- a/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js @@ -21,6 +21,8 @@ const glob = require('glob'); const path = require('path'); +// Special folder inside a theme for component themes that go inside the component shadow root +const themeComponentsFolder = 'components'; // The contents of a global CSS file with this name in a theme is always added to // the document. E.g. @font-face must be in this const themeFileAlwaysAddToDocument = 'document.css'; @@ -48,13 +50,22 @@ function generateThemeFile(themeFolder, themeName) { cwd: themeFolder, nodir: true, }); + const componentsFiles = glob.sync('*.css', { + cwd: path.resolve(themeFolder, themeComponentsFolder), + nodir: true, + }); let themeFile = headerImport; + if(componentsFiles.length > 0){ + themeFile += 'import { css, unsafeCSS, registerStyles } from \'@vaadin/vaadin-themable-mixin/register-styles\';'; + } + themeFile += injectGlobalCssMethod; const imports = []; const globalCssCode = []; + const componentCssCode = []; globalFiles.forEach((global) => { const filename = path.basename(global); @@ -66,17 +77,41 @@ function generateThemeFile(themeFolder, themeName) { globalCssCode.push(`injectGlobalCss(${variable}.toString(), target);\n`); }); + componentsFiles.forEach((componentCss) => { + const filename = path.basename(componentCss); + const tag = filename.replace('.css', ''); + const variable = camelCase(filename); + imports.push( + `import ${variable} from './${themeComponentsFolder}/${filename}';\n` + ); +// Don't format as the generated file formatting will get wonky! + const componentString = `registerStyles( + '${tag}', + css\` + \${unsafeCSS(${variable}.toString())} + \` + ); +`; + componentCssCode.push(componentString); + }); + const themeIdentifier = '_vaadinds_' + themeName + '_'; const globalCssFlag = themeIdentifier + 'globalCss'; + const componentCssFlag = themeIdentifier + 'componentCss'; themeFile += imports.join(''); // Don't format as the generated file formatting will get wonky! +// If targets check that we only register the style parts once, checks exist for global css and component css const themeFileApply = `export const applyTheme = (target) => { if (!target['${globalCssFlag}']) { ${globalCssCode.join('')} target['${globalCssFlag}'] = true; } + if (!document['${componentCssFlag}']) { + ${componentCssCode.join('')} + document['${componentCssFlag}'] = true; + } } `; @@ -94,7 +129,7 @@ function generateThemeFile(themeFolder, themeName) { function camelCase(str) { return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) { return index === 0 ? word.toLowerCase() : word.toUpperCase(); - }).replace(/\s+/g, '').replace(/\./g, ''); + }).replace(/\s+/g, '').replace(/\.|\-/g, ''); } module.exports = generateThemeFile; diff --git a/flow-tests/test-themes/frontend/theme/app-theme/components/vaadin-radio-button.css b/flow-tests/test-themes/frontend/theme/app-theme/components/vaadin-radio-button.css new file mode 100644 index 00000000000..150af1c1a1c --- /dev/null +++ b/flow-tests/test-themes/frontend/theme/app-theme/components/vaadin-radio-button.css @@ -0,0 +1,3 @@ +[part='radio'] { + background-color: red; +} diff --git a/flow-tests/test-themes/frontend/theme/app-theme/components/vaadin-text-field.css b/flow-tests/test-themes/frontend/theme/app-theme/components/vaadin-text-field.css new file mode 100644 index 00000000000..ed2bce49bda --- /dev/null +++ b/flow-tests/test-themes/frontend/theme/app-theme/components/vaadin-text-field.css @@ -0,0 +1,3 @@ +[part="input-field"] { + background: red; +} diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/AppShell.java b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/AppShell.java index 5d0bbab0fd9..d4c1b0c1f8c 100644 --- a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/AppShell.java +++ b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/AppShell.java @@ -16,11 +16,11 @@ package com.vaadin.flow.uitest.ui.theme; +import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.component.page.AppShellConfigurator; -import com.vaadin.flow.server.PWA; import com.vaadin.flow.theme.Theme; @Theme(value ="app-theme") -@PWA(name = "Project Base for Vaadin", shortName = "Project Base") +@NpmPackage(value = "@vaadin/vaadin-themable-mixin", version = "1.6.1") public class AppShell implements AppShellConfigurator { } diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyLitField.java b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyLitField.java new file mode 100644 index 00000000000..da2730d0a58 --- /dev/null +++ b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyLitField.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.uitest.ui.theme; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.dependency.NpmPackage; + +/** + * LIT version of vaadin radio button for testing component theming. + */ +@JsModule("@vaadin/vaadin-radio-button/vaadin-radio-button.ts") +@Tag("vaadin-radio-button") +@NpmPackage(value = "@vaadin/vaadin-radio-button", version = "2.0.0-alpha1") +public class MyLitField extends Component { + + /** + * Set the component id. + * + * @param id + * value to set + * @return this component + */ + public Component withId(String id) { + setId(id); + return this; + } +} diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyPolymerField.java b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyPolymerField.java new file mode 100644 index 00000000000..0b13d277564 --- /dev/null +++ b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyPolymerField.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.uitest.ui.theme; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.dependency.NpmPackage; + +/** + * Polymer version of vaadin text field for testing component theming. + */ +@JsModule("@vaadin/vaadin-text-field/vaadin-text-field.js") +@Tag("vaadin-text-field") +@NpmPackage(value="@vaadin/vaadin-text-field", version = "2.7.1") +public class MyPolymerField extends Component { + + /** + * Set the component id. + * + * @param id + * value to set + * @return this component + */ + public Component withId(String id) { + setId(id); + return this; + } +} diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java index 43c98e86a9d..01c321c1c77 100644 --- a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java +++ b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java @@ -23,6 +23,8 @@ @Route("com.vaadin.flow.uitest.ui.theme.ThemeView") public class ThemeView extends Div { + public static final String MY_POLYMER_ID = "field"; + public static final String MY_LIT_ID = "button"; public static final String TEST_TEXT_ID = "test-text"; public ThemeView() { @@ -31,5 +33,9 @@ public ThemeView() { Span subCss = new Span(); subCss.setId("sub-component"); add(textSpan, subCss); + add(new Div()); + add(new MyPolymerField().withId(MY_POLYMER_ID)); + add(new Div()); + add(new MyLitField().withId(MY_LIT_ID)); } } diff --git a/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java b/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java index 48a32014573..54fe83ca43d 100644 --- a/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java +++ b/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java @@ -22,6 +22,10 @@ import com.vaadin.flow.component.html.testbench.SpanElement; import com.vaadin.flow.testutil.ChromeBrowserTest; +import com.vaadin.testbench.TestBenchElement; + +import static com.vaadin.flow.uitest.ui.theme.ThemeView.MY_LIT_ID; +import static com.vaadin.flow.uitest.ui.theme.ThemeView.MY_POLYMER_ID; public class ThemeIT extends ChromeBrowserTest { @@ -55,6 +59,26 @@ public void applicationTheme_GlobalCss_isUsed() { driver.getPageSource().contains("Could not navigate")); } + @Test + public void componentThemeIsApplied_forPolymerAndLit() { + open(); + TestBenchElement myField = $(TestBenchElement.class).id(MY_POLYMER_ID); + TestBenchElement input = myField.$(TestBenchElement.class) + .id("vaadin-text-field-input-0"); + Assert.assertEquals("Polymer text field should have red background", + "rgba(255, 0, 0, 1)", input.getCssValue("background-color")); + + myField = $(TestBenchElement.class).id(MY_LIT_ID); + final SpanElement radio = myField.$(SpanElement.class).all().stream() + .filter(element -> "radio".equals(element.getAttribute("part"))) + .findFirst().orElseGet(null); + + Assert.assertNotNull("Element with part='radio' was not found", radio); + + Assert.assertEquals("Lit radiobutton should have red background", + "rgba(255, 0, 0, 1)", radio.getCssValue("background-color")); + } + @Test public void subCssWithRelativePath_urlPathIsNotRelative() { open();