diff --git a/docs/2-advanced/01-configuration.md b/docs/2-advanced/01-configuration.md index dd2bd817181a..9d754679a3a3 100644 --- a/docs/2-advanced/01-configuration.md +++ b/docs/2-advanced/01-configuration.md @@ -15,6 +15,7 @@ There are several configuration settings that affect all UI5 Web Components glob | [secondaryCalendarType](#calendarType) | `Gregorian`, `Islamic`, `Buddhist`, `Japanese`, `Persian` | `undefined` | Default secondary calendar type to be used for date-related components | Date/time components (`ui5-date-picker`, etc.) | | [noConflict](#noConflict) | `true`, `false` | `false` | When set to true, all events will be fired with a `ui5-` prefix only | Components that fire events (most do) | | [formatSettings](#formatSettings) | See the [Format settings](#formatSettings) section below | `{}` | Allows to override locale-specific configuration | Date/time components (`ui5-date-picker`, etc.) | +| [loadBaseThemingCSSVariables](#loadBaseThemingCSSVariables) | `true`, `false` | `true` | Whether to load CSS variables from `@sap-theming/theming-base-content` package | Framework | | [fetchDefaultLanguage](#fetchDefaultLanguage) | `true`, `false` | `false` | Whether to fetch assets even for the default language | Framework | | [defaultFontLoading](#defaultFontLoading) | `true`, `false` | `true` | Whether to fetch default font faces | Framework | | [enableDefaultTooltips](#enableDefaultTooltips) | `true`, `false` | `true` | Whether to display default tooltips | Components (Icon, Button, RatingIndicator, etc.) | @@ -219,6 +220,23 @@ Example: } ``` + +### loadBaseThemingCSSVariables + + +This configuration option controls whether the framework should load the CSS variables from `@sap-theming/theming-base-content` package. + +Typically, you would not need to modify this setting. However, if your application provides its **own** instance or version of `@sap-theming/theming-base-content` and you prefer the framework **not** to load the built-in base theming CSS variables, you can set `loadBaseThemingCSSVariables` to `false`. + +Example: +```html + +``` + ### defaultFontLoading diff --git a/packages/base/src/InitialConfiguration.ts b/packages/base/src/InitialConfiguration.ts index 93eac751df77..d1f223dc2ddf 100644 --- a/packages/base/src/InitialConfiguration.ts +++ b/packages/base/src/InitialConfiguration.ts @@ -24,6 +24,7 @@ type InitialConfig = { formatSettings: FormatSettings, fetchDefaultLanguage: boolean, defaultFontLoading: boolean, + loadBaseThemingCSSVariables: boolean, enableDefaultTooltips: boolean, }; @@ -40,6 +41,7 @@ let initialConfig: InitialConfig = { formatSettings: {}, fetchDefaultLanguage: false, defaultFontLoading: true, + loadBaseThemingCSSVariables: true, enableDefaultTooltips: true, }; @@ -94,6 +96,11 @@ const getDefaultFontLoading = () => { return initialConfig.defaultFontLoading; }; +const getLoadBaseThemingCSSVariables = () => { + initConfiguration(); + return initialConfig.loadBaseThemingCSSVariables; +}; + const getEnableDefaultTooltips = () => { initConfiguration(); return initialConfig.enableDefaultTooltips; @@ -255,6 +262,7 @@ export { getTimezone, getFormatSettings, getDefaultFontLoading, + getLoadBaseThemingCSSVariables, resetConfiguration, getEnableDefaultTooltips, }; diff --git a/packages/base/src/config/Theme.ts b/packages/base/src/config/Theme.ts index 4f5791796a05..4e14d3e32783 100644 --- a/packages/base/src/config/Theme.ts +++ b/packages/base/src/config/Theme.ts @@ -1,4 +1,4 @@ -import { getTheme as getConfiguredTheme } from "../InitialConfiguration.js"; +import { getTheme as getConfiguredTheme, getLoadBaseThemingCSSVariables as getConfiguredLoadBaseThemingCSSVariables } from "../InitialConfiguration.js"; import { reRenderAllUI5Elements } from "../Render.js"; import applyTheme from "../theming/applyTheme.js"; import getThemeDesignerTheme from "../theming/getThemeDesignerTheme.js"; @@ -7,6 +7,7 @@ import { boot, isBooted } from "../Boot.js"; import { attachConfigurationReset } from "./ConfigurationReset.js"; let curTheme: string | undefined; +let loadBaseThemingCSSVariables: boolean | undefined; attachConfigurationReset(() => { curTheme = undefined; @@ -57,6 +58,36 @@ const getDefaultTheme = (): string => { return DEFAULT_THEME; }; +/** + * Returns the current configuration for loading base theming CSS variables. + * + * @public + * @since 2.17.0 + * @returns {boolean} + */ +const getLoadBaseThemingCSSVariables = () => { + if (loadBaseThemingCSSVariables === undefined) { + loadBaseThemingCSSVariables = getConfiguredLoadBaseThemingCSSVariables(); + } + + return loadBaseThemingCSSVariables; +}; + +/** + * Configures whether to load base theming CSS variables. + * + * - When set to `true` (default), base theming CSS variables are loaded. + * - When set to `false`, base theming CSS variables are not loaded. + * + * **Note:** This method should be called before the boot process. + * + * @public + * @since 2.17.0 + */ +const setLoadBaseThemingCSSVariables = (value: boolean) => { + loadBaseThemingCSSVariables = value; +}; + /** * Returns if the given theme name is the one currently applied. * @private @@ -98,4 +129,6 @@ export { isLegacyThemeFamily, isLegacyThemeFamilyAsync, getDefaultTheme, + getLoadBaseThemingCSSVariables, + setLoadBaseThemingCSSVariables, }; diff --git a/packages/base/src/theming/applyTheme.ts b/packages/base/src/theming/applyTheme.ts index dfc0e799c4c2..fc435e0ea439 100644 --- a/packages/base/src/theming/applyTheme.ts +++ b/packages/base/src/theming/applyTheme.ts @@ -5,6 +5,7 @@ import { fireThemeLoaded } from "./ThemeLoaded.js"; import { attachCustomThemeStylesToHead, getThemeRoot } from "../config/ThemeRoot.js"; import { DEFAULT_THEME } from "../generated/AssetParameters.js"; import { getCurrentRuntimeIndex } from "../Runtimes.js"; +import { getLoadBaseThemingCSSVariables } from "../config/Theme.js"; // eslint-disable-next-line export let _lib = "ui5"; @@ -33,6 +34,10 @@ const loadComponentPackages = async (theme: string, externalThemeName?: string) const registeredPackages = getRegisteredPackages(); const packagesStylesPromises = [...registeredPackages].map(async packageName => { + if (getLoadBaseThemingCSSVariables() !== true && packageName === `${BASE_THEME_PACKAGE}-raw`) { + return; + } + if (packageName === BASE_THEME_PACKAGE) { return; } diff --git a/packages/compat/cypress/specs/Table.cy.tsx b/packages/compat/cypress/specs/Table.cy.tsx index 516307524dc4..31b4c1bc4a91 100644 --- a/packages/compat/cypress/specs/Table.cy.tsx +++ b/packages/compat/cypress/specs/Table.cy.tsx @@ -509,7 +509,6 @@ describe("Table general interaction", () => { cy.get("@popinChange") .should(stub => { expect(stub).to.have.been.calledTwice; - debugger // @ts-ignore expect(stub.args.slice(-1)[0][0].detail.poppedColumns.length).to.equal(2); }) diff --git a/packages/main/src/bundle.common.bootstrap.ts b/packages/main/src/bundle.common.bootstrap.ts index bfe51ae57a00..fd2484976fd4 100644 --- a/packages/main/src/bundle.common.bootstrap.ts +++ b/packages/main/src/bundle.common.bootstrap.ts @@ -60,7 +60,13 @@ import { resetConfiguration } from "@ui5/webcomponents-base/dist/InitialConfigur import { sanitizeHTML, URLListValidator } from "@ui5/webcomponents-base/dist/util/HTMLSanitizer.js"; import { getAnimationMode, setAnimationMode } from "@ui5/webcomponents-base/dist/config/AnimationMode.js"; -import { getTheme, setTheme, isLegacyThemeFamily } from "@ui5/webcomponents-base/dist/config/Theme.js"; +import { + getTheme, + setTheme, + isLegacyThemeFamily, + getLoadBaseThemingCSSVariables, + setLoadBaseThemingCSSVariables, +} from "@ui5/webcomponents-base/dist/config/Theme.js"; import { getThemeRoot, setThemeRoot } from "@ui5/webcomponents-base/dist/config/ThemeRoot.js"; import { getTimezone, setTimezone } from "@ui5/webcomponents-base/dist/config/Timezone.js"; import { getLanguage, setLanguage } from "@ui5/webcomponents-base/dist/config/Language.js"; @@ -104,6 +110,8 @@ const testAssets = { getFirstDayOfWeek, getTimezone, setTimezone, + getLoadBaseThemingCSSVariables, + setLoadBaseThemingCSSVariables, }, invisibleMessage: { announce, diff --git a/packages/main/test/pages/Button.html b/packages/main/test/pages/Button.html index 77fda879404d..68c5f049ab42 100644 --- a/packages/main/test/pages/Button.html +++ b/packages/main/test/pages/Button.html @@ -18,7 +18,7 @@ - + Reject Add diff --git a/packages/main/test/pages/theming/Themes7.html b/packages/main/test/pages/theming/Themes7.html new file mode 100644 index 000000000000..d6932380a927 --- /dev/null +++ b/packages/main/test/pages/theming/Themes7.html @@ -0,0 +1,21 @@ + + + + + + Theming + + + + + + + + Some content + + + \ No newline at end of file diff --git a/packages/main/test/pages/theming/Themes8.html b/packages/main/test/pages/theming/Themes8.html new file mode 100644 index 000000000000..b724131f8cb4 --- /dev/null +++ b/packages/main/test/pages/theming/Themes8.html @@ -0,0 +1,21 @@ + + + + + + Theming + + + + + + + + Some content + + + \ No newline at end of file diff --git a/packages/theming/package-scripts.cjs b/packages/theming/package-scripts.cjs index 41afd71c7137..a3a68d748144 100644 --- a/packages/theming/package-scripts.cjs +++ b/packages/theming/package-scripts.cjs @@ -24,7 +24,11 @@ module.exports = { src: `ui5nps-script "${TOOLS_LIB}copy-and-watch/index.js" "src/**/*.{json}" dist/`, typescript: "tsc", postcss: `ui5nps-script "${TOOLS_LIB}/css-processors/css-processor-themes.mjs"`, - jsonImports: `ui5nps-script "${jsonImportsScript}" src/themes src/generated/json-imports`, + jsonImports: { + default: "ui5nps build.jsonImports.scoped build.jsonImports.raw", + scoped: `ui5nps-script "${jsonImportsScript}" src/themes src/generated/json-imports`, + raw: `ui5nps-script "${jsonImportsScript}" src/themes src/generated/json-imports -raw`, + } }, generateReport: `ui5nps-script "${generateReportScript}"`, }, diff --git a/packages/theming/src/Assets-fetch.ts b/packages/theming/src/Assets-fetch.ts index ac3ffb58ab23..337bba7e811c 100644 --- a/packages/theming/src/Assets-fetch.ts +++ b/packages/theming/src/Assets-fetch.ts @@ -1,2 +1,3 @@ // The theming package provides theming assets only import "./generated/json-imports/Themes-fetch.js"; +import "./generated/json-imports/Themes-raw-fetch.js"; diff --git a/packages/theming/src/Assets-node.ts b/packages/theming/src/Assets-node.ts index ed2fcb45449c..7ec779239341 100644 --- a/packages/theming/src/Assets-node.ts +++ b/packages/theming/src/Assets-node.ts @@ -3,7 +3,7 @@ * * It serves as an alternative to the `Assets` and `Assets-fetch` modules and supports the * `with: { type: 'json' }` import attribute for loading JSON files. - * + * * This import attribute is required in some environments, such as Node.js with server-side rendering (SSR). * * Example usage: @@ -11,3 +11,4 @@ */ import "./generated/json-imports/Themes-node.js"; +import "./generated/json-imports/Themes-raw-node.js"; diff --git a/packages/theming/src/Assets.ts b/packages/theming/src/Assets.ts index a8e06e58281f..87107237a47b 100644 --- a/packages/theming/src/Assets.ts +++ b/packages/theming/src/Assets.ts @@ -1,2 +1,3 @@ // The theming package provides theming assets only import "./generated/json-imports/Themes.js"; +import "./generated/json-imports/Themes-raw.js"; diff --git a/packages/tools/lib/css-processors/css-processor-themes.mjs b/packages/tools/lib/css-processors/css-processor-themes.mjs index 656402979911..3369f86bf946 100644 --- a/packages/tools/lib/css-processors/css-processor-themes.mjs +++ b/packages/tools/lib/css-processors/css-processor-themes.mjs @@ -9,24 +9,28 @@ import { writeFileIfChanged, getFileContent } from "./shared.mjs"; import { scopeUi5Variables, scopeThemingVariables } from "./scope-variables.mjs"; import { pathToFileURL } from "url"; -async function processThemingPackageFile(f) { +async function processThemingPackageFile(f, scope = true) { const selector = ':root'; const newRule = postcss.rule({ selector }); const result = await postcss().process(f.text); result.root.walkRules(selector, rule => { for (const decl of rule.nodes) { - if (decl.type !== 'decl' ) { + if (decl.type !== 'decl') { continue; } else if (decl.prop.startsWith('--sapFontUrl')) { continue; } else if (!decl.prop.startsWith('--sap')) { newRule.append(decl.clone()); } else { - const originalProp = decl.prop; - const originalValue = decl.value; - - newRule.append(decl.clone({ prop: originalProp.replace("--sap", "--ui5-sap"), value: `var(${originalProp}, ${originalValue})` })); + if (scope) { + const originalProp = decl.prop; + const originalValue = decl.value; + + newRule.append(decl.clone({ prop: originalProp.replace("--sap", "--ui5-sap"), value: `var(${originalProp}, ${originalValue})` })); + } else { + newRule.append(decl.clone()); + } } } }); @@ -43,6 +47,24 @@ async function processComponentPackageFile(f, packageJSON) { return result; } +async function writeProcessedContent(basePath, content, packageJSON, extension) { + const cssPath = basePath; + const jsonPath = basePath.replace(/dist[\/\\]css/, "dist/generated/assets").replace(".css", ".css.json"); + const jsPath = basePath.replace(/dist[\/\\]css/, "src/generated/").replace(".css", extension); + + // Write CSS file + await mkdir(path.dirname(cssPath), { recursive: true }); + await writeFile(cssPath, content); + + // Write JSON file + await mkdir(path.dirname(jsonPath), { recursive: true }); + await writeFileIfChanged(jsonPath, JSON.stringify(content)); + + // Write JS/TS file + const jsContent = getFileContent(packageJSON.name, `\`${content}\``); + await mkdir(path.dirname(jsPath), { recursive: true }); + await writeFileIfChanged(jsPath, jsContent); +} async function generate(argv) { const tsMode = process.env.UI5_TS === "true"; @@ -55,27 +77,27 @@ async function generate(argv) { ]); const restArgs = argv.slice(2); - let scopingPlugin = { + const scopingPlugin = { name: 'scoping', setup(build) { build.initialOptions.write = false; build.onEnd(result => { result.outputFiles.forEach(async f => { - let newText = f.path.includes("packages/theming") ? await processThemingPackageFile(f) : await processComponentPackageFile(f, packageJSON); - - await mkdir(path.dirname(f.path), { recursive: true }); - writeFile(f.path, newText); - - // JSON - const jsonPath = f.path.replace(/dist[\/\\]css/, "dist/generated/assets").replace(".css", ".css.json"); - await mkdir(path.dirname(jsonPath), { recursive: true }); - writeFileIfChanged(jsonPath, JSON.stringify(newText)); - - // JS/TS - const jsPath = f.path.replace(/dist[\/\\]css/, "src/generated/").replace(".css", extension); - const jsContent = getFileContent(packageJSON.name, "\`" + newText + "\`"); - writeFileIfChanged(jsPath, jsContent); + if (f.path.includes("packages/theming")) { + const scopedText = await processThemingPackageFile(f); + const originalText = await processThemingPackageFile(f, false); + + // Write scoped version + await writeProcessedContent(f.path, scopedText, packageJSON, extension); + + // Write raw version + const originalPath = f.path.replace(/parameters-bundle.css$/, "parameters-bundle-raw.css"); + await writeProcessedContent(originalPath, originalText, packageJSON, extension); + } else { + const processedText = await processComponentPackageFile(f, packageJSON); + await writeProcessedContent(f.path, processedText, packageJSON, extension); + } }); }) }, diff --git a/packages/tools/lib/generate-json-imports/themes.js b/packages/tools/lib/generate-json-imports/themes.js index b61d28a7226a..87f3e0e0369e 100644 --- a/packages/tools/lib/generate-json-imports/themes.js +++ b/packages/tools/lib/generate-json-imports/themes.js @@ -7,9 +7,10 @@ const ext = isTypeScript ? 'ts' : 'js'; const generate = async (argv) => { const inputFolder = path.normalize(argv[2]); - const outputFileDynamic = path.normalize(`${argv[3]}/Themes.${ext}`); - const outputFileDynamicImportJSONAttr = path.normalize(`${argv[3]}/Themes-node.${ext}`); - const outputFileFetchMetaResolve = path.normalize(`${argv[3]}/Themes-fetch.${ext}`); + const outputSuffix = argv[4] || ""; + const outputFileDynamic = path.normalize(`${argv[3]}/Themes${outputSuffix}.${ext}`); + const outputFileDynamicImportJSONAttr = path.normalize(`${argv[3]}/Themes${outputSuffix}-node.${ext}`); + const outputFileFetchMetaResolve = path.normalize(`${argv[3]}/Themes${outputSuffix}-fetch.${ext}`); // All supported optional themes const allThemes = assets.themes.all; @@ -24,9 +25,9 @@ const generate = async (argv) => { const packageName = JSON.parse(await fs.readFile("package.json")).name; const availableThemesArray = `[${themesOnFileSystem.map(theme => `"${theme}"`).join(", ")}]`; - const dynamicImportLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-${theme.replace("_", "-")}-parameters-bundle" */"../assets/themes/${theme}/parameters-bundle.css.json")).default;`).join("\n"); - const dynamicImportJSONAttrLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-${theme.replace("_", "-")}-parameters-bundle" */"../assets/themes/${theme}/parameters-bundle.css.json", {with: { type: 'json'}})).default;`).join("\n"); - const fetchMetaResolveLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await fetch(new URL("../assets/themes/${theme}/parameters-bundle.css.json", import.meta.url))).json();`).join("\n"); + const dynamicImportLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-${theme.replace("_", "-")}-parameters-bundle" */"../assets/themes/${theme}/parameters-bundle${outputSuffix}.css.json")).default;`).join("\n"); + const dynamicImportJSONAttrLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-${theme.replace("_", "-")}-parameters-bundle" */"../assets/themes/${theme}/parameters-bundle${outputSuffix}.css.json", {with: { type: 'json'}})).default;`).join("\n"); + const fetchMetaResolveLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await fetch(new URL("../assets/themes/${theme}/parameters-bundle${outputSuffix}.css.json", import.meta.url))).json();`).join("\n"); // dynamic imports file content const contentDynamic = function (lines) { @@ -49,7 +50,7 @@ const loadAndCheck = async (themeName) => { }; ${availableThemesArray} - .forEach(themeName => registerThemePropertiesLoader(${packageName.split("").map(c => `"${c}"`).join(" + ")}, themeName, loadAndCheck)); + .forEach(themeName => registerThemePropertiesLoader(${`${packageName + outputSuffix}`.split("").map(c => `"${c}"`).join(" + ")}, themeName, loadAndCheck)); `; }