diff --git a/Sources/Slipstream/Documentation.docc/Guides/SlipstreamForWebDevelopers.md b/Sources/Slipstream/Documentation.docc/Guides/SlipstreamForWebDevelopers.md index 309275f2..73bd8620 100644 --- a/Sources/Slipstream/Documentation.docc/Guides/SlipstreamForWebDevelopers.md +++ b/Sources/Slipstream/Documentation.docc/Guides/SlipstreamForWebDevelopers.md @@ -271,3 +271,42 @@ provided below is an organized table of W3C HTML tags and their equivalent Slips [``](https://www.w3.org/TR/MathML3/chapter3.html#presm.mtable) | ``MTable`` [``](https://www.w3.org/TR/MathML3/chapter3.html#presm.mtr) | ``MTr`` [``](https://www.w3.org/TR/MathML3/chapter3.html#presm.mtd) | ``MTd`` + +## The global attributes of Slipstream + +Modeling after [the W3C specification](https://html.spec.whatwg.org/multipage/dom.html#global-attributes), +provided below is an organized table of W3C global HTML attributes and their equivalent Slipstream view modifiers. + +### Global attributes + +W3C attribute | Slipstream modifier | Notes +:-------------|:-------------------|:------ +[`accesskey`](https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute) | ``keyboardShortcut(_:)`` | Specifies a keyboard shortcut to activate or focus the element +[`autocapitalize`](https://html.spec.whatwg.org/multipage/interaction.html#attr-autocapitalize) | ``autocapitalize(_:)`` | Controls whether and how text input is automatically capitalized +[`autofocus`](https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus) | ``autofocus(_:)`` | Indicates that the element should be focused on page load +`class` | ``className(_:)`` | Adds classes to the element. Use TailwindCSS modifiers for styling +[`contenteditable`](https://html.spec.whatwg.org/multipage/interaction.html#attr-contenteditable) | ``contenteditable(_:)`` | Indicates whether the element's content is editable +[`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 +[`dir`](https://html.spec.whatwg.org/multipage/dom.html#attr-dir) | ``direction(_:)`` | Specifies the element's text directionality +[`draggable`](https://html.spec.whatwg.org/multipage/dnd.html#attr-draggable) | ``draggable(_:)`` | Indicates whether the element can be dragged +[`enterkeyhint`](https://html.spec.whatwg.org/multipage/interaction.html#attr-enterkeyhint) | ``submitLabel(_:)`` | Hints at the action label for the enter key on virtual keyboards +[`hidden`](https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden) | ``hidden(_:)`` | Sets the HTML hidden attribute (different from TailwindCSS hidden()) +`id` | ``id(_:)`` | Sets the element's unique identifier +[`inert`](https://html.spec.whatwg.org/multipage/interaction.html#the-inert-attribute) | ``allowsHitTesting(_:)`` | Controls whether the element can receive user interaction (inverted logic) +[`inputmode`](https://html.spec.whatwg.org/multipage/interaction.html#attr-inputmode) | ``keyboardType(_:)`` | Sets the keyboard type for text input +[`is`](https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is) | ``customElement(_:)`` | Specifies the name of a custom element +[`itemid`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemid) | ``itemID(_:)`` | The globally unique identifier of a microdata item +[`itemprop`](https://html.spec.whatwg.org/multipage/microdata.html#names:-the-itemprop-attribute) | ``itemProp(_:)`` | Used to add properties to a microdata item +[`itemref`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemref) | ``itemRef(_:)`` | Associates non-descendant properties with a microdata item +[`itemscope`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemscope) | ``itemScope(_:)`` | Creates a new microdata item +[`itemtype`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemtype) | ``itemType(_:)`` | Specifies the vocabulary URL for a microdata item +[`lang`](https://html.spec.whatwg.org/multipage/dom.html#attr-lang) | ``language(_:)`` | Sets the primary language for the view's contents +[`nonce`](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#attr-nonce) | ``nonce(_:)`` | A cryptographic nonce used by Content Security Policy +[`popover`](https://html.spec.whatwg.org/multipage/popover.html#attr-popover) | ``popover(_:)`` | Indicates that the element is a popover element +`slot` | ``slot(_:)`` | Assigns a slot in a shadow DOM shadow tree +[`spellcheck`](https://html.spec.whatwg.org/multipage/interaction.html#attr-spellcheck) | ``spellcheck(_:)`` | Specifies whether the element may be checked for spelling errors +[`style`](https://html.spec.whatwg.org/multipage/dom.html#attr-style) | ``inlineStyle(_:)`` | Contains CSS styling declarations. Prefer TailwindCSS modifiers when possible +[`tabindex`](https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex) | ``tabIndex(_:)`` or ``focusable()`` | Controls focus and keyboard navigation +[`title`](https://html.spec.whatwg.org/multipage/dom.html#attr-title) | ``tooltip(_:)`` | Advisory information, typically shown as a tooltip +[`translate`](https://html.spec.whatwg.org/multipage/dom.html#attr-translate) | ``translatable(_:)`` | Specifies whether content should be translated +[`writingsuggestions`](https://html.spec.whatwg.org/multipage/interaction.html#attr-writingsuggestions) | ``writingSuggestions(_:)`` | Controls whether browser-provided writing suggestions should be offered diff --git a/Sources/Slipstream/Fundamentals/AttributeModifier.swift b/Sources/Slipstream/Fundamentals/AttributeModifier.swift index 57d02915..206a11ea 100644 --- a/Sources/Slipstream/Fundamentals/AttributeModifier.swift +++ b/Sources/Slipstream/Fundamentals/AttributeModifier.swift @@ -25,10 +25,34 @@ public struct AttributeModifier: ViewModifier { /// A W3C global attribute, as defined in [3.2.3 Global attributes](https://html.spec.whatwg.org/multipage/dom.html#global-attributes). public enum GlobalAttribute: String { + /// Specifies a keyboard shortcut to activate or focus the element. + case accesskey + + /// Controls whether and how text input is automatically capitalized. + case autocapitalize + + /// Indicates that the element should be focused on page load. + case autofocus + /// The class attribute is most commonly used by stylesheets to apply styles /// to a view. case `class` + /// Indicates whether the element's content is editable. + case contenteditable + + /// Specifies the element's text directionality. + case dir + + /// Indicates whether the element can be dragged. + case draggable + + /// Hints at the type of data that might be entered by the user while editing the element or its contents. + case enterkeyhint + + /// Indicates that the element is not yet, or is no longer, relevant. + case hidden + /// The id attribute specifies its element's unique identifier (ID). /// /// There are no other restrictions on what form an ID can take; in particular, @@ -36,6 +60,30 @@ public struct AttributeModifier: ViewModifier { /// underscore, consist of just punctuation, etc. case id + /// Indicates that the element and its descendants should be made non-interactive. + case inert + + /// Hints at the type of data that might be entered by the user while editing the element. + case inputmode + + /// Specifies the name of a custom element. + case `is` + + /// The globally unique identifier of an item. + case itemid + + /// Used to add properties to an item. + case itemprop + + /// Properties that are not descendants of an element can be associated with the item using this attribute. + case itemref + + /// Creates a new item, a group of name-value pairs. + case itemscope + + /// Specifies the URL of the vocabulary that will be used to define item properties. + case itemtype + /// The lang attribute specifies the primary language for the view's contents /// and for any of the view's attributes that contain text. Its value must be a /// valid [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag), @@ -50,6 +98,33 @@ public struct AttributeModifier: ViewModifier { /// - [Common primary language tags](https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags) /// - [Language Subtag Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) case lang + + /// A cryptographic nonce used by Content Security Policy. + case nonce + + /// Indicates that the element is a popover element. + case popover + + /// Assigns a slot in a shadow DOM shadow tree to an element. + case slot + + /// Specifies whether the element may be checked for spelling errors. + case spellcheck + + /// Contains CSS styling declarations to be applied to the element. + case style + + /// Specifies whether the element can be focused and whether/where it participates in sequential keyboard navigation. + case tabindex + + /// Advisory information associated with the element. + case title + + /// Specifies whether an element's attribute values and text content should be translated. + case translate + + /// Controls whether browser-provided writing suggestions should be offered. + case writingsuggestions } /// Creates an attribute modifier that will set `attribute` to `value` on any modified views. diff --git a/Sources/Slipstream/W3C/Attributes/View+accesskey.swift b/Sources/Slipstream/W3C/Attributes/View+accesskey.swift new file mode 100644 index 00000000..d37e605d --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+accesskey.swift @@ -0,0 +1,23 @@ +extension View { + /// Specifies a keyboard shortcut to activate or focus the element. + /// + /// The accesskey attribute's value is used to generate a keyboard shortcut that + /// activates or focuses the element. The exact behavior depends on the browser and platform. + /// + /// ```swift + /// Button("Save") { + /// // Save action + /// } + /// .keyboardShortcut("s") + /// ``` + /// + /// - Parameter key: A single character that will be used as the keyboard shortcut. + /// The exact key combination varies by browser and platform (e.g., Alt+key on Windows, + /// Control+Option+key on macOS). + /// + /// - SeeAlso: W3C [`accesskey`](https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute) specification. + @available(iOS 17.0, macOS 14.0, *) + public func keyboardShortcut(_ key: String) -> some View { + return modifier(AttributeModifier(.accesskey, value: key)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+autocapitalize.swift b/Sources/Slipstream/W3C/Attributes/View+autocapitalize.swift new file mode 100644 index 00000000..d12dc1bb --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+autocapitalize.swift @@ -0,0 +1,32 @@ +/// The autocapitalize attribute controls whether and how text input is automatically capitalized. +@available(iOS 17.0, macOS 14.0, *) +public enum Autocapitalize: String { + /// No automatic capitalization. + case none + /// Capitalize the first letter of each sentence. + case sentences + /// Capitalize the first letter of each word. + case words + /// Capitalize all characters. + case characters +} + +extension View { + /// Controls whether and how text input is automatically capitalized. + /// + /// The autocapitalize attribute is particularly useful for mobile devices where the system + /// keyboard can automatically capitalize text based on the specified behavior. + /// + /// ```swift + /// TextField("Name", type: .text) + /// .autocapitalize(.words) + /// ``` + /// + /// - Parameter mode: The autocapitalization behavior to apply. + /// + /// - SeeAlso: W3C [`autocapitalize`](https://html.spec.whatwg.org/multipage/interaction.html#attr-autocapitalize) specification. + @available(iOS 17.0, macOS 14.0, *) + public func autocapitalize(_ mode: Autocapitalize) -> some View { + return modifier(AttributeModifier(.autocapitalize, value: mode.rawValue)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+autofocus.swift b/Sources/Slipstream/W3C/Attributes/View+autofocus.swift new file mode 100644 index 00000000..d739cd9f --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+autofocus.swift @@ -0,0 +1,20 @@ +extension View { + /// Indicates that the element should be focused when the page loads. + /// + /// Only one element in the document should have the autofocus attribute. If applied + /// to multiple elements, the first one in document order will receive focus. + /// + /// ```swift + /// TextField("Search", type: .search) + /// .autofocus() + /// ``` + /// + /// - Parameter condition: A Boolean value that determines whether the element should + /// receive focus on page load. Defaults to true. + /// + /// - SeeAlso: W3C [`autofocus`](https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus) specification. + @available(iOS 17.0, macOS 14.0, *) + public func autofocus(_ condition: Bool = true) -> some View { + return modifier(ConditionalAttributeModifier("autofocus", condition: condition)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+contenteditable.swift b/Sources/Slipstream/W3C/Attributes/View+contenteditable.swift new file mode 100644 index 00000000..a190a952 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+contenteditable.swift @@ -0,0 +1,40 @@ +/// The contenteditable attribute indicates whether the element's content is editable. +@available(iOS 17.0, macOS 14.0, *) +public enum Contenteditable: String { + /// The element is editable. + case `true` + /// The element is not editable. + case `false` + /// The element inherits the contenteditable state from its parent. + case inherit = "inherit" +} + +extension View { + /// Indicates whether the element's content is editable. + /// + /// The contenteditable attribute makes an element's content editable by the user. + /// This is commonly used to create rich text editors and inline editing experiences. + /// + /// ```swift + /// Div { + /// Text("Edit this text") + /// } + /// .contenteditable(.true) + /// ``` + /// + /// - Parameter value: The editability state of the element. + /// + /// - SeeAlso: W3C [`contenteditable`](https://html.spec.whatwg.org/multipage/interaction.html#attr-contenteditable) specification. + @available(iOS 17.0, macOS 14.0, *) + public func contenteditable(_ value: Contenteditable) -> some View { + return modifier(AttributeModifier(.contenteditable, value: value.rawValue)) + } + + /// Indicates whether the element's content is editable. + /// + /// - Parameter condition: A Boolean value that determines whether the element is editable. + @available(iOS 17.0, macOS 14.0, *) + public func contenteditable(_ condition: Bool) -> some View { + return modifier(AttributeModifier(.contenteditable, value: condition ? "true" : "false")) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+dir.swift b/Sources/Slipstream/W3C/Attributes/View+dir.swift new file mode 100644 index 00000000..3e7a1b65 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+dir.swift @@ -0,0 +1,33 @@ +/// The dir attribute specifies the element's text directionality. +@available(iOS 17.0, macOS 14.0, *) +public enum TextDirection: String { + /// Left-to-right text direction. + case ltr + /// Right-to-left text direction. + case rtl + /// Automatic text direction based on content. + case auto +} + +extension View { + /// Specifies the element's text directionality. + /// + /// The dir attribute controls the direction in which text is rendered. This is + /// particularly important for languages like Arabic and Hebrew that are written + /// right-to-left. + /// + /// ```swift + /// Div { + /// Text("مرحبا بك") + /// } + /// .direction(.rtl) + /// ``` + /// + /// - Parameter direction: The text direction for the element. + /// + /// - SeeAlso: W3C [`dir`](https://html.spec.whatwg.org/multipage/dom.html#attr-dir) specification. + @available(iOS 17.0, macOS 14.0, *) + public func direction(_ direction: TextDirection) -> some View { + return modifier(AttributeModifier(.dir, value: direction.rawValue)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+draggable.swift b/Sources/Slipstream/W3C/Attributes/View+draggable.swift new file mode 100644 index 00000000..615b78ce --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+draggable.swift @@ -0,0 +1,23 @@ +extension View { + /// Indicates whether the element can be dragged. + /// + /// The draggable attribute controls whether an element can be dragged using the + /// HTML Drag and Drop API. This is commonly used for implementing drag-and-drop + /// interfaces. + /// + /// ```swift + /// Div { + /// Text("Drag me") + /// } + /// .draggable() + /// ``` + /// + /// - Parameter condition: A Boolean value that determines whether the element can be dragged. + /// Defaults to true. + /// + /// - SeeAlso: W3C [`draggable`](https://html.spec.whatwg.org/multipage/dnd.html#attr-draggable) specification. + @available(iOS 17.0, macOS 14.0, *) + public func draggable(_ condition: Bool = true) -> some View { + return modifier(AttributeModifier(.draggable, value: condition ? "true" : "false")) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+enterkeyhint.swift b/Sources/Slipstream/W3C/Attributes/View+enterkeyhint.swift new file mode 100644 index 00000000..6bdbfadc --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+enterkeyhint.swift @@ -0,0 +1,45 @@ +/// The enterkeyhint attribute hints at the action label or icon for the enter key +/// on virtual keyboards. +@available(iOS 17.0, macOS 14.0, *) +public enum EnterKeyHint: String { + /// The user agent should present a cue for the action 'enter', typically inserting a new line. + case enter + /// The user agent should present a cue for the action 'done', typically meaning there is + /// nothing more to input and the input method editor (IME) will be closed. + case done + /// The user agent should present a cue for the action 'go', typically meaning to take the + /// user to the target of the text they typed. + case go + /// The user agent should present a cue for the action 'next', typically taking the user to + /// the next field that will accept text. + case next + /// The user agent should present a cue for the action 'previous', typically taking the user + /// to the previous field that will accept text. + case previous + /// The user agent should present a cue for the action 'search', typically taking the user + /// to the results of searching for the text they have typed. + case search + /// The user agent should present a cue for the action 'send', typically delivering the text + /// to its target. + case send +} + +extension View { + /// Hints at the action label or icon to present for the enter key on virtual keyboards. + /// + /// The submitLabel modifier is particularly useful on mobile devices where the virtual + /// keyboard can display different labels on the enter key based on the expected action. + /// + /// ```swift + /// TextField("Search", type: .search) + /// .submitLabel(.search) + /// ``` + /// + /// - Parameter label: The label for the enter key presentation. + /// + /// - SeeAlso: W3C [`enterkeyhint`](https://html.spec.whatwg.org/multipage/interaction.html#attr-enterkeyhint) specification. + @available(iOS 17.0, macOS 14.0, *) + public func submitLabel(_ label: EnterKeyHint) -> some View { + return modifier(AttributeModifier(.enterkeyhint, value: label.rawValue)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+hidden.swift b/Sources/Slipstream/W3C/Attributes/View+hidden.swift new file mode 100644 index 00000000..a7d9ea13 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+hidden.swift @@ -0,0 +1,32 @@ +/// The hidden attribute indicates that the element is not yet, or is no longer, relevant. +@available(iOS 17.0, macOS 14.0, *) +public enum HiddenState: String { + /// The element is hidden in all rendering modes. + case hidden = "" + /// The element is hidden but remains in the accessibility tree until it is found. + case untilFound = "until-found" +} + +extension View { + /// Indicates that the element is not yet, or is no longer, relevant. + /// + /// Sets the HTML `hidden` attribute to semantically mark content as not relevant. + /// This is different from the TailwindCSS `hidden()` modifier which uses CSS to + /// visually hide elements. + /// + /// ```swift + /// Div { + /// Text("This content is hidden") + /// } + /// .hidden(.hidden) + /// ``` + /// + /// - Parameter state: The hidden state of the element. + /// + /// - SeeAlso: W3C [`hidden`](https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden) specification. + @available(iOS 17.0, macOS 14.0, *) + public func hidden(_ state: HiddenState) -> some View { + return modifier(AttributeModifier(.hidden, value: state.rawValue)) + } +} + diff --git a/Sources/Slipstream/W3C/Attributes/View+inert.swift b/Sources/Slipstream/W3C/Attributes/View+inert.swift new file mode 100644 index 00000000..090cbabf --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+inert.swift @@ -0,0 +1,22 @@ +extension View { + /// Controls whether the element and its descendants can receive user interaction. + /// + /// When set to `false`, the element is made inert and cannot be interacted with by the user. + /// The element is also removed from the accessibility tree. + /// + /// ```swift + /// Div { + /// Button("Click me") { } + /// } + /// .allowsHitTesting(false) + /// ``` + /// + /// - Parameter enabled: A Boolean value that determines whether the element can receive + /// user interaction. When `false`, the HTML `inert` attribute is set. Defaults to true. + /// + /// - SeeAlso: W3C [`inert`](https://html.spec.whatwg.org/multipage/interaction.html#the-inert-attribute) specification. + @available(iOS 17.0, macOS 14.0, *) + public func allowsHitTesting(_ enabled: Bool = true) -> some View { + return modifier(ConditionalAttributeModifier("inert", condition: !enabled)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+inputmode.swift b/Sources/Slipstream/W3C/Attributes/View+inputmode.swift new file mode 100644 index 00000000..5018022e --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+inputmode.swift @@ -0,0 +1,42 @@ +/// The keyboard type to display for text input. +@available(iOS 17.0, macOS 14.0, *) +public enum KeyboardType: String { + /// No virtual keyboard. For when the page implements its own keyboard input control. + case none + /// Standard input keyboard for the user's current locale. + case text + /// Fractional numeric input keyboard containing the digits and decimal separator for the + /// user's locale (typically . or ,). Devices may or may not show a minus key (-). + case decimal + /// Numeric input keyboard, but only requires the digits 0-9. Devices may or may not show + /// a minus key. + case numeric + /// A telephone keypad input, including the digits 0-9, the asterisk (*), and the pound (#) key. + case tel + /// A virtual keyboard optimized for search input. + case search + /// A virtual keyboard optimized for entering email addresses. + case email + /// A keypad optimized for entering URLs. + case url +} + +extension View { + /// Sets the keyboard type for text input. + /// + /// The keyboardType modifier is particularly useful on mobile devices where the virtual + /// keyboard can be customized based on the expected input type. + /// + /// ```swift + /// TextField("Amount", type: .text) + /// .keyboardType(.decimal) + /// ``` + /// + /// - Parameter type: The keyboard type to display. + /// + /// - SeeAlso: W3C [`inputmode`](https://html.spec.whatwg.org/multipage/interaction.html#attr-inputmode) specification. + @available(iOS 17.0, macOS 14.0, *) + public func keyboardType(_ type: KeyboardType) -> some View { + return modifier(AttributeModifier(.inputmode, value: type.rawValue)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+is.swift b/Sources/Slipstream/W3C/Attributes/View+is.swift new file mode 100644 index 00000000..de7f05bd --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+is.swift @@ -0,0 +1,20 @@ +extension View { + /// Specifies that a custom element should be an instance of the specified custom element type. + /// + /// The is attribute is used with customized built-in elements to specify the name of the + /// custom element class that the element should be treated as. This allows you to extend + /// standard HTML elements with custom behavior. + /// + /// ```swift + /// Button("Click") { } + /// .customElement("expanding-button") + /// ``` + /// + /// - Parameter name: The name of the custom element class. + /// + /// - SeeAlso: W3C [`is`](https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is) specification. + @available(iOS 17.0, macOS 14.0, *) + public func customElement(_ name: String) -> some View { + return modifier(AttributeModifier(.is, value: name)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+microdata.swift b/Sources/Slipstream/W3C/Attributes/View+microdata.swift new file mode 100644 index 00000000..b87c193e --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+microdata.swift @@ -0,0 +1,101 @@ +extension View { + /// Creates a new item, a group of name-value pairs. + /// + /// The itemscope attribute is used to create an item. When an element has an itemscope + /// attribute, it and its descendants form a new item with associated name-value pairs. + /// + /// ```swift + /// Div { + /// Span("John Doe") + /// .itemProp("name") + /// } + /// .itemScope() + /// .itemType("https://schema.org/Person") + /// ``` + /// + /// - Parameter condition: A Boolean value that determines whether the element creates an item scope. + /// Defaults to true. + /// + /// - SeeAlso: W3C [`itemscope`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemscope) specification. + @available(iOS 17.0, macOS 14.0, *) + public func itemScope(_ condition: Bool = true) -> some View { + return modifier(ConditionalAttributeModifier("itemscope", condition: condition)) + } + + /// Specifies the URL of the vocabulary that will be used to define item properties. + /// + /// The itemtype attribute specifies the item type, typically a URL to a vocabulary like + /// schema.org that defines the properties of the item. + /// + /// ```swift + /// Div { } + /// .itemScope() + /// .itemType("https://schema.org/Person") + /// ``` + /// + /// - Parameter type: The URL of the vocabulary defining the item type. + /// + /// - SeeAlso: W3C [`itemtype`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemtype) specification. + @available(iOS 17.0, macOS 14.0, *) + public func itemType(_ type: String) -> some View { + return modifier(AttributeModifier(.itemtype, value: type)) + } + + /// The globally unique identifier of an item. + /// + /// The itemid attribute is used to specify the globally unique identifier of an item. + /// The identifier must be a valid URL. + /// + /// ```swift + /// Div { } + /// .itemScope() + /// .itemType("https://schema.org/Book") + /// .itemID("urn:isbn:978-0-596-52068-7") + /// ``` + /// + /// - Parameter id: The globally unique identifier for the item. + /// + /// - SeeAlso: W3C [`itemid`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemid) specification. + @available(iOS 17.0, macOS 14.0, *) + public func itemID(_ id: String) -> some View { + return modifier(AttributeModifier(.itemid, value: id)) + } + + /// Used to add properties to an item. + /// + /// The itemprop attribute is used to add a property to an item. Each element with an + /// itemprop attribute adds a name-value pair to its item. + /// + /// ```swift + /// Span("John Doe") + /// .itemProp("name") + /// ``` + /// + /// - Parameter property: The name of the property being added to the item. + /// + /// - SeeAlso: W3C [`itemprop`](https://html.spec.whatwg.org/multipage/microdata.html#names:-the-itemprop-attribute) specification. + @available(iOS 17.0, macOS 14.0, *) + public func itemProp(_ property: String) -> some View { + return modifier(AttributeModifier(.itemprop, value: property)) + } + + /// Properties that are not descendants of an element can be associated with the item. + /// + /// The itemref attribute allows properties that are not descendants of an element with + /// an itemscope attribute to be associated with the item. It provides a list of element + /// IDs (not itemids) with additional properties elsewhere in the document. + /// + /// ```swift + /// Div { } + /// .itemScope() + /// .itemRef("prop1 prop2") + /// ``` + /// + /// - Parameter refs: A space-separated list of element IDs to reference. + /// + /// - SeeAlso: W3C [`itemref`](https://html.spec.whatwg.org/multipage/microdata.html#attr-itemref) specification. + @available(iOS 17.0, macOS 14.0, *) + public func itemRef(_ refs: String) -> some View { + return modifier(AttributeModifier(.itemref, value: refs)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+nonce.swift b/Sources/Slipstream/W3C/Attributes/View+nonce.swift new file mode 100644 index 00000000..35a62a8f --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+nonce.swift @@ -0,0 +1,22 @@ +extension View { + /// A cryptographic nonce ("number used once") used by Content Security Policy. + /// + /// The nonce attribute represents a cryptographic nonce ("number used once") which can + /// be used by Content Security Policy to determine whether or not a given fetch will be + /// allowed to proceed. The value is text. + /// + /// ```swift + /// Script(""" + /// console.log("Hello"); + /// """) + /// .nonce("randomNonceValue123") + /// ``` + /// + /// - Parameter value: A cryptographic nonce value. + /// + /// - SeeAlso: W3C [`nonce`](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#attr-nonce) specification. + @available(iOS 17.0, macOS 14.0, *) + public func nonce(_ value: String) -> some View { + return modifier(AttributeModifier(.nonce, value: value)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+popover.swift b/Sources/Slipstream/W3C/Attributes/View+popover.swift new file mode 100644 index 00000000..6e353866 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+popover.swift @@ -0,0 +1,45 @@ +/// The popover attribute indicates that the element is a popover element. +@available(iOS 17.0, macOS 14.0, *) +public enum PopoverState: String { + /// The element is a manual popover that must be explicitly shown and hidden. + case manual + /// The element is an auto popover that can be "light dismissed" by user actions. + case auto +} + +extension View { + /// Indicates that the element is a popover element. + /// + /// The popover attribute makes an element into a popover element, which can be shown + /// and hidden using JavaScript or declarative triggers. Popovers are displayed on top + /// of other page content. + /// + /// ```swift + /// Div { + /// Text("Popover content") + /// } + /// .popover(.auto) + /// .id("my-popover") + /// ``` + /// + /// - Parameter state: The popover state determining its dismissal behavior. + /// + /// - SeeAlso: W3C [`popover`](https://html.spec.whatwg.org/multipage/popover.html#attr-popover) specification. + @available(iOS 17.0, macOS 14.0, *) + public func popover(_ state: PopoverState) -> some View { + return modifier(AttributeModifier(.popover, value: state.rawValue)) + } + + /// Indicates that the element is an auto popover element. + /// + /// - Parameter condition: A Boolean value that determines whether the element is a popover. + /// Defaults to true. + @available(iOS 17.0, macOS 14.0, *) + public func popover(_ condition: Bool = true) -> some View { + if condition { + return modifier(AttributeModifier(.popover, value: "auto")) + } else { + return modifier(AttributeModifier([:])) + } + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+slot.swift b/Sources/Slipstream/W3C/Attributes/View+slot.swift new file mode 100644 index 00000000..29bc3384 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+slot.swift @@ -0,0 +1,20 @@ +extension View { + /// Assigns a slot in a shadow DOM shadow tree to an element. + /// + /// The slot attribute assigns a slot in a shadow DOM shadow tree to an element. An element + /// with a slot attribute is assigned to the slot created by the element whose name + /// attribute's value matches that slot attribute's value. + /// + /// ```swift + /// Span("Header content") + /// .slot("header") + /// ``` + /// + /// - Parameter name: The name of the slot to assign the element to. + /// + /// - SeeAlso: W3C [`slot`](https://html.spec.whatwg.org/multipage/scripting.html#attr-slot) specification. + @available(iOS 17.0, macOS 14.0, *) + public func slot(_ name: String) -> some View { + return modifier(AttributeModifier(.slot, value: name)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+spellcheck.swift b/Sources/Slipstream/W3C/Attributes/View+spellcheck.swift new file mode 100644 index 00000000..b0c0bff3 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+spellcheck.swift @@ -0,0 +1,19 @@ +extension View { + /// Specifies whether the element may be checked for spelling errors. + /// + /// The spellcheck attribute is an enumerated attribute that defines whether the element + /// may be checked for spelling errors. It can have true or false values. + /// + /// ```swift + /// TextField("Comment", type: .text) + /// .spellcheck(true) + /// ``` + /// + /// - Parameter enabled: A Boolean value that determines whether spell checking is enabled. + /// + /// - SeeAlso: W3C [`spellcheck`](https://html.spec.whatwg.org/multipage/interaction.html#attr-spellcheck) specification. + @available(iOS 17.0, macOS 14.0, *) + public func spellcheck(_ enabled: Bool) -> some View { + return modifier(AttributeModifier(.spellcheck, value: enabled ? "true" : "false")) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+style.swift b/Sources/Slipstream/W3C/Attributes/View+style.swift new file mode 100644 index 00000000..b47e6076 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+style.swift @@ -0,0 +1,21 @@ +extension View { + /// Contains CSS styling declarations to be applied to the element. + /// + /// The style attribute contains CSS declarations that are applied to the element. + /// This allows for inline styling of elements. + /// + /// ```swift + /// Div { + /// Text("Styled text") + /// } + /// .inlineStyle("color: red; font-weight: bold;") + /// ``` + /// + /// - Parameter css: A string containing CSS declarations. + /// + /// - SeeAlso: W3C [`style`](https://html.spec.whatwg.org/multipage/dom.html#attr-style) specification. + @available(iOS 17.0, macOS 14.0, *) + public func inlineStyle(_ css: String) -> some View { + return modifier(AttributeModifier(.style, value: css)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+tabindex.swift b/Sources/Slipstream/W3C/Attributes/View+tabindex.swift new file mode 100644 index 00000000..1afd179b --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+tabindex.swift @@ -0,0 +1,34 @@ +extension View { + /// Specifies whether the element can be focused and where it participates in sequential keyboard navigation. + /// + /// The tabindex attribute allows authors to control whether an element can be focused, + /// whether it is supposed to be reachable using sequential focus navigation, and what + /// is to be the relative order of the element for the purposes of sequential focus navigation. + /// + /// ```swift + /// Div { + /// Text("Focusable content") + /// } + /// .focusable() + /// ``` + /// + /// - Parameter index: The tab index value. A negative value (typically -1) means the element + /// can be focused programmatically but not via sequential keyboard navigation. A value of 0 + /// means the element can be focused and is reachable via sequential keyboard navigation, + /// in the order defined by the document. A positive value means the element can be focused + /// and is reachable via sequential keyboard navigation, with its order defined by the value. + /// + /// - SeeAlso: W3C [`tabindex`](https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex) specification. + @available(iOS 17.0, macOS 14.0, *) + public func tabIndex(_ index: Int) -> some View { + return modifier(AttributeModifier(.tabindex, value: String(index))) + } + + /// Makes the element focusable but not reachable via sequential keyboard navigation. + /// + /// This is a convenience method that sets tabindex to -1. + @available(iOS 17.0, macOS 14.0, *) + public func focusable() -> some View { + return modifier(AttributeModifier(.tabindex, value: "-1")) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+title.swift b/Sources/Slipstream/W3C/Attributes/View+title.swift new file mode 100644 index 00000000..305a751d --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+title.swift @@ -0,0 +1,19 @@ +extension View { + /// Advisory information associated with the element. + /// + /// The title attribute represents advisory information for the element, such as would be + /// appropriate for a tooltip. It can be used on any HTML element. + /// + /// ```swift + /// Button("Save") { } + /// .tooltip("Save your changes") + /// ``` + /// + /// - Parameter text: The advisory information text. + /// + /// - SeeAlso: W3C [`title`](https://html.spec.whatwg.org/multipage/dom.html#attr-title) specification. + @available(iOS 17.0, macOS 14.0, *) + public func tooltip(_ text: String) -> some View { + return modifier(AttributeModifier(.title, value: text)) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+translate.swift b/Sources/Slipstream/W3C/Attributes/View+translate.swift new file mode 100644 index 00000000..c70b60d0 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+translate.swift @@ -0,0 +1,41 @@ +/// The translate attribute specifies whether an element's attribute values and text content +/// should be translated when the page is localized. +@available(iOS 17.0, macOS 14.0, *) +public enum TranslateMode: String { + /// The element should be translated. + case yes + /// The element should not be translated. + case no +} + +extension View { + /// Specifies whether an element's attribute values and text content should be translated. + /// + /// The translate attribute is used to specify whether the element's translatable attributes + /// and its Text node children should be translated when the page is localized, or whether + /// to leave them unchanged. + /// + /// ```swift + /// Span("Hello") + /// .translatable(.yes) + /// + /// Span("API") + /// .translatable(.no) + /// ``` + /// + /// - Parameter mode: Whether the element should be translated. + /// + /// - SeeAlso: W3C [`translate`](https://html.spec.whatwg.org/multipage/dom.html#attr-translate) specification. + @available(iOS 17.0, macOS 14.0, *) + public func translatable(_ mode: TranslateMode) -> some View { + return modifier(AttributeModifier(.translate, value: mode.rawValue)) + } + + /// Specifies whether an element's attribute values and text content should be translated. + /// + /// - Parameter condition: A Boolean value that determines whether the element should be translated. + @available(iOS 17.0, macOS 14.0, *) + public func translatable(_ condition: Bool) -> some View { + return modifier(AttributeModifier(.translate, value: condition ? "yes" : "no")) + } +} diff --git a/Sources/Slipstream/W3C/Attributes/View+writingsuggestions.swift b/Sources/Slipstream/W3C/Attributes/View+writingsuggestions.swift new file mode 100644 index 00000000..b09d9660 --- /dev/null +++ b/Sources/Slipstream/W3C/Attributes/View+writingsuggestions.swift @@ -0,0 +1,19 @@ +extension View { + /// Controls whether browser-provided writing suggestions should be offered to the user. + /// + /// The writingsuggestions attribute is used to control whether the browser should provide + /// writing suggestions (such as grammar or spelling corrections) for editable content. + /// + /// ```swift + /// TextField("Email", type: .email) + /// .writingSuggestions(false) + /// ``` + /// + /// - Parameter enabled: A Boolean value that determines whether writing suggestions should be offered. + /// + /// - SeeAlso: W3C [`writingsuggestions`](https://html.spec.whatwg.org/multipage/interaction.html#attr-writingsuggestions) specification. + @available(iOS 17.0, macOS 14.0, *) + public func writingSuggestions(_ enabled: Bool) -> some View { + return modifier(AttributeModifier(.writingsuggestions, value: enabled ? "true" : "false")) + } +} diff --git a/Tests/SlipstreamTests/Attributes/AllowsHitTestingTests.swift b/Tests/SlipstreamTests/Attributes/AllowsHitTestingTests.swift new file mode 100644 index 00000000..9febfaff --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/AllowsHitTestingTests.swift @@ -0,0 +1,17 @@ +import Testing + +import Slipstream + +struct AllowsHitTestingTests { + @Test func withDefaultTrue() throws { + try #expect(renderHTML(Div {}.allowsHitTesting()) == #"
"#) + } + + @Test func withExplicitTrue() throws { + try #expect(renderHTML(Div {}.allowsHitTesting(true)) == #"
"#) + } + + @Test func withFalse() throws { + try #expect(renderHTML(Div {}.allowsHitTesting(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/AutocapitalizeTests.swift b/Tests/SlipstreamTests/Attributes/AutocapitalizeTests.swift new file mode 100644 index 00000000..eec9d8d7 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/AutocapitalizeTests.swift @@ -0,0 +1,21 @@ +import Testing + +import Slipstream + +struct AutocapitalizeTests { + @Test func withNone() throws { + try #expect(renderHTML(Div {}.autocapitalize(.none)) == #"
"#) + } + + @Test func withSentences() throws { + try #expect(renderHTML(Div {}.autocapitalize(.sentences)) == #"
"#) + } + + @Test func withWords() throws { + try #expect(renderHTML(Div {}.autocapitalize(.words)) == #"
"#) + } + + @Test func withCharacters() throws { + try #expect(renderHTML(Div {}.autocapitalize(.characters)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/AutofocusTests.swift b/Tests/SlipstreamTests/Attributes/AutofocusTests.swift new file mode 100644 index 00000000..9c639086 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/AutofocusTests.swift @@ -0,0 +1,17 @@ +import Testing + +import Slipstream + +struct AutofocusTests { + @Test func withDefaultTrue() throws { + try #expect(renderHTML(Div {}.autofocus()) == #"
"#) + } + + @Test func withExplicitTrue() throws { + try #expect(renderHTML(Div {}.autofocus(true)) == #"
"#) + } + + @Test func withFalse() throws { + try #expect(renderHTML(Div {}.autofocus(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/ContenteditableTests.swift b/Tests/SlipstreamTests/Attributes/ContenteditableTests.swift new file mode 100644 index 00000000..d1d45bfd --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/ContenteditableTests.swift @@ -0,0 +1,25 @@ +import Testing + +import Slipstream + +struct ContenteditableTests { + @Test func withEnumTrue() throws { + try #expect(renderHTML(Div {}.contenteditable(.true)) == #"
"#) + } + + @Test func withEnumFalse() throws { + try #expect(renderHTML(Div {}.contenteditable(.false)) == #"
"#) + } + + @Test func withEnumInherit() throws { + try #expect(renderHTML(Div {}.contenteditable(.inherit)) == #"
"#) + } + + @Test func withBoolTrue() throws { + try #expect(renderHTML(Div {}.contenteditable(true)) == #"
"#) + } + + @Test func withBoolFalse() throws { + try #expect(renderHTML(Div {}.contenteditable(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/CustomElementTests.swift b/Tests/SlipstreamTests/Attributes/CustomElementTests.swift new file mode 100644 index 00000000..3532bc94 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/CustomElementTests.swift @@ -0,0 +1,15 @@ +import Testing + +import Slipstream + +struct CustomElementTests { + @Test func withCustomElement() throws { + try #expect(renderHTML(Button {}.customElement("expanding-button")) == #""#) + } + + @Test func withContent() throws { + try #expect(renderHTML(Button { + DOMString("Click me") + }.customElement("expanding-button")) == #""#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/DirectionTests.swift b/Tests/SlipstreamTests/Attributes/DirectionTests.swift new file mode 100644 index 00000000..3669fd36 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/DirectionTests.swift @@ -0,0 +1,17 @@ +import Testing + +import Slipstream + +struct DirectionTests { + @Test func withLTR() throws { + try #expect(renderHTML(Div {}.direction(.ltr)) == #"
"#) + } + + @Test func withRTL() throws { + try #expect(renderHTML(Div {}.direction(.rtl)) == #"
"#) + } + + @Test func withAuto() throws { + try #expect(renderHTML(Div {}.direction(.auto)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/DraggableTests.swift b/Tests/SlipstreamTests/Attributes/DraggableTests.swift new file mode 100644 index 00000000..3abe7775 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/DraggableTests.swift @@ -0,0 +1,17 @@ +import Testing + +import Slipstream + +struct DraggableTests { + @Test func withDefaultTrue() throws { + try #expect(renderHTML(Div {}.draggable()) == #"
"#) + } + + @Test func withExplicitTrue() throws { + try #expect(renderHTML(Div {}.draggable(true)) == #"
"#) + } + + @Test func withFalse() throws { + try #expect(renderHTML(Div {}.draggable(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/HiddenTests.swift b/Tests/SlipstreamTests/Attributes/HiddenTests.swift new file mode 100644 index 00000000..e426d624 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/HiddenTests.swift @@ -0,0 +1,13 @@ +import Testing + +import Slipstream + +struct HiddenTests { + @Test func withHiddenState() throws { + try #expect(renderHTML(Div {}.hidden(.hidden)) == #""#) + } + + @Test func withUntilFoundState() throws { + try #expect(renderHTML(Div {}.hidden(.untilFound)) == #""#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/InlineStyleTests.swift b/Tests/SlipstreamTests/Attributes/InlineStyleTests.swift new file mode 100644 index 00000000..1ed0d644 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/InlineStyleTests.swift @@ -0,0 +1,13 @@ +import Testing + +import Slipstream + +struct InlineStyleTests { + @Test func withSingleProperty() throws { + try #expect(renderHTML(Div {}.inlineStyle("color: red;")) == #"
"#) + } + + @Test func withMultipleProperties() throws { + try #expect(renderHTML(Div {}.inlineStyle("color: red; font-weight: bold;")) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/KeyboardShortcutTests.swift b/Tests/SlipstreamTests/Attributes/KeyboardShortcutTests.swift new file mode 100644 index 00000000..1ae0bf1a --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/KeyboardShortcutTests.swift @@ -0,0 +1,15 @@ +import Testing + +import Slipstream + +struct KeyboardShortcutTests { + @Test func withSingleKey() throws { + try #expect(renderHTML(Div {}.keyboardShortcut("s")) == #"
"#) + } + + @Test func onButton() throws { + try #expect(renderHTML(Button { + DOMString("Save") + }.keyboardShortcut("s")) == #""#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/KeyboardTypeTests.swift b/Tests/SlipstreamTests/Attributes/KeyboardTypeTests.swift new file mode 100644 index 00000000..2b6d4225 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/KeyboardTypeTests.swift @@ -0,0 +1,37 @@ +import Testing + +import Slipstream + +struct KeyboardTypeTests { + @Test func withNone() throws { + try #expect(renderHTML(Div {}.keyboardType(.none)) == #"
"#) + } + + @Test func withText() throws { + try #expect(renderHTML(Div {}.keyboardType(.text)) == #"
"#) + } + + @Test func withDecimal() throws { + try #expect(renderHTML(Div {}.keyboardType(.decimal)) == #"
"#) + } + + @Test func withNumeric() throws { + try #expect(renderHTML(Div {}.keyboardType(.numeric)) == #"
"#) + } + + @Test func withTel() throws { + try #expect(renderHTML(Div {}.keyboardType(.tel)) == #"
"#) + } + + @Test func withSearch() throws { + try #expect(renderHTML(Div {}.keyboardType(.search)) == #"
"#) + } + + @Test func withEmail() throws { + try #expect(renderHTML(Div {}.keyboardType(.email)) == #"
"#) + } + + @Test func withURL() throws { + try #expect(renderHTML(Div {}.keyboardType(.url)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/MicrodataTests.swift b/Tests/SlipstreamTests/Attributes/MicrodataTests.swift new file mode 100644 index 00000000..5a5db8e6 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/MicrodataTests.swift @@ -0,0 +1,50 @@ +import Testing + +import Slipstream + +struct MicrodataTests { + @Test func itemScope() throws { + try #expect(renderHTML(Div {}.itemScope()) == #"
"#) + } + + @Test func itemScopeExplicitTrue() throws { + try #expect(renderHTML(Div {}.itemScope(true)) == #"
"#) + } + + @Test func itemScopeFalse() throws { + try #expect(renderHTML(Div {}.itemScope(false)) == #"
"#) + } + + @Test func itemType() throws { + try #expect(renderHTML(Div {}.itemType("https://schema.org/Person")) == #"
"#) + } + + @Test func itemID() throws { + try #expect(renderHTML(Div {}.itemID("urn:isbn:978-0-596-52068-7")) == #"
"#) + } + + @Test func itemProp() throws { + try #expect(renderHTML(Span { + DOMString("John Doe") + }.itemProp("name")) == #"John Doe"#) + } + + @Test func itemRef() throws { + try #expect(renderHTML(Div {}.itemRef("prop1 prop2")) == #"
"#) + } + + @Test func combinedMicrodata() throws { + try #expect(renderHTML(Div { + Span { + DOMString("John Doe") + } + .itemProp("name") + } + .itemScope() + .itemType("https://schema.org/Person")) == """ +
+ John Doe +
+""") + } +} diff --git a/Tests/SlipstreamTests/Attributes/NonceTests.swift b/Tests/SlipstreamTests/Attributes/NonceTests.swift new file mode 100644 index 00000000..069301bc --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/NonceTests.swift @@ -0,0 +1,13 @@ +import Testing + +import Slipstream + +struct NonceTests { + @Test func withNonce() throws { + try #expect(renderHTML(Script("console.log('test');").nonce("abc123")) == #""#) + } + + @Test func onDiv() throws { + try #expect(renderHTML(Div {}.nonce("abc123")) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/PopoverTests.swift b/Tests/SlipstreamTests/Attributes/PopoverTests.swift new file mode 100644 index 00000000..39987b33 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/PopoverTests.swift @@ -0,0 +1,25 @@ +import Testing + +import Slipstream + +struct PopoverTests { + @Test func withManualState() throws { + try #expect(renderHTML(Div {}.popover(.manual)) == #"
"#) + } + + @Test func withAutoState() throws { + try #expect(renderHTML(Div {}.popover(.auto)) == #"
"#) + } + + @Test func withDefaultTrue() throws { + try #expect(renderHTML(Div {}.popover()) == #"
"#) + } + + @Test func withExplicitTrue() throws { + try #expect(renderHTML(Div {}.popover(true)) == #"
"#) + } + + @Test func withFalse() throws { + try #expect(renderHTML(Div {}.popover(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/SlotAttributeTests.swift b/Tests/SlipstreamTests/Attributes/SlotAttributeTests.swift new file mode 100644 index 00000000..c08e29c1 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/SlotAttributeTests.swift @@ -0,0 +1,15 @@ +import Testing + +import Slipstream + +struct SlotAttributeTests { + @Test func withSlotName() throws { + try #expect(renderHTML(Span {}.slot("header")) == #""#) + } + + @Test func withContent() throws { + try #expect(renderHTML(Span { + DOMString("Header content") + }.slot("header")) == #"Header content"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/SpellcheckTests.swift b/Tests/SlipstreamTests/Attributes/SpellcheckTests.swift new file mode 100644 index 00000000..7f47bbf5 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/SpellcheckTests.swift @@ -0,0 +1,13 @@ +import Testing + +import Slipstream + +struct SpellcheckTests { + @Test func withTrue() throws { + try #expect(renderHTML(Div {}.spellcheck(true)) == #"
"#) + } + + @Test func withFalse() throws { + try #expect(renderHTML(Div {}.spellcheck(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/SubmitLabelTests.swift b/Tests/SlipstreamTests/Attributes/SubmitLabelTests.swift new file mode 100644 index 00000000..dc4868bf --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/SubmitLabelTests.swift @@ -0,0 +1,33 @@ +import Testing + +import Slipstream + +struct SubmitLabelTests { + @Test func withEnter() throws { + try #expect(renderHTML(Div {}.submitLabel(.enter)) == #"
"#) + } + + @Test func withDone() throws { + try #expect(renderHTML(Div {}.submitLabel(.done)) == #"
"#) + } + + @Test func withGo() throws { + try #expect(renderHTML(Div {}.submitLabel(.go)) == #"
"#) + } + + @Test func withNext() throws { + try #expect(renderHTML(Div {}.submitLabel(.next)) == #"
"#) + } + + @Test func withPrevious() throws { + try #expect(renderHTML(Div {}.submitLabel(.previous)) == #"
"#) + } + + @Test func withSearch() throws { + try #expect(renderHTML(Div {}.submitLabel(.search)) == #"
"#) + } + + @Test func withSend() throws { + try #expect(renderHTML(Div {}.submitLabel(.send)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/TabIndexTests.swift b/Tests/SlipstreamTests/Attributes/TabIndexTests.swift new file mode 100644 index 00000000..c31dd119 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/TabIndexTests.swift @@ -0,0 +1,21 @@ +import Testing + +import Slipstream + +struct TabIndexTests { + @Test func withZero() throws { + try #expect(renderHTML(Div {}.tabIndex(0)) == #"
"#) + } + + @Test func withNegativeOne() throws { + try #expect(renderHTML(Div {}.tabIndex(-1)) == #"
"#) + } + + @Test func withPositive() throws { + try #expect(renderHTML(Div {}.tabIndex(1)) == #"
"#) + } + + @Test func focusable() throws { + try #expect(renderHTML(Div {}.focusable()) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/TooltipTests.swift b/Tests/SlipstreamTests/Attributes/TooltipTests.swift new file mode 100644 index 00000000..fbaac80c --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/TooltipTests.swift @@ -0,0 +1,15 @@ +import Testing + +import Slipstream + +struct TooltipTests { + @Test func withSimpleText() throws { + try #expect(renderHTML(Div {}.tooltip("Click me")) == #"
"#) + } + + @Test func onButton() throws { + try #expect(renderHTML(Button { + DOMString("Submit") + }.tooltip("Submit the form")) == #""#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/TranslatableTests.swift b/Tests/SlipstreamTests/Attributes/TranslatableTests.swift new file mode 100644 index 00000000..84456ce7 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/TranslatableTests.swift @@ -0,0 +1,21 @@ +import Testing + +import Slipstream + +struct TranslatableTests { + @Test func withEnumYes() throws { + try #expect(renderHTML(Div {}.translatable(.yes)) == #"
"#) + } + + @Test func withEnumNo() throws { + try #expect(renderHTML(Div {}.translatable(.no)) == #"
"#) + } + + @Test func withBoolTrue() throws { + try #expect(renderHTML(Div {}.translatable(true)) == #"
"#) + } + + @Test func withBoolFalse() throws { + try #expect(renderHTML(Div {}.translatable(false)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/Attributes/WritingSuggestionsTests.swift b/Tests/SlipstreamTests/Attributes/WritingSuggestionsTests.swift new file mode 100644 index 00000000..df988b60 --- /dev/null +++ b/Tests/SlipstreamTests/Attributes/WritingSuggestionsTests.swift @@ -0,0 +1,13 @@ +import Testing + +import Slipstream + +struct WritingSuggestionsTests { + @Test func withTrue() throws { + try #expect(renderHTML(Div {}.writingSuggestions(true)) == #"
"#) + } + + @Test func withFalse() throws { + try #expect(renderHTML(Div {}.writingSuggestions(false)) == #"
"#) + } +}