trotl-table 1.0.11 → 1.0.12

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/dist/index.cjs.js CHANGED
@@ -9612,7 +9612,8 @@ const DraggableRow = ({
9612
9612
  index,
9613
9613
  moveRow,
9614
9614
  children,
9615
- item,
9615
+ row,
9616
+ tableId,
9616
9617
  enableDrag,
9617
9618
  listRef
9618
9619
  }) => {
@@ -9621,7 +9622,6 @@ const DraggableRow = ({
9621
9622
  accept: ITEM_TYPE,
9622
9623
  hover(draggedItem, monitor) {
9623
9624
  if (!ref.current || !enableDrag) return;
9624
- ref.current.getBoundingClientRect();
9625
9625
  const clientOffset = monitor.getClientOffset();
9626
9626
 
9627
9627
  // Auto-scroll logic
@@ -9651,23 +9651,31 @@ const DraggableRow = ({
9651
9651
  }, drag] = reactDnd.useDrag({
9652
9652
  type: ITEM_TYPE,
9653
9653
  item: {
9654
- index
9654
+ index,
9655
+ row,
9656
+ sourceTableId: tableId
9655
9657
  },
9656
9658
  canDrag: enableDrag,
9657
9659
  collect: monitor => ({
9658
9660
  isDragging: monitor.isDragging()
9659
9661
  })
9660
9662
  });
9661
- drag(drop(ref));
9663
+ // Compose drag & drop refs in effect to avoid reading ref during render
9664
+ const setRef = React.useCallback(node => {
9665
+ if (node) {
9666
+ ref.current = node;
9667
+ drag(drop(node));
9668
+ }
9669
+ }, [drag, drop]);
9662
9670
  return /*#__PURE__*/React.createElement("div", {
9663
- ref: ref,
9671
+ ref: setRef,
9664
9672
  style: {
9665
9673
  opacity: isDragging ? 0.5 : 1,
9666
9674
  cursor: enableDrag ? "move" : "default"
9667
9675
  }
9668
9676
  }, children);
9669
9677
  };
9670
- function Table({
9678
+ function TableInner({
9671
9679
  tableId = "default",
9672
9680
  columns = [],
9673
9681
  data = [],
@@ -9684,7 +9692,11 @@ function Table({
9684
9692
  deleteCallback = null,
9685
9693
  buttons = ["view", "edit", "delete"],
9686
9694
  enableDragRow = false,
9687
- enableSearchUrlParam = false
9695
+ enableSearchUrlParam = false,
9696
+ enableMultiSelect = false,
9697
+ enableExternalRowDrop = false,
9698
+ onExternalRowDrop = () => {},
9699
+ useExternalDndContext = false
9688
9700
  }) {
9689
9701
  // Local selection & sorting state (removed TableContext dependency)
9690
9702
  const [selectedRows, setSelectedRows] = React.useState([]);
@@ -9822,7 +9834,7 @@ function Table({
9822
9834
  const toggleGroup = gid => {
9823
9835
  setExpandedGroups(prev => ({
9824
9836
  ...prev,
9825
- [gid]: !Boolean(prev[gid])
9837
+ [gid]: !prev[gid]
9826
9838
  }));
9827
9839
  };
9828
9840
  React.useEffect(() => {
@@ -9935,40 +9947,90 @@ function Table({
9935
9947
  const showDelete = buttons.includes("delete");
9936
9948
  const showActions = showView || showEdit || showDelete;
9937
9949
 
9938
- // MOVE ROW
9939
- const moveRow = React.useCallback((fromIndex, toIndex) => {
9950
+ // MOVE ROW (internal + between groups)
9951
+ // moveRow: allow toIndex to be null or an object { emptyGroupId } for empty group drop
9952
+ const moveRow = React.useCallback((fromIndex, toIndexOrGroup) => {
9940
9953
  const from = tableDataFlat[fromIndex];
9941
- const to = tableDataFlat[toIndex];
9942
- if (!from || !to || from.type !== "row" || to.type !== "row") return;
9943
-
9944
- // Only reorder within the same group to keep grouping consistent
9945
- if (isGrouped && from.groupId !== to.groupId) return;
9954
+ let to = null;
9955
+ let emptyGroupId = null;
9956
+ if (typeof toIndexOrGroup === 'object' && toIndexOrGroup && toIndexOrGroup.emptyGroupId) {
9957
+ emptyGroupId = toIndexOrGroup.emptyGroupId;
9958
+ } else {
9959
+ to = tableDataFlat[toIndexOrGroup];
9960
+ }
9961
+ if (!from || from.type !== "row" || toIndexOrGroup !== null && !to && !emptyGroupId) return;
9946
9962
  setLocalData(prev => {
9947
9963
  if (isGrouped) {
9948
- // Reorder within the target group
9949
- const gid = from.groupId;
9950
- return prev.map(group => {
9964
+ // Remove from old group
9965
+ let movedRow;
9966
+ const newGroups = prev.map(group => {
9951
9967
  const groupId = group[groupKey] ?? group.groupId ?? group.groupName;
9952
- if (groupId !== gid) return group;
9953
- const rows = [...group.rows];
9954
- const fromPos = rows.findIndex(r => r.id === from.row.id);
9955
- const toPos = rows.findIndex(r => r.id === to.row.id);
9956
- if (fromPos === -1 || toPos === -1) return group;
9957
- const [moved] = rows.splice(fromPos, 1);
9958
- rows.splice(toPos, 0, moved);
9959
- return {
9960
- ...group,
9961
- rows
9962
- };
9968
+ if (groupId === from.groupId) {
9969
+ const rows = [...group.rows];
9970
+ const fromPos = rows.findIndex(r => r.id === from.row.id);
9971
+ if (fromPos !== -1) {
9972
+ [movedRow] = rows.splice(fromPos, 1);
9973
+ }
9974
+ return {
9975
+ ...group,
9976
+ rows
9977
+ };
9978
+ }
9979
+ return group;
9980
+ });
9981
+ if (!movedRow) return prev;
9982
+ // Insert into new group at correct position
9983
+ return newGroups.map(group => {
9984
+ const groupId = group[groupKey] ?? group.groupId ?? group.groupName;
9985
+ // If dropping into empty group
9986
+ if (emptyGroupId && groupId === emptyGroupId) {
9987
+ const rows = [...group.rows];
9988
+ if (groupKey && movedRow[groupKey] !== undefined) {
9989
+ movedRow = {
9990
+ ...movedRow,
9991
+ [groupKey]: groupId
9992
+ };
9993
+ }
9994
+ rows.push(movedRow);
9995
+ return {
9996
+ ...group,
9997
+ rows
9998
+ };
9999
+ }
10000
+ // Normal drop
10001
+ if (to && groupId === to.groupId) {
10002
+ const rows = [...group.rows];
10003
+ const toPos = rows.findIndex(r => r.id === to.row.id);
10004
+ if (groupKey && movedRow[groupKey] !== undefined) {
10005
+ movedRow = {
10006
+ ...movedRow,
10007
+ [groupKey]: groupId
10008
+ };
10009
+ }
10010
+ if (toPos === -1) {
10011
+ rows.push(movedRow);
10012
+ } else {
10013
+ rows.splice(toPos, 0, movedRow);
10014
+ }
10015
+ return {
10016
+ ...group,
10017
+ rows
10018
+ };
10019
+ }
10020
+ return group;
9963
10021
  });
9964
10022
  } else {
9965
10023
  // Ungrouped: reorder the flat list
9966
10024
  const arr = Array.isArray(prev) ? [...prev] : [];
9967
10025
  const fromPos = arr.findIndex(r => r.id === from.row.id);
9968
- const toPos = arr.findIndex(r => r.id === to.row.id);
9969
- if (fromPos === -1 || toPos === -1) return prev;
10026
+ const toPos = to ? arr.findIndex(r => r.id === to.row.id) : -1;
10027
+ if (fromPos === -1 || to && toPos === -1) return prev;
9970
10028
  const [moved] = arr.splice(fromPos, 1);
9971
- arr.splice(toPos, 0, moved);
10029
+ if (to) {
10030
+ arr.splice(toPos, 0, moved);
10031
+ } else {
10032
+ arr.push(moved);
10033
+ }
9972
10034
  return arr;
9973
10035
  }
9974
10036
  });
@@ -9986,12 +10048,81 @@ function Table({
9986
10048
  const gid = item.groupId;
9987
10049
  const rows = groupRowsById[gid] || [];
9988
10050
  const allSelected = rows.length > 0 && rows.every(r => selectedRows.includes(r.id));
10051
+
10052
+ // If expanded and group is empty and drag enabled, make the group header itself a drop target
10053
+ if (item.expanded && rows.length === 0 && enableDragRow) {
10054
+ // For empty group, drop target pushes into this group
10055
+ const GroupHeaderDrop = () => {
10056
+ const ref = React.useRef(null);
10057
+ const [, drop] = reactDnd.useDrop({
10058
+ accept: ITEM_TYPE,
10059
+ drop(draggedItem) {
10060
+ if (draggedItem && typeof draggedItem.index === "number") {
10061
+ moveRow(draggedItem.index, {
10062
+ emptyGroupId: gid
10063
+ });
10064
+ }
10065
+ },
10066
+ canDrop: () => true,
10067
+ collect: monitor => ({
10068
+ isOver: monitor.isOver(),
10069
+ canDrop: monitor.canDrop()
10070
+ })
10071
+ });
10072
+ drop(ref);
10073
+ return /*#__PURE__*/React.createElement("div", {
10074
+ ref: ref,
10075
+ key: key,
10076
+ style: {
10077
+ ...style,
10078
+ background: '#f6f6fa',
10079
+ border: '2px dashed #bbb',
10080
+ textAlign: 'center',
10081
+ color: '#888',
10082
+ minHeight: rowHeight,
10083
+ display: 'flex',
10084
+ alignItems: 'center'
10085
+ },
10086
+ className: "table-row group-row empty-group-drop",
10087
+ onClick: () => toggleGroup(gid)
10088
+ }, enableMultiSelect && showDelete && /*#__PURE__*/React.createElement("div", {
10089
+ className: "table-cell checkbox-cell"
10090
+ }, /*#__PURE__*/React.createElement("input", {
10091
+ type: "checkbox",
10092
+ checked: allSelected,
10093
+ onChange: e => {
10094
+ e.stopPropagation();
10095
+ const ids = rows.map(r => r.id);
10096
+ if (allSelected) {
10097
+ setSelectedRows(prev => prev.filter(id => !ids.includes(id)));
10098
+ } else {
10099
+ setSelectedRows(prev => [...prev, ...ids.filter(id => !prev.includes(id))]);
10100
+ }
10101
+ },
10102
+ onClick: e => e.stopPropagation()
10103
+ })), showKey && /*#__PURE__*/React.createElement("div", {
10104
+ className: "table-cell key-cell"
10105
+ }), /*#__PURE__*/React.createElement("div", {
10106
+ className: "table-cell group-header",
10107
+ style: {
10108
+ width: '100%'
10109
+ }
10110
+ }, item.expanded ? "▾" : "▸", " ", item.groupName, " (0) \u2014 Drop here to move into this group"), columns.slice(1).map((_, i) => /*#__PURE__*/React.createElement("div", {
10111
+ key: i,
10112
+ className: "table-cell"
10113
+ })), showActions && /*#__PURE__*/React.createElement("div", {
10114
+ className: "table-cell action-cell"
10115
+ }));
10116
+ };
10117
+ return /*#__PURE__*/React.createElement(GroupHeaderDrop, null);
10118
+ }
10119
+ // Default: normal group header
9989
10120
  return /*#__PURE__*/React.createElement("div", {
9990
10121
  key: key,
9991
10122
  style: style,
9992
10123
  className: "table-row group-row",
9993
10124
  onClick: () => toggleGroup(gid)
9994
- }, /*#__PURE__*/React.createElement("div", {
10125
+ }, enableMultiSelect && showDelete && /*#__PURE__*/React.createElement("div", {
9995
10126
  className: "table-cell checkbox-cell"
9996
10127
  }, /*#__PURE__*/React.createElement("input", {
9997
10128
  type: "checkbox",
@@ -9999,7 +10130,11 @@ function Table({
9999
10130
  onChange: e => {
10000
10131
  e.stopPropagation();
10001
10132
  const ids = rows.map(r => r.id);
10002
- ids.forEach(id => toggleRowSelection(tableId, id));
10133
+ if (allSelected) {
10134
+ setSelectedRows(prev => prev.filter(id => !ids.includes(id)));
10135
+ } else {
10136
+ setSelectedRows(prev => [...prev, ...ids.filter(id => !prev.includes(id))]);
10137
+ }
10003
10138
  },
10004
10139
  onClick: e => e.stopPropagation()
10005
10140
  })), showKey && /*#__PURE__*/React.createElement("div", {
@@ -10019,7 +10154,7 @@ function Table({
10019
10154
  key: key,
10020
10155
  style: style,
10021
10156
  className: "table-row"
10022
- }, showDelete && /*#__PURE__*/React.createElement("div", {
10157
+ }, enableMultiSelect && showDelete && /*#__PURE__*/React.createElement("div", {
10023
10158
  className: "table-cell checkbox-cell"
10024
10159
  }, /*#__PURE__*/React.createElement("input", {
10025
10160
  type: "checkbox",
@@ -10060,20 +10195,46 @@ function Table({
10060
10195
  key: key,
10061
10196
  index: index,
10062
10197
  moveRow: moveRow,
10063
- item: item,
10198
+ row: row,
10199
+ tableId: tableId,
10064
10200
  enableDrag: true,
10065
10201
  listRef: listRef
10066
10202
  }, content);
10067
10203
  }
10068
10204
  return content;
10069
- }, [tableDataFlat, columns, selectedRows, toggleRowSelection, groupRowsById, renderCell, showActions, showDelete, showEdit, showKey, showView, tableId, viewCallback, editCallback, enableDragRow, moveRow]);
10205
+ }, [tableDataFlat, columns, selectedRows, toggleRowSelection, groupRowsById, renderCell, showActions, showDelete, showEdit, showKey, showView, viewCallback, editCallback, enableDragRow, moveRow, enableMultiSelect, rowHeight, tableId]);
10070
10206
  const rowHeightGetter = ({
10071
10207
  index
10072
10208
  }) => tableDataFlat[index]?.type === "group" ? groupHeaderHeight : rowHeight;
10073
- return /*#__PURE__*/React.createElement(reactDnd.DndProvider, {
10074
- backend: reactDndHtml5Backend.HTML5Backend
10075
- }, /*#__PURE__*/React.createElement("div", {
10076
- className: "table-container"
10209
+
10210
+ // Table-level drop target only when using external DnD context
10211
+ const tableContainerRef = React.useRef(null);
10212
+ const [{
10213
+ isOver: isExternalOver,
10214
+ canDrop: canExternalDrop
10215
+ }, externalDrop] = reactDnd.useDrop({
10216
+ accept: ITEM_TYPE,
10217
+ drop: draggedItem => {
10218
+ if (!useExternalDndContext) return; // safety
10219
+ if (!enableExternalRowDrop || isGrouped) return;
10220
+ if (!draggedItem || draggedItem.sourceTableId === tableId) return;
10221
+ onExternalRowDrop(draggedItem.row, draggedItem.sourceTableId, tableId);
10222
+ }
10223
+ });
10224
+ const setTableRef = React.useCallback(node => {
10225
+ tableContainerRef.current = node;
10226
+ if (node && useExternalDndContext && enableExternalRowDrop) {
10227
+ externalDrop(node);
10228
+ }
10229
+ }, [useExternalDndContext, enableExternalRowDrop, externalDrop]);
10230
+ const tableMarkup = /*#__PURE__*/React.createElement("div", {
10231
+ className: "table-container",
10232
+ ref: setTableRef,
10233
+ style: useExternalDndContext && enableExternalRowDrop ? {
10234
+ transition: 'background 0.15s',
10235
+ background: isExternalOver && canExternalDrop ? 'rgba(100,108,255,0.15)' : undefined,
10236
+ outline: isExternalOver && canExternalDrop ? '2px dashed #646cff' : undefined
10237
+ } : undefined
10077
10238
  }, /*#__PURE__*/React.createElement("div", {
10078
10239
  className: "table-header",
10079
10240
  style: {
@@ -10084,7 +10245,7 @@ function Table({
10084
10245
  }
10085
10246
  }, /*#__PURE__*/React.createElement("div", {
10086
10247
  className: "table-row header-row"
10087
- }, showDelete && /*#__PURE__*/React.createElement("div", {
10248
+ }, enableMultiSelect && showDelete && /*#__PURE__*/React.createElement("div", {
10088
10249
  className: "table-cell checkbox-cell"
10089
10250
  }, /*#__PURE__*/React.createElement("input", {
10090
10251
  type: "checkbox",
@@ -10132,7 +10293,20 @@ function Table({
10132
10293
  cancelLabel: "Cancel",
10133
10294
  showCancel: true,
10134
10295
  showConfirm: true
10135
- }, /*#__PURE__*/React.createElement("p", null, "Are you sure you want to delete ", /*#__PURE__*/React.createElement("strong", null, pendingDelete?.name || pendingDelete?.id), "?"))));
10296
+ }, /*#__PURE__*/React.createElement("p", null, "Are you sure you want to delete ", /*#__PURE__*/React.createElement("strong", null, pendingDelete?.name || pendingDelete?.id), "?")));
10297
+ return tableMarkup;
10298
+ }
10299
+ function Table(props) {
10300
+ const {
10301
+ useExternalDndContext = false
10302
+ } = props;
10303
+ if (useExternalDndContext) {
10304
+ return /*#__PURE__*/React.createElement(TableInner, props);
10305
+ }
10306
+ // Provide DnD context when not supplied externally
10307
+ return /*#__PURE__*/React.createElement(reactDnd.DndProvider, {
10308
+ backend: reactDndHtml5Backend.HTML5Backend
10309
+ }, /*#__PURE__*/React.createElement(TableInner, props));
10136
10310
  }
10137
10311
 
10138
10312
  // src/index.js