@@ -45,55 +45,94 @@ class CssMediaQuery {
4545
4646 /// Merges this with [other] to return a query that matches the intersection
4747 /// of both inputs.
48- CssMediaQuery merge (CssMediaQuery other) {
48+ MediaQueryMergeResult merge (CssMediaQuery other) {
4949 var ourModifier = this .modifier? .toLowerCase ();
5050 var ourType = this .type? .toLowerCase ();
5151 var theirModifier = other.modifier? .toLowerCase ();
5252 var theirType = other.type? .toLowerCase ();
5353
5454 if (ourType == null && theirType == null ) {
55- return new CssMediaQuery .condition (
56- features.toList ()..addAll (other.features));
55+ return new MediaQuerySuccessfulMergeResult ._( new CssMediaQuery .condition (
56+ this . features.toList ()..addAll (other.features) ));
5757 }
5858
5959 String modifier;
6060 String type;
61+ List <String > features;
6162 if ((ourModifier == 'not' ) != (theirModifier == 'not' )) {
62- if (ourType == theirType) return null ;
63+ if (ourType == theirType) {
64+ var negativeFeatures =
65+ ourModifier == 'not' ? this .features : other.features;
66+ var positiveFeatures =
67+ ourModifier == 'not' ? other.features : this .features;
68+
69+ // If the negative features are a subset of the positive features, the
70+ // query is empty. For example, `not screen and (color)` has no
71+ // intersection with `screen and (color) and (grid)`.
72+ //
73+ // However, `not screen and (color)` *does* intersect with `screen and
74+ // (grid)`, because it means `not (screen and (color))` and so it allows
75+ // a screen with no color but with a grid.
76+ if (negativeFeatures.every (positiveFeatures.contains)) {
77+ return MediaQueryMergeResult .empty;
78+ } else {
79+ return MediaQueryMergeResult .unrepresentable;
80+ }
81+ } else if (ourType == null || theirType == null ) {
82+ return MediaQueryMergeResult .unrepresentable;
83+ }
6384
6485 if (ourModifier == 'not' ) {
65- // The "not" would apply to the other query's features, which is not
66- // what we want.
67- if (other.features.isNotEmpty) return null ;
6886 modifier = theirModifier;
6987 type = theirType;
88+ features = other.features;
7089 } else {
71- if (this .features.isNotEmpty) return null ;
7290 modifier = ourModifier;
7391 type = ourType;
92+ features = this .features;
7493 }
7594 } else if (ourModifier == 'not' ) {
7695 assert (theirModifier == 'not' );
7796 // CSS has no way of representing "neither screen nor print".
78- if (ourType == theirType) return null ;
79- modifier = ourModifier; // "not"
80- type = ourType;
97+ if (ourType != theirType) return MediaQueryMergeResult .unrepresentable;
98+
99+ var moreFeatures = this .features.length > other.features.length
100+ ? this .features
101+ : other.features;
102+ var fewerFeatures = this .features.length > other.features.length
103+ ? other.features
104+ : this .features;
105+
106+ // If one set of features is a superset of the other, use those features
107+ // because they're strictly narrower.
108+ if (fewerFeatures.every (moreFeatures.contains)) {
109+ modifier = ourModifier; // "not"
110+ type = ourType;
111+ features = moreFeatures;
112+ } else {
113+ // Otherwise, there's no way to represent the intersection.
114+ return MediaQueryMergeResult .unrepresentable;
115+ }
81116 } else if (ourType == null ) {
82117 modifier = theirModifier;
83118 type = theirType;
119+ features = this .features.toList ()..addAll (other.features);
84120 } else if (theirType == null ) {
85121 modifier = ourModifier;
86122 type = ourType;
123+ features = this .features.toList ()..addAll (other.features);
87124 } else if (ourType != theirType) {
88- return null ;
125+ return MediaQueryMergeResult .empty ;
89126 } else {
90127 modifier = ourModifier ?? theirModifier;
91128 type = ourType;
129+ features = this .features.toList ()..addAll (other.features);
92130 }
93131
94- return new CssMediaQuery (type == ourType ? this .type : other.type,
132+ return new MediaQuerySuccessfulMergeResult ._(new CssMediaQuery (
133+ type == ourType ? this .type : other.type,
95134 modifier: modifier == ourModifier ? this .modifier : other.modifier,
96- features: features. toList ().. addAll (other.features ));
135+ features: features));
97136 }
98137
99138 bool operator == (other) =>
@@ -115,3 +154,36 @@ class CssMediaQuery {
115154 return buffer.toString ();
116155 }
117156}
157+
158+ /// The interface of possible return values of [CssMediaQuery.merge] .
159+ ///
160+ /// This is either the singleton values [empty] or [unrepresentable] , or an
161+ /// instance of [MediaQuerySuccessfulMergeResult] .
162+ abstract class MediaQueryMergeResult {
163+ /// A singleton value indicating that there are no contexts that match both
164+ /// input queries.
165+ static const empty = const _SingletonCssMediaQueryMergeResult ("empty" );
166+
167+ /// A singleton value indicating that the contexts that match both input
168+ /// queries can't be represented by a Level 3 media query.
169+ static const unrepresentable =
170+ const _SingletonCssMediaQueryMergeResult ("unrepresentable" );
171+ }
172+
173+ /// The subclass [MediaQueryMergeResult] that represents singleton enum values.
174+ class _SingletonCssMediaQueryMergeResult implements MediaQueryMergeResult {
175+ /// The name of the result type.
176+ final String _name;
177+
178+ const _SingletonCssMediaQueryMergeResult (this ._name);
179+
180+ String toString () => _name;
181+ }
182+
183+ /// A successful result of [CssMediaQuery.merge] .
184+ class MediaQuerySuccessfulMergeResult implements MediaQueryMergeResult {
185+ /// The merged media query.
186+ final CssMediaQuery query;
187+
188+ MediaQuerySuccessfulMergeResult ._(this .query);
189+ }
0 commit comments