react-form-manage 1.0.8 → 1.1.0-beta.2

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 (40) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/providers/Form.d.ts +30 -15
  3. package/dist/providers/Form.js +142 -25
  4. package/dist/stores/formStore.js +3 -0
  5. package/dist/test/testFormInstance/TestCase1_FormWithInstance.d.ts +14 -0
  6. package/dist/test/testFormInstance/TestCase1_FormWithInstance.js +21 -0
  7. package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.d.ts +14 -0
  8. package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.js +23 -0
  9. package/dist/test/testFormInstance/TestCase3_SafeFormMethods.d.ts +14 -0
  10. package/dist/test/testFormInstance/TestCase3_SafeFormMethods.js +69 -0
  11. package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.d.ts +15 -0
  12. package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.js +68 -0
  13. package/dist/test/testFormInstance/TestCase5_UseFormUtils.d.ts +1 -0
  14. package/dist/test/testFormInstance/TestCase5_UseFormUtils.js +70 -0
  15. package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.d.ts +15 -0
  16. package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.js +55 -0
  17. package/dist/test/testFormInstance/TestCase7_SetValueInEffect.d.ts +1 -0
  18. package/dist/test/testFormInstance/TestCase7_SetValueInEffect.js +76 -0
  19. package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.d.ts +1 -0
  20. package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.js +102 -0
  21. package/dist/test/testFormInstance/index.d.ts +14 -0
  22. package/dist/test/testFormInstance/index.js +66 -0
  23. package/dist/test/testSetValue/TestSetValueInEffect.d.ts +3 -0
  24. package/dist/test/testSetValue/TestSetValueInEffect.js +30 -0
  25. package/dist/types/public.d.ts +12 -8
  26. package/package.json +3 -2
  27. package/src/App.tsx +7 -7
  28. package/src/providers/Form.tsx +247 -28
  29. package/src/stores/formStore.ts +3 -1
  30. package/src/test/testFormInstance/TestCase1_FormWithInstance.tsx +116 -0
  31. package/src/test/testFormInstance/TestCase2_AutoGenerateFormName.tsx +142 -0
  32. package/src/test/testFormInstance/TestCase3_SafeFormMethods.tsx +220 -0
  33. package/src/test/testFormInstance/TestCase4_UseFormStatusFlag.tsx +278 -0
  34. package/src/test/testFormInstance/TestCase5_UseFormUtils.tsx +242 -0
  35. package/src/test/testFormInstance/TestCase6_FormWithoutWrapper.tsx +238 -0
  36. package/src/test/testFormInstance/TestCase7_SetValueInEffect.tsx +307 -0
  37. package/src/test/testFormInstance/TestCase8_TypeSafetyImprovements.tsx +390 -0
  38. package/src/test/testFormInstance/index.tsx +155 -0
  39. package/src/test/testSetValue/TestSetValueInEffect.tsx +54 -0
  40. package/src/types/public.ts +15 -8
package/CHANGELOG.md CHANGED
@@ -2,6 +2,83 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.0-beta.2] - 2026-03-03
6
+
7
+ ### Type Improvements
8
+
9
+ - **PublicFormInstance Type Safety**: Enhanced type definitions for better developer experience
10
+ - `name` parameters: Changed from `keyof T & string` to `keyof T | (string & {})` for dynamic field name support
11
+ - Applied to: `setFieldValue`, `getFieldValue`, `setFieldFocus`, `getFieldValues`, `getFieldErrors`
12
+ - `getFieldErrors` return type: Changed from `Record<string, any>` to `Array<{ name: string; errors: FormFieldError[] }>` to match actual implementation
13
+ - `submitAsync` return type: Changed from `Promise<any>` to `Promise<void>` for better type safety
14
+ - **UseFormItemProps**: Updated `name` property type to `keyof T | (string & {})` for consistency
15
+
16
+ ### New Test Cases
17
+
18
+ - **TestCase7: Set Value in useEffect with Timeout**: Test async behavior with setTimeout
19
+ - Verify setValue works correctly when called from async callbacks
20
+ - Test cleanup behavior when component unmounts before timeout
21
+ - Configurable delay with remount functionality
22
+ - Real-time event logging
23
+ - **TestCase8: Type Safety Improvements**: Comprehensive type validation test
24
+ - Test typed vs dynamic field names
25
+ - Validate getFieldErrors return type structure
26
+ - Test submitAsync Promise<void> return type
27
+ - Table display for field errors
28
+ - Comprehensive validation scenarios
29
+
30
+ ### Technical Details
31
+
32
+ - All form instance methods now consistently support dynamic field names
33
+ - Type definitions match actual runtime behavior
34
+ - Enhanced IntelliSense and autocomplete for form methods
35
+ - 8 comprehensive test cases covering all Form Instance features
36
+
37
+ ## [1.1.0-beta.1] - 2026-03-03
38
+
39
+ ### Breaking Changes
40
+
41
+ - **Form Component Props**: Form component can now accept `form` instance prop instead of only `formName`
42
+ - New usage: `<Form form={formInstance}>` alongside existing `<Form formName="myForm">`
43
+ - Form instance can be created with `Form.useForm()` before Form component mounts
44
+ - Methods are bound to external instance when provided
45
+ - **useForm Return Type**: `useForm()` now returns tuple `[instance, isReady]` instead of `[instance]`
46
+ - `isReady` flag indicates whether Form component is mounted and ready
47
+ - Can be used to conditionally enable/disable features based on Form state
48
+ - **useWatch Type**: `name` parameter now accepts `keyof T | (string & {})` for dynamic field names
49
+
50
+ ### New Features
51
+
52
+ - **Auto-generate FormName**: Form.useForm() automatically generates UUID if no parameter provided
53
+ - No need to manually manage formName for simple cases
54
+ - Multiple forms can coexist without name conflicts
55
+ - **Safe Form Methods**: Form instance methods can be called safely before Form mounts
56
+ - Proxy instance automatically finds real instance when Form mounts
57
+ - Warnings logged instead of errors when methods called before mount
58
+ - **useFormUtils Hook**: New hook for external form manipulation
59
+ - `getFormInstance(formName)`: Get form instance from store
60
+ - `setFieldValue(formName, name, value, options)`: Set field value externally
61
+ - `setFieldValues(formName, values, options)`: Set multiple values externally
62
+ - Useful for global control panels, external triggers, cross-component manipulation
63
+ - **Headless Forms**: Form instance can work independently as state manager
64
+ - Methods work without Form component wrapper
65
+ - Useful for custom implementations, multi-step forms, form wizards
66
+ - **Test Suite**: Created 6 comprehensive test cases for Form Instance features
67
+ - TestCase1: Form with Instance Prop
68
+ - TestCase2: Auto-generate FormName
69
+ - TestCase3: Safe Form Methods
70
+ - TestCase4: useForm Status Flag
71
+ - TestCase5: useFormUtils Hook
72
+ - TestCase6: Form Without Wrapper
73
+
74
+ ### Technical Details
75
+
76
+ - Form component uses internal instance or creates new one based on prop type
77
+ - Proxy instance pattern for safe method calls before mount
78
+ - UUID generation using `uuid/v4` for auto-generated form names
79
+ - FormInstance methods rebind on mount to support external instance pattern
80
+ - Type overloads for Form props: `FormPropsWithInstance` and `FormPropsWithoutInstance`
81
+
5
82
  ## [1.0.8] - 2026-02-16
6
83
 
7
84
  ### Features
@@ -1,8 +1,8 @@
1
- import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
1
+ import type { ComponentType, FormHTMLAttributes, ReactElement, ReactNode } from "react";
2
2
  import FormItem from "../components/Form/FormItem";
3
3
  import { OnChangeOptions } from "../hooks/useFormItemControl";
4
4
  import type { PublicFormInstance, UseFormItemStateWatchReturn } from "../types/public";
5
- export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule } from "../types/public";
5
+ export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule, } from "../types/public";
6
6
  export declare const FormContext: import("react").Context<any>;
7
7
  export interface SetFieldValueOptions extends OnChangeOptions {
8
8
  deepTrigger?: boolean;
@@ -10,7 +10,6 @@ export interface SetFieldValueOptions extends OnChangeOptions {
10
10
  export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
11
11
  collectHiddenFields?: boolean;
12
12
  children: ReactNode;
13
- formName: string;
14
13
  initialValues?: T;
15
14
  onFinish?: (values: T, allValues?: any) => void | Promise<void>;
16
15
  onReject?: (errorFields: any[]) => void | Promise<void>;
@@ -21,20 +20,19 @@ export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElem
21
20
  }) => void | Promise<void>;
22
21
  FormElement?: ComponentType<any>;
23
22
  }
24
- declare function Form<T = any>({ children, formName, initialValues, onFinish, onReject, onFinally, FormElement, collectHiddenFields: formCollectHiddenFields, ...props }: FormProps<T>): import("react/jsx-runtime").JSX.Element;
25
- declare namespace Form {
26
- var useForm: typeof import("./Form").useForm;
27
- var Item: typeof FormItem;
28
- var useWatch: typeof import("./Form").useWatch;
29
- var useWatchNormalized: typeof import("./Form").useWatchNormalized;
30
- var useSubmitDataWatch: typeof import("./Form").useSubmitDataWatch;
31
- var useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
32
- var useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
23
+ export interface FormPropsWithInstance<T = any> {
24
+ form: PublicFormInstance<T>;
25
+ }
26
+ export interface FormPropsWithoutInstance {
27
+ formName: string;
28
+ }
29
+ interface FormDeclearation {
30
+ <T = any>(props: FormProps<T> & FormPropsWithInstance<T>): ReactElement;
31
+ <T = any>(props: FormProps<T> & FormPropsWithoutInstance): ReactElement;
33
32
  }
34
- export default Form;
35
33
  export declare function useFormContext(): any;
36
- export declare function useForm<T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>): PublicFormInstance<T>[];
37
- export declare function useWatch<T = any>(name: keyof T & string, formNameOrFormInstance?: string | PublicFormInstance<T>): T[keyof T] | undefined;
34
+ export declare function useForm<T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>): readonly [PublicFormInstance<T>, boolean];
35
+ export declare function useWatch<T = any>(name: keyof T | (string & {}), formNameOrFormInstance?: string | PublicFormInstance<T>): T[keyof T] | undefined;
38
36
  export declare function useWatchNormalized<T, TFn extends (v: any) => any>({ name, normalizeFn, formNameOrFormInstance, }: {
39
37
  name: keyof T & string;
40
38
  normalizeFn: TFn;
@@ -47,3 +45,20 @@ export declare function useSubmitDataWatch<T = any>({ formNameOrFormInstance, tr
47
45
  }): T | undefined;
48
46
  export declare const useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
49
47
  export declare const useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
48
+ export declare const useFormUtils: () => {
49
+ getFormInstance: (formNameOrFormInstance?: string | PublicFormInstance) => any;
50
+ setFieldValue: (formNameOrFormInstance: string | PublicFormInstance, name: string, value: any, options?: SetFieldValueOptions) => void;
51
+ setFieldValues: (formNameOrFormInstance: string | PublicFormInstance, values: any, options?: SetFieldValueOptions) => void;
52
+ };
53
+ interface FormExposeType extends FormDeclearation {
54
+ useForm: typeof useForm;
55
+ Item: typeof FormItem;
56
+ useWatch: typeof useWatch;
57
+ useWatchNormalized: typeof useWatchNormalized;
58
+ useSubmitDataWatch: typeof useSubmitDataWatch;
59
+ useFormStateWatch: typeof useFormStateWatch;
60
+ useFormItemStateWatch: typeof useFormItemStateWatch;
61
+ useFormUtils: typeof useFormUtils;
62
+ }
63
+ declare const Form: FormExposeType;
64
+ export default Form;
@@ -3,13 +3,18 @@ import { cloneDeep, filter, get, isArray, isEqual, isNil, isPlainObject, last, s
3
3
  import { useTask } from "minh-custom-hooks-release";
4
4
  import { createContext, useContext, useEffect, useMemo, useState } from "react";
5
5
  import { flushSync } from "react-dom";
6
+ import { v4 } from "uuid";
6
7
  import { useShallow } from "zustand/react/shallow";
7
8
  import FormItem from "../components/Form/FormItem";
8
9
  import { SUBMIT_STATE } from "../constants/form";
9
10
  import { useFormStore } from "../stores/formStore";
10
11
  import { getAllNoneObjStringPath, getAllPathsIncludingContainers, getAllPathsStopAtArray } from "../utils/obj.util";
11
12
  const FormContext = createContext(null);
12
- function Form({ children, formName, initialValues, onFinish, onReject, onFinally, FormElement, collectHiddenFields: formCollectHiddenFields = true, ...props }) {
13
+ const InternalForm = function Form({ children, formName: externalFormName, form: externalFormInstance, initialValues, onFinish, onReject, onFinally, FormElement, collectHiddenFields: formCollectHiddenFields = true, ...props }) {
14
+ const formName = useMemo(() => {
15
+ var _a;
16
+ return (_a = externalFormInstance == null ? void 0 : externalFormInstance.formName) != null ? _a : externalFormName;
17
+ }, [externalFormInstance, externalFormName]);
13
18
  const {
14
19
  // appInitValue,
15
20
  getFormItemValue,
@@ -386,18 +391,20 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
386
391
  isInitied: true,
387
392
  submitState: "idle"
388
393
  });
389
- setFormInstance({
390
- formName,
391
- resetFields,
392
- submit: runSubmit,
393
- submitAsync: runSubmitAsync,
394
- setFieldValue,
395
- setFieldValues,
396
- getFieldValue,
397
- getFieldValues,
398
- setFieldFocus,
399
- getFieldErrors
400
- });
394
+ const formInstance = externalFormInstance != null ? externalFormInstance : {};
395
+ if (externalFormInstance) {
396
+ externalFormInstance.setFieldValue = setFieldValue;
397
+ externalFormInstance.setFieldValues = setFieldValues;
398
+ externalFormInstance.getFieldValue = getFieldValue;
399
+ externalFormInstance.getFieldValues = getFieldValues;
400
+ externalFormInstance.setFieldFocus = setFieldFocus;
401
+ externalFormInstance.getFieldErrors = getFieldErrors;
402
+ externalFormInstance.submit = runSubmit;
403
+ externalFormInstance.submitAsync = runSubmitAsync;
404
+ externalFormInstance.resetFields = resetFields;
405
+ externalFormInstance.formName = formName;
406
+ }
407
+ setFormInstance(formInstance);
401
408
  return () => {
402
409
  revokeFormInstance({ formName });
403
410
  clearFormInitialValues(formName);
@@ -416,7 +423,7 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
416
423
  e.stopPropagation();
417
424
  runSubmit(void 0);
418
425
  }, ...props, children }) });
419
- }
426
+ };
420
427
  function useFormContext() {
421
428
  const c = useContext(FormContext);
422
429
  if (!c)
@@ -425,11 +432,85 @@ function useFormContext() {
425
432
  }
426
433
  function useForm(formNameOrFormInstance) {
427
434
  const formContext = useContext(FormContext);
428
- const targetFormName = isNil(formNameOrFormInstance) ? formContext == null ? void 0 : formContext.formName : typeof formNameOrFormInstance === "object" && formNameOrFormInstance !== null ? formNameOrFormInstance.formName : formNameOrFormInstance;
435
+ const [targetFormName] = useState(() => {
436
+ return isNil(formNameOrFormInstance) ? formContext == null ? void 0 : formContext.formName : typeof formNameOrFormInstance === "object" && formNameOrFormInstance !== null ? formNameOrFormInstance.formName : formNameOrFormInstance;
437
+ });
438
+ const [initFormName] = useState(() => targetFormName != null ? targetFormName : v4());
429
439
  const formInstance = useFormStore((state) => {
430
- return state.formInstances.find((i) => i.formName === targetFormName);
440
+ return state.formInstances.find((i) => i.formName === initFormName);
441
+ });
442
+ const getFormInstance = useFormStore((state) => {
443
+ return state.getFormInstance;
431
444
  });
432
- return [formInstance];
445
+ return [
446
+ formInstance != null ? formInstance : {
447
+ formName: initFormName,
448
+ setFieldValue(name, value, options) {
449
+ const formInstanceFind = getFormInstance(initFormName);
450
+ if (formInstanceFind) {
451
+ formInstanceFind.setFieldValue(name, value, options);
452
+ return;
453
+ }
454
+ },
455
+ setFieldValues(values, options) {
456
+ const formInstanceFind = getFormInstance(initFormName);
457
+ if (formInstanceFind) {
458
+ formInstanceFind.setFieldValues(values, options);
459
+ return;
460
+ }
461
+ },
462
+ getFieldValue(name) {
463
+ const formInstanceFind = getFormInstance(initFormName);
464
+ if (formInstanceFind) {
465
+ return formInstanceFind.getFieldValue(name);
466
+ }
467
+ return void 0;
468
+ },
469
+ getFieldValues(names) {
470
+ const formInstanceFind = getFormInstance(initFormName);
471
+ if (formInstanceFind) {
472
+ return formInstanceFind.getFieldValues(names);
473
+ }
474
+ return names.map((name) => ({ name, value: void 0 }));
475
+ },
476
+ setFieldFocus(name) {
477
+ const formInstanceFind = getFormInstance(initFormName);
478
+ if (formInstanceFind) {
479
+ formInstanceFind.setFieldFocus(name);
480
+ return;
481
+ }
482
+ },
483
+ getFieldErrors(names) {
484
+ const formInstanceFind = getFormInstance(initFormName);
485
+ if (formInstanceFind) {
486
+ return formInstanceFind.getFieldErrors(names);
487
+ }
488
+ return names.map((name) => ({ name, errors: [] }));
489
+ },
490
+ submit() {
491
+ const formInstanceFind = getFormInstance(initFormName);
492
+ if (formInstanceFind) {
493
+ formInstanceFind.submit();
494
+ return;
495
+ }
496
+ },
497
+ submitAsync() {
498
+ const formInstanceFind = getFormInstance(initFormName);
499
+ if (formInstanceFind) {
500
+ return formInstanceFind.submitAsync();
501
+ }
502
+ return Promise.resolve();
503
+ },
504
+ resetFields(resetOptions) {
505
+ const formInstanceFind = getFormInstance(initFormName);
506
+ if (formInstanceFind) {
507
+ formInstanceFind.resetFields(resetOptions);
508
+ return;
509
+ }
510
+ }
511
+ },
512
+ Boolean(formInstance)
513
+ ];
433
514
  }
434
515
  function useWatch(name, formNameOrFormInstance) {
435
516
  const [formInstance] = useForm(formNameOrFormInstance);
@@ -482,20 +563,56 @@ const useFormItemStateWatch = (nameOrFormItemId, formNameOrFormInstance) => {
482
563
  errors: (listener == null ? void 0 : listener.internalErrors) || []
483
564
  };
484
565
  };
485
- Form.useForm = useForm;
486
- Form.Item = FormItem;
487
- Form.useWatch = useWatch;
488
- Form.useWatchNormalized = useWatchNormalized;
489
- Form.useSubmitDataWatch = useSubmitDataWatch;
490
- Form.useFormStateWatch = useFormStateWatch;
491
- Form.useFormItemStateWatch = useFormItemStateWatch;
566
+ const useFormUtils = () => {
567
+ const { getFormInstance } = useFormStore(useShallow((state) => ({
568
+ getFormInstance: state.getFormInstance
569
+ })));
570
+ const handleGetFormInstace = (formNameOrFormInstance) => {
571
+ const targetFormName = isNil(formNameOrFormInstance) ? void 0 : typeof formNameOrFormInstance === "object" && formNameOrFormInstance !== null ? formNameOrFormInstance.formName : formNameOrFormInstance;
572
+ if (!targetFormName) {
573
+ throw new Error("Form name is required to get form instance");
574
+ }
575
+ const formInstance = getFormInstance(targetFormName);
576
+ return formInstance;
577
+ };
578
+ const setFieldValue = (formNameOrFormInstance, name, value, options) => {
579
+ const formInstance = handleGetFormInstace(formNameOrFormInstance);
580
+ if (!formInstance) {
581
+ return;
582
+ }
583
+ formInstance.setFieldValue(name, value, options);
584
+ };
585
+ const setFieldValues = (formNameOrFormInstance, values, options) => {
586
+ const formInstance = handleGetFormInstace(formNameOrFormInstance);
587
+ if (!formInstance) {
588
+ return;
589
+ }
590
+ formInstance.setFieldValues(values, options);
591
+ };
592
+ return {
593
+ getFormInstance: handleGetFormInstace,
594
+ setFieldValue,
595
+ setFieldValues
596
+ };
597
+ };
598
+ const Form2 = InternalForm;
599
+ Form2.useForm = useForm;
600
+ Form2.Item = FormItem;
601
+ Form2.useWatch = useWatch;
602
+ Form2.useWatchNormalized = useWatchNormalized;
603
+ Form2.useSubmitDataWatch = useSubmitDataWatch;
604
+ Form2.useFormStateWatch = useFormStateWatch;
605
+ Form2.useFormItemStateWatch = useFormItemStateWatch;
606
+ Form2.useFormUtils = useFormUtils;
607
+ var stdin_default = Form2;
492
608
  export {
493
609
  FormContext,
494
- Form as default,
610
+ stdin_default as default,
495
611
  useForm,
496
612
  useFormContext,
497
613
  useFormItemStateWatch,
498
614
  useFormStateWatch,
615
+ useFormUtils,
499
616
  useSubmitDataWatch,
500
617
  useWatch,
501
618
  useWatchNormalized
@@ -130,6 +130,9 @@ const createFormStoreSlice = (storeSet, storeGet, api) => ({
130
130
  set(oldValues, `${formName}.${name}`, value);
131
131
  }));
132
132
  },
133
+ getFormInstance(formName) {
134
+ return storeGet().formInstances.find((f) => f.formName === formName);
135
+ },
133
136
  setFormInstance({ formName, resetFields, submit, submitAsync, setFieldValue, setFieldValues, getFieldValue, getFieldValues, getFieldErrors, setFieldFocus }) {
134
137
  return storeSet(produce((state) => {
135
138
  const storeListeners = state.formInstances;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * TestCase1: Form with Instance Prop
3
+ *
4
+ * Mô tả:
5
+ * - Test breaking change: Form component nhận prop "form" thay vì chỉ có "formName"
6
+ * - Form instance được tạo trước bằng Form.useForm()
7
+ * - Các methods được bind vào instance trước khi Form mount
8
+ *
9
+ * Expected Behavior:
10
+ * - Có thể gọi form.setFieldValue() trước khi Form render
11
+ * - Form sử dụng formName từ instance
12
+ * - Instance được persist và rebind methods khi Form mount
13
+ */
14
+ export default function TestCase1_FormWithInstance(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Space, Typography } from "antd";
3
+ import Form from "../../providers/Form";
4
+ const { Title, Paragraph, Text } = Typography;
5
+ function TestCase1_FormWithInstance() {
6
+ const [form] = Form.useForm();
7
+ const handleSetValueBeforeMount = () => {
8
+ form.setFieldValue("username", "admin");
9
+ form.setFieldValue("email", "admin@example.com");
10
+ };
11
+ const handleGetValues = () => {
12
+ };
13
+ const handleReset = () => {
14
+ form.resetFields();
15
+ };
16
+ return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase1: Form with Instance Prop" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Breaking Change:" }), " Form component c\xF3 th\u1EC3 nh\u1EADn prop", " ", _jsx(Text, { code: true, children: "form" }), " thay v\xEC ", _jsx(Text, { code: true, children: "formName" })] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Form instance \u0111\u01B0\u1EE3c t\u1EA1o b\u1EB1ng Form.useForm() v\xE0 truy\u1EC1n v\xE0o Form component. Instance n\xE0y c\xF3 th\u1EC3 \u0111\u01B0\u1EE3c s\u1EED d\u1EE5ng tr\u01B0\u1EDBc khi Form mount." }) }), _jsxs(Space, { style: { marginBottom: 24 }, children: [_jsx(Button, { onClick: handleSetValueBeforeMount, type: "primary", children: "Set Values Before Mount" }), _jsx(Button, { onClick: handleGetValues, children: "Get Current Values (Check Console)" }), _jsx(Button, { onClick: handleReset, danger: true, children: "Reset Form" })] }), _jsxs(Form, { form, initialValues: { username: "", email: "" }, onFinish: (values) => {
17
+ }, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx("input", { type: "text", placeholder: "Username", style: { padding: 8, width: 300 } }) }), _jsx(Form.Item, { name: "email", label: "Email", children: _jsx("input", { type: "email", placeholder: "Email", style: { padding: 8, width: 300 } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit" })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Set Values Before Mount" \u2192 Values \u0111\u01B0\u1EE3c set v\xE0 log ra console' }), _jsx("li", { children: '\u2705 Click "Get Current Values" \u2192 Values hi\u1EC7n t\u1EA1i \u0111\u01B0\u1EE3c log ra' }), _jsx("li", { children: "\u2705 Form render v\u1EDBi values \u0111\xE3 set" }), _jsx("li", { children: '\u2705 Click "Reset Form" \u2192 Form reset v\u1EC1 initialValues' }), _jsx("li", { children: "\u2705 Submit form \u2192 Console log submitted values" })] })] })] });
18
+ }
19
+ export {
20
+ TestCase1_FormWithInstance as default
21
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * TestCase2: Auto-generate FormName
3
+ *
4
+ * Mô tả:
5
+ * - Test feature: Form.useForm() tự động generate UUID nếu không truyền tham số
6
+ * - Mỗi lần gọi Form.useForm() sẽ tạo ra một formName unique
7
+ * - Form component có thể hoạt động mà không cần truyền formName prop
8
+ *
9
+ * Expected Behavior:
10
+ * - Form hoạt động bình thường với auto-generated formName
11
+ * - FormName là UUID duy nhất
12
+ * - Multiple forms có thể coexist với auto-generated names
13
+ */
14
+ export default function TestCase2_AutoGenerateFormName(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Space, Typography } from "antd";
3
+ import { useState } from "react";
4
+ import Form from "../../providers/Form";
5
+ const { Title, Paragraph, Text } = Typography;
6
+ function TestCase2_AutoGenerateFormName() {
7
+ const [form1] = Form.useForm();
8
+ const [form2] = Form.useForm();
9
+ const [showForm1, setShowForm1] = useState(true);
10
+ const [showForm2, setShowForm2] = useState(true);
11
+ const handleLogFormNames = () => {
12
+ };
13
+ const handleSetValues = () => {
14
+ form1.setFieldValue("name", "John Doe");
15
+ form2.setFieldValue("email", "john@example.com");
16
+ };
17
+ return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase2: Auto-generate FormName" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "New Feature:" }), " Form.useForm() t\u1EF1 \u0111\u1ED9ng t\u1EA1o UUID l\xE0m formName n\u1EBFu kh\xF4ng c\xF3 tham s\u1ED1"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "M\u1ED7i form instance c\xF3 m\u1ED9t UUID duy nh\u1EA5t, kh\xF4ng c\u1EA7n ph\u1EA3i manually qu\u1EA3n l\xFD formName. Multiple forms c\xF3 th\u1EC3 coexist m\xE0 kh\xF4ng lo b\u1ECB conflict." }) }), _jsxs(Space, { style: { marginBottom: 24 }, children: [_jsx(Button, { onClick: handleLogFormNames, type: "primary", children: "Log Form Names (Check Console)" }), _jsx(Button, { onClick: handleSetValues, children: "Set Values for Both Forms" }), _jsxs(Button, { onClick: () => setShowForm1(!showForm1), children: ["Toggle Form 1 ", showForm1 ? "Hide" : "Show"] }), _jsxs(Button, { onClick: () => setShowForm2(!showForm2), children: ["Toggle Form 2 ", showForm2 ? "Hide" : "Show"] })] }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [showForm1 && _jsxs("div", { style: { flex: 1, border: "1px solid #d9d9d9", padding: 16 }, children: [_jsx(Title, { level: 5, children: "Form 1 (Auto-generated Name)" }), _jsxs(Text, { type: "secondary", children: ["FormName: ", form1.formName] }), _jsxs(Form, { form: form1, initialValues: { name: "" }, onFinish: (values) => {
18
+ }, children: [_jsx(Form.Item, { name: "name", label: "Name", children: _jsx("input", { type: "text", placeholder: "Your name", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form 1" })] })] }), showForm2 && _jsxs("div", { style: { flex: 1, border: "1px solid #d9d9d9", padding: 16 }, children: [_jsx(Title, { level: 5, children: "Form 2 (Auto-generated Name)" }), _jsxs(Text, { type: "secondary", children: ["FormName: ", form2.formName] }), _jsxs(Form, { form: form2, initialValues: { email: "" }, onFinish: (values) => {
19
+ }, children: [_jsx(Form.Item, { name: "email", label: "Email", children: _jsx("input", { type: "email", placeholder: "Your email", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form 2" })] })] })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Log Form Names" \u2192 Console show 2 different UUIDs' }), _jsx("li", { children: '\u2705 Click "Set Values for Both Forms" \u2192 Both forms update correctly' }), _jsx("li", { children: "\u2705 Toggle forms \u2192 Forms can unmount/remount without losing instance" }), _jsx("li", { children: "\u2705 FormName displays unique UUIDs under each form title" }), _jsx("li", { children: "\u2705 Both forms work independently without conflicts" }), _jsx("li", { children: "\u2705 Submit each form \u2192 Console logs correct values" })] })] })] });
20
+ }
21
+ export {
22
+ TestCase2_AutoGenerateFormName as default
23
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * TestCase3: Safe Form Methods
3
+ *
4
+ * Mô tả:
5
+ * - Test feature: Form instance methods có thể gọi an toàn trước khi Form mount
6
+ * - Proxy instance tự động tìm real instance khi Form mount
7
+ * - Warning được log nếu method được gọi khi Form chưa mount
8
+ *
9
+ * Expected Behavior:
10
+ * - Gọi methods trước khi mount → Log warning
11
+ * - Sau khi mount → Methods hoạt động bình thường
12
+ * - Unmount form → Methods lại log warning
13
+ */
14
+ export default function TestCase3_SafeFormMethods(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Space, Typography } from "antd";
3
+ import { useState } from "react";
4
+ import Form from "../../providers/Form";
5
+ const { Title, Paragraph, Text } = Typography;
6
+ function TestCase3_SafeFormMethods() {
7
+ const [form] = Form.useForm();
8
+ const [formMounted, setFormMounted] = useState(false);
9
+ const [logs, setLogs] = useState([]);
10
+ const addLog = (message) => {
11
+ setLogs((prev) => [
12
+ ...prev,
13
+ `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${message}`
14
+ ]);
15
+ };
16
+ const handleSetValueBeforeMount = () => {
17
+ addLog("\u{1F534} Calling setFieldValue BEFORE Form mount");
18
+ form.setFieldValue("username", "admin");
19
+ addLog("Check console for warning");
20
+ };
21
+ const handleSetValueAfterMount = () => {
22
+ addLog("\u{1F7E2} Calling setFieldValue AFTER Form mount");
23
+ form.setFieldValue("username", "user123");
24
+ form.setFieldValue("password", "secret");
25
+ addLog("Values should be set successfully");
26
+ };
27
+ const handleGetValue = () => {
28
+ const username = form.getFieldValue("username");
29
+ const password = form.getFieldValue("password");
30
+ addLog(`Get values: username=${username}, password=${password}`);
31
+ };
32
+ const handleSubmit = () => {
33
+ addLog("\u{1F535} Calling form.submit()");
34
+ form.submit();
35
+ };
36
+ const handleMountForm = () => {
37
+ setFormMounted(true);
38
+ addLog("\u2705 Form mounted");
39
+ };
40
+ const handleUnmountForm = () => {
41
+ setFormMounted(false);
42
+ addLog("\u274C Form unmounted");
43
+ };
44
+ const clearLogs = () => {
45
+ setLogs([]);
46
+ };
47
+ return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase3: Safe Form Methods" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "New Feature:" }), " Form methods c\xF3 th\u1EC3 \u0111\u01B0\u1EE3c g\u1ECDi an to\xE0n b\u1EA5t c\u1EE9 l\xFAc n\xE0o"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "Proxy instance t\u1EF1 \u0111\u1ED9ng proxy c\xE1c method calls. N\u1EBFu Form ch\u01B0a mount, s\u1EBD log warning thay v\xEC throw error. Khi Form mount, methods ho\u1EA1t \u0111\u1ED9ng b\xECnh th\u01B0\u1EDDng." }) }), _jsxs(Space, { style: { marginBottom: 16 }, wrap: true, children: [_jsxs(Button, { onClick: handleSetValueBeforeMount, danger: !formMounted, children: ["Set Value ", !formMounted && "(Form Not Mounted)"] }), _jsx(Button, { onClick: handleSetValueAfterMount, type: "primary", disabled: !formMounted, children: "Set Value After Mount" }), _jsx(Button, { onClick: handleGetValue, children: "Get Values" }), _jsx(Button, { onClick: handleSubmit, type: "dashed", children: "Submit Form" })] }), _jsxs(Space, { style: { marginBottom: 24 }, children: [!formMounted ? _jsx(Button, { onClick: handleMountForm, type: "primary", children: "Mount Form" }) : _jsx(Button, { onClick: handleUnmountForm, danger: true, children: "Unmount Form" }), _jsx(Button, { onClick: clearLogs, children: "Clear Logs" })] }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx(Title, { level: 5, children: "Action Logs:" }), _jsx("div", { style: {
48
+ border: "1px solid #d9d9d9",
49
+ padding: 16,
50
+ height: 300,
51
+ overflow: "auto",
52
+ background: "#fafafa",
53
+ fontFamily: "monospace",
54
+ fontSize: 12
55
+ }, children: logs.length === 0 ? _jsx(Text, { type: "secondary", children: "No logs yet..." }) : logs.map((log, index) => _jsx("div", { style: { marginBottom: 4 }, children: log }, index)) })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsxs(Title, { level: 5, children: ["Form (Status: ", formMounted ? "Mounted" : "Unmounted", "):"] }), formMounted ? _jsx("div", { style: { border: "1px solid #52c41a", padding: 16 }, children: _jsxs(Form, { form, initialValues: { username: "", password: "" }, onFinish: (values) => {
56
+ addLog(`\u2705 Form submitted: ${JSON.stringify(values)}`);
57
+ }, children: [_jsx(Form.Item, { name: "username", label: "Username", children: _jsx("input", { type: "text", placeholder: "Username", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "password", label: "Password", children: _jsx("input", { type: "password", placeholder: "Password", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit" })] }) }) : _jsx("div", { style: {
58
+ border: "1px dashed #d9d9d9",
59
+ padding: 16,
60
+ height: 200,
61
+ display: "flex",
62
+ alignItems: "center",
63
+ justifyContent: "center",
64
+ background: "#fafafa"
65
+ }, children: _jsx(Text, { type: "secondary", children: "Form is not mounted" }) })] })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Set Value (Form Not Mounted)" \u2192 Console shows warning' }), _jsx("li", { children: '\u2705 Click "Mount Form" \u2192 Form appears' }), _jsx("li", { children: '\u2705 Click "Set Value After Mount" \u2192 Form values update without warning' }), _jsx("li", { children: '\u2705 Click "Get Values" \u2192 Shows current values in logs' }), _jsx("li", { children: '\u2705 Click "Unmount Form" \u2192 Form disappears' }), _jsx("li", { children: '\u2705 Click "Set Value (Form Not Mounted)" again \u2192 Warning appears again' }), _jsx("li", { children: "\u26A0\uFE0F Check browser console for detailed warnings" })] })] })] });
66
+ }
67
+ export {
68
+ TestCase3_SafeFormMethods as default
69
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * TestCase4: useForm Returns Status Flag
3
+ *
4
+ * Mô tả:
5
+ * - Test breaking change: useForm() trả về tuple [instance, isReady]
6
+ * - isReady = true khi Form đã mount và bind methods
7
+ * - isReady = false khi Form chưa mount hoặc đã unmount
8
+ *
9
+ * Expected Behavior:
10
+ * - Trước khi mount: isReady = false
11
+ * - Sau khi mount: isReady = true
12
+ * - Sau khi unmount: isReady = false
13
+ * - Có thể dùng isReady để conditionally render UI hoặc enable/disable buttons
14
+ */
15
+ export default function TestCase4_UseFormStatusFlag(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge, Button, Space, Tag, Typography } from "antd";
3
+ import { useEffect, useState } from "react";
4
+ import Form from "../../providers/Form";
5
+ const { Title, Paragraph, Text } = Typography;
6
+ function TestCase4_UseFormStatusFlag() {
7
+ const [form, isFormReady] = Form.useForm();
8
+ const [formMounted, setFormMounted] = useState(false);
9
+ const [statusHistory, setStatusHistory] = useState([]);
10
+ useEffect(() => {
11
+ setStatusHistory((prev) => [
12
+ ...prev,
13
+ {
14
+ time: (/* @__PURE__ */ new Date()).toLocaleTimeString(),
15
+ status: isFormReady,
16
+ event: isFormReady ? "Form Ready" : "Form Not Ready"
17
+ }
18
+ ]);
19
+ }, [isFormReady]);
20
+ const handleMountForm = () => {
21
+ setFormMounted(true);
22
+ };
23
+ const handleUnmountForm = () => {
24
+ setFormMounted(false);
25
+ };
26
+ const handleSetValue = () => {
27
+ if (isFormReady) {
28
+ form.setFieldValue("email", "test@example.com");
29
+ form.setFieldValue("message", "Hello from ready form!");
30
+ } else {
31
+ }
32
+ };
33
+ const clearHistory = () => {
34
+ setStatusHistory([]);
35
+ };
36
+ return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase4: useForm Returns Status Flag" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Breaking Change:" }), " useForm() b\xE2y gi\u1EDD tr\u1EA3 v\u1EC1 tuple [instance, isReady]"] }), _jsx(Paragraph, { children: _jsx(Text, { type: "secondary", children: "isReady flag cho bi\u1EBFt Form \u0111\xE3 mount v\xE0 s\u1EB5n s\xE0ng nh\u1EADn calls hay ch\u01B0a. C\xF3 th\u1EC3 d\xF9ng \u0111\u1EC3 conditionally enable/disable features d\u1EF1a tr\xEAn Form state." }) }), _jsx("div", { style: {
37
+ marginBottom: 24,
38
+ padding: 16,
39
+ background: "#fafafa",
40
+ border: "1px solid #d9d9d9"
41
+ }, children: _jsxs(Space, { size: "large", children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Ready Status: " }), _jsx(Badge, { status: isFormReady ? "success" : "error", text: _jsx(Text, { strong: true, style: { color: isFormReady ? "#52c41a" : "#ff4d4f" }, children: isFormReady ? "READY" : "NOT READY" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Mounted: " }), _jsx(Tag, { color: formMounted ? "green" : "red", children: formMounted ? "YES" : "NO" })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "FormName: " }), _jsx(Text, { code: true, children: form.formName })] })] }) }), _jsxs(Space, { style: { marginBottom: 24 }, wrap: true, children: [!formMounted ? _jsx(Button, { onClick: handleMountForm, type: "primary", size: "large", children: "Mount Form" }) : _jsx(Button, { onClick: handleUnmountForm, danger: true, size: "large", children: "Unmount Form" }), _jsxs(Button, { onClick: handleSetValue, disabled: !isFormReady, type: isFormReady ? "primary" : "default", children: ["Set Values ", !isFormReady && "(Disabled - Form Not Ready)"] }), _jsx(Button, { onClick: clearHistory, children: "Clear History" })] }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx(Title, { level: 5, children: "Status Change History:" }), _jsx("div", { style: {
42
+ border: "1px solid #d9d9d9",
43
+ padding: 16,
44
+ height: 300,
45
+ overflow: "auto",
46
+ background: "#ffffff"
47
+ }, children: statusHistory.length === 0 ? _jsx(Text, { type: "secondary", children: "No status changes yet..." }) : _jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [_jsx("thead", { children: _jsxs("tr", { style: { borderBottom: "2px solid #d9d9d9" }, children: [_jsx("th", { style: { padding: 8, textAlign: "left" }, children: "Time" }), _jsx("th", { style: { padding: 8, textAlign: "left" }, children: "Status" }), _jsx("th", { style: { padding: 8, textAlign: "left" }, children: "Event" })] }) }), _jsx("tbody", { children: statusHistory.map((entry, index) => _jsxs("tr", { style: {
48
+ borderBottom: "1px solid #f0f0f0",
49
+ background: index % 2 === 0 ? "#fafafa" : "#ffffff"
50
+ }, children: [_jsx("td", { style: {
51
+ padding: 8,
52
+ fontFamily: "monospace",
53
+ fontSize: 12
54
+ }, children: entry.time }), _jsx("td", { style: { padding: 8 }, children: _jsx(Tag, { color: entry.status ? "green" : "red", children: entry.status ? "TRUE" : "FALSE" }) }), _jsx("td", { style: { padding: 8 }, children: entry.event })] }, index)) })] }) })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsx(Title, { level: 5, children: "Form Panel:" }), formMounted ? _jsx("div", { style: { border: "2px solid #52c41a", padding: 16 }, children: _jsxs(Form, { form, initialValues: { email: "", message: "" }, onFinish: (values) => {
55
+ alert(`Submitted: ${JSON.stringify(values, null, 2)}`);
56
+ }, children: [_jsx(Form.Item, { name: "email", label: "Email", children: _jsx("input", { type: "email", placeholder: "Enter email", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "message", label: "Message", children: _jsx("textarea", { placeholder: "Enter message", rows: 4, style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit" })] }) }) : _jsx("div", { style: {
57
+ border: "2px dashed #d9d9d9",
58
+ padding: 16,
59
+ height: 300,
60
+ display: "flex",
61
+ alignItems: "center",
62
+ justifyContent: "center",
63
+ background: "#fafafa"
64
+ }, children: _jsxs("div", { style: { textAlign: "center" }, children: [_jsx(Text, { type: "secondary", style: { fontSize: 16 }, children: "Form is not mounted" }), _jsx("br", {}), _jsx(Button, { type: "primary", onClick: handleMountForm, style: { marginTop: 16 }, children: "Mount Form" })] }) })] })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: "\u2705 Initial state: isReady = false (logged in history)" }), _jsx("li", { children: '\u2705 Click "Mount Form" \u2192 isReady changes to true (logged in history)' }), _jsx("li", { children: '\u2705 "Set Values" button becomes enabled when isReady = true' }), _jsx("li", { children: '\u2705 Click "Set Values" \u2192 Form values update successfully' }), _jsx("li", { children: '\u2705 Click "Unmount Form" \u2192 isReady changes back to false (logged in history)' }), _jsx("li", { children: '\u2705 "Set Values" button becomes disabled again' }), _jsx("li", { children: "\u2705 Status badge and tag update in real-time" })] })] })] });
65
+ }
66
+ export {
67
+ TestCase4_UseFormStatusFlag as default
68
+ };