Skip to content

Commit bcf6f8e

Browse files
authored
Merge pull request #1 from puckowski/master_container_queries
fix(issue:3766) add support for container queries
2 parents 4d3189c + 856ca74 commit bcf6f8e

16 files changed

+14794
-14058
lines changed

dist/less.js

Lines changed: 7098 additions & 6964 deletions
Large diffs are not rendered by default.

dist/less.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/less.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/less/dist/less.js

Lines changed: 7098 additions & 6964 deletions
Large diffs are not rendered by default.

packages/less/dist/less.min.js

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 47 additions & 30 deletions
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,7 +1753,7 @@ 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;
@@ -1764,10 +1765,20 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
17641765
nodes.push(e);
17651766
} else if (parserInput.$char('(')) {
17661767
p = this.property();
1767-
e = this.value();
1768+
parserInput.save();
1769+
if (!p && syntaxOptions.queryInParens && parserInput.$re(/^[a-z-]*\s*([<>]=|<=|>=|[<>]|=)/)) {
1770+
parserInput.restore();
1771+
p = this.condition();
1772+
} else {
1773+
parserInput.restore();
1774+
e = this.value();
1775+
}
17681776
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)));
1777+
if (p && !e) {
1778+
nodes.push(new (tree.Paren)(new (tree.QueryInParens)(p.op, p.lvalue, p.rvalue, p._index)));
1779+
e = p;
1780+
} else if (p && e) {
1781+
nodes.push(new (tree.Paren)(new (tree.Declaration)(p, e, null, null, parserInput.i + currentIndex, fileInfo, true)));
17711782
} else if (e) {
17721783
nodes.push(new(tree.Paren)(e));
17731784
} else {
@@ -1785,12 +1796,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
17851796
}
17861797
},
17871798

1788-
mediaFeatures: function () {
1799+
mediaFeatures: function (syntaxOptions) {
17891800
const entities = this.entities;
17901801
const features = [];
17911802
let e;
17921803
do {
1793-
e = this.mediaFeature();
1804+
e = this.mediaFeature(syntaxOptions);
17941805
if (e) {
17951806
features.push(e);
17961807
if (!parserInput.$char(',')) { break; }
@@ -1806,38 +1817,44 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
18061817
return features.length > 0 ? features : null;
18071818
},
18081819

1809-
media: function () {
1810-
let features;
1811-
let rules;
1812-
let media;
1820+
prepareAndGetNestableAtRule: function (treeType, index, debugInfo, syntaxOptions) {
1821+
const features = this.mediaFeatures(syntaxOptions);
1822+
1823+
const rules = this.block();
1824+
1825+
if (!rules) {
1826+
error('media definitions require block statements after any features');
1827+
}
1828+
1829+
parserInput.forget();
1830+
1831+
const atRule = new (treeType)(rules, features, index + currentIndex, fileInfo);
1832+
if (context.dumpLineNumbers) {
1833+
atRule.debugInfo = debugInfo;
1834+
}
1835+
1836+
return atRule;
1837+
},
1838+
1839+
nestableAtRule: function () {
18131840
let debugInfo;
18141841
const index = parserInput.i;
18151842

18161843
if (context.dumpLineNumbers) {
18171844
debugInfo = getDebugInfo(index);
18181845
}
1819-
18201846
parserInput.save();
18211847

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');
1848+
if (parserInput.$peekChar('@')) {
1849+
if (parserInput.$str('@media')) {
1850+
return this.prepareAndGetNestableAtRule(tree.Media, index, debugInfo, MediaSyntaxOptions);
18291851
}
1830-
1831-
parserInput.forget();
1832-
1833-
media = new(tree.Media)(rules, features, index + currentIndex, fileInfo);
1834-
if (context.dumpLineNumbers) {
1835-
media.debugInfo = debugInfo;
1852+
1853+
if (parserInput.$str('@container')) {
1854+
return this.prepareAndGetNestableAtRule(tree.Container, index, debugInfo, ContainerSyntaxOptions);
18361855
}
1837-
1838-
return media;
18391856
}
1840-
1857+
18411858
parserInput.restore();
18421859
},
18431860

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

19201937
if (parserInput.currentChar() !== '@') { return; }
19211938

1922-
value = this['import']() || this.plugin() || this.media();
1939+
value = this['import']() || this.plugin() || this.nestableAtRule();
19231940
if (value) {
19241941
return value;
19251942
}
@@ -2422,4 +2439,4 @@ Parser.serializeVars = vars => {
24222439
return s;
24232440
};
24242441

2425-
export default Parser;
2442+
export default Parser;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const MediaSyntaxOptions = {
2+
queryInParens: false
3+
};
4+
5+
export const ContainerSyntaxOptions = {
6+
queryInParens: true
7+
};
Lines changed: 63 additions & 0 deletions
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

Lines changed: 5 additions & 2 deletions
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

packages/less/src/less/tree/media.js

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import Ruleset from './ruleset';
22
import Value from './value';
33
import Selector from './selector';
4-
import Anonymous from './anonymous';
5-
import Expression from './expression';
64
import AtRule from './atrule';
7-
import * as utils from '../utils';
5+
import NestableAtRulePrototype from './nested-at-rule';
86

97
const Media = function(value, features, index, currentFileInfo, visibilityInfo) {
108
this._index = index;
@@ -25,18 +23,7 @@ const Media = function(value, features, index, currentFileInfo, visibilityInfo)
2523
Media.prototype = Object.assign(new AtRule(), {
2624
type: 'Media',
2725

28-
isRulesetLike() {
29-
return true;
30-
},
31-
32-
accept(visitor) {
33-
if (this.features) {
34-
this.features = visitor.visit(this.features);
35-
}
36-
if (this.rules) {
37-
this.rules = visitor.visitArray(this.rules);
38-
}
39-
},
26+
...NestableAtRulePrototype,
4027

4128
genCSS(context, output) {
4229
output.add('@media ', this._fileInfo, this._index);
@@ -70,83 +57,6 @@ Media.prototype = Object.assign(new AtRule(), {
7057

7158
return context.mediaPath.length === 0 ? media.evalTop(context) :
7259
media.evalNested(context);
73-
},
74-
75-
evalTop(context) {
76-
let result = this;
77-
78-
// Render all dependent Media blocks.
79-
if (context.mediaBlocks.length > 1) {
80-
const selectors = (new Selector([], null, null, this.getIndex(), this.fileInfo())).createEmptySelectors();
81-
result = new Ruleset(selectors, context.mediaBlocks);
82-
result.multiMedia = true;
83-
result.copyVisibilityInfo(this.visibilityInfo());
84-
this.setParent(result, this);
85-
}
86-
87-
delete context.mediaBlocks;
88-
delete context.mediaPath;
89-
90-
return result;
91-
},
92-
93-
evalNested(context) {
94-
let i;
95-
let value;
96-
const path = context.mediaPath.concat([this]);
97-
98-
// Extract the media-query conditions separated with `,` (OR).
99-
for (i = 0; i < path.length; i++) {
100-
value = path[i].features instanceof Value ?
101-
path[i].features.value : path[i].features;
102-
path[i] = Array.isArray(value) ? value : [value];
103-
}
104-
105-
// Trace all permutations to generate the resulting media-query.
106-
//
107-
// (a, b and c) with nested (d, e) ->
108-
// a and d
109-
// a and e
110-
// b and c and d
111-
// b and c and e
112-
this.features = new Value(this.permute(path).map(path => {
113-
path = path.map(fragment => fragment.toCSS ? fragment : new Anonymous(fragment));
114-
115-
for (i = path.length - 1; i > 0; i--) {
116-
path.splice(i, 0, new Anonymous('and'));
117-
}
118-
119-
return new Expression(path);
120-
}));
121-
this.setParent(this.features, this);
122-
123-
// Fake a tree-node that doesn't output anything.
124-
return new Ruleset([], []);
125-
},
126-
127-
permute(arr) {
128-
if (arr.length === 0) {
129-
return [];
130-
} else if (arr.length === 1) {
131-
return arr[0];
132-
} else {
133-
const result = [];
134-
const rest = this.permute(arr.slice(1));
135-
for (let i = 0; i < rest.length; i++) {
136-
for (let j = 0; j < arr[0].length; j++) {
137-
result.push([arr[0][j]].concat(rest[i]));
138-
}
139-
}
140-
return result;
141-
}
142-
},
143-
144-
bubbleSelectors(selectors) {
145-
if (!selectors) {
146-
return;
147-
}
148-
this.rules = [new Ruleset(utils.copyArray(selectors), [this.rules[0]])];
149-
this.setParent(this.rules, this);
15060
}
15161
});
15262

0 commit comments

Comments
 (0)