jhx 0.1.1
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/LICENSE +21 -0
- package/README.md +1147 -0
- package/index.cjs +1 -0
- package/index.d.ts +2054 -0
- package/index.js +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nick-hio/jhx/main/assets/jhx-logo-light.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/nick-hio/jhx/main/assets/jhx-logo-dark.svg" height="90" alt="Logo for jhx">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<div align="center">
|
|
9
|
+
Type-Safe HTMX.
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div align="center">
|
|
13
|
+
<a target="_blank" href="https://github.com/nick-hio/jhx">GitHub</a> •
|
|
14
|
+
<a target="_blank" href="https://github.com/nick-hio/jhx">Documentation</a> •
|
|
15
|
+
<a target="_blank" href="https://github.com/nick-hio/jhx/issues/new">Report an Issue</a>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
# jhx
|
|
19
|
+
|
|
20
|
+
- **HTMX helper for React/JSX, HTML, SSR/edge, and server frameworks.**
|
|
21
|
+
Dedicated types with strong inference for HTMX patterns, features, and extensions.
|
|
22
|
+
- **Type-safe HTMX attribute and event mapping.**
|
|
23
|
+
Converts typed props into valid HTMX `hx-*` attributes for templating.
|
|
24
|
+
- **Streamlines developer experience for complex HTMX applications.**
|
|
25
|
+
Simplifies the development of large HTMX applications with advanced interactions.
|
|
26
|
+
|
|
27
|
+
> Check out the server adapters for framework integrations:
|
|
28
|
+
> - [**Elysia** - `@jhxdev/elysia`](https://github.com/nick-hio/jhx/tree/main/packages/elysia)
|
|
29
|
+
> - [**Express** - `@jhxdev/express`](https://github.com/nick-hio/jhx/tree/main/packages/express)
|
|
30
|
+
> - [**Fastify** - `@jhxdev/fastify`](https://github.com/nick-hio/jhx/tree/main/packages/fastify)
|
|
31
|
+
> - [**Hono** - `@jhxdev/hono`](https://github.com/nick-hio/jhx/tree/main/apps/hono)
|
|
32
|
+
|
|
33
|
+
## Table of Contents
|
|
34
|
+
|
|
35
|
+
- [Installation](#installation)
|
|
36
|
+
- [Quick Start](#quick-start)
|
|
37
|
+
- [API](#api)
|
|
38
|
+
- [`jhx`](#jhx-1)
|
|
39
|
+
- [`JhxComponent`](#jhxcomponent)
|
|
40
|
+
- [`htmx`](#htmx)
|
|
41
|
+
- [Examples](#examples)
|
|
42
|
+
- [Setting the Request Method](#setting-the-request-method)
|
|
43
|
+
- [`jhx` in JSX (Object Output)](#jhx-in-jsx-object-output)
|
|
44
|
+
- [`jhx` in HTML (String Output)](#jhx-in-html-string-output)
|
|
45
|
+
- [`JhxComponent` in JSX](#jhxcomponent-in-jsx)
|
|
46
|
+
- [HTMX & DOM Events](#htmx--dom-events)
|
|
47
|
+
- [DOM Interactions & Type Safety](#dom-interactions--type-safety)
|
|
48
|
+
- [Advanced Props](#advanced-props)
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Install via your preferred package manager:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
pnpm install jhx
|
|
56
|
+
npm install jhx
|
|
57
|
+
bun add jhx
|
|
58
|
+
yarn add jhx
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Import the package:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// ESM import
|
|
65
|
+
import { jhx, JhxComponent, htmx } from 'jhx';
|
|
66
|
+
|
|
67
|
+
// CJS import
|
|
68
|
+
const { jhx, JhxComponent, htmx } = require('jhx');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
Generate HTMX attributes for JSX props:
|
|
74
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
import { jhx } from 'jhx';
|
|
77
|
+
|
|
78
|
+
const attrs = jhx({
|
|
79
|
+
get: '/api',
|
|
80
|
+
target: '#container',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const button = (<button {...attrs}>Load Data</button>);
|
|
84
|
+
// <button hx-get="/api" hx-target="#container">Load Data</button>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Generate HTMX attributes as a string for HTML templating:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { jhx } from 'jhx';
|
|
91
|
+
|
|
92
|
+
const attrs = jhx({
|
|
93
|
+
get: '/api',
|
|
94
|
+
target: '#container',
|
|
95
|
+
}, { stringify: true }); // set 'stringify' to 'true'
|
|
96
|
+
|
|
97
|
+
const button = `<button ${attrs}>Load Data</button>`;
|
|
98
|
+
// <button hx-get="/api" hx-target="#container">Load Data</button>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Generate HTMX attributes with the JSX component:
|
|
102
|
+
|
|
103
|
+
```jsx
|
|
104
|
+
import { JhxComponent } from 'jhx';
|
|
105
|
+
|
|
106
|
+
const button = (
|
|
107
|
+
<JhxComponent
|
|
108
|
+
as='button' // set the element tag (defaults to 'div')
|
|
109
|
+
post='/api'
|
|
110
|
+
target='#container'
|
|
111
|
+
>
|
|
112
|
+
Load Data
|
|
113
|
+
</JhxComponent>
|
|
114
|
+
);
|
|
115
|
+
// <button hx-post="/api" hx-target="#container">Load Data</button>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## API
|
|
119
|
+
|
|
120
|
+
> See the [Examples](#examples) section for usage.
|
|
121
|
+
|
|
122
|
+
### `jhx`
|
|
123
|
+
|
|
124
|
+
A function that transforms props into an object of HTMX attributes.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
function jhx<TDom>(props: JhxDomProps<TDom>, config?: JhxConfig & { stringify: true }): string;
|
|
128
|
+
function jhx<TDom>(props: JhxProps<TDom>, config?: JhxConfig & { stringify?: false }): Record<JhxAttribute, string>;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Parameters
|
|
132
|
+
|
|
133
|
+
##### `props`
|
|
134
|
+
|
|
135
|
+
An object containing the HTMX attribute values.
|
|
136
|
+
|
|
137
|
+
> HTMX Documentation: [Core Attributes](https://htmx.org/reference/#attributes), [Additional Attributes](https://htmx.org/reference/#attributes-additional)
|
|
138
|
+
|
|
139
|
+
| Property | HTMX Attribute | Type | Description |
|
|
140
|
+
|---------------|-------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
|
141
|
+
| `boost` | [`hx-boost`](https://htmx.org/attributes/hx-boost/) | `boolean` | Enables link/form boosting. |
|
|
142
|
+
| `confirm` | [`hx-confirm`](https://htmx.org/attributes/hx-confirm/) | `string` | Confirmation text before the request is sent. |
|
|
143
|
+
| `delete` | [`hx-delete`](https://htmx.org/attributes/hx-delete/) | `string` | DELETE request path. Overrides the `route` and `method` props. |
|
|
144
|
+
| `disable` | [`hx-disable`](https://htmx.org/attributes/hx-disable/) | `boolean` | Disables an element for HTMX behavior. |
|
|
145
|
+
| `disabledElt` | [`hx-disabled-elt`](https://htmx.org/attributes/hx-disabled-elt/) | `JhxTargetAttribute` | Element(s) to disable during the request. |
|
|
146
|
+
| `disinherit` | [`hx-disinherit`](https://htmx.org/attributes/hx-disinherit/) | `JhxInheritAttribute` | Prevent inheritance of specific attributes. |
|
|
147
|
+
| `encoding` | [`hx-encoding`](https://htmx.org/attributes/hx-encoding/) | `string` | Request encoding (e.g., `multipart/form-data`). |
|
|
148
|
+
| `ext` | [`hx-ext`](https://htmx.org/attributes/hx-ext/) | `JhxExtensionAttribute` | Enable/disable extensions for an element (`string` or array of `{ name, ignore? }`). |
|
|
149
|
+
| `get` | [`hx-get`](https://htmx.org/attributes/hx-get/) | `string` | GET request path. Overrides the `route` and `method` props. |
|
|
150
|
+
| `headers` | [`hx-headers`](https://htmx.org/attributes/hx-headers/) | `JhxEvaluableAttribute<TDom>` | Additional headers to include in the request; `object`, `string`, or `function` evaluated client-side when triggered. |
|
|
151
|
+
| `history` | [`hx-history`](https://htmx.org/attributes/hx-history/) | `boolean` | Enable/disable history support for an element. |
|
|
152
|
+
| `historyElt` | [`hx-history-elt`](https://htmx.org/attributes/hx-history-elt/) | `boolean` | Set the element as a history element. |
|
|
153
|
+
| `include` | [`hx-include`](https://htmx.org/attributes/hx-include/) | `JhxTargetAttribute` | Additional element values to include in the request. |
|
|
154
|
+
| `indicator` | [`hx-indicator`](https://htmx.org/attributes/hx-indicator/) | `JhxIndicatorAttribute` | Loading indicator element; supports `closest` and `inherit`. |
|
|
155
|
+
| `inherit` | [`hx-inherit`](https://htmx.org/attributes/hx-inherit/) | `JhxInheritAttribute` | Inherit attributes from other elements. |
|
|
156
|
+
| `method` | `hx-<method>` | `HtmxHttpMethod \| string` | HTTP method for the request. Must paired with the `route` prop (unless using an adapter package). |
|
|
157
|
+
| `params` | [`hx-params`](https://htmx.org/attributes/hx-params/) | `JhxParamsAttribute` | Include/exclude parameters with the request. |
|
|
158
|
+
| `patch` | [`hx-patch`](https://htmx.org/attributes/hx-patch/) | `string` | PATCH request path. Overrides the `route` and `method` props. |
|
|
159
|
+
| `post` | [`hx-post`](https://htmx.org/attributes/hx-post/) | `string` | POST request path. Overrides the `route` and `method` props. |
|
|
160
|
+
| `preserve` | [`hx-preserve`](https://htmx.org/attributes/hx-preserve/) | `boolean` | Preserve an element state between swaps. |
|
|
161
|
+
| `prompt` | [`hx-prompt`](https://htmx.org/attributes/hx-prompt/) | `string` | Prompt user for input before the request is sent. |
|
|
162
|
+
| `pushUrl` | [`hx-push-url`](https://htmx.org/attributes/hx-push-url/) | `boolean \| string` | Push a new history entry (`true` uses request URL; `string` uses given URL). |
|
|
163
|
+
| `put` | [`hx-put`](https://htmx.org/attributes/hx-put/) | `string` | PUT request path. Overrides the `route` and `method` props. |
|
|
164
|
+
| `replaceUrl` | [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url/) | `boolean \| string` | Replaces the current history entry. |
|
|
165
|
+
| `request` | [`hx-request`](https://htmx.org/attributes/hx-request/) | `JhxRequestAttribute<TDom>` | Request configuration (e.g., `timeout`, `credentials`, `noHeaders`) or a `function` returning it. |
|
|
166
|
+
| `route` | `hx-<method>` | `string` | Request path for the `hx-` method attribute. Use the `method` prop to set the request method. Defaults to `hx-get`. |
|
|
167
|
+
| `select` | [`hx-select`](https://htmx.org/attributes/hx-select/) | `string` | Limit the update to a CSS selector within the response. |
|
|
168
|
+
| `selectOob` | [`hx-select-oob`](https://htmx.org/attributes/hx-select-oob/) | `JhxSelectOobAttribute` | Out-of-band selections to apply from the response. |
|
|
169
|
+
| `swap` | [`hx-swap`](https://htmx.org/attributes/hx-swap/) | `JhxSwapAttribute` | Swap behavior (style, delays, `scroll`/`show`, transitions, etc.). |
|
|
170
|
+
| `swapOob` | [`hx-swap-oob`](https://htmx.org/attributes/hx-swap-oob/) | `JhxSwapOobAttribute` | Out-of-band swap behavior. |
|
|
171
|
+
| `sync` | [`hx-sync`](https://htmx.org/attributes/hx-sync/) | `JhxSyncAttribute` | Synchronization strategy for multiple requests. |
|
|
172
|
+
| `target` | [`hx-target`](https://htmx.org/attributes/hx-target/) | `JhxTargetAttribute` | Target element for swaps. |
|
|
173
|
+
| `trigger` | [`hx-trigger`](https://htmx.org/attributes/hx-trigger/) | `JhxTriggerAttribute` | Events and conditions that trigger a request. |
|
|
174
|
+
| `validate` | [`hx-validate`](https://htmx.org/attributes/hx-validate/) | `boolean` | Enables HTML5 validation before requests. |
|
|
175
|
+
| `vals` | [`hx-vals`](https://htmx.org/attributes/hx-vals/) | `JhxEvaluableAttribute<TDom>` | Additional values to include in the request; `object`, `string`, or `function`. |
|
|
176
|
+
| `vars` | `hx-vars` | `string` | Recommended to use `vals`; potential XSS vulnerabilities from `vars`. |
|
|
177
|
+
|
|
178
|
+
Event Props:
|
|
179
|
+
|
|
180
|
+
> HTMX Documentation: [Events](https://htmx.org/reference/#events), [hx-on](https://htmx.org/attributes/hx-on/)
|
|
181
|
+
|
|
182
|
+
| Property | HTMX/HTML Attribute | Type | Description |
|
|
183
|
+
|-----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
184
|
+
| HTMX handlers<br>(`onAfterSwap`, `onPrompt`, `onBeforeRequest`, `onAfterRequest`, etc.) | `hx-on:htmx:afterSwap`,<br>`hx-on::prompt`,<br>`hx-on-htmx-before-request`,<br>`hx-on--after-request`, etc. | `({ event } & DomObjects & TDom) => void`<br><br>(See the `HtmxEventProps` type for details) | HTMX event and lifecycle handlers.<br><br>Serialized to an inline handler for the corresponding `hx-on--` attribute. |
|
|
185
|
+
| DOM handlers<br>(`onClick`, `onChange`, `onSubmit`, `onKeyDown`, etc.) | `onclick`,<br>`onchange`,<br>`onsubmit`,<br>`onkeydown`, etc. | `({ event }: DomEventProps & TDom) => void`<br><br>(See the `DomEventProps` type for details) | Standard DOM event handlers.<br><br>Serialized to inline handler attributes **only when `config.stringify: true`**, otherwise, they are excluded in JSX rendering.<br><br>When serializing, DOM handlers include:<br>- Props from `DomEventProps`.<br>- Function props starting with "on" (e.g., `onCustomEvent` becomes `oncustomevent`). |
|
|
186
|
+
|
|
187
|
+
##### `config` (optional)
|
|
188
|
+
|
|
189
|
+
Configuration options for the function.
|
|
190
|
+
|
|
191
|
+
| Property | Type | Description |
|
|
192
|
+
|-------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
193
|
+
| `escape` | `boolean` | Whether to escape the HTML characters in the attribute values. Defaults to `true`. |
|
|
194
|
+
| `logger` | `Logger` | Logging functions for debug messages, warnings, and errors from jhx. Defaults to `console`. |
|
|
195
|
+
| `stringify` | `boolean` | Converts the result into a string of HTML attributes. Defaults to `false`.<br>- When `true`, returns a string of HTML attributes.<br>- When `false`, returns an object for JSX spreading. |
|
|
196
|
+
|
|
197
|
+
#### Generics
|
|
198
|
+
|
|
199
|
+
- `TDom` (extends `object`) - Type for the additional DOM variables (see the [DOM Interactions & Type Safety](#dom-interactions--type-safety) section for usage).
|
|
200
|
+
|
|
201
|
+
#### Returns
|
|
202
|
+
|
|
203
|
+
`jhx` will return a `string` when `config.stringify` set to `true`, otherwise, returns a `Record<JhxAttribute, string>`.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `JhxComponent`
|
|
208
|
+
|
|
209
|
+
JSX wrapper element for the `jhx` function.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
function JhxComponent<TDom>(props: PropsWithChildren<JhxComponentProps<TDom>>): JSX.Element
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Props
|
|
216
|
+
|
|
217
|
+
| Prop | Type | Description |
|
|
218
|
+
|---------------------|---------------------------------|-----------------------------------------------------------------------------------------|
|
|
219
|
+
| `as` | `keyof JSX.IntrinsicElements` | Specifies the element tag for the component. Defaults to `div`. |
|
|
220
|
+
| `jhxConfig` | `Omit<JhxConfig, 'stringify'>` | Configuration options passed to the `jhx` function, excluding the `stringify` property. |
|
|
221
|
+
| All props for `jhx` | See the [`jhx`](#jhx-1) section | Props passed to the wrapped `jhx` function. |
|
|
222
|
+
|
|
223
|
+
#### Generics
|
|
224
|
+
|
|
225
|
+
- `TDom` (extends `object`) - Type for the additional DOM variables (see the [DOM Interactions & Type Safety](#dom-interactions--type-safety) section for usage).
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### `htmx`
|
|
230
|
+
|
|
231
|
+
An object containing constant values for HTMX attributes, events, methods, headers, and other configurations.
|
|
232
|
+
|
|
233
|
+
> HTMX Documentation: [Reference](https://htmx.org/reference/)
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { htmx } from 'jhx';
|
|
237
|
+
|
|
238
|
+
htmx.attr; // HTMX attributes
|
|
239
|
+
htmx.config; // default HTMX config
|
|
240
|
+
htmx.css; // CSS classes
|
|
241
|
+
htmx.event; // HTMX events
|
|
242
|
+
htmx.eventAttr; // HTMX event attributes
|
|
243
|
+
htmx.ext; // HTMX extensions
|
|
244
|
+
htmx.method; // HTTP methods
|
|
245
|
+
htmx.res; // response headers
|
|
246
|
+
htmx.req; // request headers
|
|
247
|
+
htmx.swap; // swap styles
|
|
248
|
+
htmx.sync; // synchronization strategies
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Examples
|
|
252
|
+
|
|
253
|
+
- [Setting the Request Method](#setting-the-request-method)
|
|
254
|
+
- [`jhx` in JSX (Object Output)](#jhx-in-jsx-object-output)
|
|
255
|
+
- [`jhx` in HTML (String Output)](#jhx-in-html-string-output)
|
|
256
|
+
- [`JhxComponent` in JSX](#jhxcomponent-in-jsx)
|
|
257
|
+
- [HTMX & DOM Events](#htmx--dom-events)
|
|
258
|
+
- [DOM Interactions & Type Safety](#dom-interactions--type-safety)
|
|
259
|
+
- [Advanced Props](#advanced-props)
|
|
260
|
+
|
|
261
|
+
### Setting the Request Method
|
|
262
|
+
|
|
263
|
+
You can set the request method and route by using one of the HTTP verb props or the `method` and `route` props.
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
// using HTTP verb props
|
|
267
|
+
jhx({ get: '/api' }); // hx-put="/api"
|
|
268
|
+
jhx({ post: '/api' }); // hx-post="/api"
|
|
269
|
+
jhx({ put: '/api' }); // hx-put="/api"
|
|
270
|
+
jhx({ patch: '/api' }); // hx-patch="/api"
|
|
271
|
+
jhx({ delete: '/api' }); // hx-delete="/api"
|
|
272
|
+
|
|
273
|
+
// using 'method' and 'route' props
|
|
274
|
+
jhx({ route: '/api' }); // hx-get="/api"
|
|
275
|
+
jhx({ route: '/api', method: 'post' }); // hx-post="/api"
|
|
276
|
+
jhx({ route: '/api', method: 'OPTIONS' }); // hx-options="/api"
|
|
277
|
+
|
|
278
|
+
// using 'method' with a generated endpoint (only with adapter packages)
|
|
279
|
+
jhx({ method: '/post' }); // hx-post="/api/<GENERATED_ID>"
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### `jhx` in JSX (Object Output)
|
|
285
|
+
|
|
286
|
+
> **Tip**: If your framework does not support the object syntax, use the `applyAttribute` function to apply the attributes to an HTML element
|
|
287
|
+
> or create a custom function to transform the object into your desired shape.
|
|
288
|
+
|
|
289
|
+
Declaring separately:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
export function LoadDataButton() {
|
|
293
|
+
const attrs = jhx({
|
|
294
|
+
post: '/api/items',
|
|
295
|
+
target: {
|
|
296
|
+
closest: true,
|
|
297
|
+
selector: '.card',
|
|
298
|
+
},
|
|
299
|
+
swap: {
|
|
300
|
+
style: 'outerHTML',
|
|
301
|
+
settle: 100,
|
|
302
|
+
},
|
|
303
|
+
trigger: {
|
|
304
|
+
event: 'click',
|
|
305
|
+
once: true,
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<button {...attrs}>
|
|
311
|
+
Submit
|
|
312
|
+
</button>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Declaring inline:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
export function SubmitButton() {
|
|
321
|
+
return (
|
|
322
|
+
<button {...jhx({
|
|
323
|
+
post: '/api/items',
|
|
324
|
+
target: {
|
|
325
|
+
closest: true,
|
|
326
|
+
selector: '.card',
|
|
327
|
+
},
|
|
328
|
+
swap: {
|
|
329
|
+
style: 'outerHTML',
|
|
330
|
+
settle: 100,
|
|
331
|
+
},
|
|
332
|
+
trigger: {
|
|
333
|
+
event: 'click',
|
|
334
|
+
once: true,
|
|
335
|
+
},
|
|
336
|
+
})}>
|
|
337
|
+
Submit
|
|
338
|
+
</button>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
### `jhx` in HTML (String Output)
|
|
346
|
+
|
|
347
|
+
DOM event handlers are **only serialized by the `jhx` function when `config.stringify` is set to `true`**.
|
|
348
|
+
|
|
349
|
+
Declaring separately:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
const attrs = jhx({
|
|
353
|
+
post: '/api/login',
|
|
354
|
+
trigger: 'submit',
|
|
355
|
+
boost: true,
|
|
356
|
+
}, { stringify: true });
|
|
357
|
+
|
|
358
|
+
const html = `
|
|
359
|
+
<div>
|
|
360
|
+
<h1>Login</h1>
|
|
361
|
+
<form class="login-form" id="login-form" ${attrs}>
|
|
362
|
+
...
|
|
363
|
+
</form>
|
|
364
|
+
</div>
|
|
365
|
+
`;
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Declaring inline:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
const html = `
|
|
372
|
+
<div>
|
|
373
|
+
<h1>Login</h1>
|
|
374
|
+
<form
|
|
375
|
+
class="login-form"
|
|
376
|
+
id="login-form"
|
|
377
|
+
${jhx({
|
|
378
|
+
post: '/api/login',
|
|
379
|
+
trigger: 'submit',
|
|
380
|
+
boost: true,
|
|
381
|
+
}, { stringify: true })}
|
|
382
|
+
>
|
|
383
|
+
...
|
|
384
|
+
</form>
|
|
385
|
+
</div>
|
|
386
|
+
`;
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
### `JhxComponent` in JSX
|
|
392
|
+
|
|
393
|
+
Using `JhxComponent` directly:
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
function HomePage() {
|
|
397
|
+
return (
|
|
398
|
+
<body>
|
|
399
|
+
<h1>Home Page</h1>
|
|
400
|
+
<JhxComponent
|
|
401
|
+
className="search-form"
|
|
402
|
+
as="form"
|
|
403
|
+
post="/api/search"
|
|
404
|
+
boost={true}
|
|
405
|
+
>
|
|
406
|
+
<input type="text" name="search-input" />
|
|
407
|
+
<button type="submit">Search</button>
|
|
408
|
+
</JhxComponent>
|
|
409
|
+
</body>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Creating custom components with `JhxComponent`:
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
function Form({ children, endpoint }: { children: React.ReactNode, endpoint: string }) {
|
|
418
|
+
return (
|
|
419
|
+
<JhxComponent
|
|
420
|
+
as="form"
|
|
421
|
+
post={endpoint}
|
|
422
|
+
boost={true}
|
|
423
|
+
>
|
|
424
|
+
{children}
|
|
425
|
+
</JhxComponent>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function HomePage() {
|
|
430
|
+
return (
|
|
431
|
+
<body>
|
|
432
|
+
<h1>Home Page</h1>
|
|
433
|
+
<Form endpoint="/api/search">
|
|
434
|
+
<input type="text" name="search-input" />
|
|
435
|
+
<button type="submit">Search</button>
|
|
436
|
+
</Form>
|
|
437
|
+
</body>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### HTMX & DOM Events
|
|
445
|
+
|
|
446
|
+
Declaring HTMX event handlers:
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
// HTMX events
|
|
450
|
+
jhx({
|
|
451
|
+
route: '/api/data',
|
|
452
|
+
onBeforeRequest: ({ event }) => {
|
|
453
|
+
console.log('Sending request', event);
|
|
454
|
+
},
|
|
455
|
+
onAfterSwap: ({ event }) => {
|
|
456
|
+
console.log('Content swapped', event);
|
|
457
|
+
},
|
|
458
|
+
/* ... */
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Declaring DOM event handlers (**only available for the `jhx` function when `config.stringify` is set to `true`**):
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// HTMX & DOM events
|
|
466
|
+
jhx({
|
|
467
|
+
route: '/api/data',
|
|
468
|
+
onBeforeRequest: ({ event }) => {
|
|
469
|
+
console.log('Sending request', event);
|
|
470
|
+
},
|
|
471
|
+
onAfterSwap: ({ event }) => {
|
|
472
|
+
console.log('Content swapped', event);
|
|
473
|
+
},
|
|
474
|
+
onSubmit: ({ event }) => {
|
|
475
|
+
console.log("Mouse over!", event);
|
|
476
|
+
},
|
|
477
|
+
onClick: ({ event }) => {
|
|
478
|
+
console.log("Clicked!", event);
|
|
479
|
+
},
|
|
480
|
+
/* ... */
|
|
481
|
+
}, { stringify: true }); // 'stringify' must be set to 'true' for DOM handlers
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
### DOM Interactions & Type Safety
|
|
487
|
+
|
|
488
|
+
In all event handlers and DOM-related props, you have access to the `document`, `window`, and `htmx` objects in the DOM.
|
|
489
|
+
The `TDom` generic allows you to **define additional variables** that are available in the DOM.
|
|
490
|
+
|
|
491
|
+
Accessing the DOM from the event handlers:
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
jhx({
|
|
495
|
+
/* HTMX event handlers */
|
|
496
|
+
onBeforeRequest: ({ document, window, htmx }) => {
|
|
497
|
+
console.log('document object', document);
|
|
498
|
+
console.log('window object', window);
|
|
499
|
+
console.log('HTMX instance', htmx);
|
|
500
|
+
},
|
|
501
|
+
/* DOM event handlers (only available when `stringify: true`) */
|
|
502
|
+
onClick: ({ document, window, htmx }) => {
|
|
503
|
+
console.log('document object', document);
|
|
504
|
+
console.log('window object', window);
|
|
505
|
+
console.log('HTMX instance', htmx);
|
|
506
|
+
},
|
|
507
|
+
/* ... */
|
|
508
|
+
}, { stringify: true });
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Accessing additional DOM variables by using the `TDom` generic with `jhx`:
|
|
512
|
+
|
|
513
|
+
```tsx
|
|
514
|
+
import type { Alpine } from 'alpinejs'; // using Alpine.js
|
|
515
|
+
|
|
516
|
+
// define the variables that are available in the DOM
|
|
517
|
+
type Dom = {
|
|
518
|
+
valueOne: 'constant-value';
|
|
519
|
+
valueTwo: number;
|
|
520
|
+
Alpine: Alpine;
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const html = `
|
|
524
|
+
<head>
|
|
525
|
+
<script src="//unpkg.com/alpinejs" defer></script>
|
|
526
|
+
</head>
|
|
527
|
+
<body>
|
|
528
|
+
<h1>Home Page</h1>
|
|
529
|
+
<button ${
|
|
530
|
+
jhx<Dom>({ // specify the generic
|
|
531
|
+
route: '/api',
|
|
532
|
+
onBeforeRequest: ({ valueOne, valueTwo, Alpine }) => {
|
|
533
|
+
// type safe access to the defined DOM variables
|
|
534
|
+
console.log('valueOne variable', valueOne);
|
|
535
|
+
console.log('valueTwo variable', valueTwo);
|
|
536
|
+
console.log('Alpine instance', Alpine);
|
|
537
|
+
},
|
|
538
|
+
onClick: ({ valueOne, valueTwo, Alpine }) => {
|
|
539
|
+
// type safe access to the defined DOM variables
|
|
540
|
+
console.log('valueOne variable', valueOne);
|
|
541
|
+
console.log('valueTwo variable', valueTwo);
|
|
542
|
+
console.log('Alpine instance', Alpine);
|
|
543
|
+
},
|
|
544
|
+
}, { stringify: true })
|
|
545
|
+
}>
|
|
546
|
+
Load Data
|
|
547
|
+
</button>
|
|
548
|
+
<script>
|
|
549
|
+
const valueOne = 'constant-value';
|
|
550
|
+
const valueTwo = Math.random() * 100;
|
|
551
|
+
</script>
|
|
552
|
+
</body>
|
|
553
|
+
`;
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
- Accessing additional DOM variables by using the `TDom` generic with `JhxComponent`:
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
import type { Alpine } from 'alpinejs'; // using Alpine.js
|
|
560
|
+
|
|
561
|
+
// define the variables that are available in the DOM
|
|
562
|
+
type Dom = {
|
|
563
|
+
Alpine: Alpine;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
function HomePage() {
|
|
567
|
+
return (
|
|
568
|
+
// Alpine.js initialized in `<head>`
|
|
569
|
+
<main>
|
|
570
|
+
<h1>Home Page</h1>
|
|
571
|
+
<JhxComponent<Dom> // specify the generic
|
|
572
|
+
as='button'
|
|
573
|
+
route='/api'
|
|
574
|
+
onBeforeRequest={({ Alpine }) => {
|
|
575
|
+
// type safe access to the defined DOM variables
|
|
576
|
+
console.log('Alpine instance', Alpine);
|
|
577
|
+
}}
|
|
578
|
+
/* ... */
|
|
579
|
+
>
|
|
580
|
+
Load Data
|
|
581
|
+
</JhxComponent>
|
|
582
|
+
</main>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
### Advanced Props
|
|
590
|
+
|
|
591
|
+
- [`disabledElt`, `include`, and `target`](#disabledelt-include-and-target)
|
|
592
|
+
- [`disinherit` and `inherit`](#disinherit-and-inherit)
|
|
593
|
+
- [`ext`](#ext)
|
|
594
|
+
- [`headers` and `vals`](#headers-and-vals)
|
|
595
|
+
- [`indicator`](#indicator)
|
|
596
|
+
- [`params`](#params)
|
|
597
|
+
- [`request`](#request)
|
|
598
|
+
- [`selectOob`](#selectoob)
|
|
599
|
+
- [`swap`](#swap)
|
|
600
|
+
- [`swapOob`](#swapoob)
|
|
601
|
+
- [`sync`](#sync)
|
|
602
|
+
- [`trigger`](#trigger)
|
|
603
|
+
|
|
604
|
+
> HTMX Documentation: [Reference](https://htmx.org/reference/)
|
|
605
|
+
|
|
606
|
+
#### `disabledElt`, `include`, and `target`
|
|
607
|
+
|
|
608
|
+
Disabling the nearest card and include its input values; target the card for the swap:
|
|
609
|
+
|
|
610
|
+
```tsx
|
|
611
|
+
function SaveCard() {
|
|
612
|
+
return (
|
|
613
|
+
<div className="card">
|
|
614
|
+
<input name="title" placeholder="Title" />
|
|
615
|
+
<button {...jhx({
|
|
616
|
+
post: '/cards/1',
|
|
617
|
+
include: {
|
|
618
|
+
position: 'closest',
|
|
619
|
+
selector: '.card',
|
|
620
|
+
},
|
|
621
|
+
disabledElt: {
|
|
622
|
+
position: 'closest',
|
|
623
|
+
selector: '.card',
|
|
624
|
+
},
|
|
625
|
+
target: {
|
|
626
|
+
position: 'closest',
|
|
627
|
+
selector: '.card',
|
|
628
|
+
},
|
|
629
|
+
swap: htmx.swap.outerHTML,
|
|
630
|
+
})}>
|
|
631
|
+
Save
|
|
632
|
+
</button>
|
|
633
|
+
</div>
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Disabling and including items in a bulk action:
|
|
639
|
+
|
|
640
|
+
```tsx
|
|
641
|
+
function BulkArchive() {
|
|
642
|
+
return (
|
|
643
|
+
<div className="toolbar">
|
|
644
|
+
<button id="archive" {...jhx({
|
|
645
|
+
post: '/items/archive',
|
|
646
|
+
include: '.list input[type=checkbox]:checked',
|
|
647
|
+
disabledElt: [
|
|
648
|
+
'#archive',
|
|
649
|
+
{
|
|
650
|
+
position: 'closest',
|
|
651
|
+
selector: '.toolbar',
|
|
652
|
+
}
|
|
653
|
+
],
|
|
654
|
+
target: {
|
|
655
|
+
position: 'find',
|
|
656
|
+
selector: '#results',
|
|
657
|
+
},
|
|
658
|
+
swap: htmx.swap.innerHTML,
|
|
659
|
+
})}>
|
|
660
|
+
Archive Selected
|
|
661
|
+
</button>
|
|
662
|
+
</div>
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
#### `disinherit` and `inherit`
|
|
668
|
+
|
|
669
|
+
Providing default target/swap for children with a child opting out of inheriting the target:
|
|
670
|
+
|
|
671
|
+
```tsx
|
|
672
|
+
function ListActions() {
|
|
673
|
+
return (
|
|
674
|
+
<div {...jhx({
|
|
675
|
+
inherit: ['hx-target', 'hx-swap'],
|
|
676
|
+
target: '#list',
|
|
677
|
+
swap: htmx.swap.beforeEnd,
|
|
678
|
+
})}>
|
|
679
|
+
<button {...jhx({get: '/list?page=2'})}>
|
|
680
|
+
Load More (inherits target/swap)
|
|
681
|
+
</button>
|
|
682
|
+
<button {...jhx({
|
|
683
|
+
get: '/stats',
|
|
684
|
+
disinherit: htmx.attr.target,
|
|
685
|
+
target: '#stats',
|
|
686
|
+
})}>
|
|
687
|
+
Load Stats
|
|
688
|
+
</button>
|
|
689
|
+
</div>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Inheriting everything from the parent with a child opting out from headers and target:
|
|
695
|
+
|
|
696
|
+
```tsx
|
|
697
|
+
function Screen() {
|
|
698
|
+
return (
|
|
699
|
+
<div {...jhx({
|
|
700
|
+
inherit: '*',
|
|
701
|
+
headers: {'X-App': 'demo'},
|
|
702
|
+
target: '#main',
|
|
703
|
+
})}>
|
|
704
|
+
<a {...jhx({
|
|
705
|
+
boosted: true,
|
|
706
|
+
get: '/home',
|
|
707
|
+
})}>
|
|
708
|
+
Home
|
|
709
|
+
</a>
|
|
710
|
+
<button {...jhx({
|
|
711
|
+
disinherit: [
|
|
712
|
+
htmx.attr.headers,
|
|
713
|
+
htmx.attr.target,
|
|
714
|
+
],
|
|
715
|
+
post: '/logout',
|
|
716
|
+
target: 'this',
|
|
717
|
+
swap: 'none',
|
|
718
|
+
})}>
|
|
719
|
+
Logout (no inherit)
|
|
720
|
+
</button>
|
|
721
|
+
</div>
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
#### `ext`
|
|
727
|
+
|
|
728
|
+
Enabling `sse` and `preload` extensions:
|
|
729
|
+
|
|
730
|
+
```tsx
|
|
731
|
+
function LiveFeed() {
|
|
732
|
+
return (
|
|
733
|
+
<div {...jhx({
|
|
734
|
+
get: '/events',
|
|
735
|
+
ext: ['sse', 'preload'],
|
|
736
|
+
trigger: 'load',
|
|
737
|
+
target: '#feed',
|
|
738
|
+
swap: 'beforeend',
|
|
739
|
+
})} />
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
Using `morph` (idiomorph) and ignoring on a child:
|
|
745
|
+
|
|
746
|
+
```tsx
|
|
747
|
+
function MorphList() {
|
|
748
|
+
return (
|
|
749
|
+
<div {...jhx({ ext: 'morph' })}>
|
|
750
|
+
<button {...jhx({
|
|
751
|
+
get: '/items',
|
|
752
|
+
target: '#list',
|
|
753
|
+
swap: 'morph',
|
|
754
|
+
ext: {
|
|
755
|
+
name: 'morph',
|
|
756
|
+
ignore: true,
|
|
757
|
+
},
|
|
758
|
+
})}>
|
|
759
|
+
Refresh (no morph)
|
|
760
|
+
</button>
|
|
761
|
+
<ul id="list" />
|
|
762
|
+
</div>
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
#### `headers` and `vals`
|
|
768
|
+
|
|
769
|
+
The `JhxEvaluableAttribute` type allows you to define static or dynamic values:
|
|
770
|
+
- When using a **function**, it will be evaluated client-side at trigger time.
|
|
771
|
+
- Function parameters can access the DOM and use `TDom` generic (see the [DOM Interactions & Type Safety](#dom-interactions--type-safety) section for usage).
|
|
772
|
+
- When using a **string** or **object**, it will be evaluated at server-render time.
|
|
773
|
+
|
|
774
|
+
Computing headers and values at server-render time:
|
|
775
|
+
|
|
776
|
+
```tsx
|
|
777
|
+
function CreateItem() {
|
|
778
|
+
return (
|
|
779
|
+
<button {...jhx({
|
|
780
|
+
post: '/api/items',
|
|
781
|
+
headers: {
|
|
782
|
+
'X-CSRF-TOKEN': 'ABCD1234',
|
|
783
|
+
},
|
|
784
|
+
vals: {
|
|
785
|
+
source: 'ui',
|
|
786
|
+
scope: 'dashboard',
|
|
787
|
+
},
|
|
788
|
+
target: '#result',
|
|
789
|
+
})}>
|
|
790
|
+
Create
|
|
791
|
+
</button>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Computing headers and values with client-side functions:
|
|
797
|
+
|
|
798
|
+
```tsx
|
|
799
|
+
function SearchButton() {
|
|
800
|
+
return (
|
|
801
|
+
<button {...jhx({
|
|
802
|
+
post: '/api/search',
|
|
803
|
+
headers: ({ window }) => ({
|
|
804
|
+
'X-Viewport': `${window.innerWidth}x${window.innerHeight}`,
|
|
805
|
+
}),
|
|
806
|
+
vals: ({ document }) => ({
|
|
807
|
+
q: document.getElementById('search-query')?.value ?? '',
|
|
808
|
+
}),
|
|
809
|
+
target: '#results',
|
|
810
|
+
})}>
|
|
811
|
+
Search
|
|
812
|
+
</button>
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
#### `indicator`
|
|
818
|
+
|
|
819
|
+
Using a spinner element:
|
|
820
|
+
|
|
821
|
+
```tsx
|
|
822
|
+
function Toolbar() {
|
|
823
|
+
return (
|
|
824
|
+
<div className="toolbar">
|
|
825
|
+
<button {...jhx({
|
|
826
|
+
get: '/api/list',
|
|
827
|
+
target: '#list',
|
|
828
|
+
indicator: '#spinner',
|
|
829
|
+
})}>
|
|
830
|
+
Refresh
|
|
831
|
+
</button>
|
|
832
|
+
<span id="spinner" className="hidden">
|
|
833
|
+
Loading...
|
|
834
|
+
</span>
|
|
835
|
+
<div id="list" />
|
|
836
|
+
</div>
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
Using a spinner element in the closest row:
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
function ItemRow() {
|
|
845
|
+
return (
|
|
846
|
+
<div className="row">
|
|
847
|
+
<button {...jhx({
|
|
848
|
+
post: '/api/item/1/like',
|
|
849
|
+
indicator: {
|
|
850
|
+
selector: '.spinner',
|
|
851
|
+
closest: true,
|
|
852
|
+
},
|
|
853
|
+
})}>
|
|
854
|
+
Like
|
|
855
|
+
</button>
|
|
856
|
+
<span className="spinner hidden">{/* ... */}</span>
|
|
857
|
+
</div>
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
#### `params`
|
|
863
|
+
|
|
864
|
+
Exclude fields from a form submission:
|
|
865
|
+
|
|
866
|
+
```tsx
|
|
867
|
+
function SignUpForm() {
|
|
868
|
+
return (
|
|
869
|
+
<form {...jhx({
|
|
870
|
+
boosted: true,
|
|
871
|
+
post: '/signup',
|
|
872
|
+
params: { exclude: ['password-confirm'] },
|
|
873
|
+
})}>
|
|
874
|
+
<input name="email" />
|
|
875
|
+
<input name="password" type="password" />
|
|
876
|
+
<input name="password-confirm" type="password" />
|
|
877
|
+
<button type="submit">Sign up</button>
|
|
878
|
+
</form>
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
Include parameters in a get request:
|
|
884
|
+
|
|
885
|
+
```tsx
|
|
886
|
+
function SearchForm() {
|
|
887
|
+
return (
|
|
888
|
+
<form {...jhx({
|
|
889
|
+
boosted: true,
|
|
890
|
+
get: '/search',
|
|
891
|
+
params: { include: ['q', 'page'] },
|
|
892
|
+
})}>
|
|
893
|
+
<input id="q" name="q" />
|
|
894
|
+
<input name="page" type="hidden" value="1" />
|
|
895
|
+
<input name="debug" />
|
|
896
|
+
<button type="submit">Search</button>
|
|
897
|
+
</form>
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
#### `request`
|
|
903
|
+
|
|
904
|
+
The `JhxRequestAttribute` type allows you to define static or dynamic values:
|
|
905
|
+
- When using a **function**, it will be evaluated client-side at trigger time.
|
|
906
|
+
- Function parameters can access the DOM and use `TDom` generic (see the [DOM Interactions & Type Safety](#dom-interactions--type-safety) section for usage).
|
|
907
|
+
- When using a **string** or **object**, it will be evaluated at server-render time.
|
|
908
|
+
|
|
909
|
+
Computing request values at server-render time:
|
|
910
|
+
|
|
911
|
+
```tsx
|
|
912
|
+
function SlowAction() {
|
|
913
|
+
return (
|
|
914
|
+
<button {...jhx({
|
|
915
|
+
get: '/slow',
|
|
916
|
+
request: {
|
|
917
|
+
timeout: 8000,
|
|
918
|
+
credentials: true,
|
|
919
|
+
},
|
|
920
|
+
})}>
|
|
921
|
+
Load
|
|
922
|
+
</button>
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
Computing request values with a client-side function:
|
|
928
|
+
|
|
929
|
+
```tsx
|
|
930
|
+
function FetchData() {
|
|
931
|
+
return (
|
|
932
|
+
<button {...jhx({
|
|
933
|
+
get: '/data',
|
|
934
|
+
request: ({ window }) => ({
|
|
935
|
+
timeout: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 10000 : 3000,
|
|
936
|
+
noHeaders: true,
|
|
937
|
+
}),
|
|
938
|
+
})}>
|
|
939
|
+
Fetch
|
|
940
|
+
</button>
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
#### `selectOob`
|
|
946
|
+
|
|
947
|
+
Update head elements out-of-band:
|
|
948
|
+
|
|
949
|
+
```tsx
|
|
950
|
+
function NavLink() {
|
|
951
|
+
return (
|
|
952
|
+
<a {...jhx({
|
|
953
|
+
boosted: true,
|
|
954
|
+
get: '/page',
|
|
955
|
+
selectOob: ['title', 'meta[name=description]'],
|
|
956
|
+
pushUrl: true,
|
|
957
|
+
})}>
|
|
958
|
+
Open Page
|
|
959
|
+
</a>
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
Apply out-of-band selection with a specific swap style and avoid main target swap:
|
|
965
|
+
|
|
966
|
+
```tsx
|
|
967
|
+
function ShowToast() {
|
|
968
|
+
return (
|
|
969
|
+
<button {...jhx({
|
|
970
|
+
get: '/alerts',
|
|
971
|
+
swap: 'none',
|
|
972
|
+
selectOob: {
|
|
973
|
+
selector: '#toast',
|
|
974
|
+
swap: htmx.swap.afterEnd,
|
|
975
|
+
},
|
|
976
|
+
})}>
|
|
977
|
+
Show Toast
|
|
978
|
+
</button>
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
#### `swap`
|
|
984
|
+
|
|
985
|
+
Delay the swap/settle:
|
|
986
|
+
|
|
987
|
+
```tsx
|
|
988
|
+
function RefreshCard() {
|
|
989
|
+
return (
|
|
990
|
+
<button {...jhx({
|
|
991
|
+
get: '/items/1',
|
|
992
|
+
target: '.card',
|
|
993
|
+
swap: {
|
|
994
|
+
style: htmx.swap.outerHTML,
|
|
995
|
+
swap: '200ms', settle: '100ms',
|
|
996
|
+
},
|
|
997
|
+
})}>
|
|
998
|
+
Refresh
|
|
999
|
+
</button>
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
Load button with transition and infinite scroll:
|
|
1005
|
+
|
|
1006
|
+
```tsx
|
|
1007
|
+
function LoadButton() {
|
|
1008
|
+
return (
|
|
1009
|
+
<button {...jhx({
|
|
1010
|
+
get: '/list?page=2',
|
|
1011
|
+
target: '#list',
|
|
1012
|
+
swap: {
|
|
1013
|
+
style: htmx.swap.afterEnd,
|
|
1014
|
+
scroll: {
|
|
1015
|
+
selector: '#list',
|
|
1016
|
+
position: 'bottom',
|
|
1017
|
+
},
|
|
1018
|
+
show: 'bottom',
|
|
1019
|
+
transition: true,
|
|
1020
|
+
},
|
|
1021
|
+
})}>
|
|
1022
|
+
Load more
|
|
1023
|
+
</button>
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
#### `swapOob`
|
|
1029
|
+
|
|
1030
|
+
Mark a fragment to swap out-of-band:
|
|
1031
|
+
|
|
1032
|
+
```tsx
|
|
1033
|
+
function CartItem({ children }: { children: React.ReactNode }) {
|
|
1034
|
+
return (
|
|
1035
|
+
<div {...jhx({
|
|
1036
|
+
swapOob: true,
|
|
1037
|
+
})}>
|
|
1038
|
+
{children}
|
|
1039
|
+
</div>
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
Append a notification item to a container:
|
|
1045
|
+
|
|
1046
|
+
```tsx
|
|
1047
|
+
function NotificationItem({ message }: { message: string }) {
|
|
1048
|
+
return (
|
|
1049
|
+
<li
|
|
1050
|
+
className="search"
|
|
1051
|
+
{...jhx({
|
|
1052
|
+
swapOob: {
|
|
1053
|
+
swap: htmx.swap.beforeEnd,
|
|
1054
|
+
selector: '#notifications',
|
|
1055
|
+
},
|
|
1056
|
+
})}
|
|
1057
|
+
>
|
|
1058
|
+
{message}
|
|
1059
|
+
</li>
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
#### `sync`
|
|
1065
|
+
|
|
1066
|
+
Queue on the nearest ".search" container while typing:
|
|
1067
|
+
|
|
1068
|
+
```tsx
|
|
1069
|
+
function SearchInput() {
|
|
1070
|
+
return (
|
|
1071
|
+
<div className="search">
|
|
1072
|
+
<input {...jhx({
|
|
1073
|
+
get: '/search',
|
|
1074
|
+
target: '#results',
|
|
1075
|
+
trigger: {
|
|
1076
|
+
event: 'input',
|
|
1077
|
+
throttle: '300ms'
|
|
1078
|
+
},
|
|
1079
|
+
sync: {
|
|
1080
|
+
strategy: htmx.sync.queueFirst,
|
|
1081
|
+
tag: '.search',
|
|
1082
|
+
closest: true
|
|
1083
|
+
},
|
|
1084
|
+
})} />
|
|
1085
|
+
<div id="results"/>
|
|
1086
|
+
</div>
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
Replace an in-flight request with the latest:
|
|
1092
|
+
|
|
1093
|
+
```tsx
|
|
1094
|
+
function PollStatus() {
|
|
1095
|
+
return (
|
|
1096
|
+
<button {...jhx({
|
|
1097
|
+
get: '/status',
|
|
1098
|
+
trigger: {
|
|
1099
|
+
poll: '5s',
|
|
1100
|
+
},
|
|
1101
|
+
sync: {
|
|
1102
|
+
strategy: htmx.sync.replace,
|
|
1103
|
+
},
|
|
1104
|
+
})}>
|
|
1105
|
+
Start Polling
|
|
1106
|
+
</button>
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
#### `trigger`
|
|
1112
|
+
|
|
1113
|
+
Text input with throttle and queue behavior:
|
|
1114
|
+
|
|
1115
|
+
```tsx
|
|
1116
|
+
function FilterInput() {
|
|
1117
|
+
return (
|
|
1118
|
+
<input {...jhx({
|
|
1119
|
+
get: '/filter',
|
|
1120
|
+
target: '#out',
|
|
1121
|
+
trigger: {
|
|
1122
|
+
event: 'input',
|
|
1123
|
+
changed: true,
|
|
1124
|
+
throttle: '300ms',
|
|
1125
|
+
queue: 'last'
|
|
1126
|
+
},
|
|
1127
|
+
})} />
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
Lazy-load when element intersects viewport (only once):
|
|
1133
|
+
|
|
1134
|
+
```tsx
|
|
1135
|
+
function LazySection() {
|
|
1136
|
+
return (
|
|
1137
|
+
<div {...jhx({
|
|
1138
|
+
get: '/lazy-content',
|
|
1139
|
+
trigger: {
|
|
1140
|
+
trigger: 'intersect',
|
|
1141
|
+
threshold: 0.25,
|
|
1142
|
+
once: true,
|
|
1143
|
+
},
|
|
1144
|
+
})} />
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
```
|