void-plugin-hono-jsx 0.1.0-alpha.3 → 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 (105) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +86 -28
  3. package/THIRD_PARTY_NOTICES.md +2 -2
  4. package/dist/browser/client.d.ts +2 -1
  5. package/dist/browser/client.d.ts.map +1 -1
  6. package/dist/browser/client.js +76 -22
  7. package/dist/browser/client.js.map +1 -1
  8. package/dist/browser/document-head.d.ts +1 -1
  9. package/dist/browser/document-head.d.ts.map +1 -1
  10. package/dist/browser/document-head.js +22 -1
  11. package/dist/browser/document-head.js.map +1 -1
  12. package/dist/browser/form-view.d.ts +11 -0
  13. package/dist/browser/form-view.d.ts.map +1 -0
  14. package/dist/browser/form-view.js +18 -0
  15. package/dist/browser/form-view.js.map +1 -0
  16. package/dist/browser/form.d.ts +28 -12
  17. package/dist/browser/form.d.ts.map +1 -1
  18. package/dist/browser/form.js +92 -23
  19. package/dist/browser/form.js.map +1 -1
  20. package/dist/browser/markdown.d.ts +15 -0
  21. package/dist/browser/markdown.d.ts.map +1 -0
  22. package/dist/browser/markdown.js +6 -0
  23. package/dist/browser/markdown.js.map +1 -0
  24. package/dist/browser/navigation.d.ts +13 -0
  25. package/dist/browser/navigation.d.ts.map +1 -0
  26. package/dist/browser/navigation.js +144 -0
  27. package/dist/browser/navigation.js.map +1 -0
  28. package/dist/browser/pages.d.ts +33 -3
  29. package/dist/browser/pages.d.ts.map +1 -1
  30. package/dist/browser/pages.js +76 -13
  31. package/dist/browser/pages.js.map +1 -1
  32. package/dist/plugins/compatibility.d.ts +2 -0
  33. package/dist/plugins/compatibility.d.ts.map +1 -1
  34. package/dist/plugins/compatibility.js +13 -4
  35. package/dist/plugins/compatibility.js.map +1 -1
  36. package/dist/plugins/hono-islands.d.ts +1 -1
  37. package/dist/plugins/hono-islands.d.ts.map +1 -1
  38. package/dist/plugins/hono-islands.js +208 -29
  39. package/dist/plugins/hono-islands.js.map +1 -1
  40. package/dist/plugins/hono-jsx.d.ts +11 -3
  41. package/dist/plugins/hono-jsx.d.ts.map +1 -1
  42. package/dist/plugins/hono-jsx.js +183 -18
  43. package/dist/plugins/hono-jsx.js.map +1 -1
  44. package/dist/plugins/hono-markdown.d.ts +4 -0
  45. package/dist/plugins/hono-markdown.d.ts.map +1 -0
  46. package/dist/plugins/hono-markdown.js +102 -0
  47. package/dist/plugins/hono-markdown.js.map +1 -0
  48. package/dist/plugins/jsx-transform.d.ts +4 -0
  49. package/dist/plugins/jsx-transform.d.ts.map +1 -0
  50. package/dist/plugins/jsx-transform.js +21 -0
  51. package/dist/plugins/jsx-transform.js.map +1 -0
  52. package/dist/plugins/markdown-api.d.ts +11 -0
  53. package/dist/plugins/markdown-api.d.ts.map +1 -0
  54. package/dist/plugins/markdown-api.js +13 -0
  55. package/dist/plugins/markdown-api.js.map +1 -0
  56. package/dist/plugins/markdown-compiler.d.ts +43 -0
  57. package/dist/plugins/markdown-compiler.d.ts.map +1 -0
  58. package/dist/plugins/markdown-compiler.js +91 -0
  59. package/dist/plugins/markdown-compiler.js.map +1 -0
  60. package/dist/plugins/resolve-component.d.ts +3 -0
  61. package/dist/plugins/resolve-component.d.ts.map +1 -0
  62. package/dist/plugins/resolve-component.js +15 -0
  63. package/dist/plugins/resolve-component.js.map +1 -0
  64. package/dist/plugins/runtime-paths.d.ts +1 -0
  65. package/dist/plugins/runtime-paths.d.ts.map +1 -1
  66. package/dist/plugins/runtime-paths.js +2 -0
  67. package/dist/plugins/runtime-paths.js.map +1 -1
  68. package/dist/server/form-view.d.ts +11 -0
  69. package/dist/server/form-view.d.ts.map +1 -0
  70. package/dist/server/form-view.js +18 -0
  71. package/dist/server/form-view.js.map +1 -0
  72. package/dist/server/form.d.ts +28 -12
  73. package/dist/server/form.d.ts.map +1 -1
  74. package/dist/server/form.js +92 -23
  75. package/dist/server/form.js.map +1 -1
  76. package/dist/server/markdown.d.ts +15 -0
  77. package/dist/server/markdown.d.ts.map +1 -0
  78. package/dist/server/markdown.js +6 -0
  79. package/dist/server/markdown.js.map +1 -0
  80. package/dist/server/pages.d.ts +33 -3
  81. package/dist/server/pages.d.ts.map +1 -1
  82. package/dist/server/pages.js +76 -13
  83. package/dist/server/pages.js.map +1 -1
  84. package/package.json +26 -8
  85. package/src/plugins/compatibility.ts +11 -4
  86. package/src/plugins/hono-islands.ts +158 -28
  87. package/src/plugins/hono-jsx.ts +176 -17
  88. package/src/plugins/hono-markdown.ts +88 -0
  89. package/src/plugins/jsx-transform.ts +20 -0
  90. package/src/plugins/markdown-api.ts +18 -0
  91. package/src/plugins/markdown-compiler.ts +77 -0
  92. package/src/plugins/resolve-component.ts +12 -0
  93. package/src/plugins/runtime-paths.ts +2 -0
  94. package/src/runtime/client.tsx +66 -24
  95. package/src/runtime/document-head.ts +20 -1
  96. package/src/runtime/form-view.tsx +27 -0
  97. package/src/runtime/form.ts +87 -41
  98. package/src/runtime/markdown.ts +7 -0
  99. package/src/runtime/navigation.ts +115 -0
  100. package/src/runtime/pages.tsx +77 -15
  101. package/dist/plugins/hono-pages.d.ts +0 -4
  102. package/dist/plugins/hono-pages.d.ts.map +0 -1
  103. package/dist/plugins/hono-pages.js +0 -150
  104. package/dist/plugins/hono-pages.js.map +0 -1
  105. package/src/plugins/hono-pages.ts +0 -143
@@ -7,7 +7,10 @@ function scriptElements(head) {
7
7
  template.innerHTML = renderHeadToString({ script: head?.script });
8
8
  return Array.from(template.content.querySelectorAll('script'));
9
9
  }
10
- /** Complements Void's title/meta/link updates without executing navigation scripts. */
10
+ function metadataSignature(element) {
11
+ return JSON.stringify([element.tagName, Array.from(element.attributes, ({ name, value }) => [name, value]).filter(([name]) => name !== 'data-void-head').sort()]);
12
+ }
13
+ /** Synchronizes the snapshot before Void's idempotent post-render head commit. */
11
14
  export function createDocumentHead(initial) {
12
15
  let htmlKeys = Object.keys(initial?.htmlAttrs ?? {});
13
16
  let bodyKeys = Object.keys(initial?.bodyAttrs ?? {});
@@ -27,6 +30,24 @@ export function createDocumentHead(initial) {
27
30
  return Object.keys(next);
28
31
  }
29
32
  return (head) => {
33
+ if (head?.title !== undefined)
34
+ document.title = head.title;
35
+ // Use Void's serializer and managed-tag marker so the router's subsequent
36
+ // commit reuses these nodes instead of inserting duplicate meta/link tags.
37
+ const template = document.createElement('template');
38
+ template.innerHTML = renderHeadToString({ meta: head?.meta, link: head?.link });
39
+ const metadata = Array.from(document.head.querySelectorAll('meta[data-void-head],link[data-void-head]'));
40
+ let cursor = document.head.querySelector('meta[data-void-head],link[data-void-head]');
41
+ for (const desired of Array.from(template.content.querySelectorAll('meta,link'))) {
42
+ const index = metadata.findIndex(element => metadataSignature(element) === metadataSignature(desired));
43
+ const element = index >= 0 ? metadata.splice(index, 1)[0] : desired;
44
+ element.setAttribute('data-void-head', '');
45
+ if (element !== cursor)
46
+ document.head.insertBefore(element, cursor);
47
+ cursor = element.nextSibling;
48
+ }
49
+ for (const previous of metadata)
50
+ previous.remove();
30
51
  htmlKeys = attributes(document.documentElement, htmlKeys, head?.htmlAttrs);
31
52
  bodyKeys = attributes(document.body, bodyKeys, head?.bodyAttrs);
32
53
  const remaining = [...scripts];
@@ -1 +1 @@
1
- {"version":3,"file":"document-head.js","sourceRoot":"","sources":["../../src/runtime/document-head.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGrD,SAAS,eAAe,CAAC,MAAyB;IAChD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AACxH,CAAC;AAED,SAAS,cAAc,CAAC,IAAgC;IACtD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,SAAS,GAAG,kBAAkB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,kBAAkB,CAAC,OAAmC;IACpE,IAAI,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACrD,IAAI,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACrD,IAAI,OAAO,GAAwB,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACnG,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,SAAS,UAAU,CAAC,OAAoB,EAAE,QAAkB,EAAE,IAAI,GAA2B,EAAE;QAC7F,KAAK,MAAM,GAAG,IAAI,QAAQ;YAAE,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;gBAAE,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC7E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAClF,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,IAAgC,EAAE,EAAE;QAC1C,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC3E,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAChE,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;QAC/B,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;YACpG,IAAI,KAAK,IAAI,CAAC;gBAAE,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,uGAAuG;YACvG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,KAAK,MAAM,MAAM,IAAI,SAAS;YAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { renderHeadToString } from 'void/pages-head';\nimport type { HeadDescriptor } from 'void';\n\nfunction scriptSignature(script: HTMLScriptElement) {\n return JSON.stringify([Array.from(script.attributes, ({ name, value }) => [name, value]).sort(), script.textContent]);\n}\n\nfunction scriptElements(head: HeadDescriptor | undefined) {\n const template = document.createElement('template');\n template.innerHTML = renderHeadToString({ script: head?.script });\n return Array.from(template.content.querySelectorAll('script'));\n}\n\n/** Complements Void's title/meta/link updates without executing navigation scripts. */\nexport function createDocumentHead(initial: HeadDescriptor | undefined) {\n let htmlKeys = Object.keys(initial?.htmlAttrs ?? {});\n let bodyKeys = Object.keys(initial?.bodyAttrs ?? {});\n let scripts: HTMLScriptElement[] = [];\n const existing = Array.from(document.head.querySelectorAll('script'));\n for (const desired of scriptElements(initial)) {\n const index = existing.findIndex((script) => scriptSignature(script) === scriptSignature(desired));\n if (index >= 0) scripts.push(...existing.splice(index, 1));\n }\n\n function attributes(element: HTMLElement, previous: string[], next: Record<string, string> = {}) {\n for (const key of previous) if (!(key in next)) element.removeAttribute(key);\n for (const [key, value] of Object.entries(next)) element.setAttribute(key, value);\n return Object.keys(next);\n }\n\n return (head: HeadDescriptor | undefined) => {\n htmlKeys = attributes(document.documentElement, htmlKeys, head?.htmlAttrs);\n bodyKeys = attributes(document.body, bodyKeys, head?.bodyAttrs);\n const remaining = [...scripts];\n scripts = scriptElements(head).map((desired) => {\n const index = remaining.findIndex((script) => scriptSignature(script) === scriptSignature(desired));\n if (index >= 0) return remaining.splice(index, 1)[0];\n // Template-parsed scripts remain inert when inserted. Head changes are metadata, not script execution.\n document.head.appendChild(desired);\n return desired;\n });\n for (const script of remaining) script.remove();\n };\n}\n"]}
1
+ {"version":3,"file":"document-head.js","sourceRoot":"","sources":["../../src/runtime/document-head.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGrD,SAAS,eAAe,CAAC,MAAyB;IAChD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AACxH,CAAC;AAED,SAAS,cAAc,CAAC,IAAgC;IACtD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,SAAS,GAAG,kBAAkB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAgB;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpK,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,kBAAkB,CAAC,OAAmC;IACpE,IAAI,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACrD,IAAI,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACrD,IAAI,OAAO,GAAwB,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACnG,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,SAAS,UAAU,CAAC,OAAoB,EAAE,QAAkB,EAAE,IAAI,GAA2B,EAAE;QAC7F,KAAK,MAAM,GAAG,IAAI,QAAQ;YAAE,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;gBAAE,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC7E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAClF,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,IAAgC,EAAE,EAAE;QAC1C,IAAI,IAAI,EAAE,KAAK,KAAK,SAAS;YAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QAC3D,0EAA0E;QAC1E,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACpD,QAAQ,CAAC,SAAS,GAAG,kBAAkB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,2CAA2C,CAAC,CAAC,CAAC;QACzG,IAAI,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,2CAA2C,CAAgB,CAAC;QACrG,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YACjF,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;YACvG,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACpE,OAAO,CAAC,YAAY,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,OAAO,KAAK,MAAM;gBAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACpE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;QAC/B,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,QAAQ;YAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACnD,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC3E,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAChE,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;QAC/B,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;YACpG,IAAI,KAAK,IAAI,CAAC;gBAAE,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,uGAAuG;YACvG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,KAAK,MAAM,MAAM,IAAI,SAAS;YAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { renderHeadToString } from 'void/pages-head';\nimport type { HeadDescriptor } from 'void';\n\nfunction scriptSignature(script: HTMLScriptElement) {\n return JSON.stringify([Array.from(script.attributes, ({ name, value }) => [name, value]).sort(), script.textContent]);\n}\n\nfunction scriptElements(head: HeadDescriptor | undefined) {\n const template = document.createElement('template');\n template.innerHTML = renderHeadToString({ script: head?.script });\n return Array.from(template.content.querySelectorAll('script'));\n}\n\nfunction metadataSignature(element: Element) {\n return JSON.stringify([element.tagName, Array.from(element.attributes, ({ name, value }) => [name, value]).filter(([name]) => name !== 'data-void-head').sort()]);\n}\n\n/** Synchronizes the snapshot before Void's idempotent post-render head commit. */\nexport function createDocumentHead(initial: HeadDescriptor | undefined) {\n let htmlKeys = Object.keys(initial?.htmlAttrs ?? {});\n let bodyKeys = Object.keys(initial?.bodyAttrs ?? {});\n let scripts: HTMLScriptElement[] = [];\n const existing = Array.from(document.head.querySelectorAll('script'));\n for (const desired of scriptElements(initial)) {\n const index = existing.findIndex((script) => scriptSignature(script) === scriptSignature(desired));\n if (index >= 0) scripts.push(...existing.splice(index, 1));\n }\n\n function attributes(element: HTMLElement, previous: string[], next: Record<string, string> = {}) {\n for (const key of previous) if (!(key in next)) element.removeAttribute(key);\n for (const [key, value] of Object.entries(next)) element.setAttribute(key, value);\n return Object.keys(next);\n }\n\n return (head: HeadDescriptor | undefined) => {\n if (head?.title !== undefined) document.title = head.title;\n // Use Void's serializer and managed-tag marker so the router's subsequent\n // commit reuses these nodes instead of inserting duplicate meta/link tags.\n const template = document.createElement('template');\n template.innerHTML = renderHeadToString({ meta: head?.meta, link: head?.link });\n const metadata = Array.from(document.head.querySelectorAll('meta[data-void-head],link[data-void-head]'));\n let cursor = document.head.querySelector('meta[data-void-head],link[data-void-head]') as Node | null;\n for (const desired of Array.from(template.content.querySelectorAll('meta,link'))) {\n const index = metadata.findIndex(element => metadataSignature(element) === metadataSignature(desired));\n const element = index >= 0 ? metadata.splice(index, 1)[0] : desired;\n element.setAttribute('data-void-head', '');\n if (element !== cursor) document.head.insertBefore(element, cursor);\n cursor = element.nextSibling;\n }\n for (const previous of metadata) previous.remove();\n htmlKeys = attributes(document.documentElement, htmlKeys, head?.htmlAttrs);\n bodyKeys = attributes(document.body, bodyKeys, head?.bodyAttrs);\n const remaining = [...scripts];\n scripts = scriptElements(head).map((desired) => {\n const index = remaining.findIndex((script) => scriptSignature(script) === scriptSignature(desired));\n if (index >= 0) return remaining.splice(index, 1)[0];\n // Template-parsed scripts remain inert when inserted. Head changes are metadata, not script execution.\n document.head.appendChild(desired);\n return desired;\n });\n for (const script of remaining) script.remove();\n };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { type JSX, type Child } from 'hono/jsx';
2
+ import { type FormMethod, type FormModel, type VoidActionError } from 'void/pages-client';
3
+ export type FormProps = Omit<JSX.IntrinsicElements['form'], 'action' | 'method' | 'onSubmit' | 'onError' | 'encType'> & {
4
+ form: FormModel<[target?: string | FormData, body?: FormData]>;
5
+ method?: FormMethod;
6
+ encType?: 'application/json' | 'multipart/form-data';
7
+ url?: string;
8
+ renderError?: (error: VoidActionError) => Child;
9
+ };
10
+ export declare function Form({ form, method, url, encType, renderError, children, ...props }: FormProps): import("hono/jsx/dom/jsx-dev-runtime").JSX.Element;
11
+ //# sourceMappingURL=form-view.d.ts.map
@@ -0,0 +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,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"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/dom/jsx-runtime";
2
+ import { useEffect, useState } from 'hono/jsx';
3
+ import { formErrorMessage, formRetryMessage, formSubmissionBlocked, scheduleFormRetry } from 'void/pages-client';
4
+ export function Form({ form, method = 'post', url, encType = 'application/json', renderError, children, ...props }) {
5
+ const [now, setNow] = useState(Date.now());
6
+ useEffect(() => scheduleFormRetry(form.error, () => setNow(Date.now())), [form.error]);
7
+ const message = formErrorMessage(form);
8
+ const retry = formRetryMessage(form.error);
9
+ const blocked = formSubmissionBlocked(form, now);
10
+ return _jsxs("form", { ...props, encType: encType, "aria-busy": form.pending, onSubmit: (event) => {
11
+ event.preventDefault();
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
+ }
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] });
17
+ }
18
+ //# sourceMappingURL=form-view.js.map
@@ -0,0 +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;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"]}
@@ -1,17 +1,33 @@
1
- import { type FormMethod } from 'void/pages-client';
2
- export declare function useForm<T extends Record<string, unknown>>(initialData: T): {
1
+ import { VoidActionError, type FormMethod } from 'void/pages-client';
2
+ import type { ActionFormOptions, ActionUrl, ResolveActionBody } from 'void/routes';
3
+ import { action } from './pages.tsx';
4
+ export { Form, type FormProps } from './form-view.tsx';
5
+ export { action };
6
+ type Fields = Record<string, unknown>;
7
+ type Body<U extends string> = ResolveActionBody<U> extends Fields ? ResolveActionBody<U> : Fields;
8
+ type SetData<T> = {
9
+ (next: T | ((current: T) => T)): void;
10
+ <K extends keyof T & string>(field: K, value: T[K]): void;
11
+ };
12
+ export type HonoForm<T extends Fields> = {
3
13
  data: T;
4
- setData: (newState: T | ((currentState: T) => T)) => void;
14
+ setData: SetData<T>;
5
15
  errors: Record<string, string>;
6
16
  pending: boolean;
17
+ error: VoidActionError | null;
18
+ hasChanges: boolean;
7
19
  wasSuccessful: boolean;
8
- error: Error | null;
9
- submit: (method: FormMethod, url: string) => Promise<void>;
10
- cancel: () => void;
11
- reset(): void;
12
- post: (url: string) => Promise<void>;
13
- put: (url: string) => Promise<void>;
14
- patch: (url: string) => Promise<void>;
15
- delete: (url: string) => Promise<void>;
16
- };
20
+ recentlySuccessful: boolean;
21
+ submit(method: FormMethod, target?: string | FormData, body?: FormData): Promise<void>;
22
+ cancel(): void;
23
+ reset: {
24
+ (): void;
25
+ (...fields: Array<keyof T & string>): void;
26
+ };
27
+ clearErrors(...fields: Array<keyof T & string>): void;
28
+ clearError(): void;
29
+ } & Record<FormMethod, (target?: string | FormData, body?: FormData) => Promise<void>>;
30
+ export declare function useForm<T extends Fields>(initialData: T): HonoForm<T>;
31
+ export declare function useForm<U extends ActionUrl & string>(url: U, defaults: Body<U>, options?: ActionFormOptions<U>): HonoForm<Body<U>>;
32
+ export declare function useIslandForm<T extends Fields>(defaults: T): HonoForm<T>;
17
33
  //# sourceMappingURL=form.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/runtime/form.ts"],"names":[],"mappings":"AACA,OAAO,EAA+C,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AASjG,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;;;;;;;qBAmBzC,UAAU,OAAO,MAAM;;;gBAoCvC,MAAM;eACP,MAAM;iBACJ,MAAM;kBACL,MAAM;EAEvB"}
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"}
@@ -1,54 +1,121 @@
1
- import { useContext, useRef, useState } from 'hono/jsx';
2
- import { isAbortError, submitAction } from 'void/pages-client';
3
- import { RouterContext, useErrors, useRouter } from "void-plugin-hono-jsx/pages";
1
+ import { useContext, useEffect, useRef, useState } from 'hono/jsx';
2
+ import { isAbortError, isEqualFormValue, interpolateRouteParams, submitAction, VoidActionError } from 'void/pages-client';
3
+ import { RouterContext, useErrors, useRouter, action } from "void-plugin-hono-jsx/pages";
4
+ export { Form } from './form-view.js';
5
+ export { action };
6
+ function toFormError(failure, url) {
7
+ if (failure instanceof VoidActionError)
8
+ return failure;
9
+ const message = failure instanceof Error ? failure.message : String(failure);
10
+ const error = new VoidActionError({ body: { message }, status: 0, statusText: 'Network Error', url });
11
+ error.message = message;
12
+ return error;
13
+ }
4
14
  function fieldErrors(error) {
5
15
  const body = error.body;
6
16
  if (typeof body !== 'object' || body === null || !('errors' in body) || typeof body.errors !== 'object' || body.errors === null)
7
17
  return {};
8
18
  return Object.fromEntries(Object.entries(body.errors).filter((entry) => typeof entry[1] === 'string'));
9
19
  }
10
- export function useForm(initialData) {
20
+ export function useForm(initial, values, options) {
11
21
  if (!useContext(RouterContext))
12
- throw new Error('Hono Pages: useForm requires the full Pages renderer; island form helpers are unsupported.');
22
+ throw new Error('Hono Pages: useForm requires the full Pages renderer; use useIslandForm for islands.');
13
23
  const router = useRouter();
14
- const initialErrors = useErrors();
15
- const [data, updateData] = useState(initialData);
24
+ const bound = typeof initial === 'string' ? initial : undefined;
25
+ 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);
26
+ }
27
+ export function useIslandForm(defaults) {
28
+ const controller = useRef(null);
29
+ useEffect(() => () => controller.current?.abort(), []);
30
+ return useFormModel(defaults, {}, async (method, url, data) => {
31
+ const target = new URL(url, window.location.href);
32
+ if (target.origin !== window.location.origin || !['http:', 'https:'].includes(target.protocol))
33
+ throw new Error('Hono island form: actions must target the current origin.');
34
+ controller.current = new AbortController();
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 });
37
+ if (response.redirected && response.ok) {
38
+ window.location.assign(response.url);
39
+ return { ok: true, pageData: undefined };
40
+ }
41
+ if (response.ok) {
42
+ window.location.reload();
43
+ return { ok: true, pageData: undefined };
44
+ }
45
+ let body;
46
+ try {
47
+ body = response.headers.get('content-type')?.includes('application/json') ? await response.json() : await response.text();
48
+ }
49
+ catch {
50
+ body = null;
51
+ }
52
+ return { ok: false, error: new VoidActionError({ body, status: response.status, statusText: response.statusText, url: target.href }) };
53
+ }, () => controller.current?.abort());
54
+ }
55
+ function useFormModel(initial, initialErrors, send, abort, bound) {
56
+ const defaults = useRef({ ...initial });
57
+ const current = useRef({ ...defaults.current });
58
+ const [data, updateData] = useState(current.current);
16
59
  const [errors, setErrors] = useState(initialErrors);
17
60
  const [pending, setPending] = useState(false);
18
61
  const [wasSuccessful, setSuccessful] = useState(false);
62
+ const [recentlySuccessful, setRecent] = useState(false);
19
63
  const [error, setError] = useState(null);
20
64
  const active = useRef(false);
21
65
  const generation = useRef(0);
22
- const setData = (next) => {
23
- updateData(next);
66
+ const timer = useRef(undefined);
67
+ useEffect(() => () => { ++generation.current; active.current = false; clearTimeout(timer.current); }, []);
68
+ const setData = (next, value) => {
69
+ current.current = typeof next === 'string' ? { ...current.current, [next]: value } : typeof next === 'function' ? next(current.current) : { ...next };
70
+ updateData(current.current);
24
71
  setSuccessful(false);
25
72
  setErrors({});
26
73
  setError(null);
74
+ setRecent(false);
75
+ clearTimeout(timer.current);
27
76
  };
28
- async function submit(method, url) {
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;
81
+ const url = typeof target === 'string' ? target : bound;
82
+ if (!url)
83
+ throw new Error('Hono Form: supply an action URL to useForm or submit.');
29
84
  if (active.current)
30
85
  return;
31
86
  active.current = true;
32
87
  const id = ++generation.current;
33
88
  setPending(true);
34
89
  setSuccessful(false);
90
+ setRecent(false);
91
+ clearTimeout(timer.current);
35
92
  setError(null);
36
93
  setErrors({});
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);
37
99
  try {
38
- const result = await submitAction(router, url, { method: method.toUpperCase(), data, preserveScroll: true });
100
+ const result = await send(method, url, payload);
39
101
  if (id !== generation.current)
40
102
  return;
41
- if (result.ok)
103
+ if (result.ok) {
104
+ if (bound && !multipart)
105
+ defaults.current = submitted;
42
106
  setSuccessful(true);
107
+ setRecent(true);
108
+ timer.current = setTimeout(() => { if (id === generation.current)
109
+ setRecent(false); }, 2000);
110
+ }
43
111
  else {
44
112
  setError(result.error);
45
113
  setErrors(fieldErrors(result.error));
46
114
  }
47
115
  }
48
116
  catch (failure) {
49
- if (id === generation.current && !isAbortError(failure)) {
50
- setError(failure instanceof Error ? failure : new Error(String(failure)));
51
- }
117
+ if (id === generation.current && !isAbortError(failure))
118
+ setError(toFormError(failure, url));
52
119
  }
53
120
  finally {
54
121
  if (id === generation.current) {
@@ -64,18 +131,20 @@ export function useForm(initialData) {
64
131
  active.current = false;
65
132
  setPending(false);
66
133
  setSuccessful(false);
67
- void router.refresh().catch((failure) => {
134
+ setRecent(false);
135
+ clearTimeout(timer.current);
136
+ void Promise.resolve(abort()).catch((failure) => {
68
137
  if (id === generation.current)
69
- setError(failure instanceof Error ? failure : new Error(String(failure)));
138
+ setError(toFormError(failure, bound ?? 'island'));
70
139
  });
71
140
  }
72
141
  return {
73
- data, setData, errors, pending, wasSuccessful, error, submit, cancel,
74
- reset() { setData(initialData); },
75
- post: (url) => submit('post', url),
76
- put: (url) => submit('put', url),
77
- patch: (url) => submit('patch', url),
78
- delete: (url) => submit('delete', url),
142
+ data, setData, errors, pending, wasSuccessful, recentlySuccessful, error, submit, cancel,
143
+ hasChanges: Object.keys({ ...defaults.current, ...data }).some((key) => !isEqualFormValue(defaults.current[key], data[key])),
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 }); },
145
+ clearErrors(...fields) { setErrors((previous) => fields.length ? Object.fromEntries(Object.entries(previous).filter(([field]) => !fields.includes(field))) : {}); },
146
+ clearError() { setError(null); },
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),
79
148
  };
80
149
  }
81
150
  //# sourceMappingURL=form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU;AACvD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAoC,MAAM,mBAAmB;AAChG,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,4BAAa;AAEjE,SAAS,WAAW,CAAC,KAAsB;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI;IACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE;IAC1I,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;AACnI;AAEA,MAAM,UAAU,OAAO,CAAoC,WAAc;IACvE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4FAA4F,CAAC;IAC7I,MAAM,MAAM,GAAG,SAAS,EAAE;IAC1B,MAAM,aAAa,GAAG,SAAS,EAAE;IACjC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC;IAChD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC;IACnD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7C,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IACtD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC;IAE5B,MAAM,OAAO,GAAsB,CAAC,IAAI,EAAE,EAAE;QAC1C,UAAU,CAAC,IAAI,CAAC;QAChB,aAAa,CAAC,KAAK,CAAC;QACpB,SAAS,CAAC,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC;IAChB,CAAC;IAED,KAAK,UAAU,MAAM,CAAC,MAAkB,EAAE,GAAW;QACnD,IAAI,MAAM,CAAC,OAAO;YAAE;QACpB,MAAM,CAAC,OAAO,GAAG,IAAI;QACrB,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,OAAO;QAC/B,UAAU,CAAC,IAAI,CAAC;QAChB,aAAa,CAAC,KAAK,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC;QACd,SAAS,CAAC,EAAE,CAAC;QACb,IAAI;YACF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;YAC5G,IAAI,EAAE,KAAK,UAAU,CAAC,OAAO;gBAAE;YAC/B,IAAI,MAAM,CAAC,EAAE;gBAAE,aAAa,CAAC,IAAI,CAAC;iBAC7B;gBAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC;gBAAE,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAAE;QACvE;QAAE,OAAO,OAAO,EAAE;YAChB,IAAI,EAAE,KAAK,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE;gBACvD,QAAQ,CAAC,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC3E;QACF;gBAAU;YACR,IAAI,EAAE,KAAK,UAAU,CAAC,OAAO,EAAE;gBAAE,MAAM,CAAC,OAAO,GAAG,KAAK;gBAAE,UAAU,CAAC,KAAK,CAAC;YAAE;QAC9E;IACF;IAEA,SAAS,MAAM;QACb,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE;QACrB,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,OAAO;QAC/B,MAAM,CAAC,OAAO,GAAG,KAAK;QACtB,UAAU,CAAC,KAAK,CAAC;QACjB,aAAa,CAAC,KAAK,CAAC;QACpB,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,OAAgB,EAAE,EAAE;YAC/C,IAAI,EAAE,KAAK,UAAU,CAAC,OAAO;gBAAE,QAAQ,CAAC,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1G,CAAC,CAAC;IACJ;IAEA,OAAO;QACL,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;QACpE,KAAK,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;QAC1C,GAAG,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC;QACxC,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC;QAC5C,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC;KAC/C;AACH","names":[],"ignoreList":[],"sources":["../../src/runtime/form.ts"],"sourcesContent":["import { useContext, useRef, useState } from 'hono/jsx';\nimport { isAbortError, submitAction, VoidActionError, type FormMethod } from 'void/pages-client';\nimport { RouterContext, useErrors, useRouter } from './pages.tsx';\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 Record<string, unknown>>(initialData: T) {\n if (!useContext(RouterContext)) throw new Error('Hono Pages: useForm requires the full Pages renderer; island form helpers are unsupported.');\n const router = useRouter();\n const initialErrors = useErrors();\n const [data, updateData] = useState(initialData);\n const [errors, setErrors] = useState(initialErrors);\n const [pending, setPending] = useState(false);\n const [wasSuccessful, setSuccessful] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const active = useRef(false);\n const generation = useRef(0);\n\n const setData: typeof updateData = (next) => {\n updateData(next);\n setSuccessful(false);\n setErrors({});\n setError(null);\n };\n\n async function submit(method: FormMethod, url: string) {\n if (active.current) return;\n active.current = true;\n const id = ++generation.current;\n setPending(true);\n setSuccessful(false);\n setError(null);\n setErrors({});\n try {\n const result = await submitAction(router, url, { method: method.toUpperCase(), data, preserveScroll: true });\n if (id !== generation.current) return;\n if (result.ok) setSuccessful(true);\n else { setError(result.error); setErrors(fieldErrors(result.error)); }\n } catch (failure) {\n if (id === generation.current && !isAbortError(failure)) {\n setError(failure instanceof Error ? failure : new Error(String(failure)));\n }\n } finally {\n if (id === generation.current) { active.current = false; setPending(false); }\n }\n }\n\n function cancel() {\n if (!active.current) return;\n const id = ++generation.current;\n active.current = false;\n setPending(false);\n setSuccessful(false);\n void router.refresh().catch((failure: unknown) => {\n if (id === generation.current) setError(failure instanceof Error ? failure : new Error(String(failure)));\n });\n }\n\n return {\n data, setData, errors, pending, wasSuccessful, error, submit, cancel,\n reset() { setData(initialData); },\n post: (url: string) => submit('post', url),\n put: (url: string) => submit('put', url),\n patch: (url: string) => submit('patch', url),\n delete: (url: string) => submit('delete', url),\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"]}
@@ -0,0 +1,15 @@
1
+ export type MarkdownHeading = {
2
+ depth: number;
3
+ slug: string;
4
+ text: string;
5
+ };
6
+ export type MarkdownContextValue = {
7
+ frontmatter: Record<string, unknown>;
8
+ headings: MarkdownHeading[];
9
+ props: Record<string, unknown>;
10
+ };
11
+ export declare const MarkdownContext: import("hono/jsx").Context<MarkdownContextValue>;
12
+ export declare function useFrontmatter<T = Record<string, unknown>>(): T;
13
+ export declare function useMarkdownHeadings(): MarkdownHeading[];
14
+ export declare function useMarkdownProps<T = Record<string, unknown>>(): T;
15
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/runtime/markdown.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC5E,MAAM,MAAM,oBAAoB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AACzI,eAAO,MAAM,eAAe,kDAAoF,CAAC;AACjH,wBAAgB,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAyD;AACzH,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAAiD;AACzG,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAmD"}
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from 'hono/jsx';
2
+ export const MarkdownContext = createContext({ frontmatter: {}, headings: [], props: {} });
3
+ export function useFrontmatter() { return useContext(MarkdownContext).frontmatter; }
4
+ export function useMarkdownHeadings() { return useContext(MarkdownContext).headings; }
5
+ export function useMarkdownProps() { return useContext(MarkdownContext).props; }
6
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/runtime/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGrD,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAuB,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;AACjH,MAAM,UAAU,cAAc,KAAqC,OAAO,UAAU,CAAC,eAAe,CAAC,CAAC,WAAgB,CAAC,CAAC,CAAC;AACzH,MAAM,UAAU,mBAAmB,KAAwB,OAAO,UAAU,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzG,MAAM,UAAU,gBAAgB,KAAqC,OAAO,UAAU,CAAC,eAAe,CAAC,CAAC,KAAU,CAAC,CAAC,CAAC","sourcesContent":["import { createContext, useContext } from 'hono/jsx';\nexport type MarkdownHeading = { depth: number; slug: string; text: string };\nexport type MarkdownContextValue = { frontmatter: Record<string, unknown>; headings: MarkdownHeading[]; props: Record<string, unknown> };\nexport const MarkdownContext = createContext<MarkdownContextValue>({ frontmatter: {}, headings: [], props: {} });\nexport function useFrontmatter<T = Record<string, unknown>>(): T { return useContext(MarkdownContext).frontmatter as T; }\nexport function useMarkdownHeadings(): MarkdownHeading[] { return useContext(MarkdownContext).headings; }\nexport function useMarkdownProps<T = Record<string, unknown>>(): T { return useContext(MarkdownContext).props as T; }\n"]}
@@ -0,0 +1,13 @@
1
+ import { type VoidRouter, type VoidRouterConfig } from 'void/pages-client';
2
+ /** DOM updates still complete when a native animation is skipped or replaced. */
3
+ export declare function applyWithTransition(enabled: boolean, update: () => Promise<void>): Promise<void>;
4
+ /** Hono render integration over Void's public navigation engine and facade. */
5
+ export declare function createHonoRouter(config: VoidRouterConfig): Promise<{
6
+ router: VoidRouter;
7
+ initialPageData: import("void/pages-protocol").PageObject;
8
+ initialComponent: unknown;
9
+ initialLayouts: unknown[];
10
+ start: () => void;
11
+ commitInitialRender: () => void;
12
+ }>;
13
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,144 @@
1
+ import { createPageNavigationEngine, createRouterFacade, idleNavigationState, isAbortError, VoidActionError } from 'void/pages-client';
2
+ /** DOM updates still complete when a native animation is skipped or replaced. */
3
+ export async function applyWithTransition(enabled, update) {
4
+ if (!enabled || !document.startViewTransition)
5
+ return update();
6
+ const transition = document.startViewTransition(update);
7
+ // Rendering failures propagate through updateCallbackDone; animation skips do not.
8
+ void transition.ready.catch(() => { });
9
+ void transition.finished?.catch(() => { });
10
+ await transition.updateCallbackDone;
11
+ }
12
+ /** Hono render integration over Void's public navigation engine and facade. */
13
+ export async function createHonoRouter(config) {
14
+ const engine = await createPageNavigationEngine(config);
15
+ const currentUrl = () => window.location.pathname + window.location.search + window.location.hash;
16
+ const toUrl = (value) => new URL(value, window.location.href);
17
+ const normalize = (value) => {
18
+ const url = toUrl(value);
19
+ // Preserve foreign origins and schemes for the shared engine's guards.
20
+ return url.origin === window.location.origin && ['http:', 'https:'].includes(url.protocol)
21
+ ? url.pathname + url.search + url.hash : url.href;
22
+ };
23
+ let routeUrl = currentUrl();
24
+ let routeParams = engine.initialPageData.params ?? {};
25
+ let active = null;
26
+ let resource;
27
+ let multipartController;
28
+ let multipartId = 0;
29
+ const setRoute = (url, params = routeParams) => { routeUrl = url; routeParams = params; config.onRouteChange?.(url); };
30
+ const assertCurrent = (id) => {
31
+ if (active !== id)
32
+ throw new DOMException('Hono Pages: stale navigation callback discarded.', 'AbortError');
33
+ };
34
+ async function apply(next, navigationMethod = next.method) {
35
+ resource = next;
36
+ active = next.id;
37
+ const previousParams = routeParams;
38
+ config.onNavigationChange?.({ state: navigationMethod === 'GET' ? 'loading' : 'submitting', location: toUrl(next.fetchUrl + next.hash), method: navigationMethod.toUpperCase() });
39
+ try {
40
+ const result = await next.shell;
41
+ if (!result)
42
+ return;
43
+ assertCurrent(next.id);
44
+ setRoute(result.pageData.url || next.fetchUrl + next.hash, result.pageData.params ?? {});
45
+ await applyWithTransition(next.options.viewTransition ?? config.viewTransitions, async () => {
46
+ assertCurrent(next.id);
47
+ await config.adapter.applyUpdate(result.pageData, result.component, result.layouts, { preserveState: next.options.preserveState ?? next.method !== 'GET' });
48
+ });
49
+ assertCurrent(next.id);
50
+ await next.commit();
51
+ if (active === next.id)
52
+ setRoute(currentUrl(), result.pageData.params ?? {});
53
+ return result.pageData;
54
+ }
55
+ catch (error) {
56
+ if (active === next.id)
57
+ routeParams = previousParams;
58
+ if (navigationMethod === 'GET' && !next.options._resolveOnShell && isAbortError(error))
59
+ return;
60
+ throw error;
61
+ }
62
+ finally {
63
+ if (resource === next)
64
+ resource = undefined;
65
+ if (active === next.id) {
66
+ active = null;
67
+ setRoute(currentUrl());
68
+ config.onNavigationChange?.(idleNavigationState());
69
+ }
70
+ }
71
+ }
72
+ async function visitMultipart(url, options) {
73
+ const method = (options.method ?? 'GET').toUpperCase();
74
+ if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(method))
75
+ throw new Error('Hono Pages: FormData requires POST, PUT, PATCH or DELETE.');
76
+ const target = toUrl(url);
77
+ if (target.origin !== window.location.origin || !['http:', 'https:'].includes(target.protocol))
78
+ throw new Error('Hono Pages: FormData actions must target the current origin.');
79
+ // Negative IDs cannot collide with the shared engine's positive generation IDs.
80
+ const id = --multipartId;
81
+ active = id;
82
+ resource?.abort();
83
+ multipartController?.abort();
84
+ const controller = multipartController = new AbortController();
85
+ engine.flushAll();
86
+ config.onNavigationChange?.({ state: 'submitting', location: target, method: method });
87
+ try {
88
+ const response = await fetch(target, { method, body: options.data, headers: { 'X-VoidPages': 'true', Accept: 'application/json' }, signal: controller.signal });
89
+ let body;
90
+ if (response.headers.get('content-type')?.includes('application/json'))
91
+ body = await response.json();
92
+ else if (!response.ok)
93
+ body = await response.text();
94
+ else
95
+ await response.body?.cancel();
96
+ assertCurrent(id);
97
+ if (!response.ok)
98
+ throw new VoidActionError({ body, status: response.status, statusText: response.statusText, url });
99
+ if (typeof body === 'object' && body !== null && 'errors' in body && body.errors && Object.keys(body.errors).length) {
100
+ throw new VoidActionError({ body: { errors: body.errors }, status: 422, statusText: 'Unprocessable Content', url });
101
+ }
102
+ if (!response.headers.get('X-VoidPages')) {
103
+ window.location.assign(response.url || target.href);
104
+ return;
105
+ }
106
+ const destination = response.redirected ? response.url : typeof body === 'object' && body !== null && 'url' in body && typeof body.url === 'string' ? body.url : target.href;
107
+ multipartController = undefined;
108
+ // The public engine cannot consume raw FormData or an externally fetched shell.
109
+ // Refresh through its GET protocol, retaining component state and mutation UI.
110
+ return await apply(engine.prepareVisit(normalize(destination), { ...options, method: 'GET', data: undefined, preserveState: options.preserveState ?? true, preserveScroll: options.preserveScroll ?? true }), method);
111
+ }
112
+ finally {
113
+ if (multipartController === controller)
114
+ multipartController = undefined;
115
+ if (active === id) {
116
+ active = null;
117
+ setRoute(currentUrl());
118
+ config.onNavigationChange?.(idleNavigationState());
119
+ }
120
+ }
121
+ }
122
+ const raw = {
123
+ _hoverDelay: engine._hoverDelay,
124
+ get url() { return toUrl(routeUrl); },
125
+ get path() { return this.url.pathname; },
126
+ get query() { return this.url.searchParams; },
127
+ get params() { return routeParams; },
128
+ visit(url, options = {}) {
129
+ if (options.data instanceof FormData)
130
+ return visitMultipart(normalize(url), options);
131
+ multipartController?.abort();
132
+ multipartController = undefined;
133
+ return apply(engine.prepareVisit(normalize(url), options));
134
+ },
135
+ refresh(options = {}) { return raw.visit(window.location.pathname + window.location.search, { preserveState: true, preserveScroll: true, ...options }); },
136
+ prefetch(url, options = {}) { return engine.prefetch(normalize(url), options); },
137
+ flush(url, method = 'GET') { engine.flush(normalize(url), method); },
138
+ flushAll() { engine.flushAll(); },
139
+ };
140
+ const router = createRouterFacade(raw, () => routeUrl, () => routeParams);
141
+ engine.setPopStateHandler((url, options) => { void router.visit(url, options).catch(error => window.dispatchEvent(new CustomEvent('hono-pages:navigation-error', { detail: error }))); });
142
+ return { router, initialPageData: engine.initialPageData, initialComponent: engine.initialComponent, initialLayouts: engine.initialLayouts, start: engine.start, commitInitialRender: engine.commitInitialRender };
143
+ }
144
+ //# sourceMappingURL=navigation.js.map