From bf713a7bab887d8ddd67b05a1dc8c68a7da55fbf Mon Sep 17 00:00:00 2001 From: Kevin Segaud Date: Sun, 1 Mar 2026 18:22:19 +0100 Subject: [PATCH] feat: add typed CssObjectFit, CssPointerEvents, and CssResize property support Add three new CSS property types following the established sealed class pattern: - CssObjectFit: fill, contain, cover, none, scaleDown keywords - CssPointerEvents: auto, none, visiblePainted, visibleFill, visibleStroke, visible, painted, fill, stroke, all keywords - CssResize: none, both, horizontal, vertical, block, inline keywords Closes #73, closes #74, closes #75 --- packages/spark_css/CHANGELOG.md | 3 + .../lib/src/css_types/css_object_fit.dart | 53 +++++++++++++++ .../lib/src/css_types/css_pointer_events.dart | 64 +++++++++++++++++++ .../lib/src/css_types/css_resize.dart | 54 ++++++++++++++++ .../lib/src/css_types/css_types.dart | 3 + packages/spark_css/lib/src/style.dart | 8 +++ .../spark_css/test/css_object_fit_test.dart | 27 ++++++++ .../test/css_pointer_events_test.dart | 35 ++++++++++ packages/spark_css/test/css_resize_test.dart | 28 ++++++++ 9 files changed, 275 insertions(+) create mode 100644 packages/spark_css/lib/src/css_types/css_object_fit.dart create mode 100644 packages/spark_css/lib/src/css_types/css_pointer_events.dart create mode 100644 packages/spark_css/lib/src/css_types/css_resize.dart create mode 100644 packages/spark_css/test/css_object_fit_test.dart create mode 100644 packages/spark_css/test/css_pointer_events_test.dart create mode 100644 packages/spark_css/test/css_resize_test.dart diff --git a/packages/spark_css/CHANGELOG.md b/packages/spark_css/CHANGELOG.md index 238d7a4..9be56b7 100644 --- a/packages/spark_css/CHANGELOG.md +++ b/packages/spark_css/CHANGELOG.md @@ -18,6 +18,9 @@ - **Feat**: Added `CssListStyle` sealed class for `list-style` shorthand property with `.none` keyword, shorthand constructor with optional `type`, `position`, and `image` parameters, plus `.variable()`, `.raw()`, and `.global()` escape hatches. - **Feat**: Added `CssListStyleType` sealed class with `.disc`, `.circle`, `.square`, `.decimal`, `.none` keywords. - **Feat**: Added `CssListStylePosition` sealed class with `.inside`, `.outside` keywords. +- **Feat**: Added `CssObjectFit` sealed class for `object-fit` property with `.fill`, `.contain`, `.cover`, `.none`, `.scaleDown` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssPointerEvents` sealed class for `pointer-events` property with `.auto`, `.none`, `.visiblePainted`, `.visibleFill`, `.visibleStroke`, `.visible`, `.painted`, `.fill`, `.stroke`, `.all` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssResize` sealed class for `resize` property with `.none`, `.both`, `.horizontal`, `.vertical`, `.block`, `.inline` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. ### Changed diff --git a/packages/spark_css/lib/src/css_types/css_object_fit.dart b/packages/spark_css/lib/src/css_types/css_object_fit.dart new file mode 100644 index 0000000..56adb22 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_object_fit.dart @@ -0,0 +1,53 @@ +import 'css_value.dart'; + +/// CSS object-fit property values. +sealed class CssObjectFit implements CssValue { + const CssObjectFit._(); + + static const CssObjectFit fill = _CssObjectFitKeyword('fill'); + static const CssObjectFit contain = _CssObjectFitKeyword('contain'); + static const CssObjectFit cover = _CssObjectFitKeyword('cover'); + static const CssObjectFit none = _CssObjectFitKeyword('none'); + static const CssObjectFit scaleDown = _CssObjectFitKeyword('scale-down'); + + /// CSS variable reference. + factory CssObjectFit.variable(String varName) = _CssObjectFitVariable; + + /// Raw CSS value escape hatch. + factory CssObjectFit.raw(String value) = _CssObjectFitRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssObjectFit.global(CssGlobal global) = _CssObjectFitGlobal; +} + +final class _CssObjectFitKeyword extends CssObjectFit { + final String keyword; + const _CssObjectFitKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssObjectFitVariable extends CssObjectFit { + final String varName; + const _CssObjectFitVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssObjectFitRaw extends CssObjectFit { + final String value; + const _CssObjectFitRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssObjectFitGlobal extends CssObjectFit { + final CssGlobal global; + const _CssObjectFitGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_pointer_events.dart b/packages/spark_css/lib/src/css_types/css_pointer_events.dart new file mode 100644 index 0000000..f64c282 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_pointer_events.dart @@ -0,0 +1,64 @@ +import 'css_value.dart'; + +/// CSS pointer-events property values. +sealed class CssPointerEvents implements CssValue { + const CssPointerEvents._(); + + static const CssPointerEvents auto = _CssPointerEventsKeyword('auto'); + static const CssPointerEvents none = _CssPointerEventsKeyword('none'); + static const CssPointerEvents visiblePainted = _CssPointerEventsKeyword( + 'visiblePainted', + ); + static const CssPointerEvents visibleFill = _CssPointerEventsKeyword( + 'visibleFill', + ); + static const CssPointerEvents visibleStroke = _CssPointerEventsKeyword( + 'visibleStroke', + ); + static const CssPointerEvents visible = _CssPointerEventsKeyword('visible'); + static const CssPointerEvents painted = _CssPointerEventsKeyword('painted'); + static const CssPointerEvents fill = _CssPointerEventsKeyword('fill'); + static const CssPointerEvents stroke = _CssPointerEventsKeyword('stroke'); + static const CssPointerEvents all = _CssPointerEventsKeyword('all'); + + /// CSS variable reference. + factory CssPointerEvents.variable(String varName) = _CssPointerEventsVariable; + + /// Raw CSS value escape hatch. + factory CssPointerEvents.raw(String value) = _CssPointerEventsRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssPointerEvents.global(CssGlobal global) = _CssPointerEventsGlobal; +} + +final class _CssPointerEventsKeyword extends CssPointerEvents { + final String keyword; + const _CssPointerEventsKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssPointerEventsVariable extends CssPointerEvents { + final String varName; + const _CssPointerEventsVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssPointerEventsRaw extends CssPointerEvents { + final String value; + const _CssPointerEventsRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssPointerEventsGlobal extends CssPointerEvents { + final CssGlobal global; + const _CssPointerEventsGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_resize.dart b/packages/spark_css/lib/src/css_types/css_resize.dart new file mode 100644 index 0000000..131c582 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_resize.dart @@ -0,0 +1,54 @@ +import 'css_value.dart'; + +/// CSS resize property values. +sealed class CssResize implements CssValue { + const CssResize._(); + + static const CssResize none = _CssResizeKeyword('none'); + static const CssResize both = _CssResizeKeyword('both'); + static const CssResize horizontal = _CssResizeKeyword('horizontal'); + static const CssResize vertical = _CssResizeKeyword('vertical'); + static const CssResize block = _CssResizeKeyword('block'); + static const CssResize inline = _CssResizeKeyword('inline'); + + /// CSS variable reference. + factory CssResize.variable(String varName) = _CssResizeVariable; + + /// Raw CSS value escape hatch. + factory CssResize.raw(String value) = _CssResizeRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssResize.global(CssGlobal global) = _CssResizeGlobal; +} + +final class _CssResizeKeyword extends CssResize { + final String keyword; + const _CssResizeKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssResizeVariable extends CssResize { + final String varName; + const _CssResizeVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssResizeRaw extends CssResize { + final String value; + const _CssResizeRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssResizeGlobal extends CssResize { + final CssGlobal global; + const _CssResizeGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_types.dart b/packages/spark_css/lib/src/css_types/css_types.dart index 2d70053..462fd73 100644 --- a/packages/spark_css/lib/src/css_types/css_types.dart +++ b/packages/spark_css/lib/src/css_types/css_types.dart @@ -30,11 +30,14 @@ export 'css_justify_items.dart'; export 'css_length.dart'; export 'css_list_style.dart'; export 'css_number.dart'; +export 'css_object_fit.dart'; export 'css_outline.dart'; export 'css_overflow.dart'; +export 'css_pointer_events.dart'; export 'css_position.dart'; export 'css_radial_shape.dart'; export 'css_radial_size.dart'; +export 'css_resize.dart'; export 'css_spacing.dart'; export 'css_text.dart'; export 'css_text_shadow.dart'; diff --git a/packages/spark_css/lib/src/style.dart b/packages/spark_css/lib/src/style.dart index b8cb8fe..60017eb 100644 --- a/packages/spark_css/lib/src/style.dart +++ b/packages/spark_css/lib/src/style.dart @@ -307,6 +307,9 @@ class Style implements CssStyle { CssCursor? cursor, CssBoxShadow? boxShadow, CssBoxSizing? boxSizing, + CssObjectFit? objectFit, + CssPointerEvents? pointerEvents, + CssResize? resize, CssFilter? filter, CssFilter? backdropFilter, // Backgrounds @@ -456,6 +459,11 @@ class Style implements CssStyle { if (cursor != null) _properties['cursor'] = cursor.toCss(); if (boxShadow != null) _properties['box-shadow'] = boxShadow.toCss(); if (boxSizing != null) _properties['box-sizing'] = boxSizing.toCss(); + if (objectFit != null) _properties['object-fit'] = objectFit.toCss(); + if (pointerEvents != null) { + _properties['pointer-events'] = pointerEvents.toCss(); + } + if (resize != null) _properties['resize'] = resize.toCss(); if (filter != null) _properties['filter'] = filter.toCss(); if (backdropFilter != null) { _properties['backdrop-filter'] = backdropFilter.toCss(); diff --git a/packages/spark_css/test/css_object_fit_test.dart b/packages/spark_css/test/css_object_fit_test.dart new file mode 100644 index 0000000..ae0d520 --- /dev/null +++ b/packages/spark_css/test/css_object_fit_test.dart @@ -0,0 +1,27 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssObjectFit', () { + test('keywords output correct CSS', () { + expect(CssObjectFit.fill.toCss(), equals('fill')); + expect(CssObjectFit.contain.toCss(), equals('contain')); + expect(CssObjectFit.cover.toCss(), equals('cover')); + expect(CssObjectFit.none.toCss(), equals('none')); + expect(CssObjectFit.scaleDown.toCss(), equals('scale-down')); + }); + test('variable outputs correct CSS', () { + expect(CssObjectFit.variable('of').toCss(), equals('var(--of)')); + }); + test('raw outputs value as-is', () { + expect(CssObjectFit.raw('cover').toCss(), equals('cover')); + }); + test('global outputs correct CSS', () { + expect(CssObjectFit.global(CssGlobal.inherit).toCss(), equals('inherit')); + }); + test('Style.typed integration', () { + final style = Style.typed(objectFit: CssObjectFit.cover); + expect(style.toCss(), contains('object-fit: cover;')); + }); + }); +} diff --git a/packages/spark_css/test/css_pointer_events_test.dart b/packages/spark_css/test/css_pointer_events_test.dart new file mode 100644 index 0000000..a06df4d --- /dev/null +++ b/packages/spark_css/test/css_pointer_events_test.dart @@ -0,0 +1,35 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssPointerEvents', () { + test('keywords output correct CSS', () { + expect(CssPointerEvents.auto.toCss(), equals('auto')); + expect(CssPointerEvents.none.toCss(), equals('none')); + expect(CssPointerEvents.visiblePainted.toCss(), equals('visiblePainted')); + expect(CssPointerEvents.visibleFill.toCss(), equals('visibleFill')); + expect(CssPointerEvents.visibleStroke.toCss(), equals('visibleStroke')); + expect(CssPointerEvents.visible.toCss(), equals('visible')); + expect(CssPointerEvents.painted.toCss(), equals('painted')); + expect(CssPointerEvents.fill.toCss(), equals('fill')); + expect(CssPointerEvents.stroke.toCss(), equals('stroke')); + expect(CssPointerEvents.all.toCss(), equals('all')); + }); + test('variable outputs correct CSS', () { + expect(CssPointerEvents.variable('pe').toCss(), equals('var(--pe)')); + }); + test('raw outputs value as-is', () { + expect(CssPointerEvents.raw('none').toCss(), equals('none')); + }); + test('global outputs correct CSS', () { + expect( + CssPointerEvents.global(CssGlobal.inherit).toCss(), + equals('inherit'), + ); + }); + test('Style.typed integration', () { + final style = Style.typed(pointerEvents: CssPointerEvents.none); + expect(style.toCss(), contains('pointer-events: none;')); + }); + }); +} diff --git a/packages/spark_css/test/css_resize_test.dart b/packages/spark_css/test/css_resize_test.dart new file mode 100644 index 0000000..2c1218a --- /dev/null +++ b/packages/spark_css/test/css_resize_test.dart @@ -0,0 +1,28 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssResize', () { + test('keywords output correct CSS', () { + expect(CssResize.none.toCss(), equals('none')); + expect(CssResize.both.toCss(), equals('both')); + expect(CssResize.horizontal.toCss(), equals('horizontal')); + expect(CssResize.vertical.toCss(), equals('vertical')); + expect(CssResize.block.toCss(), equals('block')); + expect(CssResize.inline.toCss(), equals('inline')); + }); + test('variable outputs correct CSS', () { + expect(CssResize.variable('r').toCss(), equals('var(--r)')); + }); + test('raw outputs value as-is', () { + expect(CssResize.raw('both').toCss(), equals('both')); + }); + test('global outputs correct CSS', () { + expect(CssResize.global(CssGlobal.inherit).toCss(), equals('inherit')); + }); + test('Style.typed integration', () { + final style = Style.typed(resize: CssResize.both); + expect(style.toCss(), contains('resize: both;')); + }); + }); +}