From cbb8702143bd04b68253758dbc2c4a8fb49c0237 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 9 Sep 2026 08:59:21 +0200 Subject: [PATCH] fix(compiler): namespace `@property` declarations The `@property` atrule allows users to define custom CSS variables. These changes update the compiler to account for when namespacing variables. (cherry picked from commit 9b80d4ce9e00d50d471c1a1a98ab67ec53fbce5f) --- packages/compiler/src/shadow_css.ts | 15 +-- .../test/shadow_css/shadow_css_spec.ts | 92 ++++++++++++++++++- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index ba43560b382..cfa1376020a 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -1036,32 +1036,33 @@ const _cssColonInPlaceholderReGlobal = new RegExp(COLON_IN_PLACEHOLDER, 'g'); // Matches any CSS variable name, defined by a double-hyphen followed by any valid ident. // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram -const _cssVariableRe = /(var\(\s*)?(--(?:[a-zA-Z0-9_-]|[^\x00-\x7F])+)(\s*:)?/g; +const _cssVariableRe = /(var\(\s*|@property\s+)?(--(?:[a-zA-Z0-9_-]|[^\x00-\x7F])+)(\s*:)?/g; /** * Transforms CSS variables within a stylesheet to include a namespace placeholder. * * E.g. `--foo: bar;` becomes `--%NS%foo: bar;` * E.g. `color: var(--foo);` becomes `color: var(--%NS%foo);` + * E.g. `@property --foo` becomes `@property --%NS%foo` * * If a variable is prefixed with `--global--`, it is NOT namespaced and the prefix is removed. * E.g. `--global--mycolor: red;` becomes `--mycolor: red;` */ export function namespaceCssVariables(cssText: string): string { - return cssText.replace(_cssVariableRe, (match, leadingVar, varName, trailingColon) => { - // Check for a leading `var(` or trailing `:` to approximate whether we're operating on a - // real CSS variable, not another piece of syntax that resembles it. For example, this - // guards against: + return cssText.replace(_cssVariableRe, (match, prefix, varName, trailingColon) => { + // Check for a leading `var(`, `@property`, or trailing `:` to approximate whether we're + // operating on a real CSS variable, not another piece of syntax that resembles it. + // For example, this guards against: // - `.foo--bar {}` // - `/* --foo */` // - `p { content: "--foo" }` // - `[data---bar] {}` // - `[data-status=foo--bar] {}` // etc. - if (!leadingVar && !trailingColon) { + if (!prefix && !trailingColon) { return match; } - return (leadingVar ?? '') + namespaceCssVariable(varName) + (trailingColon ?? ''); + return (prefix ?? '') + namespaceCssVariable(varName) + (trailingColon ?? ''); }); } diff --git a/packages/compiler/test/shadow_css/shadow_css_spec.ts b/packages/compiler/test/shadow_css/shadow_css_spec.ts index a0cd582caf9..e1bac0f76a4 100644 --- a/packages/compiler/test/shadow_css/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css/shadow_css_spec.ts @@ -433,7 +433,7 @@ describe('ShadowCss', () => { const input = ` .foo { border: var(--global--border-size) solid var(--border-color); - box-shadow: + box-shadow: var(--shadow-1), var(--global--shadow-2), var(--shadow-3); @@ -443,7 +443,7 @@ describe('ShadowCss', () => { const expected = ` .foo { border: var(--border-size) solid var(--%NS%border-color); - box-shadow: + box-shadow: var(--%NS%shadow-1), var(--shadow-2), var(--%NS%shadow-3); @@ -597,5 +597,93 @@ p { 'CSS variable "--global-" has a single hyphen after "--global". Use two hyphens ("--global--") to opt-out of namespacing.', ); }); + + it('should namespace @property declarations', () => { + const input = ` + @property --myColor { + syntax: ""; + inherits: true; + initial-value: rebeccapurple; + } + + @property --myWidth { + syntax: " | "; + inherits: true; + initial-value: 200px; + } + + p { + background-color: var(--myColor); + width: var(--myWidth); + color: white; + } +`.trim(); + + const expected = ` + @property --%NS%myColor { + syntax: ""; + inherits: true; + initial-value: rebeccapurple; + } + + @property --%NS%myWidth { + syntax: " | "; + inherits: true; + initial-value: 200px; + } + + p { + background-color: var(--%NS%myColor); + width: var(--%NS%myWidth); + color: white; + } +`.trim(); + + expect(namespaceCssVariables(input)).toBe(expected); + }); + + it('should not namespace @property if --global-- is present', () => { + const input = ` + @property --global--my-color { + syntax: ""; + inherits: true; + initial-value: rebeccapurple; + } + + @property --global--my-width { + syntax: " | "; + inherits: true; + initial-value: 200px; + } + + p { + background-color: var(--global--my-color); + width: var(--global--my-width); + color: white; + } +`.trim(); + + const expected = ` + @property --my-color { + syntax: ""; + inherits: true; + initial-value: rebeccapurple; + } + + @property --my-width { + syntax: " | "; + inherits: true; + initial-value: 200px; + } + + p { + background-color: var(--my-color); + width: var(--my-width); + color: white; + } +`.trim(); + + expect(namespaceCssVariables(input)).toBe(expected); + }); }); });