funda-ui 4.7.723 → 4.7.735
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/DragDropList/index.d.ts +1 -0
- package/DragDropList/index.js +143 -52
- package/EventCalendarTimeline/index.js +270 -196
- package/MultipleSelect/index.js +162 -71
- package/Table/index.css +5 -1
- package/Table/index.js +410 -90
- package/Utils/useBoundedDrag.d.ts +1 -0
- package/Utils/useBoundedDrag.js +124 -39
- package/lib/cjs/DragDropList/index.d.ts +1 -0
- package/lib/cjs/DragDropList/index.js +143 -52
- package/lib/cjs/EventCalendarTimeline/index.js +270 -196
- package/lib/cjs/MultipleSelect/index.js +162 -71
- package/lib/cjs/Table/index.js +410 -90
- package/lib/cjs/Utils/useBoundedDrag.d.ts +1 -0
- package/lib/cjs/Utils/useBoundedDrag.js +124 -39
- package/lib/css/Table/index.css +5 -1
- package/lib/esm/DragDropList/index.tsx +23 -16
- package/lib/esm/EventCalendarTimeline/index.tsx +290 -198
- package/lib/esm/Table/Table.tsx +9 -7
- package/lib/esm/Table/TableRow.tsx +9 -3
- package/lib/esm/Table/index.scss +8 -2
- package/lib/esm/Table/utils/DragHandleSprite.tsx +6 -2
- package/lib/esm/Table/utils/func.ts +12 -1
- package/lib/esm/Table/utils/hooks/useTableDraggable.tsx +401 -93
- package/lib/esm/Utils/hooks/useBoundedDrag.tsx +142 -39
- package/package.json +1 -1
|
@@ -112,6 +112,7 @@ export interface BoundedDragOptions {
|
|
|
112
112
|
dragHandleSelector?: string;
|
|
113
113
|
onDragStart?: (index: number) => void;
|
|
114
114
|
onDragOver?: (dragIndex: number | null, dropIndex: number | null) => void;
|
|
115
|
+
onDragUpdate?: (dragIndex: number | null, dropIndex: number | null) => void;
|
|
115
116
|
onDragEnd?: (dragIndex: number | null, dropIndex: number | null) => void;
|
|
116
117
|
}
|
|
117
118
|
export declare const useBoundedDrag: (options?: BoundedDragOptions) => {
|
|
@@ -220,6 +220,7 @@ var useBoundedDrag = function useBoundedDrag() {
|
|
|
220
220
|
dragHandleSelector = _options$dragHandleSe === void 0 ? '.custom-draggable-list__handle' : _options$dragHandleSe,
|
|
221
221
|
onDragStart = options.onDragStart,
|
|
222
222
|
onDragOver = options.onDragOver,
|
|
223
|
+
onDragUpdate = options.onDragUpdate,
|
|
223
224
|
onDragEnd = options.onDragEnd;
|
|
224
225
|
var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),
|
|
225
226
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -228,11 +229,35 @@ var useBoundedDrag = function useBoundedDrag() {
|
|
|
228
229
|
var dragItem = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
229
230
|
var dragOverItem = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
230
231
|
var dragNode = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
232
|
+
var draggedElement = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
233
|
+
var boundaryElement = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
231
234
|
var touchOffset = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)({
|
|
232
235
|
x: 0,
|
|
233
236
|
y: 0
|
|
234
237
|
});
|
|
235
238
|
var currentHoverItem = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
239
|
+
var rafId = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
240
|
+
var lastUpdateDragIndex = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
241
|
+
var lastUpdateDropIndex = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Performance Note:
|
|
245
|
+
*
|
|
246
|
+
* Drag-over events can fire at a very high frequency, especially on touch devices
|
|
247
|
+
* or when dragging quickly. Directly performing DOM read/write operations in the
|
|
248
|
+
* event handler (e.g. `getBoundingClientRect`, `classList` changes, style updates)
|
|
249
|
+
* can easily cause layout thrashing and frame drops when there are many items.
|
|
250
|
+
*
|
|
251
|
+
* To mitigate this, we:
|
|
252
|
+
* - Collect the pointer coordinates synchronously in the event handler.
|
|
253
|
+
* - Schedule all DOM-intensive work inside `requestAnimationFrame`, so the browser
|
|
254
|
+
* batches these operations before the next paint.
|
|
255
|
+
* - Cancel any pending frame (`cancelAnimationFrame`) before scheduling a new one,
|
|
256
|
+
* ensuring there is at most one pending DOM update per frame.
|
|
257
|
+
*
|
|
258
|
+
* This keeps drag interactions smooth even with large lists.
|
|
259
|
+
*/
|
|
260
|
+
|
|
236
261
|
var handleDragStart = function handleDragStart(e, position) {
|
|
237
262
|
var isTouch = ('touches' in e);
|
|
238
263
|
var target = e.target;
|
|
@@ -285,69 +310,127 @@ var useBoundedDrag = function useBoundedDrag() {
|
|
|
285
310
|
opacity: '0.9'
|
|
286
311
|
});
|
|
287
312
|
document.body.appendChild(dragNode.current);
|
|
313
|
+
|
|
314
|
+
// Keep track of the original element (acts as a placeholder inside the list)
|
|
315
|
+
draggedElement.current = listItem;
|
|
316
|
+
boundaryElement.current = boundary;
|
|
288
317
|
setIsDragging(true);
|
|
289
318
|
listItem.classList.add('dragging-placeholder');
|
|
290
319
|
} else {
|
|
291
|
-
//
|
|
320
|
+
// Desktop: use native drag image, but still record dragged element / boundary
|
|
321
|
+
draggedElement.current = listItem;
|
|
322
|
+
boundaryElement.current = boundary;
|
|
323
|
+
setIsDragging(true);
|
|
324
|
+
var dragEvent = e;
|
|
325
|
+
if (dragEvent.dataTransfer) {
|
|
326
|
+
dragEvent.dataTransfer.effectAllowed = 'move';
|
|
327
|
+
// Optional: customize drag preview if needed
|
|
328
|
+
dragEvent.dataTransfer.setData('text/plain', '');
|
|
329
|
+
}
|
|
330
|
+
listItem.classList.add('dragging-placeholder');
|
|
292
331
|
}
|
|
293
332
|
};
|
|
294
333
|
var handleDragOver = function handleDragOver(e) {
|
|
334
|
+
// Always prevent default synchronously
|
|
295
335
|
e.preventDefault();
|
|
296
336
|
var isTouch = ('touches' in e);
|
|
297
337
|
if (!isTouch) {
|
|
298
338
|
e.dataTransfer.dropEffect = 'move';
|
|
299
339
|
}
|
|
300
340
|
|
|
301
|
-
//
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
341
|
+
// Extract primitive coordinates synchronously to avoid using pooled events in async callbacks
|
|
342
|
+
var clientX;
|
|
343
|
+
var clientY;
|
|
344
|
+
if (isTouch) {
|
|
345
|
+
var touch = e.touches[0];
|
|
346
|
+
clientX = touch.clientX;
|
|
347
|
+
clientY = touch.clientY;
|
|
348
|
+
} else {
|
|
349
|
+
clientX = e.clientX;
|
|
350
|
+
clientY = e.clientY;
|
|
351
|
+
}
|
|
306
352
|
|
|
307
|
-
//
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
dragNode.current.style.top = "".concat(point.clientY - touchOffset.current.y, "px");
|
|
353
|
+
// Cancel any pending frame to avoid stacking DOM operations
|
|
354
|
+
if (rafId.current !== null) {
|
|
355
|
+
cancelAnimationFrame(rafId.current);
|
|
311
356
|
}
|
|
357
|
+
rafId.current = requestAnimationFrame(function () {
|
|
358
|
+
// Update dragged element position for touch events
|
|
359
|
+
if (isTouch && isDragging && dragNode.current) {
|
|
360
|
+
dragNode.current.style.left = "".concat(clientX - touchOffset.current.x, "px");
|
|
361
|
+
dragNode.current.style.top = "".concat(clientY - touchOffset.current.y, "px");
|
|
362
|
+
}
|
|
312
363
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
364
|
+
// Find the element below the pointer/touch
|
|
365
|
+
var elemBelow = document.elementFromPoint(clientX, clientY);
|
|
366
|
+
if (!elemBelow) return;
|
|
316
367
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
368
|
+
// Find the closest list item
|
|
369
|
+
var listItem = elemBelow.closest(itemSelector);
|
|
370
|
+
if (!listItem) return;
|
|
320
371
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
372
|
+
// Check boundary
|
|
373
|
+
var boundary = boundaryElement.current || listItem.closest(boundarySelector);
|
|
374
|
+
if (!boundary) return;
|
|
324
375
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
listItem.
|
|
343
|
-
|
|
344
|
-
|
|
376
|
+
// Update hover states
|
|
377
|
+
if (currentHoverItem.current && currentHoverItem.current !== listItem) {
|
|
378
|
+
currentHoverItem.current.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom');
|
|
379
|
+
}
|
|
380
|
+
currentHoverItem.current = listItem;
|
|
381
|
+
listItem.classList.add('drag-over');
|
|
382
|
+
var dragEl = draggedElement.current;
|
|
383
|
+
if (!dragEl || !dragEl.parentNode) return;
|
|
384
|
+
var container = boundary;
|
|
385
|
+
|
|
386
|
+
// Collect current ordered items in the container
|
|
387
|
+
var children = Array.from(container.querySelectorAll(itemSelector));
|
|
388
|
+
var currentIndex = children.indexOf(dragEl);
|
|
389
|
+
var targetIndex = children.indexOf(listItem);
|
|
390
|
+
if (currentIndex === -1 || targetIndex === -1) return;
|
|
391
|
+
|
|
392
|
+
// Determine drop position (top/bottom)
|
|
393
|
+
var rect = listItem.getBoundingClientRect();
|
|
394
|
+
var middleY = rect.top + rect.height / 2;
|
|
395
|
+
listItem.classList.remove('drag-over-top', 'drag-over-bottom');
|
|
396
|
+
var insertBefore = clientY < middleY ? listItem : listItem.nextElementSibling;
|
|
397
|
+
if (clientY < middleY) {
|
|
398
|
+
listItem.classList.add('drag-over-top');
|
|
399
|
+
} else {
|
|
400
|
+
listItem.classList.add('drag-over-bottom');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Only move in DOM when the effective position changes
|
|
404
|
+
if (insertBefore !== dragEl && container.contains(dragEl)) {
|
|
405
|
+
container.insertBefore(dragEl, insertBefore);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Recompute index after DOM move
|
|
409
|
+
var reorderedChildren = Array.from(container.querySelectorAll(itemSelector));
|
|
410
|
+
var newIndex = reorderedChildren.indexOf(dragEl);
|
|
411
|
+
dragOverItem.current = newIndex;
|
|
412
|
+
onDragOver === null || onDragOver === void 0 ? void 0 : onDragOver(dragItem.current, dragOverItem.current);
|
|
413
|
+
|
|
414
|
+
// Only fire onDragUpdate when the (dragIndex, dropIndex) pair actually changes.
|
|
415
|
+
if (onDragUpdate && (dragItem.current !== lastUpdateDragIndex.current || dragOverItem.current !== lastUpdateDropIndex.current)) {
|
|
416
|
+
lastUpdateDragIndex.current = dragItem.current;
|
|
417
|
+
lastUpdateDropIndex.current = dragOverItem.current;
|
|
418
|
+
onDragUpdate(dragItem.current, dragOverItem.current);
|
|
419
|
+
}
|
|
420
|
+
rafId.current = null;
|
|
421
|
+
});
|
|
345
422
|
};
|
|
346
423
|
var handleDragEnd = function handleDragEnd(e) {
|
|
347
424
|
var isTouch = ('touches' in e);
|
|
348
425
|
if (isTouch && !isDragging) return;
|
|
349
426
|
onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(dragItem.current, dragOverItem.current);
|
|
350
427
|
|
|
428
|
+
// Cancel any pending animation frame
|
|
429
|
+
if (rafId.current !== null) {
|
|
430
|
+
cancelAnimationFrame(rafId.current);
|
|
431
|
+
rafId.current = null;
|
|
432
|
+
}
|
|
433
|
+
|
|
351
434
|
// Cleanup
|
|
352
435
|
if (dragNode.current) {
|
|
353
436
|
dragNode.current.remove();
|
|
@@ -361,6 +444,8 @@ var useBoundedDrag = function useBoundedDrag() {
|
|
|
361
444
|
currentHoverItem.current = null;
|
|
362
445
|
dragItem.current = null;
|
|
363
446
|
dragOverItem.current = null;
|
|
447
|
+
draggedElement.current = null;
|
|
448
|
+
boundaryElement.current = null;
|
|
364
449
|
};
|
|
365
450
|
return {
|
|
366
451
|
isDragging: isDragging,
|
package/lib/css/Table/index.css
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
--syntable-row-active-bg: #f0f8ff;
|
|
8
8
|
--syntable-scrollbar-color: rgba(0, 0, 0, 0.2);
|
|
9
9
|
--syntable-scrollbar-track: rgba(0, 0, 0, 0);
|
|
10
|
-
--syntable-scrollbar-h:
|
|
10
|
+
--syntable-scrollbar-h: 10px;
|
|
11
11
|
--syntable-scrollbar-w: 10px;
|
|
12
12
|
--syntable-padding-x: 1rem;
|
|
13
13
|
--syntable-padding-y: 0.5rem;
|
|
@@ -414,10 +414,13 @@
|
|
|
414
414
|
}
|
|
415
415
|
.syntable__wrapper.allow-dragdrop tbody .row-obj-clonelast {
|
|
416
416
|
height: 0 !important;
|
|
417
|
+
border-color: transparent !important;
|
|
418
|
+
padding: 0 !important;
|
|
417
419
|
}
|
|
418
420
|
.syntable__wrapper.allow-dragdrop tbody .row-obj-clonelast td {
|
|
419
421
|
border: none;
|
|
420
422
|
box-shadow: none;
|
|
423
|
+
padding: 0 !important;
|
|
421
424
|
}
|
|
422
425
|
.syntable__wrapper.allow-dragdrop tbody td,
|
|
423
426
|
.syntable__wrapper.allow-dragdrop tbody th {
|
|
@@ -428,6 +431,7 @@
|
|
|
428
431
|
.syntable__wrapper.allow-dragdrop tbody.drag-trigger-mousedown th {
|
|
429
432
|
pointer-events: auto;
|
|
430
433
|
}
|
|
434
|
+
.syntable__wrapper.allow-dragdrop .row-obj-lastplaceholder,
|
|
431
435
|
.syntable__wrapper.allow-dragdrop .row-placeholder {
|
|
432
436
|
border: 2px dotted #b5ba91;
|
|
433
437
|
background-color: #e4e9c3;
|
|
@@ -8,7 +8,6 @@ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
|
|
|
8
8
|
import useBoundedDrag from 'funda-utils/dist/cjs/useBoundedDrag';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
export interface ListItem {
|
|
13
12
|
id: number;
|
|
14
13
|
parentId?: number;
|
|
@@ -19,6 +18,7 @@ export interface ListItem {
|
|
|
19
18
|
depth?: number;
|
|
20
19
|
children?: ListItem[];
|
|
21
20
|
disabled?: boolean;
|
|
21
|
+
itemDraggable?: boolean;
|
|
22
22
|
appendControl?: React.ReactNode;
|
|
23
23
|
[key: string]: any;
|
|
24
24
|
}
|
|
@@ -222,6 +222,10 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
222
222
|
onDragOver: (dragIndex: number | null, dropIndex: number | null) => {
|
|
223
223
|
// Additional drag over logic if needed
|
|
224
224
|
},
|
|
225
|
+
onDragUpdate: (dragIndex: number | null, dropIndex: number | null) => {
|
|
226
|
+
// console.log(dragIndex, dropIndex);
|
|
227
|
+
|
|
228
|
+
},
|
|
225
229
|
onDragEnd: (dragIndex: number | null, dropIndex: number | null) => {
|
|
226
230
|
if (dragIndex !== null && dropIndex !== null && dragIndex !== dropIndex) {
|
|
227
231
|
// Handle item movement
|
|
@@ -251,12 +255,12 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
251
255
|
});
|
|
252
256
|
|
|
253
257
|
// Calculate new insert position
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
+
// Directly use dropIndex as the insertion position to avoid items snapping back
|
|
259
|
+
// when dragging an item from above to directly below its neighbor.
|
|
260
|
+
const insertIndex = dropIndex;
|
|
258
261
|
|
|
259
|
-
// Insert all items
|
|
262
|
+
// Insert all items (remove first, then insert at the target index;
|
|
263
|
+
// JavaScript's splice will handle index shifting automatically).
|
|
260
264
|
newItems.splice(insertIndex, 0, ...itemsBeingMoved);
|
|
261
265
|
|
|
262
266
|
// Rebuild tree structure
|
|
@@ -397,6 +401,9 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
397
401
|
// If the item should be hidden, the rendering is skipped
|
|
398
402
|
if (!shouldShowItem(item)) return null;
|
|
399
403
|
|
|
404
|
+
// Item level draggable control, default true when not specified
|
|
405
|
+
const isItemDraggable = draggable && item.itemDraggable !== false;
|
|
406
|
+
|
|
400
407
|
// collapse
|
|
401
408
|
const hasChildItems = hasChildren(item.id);
|
|
402
409
|
const isCollapsed = collapsedItems.has(item.id);
|
|
@@ -415,7 +422,7 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
415
422
|
clsWrite(dragMode, 'handle'),
|
|
416
423
|
{
|
|
417
424
|
'disabled': item.disabled,
|
|
418
|
-
'draggable':
|
|
425
|
+
'draggable': isItemDraggable,
|
|
419
426
|
'editing': editingItem === item.id,
|
|
420
427
|
|
|
421
428
|
// collapse
|
|
@@ -423,13 +430,13 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
423
430
|
'collapsed': isCollapsed
|
|
424
431
|
}
|
|
425
432
|
)}
|
|
426
|
-
draggable={!
|
|
427
|
-
onDragStart={!
|
|
428
|
-
onDragOver={!
|
|
429
|
-
onDragEnd={!
|
|
430
|
-
onTouchStart={!
|
|
431
|
-
onTouchMove={!
|
|
432
|
-
onTouchEnd={!
|
|
433
|
+
draggable={!isItemDraggable ? undefined : editingItem !== item.id && "true"}
|
|
434
|
+
onDragStart={!isItemDraggable ? undefined : (e) => dragHandlers.handleDragStart(e, index)}
|
|
435
|
+
onDragOver={!isItemDraggable ? undefined : dragHandlers.handleDragOver}
|
|
436
|
+
onDragEnd={!isItemDraggable ? undefined : dragHandlers.handleDragEnd}
|
|
437
|
+
onTouchStart={!isItemDraggable ? undefined : (e) => dragHandlers.handleDragStart(e, index)}
|
|
438
|
+
onTouchMove={!isItemDraggable ? undefined : dragHandlers.handleDragOver}
|
|
439
|
+
onTouchEnd={!isItemDraggable ? undefined : dragHandlers.handleDragEnd}
|
|
433
440
|
style={itemStyle}
|
|
434
441
|
onDoubleClick={() => handleDoubleClick(item)}
|
|
435
442
|
>
|
|
@@ -437,14 +444,14 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
437
444
|
{renderOption ? (
|
|
438
445
|
renderOption(
|
|
439
446
|
item,
|
|
440
|
-
`${prefix}-draggable-list__handle
|
|
447
|
+
isItemDraggable ? `${prefix}-draggable-list__handle` : '',
|
|
441
448
|
index
|
|
442
449
|
)
|
|
443
450
|
) : (
|
|
444
451
|
<>
|
|
445
452
|
{/** DRAG HANDLE */}
|
|
446
453
|
{/* Fix the problem that mobile terminals cannot be touched, DO NOT USE "<svg>" */}
|
|
447
|
-
{
|
|
454
|
+
{isItemDraggable && !handleHide ? <span className={`${prefix}-draggable-list__handle ${handlePos ?? 'left'}`} draggable={dragMode === 'handle'} dangerouslySetInnerHTML={{
|
|
448
455
|
__html: `${handleIcon}`
|
|
449
456
|
}}></span> : null}
|
|
450
457
|
{/** /DRAG HANDLE */}
|