Skip to content

Commit 142b034

Browse files
jverkoeyclaude
andauthored
Implement remaining W3C HTML global attributes (#257)
This commit implements 25 W3C global HTML attributes as ViewModifiers, completing the full suite of remaining global attributes from the HTML specification. Each modifier balances W3C idioms with SwiftUI conventions to provide a familiar experience for SwiftUI developers. New modifiers added: - accesskey(_:) - Keyboard shortcuts for element activation - autocapitalize(_:) - Text capitalization control with enum - autofocus(_:) - Page load focus control - contenteditable(_:) - Content editability with enum/bool support - direction(_:) - Text directionality (ltr, rtl, auto) - draggable(_:) - Drag and drop support - enterKeyHint(_:) - Virtual keyboard enter key hints - hidden(_:) - Element visibility with state support - inert(_:) - Non-interactive element marking - inputMode(_:) - Virtual keyboard type hints - customElement(_:) - Custom element support (is attribute) - Microdata support (itemscope, itemtype, itemid, itemprop, itemref) - nonce(_:) - Content Security Policy nonce - popover(_:) - Popover element support with state - slot(_:) - Shadow DOM slot assignment - spellcheck(_:) - Spell checking control - inlineStyle(_:) - Inline CSS (style attribute) - tabIndex(_:) / focusable() - Focus and keyboard navigation - tooltip(_:) - Advisory information (title attribute) - translatable(_:) - Translation control - writingSuggestions(_:) - Browser writing suggestions Updated AttributeModifier.GlobalAttribute enum with all new cases. Updated documentation in SlipstreamForWebDevelopers.md with a new "Global attributes" section mapping W3C attributes to Slipstream modifiers. Implementation follows established patterns using AttributeModifier and ConditionalAttributeModifier for consistent behavior across the codebase. Part of #27 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dce8999 commit 142b034

44 files changed

Lines changed: 1213 additions & 0 deletions

Some content is hidden

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

Sources/Slipstream/Documentation.docc/Guides/SlipstreamForWebDevelopers.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,42 @@ provided below is an organized table of W3C HTML tags and their equivalent Slips
271271
[`<mtable>`](https://www.w3.org/TR/MathML3/chapter3.html#presm.mtable) | ``MTable``
272272
[`<mtr>`](https://www.w3.org/TR/MathML3/chapter3.html#presm.mtr) | ``MTr``
273273
[`<mtd>`](https://www.w3.org/TR/MathML3/chapter3.html#presm.mtd) | ``MTd``
274+
275+
## The global attributes of Slipstream
276+
277+
Modeling after [the W3C specification](https://html.spec.whatwg.org/multipage/dom.html#global-attributes),
278+
provided below is an organized table of W3C global HTML attributes and their equivalent Slipstream view modifiers.
279+
280+
### Global attributes
281+
282+
W3C attribute | Slipstream modifier | Notes
283+
:-------------|:-------------------|:------
284+
[`accesskey`](https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute) | ``keyboardShortcut(_:)`` | Specifies a keyboard shortcut to activate or focus the element
285+
[`autocapitalize`](https://html.spec.whatwg.org/multipage/interaction.html#attr-autocapitalize) | ``autocapitalize(_:)`` | Controls whether and how text input is automatically capitalized
286+
[`autofocus`](https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus) | ``autofocus(_:)`` | Indicates that the element should be focused on page load
287+
`class` | ``className(_:)`` | Adds classes to the element. Use TailwindCSS modifiers for styling
288+
[`contenteditable`](https://html.spec.whatwg.org/multipage/interaction.html#attr-contenteditable) | ``contenteditable(_:)`` | Indicates whether the element's content is editable
289+
[`data-*`](https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) | ``data(_:)`` | Sets custom data attributes on the view
290+
[`dir`](https://html.spec.whatwg.org/multipage/dom.html#attr-dir) | ``direction(_:)`` | Specifies the element's text directionality
291+
[`draggable`](https://html.spec.whatwg.org/multipage/dnd.html#attr-draggable) | ``draggable(_:)`` | Indicates whether the element can be dragged
292+
[`enterkeyhint`](https://html.spec.whatwg.org/multipage/interaction.html#attr-enterkeyhint) | ``submitLabel(_:)`` | Hints at the action label for the enter key on virtual keyboards
293+
[`hidden`](https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden) | ``hidden(_:)`` | Sets the HTML hidden attribute (different from TailwindCSS hidden())
294+
`id` | ``id(_:)`` | Sets the element's unique identifier
295+
[`inert`](https://html.spec.whatwg.org/multipage/interaction.html#the-inert-attribute) | ``allowsHitTesting(_:)`` | Controls whether the element can receive user interaction (inverted logic)
296+
[`inputmode`](https://html.spec.whatwg.org/multipage/interaction.html#attr-inputmode) | ``keyboardType(_:)`` | Sets the keyboard type for text input
297+
[`is`](https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is) | ``customElement(_:)`` | Specifies the name of a custom element
298+
[`itemid`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemid) | ``itemID(_:)`` | The globally unique identifier of a microdata item
299+
[`itemprop`](https://html.spec.whatwg.org/multipage/microdata.html#names:-the-itemprop-attribute) | ``itemProp(_:)`` | Used to add properties to a microdata item
300+
[`itemref`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemref) | ``itemRef(_:)`` | Associates non-descendant properties with a microdata item
301+
[`itemscope`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemscope) | ``itemScope(_:)`` | Creates a new microdata item
302+
[`itemtype`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemtype) | ``itemType(_:)`` | Specifies the vocabulary URL for a microdata item
303+
[`lang`](https://html.spec.whatwg.org/multipage/dom.html#attr-lang) | ``language(_:)`` | Sets the primary language for the view's contents
304+
[`nonce`](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#attr-nonce) | ``nonce(_:)`` | A cryptographic nonce used by Content Security Policy
305+
[`popover`](https://html.spec.whatwg.org/multipage/popover.html#attr-popover) | ``popover(_:)`` | Indicates that the element is a popover element
306+
`slot` | ``slot(_:)`` | Assigns a slot in a shadow DOM shadow tree
307+
[`spellcheck`](https://html.spec.whatwg.org/multipage/interaction.html#attr-spellcheck) | ``spellcheck(_:)`` | Specifies whether the element may be checked for spelling errors
308+
[`style`](https://html.spec.whatwg.org/multipage/dom.html#attr-style) | ``inlineStyle(_:)`` | Contains CSS styling declarations. Prefer TailwindCSS modifiers when possible
309+
[`tabindex`](https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex) | ``tabIndex(_:)`` or ``focusable()`` | Controls focus and keyboard navigation
310+
[`title`](https://html.spec.whatwg.org/multipage/dom.html#attr-title) | ``tooltip(_:)`` | Advisory information, typically shown as a tooltip
311+
[`translate`](https://html.spec.whatwg.org/multipage/dom.html#attr-translate) | ``translatable(_:)`` | Specifies whether content should be translated
312+
[`writingsuggestions`](https://html.spec.whatwg.org/multipage/interaction.html#attr-writingsuggestions) | ``writingSuggestions(_:)`` | Controls whether browser-provided writing suggestions should be offered

Sources/Slipstream/Fundamentals/AttributeModifier.swift

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,65 @@ public struct AttributeModifier<T: View>: ViewModifier {
2525

2626
/// A W3C global attribute, as defined in [3.2.3 Global attributes](https://html.spec.whatwg.org/multipage/dom.html#global-attributes).
2727
public enum GlobalAttribute: String {
28+
/// Specifies a keyboard shortcut to activate or focus the element.
29+
case accesskey
30+
31+
/// Controls whether and how text input is automatically capitalized.
32+
case autocapitalize
33+
34+
/// Indicates that the element should be focused on page load.
35+
case autofocus
36+
2837
/// The class attribute is most commonly used by stylesheets to apply styles
2938
/// to a view.
3039
case `class`
3140

41+
/// Indicates whether the element's content is editable.
42+
case contenteditable
43+
44+
/// Specifies the element's text directionality.
45+
case dir
46+
47+
/// Indicates whether the element can be dragged.
48+
case draggable
49+
50+
/// Hints at the type of data that might be entered by the user while editing the element or its contents.
51+
case enterkeyhint
52+
53+
/// Indicates that the element is not yet, or is no longer, relevant.
54+
case hidden
55+
3256
/// The id attribute specifies its element's unique identifier (ID).
3357
///
3458
/// There are no other restrictions on what form an ID can take; in particular,
3559
/// IDs can consist of just digits, start with a digit, start with an
3660
/// underscore, consist of just punctuation, etc.
3761
case id
3862

63+
/// Indicates that the element and its descendants should be made non-interactive.
64+
case inert
65+
66+
/// Hints at the type of data that might be entered by the user while editing the element.
67+
case inputmode
68+
69+
/// Specifies the name of a custom element.
70+
case `is`
71+
72+
/// The globally unique identifier of an item.
73+
case itemid
74+
75+
/// Used to add properties to an item.
76+
case itemprop
77+
78+
/// Properties that are not descendants of an element can be associated with the item using this attribute.
79+
case itemref
80+
81+
/// Creates a new item, a group of name-value pairs.
82+
case itemscope
83+
84+
/// Specifies the URL of the vocabulary that will be used to define item properties.
85+
case itemtype
86+
3987
/// The lang attribute specifies the primary language for the view's contents
4088
/// and for any of the view's attributes that contain text. Its value must be a
4189
/// valid [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag),
@@ -50,6 +98,33 @@ public struct AttributeModifier<T: View>: ViewModifier {
5098
/// - [Common primary language tags](https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags)
5199
/// - [Language Subtag Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
52100
case lang
101+
102+
/// A cryptographic nonce used by Content Security Policy.
103+
case nonce
104+
105+
/// Indicates that the element is a popover element.
106+
case popover
107+
108+
/// Assigns a slot in a shadow DOM shadow tree to an element.
109+
case slot
110+
111+
/// Specifies whether the element may be checked for spelling errors.
112+
case spellcheck
113+
114+
/// Contains CSS styling declarations to be applied to the element.
115+
case style
116+
117+
/// Specifies whether the element can be focused and whether/where it participates in sequential keyboard navigation.
118+
case tabindex
119+
120+
/// Advisory information associated with the element.
121+
case title
122+
123+
/// Specifies whether an element's attribute values and text content should be translated.
124+
case translate
125+
126+
/// Controls whether browser-provided writing suggestions should be offered.
127+
case writingsuggestions
53128
}
54129

55130
/// Creates an attribute modifier that will set `attribute` to `value` on any modified views.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
extension View {
2+
/// Specifies a keyboard shortcut to activate or focus the element.
3+
///
4+
/// The accesskey attribute's value is used to generate a keyboard shortcut that
5+
/// activates or focuses the element. The exact behavior depends on the browser and platform.
6+
///
7+
/// ```swift
8+
/// Button("Save") {
9+
/// // Save action
10+
/// }
11+
/// .keyboardShortcut("s")
12+
/// ```
13+
///
14+
/// - Parameter key: A single character that will be used as the keyboard shortcut.
15+
/// The exact key combination varies by browser and platform (e.g., Alt+key on Windows,
16+
/// Control+Option+key on macOS).
17+
///
18+
/// - SeeAlso: W3C [`accesskey`](https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute) specification.
19+
@available(iOS 17.0, macOS 14.0, *)
20+
public func keyboardShortcut(_ key: String) -> some View {
21+
return modifier(AttributeModifier(.accesskey, value: key))
22+
}
23+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// The autocapitalize attribute controls whether and how text input is automatically capitalized.
2+
@available(iOS 17.0, macOS 14.0, *)
3+
public enum Autocapitalize: String {
4+
/// No automatic capitalization.
5+
case none
6+
/// Capitalize the first letter of each sentence.
7+
case sentences
8+
/// Capitalize the first letter of each word.
9+
case words
10+
/// Capitalize all characters.
11+
case characters
12+
}
13+
14+
extension View {
15+
/// Controls whether and how text input is automatically capitalized.
16+
///
17+
/// The autocapitalize attribute is particularly useful for mobile devices where the system
18+
/// keyboard can automatically capitalize text based on the specified behavior.
19+
///
20+
/// ```swift
21+
/// TextField("Name", type: .text)
22+
/// .autocapitalize(.words)
23+
/// ```
24+
///
25+
/// - Parameter mode: The autocapitalization behavior to apply.
26+
///
27+
/// - SeeAlso: W3C [`autocapitalize`](https://html.spec.whatwg.org/multipage/interaction.html#attr-autocapitalize) specification.
28+
@available(iOS 17.0, macOS 14.0, *)
29+
public func autocapitalize(_ mode: Autocapitalize) -> some View {
30+
return modifier(AttributeModifier(.autocapitalize, value: mode.rawValue))
31+
}
32+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
extension View {
2+
/// Indicates that the element should be focused when the page loads.
3+
///
4+
/// Only one element in the document should have the autofocus attribute. If applied
5+
/// to multiple elements, the first one in document order will receive focus.
6+
///
7+
/// ```swift
8+
/// TextField("Search", type: .search)
9+
/// .autofocus()
10+
/// ```
11+
///
12+
/// - Parameter condition: A Boolean value that determines whether the element should
13+
/// receive focus on page load. Defaults to true.
14+
///
15+
/// - SeeAlso: W3C [`autofocus`](https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus) specification.
16+
@available(iOS 17.0, macOS 14.0, *)
17+
public func autofocus(_ condition: Bool = true) -> some View {
18+
return modifier(ConditionalAttributeModifier("autofocus", condition: condition))
19+
}
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/// The contenteditable attribute indicates whether the element's content is editable.
2+
@available(iOS 17.0, macOS 14.0, *)
3+
public enum Contenteditable: String {
4+
/// The element is editable.
5+
case `true`
6+
/// The element is not editable.
7+
case `false`
8+
/// The element inherits the contenteditable state from its parent.
9+
case inherit = "inherit"
10+
}
11+
12+
extension View {
13+
/// Indicates whether the element's content is editable.
14+
///
15+
/// The contenteditable attribute makes an element's content editable by the user.
16+
/// This is commonly used to create rich text editors and inline editing experiences.
17+
///
18+
/// ```swift
19+
/// Div {
20+
/// Text("Edit this text")
21+
/// }
22+
/// .contenteditable(.true)
23+
/// ```
24+
///
25+
/// - Parameter value: The editability state of the element.
26+
///
27+
/// - SeeAlso: W3C [`contenteditable`](https://html.spec.whatwg.org/multipage/interaction.html#attr-contenteditable) specification.
28+
@available(iOS 17.0, macOS 14.0, *)
29+
public func contenteditable(_ value: Contenteditable) -> some View {
30+
return modifier(AttributeModifier(.contenteditable, value: value.rawValue))
31+
}
32+
33+
/// Indicates whether the element's content is editable.
34+
///
35+
/// - Parameter condition: A Boolean value that determines whether the element is editable.
36+
@available(iOS 17.0, macOS 14.0, *)
37+
public func contenteditable(_ condition: Bool) -> some View {
38+
return modifier(AttributeModifier(.contenteditable, value: condition ? "true" : "false"))
39+
}
40+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// The dir attribute specifies the element's text directionality.
2+
@available(iOS 17.0, macOS 14.0, *)
3+
public enum TextDirection: String {
4+
/// Left-to-right text direction.
5+
case ltr
6+
/// Right-to-left text direction.
7+
case rtl
8+
/// Automatic text direction based on content.
9+
case auto
10+
}
11+
12+
extension View {
13+
/// Specifies the element's text directionality.
14+
///
15+
/// The dir attribute controls the direction in which text is rendered. This is
16+
/// particularly important for languages like Arabic and Hebrew that are written
17+
/// right-to-left.
18+
///
19+
/// ```swift
20+
/// Div {
21+
/// Text("مرحبا بك")
22+
/// }
23+
/// .direction(.rtl)
24+
/// ```
25+
///
26+
/// - Parameter direction: The text direction for the element.
27+
///
28+
/// - SeeAlso: W3C [`dir`](https://html.spec.whatwg.org/multipage/dom.html#attr-dir) specification.
29+
@available(iOS 17.0, macOS 14.0, *)
30+
public func direction(_ direction: TextDirection) -> some View {
31+
return modifier(AttributeModifier(.dir, value: direction.rawValue))
32+
}
33+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
extension View {
2+
/// Indicates whether the element can be dragged.
3+
///
4+
/// The draggable attribute controls whether an element can be dragged using the
5+
/// HTML Drag and Drop API. This is commonly used for implementing drag-and-drop
6+
/// interfaces.
7+
///
8+
/// ```swift
9+
/// Div {
10+
/// Text("Drag me")
11+
/// }
12+
/// .draggable()
13+
/// ```
14+
///
15+
/// - Parameter condition: A Boolean value that determines whether the element can be dragged.
16+
/// Defaults to true.
17+
///
18+
/// - SeeAlso: W3C [`draggable`](https://html.spec.whatwg.org/multipage/dnd.html#attr-draggable) specification.
19+
@available(iOS 17.0, macOS 14.0, *)
20+
public func draggable(_ condition: Bool = true) -> some View {
21+
return modifier(AttributeModifier(.draggable, value: condition ? "true" : "false"))
22+
}
23+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/// The enterkeyhint attribute hints at the action label or icon for the enter key
2+
/// on virtual keyboards.
3+
@available(iOS 17.0, macOS 14.0, *)
4+
public enum EnterKeyHint: String {
5+
/// The user agent should present a cue for the action 'enter', typically inserting a new line.
6+
case enter
7+
/// The user agent should present a cue for the action 'done', typically meaning there is
8+
/// nothing more to input and the input method editor (IME) will be closed.
9+
case done
10+
/// The user agent should present a cue for the action 'go', typically meaning to take the
11+
/// user to the target of the text they typed.
12+
case go
13+
/// The user agent should present a cue for the action 'next', typically taking the user to
14+
/// the next field that will accept text.
15+
case next
16+
/// The user agent should present a cue for the action 'previous', typically taking the user
17+
/// to the previous field that will accept text.
18+
case previous
19+
/// The user agent should present a cue for the action 'search', typically taking the user
20+
/// to the results of searching for the text they have typed.
21+
case search
22+
/// The user agent should present a cue for the action 'send', typically delivering the text
23+
/// to its target.
24+
case send
25+
}
26+
27+
extension View {
28+
/// Hints at the action label or icon to present for the enter key on virtual keyboards.
29+
///
30+
/// The submitLabel modifier is particularly useful on mobile devices where the virtual
31+
/// keyboard can display different labels on the enter key based on the expected action.
32+
///
33+
/// ```swift
34+
/// TextField("Search", type: .search)
35+
/// .submitLabel(.search)
36+
/// ```
37+
///
38+
/// - Parameter label: The label for the enter key presentation.
39+
///
40+
/// - SeeAlso: W3C [`enterkeyhint`](https://html.spec.whatwg.org/multipage/interaction.html#attr-enterkeyhint) specification.
41+
@available(iOS 17.0, macOS 14.0, *)
42+
public func submitLabel(_ label: EnterKeyHint) -> some View {
43+
return modifier(AttributeModifier(.enterkeyhint, value: label.rawValue))
44+
}
45+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// The hidden attribute indicates that the element is not yet, or is no longer, relevant.
2+
@available(iOS 17.0, macOS 14.0, *)
3+
public enum HiddenState: String {
4+
/// The element is hidden in all rendering modes.
5+
case hidden = ""
6+
/// The element is hidden but remains in the accessibility tree until it is found.
7+
case untilFound = "until-found"
8+
}
9+
10+
extension View {
11+
/// Indicates that the element is not yet, or is no longer, relevant.
12+
///
13+
/// Sets the HTML `hidden` attribute to semantically mark content as not relevant.
14+
/// This is different from the TailwindCSS `hidden()` modifier which uses CSS to
15+
/// visually hide elements.
16+
///
17+
/// ```swift
18+
/// Div {
19+
/// Text("This content is hidden")
20+
/// }
21+
/// .hidden(.hidden)
22+
/// ```
23+
///
24+
/// - Parameter state: The hidden state of the element.
25+
///
26+
/// - SeeAlso: W3C [`hidden`](https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden) specification.
27+
@available(iOS 17.0, macOS 14.0, *)
28+
public func hidden(_ state: HiddenState) -> some View {
29+
return modifier(AttributeModifier(.hidden, value: state.rawValue))
30+
}
31+
}
32+

0 commit comments

Comments
 (0)