diff --git a/Sources/CSS/CSSColor.swift b/Sources/CSS/CSSColor.swift index 6bf3bff..d46234b 100644 --- a/Sources/CSS/CSSColor.swift +++ b/Sources/CSS/CSSColor.swift @@ -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("#") { diff --git a/Sources/CSS/Properties/BackdropFilter.swift b/Sources/CSS/Properties/BackdropFilter.swift new file mode 100644 index 0000000..9730b9e --- /dev/null +++ b/Sources/CSS/Properties/BackdropFilter.swift @@ -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?( + _ unit: Unit + ) 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?( + blur unit: Unit + ) 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 +} diff --git a/Sources/CSS/Properties/Margin.swift b/Sources/CSS/Properties/Margin.swift index 1e7f17c..e14a7b0 100644 --- a/Sources/CSS/Properties/Margin.swift +++ b/Sources/CSS/Properties/Margin.swift @@ -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. diff --git a/Sources/CSS/Properties/Padding.swift b/Sources/CSS/Properties/Padding.swift index 3309a9b..faf6a24 100644 --- a/Sources/CSS/Properties/Padding.swift +++ b/Sources/CSS/Properties/Padding.swift @@ -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. diff --git a/Tests/CSSTests/CSSColorTests.swift b/Tests/CSSTests/CSSColorTests.swift index a030cfa..19e01f1 100644 --- a/Tests/CSSTests/CSSColorTests.swift +++ b/Tests/CSSTests/CSSColorTests.swift @@ -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)") diff --git a/Tests/CSSTests/Properties/BackdropFilterTestSuite.swift b/Tests/CSSTests/Properties/BackdropFilterTestSuite.swift new file mode 100644 index 0000000..967b9a4 --- /dev/null +++ b/Tests/CSSTests/Properties/BackdropFilterTestSuite.swift @@ -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) + } +} diff --git a/Tests/CSSTests/Properties/MarginTestSuite.swift b/Tests/CSSTests/Properties/MarginTestSuite.swift index 2bd3ed1..3ae7489 100644 --- a/Tests/CSSTests/Properties/MarginTestSuite.swift +++ b/Tests/CSSTests/Properties/MarginTestSuite.swift @@ -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) diff --git a/Tests/CSSTests/Properties/PaddingTestSuite.swift b/Tests/CSSTests/Properties/PaddingTestSuite.swift index 2992ab0..19b8988 100644 --- a/Tests/CSSTests/Properties/PaddingTestSuite.swift +++ b/Tests/CSSTests/Properties/PaddingTestSuite.swift @@ -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) diff --git a/Tests/CSSTests/SelectorTests.swift b/Tests/CSSTests/SelectorTests.swift index 5115928..e76cad4 100644 --- a/Tests/CSSTests/SelectorTests.swift +++ b/Tests/CSSTests/SelectorTests.swift @@ -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) } } } diff --git a/Tests/CSSTests/StylesheetRendererTests.swift b/Tests/CSSTests/StylesheetRendererTests.swift index 467a69b..910a22d 100644 --- a/Tests/CSSTests/StylesheetRendererTests.swift +++ b/Tests/CSSTests/StylesheetRendererTests.swift @@ -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) } } diff --git a/Tests/CSSTests/SwiftCSSTests.swift b/Tests/CSSTests/SwiftCSSTests.swift index 131f429..63926cb 100644 --- a/Tests/CSSTests/SwiftCSSTests.swift +++ b/Tests/CSSTests/SwiftCSSTests.swift @@ -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}"# ) } @@ -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) } } } @@ -45,7 +45,7 @@ struct SwiftCssTests { @charset "UTF-8"; :root { margin: 8.5px 8px; - padding: 8px 8px; + padding: 8.5px 8px; } """# ) @@ -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) } } @@ -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) } } @@ -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 { @@ -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) {