diff --git a/Sources/Ignite/Elements/Slide.swift b/Sources/Ignite/Elements/Slide.swift index ca0f5092..e473f81c 100644 --- a/Sources/Ignite/Elements/Slide.swift +++ b/Sources/Ignite/Elements/Slide.swift @@ -72,7 +72,7 @@ public struct Slide: BlockHTML { .style( .init(.height, value: "100%"), .init(.objectFit, value: "cover"), - .init(.opacity, value: backgroundOpacity.formatted()) + .init(.opacity, value: backgroundOpacity.formatted(.nonLocalizedDecimal)) ) } diff --git a/Sources/Ignite/Extensions/FormatStyle-NonLocalizedDecimal.swift b/Sources/Ignite/Extensions/FormatStyle-NonLocalizedDecimal.swift new file mode 100644 index 00000000..b2db0a76 --- /dev/null +++ b/Sources/Ignite/Extensions/FormatStyle-NonLocalizedDecimal.swift @@ -0,0 +1,42 @@ +// +// FormatStyle-NonLocalizedDecimal.swift +// Ignite +// https://www.github.com/twostraws/Ignite +// See LICENSE for license information. +// + +import Foundation + +extension FormatStyle where Self == FloatingPointFormatStyle, FormatInput == Double { + /// A format style that displays a floating point number with one decimal place, + /// enforcing the use of a `.` as the decimal separator. + static var nonLocalizedDecimal: Self { + nonLocalizedDecimal(places: 1) + } + + /// A format style that displays a floating point number enforcing the use of a `.` as the decimal separator. + /// - Parameter places: The number of decimal places to display. Defaults to 1. + static func nonLocalizedDecimal(places: Int = 1) -> Self { + let precision = max(0, places) + return FloatingPointFormatStyle() + .precision(.fractionLength(0...precision)) + .locale(Locale(identifier: "en_US")) + } +} + +extension FormatStyle where Self == FloatingPointFormatStyle, FormatInput == Float { + /// A format style that displays a floating point number with one decimal place, + /// enforcing the use of a `.` as the decimal separator. + static var nonLocalizedDecimal: Self { + nonLocalizedDecimal(places: 1) + } + + /// A format style that displays a floating point number enforcing the use of a `.` as the decimal separator. + /// - Parameter places: The number of decimal places to display. Defaults to 1. + static func nonLocalizedDecimal(places: Int = 1) -> Self { + let precision = max(0, places) + return FloatingPointFormatStyle() + .precision(.fractionLength(0...precision)) + .locale(Locale(identifier: "en_US")) + } +} diff --git a/Sources/Ignite/Modifiers/LineSpacing.swift b/Sources/Ignite/Modifiers/LineSpacing.swift index bd19b75a..beb73f81 100644 --- a/Sources/Ignite/Modifiers/LineSpacing.swift +++ b/Sources/Ignite/Modifiers/LineSpacing.swift @@ -28,13 +28,13 @@ struct LineSpacingModifier: HTMLModifier { func body(content: some HTML) -> any HTML { if content.body.isComposite { if let customHeight { - content.containerStyle(.init(.lineHeight, value: customHeight.formatted())) + content.containerStyle(.init(.lineHeight, value: customHeight.formatted(.nonLocalizedDecimal))) } else if let presetHeight { content.containerClass("lh-\(presetHeight.rawValue)") } } else { if let customHeight { - content.style(.init(.lineHeight, value: customHeight.formatted())) + content.style(.init(.lineHeight, value: customHeight.formatted(.nonLocalizedDecimal))) } else if let presetHeight { content.class("lh-\(presetHeight.rawValue)") } diff --git a/Sources/Ignite/Modifiers/Opacity.swift b/Sources/Ignite/Modifiers/Opacity.swift index c72f2bfd..f291da47 100644 --- a/Sources/Ignite/Modifiers/Opacity.swift +++ b/Sources/Ignite/Modifiers/Opacity.swift @@ -5,6 +5,8 @@ // See LICENSE for license information. // +import Foundation + /// A modifier that applies opacity styling to HTML elements struct OpacityModifier: HTMLModifier { /// The opacity value between 0% (transparent) and 100% (opaque) @@ -32,9 +34,9 @@ struct OpacityModifier: HTMLModifier { /// - Returns: The modified HTML with opacity applied func body(content: some HTML) -> any HTML { if let percentage, percentage != 100% { - content.style(.opacity, percentage.value.formatted()) + content.style(.opacity, percentage.value.formatted(.nonLocalizedDecimal(places: 3))) } else if let doubleValue, doubleValue != 1 { - content.style(.opacity, doubleValue.formatted()) + content.style(.opacity, doubleValue.formatted(.nonLocalizedDecimal(places: 3))) } content } diff --git a/Tests/IgniteTesting/Modifiers/Opacity.swift b/Tests/IgniteTesting/Modifiers/Opacity.swift index 43657183..6b0156d5 100644 --- a/Tests/IgniteTesting/Modifiers/Opacity.swift +++ b/Tests/IgniteTesting/Modifiers/Opacity.swift @@ -33,4 +33,26 @@ struct OpacityTests { #expect(output == "\"\(image.description)\"") } + + @Test("Checks that the opacity value is correctly formatted", arguments: [ + (value: 0.123456, expected: "0.123"), + (value: 0.15, expected: "0.15"), + (value: 0.1, expected: "0.1"), + (value: 0.45678, expected: "0.457"), + (value: 0, expected: "0") + ]) + func opacityFormatting(testCase: (value: Double, expected: String)) async throws { + let element = Text("Test").opacity(testCase.value) + let output = element.render() + + #expect(output == "

Test

") + } + + @Test("Checks that full opacity is not rendered") + func fullOpacity() async throws { + let element = Text("Test").opacity(1) + let output = element.render() + + #expect(output == "

Test

") + } }