@zio.dev/zio-blocks 0.0.33 → 0.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,807 @@
1
+ ---
2
+ id: index
3
+ title: HTMX
4
+ ---
5
+
6
+ `zio.http.htmx` is a **typed HTMX DSL** for building safe, compile-time HTMX attribute declarations within `zio.blocks.html`. It provides immutable types representing HTMX events, swap strategies, target selectors, and request modifiers, eliminating stringly-typed misuse through rich domain types while maintaining explicit string surfaces for URLs and raw JavaScript where needed.
7
+
8
+ Core types: `HxTrigger`, `HxSwap`, `HxTarget`, `HxParams`, `HxUrlUpdate`, `HxEncoding`, `HxSync`, `HtmxAttrKey`, `ToHtmxValue`.
9
+
10
+ Here are the core patterns of typed HTMX construction:
11
+
12
+ ```scala
13
+ import zio.blocks.html._
14
+ import zio.http.htmx._
15
+ import scala.concurrent.duration._
16
+
17
+ // Compile-safe event triggers with modifiers
18
+ div(hxPost := "/search", hxTrigger := HxTrigger.input.delay(500.millis))
19
+
20
+ // Typed swap strategies with multiple modifiers
21
+ div(hxSwap := HxSwap.InnerHTML.swap(1.second).transition)
22
+
23
+ // Type-safe target selection
24
+ button(hxTarget := HxTarget.closest("form"))
25
+
26
+ // Request control through domain types
27
+ form(hxParams := HxParams.only("query", "page"))
28
+ ```
29
+
30
+ ## Introduction
31
+
32
+ The `htmx` module eliminates accidental stringly mistakes in HTMX attribute construction. Rather than writing raw strings like `"innerHTML swap:1s settle:500ms transition:true"`, you compose domain values through a type-safe DSL that guides you toward correct HTMX syntax at compile time.
33
+
34
+ Most attributes are narrower than plain HTML strings. `hxSwap` accepts `HxSwap`, `hxTarget` accepts `HxTarget`, and selector-only attributes accept `CssSelector`. When a string surface is genuinely necessary—for URLs, custom JavaScript filters, or dynamic values—the DSL provides explicit entry points.
35
+
36
+ ## Motivation
37
+
38
+ HTMX introduces many new attributes and modifier combinations. Writing these as raw strings is error-prone:
39
+ - Typos in strategy names (`"innterHTML"`, `"delya:1s"`) silently fail at runtime
40
+ - Mixing unrelated modifiers in the same attribute (`"innerHTML queue:first threshold:0.5"`) compiles but confuses intent
41
+ - URLs and JavaScript filters have no type guidance, leading to unsafe assumptions
42
+
43
+ The typed HTMX DSL catches these mistakes at compile time:
44
+ - Strategy names are exhaustive case objects: `HxSwap.InnerHTML`, `HxSwap.AfterBegin`, etc.
45
+ - Modifier methods enforce correct grouping: repeated calls within the same modifier group (`swap()` or `settle()`) replace earlier values, while different groups (`swap`, `settle`, `transition`) can be combined
46
+ - Type-safe attributes like `hxTarget` prevent passing raw strings where structured selectors belong
47
+ - Extensible type class `ToHtmxValue` lets custom domain values render themselves to HTMX syntax
48
+
49
+ ## Installation
50
+
51
+ Add the HTMX module to your project dependencies:
52
+
53
+ ```scala
54
+ // JVM
55
+ libraryDependencies += "dev.zio" %% "zio-blocks-http-htmx" % "0.0.55"
56
+
57
+ // Scala.js
58
+ libraryDependencies += "dev.zio" %%% "zio-blocks-http-htmx" % "0.0.55"
59
+ ```
60
+
61
+ Supported Scala versions: Scala 3.x. The module is cross-compiled for JVM and Scala.js.
62
+
63
+ ## Overview
64
+
65
+ Each type in the module addresses a specific HTMX concern:
66
+
67
+ **Event Triggering:** `HxTrigger` declares which event fires a request (click, input, load, custom). Modifiers add timing (`HxTrigger#delay`, `HxTrigger#throttle`), use `HxTrigger#filter` for filtering, source control (`from`), and queue strategy (`queue`). `HxTriggerSet` composes multiple triggers in a comma-separated list for more complex interactions.
68
+
69
+ **Swap Strategies:** `HxSwap` selects how the response content replaces the DOM (innerHTML, outerHTML, beforeBegin, etc.). Modifiers control timing with `HxSwap#swap` and `HxSwap#settle`, animation (`transition`), scrolling (`scroll`, `show`), and focus behavior (`focusScroll`, `ignoreTitle`).
70
+
71
+ **Target Selection:** `HxTarget` selects where swap happens (current element, closest ancestor, next sibling, custom CSS selector). Variants support common patterns like `HxTarget.This`, `HxTarget.closest(selector)`, and `HxTarget.next(selector)`.
72
+
73
+ **Request Control:** `HxParams` filters which form fields are submitted (all, none, only listed, all except listed). `HxUrlUpdate` controls whether the URL bar updates after the request. `HxEncoding` and `HxSync` handle multi-step request concerns.
74
+
75
+ **Infrastructure:** `HtmxAttrKey` is the typed attribute key binding a name to a value type. `ToHtmxValue` is the type class rendering domain values to HTMX attribute strings, enabling custom types to participate in the DSL.
76
+
77
+ ## How They Work Together
78
+
79
+ A typical HTMX request flow combines multiple types: trigger defines when, swap defines what, target defines where, and request control refines how. Here is the overall flow:
80
+
81
+ ```
82
+ User Action
83
+ ↓
84
+ HxTrigger (when: click, input, load, every N seconds, etc.)
85
+ ├─ Modifiers: delay, throttle, filter, from, queue
86
+ ↓
87
+ Request Sent
88
+ ├─ URL: hxPost, hxGet, hxPut, hxPatch, hxDelete
89
+ ├─ Parameters: HxParams (all, none, only, not)
90
+ ├─ Encoding: HxEncoding (multipart override; default is URL-encoded)
91
+ └─ Synchronization: HxSync (queue strategy, abort behavior)
92
+ ↓
93
+ Response Received
94
+ ↓
95
+ HxTarget (where: this, closest, find, next, previous, css selector)
96
+ ↓
97
+ HxSwap (how: innerHTML, outerHTML, beforeBegin, etc.)
98
+ ├─ Modifiers: swap delay, settle delay, transition, scroll, show
99
+ └─ Focus: ignoreTitle, focusScroll
100
+ ↓
101
+ DOM Updated & Settled
102
+ ```
103
+
104
+ **Example workflow:** When a user types in a search input, fire a POST request to `/api/search` after a 500ms delay. Send only the query and page parameters. Replace the results section (the closest parent with CSS class `results`) with innerHTML strategy, wait 250ms for CSS transitions, then scroll the results into view:
105
+
106
+ ```scala
107
+ import zio.blocks.html._
108
+ import zio.http.htmx._
109
+ import scala.concurrent.duration._
110
+
111
+ input(
112
+ placeholder := "Search...",
113
+ hxPost := "/api/search",
114
+ hxTrigger := HxTrigger.input.delay(500.millis),
115
+ hxParams := HxParams.only("query", "page"),
116
+ hxTarget := HxTarget.closest(".results"),
117
+ hxSwap := HxSwap.InnerHTML.settle(250.millis).scroll(HxSwap.ScrollPosition.Top)
118
+ )
119
+ ```
120
+
121
+ This example shows how types guide each concern: `HxTrigger.input` is compile-checked (not a typo), `HxTrigger#delay` is a method not a string, `HxParams.only()` is exhaustively typed, `HxTarget.closest()` takes a string but validates non-emptiness, and `HxSwap` chains modifiers with type safety.
122
+
123
+ ## Common Patterns
124
+
125
+ The HTMX DSL supports several common interaction patterns. Here are representative examples:
126
+
127
+ ### Pattern 1: Progressive Enhancement with Boost
128
+
129
+ Use `hxBoost := true` to transform regular links and form submissions into HTMX requests, maintaining server-side rendering and graceful degradation:
130
+
131
+ ```scala
132
+ import zio.blocks.html._
133
+ import zio.http.htmx._
134
+
135
+ // Entire nav boosted—clicks are HTMX requests, but work without JS
136
+ nav(
137
+ hxBoost := true,
138
+ ul(
139
+ li(a(href := "/", "Home")),
140
+ li(a(href := "/products", "Products")),
141
+ li(a(href := "/contact", "Contact"))
142
+ )
143
+ )
144
+ ```
145
+
146
+ ### Pattern 2: Polling and Periodic Updates
147
+
148
+ Use `HxTrigger.every()` with a duration to poll an endpoint at regular intervals:
149
+
150
+ ```scala
151
+ import zio.blocks.html._
152
+ import zio.http.htmx._
153
+ import scala.concurrent.duration._
154
+
155
+ // Poll every 2 seconds for new notifications
156
+ div(
157
+ id := "notifications",
158
+ hxGet := "/api/notifications",
159
+ hxTrigger := HxTrigger.every(2.seconds),
160
+ hxSwap := HxSwap.InnerHTML
161
+ )
162
+ ```
163
+
164
+ ### Pattern 3: Chained Modifiers for Complex Interactions
165
+
166
+ Combine multiple trigger modifiers to refine when and how a request fires:
167
+
168
+ ```scala
169
+ import zio.blocks.html._
170
+ import zio.http.htmx._
171
+ import scala.concurrent.duration._
172
+
173
+ // Fire on input, throttle to once per second, only if value changed
174
+ input(
175
+ hxPost := "/search",
176
+ hxTrigger := HxTrigger.input.throttle(1.second).changed,
177
+ hxSwap := HxSwap.InnerHTML
178
+ )
179
+ ```
180
+
181
+ Modifier methods return updated `HxTrigger` instances, so you can chain: `HxTrigger.click.delay(100.millis).once` creates a single-fire click handler with a delay.
182
+
183
+ ### Pattern 4: Out-of-Band Swaps
184
+
185
+ Update multiple DOM regions with a single response using `hxSwapOob` (out-of-bounds):
186
+
187
+ ```scala
188
+ import zio.blocks.html._
189
+ import zio.http.htmx._
190
+
191
+ // Main content updates inline; status badge updates separately
192
+ div(
193
+ hxPost := "/api/action",
194
+ hxSwap := HxSwap.InnerHTML,
195
+ button("Submit"),
196
+ div(id := "status", hxSwapOob := HxSwapOob.using(HxSwap.InnerHTML), "Ready")
197
+ )
198
+ ```
199
+
200
+ The response contains both the new main content and a separate element targeted by `hxSwapOob`, allowing one request to update multiple areas.
201
+
202
+ ### Pattern 5: Conditional Rendering with JavaScript Filters
203
+
204
+ Use `HxTrigger#filter` with the `Js` type to add a JavaScript condition that gates the request:
205
+
206
+ ```scala
207
+ import zio.blocks.html._
208
+ import zio.http.htmx._
209
+
210
+ input(
211
+ hxPost := "/search",
212
+ hxTrigger := HxTrigger.input.filter(Js("event.target.value.length > 2")),
213
+ placeholder := "Only POST if search has 3+ characters"
214
+ )
215
+ ```
216
+
217
+ The `Js` type is intentionally raw—do not build it from unsanitized user input.
218
+
219
+ ## Integration Points
220
+
221
+ **With `zio.blocks.html`:** All HTMX attributes integrate seamlessly into the HTML DSL. `HtmxAttributes` is mixed into the `zio.http.htmx` package object, making attributes like `hxPost`, `hxTrigger`, and `hxSwap` directly available when you `import zio.http.htmx._`.
222
+
223
+ **With `zio.http` URL/Path types:** URL-bearing attributes accept `URL` and `Path` types from `zio.http`, which are then rendered to valid URL strings. The `ToHtmxValue` type class provides encoding for these types.
224
+
225
+ **With `zio.blocks.schema` for JSON encoding:** For domain values, use `HxVals.from(...)` and `HxHeadersValue.from(...)` to encode values via their `Schema` to JSON. This allows schema-backed types to be automatically JSON-encoded in HTMX attributes.
226
+
227
+ **With CSS selectors:** Attributes that accept selectors (hxTarget, hxSelect, hxDisabledElt, hxIndicator) accept `CssSelector` from `zio.blocks.html`, providing type-safe selector construction.
228
+
229
+ **With JavaScript:** Attributes that accept raw JavaScript expressions (`hxOn:*`, `hxTrigger.filter()`) accept the `Js` type, making it explicit that you're writing unescaped JavaScript code. This prevents accidental XSS while allowing intentional dynamic behavior.
230
+
231
+ **With headers:** The [`zio.http.htmx.headers`](./response-headers.md) submodule provides typed HTMX request/response headers (HX-Request, HX-Trigger, HX-Redirect, etc.), letting you inspect and build headers with the same type safety as attributes.
232
+
233
+ **Extending with custom types:** Implement `ToHtmxValue[MyType]` to let your domain types render themselves in the DSL. For example, a custom `enum Status { Active, Inactive }` defines `implicit val statusToHtmx: ToHtmxValue[Status] = ...` and renders directly in HTMX attributes.
234
+
235
+ ## Running the Examples
236
+
237
+ All code from this guide is available as runnable examples in the `zio-blocks-htmx-examples` module.
238
+
239
+ **1. Clone the repository and navigate to the project:**
240
+
241
+ Start by cloning the repository and entering the project directory:
242
+
243
+ ```bash
244
+ git clone https://github.com/zio/zio-blocks.git
245
+ cd zio-blocks
246
+ ```
247
+
248
+ **2. Run individual examples with sbt:**
249
+
250
+ ### Basic Usage
251
+
252
+ Demonstrates fundamental HTMX attribute construction: triggering requests (hxPost, hxGet), swapping strategies (InnerHTML, OuterHTML), and target selection (This, closest, find). Shows how the typed DSL ensures correct HTMX syntax at compile time. Here is the source code:
253
+
254
+ ```scala title="zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/BasicUsage.scala"
255
+ package zioBlocksHtmx
256
+
257
+ import zio.blocks.html._
258
+ import zio.http.htmx._
259
+ import scala.concurrent.duration._
260
+
261
+ /**
262
+ * HTMX DSL — Basic Usage
263
+ *
264
+ * Demonstrates fundamental HTMX attribute construction: triggering requests
265
+ * (hxPost, hxGet), swapping strategies (InnerHTML, OuterHTML), and target
266
+ * selection (This, closest, find). Shows how the typed DSL ensures correct HTMX
267
+ * syntax at compile time.
268
+ *
269
+ * Run with: sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.BasicUsage"
270
+ */
271
+ object BasicUsage {
272
+ def main(args: Array[String]): Unit = {
273
+ println("=== HTMX Basic Usage Examples ===\n")
274
+
275
+ // Example 1: Simple click trigger with POST
276
+ println("1. Click trigger with POST request:")
277
+ val clickButton = button(
278
+ hxPost := "/api/action",
279
+ hxTrigger := HxTrigger.click,
280
+ "Click me"
281
+ )
282
+ println(s" Rendered: $clickButton\n")
283
+
284
+ // Example 2: Input with GET request
285
+ println("2. Input with debounced GET request:")
286
+ val searchInput = input(
287
+ `type` := "text",
288
+ placeholder := "Search...",
289
+ hxGet := "/api/search",
290
+ hxTrigger := HxTrigger.input.delay(500.millis),
291
+ hxTarget := HxTarget.next("div")
292
+ )
293
+ println(s" Rendered: $searchInput\n")
294
+
295
+ // Example 3: Different swap strategies
296
+ println("3. Swap strategies:")
297
+ val innerHTMLDiv = div(
298
+ id := "content",
299
+ hxGet := "/api/content",
300
+ hxSwap := HxSwap.InnerHTML,
301
+ "Click to load inner content"
302
+ )
303
+ val outerHTMLDiv = div(
304
+ id := "card",
305
+ hxPost := "/api/replace",
306
+ hxSwap := HxSwap.OuterHTML,
307
+ "This entire div will be replaced"
308
+ )
309
+ val appendDiv = div(
310
+ id := "messages",
311
+ hxGet := "/api/new-message",
312
+ hxSwap := HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom),
313
+ "New messages will be appended"
314
+ )
315
+ println(s" InnerHTML: ${innerHTMLDiv}")
316
+ println(s" OuterHTML: ${outerHTMLDiv}")
317
+ println(s" Append with scroll: ${appendDiv}\n")
318
+
319
+ // Example 4: Target selection patterns
320
+ println("4. Target selection:")
321
+ val targetThis = button(
322
+ hxPost := "/api/toggle",
323
+ hxTarget := HxTarget.This,
324
+ "Toggle this button"
325
+ )
326
+ val targetClosest = button(
327
+ hxPost := "/api/validate",
328
+ hxTarget := HxTarget.closest("form"),
329
+ "Validate form"
330
+ )
331
+ val targetFind = div(
332
+ hxGet := "/api/update",
333
+ hxTarget := HxTarget.find(".result"),
334
+ "Content with .result child"
335
+ )
336
+ println(s" This: $targetThis")
337
+ println(s" Closest: $targetClosest")
338
+ println(s" Find: $targetFind\n")
339
+
340
+ // Example 5: Form submission with parameters
341
+ println("5. Form submission with selective parameters:")
342
+ val searchForm = form(
343
+ hxPost := "/api/search",
344
+ hxTrigger := HxTrigger.submit,
345
+ hxParams := HxParams.only("query", "page"),
346
+ hxSwap := HxSwap.InnerHTML,
347
+ input(`type` := "text", name := "query", placeholder := "Query"),
348
+ input(`type` := "hidden", name := "page", value := "1"),
349
+ input(`type` := "hidden", name := "unused", value := "ignored"),
350
+ button(`type` := "submit", "Search")
351
+ )
352
+ println(s" Form with selective params: $searchForm\n")
353
+
354
+ // Example 6: Load event with once modifier
355
+ println("6. Load event with once modifier:")
356
+ val initialLoadDiv = div(
357
+ id := "initial",
358
+ hxGet := "/api/bootstrap-data",
359
+ hxTrigger := HxTrigger.load.once,
360
+ hxSwap := HxSwap.InnerHTML,
361
+ "Loading initial data..."
362
+ )
363
+ println(s" Load once: $initialLoadDiv\n")
364
+
365
+ // Example 7: Change event on select
366
+ println("7. Change event on select element:")
367
+ val selectDropdown = select(
368
+ name := "category",
369
+ hxPost := "/api/category-changed",
370
+ hxTrigger := HxTrigger.change,
371
+ hxTarget := HxTarget.next("div"),
372
+ option(value := "all", "All Categories"),
373
+ option(value := "news", "News"),
374
+ option(value := "updates", "Updates")
375
+ )
376
+ println(s" Select with change: $selectDropdown\n")
377
+
378
+ println("✓ Basic usage examples complete")
379
+ }
380
+ }
381
+ ```
382
+
383
+ ([source](https://github.com/zio/zio-blocks/blob/main/zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/BasicUsage.scala))
384
+
385
+ Run this example with the following command:
386
+
387
+ ```bash
388
+ sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.BasicUsage"
389
+ ```
390
+
391
+ ### Advanced Patterns
392
+
393
+ Demonstrates complex HTMX interactions: combining multiple triggers, chaining modifiers, controlling request queuing, animation with transitions, and JavaScript-based filtering. Shows how modifiers compose to create sophisticated client-side behaviors. Here is the source code:
394
+
395
+ ```scala title="zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/AdvancedPatterns.scala"
396
+ package zioBlocksHtmx
397
+
398
+ import zio.blocks.html._
399
+ import zio.http.htmx._
400
+ import scala.concurrent.duration._
401
+
402
+ /**
403
+ * HTMX DSL — Advanced Patterns
404
+ *
405
+ * Demonstrates complex HTMX interactions: combining multiple triggers, chaining
406
+ * modifiers, controlling request queuing, animation with transitions, and
407
+ * JavaScript-based filtering. Shows how modifiers compose to create
408
+ * sophisticated client-side behaviors.
409
+ *
410
+ * Run with: sbt "zio-blocks-htmx-examples/runMain
411
+ * zioBlocksHtmx.AdvancedPatterns"
412
+ */
413
+ object AdvancedPatterns {
414
+ def main(args: Array[String]): Unit = {
415
+ println("=== HTMX Advanced Patterns ===\n")
416
+
417
+ // Example 1: Debounced search with state modifier
418
+ println("1. Debounced search with value-changed detection:")
419
+ val debouncedSearch = input(
420
+ `type` := "text",
421
+ name := "query",
422
+ placeholder := "Type to search...",
423
+ hxPost := "/api/search",
424
+ hxTrigger := HxTrigger.input.delay(500.millis).changed,
425
+ hxParams := HxParams.only("query"),
426
+ hxTarget := HxTarget.next("div"),
427
+ hxSwap := HxSwap.InnerHTML
428
+ )
429
+ println(s" Trigger: ${HxTrigger.input.delay(500.millis).changed.render}")
430
+ println(s" Element: $debouncedSearch\n")
431
+
432
+ // Example 2: Rate-limited input with throttle
433
+ println("2. Rate-limited input events (at most 1 request per second):")
434
+ val throttledStatus = input(
435
+ id := "status",
436
+ `type` := "text",
437
+ hxPost := "/api/status",
438
+ hxTrigger := HxTrigger.input.throttle(1.second),
439
+ hxSwap := HxSwap.OuterHTML,
440
+ placeholder := "Type slowly..."
441
+ )
442
+ println(s" Trigger: ${HxTrigger.input.throttle(1.second).render}")
443
+ println(s" Element: $throttledStatus\n")
444
+
445
+ // Example 3: Multiple triggers with polling and user action
446
+ println("3. Dual triggers: user click OR automatic polling:")
447
+ val autoRefresh = div(
448
+ id := "data",
449
+ hxGet := "/api/data",
450
+ hxTrigger := HxTrigger(
451
+ HxTrigger.click,
452
+ HxTrigger.every(30.seconds)
453
+ ),
454
+ hxSwap := HxSwap.InnerHTML.transition.settle(300.millis),
455
+ "Data refreshes on click or every 30s"
456
+ )
457
+ println(s" Triggers: ${HxTrigger(HxTrigger.click, HxTrigger.every(30.seconds)).render}")
458
+ println(s" Element: $autoRefresh\n")
459
+
460
+ // Example 4: Request queuing strategy
461
+ println("4. Queue strategy: keep only most recent request:")
462
+ val queuedInput = input(
463
+ `type` := "text",
464
+ placeholder := "Fast typing...",
465
+ hxPost := "/api/validate",
466
+ hxTrigger := HxTrigger.input.delay(300.millis).queue(HxTrigger.QueueStrategy.Last),
467
+ hxTarget := HxTarget.next("span"),
468
+ hxSwap := HxSwap.InnerHTML
469
+ )
470
+ println(s" Trigger: ${HxTrigger.input.delay(300.millis).queue(HxTrigger.QueueStrategy.Last).render}")
471
+ println(s" Element: $queuedInput\n")
472
+
473
+ // Example 5: Animation with timing modifiers
474
+ println("5. CSS transition with settle delay:")
475
+ val animatedSwap = button(
476
+ hxPost := "/api/action",
477
+ hxTrigger := HxTrigger.click,
478
+ hxSwap := HxSwap.InnerHTML.transition.settle(400.millis),
479
+ hxTarget := HxTarget.This,
480
+ "Click for animated response"
481
+ )
482
+ println(s" Swap: ${HxSwap.InnerHTML.transition.settle(400.millis).render}")
483
+ println(s" Element: $animatedSwap\n")
484
+
485
+ // Example 6: Event delegation with target modifier
486
+ println("6. Event delegation: parent listens to child clicks:")
487
+ val eventDelegation = ul(
488
+ id := "items",
489
+ hxPost := "/api/item-clicked",
490
+ hxTrigger := HxTrigger.click.target("li"),
491
+ hxSwap := HxSwap.InnerHTML,
492
+ li("Item 1"),
493
+ li("Item 2"),
494
+ li("Item 3")
495
+ )
496
+ println(s""" Trigger: ${HxTrigger.click.target("li").render}""")
497
+ println(s" Element: $eventDelegation\n")
498
+
499
+ // Example 7: JavaScript filtering
500
+ println("7. JavaScript filter: only trigger if condition met:")
501
+ val filteredTrigger = input(
502
+ `type` := "text",
503
+ placeholder := "Min 3 characters...",
504
+ hxPost := "/api/search",
505
+ hxTrigger := HxTrigger.input.filter(Js("event.target.value.length > 2")),
506
+ hxTarget := HxTarget.next("div"),
507
+ hxSwap := HxSwap.InnerHTML
508
+ )
509
+ println(s""" Trigger: ${HxTrigger.input.filter(Js("event.target.value.length > 2")).render}""")
510
+ println(s" Element: $filteredTrigger\n")
511
+
512
+ // Example 8: Source control with from modifier
513
+ println("8. Source control: button listens to input events:")
514
+ val sourceControl = div(
515
+ input(
516
+ id := "query-input",
517
+ `type` := "text",
518
+ placeholder := "Type here..."
519
+ ),
520
+ button(
521
+ id := "search-btn",
522
+ hxPost := "/api/search",
523
+ hxTrigger := HxTrigger.click.from("#query-input"),
524
+ hxTarget := HxTarget.next("div"),
525
+ "Search"
526
+ )
527
+ )
528
+ println(s""" Button trigger: ${HxTrigger.click.from("#query-input").render}""")
529
+ println(s" Element: $sourceControl\n")
530
+
531
+ // Example 9: Intersection observer with threshold
532
+ println("9. Lazy loading with intersection observer:")
533
+ val lazyLoad = img(
534
+ src := "/placeholder.jpg",
535
+ hxGet := "/api/lazy-image",
536
+ hxTrigger := HxTrigger.intersect.threshold(0.5),
537
+ hxSwap := HxSwap.OuterHTML,
538
+ alt := "Lazy loaded image"
539
+ )
540
+ println(s" Trigger: ${HxTrigger.intersect.threshold(0.5).render}")
541
+ println(s" Element: $lazyLoad\n")
542
+
543
+ // Example 10: Complex modifier chain
544
+ println("10. Complex modifier chain - multiple modifiers:")
545
+ val complexChain = input(
546
+ `type` := "text",
547
+ name := "search",
548
+ placeholder := "Advanced search",
549
+ hxPost := "/api/search",
550
+ hxTrigger := HxTrigger.input
551
+ .delay(500.millis)
552
+ .throttle(1.second)
553
+ .changed
554
+ .filter(Js("event.target.value.trim().length > 0")),
555
+ hxParams := HxParams.only("search"),
556
+ hxTarget := HxTarget.closest(".search-results"),
557
+ hxSwap := HxSwap.InnerHTML.transition.settle(250.millis)
558
+ )
559
+ println(
560
+ s""" Complex trigger: ${HxTrigger.input
561
+ .delay(500.millis)
562
+ .throttle(1.second)
563
+ .changed
564
+ .filter(Js("event.target.value.trim().length > 0"))
565
+ .render}"""
566
+ )
567
+ println(s" Swap: ${HxSwap.InnerHTML.transition.settle(250.millis).render}")
568
+ println(s" Element: $complexChain\n")
569
+
570
+ // Example 11: Scroll positioning after swap
571
+ println("11. Scroll to bottom after appending messages:")
572
+ val messageList = div(
573
+ id := "messages",
574
+ hxGet := "/api/messages",
575
+ hxTrigger := HxTrigger.every(2.seconds),
576
+ hxSwap := HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom).show(HxSwap.ShowPosition.Bottom),
577
+ "Messages will be appended and scrolled into view"
578
+ )
579
+ println(s" Swap: ${HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom).show(HxSwap.ShowPosition.Bottom).render}")
580
+ println(s" Element: $messageList\n")
581
+
582
+ println("✓ Advanced pattern examples complete")
583
+ }
584
+ }
585
+ ```
586
+
587
+ ([source](https://github.com/zio/zio-blocks/blob/main/zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/AdvancedPatterns.scala))
588
+
589
+ Run this example with the following command:
590
+
591
+ ```bash
592
+ sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.AdvancedPatterns"
593
+ ```
594
+
595
+ ### Complete Example
596
+
597
+ A realistic e-commerce search and filtering interface combining multiple HTMX attributes: debounced search input, live category filtering, paginated results, out-of-band notifications, and dynamic UI updates. Demonstrates how types compose to create a type-safe, interactive UI. Here is the source code:
598
+
599
+ ```scala title="zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/CompleteExample.scala"
600
+ package zioBlocksHtmx
601
+
602
+ import zio.blocks.html._
603
+ import zio.http.htmx._
604
+ import scala.concurrent.duration.DurationInt
605
+
606
+ /**
607
+ * HTMX DSL — Complete Realistic Example
608
+ *
609
+ * A real-world e-commerce search and filtering interface combining multiple
610
+ * HTMX attributes: debounced search input, live category filtering, paginated
611
+ * results, out-of-band notifications, and dynamic UI updates. Demonstrates how
612
+ * types compose to create a type-safe, interactive UI.
613
+ *
614
+ * Run with: sbt "zio-blocks-htmx-examples/runMain
615
+ * zioBlocksHtmx.CompleteExample"
616
+ */
617
+ object CompleteExample {
618
+ def main(args: Array[String]): Unit = {
619
+ println("=== HTMX Complete Realistic Example ===\n")
620
+
621
+ println("Building a type-safe e-commerce search interface...\n")
622
+
623
+ // Part 1: Search input with debounce and parameter filtering
624
+ val searchInput = input(
625
+ `type` := "text",
626
+ name := "query",
627
+ id := "search-query",
628
+ placeholder := "Search products...",
629
+ hxPost := "/api/search",
630
+ hxTrigger := HxTrigger.input
631
+ .delay(500.millis)
632
+ .changed,
633
+ hxParams := HxParams.only("query", "category", "page"),
634
+ hxTarget := HxTarget.find(".results-container"),
635
+ hxSwap := HxSwap.InnerHTML.transition.settle(250.millis)
636
+ )
637
+
638
+ println("1. Search Input:")
639
+ println(s" Placeholder: Search products")
640
+ println(s" Trigger: input with 500ms delay + changed")
641
+ println(s" Params: query, category, page (others excluded)")
642
+ println(s" Swap: InnerHTML with transition and 250ms settle")
643
+ println(s" Rendered: $searchInput\n")
644
+
645
+ // Part 2: Category filter with instant response
646
+ val categoryFilter = select(
647
+ name := "category",
648
+ id := "category-filter",
649
+ hxPost := "/api/search",
650
+ hxTrigger := HxTrigger.change,
651
+ hxTarget := HxTarget.find(".results-container"),
652
+ hxSwap := HxSwap.InnerHTML.transition,
653
+ option(value := "all", selected, "All Categories"),
654
+ option(value := "electronics", "Electronics"),
655
+ option(value := "books", "Books"),
656
+ option(value := "clothing", "Clothing")
657
+ )
658
+
659
+ println("2. Category Filter:")
660
+ println(s" Trigger: change event (no delay)")
661
+ println(s" Target: .results-container")
662
+ println(s" Rendered: $categoryFilter\n")
663
+
664
+ // Part 3: Results container with dynamic content
665
+ val resultsContainer = div(
666
+ id := "results",
667
+ `class` := "results-container",
668
+ hxGet := "/api/search",
669
+ hxTrigger := HxTrigger.load,
670
+ hxTarget := HxTarget.This,
671
+ hxSwap := HxSwap.InnerHTML,
672
+ div("Loading results...")
673
+ )
674
+
675
+ println("3. Results Container:")
676
+ println(s" Trigger: load (fetches initial results)")
677
+ println(s" Swap: InnerHTML")
678
+ println(s" Rendered: $resultsContainer\n")
679
+
680
+ // Part 4: Pagination buttons
681
+ val prevButton = button(
682
+ id := "btn-prev",
683
+ hxPost := "/api/search",
684
+ hxTrigger := HxTrigger.click,
685
+ hxParams := HxParams.only("query", "category", "page"),
686
+ hxTarget := HxTarget.find(".results-container"),
687
+ hxSwap := HxSwap.InnerHTML.scroll(HxSwap.ScrollPosition.Top),
688
+ "← Previous"
689
+ )
690
+
691
+ val nextButton = button(
692
+ id := "btn-next",
693
+ hxPost := "/api/search",
694
+ hxTrigger := HxTrigger.click,
695
+ hxParams := HxParams.only("query", "category", "page"),
696
+ hxTarget := HxTarget.find(".results-container"),
697
+ hxSwap := HxSwap.InnerHTML.scroll(HxSwap.ScrollPosition.Top),
698
+ "Next →"
699
+ )
700
+
701
+ println("4. Pagination Buttons:")
702
+ println(s" Behavior: POST request, replace results, scroll to top")
703
+ println(s" Prev: $prevButton")
704
+ println(s" Next: $nextButton\n")
705
+
706
+ // Part 5: Status badge with out-of-band updates
707
+ val statusBadge = div(
708
+ id := "search-status",
709
+ hxSwapOob := HxSwapOob.using(HxSwap.InnerHTML),
710
+ "Ready"
711
+ )
712
+
713
+ println("5. Status Badge (Out-of-Band):")
714
+ println(s" Updates separately from main results")
715
+ println(s" Server response can include status updates")
716
+ println(s" Rendered: $statusBadge\n")
717
+
718
+ // Part 6: Complete search form integrating all parts
719
+ val wholeForm = div(
720
+ id := "search-interface",
721
+ h2("Product Search"),
722
+ div(
723
+ `class` := "search-controls",
724
+ div(
725
+ label(`for` := "search-query", "Search:"),
726
+ searchInput
727
+ ),
728
+ div(
729
+ label(`for` := "category-filter", "Category:"),
730
+ categoryFilter
731
+ )
732
+ ),
733
+ resultsContainer,
734
+ div(
735
+ `class` := "pagination-controls",
736
+ prevButton,
737
+ span(id := "page-info", "Page 1"),
738
+ nextButton
739
+ ),
740
+ statusBadge
741
+ )
742
+
743
+ println("6. Complete Search Form:")
744
+ println(s" Integrates search input, category filter, results, pagination")
745
+ println(s" Form structure: $wholeForm\n")
746
+
747
+ // Part 7: Advanced example - filtering based on price range
748
+ val priceRangeInput = input(
749
+ `type` := "range",
750
+ name := "max-price",
751
+ id := "price-slider",
752
+ min := "0",
753
+ max := "1000",
754
+ value := "1000",
755
+ hxPost := "/api/search",
756
+ hxTrigger := HxTrigger.change.throttle(500.millis),
757
+ hxParams := HxParams.only("query", "category", "max-price"),
758
+ hxTarget := HxTarget.find(".results-container"),
759
+ hxSwap := HxSwap.InnerHTML.transition
760
+ )
761
+
762
+ println("7. Advanced - Price Range Filter:")
763
+ println(s" Trigger: change with 500ms throttle")
764
+ println(s" Prevents excessive requests while dragging slider")
765
+ println(s" Rendered: $priceRangeInput\n")
766
+
767
+ // Part 8: Bonus - polling for real-time updates
768
+ val liveUpdatesDiv = div(
769
+ id := "live-updates",
770
+ hxGet := "/api/trending",
771
+ hxTrigger := HxTrigger.every(10.seconds),
772
+ hxSwap := HxSwap.InnerHTML,
773
+ "Trending products..."
774
+ )
775
+
776
+ println("8. Bonus - Live Updates Panel:")
777
+ println(s" Polls /api/trending every 10 seconds")
778
+ println(s" Keeps trending products fresh without user action")
779
+ println(s" Rendered: $liveUpdatesDiv\n")
780
+
781
+ println("=== Type Safety in Action ===")
782
+ println("All HTMX attributes are compile-time checked:")
783
+ println("✓ HxTrigger validates event names and modifiers")
784
+ println("✓ HxSwap ensures correct strategy and modifier combinations")
785
+ println("✓ HxParams prevents typos in parameter names")
786
+ println("✓ HxTarget validates selector syntax")
787
+ println("✓ No raw strings = no HTMX syntax errors at runtime")
788
+ println("\n✓ Complete realistic example built successfully")
789
+ }
790
+ }
791
+ ```
792
+
793
+ ([source](https://github.com/zio/zio-blocks/blob/main/zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/CompleteExample.scala))
794
+
795
+ Run this example with the following command:
796
+
797
+ ```bash
798
+ sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.CompleteExample"
799
+ ```
800
+
801
+ **3. Or compile all examples at once:**
802
+
803
+ To compile all example sources without running them, use:
804
+
805
+ ```bash
806
+ sbt "zio-blocks-htmx-examples/compile"
807
+ ```