diff --git a/.changeset/thin-shrimps-sip.md b/.changeset/thin-shrimps-sip.md new file mode 100644 index 0000000000..6cb2210b54 --- /dev/null +++ b/.changeset/thin-shrimps-sip.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/css-serializer": patch +--- + +feat: support custom property declaration in keyframe rule diff --git a/packages/tools/css-serializer/src/parse.ts b/packages/tools/css-serializer/src/parse.ts index 27844d3c72..56852d4431 100644 --- a/packages/tools/css-serializer/src/parse.ts +++ b/packages/tools/css-serializer/src/parse.ts @@ -117,6 +117,21 @@ function transformDeclaration( } } +function transformVariables(block: csstree.Block): Record { + return Object.fromEntries( + block.children.toArray().filter((node) => { + return node.type === 'Declaration' && node.property.startsWith('--'); + }).map((node) => { + const declaration = node as csstree.Declaration; + return [ + declaration.property, + toString(declaration.value) + + (declaration.important ? ' !important' : ''), + ]; + }), + ); +} + function transformStyleRule( node: csstree.Rule, errors: ParserError[], @@ -129,19 +144,7 @@ function transformStyleRule( value: preludeText, loc: toLoc(node.prelude.loc!.end), }, - variables: Object.fromEntries( - node.block.children.toArray().filter(node => - node.type === 'Declaration' && node.property.startsWith('--') - ).map((node) => { - return [ - (node as csstree.Declaration).property, - toString((node as csstree.Declaration).value) - + ((node as csstree.Declaration).important - ? ' !important' - : ''), - ]; - }), - ), + variables: transformVariables(node.block), }; } @@ -337,6 +340,7 @@ export function parse(content: string, options: { value: preludeText, loc: toLoc(rule.prelude.loc!.start, preludeText.length), }, + variables: transformVariables(rule.block), style: transformBlock(rule.block, errors), }; }), diff --git a/packages/tools/css-serializer/src/types/LynxStyleNode.ts b/packages/tools/css-serializer/src/types/LynxStyleNode.ts index 9100b858f0..bdc562f70b 100644 --- a/packages/tools/css-serializer/src/types/LynxStyleNode.ts +++ b/packages/tools/css-serializer/src/types/LynxStyleNode.ts @@ -45,6 +45,7 @@ export interface ImportRule { } export interface KeyframesStyle { keyText: Value; + variables: Record; style: Declaration[]; } export interface KeyframesRule { diff --git a/packages/tools/css-serializer/test/__preparation__/keyframes.css b/packages/tools/css-serializer/test/__preparation__/keyframes.css index 8ff873e252..7ebaf554e9 100644 --- a/packages/tools/css-serializer/test/__preparation__/keyframes.css +++ b/packages/tools/css-serializer/test/__preparation__/keyframes.css @@ -33,3 +33,20 @@ c: none; } } + +@keyframes test-keyframe-declaration { + 0% { + --bg-color: red; + background-color: var(--bg-color); + } + + 50% { + --bg-color: green; + background-color: var(--bg-color); + } + + 100% { + --bg-color: blue; + background-color: var(--bg-color); + } +} diff --git a/packages/tools/css-serializer/test/__snapshots__/lynx.spec.ts.snap b/packages/tools/css-serializer/test/__snapshots__/lynx.spec.ts.snap index 79f3089118..14ee2f67d1 100644 --- a/packages/tools/css-serializer/test/__snapshots__/lynx.spec.ts.snap +++ b/packages/tools/css-serializer/test/__snapshots__/lynx.spec.ts.snap @@ -2030,6 +2030,7 @@ exports[`keyframes 1`] = ` "value": "translateX(0%)", }, ], + "variables": {}, }, { "keyText": { @@ -2053,6 +2054,7 @@ exports[`keyframes 1`] = ` "value": "translateX(100%)", }, ], + "variables": {}, }, ], "type": "KeyframesRule", @@ -2100,6 +2102,7 @@ exports[`keyframes 1`] = ` "value": "0", }, ], + "variables": {}, }, { "keyText": { @@ -2123,6 +2126,7 @@ exports[`keyframes 1`] = ` "value": "50px", }, ], + "variables": {}, }, { "keyText": { @@ -2146,6 +2150,7 @@ exports[`keyframes 1`] = ` "value": "50px", }, ], + "variables": {}, }, { "keyText": { @@ -2181,6 +2186,7 @@ exports[`keyframes 1`] = ` "value": "100%", }, ], + "variables": {}, }, ], "type": "KeyframesRule", @@ -2216,6 +2222,7 @@ exports[`keyframes 1`] = ` "value": "1", }, ], + "variables": {}, }, { "keyText": { @@ -2251,6 +2258,7 @@ exports[`keyframes 1`] = ` "value": "3", }, ], + "variables": {}, }, { "keyText": { @@ -2274,6 +2282,7 @@ exports[`keyframes 1`] = ` "value": "5", }, ], + "variables": {}, }, { "keyText": { @@ -2297,6 +2306,112 @@ exports[`keyframes 1`] = ` "value": "none", }, ], + "variables": {}, + }, + ], + "type": "KeyframesRule", + }, + { + "name": { + "loc": { + "column": 37, + "line": 37, + }, + "value": "test-keyframe-declaration", + }, + "styles": [ + { + "keyText": { + "loc": { + "column": 5, + "line": 38, + }, + "value": "0%", + }, + "style": [ + { + "defaultValue": "", + "defaultValueMap": { + "--bg-color": "", + }, + "keyLoc": { + "column": 21, + "line": 40, + }, + "name": "background-color", + "type": "css_var", + "valLoc": { + "column": 39, + "line": 40, + }, + "value": "{{--bg-color}}", + }, + ], + "variables": { + "--bg-color": "red", + }, + }, + { + "keyText": { + "loc": { + "column": 6, + "line": 43, + }, + "value": "50%", + }, + "style": [ + { + "defaultValue": "", + "defaultValueMap": { + "--bg-color": "", + }, + "keyLoc": { + "column": 21, + "line": 45, + }, + "name": "background-color", + "type": "css_var", + "valLoc": { + "column": 39, + "line": 45, + }, + "value": "{{--bg-color}}", + }, + ], + "variables": { + "--bg-color": "green", + }, + }, + { + "keyText": { + "loc": { + "column": 7, + "line": 48, + }, + "value": "100%", + }, + "style": [ + { + "defaultValue": "", + "defaultValueMap": { + "--bg-color": "", + }, + "keyLoc": { + "column": 21, + "line": 50, + }, + "name": "background-color", + "type": "css_var", + "valLoc": { + "column": 39, + "line": 50, + }, + "value": "{{--bg-color}}", + }, + ], + "variables": { + "--bg-color": "blue", + }, }, ], "type": "KeyframesRule", diff --git a/packages/web-platform/web-core/tests/__snapshots__/encode.spec.ts.snap b/packages/web-platform/web-core/tests/__snapshots__/encode.spec.ts.snap index 82f9a8cbf4..c55388634d 100644 --- a/packages/web-platform/web-core/tests/__snapshots__/encode.spec.ts.snap +++ b/packages/web-platform/web-core/tests/__snapshots__/encode.spec.ts.snap @@ -21,3 +21,5 @@ exports[`encodeCSS > scoped css 1`] = `".foo:where([l-css-id="1"]):not([l-e-name exports[`encodeCSS > scoped css, 2 css id 1`] = `".foo:where([l-css-id="1"]):not([l-e-name]),.foo:where([l-css-id="2"]):not([l-e-name]){background:red;}"`; exports[`encodeCSS > scoped css, import non existing 1`] = `".foo:where([l-css-id="1"]):not([l-e-name]),.foo:where([l-css-id="20"]):not([l-e-name]),.foo:where([l-css-id="2"]):not([l-e-name]){background:red;}"`; + +exports[`encodeCSS > should encode KeyframesRule with CSS variables 1`] = `"@keyframes my-animation-with-vars{from{--my-var:0;opacity:{{--my-var}};}to{--my-var:1;opacity:{{--my-var}};}}"`; diff --git a/packages/web-platform/web-core/tests/encode.spec.ts b/packages/web-platform/web-core/tests/encode.spec.ts index 9c6761ba27..6737ec6d61 100644 --- a/packages/web-platform/web-core/tests/encode.spec.ts +++ b/packages/web-platform/web-core/tests/encode.spec.ts @@ -124,6 +124,31 @@ describe('encodeCSS', () => { expect(buffer.length).toBeGreaterThan(0); }); + test('should encode KeyframesRule with CSS variables', () => { + const css = ` + @keyframes my-animation-with-vars { + from { + --my-var: 0; + opacity: var(--my-var); + } + to { + --my-var: 1; + opacity: var(--my-var); + } + } + `; + const cssMap = { + '1': CSS.parse(css).root, + }; + const buffer = encodeCSS(cssMap); + expect(buffer).toBeInstanceOf(Uint8Array); + expect(buffer.length).toBeGreaterThan(0); + const decodedString = get_style_content( + decode_style_info(buffer, undefined, true), + ); + expect(decodedString.trim()).toMatchSnapshot(); + }); + test('should handle complex selectors', () => { const css = ` div > .foo + #bar[attr="val"]::before:hover { diff --git a/packages/web-platform/web-core/ts/encode/encodeCSS.ts b/packages/web-platform/web-core/ts/encode/encodeCSS.ts index 78d4b009b1..fb75c70d57 100644 --- a/packages/web-platform/web-core/ts/encode/encodeCSS.ts +++ b/packages/web-platform/web-core/ts/encode/encodeCSS.ts @@ -59,6 +59,12 @@ export function encodeCSS( keyFrameChildrenRule.set_prelude(prelude); + for ( + const [key, value] of Object.entries(keyframesStyle.variables ?? {}) + ) { + keyFrameChildrenRule.push_declaration(key, value); + } + for (const decl of keyframesStyle.style) { keyFrameChildrenRule.push_declaration(decl.name, decl.value); }