@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.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hx-swap
|
|
3
|
+
title: HxSwap
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`HxSwap` represents the `hx-swap` attribute, controlling how HTMX replaces DOM content after a successful response. It combines a base strategy (innerHTML, outerHTML, etc.) with optional modifiers that refine timing, animation, scrolling, and focus behavior.
|
|
7
|
+
|
|
8
|
+
Start from one of the predefined strategies, then call modifier methods to customize timing, animation, and scroll behavior. All modifiers are optional, and calling the same modifier twice replaces the earlier value rather than stacking. Here are the core patterns:
|
|
9
|
+
|
|
10
|
+
```scala
|
|
11
|
+
import zio.http.htmx._
|
|
12
|
+
import scala.concurrent.duration._
|
|
13
|
+
|
|
14
|
+
// Base strategy only
|
|
15
|
+
HxSwap.InnerHTML
|
|
16
|
+
|
|
17
|
+
// With timing modifiers
|
|
18
|
+
HxSwap.OuterHTML.swap(1.second).settle(500.millis)
|
|
19
|
+
|
|
20
|
+
// With animation and scroll
|
|
21
|
+
HxSwap.BeforeEnd.transition.scroll(HxSwap.ScrollPosition.Top)
|
|
22
|
+
|
|
23
|
+
// Full pipeline
|
|
24
|
+
HxSwap.InnerHTML
|
|
25
|
+
.swap(500.millis)
|
|
26
|
+
.settle(250.millis)
|
|
27
|
+
.transition
|
|
28
|
+
.scroll(HxSwap.ScrollPosition.Bottom)
|
|
29
|
+
.show(HxSwap.ShowPosition.Top)
|
|
30
|
+
.focusScroll(true)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Swap Strategies
|
|
34
|
+
|
|
35
|
+
Core HTMX strategies define where and how content replaces the DOM:
|
|
36
|
+
|
|
37
|
+
- `HxSwap.InnerHTML` — Replaces the inner content of the target element. This is the default HTMX behavior.
|
|
38
|
+
- `HxSwap.OuterHTML` — Replaces the target element itself, including its opening and closing tags.
|
|
39
|
+
- `HxSwap.TextContent` — Replaces only the text content, leaving the element structure intact.
|
|
40
|
+
- `HxSwap.BeforeBegin` — Inserts the response as a sibling before the target element.
|
|
41
|
+
- `HxSwap.AfterBegin` — Inserts the response as the first child of the target element.
|
|
42
|
+
- `HxSwap.BeforeEnd` — Inserts the response as the last child of the target element.
|
|
43
|
+
- `HxSwap.AfterEnd` — Inserts the response as a sibling after the target element.
|
|
44
|
+
- `HxSwap.Delete` — Removes the target element; response content is discarded.
|
|
45
|
+
- `HxSwap.NoneSwap` — Prevents any content from being swapped into the DOM; the response is ignored.
|
|
46
|
+
|
|
47
|
+
All strategies are available as immutable `HxSwap` values, ready for modifier chaining:
|
|
48
|
+
|
|
49
|
+
```scala
|
|
50
|
+
import zio.blocks.html._
|
|
51
|
+
import zio.http.htmx._
|
|
52
|
+
|
|
53
|
+
div(hxSwap := HxSwap.InnerHTML)
|
|
54
|
+
div(hxSwap := HxSwap.OuterHTML)
|
|
55
|
+
div(hxSwap := HxSwap.BeforeBegin)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Timing Modifiers
|
|
59
|
+
|
|
60
|
+
Control when and how long the swap operation takes:
|
|
61
|
+
|
|
62
|
+
**`swap(duration: FiniteDuration)`** adds a `swap:` delay before the swap begins. Useful for coordinating with request latency or preparing the DOM:
|
|
63
|
+
|
|
64
|
+
```scala
|
|
65
|
+
import zio.blocks.html._
|
|
66
|
+
import zio.http.htmx._
|
|
67
|
+
import scala.concurrent.duration._
|
|
68
|
+
|
|
69
|
+
// Wait 500ms after response before swapping
|
|
70
|
+
div(hxSwap := HxSwap.InnerHTML.swap(500.millis))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**`settle(duration: FiniteDuration)`** adds a `settle:` delay after the swap before settling (CSS transitions, settling messages). This allows animations to run:
|
|
74
|
+
|
|
75
|
+
```scala
|
|
76
|
+
import zio.blocks.html._
|
|
77
|
+
import zio.http.htmx._
|
|
78
|
+
import scala.concurrent.duration._
|
|
79
|
+
|
|
80
|
+
// Swap immediately, but wait 250ms for CSS to settle
|
|
81
|
+
div(hxSwap := HxSwap.InnerHTML.settle(250.millis))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Both `swap()` and `settle()` accept any `FiniteDuration` that renders to a valid HTMX duration (milliseconds: `ms`, seconds: `s`, or bare integers treated as milliseconds). Calling either method twice replaces the first value:
|
|
85
|
+
|
|
86
|
+
```scala
|
|
87
|
+
import zio.http.htmx._
|
|
88
|
+
import scala.concurrent.duration._
|
|
89
|
+
|
|
90
|
+
val swap1 = HxSwap.InnerHTML.swap(1.second)
|
|
91
|
+
val swap2 = swap1.swap(500.millis) // replaces 1.second with 500.millis
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Animation & Transition
|
|
95
|
+
|
|
96
|
+
**`transition: HxSwap`** enables the `transition:true` modifier, allowing CSS transitions to run during the swap. Useful with timing modifiers to coordinate animation:
|
|
97
|
+
|
|
98
|
+
```scala
|
|
99
|
+
import zio.blocks.html._
|
|
100
|
+
import zio.http.htmx._
|
|
101
|
+
import scala.concurrent.duration._
|
|
102
|
+
|
|
103
|
+
// Enable transitions and give them 300ms to complete
|
|
104
|
+
div(hxSwap := HxSwap.InnerHTML.transition.settle(300.millis))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Scroll Behavior
|
|
108
|
+
|
|
109
|
+
Control where the page scrolls after a swap:
|
|
110
|
+
|
|
111
|
+
**`scroll(position: HxSwap.ScrollPosition)`** adds a `scroll:` modifier. Valid positions are `HxSwap.ScrollPosition.Top` and `HxSwap.ScrollPosition.Bottom`:
|
|
112
|
+
|
|
113
|
+
```scala
|
|
114
|
+
import zio.blocks.html._
|
|
115
|
+
import zio.http.htmx._
|
|
116
|
+
|
|
117
|
+
// Scroll to top after swapping
|
|
118
|
+
div(hxSwap := HxSwap.InnerHTML.scroll(HxSwap.ScrollPosition.Top))
|
|
119
|
+
|
|
120
|
+
// Scroll to bottom (useful for chat/log appends)
|
|
121
|
+
div(hxSwap := HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom))
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**`show(position: HxSwap.ShowPosition)`** adds a `show:` modifier to control where newly swapped content becomes visible. Valid positions are `HxSwap.ShowPosition.Top` and `HxSwap.ShowPosition.Bottom`:
|
|
125
|
+
|
|
126
|
+
```scala
|
|
127
|
+
import zio.blocks.html._
|
|
128
|
+
import zio.http.htmx._
|
|
129
|
+
|
|
130
|
+
// Show the top of the newly swapped content
|
|
131
|
+
div(hxSwap := HxSwap.InnerHTML.show(HxSwap.ShowPosition.Top))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Focus & Title Management
|
|
135
|
+
|
|
136
|
+
**`focusScroll(enabled: Boolean)`** sets the `focusScroll:` modifier to control whether focus moves during swap. Pass `true` to focus the swapped element, `false` to disable focus movement:
|
|
137
|
+
|
|
138
|
+
```scala
|
|
139
|
+
import zio.blocks.html._
|
|
140
|
+
import zio.http.htmx._
|
|
141
|
+
|
|
142
|
+
// Enable focus scroll (default HTMX behavior)
|
|
143
|
+
div(hxSwap := HxSwap.InnerHTML.focusScroll(true))
|
|
144
|
+
|
|
145
|
+
// Disable automatic focus movement
|
|
146
|
+
div(hxSwap := HxSwap.InnerHTML.focusScroll(false))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**`ignoreTitle: HxSwap`** enables the `ignoreTitle:true` modifier, preventing HTMX from updating the document title if the response contains an `<title>` tag:
|
|
150
|
+
|
|
151
|
+
```scala
|
|
152
|
+
import zio.blocks.html._
|
|
153
|
+
import zio.http.htmx._
|
|
154
|
+
|
|
155
|
+
// Don't update the page title even if response has one
|
|
156
|
+
div(hxSwap := HxSwap.InnerHTML.ignoreTitle)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Rendering & Parsing
|
|
160
|
+
|
|
161
|
+
**`render: String`** produces the literal `hx-swap` attribute value string:
|
|
162
|
+
|
|
163
|
+
```scala
|
|
164
|
+
import zio.http.htmx._
|
|
165
|
+
import scala.concurrent.duration._
|
|
166
|
+
|
|
167
|
+
val swap = HxSwap.InnerHTML.swap(1.second).settle(500.millis).transition
|
|
168
|
+
swap.render // "innerHTML swap:1s settle:500ms transition:true"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**`HxSwap.parse(value: String): Either[String, HxSwap]`** decodes a rendered HTMX swap string into a typed `HxSwap` value. The parser accepts the same format that `render` produces:
|
|
172
|
+
|
|
173
|
+
```scala
|
|
174
|
+
import zio.http.htmx._
|
|
175
|
+
|
|
176
|
+
val parsed = HxSwap.parse("outerHTML swap:2s settle:250ms transition:true scroll:bottom")
|
|
177
|
+
// Right(HxSwap.OuterHTML.swap(2 seconds).settle(250 millis).transition.scroll(ScrollPosition.Bottom))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Parse failures return a descriptive `Left` with the error reason:
|
|
181
|
+
|
|
182
|
+
```scala
|
|
183
|
+
import zio.http.htmx._
|
|
184
|
+
|
|
185
|
+
HxSwap.parse("") // Left("Empty HTMX swap value")
|
|
186
|
+
HxSwap.parse("bogus") // Left("Unknown HTMX swap strategy: bogus")
|
|
187
|
+
HxSwap.parse("innerHTML swap:later") // Left("Unsupported HTMX duration: later")
|
|
188
|
+
HxSwap.parse("innerHTML scroll:middle") // Left("Invalid HTMX scroll position: middle")
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Common Patterns
|
|
192
|
+
|
|
193
|
+
Effective swap strategies enhance user experience through proper timing and animation. Here are practical usage patterns:
|
|
194
|
+
|
|
195
|
+
### Fade-In New Content
|
|
196
|
+
|
|
197
|
+
Combine `HxSwap#transition` with `HxSwap#settle` delay to fade in new elements:
|
|
198
|
+
|
|
199
|
+
```scala
|
|
200
|
+
import zio.blocks.html._
|
|
201
|
+
import zio.http.htmx._
|
|
202
|
+
import scala.concurrent.duration._
|
|
203
|
+
|
|
204
|
+
div(
|
|
205
|
+
hxGet := "/new-items",
|
|
206
|
+
hxSwap := HxSwap.BeforeEnd.transition.settle(300.millis)
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Scroll New Content Into View
|
|
211
|
+
|
|
212
|
+
Use `HxSwap#scroll` with `BeforeEnd` or `AfterEnd` to append content and scroll to it:
|
|
213
|
+
|
|
214
|
+
```scala
|
|
215
|
+
import zio.blocks.html._
|
|
216
|
+
import zio.http.htmx._
|
|
217
|
+
import scala.concurrent.duration._
|
|
218
|
+
|
|
219
|
+
div(
|
|
220
|
+
id := "messages",
|
|
221
|
+
hxGet := "/new-messages",
|
|
222
|
+
hxTrigger := HxTrigger.every(2.seconds),
|
|
223
|
+
hxSwap := HxSwap.BeforeEnd.scroll(HxSwap.ScrollPosition.Bottom)
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Replace and Focus
|
|
228
|
+
|
|
229
|
+
Use `InnerHTML` or `OuterHTML` with `HxSwap#focusScroll` to replace content and move focus:
|
|
230
|
+
|
|
231
|
+
```scala
|
|
232
|
+
import zio.blocks.html._
|
|
233
|
+
import zio.http.htmx._
|
|
234
|
+
|
|
235
|
+
form(
|
|
236
|
+
hxPost := "/submit",
|
|
237
|
+
hxSwap := HxSwap.OuterHTML.focusScroll(true),
|
|
238
|
+
button("Submit")
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Preserve Animations While Settling
|
|
243
|
+
|
|
244
|
+
Increase the `HxSwap#settle` delay to let CSS animations complete before HTMX considers the swap done:
|
|
245
|
+
|
|
246
|
+
```scala
|
|
247
|
+
import zio.blocks.html._
|
|
248
|
+
import zio.http.htmx._
|
|
249
|
+
import scala.concurrent.duration._
|
|
250
|
+
|
|
251
|
+
// CSS animations run for 500ms; settle after they complete
|
|
252
|
+
div(
|
|
253
|
+
hxPost := "/update",
|
|
254
|
+
hxSwap := HxSwap.InnerHTML.transition.settle(500.millis)
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Integration with Other Module Types
|
|
259
|
+
|
|
260
|
+
`HxSwap` is most often paired with `HxTrigger` (when to request) and `HxTarget` (where to apply the swap):
|
|
261
|
+
|
|
262
|
+
```scala
|
|
263
|
+
import zio.blocks.html._
|
|
264
|
+
import zio.http.htmx._
|
|
265
|
+
import scala.concurrent.duration._
|
|
266
|
+
|
|
267
|
+
button(
|
|
268
|
+
hxPost := "/api/action",
|
|
269
|
+
hxTrigger := HxTrigger.click.delay(100.millis),
|
|
270
|
+
hxTarget := HxTarget.closest("form"),
|
|
271
|
+
hxSwap := HxSwap.InnerHTML.settle(250.millis),
|
|
272
|
+
"Perform Action"
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The `ToHtmxValue[HxSwap]` type class renders automatically, enabling you to assign `HxSwap` values directly using the `:=` operator on the `hxSwap` attribute key.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hx-sync
|
|
3
|
+
title: HxSync
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`HxSync` represents the `hx-sync` attribute, coordinating multiple HTMX requests by specifying how new requests interact with pending or running requests on a target element. This prevents race conditions and ensures predictable behavior when multiple events fire in quick succession.
|
|
7
|
+
|
|
8
|
+
Create an `HxSync` by pairing an `HxTarget` (which element to synchronize) with a `HxSyncStrategy` (how to handle conflicts):
|
|
9
|
+
|
|
10
|
+
```scala
|
|
11
|
+
import zio.http.htmx._
|
|
12
|
+
|
|
13
|
+
// Wait for this element's request to finish before starting a new one
|
|
14
|
+
HxSync(HxTarget.This, HxSyncStrategy.Queue)
|
|
15
|
+
|
|
16
|
+
// Replace a pending request with a new one
|
|
17
|
+
HxSync(HxTarget.closest("form"), HxSyncStrategy.Replace)
|
|
18
|
+
|
|
19
|
+
// Drop new requests while one is in flight
|
|
20
|
+
HxSync(HxTarget.This, HxSyncStrategy.Drop)
|
|
21
|
+
|
|
22
|
+
// Abort the current request and start the new one immediately
|
|
23
|
+
HxSync(HxTarget.This, HxSyncStrategy.Abort)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Sync Strategies
|
|
27
|
+
|
|
28
|
+
**`HxSyncStrategy.Queue`** queues new requests, executing them after the current request completes. This is the default HTMX behavior when `hx-sync` is not specified. Useful for ensuring requests are processed in order:
|
|
29
|
+
|
|
30
|
+
```scala
|
|
31
|
+
import zio.blocks.html._
|
|
32
|
+
import zio.http.htmx._
|
|
33
|
+
|
|
34
|
+
input(
|
|
35
|
+
hxPost := "/search",
|
|
36
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Queue),
|
|
37
|
+
placeholder := "Search..."
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**`HxSyncStrategy.Replace`** cancels any pending request and immediately sends the new one. Useful for searches and filters where the latest value is all that matters:
|
|
42
|
+
|
|
43
|
+
```scala
|
|
44
|
+
import zio.blocks.html._
|
|
45
|
+
import zio.http.htmx._
|
|
46
|
+
|
|
47
|
+
input(
|
|
48
|
+
hxPost := "/search",
|
|
49
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
|
|
50
|
+
placeholder := "Live Search (only latest request sent)"
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**`HxSyncStrategy.Drop`** discards new requests while one is in flight. Only the first request in a burst is sent; others are ignored. Useful for expensive operations that shouldn't be duplicated:
|
|
55
|
+
|
|
56
|
+
```scala
|
|
57
|
+
import zio.blocks.html._
|
|
58
|
+
import zio.http.htmx._
|
|
59
|
+
|
|
60
|
+
button(
|
|
61
|
+
hxPost := "/expensive-operation",
|
|
62
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Drop),
|
|
63
|
+
"Process (only first request sent)"
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**`HxSyncStrategy.Abort`** cancels any in-flight request and immediately sends the new one. The old request's response is discarded. Useful for time-sensitive operations:
|
|
68
|
+
|
|
69
|
+
```scala
|
|
70
|
+
import zio.blocks.html._
|
|
71
|
+
import zio.http.htmx._
|
|
72
|
+
|
|
73
|
+
button(
|
|
74
|
+
hxPost := "/time-sensitive-action",
|
|
75
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Abort),
|
|
76
|
+
"Execute (cancels previous)"
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Target Specification
|
|
81
|
+
|
|
82
|
+
The first parameter of `HxSync` is an `HxTarget` specifying which element to synchronize. This allows coordinating requests across multiple elements:
|
|
83
|
+
|
|
84
|
+
**Synchronize the current element:**
|
|
85
|
+
|
|
86
|
+
Use `HxTarget.This` to synchronize the element that initiates the request:
|
|
87
|
+
|
|
88
|
+
```scala
|
|
89
|
+
import zio.blocks.html._
|
|
90
|
+
import zio.http.htmx._
|
|
91
|
+
|
|
92
|
+
input(
|
|
93
|
+
hxPost := "/search",
|
|
94
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
|
|
95
|
+
placeholder := "Search..."
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Synchronize a parent form:**
|
|
100
|
+
|
|
101
|
+
Synchronize across all fields in a form using `HxTarget.closest()`:
|
|
102
|
+
|
|
103
|
+
```scala
|
|
104
|
+
import zio.blocks.html._
|
|
105
|
+
import zio.http.htmx._
|
|
106
|
+
|
|
107
|
+
input(
|
|
108
|
+
hxPost := "/validate",
|
|
109
|
+
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue),
|
|
110
|
+
placeholder := "Email"
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Synchronize multiple fields at once:**
|
|
115
|
+
|
|
116
|
+
Apply the same sync strategy to multiple fields within a form:
|
|
117
|
+
|
|
118
|
+
```scala
|
|
119
|
+
import zio.blocks.html._
|
|
120
|
+
import zio.http.htmx._
|
|
121
|
+
|
|
122
|
+
form(
|
|
123
|
+
input(
|
|
124
|
+
name := "firstName",
|
|
125
|
+
hxPost := "/validate",
|
|
126
|
+
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
|
|
127
|
+
),
|
|
128
|
+
input(
|
|
129
|
+
name := "email",
|
|
130
|
+
hxPost := "/validate",
|
|
131
|
+
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Rendering & Parsing
|
|
137
|
+
|
|
138
|
+
**`render: String`** produces the literal `hx-sync` attribute value string:
|
|
139
|
+
|
|
140
|
+
```scala
|
|
141
|
+
import zio.http.htmx._
|
|
142
|
+
|
|
143
|
+
HxSync(HxTarget.This, HxSyncStrategy.Queue).render // "this:queue"
|
|
144
|
+
HxSync(HxTarget.closest("form"), HxSyncStrategy.Replace).render // "closest form:replace"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**`HxSync.parse(value: String): Either[String, HxSync]`** parses a rendered string back into a typed `HxSync`:
|
|
148
|
+
|
|
149
|
+
```scala
|
|
150
|
+
import zio.http.htmx._
|
|
151
|
+
|
|
152
|
+
HxSync.parse("this:queue") // Right(HxSync(HxTarget.This, HxSyncStrategy.Queue))
|
|
153
|
+
HxSync.parse("closest form:replace") // Right(HxSync(HxTarget.closest("form"), HxSyncStrategy.Replace))
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Common Patterns
|
|
157
|
+
|
|
158
|
+
Synchronization strategies prevent race conditions in interactive forms. Here are practical usage patterns:
|
|
159
|
+
|
|
160
|
+
### Search with Last-Value-Wins
|
|
161
|
+
|
|
162
|
+
Use `Replace` to ensure only the most recent search is sent when typing quickly:
|
|
163
|
+
|
|
164
|
+
```scala
|
|
165
|
+
import zio.blocks.html._
|
|
166
|
+
import zio.http.htmx._
|
|
167
|
+
import scala.concurrent.duration._
|
|
168
|
+
|
|
169
|
+
input(
|
|
170
|
+
hxPost := "/search",
|
|
171
|
+
hxTrigger := HxTrigger.input.delay(300.millis),
|
|
172
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
|
|
173
|
+
hxTarget := HxTarget.css("#results"),
|
|
174
|
+
placeholder := "Search (only latest request sent)"
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Ordered Form Validation
|
|
179
|
+
|
|
180
|
+
Use `Queue` to validate form fields in the order they were changed, preventing race conditions:
|
|
181
|
+
|
|
182
|
+
```scala
|
|
183
|
+
import zio.blocks.html._
|
|
184
|
+
import zio.http.htmx._
|
|
185
|
+
|
|
186
|
+
form(
|
|
187
|
+
input(
|
|
188
|
+
name := "username",
|
|
189
|
+
hxPost := "/validate/username",
|
|
190
|
+
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
|
|
191
|
+
),
|
|
192
|
+
input(
|
|
193
|
+
name := "email",
|
|
194
|
+
hxPost := "/validate/email",
|
|
195
|
+
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Prevent Double-Submit
|
|
201
|
+
|
|
202
|
+
Use `Drop` to prevent duplicate submissions when users click a button repeatedly:
|
|
203
|
+
|
|
204
|
+
```scala
|
|
205
|
+
import zio.blocks.html._
|
|
206
|
+
import zio.http.htmx._
|
|
207
|
+
|
|
208
|
+
form(
|
|
209
|
+
button(
|
|
210
|
+
hxPost := "/submit",
|
|
211
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Drop),
|
|
212
|
+
"Submit (first click only)"
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Abort Slow Requests
|
|
218
|
+
|
|
219
|
+
Use `Abort` for requests that shouldn't queue up, replacing old requests immediately:
|
|
220
|
+
|
|
221
|
+
```scala
|
|
222
|
+
import zio.blocks.html._
|
|
223
|
+
import zio.http.htmx._
|
|
224
|
+
|
|
225
|
+
button(
|
|
226
|
+
hxPost := "/fetch-latest",
|
|
227
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Abort),
|
|
228
|
+
"Fetch Latest (aborts previous)"
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Integration with Other Attributes
|
|
233
|
+
|
|
234
|
+
`HxSync` works alongside trigger and timing attributes to coordinate complex request patterns:
|
|
235
|
+
|
|
236
|
+
```scala
|
|
237
|
+
import zio.blocks.html._
|
|
238
|
+
import zio.http.htmx._
|
|
239
|
+
import scala.concurrent.duration._
|
|
240
|
+
|
|
241
|
+
input(
|
|
242
|
+
hxPost := "/search",
|
|
243
|
+
hxTrigger := HxTrigger.input.delay(500.millis).changed,
|
|
244
|
+
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
|
|
245
|
+
hxParams := HxParams.only("query"),
|
|
246
|
+
hxTarget := HxTarget.css("#results"),
|
|
247
|
+
placeholder := "Search"
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The `ToHtmxValue[HxSync]` instance renders automatically, so `HxSync` values work seamlessly with the `hxSync` attribute key.
|