75 lines
7.3 KiB
Markdown
75 lines
7.3 KiB
Markdown
# hemx HTML examples
|
|
|
|
A copy-pasteable pattern gallery for the boring HTML UX patterns popularized by
|
|
htmx. The point is not to clone htmx attributes; it is to show the hemx idiom:
|
|
plain `.heml`, generated resources, server-owned Rust state, keyed partials, and
|
|
tiny runtime behavior. req: htmx_equivalents/001 req: htmx_equivalents/005 req: examples/001
|
|
|
|
Run it:
|
|
|
|
```sh
|
|
cargo run -p hemx-html-examples
|
|
```
|
|
|
|
Open <http://127.0.0.1:3029>.
|
|
|
|
The active-search example is URL state rather than an interaction handle: its
|
|
GET form serializes the visible `q` control into the page URL, live input uses
|
|
`data-hemx-history="replace"`, and the explicit submit button uses
|
|
`data-hemx-history="push"`. Reload, bookmark, and browser back/forward therefore
|
|
ask the same server route to re-render the filtered gallery instead of restoring
|
|
client-owned search state. req: page_swap/009 req: page_swap/010
|
|
|
|
## Pattern matrix
|
|
|
|
Names match the htmx example URL slug exactly, e.g. `modal-custom` from
|
|
`https://htmx.org/examples/modal-custom/`. Rust resource names use normal
|
|
identifier spelling only where the language requires it.
|
|
|
|
Status legend:
|
|
|
|
- **implemented**: copyable `.heml` and server handlers exist in this example.
|
|
- **integration-owned**: use hemx generated resources plus app/host/browser policy;
|
|
do not grow hemx core for the policy.
|
|
- **refused**: would clone htmx/client framework behavior or a third-party UI kit.
|
|
- **deferred**: useful, but needs a later vertical slice and proof before becoming
|
|
a copyable hemx pattern.
|
|
|
|
| htmx example slug | Status | hemx idiom / boundary | Proof anchor |
|
|
| --- | --- | --- | --- |
|
|
| `click-to-edit` | implemented | A read view and edit form are the same generated `contact_card` partial; the server toggles `editing` and returns `gallery::contact_card.replace(...)`. | `templates/partials/contact_card.heml`, `contact_card_handlers::edit_contact`, `save_contact` |
|
|
| `bulk-update` | deferred | Same generated-form path as inline validation, but needs a real multi-row selection/write slice so batch semantics are tested instead of claimed. | Next slice should add keyed batch rows plus one server-owned bulk command. |
|
|
| `click-to-load` | implemented | The server owns the loaded count and returns generated keyed `loaded_row` replacements plus status text. | `gallery_handlers::load_more`, `LoadedRow` |
|
|
| `delete-row` | implemented | Server state removes the row and returns `gallery::editable_row.remove(id)`. | `editable_row_handlers::delete_row` |
|
|
| `edit-row` | implemented | A table row is a keyed `.heml` partial with generated edit/save forms; no selector target strings. | `templates/partials/editable_row.heml`, `editable_row_handlers::edit_row`, `save_row` |
|
|
| `lazy-load` | implemented | `data-hemx-revealed` dispatches a generated form once when visible; the server swaps a generated lazy panel. | `gallery.heml`, `gallery_handlers::lazy_load`, `LazyPanel` |
|
|
| `inline-validation` | implemented | A generated form reports field failure with `validate_email_form.error(...)`, focuses the field, and updates status text. | `templates/gallery.heml`, `gallery_handlers::validate_email` |
|
|
| `infinite-scroll` | implemented | A revealed sentinel form posts to the same server-owned loading model and replaces generated keyed rows. | `gallery_handlers::infinite_scroll`, `data-hemx-revealed`, `infinite_row` |
|
|
| `active-search` | implemented | The search form uses GET URL state; the server derives result rows and reconciles generated keyed partials by removing filtered-out keys, replacing retained keys, and appending newly visible keys. | `gallery_handlers::search`, `SearchResult` |
|
|
| `progress-bar` | implemented | The Tick progress button advances server-owned progress and replaces a generated progress partial with visible percentage text. | `gallery_handlers::tick_progress`, `ProgressMeter` |
|
|
| `value-select` | implemented | The first select posts a generated form; the server derives and replaces generated option rows for the second select. | `gallery_handlers::choose_category`, `ValueOption` |
|
|
| `animations` | integration-owned | CSS transitions are presentation policy around generated replacements; hemx should only preserve stable DOM boundaries. | Use keyed partials and app CSS; no core animation framework. |
|
|
| `file-upload` | integration-owned | Upload transport, size limits, progress, storage, and security are app/integration policy. | Needs product-owned upload route before becoming copyable. |
|
|
| `file-upload-input` | integration-owned | Preserving file inputs after errors is browser/security policy; hemx should not fake file state in core effects. | Use app-owned upload form policy. |
|
|
| `reset-user-input` | implemented | A generated form updates status and returns `.clear()` after successful submission. | `gallery_handlers::reset_message` |
|
|
| `dialogs` | integration-owned | Browser `alert/confirm/prompt` are app policy; hemx can expose event boundaries but should not own dialog UX. | Use native controls or host/app code. |
|
|
| `modal-uikit` | refused | Third-party UI kit integration is not a hemx core pattern. | Keep as app-owned integration. |
|
|
| `modal-bootstrap` | refused | Third-party UI kit integration is not a hemx core pattern. | Keep as app-owned integration. |
|
|
| `modal-custom` | deferred | A custom modal can be a generated partial plus focus/escape policy, but needs accessibility proof before copy/paste. | Later slice should include keyboard/focus tests. |
|
|
| `tabs-hateoas` | deferred | Good hemx fit: server-owned selected tab and generated tab panel replacement; needs a focused slice. | Later slice should add one tab group. |
|
|
| `tabs-javascript` | refused | Client-owned tab state is exactly what generated server-owned state is meant to avoid unless a product needs it. | Prefer `tabs-hateoas`. |
|
|
| `keyboard-shortcuts` | integration-owned | Keyboard policy belongs to the app/host; hemx should only receive explicit events. | Use generated app-level events / app JS when needed. |
|
|
| `sortable` | integration-owned | Drag/drop ordering needs a browser library or pointer policy plus server reorder command. | Keep Sortable.js as app-owned integration until proven reusable. |
|
|
| `update-other-content` | implemented | Generated effects can update multiple slots from one handler; validation and search already update status plus rows/errors. | `validate_email`, `search` handlers. |
|
|
| `confirm` | integration-owned | Confirmation wording and irreversible-action policy belong to the app; hemx should not own a global confirm system. | Use native confirm/app dialog around generated delete forms. |
|
|
| `async-auth` | integration-owned | Token refresh/auth sessions belong to auth/session integration, not hemx core. | See auth/session recipe boundary. |
|
|
| `web-components` | integration-owned | Shadow DOM/custom elements are host integration; hemx can emit events but should not pierce component internals. | Use app-owned web component adapters. |
|
|
| `move-before` | refused | Experimental DOM preservation API is not a stable hemx contract. | Avoid until browser support and a product need make it boring. |
|
|
|
|
## Boundary
|
|
|
|
Implemented rows must remain runnable hemx behavior. Deferred/integration-owned/refused
|
|
rows are not failures; they prevent a trophy checklist from turning hemx into a
|
|
client framework. Promote a deferred row only when the slice proves a reusable,
|
|
boring contract with `.heml`, generated resources, server-owned state, and tests.
|