diff --git a/CHANGELOG.md b/CHANGELOG.md
index d33de5917..4bd639bee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,42 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
+### Added
+
+- `utils`
+ - `useComputedStyleFallback` option for `CssCustomProperties`: if the CSSOM does not provide any property name for the used selector, e.g. because the declarations are part of a constructed and adopted stylesheet, then the names are read from the computed style of the matching element; disabled by default because the computed style also contains all inherited custom properties
+
+### Changed
+
+- ``
+ - `shouldHaveMinimalSetup`: even if set to `false`, the edit history feature is still enabled explicitly
+- ``
+ - the two-column display is only used if the container is wide enough, this way property name columns do not get too small
+ - in narrower containers, property name and value are displayed as stacked rows
+ - make the breakpoint configurable via SCSS (`$eccgui-propertyvalue-size-column-breakpoint-small`)
+- `utils`
+ - values of CSS custom properties are resolved via the computed style of a matching element now, so they always represent what the browser really applies, e.g. references to other custom properties are already replaced
+
+### Fixed
+
+- ``
+ - inline `code` markup was hardly readable because of low contrast
+- ``
+ - fix width if it contains a `` with `` children
+ - the label tooltip is displayed correctly inside the container
+- ``
+ - default handle class names were removed as soon as an `intent` was given
+ - fix runtime error if the element holding the handle tools is not available
+- `utils`
+ - CSS custom properties are also found if their rule is nested inside a cascade layer (`@layer`) or another grouping rule like `@media`, `@supports` or `@container`; this affects `textToColorHash()`, `getEnabledColorsFromPalette()`, `getEnabledColorPropertiesFromPalette()` and `getColorConfiguration()`
+ - CSS custom properties are also found if the given selector is only one part of the selector list of a rule, e.g. `:root, :host`
+ - stylesheets that are loaded from another origin do not break the collection of CSS custom properties anymore
+ - collecting CSS custom properties does not throw an error anymore in test environments where the style declaration of a CSS rule is not an iterable object, e.g. in jsdom
+ - empty results are not cached anymore, this way they are read again if the stylesheets are loaded later on
+ - `minimalColorDistance` is part of the cache key of `getEnabledColorsFromPalette()` and `getEnabledColorPropertiesFromPalette()` now
+
+## [26.0.0] - 2026-07-08
+
This is a major release, and it might not be compatible with your current usage of our library. Please read about the necessary changes in the migration section below.
### Migration from v25 to v26
diff --git a/package.json b/package.json
index 2e1169209..51c9caa66 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@eccenca/gui-elements",
"description": "GUI elements based on other libraries, usable in React application, written in Typescript.",
- "version": "26.0.0",
+ "version": "26.1.0",
"license": "Apache-2.0",
"homepage": "https://github.com/eccenca/gui-elements",
"bugs": "https://github.com/eccenca/gui-elements/issues",
@@ -180,12 +180,21 @@
},
"resolutions": {
"**/@blueprintjs/core": "6.8.1",
- "node-sass-package-importer/**/postcss": "^8.5.10",
+ "node-sass-package-importer/**/postcss": "^8.5.18",
+ "stylelint-order/**/postcss": "^8.5.12",
+ "stylelint/**/postcss": "^8.5.18",
"hast-util-from-parse5": "8.0.0",
"**/lodash": "^4.18.1",
"**/minimatch": "^3.1.4",
"**/serialize-javascript": "^7.0.5",
- "**/ws": "^8.21.0"
+ "**/ws": "^8.21.0",
+ "babel-jest/**/js-yaml": "^3.15.1",
+ "stylelint/**/js-yaml": "^4.3.1",
+ "@eslint/eslintrc/**/js-yaml": "^4.3.1",
+ "**/nanoid": "^3.3.18",
+ "**/fast-uri": "^3.1.3",
+ "sass/**/immutable": "^5.1.8",
+ "**/brace-expansion": "^1.1.18"
},
"husky": {
"hooks": {
diff --git a/src/common/utils/CssCustomProperties.test.ts b/src/common/utils/CssCustomProperties.test.ts
new file mode 100644
index 000000000..b3df7a922
--- /dev/null
+++ b/src/common/utils/CssCustomProperties.test.ts
@@ -0,0 +1,63 @@
+import CssCustomProperties from "./CssCustomProperties";
+
+describe("CssCustomProperties in jsdom", () => {
+ beforeEach(() => {
+ const style = document.createElement("style");
+ style.textContent = `
+ :root { --eccgui-color-palette-blue-500: #1c6ecb; }
+ .config { --note-yellow: #ffde8f; }
+ `;
+ document.head.appendChild(style);
+ });
+
+ it("reads property names of a style rule without iterating the declaration", () => {
+ expect(
+ CssCustomProperties.listLocalCssStyleRuleProperties({
+ selectorText: ":root",
+ propertyType: "custom",
+ }),
+ ).toEqual([["--eccgui-color-palette-blue-500", "#1c6ecb"]]);
+ });
+
+ it("does not throw for scoped selectors", () => {
+ expect(() =>
+ new CssCustomProperties({ selectorText: ".config", returnObject: false }).customProperties(),
+ ).not.toThrow();
+ });
+
+ describe("useComputedStyleFallback", () => {
+ beforeEach(() => {
+ // the property is not part of any stylesheet, so the CSSOM does not know its name
+ const element = document.createElement("div");
+ element.classList.add("without-stylesheet");
+ element.style.setProperty("--only-computed", "#c0ffee");
+ document.body.appendChild(element);
+ });
+
+ it("is disabled by default", () => {
+ expect(
+ new CssCustomProperties({
+ selectorText: ".without-stylesheet",
+ }).customProperties(),
+ ).toEqual({});
+ });
+
+ it("reads the names from the computed style if the CSSOM does not provide any", () => {
+ expect(
+ new CssCustomProperties({
+ selectorText: ".without-stylesheet",
+ useComputedStyleFallback: true,
+ }).customProperties(),
+ ).toEqual({ "only-computed": "#c0ffee" });
+ });
+
+ it("is not used if the CSSOM provides names", () => {
+ expect(
+ new CssCustomProperties({
+ selectorText: ".config",
+ useComputedStyleFallback: true,
+ }).customProperties(),
+ ).toEqual({ "note-yellow": "#ffde8f" });
+ });
+ });
+});
diff --git a/src/common/utils/CssCustomProperties.ts b/src/common/utils/CssCustomProperties.ts
index 4d0b3c28e..1458e7849 100644
--- a/src/common/utils/CssCustomProperties.ts
+++ b/src/common/utils/CssCustomProperties.ts
@@ -1,12 +1,38 @@
/**
* Based on CSS Tricks tutorial.
* @see https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/
+ *
+ * The names of the custom properties are collected from the CSSOM, but their values are resolved
+ * via the computed style of a matching element.
+ * The CSSOM is only a reliable source for the names: declarations can be nested inside grouping
+ * rules (`@layer`, `@media`, `@supports`, `@container`), and their document order does not
+ * represent the cascade anymore, e.g. unlayered declarations win over layered ones.
+ *
+ * If the CSSOM does not provide any name, then the names can optionally be read from the computed
+ * style of the element as well, see the `useComputedStyleFallback` option.
*/
type AllowedCSSRule = CSSStyleRule | CSSPageRule; // they have necessary `selectorText` and `style` properties
+/** Rules that contain other rules, e.g. `@layer`, `@media`, `@supports`, `@container` or `@import`. */
+type CssRuleWithChildren = CSSRule & { cssRules?: CSSRuleList; styleSheet?: CSSStyleSheet };
+
+type CustomPropertyEntry = [string, string];
+
+/** Element that supports the CSS typed object model, we only need to iterate over the property names. */
+type TypedOMElement = Element & {
+ computedStyleMap?: () => { forEach: (callback: (value: unknown, propertyName: string) => void) => void };
+};
+
+const rootSelectors = [":root", "html", ":root:root"];
+const classSelectorPattern = /^(?:\.-?[_a-zA-Z][\w-]*)+$/;
+
interface getLocalCssStyleRulesProps {
cssRuleType?: "CSSStyleRule";
+ /**
+ * Selector the rule needs to use, e.g. `:root`.
+ * A rule matches if the selector is part of its selector list, e.g. `:root, :host`.
+ */
selectorText?: string;
}
interface getLocalCssStyleRulePropertiesProps extends getLocalCssStyleRulesProps {
@@ -16,11 +42,21 @@ interface getCustomPropertiesProps extends getLocalCssStyleRulesProps {
filterName?: (name: string) => boolean;
removeDashPrefix?: boolean;
returnObject?: boolean;
+ /**
+ * Read the property names from the computed style of the matching element if the CSSOM does not
+ * provide any name, e.g. because the declarations are part of a stylesheet that cannot be read
+ * or that is not listed by `document.styleSheets`, like constructed and adopted stylesheets.
+ *
+ * Disabled by default because it changes the result set: the computed style of an element also
+ * contains all custom properties it inherits from its ancestors, e.g. everything defined for
+ * `:root`, and it does not tell which rule declared them.
+ */
+ useComputedStyleFallback?: boolean;
}
export default class CssCustomProperties {
getterDefaultProps = {} as getCustomPropertiesProps;
- customprops = {};
+ customprops = {} as CustomPropertyEntry[] | Record;
constructor(props: getCustomPropertiesProps = {}) {
this.getterDefaultProps = props;
@@ -28,13 +64,14 @@ export default class CssCustomProperties {
// Methods
- customProperties = (props: getCustomPropertiesProps = {}): [string, string][] | Record => {
+ customProperties = (props: getCustomPropertiesProps = {}): CustomPropertyEntry[] | Record => {
// FIXME:
// in case of performance issues results should get saved at least into intern variables
// other cache strategies could be also tested
- if (Object.keys(this.customprops).length > 1) {
+ if (Object.keys(this.customprops).length > 0) {
return this.customprops;
}
+ // an empty result is not cached, the stylesheets may be loaded later on
const customprops = CssCustomProperties.listCustomProperties({
...this.getterDefaultProps,
...props,
@@ -44,7 +81,7 @@ export default class CssCustomProperties {
};
static listLocalStylesheets = (): CSSStyleSheet[] => {
- if (document && document.styleSheets) {
+ if (typeof document !== "undefined" && document.styleSheets) {
return (Array.from(document.styleSheets) as CSSStyleSheet[]).filter((stylesheet) => {
// is inline stylesheet or from same domain
if (!stylesheet.href) {
@@ -57,39 +94,109 @@ export default class CssCustomProperties {
return [] as CSSStyleSheet[];
};
+ /** Rules of a stylesheet are not readable if it was loaded from another origin. */
+ static readCssRules = (stylesheet: CSSStyleSheet): CSSRuleList | undefined => {
+ try {
+ return stylesheet.cssRules;
+ } catch {
+ return undefined;
+ }
+ };
+
static listLocalCssRules = (): CSSRule[] => {
+ const readStylesheets = new Set();
+
+ const collectRules = (rules: CSSRuleList | undefined): CSSRule[] => {
+ if (!rules) {
+ return [];
+ }
+
+ return Array.from(rules)
+ .map((rule) => {
+ const ruleWithChildren = rule as CssRuleWithChildren;
+
+ if (ruleWithChildren.styleSheet) {
+ // `@import` rule, e.g. `@import url(theme.css) layer(theme)`
+ if (readStylesheets.has(ruleWithChildren.styleSheet)) {
+ return [];
+ }
+ readStylesheets.add(ruleWithChildren.styleSheet);
+ return collectRules(CssCustomProperties.readCssRules(ruleWithChildren.styleSheet));
+ }
+
+ if (ruleWithChildren.cssRules) {
+ // rule that groups or nests other rules, e.g. `@layer`, `@media` or `@container`
+ return [rule, ...collectRules(ruleWithChildren.cssRules)];
+ }
+
+ return [rule];
+ })
+ .flat();
+ };
+
return CssCustomProperties.listLocalStylesheets()
.map((stylesheet) => {
- return Array.from(stylesheet.cssRules);
+ readStylesheets.add(stylesheet);
+ return collectRules(CssCustomProperties.readCssRules(stylesheet));
})
.flat();
};
+ static isCssStyleRule = (rule: CSSRule): rule is CSSStyleRule => {
+ if (typeof CSSStyleRule !== "undefined") {
+ return rule instanceof CSSStyleRule;
+ }
+ const cssrule = rule as AllowedCSSRule;
+ return !!cssrule.style && cssrule.selectorText !== undefined;
+ };
+
+ static matchesSelectorText = (rule: CSSStyleRule, selectorText: string): boolean => {
+ return (rule.selectorText ?? "")
+ .split(",")
+ .map((selector) => selector.trim())
+ .includes(selectorText.trim());
+ };
+
static listLocalCssStyleRules = (filter: getLocalCssStyleRulesProps = {}): CSSStyleRule[] => {
const { cssRuleType = "CSSStyleRule", selectorText } = filter;
const cssStyleRules = CssCustomProperties.listLocalCssRules().filter((rule) => {
- const cssrule = rule as AllowedCSSRule;
- if (cssrule.style) {
- if (cssrule.constructor.name !== cssRuleType) {
- return false;
- }
- if (!!selectorText && cssrule.selectorText !== selectorText) {
- return false;
- }
- return true;
- } else {
+ if (cssRuleType === "CSSStyleRule" && !CssCustomProperties.isCssStyleRule(rule)) {
+ return false;
+ }
+ if (!!selectorText && !CssCustomProperties.matchesSelectorText(rule as CSSStyleRule, selectorText)) {
return false;
}
+ return true;
});
return cssStyleRules as CSSStyleRule[];
};
+ /**
+ * Return the property names of a style declaration.
+ * The declaration is not iterated directly because it is not always an iterable object, e.g.
+ * the declarations of style rules are not iterable in test environments using jsdom.
+ */
+ static listStyleDeclarationPropertyNames = (style: CSSStyleDeclaration): string[] => {
+ const propertyNames = [] as string[];
+
+ for (let i = 0; i < style.length; i++) {
+ // `item()` is not available everywhere, the indexed getter is the more reliable one
+ const propertyName = style[i] ?? style.item?.(i);
+ if (propertyName) {
+ propertyNames.push(propertyName);
+ }
+ }
+
+ return propertyNames;
+ };
+
static listLocalCssStyleRuleProperties = (filter: getLocalCssStyleRulePropertiesProps = {}): string[][] => {
const { propertyType = "all", ...otherFilters } = filter;
return CssCustomProperties.listLocalCssStyleRules(otherFilters)
.map((cssrule) => {
- return [...(cssrule as CSSStyleRule).style].map((propertyname) => {
- return [propertyname.trim(), (cssrule as CSSStyleRule).style.getPropertyValue(propertyname).trim()];
+ const style = (cssrule as CSSStyleRule).style;
+ return CssCustomProperties.listStyleDeclarationPropertyNames(style).map((propertyname) => {
+ return [propertyname.trim(), style.getPropertyValue(propertyname).trim()];
});
})
.flat()
@@ -104,27 +211,141 @@ export default class CssCustomProperties {
});
};
+ /**
+ * Return the element the values of custom properties can be read from.
+ * `:root` and `html` are mapped to the root element of the document, for any other selector the
+ * first matching element is used.
+ * If nothing matches and the selector consists of class names only, then a temporary hidden
+ * element is created; the second item of the returned tuple removes it again.
+ */
+ static targetElement = (selectorText: string = ":root"): [Element | undefined, (() => void) | undefined] => {
+ if (typeof document === "undefined") {
+ return [undefined, undefined];
+ }
+
+ if (rootSelectors.includes(selectorText.trim().toLowerCase())) {
+ return [document.documentElement, undefined];
+ }
+
+ try {
+ const existingElement = document.querySelector(selectorText);
+ if (existingElement) {
+ return [existingElement, undefined];
+ }
+ } catch {
+ // selector cannot be used by the DOM API, we try to create a placeholder below
+ }
+
+ if (!classSelectorPattern.test(selectorText)) {
+ return [undefined, undefined];
+ }
+
+ // we need an element inside the DOM, otherwise the browser does not calculate the values for us
+ const placeholder = document.createElement("div");
+ placeholder.classList.add(...selectorText.split(".").filter(Boolean));
+ placeholder.setAttribute("style", "display: none");
+ (document.body ?? document.documentElement).appendChild(placeholder);
+
+ return [placeholder, () => placeholder.remove()];
+ };
+
+ /**
+ * Return the names of all custom properties that apply to an element, they are read from its
+ * computed style.
+ * Inherited custom properties are part of the computed style, so the returned list also contains
+ * the names of custom properties that were declared for one of the ancestors of the element.
+ */
+ static listElementCustomPropertyNames = (element: Element): string[] => {
+ const documentView = element.ownerDocument?.defaultView;
+ if (!documentView) {
+ return [];
+ }
+
+ const computedStyle = documentView.getComputedStyle(element);
+ const propertyNames = new Set(
+ CssCustomProperties.listStyleDeclarationPropertyNames(computedStyle).filter((propertyName) =>
+ propertyName.startsWith("--"),
+ ),
+ );
+
+ const typedOMElement = element as TypedOMElement;
+ if (propertyNames.size === 0 && typeof typedOMElement.computedStyleMap === "function") {
+ // Chromium before v141 does not enumerate custom properties in `getComputedStyle()`,
+ // but they are available via the typed object model
+ typedOMElement.computedStyleMap().forEach((_value, propertyName) => {
+ if (propertyName.startsWith("--")) {
+ propertyNames.add(propertyName);
+ }
+ });
+ }
+
+ return [...propertyNames];
+ };
+
+ /**
+ * Resolve the values of custom properties as they are applied to an element.
+ * Properties without a value are removed, they do not apply to the element, e.g. because they
+ * are only defined inside a currently not matching `@media` rule.
+ */
+ static resolveCustomPropertyValues = (element: Element, propertyNames: string[]): CustomPropertyEntry[] => {
+ const documentView = element.ownerDocument?.defaultView;
+ if (!documentView) {
+ return [];
+ }
+
+ const computedStyle = documentView.getComputedStyle(element);
+
+ return propertyNames
+ .map((propertyName): CustomPropertyEntry => {
+ return [propertyName, computedStyle.getPropertyValue(propertyName).trim()];
+ })
+ .filter(([, value]) => value !== "");
+ };
+
static listCustomProperties = (
props: getCustomPropertiesProps = {},
- ): [string, string][] | Record => {
- const { removeDashPrefix = true, returnObject = true, filterName = () => true, ...filterProps } = props;
+ ): CustomPropertyEntry[] | Record => {
+ const {
+ removeDashPrefix = true,
+ returnObject = true,
+ filterName = () => true,
+ useComputedStyleFallback = false,
+ ...filterProps
+ } = props;
- const customProperties = CssCustomProperties.listLocalCssStyleRuleProperties({
- ...filterProps,
- propertyType: "custom",
- })
- .filter((declaration) => {
- return filterName(declaration[0]);
- })
- .map((declaration) => {
- if (removeDashPrefix) {
- return [declaration[0].substr(2), declaration[1]];
- }
- return declaration;
+ // the CSSOM is used to get the names only, the cascade decides about the values
+ const propertyNames = [
+ ...new Set(
+ CssCustomProperties.listLocalCssStyleRuleProperties({
+ ...filterProps,
+ propertyType: "custom",
+ })
+ .map((declaration) => declaration[0])
+ .filter((propertyName) => filterName(propertyName)),
+ ),
+ ];
+
+ const [element, removePlaceholder] = CssCustomProperties.targetElement(filterProps.selectorText);
+
+ try {
+ const namesToResolve =
+ propertyNames.length === 0 && useComputedStyleFallback && element
+ ? CssCustomProperties.listElementCustomPropertyNames(element).filter((propertyName) =>
+ filterName(propertyName),
+ )
+ : propertyNames;
+
+ const customProperties = (
+ element ? CssCustomProperties.resolveCustomPropertyValues(element, namesToResolve) : []
+ ).map(([propertyName, value]): CustomPropertyEntry => {
+ return [removeDashPrefix ? propertyName.slice(2) : propertyName, value];
});
- return returnObject
- ? (Object.fromEntries(customProperties) as Record)
- : (customProperties as [string, string][]);
+ return returnObject
+ ? (Object.fromEntries(customProperties) as Record)
+ : (customProperties as CustomPropertyEntry[]);
+ } finally {
+ removePlaceholder?.();
+ }
};
}
diff --git a/src/common/utils/colorHash.ts b/src/common/utils/colorHash.ts
index 300ec5620..b7af083f0 100644
--- a/src/common/utils/colorHash.ts
+++ b/src/common/utils/colorHash.ts
@@ -27,22 +27,23 @@ export function getEnabledColorsFromPalette(props: getEnabledColorsProps): Color
const configId = JSON.stringify({
includePaletteGroup: props.includePaletteGroup,
includeColorWeight: props.includeColorWeight,
+ minimalColorDistance: props.minimalColorDistance,
});
if (getEnabledColorsFromPaletteCache.has(configId)) {
return getEnabledColorsFromPaletteCache.get(configId)!;
}
- const colorPropertiesFromPalette = Object.values(getEnabledColorPropertiesFromPalette(props));
+ const colorsFromPalette = getEnabledColorPropertiesFromPalette(props).map((color) => {
+ return Color(color[1]);
+ });
- getEnabledColorsFromPaletteCache.set(
- configId,
- colorPropertiesFromPalette.map((color) => {
- return Color(color[1]);
- }),
- );
+ if (colorsFromPalette.length > 0) {
+ // an empty result is not cached, the stylesheets may be loaded later on
+ getEnabledColorsFromPaletteCache.set(configId, colorsFromPalette);
+ }
- return getEnabledColorsFromPaletteCache.get(configId)!;
+ return colorsFromPalette;
}
export function getEnabledColorPropertiesFromPalette({
@@ -54,6 +55,7 @@ export function getEnabledColorPropertiesFromPalette({
const configId = JSON.stringify({
includePaletteGroup,
includeColorWeight,
+ minimalColorDistance,
});
if (getEnabledColorPropertiesFromPaletteCache.has(configId)) {
@@ -93,9 +95,12 @@ export function getEnabledColorPropertiesFromPalette({
}, colorsFromPaletteValues)
: colorsFromPaletteValues;
- getEnabledColorPropertiesFromPaletteCache.set(configId, colorsFromPaletteWithEnoughDistance);
+ if (colorsFromPaletteWithEnoughDistance.length > 0) {
+ // an empty result is not cached, the stylesheets may be loaded later on
+ getEnabledColorPropertiesFromPaletteCache.set(configId, colorsFromPaletteWithEnoughDistance);
+ }
- return getEnabledColorPropertiesFromPaletteCache.get(configId)!;
+ return colorsFromPaletteWithEnoughDistance;
}
function getColorcode(text: string): ColorOrFalse {
diff --git a/src/common/utils/getColorConfiguration.ts b/src/common/utils/getColorConfiguration.ts
index 11ac03717..8072697f9 100644
--- a/src/common/utils/getColorConfiguration.ts
+++ b/src/common/utils/getColorConfiguration.ts
@@ -17,49 +17,30 @@ const colorConfigurationMemo = new Map>();
const getColorConfiguration = (configId: colorconfigs): Record => {
if (!colorConfigurationMemo.has(configId)) {
const selectorClass = `${eccgui}-configuration--colors__${configId}`;
- colorConfigurationMemo.set(
- configId,
- Object.fromEntries(
- (
- new CssCustomProperties({
- selectorText: `.${selectorClass}`,
- removeDashPrefix: true,
- returnObject: false,
- }).customProperties() as string[][]
- ).map((setting) => {
- // check if the value could be a color
-
- let testColorValue = setting[1];
- // check if value itself is a reference to another css custom property
- if (testColorValue.slice(0, 3) === "var") {
- // we currently only extract the first part and ignore any fallbacks
- const customPropertyName = /var\(\s*(--[a-zA-Z0-9_-]+)/g.exec(testColorValue);
- if (customPropertyName && customPropertyName[1]) {
- let selectorElement = document.getElementsByClassName(selectorClass)[0];
- if (!selectorElement) {
- // we need to add an empty element that the JS API can read the value of the custom prop
- selectorElement = document.createElement("div");
- selectorElement.classList.add(selectorClass);
- selectorElement.setAttribute("style", "display: none");
- document.body.appendChild(selectorElement);
- }
- // only check 1 time, not recursive
- testColorValue = getComputedStyle(selectorElement).getPropertyValue(customPropertyName[1]);
- }
- }
-
- try {
- if (Color(testColorValue)) {
- return [setting[0], testColorValue];
- } else {
- return [setting[0], undefined];
- }
- } catch {
- return [setting[0], undefined];
- }
- }),
- ) as Record,
- );
+ const colorConfiguration = Object.fromEntries(
+ (
+ new CssCustomProperties({
+ selectorText: `.${selectorClass}`,
+ removeDashPrefix: true,
+ returnObject: false,
+ }).customProperties() as string[][]
+ ).map((setting) => {
+ // check if the value could be a color, references to other custom properties are already resolved
+ try {
+ Color(setting[1]);
+ return [setting[0], setting[1]];
+ } catch {
+ return [setting[0], undefined];
+ }
+ }),
+ ) as Record;
+
+ if (Object.keys(colorConfiguration).length === 0) {
+ // an empty result is not cached, the stylesheets may be loaded later on
+ return colorConfiguration;
+ }
+
+ colorConfigurationMemo.set(configId, colorConfiguration);
}
return colorConfigurationMemo.get(configId)!;
};
diff --git a/src/components/PropertyValuePair/PropertyName.tsx b/src/components/PropertyValuePair/PropertyName.tsx
index 75d0df33e..71e7eb7f6 100644
--- a/src/components/PropertyValuePair/PropertyName.tsx
+++ b/src/components/PropertyValuePair/PropertyName.tsx
@@ -43,9 +43,9 @@ export const PropertyName = ({