@hzab/form-render-mobile 0.4.7 → 0.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzab/form-render-mobile",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "description": "formily-form-render-mobile",
5
5
  "main": "lib",
6
6
  "scripts": {
@@ -0,0 +1,47 @@
1
+ .formily-form-item {
2
+ margin-bottom: 2vw;
3
+ &:last-child {
4
+ margin-bottom: unset;
5
+ }
6
+ .form-item-label {
7
+ display: flex;
8
+ justify-content: flex-start;
9
+ align-items: center;
10
+ .form-item-label-text {
11
+ white-space: pre;
12
+ overflow: hidden;
13
+ text-overflow: ellipsis;
14
+ }
15
+ &.item-label-wrap {
16
+ .form-item-label-text {
17
+ white-space: pre-line;
18
+ word-break: break-all;
19
+ overflow: unset;
20
+ text-overflow: unset;
21
+ }
22
+ }
23
+ &.label-align-right {
24
+ justify-content: flex-end;
25
+ text-align: right;
26
+ }
27
+ }
28
+ .form-item-content {
29
+ flex: 1;
30
+ color: #808080;
31
+ }
32
+ &.layout-vertical {
33
+ align-items: flex-start;
34
+ flex-direction: column;
35
+ margin-bottom: 4vw;
36
+ &:last-child {
37
+ margin-bottom: unset;
38
+ }
39
+ }
40
+ &.layout-between {
41
+ justify-content: space-between;
42
+ .form-item-content {
43
+ flex: unset;
44
+ text-align: right;
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,256 @@
1
+ import { isVoidField } from "@formily/core";
2
+ import { connect, mapProps, useFieldSchema, useField } from "@formily/react";
3
+ import { useFormLayout } from "../FormLayout";
4
+ import { Popover, Grid } from "antd-mobile";
5
+ import {
6
+ QuestionCircleOutline,
7
+ CloseCircleOutline,
8
+ CheckCircleOutline,
9
+ ExclamationCircleOutline,
10
+ } from "antd-mobile-icons";
11
+ import { useGlobalPropsContext } from "../../common/global-props-context";
12
+
13
+ import "./index.less";
14
+
15
+ export interface IFormItemProps {
16
+ className?: string;
17
+ style?: React.CSSProperties;
18
+ prefixCls?: string;
19
+ label?: React.ReactNode;
20
+ colon?: boolean;
21
+ tooltip?: React.ReactNode;
22
+ tooltipIcon?: React.ReactNode;
23
+ layout?: "vertical" | "horizontal" | "inline";
24
+ tooltipLayout?: "icon" | "text";
25
+ labelStyle?: React.CSSProperties;
26
+ labelAlign?: "left" | "right";
27
+ labelFor?: string;
28
+ labelWrap?: boolean;
29
+ labelWidth?: number | string;
30
+ wrapperWidth?: number | string;
31
+ labelCol?: number;
32
+ wrapperCol?: number;
33
+ wrapperAlign?: "left" | "right";
34
+ wrapperWrap?: boolean;
35
+ wrapperStyle?: React.CSSProperties;
36
+ fullness?: boolean;
37
+ addonBefore?: React.ReactNode;
38
+ addonAfter?: React.ReactNode;
39
+ size?: "small" | "default" | "large";
40
+ inset?: boolean;
41
+ extra?: React.ReactNode;
42
+ feedbackText?: React.ReactNode;
43
+ feedbackLayout?: "loose" | "terse" | "popover" | "none" | (string & {});
44
+ feedbackStatus?: "error" | "warning" | "success" | "pending" | (string & {});
45
+ feedbackIcon?: React.ReactNode;
46
+ enableOutlineFeedback?: boolean;
47
+ getPopupContainer?: (node: HTMLElement) => HTMLElement;
48
+ asterisk?: boolean;
49
+ optionalMarkHidden?: boolean;
50
+ gridSpan?: number;
51
+ bordered?: boolean;
52
+ }
53
+
54
+ type ComposeFormItem = React.FC<React.PropsWithChildren<IFormItemProps>> & {
55
+ BaseItem?: React.FC<React.PropsWithChildren<IFormItemProps>>;
56
+ };
57
+
58
+ const ICON_MAP = {
59
+ error: <CloseCircleOutline />,
60
+ success: <CheckCircleOutline />,
61
+ warning: <ExclamationCircleOutline />,
62
+ };
63
+
64
+ const useFormItemLayout = (props: IFormItemProps) => {
65
+ const layout = useFormLayout();
66
+ const layoutType = props.layout ?? layout.layout ?? "horizontal";
67
+ return {
68
+ ...props,
69
+ ...layout,
70
+ layout: layoutType,
71
+ labelAlign:
72
+ layoutType === "vertical" ? props.labelAlign ?? "left" : props.labelAlign ?? layout.labelAlign ?? "left",
73
+ labelWrap: props.labelWrap ?? layout.labelWrap,
74
+ labelWidth: props.labelWidth ?? layout.labelWidth,
75
+ wrapperWidth: props.wrapperWidth ?? layout.wrapperWidth,
76
+ wrapperAlign: props.wrapperAlign ?? layout.wrapperAlign,
77
+ wrapperWrap: props.wrapperWrap ?? layout.wrapperWrap,
78
+ size: props.size ?? layout.size,
79
+ inset: props.inset ?? layout.inset,
80
+ asterisk: props.asterisk,
81
+ optionalMarkHidden: props.optionalMarkHidden,
82
+ bordered: props.bordered ?? layout.bordered,
83
+ feedbackIcon: props.feedbackIcon,
84
+ feedbackLayout: props.feedbackLayout ?? layout.feedbackLayout ?? "loose",
85
+ tooltipLayout: props.tooltipLayout ?? layout.tooltipLayout ?? "icon",
86
+ tooltipIcon: props.tooltipIcon ?? layout.tooltipIcon ?? <QuestionCircleOutline />,
87
+ };
88
+ };
89
+
90
+ export const BaseItem = ({ children, ...props }) => {
91
+ // 组件外部传入的 props
92
+ const globalProps = useGlobalPropsContext();
93
+ const field = useField();
94
+ const { title } = field;
95
+ const { colon = ":", LabelSlots = {}, Slots = {} } = globalProps;
96
+ const schema = useFieldSchema();
97
+ const { name } = schema;
98
+
99
+ const formLayout = useFormItemLayout({ ...props });
100
+
101
+ const {
102
+ label,
103
+ style,
104
+ layout,
105
+ addonBefore,
106
+ addonAfter,
107
+ asterisk,
108
+ optionalMarkHidden = false,
109
+ feedbackStatus,
110
+ extra,
111
+ feedbackText,
112
+ fullness,
113
+ feedbackLayout,
114
+ feedbackIcon,
115
+ enableOutlineFeedback = true,
116
+ getPopupContainer,
117
+ inset,
118
+ bordered = true,
119
+ labelWidth,
120
+ wrapperWidth,
121
+ labelCol,
122
+ wrapperCol,
123
+ labelAlign,
124
+ wrapperAlign = "left",
125
+ size,
126
+ labelWrap,
127
+ wrapperWrap,
128
+ tooltipLayout,
129
+ tooltip,
130
+ tooltipIcon,
131
+ } = formLayout;
132
+
133
+ const labelStyle = { ...formLayout.labelStyle };
134
+ const wrapperStyle = { ...formLayout.wrapperStyle };
135
+
136
+ if (labelWidth || wrapperWidth) {
137
+ if (labelWidth) {
138
+ labelStyle.width = labelWidth === "auto" ? undefined : labelWidth;
139
+ labelStyle.maxWidth = labelWidth === "auto" ? undefined : labelWidth;
140
+ }
141
+ if (wrapperWidth) {
142
+ wrapperStyle.width = wrapperWidth === "auto" ? undefined : wrapperWidth;
143
+ wrapperStyle.maxWidth = wrapperWidth === "auto" ? undefined : wrapperWidth;
144
+ }
145
+ // 栅格模式
146
+ }
147
+
148
+ let formatChildren =
149
+ feedbackLayout === "popover" ? (
150
+ <Popover
151
+ placement="top"
152
+ content={
153
+ <div
154
+ // className={cls({
155
+ // [`${prefixCls}-${feedbackStatus}-help`]: !!feedbackStatus,
156
+ // [`${prefixCls}-help`]: true,
157
+ // })}
158
+ >
159
+ {ICON_MAP[feedbackStatus]} {feedbackText}
160
+ </div>
161
+ }
162
+ visible={!!feedbackText}
163
+ // getPopupContainer={getPopupContainer}
164
+ >
165
+ {children}
166
+ </Popover>
167
+ ) : (
168
+ children
169
+ );
170
+
171
+ if (Slots && Slots[name]) {
172
+ const Com = Slots[name];
173
+ // 获取数据
174
+ // @ts-ignore
175
+ const value = field?.value;
176
+ formatChildren = (
177
+ <Com {...props} value={value} data={value} field={field} itemSchema={schema} globalProps={globalProps} />
178
+ );
179
+ }
180
+
181
+ let renderLabelText = () => <div className="form-item-label-text">{title}</div>;
182
+
183
+ if (LabelSlots && LabelSlots[name]) {
184
+ const Com = LabelSlots[name];
185
+ renderLabelText = () => <Com label={props.label} />;
186
+ }
187
+
188
+ return (
189
+ <Grid columns={24} className={`formily-form-item layout-${layout}`}>
190
+ <Grid.Item span={labelCol}>
191
+ {title && (
192
+ <div
193
+ className={`form-item-label ${labelWrap ? "item-label-wrap" : ""} label-align-${labelAlign}`}
194
+ style={labelStyle}
195
+ >
196
+ {renderLabelText()}
197
+ {colon ? <span className="form-item-colon">{colon || ":"}</span> : null}
198
+ </div>
199
+ )}
200
+ </Grid.Item>
201
+ <Grid.Item className="form-item-content" span={wrapperCol}>
202
+ {formatChildren}
203
+ </Grid.Item>
204
+ </Grid>
205
+ );
206
+ };
207
+
208
+ // 适配
209
+ export const FormItem: any = connect(
210
+ BaseItem,
211
+ mapProps((props: any, field: any) => {
212
+ if (isVoidField(field))
213
+ return {
214
+ label: field.title || props.label,
215
+ asterisk: props.asterisk,
216
+ extra: props.extra || field.description,
217
+ };
218
+ if (!field) return props;
219
+ const takeFeedbackStatus = () => {
220
+ if (field.validating) return "pending";
221
+ return field.decoratorProps.feedbackStatus || field.validateStatus;
222
+ };
223
+ const takeMessage = () => {
224
+ const split = (messages: any[]) => {
225
+ return messages.reduce((buf, text, index) => {
226
+ if (!text) return buf;
227
+ return index < messages.length - 1 ? buf.concat([text, ", "]) : buf.concat([text]);
228
+ }, []);
229
+ };
230
+ if (field.validating) return;
231
+ if (props.feedbackText) return props.feedbackText;
232
+ if (field.selfErrors.length) return split(field.selfErrors);
233
+ if (field.selfWarnings.length) return split(field.selfWarnings);
234
+ if (field.selfSuccesses.length) return split(field.selfSuccesses);
235
+ };
236
+ const takeAsterisk = () => {
237
+ if (field.required && field.pattern !== "readPretty") {
238
+ return true;
239
+ }
240
+ if ("asterisk" in props) {
241
+ return props.asterisk;
242
+ }
243
+ return false;
244
+ };
245
+ return {
246
+ label: props.label || field.title,
247
+ feedbackStatus: takeFeedbackStatus(),
248
+ feedbackText: takeMessage(),
249
+ asterisk: takeAsterisk(),
250
+ optionalMarkHidden: field.pattern === "readPretty" && !("asterisk" in props),
251
+ extra: props.extra || field.description,
252
+ };
253
+ }),
254
+ );
255
+
256
+ export default FormItem;
@@ -0,0 +1,95 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import cls from "classnames";
3
+ import { usePrefixCls } from "@formily/antd-mobile/esm/__builtins__";
4
+ import { List } from "antd-mobile";
5
+ import { ListProps } from "antd-mobile/es/components/list";
6
+
7
+ export interface IFormLayoutProps extends ListProps {
8
+ prefixCls?: string;
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ layout?: "vertical" | "horizontal";
12
+ labelAlign?: "right" | "left" | ("right" | "left")[];
13
+ wrapperAlign?: "right" | "left" | ("right" | "left")[];
14
+ labelCol?: number;
15
+ wrapperCol?: number;
16
+ labelWrap?: boolean;
17
+ labelWidth?: number;
18
+ wrapperWidth?: number;
19
+ wrapperWrap?: boolean;
20
+ inset?: boolean;
21
+ shallow?: boolean;
22
+ bordered?: boolean;
23
+ spaceGap?: number;
24
+ gridColumnGap?: number;
25
+ gridRowGap?: number;
26
+ tooltipLayout?: "icon" | "text";
27
+ tooltipIcon?: React.ReactNode;
28
+ feedbackLayout?: "loose" | "terse" | "popover" | "none";
29
+ size?: "mini" | "small" | "middle" | "large";
30
+ __layout__?: boolean;
31
+ }
32
+
33
+ export type IFormLayoutContext = IFormLayoutProps;
34
+ export const FormLayoutDeepContext = createContext<IFormLayoutContext>(null);
35
+ export const FormLayoutShallowContext = createContext<IFormLayoutContext>(null);
36
+ export const useFormDeepLayout = () => useContext(FormLayoutDeepContext);
37
+ export const useFormShallowLayout = () => useContext(FormLayoutShallowContext);
38
+ export const useFormLayout = () => ({
39
+ ...useFormDeepLayout(),
40
+ ...useFormShallowLayout(),
41
+ });
42
+
43
+ export const FormLayout: React.FC<IFormLayoutProps> & {
44
+ useFormLayout: () => IFormLayoutContext;
45
+ useFormDeepLayout: () => IFormLayoutContext;
46
+ useFormShallowLayout: () => IFormLayoutContext;
47
+ } = ({ shallow, children, prefixCls, className, style, ...props }) => {
48
+ const deepLayout = useFormDeepLayout();
49
+ const formPrefixCls = usePrefixCls("form", { prefixCls });
50
+ const layoutPrefixCls = usePrefixCls("formily-layout", { prefixCls });
51
+ const layoutClassName = cls(
52
+ layoutPrefixCls,
53
+ {
54
+ [`${formPrefixCls}-${props.layout}`]: true,
55
+ [`${formPrefixCls}-${props.size}`]: props.size,
56
+ },
57
+ className,
58
+ );
59
+ const renderChildren = () => {
60
+ const newDeepLayout = {
61
+ ...deepLayout,
62
+ __layout__: true,
63
+ };
64
+ if (!shallow) {
65
+ Object.assign(newDeepLayout, props);
66
+ } else {
67
+ if (props.size) {
68
+ newDeepLayout.size = props.size;
69
+ }
70
+ }
71
+ return (
72
+ <FormLayoutDeepContext.Provider value={newDeepLayout}>
73
+ <FormLayoutShallowContext.Provider value={shallow ? props : undefined}>
74
+ {children}
75
+ </FormLayoutShallowContext.Provider>
76
+ </FormLayoutDeepContext.Provider>
77
+ );
78
+ };
79
+
80
+ return (
81
+ <List className={layoutClassName} style={style} mode={props.mode}>
82
+ {renderChildren()}
83
+ </List>
84
+ );
85
+ };
86
+
87
+ FormLayout.defaultProps = {
88
+ shallow: true,
89
+ };
90
+
91
+ FormLayout.useFormDeepLayout = useFormDeepLayout;
92
+ FormLayout.useFormShallowLayout = useFormShallowLayout;
93
+ FormLayout.useFormLayout = useFormLayout;
94
+
95
+ export default FormLayout;
@@ -0,0 +1 @@
1
+ import "antd-mobile/es/components/list/list.css";
@@ -15,3 +15,7 @@ export * from "./Checkbox";
15
15
  export * from "./LocationPicker";
16
16
  export * from "./TreeSelect";
17
17
  export * from "./Slider";
18
+ export * from "./FormItem";
19
+ import FormLayout from "./FormLayout";
20
+
21
+ export { FormLayout };
package/src/index.tsx CHANGED
@@ -1,8 +1,8 @@
1
1
  import React, { useEffect, useMemo, useImperativeHandle, forwardRef } from "react";
2
2
 
3
3
  import {
4
- FormLayout,
5
- FormItem,
4
+ // FormLayout,
5
+ // FormItem,
6
6
  CascadePicker,
7
7
  CheckList,
8
8
  Checkbox,
@@ -26,6 +26,8 @@ import * as customComponents from "./components/index";
26
26
  import { GlobalPropsContext } from "./common/global-props-context";
27
27
  import { schemaHandler } from "./common/schemaHandler";
28
28
 
29
+ import FormLayout, { FormLayoutDeepContext } from "./components/FormLayout";
30
+
29
31
  import { formPropsI } from "./type.d";
30
32
 
31
33
  import "./index.less";
@@ -36,8 +38,8 @@ const FormRender = forwardRef((props: formPropsI, parentRef) => {
36
38
  () =>
37
39
  createSchemaField({
38
40
  components: {
39
- FormItem,
40
- FormLayout,
41
+ // FormItem,
42
+ // FormLayout,
41
43
  CascadePicker,
42
44
  CheckList,
43
45
  // Checkbox,
@@ -90,21 +92,23 @@ const FormRender = forwardRef((props: formPropsI, parentRef) => {
90
92
  <GlobalPropsContext.Provider value={props}>
91
93
  <div className={`form-render ${props.className} ${isDetail ? "form-render-detail" : ""}`}>
92
94
  <FormProvider form={formRender}>
93
- <FormLayout {...formLayoutProps}>
94
- {/* @ts-ignore */}
95
- <SchemaField schema={schema?.schema} />
96
- {props.hasSubmit !== false && isDetail !== true && readOnly !== true && !props.disabled ? (
97
- <FormButtonGroup>
98
- <Submit
99
- onSubmit={(values) => {
100
- props.onSubmit && props.onSubmit(values);
101
- }}
102
- >
103
- {props.submitText || "提交"}
104
- </Submit>
105
- </FormButtonGroup>
106
- ) : null}
107
- </FormLayout>
95
+ <FormLayoutDeepContext.Provider value={formLayoutProps}>
96
+ <FormLayout {...formLayoutProps}>
97
+ {/* @ts-ignore */}
98
+ <SchemaField schema={schema?.schema} />
99
+ {props.hasSubmit !== false && isDetail !== true && readOnly !== true && !props.disabled ? (
100
+ <FormButtonGroup>
101
+ <Submit
102
+ onSubmit={(values) => {
103
+ props.onSubmit && props.onSubmit(values);
104
+ }}
105
+ >
106
+ {props.submitText || "提交"}
107
+ </Submit>
108
+ </FormButtonGroup>
109
+ ) : null}
110
+ </FormLayout>
111
+ </FormLayoutDeepContext.Provider>
108
112
  <div className="form-render-footer">{props.footerRender && props.footerRender()}</div>
109
113
  </FormProvider>
110
114
  </div>