void-plugin-hono-jsx 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +42 -12
  3. package/dist/browser/client.d.ts +1 -0
  4. package/dist/browser/client.d.ts.map +1 -0
  5. package/dist/browser/client.js +1 -0
  6. package/dist/browser/client.js.map +1 -0
  7. package/dist/browser/deferred.d.ts +1 -0
  8. package/dist/browser/deferred.d.ts.map +1 -0
  9. package/dist/browser/deferred.js +1 -0
  10. package/dist/browser/deferred.js.map +1 -0
  11. package/dist/browser/document-head.d.ts +1 -0
  12. package/dist/browser/document-head.d.ts.map +1 -0
  13. package/dist/browser/document-head.js +1 -0
  14. package/dist/browser/document-head.js.map +1 -0
  15. package/dist/browser/form.d.ts +1 -0
  16. package/dist/browser/form.d.ts.map +1 -0
  17. package/dist/browser/form.js +1 -0
  18. package/dist/browser/form.js.map +1 -0
  19. package/dist/browser/pages.d.ts +1 -0
  20. package/dist/browser/pages.d.ts.map +1 -0
  21. package/dist/browser/pages.js +1 -0
  22. package/dist/browser/pages.js.map +1 -0
  23. package/dist/plugins/compatibility.d.ts +13 -1
  24. package/dist/plugins/compatibility.d.ts.map +1 -0
  25. package/dist/plugins/compatibility.js +66 -22
  26. package/dist/plugins/compatibility.js.map +1 -0
  27. package/dist/plugins/hono-islands.d.ts +1 -0
  28. package/dist/plugins/hono-islands.d.ts.map +1 -0
  29. package/dist/plugins/hono-islands.js +1 -0
  30. package/dist/plugins/hono-islands.js.map +1 -0
  31. package/dist/plugins/hono-jsx.d.ts +1 -0
  32. package/dist/plugins/hono-jsx.d.ts.map +1 -0
  33. package/dist/plugins/hono-jsx.js +1 -0
  34. package/dist/plugins/hono-jsx.js.map +1 -0
  35. package/dist/plugins/hono-pages.d.ts +2 -1
  36. package/dist/plugins/hono-pages.d.ts.map +1 -0
  37. package/dist/plugins/hono-pages.js +5 -3
  38. package/dist/plugins/hono-pages.js.map +1 -0
  39. package/dist/plugins/runtime-paths.d.ts +1 -0
  40. package/dist/plugins/runtime-paths.d.ts.map +1 -0
  41. package/dist/plugins/runtime-paths.js +13 -6
  42. package/dist/plugins/runtime-paths.js.map +1 -0
  43. package/dist/server/form.d.ts +1 -0
  44. package/dist/server/form.d.ts.map +1 -0
  45. package/dist/server/form.js +1 -0
  46. package/dist/server/form.js.map +1 -0
  47. package/dist/server/pages.d.ts +1 -0
  48. package/dist/server/pages.d.ts.map +1 -0
  49. package/dist/server/pages.js +1 -0
  50. package/dist/server/pages.js.map +1 -0
  51. package/package.json +6 -5
  52. package/src/plugins/compatibility.ts +64 -0
  53. package/src/plugins/hono-islands.ts +105 -0
  54. package/src/plugins/hono-jsx.ts +20 -0
  55. package/src/plugins/hono-pages.ts +143 -0
  56. package/src/plugins/runtime-paths.ts +14 -0
  57. package/src/runtime/client.tsx +83 -0
  58. package/src/runtime/deferred.ts +34 -0
  59. package/src/runtime/document-head.ts +44 -0
  60. package/src/runtime/form.ts +71 -0
  61. package/src/runtime/pages.tsx +45 -0
@@ -0,0 +1,71 @@
1
+ import { useContext, useRef, useState } from 'hono/jsx';
2
+ import { isAbortError, submitAction, VoidActionError, type FormMethod } from 'void/pages-client';
3
+ import { RouterContext, useErrors, useRouter } from './pages.tsx';
4
+
5
+ function fieldErrors(error: VoidActionError): Record<string, string> {
6
+ const body = error.body;
7
+ if (typeof body !== 'object' || body === null || !('errors' in body) || typeof body.errors !== 'object' || body.errors === null) return {};
8
+ return Object.fromEntries(Object.entries(body.errors).filter((entry): entry is [string, string] => typeof entry[1] === 'string'));
9
+ }
10
+
11
+ export function useForm<T extends Record<string, unknown>>(initialData: T) {
12
+ if (!useContext(RouterContext)) throw new Error('Hono Pages: useForm requires the full Pages renderer; island form helpers are unsupported.');
13
+ const router = useRouter();
14
+ const initialErrors = useErrors();
15
+ const [data, updateData] = useState(initialData);
16
+ const [errors, setErrors] = useState(initialErrors);
17
+ const [pending, setPending] = useState(false);
18
+ const [wasSuccessful, setSuccessful] = useState(false);
19
+ const [error, setError] = useState<Error | null>(null);
20
+ const active = useRef(false);
21
+ const generation = useRef(0);
22
+
23
+ const setData: typeof updateData = (next) => {
24
+ updateData(next);
25
+ setSuccessful(false);
26
+ setErrors({});
27
+ setError(null);
28
+ };
29
+
30
+ async function submit(method: FormMethod, url: string) {
31
+ if (active.current) return;
32
+ active.current = true;
33
+ const id = ++generation.current;
34
+ setPending(true);
35
+ setSuccessful(false);
36
+ setError(null);
37
+ setErrors({});
38
+ try {
39
+ const result = await submitAction(router, url, { method: method.toUpperCase(), data, preserveScroll: true });
40
+ if (id !== generation.current) return;
41
+ if (result.ok) setSuccessful(true);
42
+ else { setError(result.error); setErrors(fieldErrors(result.error)); }
43
+ } catch (failure) {
44
+ if (id === generation.current && !isAbortError(failure)) {
45
+ setError(failure instanceof Error ? failure : new Error(String(failure)));
46
+ }
47
+ } finally {
48
+ if (id === generation.current) { active.current = false; setPending(false); }
49
+ }
50
+ }
51
+
52
+ function cancel() {
53
+ if (!active.current) return;
54
+ const id = ++generation.current;
55
+ active.current = false;
56
+ setPending(false);
57
+ setSuccessful(false);
58
+ void router.refresh().catch((failure: unknown) => {
59
+ if (id === generation.current) setError(failure instanceof Error ? failure : new Error(String(failure)));
60
+ });
61
+ }
62
+
63
+ return {
64
+ data, setData, errors, pending, wasSuccessful, error, submit, cancel,
65
+ reset() { setData(initialData); },
66
+ post: (url: string) => submit('post', url),
67
+ put: (url: string) => submit('put', url),
68
+ patch: (url: string) => submit('patch', url),
69
+ delete: (url: string) => submit('delete', url),
70
+ };
71
+ }
@@ -0,0 +1,45 @@
1
+ import { createContext, useContext, type JSX } from 'hono/jsx';
2
+ import type { VoidRouter } from 'void/pages-client';
3
+ import type { PageObject } from 'void/pages-protocol';
4
+ export type { DeferredState as Deferred } from 'void';
5
+
6
+ export const RouterContext = createContext<VoidRouter | null>(null);
7
+ export const PageContext = createContext<PageObject | null>(null);
8
+
9
+ function usePage() {
10
+ const page = useContext(PageContext);
11
+ if (!page) throw new Error('Hono Pages: page data requires a page provider.');
12
+ return page;
13
+ }
14
+
15
+ export function useShared() { return usePage().shared; }
16
+ export function useParams() { return usePage().params; }
17
+ export function useErrors() { return usePage().errors ?? {}; }
18
+
19
+ export function useRouter(): VoidRouter {
20
+ const router = useContext(RouterContext);
21
+ if (!router) throw new Error('Hono Pages: useRouter requires a page provider.');
22
+ return router;
23
+ }
24
+
25
+ type LinkProps = Omit<JSX.IntrinsicElements['a'], 'href' | 'onClick'> & {
26
+ href: string;
27
+ reloadDocument?: boolean;
28
+ replace?: boolean;
29
+ onClick?: (event: MouseEvent) => void;
30
+ };
31
+
32
+ export function Link({ href, reloadDocument, replace, onClick, ...attributes }: LinkProps) {
33
+ const router = useRouter();
34
+ return <a {...attributes} href={href} onClick={(event: MouseEvent) => {
35
+ onClick?.(event);
36
+ if (event.defaultPrevented || reloadDocument || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
37
+ if (attributes.download !== undefined || (attributes.target && attributes.target !== '_self') || attributes.rel?.split(/\s+/).includes('external')) return;
38
+ const url = new URL(href, window.location.href);
39
+ if (url.origin !== window.location.origin || !['http:', 'https:'].includes(url.protocol)) return;
40
+ event.preventDefault();
41
+ void router.visit(url, { replace }).catch((error: unknown) => {
42
+ window.dispatchEvent(new CustomEvent('hono-pages:navigation-error', { detail: error }));
43
+ });
44
+ }} />;
45
+ }