Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { fetchCldr } from "./asset-registries/LocaleData.js";
import getLocale from "./locale/getLocale.js";
import { getLanguageChangePending } from "./config/Language.js";
import createInstanceChecker from "./util/createInstanceChecker.js";
import { registerThemeInstance, unregisterThemeInstance } from "./theming/ThemeManager.js";

const DEV_MODE = true;
let autoId = 0;
Expand Down Expand Up @@ -211,6 +212,7 @@ abstract class UI5Element extends HTMLElement {
_internals: ElementInternals;
_individualSlot?: string;
_getRealDomRef?: () => HTMLElement;
_parametersStyleSheet?: Array<CSSStyleSheet>;

static template?: TemplateFunction;
static _metadata: UI5ElementMetadata;
Expand Down Expand Up @@ -331,6 +333,7 @@ abstract class UI5Element extends HTMLElement {
const slotsAreManaged = ctor.getMetadata().slotsAreManaged();

this._inDOM = true;
registerThemeInstance(this);

if (slotsAreManaged) {
// always register the observer before yielding control to the main thread (await)
Expand Down Expand Up @@ -369,6 +372,7 @@ abstract class UI5Element extends HTMLElement {
const slotsAreManaged = ctor.getMetadata().slotsAreManaged();

this._inDOM = false;
unregisterThemeInstance(this);

if (slotsAreManaged) {
this._stopObservingDOMChildren();
Expand Down
12 changes: 12 additions & 0 deletions packages/base/src/decorators/customElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type UI5Element from "../UI5Element.js";
import type { Renderer } from "../UI5Element.js";
import type { TemplateFunction as Template } from "../renderer/executeTemplate.js";
import type { ComponentStylesData as Styles } from "../types.js";
import { registerParametersLoadersForTag } from "../theming/ThemeManager.js";

type ParametersLoader = (themeName: string) => Promise<string>;

/**
* Returns a custom element class decorator.
Expand All @@ -22,6 +25,10 @@ const customElement = (tagNameOrComponentSettings: string | {
* The styles to be injected into the shadow root of the custom element.
*/
styles?: Styles,
/**
* The theme parameters loaders to be registered for this custom element.
*/
cssParameters?: ParametersLoader | Array<ParametersLoader>,
/**
* The template function of the custom element - must match the renderer.
*/
Expand Down Expand Up @@ -79,9 +86,14 @@ const customElement = (tagNameOrComponentSettings: string | {
fastNavigation,
formAssociated,
shadowRootOptions,
cssParameters,
} = tagNameOrComponentSettings;

target.metadata.tag = tag;
if (tag && cssParameters) {
const loaders = Array.isArray(cssParameters) ? cssParameters : [cssParameters];
registerParametersLoadersForTag(tag, loaders);
}
if (languageAware) {
target.metadata.languageAware = languageAware;
}
Expand Down
135 changes: 135 additions & 0 deletions packages/base/src/theming/ThemeManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { getTheme } from "../config/Theme.js";
import { attachThemeLoaded } from "./ThemeLoaded.js";
import getConstructableStyle from "./getConstructableStyle.js";
import { getComponentStyles } from "./componentStyles.js";
import { getEffectiveScopingSuffixForTag } from "../CustomElementsScopeUtils.js";
import type UI5Element from "../UI5Element.js";

type ParametersLoader = (themeName: string) => Promise<string>;

type ParametersKey = string;

const parametersLoaders = new Map<string, Array<ParametersLoader>>();
const parametersSheets = new Map<ParametersLoader, CSSStyleSheet>();
const parametersSheetThemes = new Map<ParametersLoader, string>();
const parametersLoadPromises = new Map<ParametersLoader, Promise<void>>();
const parametersLoadErrors = new Map<ParametersLoader, Set<ParametersKey>>();

const instances = new Set<UI5Element>();

const buildKey = (tagName: string, themeName: string) => `${tagName}:${themeName}`;

const loadParametersIntoSheet = (loader: ParametersLoader, tagName: string, themeName: string, sheet: CSSStyleSheet) => {
if (parametersSheetThemes.get(loader) === themeName && !parametersLoadPromises.has(loader)) {
return;
}

if (parametersLoadPromises.has(loader) && parametersSheetThemes.get(loader) === themeName) {
return;
}

const key = buildKey(tagName, themeName);
const loadPromise = loader(themeName)
.then(cssText => {
sheet.replaceSync(cssText);
})
.catch(error => {
let loaderErrors = parametersLoadErrors.get(loader);
if (!loaderErrors) {
loaderErrors = new Set<ParametersKey>();
parametersLoadErrors.set(loader, loaderErrors);
}

if (!loaderErrors.has(key)) {
loaderErrors.add(key);
// eslint-disable-next-line no-console
console.warn(`[UI5] Failed to load theme parameters for ${tagName} (${themeName})`, error);
}
})
.finally(() => {
parametersLoadPromises.delete(loader);
});

parametersSheetThemes.set(loader, themeName);
parametersLoadPromises.set(loader, loadPromise);
};

const getParametersSheets = (tagName: string, themeName = getTheme()) => {
const loaders = parametersLoaders.get(tagName);
if (!loaders || !loaders.length) {
return [] as Array<CSSStyleSheet>;
}

return loaders.map(loader => {
let sheet = parametersSheets.get(loader);
if (!sheet) {
sheet = new CSSStyleSheet();
parametersSheets.set(loader, sheet);
}

loadParametersIntoSheet(loader, tagName, themeName, sheet);
return sheet;
});
};

const getParametersSheet = (tagName: string, themeName = getTheme()) => {
return getParametersSheets(tagName, themeName)[0];
};

const applyParametersToInstance = (instance: UI5Element, themeName: string) => {
const ctor = instance.constructor as typeof UI5Element;
const tagName = ctor.getMetadata().getTag();
const parametersSheetsForTag = getParametersSheets(tagName, themeName);
const shadowRoot = instance.shadowRoot;

instance._parametersStyleSheet = parametersSheetsForTag;

if (!shadowRoot) {
return;
}

shadowRoot.adoptedStyleSheets = [getComponentStyles(), ...parametersSheetsForTag, ...getConstructableStyle(ctor)];
};

const registerParametersLoader = (tagName: string, loader: ParametersLoader) => {
const loaders = parametersLoaders.get(tagName);
if (loaders) {
loaders.push(loader);
return;
}
parametersLoaders.set(tagName, [loader]);
};

const registerParametersLoaderForTag = (tagName: string, loader: ParametersLoader) => {
registerParametersLoader(tagName, loader);
const suffix = getEffectiveScopingSuffixForTag(tagName);
if (suffix) {
registerParametersLoader(`${tagName}-${suffix}`, loader);
}
};

const registerParametersLoadersForTag = (tagName: string, loaders: Array<ParametersLoader>) => {
loaders.forEach(loader => registerParametersLoaderForTag(tagName, loader));
};

const registerThemeInstance = (instance: UI5Element) => {
instances.add(instance);
};

const unregisterThemeInstance = (instance: UI5Element) => {
instances.delete(instance);
};

attachThemeLoaded(themeName => {
instances.forEach(instance => applyParametersToInstance(instance, themeName));
});

export {
registerParametersLoader,
registerParametersLoaderForTag,
registerParametersLoadersForTag,
getParametersSheets,
getParametersSheet,
registerThemeInstance,
unregisterThemeInstance,
};
37 changes: 19 additions & 18 deletions packages/base/src/theming/applyTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
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";

Check failure on line 10 in packages/base/src/theming/applyTheme.ts

View workflow job for this annotation

GitHub Actions / check

'getCurrentRuntimeIndex' is defined but never used
import { updateComponentStyles } from "./componentStyles.js";

Check failure on line 11 in packages/base/src/theming/applyTheme.ts

View workflow job for this annotation

GitHub Actions / check

'updateComponentStyles' is defined but never used

// eslint-disable-next-line
export let _lib = "ui5";
Expand Down Expand Up @@ -37,25 +37,26 @@
removeStyle("data-ui5-theme-properties", BASE_THEME_PACKAGE);
};

const loadComponentPackages = async (theme: string, externalThemeName?: string) => {

Check failure on line 40 in packages/base/src/theming/applyTheme.ts

View workflow job for this annotation

GitHub Actions / check

'externalThemeName' is defined but never used

Check failure on line 40 in packages/base/src/theming/applyTheme.ts

View workflow job for this annotation

GitHub Actions / check

'theme' is defined but never used
const registeredPackages = getRegisteredPackages();

const packagesStylesPromises = [...registeredPackages.entries()].map(async ([packageName, { cssVariablesTarget }]) => {
if (packageName === BASE_THEME_PACKAGE) {
return;
}

const cssData = await getThemeProperties(packageName, theme, externalThemeName);
if (cssData) {
if (cssVariablesTarget === "root") {
createOrUpdateStyle(cssData, `data-ui5-component-properties-${getCurrentRuntimeIndex()}`, packageName);
} else if (cssVariablesTarget === "host") {
updateComponentStyles(packageName, cssData);
}
}
});

return Promise.all(packagesStylesPromises);
// const registeredPackages = getRegisteredPackages();

// const packagesStylesPromises = [...registeredPackages.entries()].map(async ([packageName, { cssVariablesTarget }]) => {
// if (packageName === BASE_THEME_PACKAGE) {
// return;
// }

// const cssData = await getThemeProperties(packageName, theme, externalThemeName);
// if (cssData) {
// if (cssVariablesTarget === "root") {
// createOrUpdateStyle(cssData, `data-ui5-component-properties-${getCurrentRuntimeIndex()}`, packageName);
// } else if (cssVariablesTarget === "host") {
// updateComponentStyles(packageName, cssData);
// }
// }
// });

// return Promise.all(packagesStylesPromises);
return Promise.resolve();
};

const detectExternalTheme = async (theme: string) => {
Expand Down
6 changes: 5 additions & 1 deletion packages/base/src/updateShadowRoot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getConstructableStyle from "./theming/getConstructableStyle.js";
import { getComponentStyles } from "./theming/componentStyles.js";
import { getParametersSheets } from "./theming/ThemeManager.js";
import type UI5Element from "./UI5Element.js";

/**
Expand All @@ -15,7 +16,10 @@ const updateShadowRoot = (element: UI5Element) => {
return;
}

shadowRoot.adoptedStyleSheets = [getComponentStyles(), ...getConstructableStyle(ctor)];
const tagName = ctor.getMetadata().getTag();
const parametersSheets = getParametersSheets(tagName);
element._parametersStyleSheet = parametersSheets;
shadowRoot.adoptedStyleSheets = [getComponentStyles(), ...parametersSheets, ...getConstructableStyle(ctor)];
ctor.renderer(element, shadowRoot);
};

Expand Down
2 changes: 2 additions & 0 deletions packages/main/src/Button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {

// Styles
import buttonCss from "./generated/themes/Button.css.js";
import buttonParameters from "./generated/themes/parameters/Button-parameters.css.js";

/**
* Interface for components that may be used as a button inside numerous higher-order components
Expand Down Expand Up @@ -109,6 +110,7 @@ type ButtonClickEventDetail = {
renderer: jsxRenderer,
template: ButtonTemplate,
styles: buttonCss,
cssParameters: buttonParameters,
shadowRootOptions: { delegatesFocus: true },
})
/**
Expand Down
3 changes: 3 additions & 0 deletions packages/main/src/ToggleButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import ToggleButtonTemplate from "./ToggleButtonTemplate.js";

// Styles
import toggleBtnCss from "./generated/themes/ToggleButton.css.js";
import buttonParameters from "./generated/themes/parameters/Button-parameters.css.js";
import toggleButtonParameters from "./generated/themes/parameters/ToggleButton-parameters.css.js";

/**
* @class
Expand All @@ -33,6 +35,7 @@ import toggleBtnCss from "./generated/themes/ToggleButton.css.js";
tag: "ui5-toggle-button",
template: ToggleButtonTemplate,
styles: [Button.styles, toggleBtnCss],
cssParameters: [buttonParameters, toggleButtonParameters],
})
class ToggleButton extends Button {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "../base/ToggleButton-parameters.css";
7 changes: 5 additions & 2 deletions packages/tools/components-package/nps.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@ const getScripts = (options) => {
default: "ui5nps prepare lint build.bundle", // build.bundle2
templates: options.legacy ? `node "${LIB}hbs2ui5/index.js" -d src/ -o src/generated/templates` : "",
styles: {
default: `ui5nps-p build.styles.themes build.styles.components`, // concurently
default: `ui5nps-p build.styles.themes build.styles.components build.styles.parameters`, // concurently
themes: `ui5nps-script "${LIB}css-processors/css-processor-themes.mjs"`,
themesWithWatch: `ui5nps-script "${LIB}css-processors/css-processor-themes.mjs" -w`,
components: `ui5nps-script "${LIB}css-processors/css-processor-components.mjs"`,
componentsWithWatch: `ui5nps-script "${LIB}css-processors/css-processor-components.mjs" -w`,
parameters: `ui5nps-script "${LIB}css-processors/css-processor-parameters.mjs"`,
parametersWithWatch: `ui5nps-script "${LIB}css-processors/css-processor-parameters.mjs" -w`,
},
i18n: {
default: "ui5nps build.i18n.defaultsjs build.i18n.json",
Expand Down Expand Up @@ -135,9 +137,10 @@ const getScripts = (options) => {
props: 'ui5nps copyPropsWithWatch',
bundle: `ui5nps-script ${LIB}dev-server/dev-server.mjs ${viteConfig}`,
styles: {
default: 'ui5nps-p watch.styles.themes watch.styles.components', // concurently
default: 'ui5nps-p watch.styles.themes watch.styles.components watch.styles.parameters', // concurently
themes: 'ui5nps build.styles.themesWithWatch',
components: `ui5nps build.styles.componentsWithWatch`,
parameters: `ui5nps build.styles.parametersWithWatch`,
},
templates: options.legacy ? `ui5nps-script "${LIB}chokidar/chokidar.js" "src/**/*.hbs" "ui5nps build.templates"` : "",
i18n: `ui5nps-script "${LIB}chokidar/chokidar.js" "src/i18n/messagebundle.properties" "ui5nps build.i18n.defaultsjs"`
Expand Down
Loading
Loading