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
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.2.1
4 changes: 2 additions & 2 deletions Benchmarks/Benchmarks/ElementaryBenchmarks/Benchmarks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,15 @@ struct MyCustomElement<H: HTML>: HTML {
myContent = content()
}

var content: some HTML {
var body: some HTML {
myContent
}
}

struct MyListItem: HTML {
let number: Int

var content: some HTML {
var body: some HTML {
let isEven = number.isMultiple(of: 2)

li(.id("\(number)")) {
Expand Down
1 change: 1 addition & 0 deletions Examples/HummingbirdDemo/.swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.2.1
4 changes: 2 additions & 2 deletions Examples/HummingbirdDemo/Sources/App/Pages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct MainLayout<Body: HTML>: HTMLDocument {
}

struct WelcomePage: HTML {
var content: some HTML {
var body: some HTML {
div(.class("flex flex-col gap-4")) {
p {
"This is a simple example of using "
Expand All @@ -52,7 +52,7 @@ struct GreetingPage: HTML {
@Environment(requiring: EnvironmentValues.$name) var name
var greetingCount: Int

var content: some HTML {
var body: some HTML {
if greetingCount < 1 {
p(.class("text-red-500")) {
"No greetings to show."
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct MainPage: HTMLDocument {
struct FeatureList: HTML {
var features: [String]

var content: some HTML {
var body: some HTML {
ul {
for feature in features {
li { feature }
Expand Down Expand Up @@ -122,7 +122,7 @@ struct List: HTML {
var items: [String]
var importantIndex: Int

var content: some HTML {
var body: some HTML {
// conditional rendering
if items.isEmpty {
p { "No items" }
Expand All @@ -142,7 +142,7 @@ struct ListItem: HTML {
var text: String
var isImportant: Bool = false

var content: some HTML {
var body: some HTML {
// conditional attributes
li { text }
.attributes(.class("important"), when: isImportant)
Expand Down Expand Up @@ -183,7 +183,7 @@ struct Button: HTML {
var text: String

// by exposing the HTMLTag type information...
var content: some HTML<HTMLTag.input> {
var body: some HTML<HTMLTag.input> {
input(.type(.button), .value(text))
}
}
Expand All @@ -209,7 +209,7 @@ div {
}

struct MyComponent: HTML {
var content: some HTML {
var body: some HTML {
AsyncContent {
"So does this: \(await getMoreData())"
}
Expand Down Expand Up @@ -245,7 +245,7 @@ struct MyComponent: HTML {
// ... their values can be accessed ...
@Environment(MyValues.$userName) var userName

var content: some HTML {
var body: some HTML {
p { "Hello, \(userName)!" }
}
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/Elementary/Core/AsyncContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
/// The this element can only be rendered in an async context (ie: by calling ``HTML/render(into:chunkSize:)`` or ``HTML/renderAsync()``).
/// All HTML tag types (``HTMLElement``) support async content closures in their initializers, so you don't need to use this element directly in most cases.
public struct AsyncContent<Content: HTML>: HTML, Sendable {
public typealias Body = Never
public typealias Tag = Content.Tag

@usableFromInline
var content: @Sendable () async throws -> Content
public typealias Tag = Content.Tag

/// Creates a new async HTML element with the specified content.
///
Expand Down
2 changes: 2 additions & 0 deletions Sources/Elementary/Core/AsyncForEach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
/// }
/// ```
public struct AsyncForEach<Source: AsyncSequence, Content: HTML>: HTML {
public typealias Body = Never

@usableFromInline
var sequence: Source
@usableFromInline
Expand Down
32 changes: 26 additions & 6 deletions Sources/Elementary/Core/CoreModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/// struct FeatureList: HTML {
/// var features: [String]
///
/// var content: some HTML {
/// var body: some HTML {
/// ul {
/// for feature in features {
/// li { feature }
Expand All @@ -22,13 +22,18 @@ public protocol HTML<Tag> {
/// The Tag type defines which attributes can be attached to an HTML element.
/// If an element does not represent a specific HTML tag, the Tag type will
/// be ``Swift/Never`` and the element cannot be attributed.
associatedtype Tag: HTMLTagDefinition = Content.Tag
associatedtype Tag: HTMLTagDefinition = Body.Tag

/// The type of the HTML content this component represents.
associatedtype Content: HTML = Never
associatedtype Body: HTML

/// The HTML content of this component.
@HTMLBuilder var content: Content { get }
@HTMLBuilder var body: Body { get }

/// The HTML content of this component.
@HTMLBuilder
@available(*, deprecated, message: "`var content` is deprecated, use `var body` instead")
var content: Body { get }

static func _render<Renderer: _HTMLRendering>(
_ html: consuming Self,
Expand All @@ -42,6 +47,21 @@ public protocol HTML<Tag> {
) async throws
}

extension HTML {
@available(*, deprecated, message: "`var content` is deprecated, use `var body` instead")
public var body: Body {
// NOTE: sorry for the change
content
}

@available(*, deprecated, message: "Content was renamed, use Body instead")
public typealias Content = Body

public var content: Body {
fatalError("Please make sure to add a `var body` implementation to your HTML type.")
}
}

/// A type that represents an HTML tag.
public protocol HTMLTagDefinition: Sendable {
/// The name of the HTML tag as it is rendered in an HTML document.
Expand Down Expand Up @@ -93,7 +113,7 @@ public extension HTML {
into renderer: inout Renderer,
with context: consuming _RenderingContext
) {
Content._render(html.content, into: &renderer, with: context)
Body._render(html.body, into: &renderer, with: context)
}

@inlinable
Expand All @@ -102,7 +122,7 @@ public extension HTML {
into renderer: inout Renderer,
with context: consuming _RenderingContext
) async throws {
try await Content._render(html.content, into: &renderer, with: context)
try await Body._render(html.body, into: &renderer, with: context)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Elementary/Core/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// struct MyNumber: HTML {
/// @Environment(Values.$myNumber) var number
///
/// var content: some HTML {
/// var body: some HTML {
/// p { "\(number)" }
/// }
/// }
Expand Down
3 changes: 3 additions & 0 deletions Sources/Elementary/Core/Html+Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ extension HTMLAttribute {
}

public struct _AttributedElement<Content: HTML>: HTML {
public typealias Body = Never
public typealias Tag = Content.Tag

public var content: Content
public var attributes: _AttributeStorage

Expand Down
3 changes: 3 additions & 0 deletions Sources/Elementary/Core/Html+Elements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag: HTMLTrait.Paired {
/// The type of the HTML tag this element represents.
public typealias Tag = Tag
public typealias Body = Never
public typealias Content = Content

public var _attributes: _AttributeStorage

// The content of the element.
Expand Down
6 changes: 3 additions & 3 deletions Sources/Elementary/Core/HtmlBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
}
}

public extension HTML where Content == Never {
var content: Never {
public extension HTML where Body == Never {
var body: Never {
#if hasFeature(Embedded)
fatalError("content was called on an unsupported type")
#else
Expand All @@ -61,7 +61,7 @@ public extension HTML where Content == Never {

extension Never: HTML {
public typealias Tag = Never
public typealias Content = Never
public typealias Body = Never
}

extension Optional: HTML where Wrapped: HTML {
Expand Down
57 changes: 43 additions & 14 deletions Sources/Elementary/HtmlDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,50 @@ public protocol HTMLDocument: HTML {
@HTMLBuilder var body: HTMLBody { get }
}

// NOTE: The default implementation uses an empty string as the "magic value" for undefined.
// This is to avoid the need for an optional `lang` or `dir`` property on the protocol,
// which would cause confusing issues when adopters provide a property of a non-optional type.
private let defaultUndefinedLanguage = ""
private let defaultUndefinedDirection = ""

public extension HTMLDocument {
@HTMLBuilder var content: some HTML {
/// The default value for the `lang` property is an empty string and will not be rendered in the HTML.
var lang: String { defaultUndefinedLanguage }
/// The default value for the `dir` property is an empty string and will not be rendered in the HTML.
var dir: HTMLAttributeValue.Direction { .init(value: defaultUndefinedDirection) }
}

// NOTE: this is a bit messy after the renaming of var content to var body
public extension HTMLDocument {
static func _render<Renderer: _HTMLRendering>(
_ html: consuming Self,
into renderer: inout Renderer,
with context: consuming _RenderingContext
) {
func render<H: HTML>(_ html: H, into renderer: inout some _HTMLRendering, with context: consuming _RenderingContext) {
H._render(html, into: &renderer, with: context)
}

render(html.__body, into: &renderer, with: context)
}

static func _render<Renderer: _AsyncHTMLRendering>(
_ html: consuming Self,
into renderer: inout Renderer,
with context: consuming _RenderingContext
) async throws {
func render<H: HTML>(
_ html: H,
into renderer: inout some _AsyncHTMLRendering,
with context: consuming _RenderingContext
) async throws {
try await H._render(html, into: &renderer, with: context)
}

try await render(html.__body, into: &renderer, with: context)
}

@HTMLBuilder var __body: some HTML {
HTMLRaw("<!DOCTYPE html>")
html {
Elementary.head {
Expand All @@ -55,16 +97,3 @@ public extension HTMLDocument {
.attributes(.dir(dir), when: dir.value != defaultUndefinedDirection)
}
}

// NOTE: The default implementation uses an empty string as the "magic value" for undefined.
// This is to avoid the need for an optional `lang` or `dir`` property on the protocol,
// which would cause confusing issues when adopters provide a property of a non-optional type.
private let defaultUndefinedLanguage = ""
private let defaultUndefinedDirection = ""

public extension HTMLDocument {
/// The default value for the `lang` property is an empty string and will not be rendered in the HTML.
var lang: String { defaultUndefinedLanguage }
/// The default value for the `dir` property is an empty string and will not be rendered in the HTML.
var dir: HTMLAttributeValue.Direction { .init(value: defaultUndefinedDirection) }
}
2 changes: 1 addition & 1 deletion Tests/ElementaryTests/AsyncRenderingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ final class AsyncRenderingTests: XCTestCase {

private struct AwaitedP: HTML {
var number: Int
var content: some HTML {
var body: some HTML {
AsyncContent {
let _ = try await Task.sleep(for: .milliseconds(1))
p { "\(number)" }
Expand Down
4 changes: 2 additions & 2 deletions Tests/ElementaryTests/CompositionRenderingTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct MyList: HTML {
var items: [String]
var selectedIndex: Int

var content: some HTML {
var body: some HTML {
ul {
for (index, item) in items.enumerated() {
MyListItem(text: item, isSelected: index == selectedIndex)
Expand All @@ -79,7 +79,7 @@ struct MyListItem: HTML {
var text: String
var isSelected: Bool = false

var content: some HTML<HTMLTag.li> {
var body: some HTML<HTMLTag.li> {
li { text }
.attributes(.class("selected"), when: isSelected)
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/ElementaryTests/EnvironmentRenderingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ final class EnvironmentRenderingTests: XCTestCase {
struct MyNumber: HTML {
@Environment(Values.$number) var number

var content: some HTML {
var body: some HTML {
"\(number)"
}
}

struct MyDatabaseValue: HTML {
@Environment(requiring: Values.$database) var database
var content: some HTML {
var body: some HTML {
p {
await database.value
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/ElementaryTests/SendableAnyHTMLBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class NonSendable {

struct MyComponent: HTML {
let ns = NonSendable()
var content: some HTML {
var body: some HTML {
div { "\(ns.x)" }
}
}
2 changes: 1 addition & 1 deletion Tests/ElementaryTests/TextRenderingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Bar {
class JO: HTML {
var bar: Bar = .init()

var content: some HTML {
var body: some HTML {
"Hello, World!"
}
}
Loading