Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 17 additions & 20 deletions Benchmark/Benchmarks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,27 @@ type Benchmarks() =
member _.BuildStyleWithFeliz() =
makeStyles
".my-feliz-style"
[
felizStyle.backgroundColor "#44c767"
felizStyle.borderRadius 30
felizStyle.borderWidth 1
felizStyle.borderStyleSolid
felizStyle.borderColor "#18ab29"
felizStyle.displayInlineBlock
felizStyle.cursorPointer
felizStyle.fontSize 17
]
[ felizStyle.backgroundColor "#44c767"
felizStyle.borderRadius 30
felizStyle.borderWidth 1
felizStyle.borderStyleSolid
felizStyle.borderColor "#18ab29"
felizStyle.displayInlineBlock
felizStyle.cursorPointer
felizStyle.fontSize 17 ]


[<Benchmark>]
member _.BuildStyleWithFss() =
createFss [
BackgroundColor.hex "44c767"
BorderRadius.value (px 30)
BorderWidth.value (px 1)
BorderStyle.solid
BorderColor.hex "18ab29"
Display.inlineBlock
Cursor.pointer
FontSize.value (px 17)
]
createFss
[ BackgroundColor.hex "44c767"
BorderRadius.value (px 30)
BorderWidth.value (px 1)
BorderStyle.solid
BorderColor.hex "18ab29"
Display.inlineBlock
Cursor.pointer
FontSize.value (px 17) ]


// From Fun.Css
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

## [Unreleased]

## 1.1.0 - 2026-08-28

### Added

- Optional `important` flag: `type CssBuilder(?important: bool)`. When `true`, every emitted property is suffixed with ` !important`. Subclasses that override `Run` route the final combine through the new public `ApplyImportant` member to honor the flag. Defaults to `false` (fully backward compatible).
- `(value: string)` overloads for all keyword-only properties (Option 1 consistency pass) — every shorthand/keyword property group now accepts a CSS-variable string alongside its typed unit ops, so `display "var(--display)"` works everywhere, not just on the few groups that already had it.
- Broad standard-CSS coverage (all additive, following the camelCase property + PascalCase keyword + `(string)` overload + `...Initial`/`...InheritFromParent` conventions):
- Layout: `clear`, `objectFit`/`objectPosition`, `pointerEvents`, `touchAction`, `willChange`, `overscrollBehavior`/`X`/`Y`, `isolation`, `mixBlendMode`, `overflowBlock`/`overflowInline`, `content`, `perspective`/`perspectiveOrigin`, `inset`, `aspectRatio`, `contain`, logical margin/padding longhands, `scrollSnapAlign`/`Type`/`Stop`, `scrollMargin`/`scrollPadding` (+ per-side longhands).
- Border/outline: `outline` shorthand + `outlineWidth(float)`, border corner radii, `borderImage` shorthand + `borderImageSource`/`Slice`/`Width`/`Outset`/`Repeat`, logical borders (`borderBlock*`/`borderInline*`).
- Background/mask: `background` shorthand + `backgroundAttachment`/`Origin`/`PositionX`/`Y`, `mask` shorthand + `maskImage`/`Size`/`Repeat`/`Position`/`Clip`/`Origin`/`Composite`.
- Typography: `font` shorthand, `fontSizeAdjust`, `fontSynthesis`, `fontFeatureSettings`, `fontVariationSettings`, all `fontVariant-*` longhands, `textDecorationThickness`/`SkipInk`, `textAlignLast`, `tabSize`, `overflowWrap`, `textRendering`, `textSizeAdjust`, `textOrientation`, `hyphens`, `wordSpacing`, `lineClamp`, `unicodeBidi`, `colorScheme`, `forcedColorAdjust`.
- Grid/flex/table/list: `order`, `flexFlow`, `grid` shorthand + `gridAutoRows`/`Columns`/`Flow`, `listStyle` shorthand, `captionSide`, `counterReset`/`Increment`/`Set`, multi-column (`columns`, `columnWidth`/`Count`/`Fill`/`Span`, `columnRule*`).
- Animation/transition/transform: `animationIterationCount` numeric + string overloads, `transitionBehavior`, `transformBox`, `backfaceVisibility`.
- Fragmentation/containment: `containIntrinsic*`, `breakBefore`/`After`/`Inside`, `pageBreakBefore`/`After`/`Inside`.
- `backgroundSize` now has `int`/`float` (px) overloads; `transitionProperty` now has a `string seq` overload joining with `", "`.
- `SKILL.md` — an Agent Skills-format guide for AI coding assistants (and humans) covering the naming/consistency patterns, composition, design-token usage, extension patterns, and gotchas. Referenced from the README.
- Comprehensive unit-test suite covering all public API surfaces (138 tests).

### Changed

- Typo'd/inconsistent operation names kept working but now warn via `[<Obsolete>]` pointing to the corrected spelling (never removed): `justifyItemsStrench`/`justifySelfStrench` → `justifyItemsStretch`/`justifySelfStretch`, `backgroundBlendModeCollorDodge` → `backgroundBlendModeColorDodge`, `wordbreak*` → `wordBreak*`, `tableLayoutFixed'` → `tableLayoutFixed`, `displayInlineElement` → `displayInline`, `positionDefaultStatic` → `positionStatic`, `floatLeft`/`floatRight`/`floatNone` → `floatStyleLeft`/`floatStyleRight`/`floatStyleNone`, `animationDurationCount` → `animationIterationCount(int)`.
- `CssBuilder.fs` reorganized into theme sections (Align, Animation, Background, Border, BoxSpacing, Color, Filter, Flex, Grid, Layout, List, Table, Transform, Transition, Typography) with banner comments; `color`/`font` helper modules moved to `Color.fs`/`Font.fs`. No public API change.

### Fixed

- `transformMatrix` now emits all six `matrix(...)` parameters (previously skipped `y1`).
- `boxShadow` 3-arg overload now emits the `": "` separator.
- `verticalAlignSub` now emits `"sub"` (was `"sup"`).
- `textIndent(int)` now emits `px` (`text-indent: 10px;`).
- `gridTemplateColumns`/`gridTemplateRows` int-seq overloads no longer emit a trailing space.
- `borderWidth` multi-value overloads and `backgroundSize` two-string overload now space-separate values (were comma-separated / emitted `", , "` when an optional arg was omitted).
- `gridTemplateAreas` no longer emits a trailing space.
- Performance: several string-concat paths converted to direct `StringBuilder` appends (`gap`, `transformOrigin`, `gridColumn`/`gridRow`, `borderWidth`, `backgroundSize`).

## 1.0.3 - 2026-08-17

- Add missing `///` doc comments so all `CustomOperation` overloads surface in `Fun.Css.xml` and IDE IntelliSense
Expand Down
218 changes: 218 additions & 0 deletions Fun.Css.Tests/AnimationTransitionTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#nowarn "44" // obsolete-op usage is intentional: verifies legacy names keep working (backward compatibility)

module Fun.Css.Tests.AnimationTransitionTests

open System
open Microsoft.Extensions.ObjectPool
open Xunit

let objectPoolProvider = DefaultObjectPoolProvider()
let stringBuilderPool = objectPoolProvider.CreateStringBuilderPool()

type StyleStrBuilder() =
inherit Fun.Css.CssBuilder()

member inline _.Run([<InlineIfLambda>] combine: Fun.Css.Internal.CombineKeyValue) =
let sb = stringBuilderPool.Get()
let str = combine.Invoke(sb).ToString()
stringBuilderPool.Return sb
str

let style = StyleStrBuilder()

[<Fact>]
let ``animation shorthand and name/duration/delay`` () =
let actual =
style {
animation "slidein 3s ease-in 1s infinite"
animationName "my-keyframes"
animationDuration 3
animationDuration (TimeSpan.FromMilliseconds 500.0)
animationDelay 1
animationDelay (TimeSpan.FromMilliseconds 250.0)
}

Assert.Equal(
"animation: slidein 3s ease-in 1s infinite; animation-name: my-keyframes; animation-duration: 3s; animation-duration: 500ms; animation-delay: 1s; animation-delay: 250ms; ",
actual
)

[<Fact>]
let ``animation timing functions`` () =
let actual =
style {
animationTimingFunctionEase
animationTimingFunctionLinear
animationTimingFunctionEaseIn
animationTimingFunctionEaseOut
animationTimingFunctionEaseInOut
animationTimingFunctionCubicBezier 0.1 0.2 0.3 0.4
animationTimingFunctionInitial
animationTimingFunctionInheritFromParent
}

Assert.Equal(
"animation-timing-function: ease; animation-timing-function: linear; animation-timing-function: ease-in; animation-timing-function: ease-out; animation-timing-function: ease-in-out; animation-timing-function: cubic-bezier(0.1,0.2,0.3,0.4); animation-timing-function: initial; animation-timing-function: inherit; ",
actual
)

[<Fact>]
let ``animation direction`` () =
let actual =
style {
animationDirectionNormal
animationDirectionReverse
animationDirectionAlternate
animationDirectionAlternateReverse
animationDirectionInitial
animationDirectionInheritFromParent
}

Assert.Equal(
"animation-direction: normal; animation-direction: reverse; animation-direction: alternate; animation-direction: alternate-reverse; animation-direction: initial; animation-direction: inherit; ",
actual
)

[<Fact>]
let ``animation play state`` () =
let actual =
style {
animationPlayStateRunning
animationPlayStatePaused
animationPlayStateInitial
animationPlayStateInheritFromParent
}

Assert.Equal(
"animation-play-state: running; animation-play-state: paused; animation-play-state: initial; animation-play-state: inherit; ",
actual
)

[<Fact>]
let ``animation iteration count`` () =
let actual =
style {
animationIterationCountInfinite
animationIterationCountInitial
animationIterationCountInheritFromParent
}

Assert.Equal(
"animation-iteration-count: infinite; animation-iteration-count: initial; animation-iteration-count: inherit; ",
actual
)

[<Fact>]
let ``animation fill mode`` () =
let actual =
style {
animationFillModeNone
animationFillModeForwards
animationFillModeBackwards
animationFillModeBoth
animationFillModeInitial
animationFillModeInheritFromParent
}

Assert.Equal(
"animation-fill-mode: none; animation-fill-mode: forwards; animation-fill-mode: backwards; animation-fill-mode: both; animation-fill-mode: initial; animation-fill-mode: inherit; ",
actual
)

[<Fact>]
let ``animation duration count (obsolete) still emits animation-iteration-count`` () =
let actual = style { animationDurationCount 3 }
Assert.Equal("animation-iteration-count: 3; ", actual)

[<Fact>]
let ``animationIterationCount overloads`` () =
let actual =
style {
animationIterationCount 3
animationIterationCount "var(--animation-count)"
}

Assert.Equal("animation-iteration-count: 3; animation-iteration-count: var(--animation-count); ", actual)

[<Fact>]
let ``animation timing and direction string overloads`` () =
let actual =
style {
animationTimingFunction "cubic-bezier(0.1, 0.7, 1, 0.1)"
animationDirection "var(--animation-direction)"
animationPlayState "var(--animation-play-state)"
animationFillMode "var(--animation-fill-mode)"
}

Assert.Equal(
"animation-timing-function: cubic-bezier(0.1, 0.7, 1, 0.1); animation-direction: var(--animation-direction); animation-play-state: var(--animation-play-state); animation-fill-mode: var(--animation-fill-mode); ",
actual
)

[<Fact>]
let ``transitionTimingFunction string overload`` () =
let actual = style { transitionTimingFunction "steps(4, end)" }
Assert.Equal("transition-timing-function: steps(4, end); ", actual)

[<Fact>]
let ``transition shorthand and property`` () =
let actual =
style {
transition "all 0.3s ease-in-out"
transitionProperty "opacity"
}

Assert.Equal("transition: all 0.3s ease-in-out; transition-property: opacity; ", actual)

[<Fact>]
let ``transition duration overloads`` () =
let actual =
style {
transitionDuration (TimeSpan.FromMilliseconds 300.0)
transitionDurationSeconds 2
transitionDurationSeconds 1.5
transitionDurationMilliseconds 250
transitionDurationMilliseconds 250.0
}

Assert.Equal(
"transition-duration: 300ms; transition-duration: 2s; transition-duration: 1.5s; transition-duration: 250ms; transition-duration: 250ms; ",
actual
)

[<Fact>]
let ``transition delay overloads`` () =
let actual =
style {
transitionDelay (TimeSpan.FromMilliseconds 100.0)
transitionDelaySeconds 1
transitionDelaySeconds 0.5
transitionDelayMilliseconds 200
transitionDelayMilliseconds 200.0
}

Assert.Equal(
"transition-delay: 100ms; transition-delay: 1s; transition-delay: 0.5s; transition-delay: 200ms; transition-delay: 200ms; ",
actual
)

[<Fact>]
let ``transition timing functions`` () =
let actual =
style {
transitionTimingFunctionEase
transitionTimingFunctionLinear
transitionTimingFunctionEaseIn
transitionTimingFunctionEaseOut
transitionTimingFunctionEaseInOut
transitionTimingFunctionStepStart
transitionTimingFunctionStepEnd
transitionTimingFunctionCubicBezier 0.1 0.2 0.3 0.4
transitionTimingFunctionInitial
transitionTimingFunctionInheritFromParent
}

Assert.Equal(
"transition-timing-function: ease; transition-timing-function: linear; transition-timing-function: ease-in; transition-timing-function: ease-out; transition-timing-function: ease-in-out; transition-timing-function: step-start; transition-timing-function: step-end; transition-timing-function: cubic-bezier(0.1,0.2,0.3,0.4); transition-timing-function: initial; transition-timing-function: inherit; ",
actual
)
14 changes: 12 additions & 2 deletions Fun.Css.Tests/BasicTests.fs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#nowarn "44" // obsolete-op usage is intentional: verifies legacy names keep working (backward compatibility)

module Fun.Css.Tests.BasicTests

open Microsoft.Extensions.ObjectPool
Expand Down Expand Up @@ -73,13 +75,21 @@ let ``outline-offset should emit outline-offset`` () =
Assert.Equal("outline-offset: 4px; outline-offset: 1rem; ", actual)

[<Fact>]
let ``justify-items and justify-self Strench should emit the valid stretch value`` () =
let ``justify-items and justify-self Strench (obsolete) should emit the valid stretch value`` () =
let actual = style {
justifyItemsStrench
justifySelfStrench
}
Assert.Equal("justify-items: stretch; justify-self: stretch; ", actual)


[<Fact>]
let ``justifyItemsStretch and justifySelfStretch should emit the valid stretch value`` () =
let actual = style {
justifyItemsStretch
justifySelfStretch
}
Assert.Equal("justify-items: stretch; justify-self: stretch; ", actual)

[<Fact>]
let ``font-weight string should work`` () =
let actual = style {
Expand Down
Loading