From 6c861de52a670ede5fb7274f31fb38bce01a1cbd Mon Sep 17 00:00:00 2001 From: OlgaLarina Date: Mon, 25 May 2026 17:38:38 +0300 Subject: [PATCH 1/6] V3 add getComputedCssVariableValues --- packages/survey-core/entries/chunks/model.ts | 1 + .../survey-core/src/global_variables_utils.ts | 14 +++ packages/survey-core/src/question_rating.ts | 4 +- .../survey-core/src/question_signaturepad.ts | 7 +- .../survey-core/src/utils/css-variables.ts | 107 ++++++++++++++++++ packages/survey-core/src/utils/utils.ts | 8 -- 6 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 packages/survey-core/src/utils/css-variables.ts diff --git a/packages/survey-core/entries/chunks/model.ts b/packages/survey-core/entries/chunks/model.ts index a6fd587f8b..7a1b3e2707 100644 --- a/packages/survey-core/entries/chunks/model.ts +++ b/packages/survey-core/entries/chunks/model.ts @@ -314,6 +314,7 @@ export { export { IsMobile, IsTouch, _setIsTouch, _setIsTablet } from "../../src/utils/devices"; export * from "../../src/utils/browser"; export * from "../../src/utils/color"; +export * from "../../src/utils/css-variables"; export * from "../../src/utils/confirm-dialog"; export * from "../../src/utils/dom-utils"; export * from "../../src/utils/file-utils"; diff --git a/packages/survey-core/src/global_variables_utils.ts b/packages/survey-core/src/global_variables_utils.ts index 45c0b16689..1cdbc10938 100644 --- a/packages/survey-core/src/global_variables_utils.ts +++ b/packages/survey-core/src/global_variables_utils.ts @@ -101,6 +101,20 @@ export class DomDocumentHelper { if (!DomDocumentHelper.isAvailable()) return; return document.createElement(tagName); } + public static setStyles(element: HTMLElement, styles: Record): void { + if (!element || !styles) return; + + Object.keys(styles).forEach(property => { + element.style.setProperty(property, styles[property]); + }); + } + public static removeStyles(element: HTMLElement, properties: string[]): void { + if (!element || !properties) return; + + properties.forEach(property => { + element.style.removeProperty(property); + }); + } public static getComputedStyle(elt: Element): CSSStyleDeclaration { if (!DomDocumentHelper.isAvailable()) return new CSSStyleDeclaration(); return document.defaultView.getComputedStyle(elt); diff --git a/packages/survey-core/src/question_rating.ts b/packages/survey-core/src/question_rating.ts index 89004970eb..4d28e27cd3 100644 --- a/packages/survey-core/src/question_rating.ts +++ b/packages/survey-core/src/question_rating.ts @@ -12,8 +12,8 @@ import { DropdownListModel } from "./dropdownListModel"; import { SurveyModel } from "./survey"; import { ISurveyImpl } from "./base-interfaces"; import { IsTouch } from "./utils/devices"; -import { getColorFromProperty } from "./utils/utils"; import { getRGBaColor } from "./utils/color"; +import { getComputedCssVariableValue } from "./utils/css-variables"; import { ITheme } from "./themes"; import { DomDocumentHelper } from "./global_variables_utils"; import { HashTable } from "./helpers"; @@ -23,7 +23,7 @@ const RGBA_BLACK = "rgba(0, 0, 0, 1)"; function getRGBColor(themeVariables: any, colorName: string, varName: string, rootElement: HTMLElement): number[] | null { let str: string = !!themeVariables && themeVariables[colorName] as any; - const fallback = getColorFromProperty(varName, rootElement); + const fallback = getComputedCssVariableValue(varName, rootElement); if (!str) str = fallback; if (!str) return null; diff --git a/packages/survey-core/src/question_signaturepad.ts b/packages/survey-core/src/question_signaturepad.ts index 7ceb6f60a7..1b1cfa64c1 100644 --- a/packages/survey-core/src/question_signaturepad.ts +++ b/packages/survey-core/src/question_signaturepad.ts @@ -7,12 +7,12 @@ import { SurveyModel } from "./survey"; import { ConsoleWarnings } from "./console-warnings"; import { ITheme } from "./themes"; import { dataUrl2File, FileLoader, QuestionFileModelBase } from "./question_file"; -import { getColorFromProperty } from "./utils/utils"; import { isBase64URL } from "./utils/dom-utils"; import { DomDocumentHelper, DomWindowHelper } from "./global_variables_utils"; import { Action } from "./actions/action"; import { ComputedUpdater } from "./base"; import { ActionContainer } from "./actions/container"; +import { getComputedCssVariableValue } from "./utils/css-variables"; var defaultWidth = 300; var defaultHeight = 200; @@ -34,9 +34,8 @@ export class QuestionSignaturePadModel extends QuestionFileModelBase { } } } - private getPenColorFromTheme(element: HTMLElement): string { - const cssVariable = "--sjs2-color-bg-brand-primary"; - return getColorFromProperty(cssVariable, element); + private getPenColorFromTheme(element?: HTMLElement): string { + return getComputedCssVariableValue("--sjs2-color-bg-brand-primary", element); } private updateColors(signaturePad: SignaturePad, element?: HTMLElement) { const penColorFromTheme = this.getPenColorFromTheme(element); diff --git a/packages/survey-core/src/utils/css-variables.ts b/packages/survey-core/src/utils/css-variables.ts new file mode 100644 index 0000000000..4fbcce8dfd --- /dev/null +++ b/packages/survey-core/src/utils/css-variables.ts @@ -0,0 +1,107 @@ +import { DomDocumentHelper } from "../global_variables_utils"; +import { getRGBaColor } from "./color"; +import { createBoxShadow, parseBoxShadow } from "./shadow-effects"; + +const THEME_ROOT_CLASS = "sd-theme-root"; + +function getCssVariableProxyProperty(varName: string): string | null { + if (varName.indexOf("color") !== -1 || varName.indexOf("palette") !== -1) return "color"; + if (varName.indexOf("border-effect") !== -1) return "box-shadow"; + if (varName.indexOf("font-family") !== -1) return null; + if (varName.indexOf("font-weight") !== -1) return null; + if (varName.indexOf("text-case") !== -1) return null; + if (varName.indexOf("opacity") !== -1) return null; + if (varName.indexOf("scale") !== -1) return null; + return "width"; +} + +function normalizeCssVariableValue(value: string, proxyProperty: string | null): string { + if (value == null) return value; + if (proxyProperty === "color" && value !== "transparent") { + return getRGBaColor(value) || value; + } + if (proxyProperty === "box-shadow") { + return createBoxShadow(parseBoxShadow(value)) || value; + } + return value; +} + +function createCssVariableProbeElement(host: HTMLElement): HTMLElement | null { + const div = DomDocumentHelper.createElement("div") as HTMLElement; + if (!div) return null; + div.style.position = "absolute"; + div.style.visibility = "hidden"; + div.style.top = "0"; + div.style.left = "0"; + host.appendChild(div); + return div; +} + +export function getComputedCssVariableValue(varName: string, rootElement?: HTMLElement): string { + const host = rootElement || DomDocumentHelper.getBody() || DomDocumentHelper.getDocumentElement(); + if (!host) return ""; + + const proxyProperty = getCssVariableProxyProperty(varName); + if (proxyProperty === null) { + return DomDocumentHelper.getComputedStyle(host).getPropertyValue(varName).trim(); + } + + const tempElement = createCssVariableProbeElement(host); + if (!tempElement) return ""; + try { + tempElement.style.setProperty(proxyProperty, `var(${varName})`); + const value = DomDocumentHelper.getComputedStyle(tempElement).getPropertyValue(proxyProperty); + return normalizeCssVariableValue(value, proxyProperty); + } finally { + host.removeChild(tempElement); + } +} + +export function getComputedCssVariableValues( + cssVariables: { [key: string]: string } = {}, + additionalCssVariables: string[] = [], + rootElement?: HTMLElement +): { [key: string]: string } { + const sourceCssVariables: { [key: string]: string } = cssVariables || {}; + const hasCssVariablesToApply = Object.keys(sourceCssVariables).length > 0; + const clonedCssVariables: { [key: string]: string } = hasCssVariablesToApply ? JSON.parse(JSON.stringify(sourceCssVariables)) : {}; + if (!DomDocumentHelper.isAvailable()) return clonedCssVariables; + const host = rootElement || DomDocumentHelper.getBody() || DomDocumentHelper.getDocumentElement(); + if (!host) return clonedCssVariables; + + const tempElement = createCssVariableProbeElement(host); + if (!tempElement) return clonedCssVariables; + if (hasCssVariablesToApply) { + DomDocumentHelper.setStyles(tempElement, sourceCssVariables); + tempElement.classList.add(THEME_ROOT_CLASS); + } + + const result: { [key: string]: string } = {}; + try { + const probeComputed = DomDocumentHelper.getComputedStyle(tempElement); + const varNames = [...Object.keys(sourceCssVariables), ...additionalCssVariables]; + for (const varName of varNames) { + const sourceValue = sourceCssVariables[varName]; + const proxyProperty = getCssVariableProxyProperty(varName); + let value: string; + if (proxyProperty === null) { + value = probeComputed.getPropertyValue(varName).trim(); + } else { + tempElement.style.setProperty(proxyProperty, `var(${varName})`); + const raw = probeComputed.getPropertyValue(proxyProperty); + value = normalizeCssVariableValue(raw, proxyProperty); + tempElement.style.removeProperty(proxyProperty); + } + if (typeof value === "string" && value.trim() === "") { + if (sourceValue !== undefined) { + result[varName] = sourceValue; + } + continue; + } + result[varName] = value; + } + } finally { + host.removeChild(tempElement); + } + return result; +} \ No newline at end of file diff --git a/packages/survey-core/src/utils/utils.ts b/packages/survey-core/src/utils/utils.ts index 94fc413736..ebef8ae738 100644 --- a/packages/survey-core/src/utils/utils.ts +++ b/packages/survey-core/src/utils/utils.ts @@ -173,14 +173,6 @@ export function getActiveElement(): Element | null { return activeElement; } -export function getColorFromProperty(varName: string, element?: HTMLElement) { - if ("function" === typeof getComputedStyle) { - const style = getComputedStyle(element || DomDocumentHelper.getDocumentElement()); - return style.getPropertyValue && style.getPropertyValue(varName); - } - return ""; -} - export function mulberry32(seed: number): () => number { return function() { var t = seed += 0x6D2B79F5; From b7812b4522020954a831b7cf8b4e956d7a7fb360 Mon Sep 17 00:00:00 2001 From: OlgaLarina Date: Tue, 26 May 2026 14:35:38 +0300 Subject: [PATCH 2/6] V3 export mergeObjects function --- .../survey-core/entries/chunks/core-wo-model.ts | 2 +- packages/survey-core/src/utils/utils.ts | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/survey-core/entries/chunks/core-wo-model.ts b/packages/survey-core/entries/chunks/core-wo-model.ts index cb9647cf5c..3242728137 100644 --- a/packages/survey-core/entries/chunks/core-wo-model.ts +++ b/packages/survey-core/entries/chunks/core-wo-model.ts @@ -6,7 +6,7 @@ export * from "../../src/svgbundle"; // utils export * from "../../src/rendererFactory"; export * from "../../src/utils/responsivity-manager"; -export { unwrap } from "../../src/utils/utils"; +export * from "../../src/utils/utils"; export { getOriginalEvent } from "../../src/utils/dom-utils"; export { getElement, activateLazyRenderingChecks } from "../../src/utils/dom-utils"; export * from "../../src/actions/action"; diff --git a/packages/survey-core/src/utils/utils.ts b/packages/survey-core/src/utils/utils.ts index 3f22c0502c..0eba7e5b51 100644 --- a/packages/survey-core/src/utils/utils.ts +++ b/packages/survey-core/src/utils/utils.ts @@ -164,22 +164,6 @@ export function floorTo2Decimals(number: number): number { return Math.floor(number * 100) / 100; } -export function getRootNode(node: Element): Document | ShadowRoot | null { - const root = node?.getRootNode() || settings.environment.root; - if (!(root instanceof Document || root instanceof ShadowRoot)) return null; - return root; -} - -export function getActiveElement(): Element | null { - const doc = DomDocumentHelper.getDocument(); - if (!doc) return null; - let activeElement = doc.activeElement; - if (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { - activeElement = activeElement.shadowRoot.activeElement; - } - return activeElement; -} - export function mulberry32(seed: number): () => number { return function() { var t = seed += 0x6D2B79F5; From aaf2b71133c87837860aa4212f849863d4e295ef Mon Sep 17 00:00:00 2001 From: OlgaLarina Date: Tue, 26 May 2026 16:06:20 +0300 Subject: [PATCH 3/6] update getStylePropertyValue --- .../survey-core/src/question_signaturepad.ts | 6 +- .../survey-core/src/utils/css-variables.ts | 24 +++---- .../survey-core/tests/question_ratingtests.ts | 62 ++++++++++--------- .../tests/question_signaturepadtests.ts | 25 +++++--- 4 files changed, 59 insertions(+), 58 deletions(-) diff --git a/packages/survey-core/src/question_signaturepad.ts b/packages/survey-core/src/question_signaturepad.ts index 1b1cfa64c1..490e423807 100644 --- a/packages/survey-core/src/question_signaturepad.ts +++ b/packages/survey-core/src/question_signaturepad.ts @@ -34,11 +34,9 @@ export class QuestionSignaturePadModel extends QuestionFileModelBase { } } } - private getPenColorFromTheme(element?: HTMLElement): string { - return getComputedCssVariableValue("--sjs2-color-bg-brand-primary", element); - } + private updateColors(signaturePad: SignaturePad, element?: HTMLElement) { - const penColorFromTheme = this.getPenColorFromTheme(element); + const penColorFromTheme = getComputedCssVariableValue("--sjs2-color-bg-brand-primary", element); const penColorProperty = this.getPropertyByName("penColor"); signaturePad.penColor = this.penColor || penColorFromTheme || penColorProperty.defaultValue || "#1ab394"; diff --git a/packages/survey-core/src/utils/css-variables.ts b/packages/survey-core/src/utils/css-variables.ts index 4fbcce8dfd..c3b58d7589 100644 --- a/packages/survey-core/src/utils/css-variables.ts +++ b/packages/survey-core/src/utils/css-variables.ts @@ -37,24 +37,16 @@ function createCssVariableProbeElement(host: HTMLElement): HTMLElement | null { return div; } -export function getComputedCssVariableValue(varName: string, rootElement?: HTMLElement): string { - const host = rootElement || DomDocumentHelper.getBody() || DomDocumentHelper.getDocumentElement(); - if (!host) return ""; +export function getComputedCssVariableValue(varName: string, element?: HTMLElement): string { + const el = element || DomDocumentHelper.getBody() || DomDocumentHelper.getDocumentElement(); + if (!el) return ""; - const proxyProperty = getCssVariableProxyProperty(varName); - if (proxyProperty === null) { - return DomDocumentHelper.getComputedStyle(host).getPropertyValue(varName).trim(); - } + const computedStyle = DomDocumentHelper.getComputedStyle(el); + if (!computedStyle || !computedStyle.getPropertyValue) return ""; - const tempElement = createCssVariableProbeElement(host); - if (!tempElement) return ""; - try { - tempElement.style.setProperty(proxyProperty, `var(${varName})`); - const value = DomDocumentHelper.getComputedStyle(tempElement).getPropertyValue(proxyProperty); - return normalizeCssVariableValue(value, proxyProperty); - } finally { - host.removeChild(tempElement); - } + const value = computedStyle.getPropertyValue(varName); + const proxyProperty = getCssVariableProxyProperty(varName); + return normalizeCssVariableValue(value, proxyProperty); } export function getComputedCssVariableValues( diff --git a/packages/survey-core/tests/question_ratingtests.ts b/packages/survey-core/tests/question_ratingtests.ts index 905482aea0..454f00f739 100644 --- a/packages/survey-core/tests/question_ratingtests.ts +++ b/packages/survey-core/tests/question_ratingtests.ts @@ -129,35 +129,41 @@ test("check rating resize observer behavior", () => { }, ], }; - const survey = new SurveyModel(json); - survey.css = defaultCss; - const q1 = survey.getQuestionByName("q1"); - q1.afterRender(rootElement); - expect(q1["resizeObserver"]).toBeTruthy(); - expect(q1.renderAs).toBe("default"); - currentOffsetWidth = 300; - currentScrollWidth = 300; - (q1["resizeObserver"]).call(); - expect(q1.renderAs).toBe("default"); - currentOffsetWidth = 200; - (q1["resizeObserver"]).call(); - (q1["resizeObserver"]).call(); //double process to reset isProcessed flag - expect(q1.renderAs).toBe("dropdown"); - currentOffsetWidth = 400; - (q1["resizeObserver"]).call(); - (q1["resizeObserver"]).call(); //double process to reset isProcessed flag - expect(q1.renderAs).toBe("default"); - currentOffsetWidth = 200; - (q1["resizeObserver"]).call(); - (q1["resizeObserver"]).call(); //double process to reset isProcessed flag - expect(q1.renderAs).toBe("dropdown"); - q1["destroyResizeObserver"](); - expect(q1.renderAs, "https://github.com/surveyjs/survey-creator/issues/2966: after destroying resize observer renderAs should return to default state").toBe("default"); - window.getComputedStyle = getComputedStyle; - window.ResizeObserver = ResizeObserver; - contentElement.remove(); - rootElement.remove(); + try { + const survey = new SurveyModel(json); + survey.css = defaultCss; + const q1 = survey.getQuestionByName("q1"); + q1.afterRender(rootElement); + expect(q1["resizeObserver"]).toBeTruthy(); + expect(q1.renderAs).toBe("default"); + currentOffsetWidth = 300; + currentScrollWidth = 300; + (q1["resizeObserver"]).call(); + expect(q1.renderAs).toBe("default"); + currentOffsetWidth = 200; + (q1["resizeObserver"]).call(); + (q1["resizeObserver"]).call(); //double process to reset isProcessed flag + expect(q1.renderAs).toBe("dropdown"); + currentOffsetWidth = 400; + (q1["resizeObserver"]).call(); + (q1["resizeObserver"]).call(); //double process to reset isProcessed flag + expect(q1.renderAs).toBe("default"); + currentOffsetWidth = 200; + (q1["resizeObserver"]).call(); + (q1["resizeObserver"]).call(); //double process to reset isProcessed flag + expect(q1.renderAs).toBe("dropdown"); + q1["destroyResizeObserver"](); + expect(q1.renderAs, "https://github.com/surveyjs/survey-creator/issues/2966: after destroying resize observer renderAs should return to default state").toBe("default"); + + } finally { + window.ResizeObserver = ResizeObserver; + window.getComputedStyle = getComputedStyle; + window.ResizeObserver = ResizeObserver; + + contentElement.remove(); + rootElement.remove(); + } }); test("check rating in case of state 'collapsed'", () => { diff --git a/packages/survey-core/tests/question_signaturepadtests.ts b/packages/survey-core/tests/question_signaturepadtests.ts index 90b4dff77f..d628d27e3a 100644 --- a/packages/survey-core/tests/question_signaturepadtests.ts +++ b/packages/survey-core/tests/question_signaturepadtests.ts @@ -223,6 +223,9 @@ describe("question signaturepad", () => { }); test("check penColor & background color from theme", () => { + const defaultThemePenColor = "#1ab394"; + const defaultBackgroundColor = "transparent"; + const json = { elements: [ { @@ -242,7 +245,7 @@ describe("question signaturepad", () => { expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor undefined").toBeUndefined(); - expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe("#1ab394"); + expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe(defaultThemePenColor); expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor default").toBe("#ffffff"); containerEl.style.setProperty("--sjs2-color-bg-brand-primary", "rgba(103, 58, 176, 1)"); @@ -250,19 +253,21 @@ describe("question signaturepad", () => { expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor undefined").toBeUndefined(); expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor from theme").toBe("rgba(103, 58, 176, 1)"); - expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor from theme").toBe("transparent"); + expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor from theme").toBe(defaultBackgroundColor); containerEl.style.setProperty("--sjs2-color-bg-brand-primary", ""); survey.applyTheme({ "cssVariables": {} }); expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor undefined").toBeUndefined(); - expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe("#1ab394"); + expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe(defaultThemePenColor); expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor default").toBe("#ffffff"); containerEl.remove(); }); test("check penColor & background color if background image", () => { + const defaultThemePenColor = "#1ab394"; + const defaultBackgroundColor = "transparent"; const json = { elements: [ { @@ -283,28 +288,28 @@ describe("question signaturepad", () => { expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor undefined").toBeUndefined(); - expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor #1ab394").toBe("#1ab394"); - expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor transparent").toBe("transparent"); + expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe(defaultThemePenColor); + expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor transparent").toBe(defaultBackgroundColor); containerEl.style.setProperty("--sjs2-color-bg-brand-primary", "rgba(103, 58, 176, 1)"); survey.applyTheme({ "cssVariables": { "--sjs2-color-bg-brand-primary": "rgba(103, 58, 176, 1)" } }); expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor undefined").toBeUndefined(); expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor from theme").toBe("rgba(103, 58, 176, 1)"); - expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor from theme").toBe("transparent"); + expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor from theme").toBe(defaultBackgroundColor); containerEl.style.setProperty("--sjs2-color-bg-brand-primary", ""); survey.applyTheme({ "cssVariables": {} }); expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor undefined").toBeUndefined(); - expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor #1ab394").toBe("#1ab394"); - expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor transparent").toBe("transparent"); + expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe(defaultThemePenColor); + expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor transparent").toBe(defaultBackgroundColor); signaturepadQuestion.backgroundColor = "#dde6db"; expect(signaturepadQuestion.penColor, "penColor undefined").toBeUndefined(); expect(signaturepadQuestion.backgroundColor, "backgroundColor #dde6db").toBe("#dde6db"); - expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor #1ab394").toBe("#1ab394"); - expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor transparent").toBe("transparent"); + expect(signaturepadQuestion.signaturePad.penColor, "signaturePad.penColor default").toBe(defaultThemePenColor); + expect(signaturepadQuestion.signaturePad.backgroundColor, "signaturePad.backgroundColor transparent").toBe(defaultBackgroundColor); containerEl.remove(); }); From c5aa99da80daf640a5110b3f8d568c55288fbf35 Mon Sep 17 00:00:00 2001 From: OlgaLarina Date: Tue, 26 May 2026 16:56:36 +0300 Subject: [PATCH 4/6] rerun From 55ab17c5255551beeb9a6f25d960cbe1d399d305 Mon Sep 17 00:00:00 2001 From: OlgaLarina Date: Tue, 26 May 2026 19:13:59 +0300 Subject: [PATCH 5/6] update test --- packages/survey-core/tests/question_ratingtests.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/survey-core/tests/question_ratingtests.ts b/packages/survey-core/tests/question_ratingtests.ts index 454f00f739..b1b9094c54 100644 --- a/packages/survey-core/tests/question_ratingtests.ts +++ b/packages/survey-core/tests/question_ratingtests.ts @@ -157,7 +157,6 @@ test("check rating resize observer behavior", () => { expect(q1.renderAs, "https://github.com/surveyjs/survey-creator/issues/2966: after destroying resize observer renderAs should return to default state").toBe("default"); } finally { - window.ResizeObserver = ResizeObserver; window.getComputedStyle = getComputedStyle; window.ResizeObserver = ResizeObserver; From cb258afa9365d14922896cb1a2cb49eb13bd3d20 Mon Sep 17 00:00:00 2001 From: OlgaLarina Date: Wed, 27 May 2026 17:40:43 +0300 Subject: [PATCH 6/6] refactoring getComputedCssVariableValues --- .../survey-core/src/utils/css-variables.ts | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/survey-core/src/utils/css-variables.ts b/packages/survey-core/src/utils/css-variables.ts index c3b58d7589..d6c2e72009 100644 --- a/packages/survey-core/src/utils/css-variables.ts +++ b/packages/survey-core/src/utils/css-variables.ts @@ -49,51 +49,55 @@ export function getComputedCssVariableValue(varName: string, element?: HTMLEleme return normalizeCssVariableValue(value, proxyProperty); } +function resolveComputedCssVariableValue( + probe: HTMLElement, + computed: CSSStyleDeclaration, + varName: string +): string { + const rawValue = computed.getPropertyValue(varName); + if (typeof rawValue !== "string" || rawValue.trim() === "") return ""; + const proxyProperty = getCssVariableProxyProperty(varName); + if (proxyProperty === null) return rawValue.trim(); + + probe.style.setProperty(proxyProperty, `var(${varName})`); + const raw = computed.getPropertyValue(proxyProperty); + probe.style.removeProperty(proxyProperty); + const proxyValue = normalizeCssVariableValue(raw, proxyProperty); + + return typeof proxyValue === "string" ? proxyValue.trim() : ""; +} + export function getComputedCssVariableValues( - cssVariables: { [key: string]: string } = {}, + sourceCssVariables: { [key: string]: string } = {}, additionalCssVariables: string[] = [], rootElement?: HTMLElement ): { [key: string]: string } { - const sourceCssVariables: { [key: string]: string } = cssVariables || {}; - const hasCssVariablesToApply = Object.keys(sourceCssVariables).length > 0; - const clonedCssVariables: { [key: string]: string } = hasCssVariablesToApply ? JSON.parse(JSON.stringify(sourceCssVariables)) : {}; - if (!DomDocumentHelper.isAvailable()) return clonedCssVariables; + const fallbackResult = { ...sourceCssVariables }; + if (!DomDocumentHelper.isAvailable()) return fallbackResult; + const host = rootElement || DomDocumentHelper.getBody() || DomDocumentHelper.getDocumentElement(); - if (!host) return clonedCssVariables; + const probe = host ? createCssVariableProbeElement(host) : null; + if (!probe) return fallbackResult; - const tempElement = createCssVariableProbeElement(host); - if (!tempElement) return clonedCssVariables; - if (hasCssVariablesToApply) { - DomDocumentHelper.setStyles(tempElement, sourceCssVariables); - tempElement.classList.add(THEME_ROOT_CLASS); + if (Object.keys(sourceCssVariables).length !== 0) { + DomDocumentHelper.setStyles(probe, sourceCssVariables); + probe.classList.add(THEME_ROOT_CLASS); } const result: { [key: string]: string } = {}; try { - const probeComputed = DomDocumentHelper.getComputedStyle(tempElement); + const computed = DomDocumentHelper.getComputedStyle(probe); const varNames = [...Object.keys(sourceCssVariables), ...additionalCssVariables]; for (const varName of varNames) { - const sourceValue = sourceCssVariables[varName]; - const proxyProperty = getCssVariableProxyProperty(varName); - let value: string; - if (proxyProperty === null) { - value = probeComputed.getPropertyValue(varName).trim(); - } else { - tempElement.style.setProperty(proxyProperty, `var(${varName})`); - const raw = probeComputed.getPropertyValue(proxyProperty); - value = normalizeCssVariableValue(raw, proxyProperty); - tempElement.style.removeProperty(proxyProperty); - } - if (typeof value === "string" && value.trim() === "") { - if (sourceValue !== undefined) { - result[varName] = sourceValue; - } - continue; + const value = resolveComputedCssVariableValue(probe, computed, varName); + if (value !== "") { + result[varName] = value; + } else if (sourceCssVariables[varName] !== undefined) { + result[varName] = sourceCssVariables[varName]; } - result[varName] = value; } } finally { - host.removeChild(tempElement); + host.removeChild(probe); } return result; } \ No newline at end of file