Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Add types for buttons and text fields. #162

Merged
merged 2 commits into from
Aug 13, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``Button``

## Topics

### Supporting types

- ``ButtonType``
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``TextField``

## Topics

### Supporting types

- ``TextFieldInputType``
25 changes: 21 additions & 4 deletions Sources/Slipstream/W3C/Elements/Forms/Button.swift
Original file line number Diff line number Diff line change
@@ -1,49 +1,66 @@
import SwiftSoup

/// Constants that control the behavior of a ``Button`` when it is activated.
///
/// - SeeAlso: W3C [button type](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type) specification.
@available(iOS 17.0, macOS 14.0, *)
public enum ButtonType: String {
case submit
case reset
}

/// A control that initiates an action.
///
/// - SeeAlso: W3C [button](https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element) specification.
@available(iOS 17.0, macOS 14.0, *)
public struct Button<Label>: View where Label: View {
/// Creates a button that displays a custom label and executes a custom action
/// when clicked.
public init(action: String, @ViewBuilder label: @escaping () -> Label) {
public init(action: String, type: ButtonType? = nil, @ViewBuilder label: @escaping () -> Label) {
self.label = label
self.action = action
self.type = type
}

/// Creates a button that displays a custom label.
public init(@ViewBuilder label: @escaping () -> Label) {
public init(type: ButtonType? = nil, @ViewBuilder label: @escaping () -> Label) {
self.label = label
self.action = nil
self.type = type
}

/// Creates a button that generates its label from a string.
public init(_ text: String) where Label == DOMString {
public init(_ text: String, type: ButtonType? = nil) where Label == DOMString {
self.label = {
DOMString(text)
}
self.action = nil
self.type = type
}

/// Creates a button that generates its label from a string and executes a custom
/// action when clicked.
public init(_ text: String, action: String) where Label == DOMString {
public init(_ text: String, action: String, type: ButtonType? = nil) where Label == DOMString {
self.label = {
DOMString(text)
}
self.action = action
self.type = type
}

@ViewBuilder private let label: () -> Label
private let action: String?
private let type: ButtonType?

@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {
let element = try container.appendElement("button")
if let action {
try element.attr("onclick", action)
}
if let type {
try element.attr("type", type.rawValue)
}
try self.label().render(element, environment: environment)
}
}
91 changes: 89 additions & 2 deletions Sources/Slipstream/W3C/Elements/Forms/TextField.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
import SwiftSoup

/// Constants that control the content type of a ``TextField``.
///
/// - SeeAlso: W3C [input type](https://html.spec.whatwg.org/multipage/input.html#attr-input-type) specification.
@available(iOS 17.0, macOS 14.0, *)
public enum TextFieldInputType: String {
/// Represents a one line plain text edit control.
///
/// This is the default input type if no type is provided.
///
/// - SeeAlso: W3C [input type="text"](https://html.spec.whatwg.org/multipage/input.html#text-(type=text)-state-and-search-state-(type=search)) specification.
case text

/// Similar to ``text``
///
/// Note from the W3C spec: The difference between the ``text`` state and the
/// ``search`` state is primarily stylistic: on platforms where search controls
/// are distinguished from regular text controls, the ``search`` state might
/// result in an appearance consistent with the platform's search controls
/// rather than appearing like a regular text control.
///
/// - SeeAlso: W3C [input type="search"](https://html.spec.whatwg.org/multipage/input.html#text-(type=text)-state-and-search-state-(type=search)) specification.
case search

/// Represents a control for editing a telephone number.
///
/// - SeeAlso: W3C [input type="tel"](https://html.spec.whatwg.org/multipage/input.html#telephone-state-(type=tel)) specification.
case telephone = "tel"

/// Represents a control for editing a single absolute URL.
///
/// - SeeAlso: W3C [input type="url"](https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url)) specification.
case url

/// Represents a control for editing an email.
case email
///
/// - SeeAlso: W3C [input type="email"](https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)) specification.

/// Represents a one line plain text edit control.
///
/// The browser will typically obscure the value so that people other than
/// the user cannot see it.
///
/// - SeeAlso: W3C [input type="password"](https://html.spec.whatwg.org/multipage/input.html#password-state-(type=password)) specification.
case password

/// Represents a control for setting the view''s value to a string
/// representing a specific date.
///
/// - SeeAlso: W3C [input type="date"](https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date)) specification.
case date

/// Represents a control for setting the view''s value to a string
/// representing a specific month.
///
/// - SeeAlso: W3C [input type="month"](https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month)) specification.
case month

/// Represents a control for setting the view''s value to a string
/// representing a specific week.
///
/// - SeeAlso: W3C [input type="week"](https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week)) specification.
case week

/// Represents a control for setting the view''s value to a string
/// representing a specific time.
///
/// - SeeAlso: W3C [input type="time"](https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time)) specification.
case time

/// Represents a control for setting the view''s value to a string
/// representing a local date and time, with no time-zone offset
/// information.
///
/// - SeeAlso: W3C [input type="datetime-local"](https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local)) specification.
case localDateAndTime = "datetime-local"

/// Represents a control for setting the view''s value to a string
/// representing a number.
///
/// - SeeAlso: W3C [input type="number"](https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number)) specification.
case number
}

/// A control that displays an editable text interface.
///
/// - SeeAlso: W3C [input](https://html.spec.whatwg.org/multipage/input.html#the-input-element) specification.
Expand All @@ -9,18 +93,20 @@ public struct TextField: View {
///
/// - Parameters:
/// - text: The placeholder text to display in the text field when it is empty.
/// - type: The text field's content type.
/// - autoFocus: If true, indicates that the view should be focused as soon as
/// the page is loaded, allowing the user to start typing without having to
/// manually focus the main view.
public init(_ text: String, autoFocus: Bool = false) {
public init(_ text: String, type: TextFieldInputType = .text, autoFocus: Bool = false) {
self.text = text
self.type = type
self.autoFocus = autoFocus
}

@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {
let element = try container.appendElement("input")
try element.attr("type", "text")
try element.attr("type", type.rawValue)
try element.attr("placeholder", text)
if autoFocus {
try element.attr("autofocus", "")
Expand All @@ -29,4 +115,5 @@ public struct TextField: View {

private let text: String
private let autoFocus: Bool
private let type: TextFieldInputType
}
5 changes: 5 additions & 0 deletions Tests/SlipstreamTests/W3C/Forms/ButtonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ struct ButtonTests {
try #expect(renderHTML(Button("Tap me")) == #"<button>Tap me</button>"#)
}

@Test func withType() throws {
try #expect(renderHTML(Button("Tap me", type: .submit)) == #"<button type="submit">Tap me</button>"#)
try #expect(renderHTML(Button("Tap me", type: .reset)) == #"<button type="reset">Tap me</button>"#)
}

@Test func withAction() throws {
try #expect(renderHTML(Button(action: "alert('hello world')") {
DOMString("Tap me")
Expand Down
15 changes: 15 additions & 0 deletions Tests/SlipstreamTests/W3C/Forms/TextFieldTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,19 @@ struct TextFieldTests {
@Test func autoFocus() throws {
try #expect(renderHTML(TextField("Placeholder", autoFocus: true)) == #"<input type="text" placeholder="Placeholder" autofocus />"#)
}

@Test func withType() throws {
try #expect(renderHTML(TextField("Placeholder", type: .text)) == #"<input type="text" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .search)) == #"<input type="search" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .telephone)) == #"<input type="tel" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .url)) == #"<input type="url" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .email)) == #"<input type="email" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .password)) == #"<input type="password" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .date)) == #"<input type="date" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .month)) == #"<input type="month" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .week)) == #"<input type="week" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .time)) == #"<input type="time" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .localDateAndTime)) == #"<input type="datetime-local" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .number)) == #"<input type="number" placeholder="Placeholder" />"#)
}
}