void-plugin-hono-jsx 0.1.0-alpha.4 → 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +51 -22
  3. package/dist/browser/form-view.d.ts +4 -3
  4. package/dist/browser/form-view.d.ts.map +1 -1
  5. package/dist/browser/form-view.js +6 -4
  6. package/dist/browser/form-view.js.map +1 -1
  7. package/dist/browser/form.d.ts +2 -2
  8. package/dist/browser/form.d.ts.map +1 -1
  9. package/dist/browser/form.js +13 -5
  10. package/dist/browser/form.js.map +1 -1
  11. package/dist/browser/navigation.d.ts.map +1 -1
  12. package/dist/browser/navigation.js +82 -21
  13. package/dist/browser/navigation.js.map +1 -1
  14. package/dist/plugins/compatibility.d.ts +2 -0
  15. package/dist/plugins/compatibility.d.ts.map +1 -1
  16. package/dist/plugins/compatibility.js +13 -4
  17. package/dist/plugins/compatibility.js.map +1 -1
  18. package/dist/plugins/hono-islands.d.ts.map +1 -1
  19. package/dist/plugins/hono-islands.js +78 -31
  20. package/dist/plugins/hono-islands.js.map +1 -1
  21. package/dist/plugins/hono-jsx.d.ts +11 -3
  22. package/dist/plugins/hono-jsx.d.ts.map +1 -1
  23. package/dist/plugins/hono-jsx.js +183 -18
  24. package/dist/plugins/hono-jsx.js.map +1 -1
  25. package/dist/plugins/hono-markdown.d.ts.map +1 -1
  26. package/dist/plugins/hono-markdown.js +25 -14
  27. package/dist/plugins/hono-markdown.js.map +1 -1
  28. package/dist/plugins/jsx-transform.d.ts +4 -0
  29. package/dist/plugins/jsx-transform.d.ts.map +1 -0
  30. package/dist/plugins/jsx-transform.js +21 -0
  31. package/dist/plugins/jsx-transform.js.map +1 -0
  32. package/dist/plugins/markdown-api.d.ts +2 -1
  33. package/dist/plugins/markdown-api.d.ts.map +1 -1
  34. package/dist/plugins/markdown-api.js.map +1 -1
  35. package/dist/server/form-view.d.ts +4 -3
  36. package/dist/server/form-view.d.ts.map +1 -1
  37. package/dist/server/form-view.js +6 -4
  38. package/dist/server/form-view.js.map +1 -1
  39. package/dist/server/form.d.ts +2 -2
  40. package/dist/server/form.d.ts.map +1 -1
  41. package/dist/server/form.js +13 -5
  42. package/dist/server/form.js.map +1 -1
  43. package/package.json +6 -6
  44. package/src/plugins/compatibility.ts +11 -4
  45. package/src/plugins/hono-islands.ts +59 -27
  46. package/src/plugins/hono-jsx.ts +176 -17
  47. package/src/plugins/hono-markdown.ts +20 -10
  48. package/src/plugins/jsx-transform.ts +20 -0
  49. package/src/plugins/markdown-api.ts +2 -1
  50. package/src/runtime/form-view.tsx +9 -5
  51. package/src/runtime/form.ts +13 -8
  52. package/src/runtime/navigation.ts +64 -21
  53. package/dist/plugins/hono-pages.d.ts +0 -12
  54. package/dist/plugins/hono-pages.d.ts.map +0 -1
  55. package/dist/plugins/hono-pages.js +0 -186
  56. package/dist/plugins/hono-pages.js.map +0 -1
  57. package/src/plugins/hono-pages.ts +0 -179
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-rc.0](https://github.com/ts-76/void-plugin-hono-jsx/compare/v0.1.0-alpha.4...v0.1.0-rc.0) (2026-10-17)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * the release candidate supports Void >=1.1.11 <1.2 and Node >=24.21.0 <25; old Void 0.x launchers are historical fixtures only.
9
+
10
+ ### Features
11
+
12
+ * consolidate the Void 1.1 release candidate ([#92](https://github.com/ts-76/void-plugin-hono-jsx/issues/92)) ([2793b54](https://github.com/ts-76/void-plugin-hono-jsx/commit/2793b54937a01a890d8df858f82ae2f612e5aacf))
13
+
3
14
  ## [0.1.0-alpha.4](https://github.com/ts-76/void-plugin-hono-jsx/compare/v0.1.0-alpha.3...v0.1.0-alpha.4) (2026-10-17)
4
15
 
5
16
 
package/README.md CHANGED
@@ -1,36 +1,38 @@
1
1
  # Experimental Hono Pages adapter for Void
2
2
 
3
- This experimental package integrates Hono JSX with Void Pages. The package name is `void-plugin-hono-jsx`. Alpha releases are published on the `next` dist-tag under the [MIT license](LICENSE). Use `@next` or an explicit alpha version. The maintainer also assigned `latest` to the published `0.1.0-alpha.1`; an unqualified install currently selects this experimental alpha, not a stable release. It is not an official Void adapter.
3
+ This package integrates Hono JSX with Void Pages. This source documents the upcoming 0.1.0 RC/normal release; published alpha.4 has a different compatibility contract. Prereleases use `next`; normal releases move to `latest` after public verification. Before registry approval, use a local tarball. The package is [MIT licensed](LICENSE) and is not an official Void adapter.
4
4
 
5
5
  ## Supported versions
6
6
 
7
7
  | Component | Accepted stable range | Full consumer fixtures |
8
8
  | --- | --- | --- |
9
- | Node.js | >=24.21.0 | 24.21.0 |
10
- | Void | >=0.26.1 <0.28.0 | 0.26.1, 0.27.0 |
9
+ | Node.js | >=24.21.0 <25.0.0 | 24.21.0 |
10
+ | Void | >=1.1.11 <1.2.0 | 1.1.11 |
11
11
  | Hono | >=4.13.13 <5.0.0 | 4.13.13 |
12
- | Vite | >=8.0.16 <9.0.0 | 8.0.16 (Vite Plus), 8.3.3, 8.3.4 |
13
- | Vite Plus | Host Vite must satisfy the Vite range | 0.1.24 (scaffold), 1.1.0 (latest) |
12
+ | Vite | >=8.0.16 <9.0.0 | 8.3.4 |
13
+ | Vite Plus | Host Vite must satisfy the Vite range | 1.3.0 |
14
14
  | TypeScript | Project tooling | 7.0.2 |
15
15
 
16
- Published alpha.3 includes these peer ranges, running-Vite diagnostics and packaged source/maps. Alpha.1 and alpha.2 retain their exact-version gate. Stable Vite/Hono updates within the accepted ranges are admitted by compatibility policy; Void 0.28 and later require explicit validation. Prereleases are excluded. The fixture versions above are the 2026-10-12 snapshot; admitting a range does not mean every future version has already been tested. New upstream releases require refreshed fixtures, full checks and dependency audit review.
16
+ The upcoming RC targets Void 1.1 and Node 24 only. Published alpha.4 retains its earlier Void 0.x contract; it does not provide this support change or multipart transport. Old Void/launcher manifests remain historical comparison records, not release-selectable configurations. The two supported Hono-only consumers use Void 1.1.11 with plain Vite 8.3.4 or aligned Vite Plus 1.3.0 (host Vite 8.3.4). Ranges admit stable updates, but only locked fixtures are observed test results. New upstream releases require refreshed fixtures, full checks and audit review; prereleases and other Void minors are excluded.
17
17
 
18
- The expanded Link/Form APIs, island forms and graphs, deferred contracts, page options and optional Markdown documented below are additions after alpha.3. Use a later alpha containing the corresponding exports, or a local tarball to validate this source tree. Alpha.3 does not provide these additions.
18
+ Alpha.4 introduced the expanded Link/Form APIs, island forms and graphs, deferred contracts, page options and optional Markdown. This source adds the Void 1.1 contract and multipart transport for the RC; use the local tarball until the RC is approved. Do not combine the RC setup instructions with alpha.4.
19
19
 
20
20
  ## Install
21
21
 
22
- Use Node 24.21.0 or newer:
22
+ Use Node 24.21.0 or newer within Node 24:
23
+
24
+ The commands below target the RC line. Check registry availability after owner approval. Published alpha.4 does not accept Void 1.1; use a local tarball until the RC has been approved. Use Node >=24.21.0 <25.0.0.
23
25
 
24
26
  ```sh
25
- npm install void-plugin-hono-jsx@next hono@4.13.13
26
- npm install --save-dev void@0.27.0 vite@8.3.4 typescript@7.0.2
27
+ npm install void-plugin-hono-jsx@0.1.0-rc.0 hono@4.13.13
28
+ npm install --save-dev void@1.1.11 vite@8.3.4 typescript@7.0.2
27
29
  ```
28
30
 
29
31
  For a local validation tarball, in the repository, run `npm ci`, `npm run build:package`, then `npm pack ./packages/hono-pages`. Install the resulting tarball in your project alongside the supported peer versions:
30
32
 
31
33
  ```sh
32
34
  npm install ./void-plugin-hono-jsx-<version>.tgz hono@4.13.13
33
- npm install --save-dev void@0.27.0 vite@8.3.4 typescript@7.0.2
35
+ npm install --save-dev void@1.1.11 vite@8.3.4 typescript@7.0.2
34
36
  ```
35
37
 
36
38
  ## Configure
@@ -54,7 +56,7 @@ Use `jsx: "react-jsx"` and `jsxImportSource: "hono/jsx"` in TypeScript. The plug
54
56
 
55
57
  The config hook reads `this.meta.viteVersion` from the Vite instance executing the hook. Resolving `vite/package.json` from the adapter or project is not evidence of the running host. The adapter also checks the actual Vite helper module it imports. Errors include the host version, helper version, and absolute adapter/project package.json paths; different pnpm peer copies therefore remain visible.
56
58
 
57
- Bundled launchers such as `vp dev` can execute another Vite even when the project has a direct `vite` dependency. The Void 0.27.0 scaffold requests `vite-plus: ^0.1.21`, resolving to 0.1.24 with host Vite 8.0.16. Latest Vite Plus 1.1.0 runs host Vite 8.3.3. The scaffold fixture retains direct npm Vite 8.3.4. Latest Vite Plus requires its official aligned toolchain: `vite` aliases `npm:@voidzero-dev/vite-plus-core@1.1.0`, and the fixture follows its documented consumer-only Vite/Vitest overrides (Vitest 5.0.3). Both are exercised in independent locked consumers, including dev, build and preview. These settings align the toolchain; they do not replace vulnerable transitive dependencies. Standard Vite 8.3.3/8.3.4 is also covered. Installing project Vite alone does not change a bundled host; unsupported host errors identify the launcher action separately from stale helper peer resolution.
59
+ Bundled launchers such as `vp dev` can execute another Vite even when a direct npm `vite` is installed. The supported Vite Plus 1.3.0 configuration aliases `vite` to `npm:@voidzero-dev/vite-plus-core@1.3.0` and uses `overrides: { vite: "$vite" }` to keep helper and host peers aligned. The real config-hook host is Vite 8.3.4. Plain Vite 8.3.4 is independently exercised. Installing npm Vite alone does not change a bundled host; diagnostics identify launcher and helper peer paths separately. Historical 0.x scaffold settings are outside RC support. The alias aligns the launcher; it is not a vulnerability override.
58
60
 
59
61
  If the project version is already correct, inspect the paths in the diagnostic and refresh stale adapter peer resolutions/lockfiles. Do not infer the host identity from the project dependency alone. Missing hook version information fails closed. [Vite plugin context metadata](https://vite.dev/guide/api-plugin.html#plugin-context-meta)
60
62
 
@@ -68,7 +70,7 @@ Void's init wizard currently offers React, Vue, Svelte and Solid adapters rather
68
70
  4. Once React code is removed, uninstall `@void/react`, `react`, `react-dom`, `@types/react`, `@types/react-dom` and `oxc-transform-react` if they are otherwise unused. The Hono plugin supplies its own JSX transform.
69
71
  5. Keep `vp dev`, `vp build`, `vp preview`, or use the plain Vite scaffold. Verify the app's loaders/actions and database migrations locally.
70
72
 
71
- The latest consumer fixtures also install React scaffold dependencies (React/DOM/types 19.3.0, `@void/react` 0.27.0 and `oxc-transform-react` 0.153.0), showing that their presence does not block the Hono adapter. This is not a React-to-Hono source conversion or a claim that other UI frameworks run through this adapter.
73
+ The supported RC fixtures are Hono-only. Historical React/Void 0.x scaffold metadata is retained for comparison and is not a supported installation path.
72
74
 
73
75
  ## Source and debugging files
74
76
 
@@ -83,12 +85,33 @@ import { Link, useRouter, useShared, useParams, useErrors, useNavigation } from
83
85
  import { Form, useForm, useIslandForm, action } from 'void-plugin-hono-jsx/form';
84
86
  ```
85
87
 
86
- `Link` and `useRouter` use Void's shared navigation protocol. Link accepts `method`, `data`, `preserveScroll`, `preserveState`, `replace`, `reloadDocument` and cancellable `onNavigate`. GET data is serialized into its href; mutation links render buttons. Prefetch supports `true` (hover), `hover`, `click`, `mount`, `visible` or an array, with optional `cacheFor`. Prefetch and document reload are GET-only. User event handlers can prevent the adapter's action; external/download/target links retain native behavior. Ordinary GET visits reset page state; refresh preserves it. Root/nested/named layouts retain state while they remain in the active chain. Shared data, route params and field errors are available in SSR and browser components. `useNavigation()` returns Void's reactive `{ state, location, method }` (`idle`, `loading`, `submitting`); SSR starts idle and pending visits clear on completion, failure or cancellation. `useForm` supports all mutation methods, named actions, validation, duplicate suppression and cancellation. Existing `useForm(defaults)` with `post(url)` remains supported. The bound `useForm('/edit?save', defaults, options)` form also supports `post()`/`put()`/`patch()`/`delete()` and `<Form form={form}>`; provide `url` to Form for a legacy unbound model. `setData` accepts a complete object/updater or a typed field/value pair. `hasChanges`, `recentlySuccessful` (two seconds), selective `reset`/`clearErrors`, and `clearError` describe and reset state. A successful bound submission becomes the new defaults. `Form` displays field/action errors (customizable with `renderError`), respects quota retry deadlines, and never automatically resubmits. The typed `action(url, options)` helper performs a single action through the initialized Pages router. Cancellation does not undo server writes.
88
+ `Link` and `useRouter` use Void's shared navigation protocol. Link accepts `method`, `data`, `preserveScroll`, `preserveState`, `replace`, `reloadDocument` and cancellable `onNavigate`. GET data is serialized into its href; mutation links render buttons. Prefetch supports `true` (hover), `hover`, `click`, `mount`, `visible` or an array, with optional `cacheFor`. Prefetch and document reload are GET-only. User event handlers can prevent the adapter's action; external/download/target links retain native behavior. Ordinary GET visits reset page state; refresh preserves it. Root/nested/named layouts retain state while they remain in the active chain. Shared data, route params and field errors are available in SSR and browser components. `useNavigation()` returns Void's reactive `{ state, location, method }` (`idle`, `loading`, `submitting`); SSR starts idle and pending visits clear on completion, failure or cancellation. `useForm` supports all mutation methods, named actions, validation, duplicate suppression and cancellation. Existing `useForm(defaults)` with `post(url)` remains supported. The bound `useForm('/edit?save', defaults, options)` form also supports `post()`/`put()`/`patch()`/`delete()` and `<Form form={form}>`; provide `url` to Form for a legacy unbound model. `setData` accepts a complete object/updater or a typed field/value pair. `hasChanges`, `recentlySuccessful` (two seconds), selective `reset`/`clearErrors`, and `clearError` describe and reset state. A successful bound JSON submission becomes the new defaults. `Form` displays field/action errors (customizable with `renderError`), respects quota retry deadlines, and never automatically resubmits. The typed `action(url, options)` helper performs a single action through the initialized Pages router. Cancellation does not undo server writes.
87
89
 
88
90
  Head resolution and escaping follow Void. The adapter synchronizes document attributes and inert script metadata; navigation does not execute newly inserted scripts. Raw `script.innerHTML` is authored content. Serialize dynamic JSON-LD and escape `<` before assigning it.
89
91
 
90
92
  If a page or layout throws during browser rendering, the visit rejects and navigation returns to idle. The URL and head remain on the last successful page. Recovery rebuilds that page and its layouts, resetting their local component state; a later visit can proceed normally. State preservation during successful navigation is unchanged.
91
93
 
94
+ ### Multipart forms (unreleased)
95
+
96
+ JSON remains the default: `<Form form={form}>` sends `form.data`. Opt into browser form fields with `encType="multipart/form-data"`. Give inputs a `name`; files, repeated names and the clicked submit button are included without converting them into a JSON object.
97
+
98
+ ```tsx
99
+ const form = useForm('/profile?save', { name: 'Initial' });
100
+ return <Form form={form} encType="multipart/form-data">
101
+ <input name="name" value="Native field" />
102
+ <input name="files" type="file" multiple />
103
+ <button name="intent" value="save">Save</button>
104
+ </Form>;
105
+ ```
106
+
107
+ For direct calls use `form.post(data)` on a bound form, `form.post('/profile?save', data)` with an explicit URL, or `form.submit('patch', data)`. All four mutation methods accept FormData. Unbound forms require a URL. For an island, use `useIslandForm(defaults)` and `<Form form={form} url="/profile?save" encType="multipart/form-data">`.
108
+
109
+ The adapter snapshots FormData at submission and leaves `Content-Type` unset so fetch generates the multipart boundary. Validation retains native field/file values. FormData submission does not replace the JSON model or its defaults: `data`, `hasChanges` and `reset()` continue to describe the JSON model, not uncontrolled DOM/file inputs. Use the native form's reset operation to clear those controls. Cancellation prevents a late response from updating the view, but cannot undo a server write.
110
+
111
+ Full Pages multipart uses a dedicated same-origin mutation request followed by **one explicit GET** through Void's public router on success. Void's public engine cannot consume a raw multipart body or an externally fetched page shell. The GET commits through normal Hono rendering, history, layout/head, navigation state and cancellation checks while preserving component state. It also reruns loaders and may re-evaluate deferred data; the resolved-deferred retention contract for JSON mutations does not apply to multipart. The destination must support a GET. Island multipart retains its existing success behavior: reload or redirect the document.
112
+
113
+ This API is part of the release preparation branch and is not in published alpha.4. It does not add a multipart-capable public transport hook to Void or change the one-shot `action()` helper's body contract.
114
+
92
115
  ## Static output and prerendering
93
116
 
94
117
  Set `output: 'static'` in `void.config.ts`, then run `vite build`. Void collects known page routes and dynamic `getPrerenderPaths()` results and generates HTML plus `/_void/pages/*.json` under `dist/client`. The Hono adapter supplies its renderer and enables the shared router's static-data mode. It adds no route crawler.
@@ -103,7 +126,7 @@ export const loader = defineHandler((c) => ({ id: c.req.param('id') }));
103
126
 
104
127
  Known routes auto-prerender in static output. `prerender = true` explicitly selects a route; `prerender = false` excludes it. Excluded routes and undeclared dynamic paths have no generated HTML or page-data file and require a server to work. Serve `dist/client` with clean URL mapping such as `/users/alice` to `users/alice.html`, including the generated assets and `/_void/pages` JSON files. Direct requests, reload and counters use the generated output; Pages links fetch the static JSON and keep the document.
105
128
 
106
- Actions and named actions require a server. The plugin rejects them during static discovery. Deferred props are unsupported in static output and produce an explicit renderer error. Void 0.26.1 reports failed prerender paths as warnings, continues the build, and omits that path's HTML; verify the expected file set before release. The consumer check exercises both diagnostics and rejects any unexpected prerender failures in the supported fixture.
129
+ Actions and named actions require a server. The plugin rejects them during static discovery. Deferred props are unsupported in static output and produce an explicit renderer error. The tested Void runtime reports failed prerender paths as warnings, continues the build, and omits that path's HTML; verify the expected file set before release. The consumer check exercises both diagnostics and rejects any unexpected prerender failures in the supported fixture.
107
130
 
108
131
  `fixtures/static` demonstrates loader-backed counters, declared dynamic paths and true/false overrides. `scripts/check-static-consumer.mjs`, called by `npm run check:consumer`, builds with Void's own collection and serves only generated files. ISR, CDN configuration and managed deployment are separate follow-up work.
109
132
 
@@ -124,9 +147,9 @@ The server renders immediate props and a loading state before the resolver compl
124
147
 
125
148
  A resolved value or error updates the current page without resetting page-local state or surviving layouts. Updates from a pending page are ignored until that page commits. Void aborts and disposes old navigation resources; the adapter additionally accepts only updates belonging to the rendered props object.
126
149
 
127
- Void 0.26.1 forwards ordinary deferred resolver error messages in development; production streams use `Internal Server Error`. Deliberate HTTP exception messages may be forwarded in production. The adapter turns the received message into an `Error` and exposes it through `error`; server stack traces are not included. Author deliberate HTTP exception messages suitable for display to users. This follows the verified streaming error behavior of the pinned Void version.
150
+ The tested Void runtime forwards ordinary deferred resolver error messages in development; production streams use `Internal Server Error`. Deliberate HTTP exception messages may be forwarded in production. The adapter turns the received message into an `Error` and exposes it through `error`; server stack traces are not included. Author deliberate HTTP exception messages suitable for display to users. This follows the verified streaming error behavior of the pinned Void version.
128
151
 
129
- The verified contract covers single and grouped deferred props during GET document requests and SPA navigation, successful prefetch followed by a visit, and retention of resolved values after actions. Void preserves these values until the next load/navigation; mutation actions do not rerun the deferred resolver. Failed deferred prefetch currently becomes a resolved `null` because Void does not transmit its error; use document/SPA streaming when error state is required. Grouped resolver projections require explicit prop types with the current Void `defer<T>` declaration. Deferred island pages and static deferred output remain unsupported. The ordinary Pages action APIs remain documented under runtime exports.
152
+ For JSON actions, the verified contract covers single and grouped deferred props during GET document requests and SPA navigation, successful prefetch followed by a visit, and retention of resolved values after actions. Void preserves these values until the next load/navigation; mutation actions do not rerun the deferred resolver. Failed deferred prefetch currently becomes a resolved `null` because Void does not transmit its error; use document/SPA streaming when error state is required. Grouped resolver projections require explicit prop types with the current Void `defer<T>` declaration. Deferred island pages and static deferred output remain unsupported. The ordinary Pages action APIs remain documented under runtime exports.
130
153
 
131
154
  ## Islands
132
155
 
@@ -144,11 +167,11 @@ Place the counter outside the pages directory. It can use Hono hooks and event h
144
167
 
145
168
  A static-only island page emits no client script or module preload tags. Static page modules stay outside the normal Pages browser manifest. A project containing only static island pages builds empty client entry modules with no framework code. Void's own automatic prerender policy still applies to eligible island pages. Full static output and explicit prerender configuration follow the static output contract below.
146
169
 
147
- The `load`, `visible`, `idle` and `media:(query)` policies use Void's shared scheduler. Use a single default relative TSX/JSX import, with attributes directly in the island page. Unknown policies and obvious malformed media queries fail discovery; the browser checks the media query through CSSOM before scheduling. Each island mounts once when its condition is met. Island layouts and attributed imports in their static TSX/JSX component graph are supported, including named layouts and repeated components with independent JSON props. The graph handles cycles and invalidates island entries when a known static component changes. Static chains without interactive imports emit no client script. Interactive components form a boundary: use ordinary imports inside them; another attributed island inside an already interactive component is rejected to avoid overlapping mounts. Full Pages cannot inherit an island layout (Void diagnoses the mixed mode). `useIslandForm(defaults)` provides the same data/dirty/error/submit/reset/clear state without a Pages provider. Submit to an explicit same-origin URL, or use `<Form form={form} url="/blog?save">`. Island success reloads or redirects the document, so state is rebuilt from SSR props. Duplicate submissions are suppressed; cancellation aborts the owned fetch but does not roll back a server write. Fields are sent as JSON. Ordinary `useForm` and the one-shot Pages `action` still require full Pages; use named imports of `useIslandForm`/`Form` in islands. Pages-only form imports (`useForm`, `action`) and ambiguous namespace/default form imports fail with a diagnostic in an island page or component. `useForm` also requires a full Pages provider at runtime. Use ordinary document links in static island pages. Pages navigation falls back to a document request for island routes. Static layouts remain server content and do not retain interactive state.
170
+ The `load`, `visible`, `idle` and `media:(query)` policies use Void's shared scheduler. Use a single default relative TSX/JSX import, with attributes directly in the island page. Unknown policies and obvious malformed media queries fail discovery; the browser checks the media query through CSSOM before scheduling. Each island mounts once when its condition is met. Island layouts and attributed imports in their static TSX/JSX component graph are supported, including named layouts and repeated components with independent JSON props. The graph handles cycles and invalidates island entries when a known static component changes. Static chains without interactive imports emit no client script. Interactive components form a boundary: use ordinary imports inside them; another attributed island inside an already interactive component is rejected to avoid overlapping mounts. Full Pages cannot inherit an island layout (Void diagnoses the mixed mode). `useIslandForm(defaults)` provides the same data/dirty/error/submit/reset/clear state without a Pages provider. Submit to an explicit same-origin URL, or use `<Form form={form} url="/blog?save">`. Island success reloads or redirects the document, so state is rebuilt from SSR props. Duplicate submissions are suppressed; cancellation aborts the owned fetch but does not roll back a server write. Fields are sent as JSON by default; explicit FormData/multipart is described above. Ordinary `useForm` and the one-shot Pages `action` still require full Pages; use named imports of `useIslandForm`/`Form` in islands. Pages-only form imports (`useForm`, `action`) and ambiguous namespace/default form imports fail with a diagnostic in an island page or component. `useForm` also requires a full Pages provider at runtime. Use ordinary document links in static island pages. Pages navigation falls back to a document request for island routes. Static layouts remain server content and do not retain interactive state.
148
171
 
149
172
  ## Optional Markdown
150
173
 
151
- Use a later alpha exporting `/markdown/plugin`; alpha.3 predates this API. For source validation, install the adapter using the local tarball steps above. Add parser peers explicitly only when using Markdown:
174
+ For this source and its Void 1.1 baseline, use the RC after approval or the local tarball steps above. Published alpha.4 has the Markdown exports but retains its earlier Void 0.x contract; alpha.3 predates this API. Add parser peers explicitly only when using Markdown:
152
175
 
153
176
  ```sh
154
177
  npm install markdown-exit@1.3.0 yaml@2.9.1
@@ -175,13 +198,15 @@ The virtual renderer/client entries follow version-specific conventions observed
175
198
 
176
199
  ## Known upstream dependency advisory
177
200
 
201
+ The RC preparation reassessment on 2026-10-17 found the same single esbuild advisory on Void 1.1.11 in both supported consumers, including optional Markdown: Moderate 5 propagated entries, High/Critical 0. Its development-server path is outside the reviewed transform/loader path. No override is added. New or changed findings block upload, and fresh checks are required for every final candidate. The package does not ship the publisher tooling; its separate scoped findings do not imply a zero consumer audit. See the repository's `docs/rc-security-assessment.md` for evidence and limits.
202
+
178
203
  The initial alpha accepts [GHSA-67mh-4wv8-2f99](https://github.com/advisories/GHSA-67mh-4wv8-2f99) as a known limitation on the pinned peer baseline. The reviewed audits on 2026-10-11 reported Moderate findings propagated across multiple package entries from this single esbuild advisory; package-entry counts can differ between audit reports. The affected esbuild 0.18.20 is installed through Void 0.26.1 → drizzle-kit 0.31.11 → @esbuild-kit/esm-loader 2.6.5 → @esbuild-kit/core-utils 3.3.2.
179
204
 
180
205
  The advisory concerns esbuild's own development server. The adapter uses Vite/Oxc for JSX and TypeScript for package compilation; the inspected upstream loader uses esbuild transforms. No esbuild serve invocation was found in the adapter or these loader modules, but other upstream commands and consumer configurations have not been exhaustively assessed. Avoid the affected esbuild serve feature. This is an accepted audit finding, not a claim of overall security or an audit-zero result.
181
206
 
182
207
  The package adds no overrides and does not force changes to Void's internal dependencies. Consumers install the framework peers and their dependencies even though this archive contains no dependency implementation. Follow compatible upstream fixes; a peer upgrade requires renewed validation. New unreviewed advisories continue to block release preparation policy.
183
208
 
184
- The scaffold Vite Plus 0.1.24 additionally brings `oxfmt 0.52.0 → tinypool 2.1.0`. On 2026-10-12 its consumer audit reported three propagated Critical package entries from [GHSA-5gmw-xhrv-c9v3](https://github.com/advisories/GHSA-5gmw-xhrv-c9v3) and [GHSA-85c8-ppgw-ccpr](https://github.com/advisories/GHSA-85c8-ppgw-ccpr). These require prototype pollution followed by pool worker creation/run options; `oxfmt/dist/cli.js` imports Tinypool. Dev/build/preview worked, but that does not prove formatter paths safe. These advisories are unapproved and continue to block that stack's release plan. Prefer latest Vite Plus 1.1.0 with its aligned alias or plain Vite; both retain only the existing esbuild advisory in reviewed audits. No tinypool override or security exception was added. See the repository `docs/scaffold-compatibility.md` for the complete matrix and audit assessment.
209
+ The historical scaffold Vite Plus 0.1.24 brings unapproved tinypool Critical findings. It is outside the RC support range; use the tested Vite Plus 1.3.0/core alias or plain Vite 8.3.4. Old functional CI success does not accept those findings. See the repository `docs/scaffold-compatibility.md` for historical evidence.
185
210
 
186
211
  ## Verification
187
212
 
@@ -195,7 +220,7 @@ Page and companion-loader edits reload the document and reset page/layout state.
195
220
 
196
221
  In development, the SSR renderer eagerly imports server-rendered page and layout modules with their providers. This keeps in-flight requests within the same module generation during a reload. Server-side module initialization therefore happens when the renderer loads, including pages not yet visited. Browser-only page components are excluded from this server graph. Production keeps lazy component imports. The packed-consumer regression also sends parallel SSR requests during six simultaneous page/loader revisions and requires every response to succeed.
197
222
 
198
- The packed-consumer check edits a page, changes its loader value, adds and removes a route, checks counter interaction after reloading, and asserts that the server stays running. It also verifies resolved dependency paths and rejection of an unsupported running Vite even when a supported npm Vite is installed. Five locked consumers cover the original baseline, the Vite patch update, latest Void with plain Vite, the scaffold Vite Plus, and latest Vite Plus on Node 24.21.0. The existing required baseline context aggregates all five results. It also runs `npm run check:source` for navigation, persistent layouts, action forms and head metadata in development and production preview. Inspect the GitHub Actions result on the commit being reviewed.
223
+ The packed-consumer check edits pages/loaders, adds/removes routes and verifies interaction without restarting the server. It rejects old Void lines and an unsupported running Vite even when installed metadata is supported. Both required consumers use Node 24.21.0/Void 1.1.11: plain Vite 8.3.4 and Vite Plus/core 1.3.0. They run the complete dev/build/preview/browser/static/islands/deferred/Markdown/local-ISR checks on identical packed bytes. Local ISR checks do not guarantee production ISR.
199
224
 
200
225
  ## Local ISR
201
226
 
@@ -205,4 +230,8 @@ The fixture binds local `ISR_CACHE` storage for Void production preview. Real de
205
230
 
206
231
  ## Known local development limitation
207
232
 
208
- On the tested macOS Void/Miniflare development runtime, a POST to an undefined named action can reset the next request on a reused connection. Declared-action browser behavior is verified; this transport edge case remains unresolved upstream. Managed runtime behavior for the edge case is unverified. Review this limitation before adopting the initial alpha.
233
+ On the tested macOS Void/Miniflare development runtime, a POST to an undefined named action can reset the next request on a reused connection. Declared-action browser behavior is verified; this transport edge case remains unresolved upstream. Managed runtime behavior for the edge case is unverified. This historical local limitation must be rechecked on the selected RC baseline; it is not evidence about managed production behavior.
234
+
235
+ ## RC and normal-release policy
236
+
237
+ The planned first candidate is 0.1.0-rc.0 on next, followed by 0.1.0 after public-consumer and real Workers verification. It is not yet a published-release claim. Production ISR remains outside the initial guarantee. Patches in 0.1.x preserve documented APIs; breaking changes move to 0.2.0 or later. Read the [release contract](https://github.com/ts-76/void-plugin-hono-jsx/blob/main/docs/release-contract.md) and [security policy](https://github.com/ts-76/void-plugin-hono-jsx/blob/main/SECURITY.md). Report vulnerabilities [privately](https://github.com/ts-76/void-plugin-hono-jsx/security/advisories/new).
@@ -1,10 +1,11 @@
1
1
  import { type JSX, type Child } from 'hono/jsx';
2
2
  import { type FormMethod, type FormModel, type VoidActionError } from 'void/pages-client';
3
- export type FormProps = Omit<JSX.IntrinsicElements['form'], 'action' | 'method' | 'onSubmit' | 'onError'> & {
4
- form: FormModel<[string | FormData | undefined]>;
3
+ export type FormProps = Omit<JSX.IntrinsicElements['form'], 'action' | 'method' | 'onSubmit' | 'onError' | 'encType'> & {
4
+ form: FormModel<[target?: string | FormData, body?: FormData]>;
5
5
  method?: FormMethod;
6
+ encType?: 'application/json' | 'multipart/form-data';
6
7
  url?: string;
7
8
  renderError?: (error: VoidActionError) => Child;
8
9
  };
9
- export declare function Form({ form, method, url, renderError, children, ...props }: FormProps): import("hono/jsx/dom/jsx-dev-runtime").JSX.Element;
10
+ export declare function Form({ form, method, url, encType, renderError, children, ...props }: FormProps): import("hono/jsx/dom/jsx-dev-runtime").JSX.Element;
10
11
  //# sourceMappingURL=form-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-view.d.ts","sourceRoot":"","sources":["../../src/runtime/form-view.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EAAgF,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAExK,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC,GAAG;IAC1G,IAAI,EAAE,SAAS,CAAC,CAAC,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC;IACjD,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,KAAK,CAAC;CACjD,CAAC;AACF,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,sDAa9F"}
1
+ {"version":3,"file":"form-view.d.ts","sourceRoot":"","sources":["../../src/runtime/form-view.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EAAgF,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAExK,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC,GAAG;IACtH,IAAI,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,kBAAkB,GAAG,qBAAqB,CAAC;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,KAAK,CAAC;CACjD,CAAC;AACF,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,GAAG,EAAE,OAA4B,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,sDAgB5H"}
@@ -1,16 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/dom/jsx-runtime";
2
2
  import { useEffect, useState } from 'hono/jsx';
3
3
  import { formErrorMessage, formRetryMessage, formSubmissionBlocked, scheduleFormRetry } from 'void/pages-client';
4
- export function Form({ form, method = 'post', url, renderError, children, ...props }) {
4
+ export function Form({ form, method = 'post', url, encType = 'application/json', renderError, children, ...props }) {
5
5
  const [now, setNow] = useState(Date.now());
6
6
  useEffect(() => scheduleFormRetry(form.error, () => setNow(Date.now())), [form.error]);
7
7
  const message = formErrorMessage(form);
8
8
  const retry = formRetryMessage(form.error);
9
9
  const blocked = formSubmissionBlocked(form, now);
10
- return _jsxs("form", { ...props, "aria-busy": form.pending, onSubmit: (event) => {
10
+ return _jsxs("form", { ...props, encType: encType, "aria-busy": form.pending, onSubmit: (event) => {
11
11
  event.preventDefault();
12
- if (!blocked)
13
- void form[method](url ?? new FormData(event.currentTarget));
12
+ if (!blocked) {
13
+ const data = encType === 'multipart/form-data' ? new FormData(event.currentTarget, event.submitter) : undefined;
14
+ void form[method](url ?? data, url ? data : undefined);
15
+ }
14
16
  }, children: [message && _jsxs("div", { role: "alert", "data-void-form-error": true, children: [form.error && renderError ? renderError(form.error) : message, retry && _jsx("p", { children: retry })] }), children] });
15
17
  }
16
18
  //# sourceMappingURL=form-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-view.js","sourceRoot":"","sources":["../../src/runtime/form-view.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAwB,MAAM,UAAU,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,iBAAiB,EAAyD,MAAM,mBAAmB,CAAC;AAQxK,MAAM,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAa;IAC7F,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,SAAS,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACvF,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,mBAAU,KAAK,eAAa,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAY,EAAE,EAAE;YAC1E,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO;gBAAE,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,aAAgC,CAAC,CAAC,CAAC;QAC/F,CAAC,aACE,OAAO,IAAI,eAAK,IAAI,EAAC,OAAO,2CAAuB,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,IAAI,sBAAI,KAAK,GAAK,IAAO,EAChJ,QAAQ,IACJ,CAAC;AACV,CAAC","sourcesContent":["import { useEffect, useState, type JSX, type Child } from 'hono/jsx';\nimport { formErrorMessage, formRetryMessage, formSubmissionBlocked, scheduleFormRetry, type FormMethod, type FormModel, type VoidActionError } from 'void/pages-client';\n\nexport type FormProps = Omit<JSX.IntrinsicElements['form'], 'action' | 'method' | 'onSubmit' | 'onError'> & {\n form: FormModel<[string | FormData | undefined]>;\n method?: FormMethod;\n url?: string;\n renderError?: (error: VoidActionError) => Child;\n};\nexport function Form({ form, method = 'post', url, renderError, children, ...props }: FormProps) {\n const [now, setNow] = useState(Date.now());\n useEffect(() => scheduleFormRetry(form.error, () => setNow(Date.now())), [form.error]);\n const message = formErrorMessage(form);\n const retry = formRetryMessage(form.error);\n const blocked = formSubmissionBlocked(form, now);\n return <form {...props} aria-busy={form.pending} onSubmit={(event: Event) => {\n event.preventDefault();\n if (!blocked) void form[method](url ?? new FormData(event.currentTarget as HTMLFormElement));\n }}>\n {message && <div role=\"alert\" data-void-form-error>{form.error && renderError ? renderError(form.error) : message}{retry && <p>{retry}</p>}</div>}\n {children}\n </form>;\n}\n"]}
1
+ {"version":3,"file":"form-view.js","sourceRoot":"","sources":["../../src/runtime/form-view.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAwB,MAAM,UAAU,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,iBAAiB,EAAyD,MAAM,mBAAmB,CAAC;AASxK,MAAM,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,kBAAkB,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAa;IAC3H,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,SAAS,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACvF,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,mBAAU,KAAK,EAAE,OAAO,EAAE,OAAO,eAAa,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAY,EAAE,EAAE;YAC5F,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,GAAG,OAAO,KAAK,qBAAqB,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,aAAgC,EAAG,KAAqB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBACpJ,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,aACE,OAAO,IAAI,eAAK,IAAI,EAAC,OAAO,2CAAuB,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,IAAI,sBAAI,KAAK,GAAK,IAAO,EAChJ,QAAQ,IACJ,CAAC;AACV,CAAC","sourcesContent":["import { useEffect, useState, type JSX, type Child } from 'hono/jsx';\nimport { formErrorMessage, formRetryMessage, formSubmissionBlocked, scheduleFormRetry, type FormMethod, type FormModel, type VoidActionError } from 'void/pages-client';\n\nexport type FormProps = Omit<JSX.IntrinsicElements['form'], 'action' | 'method' | 'onSubmit' | 'onError' | 'encType'> & {\n form: FormModel<[target?: string | FormData, body?: FormData]>;\n method?: FormMethod;\n encType?: 'application/json' | 'multipart/form-data';\n url?: string;\n renderError?: (error: VoidActionError) => Child;\n};\nexport function Form({ form, method = 'post', url, encType = 'application/json', renderError, children, ...props }: FormProps) {\n const [now, setNow] = useState(Date.now());\n useEffect(() => scheduleFormRetry(form.error, () => setNow(Date.now())), [form.error]);\n const message = formErrorMessage(form);\n const retry = formRetryMessage(form.error);\n const blocked = formSubmissionBlocked(form, now);\n return <form {...props} encType={encType} aria-busy={form.pending} onSubmit={(event: Event) => {\n event.preventDefault();\n if (!blocked) {\n const data = encType === 'multipart/form-data' ? new FormData(event.currentTarget as HTMLFormElement, (event as SubmitEvent).submitter) : undefined;\n void form[method](url ?? data, url ? data : undefined);\n }\n }}>\n {message && <div role=\"alert\" data-void-form-error>{form.error && renderError ? renderError(form.error) : message}{retry && <p>{retry}</p>}</div>}\n {children}\n </form>;\n}\n"]}
@@ -18,7 +18,7 @@ export type HonoForm<T extends Fields> = {
18
18
  hasChanges: boolean;
19
19
  wasSuccessful: boolean;
20
20
  recentlySuccessful: boolean;
21
- submit(method: FormMethod, target?: string | FormData): Promise<void>;
21
+ submit(method: FormMethod, target?: string | FormData, body?: FormData): Promise<void>;
22
22
  cancel(): void;
23
23
  reset: {
24
24
  (): void;
@@ -26,7 +26,7 @@ export type HonoForm<T extends Fields> = {
26
26
  };
27
27
  clearErrors(...fields: Array<keyof T & string>): void;
28
28
  clearError(): void;
29
- } & Record<FormMethod, (target?: string | FormData) => Promise<void>>;
29
+ } & Record<FormMethod, (target?: string | FormData, body?: FormData) => Promise<void>>;
30
30
  export declare function useForm<T extends Fields>(initialData: T): HonoForm<T>;
31
31
  export declare function useForm<U extends ActionUrl & string>(url: U, defaults: Body<U>, options?: ActionFormOptions<U>): HonoForm<Body<U>>;
32
32
  export declare function useIslandForm<T extends Fields>(defaults: T): HonoForm<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/runtime/form.ts"],"names":[],"mappings":"AACA,OAAO,EAAwE,eAAe,EAAqB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC9J,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAuC,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACtC,KAAK,IAAI,CAAC,CAAC,SAAS,MAAM,IAAI,iBAAiB,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAClG,KAAK,OAAO,CAAC,CAAC,IAAI;IAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;IAAC,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC;AACvH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,IAAI;IACvC,IAAI,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAC/E,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAC;IACxG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,IAAI,IAAI,CAAC;IACtF,KAAK,EAAE;QAAE,IAAI,IAAI,CAAC;QAAC,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;IAAC,WAAW,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAAC,UAAU,IAAI,IAAI,CAAC;CAC5I,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAgBtE,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,WAAW,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AACvE,wBAAgB,OAAO,CAAC,CAAC,SAAS,SAAS,GAAG,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAQpI,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAcxE"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/runtime/form.ts"],"names":[],"mappings":"AACA,OAAO,EAAwE,eAAe,EAAqB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC9J,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAuC,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACtC,KAAK,IAAI,CAAC,CAAC,SAAS,MAAM,IAAI,iBAAiB,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAClG,KAAK,OAAO,CAAC,CAAC,IAAI;IAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;IAAC,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC;AACvH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,IAAI;IACvC,IAAI,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAC/E,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAC;IACxG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,IAAI,IAAI,CAAC;IACvG,KAAK,EAAE;QAAE,IAAI,IAAI,CAAC;QAAC,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;IAAC,WAAW,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAAC,UAAU,IAAI,IAAI,CAAC;CAC5I,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAgBvF,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,WAAW,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AACvE,wBAAgB,OAAO,CAAC,CAAC,SAAS,SAAS,GAAG,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAQpI,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAexE"}
@@ -32,7 +32,8 @@ export function useIslandForm(defaults) {
32
32
  if (target.origin !== window.location.origin || !['http:', 'https:'].includes(target.protocol))
33
33
  throw new Error('Hono island form: actions must target the current origin.');
34
34
  controller.current = new AbortController();
35
- const response = await fetch(target, { method: method.toUpperCase(), headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), signal: controller.current.signal });
35
+ const multipart = data instanceof FormData;
36
+ const response = await fetch(target, { method: method.toUpperCase(), headers: multipart ? undefined : { 'Content-Type': 'application/json' }, body: multipart ? data : JSON.stringify(data), signal: controller.current.signal });
36
37
  if (response.redirected && response.ok) {
37
38
  window.location.assign(response.url);
38
39
  return { ok: true, pageData: undefined };
@@ -73,7 +74,10 @@ function useFormModel(initial, initialErrors, send, abort, bound) {
73
74
  setRecent(false);
74
75
  clearTimeout(timer.current);
75
76
  };
76
- async function submit(method, target) {
77
+ async function submit(method, target, body) {
78
+ if (target instanceof FormData && body)
79
+ throw new Error('Hono Form: supply FormData once.');
80
+ const multipart = target instanceof FormData ? target : body;
77
81
  const url = typeof target === 'string' ? target : bound;
78
82
  if (!url)
79
83
  throw new Error('Hono Form: supply an action URL to useForm or submit.');
@@ -88,12 +92,16 @@ function useFormModel(initial, initialErrors, send, abort, bound) {
88
92
  setError(null);
89
93
  setErrors({});
90
94
  const submitted = { ...current.current };
95
+ const payload = multipart ? new FormData() : submitted;
96
+ if (multipart)
97
+ for (const [name, value] of multipart)
98
+ payload.append(name, value);
91
99
  try {
92
- const result = await send(method, url, submitted);
100
+ const result = await send(method, url, payload);
93
101
  if (id !== generation.current)
94
102
  return;
95
103
  if (result.ok) {
96
- if (bound)
104
+ if (bound && !multipart)
97
105
  defaults.current = submitted;
98
106
  setSuccessful(true);
99
107
  setRecent(true);
@@ -136,7 +144,7 @@ function useFormModel(initial, initialErrors, send, abort, bound) {
136
144
  reset(...fields) { const keys = fields.filter((field) => typeof field === 'string'); setData(keys.length ? { ...current.current, ...Object.fromEntries(keys.map((key) => [key, defaults.current[key]])) } : { ...defaults.current }); },
137
145
  clearErrors(...fields) { setErrors((previous) => fields.length ? Object.fromEntries(Object.entries(previous).filter(([field]) => !fields.includes(field))) : {}); },
138
146
  clearError() { setError(null); },
139
- post: (target) => submit('post', target), put: (target) => submit('put', target), patch: (target) => submit('patch', target), delete: (target) => submit('delete', target),
147
+ post: (target, body) => submit('post', target, body), put: (target, body) => submit('put', target, body), patch: (target, body) => submit('patch', target, body), delete: (target, body) => submit('delete', target, body),
140
148
  };
141
149
  }
142
150
  //# sourceMappingURL=form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","mappings":"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","names":[],"ignoreList":[],"sources":["../../src/runtime/form.ts"],"sourcesContent":["import { useContext, useEffect, useRef, useState } from 'hono/jsx';\nimport { isAbortError, isEqualFormValue, interpolateRouteParams, submitAction, VoidActionError, type ActionResult, type FormMethod } from 'void/pages-client';\nimport type { ActionFormOptions, ActionUrl, ResolveActionBody } from 'void/routes';\nimport { RouterContext, useErrors, useRouter, action } from './pages.tsx';\nexport { Form, type FormProps } from './form-view.tsx';\nexport { action };\n\ntype Fields = Record<string, unknown>;\ntype Body<U extends string> = ResolveActionBody<U> extends Fields ? ResolveActionBody<U> : Fields;\ntype SetData<T> = { (next: T | ((current: T) => T)): void; <K extends keyof T & string>(field: K, value: T[K]): void };\nexport type HonoForm<T extends Fields> = {\n data: T; setData: SetData<T>; errors: Record<string, string>; pending: boolean;\n error: VoidActionError | null; hasChanges: boolean; wasSuccessful: boolean; recentlySuccessful: boolean;\n submit(method: FormMethod, target?: string | FormData): Promise<void>; cancel(): void;\n reset: { (): void; (...fields: Array<keyof T & string>): void }; clearErrors(...fields: Array<keyof T & string>): void; clearError(): void;\n} & Record<FormMethod, (target?: string | FormData) => Promise<void>>;\n\nfunction toFormError(failure: unknown, url: string): VoidActionError {\n if (failure instanceof VoidActionError) return failure;\n const message = failure instanceof Error ? failure.message : String(failure);\n const error = new VoidActionError({ body: { message }, status: 0, statusText: 'Network Error', url });\n error.message = message;\n return error;\n}\n\nfunction fieldErrors(error: VoidActionError): Record<string, string> {\n const body = error.body;\n if (typeof body !== 'object' || body === null || !('errors' in body) || typeof body.errors !== 'object' || body.errors === null) return {};\n return Object.fromEntries(Object.entries(body.errors).filter((entry): entry is [string, string] => typeof entry[1] === 'string'));\n}\n\nexport function useForm<T extends Fields>(initialData: T): HonoForm<T>;\nexport function useForm<U extends ActionUrl & string>(url: U, defaults: Body<U>, options?: ActionFormOptions<U>): HonoForm<Body<U>>;\nexport function useForm(initial: string | Fields, values?: Fields, options?: { params?: Record<string, string> }): HonoForm<Fields> {\n if (!useContext(RouterContext)) throw new Error('Hono Pages: useForm requires the full Pages renderer; use useIslandForm for islands.');\n const router = useRouter();\n const bound = typeof initial === 'string' ? initial : undefined;\n return useFormModel(typeof initial === 'string' ? values ?? {} : initial, useErrors(), async (method, url, data) => submitAction(router, interpolateRouteParams(url, options?.params), { method: method.toUpperCase(), data, preserveScroll: true }), () => router.refresh(), bound);\n}\n\nexport function useIslandForm<T extends Fields>(defaults: T): HonoForm<T> {\n const controller = useRef<AbortController | null>(null);\n useEffect(() => () => controller.current?.abort(), []);\n return useFormModel(defaults, {}, async (method, url, data) => {\n const target = new URL(url, window.location.href);\n if (target.origin !== window.location.origin || !['http:', 'https:'].includes(target.protocol)) throw new Error('Hono island form: actions must target the current origin.');\n controller.current = new AbortController();\n const response = await fetch(target, { method: method.toUpperCase(), headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), signal: controller.current.signal });\n if (response.redirected && response.ok) { window.location.assign(response.url); return { ok: true, pageData: undefined }; }\n if (response.ok) { window.location.reload(); return { ok: true, pageData: undefined }; }\n let body: unknown;\n try { body = response.headers.get('content-type')?.includes('application/json') ? await response.json() : await response.text(); } catch { body = null; }\n return { ok: false, error: new VoidActionError({ body, status: response.status, statusText: response.statusText, url: target.href }) };\n }, () => controller.current?.abort());\n}\n\ntype FormTransport = (method: FormMethod, url: string, data: Fields) => Promise<ActionResult>;\nfunction useFormModel<T extends Fields>(initial: T, initialErrors: Record<string, string>, send: FormTransport, abort: () => void | Promise<unknown>, bound?: string): HonoForm<T> {\n const defaults = useRef({ ...initial });\n const current = useRef({ ...defaults.current });\n const [data, updateData] = useState(current.current);\n const [errors, setErrors] = useState(initialErrors);\n const [pending, setPending] = useState(false);\n const [wasSuccessful, setSuccessful] = useState(false);\n const [recentlySuccessful, setRecent] = useState(false);\n const [error, setError] = useState<VoidActionError | null>(null);\n const active = useRef(false);\n const generation = useRef(0);\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => { ++generation.current; active.current = false; clearTimeout(timer.current); }, []);\n\n const setData: SetData<T> = (next: T | ((current: T) => T) | string, value?: unknown) => {\n current.current = typeof next === 'string' ? { ...current.current, [next]: value } as T : typeof next === 'function' ? next(current.current) : { ...next };\n updateData(current.current); setSuccessful(false); setErrors({}); setError(null); setRecent(false); clearTimeout(timer.current);\n };\n async function submit(method: FormMethod, target?: string | FormData) {\n const url = typeof target === 'string' ? target : bound;\n if (!url) throw new Error('Hono Form: supply an action URL to useForm or submit.');\n if (active.current) return;\n active.current = true; const id = ++generation.current;\n setPending(true); setSuccessful(false); setRecent(false); clearTimeout(timer.current); setError(null); setErrors({});\n const submitted = { ...current.current };\n try {\n const result = await send(method, url, submitted);\n if (id !== generation.current) return;\n if (result.ok) {\n if (bound) defaults.current = submitted;\n setSuccessful(true); setRecent(true);\n timer.current = setTimeout(() => { if (id === generation.current) setRecent(false); }, 2000);\n } else { setError(result.error); setErrors(fieldErrors(result.error)); }\n } catch (failure) {\n if (id === generation.current && !isAbortError(failure)) setError(toFormError(failure, url));\n } finally {\n if (id === generation.current) { active.current = false; setPending(false); }\n }\n }\n function cancel() {\n if (!active.current) return;\n const id = ++generation.current; active.current = false; setPending(false); setSuccessful(false); setRecent(false); clearTimeout(timer.current);\n void Promise.resolve(abort()).catch((failure: unknown) => {\n if (id === generation.current) setError(toFormError(failure, bound ?? 'island'));\n });\n }\n return {\n data, setData, errors, pending, wasSuccessful, recentlySuccessful, error, submit, cancel,\n hasChanges: Object.keys({ ...defaults.current, ...data }).some((key) => !isEqualFormValue(defaults.current[key], data[key])),\n reset(...fields: Array<string | MouseEvent>) { const keys = fields.filter((field): field is string => typeof field === 'string'); setData(keys.length ? { ...current.current, ...Object.fromEntries(keys.map((key) => [key, defaults.current[key]])) } as T : { ...defaults.current }); },\n clearErrors(...fields) { setErrors((previous) => fields.length ? Object.fromEntries(Object.entries(previous).filter(([field]) => !fields.includes(field))) : {}); },\n clearError() { setError(null); },\n post: (target) => submit('post', target), put: (target) => submit('put', target), patch: (target) => submit('patch', target), delete: (target) => submit('delete', target),\n };\n}\n"]}
1
+ {"version":3,"file":"form.js","mappings":"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","names":[],"ignoreList":[],"sources":["../../src/runtime/form.ts"],"sourcesContent":["import { useContext, useEffect, useRef, useState } from 'hono/jsx';\nimport { isAbortError, isEqualFormValue, interpolateRouteParams, submitAction, VoidActionError, type ActionResult, type FormMethod } from 'void/pages-client';\nimport type { ActionFormOptions, ActionUrl, ResolveActionBody } from 'void/routes';\nimport { RouterContext, useErrors, useRouter, action } from './pages.tsx';\nexport { Form, type FormProps } from './form-view.tsx';\nexport { action };\n\ntype Fields = Record<string, unknown>;\ntype Body<U extends string> = ResolveActionBody<U> extends Fields ? ResolveActionBody<U> : Fields;\ntype SetData<T> = { (next: T | ((current: T) => T)): void; <K extends keyof T & string>(field: K, value: T[K]): void };\nexport type HonoForm<T extends Fields> = {\n data: T; setData: SetData<T>; errors: Record<string, string>; pending: boolean;\n error: VoidActionError | null; hasChanges: boolean; wasSuccessful: boolean; recentlySuccessful: boolean;\n submit(method: FormMethod, target?: string | FormData, body?: FormData): Promise<void>; cancel(): void;\n reset: { (): void; (...fields: Array<keyof T & string>): void }; clearErrors(...fields: Array<keyof T & string>): void; clearError(): void;\n} & Record<FormMethod, (target?: string | FormData, body?: FormData) => Promise<void>>;\n\nfunction toFormError(failure: unknown, url: string): VoidActionError {\n if (failure instanceof VoidActionError) return failure;\n const message = failure instanceof Error ? failure.message : String(failure);\n const error = new VoidActionError({ body: { message }, status: 0, statusText: 'Network Error', url });\n error.message = message;\n return error;\n}\n\nfunction fieldErrors(error: VoidActionError): Record<string, string> {\n const body = error.body;\n if (typeof body !== 'object' || body === null || !('errors' in body) || typeof body.errors !== 'object' || body.errors === null) return {};\n return Object.fromEntries(Object.entries(body.errors).filter((entry): entry is [string, string] => typeof entry[1] === 'string'));\n}\n\nexport function useForm<T extends Fields>(initialData: T): HonoForm<T>;\nexport function useForm<U extends ActionUrl & string>(url: U, defaults: Body<U>, options?: ActionFormOptions<U>): HonoForm<Body<U>>;\nexport function useForm(initial: string | Fields, values?: Fields, options?: { params?: Record<string, string> }): HonoForm<Fields> {\n if (!useContext(RouterContext)) throw new Error('Hono Pages: useForm requires the full Pages renderer; use useIslandForm for islands.');\n const router = useRouter();\n const bound = typeof initial === 'string' ? initial : undefined;\n return useFormModel(typeof initial === 'string' ? values ?? {} : initial, useErrors(), async (method, url, data) => submitAction(router, interpolateRouteParams(url, options?.params), { method: method.toUpperCase(), data, preserveScroll: true }), () => router.refresh(), bound);\n}\n\nexport function useIslandForm<T extends Fields>(defaults: T): HonoForm<T> {\n const controller = useRef<AbortController | null>(null);\n useEffect(() => () => controller.current?.abort(), []);\n return useFormModel(defaults, {}, async (method, url, data) => {\n const target = new URL(url, window.location.href);\n if (target.origin !== window.location.origin || !['http:', 'https:'].includes(target.protocol)) throw new Error('Hono island form: actions must target the current origin.');\n controller.current = new AbortController();\n const multipart = data instanceof FormData;\n const response = await fetch(target, { method: method.toUpperCase(), headers: multipart ? undefined : { 'Content-Type': 'application/json' }, body: multipart ? data : JSON.stringify(data), signal: controller.current.signal });\n if (response.redirected && response.ok) { window.location.assign(response.url); return { ok: true, pageData: undefined }; }\n if (response.ok) { window.location.reload(); return { ok: true, pageData: undefined }; }\n let body: unknown;\n try { body = response.headers.get('content-type')?.includes('application/json') ? await response.json() : await response.text(); } catch { body = null; }\n return { ok: false, error: new VoidActionError({ body, status: response.status, statusText: response.statusText, url: target.href }) };\n }, () => controller.current?.abort());\n}\n\ntype FormTransport = (method: FormMethod, url: string, data: Fields | FormData) => Promise<ActionResult>;\nfunction useFormModel<T extends Fields>(initial: T, initialErrors: Record<string, string>, send: FormTransport, abort: () => void | Promise<unknown>, bound?: string): HonoForm<T> {\n const defaults = useRef({ ...initial });\n const current = useRef({ ...defaults.current });\n const [data, updateData] = useState(current.current);\n const [errors, setErrors] = useState(initialErrors);\n const [pending, setPending] = useState(false);\n const [wasSuccessful, setSuccessful] = useState(false);\n const [recentlySuccessful, setRecent] = useState(false);\n const [error, setError] = useState<VoidActionError | null>(null);\n const active = useRef(false);\n const generation = useRef(0);\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => { ++generation.current; active.current = false; clearTimeout(timer.current); }, []);\n\n const setData: SetData<T> = (next: T | ((current: T) => T) | string, value?: unknown) => {\n current.current = typeof next === 'string' ? { ...current.current, [next]: value } as T : typeof next === 'function' ? next(current.current) : { ...next };\n updateData(current.current); setSuccessful(false); setErrors({}); setError(null); setRecent(false); clearTimeout(timer.current);\n };\n async function submit(method: FormMethod, target?: string | FormData, body?: FormData) {\n if (target instanceof FormData && body) throw new Error('Hono Form: supply FormData once.');\n const multipart = target instanceof FormData ? target : body;\n const url = typeof target === 'string' ? target : bound;\n if (!url) throw new Error('Hono Form: supply an action URL to useForm or submit.');\n if (active.current) return;\n active.current = true; const id = ++generation.current;\n setPending(true); setSuccessful(false); setRecent(false); clearTimeout(timer.current); setError(null); setErrors({});\n const submitted = { ...current.current };\n const payload = multipart ? new FormData() : submitted;\n if (multipart) for (const [name, value] of multipart) (payload as FormData).append(name, value);\n try {\n const result = await send(method, url, payload);\n if (id !== generation.current) return;\n if (result.ok) {\n if (bound && !multipart) defaults.current = submitted;\n setSuccessful(true); setRecent(true);\n timer.current = setTimeout(() => { if (id === generation.current) setRecent(false); }, 2000);\n } else { setError(result.error); setErrors(fieldErrors(result.error)); }\n } catch (failure) {\n if (id === generation.current && !isAbortError(failure)) setError(toFormError(failure, url));\n } finally {\n if (id === generation.current) { active.current = false; setPending(false); }\n }\n }\n function cancel() {\n if (!active.current) return;\n const id = ++generation.current; active.current = false; setPending(false); setSuccessful(false); setRecent(false); clearTimeout(timer.current);\n void Promise.resolve(abort()).catch((failure: unknown) => {\n if (id === generation.current) setError(toFormError(failure, bound ?? 'island'));\n });\n }\n return {\n data, setData, errors, pending, wasSuccessful, recentlySuccessful, error, submit, cancel,\n hasChanges: Object.keys({ ...defaults.current, ...data }).some((key) => !isEqualFormValue(defaults.current[key], data[key])),\n reset(...fields: Array<string | MouseEvent>) { const keys = fields.filter((field): field is string => typeof field === 'string'); setData(keys.length ? { ...current.current, ...Object.fromEntries(keys.map((key) => [key, defaults.current[key]])) } as T : { ...defaults.current }); },\n clearErrors(...fields) { setErrors((previous) => fields.length ? Object.fromEntries(Object.entries(previous).filter(([field]) => !fields.includes(field))) : {}); },\n clearError() { setError(null); },\n post: (target, body) => submit('post', target, body), put: (target, body) => submit('put', target, body), patch: (target, body) => submit('patch', target, body), delete: (target, body) => submit('delete', target, body),\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/runtime/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkH,KAAK,UAAU,EAAE,KAAK,gBAAgB,EAAyB,MAAM,mBAAmB,CAAC;AAElN,iFAAiF;AACjF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,iBAQtF;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB;;;;;;;GAyD9D"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/runtime/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsJ,KAAK,UAAU,EAAE,KAAK,gBAAgB,EAAyB,MAAM,mBAAmB,CAAC;AAEtP,iFAAiF;AACjF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,iBAOtF;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB;;;;;;;GAqG9D"}