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
6 changes: 3 additions & 3 deletions Sources/CSS/CSSColor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ public struct CSSColor: Sendable, ExpressibleByStringLiteral {

/// Creates a color from a hex string literal.
///
/// Valid lengths are 3, 4, 6, or 7 characters, with an optional `#` prefix.
/// Valid lengths are 3, 4, 6, 7, 8, or 9 characters, with an optional `#` prefix.
/// - Parameter value: The hex string literal.
public init(stringLiteral value: StringLiteralType) {
colorValue = value
/// check if length is valid (000, #000, cafe00, #cafe00).
assert([3, 4, 6, 7].contains(value.count), "Invalid hex string")
/// check if length is valid (000, #000, cafe00, #cafe00, 12345678, #123456789).
assert([3, 4, 6, 7, 8, 9].contains(value.count), "Invalid hex string")

/// add # prefix if missing.
if !colorValue.hasPrefix("#") {
Expand Down
91 changes: 91 additions & 0 deletions Sources/CSS/Properties/BackdropFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// BackdropFilter.swift
// swift-web-standards
//
// Created by Binary Birds on 2026. 02. 25.

/// CSS `backdrop-filter` property.
/// Provides typed values for this declaration.
public struct BackdropFilter: Property {
/// Supported length value for `backdrop-filter: blur(...)`.
public struct BlurLength: UnitRepresentable, Sendable {
public let rawValue: String

/// Creates a blur length from a CSS `Unit`.
/// Returns `nil` for unsupported units (for example `%`).
public init?<T>(
_ unit: Unit<T>
) where T: Numeric & Sendable {
switch unit.type {
case .cm, .mm, .in, .px, .pt, .pc, .em, .ex, .ch, .rem, .vw, .vh,
.vmin, .vmax:
self.rawValue = unit.rawValue
case .percent:
return nil
}
}
}

/// Value options for the `backdrop-filter` property.
public enum Value: Sendable {
/// Default value. Specifies no effects.
case none
/// Applies a blur effect to the backdrop.
case blur(BlurLength)
/// Sets this property to its default value.
case initial
/// Inherits this property from its parent element.
case inherit

var rawValue: String {
switch self {
case .none:
return "none"
case .blur(let value):
return "blur(\(value.rawValue))"
case .initial:
return "initial"
case .inherit:
return "inherit"
}
}
}

public let name: String
public let value: String
public var isImportant: Bool

/// Applies graphical effects such as blurring to the area behind an element.
/// - Parameter value: The property value.
public init(
_ value: Value = .none
) {
self.name = "backdrop-filter"
self.value = value.rawValue
self.isImportant = false
}

/// Convenience initializer for `backdrop-filter: blur(...)` using `Unit`.
/// Returns `nil` when the supplied unit is unsupported by blur().
public init?<T>(
blur unit: Unit<T>
) where T: Numeric & Sendable {
guard let blurLength = BlurLength(unit) else {
return nil
}
self.init(.blur(blurLength))
}

// TODO: add support for the remaining backdrop-filter functions:
// - brightness()
// - contrast()
// - drop-shadow()
// - grayscale()
// - hue-rotate()
// - invert()
// - opacity()
// - saturate()
// - sepia()
// - url()
// - multiple functions in a single declaration
}
16 changes: 8 additions & 8 deletions Sources/CSS/Properties/Margin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,27 @@ public struct Margin: Property {
/// Creates a `margin` declaration.
/// Used by `StylesheetRenderer` when emitting CSS.
/// - Parameters:
/// - horizontal: The horizontal value.
/// - vertical: The vertical value.
/// - horizontal: The horizontal value.
public init(
horizontal: Value,
vertical: Value
vertical: Value,
horizontal: Value
) {
self.name = "margin"
self.value = horizontal.rawValue + " " + vertical.rawValue
self.value = vertical.rawValue + " " + horizontal.rawValue
self.isImportant = false
}

/// Creates a `margin` declaration.
/// Used by `StylesheetRenderer` when emitting CSS.
/// - Parameters:
/// - horizontal: The horizontal value.
/// - vertical: The vertical value.
/// - horizontal: The horizontal value.
public init(
horizontal: UnitRepresentable = 0,
vertical: UnitRepresentable = 0
vertical: UnitRepresentable = 0,
horizontal: UnitRepresentable = 0
) {
self.init(horizontal: .length(horizontal), vertical: .length(vertical))
self.init(vertical: .length(vertical), horizontal: .length(horizontal))
}

/// Creates a `margin` declaration.
Expand Down
16 changes: 8 additions & 8 deletions Sources/CSS/Properties/Padding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,27 @@ public struct Padding: Property {
/// Creates a `padding` declaration.
/// Used by `StylesheetRenderer` when emitting CSS.
/// - Parameters:
/// - horizontal: The horizontal value.
/// - vertical: The vertical value.
/// - horizontal: The horizontal value.
public init(
horizontal: Value,
vertical: Value
vertical: Value,
horizontal: Value
) {
self.name = "padding"
self.value = horizontal.rawValue + " " + vertical.rawValue
self.value = vertical.rawValue + " " + horizontal.rawValue
self.isImportant = false
}

/// Creates a `padding` declaration.
/// Used by `StylesheetRenderer` when emitting CSS.
/// - Parameters:
/// - horizontal: The horizontal value.
/// - vertical: The vertical value.
/// - horizontal: The horizontal value.
public init(
horizontal: UnitRepresentable = 0,
vertical: UnitRepresentable = 0
vertical: UnitRepresentable = 0,
horizontal: UnitRepresentable = 0
) {
self.init(horizontal: .length(horizontal), vertical: .length(vertical))
self.init(vertical: .length(vertical), horizontal: .length(horizontal))
}

/// Creates a `padding` declaration.
Expand Down
3 changes: 3 additions & 0 deletions Tests/CSSTests/CSSColorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ struct CSSColorTests {
let short: CSSColor = "#fff"
#expect(short.rawValue == "#fff")

let hexOpacity: CSSColor = "#12345678"
#expect(hexOpacity.rawValue == "#12345678")

let rgb = CSSColor(r: 1, g: 2, b: 3)
#expect(rgb.rawValue == "rgb(1,2,3)")

Expand Down
79 changes: 79 additions & 0 deletions Tests/CSSTests/Properties/BackdropFilterTestSuite.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// BackdropFilterTestSuite.swift
// swift-web-standards
//
// Created by Binary Birds on 2026. 02. 25.

import Testing

@testable import CSS

@Suite
struct BackdropFilterTests {

@Test
func initializers() {
let property = BackdropFilter()

let renderer = StylesheetRenderer()
let result = renderer.renderProperty(property)

let expectation = "\(property.name): \(property.value)"

#expect(result == expectation)
}

@Test
func important() {
let property = BackdropFilter()
.important()

let renderer = StylesheetRenderer()
let result = renderer.renderProperty(property)

let expectation = "\(property.name): \(property.value) !important"

#expect(result == expectation)
}

@Test
func values() {
let blur = BackdropFilter(blur: 100.px)
let blurRem = BackdropFilter(blur: 2.rem)
let inherit = BackdropFilter(.inherit)

let renderer = StylesheetRenderer()
if let blur {
#expect(
renderer.renderProperty(blur)
== "backdrop-filter: blur(100px)"
)
}
else {
Issue.record(
"Expected px unit to be accepted by BackdropFilter blur."
)
}

if let blurRem {
#expect(
renderer.renderProperty(blurRem)
== "backdrop-filter: blur(2rem)"
)
}
else {
Issue.record(
"Expected rem unit to be accepted by BackdropFilter blur."
)
}

#expect(renderer.renderProperty(inherit) == "backdrop-filter: inherit")
}

@Test
func unsupportedUnits() {
let invalid = BackdropFilter(blur: 50.percent)

#expect(invalid == nil)
}
}
2 changes: 1 addition & 1 deletion Tests/CSSTests/Properties/MarginTestSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct MarginTests {
@Test
func values() {
let single = Margin(12.px)
let axis = Margin(horizontal: 8.px, vertical: 16.px)
let axis = Margin(vertical: 8.px, horizontal: 16.px)
let sides = Margin(top: 1.px, right: 2.px, bottom: 3.px, left: 4.px)
let auto = Margin(.auto)
let initial = Margin(.initial)
Expand Down
2 changes: 1 addition & 1 deletion Tests/CSSTests/Properties/PaddingTestSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct PaddingTests {
@Test
func values() {
let single = Padding(12.px)
let axis = Padding(horizontal: 8.px, vertical: 16.px)
let axis = Padding(vertical: 8.px, horizontal: 16.px)
let sides = Padding(top: 1.px, right: 2.px, bottom: 3.px, left: 4.px)
let inherit = Padding(.inherit)

Expand Down
4 changes: 2 additions & 2 deletions Tests/CSSTests/SelectorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ struct SelectorTests {
AllElements {
Padding(0)
Padding(8.rem)
Padding(horizontal: 8.px)
Padding(horizontal: .length(0), vertical: .inherit)
Padding(vertical: 8.px)
Padding(vertical: .length(0), horizontal: .inherit)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/CSSTests/StylesheetRendererTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct StylesheetRendererTests {
let css = Stylesheet {
Media {
Class("badge") {
Padding(horizontal: 6.px, vertical: 2.px)
Padding(vertical: 6.px, horizontal: 2.px)
BackgroundColor(.red)
}
}
Expand Down
22 changes: 11 additions & 11 deletions Tests/CSSTests/SwiftCSSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ struct SwiftCssTests {
Charset("UTF-8")
Media {
Root {
Margin(horizontal: 8.5.px, vertical: 8.px)
Padding(horizontal: 8.px, vertical: 8.px)
Margin(vertical: 8.5.px, horizontal: 8.px)
Padding(vertical: 8.5.px, horizontal: 8.px)
}
}
}
#expect(
StylesheetRenderer(minify: true, indent: 2).render(css)
== #"@charset "UTF-8";:root{margin:8.5px 8px;padding:8px 8px}"#
== #"@charset "UTF-8";:root{margin:8.5px 8px;padding:8.5px 8px}"#
)
}

Expand All @@ -34,8 +34,8 @@ struct SwiftCssTests {
Charset("UTF-8")
Media {
Root {
Margin(horizontal: 8.5.px, vertical: 8.px)
Padding(horizontal: 8.px, vertical: 8.px)
Margin(vertical: 8.5.px, horizontal: 8.px)
Padding(vertical: 8.5.px, horizontal: 8.px)
}
}
}
Expand All @@ -45,7 +45,7 @@ struct SwiftCssTests {
@charset "UTF-8";
:root {
margin: 8.5px 8px;
padding: 8px 8px;
padding: 8.5px 8px;
}
"""#
)
Expand All @@ -58,8 +58,8 @@ struct SwiftCssTests {

Media {
Root {
Margin(horizontal: 8.5.px, vertical: 8.px)
Padding(horizontal: 8.px, vertical: 8.px)
Margin(vertical: 8.5.px, horizontal: 8.px)
Padding(vertical: 8.5.px, horizontal: 8.px)
}
}

Expand All @@ -70,7 +70,7 @@ struct SwiftCssTests {
}
Media(.screen && .prefersColorScheme(.dark)) {
Universal {
Margin(horizontal: 8.px, vertical: 8.px)
Margin(vertical: 8.5.px, horizontal: 8.px)
}
}

Expand All @@ -86,7 +86,7 @@ struct SwiftCssTests {
@charset "UTF-8";
:root {
margin: 8.5px 8px;
padding: 8px 8px;
padding: 8.5px 8px;
}
@media screen and (min-width: 600px) {
.button {
Expand All @@ -95,7 +95,7 @@ struct SwiftCssTests {
}
@media screen and (prefers-color-scheme: dark) {
* {
margin: 8px 8px;
margin: 8.5px 8px;
}
}
@media screen and (display-mode: standalone) {
Expand Down