@fairys/valtio-form-basic 0.0.8
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/README.md +10 -0
- package/esm/form/form.d.ts +66 -0
- package/esm/form/form.item.d.ts +150 -0
- package/esm/form/form.item.js +163 -0
- package/esm/form/form.js +17 -0
- package/esm/form/instance/index.d.ts +64 -0
- package/esm/form/instance/index.js +140 -0
- package/esm/form/layout.d.ts +2728 -0
- package/esm/form/layout.js +125 -0
- package/esm/index.d.ts +4 -0
- package/esm/index.js +4 -0
- package/esm/interface.d.ts +4 -0
- package/esm/interface.js +0 -0
- package/esm/styles/index.css +266 -0
- package/lib/index.js +87 -0
- package/package.json +38 -0
- package/src/form/form.item.tsx +382 -0
- package/src/form/form.tsx +68 -0
- package/src/form/instance/index.ts +219 -0
- package/src/form/layout.tsx +321 -0
- package/src/index.tsx +4 -0
- package/src/interface.ts +4 -0
- package/src/styles/index.css +41 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**表单项*/
|
|
2
|
+
|
|
3
|
+
import { MObject } from 'interface';
|
|
4
|
+
import React, { useMemo } from 'react';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
import { useFairysValtioFormInstanceContextState, FairysValtioFormInstance } from './instance';
|
|
7
|
+
import { useFairysValtioFormLayoutContext, FairysValtioFormLayoutContextOptions } from './layout';
|
|
8
|
+
|
|
9
|
+
export interface FairysValtioFormItemAttrsProps<T extends MObject<T> = object> {
|
|
10
|
+
/**表单项名称*/
|
|
11
|
+
name: string;
|
|
12
|
+
/**表单项标签*/
|
|
13
|
+
label?: string;
|
|
14
|
+
/**传递组件字段*/
|
|
15
|
+
valuePropName?: string;
|
|
16
|
+
/**自定义获取值*/
|
|
17
|
+
getValueFromEvent?: (event: any, form: FairysValtioFormInstance<T>) => any;
|
|
18
|
+
/**值格式化*/
|
|
19
|
+
formatValue?: (value: any, form: FairysValtioFormInstance<T>, event: any) => any;
|
|
20
|
+
/**触发数据更新之后触发(用于数据联动之类的)*/
|
|
21
|
+
onAfterUpdate?: (value: any, form: FairysValtioFormInstance<T>, event: any) => void;
|
|
22
|
+
/**事件名称*/
|
|
23
|
+
trigger?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: React.CSSProperties;
|
|
26
|
+
labelClassName?: string;
|
|
27
|
+
labelStyle?: React.CSSProperties;
|
|
28
|
+
bodyClassName?: string;
|
|
29
|
+
bodyStyle?: React.CSSProperties;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
/**规则校验失败错误提示位置*/
|
|
32
|
+
errorLayout?: 'left-bottom' | 'right-bottom' | 'top-right' | 'top-left';
|
|
33
|
+
/**label显示模式*/
|
|
34
|
+
labelMode?: 'left' | 'top' | 'between';
|
|
35
|
+
/**额外内容*/
|
|
36
|
+
extra?: React.ReactNode;
|
|
37
|
+
/**底部提示内容*/
|
|
38
|
+
helpText?: React.ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* 表单项占据列数
|
|
41
|
+
* @default 1
|
|
42
|
+
*/
|
|
43
|
+
colSpan?: number;
|
|
44
|
+
/**
|
|
45
|
+
* 表单项占据行数
|
|
46
|
+
* @default 1
|
|
47
|
+
*/
|
|
48
|
+
rowSpan?: number;
|
|
49
|
+
/**是否必填*/
|
|
50
|
+
isRequired?: boolean;
|
|
51
|
+
/**是否显示冒号*/
|
|
52
|
+
showColon?: boolean;
|
|
53
|
+
/**底部显示边框*/
|
|
54
|
+
borderedType?: FairysValtioFormLayoutContextOptions['borderedType'];
|
|
55
|
+
/**输入框属性*/
|
|
56
|
+
attrs?: any;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 处理表单表单项属性
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
*
|
|
64
|
+
* ```tsx
|
|
65
|
+
import { Fragment } from 'react'
|
|
66
|
+
import { useFairysValtioFormItemAttrs } from "@fairys/valtio-form"
|
|
67
|
+
import type { FairysValtioFormItemAttrsProps } from "@fairys/valtio-form"
|
|
68
|
+
export interface FormItemProps extends FairysValtioFormItemAttrsProps{}
|
|
69
|
+
|
|
70
|
+
export const FormItem = (props: FormItemProps) => {
|
|
71
|
+
const { label, extra, helpText } = props
|
|
72
|
+
const {
|
|
73
|
+
itemClassName, itemStyle, containerClassName, itemLabelClassName, itemLabelStyle,
|
|
74
|
+
itemBodyClassName, itemBodyStyle, itemInputClassName, itemExtraClassName, errorClassName, helpClassName,
|
|
75
|
+
isInvalid, borderedType, children, error
|
|
76
|
+
} = useFairysValtioFormItemAttrs(props)
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<View className={itemClassName} style={itemStyle}>
|
|
80
|
+
<View className={containerClassName}>
|
|
81
|
+
<View className={itemLabelClassName} style={itemLabelStyle}>
|
|
82
|
+
{label}
|
|
83
|
+
</View>
|
|
84
|
+
<View className={itemBodyClassName} style={itemBodyStyle}>
|
|
85
|
+
<View className={itemInputClassName}>
|
|
86
|
+
{children}
|
|
87
|
+
</View>
|
|
88
|
+
{extra ? <View className={itemExtraClassName}>{extra}</View> : <Fragment />}
|
|
89
|
+
{borderedType === 'body' && isInvalid ? <View className={errorClassName}>{error}</View> : <Fragment />}
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
{helpText ? <View className={helpClassName}>{helpText}</View> : <Fragment />}
|
|
93
|
+
{isInvalid && borderedType !== 'body' ? <View className={errorClassName}>{error}</View> : <Fragment />}
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
*/
|
|
100
|
+
export function useFairysValtioFormItemAttrs<T extends MObject<T> = object>(props: FairysValtioFormItemAttrsProps<T>) {
|
|
101
|
+
const [layoutAttrs] = useFairysValtioFormLayoutContext();
|
|
102
|
+
const colCount = layoutAttrs.colCount || 1;
|
|
103
|
+
const parent_borderedType = layoutAttrs.borderedType || 'bottom';
|
|
104
|
+
const parent_errorLayout = layoutAttrs.errorLayout || 'right-bottom';
|
|
105
|
+
const parent_formItemClassName = layoutAttrs.formItemClassName;
|
|
106
|
+
const parent_formItemLabelClassName = layoutAttrs.formItemLabelClassName;
|
|
107
|
+
const parent_formItemLabelStyle = layoutAttrs.formItemLabelStyle;
|
|
108
|
+
const parent_formItemStyle = layoutAttrs.formItemStyle;
|
|
109
|
+
const parent_formItemBodyClassName = layoutAttrs.formItemBodyClassName;
|
|
110
|
+
const parent_formItemBodyStyle = layoutAttrs.formItemBodyStyle;
|
|
111
|
+
const parent_labelMode = layoutAttrs.labelMode || 'between';
|
|
112
|
+
const {
|
|
113
|
+
name,
|
|
114
|
+
valuePropName = 'value',
|
|
115
|
+
getValueFromEvent,
|
|
116
|
+
formatValue,
|
|
117
|
+
onAfterUpdate,
|
|
118
|
+
trigger = 'onChange',
|
|
119
|
+
className,
|
|
120
|
+
style,
|
|
121
|
+
labelClassName,
|
|
122
|
+
labelStyle,
|
|
123
|
+
bodyClassName,
|
|
124
|
+
bodyStyle,
|
|
125
|
+
children,
|
|
126
|
+
labelMode = parent_labelMode,
|
|
127
|
+
errorLayout = parent_errorLayout,
|
|
128
|
+
colSpan = 1,
|
|
129
|
+
rowSpan = 1,
|
|
130
|
+
isRequired: _isRequired,
|
|
131
|
+
borderedType = parent_borderedType,
|
|
132
|
+
attrs = {},
|
|
133
|
+
showColon = false,
|
|
134
|
+
} = props;
|
|
135
|
+
const [state, errorState, formInstance] = useFairysValtioFormInstanceContextState<T>();
|
|
136
|
+
const rules = formInstance.rules?.[name];
|
|
137
|
+
const value = state[name];
|
|
138
|
+
const error = errorState[name];
|
|
139
|
+
|
|
140
|
+
const onValueChange = (event: any) => {
|
|
141
|
+
let value = event;
|
|
142
|
+
const target = event?.detail || event?.target;
|
|
143
|
+
if (typeof getValueFromEvent === 'function') {
|
|
144
|
+
value = getValueFromEvent(event, formInstance);
|
|
145
|
+
} else if (event && target && typeof target === 'object' && valuePropName in target) {
|
|
146
|
+
value = target.valuePropName;
|
|
147
|
+
}
|
|
148
|
+
if (typeof formatValue === 'function') {
|
|
149
|
+
value = formatValue(value, formInstance, event);
|
|
150
|
+
}
|
|
151
|
+
formInstance.updated({ [name]: value });
|
|
152
|
+
if (typeof onAfterUpdate === 'function') {
|
|
153
|
+
onAfterUpdate(value, formInstance, event);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**基础组件参数*/
|
|
158
|
+
const baseControl = {
|
|
159
|
+
...attrs,
|
|
160
|
+
name,
|
|
161
|
+
[valuePropName]: value,
|
|
162
|
+
[trigger]: onValueChange,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**判断是否必填*/
|
|
166
|
+
const isRequired = useMemo(() => {
|
|
167
|
+
if (_isRequired) {
|
|
168
|
+
return _isRequired;
|
|
169
|
+
} else if (Array.isArray(rules) && rules.length) {
|
|
170
|
+
return rules.some((rule) => rule.required);
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}, [rules, formInstance]);
|
|
174
|
+
|
|
175
|
+
/**校验是否存在错误信息*/
|
|
176
|
+
const isInvalid = useMemo(() => {
|
|
177
|
+
if (Array.isArray(error) && error.length) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}, [error]);
|
|
182
|
+
|
|
183
|
+
/**表单项类名*/
|
|
184
|
+
const item_cls = useMemo(() => {
|
|
185
|
+
return clsx(
|
|
186
|
+
'fairys-valtio-form-item fairystaroform__p-[4px] fairystaroform__text-[12px] fairystaroform__relative fairystaroform__flex fairystaroform__flex-col fairystaroform__box-border fairystaroform__break-all',
|
|
187
|
+
{
|
|
188
|
+
'fairystaroform__border-b fairystaroform__border-b-solid fairystaroform__border-b-gray-200':
|
|
189
|
+
borderedType === 'bottom',
|
|
190
|
+
[labelMode]: labelMode,
|
|
191
|
+
},
|
|
192
|
+
className,
|
|
193
|
+
parent_formItemClassName,
|
|
194
|
+
);
|
|
195
|
+
}, [className, parent_formItemClassName, labelMode, borderedType]);
|
|
196
|
+
|
|
197
|
+
/**表单项容器类名*/
|
|
198
|
+
const itemContainer_cls = useMemo(() => {
|
|
199
|
+
// 默认两端显示
|
|
200
|
+
return clsx(
|
|
201
|
+
'fairys-valtio-form-item-container fairystaroform__flex-1 fairystaroform__h-full fairystaroform__flex fairystaroform__box-border',
|
|
202
|
+
{
|
|
203
|
+
'fairystaroform__flex-row fairystaroform__items-center fairystaroform__justify-between fairystaroform__gap-[8px]':
|
|
204
|
+
labelMode === 'between',
|
|
205
|
+
'fairystaroform__flex-col fairystaroform__gap-[4px]': labelMode === 'top',
|
|
206
|
+
'fairystaroform__flex-row fairystaroform__gap-[8px]': labelMode === 'left',
|
|
207
|
+
},
|
|
208
|
+
labelClassName,
|
|
209
|
+
);
|
|
210
|
+
}, [labelClassName, labelMode]);
|
|
211
|
+
|
|
212
|
+
/**表单项标签类名*/
|
|
213
|
+
const itemLabel_cls = useMemo(() => {
|
|
214
|
+
// 默认两端显示
|
|
215
|
+
return clsx(
|
|
216
|
+
'fairys-valtio-form-item-label fairystaroform__text-gray-800 fairystaroform__flex fairystaroform__items-center fairystaroform__relative fairystaroform__box-border',
|
|
217
|
+
{
|
|
218
|
+
'fairystaroform__justify-start': labelMode !== 'left',
|
|
219
|
+
'fairystaroform__justify-end': labelMode === 'left',
|
|
220
|
+
required: isRequired,
|
|
221
|
+
'show-colon': showColon,
|
|
222
|
+
},
|
|
223
|
+
labelClassName,
|
|
224
|
+
parent_formItemLabelClassName,
|
|
225
|
+
);
|
|
226
|
+
}, [labelClassName, parent_formItemLabelClassName, labelMode, isRequired, showColon]);
|
|
227
|
+
|
|
228
|
+
/**表单项主体类名*/
|
|
229
|
+
const itemBody_cls = useMemo(() => {
|
|
230
|
+
// 默认两端显示
|
|
231
|
+
return clsx(
|
|
232
|
+
'fairys-valtio-form-item-body fairystaroform__relative fairystaroform__flex-1 fairystaroform__flex fairystaroform__box-border',
|
|
233
|
+
{
|
|
234
|
+
'fairystaroform__flex-row fairystaroform__justify-start': labelMode === 'left',
|
|
235
|
+
'fairystaroform__flex-row fairystaroform__justify-end': labelMode === 'between' || labelMode === 'top',
|
|
236
|
+
'fairystaroform__flex-row': labelMode === 'top',
|
|
237
|
+
'fairystaroform__border-b fairystaroform__border-b-solid fairystaroform__border-b-gray-200 ':
|
|
238
|
+
borderedType === 'body',
|
|
239
|
+
},
|
|
240
|
+
bodyClassName,
|
|
241
|
+
parent_formItemBodyClassName,
|
|
242
|
+
);
|
|
243
|
+
}, [bodyClassName, labelMode, borderedType, parent_formItemBodyClassName]);
|
|
244
|
+
|
|
245
|
+
// 表单项输入类名
|
|
246
|
+
const itemInput_cls = useMemo(() => {
|
|
247
|
+
return clsx(
|
|
248
|
+
'fairys-valtio-form-item-body fairystaroform__flex fairystaroform__flex-row fairystaroform__flex-1 fairystaroform__box-border',
|
|
249
|
+
{
|
|
250
|
+
'fairystaroform__justify-end fairystaroform__text-right': labelMode === 'between',
|
|
251
|
+
'fairystaroform__justify-start fairystaroform__text-left fairystaroform__items-center': labelMode !== 'between',
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
}, [labelMode]);
|
|
255
|
+
|
|
256
|
+
/**表单项额外内容类名*/
|
|
257
|
+
const itemExtra_cls = useMemo(() => {
|
|
258
|
+
return clsx(
|
|
259
|
+
'fairys-valtio-form-item-body-extra fairystaroform__box-border fairystaroform__flex fairystaroform__items-center fairystaroform__justify-center',
|
|
260
|
+
);
|
|
261
|
+
}, []);
|
|
262
|
+
|
|
263
|
+
/**表单项底部提示内容类名*/
|
|
264
|
+
const itemHelp_cls = useMemo(() => {
|
|
265
|
+
return clsx(
|
|
266
|
+
'fairys-valtio-form-item-body-help fairystaroform__text-[10px] fairystaroform__w-full fairystaroform__box-border',
|
|
267
|
+
);
|
|
268
|
+
}, []);
|
|
269
|
+
|
|
270
|
+
/**表单项错误提示类名*/
|
|
271
|
+
const itemError_cls = useMemo(() => {
|
|
272
|
+
return clsx(
|
|
273
|
+
'fairys-valtio-form-item-body-error fairystaroform__px-[4px] fairystaroform__w-full fairystaroform__flex fairystaroform__flex-row fairystaroform__box-border fairystaroform__text-red fairystaroform__absolute fairystaroform__text-[10px] fairystaroform__z-10',
|
|
274
|
+
{
|
|
275
|
+
'fairystaroform__bottom-[-14px] fairystaroform__left-0 fairystaroform__justify-start':
|
|
276
|
+
errorLayout === 'left-bottom',
|
|
277
|
+
'fairystaroform__bottom-[-14px] fairystaroform__right-0 fairystaroform__justify-end':
|
|
278
|
+
errorLayout === 'right-bottom',
|
|
279
|
+
'fairystaroform__top-[-14px] fairystaroform__right-0 fairystaroform__justify-end': errorLayout === 'top-right',
|
|
280
|
+
'fairystaroform__top-[-14px] fairystaroform__left-0 fairystaroform__justify-start': errorLayout === 'top-left',
|
|
281
|
+
},
|
|
282
|
+
);
|
|
283
|
+
}, [errorLayout]);
|
|
284
|
+
|
|
285
|
+
const styleBase = useMemo(() => {
|
|
286
|
+
const css: React.CSSProperties = {};
|
|
287
|
+
if (colSpan) {
|
|
288
|
+
const end = colCount > colSpan ? colSpan : colCount;
|
|
289
|
+
css.gridColumnEnd = `span ${end}`;
|
|
290
|
+
}
|
|
291
|
+
if (rowSpan) {
|
|
292
|
+
css.gridRowEnd = `span ${rowSpan}`;
|
|
293
|
+
}
|
|
294
|
+
return css;
|
|
295
|
+
}, [colSpan, rowSpan, colCount]);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
value,
|
|
299
|
+
isInvalid,
|
|
300
|
+
borderedType,
|
|
301
|
+
onValueChange,
|
|
302
|
+
colSpan,
|
|
303
|
+
rowSpan,
|
|
304
|
+
colCount,
|
|
305
|
+
labelMode,
|
|
306
|
+
errorLayout,
|
|
307
|
+
isRequired,
|
|
308
|
+
state,
|
|
309
|
+
errorState,
|
|
310
|
+
formInstance,
|
|
311
|
+
error,
|
|
312
|
+
// ================================================================================
|
|
313
|
+
itemClassName: item_cls,
|
|
314
|
+
itemStyle: { ...(parent_formItemStyle || {}), ...styleBase, ...(style || {}) },
|
|
315
|
+
containerClassName: itemContainer_cls,
|
|
316
|
+
itemLabelClassName: itemLabel_cls,
|
|
317
|
+
itemLabelStyle: { ...(parent_formItemLabelStyle || {}), ...(labelStyle || {}) },
|
|
318
|
+
itemBodyClassName: itemBody_cls,
|
|
319
|
+
itemBodyStyle: { ...(parent_formItemBodyStyle || {}), ...(bodyStyle || {}) },
|
|
320
|
+
itemInputClassName: itemInput_cls,
|
|
321
|
+
itemExtraClassName: itemExtra_cls,
|
|
322
|
+
errorClassName: itemError_cls,
|
|
323
|
+
helpClassName: itemHelp_cls,
|
|
324
|
+
children: React.isValidElement(children) ? React.cloneElement(children, { ...baseControl }) : children,
|
|
325
|
+
} as FairysValtioFormItemAttrsReturn<T>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface FairysValtioFormItemAttrsReturn<T extends MObject<T> = object> {
|
|
329
|
+
/**表单项值*/
|
|
330
|
+
value?: any;
|
|
331
|
+
/**是否校验错误*/
|
|
332
|
+
isInvalid?: boolean;
|
|
333
|
+
/**边框类型*/
|
|
334
|
+
borderedType?: FairysValtioFormLayoutContextOptions['borderedType'];
|
|
335
|
+
/**值改变事件*/
|
|
336
|
+
onValueChange?: (event: any) => void;
|
|
337
|
+
/**当前表单项占据列数*/
|
|
338
|
+
colSpan?: number;
|
|
339
|
+
/**当前表单项占据行数*/
|
|
340
|
+
rowSpan?: number;
|
|
341
|
+
/**列数*/
|
|
342
|
+
colCount?: number;
|
|
343
|
+
/**标签显示模式*/
|
|
344
|
+
labelMode?: FairysValtioFormLayoutContextOptions['labelMode'];
|
|
345
|
+
/**错误提示位置*/
|
|
346
|
+
errorLayout?: FairysValtioFormLayoutContextOptions['errorLayout'];
|
|
347
|
+
/**是否必填*/
|
|
348
|
+
isRequired?: boolean;
|
|
349
|
+
/**表单状态*/
|
|
350
|
+
state?: T;
|
|
351
|
+
/**错误状态*/
|
|
352
|
+
errorState?: Record<PropertyKey, string[]>;
|
|
353
|
+
/**表单实例*/
|
|
354
|
+
formInstance?: FairysValtioFormInstance<T>;
|
|
355
|
+
/**错误信息*/
|
|
356
|
+
error?: string[];
|
|
357
|
+
// =========================================
|
|
358
|
+
/**表单项类名*/
|
|
359
|
+
itemClassName: string;
|
|
360
|
+
/**表单项样式*/
|
|
361
|
+
itemStyle: React.CSSProperties;
|
|
362
|
+
/**容器类名*/
|
|
363
|
+
containerClassName: string;
|
|
364
|
+
/**标签类名*/
|
|
365
|
+
itemLabelClassName: string;
|
|
366
|
+
/**标签样式*/
|
|
367
|
+
itemLabelStyle: React.CSSProperties;
|
|
368
|
+
/**体类名*/
|
|
369
|
+
itemBodyClassName: string;
|
|
370
|
+
/**体样式*/
|
|
371
|
+
itemBodyStyle: React.CSSProperties;
|
|
372
|
+
/**输入框类名*/
|
|
373
|
+
itemInputClassName: string;
|
|
374
|
+
/**额外内容类名*/
|
|
375
|
+
itemExtraClassName: string;
|
|
376
|
+
/**错误提示类名*/
|
|
377
|
+
errorClassName: string;
|
|
378
|
+
/**帮助提示类名*/
|
|
379
|
+
helpClassName: string;
|
|
380
|
+
/**子元素*/
|
|
381
|
+
children?: React.ReactNode;
|
|
382
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MObject } from 'interface';
|
|
2
|
+
import { FairysValtioFormInstance, useFairysValtioFormInstance } from './instance';
|
|
3
|
+
import { useMemo, type ReactNode } from 'react';
|
|
4
|
+
import { FairysValtioFormLayoutAttrsProps } from './layout';
|
|
5
|
+
|
|
6
|
+
export interface FairysValtioFormAttrsProps<T extends MObject<T> = object> extends FairysValtioFormLayoutAttrsProps {
|
|
7
|
+
/**表单实例*/
|
|
8
|
+
form?: FairysValtioFormInstance<T>;
|
|
9
|
+
/**子元素*/
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/**表单项规则*/
|
|
12
|
+
rules?: FairysValtioFormInstance<T>['rules'];
|
|
13
|
+
/**表单初始值*/
|
|
14
|
+
formData?: FairysValtioFormInstance<T>['state'];
|
|
15
|
+
/**表单隐藏状态*/
|
|
16
|
+
hideState?: FairysValtioFormInstance<T>['hideState'];
|
|
17
|
+
/**formData 是否进行深度拷贝,如果不是则直接把 formData 赋值到 state ,否则使用 copy 方法深度拷贝后赋值 */
|
|
18
|
+
isDeepCopy?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 表单属性处理
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
import { useFairysValtioForm } from "@fairys/valtio-form"
|
|
28
|
+
import type { FairysValtioFormAttrProps } from "@fairys/valtio-form"
|
|
29
|
+
export interface FormProps extends FairysValtioFormAttrProps{}
|
|
30
|
+
|
|
31
|
+
export const Form = (props: FormProps) => {
|
|
32
|
+
const { formInstance,children, ...rest } = useFairysValtioForm(props)
|
|
33
|
+
return (
|
|
34
|
+
<FairysValtioFormInstanceContext.Provider value={formInstance}>
|
|
35
|
+
<布局组件 {...rest}>{children}</布局组件>
|
|
36
|
+
</FairysValtioFormInstanceContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function useFairysValtioForm<T extends MObject<T> = object>(props: FairysValtioFormAttrsProps<T>) {
|
|
42
|
+
const { form, rules, formData, hideState, isDeepCopy = true, ...rest } = props;
|
|
43
|
+
const formInstance = useFairysValtioFormInstance(form);
|
|
44
|
+
/**表单规则*/
|
|
45
|
+
formInstance.rules = rules;
|
|
46
|
+
/**初始化表单值*/
|
|
47
|
+
useMemo(() => formInstance.ctor({ formData, hideState, isDeepCopy }), []);
|
|
48
|
+
return {
|
|
49
|
+
...rest,
|
|
50
|
+
formInstance,
|
|
51
|
+
};
|
|
52
|
+
// return (
|
|
53
|
+
// <FairysValtioFormInstanceContext.Provider value={formInstance}>
|
|
54
|
+
// <FairysValtioFormLayout {...rest}>{children}</FairysValtioFormLayout>
|
|
55
|
+
// </FairysValtioFormInstanceContext.Provider>
|
|
56
|
+
// );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// /**初始化实例*/
|
|
60
|
+
// FairysValtioForm.useForm = useFairysValtioFormInstance;
|
|
61
|
+
// /**获取状态*/
|
|
62
|
+
// FairysValtioForm.useFormState = useFairysValtioFormInstanceContextState;
|
|
63
|
+
// /**获取隐藏状态*/
|
|
64
|
+
// FairysValtioForm.useFormHideState = useFairysValtioFormInstanceContextHideState;
|
|
65
|
+
// /**获取上下文实例*/
|
|
66
|
+
// FairysValtioForm.useFormInstance = useFairysValtioFormInstanceContext;
|
|
67
|
+
// /**表单项*/
|
|
68
|
+
// FairysValtioForm.useFormItemAttrs = useFairysValtioFormItemAttrs;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { MObject } from 'interface';
|
|
2
|
+
import { createContext, useContext, useRef } from 'react';
|
|
3
|
+
import { proxy, ref, snapshot, useSnapshot } from 'valtio';
|
|
4
|
+
import AsyncValidator, { RuleItem, ValidateFieldsError, Values } from 'async-validator';
|
|
5
|
+
import { copy } from 'fast-copy';
|
|
6
|
+
|
|
7
|
+
/**表单实例*/
|
|
8
|
+
export class FairysValtioFormInstance<T extends MObject<T> = Record<string, any>> {
|
|
9
|
+
/**状态*/
|
|
10
|
+
state = proxy<T>({} as T);
|
|
11
|
+
/**
|
|
12
|
+
* 错误信息
|
|
13
|
+
*/
|
|
14
|
+
errorState = proxy<Record<PropertyKey, string[]>>({});
|
|
15
|
+
/**隐藏状态*/
|
|
16
|
+
hideState = proxy<Record<PropertyKey, boolean>>({});
|
|
17
|
+
/**初始化表单值*/
|
|
18
|
+
ctor = (options?: { formData?: Partial<T>; hideState?: Record<PropertyKey, boolean>; isDeepCopy?: boolean }) => {
|
|
19
|
+
const { formData, hideState, isDeepCopy = true } = options || {};
|
|
20
|
+
// 如果是 isProxy,则直接赋值
|
|
21
|
+
this.errorState = proxy<Record<PropertyKey, string[]>>({});
|
|
22
|
+
this.hideState = proxy<Record<PropertyKey, boolean>>(hideState ? copy(hideState) : {});
|
|
23
|
+
if (isDeepCopy) {
|
|
24
|
+
this.state = proxy((formData ? copy(formData) : {}) as T);
|
|
25
|
+
} else if (formData) {
|
|
26
|
+
this.state = proxy(formData as T);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 更新数据
|
|
31
|
+
* @param state 更新数据对象
|
|
32
|
+
* @param isValidate 是否验证(可选)
|
|
33
|
+
*/
|
|
34
|
+
updated = <M = T>(state: Partial<M>, isValidate: boolean = true) => {
|
|
35
|
+
const keys = Object.keys(state);
|
|
36
|
+
for (let index = 0; index < keys.length; index++) {
|
|
37
|
+
const key = keys[index];
|
|
38
|
+
this.state[key] = state[key];
|
|
39
|
+
}
|
|
40
|
+
if (isValidate) {
|
|
41
|
+
this.validate(keys, false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ===================================================隐藏状态================================================================
|
|
46
|
+
/**
|
|
47
|
+
* 更新行数据的隐藏信息
|
|
48
|
+
* @param objectHideInfo 行数据隐藏信息对象
|
|
49
|
+
*/
|
|
50
|
+
updatedHideInfo = (objectHideInfo: Record<PropertyKey, boolean>) => {
|
|
51
|
+
const keys = Object.keys(objectHideInfo);
|
|
52
|
+
for (let index = 0; index < keys.length; index++) {
|
|
53
|
+
const field = keys[index];
|
|
54
|
+
this.hideState[field] = objectHideInfo[field];
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* 清理隐藏信息
|
|
60
|
+
*/
|
|
61
|
+
clearHideInfo = (fields?: PropertyKey[]) => {
|
|
62
|
+
let _fields = fields;
|
|
63
|
+
if (!Array.isArray(fields)) {
|
|
64
|
+
_fields = Object.keys(this.hideState) as PropertyKey[];
|
|
65
|
+
}
|
|
66
|
+
for (let index = 0; index < _fields.length; index++) {
|
|
67
|
+
const field = _fields[index];
|
|
68
|
+
delete this.hideState[field];
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
};
|
|
72
|
+
// ===================================================隐藏状态===================================================================
|
|
73
|
+
|
|
74
|
+
// ===================================================错误信息处理================================================================
|
|
75
|
+
/**
|
|
76
|
+
* 更新行数据的错误信息
|
|
77
|
+
* @param objectErrorInfo 行数据错误信息对象
|
|
78
|
+
*/
|
|
79
|
+
updatedErrorInfo = (objectErrorInfo: Record<PropertyKey, string[]>) => {
|
|
80
|
+
const keys = Object.keys(objectErrorInfo);
|
|
81
|
+
for (let index = 0; index < keys.length; index++) {
|
|
82
|
+
const field = keys[index];
|
|
83
|
+
this.errorState[field] = objectErrorInfo[field];
|
|
84
|
+
}
|
|
85
|
+
return this;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* 清理错误信息
|
|
89
|
+
*/
|
|
90
|
+
clearErrorInfo = (fields?: PropertyKey[]) => {
|
|
91
|
+
let _fields = fields;
|
|
92
|
+
if (!Array.isArray(fields)) {
|
|
93
|
+
_fields = Object.keys(this.errorState) as PropertyKey[];
|
|
94
|
+
}
|
|
95
|
+
for (let index = 0; index < _fields.length; index++) {
|
|
96
|
+
const field = _fields[index];
|
|
97
|
+
delete this.errorState[field];
|
|
98
|
+
}
|
|
99
|
+
return this;
|
|
100
|
+
};
|
|
101
|
+
// ===================================================错误信息处理================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 清理所有数据
|
|
105
|
+
*/
|
|
106
|
+
clear = () => {
|
|
107
|
+
this.state = proxy<T>({} as T);
|
|
108
|
+
this.errorState = proxy<Record<PropertyKey, string[]>>({});
|
|
109
|
+
this.hideState = proxy<Record<PropertyKey, boolean>>({});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ===================================================规则处理================================================================
|
|
113
|
+
/**列规则 */
|
|
114
|
+
rules: Record<
|
|
115
|
+
PropertyKey,
|
|
116
|
+
((formData: T, instance: FairysValtioFormInstance<T>) => RuleItem[] | Promise<RuleItem[]>) | RuleItem[]
|
|
117
|
+
> = {};
|
|
118
|
+
/**规则验证
|
|
119
|
+
* @param fields 列字段数组(可选)
|
|
120
|
+
* @param isReturn 是否返回验证结果(可选)
|
|
121
|
+
*/
|
|
122
|
+
validate = async (fields?: PropertyKey[], isReturn: boolean = true): Promise<ValidateFieldsError | Values> => {
|
|
123
|
+
let _fields = fields;
|
|
124
|
+
const _formData = snapshot(this.state) as T;
|
|
125
|
+
// 没有规则,直接返回数据
|
|
126
|
+
if (!this.rules) {
|
|
127
|
+
return Promise.resolve({ ..._formData });
|
|
128
|
+
}
|
|
129
|
+
const rules: Record<PropertyKey, RuleItem[]> = {};
|
|
130
|
+
let isNeedValidate = false;
|
|
131
|
+
// 没有 fields 值,验证所有
|
|
132
|
+
if (!fields || (Array.isArray(fields) && fields.length === 0)) {
|
|
133
|
+
_fields = Object.keys(this.rules);
|
|
134
|
+
}
|
|
135
|
+
for (let index = 0; index < _fields.length; index++) {
|
|
136
|
+
isNeedValidate = true;
|
|
137
|
+
const element = _fields[index];
|
|
138
|
+
const rule = this.rules[element];
|
|
139
|
+
if (typeof rule === 'function') {
|
|
140
|
+
const _rules = await rule(_formData, this);
|
|
141
|
+
rules[element] = _rules;
|
|
142
|
+
} else if (Array.isArray(rule)) {
|
|
143
|
+
rules[element] = rule;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!isNeedValidate) {
|
|
147
|
+
console.warn('no rules to validate');
|
|
148
|
+
return Promise.resolve({ ..._formData });
|
|
149
|
+
}
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
new AsyncValidator({ ...rules }).validate({ ..._formData }, (errors, fields) => {
|
|
152
|
+
for (let index = 0; index < _fields.length; index++) {
|
|
153
|
+
const field = _fields[index];
|
|
154
|
+
const fidError = Array.isArray(errors) ? errors.filter((item) => item.field === field) : undefined;
|
|
155
|
+
if (fidError) {
|
|
156
|
+
this.errorState[field] = ref(fidError.map((item) => item.message || ''));
|
|
157
|
+
} else {
|
|
158
|
+
delete this.errorState[field];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (isReturn) {
|
|
162
|
+
if (errors) {
|
|
163
|
+
reject({ errors, fields });
|
|
164
|
+
} else {
|
|
165
|
+
resolve(fields);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**声明实例*/
|
|
174
|
+
export function useFairysValtioFormInstance<T extends MObject<T> = object>(instance?: FairysValtioFormInstance<T>) {
|
|
175
|
+
const ref = useRef<FairysValtioFormInstance<T>>();
|
|
176
|
+
if (!ref.current) {
|
|
177
|
+
if (instance) {
|
|
178
|
+
ref.current = instance;
|
|
179
|
+
} else {
|
|
180
|
+
ref.current = new FairysValtioFormInstance<T>();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return ref.current;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**表单实例上下文*/
|
|
187
|
+
export const FairysValtioFormInstanceContext = createContext<FairysValtioFormInstance<any>>(
|
|
188
|
+
new FairysValtioFormInstance<any>(),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
/**表单实例上下文*/
|
|
192
|
+
export function useFairysValtioFormInstanceContext<T extends MObject<T> = object>() {
|
|
193
|
+
return useContext(FairysValtioFormInstanceContext) as FairysValtioFormInstance<T>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**状态取值*/
|
|
197
|
+
export function useFairysValtioFormInstanceContextState<T extends MObject<T> = object>() {
|
|
198
|
+
const instance = useFairysValtioFormInstanceContext<T>();
|
|
199
|
+
const state = useSnapshot(instance.state) as T;
|
|
200
|
+
const errorState = useSnapshot(instance.errorState) as Record<PropertyKey, string[]>;
|
|
201
|
+
return [state, errorState, instance, (state as any).__defaultValue, errorState.__defaultValue] as [
|
|
202
|
+
T,
|
|
203
|
+
Record<PropertyKey, string[]>,
|
|
204
|
+
FairysValtioFormInstance<T>,
|
|
205
|
+
any,
|
|
206
|
+
any,
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**隐藏组件状态取值*/
|
|
211
|
+
export function useFairysValtioFormInstanceContextHideState<T extends MObject<T> = object>() {
|
|
212
|
+
const instance = useFairysValtioFormInstanceContext<T>();
|
|
213
|
+
const hideState = useSnapshot(instance.hideState) as Record<PropertyKey, boolean>;
|
|
214
|
+
return [hideState, instance, (hideState as any).__defaultValue] as [
|
|
215
|
+
Record<PropertyKey, boolean>,
|
|
216
|
+
FairysValtioFormInstance<T>,
|
|
217
|
+
any,
|
|
218
|
+
];
|
|
219
|
+
}
|