@rainbow-oh/yee-c 0.9.0 → 0.9.1

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 (37) hide show
  1. package/dist/cjs/Form/demo/async-validate.js +2 -2
  2. package/dist/cjs/Form/demo/validate.js +2 -2
  3. package/dist/cjs/Form/form-store.js +19 -6
  4. package/dist/cjs/Form/index.d.ts +1 -1
  5. package/dist/cjs/Form/interface.d.ts +9 -2
  6. package/dist/cjs/Form/utils/path.js +1 -1
  7. package/dist/cjs/TreeSelect/tree-select.js +14 -4
  8. package/dist/cjs/style/themes.css +1 -1
  9. package/dist/cjs/style/variables.css +1 -1
  10. package/dist/cjs/version.d.ts +1 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/esm/Form/demo/async-validate.js +2 -2
  13. package/dist/esm/Form/demo/validate.js +2 -2
  14. package/dist/esm/Form/form-store.js +33 -10
  15. package/dist/esm/Form/hooks/useForm.js +0 -175
  16. package/dist/esm/Form/index.d.ts +1 -1
  17. package/dist/esm/Form/interface.d.ts +9 -2
  18. package/dist/esm/Form/utils/path.js +2 -1
  19. package/dist/esm/TreeSelect/tree-select.js +20 -6
  20. package/dist/esm/style/themes.css +1 -1
  21. package/dist/esm/style/variables.css +1 -1
  22. package/dist/esm/version.d.ts +1 -1
  23. package/dist/esm/version.js +1 -1
  24. package/dist/umd/yee-c.min.js +1 -1
  25. package/package.json +1 -1
  26. package/src/Form/demo/async-validate.tsx +3 -2
  27. package/src/Form/demo/validate.tsx +3 -2
  28. package/src/Form/form-store.test.ts +156 -0
  29. package/src/Form/form-store.ts +28 -9
  30. package/src/Form/hooks/useForm.ts +0 -175
  31. package/src/Form/index.en-US.md +92 -92
  32. package/src/Form/index.ts +1 -0
  33. package/src/Form/index.zh-CN.md +55 -55
  34. package/src/Form/interface.ts +9 -2
  35. package/src/Form/utils/path.ts +2 -1
  36. package/src/TreeSelect/tree-select.tsx +21 -4
  37. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-oh/yee-c",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "yee basic components",
5
5
  "keywords": [
6
6
  "react",
@@ -1,4 +1,5 @@
1
1
  import { Button, Form, Grid, Input, Space } from '@rainbow-oh/yee-c';
2
+ import type { ValidateMessage } from '@rainbow-oh/yee-c';
2
3
  import React from 'react';
3
4
 
4
5
  // Simulate an async uniqueness check against a backend API.
@@ -16,8 +17,8 @@ export default () => {
16
17
  console.log('Received values:', values);
17
18
  };
18
19
 
19
- const onFinishFailed = (errorInfo: unknown) => {
20
- console.log('Failed:', errorInfo);
20
+ const onFinishFailed = (errors: ValidateMessage[]) => {
21
+ console.log('Failed:', errors);
21
22
  };
22
23
 
23
24
  return (
@@ -1,4 +1,5 @@
1
1
  import { Button, Form, Grid, Input, Space } from '@rainbow-oh/yee-c';
2
+ import type { ValidateMessage } from '@rainbow-oh/yee-c';
2
3
  import React from 'react';
3
4
 
4
5
  export default () => {
@@ -8,8 +9,8 @@ export default () => {
8
9
  console.log('Received values:', values);
9
10
  };
10
11
 
11
- const onFinishFailed = (errorInfo: unknown) => {
12
- console.log('Failed:', errorInfo);
12
+ const onFinishFailed = (errors: ValidateMessage[]) => {
13
+ console.log('Failed:', errors);
13
14
  };
14
15
 
15
16
  return (
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { ReactElement } from 'react';
3
+ import { FormStore } from './form-store';
4
+ import type { FieldEntity, Rule } from './interface';
5
+
6
+ const createEntity = (
7
+ name: string,
8
+ onStoreChange = vi.fn(),
9
+ rules?: Rule[],
10
+ ): FieldEntity => ({
11
+ props: {
12
+ name,
13
+ rules,
14
+ children: {} as unknown as ReactElement,
15
+ },
16
+ onStoreChange,
17
+ });
18
+
19
+ describe('FormStore.clearFields', () => {
20
+ it('keeps values of unrelated fields on partial clear', () => {
21
+ const store = new FormStore();
22
+ store.setFieldsValue({ a: 1, b: 2 }, 'fill');
23
+
24
+ store.clearFields(['a']);
25
+
26
+ expect(store.getFieldsValue()).toEqual({ a: undefined, b: 2 });
27
+ });
28
+
29
+ it('clears a nested path leaf and keeps its siblings', () => {
30
+ const store = new FormStore();
31
+ store.setFieldsValue({ 'user.name': 'tom', 'user.age': 18 }, 'fill');
32
+
33
+ store.clearFields(['user.name']);
34
+
35
+ expect(store.getFieldValue('user.name')).toBeUndefined();
36
+ expect(store.getFieldValue('user.age')).toBe(18);
37
+ });
38
+
39
+ it('sets every top-level key to undefined when name is omitted', () => {
40
+ const store = new FormStore();
41
+ store.setFieldsValue({ a: 1, 'user.name': 'tom' }, 'fill');
42
+
43
+ store.clearFields();
44
+
45
+ const values = store.getFieldsValue();
46
+ expect(values.a).toBeUndefined();
47
+ expect(store.getFieldValue('user.name')).toBeUndefined();
48
+ expect(Object.keys(values)).toEqual(['a', 'user']);
49
+ });
50
+
51
+ it('clears validation messages only for cleared fields', async () => {
52
+ const store = new FormStore();
53
+ store.registerFieldEntities(
54
+ createEntity('a', vi.fn(), [{ required: true, message: 'a required' }]),
55
+ );
56
+ store.registerFieldEntities(
57
+ createEntity('b', vi.fn(), [{ required: true, message: 'b required' }]),
58
+ );
59
+
60
+ const errors = await store.validateFields();
61
+ expect(errors).toHaveLength(2);
62
+ expect(store.getFieldValidate('a')?.message).toBe('a required');
63
+
64
+ store.clearFields(['a']);
65
+
66
+ expect(store.getFieldValidate('a')).toBeNull();
67
+ expect(store.getFieldValidate('b')?.message).toBe('b required');
68
+ });
69
+
70
+ it('clears manual field statuses only for cleared fields', () => {
71
+ const store = new FormStore();
72
+ store.setFieldStatus('a', { status: 'warning', message: 'check a' });
73
+ store.setFieldStatus('b', { status: 'warning', message: 'check b' });
74
+
75
+ store.clearFields(['a']);
76
+
77
+ expect(store.getFieldStatus('a')).toBeNull();
78
+ expect(store.getFieldStatus('b')?.message).toBe('check b');
79
+ });
80
+
81
+ it('refreshes only fields related to the cleared names', () => {
82
+ const store = new FormStore();
83
+ const onChangeA = vi.fn();
84
+ const onChangeB = vi.fn();
85
+ const onChangeNested = vi.fn();
86
+ store.registerFieldEntities(createEntity('a', onChangeA));
87
+ store.registerFieldEntities(createEntity('b', onChangeB));
88
+ store.registerFieldEntities(createEntity('a.x', onChangeNested));
89
+ store.setFieldsValue({ a: 1, b: 2 }, 'fill');
90
+ onChangeA.mockClear();
91
+ onChangeB.mockClear();
92
+ onChangeNested.mockClear();
93
+
94
+ store.clearFields(['a']);
95
+
96
+ expect(onChangeA).toHaveBeenCalled();
97
+ expect(onChangeNested).toHaveBeenCalled();
98
+ expect(onChangeB).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it('notifies only watchers related to the cleared names', () => {
102
+ const store = new FormStore();
103
+ const watchA = vi.fn();
104
+ const watchB = vi.fn();
105
+ const watchAll = vi.fn();
106
+ store.subscribe('a', watchA);
107
+ store.subscribe('b', watchB);
108
+ store.subscribe(undefined, watchAll);
109
+
110
+ store.setFieldsValue({ a: 1, b: 2 }, 'fill');
111
+ watchA.mockClear();
112
+ watchB.mockClear();
113
+ watchAll.mockClear();
114
+
115
+ store.clearFields(['a']);
116
+
117
+ expect(watchA).toHaveBeenCalled();
118
+ expect(watchAll).toHaveBeenCalled();
119
+ expect(watchB).not.toHaveBeenCalled();
120
+ });
121
+
122
+ it('fires the onClear callback', () => {
123
+ const store = new FormStore();
124
+ const onClear = vi.fn();
125
+ store.setCallbacks({ onClear });
126
+
127
+ store.clearFields(['a']);
128
+
129
+ expect(onClear).toHaveBeenCalled();
130
+ });
131
+ });
132
+
133
+ describe('FormStore.initialize initialValues', () => {
134
+ it('resolves nested objects by field path', () => {
135
+ const store = new FormStore();
136
+ store.initialize({ initialValues: { user: { name: 'tom' }, age: 18 } });
137
+
138
+ expect(store.getFieldValue('user.name')).toBe('tom');
139
+ expect(store.getFieldValue('age')).toBe(18);
140
+
141
+ store.setFieldsValue({ 'user.name': 'jerry' }, 'fill');
142
+ store.resetFields(['user.name']);
143
+ expect(store.getFieldValue('user.name')).toBe('tom');
144
+ });
145
+ });
146
+
147
+ describe('FormStore.setFieldsValue with clear trigger', () => {
148
+ it('merges over the store instead of replacing it', () => {
149
+ const store = new FormStore();
150
+ store.setFieldsValue({ a: 1, b: 2 }, 'fill');
151
+
152
+ store.setFieldsValue({ a: undefined }, 'clear');
153
+
154
+ expect(store.getFieldsValue()).toEqual({ a: undefined, b: 2 });
155
+ });
156
+ });
@@ -174,9 +174,7 @@ export class FormStore {
174
174
  stringifyPath(getNamePath(name)),
175
175
  );
176
176
  Array.from(entries.keys()).forEach((fieldName) => {
177
- if (
178
- normalizedNames.some((name) => isRelatedNamePath(fieldName, name))
179
- ) {
177
+ if (normalizedNames.some((name) => isRelatedNamePath(fieldName, name))) {
180
178
  entries.delete(fieldName);
181
179
  }
182
180
  });
@@ -188,7 +186,11 @@ export class FormStore {
188
186
 
189
187
  setFieldsValue = (newStore: Store, trigger: TRIGGER = 'onChange') => {
190
188
  if (trigger === 'reset' || trigger === 'clear') {
191
- this.store = newStore;
189
+ // 'reset' replaces the whole store; 'clear' merges the given keys
190
+ // (typically undefined) over the store so a partial clear never
191
+ // wipes values of unrelated fields.
192
+ this.store =
193
+ trigger === 'reset' ? newStore : { ...this.store, ...newStore };
192
194
  if (trigger === 'reset') {
193
195
  this.callbacks?.onReset?.();
194
196
  } else {
@@ -279,12 +281,29 @@ export class FormStore {
279
281
  clearFields = (name?: Name[]) => {
280
282
  const names = name || Object.keys(this.store);
281
283
 
282
- const clearStore = names.reduce((acc, name) => {
283
- acc[name] = undefined;
284
- return acc;
285
- }, {} as Store);
284
+ // Merge undefined over the current store (nested paths included) —
285
+ // unrelated fields keep their values.
286
+ let clearStore = { ...this.store };
287
+ names.forEach((n) => {
288
+ const parsedPath = parsePath(n);
289
+ if (parsedPath.length > 1) {
290
+ clearStore = setValueByPath(clearStore, parsedPath, undefined);
291
+ } else {
292
+ clearStore[n] = undefined;
293
+ }
294
+ });
295
+ this.deleteRelatedEntries(this.validateMessages, name);
286
296
  this.clearFieldStatuses(name);
287
- this.setFieldsValue(clearStore, 'clear');
297
+ this.store = clearStore;
298
+ this.callbacks?.onClear?.();
299
+
300
+ // clearStore carries the whole store (nested paths must materialize
301
+ // it), so derive the refresh scope from `names` — only cleared
302
+ // fields re-render, not the entire form.
303
+ this.getRelatedFieldNames(names).forEach((fieldName) => {
304
+ this.fieldMap.get(fieldName)?.onStoreChange();
305
+ });
306
+ this.notifyWatchers(names);
288
307
  };
289
308
 
290
309
  validateField = async (
@@ -1,178 +1,3 @@
1
- // import { useRef } from 'react';
2
- // import type {
3
- // Callbacks,
4
- // FieldEntity,
5
- // FormInstance,
6
- // NamePath,
7
- // Store,
8
- // validateMessage,
9
- // } from './interface';
10
- // import validate from './utils/validate';
11
-
12
- // class FormStore {
13
- // private store: Store = {};
14
- // private callbacks: Callbacks = {};
15
- // private fieldEntities: FieldEntity[] = [];
16
- // private validateMessages: validateMessage[] = [];
17
- // private initialized = false;
18
- // // 初始化form实例
19
- // initialize = ({
20
- // initialValues,
21
- // callbacks,
22
- // }: {
23
- // initialValues?: Store;
24
- // callbacks?: Callbacks;
25
- // }) => {
26
- // if (!this.initialized) {
27
- // this.initialized = true;
28
-
29
- // if (initialValues) {
30
- // this.store = initialValues;
31
- // } else {
32
- // this.store = {};
33
- // }
34
-
35
- // if (callbacks) {
36
- // this.setCallbacks(callbacks);
37
- // }
38
- // }
39
- // };
40
-
41
- // getFieldsValue = () => {
42
- // return { ...this.store };
43
- // };
44
-
45
- // getFieldValue = (name: NamePath) => {
46
- // return this.store[name];
47
- // };
48
-
49
- // getFieldValidate = (name: NamePath) => {
50
- // return this.validateMessages?.find((item) => item.name === name);
51
- // };
52
-
53
- // resetFields = (name?: NamePath[]) => {
54
- // if (name) {
55
- // name.forEach((n) => {
56
- // this.setFieldsValue({ [n]: undefined });
57
- // });
58
- // } else {
59
- // Object.keys(this.store).forEach((key) => {
60
- // this.setFieldsValue({ [key]: undefined });
61
- // });
62
- // }
63
- // };
64
-
65
- // validateFields = () => {
66
- // const err: validateMessage[] = [];
67
- // this.fieldEntities.forEach((entity) => {
68
- // const { name, rules, required } = entity.props;
69
- // const value: NamePath = name && this.getFieldValue(name);
70
-
71
- // const mergedRules = rules || [];
72
-
73
- // if (required) {
74
- // mergedRules.push({
75
- // required: true,
76
- // message: 'This is required field.',
77
- // });
78
- // }
79
-
80
- // mergedRules.forEach((rule) => {
81
- // const mark = validate(rule, value);
82
- // if (name && !mark) {
83
- // err.push({
84
- // name: name,
85
- // status: 'error',
86
- // value,
87
- // ...rule
88
- // });
89
- // }
90
- // });
91
- // });
92
- // return err;
93
- // };
94
-
95
- // setFieldsValue = (newStore: Store, isChange?: boolean) => {
96
- // // const { onValuesChange } = this.callbacks;
97
- // this.store = {
98
- // ...this.store,
99
- // ...newStore,
100
- // };
101
- // // onValuesChange?.(newStore, this.store);
102
- // this.refreshField(newStore, isChange);
103
- // };
104
-
105
- // refreshField = (newStore?: Store, isChange?: boolean) => {
106
- // if (newStore) {
107
- // this.fieldEntities.forEach((entity) => {
108
- // Object.keys(newStore).forEach((k) => {
109
- // if (k === entity.props.name) {
110
- // if (isChange) {
111
- // this.validateMessages = this.validateFields();
112
- // }
113
- // entity.onStoreChange();
114
- // }
115
- // });
116
- // });
117
- // } else {
118
- // this.fieldEntities.forEach((entity) => {
119
- // if (entity.props.rules || entity.props.required) {
120
- // entity?.onStoreChange();
121
- // }
122
- // });
123
- // }
124
- // };
125
-
126
- // setCallbacks = (callbacks: Callbacks) => {
127
- // this.callbacks = { ...this.callbacks, ...callbacks };
128
- // };
129
-
130
- // getCallbacks = () => {
131
- // return this.callbacks;
132
- // };
133
-
134
- // // 订阅与取消订阅
135
- // registerFieldEntities = (entity: FieldEntity) => {
136
- // this.fieldEntities.push(entity);
137
- // if (entity.props.name) {
138
- // this.store = { ...this.store, [entity.props.name]: undefined };
139
- // }
140
- // return () => {
141
- // this.fieldEntities = this.fieldEntities.filter((item) => item !== entity);
142
- // const { name } = entity.props;
143
- // if (name) {
144
- // delete this.store[name];
145
- // }
146
- // };
147
- // };
148
-
149
- // submit = () => {
150
- // const { onFinish, onFinishFailed } = this.callbacks;
151
- // this.validateMessages = this.validateFields();
152
- // if (this.validateMessages.length === 0) {
153
- // onFinish?.(this.getFieldsValue());
154
- // } else {
155
- // onFinishFailed?.(this.validateMessages);
156
- // }
157
- // this.refreshField();
158
- // };
159
-
160
- // getForm(): FormInstance {
161
- // return {
162
- // getFieldValidate: this.getFieldValidate,
163
- // getFieldsValue: this.getFieldsValue,
164
- // getFieldValue: this.getFieldValue,
165
- // setFieldsValue: this.setFieldsValue,
166
- // resetFields: this.resetFields,
167
- // submit: this.submit,
168
- // setCallbacks: this.setCallbacks,
169
- // getCallbacks: this.getCallbacks,
170
- // registerFieldEntities: this.registerFieldEntities,
171
- // initialize: this.initialize,
172
- // };
173
- // }
174
- // }
175
-
176
1
  import { useRef } from 'react';
177
2
  import { useLocale } from '../../locale';
178
3
  import { FormStore } from '../form-store';