Skip to content

Commit 964aea2

Browse files
committed
Added Support for @container rules and comparison operators defined in media queries level 4
1 parent d0a5763 commit 964aea2

28 files changed

+403
-29
lines changed

src/ExCSS.Tests/Container.cs

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
namespace ExCSS.Tests
2+
{
3+
using ExCSS;
4+
using Xunit;
5+
using System.Linq;
6+
7+
public class CssContainerTests : CssConstructionFunctions
8+
{
9+
[Fact]
10+
public void SimpleContainer()
11+
{
12+
const string source = "@container tall (min-width: 500px) and (min-height: 300px) {h2 { line-height: 1.6; } }";
13+
var result = ParseStyleSheet(source);
14+
Assert.Equal(source, result.StylesheetText.Text);
15+
var rule = result.Rules[0] as ContainerRule;
16+
Assert.NotNull(rule);
17+
Assert.Equal("@container tall (min-width: 500px) and (min-height: 300px) { h2 { line-height: 1.6 } }", rule.Text);
18+
Assert.Equal("tall", rule.Name);
19+
Assert.Equal("(min-width: 500px) and (min-height: 300px)", rule.ConditionText);
20+
var childRule = rule.Children.OfType<StyleRule>().First();
21+
Assert.Equal("h2 { line-height: 1.6 }", childRule.ToCss());
22+
}
23+
24+
[Fact]
25+
public void ContainerWithoutName()
26+
{
27+
const string source = "@container (min-width: 500px) and (min-height: 300px) {h2 { line-height: 1.6; } }";
28+
var result = ParseStyleSheet(source);
29+
Assert.Equal(source, result.StylesheetText.Text);
30+
var rule = result.Rules[0] as ContainerRule;
31+
Assert.NotNull(rule);
32+
Assert.Equal("@container (min-width: 500px) and (min-height: 300px) { h2 { line-height: 1.6 } }", rule.Text);
33+
Assert.Equal(string.Empty, rule.Name);
34+
Assert.Equal("(min-width: 500px) and (min-height: 300px)", rule.ConditionText);
35+
var childRule = rule.Children.OfType<StyleRule>().First();
36+
Assert.Equal("h2 { line-height: 1.6 }", childRule.ToCss());
37+
}
38+
39+
[Fact]
40+
public void ContainerWithoutCondition()
41+
{
42+
const string source = "@container tall {h2 { line-height: 1.6; } }";
43+
var result = ParseStyleSheet(source);
44+
Assert.Equal(source, result.StylesheetText.Text);
45+
var rule = result.Rules[0] as ContainerRule;
46+
Assert.NotNull(rule);
47+
Assert.Equal("@container tall { h2 { line-height: 1.6 } }", rule.Text);
48+
Assert.Equal("tall", rule.Name);
49+
Assert.Equal(string.Empty, rule.ConditionText);
50+
var childRule = rule.Children.OfType<StyleRule>().First();
51+
Assert.Equal("h2 { line-height: 1.6 }", childRule.ToCss());
52+
}
53+
54+
[Fact]
55+
public void ContainerWithComparisonOperators()
56+
{
57+
const string source = "@container tall (width < 500px) and (height >= 300px) {h2 { line-height: 1.6; } }";
58+
var result = ParseStyleSheet(source);
59+
Assert.Equal(source, result.StylesheetText.Text);
60+
var rule = result.Rules[0] as ContainerRule;
61+
Assert.NotNull(rule);
62+
Assert.Equal("@container tall (width < 500px) and (height >= 300px) { h2 { line-height: 1.6 } }", rule.Text);
63+
Assert.Equal("tall", rule.Name);
64+
Assert.Equal("(width < 500px) and (height >= 300px)", rule.ConditionText);
65+
var childRule = rule.Children.OfType<StyleRule>().First();
66+
Assert.Equal("h2 { line-height: 1.6 }", childRule.ToCss());
67+
}
68+
69+
[Fact]
70+
public void CSSWithTwoContainers()
71+
{
72+
const string source = @"li {
73+
container-type: inline-size;
74+
}
75+
76+
@container (min-width: 45ch) {
77+
li span {
78+
color: rgb(255, 0, 0);
79+
font-size: 2rem !important;
80+
}
81+
}
82+
83+
@container (min-width: 70ch) {
84+
li span {
85+
color: rgb(0, 0, 255);
86+
font-size: 3rem !important;
87+
}
88+
}";
89+
var result = ParseStyleSheet(source);
90+
Assert.Equal(source, result.StylesheetText.Text);
91+
Assert.Equal(3, result.Rules.Length);
92+
var rule1 = result.Rules[0] as StyleRule;
93+
var rule2 = result.Rules[1] as ContainerRule;
94+
var rule3 = result.Rules[2] as ContainerRule;
95+
Assert.NotNull(rule1);
96+
Assert.NotNull(rule2);
97+
Assert.NotNull(rule3);
98+
Assert.Equal("li { container-type: inline-size }", rule1.ToCss());
99+
Assert.Equal("@container (min-width: 45ch) { li span { color: rgb(255, 0, 0); font-size: 2rem !important } }", rule2.ToCss());
100+
Assert.Equal("@container (min-width: 70ch) { li span { color: rgb(0, 0, 255); font-size: 3rem !important } }", rule3.ToCss());
101+
}
102+
}
103+
}

src/ExCSS.Tests/MediaList.cs

+34-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using ExCSS;
44
using Xunit;
55
using System;
6-
6+
77
public class CssMediaListTests : CssConstructionFunctions
88
{
99
[Fact]
@@ -375,21 +375,48 @@ public void ImplicitAllFeatureMinResolutionAndMaxResolutionMediaList()
375375
[Fact]
376376
public void CssMediaListApiWithAppendDeleteAndTextShouldWork()
377377
{
378-
var media = new [] { "handheld", "screen", "only screen and (max-device-width: 480px)" };
378+
var media = new[] { "handheld", "screen", "only screen and (max-device-width: 480px)" };
379379
var p = new StylesheetParser();
380-
var m = new MediaList(p);
380+
var m = new MediaList(p);
381381
Assert.Equal(0, m.Length);
382382

383-
m.Add(media[0]);
384-
m.Add(media[1]);
385-
m.Add(media[2]);
383+
m.Add(media[0]);
384+
m.Add(media[1]);
385+
m.Add(media[2]);
386386

387-
m.Remove(media[1]);
387+
m.Remove(media[1]);
388388

389389
Assert.Equal(2, m.Length);
390390
Assert.Equal(media[0], m[0]);
391391
Assert.Equal(media[2], m[1]);
392392
Assert.Equal(String.Concat(media[0], ", ", media[2]), m.MediaText);
393393
}
394+
395+
[Fact]
396+
public void CombinedConditionMediaQueriesLevel4()
397+
{
398+
const string source = @"/* Traditionelle Syntax */
399+
@media (min-height: 500px) and (max-height: 800px) {
400+
/* Styles */
401+
h1 { color: rgb(255, 0, 0); }
402+
}
403+
404+
/* Mit Vergleichsoperatoren */
405+
@media (height >= 500px) and (height <= 800px) {
406+
/* Gleiche Styles */
407+
h1 { color: rgb(255, 0, 0); }
408+
}";
409+
var result = ParseStyleSheet(source);
410+
Assert.Equal(source, result.StylesheetText.Text);
411+
Assert.Equal(2, result.Rules.Length);
412+
var rule1 = result.Rules[0] as MediaRule;
413+
var rule2 = result.Rules[1] as MediaRule;
414+
Assert.NotNull(rule1);
415+
Assert.NotNull(rule2);
416+
Assert.Equal("(min-height: 500px) and (max-height: 800px)", rule1.ConditionText);
417+
Assert.Equal("(height >= 500px) and (height <= 800px)", rule2.ConditionText);
418+
Assert.Equal("@media (min-height: 500px) and (max-height: 800px) { h1 { color: rgb(255, 0, 0) } }", rule1.ToCss());
419+
Assert.Equal("@media (height >= 500px) and (height <= 800px) { h1 { color: rgb(255, 0, 0) } }", rule2.ToCss());
420+
}
394421
}
395422
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace ExCSS
2+
{
3+
public enum ContainerType : byte
4+
{
5+
Normal,
6+
Size,
7+
InlineSize
8+
}
9+
}

src/ExCSS/Enumerations/FeatureNames.cs

+2
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,7 @@ public static class FeatureNames
4242
public static readonly string Scripting = "scripting";
4343
public static readonly string Pointer = "pointer";
4444
public static readonly string Hover = "hover";
45+
public static readonly string BlockSize = "block-size";
46+
public static readonly string InlineSize = "inline-size";
4547
}
4648
}

src/ExCSS/Enumerations/Keywords.cs

+2
Original file line numberDiff line numberDiff line change
@@ -310,5 +310,7 @@ internal static class Keywords
310310
public static readonly string Last = "last";
311311
public static readonly string SelfStart = "self-start";
312312
public static readonly string SelfEnd = "self-end";
313+
public static readonly string Size = "size";
314+
public static readonly string InlineSize = "inline-size";
313315
}
314316
}

src/ExCSS/Enumerations/PropertyNames.cs

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public static class PropertyNames
9191
public static readonly string ClipRule = "clip-rule";
9292
public static readonly string Color = "color";
9393
public static readonly string ColorInterpolationFilters = "color-interpolation-filters";
94+
public static readonly string ContainerName= "container-name";
95+
public static readonly string ContainerType = "container-type";
9496
public static readonly string Content = "content";
9597
public static readonly string CounterIncrement = "counter-increment";
9698
public static readonly string CounterReset = "counter-reset";

src/ExCSS/Enumerations/RuleNames.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ public static class RuleNames
1212
public static readonly string Media = "media";
1313
public static readonly string Namespace = "namespace";
1414
public static readonly string Page = "page";
15+
public static readonly string Container = "container";
1516
}
1617
}

src/ExCSS/Enumerations/RuleType.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public enum RuleType : byte
1818
Document,
1919
FontFeatureValues,
2020
Viewport,
21-
RegionStyle
21+
RegionStyle,
22+
Container
2223
}
2324
}

src/ExCSS/Enumerations/TokenType.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ internal enum TokenType : byte
2929
Comma,
3030
Semicolon,
3131
Whitespace,
32-
EndOfFile
32+
EndOfFile,
33+
GreaterThan,
34+
GreaterThanOrEqual,
35+
LessThan,
36+
LessThanOrEqual,
37+
Equal
3338
}
3439
}

src/ExCSS/Factories/MediaFeatureFactory.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ internal sealed class MediaFeatureFactory
6060
{FeatureNames.UpdateFrequency, () => new UpdateFrequencyMediaFeature()},
6161
{FeatureNames.Scripting, () => new ScriptingMediaFeature()},
6262
{FeatureNames.Pointer, () => new PointerMediaFeature()},
63-
{FeatureNames.Hover, () => new HoverMediaFeature()}
63+
{FeatureNames.Hover, () => new HoverMediaFeature()},
64+
{FeatureNames.InlineSize, () => new SizeMediaFeature(FeatureNames.InlineSize)},
65+
{FeatureNames.BlockSize, () => new SizeMediaFeature(FeatureNames.BlockSize)},
6466
};
6567

6668
#endregion

src/ExCSS/Factories/PropertyFactory.cs

+2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ private PropertyFactory()
173173
AddLonghand(PropertyNames.Clear, () => new ClearProperty());
174174
AddLonghand(PropertyNames.Clip, () => new ClipProperty(), true);
175175
AddLonghand(PropertyNames.Color, () => new ColorProperty(), true);
176+
AddLonghand(PropertyNames.ContainerName, () => new ContainerNameProperty());
177+
AddLonghand(PropertyNames.ContainerType, () => new ContainerTypeProperty());
176178
AddLonghand(PropertyNames.Content, () => new ContentProperty());
177179
AddLonghand(PropertyNames.CounterIncrement, () => new CounterIncrementProperty());
178180
AddLonghand(PropertyNames.CounterReset, () => new CounterResetProperty());

src/ExCSS/Formatting/CompressedStyleFormatter.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ string IStyleFormatter.Declaration(string name, string value, bool important)
1515
return string.Concat(name, ": ", rest);
1616
}
1717

18-
string IStyleFormatter.Constraint(string name, string value)
18+
string IStyleFormatter.Constraint(string name, string value, string constraintDelimiter)
1919
{
20-
var ending = value != null ? ": " + value : string.Empty;
20+
var ending = value != null ? constraintDelimiter + value : string.Empty;
2121
return string.Concat("(", name, ending, ")");
2222
}
2323

src/ExCSS/MediaFeatures/MediaFeature.cs

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23

34
namespace ExCSS
45
{
56
public abstract class MediaFeature : StylesheetNode, IMediaFeature
67
{
78
private TokenValue _tokenValue;
9+
private TokenType _constraintDelimiter;
810

911
internal MediaFeature(string name)
1012
{
@@ -27,11 +29,29 @@ internal MediaFeature(string name)
2729

2830
public override void ToCss(TextWriter writer, IStyleFormatter formatter)
2931
{
32+
var constraintDelimiter = GetConstraintDelimiter();
3033
var value = HasValue ? Value : null;
31-
writer.Write(formatter.Constraint(Name, value));
34+
writer.Write(formatter.Constraint(Name, value, GetConstraintDelimiter()));
3235
}
3336

34-
internal bool TrySetValue(TokenValue tokenValue)
37+
private string GetConstraintDelimiter()
38+
{
39+
if (_constraintDelimiter == TokenType.Colon)
40+
return ": ";
41+
if (_constraintDelimiter == TokenType.GreaterThan)
42+
return " > ";
43+
if (_constraintDelimiter == TokenType.LessThan)
44+
return " < ";
45+
if (_constraintDelimiter == TokenType.Equal)
46+
return " = ";
47+
if (_constraintDelimiter == TokenType.GreaterThanOrEqual)
48+
return " >= ";
49+
if (_constraintDelimiter == TokenType.LessThanOrEqual)
50+
return " <= ";
51+
return ": ";
52+
}
53+
54+
internal bool TrySetValue(TokenValue tokenValue, TokenType constraintDelimiter)
3555
{
3656
bool result;
3757

@@ -42,6 +62,8 @@ internal bool TrySetValue(TokenValue tokenValue)
4262

4363
if (result) _tokenValue = tokenValue;
4464

65+
_constraintDelimiter = constraintDelimiter;
66+
4567
return result;
4668
}
4769
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace ExCSS
2+
{
3+
internal sealed class SizeMediaFeature : MediaFeature
4+
{
5+
public SizeMediaFeature(string name) : base(name)
6+
{
7+
}
8+
9+
internal override IValueConverter Converter => Converters.LengthConverter;
10+
}
11+
}

src/ExCSS/Model/Converters.cs

+1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ public static readonly IValueConverter
264264
public static readonly IValueConverter OverflowModeConverter = Map.OverflowModes.ToConverter();
265265
public static readonly IValueConverter FloatingConverter = Map.FloatingModes.ToConverter();
266266
public static readonly IValueConverter DisplayModeConverter = Map.DisplayModes.ToConverter();
267+
public static readonly IValueConverter ContainerTypeConverter = Map.ContainerTypes.ToConverter();
267268
public static readonly IValueConverter ClearModeConverter = Map.ClearModes.ToConverter();
268269
public static readonly IValueConverter FontStretchConverter = Map.FontStretches.ToConverter();
269270
public static readonly IValueConverter FontStyleConverter = Map.FontStyles.ToConverter();

src/ExCSS/Model/IStyleFormatter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public interface IStyleFormatter
99
string Declaration(string name, string value, bool important);
1010
string Declarations(IEnumerable<string> declarations);
1111
string Medium(bool exclusive, bool inverse, string type, IEnumerable<IStyleFormattable> constraints);
12-
string Constraint(string name, string value);
12+
string Constraint(string name, string value, string constraintDelimiter);
1313
string Rule(string name, string value);
1414
string Rule(string name, string prelude, string rules);
1515
string Style(string selector, IStyleFormattable rules);

src/ExCSS/Model/Map.cs

+8
Original file line numberDiff line numberDiff line change
@@ -592,5 +592,13 @@ internal static class Map
592592
{ Keywords.SelfEnd, AlignItem.SelfEnd },
593593
{ Keywords.Baseline, AlignItem.Baseline },
594594
};
595+
596+
public static readonly Dictionary<string, ContainerType> ContainerTypes =
597+
new(StringComparer.OrdinalIgnoreCase)
598+
{
599+
{Keywords.Normal, ContainerType.Normal},
600+
{Keywords.Size, ContainerType.Size},
601+
{Keywords.InlineSize, ContainerType.InlineSize}
602+
};
595603
}
596604
}

src/ExCSS/Model/ParserExtensions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ public static Rule CreateRule(this StylesheetParser parser, RuleType type)
138138
return new KeyframesRule(parser);
139139
case RuleType.Media:
140140
return new MediaRule(parser);
141+
case RuleType.Container:
142+
return new ContainerRule(parser);
141143
case RuleType.Namespace:
142144
return new NamespaceRule(parser);
143145
case RuleType.Page:

src/ExCSS/Model/StyleDeclaration.cs

+12
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,18 @@ public string ColumnWidth
842842
set => SetPropertyValue(PropertyNames.ColumnWidth, value);
843843
}
844844

845+
public string ContainerName
846+
{
847+
get => GetPropertyValue(PropertyNames.ContainerName);
848+
set => SetPropertyValue(PropertyNames.ContainerName, value);
849+
}
850+
851+
public string ContainerType
852+
{
853+
get => GetPropertyValue(PropertyNames.ContainerType);
854+
set => SetPropertyValue(PropertyNames.ContainerType, value);
855+
}
856+
845857
public string Content
846858
{
847859
get => GetPropertyValue(PropertyNames.Content);

0 commit comments

Comments
 (0)