Skip to content

Commit 8f9a3f1

Browse files
added HTMX SSE; some README touchups
1 parent 817deea commit 8f9a3f1

File tree

4 files changed

+92
-4
lines changed

4 files changed

+92
-4
lines changed

README.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,20 @@ Write HTML using Swift Macros. Supports HTMX via global attributes.
2424
<details>
2525
<summary>How do I use this library?</summary>
2626

27-
Syntax: `#<html element>(attributes: [], <element specific attributes>: V?, _ innerHTML: ExpressibleByStringLiteral...)`
27+
#### Syntax
28+
29+
```swift
30+
31+
#<html element>(
32+
attributes: [<global attribute>],
33+
<element specific attribute>: <element specific attribute value>?,
34+
_ innerHTML: ExpressibleByStringLiteral...
35+
)
36+
37+
```
38+
39+
- all parameters are optional by default
40+
2841
#### Examples
2942

3043
```swift
@@ -104,20 +117,26 @@ Using String Interpolation.
104117

105118
> You will get a compiler warning saying *interpolation may introduce raw HTML*.
106119
>
107-
> Its up to you whether or not to suppress this warning or escape the HTML at runtime using an method described above.
120+
> Its up to you whether or not to suppress this warning or escape the HTML at runtime using a method described above.
108121
>
109122
> Swift HTMLKit tries to [replace](https://github.com/RandomHashTags/swift-htmlkit/blob/94793984763308ef5275dd9f71ea0b5e83fea417/Sources/HTMLKitMacros/HTMLElement.swift#L423) known interpolation at compile time with a compile time equivalent for the best performance. It is currently limited due to macro expansions being sandboxed and lexical contexts/AST not being available for macro arguments. This means referencing content known at compile time in a html macro won't get replaced by its literal contents. [Read more about this limitation](https://forums.swift.org/t/swift-lexical-lookup-for-referenced-stuff-located-outside-scope-current-file/75776/6).
110123
111124
#### Example
112125
```swift
113126
let string:String = "any string value", integer:Int = -69, float:Float = 3.141592
114127

128+
// ✅ DO
115129
let _:String = #p("\(string); \(integer); \(float)")
116130
let _:String = #p("\(string)", "; ", String(describing: integer), "; ", float.description)
117-
// the below also works
131+
118132
let integer_string:String = String(describing: integer), float_string:String = String(describing: float)
119133
let _:String = #p(string, "; ", integer_string, "; ", float_string)
120134

135+
// ❌ DON'T; compiler error; compile time value cannot contain interpolation
136+
let _:StaticString = #p("\(string); \(integer); \(float)")
137+
let _:StaticString = #p("\(string)", "; ", String(describing: integer), "; ", float.description)
138+
let _:StaticString = #p(string, "; ", integer_string, "; ", float_string)
139+
121140
```
122141

123142
</details>
@@ -194,7 +213,7 @@ Use a different html macro. Currently supported types (more to come with new lan
194213

195214
<summary>How do I use HTMX?</summary>
196215

197-
Use the `.htmx(<htmx attribute>)` global attribute. All HTMX 2.0 attributes are supported (including web socket).
216+
Use the `.htmx(<htmx attribute>)` global attribute. All HTMX 2.0 attributes are supported (including Server Sent Events & Web Sockets).
198217

199218
#### Examples
200219
```swift
@@ -217,6 +236,9 @@ string = #div(attributes: [.htmx(.onevent(.click, "thing()"))])
217236
// <div hx-preserve></div>
218237
string = #div(attributes: [.htmx(.preserve(true))])
219238

239+
// <div sse-connect="/connect"></div>
240+
string = #div(attributes: [.htmx(.sse(.connect("/connect")))])
241+
220242
// <div ws-connect="/chatroom"></div>
221243
string = #div(attributes: [.htmx(.ws(.connect("/chatroom")))])
222244

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ private extension HTMLElement {
202202
case .request(_, _, _, _), .headers(_, _):
203203
delimiter = "'"
204204
break
205+
case .sse(let sse_value):
206+
key = "sse-" + sse_value.key
207+
break
205208
case .ws(let ws_value):
206209
key = "ws-" + ws_value.key
207210
switch ws_value {

Sources/HTMLKitUtilities/HTMX.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public extension HTMLElementAttribute {
4444
case trigger(String)
4545
case vals(String)
4646

47+
case sse(ServerSentEvents)
4748
case ws(WebSocket)
4849

4950
public init?(rawValue: String) {
@@ -182,6 +183,7 @@ public extension HTMLElementAttribute {
182183
case "trigger": self = .trigger(string())
183184
case "vals": self = .vals(string())
184185

186+
case "sse": self = .sse(ServerSentEvents(rawValue: literal())!)
185187
case "ws": self = .ws(WebSocket(rawValue: literal())!)
186188
default: return nil
187189
}
@@ -226,6 +228,7 @@ public extension HTMLElementAttribute {
226228
case .trigger(_): return "trigger"
227229
case .vals(_): return "vals"
228230

231+
case .sse(let event): return "sse-" + event.key
229232
case .ws(let value): return "ws-" + value.key
230233
}
231234
}
@@ -280,6 +283,7 @@ public extension HTMLElementAttribute {
280283
case .trigger(let value): return value
281284
case .vals(let value): return value
282285

286+
case .sse(let value): return value.htmlValue
283287
case .ws(let value): return value.htmlValue
284288
}
285289
}
@@ -506,6 +510,47 @@ public extension HTMLElementAttribute.HTMX {
506510
}
507511
}
508512

513+
// MARK: Server Sent Events
514+
public extension HTMLElementAttribute.HTMX {
515+
enum ServerSentEvents {
516+
case connect(String)
517+
case swap(String)
518+
case close(String)
519+
520+
public init?(rawValue: String) {
521+
guard rawValue.last == ")" else { return nil }
522+
let start:String.Index = rawValue.startIndex, end:String.Index = rawValue.index(before: rawValue.endIndex), end_minus_one:String.Index = rawValue.index(before: end)
523+
let key:Substring = rawValue.split(separator: "(")[0]
524+
func string() -> String {
525+
return String(rawValue[rawValue.index(start, offsetBy: key.count + 2)..<end_minus_one])
526+
}
527+
switch key {
528+
case "connect": self = .connect(string())
529+
case "swap": self = .swap(string())
530+
case "close": self = .close(string())
531+
default: return nil
532+
}
533+
}
534+
535+
public var key : String {
536+
switch self {
537+
case .connect(_): return "connect"
538+
case .swap(_): return "swap"
539+
case .close(_): return "close"
540+
}
541+
}
542+
543+
public var htmlValue : String {
544+
switch self {
545+
case .connect(let value),
546+
.swap(let value),
547+
.close(let value):
548+
return value
549+
}
550+
}
551+
}
552+
}
553+
509554
// MARK: WebSocket
510555
public extension HTMLElementAttribute.HTMX {
511556
enum WebSocket {

Tests/HTMLKitTests/HTMXTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,24 @@ struct HTMXTests {
154154
#expect(string == "<div hx-sync=\"#submit-button:queue first\"></div>")
155155
}
156156

157+
// MARK: sse
158+
@Test func sse() {
159+
var string:StaticString = #div(attributes: [.htmx(.sse(.connect("/connect")))])
160+
#expect(string == "<div sse-connect=\"/connect\"></div>")
161+
162+
string = #div(attributes: [.htmx(.sse(.swap("message")))])
163+
#expect(string == "<div sse-swap=\"message\"></div>")
164+
165+
string = #div(attributes: [.htmx(.sse(.close("message")))])
166+
#expect(string == "<div sse-close=\"message\"></div>")
167+
}
168+
169+
// MARK: trigger
170+
@Test func trigger() {
171+
let string:StaticString = #div(attributes: [.htmx(.trigger("sse:chatter"))])
172+
#expect(string == "<div hx-trigger=\"sse:chatter\"></div>")
173+
}
174+
157175
// MARK: ws
158176
@Test func ws() {
159177
var string:StaticString = #div(attributes: [.htmx(.ws(.connect("/chatroom")))])

0 commit comments

Comments
 (0)