diff --git a/packages/spark_css/CHANGELOG.md b/packages/spark_css/CHANGELOG.md index 9be56b7..ec8a3f7 100644 --- a/packages/spark_css/CHANGELOG.md +++ b/packages/spark_css/CHANGELOG.md @@ -21,6 +21,10 @@ - **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. +- **Feat**: Added `CssScrollBehavior` sealed class for `scroll-behavior` property with `.auto`, `.smooth` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssTextOverflow` sealed class for `text-overflow` property with `.clip`, `.ellipsis` keywords, `.value()` for custom strings, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssAspectRatio` sealed class for `aspect-ratio` property with `.auto` keyword, `.ratio()`, `.number()`, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssPlaceItems` sealed class for `place-items` shorthand property with align and optional justify parameters, plus `.variable()`, `.raw()`, and `.global()` escape hatches. ### Changed diff --git a/packages/spark_css/lib/src/css_types/css_aspect_ratio.dart b/packages/spark_css/lib/src/css_types/css_aspect_ratio.dart new file mode 100644 index 0000000..394b3a1 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_aspect_ratio.dart @@ -0,0 +1,72 @@ +import 'css_value.dart'; + +/// CSS aspect-ratio property values. +sealed class CssAspectRatio implements CssValue { + const CssAspectRatio._(); + + static const CssAspectRatio auto = _CssAspectRatioKeyword('auto'); + + /// Ratio value (e.g., `16 / 9`). + factory CssAspectRatio.ratio(num width, num height) = _CssAspectRatioRatio; + + /// Single number value (e.g., `1` or `0.5`). + factory CssAspectRatio.number(num value) = _CssAspectRatioNumber; + + /// CSS variable reference. + factory CssAspectRatio.variable(String varName) = _CssAspectRatioVariable; + + /// Raw CSS value escape hatch. + factory CssAspectRatio.raw(String value) = _CssAspectRatioRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssAspectRatio.global(CssGlobal global) = _CssAspectRatioGlobal; +} + +final class _CssAspectRatioKeyword extends CssAspectRatio { + final String keyword; + const _CssAspectRatioKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssAspectRatioRatio extends CssAspectRatio { + final num width; + final num height; + const _CssAspectRatioRatio(this.width, this.height) : super._(); + + @override + String toCss() => '$width / $height'; +} + +final class _CssAspectRatioNumber extends CssAspectRatio { + final num value; + const _CssAspectRatioNumber(this.value) : super._(); + + @override + String toCss() => '$value'; +} + +final class _CssAspectRatioVariable extends CssAspectRatio { + final String varName; + const _CssAspectRatioVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssAspectRatioRaw extends CssAspectRatio { + final String value; + const _CssAspectRatioRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssAspectRatioGlobal extends CssAspectRatio { + final CssGlobal global; + const _CssAspectRatioGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_place_items.dart b/packages/spark_css/lib/src/css_types/css_place_items.dart new file mode 100644 index 0000000..14e7ca9 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_place_items.dart @@ -0,0 +1,57 @@ +import 'css_flex.dart'; +import 'css_justify_items.dart'; +import 'css_value.dart'; + +/// CSS place-items shorthand property values. +sealed class CssPlaceItems implements CssValue { + const CssPlaceItems._(); + + /// Shorthand with align and optional justify. + factory CssPlaceItems(CssAlignItems align, [CssJustifyItems? justify]) = + _CssPlaceItemsShorthand; + + /// CSS variable reference. + factory CssPlaceItems.variable(String varName) = _CssPlaceItemsVariable; + + /// Raw CSS value escape hatch. + factory CssPlaceItems.raw(String value) = _CssPlaceItemsRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssPlaceItems.global(CssGlobal global) = _CssPlaceItemsGlobal; +} + +final class _CssPlaceItemsShorthand extends CssPlaceItems { + final CssAlignItems align; + final CssJustifyItems? justify; + const _CssPlaceItemsShorthand(this.align, [this.justify]) : super._(); + + @override + String toCss() { + if (justify == null) return align.toCss(); + return '${align.toCss()} ${justify!.toCss()}'; + } +} + +final class _CssPlaceItemsVariable extends CssPlaceItems { + final String varName; + const _CssPlaceItemsVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssPlaceItemsRaw extends CssPlaceItems { + final String value; + const _CssPlaceItemsRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssPlaceItemsGlobal extends CssPlaceItems { + final CssGlobal global; + const _CssPlaceItemsGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_scroll_behavior.dart b/packages/spark_css/lib/src/css_types/css_scroll_behavior.dart new file mode 100644 index 0000000..ea02fff --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_scroll_behavior.dart @@ -0,0 +1,51 @@ +import 'css_value.dart'; + +/// CSS scroll-behavior property values. +sealed class CssScrollBehavior implements CssValue { + const CssScrollBehavior._(); + + static const CssScrollBehavior auto = _CssScrollBehaviorKeyword('auto'); + static const CssScrollBehavior smooth = _CssScrollBehaviorKeyword('smooth'); + + /// CSS variable reference. + factory CssScrollBehavior.variable(String varName) = + _CssScrollBehaviorVariable; + + /// Raw CSS value escape hatch. + factory CssScrollBehavior.raw(String value) = _CssScrollBehaviorRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssScrollBehavior.global(CssGlobal global) = _CssScrollBehaviorGlobal; +} + +final class _CssScrollBehaviorKeyword extends CssScrollBehavior { + final String keyword; + const _CssScrollBehaviorKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssScrollBehaviorVariable extends CssScrollBehavior { + final String varName; + const _CssScrollBehaviorVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssScrollBehaviorRaw extends CssScrollBehavior { + final String value; + const _CssScrollBehaviorRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssScrollBehaviorGlobal extends CssScrollBehavior { + final CssGlobal global; + const _CssScrollBehaviorGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_text_overflow.dart b/packages/spark_css/lib/src/css_types/css_text_overflow.dart new file mode 100644 index 0000000..1dc14af --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_text_overflow.dart @@ -0,0 +1,61 @@ +import 'css_value.dart'; + +/// CSS text-overflow property values. +sealed class CssTextOverflow implements CssValue { + const CssTextOverflow._(); + + static const CssTextOverflow clip = _CssTextOverflowKeyword('clip'); + static const CssTextOverflow ellipsis = _CssTextOverflowKeyword('ellipsis'); + + /// Custom string value (outputs with quotes, e.g., `"…"`). + factory CssTextOverflow.value(String value) = _CssTextOverflowValue; + + /// CSS variable reference. + factory CssTextOverflow.variable(String varName) = _CssTextOverflowVariable; + + /// Raw CSS value escape hatch. + factory CssTextOverflow.raw(String value) = _CssTextOverflowRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssTextOverflow.global(CssGlobal global) = _CssTextOverflowGlobal; +} + +final class _CssTextOverflowKeyword extends CssTextOverflow { + final String keyword; + const _CssTextOverflowKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssTextOverflowValue extends CssTextOverflow { + final String value; + const _CssTextOverflowValue(this.value) : super._(); + + @override + String toCss() => '"$value"'; +} + +final class _CssTextOverflowVariable extends CssTextOverflow { + final String varName; + const _CssTextOverflowVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssTextOverflowRaw extends CssTextOverflow { + final String value; + const _CssTextOverflowRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssTextOverflowGlobal extends CssTextOverflow { + final CssGlobal global; + const _CssTextOverflowGlobal(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 462fd73..0f51541 100644 --- a/packages/spark_css/lib/src/css_types/css_types.dart +++ b/packages/spark_css/lib/src/css_types/css_types.dart @@ -3,6 +3,7 @@ library; export 'css_animation.dart'; export 'css_angle.dart'; +export 'css_aspect_ratio.dart'; export 'css_background.dart'; export 'css_background_attachment.dart'; export 'css_background_clip.dart'; @@ -33,13 +34,16 @@ export 'css_number.dart'; export 'css_object_fit.dart'; export 'css_outline.dart'; export 'css_overflow.dart'; +export 'css_place_items.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_scroll_behavior.dart'; export 'css_spacing.dart'; export 'css_text.dart'; +export 'css_text_overflow.dart'; export 'css_text_shadow.dart'; export 'css_transform.dart'; export 'css_transition.dart'; diff --git a/packages/spark_css/lib/src/style.dart b/packages/spark_css/lib/src/style.dart index 60017eb..0b0202a 100644 --- a/packages/spark_css/lib/src/style.dart +++ b/packages/spark_css/lib/src/style.dart @@ -237,6 +237,7 @@ class Style implements CssStyle { // Layout CssDisplay? display, CssPosition? position, + CssAspectRatio? aspectRatio, // Sizing CssLength? width, CssLength? height, @@ -271,6 +272,7 @@ class Style implements CssStyle { CssNumber? flexShrink, CssLength? gap, CssFlexShorthand? flex, + CssPlaceItems? placeItems, // Typography CssLength? fontSize, CssFontWeight? fontWeight, @@ -284,6 +286,7 @@ class Style implements CssStyle { CssNumber? lineHeight, CssLength? letterSpacing, CssTextShadow? textShadow, + CssTextOverflow? textOverflow, // Borders CssBorder? border, CssBorder? borderTop, @@ -310,6 +313,7 @@ class Style implements CssStyle { CssObjectFit? objectFit, CssPointerEvents? pointerEvents, CssResize? resize, + CssScrollBehavior? scrollBehavior, CssFilter? filter, CssFilter? backdropFilter, // Backgrounds @@ -348,6 +352,9 @@ class Style implements CssStyle { // Layout if (display != null) _properties['display'] = display.toCss(); if (position != null) _properties['position'] = position.toCss(); + if (aspectRatio != null) { + _properties['aspect-ratio'] = aspectRatio.toCss(); + } // Sizing if (width != null) _properties['width'] = width.toCss(); @@ -398,6 +405,7 @@ class Style implements CssStyle { if (flexShrink != null) _properties['flex-shrink'] = flexShrink.toCss(); if (gap != null) _properties['gap'] = gap.toCss(); if (flex != null) _properties['flex'] = flex.toCss(); + if (placeItems != null) _properties['place-items'] = placeItems.toCss(); // Typography if (fontSize != null) _properties['font-size'] = fontSize.toCss(); @@ -418,6 +426,9 @@ class Style implements CssStyle { _properties['letter-spacing'] = letterSpacing.toCss(); } if (textShadow != null) _properties['text-shadow'] = textShadow.toCss(); + if (textOverflow != null) { + _properties['text-overflow'] = textOverflow.toCss(); + } // Borders if (border != null) _properties['border'] = border.toCss(); @@ -464,6 +475,9 @@ class Style implements CssStyle { _properties['pointer-events'] = pointerEvents.toCss(); } if (resize != null) _properties['resize'] = resize.toCss(); + if (scrollBehavior != null) { + _properties['scroll-behavior'] = scrollBehavior.toCss(); + } if (filter != null) _properties['filter'] = filter.toCss(); if (backdropFilter != null) { _properties['backdrop-filter'] = backdropFilter.toCss(); diff --git a/packages/spark_css/test/css_aspect_ratio_test.dart b/packages/spark_css/test/css_aspect_ratio_test.dart new file mode 100644 index 0000000..b77bea7 --- /dev/null +++ b/packages/spark_css/test/css_aspect_ratio_test.dart @@ -0,0 +1,34 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssAspectRatio', () { + test('auto keyword outputs correct CSS', () { + expect(CssAspectRatio.auto.toCss(), equals('auto')); + }); + test('ratio outputs correct CSS', () { + expect(CssAspectRatio.ratio(16, 9).toCss(), equals('16 / 9')); + expect(CssAspectRatio.ratio(4, 3).toCss(), equals('4 / 3')); + }); + test('number outputs correct CSS', () { + expect(CssAspectRatio.number(1).toCss(), equals('1')); + expect(CssAspectRatio.number(0.5).toCss(), equals('0.5')); + }); + test('variable outputs correct CSS', () { + expect(CssAspectRatio.variable('ar').toCss(), equals('var(--ar)')); + }); + test('raw outputs value as-is', () { + expect(CssAspectRatio.raw('16 / 9').toCss(), equals('16 / 9')); + }); + test('global outputs correct CSS', () { + expect( + CssAspectRatio.global(CssGlobal.inherit).toCss(), + equals('inherit'), + ); + }); + test('Style.typed integration', () { + final style = Style.typed(aspectRatio: CssAspectRatio.ratio(16, 9)); + expect(style.toCss(), contains('aspect-ratio: 16 / 9;')); + }); + }); +} diff --git a/packages/spark_css/test/css_place_items_test.dart b/packages/spark_css/test/css_place_items_test.dart new file mode 100644 index 0000000..d0df591 --- /dev/null +++ b/packages/spark_css/test/css_place_items_test.dart @@ -0,0 +1,33 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssPlaceItems', () { + test('align only outputs single value', () { + final pi = CssPlaceItems(CssAlignItems.center); + expect(pi.toCss(), equals('center')); + }); + test('align and justify outputs both values', () { + final pi = CssPlaceItems(CssAlignItems.center, CssJustifyItems.start); + expect(pi.toCss(), equals('center start')); + }); + test('variable outputs correct CSS', () { + expect(CssPlaceItems.variable('pi').toCss(), equals('var(--pi)')); + }); + test('raw outputs value as-is', () { + expect(CssPlaceItems.raw('center start').toCss(), equals('center start')); + }); + test('global outputs correct CSS', () { + expect( + CssPlaceItems.global(CssGlobal.inherit).toCss(), + equals('inherit'), + ); + }); + test('Style.typed integration', () { + final style = Style.typed( + placeItems: CssPlaceItems(CssAlignItems.center, CssJustifyItems.start), + ); + expect(style.toCss(), contains('place-items: center start;')); + }); + }); +} diff --git a/packages/spark_css/test/css_scroll_behavior_test.dart b/packages/spark_css/test/css_scroll_behavior_test.dart new file mode 100644 index 0000000..f30a496 --- /dev/null +++ b/packages/spark_css/test/css_scroll_behavior_test.dart @@ -0,0 +1,27 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssScrollBehavior', () { + test('keywords output correct CSS', () { + expect(CssScrollBehavior.auto.toCss(), equals('auto')); + expect(CssScrollBehavior.smooth.toCss(), equals('smooth')); + }); + test('variable outputs correct CSS', () { + expect(CssScrollBehavior.variable('sb').toCss(), equals('var(--sb)')); + }); + test('raw outputs value as-is', () { + expect(CssScrollBehavior.raw('smooth').toCss(), equals('smooth')); + }); + test('global outputs correct CSS', () { + expect( + CssScrollBehavior.global(CssGlobal.inherit).toCss(), + equals('inherit'), + ); + }); + test('Style.typed integration', () { + final style = Style.typed(scrollBehavior: CssScrollBehavior.smooth); + expect(style.toCss(), contains('scroll-behavior: smooth;')); + }); + }); +} diff --git a/packages/spark_css/test/css_text_overflow_test.dart b/packages/spark_css/test/css_text_overflow_test.dart new file mode 100644 index 0000000..7f30d6b --- /dev/null +++ b/packages/spark_css/test/css_text_overflow_test.dart @@ -0,0 +1,31 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssTextOverflow', () { + test('keywords output correct CSS', () { + expect(CssTextOverflow.clip.toCss(), equals('clip')); + expect(CssTextOverflow.ellipsis.toCss(), equals('ellipsis')); + }); + test('value outputs quoted string', () { + expect(CssTextOverflow.value('…').toCss(), equals('"…"')); + expect(CssTextOverflow.value('>>').toCss(), equals('">>"')); + }); + test('variable outputs correct CSS', () { + expect(CssTextOverflow.variable('to').toCss(), equals('var(--to)')); + }); + test('raw outputs value as-is', () { + expect(CssTextOverflow.raw('ellipsis').toCss(), equals('ellipsis')); + }); + test('global outputs correct CSS', () { + expect( + CssTextOverflow.global(CssGlobal.inherit).toCss(), + equals('inherit'), + ); + }); + test('Style.typed integration', () { + final style = Style.typed(textOverflow: CssTextOverflow.ellipsis); + expect(style.toCss(), contains('text-overflow: ellipsis;')); + }); + }); +}