@hyperspan/framework 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "dist/server.ts",
6
6
  "types": "src/server.ts",
package/src/actions.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import { html, HSHtml } from '@hyperspan/html';
2
2
  import * as z from 'zod/v4';
3
3
  import { HTTPException } from 'hono/http-exception';
4
-
4
+ import { assetHash } from './assets';
5
5
  import { IS_PROD, returnHTMLResponse, type THSResponseTypes } from './server';
6
6
  import type { Context, MiddlewareHandler } from 'hono';
7
7
  import type { HandlerResponse, Next, TypedResponse } from 'hono/types';
8
- import { assetHash } from './assets';
9
8
 
10
9
  /**
11
10
  * Actions = Form + route handler
@@ -20,13 +19,19 @@ import { assetHash } from './assets';
20
19
  * 5. Replaces form content in place with HTML response content from server via the Idiomorph library
21
20
  * 6. Handles any Exception thrown on server as error displayed back to user on the page
22
21
  */
23
- type TActionResponse = THSResponseTypes | HandlerResponse<any> | TypedResponse<any, any, any>;
22
+ export type TActionResponse =
23
+ | THSResponseTypes
24
+ | HandlerResponse<any>
25
+ | TypedResponse<any, any, any>;
24
26
  export interface HSAction<T extends z.ZodTypeAny> {
25
27
  _kind: string;
26
28
  _route: string;
27
29
  _form: Parameters<HSAction<T>['form']>[0];
28
30
  form(
29
- renderForm: ({ data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }) => HSHtml
31
+ renderForm: (
32
+ c: Context<any, any, {}>,
33
+ { data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
34
+ ) => HSHtml | void | null | Promise<HSHtml | void | null>
30
35
  ): HSAction<T>;
31
36
  post(
32
37
  handler: (
@@ -40,12 +45,17 @@ export interface HSAction<T extends z.ZodTypeAny> {
40
45
  { data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
41
46
  ) => TActionResponse
42
47
  ): HSAction<T>;
43
- render(props?: { data?: z.infer<T>; error?: z.ZodError | Error }): TActionResponse;
48
+ render(
49
+ c: Context<any, any, {}>,
50
+ props?: { data?: z.infer<T>; error?: z.ZodError | Error }
51
+ ): TActionResponse;
44
52
  run(c: Context<any, any, {}>): TActionResponse | Promise<TActionResponse>;
45
53
  middleware: (
46
54
  middleware: Array<
47
55
  | MiddlewareHandler
48
- | ((context: Context<any, string, {}>) => TActionResponse | Promise<TActionResponse>)
56
+ | ((
57
+ context: Context<any, string, {}>
58
+ ) => TActionResponse | Promise<TActionResponse> | void | Promise<void>)
49
59
  >
50
60
  ) => HSAction<T>;
51
61
  _getRouteHandlers: () => Array<
@@ -103,8 +113,11 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
103
113
  /**
104
114
  * Get form renderer method
105
115
  */
106
- render(formState?: { data?: z.infer<T>; error?: z.ZodError | Error }) {
107
- const form = _form ? _form(formState || {}) : null;
116
+ render(
117
+ c: Context<any, any, {}>,
118
+ formState?: { data?: z.infer<T>; error?: z.ZodError | Error }
119
+ ) {
120
+ const form = _form ? _form(c, formState || {}) : null;
108
121
  return form ? html`<hs-action url="${this._route}">${form}</hs-action>` : null;
109
122
  },
110
123
 
@@ -136,7 +149,7 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
136
149
  const method = c.req.method;
137
150
 
138
151
  if (method === 'GET') {
139
- return await api.render();
152
+ return await api.render(c);
140
153
  }
141
154
 
142
155
  if (method !== 'POST') {
@@ -171,18 +184,13 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
171
184
  });
172
185
  }
173
186
 
174
- return await returnHTMLResponse(c, () => api.render({ data, error }), { status: 400 });
187
+ return await returnHTMLResponse(c, () => api.render(c, { data, error }), { status: 400 });
175
188
  },
176
189
  };
177
190
 
178
191
  return api;
179
192
  }
180
193
 
181
- /**
182
- * Form route handler helper
183
- */
184
- export type THSHandlerResponse = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
185
-
186
194
  /**
187
195
  * Return JSON data structure for a given FormData object
188
196
  * Accounts for array fields (e.g. name="options[]" or <select multiple>)
@@ -73,24 +73,33 @@ class HSAction extends HTMLElement {
73
73
  }
74
74
 
75
75
  connectedCallback() {
76
- // Have to run this code AFTER it is added to the DOM...
77
- setTimeout(() => {
78
- const form = this.querySelector('form');
79
-
80
- if (form) {
81
- form.setAttribute('action', this.getAttribute('url') || '');
82
- const submitHandler = (e: Event) => {
83
- formSubmitToRoute(e, form as HTMLFormElement, {
84
- afterResponse: () => this.connectedCallback(),
85
- });
86
- form.removeEventListener('submit', submitHandler);
87
- };
88
- form.addEventListener('submit', submitHandler);
89
- }
90
- });
76
+ actionFormObserver.observe(this, { childList: true, subtree: true });
91
77
  }
92
78
  }
93
79
  window.customElements.define('hs-action', HSAction);
80
+ const actionFormObserver = new MutationObserver((list) => {
81
+ list.forEach((mutation) => {
82
+ mutation.addedNodes.forEach((node) => {
83
+ if (node instanceof HTMLFormElement) {
84
+ bindHSActionForm(node.closest('hs-action') as HSAction, node);
85
+ }
86
+ });
87
+ });
88
+ });
89
+
90
+ /**
91
+ * Bind the form inside an hs-action element to the action URL and submit handler
92
+ */
93
+ function bindHSActionForm(hsActionElement: HSAction, form: HTMLFormElement) {
94
+ form.setAttribute('action', hsActionElement.getAttribute('url') || '');
95
+ const submitHandler = (e: Event) => {
96
+ formSubmitToRoute(e, form as HTMLFormElement, {
97
+ afterResponse: () => bindHSActionForm(hsActionElement, form),
98
+ });
99
+ form.removeEventListener('submit', submitHandler);
100
+ };
101
+ form.addEventListener('submit', submitHandler);
102
+ }
94
103
 
95
104
  /**
96
105
  * Submit form data to route and replace contents with response