From 2b362c1d882ca15075f21896e5f66aa2af0e40a7 Mon Sep 17 00:00:00 2001 From: Erik Bautista Santibanez Date: Fri, 13 Mar 2026 23:33:04 -0700 Subject: [PATCH 1/7] fix: reduce nested _AttributedElement. Use the same array of the original attributes instead of wrapping the html content on every attributes function call. --- Sources/Elementary/Core/Html+Attributes.swift | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Sources/Elementary/Core/Html+Attributes.swift b/Sources/Elementary/Core/Html+Attributes.swift index 872da00..d63d4a1 100644 --- a/Sources/Elementary/Core/Html+Attributes.swift +++ b/Sources/Elementary/Core/Html+Attributes.swift @@ -96,6 +96,49 @@ public struct _AttributedElement: HTML { context.prependAttributes(html.attributes) try await Content._render(html.content, into: &renderer, with: context) } + + /// Adds the specified attribute to the element. + /// - Parameters: + /// - attribute: The attribute to add to the element. + /// - condition: If set to false, the attribute will not be added. + /// - Returns: A new element with the specified attribute added. + @inlinable + public func attributes(_ attribute: HTMLAttribute, when condition: Bool = true) -> Self { + if condition { + var element = self + element.attributes.append(_AttributeStorage(attribute)) + return element + } else { + return self + } + } + + /// Adds the specified attributes to the element. + /// - Parameters: + /// - attributes: The attributes to add to the element. + /// - condition: If set to false, the attributes will not be added. + /// - Returns: A new element with the specified attributes added. + @inlinable + public func attributes(_ attributes: HTMLAttribute..., when condition: Bool = true) -> Self { + self.attributes(contentsOf: attributes, when: condition) + } + + /// Adds the specified attributes to the element. + /// - Parameters: + /// - attributes: The attributes to add to the element as an array. + /// - condition: If set to false, the attributes will not be added. + /// - Returns: A new element with the specified attributes added. + @inlinable + public func attributes(contentsOf attributes: [HTMLAttribute], when condition: Bool = true) -> Self { + if condition { + var element = self + element.attributes.append(_AttributeStorage(attributes)) + return element + } else { + return self + } + } + } extension _AttributedElement: Sendable where Content: Sendable {} From 12b9890959a47ed84d0ce352f96e2256f061e7ae Mon Sep 17 00:00:00 2001 From: Erik Bautista Santibanez Date: Sat, 14 Mar 2026 21:27:04 -0700 Subject: [PATCH 2/7] wip: improve attribute adding --- Sources/Elementary/Core/Html+Attributes.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/Elementary/Core/Html+Attributes.swift b/Sources/Elementary/Core/Html+Attributes.swift index d63d4a1..b11d379 100644 --- a/Sources/Elementary/Core/Html+Attributes.swift +++ b/Sources/Elementary/Core/Html+Attributes.swift @@ -138,7 +138,6 @@ public struct _AttributedElement: HTML { return self } } - } extension _AttributedElement: Sendable where Content: Sendable {} @@ -149,7 +148,7 @@ public extension HTML where Tag: HTMLTrait.Attributes.Global { /// - attribute: The attribute to add to the element. /// - condition: If set to false, the attribute will not be added. /// - Returns: A new element with the specified attribute added. - @inlinable + @inlinable @_disfavoredOverload func attributes(_ attribute: HTMLAttribute, when condition: Bool = true) -> _AttributedElement { if condition { return _AttributedElement(content: self, attributes: .init(attribute)) @@ -163,7 +162,7 @@ public extension HTML where Tag: HTMLTrait.Attributes.Global { /// - attributes: The attributes to add to the element. /// - condition: If set to false, the attributes will not be added. /// - Returns: A new element with the specified attributes added. - @inlinable + @inlinable @_disfavoredOverload func attributes(_ attributes: HTMLAttribute..., when condition: Bool = true) -> _AttributedElement { _AttributedElement(content: self, attributes: .init(condition ? attributes : [])) } @@ -173,7 +172,7 @@ public extension HTML where Tag: HTMLTrait.Attributes.Global { /// - attributes: The attributes to add to the element as an array. /// - condition: If set to false, the attributes will not be added. /// - Returns: A new element with the specified attributes added. - @inlinable + @inlinable @_disfavoredOverload func attributes(contentsOf attributes: [HTMLAttribute], when condition: Bool = true) -> _AttributedElement { _AttributedElement(content: self, attributes: .init(condition ? attributes : [])) } From f907d68572d7b0a733d165dbb0cf4d79b31af129 Mon Sep 17 00:00:00 2001 From: Erik Bautista Santibanez Date: Tue, 17 Mar 2026 22:01:36 -0700 Subject: [PATCH 3/7] feat: increase HTMLTuples up to 12 --- .../Elementary/Core/HtmlBuilder+Tuples.swift | 495 ++++++++++++++++++ 1 file changed, 495 insertions(+) diff --git a/Sources/Elementary/Core/HtmlBuilder+Tuples.swift b/Sources/Elementary/Core/HtmlBuilder+Tuples.swift index 000c3cf..fa2db58 100644 --- a/Sources/Elementary/Core/HtmlBuilder+Tuples.swift +++ b/Sources/Elementary/Core/HtmlBuilder+Tuples.swift @@ -41,6 +41,99 @@ public extension HTMLBuilder { _HTMLTuple6(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5) } + @inlinable + static func buildBlock( + _ v0: V0, + _ v1: V1, + _ v2: V2, + _ v3: V3, + _ v4: V4, + _ v5: V5, + _ v6: V6 + ) -> _HTMLTuple7 { + _HTMLTuple7(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6) + } + + @inlinable + static func buildBlock( + _ v0: V0, + _ v1: V1, + _ v2: V2, + _ v3: V3, + _ v4: V4, + _ v5: V5, + _ v6: V6, + _ v7: V7 + ) -> _HTMLTuple8 { + _HTMLTuple8(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7) + } + + @inlinable + static func buildBlock( + _ v0: V0, + _ v1: V1, + _ v2: V2, + _ v3: V3, + _ v4: V4, + _ v5: V5, + _ v6: V6, + _ v7: V7, + _ v8: V8 + ) -> _HTMLTuple9 { + _HTMLTuple9(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8) + } + + @inlinable + static func buildBlock( + _ v0: V0, + _ v1: V1, + _ v2: V2, + _ v3: V3, + _ v4: V4, + _ v5: V5, + _ v6: V6, + _ v7: V7, + _ v8: V8, + _ v9: V9 + ) -> _HTMLTuple10 { + _HTMLTuple10(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8, v9: v9) + } + + @inlinable + static func buildBlock( + _ v0: V0, + _ v1: V1, + _ v2: V2, + _ v3: V3, + _ v4: V4, + _ v5: V5, + _ v6: V6, + _ v7: V7, + _ v8: V8, + _ v9: V9, + _ v10: V10 + ) -> _HTMLTuple11 { + _HTMLTuple11(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8, v9: v9, v10: v10) + } + + @inlinable + static func buildBlock( + _ v0: V0, + _ v1: V1, + _ v2: V2, + _ v3: V3, + _ v4: V4, + _ v5: V5, + _ v6: V6, + _ v7: V7, + _ v8: V8, + _ v9: V9, + _ v10: V10, + _ v11: V11 + ) -> _HTMLTuple12 { + _HTMLTuple12(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8, v9: v9, v10: v10, v11: v11) + } + // variadic generics currently not supported in embedded #if !hasFeature(Embedded) @inlinable @@ -277,6 +370,408 @@ public struct _HTMLTuple6: HTML { + public let v0: V0 + public let v1: V1 + public let v2: V2 + public let v3: V3 + public let v4: V4 + public let v5: V5 + public let v6: V6 + + @inlinable + public init(v0: V0, v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) { + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.v4 = v4 + self.v5 = v5 + self.v6 = v6 + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.assertNoAttributes(self) + + V0._render(html.v0, into: &renderer, with: copy context) + V1._render(html.v1, into: &renderer, with: copy context) + V2._render(html.v2, into: &renderer, with: copy context) + V3._render(html.v3, into: &renderer, with: copy context) + V4._render(html.v4, into: &renderer, with: copy context) + V5._render(html.v5, into: &renderer, with: copy context) + V6._render(html.v6, into: &renderer, with: copy context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.assertNoAttributes(self) + + try await V0._render(html.v0, into: &renderer, with: copy context) + try await V1._render(html.v1, into: &renderer, with: copy context) + try await V2._render(html.v2, into: &renderer, with: copy context) + try await V3._render(html.v3, into: &renderer, with: copy context) + try await V4._render(html.v4, into: &renderer, with: copy context) + try await V5._render(html.v5, into: &renderer, with: copy context) + try await V6._render(html.v6, into: &renderer, with: copy context) + } +} + +extension _HTMLTuple8: Sendable where V0: Sendable, V1: Sendable, V2: Sendable, V3: Sendable, V4: Sendable, V5: Sendable, V6: Sendable, V7: Sendable {} +public struct _HTMLTuple8: HTML { + public let v0: V0 + public let v1: V1 + public let v2: V2 + public let v3: V3 + public let v4: V4 + public let v5: V5 + public let v6: V6 + public let v7: V7 + + @inlinable + public init(v0: V0, v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6, v7: V7) { + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.v4 = v4 + self.v5 = v5 + self.v6 = v6 + self.v7 = v7 + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.assertNoAttributes(self) + + V0._render(html.v0, into: &renderer, with: copy context) + V1._render(html.v1, into: &renderer, with: copy context) + V2._render(html.v2, into: &renderer, with: copy context) + V3._render(html.v3, into: &renderer, with: copy context) + V4._render(html.v4, into: &renderer, with: copy context) + V5._render(html.v5, into: &renderer, with: copy context) + V6._render(html.v6, into: &renderer, with: copy context) + V7._render(html.v7, into: &renderer, with: copy context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.assertNoAttributes(self) + + try await V0._render(html.v0, into: &renderer, with: copy context) + try await V1._render(html.v1, into: &renderer, with: copy context) + try await V2._render(html.v2, into: &renderer, with: copy context) + try await V3._render(html.v3, into: &renderer, with: copy context) + try await V4._render(html.v4, into: &renderer, with: copy context) + try await V5._render(html.v5, into: &renderer, with: copy context) + try await V6._render(html.v6, into: &renderer, with: copy context) + try await V7._render(html.v7, into: &renderer, with: copy context) + } +} + +extension _HTMLTuple9: Sendable where V0: Sendable, V1: Sendable, V2: Sendable, V3: Sendable, V4: Sendable, V5: Sendable, V6: Sendable, V7: Sendable, V8: Sendable {} +public struct _HTMLTuple9: HTML { + public let v0: V0 + public let v1: V1 + public let v2: V2 + public let v3: V3 + public let v4: V4 + public let v5: V5 + public let v6: V6 + public let v7: V7 + public let v8: V8 + + @inlinable + public init(v0: V0, v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6, v7: V7, v8: V8) { + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.v4 = v4 + self.v5 = v5 + self.v6 = v6 + self.v7 = v7 + self.v8 = v8 + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.assertNoAttributes(self) + + V0._render(html.v0, into: &renderer, with: copy context) + V1._render(html.v1, into: &renderer, with: copy context) + V2._render(html.v2, into: &renderer, with: copy context) + V3._render(html.v3, into: &renderer, with: copy context) + V4._render(html.v4, into: &renderer, with: copy context) + V5._render(html.v5, into: &renderer, with: copy context) + V6._render(html.v6, into: &renderer, with: copy context) + V7._render(html.v7, into: &renderer, with: copy context) + V8._render(html.v8, into: &renderer, with: copy context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.assertNoAttributes(self) + + try await V0._render(html.v0, into: &renderer, with: copy context) + try await V1._render(html.v1, into: &renderer, with: copy context) + try await V2._render(html.v2, into: &renderer, with: copy context) + try await V3._render(html.v3, into: &renderer, with: copy context) + try await V4._render(html.v4, into: &renderer, with: copy context) + try await V5._render(html.v5, into: &renderer, with: copy context) + try await V6._render(html.v6, into: &renderer, with: copy context) + try await V7._render(html.v7, into: &renderer, with: copy context) + try await V8._render(html.v8, into: &renderer, with: copy context) + } +} + +extension _HTMLTuple10: Sendable where V0: Sendable, V1: Sendable, V2: Sendable, V3: Sendable, V4: Sendable, V5: Sendable, V6: Sendable, V7: Sendable, V8: Sendable, V9: Sendable {} +public struct _HTMLTuple10: HTML { + public let v0: V0 + public let v1: V1 + public let v2: V2 + public let v3: V3 + public let v4: V4 + public let v5: V5 + public let v6: V6 + public let v7: V7 + public let v8: V8 + public let v9: V9 + + @inlinable + public init(v0: V0, v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6, v7: V7, v8: V8, v9: V9) { + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.v4 = v4 + self.v5 = v5 + self.v6 = v6 + self.v7 = v7 + self.v8 = v8 + self.v9 = v9 + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.assertNoAttributes(self) + + V0._render(html.v0, into: &renderer, with: copy context) + V1._render(html.v1, into: &renderer, with: copy context) + V2._render(html.v2, into: &renderer, with: copy context) + V3._render(html.v3, into: &renderer, with: copy context) + V4._render(html.v4, into: &renderer, with: copy context) + V5._render(html.v5, into: &renderer, with: copy context) + V6._render(html.v6, into: &renderer, with: copy context) + V7._render(html.v7, into: &renderer, with: copy context) + V8._render(html.v8, into: &renderer, with: copy context) + V9._render(html.v9, into: &renderer, with: copy context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.assertNoAttributes(self) + + try await V0._render(html.v0, into: &renderer, with: copy context) + try await V1._render(html.v1, into: &renderer, with: copy context) + try await V2._render(html.v2, into: &renderer, with: copy context) + try await V3._render(html.v3, into: &renderer, with: copy context) + try await V4._render(html.v4, into: &renderer, with: copy context) + try await V5._render(html.v5, into: &renderer, with: copy context) + try await V6._render(html.v6, into: &renderer, with: copy context) + try await V7._render(html.v7, into: &renderer, with: copy context) + try await V8._render(html.v8, into: &renderer, with: copy context) + try await V9._render(html.v9, into: &renderer, with: copy context) + } +} + +extension _HTMLTuple11: Sendable where V0: Sendable, V1: Sendable, V2: Sendable, V3: Sendable, V4: Sendable, V5: Sendable, V6: Sendable, V7: Sendable, V8: Sendable, V9: Sendable, V10: Sendable {} +public struct _HTMLTuple11: HTML { + public let v0: V0 + public let v1: V1 + public let v2: V2 + public let v3: V3 + public let v4: V4 + public let v5: V5 + public let v6: V6 + public let v7: V7 + public let v8: V8 + public let v9: V9 + public let v10: V10 + + @inlinable + public init(v0: V0, v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6, v7: V7, v8: V8, v9: V9, v10: V10) { + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.v4 = v4 + self.v5 = v5 + self.v6 = v6 + self.v7 = v7 + self.v8 = v8 + self.v9 = v9 + self.v10 = v10 + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.assertNoAttributes(self) + + V0._render(html.v0, into: &renderer, with: copy context) + V1._render(html.v1, into: &renderer, with: copy context) + V2._render(html.v2, into: &renderer, with: copy context) + V3._render(html.v3, into: &renderer, with: copy context) + V4._render(html.v4, into: &renderer, with: copy context) + V5._render(html.v5, into: &renderer, with: copy context) + V6._render(html.v6, into: &renderer, with: copy context) + V7._render(html.v7, into: &renderer, with: copy context) + V8._render(html.v8, into: &renderer, with: copy context) + V9._render(html.v9, into: &renderer, with: copy context) + V10._render(html.v10, into: &renderer, with: copy context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.assertNoAttributes(self) + + try await V0._render(html.v0, into: &renderer, with: copy context) + try await V1._render(html.v1, into: &renderer, with: copy context) + try await V2._render(html.v2, into: &renderer, with: copy context) + try await V3._render(html.v3, into: &renderer, with: copy context) + try await V4._render(html.v4, into: &renderer, with: copy context) + try await V5._render(html.v5, into: &renderer, with: copy context) + try await V6._render(html.v6, into: &renderer, with: copy context) + try await V7._render(html.v7, into: &renderer, with: copy context) + try await V8._render(html.v8, into: &renderer, with: copy context) + try await V9._render(html.v9, into: &renderer, with: copy context) + try await V10._render(html.v10, into: &renderer, with: copy context) + } +} + +extension _HTMLTuple12: Sendable where V0: Sendable, V1: Sendable, V2: Sendable, V3: Sendable, V4: Sendable, V5: Sendable, V6: Sendable, V7: Sendable, V8: Sendable, V9: Sendable, V10: Sendable, V11: Sendable {} +public struct _HTMLTuple12: HTML { + public let v0: V0 + public let v1: V1 + public let v2: V2 + public let v3: V3 + public let v4: V4 + public let v5: V5 + public let v6: V6 + public let v7: V7 + public let v8: V8 + public let v9: V9 + public let v10: V10 + public let v11: V11 + + @inlinable + public init(v0: V0, v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6, v7: V7, v8: V8, v9: V9, v10: V10, v11: V11) { + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.v4 = v4 + self.v5 = v5 + self.v6 = v6 + self.v7 = v7 + self.v8 = v8 + self.v9 = v9 + self.v10 = v10 + self.v11 = v11 + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.assertNoAttributes(self) + + V0._render(html.v0, into: &renderer, with: copy context) + V1._render(html.v1, into: &renderer, with: copy context) + V2._render(html.v2, into: &renderer, with: copy context) + V3._render(html.v3, into: &renderer, with: copy context) + V4._render(html.v4, into: &renderer, with: copy context) + V5._render(html.v5, into: &renderer, with: copy context) + V6._render(html.v6, into: &renderer, with: copy context) + V7._render(html.v7, into: &renderer, with: copy context) + V8._render(html.v8, into: &renderer, with: copy context) + V9._render(html.v9, into: &renderer, with: copy context) + V10._render(html.v10, into: &renderer, with: copy context) + V11._render(html.v11, into: &renderer, with: copy context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.assertNoAttributes(self) + + try await V0._render(html.v0, into: &renderer, with: copy context) + try await V1._render(html.v1, into: &renderer, with: copy context) + try await V2._render(html.v2, into: &renderer, with: copy context) + try await V3._render(html.v3, into: &renderer, with: copy context) + try await V4._render(html.v4, into: &renderer, with: copy context) + try await V5._render(html.v5, into: &renderer, with: copy context) + try await V6._render(html.v6, into: &renderer, with: copy context) + try await V7._render(html.v7, into: &renderer, with: copy context) + try await V8._render(html.v8, into: &renderer, with: copy context) + try await V9._render(html.v9, into: &renderer, with: copy context) + try await V10._render(html.v10, into: &renderer, with: copy context) + try await V11._render(html.v11, into: &renderer, with: copy context) + } +} + // variadic generics currently not supported in embedded #if !hasFeature(Embedded) @available(iOS 17, *) From d596db17f0829db360194e8bce6ecb6275fbff71 Mon Sep 17 00:00:00 2001 From: Erik Bautista Santibanez Date: Wed, 25 Mar 2026 22:28:30 -0700 Subject: [PATCH 4/7] chore: run swift-format --- .../Elementary/Core/HtmlBuilder+Tuples.swift | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/Sources/Elementary/Core/HtmlBuilder+Tuples.swift b/Sources/Elementary/Core/HtmlBuilder+Tuples.swift index fa2db58..832c3ba 100644 --- a/Sources/Elementary/Core/HtmlBuilder+Tuples.swift +++ b/Sources/Elementary/Core/HtmlBuilder+Tuples.swift @@ -117,7 +117,20 @@ public extension HTMLBuilder { } @inlinable - static func buildBlock( + static func buildBlock< + V0: HTML, + V1: HTML, + V2: HTML, + V3: HTML, + V4: HTML, + V5: HTML, + V6: HTML, + V7: HTML, + V8: HTML, + V9: HTML, + V10: HTML, + V11: HTML + >( _ v0: V0, _ v1: V1, _ v2: V2, @@ -427,7 +440,8 @@ public struct _HTMLTuple7: HTML { public let v0: V0 public let v1: V1 @@ -488,7 +502,8 @@ public struct _HTMLTuple8: HTML { public let v0: V0 public let v1: V1 @@ -553,7 +568,19 @@ public struct _HTMLTuple9: HTML { public let v0: V0 public let v1: V1 @@ -622,8 +649,23 @@ public struct _HTMLTuple10: HTML { +extension _HTMLTuple11: Sendable +where + V0: Sendable, + V1: Sendable, + V2: Sendable, + V3: Sendable, + V4: Sendable, + V5: Sendable, + V6: Sendable, + V7: Sendable, + V8: Sendable, + V9: Sendable, + V10: Sendable +{} +public struct _HTMLTuple11: + HTML +{ public let v0: V0 public let v1: V1 public let v2: V2 @@ -695,8 +737,35 @@ public struct _HTMLTuple11: HTML { +extension _HTMLTuple12: Sendable +where + V0: Sendable, + V1: Sendable, + V2: Sendable, + V3: Sendable, + V4: Sendable, + V5: Sendable, + V6: Sendable, + V7: Sendable, + V8: Sendable, + V9: Sendable, + V10: Sendable, + V11: Sendable +{} +public struct _HTMLTuple12< + V0: HTML, + V1: HTML, + V2: HTML, + V3: HTML, + V4: HTML, + V5: HTML, + V6: HTML, + V7: HTML, + V8: HTML, + V9: HTML, + V10: HTML, + V11: HTML +>: HTML { public let v0: V0 public let v1: V1 public let v2: V2 From ece1d68a07ebc90161e69b25f5e4869d31d93669 Mon Sep 17 00:00:00 2001 From: Erik Bautista Santibanez Date: Wed, 25 Mar 2026 22:44:09 -0700 Subject: [PATCH 5/7] refactor: merge internal implementation for attributes into `_Attributed` --- Sources/Elementary/Core/Html+Attributes.swift | 77 ++++++++++--------- Sources/Elementary/Core/Html+Elements.swift | 4 +- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/Sources/Elementary/Core/Html+Attributes.swift b/Sources/Elementary/Core/Html+Attributes.swift index b11d379..fb4d7d2 100644 --- a/Sources/Elementary/Core/Html+Attributes.swift +++ b/Sources/Elementary/Core/Html+Attributes.swift @@ -63,40 +63,11 @@ extension HTMLAttribute { } } -public struct _AttributedElement: HTML { - public typealias Body = Never - public typealias Tag = Content.Tag - - public var content: Content - public var attributes: _AttributeStorage - - @usableFromInline - init(content: Content, attributes: _AttributeStorage) { - self.content = content - self.attributes = attributes - } - - @inlinable - public static func _render( - _ html: consuming Self, - into renderer: inout Renderer, - with context: consuming _RenderingContext - ) { - context.prependAttributes(html.attributes) - Content._render(html.content, into: &renderer, with: context) - } - - @inlinable - @_unavailableInEmbedded - public static func _render( - _ html: consuming Self, - into renderer: inout Renderer, - with context: consuming _RenderingContext - ) async throws { - context.prependAttributes(html.attributes) - try await Content._render(html.content, into: &renderer, with: context) - } +public protocol _Attributed { + var _attributes: _AttributeStorage { get set } +} +extension HTML where Self: _Attributed { /// Adds the specified attribute to the element. /// - Parameters: /// - attribute: The attribute to add to the element. @@ -106,7 +77,7 @@ public struct _AttributedElement: HTML { public func attributes(_ attribute: HTMLAttribute, when condition: Bool = true) -> Self { if condition { var element = self - element.attributes.append(_AttributeStorage(attribute)) + element._attributes.append(_AttributeStorage(attribute)) return element } else { return self @@ -132,7 +103,7 @@ public struct _AttributedElement: HTML { public func attributes(contentsOf attributes: [HTMLAttribute], when condition: Bool = true) -> Self { if condition { var element = self - element.attributes.append(_AttributeStorage(attributes)) + element._attributes.append(_AttributeStorage(attributes)) return element } else { return self @@ -140,6 +111,42 @@ public struct _AttributedElement: HTML { } } +public struct _AttributedElement: HTML, _Attributed { + public typealias Body = Never + public typealias Tag = Content.Tag + + public var content: Content + + public var _attributes: _AttributeStorage + + @usableFromInline + init(content: Content, attributes: _AttributeStorage) { + self.content = content + self._attributes = attributes + } + + @inlinable + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) { + context.prependAttributes(html._attributes) + Content._render(html.content, into: &renderer, with: context) + } + + @inlinable + @_unavailableInEmbedded + public static func _render( + _ html: consuming Self, + into renderer: inout Renderer, + with context: consuming _RenderingContext + ) async throws { + context.prependAttributes(html._attributes) + try await Content._render(html.content, into: &renderer, with: context) + } +} + extension _AttributedElement: Sendable where Content: Sendable {} public extension HTML where Tag: HTMLTrait.Attributes.Global { diff --git a/Sources/Elementary/Core/Html+Elements.swift b/Sources/Elementary/Core/Html+Elements.swift index 8f1c98e..5660f31 100644 --- a/Sources/Elementary/Core/Html+Elements.swift +++ b/Sources/Elementary/Core/Html+Elements.swift @@ -1,5 +1,5 @@ /// An HTML element that can contain content. -public struct HTMLElement: HTML where Tag: HTMLTrait.Paired { +public struct HTMLElement: HTML, _Attributed where Tag: HTMLTrait.Paired { /// The type of the HTML tag this element represents. public typealias Tag = Tag public typealias Body = Never @@ -79,7 +79,7 @@ public struct HTMLElement: HTML where Tag } /// An HTML element that does not contain content. -public struct HTMLVoidElement: HTML where Tag: HTMLTrait.Unpaired { +public struct HTMLVoidElement: HTML, _Attributed where Tag: HTMLTrait.Unpaired { /// The type of the HTML tag this element represents. public typealias Tag = Tag public var _attributes: _AttributeStorage From ac8e1819b07bbc58885fcbc33bf3c0eb6e7b3354 Mon Sep 17 00:00:00 2001 From: Erik Bautista Santibanez Date: Wed, 25 Mar 2026 23:08:24 -0700 Subject: [PATCH 6/7] refactor: deprecate `_attributes` in favor of `attributes` --- Sources/Elementary/Core/Html+Attributes.swift | 14 +++--- Sources/Elementary/Core/Html+Elements.swift | 49 ++++++++++++------- .../Elementary/Core/HtmlElement+Async.swift | 4 +- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/Sources/Elementary/Core/Html+Attributes.swift b/Sources/Elementary/Core/Html+Attributes.swift index fb4d7d2..d1bf278 100644 --- a/Sources/Elementary/Core/Html+Attributes.swift +++ b/Sources/Elementary/Core/Html+Attributes.swift @@ -64,7 +64,7 @@ extension HTMLAttribute { } public protocol _Attributed { - var _attributes: _AttributeStorage { get set } + var attributes: _AttributeStorage { get set } } extension HTML where Self: _Attributed { @@ -77,7 +77,7 @@ extension HTML where Self: _Attributed { public func attributes(_ attribute: HTMLAttribute, when condition: Bool = true) -> Self { if condition { var element = self - element._attributes.append(_AttributeStorage(attribute)) + element.attributes.append(_AttributeStorage(attribute)) return element } else { return self @@ -103,7 +103,7 @@ extension HTML where Self: _Attributed { public func attributes(contentsOf attributes: [HTMLAttribute], when condition: Bool = true) -> Self { if condition { var element = self - element._attributes.append(_AttributeStorage(attributes)) + element.attributes.append(_AttributeStorage(attributes)) return element } else { return self @@ -117,12 +117,12 @@ public struct _AttributedElement: HTML, _Attributed { public var content: Content - public var _attributes: _AttributeStorage + public var attributes: _AttributeStorage @usableFromInline init(content: Content, attributes: _AttributeStorage) { self.content = content - self._attributes = attributes + self.attributes = attributes } @inlinable @@ -131,7 +131,7 @@ public struct _AttributedElement: HTML, _Attributed { into renderer: inout Renderer, with context: consuming _RenderingContext ) { - context.prependAttributes(html._attributes) + context.prependAttributes(html.attributes) Content._render(html.content, into: &renderer, with: context) } @@ -142,7 +142,7 @@ public struct _AttributedElement: HTML, _Attributed { into renderer: inout Renderer, with context: consuming _RenderingContext ) async throws { - context.prependAttributes(html._attributes) + context.prependAttributes(html.attributes) try await Content._render(html.content, into: &renderer, with: context) } } diff --git a/Sources/Elementary/Core/Html+Elements.swift b/Sources/Elementary/Core/Html+Elements.swift index 5660f31..c5e5189 100644 --- a/Sources/Elementary/Core/Html+Elements.swift +++ b/Sources/Elementary/Core/Html+Elements.swift @@ -5,7 +5,13 @@ public struct HTMLElement: HTML, _Attribu public typealias Body = Never public typealias Content = Content - public var _attributes: _AttributeStorage + @available(*, deprecated, message: "`var _attributes` is deprecated, use `var attributes` instead") + public var _attributes: _AttributeStorage { + get { attributes } + set { attributes = newValue } + } + + public var attributes: _AttributeStorage // The content of the element. public var content: Content @@ -14,7 +20,7 @@ public struct HTMLElement: HTML, _Attribu /// - Parameter content: The content of the element. @inlinable public init(@HTMLBuilder content: () -> Content) { - _attributes = .init() + self.attributes = .init() self.content = content() } @@ -24,7 +30,7 @@ public struct HTMLElement: HTML, _Attribu /// - content: The content of the element. @inlinable public init(_ attribute: HTMLAttribute, @HTMLBuilder content: () -> Content) { - _attributes = .init(attribute) + self.attributes = .init(attribute) self.content = content() } @@ -34,7 +40,7 @@ public struct HTMLElement: HTML, _Attribu /// - content: The content of the element. @inlinable public init(_ attributes: HTMLAttribute..., @HTMLBuilder content: () -> Content) { - _attributes = .init(attributes) + self.attributes = .init(attributes) self.content = content() } @@ -44,7 +50,7 @@ public struct HTMLElement: HTML, _Attribu /// - content: The content of the element. @inlinable public init(attributes: [HTMLAttribute], @HTMLBuilder content: () -> Content) { - _attributes = .init(attributes) + self.attributes = .init(attributes) self.content = content() } @@ -54,9 +60,9 @@ public struct HTMLElement: HTML, _Attribu into renderer: inout Renderer, with context: consuming _RenderingContext ) { - html._attributes.append(context.attributes) + html.attributes.append(context.attributes) - renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: false, type: Tag.renderingType)) + renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: false, type: Tag.renderingType)) Content._render(html.content, into: &renderer, with: .emptyContext) renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType)) } @@ -68,10 +74,10 @@ public struct HTMLElement: HTML, _Attribu into renderer: inout Renderer, with context: consuming _RenderingContext ) async throws { - html._attributes.append(context.attributes) + html.attributes.append(context.attributes) try await renderer.appendToken( - .startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: false, type: Tag.renderingType) + .startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: false, type: Tag.renderingType) ) try await Content._render(html.content, into: &renderer, with: .emptyContext) try await renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType)) @@ -82,33 +88,40 @@ public struct HTMLElement: HTML, _Attribu public struct HTMLVoidElement: HTML, _Attributed where Tag: HTMLTrait.Unpaired { /// The type of the HTML tag this element represents. public typealias Tag = Tag - public var _attributes: _AttributeStorage + + @available(*, deprecated, message: "`var _attributes` is deprecated, use `var attributes` instead") + public var _attributes: _AttributeStorage { + get { attributes } + set { attributes = newValue } + } + + public var attributes: _AttributeStorage /// Creates a new HTML void element. @inlinable public init() { - _attributes = .init() + self.attributes = .init() } /// Creates a new HTML void element with the specified attribute. /// - Parameter attribute: The attribute to apply to the element. @inlinable public init(_ attribute: HTMLAttribute) { - _attributes = .init(attribute) + self.attributes = .init(attribute) } /// Creates a new HTML void element with the specified attributes. /// - Parameter attributes: The attributes to apply to the element. @inlinable public init(_ attributes: HTMLAttribute...) { - _attributes = .init(attributes) + self.attributes = .init(attributes) } /// Creates a new HTML void element with the specified attributes. /// - Parameter attributes: The attributes to apply to the element as an array. @inlinable public init(attributes: [HTMLAttribute]) { - _attributes = .init(attributes) + self.attributes = .init(attributes) } @inlinable @@ -117,8 +130,8 @@ public struct HTMLVoidElement: HTML, _Attributed where T into renderer: inout Renderer, with context: consuming _RenderingContext ) { - html._attributes.append(context.attributes) - renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType)) + html.attributes.append(context.attributes) + renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: true, type: Tag.renderingType)) } @inlinable @@ -128,9 +141,9 @@ public struct HTMLVoidElement: HTML, _Attributed where T into renderer: inout Renderer, with context: consuming _RenderingContext ) async throws { - html._attributes.append(context.attributes) + html.attributes.append(context.attributes) try await renderer.appendToken( - .startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType) + .startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: true, type: Tag.renderingType) ) } } diff --git a/Sources/Elementary/Core/HtmlElement+Async.swift b/Sources/Elementary/Core/HtmlElement+Async.swift index a24cc35..c8a7f25 100644 --- a/Sources/Elementary/Core/HtmlElement+Async.swift +++ b/Sources/Elementary/Core/HtmlElement+Async.swift @@ -13,7 +13,7 @@ public extension HTMLElement { @HTMLBuilder content: @escaping @Sendable () async throws -> AwaitedContent ) where Self.Content == AsyncContent { - _attributes = .init(attributes) + self.attributes = .init(attributes) self.content = AsyncContent(content: content) } @@ -31,7 +31,7 @@ public extension HTMLElement { @HTMLBuilder content: @escaping @Sendable () async throws -> AwaitedContent ) where Self.Content == AsyncContent { - _attributes = .init(attributes) + self.attributes = .init(attributes) self.content = AsyncContent(content: content) } } From e5c59d95a4e28c07bad31ed41c5e7e7c456b4dfe Mon Sep 17 00:00:00 2001 From: Simon Leeb <52261246+sliemeobn@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:59:34 +0100 Subject: [PATCH 7/7] stick to _attributes for now --- Sources/Elementary/Core/Html+Attributes.swift | 20 +++++--- Sources/Elementary/Core/Html+Elements.swift | 48 +++++++------------ .../Elementary/Core/HtmlElement+Async.swift | 4 +- 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/Sources/Elementary/Core/Html+Attributes.swift b/Sources/Elementary/Core/Html+Attributes.swift index d1bf278..b9eeb4b 100644 --- a/Sources/Elementary/Core/Html+Attributes.swift +++ b/Sources/Elementary/Core/Html+Attributes.swift @@ -64,7 +64,7 @@ extension HTMLAttribute { } public protocol _Attributed { - var attributes: _AttributeStorage { get set } + var _attributes: _AttributeStorage { get set } } extension HTML where Self: _Attributed { @@ -77,7 +77,7 @@ extension HTML where Self: _Attributed { public func attributes(_ attribute: HTMLAttribute, when condition: Bool = true) -> Self { if condition { var element = self - element.attributes.append(_AttributeStorage(attribute)) + element._attributes.append(_AttributeStorage(attribute)) return element } else { return self @@ -103,7 +103,7 @@ extension HTML where Self: _Attributed { public func attributes(contentsOf attributes: [HTMLAttribute], when condition: Bool = true) -> Self { if condition { var element = self - element.attributes.append(_AttributeStorage(attributes)) + element._attributes.append(_AttributeStorage(attributes)) return element } else { return self @@ -117,12 +117,18 @@ public struct _AttributedElement: HTML, _Attributed { public var content: Content - public var attributes: _AttributeStorage + @available(*, renamed: "_attributes") + public var attributes: _AttributeStorage { + _read { yield _attributes } + _modify { yield &_attributes } + } + + public var _attributes: _AttributeStorage @usableFromInline init(content: Content, attributes: _AttributeStorage) { self.content = content - self.attributes = attributes + self._attributes = attributes } @inlinable @@ -131,7 +137,7 @@ public struct _AttributedElement: HTML, _Attributed { into renderer: inout Renderer, with context: consuming _RenderingContext ) { - context.prependAttributes(html.attributes) + context.prependAttributes(html._attributes) Content._render(html.content, into: &renderer, with: context) } @@ -142,7 +148,7 @@ public struct _AttributedElement: HTML, _Attributed { into renderer: inout Renderer, with context: consuming _RenderingContext ) async throws { - context.prependAttributes(html.attributes) + context.prependAttributes(html._attributes) try await Content._render(html.content, into: &renderer, with: context) } } diff --git a/Sources/Elementary/Core/Html+Elements.swift b/Sources/Elementary/Core/Html+Elements.swift index c5e5189..5edd5fe 100644 --- a/Sources/Elementary/Core/Html+Elements.swift +++ b/Sources/Elementary/Core/Html+Elements.swift @@ -5,13 +5,7 @@ public struct HTMLElement: HTML, _Attribu public typealias Body = Never public typealias Content = Content - @available(*, deprecated, message: "`var _attributes` is deprecated, use `var attributes` instead") - public var _attributes: _AttributeStorage { - get { attributes } - set { attributes = newValue } - } - - public var attributes: _AttributeStorage + public var _attributes: _AttributeStorage // The content of the element. public var content: Content @@ -20,7 +14,7 @@ public struct HTMLElement: HTML, _Attribu /// - Parameter content: The content of the element. @inlinable public init(@HTMLBuilder content: () -> Content) { - self.attributes = .init() + self._attributes = .init() self.content = content() } @@ -30,7 +24,7 @@ public struct HTMLElement: HTML, _Attribu /// - content: The content of the element. @inlinable public init(_ attribute: HTMLAttribute, @HTMLBuilder content: () -> Content) { - self.attributes = .init(attribute) + self._attributes = .init(attribute) self.content = content() } @@ -40,7 +34,7 @@ public struct HTMLElement: HTML, _Attribu /// - content: The content of the element. @inlinable public init(_ attributes: HTMLAttribute..., @HTMLBuilder content: () -> Content) { - self.attributes = .init(attributes) + self._attributes = .init(attributes) self.content = content() } @@ -50,7 +44,7 @@ public struct HTMLElement: HTML, _Attribu /// - content: The content of the element. @inlinable public init(attributes: [HTMLAttribute], @HTMLBuilder content: () -> Content) { - self.attributes = .init(attributes) + self._attributes = .init(attributes) self.content = content() } @@ -60,9 +54,9 @@ public struct HTMLElement: HTML, _Attribu into renderer: inout Renderer, with context: consuming _RenderingContext ) { - html.attributes.append(context.attributes) + html._attributes.append(context.attributes) - renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: false, type: Tag.renderingType)) + renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: false, type: Tag.renderingType)) Content._render(html.content, into: &renderer, with: .emptyContext) renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType)) } @@ -74,10 +68,10 @@ public struct HTMLElement: HTML, _Attribu into renderer: inout Renderer, with context: consuming _RenderingContext ) async throws { - html.attributes.append(context.attributes) + html._attributes.append(context.attributes) try await renderer.appendToken( - .startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: false, type: Tag.renderingType) + .startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: false, type: Tag.renderingType) ) try await Content._render(html.content, into: &renderer, with: .emptyContext) try await renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType)) @@ -89,39 +83,33 @@ public struct HTMLVoidElement: HTML, _Attributed where T /// The type of the HTML tag this element represents. public typealias Tag = Tag - @available(*, deprecated, message: "`var _attributes` is deprecated, use `var attributes` instead") - public var _attributes: _AttributeStorage { - get { attributes } - set { attributes = newValue } - } - - public var attributes: _AttributeStorage + public var _attributes: _AttributeStorage /// Creates a new HTML void element. @inlinable public init() { - self.attributes = .init() + self._attributes = .init() } /// Creates a new HTML void element with the specified attribute. /// - Parameter attribute: The attribute to apply to the element. @inlinable public init(_ attribute: HTMLAttribute) { - self.attributes = .init(attribute) + self._attributes = .init(attribute) } /// Creates a new HTML void element with the specified attributes. /// - Parameter attributes: The attributes to apply to the element. @inlinable public init(_ attributes: HTMLAttribute...) { - self.attributes = .init(attributes) + self._attributes = .init(attributes) } /// Creates a new HTML void element with the specified attributes. /// - Parameter attributes: The attributes to apply to the element as an array. @inlinable public init(attributes: [HTMLAttribute]) { - self.attributes = .init(attributes) + self._attributes = .init(attributes) } @inlinable @@ -130,8 +118,8 @@ public struct HTMLVoidElement: HTML, _Attributed where T into renderer: inout Renderer, with context: consuming _RenderingContext ) { - html.attributes.append(context.attributes) - renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: true, type: Tag.renderingType)) + html._attributes.append(context.attributes) + renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType)) } @inlinable @@ -141,9 +129,9 @@ public struct HTMLVoidElement: HTML, _Attributed where T into renderer: inout Renderer, with context: consuming _RenderingContext ) async throws { - html.attributes.append(context.attributes) + html._attributes.append(context.attributes) try await renderer.appendToken( - .startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: true, type: Tag.renderingType) + .startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType) ) } } diff --git a/Sources/Elementary/Core/HtmlElement+Async.swift b/Sources/Elementary/Core/HtmlElement+Async.swift index c8a7f25..0b50415 100644 --- a/Sources/Elementary/Core/HtmlElement+Async.swift +++ b/Sources/Elementary/Core/HtmlElement+Async.swift @@ -13,7 +13,7 @@ public extension HTMLElement { @HTMLBuilder content: @escaping @Sendable () async throws -> AwaitedContent ) where Self.Content == AsyncContent { - self.attributes = .init(attributes) + self._attributes = .init(attributes) self.content = AsyncContent(content: content) } @@ -31,7 +31,7 @@ public extension HTMLElement { @HTMLBuilder content: @escaping @Sendable () async throws -> AwaitedContent ) where Self.Content == AsyncContent { - self.attributes = .init(attributes) + self._attributes = .init(attributes) self.content = AsyncContent(content: content) } }