diff --git a/packages/base/cypress/specs/Boot.cy.tsx b/packages/base/cypress/specs/Boot.cy.tsx index d13bc8a1a81b..4318b72308c3 100644 --- a/packages/base/cypress/specs/Boot.cy.tsx +++ b/packages/base/cypress/specs/Boot.cy.tsx @@ -16,4 +16,50 @@ describe("Framework boot", () => { .invoke("hasStyle", "data-ui5-theme-properties", "@ui5/webcomponents-theming") .should("be.true"); }); + + it("Tests theme loading, when registered after boot + theme props are registered for the base theme of an external theme", () => { + const baseTheme = "sap_horizon" + const customTheme = "my_custom_theme"; + const customThemeStyles = `.sapThemeMetaData-Base-baseLib{background-image: url('data:text/plain;utf-8,{"Path": "Base.baseLib.${customTheme}.css_variables", "PathPattern": "/%frameworkId%/%libId%/%themeId%/%fileId%.css", "Extends": ["${baseTheme}"], "Tags": ["Fiori_3","LightColorScheme"], "FallbackThemeId": "${baseTheme}", "Engine": {"Name": "theming-engine", "Version": "16.1.10"}, "Version": { "Build": "11.32.2.20251030081249", "Source": "11.32.2"}}');}`; + const dataPropAttr = `data-ui5-component-properties-0`; + + // Append meta style and config script to the head setting the custom theme + cy.window() + .then($el => { + const style = document.createElement("style"); + style.innerHTML = customThemeStyles; + return $el.document.head.append(style); + }) + .then($el => { + const scriptElement = document.createElement("script"); + scriptElement.type = "application/json"; + scriptElement.setAttribute("data-ui5-config", "true"); + scriptElement.innerHTML = JSON.stringify({ + "theme": customTheme, + }); + return $el.document.head.append(scriptElement); + }); + + + // Trigger boot + cy.wrap({ boot }) + .invoke("boot"); + + // After boot, register theme properties loader for the base theme + cy.wrap({ registerThemePropertiesLoader }) + .invoke("registerThemePropertiesLoader", "@ui5/webcomponents-test", baseTheme, () => { + return Promise.resolve(`:root{--_ui5_internal_var: #ccc }`); + }); + + // Assert - theme properties for the base theme are loaded + cy.document().should(($doc) => { + const adoptedStyleSheets = $doc.adoptedStyleSheets; + const sheet = adoptedStyleSheets.find(sh => (sh as any)._ui5StyleId === `${dataPropAttr}|@ui5/webcomponents-test`); + + expect(sheet, "stylesheet should exist").to.exist; + expect(sheet!.cssRules, "cssRules should exist").to.exist; + expect(sheet!.cssRules.length, "cssRules should have at least one rule").to.be.greaterThan(0); + expect(sheet!.cssRules[0].cssText, "the custom variable should exist").to.include("--_ui5_internal_var: #ccc"); + }); + }); }); diff --git a/packages/base/src/Boot.ts b/packages/base/src/Boot.ts index dd03ed99a2b3..e5259a9cd8ed 100644 --- a/packages/base/src/Boot.ts +++ b/packages/base/src/Boot.ts @@ -3,7 +3,7 @@ import EventProvider from "./EventProvider.js"; import insertFontFace from "./FontFace.js"; import insertSystemCSSVars from "./SystemCSSVars.js"; import insertScrollbarStyles from "./ScrollbarStyles.js"; -import { getTheme } from "./config/Theme.js"; +import { getTheme, getBaseTheme } from "./config/Theme.js"; import applyTheme from "./theming/applyTheme.js"; import { registerCurrentRuntime } from "./Runtimes.js"; import { getFeature } from "./FeaturesRegistry.js"; @@ -88,8 +88,15 @@ const boot = async (): Promise => { * @param { string } theme */ const onThemeRegistered = (theme: string) => { - if (booted && theme === getTheme()) { // getTheme should only be called if "booted" is true - applyTheme(getTheme()); + if (!booted) { + return; + } + + const currentTheme = getTheme(); + const currentBaseTheme = getBaseTheme(); + + if (theme === currentTheme || theme === currentBaseTheme) { + applyTheme(currentTheme); } }; diff --git a/packages/base/src/config/Theme.ts b/packages/base/src/config/Theme.ts index 4f5791796a05..0cea7adcb994 100644 --- a/packages/base/src/config/Theme.ts +++ b/packages/base/src/config/Theme.ts @@ -7,6 +7,7 @@ import { boot, isBooted } from "../Boot.js"; import { attachConfigurationReset } from "./ConfigurationReset.js"; let curTheme: string | undefined; +let curBaseTheme: string | undefined; attachConfigurationReset(() => { curTheme = undefined; @@ -91,6 +92,24 @@ const isLegacyThemeFamilyAsync = async () => { const isKnownTheme = (theme: string) => SUPPORTED_THEMES.includes(theme); +/** + * Returns the base theme of external theme. + * @private + * @returns {string | undefined} the base theme name + */ +const getBaseTheme = (): string | undefined => { + return curBaseTheme; +}; + +/** + * Sets the base theme of the current external theme. + * @param { string | undefined } theme the name of the new base theme + * @private + */ +const setBaseTheme = (theme: string | undefined): void => { + curBaseTheme = theme; +}; + export { getTheme, setTheme, @@ -98,4 +117,6 @@ export { isLegacyThemeFamily, isLegacyThemeFamilyAsync, getDefaultTheme, + getBaseTheme, + setBaseTheme, }; diff --git a/packages/base/src/theming/applyTheme.ts b/packages/base/src/theming/applyTheme.ts index c8f7d729c156..9ed487f67b25 100644 --- a/packages/base/src/theming/applyTheme.ts +++ b/packages/base/src/theming/applyTheme.ts @@ -4,6 +4,7 @@ import getThemeDesignerTheme from "./getThemeDesignerTheme.js"; import { fireThemeLoaded } from "./ThemeLoaded.js"; import { getFeature } from "../FeaturesRegistry.js"; import { attachCustomThemeStylesToHead, getThemeRoot } from "../config/ThemeRoot.js"; +import { setBaseTheme } from "../config/Theme.js"; import type OpenUI5Support from "../features/OpenUI5Support.js"; import { DEFAULT_THEME } from "../generated/AssetParameters.js"; import { getCurrentRuntimeIndex } from "../Runtimes.js"; @@ -87,9 +88,13 @@ const applyTheme = async (theme: string) => { } // Always load component packages properties. For non-registered themes, try with the base theme, if any - const packagesTheme = isThemeRegistered(theme) ? theme : extTheme && extTheme.baseThemeName; - await loadComponentPackages(packagesTheme || DEFAULT_THEME, extTheme && extTheme.themeName === theme ? theme : undefined); + const externalThemeName = extTheme && extTheme.themeName === theme ? theme : undefined; + const baseThemeName = extTheme && extTheme.baseThemeName; + const effectiveThemeName = isThemeRegistered(theme) ? theme : (baseThemeName || DEFAULT_THEME); + await loadComponentPackages(effectiveThemeName, externalThemeName); + + setBaseTheme(baseThemeName); fireThemeLoaded(theme); };