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.
- package/CHANGELOG.md +77 -0
- package/dist/providers/Form.d.ts +30 -15
- package/dist/providers/Form.js +142 -25
- package/dist/stores/formStore.js +3 -0
- package/dist/test/testFormInstance/TestCase1_FormWithInstance.d.ts +14 -0
- package/dist/test/testFormInstance/TestCase1_FormWithInstance.js +21 -0
- package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.d.ts +14 -0
- package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.js +23 -0
- package/dist/test/testFormInstance/TestCase3_SafeFormMethods.d.ts +14 -0
- package/dist/test/testFormInstance/TestCase3_SafeFormMethods.js +69 -0
- package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.d.ts +15 -0
- package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.js +68 -0
- package/dist/test/testFormInstance/TestCase5_UseFormUtils.d.ts +1 -0
- package/dist/test/testFormInstance/TestCase5_UseFormUtils.js +70 -0
- package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.d.ts +15 -0
- package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.js +55 -0
- package/dist/test/testFormInstance/TestCase7_SetValueInEffect.d.ts +1 -0
- package/dist/test/testFormInstance/TestCase7_SetValueInEffect.js +76 -0
- package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.d.ts +1 -0
- package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.js +102 -0
- package/dist/test/testFormInstance/index.d.ts +14 -0
- package/dist/test/testFormInstance/index.js +66 -0
- package/dist/test/testSetValue/TestSetValueInEffect.d.ts +3 -0
- package/dist/test/testSetValue/TestSetValueInEffect.js +30 -0
- package/dist/types/public.d.ts +12 -8
- package/package.json +3 -2
- package/src/App.tsx +7 -7
- package/src/providers/Form.tsx +247 -28
- package/src/stores/formStore.ts +3 -1
- package/src/test/testFormInstance/TestCase1_FormWithInstance.tsx +116 -0
- package/src/test/testFormInstance/TestCase2_AutoGenerateFormName.tsx +142 -0
- package/src/test/testFormInstance/TestCase3_SafeFormMethods.tsx +220 -0
- package/src/test/testFormInstance/TestCase4_UseFormStatusFlag.tsx +278 -0
- package/src/test/testFormInstance/TestCase5_UseFormUtils.tsx +242 -0
- package/src/test/testFormInstance/TestCase6_FormWithoutWrapper.tsx +238 -0
- package/src/test/testFormInstance/TestCase7_SetValueInEffect.tsx +307 -0
- package/src/test/testFormInstance/TestCase8_TypeSafetyImprovements.tsx +390 -0
- package/src/test/testFormInstance/index.tsx +155 -0
- package/src/test/testSetValue/TestSetValueInEffect.tsx +54 -0
- 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
|
package/dist/providers/Form.d.ts
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 &
|
|
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;
|
package/dist/providers/Form.js
CHANGED
|
@@ -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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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 =
|
|
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 ===
|
|
440
|
+
return state.formInstances.find((i) => i.formName === initFormName);
|
|
441
|
+
});
|
|
442
|
+
const getFormInstance = useFormStore((state) => {
|
|
443
|
+
return state.getFormInstance;
|
|
431
444
|
});
|
|
432
|
-
return [
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
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
|
package/dist/stores/formStore.js
CHANGED
|
@@ -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
|
+
};
|