From 1eb9f89fa69e51db9a9cbf6c665586dd34ec86ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 16:50:38 +0800 Subject: [PATCH 1/8] test: test driven --- docs/demo/screen.md | 14 ++++++++++++++ docs/examples/screen.tsx | 33 +++++++++++++++++++++++++++++++++ src/Screen/index.tsx | 33 +++++++++++++++++++++++++++++++++ src/index.tsx | 5 ++++- tests/list.test.tsx | 18 ++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 docs/demo/screen.md create mode 100644 docs/examples/screen.tsx create mode 100644 src/Screen/index.tsx diff --git a/docs/demo/screen.md b/docs/demo/screen.md new file mode 100644 index 000000000..bccbd966d --- /dev/null +++ b/docs/demo/screen.md @@ -0,0 +1,14 @@ +# Screen + +Screen component with animation effects. + + + +## API + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| visible | Whether the screen is visible | boolean | false | +| children | Content to be rendered | ReactNode | - | +| className | Additional class name | string | - | +| style | Additional style | CSSProperties | - | diff --git a/docs/examples/screen.tsx b/docs/examples/screen.tsx new file mode 100644 index 000000000..e43444556 --- /dev/null +++ b/docs/examples/screen.tsx @@ -0,0 +1,33 @@ +import Form, { Field, Screen } from 'rc-field-form'; +import React, { useState } from 'react'; +import Input from './components/Input'; + +type FormData = { + name?: string; + password?: string; +}; + +export default () => { + const [form] = Form.useForm(); + const [showPassword, setShowPassword] = useState(false); + + return ( +
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/src/Screen/index.tsx b/src/Screen/index.tsx new file mode 100644 index 000000000..a19149337 --- /dev/null +++ b/src/Screen/index.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; + +export interface ScreenProps { + visible?: boolean; + children?: React.ReactNode; + className?: string; + style?: React.CSSProperties; +} + +const Screen: React.FC = ({ visible = false, children, className, style }) => { + // Simple fade in/out animation using inline styles + const screenStyle: React.CSSProperties = { + transition: 'opacity 0.3s, transform 0.3s', + opacity: visible ? 1 : 0, + transform: visible ? 'scale(1)' : 'scale(0.8)', + display: visible ? 'block' : 'none', + ...style, + }; + + // Combine className if provided + const combinedClassName = className ? `rc-field-form-screen ${className}` : 'rc-field-form-screen'; + + return ( +
+ {children} +
+ ); +}; + +export default Screen; diff --git a/src/index.tsx b/src/index.tsx index 4433aa77c..6ef7a6e58 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import type { FormRef, FormInstance } from './interface'; import Field from './Field'; import List from './List'; +import Screen from './Screen'; import useForm from './hooks/useForm'; import type { FormProps } from './Form'; import FieldForm from './Form'; @@ -19,6 +20,7 @@ interface RefFormType extends InternalFormType { FormProvider: typeof FormProvider; Field: typeof Field; List: typeof List; + Screen: typeof Screen; useForm: typeof useForm; useWatch: typeof useWatch; } @@ -28,10 +30,11 @@ const RefForm: RefFormType = InternalForm as RefFormType; RefForm.FormProvider = FormProvider; RefForm.Field = Field; RefForm.List = List; +RefForm.Screen = Screen; RefForm.useForm = useForm; RefForm.useWatch = useWatch; -export { Field, List, useForm, FormProvider, FieldContext, ListContext, useWatch }; +export { Field, List, Screen, useForm, FormProvider, FieldContext, ListContext, useWatch }; export type { FormProps, FormInstance, FormRef }; diff --git a/tests/list.test.tsx b/tests/list.test.tsx index 8c312d313..15efca33d 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1171,4 +1171,22 @@ describe('Form.List', () => { list: [{ name: 'A' }, { name: 'BB' }, { name: 'C' }, { name: 'D' }], }); }); + + it('getFieldsValue(["list"]) should same as getFieldsValue().list', async () => { + generateForm( + fields => ( + + + + ), + { + initialValues: { + list: [{ name: '123' }], + }, + }, + ); + + expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '123' }] }); + expect(form.current!.getFieldsValue().list).toEqual({ list: [{ name: '123' }] }); + }); }); From 4e063b735f4160f8a54df4c7bca02e2084a9830a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:26:15 +0800 Subject: [PATCH 2/8] chore: todo --- src/hooks/useForm.ts | 21 ++++++++++++++++----- src/utils/NameMap.ts | 4 ++++ tests/list.test.tsx | 13 +++++++------ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts index 3353d8e61..153ccb3e4 100644 --- a/src/hooks/useForm.ts +++ b/src/hooks/useForm.ts @@ -239,6 +239,11 @@ export class FormStore { return this.fieldEntities.filter(field => field.getNamePath().length); }; + /** + * Get a map of registered field entities with their name path as the key. + * @param pure Only include fields which have a `name`. Default: false + * @returns A NameMap containing field entities indexed by their name paths + */ private getFieldsMap = (pure: boolean = false) => { const cache: NameMap = new NameMap(); this.getFieldEntities(pure).forEach(field => { @@ -248,15 +253,21 @@ export class FormStore { return cache; }; - private getFieldEntitiesForNamePathList = (nameList?: NamePath[]): FlexibleFieldEntity[] => { + private getFieldEntitiesForNamePathList = ( + nameList?: NamePath[], + includesSubNamePath = false, + ): FlexibleFieldEntity[] => { if (!nameList) { return this.getFieldEntities(true); } const cache = this.getFieldsMap(true); - return nameList.map(name => { - const namePath = getNamePath(name); - return cache.get(namePath) || { INVALIDATE_NAME_PATH: getNamePath(name) }; - }); + + if (!includesSubNamePath) { + return nameList.map(name => { + const namePath = getNamePath(name); + return cache.get(namePath) || { INVALIDATE_NAME_PATH: getNamePath(name) }; + }); + } }; private getFieldsValue = ( diff --git a/src/utils/NameMap.ts b/src/utils/NameMap.ts index b4259dd46..80cda7f60 100644 --- a/src/utils/NameMap.ts +++ b/src/utils/NameMap.ts @@ -33,6 +33,10 @@ class NameMap { return this.kvs.get(normalize(key)); } + public getAsPrefix(key: InternalNamePath): T[] { + // TODO: 实现前缀获取,如果 InternalNamePath 满足前缀则返回所有包含本身的集合 + } + public update(key: InternalNamePath, updater: (origin: T) => T | null) { const origin = this.get(key); const next = updater(origin); diff --git a/tests/list.test.tsx b/tests/list.test.tsx index 15efca33d..ae90752c2 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1174,11 +1174,12 @@ describe('Form.List', () => { it('getFieldsValue(["list"]) should same as getFieldsValue().list', async () => { generateForm( - fields => ( - - - - ), + fields => + fields.map(field => ( + + + + )), { initialValues: { list: [{ name: '123' }], @@ -1186,7 +1187,7 @@ describe('Form.List', () => { }, ); + // expect(form.current!.getFieldsValue()).toEqual({ list: [{ name: '123' }] }); expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '123' }] }); - expect(form.current!.getFieldsValue().list).toEqual({ list: [{ name: '123' }] }); }); }); From 2d791fbaccbd916f7eea1e2ab0a0d18d170bf727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:36:31 +0800 Subject: [PATCH 3/8] fix: getFieldsValue logic --- src/hooks/useForm.ts | 16 ++++++++++++++++ src/utils/NameMap.ts | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts index 153ccb3e4..12f99f9d7 100644 --- a/src/hooks/useForm.ts +++ b/src/hooks/useForm.ts @@ -253,6 +253,11 @@ export class FormStore { return cache; }; + /** + * Get field entities based on a list of name paths. + * @param nameList - Array of name paths to search for. If not provided, returns all field entities with names. + * @param includesSubNamePath - Whether to include fields that have the given name path as a prefix. + */ private getFieldEntitiesForNamePathList = ( nameList?: NamePath[], includesSubNamePath = false, @@ -268,6 +273,16 @@ export class FormStore { return cache.get(namePath) || { INVALIDATE_NAME_PATH: getNamePath(name) }; }); } + + return nameList.flatMap(name => { + const namePath = getNamePath(name); + const fields: FlexibleFieldEntity[] = cache.getAsPrefix(namePath); + + if (fields.length) { + return fields; + } + return [{ INVALIDATE_NAME_PATH: namePath }]; + }); }; private getFieldsValue = ( @@ -293,6 +308,7 @@ export class FormStore { const fieldEntities = this.getFieldEntitiesForNamePathList( Array.isArray(mergedNameList) ? mergedNameList : null, + true, ); const filteredNameList: NamePath[] = []; diff --git a/src/utils/NameMap.ts b/src/utils/NameMap.ts index 80cda7f60..84747acde 100644 --- a/src/utils/NameMap.ts +++ b/src/utils/NameMap.ts @@ -34,7 +34,22 @@ class NameMap { } public getAsPrefix(key: InternalNamePath): T[] { - // TODO: 实现前缀获取,如果 InternalNamePath 满足前缀则返回所有包含本身的集合 + const normalizedKey = normalize(key); + const normalizedPrefix = normalizedKey + SPLIT; + const results: T[] = []; + + const current = this.kvs.get(normalizedKey); + if (current !== undefined) { + results.push(current); + } + + this.kvs.forEach((value, itemNormalizedKey) => { + if (itemNormalizedKey.startsWith(normalizedPrefix)) { + results.push(value); + } + }); + + return results; } public update(key: InternalNamePath, updater: (origin: T) => T | null) { From 620a6b9b21ebc94b45f8a9d578ab49177569dd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:39:22 +0800 Subject: [PATCH 4/8] test: update test case --- tests/list.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/list.test.tsx b/tests/list.test.tsx index ae90752c2..9aac91b39 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1182,12 +1182,12 @@ describe('Form.List', () => { )), { initialValues: { - list: [{ name: '123' }], + list: [{ name: 'bamboo', notExist: 'little' }], }, }, ); - // expect(form.current!.getFieldsValue()).toEqual({ list: [{ name: '123' }] }); - expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '123' }] }); + expect(form.current!.getFieldsValue()).toEqual({ list: [{ name: 'bamboo' }] }); + expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: 'bamboo' }] }); }); }); From 438396c9ef8663e4360a272e21804cdf28be0d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:42:16 +0800 Subject: [PATCH 5/8] chore: clean up --- docs/demo/screen.md | 14 -------------- docs/examples/screen.tsx | 33 --------------------------------- 2 files changed, 47 deletions(-) delete mode 100644 docs/demo/screen.md delete mode 100644 docs/examples/screen.tsx diff --git a/docs/demo/screen.md b/docs/demo/screen.md deleted file mode 100644 index bccbd966d..000000000 --- a/docs/demo/screen.md +++ /dev/null @@ -1,14 +0,0 @@ -# Screen - -Screen component with animation effects. - - - -## API - -| Property | Description | Type | Default | -| --- | --- | --- | --- | -| visible | Whether the screen is visible | boolean | false | -| children | Content to be rendered | ReactNode | - | -| className | Additional class name | string | - | -| style | Additional style | CSSProperties | - | diff --git a/docs/examples/screen.tsx b/docs/examples/screen.tsx deleted file mode 100644 index e43444556..000000000 --- a/docs/examples/screen.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Form, { Field, Screen } from 'rc-field-form'; -import React, { useState } from 'react'; -import Input from './components/Input'; - -type FormData = { - name?: string; - password?: string; -}; - -export default () => { - const [form] = Form.useForm(); - const [showPassword, setShowPassword] = useState(false); - - return ( -
- - - - - - - - - - - - - -
- ); -}; From 9d6a81d65b4d74e7736ea9887a474fecdfed4fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:42:39 +0800 Subject: [PATCH 6/8] chore: clean up --- src/Screen/index.tsx | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/Screen/index.tsx diff --git a/src/Screen/index.tsx b/src/Screen/index.tsx deleted file mode 100644 index a19149337..000000000 --- a/src/Screen/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from 'react'; - -export interface ScreenProps { - visible?: boolean; - children?: React.ReactNode; - className?: string; - style?: React.CSSProperties; -} - -const Screen: React.FC = ({ visible = false, children, className, style }) => { - // Simple fade in/out animation using inline styles - const screenStyle: React.CSSProperties = { - transition: 'opacity 0.3s, transform 0.3s', - opacity: visible ? 1 : 0, - transform: visible ? 'scale(1)' : 'scale(0.8)', - display: visible ? 'block' : 'none', - ...style, - }; - - // Combine className if provided - const combinedClassName = className ? `rc-field-form-screen ${className}` : 'rc-field-form-screen'; - - return ( -
- {children} -
- ); -}; - -export default Screen; From a45d44a624ce7c3bd635f5ec5c7411515ccb3492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:43:29 +0800 Subject: [PATCH 7/8] chore: clean up --- src/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 6ef7a6e58..fdcb43730 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import type { FormRef, FormInstance } from './interface'; import Field from './Field'; import List from './List'; -import Screen from './Screen'; import useForm from './hooks/useForm'; import type { FormProps } from './Form'; import FieldForm from './Form'; @@ -34,7 +33,7 @@ RefForm.Screen = Screen; RefForm.useForm = useForm; RefForm.useWatch = useWatch; -export { Field, List, Screen, useForm, FormProvider, FieldContext, ListContext, useWatch }; +export { Field, List, useForm, FormProvider, FieldContext, ListContext, useWatch }; export type { FormProps, FormInstance, FormRef }; From 425d1194a18d0e117b09babf8a8b34add3eb12e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Jan 2026 17:44:50 +0800 Subject: [PATCH 8/8] chore: clean up --- src/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index fdcb43730..4433aa77c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,7 +19,6 @@ interface RefFormType extends InternalFormType { FormProvider: typeof FormProvider; Field: typeof Field; List: typeof List; - Screen: typeof Screen; useForm: typeof useForm; useWatch: typeof useWatch; } @@ -29,7 +28,6 @@ const RefForm: RefFormType = InternalForm as RefFormType; RefForm.FormProvider = FormProvider; RefForm.Field = Field; RefForm.List = List; -RefForm.Screen = Screen; RefForm.useForm = useForm; RefForm.useWatch = useWatch;