funda-ui 4.7.723 → 4.7.730

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.
@@ -9,6 +9,7 @@ export interface ListItem {
9
9
  depth?: number;
10
10
  children?: ListItem[];
11
11
  disabled?: boolean;
12
+ itemDraggable?: boolean;
12
13
  appendControl?: React.ReactNode;
13
14
  [key: string]: any;
14
15
  }
@@ -895,6 +895,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
895
895
  dragHandleSelector = _options$dragHandleSe === void 0 ? '.custom-draggable-list__handle' : _options$dragHandleSe,
896
896
  onDragStart = options.onDragStart,
897
897
  onDragOver = options.onDragOver,
898
+ onDragUpdate = options.onDragUpdate,
898
899
  onDragEnd = options.onDragEnd;
899
900
  var _useState = (0, react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),
900
901
  _useState2 = _slicedToArray(_useState, 2),
@@ -903,11 +904,35 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
903
904
  var dragItem = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
904
905
  var dragOverItem = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
905
906
  var dragNode = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
907
+ var draggedElement = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
908
+ var boundaryElement = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
906
909
  var touchOffset = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)({
907
910
  x: 0,
908
911
  y: 0
909
912
  });
910
913
  var currentHoverItem = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
914
+ var rafId = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
915
+ var lastUpdateDragIndex = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
916
+ var lastUpdateDropIndex = (0, react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
917
+
918
+ /**
919
+ * Performance Note:
920
+ *
921
+ * Drag-over events can fire at a very high frequency, especially on touch devices
922
+ * or when dragging quickly. Directly performing DOM read/write operations in the
923
+ * event handler (e.g. `getBoundingClientRect`, `classList` changes, style updates)
924
+ * can easily cause layout thrashing and frame drops when there are many items.
925
+ *
926
+ * To mitigate this, we:
927
+ * - Collect the pointer coordinates synchronously in the event handler.
928
+ * - Schedule all DOM-intensive work inside `requestAnimationFrame`, so the browser
929
+ * batches these operations before the next paint.
930
+ * - Cancel any pending frame (`cancelAnimationFrame`) before scheduling a new one,
931
+ * ensuring there is at most one pending DOM update per frame.
932
+ *
933
+ * This keeps drag interactions smooth even with large lists.
934
+ */
935
+
911
936
  var handleDragStart = function handleDragStart(e, position) {
912
937
  var isTouch = ('touches' in e);
913
938
  var target = e.target;
@@ -960,69 +985,127 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
960
985
  opacity: '0.9'
961
986
  });
962
987
  document.body.appendChild(dragNode.current);
988
+
989
+ // Keep track of the original element (acts as a placeholder inside the list)
990
+ draggedElement.current = listItem;
991
+ boundaryElement.current = boundary;
963
992
  setIsDragging(true);
964
993
  listItem.classList.add('dragging-placeholder');
965
994
  } else {
966
- // ... desktop drag logic remains the same ...
995
+ // Desktop: use native drag image, but still record dragged element / boundary
996
+ draggedElement.current = listItem;
997
+ boundaryElement.current = boundary;
998
+ setIsDragging(true);
999
+ var dragEvent = e;
1000
+ if (dragEvent.dataTransfer) {
1001
+ dragEvent.dataTransfer.effectAllowed = 'move';
1002
+ // Optional: customize drag preview if needed
1003
+ dragEvent.dataTransfer.setData('text/plain', '');
1004
+ }
1005
+ listItem.classList.add('dragging-placeholder');
967
1006
  }
968
1007
  };
969
1008
  var handleDragOver = function handleDragOver(e) {
1009
+ // Always prevent default synchronously
970
1010
  e.preventDefault();
971
1011
  var isTouch = ('touches' in e);
972
1012
  if (!isTouch) {
973
1013
  e.dataTransfer.dropEffect = 'move';
974
1014
  }
975
1015
 
976
- // Get the current pointer/touch position
977
- var point = isTouch ? e.touches[0] : {
978
- clientX: e.clientX,
979
- clientY: e.clientY
980
- };
1016
+ // Extract primitive coordinates synchronously to avoid using pooled events in async callbacks
1017
+ var clientX;
1018
+ var clientY;
1019
+ if (isTouch) {
1020
+ var touch = e.touches[0];
1021
+ clientX = touch.clientX;
1022
+ clientY = touch.clientY;
1023
+ } else {
1024
+ clientX = e.clientX;
1025
+ clientY = e.clientY;
1026
+ }
981
1027
 
982
- // Update dragged element position for touch events
983
- if (isTouch && isDragging && dragNode.current) {
984
- dragNode.current.style.left = "".concat(point.clientX - touchOffset.current.x, "px");
985
- dragNode.current.style.top = "".concat(point.clientY - touchOffset.current.y, "px");
1028
+ // Cancel any pending frame to avoid stacking DOM operations
1029
+ if (rafId.current !== null) {
1030
+ cancelAnimationFrame(rafId.current);
986
1031
  }
1032
+ rafId.current = requestAnimationFrame(function () {
1033
+ // Update dragged element position for touch events
1034
+ if (isTouch && isDragging && dragNode.current) {
1035
+ dragNode.current.style.left = "".concat(clientX - touchOffset.current.x, "px");
1036
+ dragNode.current.style.top = "".concat(clientY - touchOffset.current.y, "px");
1037
+ }
987
1038
 
988
- // Find the element below the pointer/touch
989
- var elemBelow = document.elementFromPoint(point.clientX, point.clientY);
990
- if (!elemBelow) return;
1039
+ // Find the element below the pointer/touch
1040
+ var elemBelow = document.elementFromPoint(clientX, clientY);
1041
+ if (!elemBelow) return;
991
1042
 
992
- // Find the closest list item
993
- var listItem = elemBelow.closest(itemSelector);
994
- if (!listItem || listItem === currentHoverItem.current) return;
1043
+ // Find the closest list item
1044
+ var listItem = elemBelow.closest(itemSelector);
1045
+ if (!listItem) return;
995
1046
 
996
- // Check boundary
997
- var boundary = listItem.closest(boundarySelector);
998
- if (!boundary) return;
1047
+ // Check boundary
1048
+ var boundary = boundaryElement.current || listItem.closest(boundarySelector);
1049
+ if (!boundary) return;
999
1050
 
1000
- // Update hover states
1001
- if (currentHoverItem.current) {
1002
- currentHoverItem.current.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom');
1003
- }
1004
- currentHoverItem.current = listItem;
1005
- listItem.classList.add('drag-over');
1006
-
1007
- // Calculate position in list
1008
- var position = Array.from(listItem.parentNode.children).indexOf(listItem);
1009
- dragOverItem.current = position;
1010
-
1011
- // Determine drop position (top/bottom)
1012
- var rect = listItem.getBoundingClientRect();
1013
- var middleY = rect.top + rect.height / 2;
1014
- if (point.clientY < middleY) {
1015
- listItem.classList.add('drag-over-top');
1016
- } else {
1017
- listItem.classList.add('drag-over-bottom');
1018
- }
1019
- onDragOver === null || onDragOver === void 0 ? void 0 : onDragOver(dragItem.current, dragOverItem.current);
1051
+ // Update hover states
1052
+ if (currentHoverItem.current && currentHoverItem.current !== listItem) {
1053
+ currentHoverItem.current.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom');
1054
+ }
1055
+ currentHoverItem.current = listItem;
1056
+ listItem.classList.add('drag-over');
1057
+ var dragEl = draggedElement.current;
1058
+ if (!dragEl || !dragEl.parentNode) return;
1059
+ var container = boundary;
1060
+
1061
+ // Collect current ordered items in the container
1062
+ var children = Array.from(container.querySelectorAll(itemSelector));
1063
+ var currentIndex = children.indexOf(dragEl);
1064
+ var targetIndex = children.indexOf(listItem);
1065
+ if (currentIndex === -1 || targetIndex === -1) return;
1066
+
1067
+ // Determine drop position (top/bottom)
1068
+ var rect = listItem.getBoundingClientRect();
1069
+ var middleY = rect.top + rect.height / 2;
1070
+ listItem.classList.remove('drag-over-top', 'drag-over-bottom');
1071
+ var insertBefore = clientY < middleY ? listItem : listItem.nextElementSibling;
1072
+ if (clientY < middleY) {
1073
+ listItem.classList.add('drag-over-top');
1074
+ } else {
1075
+ listItem.classList.add('drag-over-bottom');
1076
+ }
1077
+
1078
+ // Only move in DOM when the effective position changes
1079
+ if (insertBefore !== dragEl && container.contains(dragEl)) {
1080
+ container.insertBefore(dragEl, insertBefore);
1081
+ }
1082
+
1083
+ // Recompute index after DOM move
1084
+ var reorderedChildren = Array.from(container.querySelectorAll(itemSelector));
1085
+ var newIndex = reorderedChildren.indexOf(dragEl);
1086
+ dragOverItem.current = newIndex;
1087
+ onDragOver === null || onDragOver === void 0 ? void 0 : onDragOver(dragItem.current, dragOverItem.current);
1088
+
1089
+ // Only fire onDragUpdate when the (dragIndex, dropIndex) pair actually changes.
1090
+ if (onDragUpdate && (dragItem.current !== lastUpdateDragIndex.current || dragOverItem.current !== lastUpdateDropIndex.current)) {
1091
+ lastUpdateDragIndex.current = dragItem.current;
1092
+ lastUpdateDropIndex.current = dragOverItem.current;
1093
+ onDragUpdate(dragItem.current, dragOverItem.current);
1094
+ }
1095
+ rafId.current = null;
1096
+ });
1020
1097
  };
1021
1098
  var handleDragEnd = function handleDragEnd(e) {
1022
1099
  var isTouch = ('touches' in e);
1023
1100
  if (isTouch && !isDragging) return;
1024
1101
  onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(dragItem.current, dragOverItem.current);
1025
1102
 
1103
+ // Cancel any pending animation frame
1104
+ if (rafId.current !== null) {
1105
+ cancelAnimationFrame(rafId.current);
1106
+ rafId.current = null;
1107
+ }
1108
+
1026
1109
  // Cleanup
1027
1110
  if (dragNode.current) {
1028
1111
  dragNode.current.remove();
@@ -1036,6 +1119,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
1036
1119
  currentHoverItem.current = null;
1037
1120
  dragItem.current = null;
1038
1121
  dragOverItem.current = null;
1122
+ draggedElement.current = null;
1123
+ boundaryElement.current = null;
1039
1124
  };
1040
1125
  return {
1041
1126
  isDragging: isDragging,
@@ -1366,6 +1451,9 @@ var DragDropList = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef
1366
1451
  onDragOver: function onDragOver(dragIndex, dropIndex) {
1367
1452
  // Additional drag over logic if needed
1368
1453
  },
1454
+ onDragUpdate: function onDragUpdate(dragIndex, dropIndex) {
1455
+ // console.log(dragIndex, dropIndex);
1456
+ },
1369
1457
  onDragEnd: function onDragEnd(dragIndex, dropIndex) {
1370
1458
  if (dragIndex !== null && dropIndex !== null && dragIndex !== dropIndex) {
1371
1459
  var _newItems$dragIndex, _newItems$dragIndex2, _newItems$dropIndex;
@@ -1398,12 +1486,12 @@ var DragDropList = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef
1398
1486
  });
1399
1487
 
1400
1488
  // Calculate new insert position
1489
+ // Directly use dropIndex as the insertion position to avoid items snapping back
1490
+ // when dragging an item from above to directly below its neighbor.
1401
1491
  var insertIndex = dropIndex;
1402
- if (dropIndex > dragIndex) {
1403
- insertIndex -= itemsToMove.length;
1404
- }
1405
1492
 
1406
- // Insert all items
1493
+ // Insert all items (remove first, then insert at the target index;
1494
+ // JavaScript's splice will handle index shifting automatically).
1407
1495
  newItems.splice.apply(newItems, [insertIndex, 0].concat(_toConsumableArray(itemsBeingMoved)));
1408
1496
 
1409
1497
  // Rebuild tree structure
@@ -1516,6 +1604,9 @@ var DragDropList = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef
1516
1604
  // If the item should be hidden, the rendering is skipped
1517
1605
  if (!shouldShowItem(item)) return null;
1518
1606
 
1607
+ // Item level draggable control, default true when not specified
1608
+ var isItemDraggable = draggable && item.itemDraggable !== false;
1609
+
1519
1610
  // collapse
1520
1611
  var hasChildItems = hasChildren(item.id);
1521
1612
  var isCollapsed = collapsedItems.has(item.id);
@@ -1529,30 +1620,30 @@ var DragDropList = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef
1529
1620
  "data-listitemlabel": item.listItemLabel,
1530
1621
  className: (0,funda_utils_dist_cjs_cls__WEBPACK_IMPORTED_MODULE_2__.combinedCls)("".concat(prefix, "-draggable-list__item"), (0,funda_utils_dist_cjs_cls__WEBPACK_IMPORTED_MODULE_2__.clsWrite)(dragMode, 'handle'), {
1531
1622
  'disabled': item.disabled,
1532
- 'draggable': draggable,
1623
+ 'draggable': isItemDraggable,
1533
1624
  'editing': editingItem === item.id,
1534
1625
  // collapse
1535
1626
  'has-children': hasChildItems,
1536
1627
  'collapsed': isCollapsed
1537
1628
  }),
1538
- draggable: !draggable ? undefined : editingItem !== item.id && "true",
1539
- onDragStart: !draggable ? undefined : function (e) {
1629
+ draggable: !isItemDraggable ? undefined : editingItem !== item.id && "true",
1630
+ onDragStart: !isItemDraggable ? undefined : function (e) {
1540
1631
  return dragHandlers.handleDragStart(e, index);
1541
1632
  },
1542
- onDragOver: !draggable ? undefined : dragHandlers.handleDragOver,
1543
- onDragEnd: !draggable ? undefined : dragHandlers.handleDragEnd,
1544
- onTouchStart: !draggable ? undefined : function (e) {
1633
+ onDragOver: !isItemDraggable ? undefined : dragHandlers.handleDragOver,
1634
+ onDragEnd: !isItemDraggable ? undefined : dragHandlers.handleDragEnd,
1635
+ onTouchStart: !isItemDraggable ? undefined : function (e) {
1545
1636
  return dragHandlers.handleDragStart(e, index);
1546
1637
  },
1547
- onTouchMove: !draggable ? undefined : dragHandlers.handleDragOver,
1548
- onTouchEnd: !draggable ? undefined : dragHandlers.handleDragEnd,
1638
+ onTouchMove: !isItemDraggable ? undefined : dragHandlers.handleDragOver,
1639
+ onTouchEnd: !isItemDraggable ? undefined : dragHandlers.handleDragEnd,
1549
1640
  style: itemStyle,
1550
1641
  onDoubleClick: function onDoubleClick() {
1551
1642
  return handleDoubleClick(item);
1552
1643
  }
1553
1644
  }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
1554
1645
  className: "".concat(prefix, "-draggable-list__itemcontent")
1555
- }, renderOption ? renderOption(item, "".concat(prefix, "-draggable-list__handle"), index) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, draggable && !handleHide ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
1646
+ }, renderOption ? renderOption(item, isItemDraggable ? "".concat(prefix, "-draggable-list__handle") : '', index) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, isItemDraggable && !handleHide ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
1556
1647
  className: "".concat(prefix, "-draggable-list__handle ").concat(handlePos !== null && handlePos !== void 0 ? handlePos : 'left'),
1557
1648
  draggable: dragMode === 'handle',
1558
1649
  dangerouslySetInnerHTML: {