= {
37 | string: { type: 'string', whitespace: true },
38 | pureString: { type: 'string' },
39 | number: { type: 'number' },
40 | array: { type: 'array' },
41 | };
42 |
43 | const getRules = (rules: PlusShortRule[]): FormRule[] => {
44 | const ruleList: FormRule[] = [];
45 | let numTypeRule: FormRule | null = null;
46 | let numValRules: FormRule[] = [];
47 |
48 | rules.forEach((rule) => {
49 | if (typeof rule !== 'string') {
50 | ruleList.push(rule);
51 | return;
52 | }
53 |
54 | if (rule === 'required') {
55 | ruleList.push({ required: true });
56 | return;
57 | }
58 |
59 | if (rule in miscTypeMap) {
60 | ruleList.push(miscTypeMap[rule]);
61 | return;
62 | }
63 |
64 | if (rule in numTypeMap) {
65 | numTypeRule = numTypeMap[rule];
66 | return;
67 | }
68 |
69 | const [key, val] = rule.split('=');
70 |
71 | if (val === undefined) {
72 | return;
73 | }
74 |
75 | if (key === 'required') {
76 | ruleList.push({ required: true, message: val });
77 | return;
78 | }
79 |
80 | if (key === 'len' || key === 'min' || key === 'max') {
81 | numValRules.push({ [key]: +val });
82 | return;
83 | }
84 | });
85 |
86 | if (numTypeRule && numValRules.length > 0) {
87 | ruleList.push(...numValRules.map((obj) => ({ ...numTypeRule, ...obj })));
88 | } else if (numTypeRule) {
89 | ruleList.push(numTypeRule);
90 | } else if (numValRules.length > 0) {
91 | ruleList.push(...numValRules);
92 | }
93 |
94 | return ruleList;
95 | };
96 |
97 | // ─── Form.Item & Field ↓↓↓ ───────────────────────────────────────────────────
98 | type ConflictProps = 'className' | 'style' | 'name' | 'tooltip';
99 |
100 | type ReplaceProps = {
101 | selfClass?: SliderSingleProps['className'];
102 | selfStyle?: SliderSingleProps['style'];
103 | selfName?: string;
104 | selfTooltip?: SliderSingleProps['tooltip'];
105 | };
106 |
107 | export type PlusProps = Omit
&
108 | ReplaceProps &
109 | Omit & { rules?: PlusShortRule[] };
110 |
111 | const replaceMap: Record = {
112 | selfClass: 'className',
113 | selfStyle: 'style',
114 | selfName: 'name',
115 | selfTooltip: 'tooltip',
116 | };
117 |
118 | const formItemKeys = [
119 | 'colon',
120 | 'dependencies',
121 | 'extra',
122 | 'getValueFromEvent',
123 | 'getValueProps',
124 | 'hasFeedback',
125 | 'help',
126 | 'hidden',
127 | 'htmlFor',
128 | 'initialValue',
129 | 'label',
130 | 'labelAlign',
131 | 'labelCol',
132 | 'messageVariables',
133 | 'name',
134 | 'normalize',
135 | 'noStyle',
136 | 'preserve',
137 | 'required',
138 | 'rules',
139 | 'shouldUpdate',
140 | 'tooltip',
141 | 'trigger',
142 | 'validateFirst',
143 | 'validateStatus',
144 | 'validateTrigger',
145 | 'valuePropName',
146 | 'wrapperCol',
147 |
148 | // extra
149 | 'className',
150 | 'style',
151 | ] as (keyof FormItemProps)[];
152 |
153 | const formItemProps = formItemKeys.reduce(
154 | (obj, key) => {
155 | obj[key] = true;
156 | return obj;
157 | },
158 | {} as Record,
159 | );
160 |
161 | const create = >(
162 | Field: T,
163 | getDefaultFieldProps?: (p: ComponentProps) => Partial>,
164 | ) => {
165 | type P = ComponentProps;
166 | type Props = PropsWithoutRef>;
167 |
168 | const ItemField = forwardRef((props: Props, ref: ForwardedRef) => {
169 | const itemProps: FormItemProps = {};
170 | const fieldProps: P = {} as P;
171 |
172 | // split props
173 | if (props) {
174 | Object.keys(props).forEach((key) => {
175 | const val = props[key as keyof Props];
176 |
177 | if (key in formItemProps) {
178 | const itemKey = key as keyof FormItemProps;
179 | itemProps[itemKey] = key === 'rules' && val ? getRules(val) : val;
180 | } else {
181 | const fieldKey = replaceMap[key as keyof ReplaceProps] || key;
182 | fieldProps[fieldKey as keyof P] = val;
183 | }
184 | });
185 | }
186 |
187 | // default field props
188 | if (getDefaultFieldProps) {
189 | const extraFieldProps = getDefaultFieldProps(fieldProps);
190 |
191 | Object.keys(extraFieldProps).forEach((key) => {
192 | if (!(key in fieldProps)) {
193 | fieldProps[key as keyof P] = extraFieldProps[key] as P[keyof P];
194 | }
195 | });
196 | }
197 |
198 | const RawField = Field as ComponentType;
199 |
200 | return (
201 | -
202 |
203 |
204 | );
205 | });
206 |
207 | Object.keys(Field).forEach((key) => {
208 | if (!(key in ItemField)) {
209 | (ItemField as any)[key] = (Field as any)[key];
210 | }
211 | });
212 |
213 | return ItemField as Omit & typeof ItemField;
214 | };
215 |
216 | export default create;
217 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import type {
2 | AutoCompleteProps as AntAutoCompleteProps,
3 | CascaderProps as AntCascaderProps,
4 | CheckboxProps as AntCheckboxProps,
5 | DatePickerProps as AntDatePickerProps,
6 | InputNumberProps as AntInputNumberProps,
7 | InputProps as AntInputProps,
8 | MentionProps as AntMentionProps,
9 | RadioGroupProps as AntRadioGroupProps,
10 | RadioProps as AntRadioProps,
11 | RateProps as AntRateProps,
12 | SelectProps as AntSelectProps,
13 | SliderSingleProps as AntSliderSingleProps,
14 | SwitchProps as AntSwitchProps,
15 | TimePickerProps as AntTimePickerProps,
16 | TimeRangePickerProps as AntTimeRangePickerProps,
17 | TransferProps as AntTransferProps,
18 | TreeSelectProps as AntTreeSelectProps,
19 | UploadProps as AntUploadProps,
20 | } from 'antd';
21 | import {
22 | AutoComplete as AntAutoComplete,
23 | Cascader as AntCascader,
24 | Checkbox as AntCheckbox,
25 | DatePicker as AntDatePicker,
26 | Input as AntInput,
27 | InputNumber as AntInputNumber,
28 | Mentions as AntMentions,
29 | Radio as AntRadio,
30 | Rate as AntRate,
31 | Select as AntSelect,
32 | Slider as AntSlider,
33 | Switch as AntSwitch,
34 | TimePicker as AntTimePicker,
35 | Transfer as AntTransfer,
36 | TreeSelect as AntTreeSelect,
37 | Upload as AntUpload,
38 | } from 'antd';
39 | import type { CheckboxGroupProps as AntCheckboxGroupProps } from 'antd/es/checkbox';
40 | import type { RangePickerProps as AntRangePickerProps } from 'antd/es/date-picker';
41 | import type {
42 | PasswordProps as AntPasswordProps,
43 | SearchProps as AntSearchProps,
44 | TextAreaProps as AntTextAreaProps,
45 | } from 'antd/es/input';
46 | import type { MentionsRef } from 'antd/es/mentions';
47 | import type { DraggerProps as AntDraggerProps } from 'antd/es/upload';
48 | import * as React from 'react';
49 | import type { PlusProps } from './create';
50 | import create from './create';
51 |
52 | // mentions
53 | interface MentionsConfig {
54 | prefix?: string | string[];
55 | split?: string;
56 | }
57 | interface MentionsEntity {
58 | prefix: string;
59 | value: string;
60 | }
61 | type CompoundedComponent = React.ForwardRefExoticComponent<
62 | AntMentionProps & React.RefAttributes
63 | > & {
64 | Option: typeof Option;
65 | _InternalPanelDoNotUseOrYouWillBeFired: any;
66 | getMentions: (value: string, config?: MentionsConfig) => MentionsEntity[];
67 | };
68 |
69 | /* create
70 | ---------------------------------------------------------------------- */
71 | export { default as create } from './create';
72 | export type { PlusProps, PlusShortRule } from './create';
73 |
74 | /* custom
75 | ---------------------------------------------------------------------- */
76 | export type { WatchProps } from './Watch';
77 | export type { WrapperColProps } from './WrapperCol';
78 |
79 | export { default as Watch } from './Watch';
80 | export { default as WrapperCol } from './WrapperCol';
81 |
82 | /* 1st
83 | ---------------------------------------------------------------------- */
84 | export type AutoCompleteProps = PlusProps;
85 | export type CascaderProps = PlusProps;
86 | export type CheckboxProps = PlusProps;
87 | export type DatePickerProps = PlusProps;
88 | export type InputProps = PlusProps;
89 | export type InputNumberProps = PlusProps;
90 | export type MentionProps = PlusProps;
91 | export type RadioProps = PlusProps;
92 | export type RateProps = PlusProps;
93 | export type SelectProps = PlusProps;
94 | export type SliderProps = PlusProps;
95 | export type SwitchProps = PlusProps;
96 | export type TimePickerProps = PlusProps;
97 | export type TransferProps = PlusProps>;
98 | export type TreeSelectProps = PlusProps;
99 | export type UploadProps = PlusProps;
100 |
101 | export const AutoComplete = create(AntAutoComplete);
102 | export const Cascader = create(AntCascader);
103 | export const Checkbox = create(AntCheckbox);
104 | export const DatePicker = create(AntDatePicker);
105 | export const Input = create(AntInput);
106 | export const InputNumber = create(AntInputNumber);
107 | export const Mentions = create(AntMentions as unknown as CompoundedComponent);
108 | export const Radio = create(AntRadio);
109 | export const Rate = create(AntRate);
110 | export const Select = create(AntSelect);
111 | export const Slider = create(AntSlider);
112 | export const Switch = create(AntSwitch);
113 | export const TimePicker = create(AntTimePicker);
114 | export const Transfer = create(AntTransfer);
115 | export const TreeSelect = create(AntTreeSelect);
116 | export const Upload = create(AntUpload);
117 |
118 | /* 2nd
119 | ---------------------------------------------------------------------- */
120 | export type CheckboxGroupProps = PlusProps;
121 | export type DateRangeProps = PlusProps;
122 | export type TextAreaProps = PlusProps;
123 | export type SearchProps = PlusProps;
124 | export type PasswordProps = PlusProps;
125 | export type RadioGroupProps = PlusProps;
126 | export type TimeRangeProps = PlusProps;
127 | export type DraggerProps = PlusProps;
128 |
129 | export const CheckboxGroup = create(AntCheckbox.Group);
130 | export const DateRange = create(AntDatePicker.RangePicker);
131 | export const TextArea = create(AntInput.TextArea);
132 | export const Search = create(AntInput.Search);
133 | export const Password = create(AntInput.Password);
134 | export const RadioGroup = create(AntRadio.Group);
135 | export const TimeRange = create(AntTimePicker.RangePicker);
136 | export const Dragger = create(AntUpload.Dragger);
137 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "noFallthroughCasesInSwitch": true,
5 | "noImplicitAny": true,
6 | "noImplicitOverride": true,
7 | "noImplicitReturns": false,
8 | "noImplicitThis": true,
9 | "noUnusedLocals": true,
10 | "noUnusedParameters": true,
11 | "strict": true,
12 | "strictBindCallApply": true,
13 | "strictFunctionTypes": true,
14 | "strictNullChecks": true,
15 | "strictPropertyInitialization": true,
16 |
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "resolveJsonModule": true,
20 |
21 | "outDir": "dist",
22 |
23 | "allowSyntheticDefaultImports": true,
24 | "esModuleInterop": true,
25 | "forceConsistentCasingInFileNames": true,
26 | "isolatedModules": true,
27 |
28 | "jsx": "react-jsx",
29 | "target": "es6"
30 | },
31 | "exclude": ["node_modules", "dist"]
32 | }
33 |
--------------------------------------------------------------------------------