Skip to content

Commit ad65aa2

Browse files
authored
fix: apply color-scheme CSS property in @colorscheme annotation (#22828)
Make @colorscheme annotation apply both the theme attribute and the color-scheme CSS property to the html element, matching the behavior of Page.setColorScheme() method for consistent behavior between static and dynamic color scheme configuration.
1 parent ff318c9 commit ad65aa2

File tree

2 files changed

+130
-7
lines changed

2 files changed

+130
-7
lines changed

flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.vaadin.experimental.Feature;
4444
import com.vaadin.experimental.FeatureFlags;
4545
import com.vaadin.flow.component.UI;
46+
import com.vaadin.flow.component.page.ColorScheme;
4647
import com.vaadin.flow.function.DeploymentConfiguration;
4748
import com.vaadin.flow.internal.BootstrapHandlerHelper;
4849
import com.vaadin.flow.internal.BrowserLiveReload;
@@ -259,14 +260,24 @@ private void applyColorScheme(Document indexDocument,
259260
AppShellRegistry registry = AppShellRegistry.getInstance(context);
260261
Class<?> shell = registry.getShell();
261262
if (shell != null) {
262-
com.vaadin.flow.component.page.ColorScheme colorSchemeAnnotation = shell
263-
.getAnnotation(
264-
com.vaadin.flow.component.page.ColorScheme.class);
263+
ColorScheme colorSchemeAnnotation = shell
264+
.getAnnotation(ColorScheme.class);
265265
if (colorSchemeAnnotation != null) {
266-
String colorScheme = colorSchemeAnnotation.value()
267-
.getThemeValue();
268-
if (!colorScheme.isEmpty() && !colorScheme.equals("normal")) {
269-
indexDocument.head().parent().attr("theme", colorScheme);
266+
ColorScheme.Value colorSchemeValue = colorSchemeAnnotation
267+
.value();
268+
String themeValue = colorSchemeValue.getThemeValue();
269+
if (!themeValue.isEmpty() && !themeValue.equals("normal")) {
270+
Element html = indexDocument.head().parent();
271+
html.attr("theme", themeValue);
272+
String colorSchemeStyle = "color-scheme: "
273+
+ colorSchemeValue.getValue() + ";";
274+
String existingStyle = html.attr("style");
275+
if (existingStyle != null && !existingStyle.isBlank()) {
276+
html.attr("style",
277+
existingStyle.trim() + " " + colorSchemeStyle);
278+
} else {
279+
html.attr("style", colorSchemeStyle);
280+
}
270281
}
271282
}
272283
}

flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
import com.vaadin.flow.component.UI;
5353
import com.vaadin.flow.component.page.AppShellConfigurator;
54+
import com.vaadin.flow.component.page.ColorScheme;
5455
import com.vaadin.flow.di.Lookup;
5556
import com.vaadin.flow.di.ResourceProvider;
5657
import com.vaadin.flow.internal.JacksonUtils;
@@ -841,6 +842,117 @@ public void should_apply_theme_variant() throws IOException {
841842
assertEquals("dark", document.head().parent().attr("theme"));
842843
}
843844

845+
@ColorScheme(ColorScheme.Value.DARK)
846+
public static class ClassWithDarkColorScheme
847+
implements AppShellConfigurator {
848+
}
849+
850+
@Test
851+
public void should_apply_colorScheme_dark() throws IOException {
852+
AppShellRegistry registry = AppShellRegistry.getInstance(context);
853+
registry.setShell(ClassWithDarkColorScheme.class);
854+
mocks.setAppShellRegistry(registry);
855+
856+
indexHtmlRequestHandler.synchronizedHandleRequest(session,
857+
createVaadinRequest("/"), response);
858+
859+
String indexHtml = responseOutput.toString(StandardCharsets.UTF_8);
860+
Document document = Jsoup.parse(indexHtml);
861+
862+
assertEquals("dark", document.head().parent().attr("theme"));
863+
assertEquals("color-scheme: dark;",
864+
document.head().parent().attr("style"));
865+
}
866+
867+
@ColorScheme(ColorScheme.Value.LIGHT_DARK)
868+
public static class ClassWithLightDarkColorScheme
869+
implements AppShellConfigurator {
870+
}
871+
872+
@Test
873+
public void should_apply_colorScheme_lightDark() throws IOException {
874+
AppShellRegistry registry = AppShellRegistry.getInstance(context);
875+
registry.setShell(ClassWithLightDarkColorScheme.class);
876+
mocks.setAppShellRegistry(registry);
877+
878+
indexHtmlRequestHandler.synchronizedHandleRequest(session,
879+
createVaadinRequest("/"), response);
880+
881+
String indexHtml = responseOutput.toString(StandardCharsets.UTF_8);
882+
Document document = Jsoup.parse(indexHtml);
883+
884+
assertEquals("light-dark", document.head().parent().attr("theme"));
885+
assertEquals("color-scheme: light dark;",
886+
document.head().parent().attr("style"));
887+
}
888+
889+
@ColorScheme(ColorScheme.Value.NORMAL)
890+
public static class ClassWithNormalColorScheme
891+
implements AppShellConfigurator {
892+
}
893+
894+
@Test
895+
public void should_not_apply_colorScheme_normal() throws IOException {
896+
AppShellRegistry registry = AppShellRegistry.getInstance(context);
897+
registry.setShell(ClassWithNormalColorScheme.class);
898+
mocks.setAppShellRegistry(registry);
899+
900+
indexHtmlRequestHandler.synchronizedHandleRequest(session,
901+
createVaadinRequest("/"), response);
902+
903+
String indexHtml = responseOutput.toString(StandardCharsets.UTF_8);
904+
Document document = Jsoup.parse(indexHtml);
905+
906+
assertEquals("", document.head().parent().attr("theme"));
907+
assertEquals("", document.head().parent().attr("style"));
908+
}
909+
910+
@Test
911+
public void should_append_colorScheme_to_existing_style()
912+
throws IOException {
913+
File projectRootFolder = temporaryFolder.newFolder();
914+
915+
// Create custom index.html with existing style attribute on html
916+
// element
917+
String indexHtmlWithStyle = """
918+
<!DOCTYPE html>
919+
<html style="--custom-prop: value;">
920+
<head>
921+
<meta charset="UTF-8" />
922+
<meta name="viewport" content="width=device-width, initial-scale=1" />
923+
</head>
924+
<body>
925+
<div id="outlet"></div>
926+
</body>
927+
</html>
928+
""";
929+
930+
File frontendDir = new File(projectRootFolder, "frontend");
931+
frontendDir.mkdirs();
932+
File indexHtml = new File(frontendDir, "index.html");
933+
java.nio.file.Files.writeString(indexHtml.toPath(), indexHtmlWithStyle);
934+
935+
TestUtil.createStatsJsonStub(projectRootFolder);
936+
937+
deploymentConfiguration.setProductionMode(false);
938+
deploymentConfiguration.setProjectFolder(projectRootFolder);
939+
940+
AppShellRegistry registry = AppShellRegistry.getInstance(context);
941+
registry.setShell(ClassWithDarkColorScheme.class);
942+
mocks.setAppShellRegistry(registry);
943+
944+
indexHtmlRequestHandler.synchronizedHandleRequest(session,
945+
createVaadinRequest("/"), response);
946+
947+
String indexHtmlOutput = responseOutput
948+
.toString(StandardCharsets.UTF_8);
949+
Document document = Jsoup.parse(indexHtmlOutput);
950+
951+
assertEquals("dark", document.head().parent().attr("theme"));
952+
assertEquals("--custom-prop: value; color-scheme: dark;",
953+
document.head().parent().attr("style"));
954+
}
955+
844956
@Test
845957
public void should_store_IndexHtmltitleToUI_When_LoadingServerEagerly()
846958
throws IOException {

0 commit comments

Comments
 (0)