@assistant-ui/react-hook-form 0.12.33 → 0.12.35
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/dist/formTools.d.ts +16 -19
- package/dist/formTools.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/useAssistantForm.d.ts +17 -20
- package/dist/useAssistantForm.d.ts.map +1 -1
- package/dist/useAssistantForm.js +16 -3
- package/dist/useAssistantForm.js.map +1 -1
- package/package.json +11 -10
- package/src/useAssistantForm.test.tsx +37 -0
- package/src/useAssistantForm.ts +27 -6
package/dist/formTools.d.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
2
|
+
export declare const formTools: {
|
|
3
|
+
set_form_field: {
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: z.ZodObject<{
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
value: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
};
|
|
10
|
+
submit_form: {
|
|
11
|
+
description: string;
|
|
12
|
+
parameters: z.ZodObject<{}, z.core.$strip>;
|
|
13
|
+
};
|
|
14
|
+
reset_form: {
|
|
15
|
+
description: string;
|
|
16
|
+
parameters: z.ZodObject<{}, z.core.$strip>;
|
|
17
|
+
};
|
|
19
18
|
};
|
|
20
|
-
//#endregion
|
|
21
|
-
export { formTools };
|
|
22
19
|
//# sourceMappingURL=formTools.d.ts.map
|
package/dist/formTools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formTools.d.ts","
|
|
1
|
+
{"version":3,"file":"formTools.d.ts","sourceRoot":"","sources":["../src/formTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;CAkBrB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export { useAssistantForm, type UseAssistantFormProps, } from "./useAssistantForm.js";
|
|
2
|
+
export { formTools } from "./formTools.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,8BAA2B;AAC5B,OAAO,EAAE,SAAS,EAAE,uBAAoB"}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
|
+
import { type ToolCallMessagePartComponent } from "@assistant-ui/core/react";
|
|
2
|
+
import { type FieldValues, type UseFormProps, type UseFormReturn } from "react-hook-form";
|
|
3
|
+
import type { z } from "zod";
|
|
1
4
|
import { formTools } from "./formTools.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
reset_form?: {
|
|
16
|
-
render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.reset_form)["parameters"]>, unknown> | undefined;
|
|
17
|
-
} | undefined;
|
|
5
|
+
export type UseAssistantFormProps<TFieldValues extends FieldValues, TContext, TTransformedValues> = UseFormProps<TFieldValues, TContext, TTransformedValues> & {
|
|
6
|
+
assistant?: {
|
|
7
|
+
tools?: {
|
|
8
|
+
set_form_field?: {
|
|
9
|
+
render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.set_form_field)["parameters"]>, unknown> | undefined;
|
|
10
|
+
} | undefined;
|
|
11
|
+
submit_form?: {
|
|
12
|
+
render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.submit_form)["parameters"]>, unknown> | undefined;
|
|
13
|
+
} | undefined;
|
|
14
|
+
reset_form?: {
|
|
15
|
+
render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.reset_form)["parameters"]>, unknown> | undefined;
|
|
16
|
+
} | undefined;
|
|
17
|
+
} | undefined;
|
|
18
18
|
} | undefined;
|
|
19
|
-
} | undefined;
|
|
20
19
|
};
|
|
21
|
-
declare const useAssistantForm: <TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>(props?: UseAssistantFormProps<TFieldValues, TContext, TTransformedValues>) => UseFormReturn<TFieldValues, TContext, TTransformedValues>;
|
|
22
|
-
//#endregion
|
|
23
|
-
export { UseAssistantFormProps, useAssistantForm };
|
|
20
|
+
export declare const useAssistantForm: <TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>(props?: UseAssistantFormProps<TFieldValues, TContext, TTransformedValues>) => UseFormReturn<TFieldValues, TContext, TTransformedValues>;
|
|
24
21
|
//# sourceMappingURL=useAssistantForm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAssistantForm.d.ts","
|
|
1
|
+
{"version":3,"file":"useAssistantForm.d.ts","sourceRoot":"","sources":["../src/useAssistantForm.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAG7E,OAAO,EAEL,KAAK,WAAW,EAGhB,KAAK,YAAY,EACjB,KAAK,aAAa,EAGnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,uBAAoB;AAYxC,MAAM,MAAM,qBAAqB,CAC/B,YAAY,SAAS,WAAW,EAChC,QAAQ,EACR,kBAAkB,IAChB,YAAY,CAAC,YAAY,EAAE,QAAQ,EAAE,kBAAkB,CAAC,GAAG;IAC7D,SAAS,CAAC,EACN;QACE,KAAK,CAAC,EACF;YACE,cAAc,CAAC,EACX;gBACE,MAAM,CAAC,EACH,4BAA4B,CAC1B,CAAC,CAAC,KAAK,CACL,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,CAAC,YAAY,CAAC,CAChD,EACD,OAAO,CACR,GACD,SAAS,CAAC;aACf,GACD,SAAS,CAAC;YACd,WAAW,CAAC,EACR;gBACE,MAAM,CAAC,EACH,4BAA4B,CAC1B,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,SAAS,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,EACrD,OAAO,CACR,GACD,SAAS,CAAC;aACf,GACD,SAAS,CAAC;YACd,UAAU,CAAC,EACP;gBACE,MAAM,CAAC,EACH,4BAA4B,CAC1B,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,SAAS,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,EACpD,OAAO,CACR,GACD,SAAS,CAAC;aACf,GACD,SAAS,CAAC;SACf,GACD,SAAS,CAAC;KACf,GACD,SAAS,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,YAAY,SAAS,WAAW,GAAG,WAAW,EAC9C,QAAQ,GAAG,GAAG,EACd,kBAAkB,GAAG,YAAY,EAEjC,QAAQ,qBAAqB,CAAC,YAAY,EAAE,QAAQ,EAAE,kBAAkB,CAAC,KACxE,aAAa,CAAC,YAAY,EAAE,QAAQ,EAAE,kBAAkB,CA2R1D,CAAC"}
|
package/dist/useAssistantForm.js
CHANGED
|
@@ -22,16 +22,28 @@ const useAssistantForm = (props) => {
|
|
|
22
22
|
pendingAssistantSubmitRef.current = null;
|
|
23
23
|
pending.reject(error);
|
|
24
24
|
}, []);
|
|
25
|
+
useEffect(() => () => {
|
|
26
|
+
const pending = pendingAssistantSubmitRef.current;
|
|
27
|
+
if (!pending) return;
|
|
28
|
+
pending.unmounted = true;
|
|
29
|
+
pending.resolve(false);
|
|
30
|
+
}, []);
|
|
25
31
|
const handleSubmit = useCallback((onValid, onInvalid) => {
|
|
26
32
|
const submit = baseHandleSubmit((...args) => {
|
|
27
33
|
const pending = pendingAssistantSubmitRef.current;
|
|
28
34
|
const event = args[1]?.nativeEvent ?? args[1];
|
|
29
|
-
if (pending && pending.event === event)
|
|
35
|
+
if (pending && pending.event === event) {
|
|
36
|
+
pending.outcome = true;
|
|
37
|
+
if (pending.unmounted) return;
|
|
38
|
+
}
|
|
30
39
|
return onValid(...args);
|
|
31
40
|
}, (...args) => {
|
|
32
41
|
const pending = pendingAssistantSubmitRef.current;
|
|
33
42
|
const event = args[1]?.nativeEvent ?? args[1];
|
|
34
|
-
if (pending && pending.event === event)
|
|
43
|
+
if (pending && pending.event === event) {
|
|
44
|
+
pending.outcome = false;
|
|
45
|
+
if (pending.unmounted) return;
|
|
46
|
+
}
|
|
35
47
|
return onInvalid?.(...args);
|
|
36
48
|
});
|
|
37
49
|
return (async (event) => {
|
|
@@ -109,6 +121,7 @@ const useAssistantForm = (props) => {
|
|
|
109
121
|
dispatching: true,
|
|
110
122
|
event: void 0,
|
|
111
123
|
handlerInvoked: false,
|
|
124
|
+
unmounted: false,
|
|
112
125
|
outcome: void 0,
|
|
113
126
|
resolve: resolveSubmission,
|
|
114
127
|
reject: rejectSubmission
|
|
@@ -135,7 +148,7 @@ const useAssistantForm = (props) => {
|
|
|
135
148
|
if (await submissionResult) return { success: true };
|
|
136
149
|
return {
|
|
137
150
|
success: false,
|
|
138
|
-
message: dispatched ? "The form contains invalid fields and was not submitted." : "The form did not accept the submission."
|
|
151
|
+
message: assistantSubmit.unmounted ? "The form is no longer available." : dispatched ? "The form contains invalid fields and was not submitted." : "The form did not accept the submission."
|
|
139
152
|
};
|
|
140
153
|
}
|
|
141
154
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAssistantForm.js","names":[],"sources":["../src/useAssistantForm.ts"],"sourcesContent":["\"use client\";\n\nimport { type ModelContext, tool } from \"@assistant-ui/core\";\nimport type {} from \"@assistant-ui/core/store\";\nimport { type ToolCallMessagePartComponent } from \"@assistant-ui/core/react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport {\n type Field,\n type FieldValues,\n type Path,\n type PathValue,\n type UseFormProps,\n type UseFormReturn,\n get,\n useForm,\n} from \"react-hook-form\";\nimport type { z } from \"zod\";\nimport { formTools } from \"./formTools\";\n\ntype PendingAssistantSubmit = {\n dispatching: boolean;\n event: unknown;\n handlerInvoked: boolean;\n outcome: boolean | undefined;\n resolve: (outcome: boolean) => void;\n reject: (error: unknown) => void;\n};\n\nexport type UseAssistantFormProps<\n TFieldValues extends FieldValues,\n TContext,\n TTransformedValues,\n> = UseFormProps<TFieldValues, TContext, TTransformedValues> & {\n assistant?:\n | {\n tools?:\n | {\n set_form_field?:\n | {\n render?:\n | ToolCallMessagePartComponent<\n z.infer<\n (typeof formTools.set_form_field)[\"parameters\"]\n >,\n unknown\n >\n | undefined;\n }\n | undefined;\n submit_form?:\n | {\n render?:\n | ToolCallMessagePartComponent<\n z.infer<(typeof formTools.submit_form)[\"parameters\"]>,\n unknown\n >\n | undefined;\n }\n | undefined;\n reset_form?:\n | {\n render?:\n | ToolCallMessagePartComponent<\n z.infer<(typeof formTools.reset_form)[\"parameters\"]>,\n unknown\n >\n | undefined;\n }\n | undefined;\n }\n | undefined;\n }\n | undefined;\n};\n\nexport const useAssistantForm = <\n TFieldValues extends FieldValues = FieldValues,\n TContext = any,\n TTransformedValues = TFieldValues,\n>(\n props?: UseAssistantFormProps<TFieldValues, TContext, TTransformedValues>,\n): UseFormReturn<TFieldValues, TContext, TTransformedValues> => {\n const form = useForm<TFieldValues, TContext, TTransformedValues>(props);\n const {\n control,\n getValues,\n setValue,\n reset,\n handleSubmit: baseHandleSubmit,\n formState: { isSubmitting },\n } = form;\n\n const pendingAssistantSubmitRef = useRef<PendingAssistantSubmit | null>(null);\n const settleAssistantSubmit = useCallback((outcome: boolean) => {\n const pending = pendingAssistantSubmitRef.current;\n if (!pending) return;\n\n pendingAssistantSubmitRef.current = null;\n pending.resolve(outcome);\n }, []);\n const rejectAssistantSubmit = useCallback((error: unknown) => {\n const pending = pendingAssistantSubmitRef.current;\n if (!pending) return;\n\n pendingAssistantSubmitRef.current = null;\n pending.reject(error);\n }, []);\n\n const handleSubmit = useCallback<\n UseFormReturn<TFieldValues, TContext, TTransformedValues>[\"handleSubmit\"]\n >(\n (onValid, onInvalid) => {\n const submit = baseHandleSubmit(\n (...args) => {\n const pending = pendingAssistantSubmitRef.current;\n const event = args[1]?.nativeEvent ?? args[1];\n if (pending && pending.event === event) pending.outcome = true;\n return onValid(...args);\n },\n (...args) => {\n const pending = pendingAssistantSubmitRef.current;\n const event = args[1]?.nativeEvent ?? args[1];\n if (pending && pending.event === event) pending.outcome = false;\n return onInvalid?.(...args);\n },\n );\n\n return (async (event) => {\n const pending = pendingAssistantSubmitRef.current;\n const nativeEvent = event?.nativeEvent ?? event;\n const assistantSubmit =\n pending &&\n (pending.event === nativeEvent ||\n (pending.dispatching &&\n pending.event === undefined &&\n nativeEvent !== undefined))\n ? pending\n : null;\n if (assistantSubmit) {\n assistantSubmit.event = nativeEvent;\n assistantSubmit.handlerInvoked = true;\n }\n\n try {\n const result = await submit(event);\n if (\n assistantSubmit &&\n pendingAssistantSubmitRef.current === assistantSubmit\n ) {\n settleAssistantSubmit(assistantSubmit.outcome ?? true);\n }\n return result;\n } catch (error) {\n if (pendingAssistantSubmitRef.current === assistantSubmit) {\n rejectAssistantSubmit(error);\n }\n throw error;\n }\n }) as typeof submit;\n },\n [baseHandleSubmit, rejectAssistantSubmit, settleAssistantSubmit],\n );\n const assistantForm = useMemo<\n UseFormReturn<TFieldValues, TContext, TTransformedValues>\n >(\n () => ({\n ...form,\n handleSubmit,\n get formState() {\n return form.formState;\n },\n }),\n [form, handleSubmit],\n );\n\n const aui = useAui();\n useEffect(() => {\n const value: ModelContext = {\n tools: {\n set_form_field: tool({\n ...formTools.set_form_field,\n parameters: formTools.set_form_field.parameters,\n execute: async (args) => {\n setValue(\n args.name as Path<TFieldValues>,\n args.value as PathValue<TFieldValues, Path<TFieldValues>>,\n );\n\n return { success: true };\n },\n }),\n submit_form: tool({\n ...formTools.submit_form,\n execute: async () => {\n if (isSubmitting || pendingAssistantSubmitRef.current) {\n return {\n success: false,\n message: \"The form is already submitting.\",\n };\n }\n const { _names, _fields } = control;\n let formElement: HTMLFormElement | null = null;\n for (const name of _names.mount) {\n const field: Field | undefined = get(_fields, name);\n if (field?._f) {\n const fieldReference = Array.isArray(field._f.refs)\n ? field._f.refs[0]\n : field._f.ref;\n\n if (fieldReference instanceof HTMLElement) {\n if (\"form\" in fieldReference) {\n formElement =\n fieldReference.form instanceof HTMLFormElement\n ? fieldReference.form\n : null;\n } else {\n formElement = fieldReference.closest(\"form\");\n }\n if (formElement) break;\n }\n }\n }\n\n if (formElement) {\n if (!formElement.noValidate && !formElement.reportValidity()) {\n return {\n success: false,\n message:\n \"The form contains invalid fields and was not submitted.\",\n };\n }\n\n let resolveSubmission: (outcome: boolean) => void = () => {};\n let rejectSubmission: (error: unknown) => void = () => {};\n const submissionResult = new Promise<boolean>(\n (resolve, reject) => {\n resolveSubmission = resolve;\n rejectSubmission = reject;\n },\n );\n const assistantSubmit: PendingAssistantSubmit = {\n dispatching: true,\n event: undefined,\n handlerInvoked: false,\n outcome: undefined,\n resolve: resolveSubmission,\n reject: rejectSubmission,\n };\n pendingAssistantSubmitRef.current = assistantSubmit;\n const onSubmit = (event: SubmitEvent) => {\n if (\n pendingAssistantSubmitRef.current === assistantSubmit &&\n assistantSubmit.event === undefined\n ) {\n assistantSubmit.event = event;\n }\n queueMicrotask(() => {\n if (\n pendingAssistantSubmitRef.current === assistantSubmit &&\n !assistantSubmit.handlerInvoked\n ) {\n settleAssistantSubmit(true);\n }\n });\n };\n\n formElement.addEventListener(\"submit\", onSubmit, { once: true });\n try {\n formElement.requestSubmit();\n } catch (error) {\n settleAssistantSubmit(false);\n throw error;\n } finally {\n assistantSubmit.dispatching = false;\n formElement.removeEventListener(\"submit\", onSubmit);\n }\n\n const dispatched = assistantSubmit.event !== undefined;\n if (!dispatched) {\n settleAssistantSubmit(false);\n }\n\n if (await submissionResult) return { success: true };\n return {\n success: false,\n message: dispatched\n ? \"The form contains invalid fields and was not submitted.\"\n : \"The form did not accept the submission.\",\n };\n }\n\n return {\n success: false,\n message:\n \"Unable retrieve the form element. This is a coding error.\",\n };\n },\n }),\n reset_form: tool({\n ...formTools.reset_form,\n execute: async () => {\n reset();\n return { success: true };\n },\n }),\n },\n };\n\n return aui.modelContext.register({\n getModelContext: () => ({\n ...value,\n system: `Form State:\\n${JSON.stringify(getValues())}`,\n }),\n });\n }, [\n control,\n setValue,\n getValues,\n aui,\n reset,\n isSubmitting,\n rejectAssistantSubmit,\n settleAssistantSubmit,\n ]);\n\n const renderFormFieldTool = props?.assistant?.tools?.set_form_field?.render;\n useEffect(() => {\n if (!renderFormFieldTool) return undefined;\n return aui.tools.setToolUI(\"set_form_field\", renderFormFieldTool);\n }, [aui, renderFormFieldTool]);\n\n const renderSubmitFormTool = props?.assistant?.tools?.submit_form?.render;\n useEffect(() => {\n if (!renderSubmitFormTool) return undefined;\n return aui.tools.setToolUI(\"submit_form\", renderSubmitFormTool);\n }, [aui, renderSubmitFormTool]);\n\n const renderResetFormTool = props?.assistant?.tools?.reset_form?.render;\n useEffect(() => {\n if (!renderResetFormTool) return undefined;\n return aui.tools.setToolUI(\"reset_form\", renderResetFormTool);\n }, [aui, renderResetFormTool]);\n\n return assistantForm;\n};\n"],"mappings":";;;;;;;;AA4EA,MAAa,oBAKX,UAC8D;CAC9D,MAAM,OAAO,QAAoD,KAAK;CACtE,MAAM,EACJ,SACA,WACA,UACA,OACA,cAAc,kBACd,WAAW,EAAE,mBACX;CAEJ,MAAM,4BAA4B,OAAsC,IAAI;CAC5E,MAAM,wBAAwB,aAAa,YAAqB;EAC9D,MAAM,UAAU,0BAA0B;EAC1C,IAAI,CAAC,SAAS;EAEd,0BAA0B,UAAU;EACpC,QAAQ,QAAQ,OAAO;CACzB,GAAG,CAAC,CAAC;CACL,MAAM,wBAAwB,aAAa,UAAmB;EAC5D,MAAM,UAAU,0BAA0B;EAC1C,IAAI,CAAC,SAAS;EAEd,0BAA0B,UAAU;EACpC,QAAQ,OAAO,KAAK;CACtB,GAAG,CAAC,CAAC;CAEL,MAAM,eAAe,aAGlB,SAAS,cAAc;EACtB,MAAM,SAAS,kBACZ,GAAG,SAAS;GACX,MAAM,UAAU,0BAA0B;GAC1C,MAAM,QAAQ,KAAK,EAAE,EAAE,eAAe,KAAK;GAC3C,IAAI,WAAW,QAAQ,UAAU,OAAO,QAAQ,UAAU;GAC1D,OAAO,QAAQ,GAAG,IAAI;EACxB,IACC,GAAG,SAAS;GACX,MAAM,UAAU,0BAA0B;GAC1C,MAAM,QAAQ,KAAK,EAAE,EAAE,eAAe,KAAK;GAC3C,IAAI,WAAW,QAAQ,UAAU,OAAO,QAAQ,UAAU;GAC1D,OAAO,YAAY,GAAG,IAAI;EAC5B,CACF;EAEA,QAAQ,OAAO,UAAU;GACvB,MAAM,UAAU,0BAA0B;GAC1C,MAAM,cAAc,OAAO,eAAe;GAC1C,MAAM,kBACJ,YACC,QAAQ,UAAU,eAChB,QAAQ,eACP,QAAQ,UAAU,KAAA,KAClB,gBAAgB,KAAA,KAChB,UACA;GACN,IAAI,iBAAiB;IACnB,gBAAgB,QAAQ;IACxB,gBAAgB,iBAAiB;GACnC;GAEA,IAAI;IACF,MAAM,SAAS,MAAM,OAAO,KAAK;IACjC,IACE,mBACA,0BAA0B,YAAY,iBAEtC,sBAAsB,gBAAgB,WAAW,IAAI;IAEvD,OAAO;GACT,SAAS,OAAO;IACd,IAAI,0BAA0B,YAAY,iBACxC,sBAAsB,KAAK;IAE7B,MAAM;GACR;EACF;CACF,GACA;EAAC;EAAkB;EAAuB;CAAqB,CACjE;CACA,MAAM,gBAAgB,eAGb;EACL,GAAG;EACH;EACA,IAAI,YAAY;GACd,OAAO,KAAK;EACd;CACF,IACA,CAAC,MAAM,YAAY,CACrB;CAEA,MAAM,MAAM,OAAO;CACnB,gBAAgB;EACd,MAAM,QAAsB,EAC1B,OAAO;GACL,gBAAgB,KAAK;IACnB,GAAG,UAAU;IACb,YAAY,UAAU,eAAe;IACrC,SAAS,OAAO,SAAS;KACvB,SACE,KAAK,MACL,KAAK,KACP;KAEA,OAAO,EAAE,SAAS,KAAK;IACzB;GACF,CAAC;GACD,aAAa,KAAK;IAChB,GAAG,UAAU;IACb,SAAS,YAAY;KACnB,IAAI,gBAAgB,0BAA0B,SAC5C,OAAO;MACL,SAAS;MACT,SAAS;KACX;KAEF,MAAM,EAAE,QAAQ,YAAY;KAC5B,IAAI,cAAsC;KAC1C,KAAK,MAAM,QAAQ,OAAO,OAAO;MAC/B,MAAM,QAA2B,IAAI,SAAS,IAAI;MAClD,IAAI,OAAO,IAAI;OACb,MAAM,iBAAiB,MAAM,QAAQ,MAAM,GAAG,IAAI,IAC9C,MAAM,GAAG,KAAK,KACd,MAAM,GAAG;OAEb,IAAI,0BAA0B,aAAa;QACzC,IAAI,UAAU,gBACZ,cACE,eAAe,gBAAgB,kBAC3B,eAAe,OACf;aAEN,cAAc,eAAe,QAAQ,MAAM;QAE7C,IAAI,aAAa;OACnB;MACF;KACF;KAEA,IAAI,aAAa;MACf,IAAI,CAAC,YAAY,cAAc,CAAC,YAAY,eAAe,GACzD,OAAO;OACL,SAAS;OACT,SACE;MACJ;MAGF,IAAI,0BAAsD,CAAC;MAC3D,IAAI,yBAAmD,CAAC;MACxD,MAAM,mBAAmB,IAAI,SAC1B,SAAS,WAAW;OACnB,oBAAoB;OACpB,mBAAmB;MACrB,CACF;MACA,MAAM,kBAA0C;OAC9C,aAAa;OACb,OAAO,KAAA;OACP,gBAAgB;OAChB,SAAS,KAAA;OACT,SAAS;OACT,QAAQ;MACV;MACA,0BAA0B,UAAU;MACpC,MAAM,YAAY,UAAuB;OACvC,IACE,0BAA0B,YAAY,mBACtC,gBAAgB,UAAU,KAAA,GAE1B,gBAAgB,QAAQ;OAE1B,qBAAqB;QACnB,IACE,0BAA0B,YAAY,mBACtC,CAAC,gBAAgB,gBAEjB,sBAAsB,IAAI;OAE9B,CAAC;MACH;MAEA,YAAY,iBAAiB,UAAU,UAAU,EAAE,MAAM,KAAK,CAAC;MAC/D,IAAI;OACF,YAAY,cAAc;MAC5B,SAAS,OAAO;OACd,sBAAsB,KAAK;OAC3B,MAAM;MACR,UAAU;OACR,gBAAgB,cAAc;OAC9B,YAAY,oBAAoB,UAAU,QAAQ;MACpD;MAEA,MAAM,aAAa,gBAAgB,UAAU,KAAA;MAC7C,IAAI,CAAC,YACH,sBAAsB,KAAK;MAG7B,IAAI,MAAM,kBAAkB,OAAO,EAAE,SAAS,KAAK;MACnD,OAAO;OACL,SAAS;OACT,SAAS,aACL,4DACA;MACN;KACF;KAEA,OAAO;MACL,SAAS;MACT,SACE;KACJ;IACF;GACF,CAAC;GACD,YAAY,KAAK;IACf,GAAG,UAAU;IACb,SAAS,YAAY;KACnB,MAAM;KACN,OAAO,EAAE,SAAS,KAAK;IACzB;GACF,CAAC;EACH,EACF;EAEA,OAAO,IAAI,aAAa,SAAS,EAC/B,wBAAwB;GACtB,GAAG;GACH,QAAQ,gBAAgB,KAAK,UAAU,UAAU,CAAC;EACpD,GACF,CAAC;CACH,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,sBAAsB,OAAO,WAAW,OAAO,gBAAgB;CACrE,gBAAgB;EACd,IAAI,CAAC,qBAAqB,OAAO,KAAA;EACjC,OAAO,IAAI,MAAM,UAAU,kBAAkB,mBAAmB;CAClE,GAAG,CAAC,KAAK,mBAAmB,CAAC;CAE7B,MAAM,uBAAuB,OAAO,WAAW,OAAO,aAAa;CACnE,gBAAgB;EACd,IAAI,CAAC,sBAAsB,OAAO,KAAA;EAClC,OAAO,IAAI,MAAM,UAAU,eAAe,oBAAoB;CAChE,GAAG,CAAC,KAAK,oBAAoB,CAAC;CAE9B,MAAM,sBAAsB,OAAO,WAAW,OAAO,YAAY;CACjE,gBAAgB;EACd,IAAI,CAAC,qBAAqB,OAAO,KAAA;EACjC,OAAO,IAAI,MAAM,UAAU,cAAc,mBAAmB;CAC9D,GAAG,CAAC,KAAK,mBAAmB,CAAC;CAE7B,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"useAssistantForm.js","names":[],"sources":["../src/useAssistantForm.ts"],"sourcesContent":["\"use client\";\n\nimport { type ModelContext, tool } from \"@assistant-ui/core\";\nimport type {} from \"@assistant-ui/core/store\";\nimport { type ToolCallMessagePartComponent } from \"@assistant-ui/core/react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport {\n type Field,\n type FieldValues,\n type Path,\n type PathValue,\n type UseFormProps,\n type UseFormReturn,\n get,\n useForm,\n} from \"react-hook-form\";\nimport type { z } from \"zod\";\nimport { formTools } from \"./formTools\";\n\ntype PendingAssistantSubmit = {\n dispatching: boolean;\n event: unknown;\n handlerInvoked: boolean;\n unmounted: boolean;\n outcome: boolean | undefined;\n resolve: (outcome: boolean) => void;\n reject: (error: unknown) => void;\n};\n\nexport type UseAssistantFormProps<\n TFieldValues extends FieldValues,\n TContext,\n TTransformedValues,\n> = UseFormProps<TFieldValues, TContext, TTransformedValues> & {\n assistant?:\n | {\n tools?:\n | {\n set_form_field?:\n | {\n render?:\n | ToolCallMessagePartComponent<\n z.infer<\n (typeof formTools.set_form_field)[\"parameters\"]\n >,\n unknown\n >\n | undefined;\n }\n | undefined;\n submit_form?:\n | {\n render?:\n | ToolCallMessagePartComponent<\n z.infer<(typeof formTools.submit_form)[\"parameters\"]>,\n unknown\n >\n | undefined;\n }\n | undefined;\n reset_form?:\n | {\n render?:\n | ToolCallMessagePartComponent<\n z.infer<(typeof formTools.reset_form)[\"parameters\"]>,\n unknown\n >\n | undefined;\n }\n | undefined;\n }\n | undefined;\n }\n | undefined;\n};\n\nexport const useAssistantForm = <\n TFieldValues extends FieldValues = FieldValues,\n TContext = any,\n TTransformedValues = TFieldValues,\n>(\n props?: UseAssistantFormProps<TFieldValues, TContext, TTransformedValues>,\n): UseFormReturn<TFieldValues, TContext, TTransformedValues> => {\n const form = useForm<TFieldValues, TContext, TTransformedValues>(props);\n const {\n control,\n getValues,\n setValue,\n reset,\n handleSubmit: baseHandleSubmit,\n formState: { isSubmitting },\n } = form;\n\n const pendingAssistantSubmitRef = useRef<PendingAssistantSubmit | null>(null);\n const settleAssistantSubmit = useCallback((outcome: boolean) => {\n const pending = pendingAssistantSubmitRef.current;\n if (!pending) return;\n\n pendingAssistantSubmitRef.current = null;\n pending.resolve(outcome);\n }, []);\n const rejectAssistantSubmit = useCallback((error: unknown) => {\n const pending = pendingAssistantSubmitRef.current;\n if (!pending) return;\n\n pendingAssistantSubmitRef.current = null;\n pending.reject(error);\n }, []);\n\n useEffect(\n () => () => {\n const pending = pendingAssistantSubmitRef.current;\n if (!pending) return;\n\n pending.unmounted = true;\n pending.resolve(false);\n },\n [],\n );\n\n const handleSubmit = useCallback<\n UseFormReturn<TFieldValues, TContext, TTransformedValues>[\"handleSubmit\"]\n >(\n (onValid, onInvalid) => {\n const submit = baseHandleSubmit(\n (...args) => {\n const pending = pendingAssistantSubmitRef.current;\n const event = args[1]?.nativeEvent ?? args[1];\n if (pending && pending.event === event) {\n pending.outcome = true;\n if (pending.unmounted) return;\n }\n return onValid(...args);\n },\n (...args) => {\n const pending = pendingAssistantSubmitRef.current;\n const event = args[1]?.nativeEvent ?? args[1];\n if (pending && pending.event === event) {\n pending.outcome = false;\n if (pending.unmounted) return;\n }\n return onInvalid?.(...args);\n },\n );\n\n return (async (event) => {\n const pending = pendingAssistantSubmitRef.current;\n const nativeEvent = event?.nativeEvent ?? event;\n const assistantSubmit =\n pending &&\n (pending.event === nativeEvent ||\n (pending.dispatching &&\n pending.event === undefined &&\n nativeEvent !== undefined))\n ? pending\n : null;\n if (assistantSubmit) {\n assistantSubmit.event = nativeEvent;\n assistantSubmit.handlerInvoked = true;\n }\n\n try {\n const result = await submit(event);\n if (\n assistantSubmit &&\n pendingAssistantSubmitRef.current === assistantSubmit\n ) {\n settleAssistantSubmit(assistantSubmit.outcome ?? true);\n }\n return result;\n } catch (error) {\n if (pendingAssistantSubmitRef.current === assistantSubmit) {\n rejectAssistantSubmit(error);\n }\n throw error;\n }\n }) as typeof submit;\n },\n [baseHandleSubmit, rejectAssistantSubmit, settleAssistantSubmit],\n );\n const assistantForm = useMemo<\n UseFormReturn<TFieldValues, TContext, TTransformedValues>\n >(\n () => ({\n ...form,\n handleSubmit,\n get formState() {\n return form.formState;\n },\n }),\n [form, handleSubmit],\n );\n\n const aui = useAui();\n useEffect(() => {\n const value: ModelContext = {\n tools: {\n set_form_field: tool({\n ...formTools.set_form_field,\n parameters: formTools.set_form_field.parameters,\n execute: async (args) => {\n setValue(\n args.name as Path<TFieldValues>,\n args.value as PathValue<TFieldValues, Path<TFieldValues>>,\n );\n\n return { success: true };\n },\n }),\n submit_form: tool({\n ...formTools.submit_form,\n execute: async () => {\n if (isSubmitting || pendingAssistantSubmitRef.current) {\n return {\n success: false,\n message: \"The form is already submitting.\",\n };\n }\n const { _names, _fields } = control;\n let formElement: HTMLFormElement | null = null;\n for (const name of _names.mount) {\n const field: Field | undefined = get(_fields, name);\n if (field?._f) {\n const fieldReference: unknown = Array.isArray(field._f.refs)\n ? field._f.refs[0]\n : field._f.ref;\n\n if (fieldReference instanceof HTMLElement) {\n if (\"form\" in fieldReference) {\n formElement =\n fieldReference.form instanceof HTMLFormElement\n ? fieldReference.form\n : null;\n } else {\n formElement = fieldReference.closest(\"form\");\n }\n if (formElement) break;\n }\n }\n }\n\n if (formElement) {\n if (!formElement.noValidate && !formElement.reportValidity()) {\n return {\n success: false,\n message:\n \"The form contains invalid fields and was not submitted.\",\n };\n }\n\n let resolveSubmission: (outcome: boolean) => void = () => {};\n let rejectSubmission: (error: unknown) => void = () => {};\n const submissionResult = new Promise<boolean>(\n (resolve, reject) => {\n resolveSubmission = resolve;\n rejectSubmission = reject;\n },\n );\n const assistantSubmit: PendingAssistantSubmit = {\n dispatching: true,\n event: undefined,\n handlerInvoked: false,\n unmounted: false,\n outcome: undefined,\n resolve: resolveSubmission,\n reject: rejectSubmission,\n };\n pendingAssistantSubmitRef.current = assistantSubmit;\n const onSubmit = (event: SubmitEvent) => {\n if (\n pendingAssistantSubmitRef.current === assistantSubmit &&\n assistantSubmit.event === undefined\n ) {\n assistantSubmit.event = event;\n }\n queueMicrotask(() => {\n if (\n pendingAssistantSubmitRef.current === assistantSubmit &&\n !assistantSubmit.handlerInvoked\n ) {\n settleAssistantSubmit(true);\n }\n });\n };\n\n formElement.addEventListener(\"submit\", onSubmit, { once: true });\n try {\n formElement.requestSubmit();\n } catch (error) {\n settleAssistantSubmit(false);\n throw error;\n } finally {\n assistantSubmit.dispatching = false;\n formElement.removeEventListener(\"submit\", onSubmit);\n }\n\n const dispatched = assistantSubmit.event !== undefined;\n if (!dispatched) {\n settleAssistantSubmit(false);\n }\n\n if (await submissionResult) return { success: true };\n return {\n success: false,\n message: assistantSubmit.unmounted\n ? \"The form is no longer available.\"\n : dispatched\n ? \"The form contains invalid fields and was not submitted.\"\n : \"The form did not accept the submission.\",\n };\n }\n\n return {\n success: false,\n message:\n \"Unable retrieve the form element. This is a coding error.\",\n };\n },\n }),\n reset_form: tool({\n ...formTools.reset_form,\n execute: async () => {\n reset();\n return { success: true };\n },\n }),\n },\n };\n\n return aui.modelContext.register({\n getModelContext: () => ({\n ...value,\n system: `Form State:\\n${JSON.stringify(getValues())}`,\n }),\n });\n }, [\n control,\n setValue,\n getValues,\n aui,\n reset,\n isSubmitting,\n rejectAssistantSubmit,\n settleAssistantSubmit,\n ]);\n\n const renderFormFieldTool = props?.assistant?.tools?.set_form_field?.render;\n useEffect(() => {\n if (!renderFormFieldTool) return undefined;\n return aui.tools.setToolUI(\"set_form_field\", renderFormFieldTool);\n }, [aui, renderFormFieldTool]);\n\n const renderSubmitFormTool = props?.assistant?.tools?.submit_form?.render;\n useEffect(() => {\n if (!renderSubmitFormTool) return undefined;\n return aui.tools.setToolUI(\"submit_form\", renderSubmitFormTool);\n }, [aui, renderSubmitFormTool]);\n\n const renderResetFormTool = props?.assistant?.tools?.reset_form?.render;\n useEffect(() => {\n if (!renderResetFormTool) return undefined;\n return aui.tools.setToolUI(\"reset_form\", renderResetFormTool);\n }, [aui, renderResetFormTool]);\n\n return assistantForm;\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-hook-form",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.35",
|
|
4
4
|
"description": "React Hook Form integration for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-hook-form",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@assistant-ui/core": "^0.3.
|
|
33
|
-
"@assistant-ui/store": "^0.3.
|
|
34
|
-
"zod": "^4.5
|
|
32
|
+
"@assistant-ui/core": "^0.3.21",
|
|
33
|
+
"@assistant-ui/store": "^0.3.15",
|
|
34
|
+
"zod": "^4.6.5"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@types/react": "*",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
47
|
+
"@assistant-ui/x-buildutils": "0.0.29",
|
|
48
48
|
"@testing-library/react": "^16.3.3",
|
|
49
|
-
"@types/react": "^19.
|
|
50
|
-
"@types/react-dom": "^19.
|
|
49
|
+
"@types/react": "^19.3.0",
|
|
50
|
+
"@types/react-dom": "^19.3.0",
|
|
51
51
|
"jsdom": "^30.0.1",
|
|
52
|
-
"react": "^19.
|
|
53
|
-
"react-hook-form": "^7.
|
|
54
|
-
"vitest": "^5.0.
|
|
52
|
+
"react": "^19.3.0",
|
|
53
|
+
"react-hook-form": "^7.88.0",
|
|
54
|
+
"vitest": "^5.0.1"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "aui-build",
|
|
71
71
|
"test": "vitest run",
|
|
72
|
+
"typecheck": "tsc --noEmit",
|
|
72
73
|
"test:watch": "vitest"
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -392,6 +392,43 @@ describe("useAssistantForm", () => {
|
|
|
392
392
|
});
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
+
it("settles a pending assistant submission when the form unmounts", async () => {
|
|
396
|
+
type FormValues = { name: string };
|
|
397
|
+
let resolveValidation!: (result: ResolverResult<FormValues>) => void;
|
|
398
|
+
const resolver: Resolver<FormValues> = vi.fn(
|
|
399
|
+
() =>
|
|
400
|
+
new Promise<ResolverResult<FormValues>>((resolve) => {
|
|
401
|
+
resolveValidation = resolve;
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
404
|
+
const onValid = vi.fn();
|
|
405
|
+
const Form = () => {
|
|
406
|
+
const form = useAssistantForm<FormValues>({ resolver });
|
|
407
|
+
return (
|
|
408
|
+
<form onSubmit={form.handleSubmit(onValid)}>
|
|
409
|
+
<input {...form.register("name")} />
|
|
410
|
+
</form>
|
|
411
|
+
);
|
|
412
|
+
};
|
|
413
|
+
const { unmount } = render(<Form />);
|
|
414
|
+
|
|
415
|
+
const assistantSubmit = executeSubmitForm();
|
|
416
|
+
await waitFor(() => expect(resolver).toHaveBeenCalledOnce());
|
|
417
|
+
|
|
418
|
+
unmount();
|
|
419
|
+
|
|
420
|
+
await expect(assistantSubmit).resolves.toEqual({
|
|
421
|
+
success: false,
|
|
422
|
+
message: "The form is no longer available.",
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
await act(async () => {
|
|
426
|
+
resolveValidation({ values: { name: "Ada" }, errors: {} });
|
|
427
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
428
|
+
});
|
|
429
|
+
expect(onValid).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
|
|
395
432
|
it("reports when requestSubmit does not dispatch a submit event", async () => {
|
|
396
433
|
const requestSubmit = vi
|
|
397
434
|
.spyOn(HTMLFormElement.prototype, "requestSubmit")
|
package/src/useAssistantForm.ts
CHANGED
|
@@ -22,6 +22,7 @@ type PendingAssistantSubmit = {
|
|
|
22
22
|
dispatching: boolean;
|
|
23
23
|
event: unknown;
|
|
24
24
|
handlerInvoked: boolean;
|
|
25
|
+
unmounted: boolean;
|
|
25
26
|
outcome: boolean | undefined;
|
|
26
27
|
resolve: (outcome: boolean) => void;
|
|
27
28
|
reject: (error: unknown) => void;
|
|
@@ -107,6 +108,17 @@ export const useAssistantForm = <
|
|
|
107
108
|
pending.reject(error);
|
|
108
109
|
}, []);
|
|
109
110
|
|
|
111
|
+
useEffect(
|
|
112
|
+
() => () => {
|
|
113
|
+
const pending = pendingAssistantSubmitRef.current;
|
|
114
|
+
if (!pending) return;
|
|
115
|
+
|
|
116
|
+
pending.unmounted = true;
|
|
117
|
+
pending.resolve(false);
|
|
118
|
+
},
|
|
119
|
+
[],
|
|
120
|
+
);
|
|
121
|
+
|
|
110
122
|
const handleSubmit = useCallback<
|
|
111
123
|
UseFormReturn<TFieldValues, TContext, TTransformedValues>["handleSubmit"]
|
|
112
124
|
>(
|
|
@@ -115,13 +127,19 @@ export const useAssistantForm = <
|
|
|
115
127
|
(...args) => {
|
|
116
128
|
const pending = pendingAssistantSubmitRef.current;
|
|
117
129
|
const event = args[1]?.nativeEvent ?? args[1];
|
|
118
|
-
if (pending && pending.event === event)
|
|
130
|
+
if (pending && pending.event === event) {
|
|
131
|
+
pending.outcome = true;
|
|
132
|
+
if (pending.unmounted) return;
|
|
133
|
+
}
|
|
119
134
|
return onValid(...args);
|
|
120
135
|
},
|
|
121
136
|
(...args) => {
|
|
122
137
|
const pending = pendingAssistantSubmitRef.current;
|
|
123
138
|
const event = args[1]?.nativeEvent ?? args[1];
|
|
124
|
-
if (pending && pending.event === event)
|
|
139
|
+
if (pending && pending.event === event) {
|
|
140
|
+
pending.outcome = false;
|
|
141
|
+
if (pending.unmounted) return;
|
|
142
|
+
}
|
|
125
143
|
return onInvalid?.(...args);
|
|
126
144
|
},
|
|
127
145
|
);
|
|
@@ -204,7 +222,7 @@ export const useAssistantForm = <
|
|
|
204
222
|
for (const name of _names.mount) {
|
|
205
223
|
const field: Field | undefined = get(_fields, name);
|
|
206
224
|
if (field?._f) {
|
|
207
|
-
const fieldReference = Array.isArray(field._f.refs)
|
|
225
|
+
const fieldReference: unknown = Array.isArray(field._f.refs)
|
|
208
226
|
? field._f.refs[0]
|
|
209
227
|
: field._f.ref;
|
|
210
228
|
|
|
@@ -243,6 +261,7 @@ export const useAssistantForm = <
|
|
|
243
261
|
dispatching: true,
|
|
244
262
|
event: undefined,
|
|
245
263
|
handlerInvoked: false,
|
|
264
|
+
unmounted: false,
|
|
246
265
|
outcome: undefined,
|
|
247
266
|
resolve: resolveSubmission,
|
|
248
267
|
reject: rejectSubmission,
|
|
@@ -284,9 +303,11 @@ export const useAssistantForm = <
|
|
|
284
303
|
if (await submissionResult) return { success: true };
|
|
285
304
|
return {
|
|
286
305
|
success: false,
|
|
287
|
-
message:
|
|
288
|
-
? "The form
|
|
289
|
-
:
|
|
306
|
+
message: assistantSubmit.unmounted
|
|
307
|
+
? "The form is no longer available."
|
|
308
|
+
: dispatched
|
|
309
|
+
? "The form contains invalid fields and was not submitted."
|
|
310
|
+
: "The form did not accept the submission.",
|
|
290
311
|
};
|
|
291
312
|
}
|
|
292
313
|
|