-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #6969 As discussed there, container query support is quite useful to add to Svelte as it is now broadly available with Firefox releasing support imminently w/ FF v110 this upcoming week (~Feb 14th). Chrome has had support since ~Aug '22. The central issue is that css-tree which is a dependency for CSS AST parsing is significantly lagging behind on adding more recent features such as container query support. Ample time has been given to the maintainer to update css-tree and I do have every confidence that in time css-tree will receive a new major version with all sorts of modern CSS syntax supported including container queries. This PR provides an interim solution for what Svelte needs to support container queries now.
- Loading branch information
Showing
11 changed files
with
607 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// @ts-nocheck | ||
// Note: Must import from the `css-tree` browser bundled distribution due to `createRequire` usage if importing from | ||
// `css-tree` Node module directly. This allows the production build of Svelte to work correctly. | ||
import { fork } from '../../../../../node_modules/css-tree/dist/csstree.esm.js'; | ||
|
||
import * as Comparison from './node/comparison'; | ||
import * as ContainerFeature from './node/container_feature'; | ||
import * as ContainerFeatureRange from './node/container_feature_range'; | ||
import * as ContainerFeatureStyle from './node/container_feature_style'; | ||
import * as ContainerQuery from './node/container_query'; | ||
import * as QueryCSSFunction from './node/query_css_function'; | ||
|
||
/** | ||
* Extends `css-tree` for container query support by forking and adding new nodes and at-rule support for `@container`. | ||
* | ||
* The new nodes are located in `./node`. | ||
*/ | ||
const cqSyntax = fork({ | ||
atrule: { // extend or override at-rule dictionary | ||
container: { | ||
parse: { | ||
prelude() { | ||
return this.createSingleNodeList( | ||
this.ContainerQuery() | ||
); | ||
}, | ||
block(isStyleBlock = false) { | ||
return this.Block(isStyleBlock); | ||
} | ||
} | ||
} | ||
}, | ||
node: { // extend node types | ||
Comparison, | ||
ContainerFeature, | ||
ContainerFeatureRange, | ||
ContainerFeatureStyle, | ||
ContainerQuery, | ||
QueryCSSFunction | ||
} | ||
}); | ||
|
||
export const parse = cqSyntax.parse; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// @ts-nocheck | ||
import { Delim } from 'css-tree/tokenizer'; | ||
|
||
export const name = 'Comparison'; | ||
export const structure = { | ||
value: String | ||
}; | ||
|
||
export function parse() { | ||
const start = this.tokenStart; | ||
|
||
const char1 = this.consume(Delim); | ||
|
||
// The first character in the comparison operator must match '<', '=', or '>'. | ||
if (char1 !== '<' && char1 !== '>' && char1 !== '=') { | ||
this.error('Malformed comparison operator'); | ||
} | ||
|
||
let char2; | ||
|
||
if (this.tokenType === Delim) { | ||
char2 = this.consume(Delim); | ||
|
||
// The second character in the comparison operator must match '='. | ||
if (char2 !== '=') { | ||
this.error('Malformed comparison operator'); | ||
} | ||
} | ||
|
||
// If the next token is also 'Delim' then it is malformed. | ||
if (this.tokenType === Delim) { | ||
this.error('Malformed comparison operator'); | ||
} | ||
|
||
const value = char2 ? `${char1}${char2}` : char1; | ||
|
||
return { | ||
type: 'Comparison', | ||
loc: this.getLocation(start, this.tokenStart), | ||
value | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
for (let index = 0; index < node.value.length; index++) { | ||
this.token(Delim, node.value.charAt(index)); | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
src/compiler/parse/read/css-tree-cq/node/container_feature.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// @ts-nocheck | ||
import { | ||
Ident, | ||
Number, | ||
Dimension, | ||
Function, | ||
LeftParenthesis, | ||
RightParenthesis, | ||
Colon, | ||
Delim | ||
} from 'css-tree/tokenizer'; | ||
|
||
export const name = 'ContainerFeature'; | ||
export const structure = { | ||
name: String, | ||
value: ['Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null] | ||
}; | ||
|
||
export function parse() { | ||
const start = this.tokenStart; | ||
let value = null; | ||
|
||
this.eat(LeftParenthesis); | ||
this.skipSC(); | ||
|
||
const name = this.consume(Ident); | ||
this.skipSC(); | ||
|
||
if (this.tokenType !== RightParenthesis) { | ||
this.eat(Colon); | ||
this.skipSC(); | ||
|
||
switch (this.tokenType) { | ||
case Number: | ||
if (this.lookupNonWSType(1) === Delim) { | ||
value = this.Ratio(); | ||
} else { | ||
value = this.Number(); | ||
} | ||
break; | ||
|
||
case Dimension: | ||
value = this.Dimension(); | ||
break; | ||
|
||
case Function: | ||
value = this.QueryCSSFunction(); | ||
break; | ||
|
||
case Ident: | ||
value = this.Identifier(); | ||
break; | ||
|
||
default: | ||
this.error('Number, dimension, ratio, function, or identifier is expected'); | ||
break; | ||
} | ||
|
||
this.skipSC(); | ||
} | ||
|
||
this.eat(RightParenthesis); | ||
|
||
return { | ||
type: 'ContainerFeature', | ||
loc: this.getLocation(start, this.tokenStart), | ||
name, | ||
value | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
this.token(LeftParenthesis, '('); | ||
this.token(Ident, node.name); | ||
|
||
if (node.value !== null) { | ||
this.token(Colon, ':'); | ||
this.node(node.value); | ||
} | ||
|
||
this.token(RightParenthesis, ')'); | ||
} |
86 changes: 86 additions & 0 deletions
86
src/compiler/parse/read/css-tree-cq/node/container_feature_range.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// @ts-nocheck | ||
import { | ||
Ident, | ||
Number, | ||
Delim, | ||
Dimension, | ||
Function, | ||
LeftParenthesis, | ||
RightParenthesis, | ||
WhiteSpace | ||
} from 'css-tree/tokenizer'; | ||
|
||
export const name = 'ContainerFeatureRange'; | ||
export const structure = { | ||
name: String, | ||
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null] | ||
}; | ||
|
||
function lookup_non_WS_type_and_value(offset, type, referenceStr) { | ||
let current_type; | ||
|
||
do { | ||
current_type = this.lookupType(offset++); | ||
if (current_type !== WhiteSpace) { | ||
break; | ||
} | ||
} while (current_type !== 0); // NULL -> 0 | ||
|
||
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false; | ||
} | ||
|
||
export function parse() { | ||
const children = this.createList(); | ||
let child = null; | ||
|
||
this.eat(LeftParenthesis); | ||
this.skipSC(); | ||
|
||
while (!this.eof && this.tokenType !== RightParenthesis) { | ||
switch (this.tokenType) { | ||
case Number: | ||
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) { | ||
child = this.Ratio(); | ||
} else { | ||
child = this.Number(); | ||
} | ||
break; | ||
|
||
case Delim: | ||
child = this.Comparison(); | ||
break; | ||
|
||
case Dimension: | ||
child = this.Dimension(); | ||
break; | ||
|
||
case Function: | ||
child = this.QueryCSSFunction(); | ||
break; | ||
|
||
case Ident: | ||
child = this.Identifier(); | ||
break; | ||
|
||
default: | ||
this.error('Number, dimension, comparison, ratio, function, or identifier is expected'); | ||
break; | ||
} | ||
|
||
children.push(child); | ||
|
||
this.skipSC(); | ||
} | ||
|
||
this.eat(RightParenthesis); | ||
|
||
return { | ||
type: 'ContainerFeatureRange', | ||
loc: this.getLocationFromList(children), | ||
children | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
this.children(node); | ||
} |
85 changes: 85 additions & 0 deletions
85
src/compiler/parse/read/css-tree-cq/node/container_feature_style.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// @ts-nocheck | ||
import { | ||
Function, | ||
Ident, | ||
Number, | ||
Dimension, | ||
RightParenthesis, | ||
Colon, | ||
Delim | ||
} from 'css-tree/tokenizer'; | ||
|
||
export const name = 'ContainerFeatureStyle'; | ||
export const structure = { | ||
name: String, | ||
value: ['Function', 'Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null] | ||
}; | ||
|
||
export function parse() { | ||
const start = this.tokenStart; | ||
let value = null; | ||
|
||
const function_name = this.consumeFunctionName(); | ||
if (function_name !== 'style') { | ||
this.error('Unknown container style query identifier; "style" is expected'); | ||
} | ||
|
||
this.skipSC(); | ||
|
||
const name = this.consume(Ident); | ||
this.skipSC(); | ||
|
||
if (this.tokenType !== RightParenthesis) { | ||
this.eat(Colon); | ||
this.skipSC(); | ||
|
||
switch (this.tokenType) { | ||
case Number: | ||
if (this.lookupNonWSType(1) === Delim) { | ||
value = this.Ratio(); | ||
} else { | ||
value = this.Number(); | ||
} | ||
break; | ||
|
||
case Dimension: | ||
value = this.Dimension(); | ||
break; | ||
|
||
case Function: | ||
value = this.QueryCSSFunction(); | ||
break; | ||
|
||
case Ident: | ||
value = this.Identifier(); | ||
break; | ||
|
||
default: | ||
this.error('Number, dimension, ratio, function or identifier is expected'); | ||
break; | ||
} | ||
|
||
this.skipSC(); | ||
} | ||
|
||
this.eat(RightParenthesis); | ||
|
||
return { | ||
type: 'ContainerFeatureStyle', | ||
loc: this.getLocation(start, this.tokenStart), | ||
name, | ||
value | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
this.token(Function, 'style('); | ||
this.token(Ident, node.name); | ||
|
||
if (node.value !== null) { | ||
this.token(Colon, ':'); | ||
this.node(node.value); | ||
} | ||
|
||
this.token(RightParenthesis, ')'); | ||
} |
Oops, something went wrong.