form-driver 0.4.1 → 0.4.2

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.
@@ -0,0 +1,498 @@
1
+ import { HolderOutlined } from "@ant-design/icons";
2
+ import {
3
+ draggable,
4
+ dropTargetForElements,
5
+ monitorForElements,
6
+ } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
7
+ import clsx from "clsx";
8
+ import React, {
9
+ memo,
10
+ ReactNode,
11
+ useCallback,
12
+ useEffect,
13
+ useRef,
14
+ useState,
15
+ } from "react";
16
+
17
+ import "./EnhancedSortDrag.less";
18
+
19
+ /**
20
+ * 拖拽项数据类型定义
21
+ */
22
+ export interface DragItem {
23
+ id: string;
24
+ cpn: ReactNode;
25
+ isChecked: boolean;
26
+ label: string;
27
+ checkedIndex: number;
28
+ }
29
+
30
+ /**
31
+ * 拖拽组件属性定义
32
+ */
33
+ export interface EnhancedSortDragProps {
34
+ /** 拖拽项列表 */
35
+ items: DragItem[];
36
+ /** 数据变化回调 */
37
+ onChange: (newItems: DragItem[]) => void;
38
+ /** 是否启用动画效果,默认开启 */
39
+ enableAnimation?: boolean;
40
+ /** 是否支持跨容器拖拽,默认关闭 */
41
+ enableCrossContainer?: boolean;
42
+ /** 拖拽方向,默认垂直 */
43
+ direction?: "vertical" | "horizontal";
44
+ /** 拖拽失败回调 */
45
+ onDragFail?: (error: DragError) => void;
46
+ /** 自定义拖拽句柄渲染 */
47
+ renderDragHandle?: (item: DragItem, isDragging: boolean) => ReactNode;
48
+ /** 拖拽预览内容 */
49
+ renderDragPreview?: (item: DragItem) => ReactNode;
50
+ /** 是否为表格行模式 */
51
+ isTableRow?: boolean;
52
+ }
53
+
54
+ /**
55
+ * 拖拽错误类型定义
56
+ */
57
+ export interface DragError {
58
+ type: "VALIDATION_ERROR" | "DATA_ERROR" | "SYSTEM_ERROR";
59
+ message: string;
60
+ itemId?: string;
61
+ sourceId?: string;
62
+ targetId?: string;
63
+ }
64
+
65
+ /**
66
+ * 拖拽状态类型定义
67
+ */
68
+ export interface DragState {
69
+ draggingId: string | null;
70
+ droppingId: string | null;
71
+ isAnimating: boolean;
72
+ }
73
+
74
+ /**
75
+ * 拖拽验证结果
76
+ */
77
+ interface ValidationResult {
78
+ isValid: boolean;
79
+ error?: DragError;
80
+ }
81
+
82
+ /**
83
+ * 增强版通用拖拽排序组件
84
+ * 解决原组件的性能问题和功能局限性
85
+ */
86
+ const EnhancedSortDrag: React.FC<EnhancedSortDragProps> = memo((props) => {
87
+ const {
88
+ items,
89
+ onChange,
90
+ enableAnimation = true,
91
+ enableCrossContainer = false,
92
+ direction = "vertical",
93
+ onDragFail,
94
+ renderDragHandle,
95
+ renderDragPreview,
96
+ isTableRow = false,
97
+ } = props;
98
+
99
+ // 使用 useRef 缓存 items 避免不必要的重渲染
100
+ const itemsRef = useRef<DragItem[]>(items);
101
+ itemsRef.current = items;
102
+
103
+ // 拖拽状态管理
104
+ const [dragState, setDragState] = useState<DragState>({
105
+ draggingId: null,
106
+ droppingId: null,
107
+ isAnimating: false,
108
+ });
109
+
110
+ // 使用 Map 替代对象,提高查找性能
111
+ const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());
112
+ const dragHandleRefs = useRef<Map<string, HTMLSpanElement>>(new Map());
113
+
114
+ // 清理函数缓存,避免重复注册
115
+ const cleanupRefs = useRef<Map<string, () => void>>(new Map());
116
+
117
+ /**
118
+ * 验证拖拽操作是否有效
119
+ */
120
+ const validateDragOperation = useCallback(
121
+ (
122
+ sourceId: string,
123
+ targetId: string,
124
+ items: DragItem[]
125
+ ): ValidationResult => {
126
+ // 检查源项目是否存在
127
+ const sourceItem = items.find((item) => item.id === sourceId);
128
+ if (!sourceItem) {
129
+ return {
130
+ isValid: false,
131
+ error: {
132
+ type: "VALIDATION_ERROR",
133
+ message: "源项目不存在",
134
+ sourceId,
135
+ },
136
+ };
137
+ }
138
+
139
+ // 检查目标项目是否存在
140
+ const targetItem = items.find((item) => item.id === targetId);
141
+ if (!targetItem) {
142
+ return {
143
+ isValid: false,
144
+ error: {
145
+ type: "VALIDATION_ERROR",
146
+ message: "目标项目不存在",
147
+ targetId,
148
+ },
149
+ };
150
+ }
151
+
152
+ // 检查是否允许拖拽
153
+ if (!sourceItem.isChecked) {
154
+ return {
155
+ isValid: false,
156
+ error: {
157
+ type: "VALIDATION_ERROR",
158
+ message: "源项目不允许拖拽",
159
+ sourceId,
160
+ },
161
+ };
162
+ }
163
+
164
+ // 检查是否允许放置
165
+ if (!targetItem.isChecked) {
166
+ return {
167
+ isValid: false,
168
+ error: {
169
+ type: "VALIDATION_ERROR",
170
+ message: "目标位置不允许放置",
171
+ targetId,
172
+ },
173
+ };
174
+ }
175
+
176
+ // 检查是否为同一项目
177
+ if (sourceId === targetId) {
178
+ return {
179
+ isValid: false,
180
+ error: {
181
+ type: "VALIDATION_ERROR",
182
+ message: "不能拖拽到自身位置",
183
+ sourceId,
184
+ targetId,
185
+ },
186
+ };
187
+ }
188
+
189
+ return {
190
+ isValid: true,
191
+ };
192
+ },
193
+ []
194
+ );
195
+
196
+ /**
197
+ * 交换数据逻辑 - 核心拖拽算法
198
+ */
199
+ const swapItems = useCallback(
200
+ (fromId: string, toId: string) => {
201
+ try {
202
+ const currentItems = [...itemsRef.current];
203
+
204
+ // 验证拖拽操作
205
+ const validation = validateDragOperation(fromId, toId, currentItems);
206
+ if (!validation.isValid) {
207
+ throw new Error(validation.error?.message || "拖拽验证失败");
208
+ }
209
+
210
+ const fromIndex = currentItems.findIndex((item) => item.id === fromId);
211
+ const toIndex = currentItems.findIndex((item) => item.id === toId);
212
+
213
+ // 边界条件检查
214
+ if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
215
+ return;
216
+ }
217
+
218
+ const [moved] = currentItems.splice(fromIndex, 1);
219
+ currentItems.splice(toIndex, 0, moved);
220
+
221
+ // 更新引用
222
+ itemsRef.current = currentItems;
223
+
224
+ // 触发回调
225
+ onChange(currentItems);
226
+ } catch (error) {
227
+ console.error("拖拽交换数据失败:", error);
228
+
229
+ // 触发错误回调
230
+ const dragError: DragError = {
231
+ type: "DATA_ERROR",
232
+ message: error instanceof Error ? error.message : "未知错误",
233
+ sourceId: fromId,
234
+ targetId: toId,
235
+ };
236
+
237
+ onDragFail?.(dragError);
238
+ }
239
+ },
240
+ [onChange, onDragFail, validateDragOperation]
241
+ );
242
+
243
+ /**
244
+ * 注册单个项目的拖拽监听器
245
+ */
246
+ const registerDragListeners = useCallback(
247
+ (item: DragItem, index: number) => {
248
+ const el = itemRefs.current.get(item.id);
249
+ const dragHandle = dragHandleRefs.current.get(item.id);
250
+
251
+ if (!el) return;
252
+
253
+ // 清理之前的监听器
254
+ const existingCleanup = cleanupRefs.current.get(item.id);
255
+ if (existingCleanup) {
256
+ existingCleanup();
257
+ }
258
+
259
+ const cleanups: (() => void)[] = [];
260
+
261
+ // 注册为可拖拽元素
262
+ cleanups.push(
263
+ draggable({
264
+ element: el,
265
+ dragHandle: dragHandle,
266
+ canDrag: () => item.isChecked,
267
+ getInitialData: () => ({
268
+ id: item.id,
269
+ index,
270
+ label: item.label,
271
+ }),
272
+ onDragStart: () => {
273
+ setDragState((prev) => ({
274
+ ...prev,
275
+ draggingId: item.id,
276
+ isAnimating: !!enableAnimation,
277
+ }));
278
+ console.log("开始拖拽:", item.label);
279
+ },
280
+ onDrop: () => {
281
+ setDragState((prev) => ({
282
+ ...prev,
283
+ draggingId: null,
284
+ droppingId: null,
285
+ isAnimating: false,
286
+ }));
287
+ },
288
+ })
289
+ );
290
+
291
+ // 注册为拖拽目标
292
+ cleanups.push(
293
+ dropTargetForElements({
294
+ element: el,
295
+ canDrop: () => item.isChecked,
296
+ onDragEnter: (args) => {
297
+ const sourceId = args.source.data.id as string;
298
+ if (sourceId === item.id) return;
299
+
300
+ console.log("拖拽进入:", item.id);
301
+ setDragState((prev) => ({
302
+ ...prev,
303
+ droppingId: item.id,
304
+ }));
305
+ },
306
+ onDragLeave: () => {
307
+ setDragState((prev) => ({
308
+ ...prev,
309
+ droppingId: null,
310
+ }));
311
+ },
312
+ onDrop: ({ source }) => {
313
+ if (!source) return;
314
+ const sourceId = source.data.id as string;
315
+ if (sourceId === item.id) return;
316
+
317
+ // 执行数据交换
318
+ swapItems(sourceId, item.id);
319
+ },
320
+ })
321
+ );
322
+
323
+ // 存储清理函数
324
+ const combinedCleanup = () => {
325
+ cleanups.forEach((fn) => fn());
326
+ };
327
+ cleanupRefs.current.set(item.id, combinedCleanup);
328
+ },
329
+ [swapItems, enableAnimation]
330
+ );
331
+
332
+ /**
333
+ * 性能优化:只在必要的时候注册监听器
334
+ */
335
+ useEffect(() => {
336
+ // 清理所有旧的监听器
337
+ cleanupRefs.current.forEach((cleanup) => cleanup());
338
+ cleanupRefs.current.clear();
339
+
340
+ // 注册所有项目的监听器
341
+ items.forEach((item, index) => {
342
+ registerDragListeners(item, index);
343
+ });
344
+
345
+ // 监听全局拖拽结束
346
+ const monitorCleanup = monitorForElements({
347
+ onDrop: () => {
348
+ setDragState((prev) => ({
349
+ ...prev,
350
+ draggingId: null,
351
+ droppingId: null,
352
+ isAnimating: false,
353
+ }));
354
+ },
355
+ });
356
+ cleanupRefs.current.set("monitor", monitorCleanup);
357
+
358
+ return () => {
359
+ cleanupRefs.current.forEach((cleanup) => cleanup());
360
+ cleanupRefs.current.clear();
361
+ };
362
+ }, [items.length, registerDragListeners]); // 只在列表长度变化时重新注册
363
+
364
+ /**
365
+ * 渲染默认拖拽句柄
366
+ */
367
+ const renderDefaultDragHandle = useCallback(
368
+ (item: DragItem, isDragging: boolean) => (
369
+ <HolderOutlined
370
+ ref={(el) => {
371
+ if (el) {
372
+ dragHandleRefs.current.set(item.id, el);
373
+ }
374
+ }}
375
+ style={{
376
+ cursor: isDragging ? "grabbing" : "grab",
377
+ marginLeft: "8px",
378
+ opacity: isDragging ? 0.5 : 1,
379
+ transition: enableAnimation ? "opacity 0.2s" : "none",
380
+ }}
381
+ />
382
+ ),
383
+ [enableAnimation]
384
+ );
385
+
386
+ /**
387
+ * 渲染拖拽项
388
+ */
389
+ const renderDragItem = useCallback(
390
+ (item: DragItem, index: number) => {
391
+ const isDragging = dragState.draggingId === item.id;
392
+ const isDropping = dragState.droppingId === item.id;
393
+
394
+ // 如果是表格行模式,直接返回组件内容
395
+ if (isTableRow) {
396
+ // console.log("表格行数据", item);
397
+ return (
398
+ <tr
399
+ ref={(el) => {
400
+ if (el) {
401
+ itemRefs.current.set(item.id, el as unknown as HTMLDivElement);
402
+ }
403
+ }}
404
+ key={item.id}
405
+ data-drag-id={item.id}
406
+ data-drag-index={index}
407
+ style={{
408
+ transition: enableAnimation ? "all 0.3s ease" : "none",
409
+ opacity: isDragging ? 0.5 : 1,
410
+ backgroundColor: isDropping ? "#e6f7ff" : "transparent",
411
+ }}
412
+ >
413
+ {/* 如果提供了自定义拖拽句柄,则将其作为第一列 */}
414
+ {renderDragHandle && renderDragHandle(item, isDragging)}
415
+ {!renderDragHandle && (
416
+ <td
417
+ ref={(el) => {
418
+ if (el) {
419
+ dragHandleRefs.current.set(
420
+ item.id,
421
+ el as unknown as HTMLSpanElement
422
+ );
423
+ }
424
+ }}
425
+ style={{
426
+ cursor: isDragging ? "grabbing" : "grab",
427
+ textAlign: "center",
428
+ width: "40px",
429
+ }}
430
+ >
431
+ <HolderOutlined
432
+ style={{
433
+ opacity: isDragging ? 0.5 : 1,
434
+ transition: enableAnimation ? "opacity 0.2s" : "none",
435
+ }}
436
+ />
437
+ </td>
438
+ )}
439
+ {item.cpn}
440
+ </tr>
441
+ );
442
+ }
443
+
444
+ const itemClass = clsx("enhanced-dragItem", {
445
+ dragging: isDragging,
446
+ dropping: isDropping,
447
+ disabled: !item.isChecked,
448
+ [`direction-${direction}`]: true,
449
+ });
450
+
451
+ return (
452
+ <div
453
+ className={itemClass}
454
+ ref={(el) => {
455
+ if (el) {
456
+ itemRefs.current.set(item.id, el);
457
+ }
458
+ }}
459
+ key={item.id}
460
+ data-drag-id={item.id}
461
+ data-drag-index={index}
462
+ style={{
463
+ transition: enableAnimation ? "all 0.3s ease" : "none",
464
+ }}
465
+ >
466
+ <div className="enhanced-dragBody">{item.cpn}</div>
467
+ <div className="enhanced-dragHandle">
468
+ {item.isChecked &&
469
+ (renderDragHandle
470
+ ? renderDragHandle(item, isDragging)
471
+ : renderDefaultDragHandle(item, isDragging))}
472
+ </div>
473
+ </div>
474
+ );
475
+ },
476
+ [
477
+ dragState,
478
+ direction,
479
+ enableAnimation,
480
+ renderDragHandle,
481
+ renderDefaultDragHandle,
482
+ isTableRow,
483
+ ]
484
+ );
485
+
486
+ // 如果是表格行模式,直接渲染子元素而不是包装div
487
+ if (isTableRow) {
488
+ return <>{items.map(renderDragItem)}</>;
489
+ }
490
+
491
+ return (
492
+ <div className={`enhanced-sortDrag direction-${direction}`}>
493
+ {items.map(renderDragItem)}
494
+ </div>
495
+ );
496
+ });
497
+
498
+ export default EnhancedSortDrag;
@@ -1,5 +1,5 @@
1
1
  import { ClassType } from "react";
2
- import { MFieldSchemaAnonymity, MProp, MValidationFail } from './Schema';
2
+ import { MFieldSchemaAnonymity, MProp, MValidationFail } from "./Schema";
3
3
  import { MType, PluginType } from "../types/MType";
4
4
  export type MORPH = "readable" | "editor";
5
5
  export type VIEWER = ClassType<MProp, any, any>;
@@ -44,6 +44,7 @@ interface State {
44
44
  export declare class MViewer extends React.Component<MViewerProp, State> {
45
45
  database: any;
46
46
  constructor(p: MViewerProp);
47
+ componentDidUpdate(prevProps: MViewerProp): void;
47
48
  recover(): void;
48
49
  render(): JSX.Element;
49
50
  }
@@ -1,5 +1,5 @@
1
- import { Assembly } from './Assembly';
2
- import { MFieldSchemaAnonymity, MValidationResult } from './Schema';
1
+ import { Assembly } from "./Assembly";
2
+ import { MFieldSchemaAnonymity, MValidationResult } from "./Schema";
3
3
  export type VALIDATOR = (a: Assembly, schema: MFieldSchemaAnonymity, value: any, path: string) => MValidationResult;
4
4
  /**
5
5
  * 非空校验,数据不能是null/undefined/""/NaN/[]
@@ -0,0 +1,2 @@
1
+ import { MType } from "./MType";
2
+ export declare const MWeightType: MType;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { BaseViewer } from '../../BaseViewer';
2
+ import { BaseViewer } from "../../BaseViewer";
3
3
  /**
4
4
  * 数据表格
5
5
  * 数据是这样的数组:
@@ -1,7 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import "./AForm.less";
3
- import { Viewer, ViewerState } from '../../BaseViewer';
4
- import { MProp } from '../../../framework/Schema';
3
+ import { Viewer, ViewerState } from "../../BaseViewer";
4
+ import { MProp } from "../../../framework/Schema";
5
5
  interface State extends ViewerState {
6
6
  /** 编辑中的分组label,以及编辑前的数据 */
7
7
  editing: {
@@ -0,0 +1,42 @@
1
+ /// <reference types="react" />
2
+ import { Viewer } from "../../BaseViewer";
3
+ import { MEnumField, MProp, ValueConst } from "../../../framework/Schema";
4
+ import { ViewerState } from "../../BaseViewer";
5
+ export interface AWeightProps {
6
+ /** 总比值,默认100 */
7
+ total?: number;
8
+ /** 是否允许小数,默认false只允许整数 */
9
+ allowDecimal?: boolean;
10
+ /** 小数位数,当allowDecimal为true时有效,默认1 */
11
+ decimalPlaces?: number;
12
+ /** 最小步长,默认1 */
13
+ step?: number;
14
+ /** 每个选项的最小比重,默认0 */
15
+ minWeight?: number;
16
+ /** 每个选项的最大比重,默认等于total */
17
+ maxWeight?: number;
18
+ /** 是否显示百分比符号,默认true */
19
+ showPercentage?: boolean;
20
+ /** 是否允许某些选项为0,默认true */
21
+ allowZero?: boolean;
22
+ }
23
+ interface AWeightState extends ViewerState {
24
+ allSliderValues: any;
25
+ }
26
+ export declare class AWeight extends Viewer<AWeightState> {
27
+ _enumFields: MEnumField[];
28
+ _enumValues: ValueConst[];
29
+ allSliderValuesChanged: any;
30
+ private debounceTimer;
31
+ private lastUpdateTime;
32
+ private readonly THROTTLE_DELAY;
33
+ private totalWeight;
34
+ private allValues;
35
+ constructor(props: MProp);
36
+ componentDidMount(): void;
37
+ componentDidUpdate(pre: any, cur: any): void;
38
+ private updateLinkedSliders;
39
+ private smoothValue;
40
+ element(): JSX.Element;
41
+ }
42
+ export {};
@@ -0,0 +1,59 @@
1
+ import React, { ReactNode } from "react";
2
+ import "./EnhancedSortDrag.less";
3
+ /**
4
+ * 拖拽项数据类型定义
5
+ */
6
+ export interface DragItem {
7
+ id: string;
8
+ cpn: ReactNode;
9
+ isChecked: boolean;
10
+ label: string;
11
+ checkedIndex: number;
12
+ }
13
+ /**
14
+ * 拖拽组件属性定义
15
+ */
16
+ export interface EnhancedSortDragProps {
17
+ /** 拖拽项列表 */
18
+ items: DragItem[];
19
+ /** 数据变化回调 */
20
+ onChange: (newItems: DragItem[]) => void;
21
+ /** 是否启用动画效果,默认开启 */
22
+ enableAnimation?: boolean;
23
+ /** 是否支持跨容器拖拽,默认关闭 */
24
+ enableCrossContainer?: boolean;
25
+ /** 拖拽方向,默认垂直 */
26
+ direction?: "vertical" | "horizontal";
27
+ /** 拖拽失败回调 */
28
+ onDragFail?: (error: DragError) => void;
29
+ /** 自定义拖拽句柄渲染 */
30
+ renderDragHandle?: (item: DragItem, isDragging: boolean) => ReactNode;
31
+ /** 拖拽预览内容 */
32
+ renderDragPreview?: (item: DragItem) => ReactNode;
33
+ /** 是否为表格行模式 */
34
+ isTableRow?: boolean;
35
+ }
36
+ /**
37
+ * 拖拽错误类型定义
38
+ */
39
+ export interface DragError {
40
+ type: "VALIDATION_ERROR" | "DATA_ERROR" | "SYSTEM_ERROR";
41
+ message: string;
42
+ itemId?: string;
43
+ sourceId?: string;
44
+ targetId?: string;
45
+ }
46
+ /**
47
+ * 拖拽状态类型定义
48
+ */
49
+ export interface DragState {
50
+ draggingId: string | null;
51
+ droppingId: string | null;
52
+ isAnimating: boolean;
53
+ }
54
+ /**
55
+ * 增强版通用拖拽排序组件
56
+ * 解决原组件的性能问题和功能局限性
57
+ */
58
+ declare const EnhancedSortDrag: React.FC<EnhancedSortDragProps>;
59
+ export default EnhancedSortDrag;