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/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
+ ```