Skip to content

Commit

Permalink
Add support for the HTML <picture> element (#32)
Browse files Browse the repository at this point in the history
This change adds support for `<picture>`, and its associated `srcset`
and `media` attributes. This is done by creating a new `HTMLSourceListContext`
protocol which `<picture>` shares with `<audio>` and `<video>`, enabling
maximum code reuse while still preserving type safety. Since an `<img>`
element can also appear inside of `<picture>`, image tags are now constrained
to a new `HTMLImageContainerContext`, which `HTML.BodyContext` also adopts.
  • Loading branch information
JohnSundell authored Mar 5, 2020
1 parent 620991f commit e81e059
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 20 deletions.
21 changes: 16 additions & 5 deletions Sources/Plot/API/HTML.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public extension HTML {
/// The context within an HTML document's `<head>` element.
enum HeadContext: HTMLContext, HTMLScriptableContext {}
/// The context within an HTML document's `<body>` element.
class BodyContext: HTMLStylableContext, HTMLScriptableContext {}
class BodyContext: HTMLStylableContext, HTMLScriptableContext, HTMLImageContainerContext {}
/// The context within an HTML `<abbr>` element.
final class AbbreviationContext: BodyContext {}
/// The context within an HTML `<a>` element.
Expand Down Expand Up @@ -85,6 +85,12 @@ public extension HTML {
enum MetaContext: HTMLNamableContext {}
/// The context within an HTML `<option>` element.
enum OptionContext: HTMLValueContext {}
/// The context within an HTML `<picture>` element.
enum PictureContext: HTMLSourceListContext, HTMLImageContainerContext {
public typealias SourceContext = PictureSourceContext
}
/// The context within a picture `<source>` element.
enum PictureSourceContext {}
/// The context within an HTML `<script>` element.
enum ScriptContext: HTMLSourceContext, HTMLIntegrityContext {}
/// The context within an HTML `<select>` element.
Expand All @@ -106,15 +112,14 @@ public protocol HTMLContext {}
/// Context shared among all HTML elements that can have their dimensions
/// (width and height) specified through attributes, such as `<video>`.
public protocol HTMLDimensionContext: HTMLContext {}
/// Context shared among all HTML elements that can contain an `<img>` element.
public protocol HTMLImageContainerContext: HTMLContext {}
/// Context shared among all HTML elements that act as some form
/// of link to an external resource, such as `<link>` or `<a>`.
public protocol HTMLLinkableContext: HTMLContext {}
/// Context shared among all HTML elements that enable media playback,
/// such as `<audio>` and `<video>`.
public protocol HTMLMediaContext: HTMLContext {
/// The context within the media element's `<source>` element.
associatedtype SourceContext: HTMLSourceContext
}
public protocol HTMLMediaContext: HTMLSourceListContext {}
/// Context shared among all HTML elements that support the `integrity`
/// attribute, such as `<link>` and `<script>`
public protocol HTMLIntegrityContext: HTMLContext {}
Expand All @@ -130,6 +135,12 @@ public protocol HTMLScriptableContext: HTMLContext {}
/// Context shared among all HTML elements that support the `src`
/// attribute, for example `<img>` and `<iframe>`.
public protocol HTMLSourceContext: HTMLContext {}
/// Context shared among all HTML elements that can act as containers
/// for a list of `<source>` elements.
public protocol HTMLSourceListContext: HTMLContext {
/// The context within the element's `<source>` child elements.
associatedtype SourceContext
}
/// Context shared among all HTML elements that can be styled using
/// inline CSS through the `style` attribute.
public protocol HTMLStylableContext: HTMLContext {}
Expand Down
14 changes: 14 additions & 0 deletions Sources/Plot/API/HTMLAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,20 @@ public extension Attribute where Context == HTML.VideoSourceContext {
}
}

public extension Attribute where Context == HTML.PictureSourceContext {
/// Assign a string describing a set of sources, using the `srcset` attribute.
/// - parameter set: The set of sources that this element should point to.
static func srcset(_ set: String) -> Attribute {
Attribute(name: "srcset", value: set)
}

/// Assign a media query that determines whether this source should be used.
/// - parameter query: The media query that the browser should evaluate.
static func media(_ query: String) -> Attribute {
Attribute(name: "media", value: query)
}
}

// MARK: - Forms, input and options

public extension Node where Context == HTML.FormContext {
Expand Down
38 changes: 24 additions & 14 deletions Sources/Plot/API/HTMLElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,6 @@ public extension Node where Context: HTML.BodyContext {
.element(named: "iframe", attributes: attributes)
}

/// Add an `<img/>` HTML element within the current context.
/// - parameter attributes: The element's attributes.
static func img(_ attributes: Attribute<HTML.ImageContext>...) -> Node {
.selfClosedElement(named: "img", attributes: attributes)
}

/// Add an `<ins>` HTML element within the current context.
/// - parameter nodes: The element's attributes and child elements.
static func ins(_ nodes: Node<HTML.BodyContext>...) -> Node {
Expand Down Expand Up @@ -301,6 +295,12 @@ public extension Node where Context: HTML.BodyContext {
.element(named: "p", nodes: nodes)
}

/// Add a `<picture>` HTML element within the current context.
/// - parameter nodes: The element's attributes and child elements.
static func picture(_ nodes: Node<HTML.PictureContext>...) -> Node {
.element(named: "picture", nodes: nodes)
}

/// Add a `<pre>` HTML element within the current context.
/// - parameter nodes: The element's attributes and child elements.
static func pre(_ nodes: Node<HTML.BodyContext>...) -> Node {
Expand Down Expand Up @@ -424,6 +424,24 @@ public extension Node where Context == HTML.TableRowContext {
}
}

// MARK: - Media

public extension Node where Context: HTMLImageContainerContext {
/// Add an `<img/>` HTML element within the current context.
/// - parameter attributes: The element's attributes.
static func img(_ attributes: Attribute<HTML.ImageContext>...) -> Node {
.selfClosedElement(named: "img", attributes: attributes)
}
}

public extension Node where Context: HTMLSourceListContext {
/// Add a `<source/>` HTML element within the current context.
/// - parameter attributes: The element's attributes.
static func source(_ attributes: Attribute<Context.SourceContext>...) -> Node {
.selfClosedElement(named: "source", attributes: attributes)
}
}

// MARK: - Forms

public extension Node where Context == HTML.FormContext {
Expand All @@ -448,14 +466,6 @@ public extension Node where Context == HTML.FormContext {

// MARK: - Other

public extension Node where Context: HTMLMediaContext {
/// Add a `<source/>` HTML element within the current context.
/// - parameter attributes: The element's attributes.
static func source(_ attributes: Attribute<Context.SourceContext>...) -> Node {
.selfClosedElement(named: "source", attributes: attributes)
}
}

public extension Node where Context == HTML.DetailsContext {
/// Add a `<summary>` HTML element within the current context.
/// - parameter nodes: The element's attributes and child elements.
Expand Down
20 changes: 19 additions & 1 deletion Tests/PlotTests/HTMLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,23 @@ final class HTMLTests: XCTestCase {
let html = HTML(.comment("Hello"), .body(.comment("World")))
assertEqualHTMLContent(html, "<!--Hello--><body><!--World--></body>")
}

func testPicture() {
let html = HTML(.body(.picture(
.source(
.srcset("dark.jpg"),
.media("(prefers-color-scheme: dark)")
),
.img(.src("default.jpg"))
)))

assertEqualHTMLContent(html, """
<body><picture>\
<source srcset="dark.jpg" media="(prefers-color-scheme: dark)"/>\
<img src="default.jpg"/>\
</picture></body>
""")
}
}

extension HTMLTests {
Expand Down Expand Up @@ -695,7 +712,8 @@ extension HTMLTests {
("testAccessibilityExpanded", testAccessibilityExpanded),
("testDataAttributes", testDataAttributes),
("testSubresourceIntegrity", testSubresourceIntegrity),
("testComments", testComments)
("testComments", testComments),
("testPicture", testPicture)
]
}
}

0 comments on commit e81e059

Please sign in to comment.