Skip to content

Commit 012d549

Browse files
authored
fix(issue:3766) add support for container queries (#3811)
* fix(issue:3766) add support for container queries * Add support for CSS Container Queries * Add tests for CSS Container Queries * feat(media-queries-level-4) update media query * Add support for Media Queries Level 4. * Add tests for Media Queries Level 4. * fix(mq-4 regex) fix regex for media queries * Fix regex used for Media Queries Level 4 syntax parsing. * fix(media-query-syntax) fix parsing of invalid CSS * Fix parsing of invalid CSS for media queries to be consistent with Less.js version 4.1.3 handling.
1 parent 8b5aef9 commit 012d549

18 files changed

+14977
-14066
lines changed

dist/less.js

+7,123-6,966
Large diffs are not rendered by default.

dist/less.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/less.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/less/dist/less.js

+7,123-6,966
Large diffs are not rendered by default.

packages/less/dist/less.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/less/dist/less.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/less/src/less/parser/parser-input.js

+7
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ export default () => {
149149
return tok;
150150
};
151151

152+
parserInput.$peekChar = tok => {
153+
if (input.charAt(parserInput.i) !== tok) {
154+
return null;
155+
}
156+
return tok;
157+
};
158+
152159
parserInput.$str = tok => {
153160
const tokLength = tok.length;
154161

packages/less/src/less/parser/parser.js

+62-33
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import visitors from '../visitors';
44
import getParserInput from './parser-input';
55
import * as utils from '../utils';
66
import functionRegistry from '../functions/function-registry';
7+
import { ContainerSyntaxOptions, MediaSyntaxOptions } from '../tree/atrule-syntax';
78

89
//
910
// less.js - parser
@@ -1698,7 +1699,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
16981699
const options = (dir ? this.importOptions() : null) || {};
16991700

17001701
if ((path = this.entities.quoted() || this.entities.url())) {
1701-
features = this.mediaFeatures();
1702+
features = this.mediaFeatures({});
17021703

17031704
if (!parserInput.$char(';')) {
17041705
parserInput.i = index;
@@ -1752,22 +1753,39 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
17521753
}
17531754
},
17541755

1755-
mediaFeature: function () {
1756+
mediaFeature: function (syntaxOptions) {
17561757
const entities = this.entities;
17571758
const nodes = [];
17581759
let e;
17591760
let p;
1761+
let rangeP;
17601762
parserInput.save();
17611763
do {
17621764
e = entities.keyword() || entities.variable() || entities.mixinLookup();
17631765
if (e) {
17641766
nodes.push(e);
17651767
} else if (parserInput.$char('(')) {
17661768
p = this.property();
1767-
e = this.value();
1769+
parserInput.save();
1770+
if (!p && syntaxOptions.queryInParens && parserInput.$re(/^[0-9a-z-]*\s*([<>]=|<=|>=|[<>]|=)/)) {
1771+
parserInput.restore();
1772+
p = this.condition();
1773+
1774+
parserInput.save();
1775+
rangeP = this.atomicCondition(null, p.rvalue);
1776+
if (!rangeP) {
1777+
parserInput.restore();
1778+
}
1779+
} else {
1780+
parserInput.restore();
1781+
e = this.value();
1782+
}
17681783
if (parserInput.$char(')')) {
1769-
if (p && e) {
1770-
nodes.push(new(tree.Paren)(new(tree.Declaration)(p, e, null, null, parserInput.i + currentIndex, fileInfo, true)));
1784+
if (p && !e) {
1785+
nodes.push(new (tree.Paren)(new (tree.QueryInParens)(p.op, p.lvalue, p.rvalue, rangeP ? rangeP.op : null, rangeP ? rangeP.rvalue : null, p._index)));
1786+
e = p;
1787+
} else if (p && e) {
1788+
nodes.push(new (tree.Paren)(new (tree.Declaration)(p, e, null, null, parserInput.i + currentIndex, fileInfo, true)));
17711789
} else if (e) {
17721790
nodes.push(new(tree.Paren)(e));
17731791
} else {
@@ -1785,12 +1803,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
17851803
}
17861804
},
17871805

1788-
mediaFeatures: function () {
1806+
mediaFeatures: function (syntaxOptions) {
17891807
const entities = this.entities;
17901808
const features = [];
17911809
let e;
17921810
do {
1793-
e = this.mediaFeature();
1811+
e = this.mediaFeature(syntaxOptions);
17941812
if (e) {
17951813
features.push(e);
17961814
if (!parserInput.$char(',')) { break; }
@@ -1806,38 +1824,44 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
18061824
return features.length > 0 ? features : null;
18071825
},
18081826

1809-
media: function () {
1810-
let features;
1811-
let rules;
1812-
let media;
1827+
prepareAndGetNestableAtRule: function (treeType, index, debugInfo, syntaxOptions) {
1828+
const features = this.mediaFeatures(syntaxOptions);
1829+
1830+
const rules = this.block();
1831+
1832+
if (!rules) {
1833+
error('media definitions require block statements after any features');
1834+
}
1835+
1836+
parserInput.forget();
1837+
1838+
const atRule = new (treeType)(rules, features, index + currentIndex, fileInfo);
1839+
if (context.dumpLineNumbers) {
1840+
atRule.debugInfo = debugInfo;
1841+
}
1842+
1843+
return atRule;
1844+
},
1845+
1846+
nestableAtRule: function () {
18131847
let debugInfo;
18141848
const index = parserInput.i;
18151849

18161850
if (context.dumpLineNumbers) {
18171851
debugInfo = getDebugInfo(index);
18181852
}
1819-
18201853
parserInput.save();
18211854

1822-
if (parserInput.$str('@media')) {
1823-
features = this.mediaFeatures();
1824-
1825-
rules = this.block();
1826-
1827-
if (!rules) {
1828-
error('media definitions require block statements after any features');
1855+
if (parserInput.$peekChar('@')) {
1856+
if (parserInput.$str('@media')) {
1857+
return this.prepareAndGetNestableAtRule(tree.Media, index, debugInfo, MediaSyntaxOptions);
18291858
}
1830-
1831-
parserInput.forget();
1832-
1833-
media = new(tree.Media)(rules, features, index + currentIndex, fileInfo);
1834-
if (context.dumpLineNumbers) {
1835-
media.debugInfo = debugInfo;
1859+
1860+
if (parserInput.$str('@container')) {
1861+
return this.prepareAndGetNestableAtRule(tree.Container, index, debugInfo, ContainerSyntaxOptions);
18361862
}
1837-
1838-
return media;
18391863
}
1840-
1864+
18411865
parserInput.restore();
18421866
},
18431867

@@ -1919,7 +1943,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
19191943

19201944
if (parserInput.currentChar() !== '@') { return; }
19211945

1922-
value = this['import']() || this.plugin() || this.media();
1946+
value = this['import']() || this.plugin() || this.nestableAtRule();
19231947
if (value) {
19241948
return value;
19251949
}
@@ -2231,7 +2255,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
22312255
parserInput.forget();
22322256
return body;
22332257
},
2234-
atomicCondition: function () {
2258+
atomicCondition: function (needsParens, preparsedCond) {
22352259
const entities = this.entities;
22362260
const index = parserInput.i;
22372261
let a;
@@ -2243,7 +2267,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
22432267
return this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
22442268
}).bind(this)
22452269

2246-
a = cond();
2270+
if (preparsedCond) {
2271+
a = preparsedCond;
2272+
} else {
2273+
a = cond();
2274+
}
2275+
22472276
if (a) {
22482277
if (parserInput.$char('>')) {
22492278
if (parserInput.$char('=')) {
@@ -2275,7 +2304,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
22752304
} else {
22762305
error('expected expression');
22772306
}
2278-
} else {
2307+
} else if (!preparsedCond) {
22792308
c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index + currentIndex, false);
22802309
}
22812310
return c;
@@ -2422,4 +2451,4 @@ Parser.serializeVars = vars => {
24222451
return s;
24232452
};
24242453

2425-
export default Parser;
2454+
export default Parser;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const MediaSyntaxOptions = {
2+
queryInParens: true
3+
};
4+
5+
export const ContainerSyntaxOptions = {
6+
queryInParens: true
7+
};
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import Ruleset from './ruleset';
2+
import Value from './value';
3+
import Selector from './selector';
4+
import AtRule from './atrule';
5+
import NestableAtRulePrototype from './nested-at-rule';
6+
7+
const Container = function(value, features, index, currentFileInfo, visibilityInfo) {
8+
this._index = index;
9+
this._fileInfo = currentFileInfo;
10+
11+
const selectors = (new Selector([], null, null, this._index, this._fileInfo)).createEmptySelectors();
12+
13+
this.features = new Value(features);
14+
this.rules = [new Ruleset(selectors, value)];
15+
this.rules[0].allowImports = true;
16+
this.copyVisibilityInfo(visibilityInfo);
17+
this.allowRoot = true;
18+
this.setParent(selectors, this);
19+
this.setParent(this.features, this);
20+
this.setParent(this.rules, this);
21+
};
22+
23+
Container.prototype = Object.assign(new AtRule(), {
24+
type: 'Container',
25+
26+
...NestableAtRulePrototype,
27+
28+
genCSS(context, output) {
29+
output.add('@container ', this._fileInfo, this._index);
30+
this.features.genCSS(context, output);
31+
this.outputRuleset(context, output, this.rules);
32+
},
33+
34+
eval(context) {
35+
if (!context.mediaBlocks) {
36+
context.mediaBlocks = [];
37+
context.mediaPath = [];
38+
}
39+
40+
const media = new Container(null, [], this._index, this._fileInfo, this.visibilityInfo());
41+
if (this.debugInfo) {
42+
this.rules[0].debugInfo = this.debugInfo;
43+
media.debugInfo = this.debugInfo;
44+
}
45+
46+
media.features = this.features.eval(context);
47+
48+
context.mediaPath.push(media);
49+
context.mediaBlocks.push(media);
50+
51+
this.rules[0].functionRegistry = context.frames[0].functionRegistry.inherit();
52+
context.frames.unshift(this.rules[0]);
53+
media.rules = [this.rules[0].eval(context)];
54+
context.frames.shift();
55+
56+
context.mediaPath.pop();
57+
58+
return context.mediaPath.length === 0 ? media.evalTop(context) :
59+
media.evalNested(context);
60+
}
61+
});
62+
63+
export default Container;

packages/less/src/less/tree/index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import Value from './value';
2525
import JavaScript from './javascript';
2626
import Assignment from './assignment';
2727
import Condition from './condition';
28+
import QueryInParens from './query-in-parens';
2829
import Paren from './paren';
2930
import Media from './media';
31+
import Container from './container';
3032
import UnicodeDescriptor from './unicode-descriptor';
3133
import Negative from './negative';
3234
import Extend from './extend';
@@ -43,8 +45,9 @@ export default {
4345
Ruleset, Element, Attribute, Combinator, Selector,
4446
Quoted, Expression, Declaration, Call, URL, Import,
4547
Comment, Anonymous, Value, JavaScript, Assignment,
46-
Condition, Paren, Media, UnicodeDescriptor, Negative,
47-
Extend, VariableCall, NamespaceValue,
48+
Condition, Paren, Media, Container, QueryInParens,
49+
UnicodeDescriptor, Negative, Extend, VariableCall,
50+
NamespaceValue,
4851
mixin: {
4952
Call: MixinCall,
5053
Definition: MixinDefinition

0 commit comments

Comments
 (0)