Skip to content

Commit b031782

Browse files
authored
feat(framework): external themes support (#1463)
1 parent ee7f300 commit b031782

14 files changed

+638
-42
lines changed

packages/base/src/Theming.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
11
import { addCustomCSS } from "./theming/CustomStyle.js";
2-
import { setExternalThemePresent } from "./theming/ExternalThemePresent.js";
32

4-
export {
5-
addCustomCSS,
6-
setExternalThemePresent,
7-
};
3+
export { addCustomCSS }; // eslint-disable-line

packages/base/src/asset-registries/LocaleData.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { fetchJsonOnce } from "../util/FetchHelper.js";
22
import { getFeature } from "../FeaturesRegistry.js";
33
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "../generated/AssetParameters.js";
44

5-
const OpenUI5Support = getFeature("OpenUI5Support");
6-
75
const resources = new Map();
86
const cldrData = {};
97
const cldrUrls = {};
@@ -86,6 +84,7 @@ const fetchCldr = async (language, region, script) => {
8684
let cldrObj = cldrData[localeId];
8785
const url = cldrUrls[localeId];
8886

87+
const OpenUI5Support = getFeature("OpenUI5Support");
8988
if (!cldrObj && OpenUI5Support) {
9089
cldrObj = OpenUI5Support.getLocaleDataObject();
9190
}

packages/base/src/asset-registries/Themes.js

+5
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,13 @@ const getRegisteredPackages = () => {
7575
return registeredPackages;
7676
};
7777

78+
const isThemeRegistered = theme => {
79+
return registeredThemes.has(theme);
80+
};
81+
7882
export {
7983
registerThemeProperties,
8084
getThemeProperties,
8185
getRegisteredPackages,
86+
isThemeRegistered,
8287
};

packages/base/src/boot.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import whenPolyfillLoaded from "./compatibility/whenPolyfillLoaded.js";
66
import { getFeature } from "./FeaturesRegistry.js";
77

88
let bootPromise;
9-
const OpenUI5Support = getFeature("OpenUI5Support");
109

1110
const boot = () => {
1211
if (bootPromise) {
1312
return bootPromise;
1413
}
1514

1615
bootPromise = new Promise(async resolve => {
16+
const OpenUI5Support = getFeature("OpenUI5Support");
1717
if (OpenUI5Support) {
1818
await OpenUI5Support.init();
1919
}

packages/base/src/features/OpenUI5Support.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const getConfigurationSettingsObject = () => {
3535
rtl: config.getRTL(),
3636
calendarType: config.getCalendarType(),
3737
formatSettings: {
38-
firstDayOfWeek: LocaleData.getInstance(config.getLocale()).getFirstDayOfWeek(),
38+
firstDayOfWeek: LocaleData ? LocaleData.getInstance(config.getLocale()).getFirstDayOfWeek() : undefined,
3939
},
4040
};
4141
};
@@ -65,12 +65,26 @@ const attachListeners = () => {
6565
listenForThemeChange();
6666
};
6767

68+
const cssVariablesLoaded = () => {
69+
if (!core) {
70+
return;
71+
}
72+
73+
const link = [...document.head.children].find(el => el.id === "sap-ui-theme-sap.ui.core"); // more reliable than querySelector early
74+
if (!link) {
75+
return;
76+
}
77+
78+
return !!link.href.match(/\/css-variables\.css/);
79+
};
80+
6881
const OpenUI5Support = {
6982
isLoaded,
7083
init,
7184
getConfigurationSettingsObject,
7285
getLocaleDataObject,
7386
attachListeners,
87+
cssVariablesLoaded,
7488
};
7589

7690
registerFeature("OpenUI5Support", OpenUI5Support);

packages/base/src/theming/CSSVarsPonyfill.js

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const runPonyfill = () => {
77

88
window.CSSVarsPonyfill.cssVars({
99
rootElement: document.head,
10-
include: "style[data-ui5-theme-properties],style[data-ui5-element-styles]",
1110
silent: true,
1211
});
1312
};

packages/base/src/theming/ExternalThemePresent.js

-12
This file was deleted.
+69-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,81 @@
1-
import { getThemeProperties, getRegisteredPackages } from "../asset-registries/Themes.js";
2-
import { getExternalThemePresent } from "./ExternalThemePresent.js";
1+
import { getThemeProperties, getRegisteredPackages, isThemeRegistered } from "../asset-registries/Themes.js";
32
import createThemePropertiesStyleTag from "./createThemePropertiesStyleTag.js";
3+
import getThemeDesignerTheme from "./getThemeDesignerTheme.js";
4+
import { ponyfillNeeded, runPonyfill } from "./CSSVarsPonyfill.js";
5+
import { getFeature } from "../FeaturesRegistry.js";
46

5-
const applyTheme = async theme => {
6-
let cssText = "";
7+
const BASE_THEME_PACKAGE = "@ui5/webcomponents-theme-base";
78

9+
const isThemeBaseRegistered = () => {
810
const registeredPackages = getRegisteredPackages();
9-
if (getExternalThemePresent()) {
10-
registeredPackages.delete("@ui5/webcomponents-theme-base");
11+
return registeredPackages.has(BASE_THEME_PACKAGE);
12+
};
13+
14+
const loadThemeBase = async theme => {
15+
if (!isThemeBaseRegistered()) {
16+
return;
17+
}
18+
19+
const cssText = await getThemeProperties(BASE_THEME_PACKAGE, theme);
20+
createThemePropertiesStyleTag(cssText, BASE_THEME_PACKAGE);
21+
};
22+
23+
const deleteThemeBase = () => {
24+
const styleElement = document.head.querySelector(`style[data-ui5-theme-properties="${BASE_THEME_PACKAGE}"]`);
25+
if (styleElement) {
26+
styleElement.parentElement.removeChild(styleElement);
1127
}
28+
};
1229

30+
const loadComponentPackages = async theme => {
31+
const registeredPackages = getRegisteredPackages();
1332
registeredPackages.forEach(async packageName => {
14-
cssText = await getThemeProperties(packageName, theme);
33+
if (packageName === BASE_THEME_PACKAGE) {
34+
return;
35+
}
36+
37+
const cssText = await getThemeProperties(packageName, theme);
1538
createThemePropertiesStyleTag(cssText, packageName);
1639
});
1740
};
1841

42+
const detectExternalTheme = () => {
43+
// If theme designer theme is detected, use this
44+
const extTheme = getThemeDesignerTheme();
45+
if (extTheme) {
46+
return extTheme;
47+
}
48+
49+
// If OpenUI5Support is enabled, try to find out if it loaded variables
50+
const OpenUI5Support = getFeature("OpenUI5Support");
51+
if (OpenUI5Support) {
52+
const varsLoaded = OpenUI5Support.cssVariablesLoaded();
53+
if (varsLoaded) {
54+
return {
55+
themeName: OpenUI5Support.getConfigurationSettingsObject().theme, // just themeName, baseThemeName is only relevant for custom themes
56+
};
57+
}
58+
}
59+
};
60+
61+
const applyTheme = async theme => {
62+
const extTheme = detectExternalTheme();
63+
64+
// Only load theme_base properties if there is no externally loaded theme, or there is, but it is not being loaded
65+
if (!extTheme || theme !== extTheme.themeName) {
66+
await loadThemeBase(theme);
67+
} else {
68+
deleteThemeBase();
69+
}
70+
71+
// Always load component packages properties. For non-registered themes, try with the base theme, if any
72+
const packagesTheme = isThemeRegistered(theme) ? theme : extTheme && extTheme.baseThemeName;
73+
await loadComponentPackages(packagesTheme);
74+
75+
// When changing the theme, run the ponyfill immediately
76+
if (ponyfillNeeded()) {
77+
runPonyfill();
78+
}
79+
};
80+
1981
export default applyTheme;
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import createStyleInHead from "../util/createStyleInHead.js";
2-
import { ponyfillNeeded, runPonyfill } from "./CSSVarsPonyfill.js";
32

43
/**
54
* Creates/updates a style element holding all CSS Custom Properties
65
* @param cssText
76
* @param packageName
87
*/
98
const createThemePropertiesStyleTag = (cssText, packageName) => {
10-
// Needed for all browsers
119
const styleElement = document.head.querySelector(`style[data-ui5-theme-properties="${packageName}"]`);
1210
if (styleElement) {
1311
styleElement.textContent = cssText || ""; // in case of undefined
@@ -17,11 +15,6 @@ const createThemePropertiesStyleTag = (cssText, packageName) => {
1715
};
1816
createStyleInHead(cssText, attributes);
1917
}
20-
21-
// When changing the theme, run the ponyfill immediately
22-
if (ponyfillNeeded()) {
23-
runPonyfill();
24-
}
2518
};
2619

2720
export default createThemePropertiesStyleTag;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const getThemeMetadata = () => {
2+
// Check if the class was already applied, most commonly to the link/style tag with the CSS Variables
3+
let el = document.querySelector(".sapThemeMetaData-Base-baseLib");
4+
if (el) {
5+
return getComputedStyle(el).backgroundImage;
6+
}
7+
8+
el = document.createElement("span");
9+
el.style.display = "none";
10+
el.classList.add("sapThemeMetaData-Base-baseLib");
11+
document.body.appendChild(el);
12+
const metadata = getComputedStyle(el).backgroundImage;
13+
document.body.removeChild(el);
14+
15+
return metadata;
16+
};
17+
18+
const parseThemeMetadata = metadataString => {
19+
const params = /\(["']?data:text\/plain;utf-8,(.*?)['"]?\)$/i.exec(metadataString);
20+
if (params && params.length >= 2) {
21+
let paramsString = params[1];
22+
paramsString = paramsString.replace(/\\"/g, `"`);
23+
if (paramsString.charAt(0) !== "{" && paramsString.charAt(paramsString.length - 1) !== "}") {
24+
try {
25+
paramsString = decodeURIComponent(paramsString);
26+
} catch (ex) {
27+
console.warn("Malformed theme metadata string, unable to decodeURIComponent"); // eslint-disable-line
28+
return;
29+
}
30+
}
31+
try {
32+
return JSON.parse(paramsString);
33+
} catch (ex) {
34+
console.warn("Malformed theme metadata string, unable to parse JSON"); // eslint-disable-line
35+
}
36+
}
37+
};
38+
39+
const processThemeMetadata = metadata => {
40+
let themeName;
41+
let baseThemeName;
42+
43+
try {
44+
themeName = metadata.Path.match(/\.([^.]+)\.css_variables$/)[1];
45+
baseThemeName = metadata.Extends[0];
46+
} catch (ex) {
47+
console.warn("Malformed theme metadata Object", metadata); // eslint-disable-line
48+
return;
49+
}
50+
51+
return {
52+
themeName,
53+
baseThemeName,
54+
};
55+
};
56+
57+
const getThemeDesignerTheme = () => {
58+
const metadataString = getThemeMetadata();
59+
if (!metadataString || metadataString === "none") {
60+
return;
61+
}
62+
63+
const metadata = parseThemeMetadata(metadataString);
64+
return processThemeMetadata(metadata);
65+
};
66+
67+
export default getThemeDesignerTheme;

packages/base/test/dev-helpers/ExternalThemePresent.js

-3
This file was deleted.

packages/main/bundle.esm.js

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// import "@ui5/webcomponents-base/test/dev-helpers/ExternalThemePresent.js";
2-
31
// OpenUI5 integration
42
import "@ui5/webcomponents-base/dist/features/OpenUI5Support.js";
53

packages/main/test/pages/Kitchen.html

+18-1
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,33 @@
1313
*:not(:defined) {
1414
visibility: hidden;
1515
}
16+
.body-bg {
17+
background-color: var(--sapBackgroundColor);
18+
}
1619
</style>
1720

21+
<!-- CUSTOM THEME: my_custom_theme
22+
<link rel="stylesheet" type="text/css" href="./css/css_variables.css">
23+
-->
24+
25+
<!-- OPENUI5 WITH CSS VARIABLES
26+
<script id='sap-ui-bootstrap'
27+
src='https://openui5.hana.ondemand.com/resources/sap-ui-core.js'
28+
data-sap-ui-theme='sap_belize_hcb'
29+
data-sap-ui-libs='sap.m'
30+
data-sap-ui-preload="async"
31+
data-sap-ui-xx-cssVariables="true"
32+
></script>
33+
-->
34+
1835
<script src="../../webcomponentsjs/webcomponents-loader.js"></script>
1936
<script src="../../resources/bundle.esm.js" type="module"></script>
2037
<script nomodule src="../../resources/bundle.es5.js"></script>
2138

2239
<script>delete Document.prototype.adoptedStyleSheets;</script>
2340
</head>
2441

25-
<body style="background-color: var(--sapBackgroundColor);">
42+
<body class="body-bg">
2643
<div id="nav" class="navbar">
2744
<ui5-togglebutton id="menu-btn" icon="menu" design="Transparent"></ui5-togglebutton>
2845
<ui5-title level="H5">UI5 webcomponents</ui5-title>

0 commit comments

Comments
 (0)