Skip to content

Commit 37c6483

Browse files
feat(spark_css): add typed CssFilter support
Added `CssFilter` sealed class in `packages/spark_css/lib/src/css_types/css_filter.dart` to support typed CSS filter functions. - `CssFilter` supports `blur`, `brightness`, `contrast`, `dropShadow`, `grayscale`, `hueRotate`, `invert`, `opacity`, `saturate`, `sepia`, `compose`. - Explicit factories (e.g., `brightness` vs `brightnessPercent`, `hueRotate` vs `hueRotateRaw`) are used for strict typing. - Added comprehensive tests in `packages/spark_css/test/css_filter_test.dart`.
1 parent c6eb91d commit 37c6483

2 files changed

Lines changed: 402 additions & 0 deletions

File tree

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import 'css_color.dart';
2+
import 'css_length.dart';
3+
import 'css_value.dart';
4+
5+
/// CSS filter function values.
6+
sealed class CssFilter implements CssValue {
7+
const CssFilter._();
8+
9+
static const CssFilter none = _CssFilterKeyword('none');
10+
11+
/// Applies a Gaussian blur to the input image.
12+
factory CssFilter.blur(CssLength radius) = _CssFilterBlur;
13+
14+
/// Applies a linear multiplier to the input image, making it appear more or less bright.
15+
/// `amount`: A number multiplier (0-1, or >1). 0 is black, 1 is unchanged.
16+
factory CssFilter.brightness(num amount) = _CssFilterBrightness;
17+
18+
/// Same as [CssFilter.brightness] but takes a percentage (0-100, or >100).
19+
factory CssFilter.brightnessPercent(num amount) = _CssFilterBrightnessPercent;
20+
21+
/// Adjusts the contrast of the input.
22+
/// `amount`: A number multiplier (0-1, or >1). 0 is gray, 1 is unchanged.
23+
factory CssFilter.contrast(num amount) = _CssFilterContrast;
24+
25+
/// Same as [CssFilter.contrast] but takes a percentage (0-100, or >100).
26+
factory CssFilter.contrastPercent(num amount) = _CssFilterContrastPercent;
27+
28+
/// Applies a drop shadow effect to the input image.
29+
factory CssFilter.dropShadow({
30+
required CssLength offsetX,
31+
required CssLength offsetY,
32+
CssLength? blurRadius,
33+
CssColor? color,
34+
}) = _CssFilterDropShadow;
35+
36+
/// Converts the input image to grayscale.
37+
/// `amount`: A number multiplier (0-1). 1 is grayscale, 0 is unchanged.
38+
factory CssFilter.grayscale(num amount) = _CssFilterGrayscale;
39+
40+
/// Same as [CssFilter.grayscale] but takes a percentage (0-100).
41+
factory CssFilter.grayscalePercent(num amount) = _CssFilterGrayscalePercent;
42+
43+
/// Applies a hue rotation on the input image.
44+
///
45+
/// TODO: Update to accept `CssAngle` once implemented.
46+
factory CssFilter.hueRotate(num angle) = _CssFilterHueRotate;
47+
48+
/// Same as [CssFilter.hueRotate] but takes a unit string (e.g. '90deg', '0.5turn').
49+
factory CssFilter.hueRotateRaw(String angle) = _CssFilterHueRotateRaw;
50+
51+
/// Inverts the samples in the input image.
52+
/// `amount`: A number multiplier (0-1). 1 is inverted, 0 is unchanged.
53+
factory CssFilter.invert(num amount) = _CssFilterInvert;
54+
55+
/// Same as [CssFilter.invert] but takes a percentage (0-100).
56+
factory CssFilter.invertPercent(num amount) = _CssFilterInvertPercent;
57+
58+
/// Applies transparency to the samples in the input image.
59+
/// `amount`: A number multiplier (0-1). 0 is transparent, 1 is unchanged.
60+
factory CssFilter.opacity(num amount) = _CssFilterOpacity;
61+
62+
/// Same as [CssFilter.opacity] but takes a percentage (0-100).
63+
factory CssFilter.opacityPercent(num amount) = _CssFilterOpacityPercent;
64+
65+
/// Saturates the input image.
66+
/// `amount`: A number multiplier (0-1, or >1). 0 is un-saturated, 1 is unchanged.
67+
factory CssFilter.saturate(num amount) = _CssFilterSaturate;
68+
69+
/// Same as [CssFilter.saturate] but takes a percentage (0-100, or >100).
70+
factory CssFilter.saturatePercent(num amount) = _CssFilterSaturatePercent;
71+
72+
/// Converts the input image to sepia.
73+
/// `amount`: A number multiplier (0-1). 1 is sepia, 0 is unchanged.
74+
factory CssFilter.sepia(num amount) = _CssFilterSepia;
75+
76+
/// Same as [CssFilter.sepia] but takes a percentage (0-100).
77+
factory CssFilter.sepiaPercent(num amount) = _CssFilterSepiaPercent;
78+
79+
/// Composes multiple filter functions.
80+
factory CssFilter.compose(List<CssFilter> filters) = _CssFilterCompose;
81+
82+
/// CSS variable reference.
83+
factory CssFilter.variable(String varName) = _CssFilterVariable;
84+
85+
/// Raw CSS value escape hatch.
86+
factory CssFilter.raw(String value) = _CssFilterRaw;
87+
88+
/// Global keyword (inherit, initial, unset, revert).
89+
factory CssFilter.global(CssGlobal global) = _CssFilterGlobal;
90+
}
91+
92+
final class _CssFilterKeyword extends CssFilter {
93+
final String keyword;
94+
const _CssFilterKeyword(this.keyword) : super._();
95+
96+
@override
97+
String toCss() => keyword;
98+
}
99+
100+
final class _CssFilterBlur extends CssFilter {
101+
final CssLength radius;
102+
const _CssFilterBlur(this.radius) : super._();
103+
104+
@override
105+
String toCss() => 'blur(${radius.toCss()})';
106+
}
107+
108+
final class _CssFilterBrightness extends CssFilter {
109+
final num amount;
110+
const _CssFilterBrightness(this.amount) : super._();
111+
112+
@override
113+
String toCss() => 'brightness($amount)';
114+
}
115+
116+
final class _CssFilterBrightnessPercent extends CssFilter {
117+
final num amount;
118+
const _CssFilterBrightnessPercent(this.amount) : super._();
119+
120+
@override
121+
String toCss() => 'brightness($amount%)';
122+
}
123+
124+
final class _CssFilterContrast extends CssFilter {
125+
final num amount;
126+
const _CssFilterContrast(this.amount) : super._();
127+
128+
@override
129+
String toCss() => 'contrast($amount)';
130+
}
131+
132+
final class _CssFilterContrastPercent extends CssFilter {
133+
final num amount;
134+
const _CssFilterContrastPercent(this.amount) : super._();
135+
136+
@override
137+
String toCss() => 'contrast($amount%)';
138+
}
139+
140+
final class _CssFilterDropShadow extends CssFilter {
141+
final CssLength offsetX;
142+
final CssLength offsetY;
143+
final CssLength? blurRadius;
144+
final CssColor? color;
145+
146+
const _CssFilterDropShadow({
147+
required this.offsetX,
148+
required this.offsetY,
149+
this.blurRadius,
150+
this.color,
151+
}) : super._();
152+
153+
@override
154+
String toCss() {
155+
final parts = [offsetX.toCss(), offsetY.toCss()];
156+
if (blurRadius != null) parts.add(blurRadius!.toCss());
157+
if (color != null) parts.add(color!.toCss());
158+
return 'drop-shadow(${parts.join(' ')})';
159+
}
160+
}
161+
162+
final class _CssFilterGrayscale extends CssFilter {
163+
final num amount;
164+
const _CssFilterGrayscale(this.amount) : super._();
165+
166+
@override
167+
String toCss() => 'grayscale($amount)';
168+
}
169+
170+
final class _CssFilterGrayscalePercent extends CssFilter {
171+
final num amount;
172+
const _CssFilterGrayscalePercent(this.amount) : super._();
173+
174+
@override
175+
String toCss() => 'grayscale($amount%)';
176+
}
177+
178+
final class _CssFilterHueRotate extends CssFilter {
179+
final num angle;
180+
const _CssFilterHueRotate(this.angle) : super._();
181+
182+
@override
183+
String toCss() => 'hue-rotate(${angle}deg)';
184+
}
185+
186+
final class _CssFilterHueRotateRaw extends CssFilter {
187+
final String angle;
188+
const _CssFilterHueRotateRaw(this.angle) : super._();
189+
190+
@override
191+
String toCss() => 'hue-rotate($angle)';
192+
}
193+
194+
final class _CssFilterInvert extends CssFilter {
195+
final num amount;
196+
const _CssFilterInvert(this.amount) : super._();
197+
198+
@override
199+
String toCss() => 'invert($amount)';
200+
}
201+
202+
final class _CssFilterInvertPercent extends CssFilter {
203+
final num amount;
204+
const _CssFilterInvertPercent(this.amount) : super._();
205+
206+
@override
207+
String toCss() => 'invert($amount%)';
208+
}
209+
210+
final class _CssFilterOpacity extends CssFilter {
211+
final num amount;
212+
const _CssFilterOpacity(this.amount) : super._();
213+
214+
@override
215+
String toCss() => 'opacity($amount)';
216+
}
217+
218+
final class _CssFilterOpacityPercent extends CssFilter {
219+
final num amount;
220+
const _CssFilterOpacityPercent(this.amount) : super._();
221+
222+
@override
223+
String toCss() => 'opacity($amount%)';
224+
}
225+
226+
final class _CssFilterSaturate extends CssFilter {
227+
final num amount;
228+
const _CssFilterSaturate(this.amount) : super._();
229+
230+
@override
231+
String toCss() => 'saturate($amount)';
232+
}
233+
234+
final class _CssFilterSaturatePercent extends CssFilter {
235+
final num amount;
236+
const _CssFilterSaturatePercent(this.amount) : super._();
237+
238+
@override
239+
String toCss() => 'saturate($amount%)';
240+
}
241+
242+
final class _CssFilterSepia extends CssFilter {
243+
final num amount;
244+
const _CssFilterSepia(this.amount) : super._();
245+
246+
@override
247+
String toCss() => 'sepia($amount)';
248+
}
249+
250+
final class _CssFilterSepiaPercent extends CssFilter {
251+
final num amount;
252+
const _CssFilterSepiaPercent(this.amount) : super._();
253+
254+
@override
255+
String toCss() => 'sepia($amount%)';
256+
}
257+
258+
final class _CssFilterCompose extends CssFilter {
259+
final List<CssFilter> filters;
260+
const _CssFilterCompose(this.filters) : super._();
261+
262+
@override
263+
String toCss() => filters.map((f) => f.toCss()).join(' ');
264+
}
265+
266+
final class _CssFilterVariable extends CssFilter {
267+
final String varName;
268+
const _CssFilterVariable(this.varName) : super._();
269+
270+
@override
271+
String toCss() => 'var(--$varName)';
272+
}
273+
274+
final class _CssFilterRaw extends CssFilter {
275+
final String value;
276+
const _CssFilterRaw(this.value) : super._();
277+
278+
@override
279+
String toCss() => value;
280+
}
281+
282+
final class _CssFilterGlobal extends CssFilter {
283+
final CssGlobal global;
284+
const _CssFilterGlobal(this.global) : super._();
285+
286+
@override
287+
String toCss() => global.toCss();
288+
}

0 commit comments

Comments
 (0)