@zio.dev/zio-blocks 0.0.33 → 0.0.51

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 (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,855 @@
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.51"
56
+
57
+ // Scala.js
58
+ libraryDependencies += "dev.zio" %%% "zio-blocks-http-htmx" % "0.0.51"
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
+ "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` 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
+ /*
256
+ * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
257
+ *
258
+ * Licensed under the Apache License, Version 2.0 (the "License");
259
+ * you may not use this file except in compliance with the License.
260
+ * You may obtain a copy of the License at
261
+ *
262
+ * http://www.apache.org/licenses/LICENSE-2.0
263
+ *
264
+ * Unless required by applicable law or agreed to in writing, software
265
+ * distributed under the License is distributed on an "AS IS" BASIS,
266
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
267
+ * See the License for the specific language governing permissions and
268
+ * limitations under the License.
269
+ */
270
+
271
+ package zioBlocksHtmx
272
+
273
+ import zio.blocks.html._
274
+ import zio.http.htmx._
275
+ import scala.concurrent.duration._
276
+
277
+ /**
278
+ * HTMX DSL — Basic Usage
279
+ *
280
+ * Demonstrates fundamental HTMX attribute construction: triggering requests
281
+ * (hxPost, hxGet), swapping strategies (InnerHTML, OuterHTML), and target
282
+ * selection (This, closest, find). Shows how the typed DSL ensures correct HTMX
283
+ * syntax at compile time.
284
+ *
285
+ * Run with: sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.BasicUsage"
286
+ */
287
+ object BasicUsage {
288
+ def main(args: Array[String]): Unit = {
289
+ println("=== HTMX Basic Usage Examples ===\n")
290
+
291
+ // Example 1: Simple click trigger with POST
292
+ println("1. Click trigger with POST request:")
293
+ val clickButton = button(
294
+ hxPost := "/api/action",
295
+ hxTrigger := HxTrigger.click,
296
+ "Click me"
297
+ )
298
+ println(s" Rendered: $clickButton\n")
299
+
300
+ // Example 2: Input with GET request
301
+ println("2. Input with debounced GET request:")
302
+ val searchInput = input(
303
+ `type` := "text",
304
+ placeholder := "Search...",
305
+ hxGet := "/api/search",
306
+ hxTrigger := HxTrigger.input.delay(500.millis),
307
+ hxTarget := HxTarget.next("div")
308
+ )
309
+ println(s" Rendered: $searchInput\n")
310
+
311
+ // Example 3: Different swap strategies
312
+ println("3. Swap strategies:")
313
+ val innerHTMLDiv = div(
314
+ id := "content",
315
+ hxGet := "/api/content",
316
+ hxSwap := HxSwap.InnerHTML,
317
+ "Click to load inner content"
318
+ )
319
+ val outerHTMLDiv = div(
320
+ id := "card",
321
+ hxPost := "/api/replace",
322
+ hxSwap := HxSwap.OuterHTML,
323
+ "This entire div will be replaced"
324
+ )
325
+ val appendDiv = div(
326
+ id := "messages",
327
+ hxGet := "/api/new-message",
328
+ hxSwap := HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom),
329
+ "New messages will be appended"
330
+ )
331
+ println(s" InnerHTML: ${innerHTMLDiv}")
332
+ println(s" OuterHTML: ${outerHTMLDiv}")
333
+ println(s" Append with scroll: ${appendDiv}\n")
334
+
335
+ // Example 4: Target selection patterns
336
+ println("4. Target selection:")
337
+ val targetThis = button(
338
+ hxPost := "/api/toggle",
339
+ hxTarget := HxTarget.This,
340
+ "Toggle this button"
341
+ )
342
+ val targetClosest = button(
343
+ hxPost := "/api/validate",
344
+ hxTarget := HxTarget.closest("form"),
345
+ "Validate form"
346
+ )
347
+ val targetFind = div(
348
+ hxGet := "/api/update",
349
+ hxTarget := HxTarget.find(".result"),
350
+ "Content with .result child"
351
+ )
352
+ println(s" This: $targetThis")
353
+ println(s" Closest: $targetClosest")
354
+ println(s" Find: $targetFind\n")
355
+
356
+ // Example 5: Form submission with parameters
357
+ println("5. Form submission with selective parameters:")
358
+ val searchForm = form(
359
+ hxPost := "/api/search",
360
+ hxTrigger := HxTrigger.submit,
361
+ hxParams := HxParams.only("query", "page"),
362
+ hxSwap := HxSwap.InnerHTML,
363
+ input(`type` := "text", name := "query", placeholder := "Query"),
364
+ input(`type` := "hidden", name := "page", value := "1"),
365
+ input(`type` := "hidden", name := "unused", value := "ignored"),
366
+ button(`type` := "submit", "Search")
367
+ )
368
+ println(s" Form with selective params: $searchForm\n")
369
+
370
+ // Example 6: Load event with once modifier
371
+ println("6. Load event with once modifier:")
372
+ val initialLoadDiv = div(
373
+ id := "initial",
374
+ hxGet := "/api/bootstrap-data",
375
+ hxTrigger := HxTrigger.load.once,
376
+ hxSwap := HxSwap.InnerHTML,
377
+ "Loading initial data..."
378
+ )
379
+ println(s" Load once: $initialLoadDiv\n")
380
+
381
+ // Example 7: Change event on select
382
+ println("7. Change event on select element:")
383
+ val selectDropdown = select(
384
+ name := "category",
385
+ hxPost := "/api/category-changed",
386
+ hxTrigger := HxTrigger.change,
387
+ hxTarget := HxTarget.next("div"),
388
+ option(value := "all", "All Categories"),
389
+ option(value := "news", "News"),
390
+ option(value := "updates", "Updates")
391
+ )
392
+ println(s" Select with change: $selectDropdown\n")
393
+
394
+ println("✓ Basic usage examples complete")
395
+ }
396
+ }
397
+ ```
398
+
399
+ ([source](https://github.com/zio/zio-blocks/blob/main/zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/BasicUsage.scala))
400
+
401
+ Run this example with the following command:
402
+
403
+ ```bash
404
+ sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.BasicUsage"
405
+ ```
406
+
407
+ ### Advanced Patterns
408
+
409
+ 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:
410
+
411
+ ```scala title="zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/AdvancedPatterns.scala"
412
+ /*
413
+ * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
414
+ *
415
+ * Licensed under the Apache License, Version 2.0 (the "License");
416
+ * you may not use this file except in compliance with the License.
417
+ * You may obtain a copy of the License at
418
+ *
419
+ * http://www.apache.org/licenses/LICENSE-2.0
420
+ *
421
+ * Unless required by applicable law or agreed to in writing, software
422
+ * distributed under the License is distributed on an "AS IS" BASIS,
423
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
424
+ * See the License for the specific language governing permissions and
425
+ * limitations under the License.
426
+ */
427
+
428
+ package zioBlocksHtmx
429
+
430
+ import zio.blocks.html._
431
+ import zio.http.htmx._
432
+ import scala.concurrent.duration._
433
+
434
+ /**
435
+ * HTMX DSL — Advanced Patterns
436
+ *
437
+ * Demonstrates complex HTMX interactions: combining multiple triggers, chaining
438
+ * modifiers, controlling request queuing, animation with transitions, and
439
+ * JavaScript-based filtering. Shows how modifiers compose to create
440
+ * sophisticated client-side behaviors.
441
+ *
442
+ * Run with: sbt "zio-blocks-htmx-examples/runMain
443
+ * zioBlocksHtmx.AdvancedPatterns"
444
+ */
445
+ object AdvancedPatterns {
446
+ def main(args: Array[String]): Unit = {
447
+ println("=== HTMX Advanced Patterns ===\n")
448
+
449
+ // Example 1: Debounced search with state modifier
450
+ println("1. Debounced search with value-changed detection:")
451
+ val debouncedSearch = input(
452
+ `type` := "text",
453
+ name := "query",
454
+ placeholder := "Type to search...",
455
+ hxPost := "/api/search",
456
+ hxTrigger := HxTrigger.input.delay(500.millis).changed,
457
+ hxParams := HxParams.only("query"),
458
+ hxTarget := HxTarget.next("div"),
459
+ hxSwap := HxSwap.InnerHTML
460
+ )
461
+ println(s" Trigger: ${HxTrigger.input.delay(500.millis).changed.render}")
462
+ println(s" Element: $debouncedSearch\n")
463
+
464
+ // Example 2: Rate-limited input with throttle
465
+ println("2. Rate-limited input events (at most 1 request per second):")
466
+ val throttledStatus = input(
467
+ id := "status",
468
+ `type` := "text",
469
+ hxPost := "/api/status",
470
+ hxTrigger := HxTrigger.input.throttle(1.second),
471
+ hxSwap := HxSwap.OuterHTML,
472
+ placeholder := "Type slowly..."
473
+ )
474
+ println(s" Trigger: ${HxTrigger.input.throttle(1.second).render}")
475
+ println(s" Element: $throttledStatus\n")
476
+
477
+ // Example 3: Multiple triggers with polling and user action
478
+ println("3. Dual triggers: user click OR automatic polling:")
479
+ val autoRefresh = div(
480
+ id := "data",
481
+ hxGet := "/api/data",
482
+ hxTrigger := HxTrigger(
483
+ HxTrigger.click,
484
+ HxTrigger.every(30.seconds)
485
+ ),
486
+ hxSwap := HxSwap.InnerHTML.transition.settle(300.millis),
487
+ "Data refreshes on click or every 30s"
488
+ )
489
+ println(s" Triggers: ${HxTrigger(HxTrigger.click, HxTrigger.every(30.seconds)).render}")
490
+ println(s" Element: $autoRefresh\n")
491
+
492
+ // Example 4: Request queuing strategy
493
+ println("4. Queue strategy: keep only most recent request:")
494
+ val queuedInput = input(
495
+ `type` := "text",
496
+ placeholder := "Fast typing...",
497
+ hxPost := "/api/validate",
498
+ hxTrigger := HxTrigger.input.delay(300.millis).queue(HxTrigger.QueueStrategy.Last),
499
+ hxTarget := HxTarget.next("span"),
500
+ hxSwap := HxSwap.InnerHTML
501
+ )
502
+ println(s" Trigger: ${HxTrigger.input.delay(300.millis).queue(HxTrigger.QueueStrategy.Last).render}")
503
+ println(s" Element: $queuedInput\n")
504
+
505
+ // Example 5: Animation with timing modifiers
506
+ println("5. CSS transition with settle delay:")
507
+ val animatedSwap = button(
508
+ hxPost := "/api/action",
509
+ hxTrigger := HxTrigger.click,
510
+ hxSwap := HxSwap.InnerHTML.transition.settle(400.millis),
511
+ hxTarget := HxTarget.This,
512
+ "Click for animated response"
513
+ )
514
+ println(s" Swap: ${HxSwap.InnerHTML.transition.settle(400.millis).render}")
515
+ println(s" Element: $animatedSwap\n")
516
+
517
+ // Example 6: Event delegation with target modifier
518
+ println("6. Event delegation: parent listens to child clicks:")
519
+ val eventDelegation = ul(
520
+ id := "items",
521
+ hxPost := "/api/item-clicked",
522
+ hxTrigger := HxTrigger.click.target("li"),
523
+ hxSwap := HxSwap.InnerHTML,
524
+ li("Item 1"),
525
+ li("Item 2"),
526
+ li("Item 3")
527
+ )
528
+ println(s""" Trigger: ${HxTrigger.click.target("li").render}""")
529
+ println(s" Element: $eventDelegation\n")
530
+
531
+ // Example 7: JavaScript filtering
532
+ println("7. JavaScript filter: only trigger if condition met:")
533
+ val filteredTrigger = input(
534
+ `type` := "text",
535
+ placeholder := "Min 3 characters...",
536
+ hxPost := "/api/search",
537
+ hxTrigger := HxTrigger.input.filter(Js("event.target.value.length > 2")),
538
+ hxTarget := HxTarget.next("div"),
539
+ hxSwap := HxSwap.InnerHTML
540
+ )
541
+ println(s""" Trigger: ${HxTrigger.input.filter(Js("event.target.value.length > 2")).render}""")
542
+ println(s" Element: $filteredTrigger\n")
543
+
544
+ // Example 8: Source control with from modifier
545
+ println("8. Source control: button listens to input events:")
546
+ val sourceControl = div(
547
+ input(
548
+ id := "query-input",
549
+ `type` := "text",
550
+ placeholder := "Type here..."
551
+ ),
552
+ button(
553
+ id := "search-btn",
554
+ hxPost := "/api/search",
555
+ hxTrigger := HxTrigger.click.from("#query-input"),
556
+ hxTarget := HxTarget.next("div"),
557
+ "Search"
558
+ )
559
+ )
560
+ println(s""" Button trigger: ${HxTrigger.click.from("#query-input").render}""")
561
+ println(s" Element: $sourceControl\n")
562
+
563
+ // Example 9: Intersection observer with threshold
564
+ println("9. Lazy loading with intersection observer:")
565
+ val lazyLoad = img(
566
+ src := "/placeholder.jpg",
567
+ hxGet := "/api/lazy-image",
568
+ hxTrigger := HxTrigger.intersect.threshold(0.5),
569
+ hxSwap := HxSwap.OuterHTML,
570
+ alt := "Lazy loaded image"
571
+ )
572
+ println(s" Trigger: ${HxTrigger.intersect.threshold(0.5).render}")
573
+ println(s" Element: $lazyLoad\n")
574
+
575
+ // Example 10: Complex modifier chain
576
+ println("10. Complex modifier chain - multiple modifiers:")
577
+ val complexChain = input(
578
+ `type` := "text",
579
+ name := "search",
580
+ placeholder := "Advanced search",
581
+ hxPost := "/api/search",
582
+ hxTrigger := HxTrigger.input
583
+ .delay(500.millis)
584
+ .throttle(1.second)
585
+ .changed
586
+ .filter(Js("event.target.value.trim().length > 0")),
587
+ hxParams := HxParams.only("search"),
588
+ hxTarget := HxTarget.closest(".search-results"),
589
+ hxSwap := HxSwap.InnerHTML.transition.settle(250.millis)
590
+ )
591
+ println(
592
+ s""" Complex trigger: ${HxTrigger.input
593
+ .delay(500.millis)
594
+ .throttle(1.second)
595
+ .changed
596
+ .filter(Js("event.target.value.trim().length > 0"))
597
+ .render}"""
598
+ )
599
+ println(s" Swap: ${HxSwap.InnerHTML.transition.settle(250.millis).render}")
600
+ println(s" Element: $complexChain\n")
601
+
602
+ // Example 11: Scroll positioning after swap
603
+ println("11. Scroll to bottom after appending messages:")
604
+ val messageList = div(
605
+ id := "messages",
606
+ hxGet := "/api/messages",
607
+ hxTrigger := HxTrigger.every(2.seconds),
608
+ hxSwap := HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom).show(HxSwap.ShowPosition.Bottom),
609
+ "Messages will be appended and scrolled into view"
610
+ )
611
+ println(s" Swap: ${HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom).show(HxSwap.ShowPosition.Bottom).render}")
612
+ println(s" Element: $messageList\n")
613
+
614
+ println("✓ Advanced pattern examples complete")
615
+ }
616
+ }
617
+ ```
618
+
619
+ ([source](https://github.com/zio/zio-blocks/blob/main/zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/AdvancedPatterns.scala))
620
+
621
+ Run this example with the following command:
622
+
623
+ ```bash
624
+ sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.AdvancedPatterns"
625
+ ```
626
+
627
+ ### Complete Example
628
+
629
+ 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:
630
+
631
+ ```scala title="zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/CompleteExample.scala"
632
+ /*
633
+ * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
634
+ *
635
+ * Licensed under the Apache License, Version 2.0 (the "License");
636
+ * you may not use this file except in compliance with the License.
637
+ * You may obtain a copy of the License at
638
+ *
639
+ * http://www.apache.org/licenses/LICENSE-2.0
640
+ *
641
+ * Unless required by applicable law or agreed to in writing, software
642
+ * distributed under the License is distributed on an "AS IS" BASIS,
643
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
644
+ * See the License for the specific language governing permissions and
645
+ * limitations under the License.
646
+ */
647
+
648
+ package zioBlocksHtmx
649
+
650
+ import zio.blocks.html._
651
+ import zio.http.htmx._
652
+ import scala.concurrent.duration.DurationInt
653
+
654
+ /**
655
+ * HTMX DSL — Complete Realistic Example
656
+ *
657
+ * A real-world e-commerce search and filtering interface combining multiple
658
+ * HTMX attributes: debounced search input, live category filtering, paginated
659
+ * results, out-of-band notifications, and dynamic UI updates. Demonstrates how
660
+ * types compose to create a type-safe, interactive UI.
661
+ *
662
+ * Run with: sbt "zio-blocks-htmx-examples/runMain
663
+ * zioBlocksHtmx.CompleteExample"
664
+ */
665
+ object CompleteExample {
666
+ def main(args: Array[String]): Unit = {
667
+ println("=== HTMX Complete Realistic Example ===\n")
668
+
669
+ println("Building a type-safe e-commerce search interface...\n")
670
+
671
+ // Part 1: Search input with debounce and parameter filtering
672
+ val searchInput = input(
673
+ `type` := "text",
674
+ name := "query",
675
+ id := "search-query",
676
+ placeholder := "Search products...",
677
+ hxPost := "/api/search",
678
+ hxTrigger := HxTrigger.input
679
+ .delay(500.millis)
680
+ .changed,
681
+ hxParams := HxParams.only("query", "category", "page"),
682
+ hxTarget := HxTarget.find(".results-container"),
683
+ hxSwap := HxSwap.InnerHTML.transition.settle(250.millis)
684
+ )
685
+
686
+ println("1. Search Input:")
687
+ println(s" Placeholder: Search products")
688
+ println(s" Trigger: input with 500ms delay + changed")
689
+ println(s" Params: query, category, page (others excluded)")
690
+ println(s" Swap: InnerHTML with transition and 250ms settle")
691
+ println(s" Rendered: $searchInput\n")
692
+
693
+ // Part 2: Category filter with instant response
694
+ val categoryFilter = select(
695
+ name := "category",
696
+ id := "category-filter",
697
+ hxPost := "/api/search",
698
+ hxTrigger := HxTrigger.change,
699
+ hxTarget := HxTarget.find(".results-container"),
700
+ hxSwap := HxSwap.InnerHTML.transition,
701
+ option(value := "all", selected, "All Categories"),
702
+ option(value := "electronics", "Electronics"),
703
+ option(value := "books", "Books"),
704
+ option(value := "clothing", "Clothing")
705
+ )
706
+
707
+ println("2. Category Filter:")
708
+ println(s" Trigger: change event (no delay)")
709
+ println(s" Target: .results-container")
710
+ println(s" Rendered: $categoryFilter\n")
711
+
712
+ // Part 3: Results container with dynamic content
713
+ val resultsContainer = div(
714
+ id := "results",
715
+ `class` := "results-container",
716
+ hxGet := "/api/search",
717
+ hxTrigger := HxTrigger.load,
718
+ hxTarget := HxTarget.This,
719
+ hxSwap := HxSwap.InnerHTML,
720
+ div("Loading results...")
721
+ )
722
+
723
+ println("3. Results Container:")
724
+ println(s" Trigger: load (fetches initial results)")
725
+ println(s" Swap: InnerHTML")
726
+ println(s" Rendered: $resultsContainer\n")
727
+
728
+ // Part 4: Pagination buttons
729
+ val prevButton = button(
730
+ id := "btn-prev",
731
+ hxPost := "/api/search",
732
+ hxTrigger := HxTrigger.click,
733
+ hxParams := HxParams.only("query", "category", "page"),
734
+ hxTarget := HxTarget.find(".results-container"),
735
+ hxSwap := HxSwap.InnerHTML.scroll(HxSwap.ScrollPosition.Top),
736
+ "← Previous"
737
+ )
738
+
739
+ val nextButton = button(
740
+ id := "btn-next",
741
+ hxPost := "/api/search",
742
+ hxTrigger := HxTrigger.click,
743
+ hxParams := HxParams.only("query", "category", "page"),
744
+ hxTarget := HxTarget.find(".results-container"),
745
+ hxSwap := HxSwap.InnerHTML.scroll(HxSwap.ScrollPosition.Top),
746
+ "Next →"
747
+ )
748
+
749
+ println("4. Pagination Buttons:")
750
+ println(s" Behavior: POST request, replace results, scroll to top")
751
+ println(s" Prev: $prevButton")
752
+ println(s" Next: $nextButton\n")
753
+
754
+ // Part 5: Status badge with out-of-band updates
755
+ val statusBadge = div(
756
+ id := "search-status",
757
+ hxSwapOob := HxSwapOob.using(HxSwap.InnerHTML),
758
+ "Ready"
759
+ )
760
+
761
+ println("5. Status Badge (Out-of-Band):")
762
+ println(s" Updates separately from main results")
763
+ println(s" Server response can include status updates")
764
+ println(s" Rendered: $statusBadge\n")
765
+
766
+ // Part 6: Complete search form integrating all parts
767
+ val wholeForm = div(
768
+ id := "search-interface",
769
+ h2("Product Search"),
770
+ div(
771
+ `class` := "search-controls",
772
+ div(
773
+ label(`for` := "search-query", "Search:"),
774
+ searchInput
775
+ ),
776
+ div(
777
+ label(`for` := "category-filter", "Category:"),
778
+ categoryFilter
779
+ )
780
+ ),
781
+ resultsContainer,
782
+ div(
783
+ `class` := "pagination-controls",
784
+ prevButton,
785
+ span(id := "page-info", "Page 1"),
786
+ nextButton
787
+ ),
788
+ statusBadge
789
+ )
790
+
791
+ println("6. Complete Search Form:")
792
+ println(s" Integrates search input, category filter, results, pagination")
793
+ println(s" Form structure: $wholeForm\n")
794
+
795
+ // Part 7: Advanced example - filtering based on price range
796
+ val priceRangeInput = input(
797
+ `type` := "range",
798
+ name := "max-price",
799
+ id := "price-slider",
800
+ min := "0",
801
+ max := "1000",
802
+ value := "1000",
803
+ hxPost := "/api/search",
804
+ hxTrigger := HxTrigger.change.throttle(500.millis),
805
+ hxParams := HxParams.only("query", "category", "max-price"),
806
+ hxTarget := HxTarget.find(".results-container"),
807
+ hxSwap := HxSwap.InnerHTML.transition
808
+ )
809
+
810
+ println("7. Advanced - Price Range Filter:")
811
+ println(s" Trigger: change with 500ms throttle")
812
+ println(s" Prevents excessive requests while dragging slider")
813
+ println(s" Rendered: $priceRangeInput\n")
814
+
815
+ // Part 8: Bonus - polling for real-time updates
816
+ val liveUpdatesDiv = div(
817
+ id := "live-updates",
818
+ hxGet := "/api/trending",
819
+ hxTrigger := HxTrigger.every(10.seconds),
820
+ hxSwap := HxSwap.InnerHTML,
821
+ "Trending products..."
822
+ )
823
+
824
+ println("8. Bonus - Live Updates Panel:")
825
+ println(s" Polls /api/trending every 10 seconds")
826
+ println(s" Keeps trending products fresh without user action")
827
+ println(s" Rendered: $liveUpdatesDiv\n")
828
+
829
+ println("=== Type Safety in Action ===")
830
+ println("All HTMX attributes are compile-time checked:")
831
+ println("✓ HxTrigger validates event names and modifiers")
832
+ println("✓ HxSwap ensures correct strategy and modifier combinations")
833
+ println("✓ HxParams prevents typos in parameter names")
834
+ println("✓ HxTarget validates selector syntax")
835
+ println("✓ No raw strings = no HTMX syntax errors at runtime")
836
+ println("\n✓ Complete realistic example built successfully")
837
+ }
838
+ }
839
+ ```
840
+
841
+ ([source](https://github.com/zio/zio-blocks/blob/main/zio-blocks-htmx-examples/src/main/scala/zioBlocksHtmx/CompleteExample.scala))
842
+
843
+ Run this example with the following command:
844
+
845
+ ```bash
846
+ sbt "zio-blocks-htmx-examples/runMain zioBlocksHtmx.CompleteExample"
847
+ ```
848
+
849
+ **3. Or compile all examples at once:**
850
+
851
+ To compile all example sources without running them, use:
852
+
853
+ ```bash
854
+ sbt "zio-blocks-htmx-examples/compile"
855
+ ```