Skip to content

Commit 64bb60d

Browse files
feat(spark_css): add typed CssBackgroundImage and gradient support
- Added `CssBackgroundImage` sealed class with support for `url`, `linear-gradient`, `radial-gradient`, and `none`. - Added supporting types: `CssGradientDirection`, `CssRadialShape`, `CssRadialSize`, `CssBackgroundPosition`. - Added `CssGradientStop` helper class. - Added comprehensive tests in `packages/spark_css/test/css_background_image_test.dart`. - NOTE: `CssGradientDirection.angle` currently accepts `String`; `CssAngle` will be integrated in a future PR.
1 parent 053d8a7 commit 64bb60d

6 files changed

Lines changed: 717 additions & 0 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import 'css_value.dart';
2+
import 'css_color.dart';
3+
import 'css_length.dart';
4+
import 'css_gradient_direction.dart';
5+
import 'css_radial_shape.dart';
6+
import 'css_radial_size.dart';
7+
import 'css_background_position.dart';
8+
9+
/// Represents a color stop in a gradient.
10+
class CssGradientStop {
11+
/// The color at this stop.
12+
final CssColor color;
13+
14+
/// The position of the stop along the gradient line.
15+
final CssLength? offset;
16+
17+
/// Creates a new gradient color stop.
18+
const CssGradientStop(this.color, [this.offset]);
19+
20+
/// Converts the stop to its CSS string representation.
21+
String toCss() {
22+
if (offset != null) {
23+
return '${color.toCss()} ${offset!.toCss()}';
24+
}
25+
return color.toCss();
26+
}
27+
}
28+
29+
/// CSS background-image property values.
30+
sealed class CssBackgroundImage implements CssValue {
31+
const CssBackgroundImage._();
32+
33+
/// `none` keyword.
34+
static const CssBackgroundImage none = _CssBackgroundImageKeyword('none');
35+
36+
/// URL image.
37+
factory CssBackgroundImage.url(String url) = _CssBackgroundImageUrl;
38+
39+
/// Linear gradient.
40+
///
41+
/// [direction] can be an angle (e.g. '45deg') or side/corner (e.g. 'to bottom right').
42+
factory CssBackgroundImage.linearGradient({
43+
CssGradientDirection? direction,
44+
required List<CssGradientStop> stops,
45+
bool repeating,
46+
}) = _CssBackgroundImageLinearGradient;
47+
48+
/// Radial gradient.
49+
///
50+
/// [shape] can be 'circle' or 'ellipse'.
51+
/// [size] can be 'closest-side', 'farthest-corner', etc. or a length.
52+
/// [position] is the center position (e.g. 'center', '50% 50%').
53+
factory CssBackgroundImage.radialGradient({
54+
CssRadialShape? shape,
55+
CssRadialSize? size,
56+
CssBackgroundPosition? position,
57+
required List<CssGradientStop> stops,
58+
bool repeating,
59+
}) = _CssBackgroundImageRadialGradient;
60+
61+
/// Multiple background images.
62+
factory CssBackgroundImage.list(List<CssBackgroundImage> images) =
63+
_CssBackgroundImageList;
64+
65+
/// Raw CSS value escape hatch.
66+
factory CssBackgroundImage.raw(String value) = _CssBackgroundImageRaw;
67+
68+
/// Global keyword (inherit, initial, unset, revert).
69+
factory CssBackgroundImage.global(CssGlobal global) =
70+
_CssBackgroundImageGlobal;
71+
}
72+
73+
final class _CssBackgroundImageKeyword extends CssBackgroundImage {
74+
final String keyword;
75+
const _CssBackgroundImageKeyword(this.keyword) : super._();
76+
77+
@override
78+
String toCss() => keyword;
79+
}
80+
81+
final class _CssBackgroundImageUrl extends CssBackgroundImage {
82+
final String url;
83+
const _CssBackgroundImageUrl(this.url) : super._();
84+
85+
@override
86+
String toCss() => 'url($url)';
87+
}
88+
89+
final class _CssBackgroundImageLinearGradient extends CssBackgroundImage {
90+
final CssGradientDirection? direction;
91+
final List<CssGradientStop> stops;
92+
final bool repeating;
93+
94+
const _CssBackgroundImageLinearGradient({
95+
this.direction,
96+
required this.stops,
97+
this.repeating = false,
98+
}) : super._();
99+
100+
@override
101+
String toCss() {
102+
final buffer = StringBuffer(
103+
repeating ? 'repeating-linear-gradient(' : 'linear-gradient(',
104+
);
105+
if (direction != null) {
106+
buffer.write('${direction!.toCss()}, ');
107+
}
108+
buffer.write(stops.map((s) => s.toCss()).join(', '));
109+
buffer.write(')');
110+
return buffer.toString();
111+
}
112+
}
113+
114+
final class _CssBackgroundImageRadialGradient extends CssBackgroundImage {
115+
final CssRadialShape? shape;
116+
final CssRadialSize? size;
117+
final CssBackgroundPosition? position;
118+
final List<CssGradientStop> stops;
119+
final bool repeating;
120+
121+
const _CssBackgroundImageRadialGradient({
122+
this.shape,
123+
this.size,
124+
this.position,
125+
required this.stops,
126+
this.repeating = false,
127+
}) : super._();
128+
129+
@override
130+
String toCss() {
131+
final buffer = StringBuffer(
132+
repeating ? 'repeating-radial-gradient(' : 'radial-gradient(',
133+
);
134+
135+
final hasShapeOrSize = shape != null || size != null;
136+
final hasPosition = position != null;
137+
138+
if (hasShapeOrSize || hasPosition) {
139+
if (shape != null) {
140+
buffer.write(shape!.toCss());
141+
if (size != null) buffer.write(' ');
142+
}
143+
if (size != null) buffer.write(size!.toCss());
144+
145+
if (hasPosition) {
146+
if (hasShapeOrSize) buffer.write(' ');
147+
buffer.write('at ${position!.toCss()}');
148+
}
149+
buffer.write(', ');
150+
}
151+
152+
buffer.write(stops.map((s) => s.toCss()).join(', '));
153+
buffer.write(')');
154+
return buffer.toString();
155+
}
156+
}
157+
158+
final class _CssBackgroundImageList extends CssBackgroundImage {
159+
final List<CssBackgroundImage> images;
160+
const _CssBackgroundImageList(this.images) : super._();
161+
162+
@override
163+
String toCss() => images.map((i) => i.toCss()).join(', ');
164+
}
165+
166+
final class _CssBackgroundImageRaw extends CssBackgroundImage {
167+
final String value;
168+
const _CssBackgroundImageRaw(this.value) : super._();
169+
170+
@override
171+
String toCss() => value;
172+
}
173+
174+
final class _CssBackgroundImageGlobal extends CssBackgroundImage {
175+
final CssGlobal global;
176+
const _CssBackgroundImageGlobal(this.global) : super._();
177+
178+
@override
179+
String toCss() => global.toCss();
180+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import 'css_length.dart';
2+
import 'css_value.dart';
3+
4+
/// CSS background-position property.
5+
sealed class CssBackgroundPosition implements CssValue {
6+
const CssBackgroundPosition._();
7+
8+
/// Center position (`center`).
9+
static const CssBackgroundPosition center = _CssBackgroundPositionKeyword(
10+
'center',
11+
);
12+
13+
/// Top position (`top` - equivalent to `top center`).
14+
static const CssBackgroundPosition top = _CssBackgroundPositionKeyword('top');
15+
16+
/// Bottom position (`bottom` - equivalent to `bottom center`).
17+
static const CssBackgroundPosition bottom = _CssBackgroundPositionKeyword(
18+
'bottom',
19+
);
20+
21+
/// Left position (`left` - equivalent to `left center`).
22+
static const CssBackgroundPosition left = _CssBackgroundPositionKeyword(
23+
'left',
24+
);
25+
26+
/// Right position (`right` - equivalent to `right center`).
27+
static const CssBackgroundPosition right = _CssBackgroundPositionKeyword(
28+
'right',
29+
);
30+
31+
/// Top left position (`top left`).
32+
static const CssBackgroundPosition topLeft = _CssBackgroundPositionKeyword(
33+
'top left',
34+
);
35+
36+
/// Top right position (`top right`).
37+
static const CssBackgroundPosition topRight = _CssBackgroundPositionKeyword(
38+
'top right',
39+
);
40+
41+
/// Bottom left position (`bottom left`).
42+
static const CssBackgroundPosition bottomLeft = _CssBackgroundPositionKeyword(
43+
'bottom left',
44+
);
45+
46+
/// Bottom right position (`bottom right`).
47+
static const CssBackgroundPosition bottomRight =
48+
_CssBackgroundPositionKeyword('bottom right');
49+
50+
/// Position defined by x and y coordinates.
51+
factory CssBackgroundPosition.xy(CssLength x, CssLength y) =
52+
_CssBackgroundPositionXY;
53+
54+
/// Position defined by x coordinate (y defaults to center).
55+
factory CssBackgroundPosition.x(CssLength x) = _CssBackgroundPositionX;
56+
57+
/// Raw CSS value escape hatch.
58+
factory CssBackgroundPosition.raw(String value) = _CssBackgroundPositionRaw;
59+
60+
/// Global keyword (inherit, initial, unset, revert).
61+
factory CssBackgroundPosition.global(CssGlobal global) =
62+
_CssBackgroundPositionGlobal;
63+
}
64+
65+
final class _CssBackgroundPositionKeyword extends CssBackgroundPosition {
66+
final String keyword;
67+
const _CssBackgroundPositionKeyword(this.keyword) : super._();
68+
69+
@override
70+
String toCss() => keyword;
71+
}
72+
73+
final class _CssBackgroundPositionXY extends CssBackgroundPosition {
74+
final CssLength x;
75+
final CssLength y;
76+
const _CssBackgroundPositionXY(this.x, this.y) : super._();
77+
78+
@override
79+
String toCss() => '${x.toCss()} ${y.toCss()}';
80+
}
81+
82+
final class _CssBackgroundPositionX extends CssBackgroundPosition {
83+
final CssLength x;
84+
const _CssBackgroundPositionX(this.x) : super._();
85+
86+
@override
87+
String toCss() => x.toCss();
88+
}
89+
90+
final class _CssBackgroundPositionRaw extends CssBackgroundPosition {
91+
final String value;
92+
const _CssBackgroundPositionRaw(this.value) : super._();
93+
94+
@override
95+
String toCss() => value;
96+
}
97+
98+
final class _CssBackgroundPositionGlobal extends CssBackgroundPosition {
99+
final CssGlobal global;
100+
const _CssBackgroundPositionGlobal(this.global) : super._();
101+
102+
@override
103+
String toCss() => global.toCss();
104+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import 'css_value.dart';
2+
3+
/// CSS linear-gradient direction.
4+
sealed class CssGradientDirection implements CssValue {
5+
const CssGradientDirection._();
6+
7+
/// Direction towards top (`to top`).
8+
static const CssGradientDirection toTop = _CssGradientDirectionKeyword(
9+
'to top',
10+
);
11+
12+
/// Direction towards bottom (`to bottom`).
13+
static const CssGradientDirection toBottom = _CssGradientDirectionKeyword(
14+
'to bottom',
15+
);
16+
17+
/// Direction towards left (`to left`).
18+
static const CssGradientDirection toLeft = _CssGradientDirectionKeyword(
19+
'to left',
20+
);
21+
22+
/// Direction towards right (`to right`).
23+
static const CssGradientDirection toRight = _CssGradientDirectionKeyword(
24+
'to right',
25+
);
26+
27+
/// Direction towards top left (`to top left`).
28+
static const CssGradientDirection toTopLeft = _CssGradientDirectionKeyword(
29+
'to top left',
30+
);
31+
32+
/// Direction towards top right (`to top right`).
33+
static const CssGradientDirection toTopRight = _CssGradientDirectionKeyword(
34+
'to top right',
35+
);
36+
37+
/// Direction towards bottom left (`to bottom left`).
38+
static const CssGradientDirection toBottomLeft = _CssGradientDirectionKeyword(
39+
'to bottom left',
40+
);
41+
42+
/// Direction towards bottom right (`to bottom right`).
43+
static const CssGradientDirection toBottomRight =
44+
_CssGradientDirectionKeyword('to bottom right');
45+
46+
/// Direction defined by an angle.
47+
/// TODO: Use CssAngle once it is merged.
48+
factory CssGradientDirection.angle(String angle) = _CssGradientDirectionAngle;
49+
50+
/// Raw CSS value escape hatch.
51+
factory CssGradientDirection.raw(String value) = _CssGradientDirectionRaw;
52+
53+
/// Global keyword (inherit, initial, unset, revert).
54+
factory CssGradientDirection.global(CssGlobal global) =
55+
_CssGradientDirectionGlobal;
56+
}
57+
58+
final class _CssGradientDirectionKeyword extends CssGradientDirection {
59+
final String keyword;
60+
const _CssGradientDirectionKeyword(this.keyword) : super._();
61+
62+
@override
63+
String toCss() => keyword;
64+
}
65+
66+
final class _CssGradientDirectionAngle extends CssGradientDirection {
67+
final String angle;
68+
const _CssGradientDirectionAngle(this.angle) : super._();
69+
70+
@override
71+
String toCss() => angle;
72+
}
73+
74+
final class _CssGradientDirectionRaw extends CssGradientDirection {
75+
final String value;
76+
const _CssGradientDirectionRaw(this.value) : super._();
77+
78+
@override
79+
String toCss() => value;
80+
}
81+
82+
final class _CssGradientDirectionGlobal extends CssGradientDirection {
83+
final CssGlobal global;
84+
const _CssGradientDirectionGlobal(this.global) : super._();
85+
86+
@override
87+
String toCss() => global.toCss();
88+
}

0 commit comments

Comments
 (0)