diff --git a/packages/spark_css/CHANGELOG.md b/packages/spark_css/CHANGELOG.md index 32cce2b..238d7a4 100644 --- a/packages/spark_css/CHANGELOG.md +++ b/packages/spark_css/CHANGELOG.md @@ -13,6 +13,11 @@ - **Feat**: Added `CssBorderCollapse` sealed class for `border-collapse` property with `.separate`, `.collapse` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. - **Feat**: Added `border-top-color`, `border-right-color`, `border-bottom-color`, and `border-left-color` CSS property support via `CssColor?` parameters in `Style.typed()`. - **Feat**: Added `CssBoxSizing` sealed class for `box-sizing` property with `.contentBox`, `.borderBox` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssContent` sealed class for `content` property with `.normal`, `.none` keywords, `.value()` for arbitrary content, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **Feat**: Added `CssJustifyItems` sealed class for `justify-items` property with `.normal`, `.stretch`, `.start`, `.end`, `.center`, `.left`, `.right`, `.baseline`, `.firstBaseline`, `.lastBaseline` keywords, plus `.variable()`, `.raw()`, and `.global()` escape hatches. +- **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. ### Changed diff --git a/packages/spark_css/lib/src/css_types/css_content.dart b/packages/spark_css/lib/src/css_types/css_content.dart new file mode 100644 index 0000000..977cfcc --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_content.dart @@ -0,0 +1,61 @@ +import 'css_value.dart'; + +/// CSS content property values. +sealed class CssContent implements CssValue { + const CssContent._(); + + static const CssContent normal = _CssContentKeyword('normal'); + static const CssContent none = _CssContentKeyword('none'); + + /// Arbitrary content value (e.g., `'"Hello"'`). + factory CssContent.value(String value) = _CssContentValue; + + /// CSS variable reference. + factory CssContent.variable(String varName) = _CssContentVariable; + + /// Raw CSS value escape hatch. + factory CssContent.raw(String value) = _CssContentRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssContent.global(CssGlobal global) = _CssContentGlobal; +} + +final class _CssContentKeyword extends CssContent { + final String keyword; + const _CssContentKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssContentValue extends CssContent { + final String value; + const _CssContentValue(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssContentVariable extends CssContent { + final String varName; + const _CssContentVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssContentRaw extends CssContent { + final String value; + const _CssContentRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssContentGlobal extends CssContent { + final CssGlobal global; + const _CssContentGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_justify_items.dart b/packages/spark_css/lib/src/css_types/css_justify_items.dart new file mode 100644 index 0000000..11dd912 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_justify_items.dart @@ -0,0 +1,62 @@ +import 'css_value.dart'; + +/// CSS justify-items property values. +sealed class CssJustifyItems implements CssValue { + const CssJustifyItems._(); + + static const CssJustifyItems normal = _CssJustifyItemsKeyword('normal'); + static const CssJustifyItems stretch = _CssJustifyItemsKeyword('stretch'); + static const CssJustifyItems start = _CssJustifyItemsKeyword('start'); + static const CssJustifyItems end = _CssJustifyItemsKeyword('end'); + static const CssJustifyItems center = _CssJustifyItemsKeyword('center'); + static const CssJustifyItems left = _CssJustifyItemsKeyword('left'); + static const CssJustifyItems right = _CssJustifyItemsKeyword('right'); + static const CssJustifyItems baseline = _CssJustifyItemsKeyword('baseline'); + static const CssJustifyItems firstBaseline = _CssJustifyItemsKeyword( + 'first baseline', + ); + static const CssJustifyItems lastBaseline = _CssJustifyItemsKeyword( + 'last baseline', + ); + + /// CSS variable reference. + factory CssJustifyItems.variable(String varName) = _CssJustifyItemsVariable; + + /// Raw CSS value escape hatch. + factory CssJustifyItems.raw(String value) = _CssJustifyItemsRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssJustifyItems.global(CssGlobal global) = _CssJustifyItemsGlobal; +} + +final class _CssJustifyItemsKeyword extends CssJustifyItems { + final String keyword; + const _CssJustifyItemsKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssJustifyItemsVariable extends CssJustifyItems { + final String varName; + const _CssJustifyItemsVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssJustifyItemsRaw extends CssJustifyItems { + final String value; + const _CssJustifyItemsRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssJustifyItemsGlobal extends CssJustifyItems { + final CssGlobal global; + const _CssJustifyItemsGlobal(this.global) : super._(); + + @override + String toCss() => global.toCss(); +} diff --git a/packages/spark_css/lib/src/css_types/css_list_style.dart b/packages/spark_css/lib/src/css_types/css_list_style.dart new file mode 100644 index 0000000..622c8f6 --- /dev/null +++ b/packages/spark_css/lib/src/css_types/css_list_style.dart @@ -0,0 +1,110 @@ +import 'css_value.dart'; + +/// CSS list-style-type values. +sealed class CssListStyleType implements CssValue { + const CssListStyleType._(); + + static const CssListStyleType disc = _CssListStyleTypeKeyword('disc'); + static const CssListStyleType circle = _CssListStyleTypeKeyword('circle'); + static const CssListStyleType square = _CssListStyleTypeKeyword('square'); + static const CssListStyleType decimal = _CssListStyleTypeKeyword('decimal'); + static const CssListStyleType none = _CssListStyleTypeKeyword('none'); +} + +final class _CssListStyleTypeKeyword extends CssListStyleType { + final String keyword; + const _CssListStyleTypeKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +/// CSS list-style-position values. +sealed class CssListStylePosition implements CssValue { + const CssListStylePosition._(); + + static const CssListStylePosition inside = _CssListStylePositionKeyword( + 'inside', + ); + static const CssListStylePosition outside = _CssListStylePositionKeyword( + 'outside', + ); +} + +final class _CssListStylePositionKeyword extends CssListStylePosition { + final String keyword; + const _CssListStylePositionKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +/// CSS list-style shorthand property values. +sealed class CssListStyle implements CssValue { + const CssListStyle._(); + + static const CssListStyle none = _CssListStyleKeyword('none'); + + /// Shorthand with optional type, position, and image. + factory CssListStyle({ + CssListStyleType? type, + CssListStylePosition? position, + String? image, + }) = _CssListStyleShorthand; + + /// CSS variable reference. + factory CssListStyle.variable(String varName) = _CssListStyleVariable; + + /// Raw CSS value escape hatch. + factory CssListStyle.raw(String value) = _CssListStyleRaw; + + /// Global keyword (inherit, initial, unset, revert). + factory CssListStyle.global(CssGlobal global) = _CssListStyleGlobal; +} + +final class _CssListStyleKeyword extends CssListStyle { + final String keyword; + const _CssListStyleKeyword(this.keyword) : super._(); + + @override + String toCss() => keyword; +} + +final class _CssListStyleShorthand extends CssListStyle { + final CssListStyleType? type; + final CssListStylePosition? position; + final String? image; + + const _CssListStyleShorthand({this.type, this.position, this.image}) + : super._(); + + @override + String toCss() { + final parts = [?type?.toCss(), ?position?.toCss(), ?image]; + return parts.join(' '); + } +} + +final class _CssListStyleVariable extends CssListStyle { + final String varName; + const _CssListStyleVariable(this.varName) : super._(); + + @override + String toCss() => 'var(--$varName)'; +} + +final class _CssListStyleRaw extends CssListStyle { + final String value; + const _CssListStyleRaw(this.value) : super._(); + + @override + String toCss() => value; +} + +final class _CssListStyleGlobal extends CssListStyle { + final CssGlobal global; + const _CssListStyleGlobal(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 51eb763..2d70053 100644 --- a/packages/spark_css/lib/src/css_types/css_types.dart +++ b/packages/spark_css/lib/src/css_types/css_types.dart @@ -17,6 +17,7 @@ export 'css_border_radius.dart'; export 'css_box_shadow.dart'; export 'css_box_sizing.dart'; export 'css_color.dart'; +export 'css_content.dart'; export 'css_cursor.dart'; export 'css_display.dart'; export 'css_filter.dart'; @@ -25,7 +26,9 @@ export 'css_flex_shorthand.dart'; export 'css_font.dart'; export 'css_gradient_direction.dart'; export 'css_grid_template_columns.dart'; +export 'css_justify_items.dart'; export 'css_length.dart'; +export 'css_list_style.dart'; export 'css_number.dart'; export 'css_outline.dart'; export 'css_overflow.dart'; diff --git a/packages/spark_css/lib/src/style.dart b/packages/spark_css/lib/src/style.dart index 0f41291..b8cb8fe 100644 --- a/packages/spark_css/lib/src/style.dart +++ b/packages/spark_css/lib/src/style.dart @@ -323,8 +323,14 @@ class Style implements CssStyle { CssTransform? transform, // Background shorthand CssBackground? background, + // Content + CssContent? content, // Grid CssGridTemplateColumns? gridTemplateColumns, + // Alignment + CssJustifyItems? justifyItems, + // Lists + CssListStyle? listStyle, Stylesheet? css, }) : stylesheet = css { // Colors @@ -485,10 +491,22 @@ class Style implements CssStyle { // Background shorthand if (background != null) _properties['background'] = background.toCss(); + + // Content + if (content != null) _properties['content'] = content.toCss(); + // Grid if (gridTemplateColumns != null) { _properties['grid-template-columns'] = gridTemplateColumns.toCss(); } + + // Alignment + if (justifyItems != null) { + _properties['justify-items'] = justifyItems.toCss(); + } + + // Lists + if (listStyle != null) _properties['list-style'] = listStyle.toCss(); } /// Adds a custom property not covered by the named arguments. diff --git a/packages/spark_css/test/css_content_test.dart b/packages/spark_css/test/css_content_test.dart new file mode 100644 index 0000000..4e0eb4b --- /dev/null +++ b/packages/spark_css/test/css_content_test.dart @@ -0,0 +1,27 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssContent', () { + test('keywords output correct CSS', () { + expect(CssContent.normal.toCss(), equals('normal')); + expect(CssContent.none.toCss(), equals('none')); + }); + test('value outputs correct CSS', () { + expect(CssContent.value('"Hello"').toCss(), equals('"Hello"')); + }); + test('variable outputs correct CSS', () { + expect(CssContent.variable('c').toCss(), equals('var(--c)')); + }); + test('raw outputs value as-is', () { + expect(CssContent.raw('counter(item)').toCss(), equals('counter(item)')); + }); + test('global outputs correct CSS', () { + expect(CssContent.global(CssGlobal.inherit).toCss(), equals('inherit')); + }); + test('Style.typed integration', () { + final style = Style.typed(content: CssContent.normal); + expect(style.toCss(), contains('content: normal;')); + }); + }); +} diff --git a/packages/spark_css/test/css_justify_items_test.dart b/packages/spark_css/test/css_justify_items_test.dart new file mode 100644 index 0000000..2e62b59 --- /dev/null +++ b/packages/spark_css/test/css_justify_items_test.dart @@ -0,0 +1,38 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssJustifyItems', () { + test('keywords output correct CSS', () { + expect(CssJustifyItems.normal.toCss(), equals('normal')); + expect(CssJustifyItems.stretch.toCss(), equals('stretch')); + expect(CssJustifyItems.start.toCss(), equals('start')); + expect(CssJustifyItems.end.toCss(), equals('end')); + expect(CssJustifyItems.center.toCss(), equals('center')); + expect(CssJustifyItems.left.toCss(), equals('left')); + expect(CssJustifyItems.right.toCss(), equals('right')); + expect(CssJustifyItems.baseline.toCss(), equals('baseline')); + expect(CssJustifyItems.firstBaseline.toCss(), equals('first baseline')); + expect(CssJustifyItems.lastBaseline.toCss(), equals('last baseline')); + }); + test('variable outputs correct CSS', () { + expect(CssJustifyItems.variable('ji').toCss(), equals('var(--ji)')); + }); + test('raw outputs value as-is', () { + expect( + CssJustifyItems.raw('legacy center').toCss(), + equals('legacy center'), + ); + }); + test('global outputs correct CSS', () { + expect( + CssJustifyItems.global(CssGlobal.inherit).toCss(), + equals('inherit'), + ); + }); + test('Style.typed integration', () { + final style = Style.typed(justifyItems: CssJustifyItems.center); + expect(style.toCss(), contains('justify-items: center;')); + }); + }); +} diff --git a/packages/spark_css/test/css_list_style_test.dart b/packages/spark_css/test/css_list_style_test.dart new file mode 100644 index 0000000..8885da8 --- /dev/null +++ b/packages/spark_css/test/css_list_style_test.dart @@ -0,0 +1,60 @@ +import 'package:spark_css/spark_css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CssListStyleType', () { + test('keywords output correct CSS', () { + expect(CssListStyleType.disc.toCss(), equals('disc')); + expect(CssListStyleType.circle.toCss(), equals('circle')); + expect(CssListStyleType.square.toCss(), equals('square')); + expect(CssListStyleType.decimal.toCss(), equals('decimal')); + expect(CssListStyleType.none.toCss(), equals('none')); + }); + }); + + group('CssListStylePosition', () { + test('keywords output correct CSS', () { + expect(CssListStylePosition.inside.toCss(), equals('inside')); + expect(CssListStylePosition.outside.toCss(), equals('outside')); + }); + }); + + group('CssListStyle', () { + test('none keyword outputs correct CSS', () { + expect(CssListStyle.none.toCss(), equals('none')); + }); + test('shorthand with type only', () { + final style = CssListStyle(type: CssListStyleType.disc); + expect(style.toCss(), equals('disc')); + }); + test('shorthand with position only', () { + final style = CssListStyle(position: CssListStylePosition.inside); + expect(style.toCss(), equals('inside')); + }); + test('shorthand with image only', () { + final style = CssListStyle(image: 'url("marker.png")'); + expect(style.toCss(), equals('url("marker.png")')); + }); + test('shorthand with all parts', () { + final style = CssListStyle( + type: CssListStyleType.square, + position: CssListStylePosition.outside, + image: 'url("marker.png")', + ); + expect(style.toCss(), equals('square outside url("marker.png")')); + }); + test('variable outputs correct CSS', () { + expect(CssListStyle.variable('ls').toCss(), equals('var(--ls)')); + }); + test('raw outputs value as-is', () { + expect(CssListStyle.raw('disc inside').toCss(), equals('disc inside')); + }); + test('global outputs correct CSS', () { + expect(CssListStyle.global(CssGlobal.inherit).toCss(), equals('inherit')); + }); + test('Style.typed integration', () { + final style = Style.typed(listStyle: CssListStyle.none); + expect(style.toCss(), contains('list-style: none;')); + }); + }); +}