From a4f16ade0b7c309cdb37e1ca80a0201362a2c2f9 Mon Sep 17 00:00:00 2001 From: abdelfattahradwan Date: Sat, 16 May 2026 23:47:16 +0300 Subject: [PATCH] Support container scroll-state queries --- src/parser/cssParser.ts | 27 +++++++++++++++++++++++++-- src/test/css/parser.test.ts | 4 ++++ src/test/less/parser.test.ts | 1 + src/test/scss/parser.test.ts | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/parser/cssParser.ts b/src/parser/cssParser.ts index bfffb4f4..13fdabd5 100644 --- a/src/parser/cssParser.ts +++ b/src/parser/cssParser.ts @@ -1472,14 +1472,14 @@ export class Parser { const node = this.create(nodes.Container); this.consumeToken(); // @container - node.addChild(this._parseIdent()); // optional container name + node.addChild(this._parseContainerName()); // optional container name if (node.addChild(this._parseContainerQuery())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.CurlyL)) { break; } - node.addChild(this._parseIdent()); // optional container name + node.addChild(this._parseContainerName()); // optional container name node.addChild(this._parseContainerQuery()); } } @@ -1487,6 +1487,20 @@ export class Parser { return this._parseBody(node, this._parseContainerDeclaration.bind(this, isNested)); } + public _parseContainerName(): nodes.Node | null { + if (this.peekIdent('style') || this.peekIdent('scroll-state')) { + const mark = this.mark(); + this.consumeToken(); + if (!this.hasWhitespace() && this.peek(TokenType.ParenthesisL)) { + this.restoreAtMark(mark); + return null; + } + this.restoreAtMark(mark); + } + + return this._parseIdent(); + } + public _parseContainerQuery(): nodes.Node | null { // = not // | [ [ and ]* | [ or ]* ] @@ -1512,6 +1526,7 @@ export class Parser { // = ( ) // | ( ) // | style( ) + // | scroll-state( ) // | const node = this.create(nodes.Node); if (this.accept(TokenType.ParenthesisL)) { @@ -1531,6 +1546,14 @@ export class Parser { if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); } + } else if (this.acceptIdent('scroll-state')) { + if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) { + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + } + node.addChild(this._parseDeclaration([TokenType.ParenthesisR], true)); + if (!this.accept(TokenType.ParenthesisR)) { + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + } } else { if (optional) { return null; diff --git a/src/test/css/parser.test.ts b/src/test/css/parser.test.ts index 54116fb4..0d6154e8 100644 --- a/src/test/css/parser.test.ts +++ b/src/test/css/parser.test.ts @@ -187,6 +187,10 @@ suite('CSS - Parser', () => { assertNode(`@container card (inline-size > 30em), style(--responsive: true) { }`, parser, parser._parseStylesheet.bind(parser)); assertNode(`@container card (inline-size > 30em), summary style(--responsive: true) { }`, parser, parser._parseStylesheet.bind(parser)); assertNode(`@container card (inline-size > 30em) { @container style(--responsive: true) {} }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media (pointer: fine) and (hover: hover) { @container my-container scroll-state(scrollable: y) { #my-list { padding-inline-end: calc(var(--spacing) * 2); } } }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@container scroll-state(scrollable: y) { }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@container my-container scroll-state(scrollable: y) and style(--responsive: true) { }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@container card (inline-size > 30em), my-container scroll-state(stuck: top) { }`, parser, parser._parseStylesheet.bind(parser)); }); test('@starting-style', function () { diff --git a/src/test/less/parser.test.ts b/src/test/less/parser.test.ts index 417e7ebc..06023bb3 100644 --- a/src/test/less/parser.test.ts +++ b/src/test/less/parser.test.ts @@ -354,5 +354,6 @@ suite('LESS - Parser', () => { const parser = new LESSParser(); assertNode(`.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, parser, parser._parseStylesheet.bind(parser)); assertNode(`:root { @container (max-height: 100px) { display: none;} }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@container my-container scroll-state(scrollable: y) { .item-icon { display: none; } }`, parser, parser._parseStylesheet.bind(parser)); }); }); diff --git a/src/test/scss/parser.test.ts b/src/test/scss/parser.test.ts index 9e73f9ba..11fb2ba0 100644 --- a/src/test/scss/parser.test.ts +++ b/src/test/scss/parser.test.ts @@ -294,6 +294,7 @@ suite('SCSS - Parser', () => { assertNode(`@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, parser, parser._parseStylesheet.bind(parser)); assertNode(`.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, parser, parser._parseStylesheet.bind(parser)); assertNode(`:root { @container (max-height: 100px) { display: none;} }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@container my-container scroll-state(scrollable: y) { .item-icon { display: none; } }`, parser, parser._parseStylesheet.bind(parser)); }); test('@use', function () {