yy-forms 1.0.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.
Files changed (62) hide show
  1. package/.fatherrc.js +37 -0
  2. package/CHANGELOG.md +254 -0
  3. package/LICENSE +21 -0
  4. package/README.md +99 -0
  5. package/dist/index.d.ts +145 -0
  6. package/dist/index.esm.js +4006 -0
  7. package/dist/index.js +4041 -0
  8. package/es/Provider.js +248 -0
  9. package/es/index.d.ts +145 -0
  10. package/es/index.js +44 -0
  11. package/es/settings/index.js +975 -0
  12. package/es/styles/atom.less +1134 -0
  13. package/es/styles/index.less +358 -0
  14. package/es/transformer/form-render.js +75 -0
  15. package/es/utils/context.js +3 -0
  16. package/es/utils/hooks.js +48 -0
  17. package/es/utils/index.js +706 -0
  18. package/es/utils/mapping.js +31 -0
  19. package/es/utils/serialize.js +276 -0
  20. package/es/widgets/htmlInput.js +20 -0
  21. package/es/widgets/idInput.js +23 -0
  22. package/es/widgets/index.js +5 -0
  23. package/es/widgets/jsonInput.js +24 -0
  24. package/es/widgets/list.js +24 -0
  25. package/es/widgets/percentSlider.js +89 -0
  26. package/package.json +53 -0
  27. package/src/Provider.jsx +239 -0
  28. package/src/components/Canvas/core/RenderChildren.jsx +18 -0
  29. package/src/components/Canvas/core/RenderField.jsx +129 -0
  30. package/src/components/Canvas/core/Wrapper.jsx +298 -0
  31. package/src/components/Canvas/core/Wrapper.less +57 -0
  32. package/src/components/Canvas/core/index.jsx +171 -0
  33. package/src/components/Canvas/index.jsx +178 -0
  34. package/src/components/Settings/GlobalSettings.jsx +48 -0
  35. package/src/components/Settings/ItemSettings.jsx +143 -0
  36. package/src/components/Settings/index.jsx +75 -0
  37. package/src/components/Settings/index.less +25 -0
  38. package/src/components/Sidebar/Element.jsx +80 -0
  39. package/src/components/Sidebar/Element.less +18 -0
  40. package/src/components/Sidebar/index.jsx +47 -0
  41. package/src/components/Sidebar/index.less +23 -0
  42. package/src/i18next/index.ts +14 -0
  43. package/src/i18next/locales/enUS.json +60 -0
  44. package/src/i18next/locales/resources.ts +7 -0
  45. package/src/i18next/locales/zhCN.json +3 -0
  46. package/src/index.d.ts +145 -0
  47. package/src/index.js +45 -0
  48. package/src/settings/index.js +1058 -0
  49. package/src/styles/atom.less +1134 -0
  50. package/src/styles/index.less +358 -0
  51. package/src/transformer/form-render.js +65 -0
  52. package/src/utils/context.js +4 -0
  53. package/src/utils/hooks.js +35 -0
  54. package/src/utils/index.js +678 -0
  55. package/src/utils/mapping.js +29 -0
  56. package/src/utils/serialize.js +368 -0
  57. package/src/widgets/htmlInput.jsx +24 -0
  58. package/src/widgets/idInput.jsx +27 -0
  59. package/src/widgets/index.js +6 -0
  60. package/src/widgets/jsonInput.jsx +29 -0
  61. package/src/widgets/list.jsx +28 -0
  62. package/src/widgets/percentSlider.jsx +74 -0
@@ -0,0 +1,239 @@
1
+ import { ConfigProvider } from 'antd';
2
+ import zhCN from 'antd/lib/locale/zh_CN';
3
+ import copyTOClipboard from 'copy-text-to-clipboard';
4
+
5
+ import {
6
+ mapping as defaultMapping,
7
+ widgets as defaultWidgets,
8
+ } from 'form-render';
9
+ import {
10
+ forwardRef,
11
+ useEffect,
12
+ useImperativeHandle,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
16
+ import { DndProvider } from 'react-dnd';
17
+ import { HTML5Backend } from 'react-dnd-html5-backend';
18
+ import { fromSetting, toSetting } from './transformer/form-render';
19
+ import {
20
+ combineSchema,
21
+ dataToFlatten,
22
+ defaultGetId,
23
+ flattenSchema,
24
+ flattenToData,
25
+ idToSchema,
26
+ newSchemaToOld,
27
+ schemaToState,
28
+ } from './utils';
29
+ import { Ctx, StoreCtx } from './utils/context';
30
+ import { useSet } from './utils/hooks';
31
+ import { serializeToDraft } from './utils/serialize';
32
+ import list from './widgets/list';
33
+
34
+ const DEFAULT_SCHEMA = {
35
+ type: 'object',
36
+ properties: {},
37
+ };
38
+
39
+ // TODO: formData 不存在的时候会报错:can't find # of undefined
40
+ function Provider(props, ref) {
41
+ const {
42
+ defaultValue,
43
+ canDrag,
44
+ canDelete,
45
+ submit,
46
+ transformer: _transformer,
47
+ extraButtons,
48
+ controlButtons,
49
+ preview: _preview,
50
+ hideId,
51
+ getId = defaultGetId,
52
+ settings,
53
+ commonSettings,
54
+ globalSettings,
55
+ widgets = {},
56
+ mapping = {},
57
+ methods = {},
58
+ configProvider = {},
59
+ validation = true,
60
+ children,
61
+ fieldRender,
62
+ fieldWrapperRender,
63
+ elementRender,
64
+ prefixCls,
65
+ } = props;
66
+
67
+ const transformer = {
68
+ from: schema => schema,
69
+ to: schema => schema,
70
+ fromSetting,
71
+ toSetting,
72
+ ..._transformer,
73
+ };
74
+
75
+ const frwRef = ref || useRef();
76
+ const [state, setState] = useSet({
77
+ formData: {},
78
+ frProps: {}, // form-render 的全局 props 等
79
+ isNewVersion: true, // 用schema字段,还是用propsSchema字段,这是一个问题
80
+ preview: false, // preview = false 是编辑模式
81
+ schema: {},
82
+ selected: undefined, // 被选中的$id, 如果object/array的内部,以首字母0标识
83
+ settingsForm: null,
84
+ });
85
+ const [errorFields, setErrorFields] = useState([]);
86
+
87
+ // 收口点 propsSchema 到 schema 的转换 (一共3处,其他两个是 importSchema 和 setValue,在 FRWrapper 文件)
88
+ useEffect(() => {
89
+ const schema = defaultValue
90
+ ? transformer.from(defaultValue)
91
+ : DEFAULT_SCHEMA;
92
+ if (schema) setState(schemaToState(schema));
93
+ }, [defaultValue]);
94
+
95
+ const { formData, frProps, isNewVersion, preview, schema, selected } = state;
96
+
97
+ const onChange = data => {
98
+ setState({ formData: data });
99
+ props.onChange && props.onChange(data);
100
+ };
101
+
102
+ const onSchemaChange = newSchema => {
103
+ setState({ schema: newSchema });
104
+ if (props.onSchemaChange) {
105
+ setTimeout(() => {
106
+ if (!frwRef.current) return;
107
+ const pureSchema = frwRef.current.getValue();
108
+ props.onSchemaChange(pureSchema);
109
+ }, 0);
110
+ }
111
+ };
112
+
113
+ const _mapping = { ...defaultMapping, ...mapping };
114
+ const _widgets = { ...defaultWidgets, ...widgets, list };
115
+
116
+ const rootState = {
117
+ preview: _preview ?? preview,
118
+ mapping: _mapping,
119
+ widgets: _widgets,
120
+ methods,
121
+ selected,
122
+ };
123
+
124
+ const userProps = {
125
+ canDrag,
126
+ canDelete,
127
+ submit,
128
+ transformer,
129
+ isNewVersion,
130
+ extraButtons,
131
+ controlButtons,
132
+ hideId,
133
+ getId,
134
+ validation,
135
+ settings,
136
+ commonSettings,
137
+ globalSettings,
138
+ };
139
+
140
+ let _schema = {};
141
+ if (schema) {
142
+ _schema = combineSchema({ ...schema, ...frProps }); // TODO: 要不要判断是否都是object
143
+ }
144
+ const flatten = flattenSchema(_schema);
145
+ const flattenWithData = transformer.from(dataToFlatten(flatten, formData));
146
+
147
+ const onFlattenChange = (newFlatten, changeSource = 'schema') => {
148
+ const newSchema = idToSchema(newFlatten);
149
+ const newData = flattenToData(newFlatten);
150
+ // 判断只有schema变化时才调用,一般需求的用户不需要
151
+ if (changeSource === 'schema') {
152
+ onSchemaChange(newSchema);
153
+ }
154
+ // schema 变化大都会触发 data 变化
155
+ onChange(newData);
156
+ };
157
+
158
+ const onItemChange = (key, value, changeSource) => {
159
+ flattenWithData[key] = value;
160
+ onFlattenChange(flattenWithData, changeSource);
161
+ };
162
+
163
+ let displaySchema = {};
164
+ let displaySchemaString = '';
165
+ try {
166
+ const _schema = {
167
+ ...idToSchema(flattenWithData, '#', true),
168
+ ...frProps,
169
+ };
170
+ displaySchema = transformer.to(_schema);
171
+ if (!isNewVersion) {
172
+ displaySchema = newSchemaToOld(displaySchema);
173
+ }
174
+ // displaySchemaString = JSON.stringify(displaySchema, null, 2);
175
+ // 支持直接保存函数之后(解决validtor不能正常保存的问题),这里因为导入导出的问题,序列化也用内置的api序列化
176
+ displaySchemaString = serializeToDraft(displaySchema);
177
+ } catch (error) {}
178
+
179
+ const getValue = () => displaySchema;
180
+
181
+ const setValue = value => {
182
+ try {
183
+ setState(state => ({
184
+ ...state,
185
+ selected: undefined,
186
+ ...schemaToState(transformer.from(value)),
187
+ }));
188
+ } catch (error) {
189
+ console.error(error);
190
+ }
191
+ };
192
+
193
+ const copyValue = () => {
194
+ copyTOClipboard(displaySchemaString);
195
+ };
196
+
197
+ const getErrorFields = () => errorFields;
198
+
199
+ const getSettingsForm = () => state.settingsForm;
200
+
201
+ useImperativeHandle(frwRef, () => ({
202
+ getValue,
203
+ setValue,
204
+ copyValue,
205
+ getErrorFields,
206
+ getSettingsForm,
207
+ }));
208
+
209
+ // TODO: flatten是频繁在变的,应该和其他两个函数分开
210
+ const store = {
211
+ flatten: flattenWithData, // schema + formData = flattenWithData
212
+ onFlattenChange, // onChange + onSchemaChange = onFlattenChange
213
+ onItemChange, // onFlattenChange 里只改一个item的flatten,使用这个方法
214
+ onSchemaChange,
215
+ onChange,
216
+ errorFields,
217
+ onItemErrorChange: setErrorFields,
218
+ userProps,
219
+ frProps,
220
+ displaySchema,
221
+ displaySchemaString,
222
+ fieldRender,
223
+ fieldWrapperRender,
224
+ elementRender,
225
+ ...rootState,
226
+ };
227
+
228
+ return (
229
+ <DndProvider backend={HTML5Backend} context={window}>
230
+ <ConfigProvider locale={zhCN} {...configProvider}>
231
+ <Ctx.Provider value={setState}>
232
+ <StoreCtx.Provider value={store}>{children}</StoreCtx.Provider>
233
+ </Ctx.Provider>
234
+ </ConfigProvider>
235
+ </DndProvider>
236
+ );
237
+ }
238
+
239
+ export default forwardRef(Provider);
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import FR from './index';
3
+
4
+ const RenderChildren = ({ children = [], preview }) => {
5
+ return (
6
+ <>
7
+ {children.map((child, i) => {
8
+ const FRProps = {
9
+ id: child,
10
+ preview,
11
+ };
12
+ return <FR key={i.toString()} {...FRProps} />;
13
+ })}
14
+ </>
15
+ );
16
+ };
17
+
18
+ export default RenderChildren;
@@ -0,0 +1,129 @@
1
+ import React, { Suspense } from 'react';
2
+ import {
3
+ getParentProps,
4
+ isCssLength,
5
+ isLooselyNumber,
6
+ transformProps,
7
+ } from '../../../utils';
8
+ import { useStore } from '../../../utils/hooks';
9
+ import { getWidgetName } from '../../../utils/mapping';
10
+ import cn from 'classnames';
11
+
12
+ const RenderField = ({
13
+ $id,
14
+ item,
15
+ labelClass,
16
+ contentClass,
17
+ isComplex,
18
+ children,
19
+ }) => {
20
+ const { schema, data } = item;
21
+ const {
22
+ onItemChange,
23
+ flatten,
24
+ widgets,
25
+ mapping,
26
+ frProps = {},
27
+ fieldRender,
28
+ } = useStore();
29
+ const { labelWidth, displayType, showValidate } = frProps;
30
+ const { title, description, required } = schema;
31
+
32
+ let widgetName = getWidgetName(schema, mapping);
33
+ const customWidget = schema['widget'];
34
+ if (customWidget && widgets[customWidget]) {
35
+ widgetName = customWidget;
36
+ }
37
+ let Widget = widgets[widgetName];
38
+ // 如果不存在,比如有外部的自定义组件名称,使用默认展示组件
39
+ if (!Widget) {
40
+ const defaultSchema = { ...schema };
41
+ delete defaultSchema['widget'];
42
+ widgetName = getWidgetName(defaultSchema, mapping);
43
+ Widget = widgets[widgetName] || 'input';
44
+ }
45
+ // 真正有效的label宽度需要从现在所在item开始一直往上回溯(设计成了继承关系),找到的第一个有值的 labelWidth
46
+ const effectiveLabelWidth =
47
+ getParentProps('labelWidth', $id, flatten) || labelWidth;
48
+ const _labelWidth = isLooselyNumber(effectiveLabelWidth)
49
+ ? Number(effectiveLabelWidth)
50
+ : isCssLength(effectiveLabelWidth)
51
+ ? effectiveLabelWidth
52
+ : 110; // 默认是 110px 的长度
53
+
54
+ let labelStyle = { width: _labelWidth };
55
+ if (widgetName === 'checkbox') {
56
+ labelStyle = { flexGrow: 1 };
57
+ } else if (isComplex || displayType === 'column') {
58
+ labelStyle = { flexGrow: 1 };
59
+ }
60
+
61
+ const onChange = value => {
62
+ const newItem = { ...item };
63
+ if (item.schema.type === 'boolean' && item.schema.widget === 'checkbox') {
64
+ newItem.data = !value;
65
+ } else {
66
+ newItem.data = value;
67
+ }
68
+ onItemChange($id, newItem, 'data');
69
+ };
70
+
71
+ let contentStyle = {};
72
+ if (widgetName === 'checkbox' && displayType === 'row') {
73
+ contentStyle.marginLeft = effectiveLabelWidth;
74
+ }
75
+
76
+ // TODO: useMemo
77
+ // 改为直接使用form-render内部自带组件后不需要再包一层options
78
+ const usefulWidgetProps = transformProps({
79
+ value: data || schema.default,
80
+ checked: data,
81
+ disabled: schema.disabled,
82
+ readOnly: schema.readOnly,
83
+ format: schema.format,
84
+ onChange,
85
+ schema,
86
+ ...schema['props'],
87
+ });
88
+
89
+ const originNode = (
90
+ <>
91
+ {schema.title ? (
92
+ <div className={labelClass} style={labelStyle}>
93
+ <label
94
+ className={cn('fr-label-title', {
95
+ 'no-colon': widgetName === 'checkbox' || displayType === 'column',
96
+ })} // checkbox不带冒号
97
+ title={title}
98
+ >
99
+ {required && <span className="fr-label-required"> *</span>}
100
+ <span
101
+ className={cn({
102
+ b: isComplex,
103
+ 'flex-none': displayType === 'column',
104
+ })}
105
+ >
106
+ <span dangerouslySetInnerHTML={{ __html: title }} />
107
+ </span>
108
+ {description && (
109
+ <span className="fr-desc ml2">(&nbsp;{description}&nbsp;)</span>
110
+ )}
111
+ {displayType !== 'row' && showValidate && (
112
+ <span className="fr-validate">validation</span>
113
+ )}
114
+ </label>
115
+ </div>
116
+ ) : null}
117
+ <div className={contentClass} style={contentStyle}>
118
+ <Suspense fallback={<div></div>}>
119
+ <Widget {...usefulWidgetProps}>{children || null}</Widget>
120
+ </Suspense>
121
+ </div>
122
+ </>
123
+ );
124
+
125
+ if (!fieldRender) return originNode;
126
+ return fieldRender(schema, usefulWidgetProps, children, originNode);
127
+ };
128
+
129
+ export default RenderField;
@@ -0,0 +1,298 @@
1
+ import { CopyOutlined, DeleteOutlined, DragOutlined } from '@ant-design/icons';
2
+ import React, { useRef, useState } from 'react';
3
+ import { useDrag, useDrop } from 'react-dnd';
4
+ import {
5
+ copyItem,
6
+ dropItem,
7
+ getKeyFromUniqueId,
8
+ isObject,
9
+ } from '../../../utils';
10
+ import { useGlobal, useStore } from '../../../utils/hooks';
11
+ import './Wrapper.less';
12
+
13
+ function Wrapper({ $id, item, inside = false, children, style }) {
14
+ const [position, setPosition] = useState();
15
+ const {
16
+ flatten,
17
+ onFlattenChange,
18
+ selected,
19
+ userProps,
20
+ errorFields,
21
+ fieldWrapperRender,
22
+ } = useStore();
23
+ const {
24
+ controlButtons,
25
+ canDrag = true,
26
+ canDelete = true,
27
+ hideId,
28
+ getId,
29
+ } = userProps;
30
+ const setGlobal = useGlobal();
31
+ const { schema } = item;
32
+ const { type } = schema;
33
+ const boxRef = useRef(null);
34
+
35
+ const [{ isDragging }, dragRef, dragPreview] = useDrag({
36
+ type: 'box',
37
+ item: { $id: inside ? 0 + $id : $id },
38
+ canDrag: () => (typeof canDrag === 'function' ? canDrag(schema) : canDrag),
39
+ collect: monitor => ({
40
+ isDragging: monitor.isDragging(),
41
+ }),
42
+ });
43
+
44
+ const [{ canDrop, isOver }, dropRef] = useDrop({
45
+ accept: 'box',
46
+ drop: async (item, monitor) => {
47
+ // 如果 children 已经作为了 drop target,不处理
48
+ const didDrop = monitor.didDrop();
49
+
50
+ if (didDrop || errorFields?.length) {
51
+ return;
52
+ }
53
+
54
+ const [newFlatten, newId] = dropItem({
55
+ dragId: item.$id, // 内部拖拽用dragId
56
+ dragItem: item.dragItem, // 从左边栏过来的,用dragItem
57
+ dropId: $id,
58
+ position,
59
+ flatten,
60
+ });
61
+ onFlattenChange(newFlatten);
62
+ setGlobal({ selected: newId });
63
+ },
64
+ hover: (item, monitor) => {
65
+ // 只检查被hover的最小元素
66
+ const didHover = monitor.isOver({ shallow: true });
67
+ if (didHover) {
68
+ // Determine rectangle on screen
69
+ const hoverBoundingRect =
70
+ boxRef.current && boxRef.current.getBoundingClientRect();
71
+ // Get vertical middle
72
+ const hoverMiddleY =
73
+ (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
74
+ // Determine mouse position
75
+ // const clientOffset = monitor.getClientOffset();
76
+ const dragOffset = monitor.getSourceClientOffset();
77
+ // Get pixels to the top
78
+ const hoverClientY = dragOffset.y - hoverBoundingRect.top;
79
+ // Only perform the move when the mouse has crossed half of the items height
80
+ // When dragging downwards, only move when the cursor is below 50%
81
+ // When dragging upwards, only move when the cursor is above 50%
82
+ // Dragging downwards
83
+ if (inside) {
84
+ setPosition('inside');
85
+ } else {
86
+ if (hoverClientY <= hoverMiddleY) {
87
+ setPosition('up');
88
+ }
89
+ // Dragging upwards
90
+ if (hoverClientY > hoverMiddleY) {
91
+ setPosition('down');
92
+ }
93
+ }
94
+ }
95
+ },
96
+ collect: monitor => ({
97
+ isOver: monitor.isOver({ shallow: true }),
98
+ canDrop: monitor.canDrop(),
99
+ }),
100
+ });
101
+
102
+ const isActive = canDrop && isOver;
103
+ dragPreview(dropRef(boxRef));
104
+
105
+ const handleClick = async e => {
106
+ e.stopPropagation();
107
+ if (errorFields?.length) return;
108
+ const _id = inside ? '0' + $id : $id;
109
+ setGlobal({ selected: _id });
110
+ };
111
+
112
+ const deleteItem = async e => {
113
+ e.stopPropagation();
114
+ const newFlatten = { ...flatten };
115
+ let newSelect = '#';
116
+ // 计算删除后新被选中的元素:
117
+ // 1. 如果是第一个,选第二个
118
+ // 2. 如果不是第一,选它前一个
119
+ // 3. 如果同级元素没了,选parent
120
+ try {
121
+ const parent = newFlatten[$id].parent;
122
+ const siblings = newFlatten[parent].children;
123
+ const idx = siblings.indexOf($id);
124
+ if (idx > 0) {
125
+ newSelect = siblings[idx - 1];
126
+ } else {
127
+ newSelect = siblings[1] || parent;
128
+ }
129
+ } catch (error) {
130
+ console.error(error, 'catch');
131
+ }
132
+ let _canDelete = canDelete;
133
+ if (typeof canDelete === 'function') {
134
+ _canDelete = await Promise.resolve(canDelete(newFlatten[$id].schema));
135
+ }
136
+ if (!_canDelete) return;
137
+ delete newFlatten[$id];
138
+ onFlattenChange(newFlatten);
139
+ setGlobal({ selected: newSelect });
140
+ };
141
+
142
+ const handleItemCopy = e => {
143
+ e.stopPropagation();
144
+ if (errorFields?.length) return;
145
+ const [newFlatten, newId] = copyItem(flatten, $id, getId);
146
+ onFlattenChange(newFlatten);
147
+ setGlobal({ selected: newId });
148
+ };
149
+
150
+ // 一些computed
151
+ let isSelected = selected === $id && !inside;
152
+ if (selected && selected[0] === '0') {
153
+ isSelected = selected.substring(1) === $id && inside;
154
+ }
155
+
156
+ let overwriteStyle = {
157
+ backgroundColor: '#fff',
158
+ opacity: isDragging ? 0 : 1,
159
+ };
160
+ if (inside) {
161
+ overwriteStyle = {
162
+ ...overwriteStyle,
163
+ borderColor: '#777',
164
+ // marginLeft: 12,
165
+ padding: '12px 12px 0',
166
+ backgroundColor: '#f6f5f6',
167
+ };
168
+ } else if ($id === '#') {
169
+ overwriteStyle = {
170
+ ...overwriteStyle,
171
+ borderColor: '#777',
172
+ padding: 12,
173
+ height: '100%',
174
+ overflow: 'auto',
175
+ backgroundColor: '#f6f5f6',
176
+ };
177
+ } else if (type === 'object') {
178
+ overwriteStyle = { ...overwriteStyle, paddingTop: 12 };
179
+ }
180
+ if (isActive) {
181
+ if (inside) {
182
+ overwriteStyle = {
183
+ ...overwriteStyle,
184
+ boxShadow: '0 -3px 0 red',
185
+ };
186
+ } else if (position === 'up') {
187
+ overwriteStyle = {
188
+ ...overwriteStyle,
189
+ boxShadow: '0 -3px 0 red',
190
+ };
191
+ } else if (position === 'down') {
192
+ overwriteStyle = {
193
+ ...overwriteStyle,
194
+ boxShadow: '0 3px 0 red',
195
+ };
196
+ }
197
+ }
198
+ if (isSelected) {
199
+ overwriteStyle = {
200
+ ...overwriteStyle,
201
+ outline: '2px solid #409eff',
202
+ borderColor: '#fff',
203
+ zIndex: 1,
204
+ };
205
+ }
206
+ if (style && typeof style === 'object') {
207
+ overwriteStyle = {
208
+ ...overwriteStyle,
209
+ ...style,
210
+ };
211
+ }
212
+
213
+ if ($id === '#' && inside) return children;
214
+
215
+ // 展示的id
216
+ const shownId = getKeyFromUniqueId(schema.$id);
217
+
218
+ // 操作按钮
219
+ const _controlButtons = Array.isArray(controlButtons)
220
+ ? controlButtons
221
+ : [true, true];
222
+ const _showDefaultBtns = _controlButtons
223
+ .filter(item => ['boolean', 'function'].includes(typeof item))
224
+ .map(item => {
225
+ if (typeof item === 'boolean') return item;
226
+ return item(schema);
227
+ });
228
+ const _extraBtns = _controlButtons.filter(
229
+ item => isObject(item) && (item.text || item.children)
230
+ );
231
+ const { length: _numOfBtns } = _showDefaultBtns
232
+ .concat(_extraBtns)
233
+ .filter(Boolean);
234
+
235
+ const hasDuplicateId =
236
+ Object.keys(flatten)
237
+ .map(key => flatten[key].schema.$id)
238
+ .filter(key => key === schema.$id).length > 1;
239
+
240
+ const originNode = (
241
+ <div
242
+ ref={boxRef}
243
+ style={overwriteStyle}
244
+ className={`field-wrapper ${
245
+ $id !== '#' && isSelected ? 'selected-field-wrapper' : ''
246
+ } relative w-100`}
247
+ onClick={handleClick}
248
+ >
249
+ {children}
250
+
251
+ <div className="absolute top-0 right-1 f7">
252
+ {!inside && $id !== '#' && !hideId && (
253
+ <span className={hasDuplicateId ? 'red' : 'blue'}>{shownId}</span>
254
+ )}
255
+ {schema.hidden && (
256
+ <span style={{ color: '#666', marginLeft: '6px' }}>[hidden]</span>
257
+ )}
258
+ </div>
259
+
260
+ {!inside && $id !== '#' && isSelected && (
261
+ <div className="pointer-move" ref={dragRef}>
262
+ <DragOutlined />
263
+ </div>
264
+ )}
265
+
266
+ {!inside && $id !== '#' && isSelected && _numOfBtns > 0 && (
267
+ <div className="pointer-wrapper">
268
+ {_showDefaultBtns[0] !== false && (
269
+ <div className="pointer" onClick={deleteItem}>
270
+ <DeleteOutlined />
271
+ </div>
272
+ )}
273
+ {_showDefaultBtns[1] !== false && (
274
+ <div className="pointer" onClick={handleItemCopy}>
275
+ <CopyOutlined />
276
+ </div>
277
+ )}
278
+ {_extraBtns.map((item, idx) => {
279
+ return (
280
+ <div
281
+ key={idx.toString()}
282
+ className="pointer"
283
+ onClick={e => item.onClick && item.onClick(e, schema)}
284
+ >
285
+ {item.text || item.children}
286
+ </div>
287
+ );
288
+ })}
289
+ </div>
290
+ )}
291
+ </div>
292
+ );
293
+
294
+ if (!fieldWrapperRender) return originNode;
295
+ return fieldWrapperRender(schema, isSelected, children, originNode);
296
+ }
297
+
298
+ export default Wrapper;