Skip to content

Commit 800bd67

Browse files
authored
Release next-1.0.0 (#65)
* Now follows the new Website, Document and Element patterns * Fixes a small amount of element rendering issues * Move style modifier tests into seperate file * Tests for aria labels, style modifiers and HTML escaping all passing * Script rendering tests passing
1 parent 630e06f commit 800bd67

File tree

155 files changed

+4739
-6399
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+4739
-6399
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ swift package --disable-sandbox preview-documentation
104104
```
105105

106106
> [!NOTE]
107-
> You can also run `Build Documentation` inside of Xcode to view the documentation in
107+
> You can also run `Build Documentation` inside of Xcode to view the documentation in
108108
109109
### Adding New Elements
110110

@@ -367,7 +367,7 @@ WebUI supports two different ways to apply responsive styles:
367367
```swift
368368
Text { "Responsive Content" }
369369
.font(size: .sm)
370-
.responsive {
370+
.on {
371371
md {
372372
font(size: .lg)
373373
background(color: .neutral(._700))

Sources/WebUI/Core/Document.swift

Lines changed: 0 additions & 171 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
public enum Attribute {
2+
/// Builds an HTML attribute string if the value exists.
3+
///
4+
/// - Parameters:
5+
/// - name: Attribute name (e.g., "id", "class", "src").
6+
/// - value: Attribute value, optional.
7+
/// - Returns: Formatted attribute string (e.g., `id="header"`) or nil if value is empty.
8+
public static func string(_ name: String, _ value: String?) -> String? {
9+
guard let value = value, !value.isEmpty else { return nil }
10+
let escapedValue = HTMLEscaper.escapeAttribute(value)
11+
return "\(name)=\"\(escapedValue)\""
12+
}
13+
14+
/// Builds an HTML attribute string for a non-optional value.
15+
///
16+
/// - Parameters:
17+
/// - name: Attribute name (e.g., "id", "class", "src").
18+
/// - value: Attribute value.
19+
/// - Returns: Formatted attribute string (e.g., `id="header"`) or nil if value is empty.
20+
public static func string(_ name: String, _ value: String) -> String? {
21+
guard !value.isEmpty else { return nil }
22+
let escapedValue = HTMLEscaper.escapeAttribute(value)
23+
return "\(name)=\"\(escapedValue)\""
24+
}
25+
26+
/// Builds a boolean HTML attribute if enabled.
27+
///
28+
/// - Parameters:
29+
/// - name: Attribute name (e.g., "disabled", "checked", "required").
30+
/// - enabled: Boolean enabling the attribute, optional.
31+
/// - Returns: Attribute name if true, nil otherwise.
32+
public static func bool(_ name: String, _ enabled: Bool?) -> String? {
33+
enabled == true ? name : nil
34+
}
35+
36+
/// Builds an HTML attribute string from a typed enum value.
37+
///
38+
/// - Parameters:
39+
/// - name: Attribute name (e.g., "type", "role").
40+
/// - value: Enum value with String rawValue, optional.
41+
/// - Returns: Formatted attribute string (e.g., `type="submit"`) or nil if value is nil or empty.
42+
public static func typed<T: RawRepresentable>(_ name: String, _ value: T?) -> String?
43+
where T.RawValue == String {
44+
guard let stringValue = value?.rawValue, !stringValue.isEmpty else { return nil }
45+
let escapedValue = HTMLEscaper.escapeAttribute(stringValue)
46+
return "\(name)=\"\(escapedValue)\""
47+
}
48+
49+
/// Builds an HTML attribute string from a non-optional typed enum value.
50+
///
51+
/// - Parameters:
52+
/// - name: Attribute name (e.g., "type", "role").
53+
/// - value: Enum value with String rawValue.
54+
/// - Returns: Formatted attribute string (e.g., `type="submit"`) or nil if value is empty.
55+
public static func typed<T: RawRepresentable>(_ name: String, _ value: T) -> String?
56+
where T.RawValue == String {
57+
let stringValue = value.rawValue
58+
guard !stringValue.isEmpty else { return nil }
59+
let escapedValue = HTMLEscaper.escapeAttribute(stringValue)
60+
return "\(name)=\"\(escapedValue)\""
61+
}
62+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import Foundation
2+
3+
/// Utility for building HTML attributes
4+
///
5+
/// The `AttributeBuilder` provides helper methods for generating HTML attribute strings
6+
/// in a consistent way across all elements.
7+
public enum AttributeBuilder {
8+
/// Builds a collection of HTML attributes from common parameters
9+
///
10+
/// - Parameters:
11+
/// - id: Optional unique identifier for the HTML element
12+
/// - classes: Optional array of CSS class names
13+
/// - role: Optional ARIA role for accessibility
14+
/// - label: Optional ARIA label for accessibility
15+
/// - data: Optional dictionary of data attributes
16+
/// - additional: Optional array of additional attribute strings
17+
/// - Returns: Array of attribute strings for use in HTML tags
18+
public static func buildAttributes(
19+
id: String? = nil,
20+
classes: [String]? = nil,
21+
role: AriaRole? = nil,
22+
label: String? = nil,
23+
data: [String: String]? = nil,
24+
additional: [String] = []
25+
) -> [String] {
26+
var attributes: [String] = []
27+
28+
if let id = id {
29+
attributes.append(Attribute.string("id", id)!)
30+
}
31+
32+
if let classes = classes, !classes.isEmpty {
33+
attributes.append(Attribute.string("class", classes.joined(separator: " "))!)
34+
}
35+
36+
if let role = role {
37+
attributes.append(Attribute.typed("role", role)!)
38+
}
39+
40+
if let label = label {
41+
attributes.append(Attribute.string("aria-label", label)!)
42+
}
43+
44+
if let data = data {
45+
for (key, value) in data {
46+
attributes.append(Attribute.string("data-\(key)", value)!)
47+
}
48+
}
49+
50+
attributes.append(contentsOf: additional)
51+
52+
return attributes
53+
}
54+
55+
/// Renders a complete HTML tag with attributes and content
56+
///
57+
/// - Parameters:
58+
/// - tag: The HTML tag name
59+
/// - attributes: Array of attribute strings
60+
/// - content: Optional content to include between opening and closing tags
61+
/// - isSelfClosing: Whether this is a self-closing tag
62+
/// - noClosingTag: Whether this should be rendered without a self-close and without a seperate close
63+
/// - Returns: Complete HTML tag as a string
64+
public static func renderTag(
65+
_ tag: String,
66+
attributes: [String],
67+
content: String = "",
68+
isSelfClosing: Bool = false,
69+
hasNoClosingTag: Bool = false,
70+
) -> String {
71+
let attributeString = attributes.isEmpty ? "" : " " + attributes.joined(separator: " ")
72+
73+
if isSelfClosing {
74+
return "<\(tag)\(attributeString) />"
75+
} else if hasNoClosingTag {
76+
return "<\(tag)\(attributeString)>"
77+
} else {
78+
return "<\(tag)\(attributeString)>\(content)</\(tag)>"
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)