@ncds/ui-admin 1.8.28-alpha.3 → 1.8.28-alpha.4
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/cjs/assets/scripts/table/rowSpanBlock.js +97 -0
- package/dist/cjs/src/components/data-display/table/Table.js +135 -57
- package/dist/cjs/src/components/data-display/table/__tests__/Table.dnd.dragCell.test.js +7 -2
- package/dist/cjs/src/components/data-display/table/__tests__/Table.dnd.test.js +13 -8
- package/dist/cjs/src/components/data-display/table/__tests__/Table.rowGroup.test.js +54 -0
- package/dist/cjs/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.js +212 -0
- package/dist/cjs/src/components/data-display/table/__tests__/dnd-context.test.js +5 -2
- package/dist/cjs/src/components/data-display/table/__tests__/tableSelectableStyle.test.js +75 -0
- package/dist/cjs/src/components/data-display/table/dnd-context.js +4 -1
- package/dist/cjs/src/components/data-display/table/dnd-move.js +46 -0
- package/dist/cjs/src/components/data-display/table/dnd-preview.js +8 -3
- package/dist/cjs/src/generated/scoped-css.js +1 -1
- package/dist/esm/assets/scripts/table/rowSpanBlock.js +85 -0
- package/dist/esm/src/components/data-display/table/Table.js +135 -57
- package/dist/esm/src/components/data-display/table/__tests__/Table.dnd.dragCell.test.js +7 -2
- package/dist/esm/src/components/data-display/table/__tests__/Table.dnd.test.js +13 -8
- package/dist/esm/src/components/data-display/table/__tests__/Table.rowGroup.test.js +51 -0
- package/dist/esm/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.js +210 -0
- package/dist/esm/src/components/data-display/table/__tests__/dnd-context.test.js +5 -2
- package/dist/esm/src/components/data-display/table/__tests__/tableSelectableStyle.test.js +72 -0
- package/dist/esm/src/components/data-display/table/dnd-context.js +4 -1
- package/dist/esm/src/components/data-display/table/dnd-move.js +38 -0
- package/dist/esm/src/components/data-display/table/dnd-preview.js +8 -3
- package/dist/esm/src/generated/scoped-css.js +1 -1
- package/dist/temp/assets/scripts/table/rowSpanBlock.d.ts +29 -0
- package/dist/temp/assets/scripts/table/rowSpanBlock.js +77 -0
- package/dist/temp/src/components/data-display/table/Table.d.ts +7 -3
- package/dist/temp/src/components/data-display/table/Table.js +107 -47
- package/dist/temp/src/components/data-display/table/__tests__/Table.dnd.dragCell.test.js +10 -2
- package/dist/temp/src/components/data-display/table/__tests__/Table.dnd.test.js +16 -8
- package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroup.test.d.ts +1 -0
- package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroup.test.js +52 -0
- package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.d.ts +1 -0
- package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.js +137 -0
- package/dist/temp/src/components/data-display/table/__tests__/dnd-context.test.js +2 -2
- package/dist/temp/src/components/data-display/table/__tests__/tableSelectableStyle.test.d.ts +1 -0
- package/dist/temp/src/components/data-display/table/__tests__/tableSelectableStyle.test.js +71 -0
- package/dist/temp/src/components/data-display/table/dnd-context.d.ts +3 -0
- package/dist/temp/src/components/data-display/table/dnd-context.js +1 -0
- package/dist/temp/src/components/data-display/table/dnd-move.d.ts +17 -0
- package/dist/temp/src/components/data-display/table/dnd-move.js +31 -0
- package/dist/temp/src/components/data-display/table/dnd-preview.d.ts +5 -1
- package/dist/temp/src/components/data-display/table/dnd-preview.js +7 -3
- package/dist/temp/src/components/data-display/table/types.d.ts +14 -5
- package/dist/temp/src/generated/scoped-css.js +1 -1
- package/dist/types/assets/scripts/table/rowSpanBlock.d.ts +29 -0
- package/dist/types/src/components/data-display/table/Table.d.ts +7 -3
- package/dist/types/src/components/data-display/table/__tests__/Table.rowGroup.test.d.ts +1 -0
- package/dist/types/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.d.ts +1 -0
- package/dist/types/src/components/data-display/table/__tests__/tableSelectableStyle.test.d.ts +1 -0
- package/dist/types/src/components/data-display/table/dnd-context.d.ts +3 -0
- package/dist/types/src/components/data-display/table/dnd-move.d.ts +17 -0
- package/dist/types/src/components/data-display/table/dnd-preview.d.ts +5 -1
- package/dist/types/src/components/data-display/table/types.d.ts +14 -5
- package/dist/ui-admin/assets/styles/style.css +60 -4
- package/package.json +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.readRowSpans = exports.locateRow = exports.expandRowsToBlocks = exports.computeRowSpanBlock = exports.computeOpenEdges = exports.collectBlockIndices = void 0;
|
|
7
|
+
const SINGLE_ROW = 1;
|
|
8
|
+
const collectRanges = rowSpans => rowSpans.flatMap((spans, index) => spans.filter(span => span > SINGLE_ROW).map(span => ({
|
|
9
|
+
start: index,
|
|
10
|
+
end: Math.min(index + span - SINGLE_ROW, rowSpans.length - 1)
|
|
11
|
+
})));
|
|
12
|
+
const overlaps = (a, b) => a.start <= b.end && b.start <= a.end;
|
|
13
|
+
/**
|
|
14
|
+
* 병합을 쪼개지 않고 통째로 옮길 수 있는 최소 행 구간을 구한다.
|
|
15
|
+
* 범위가 겹치면 어느 쪽도 쪼갤 수 없으므로 연결된 범위를 모두 합친다.
|
|
16
|
+
*
|
|
17
|
+
* @param rowSpans 행별 셀 rowSpan 목록. 병합에 덮여 생략된 셀은 포함하지 않는다
|
|
18
|
+
* @param index 기준 행의 위치
|
|
19
|
+
*/
|
|
20
|
+
const computeRowSpanBlock = (rowSpans, index) => {
|
|
21
|
+
const ranges = collectRanges(rowSpans);
|
|
22
|
+
let block = {
|
|
23
|
+
start: index,
|
|
24
|
+
end: index
|
|
25
|
+
};
|
|
26
|
+
let expanded = true;
|
|
27
|
+
while (expanded) {
|
|
28
|
+
expanded = false;
|
|
29
|
+
for (const range of ranges) {
|
|
30
|
+
if (!overlaps(range, block)) continue;
|
|
31
|
+
const start = Math.min(block.start, range.start);
|
|
32
|
+
const end = Math.max(block.end, range.end);
|
|
33
|
+
if (start !== block.start || end !== block.end) {
|
|
34
|
+
block = {
|
|
35
|
+
start,
|
|
36
|
+
end
|
|
37
|
+
};
|
|
38
|
+
expanded = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return block;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* 드롭을 받을 수 있는 변. 블록 가운데에 떨어뜨리면 그 블록이 쪼개지므로 경계만 연다.
|
|
46
|
+
*/
|
|
47
|
+
exports.computeRowSpanBlock = computeRowSpanBlock;
|
|
48
|
+
const computeOpenEdges = (rowSpans, index) => {
|
|
49
|
+
const block = computeRowSpanBlock(rowSpans, index);
|
|
50
|
+
const edges = [];
|
|
51
|
+
if (index === block.start) edges.push('top');
|
|
52
|
+
if (index === block.end) edges.push('bottom');
|
|
53
|
+
return edges;
|
|
54
|
+
};
|
|
55
|
+
/** 고른 행들이 끌고 오는 행 전부. 병합 블록을 쪼개면 병합이 깨지므로 블록째 더한다. */
|
|
56
|
+
exports.computeOpenEdges = computeOpenEdges;
|
|
57
|
+
const collectBlockIndices = (rowSpans, indices) => {
|
|
58
|
+
const picked = new Set();
|
|
59
|
+
for (const index of indices) {
|
|
60
|
+
const block = computeRowSpanBlock(rowSpans, index);
|
|
61
|
+
for (let row = block.start; row <= block.end; row++) {
|
|
62
|
+
picked.add(row);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return Array.from(picked).sort((a, b) => a - b);
|
|
66
|
+
};
|
|
67
|
+
/** 행이 속한 `tbody` 와 그 안에서의 위치. 표 밖 요소면 `null`. */
|
|
68
|
+
exports.collectBlockIndices = collectBlockIndices;
|
|
69
|
+
const locateRow = rowEl => {
|
|
70
|
+
const body = rowEl.closest('tbody');
|
|
71
|
+
if (!body) return null;
|
|
72
|
+
const index = Array.from(body.rows).indexOf(rowEl);
|
|
73
|
+
return index < 0 ? null : {
|
|
74
|
+
body,
|
|
75
|
+
index
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/** 표 본문의 행별 rowSpan 을 읽어 `computeRowSpanBlock` 에 넘길 형태로 만든다. */
|
|
79
|
+
exports.locateRow = locateRow;
|
|
80
|
+
const readRowSpans = body => Array.from(body.rows).map(row => Array.from(row.cells).map(cell => cell.rowSpan));
|
|
81
|
+
/** 다중 선택으로 고른 행들이 끌고 오는 행 전부. 행 순서는 표에 놓인 순서를 따른다. */
|
|
82
|
+
exports.readRowSpans = readRowSpans;
|
|
83
|
+
const expandRowsToBlocks = rows => {
|
|
84
|
+
const perBody = new Map();
|
|
85
|
+
for (const row of rows) {
|
|
86
|
+
const located = locateRow(row);
|
|
87
|
+
if (!located) continue;
|
|
88
|
+
const picked = perBody.get(located.body) ?? [];
|
|
89
|
+
picked.push(located.index);
|
|
90
|
+
perBody.set(located.body, picked);
|
|
91
|
+
}
|
|
92
|
+
return Array.from(perBody).flatMap(_ref => {
|
|
93
|
+
let [body, indices] = _ref;
|
|
94
|
+
return collectBlockIndices(readRowSpans(body), indices).map(index => body.rows[index]);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
exports.expandRowsToBlocks = expandRowsToBlocks;
|
|
@@ -12,8 +12,10 @@ var _closestEdge = require("@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edg
|
|
|
12
12
|
var _uiAdminIcon = require("@ncds/ui-admin-icon");
|
|
13
13
|
var _classnames = _interopRequireDefault(require("classnames"));
|
|
14
14
|
var _react = require("react");
|
|
15
|
+
var _rowSpanBlock = require("../../../../assets/scripts/table/rowSpanBlock");
|
|
15
16
|
var _FloatingContext = require("../../../contexts/FloatingContext");
|
|
16
17
|
var _dndContext = require("./dnd-context");
|
|
18
|
+
var _dndMove = require("./dnd-move");
|
|
17
19
|
var _dndPreview = require("./dnd-preview");
|
|
18
20
|
var _useTableScrollbars = require("./useTableScrollbars");
|
|
19
21
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -53,49 +55,51 @@ const Header = _ref => {
|
|
|
53
55
|
});
|
|
54
56
|
};
|
|
55
57
|
Header.displayName = 'Table.Header';
|
|
58
|
+
// 묶음마다 호출되므로 핸들러 없는 묶음이 있는 묶음을 덮지 않게 둔다.
|
|
59
|
+
// 거둘 때는 자기가 넣은 것만 비운다 — 조건부로 넘긴 핸들러를 끄면 드롭도 멈춰야 한다.
|
|
60
|
+
const useRowDropHandler = onRowDrop => {
|
|
61
|
+
const {
|
|
62
|
+
onRowDropRef
|
|
63
|
+
} = (0, _react.useContext)(_dndContext.TableDndContext);
|
|
64
|
+
(0, _react.useEffect)(() => {
|
|
65
|
+
if (!onRowDrop) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
onRowDropRef.current = onRowDrop;
|
|
69
|
+
return () => {
|
|
70
|
+
if (onRowDropRef.current === onRowDrop) {
|
|
71
|
+
onRowDropRef.current = undefined;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}, [onRowDrop, onRowDropRef]);
|
|
75
|
+
};
|
|
56
76
|
const Body = _ref2 => {
|
|
57
77
|
let {
|
|
58
78
|
children,
|
|
59
79
|
className,
|
|
60
80
|
onRowDrop
|
|
61
81
|
} = _ref2;
|
|
62
|
-
|
|
63
|
-
tableId
|
|
64
|
-
} = (0, _react.useContext)(_dndContext.TableDndContext);
|
|
65
|
-
// onRowDrop 참조를 ref로 유지 — 콜백이 매 렌더마다 재생성되어도 effect가 재구독하지 않도록
|
|
66
|
-
const onRowDropRef = (0, _react.useRef)(onRowDrop);
|
|
67
|
-
onRowDropRef.current = onRowDrop;
|
|
68
|
-
(0, _react.useEffect)(() => {
|
|
69
|
-
return (0, _adapter.monitorForElements)({
|
|
70
|
-
canMonitor: _ref3 => {
|
|
71
|
-
let {
|
|
72
|
-
source
|
|
73
|
-
} = _ref3;
|
|
74
|
-
return source.data.tableId === tableId;
|
|
75
|
-
},
|
|
76
|
-
onDrop: _ref4 => {
|
|
77
|
-
let {
|
|
78
|
-
source,
|
|
79
|
-
location
|
|
80
|
-
} = _ref4;
|
|
81
|
-
if (!onRowDropRef.current) return;
|
|
82
|
-
const target = location.current.dropTargets[0];
|
|
83
|
-
if (!target) return;
|
|
84
|
-
const fromId = source.data.id;
|
|
85
|
-
const toId = target.data.id;
|
|
86
|
-
if (typeof fromId !== 'string' || typeof toId !== 'string' || fromId === toId) return;
|
|
87
|
-
const edge = (0, _closestEdge.extractClosestEdge)(target.data);
|
|
88
|
-
onRowDropRef.current(fromId, toId, edge === 'bottom' ? 'bottom' : 'top');
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}, [tableId]);
|
|
82
|
+
useRowDropHandler(onRowDrop);
|
|
92
83
|
return (0, _jsxRuntime.jsx)("tbody", {
|
|
93
84
|
className: (0, _classnames.default)('ncua-table__body', className),
|
|
94
85
|
children: children
|
|
95
86
|
});
|
|
96
87
|
};
|
|
97
88
|
Body.displayName = 'Table.Body';
|
|
98
|
-
const
|
|
89
|
+
const RowGroup = _ref3 => {
|
|
90
|
+
let {
|
|
91
|
+
children,
|
|
92
|
+
className,
|
|
93
|
+
onRowDrop
|
|
94
|
+
} = _ref3;
|
|
95
|
+
useRowDropHandler(onRowDrop);
|
|
96
|
+
return (0, _jsxRuntime.jsx)("tbody", {
|
|
97
|
+
className: (0, _classnames.default)('ncua-table__body', 'ncua-table__row-group', className),
|
|
98
|
+
children: children
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
RowGroup.displayName = 'Table.RowGroup';
|
|
102
|
+
const Row = /*#__PURE__*/(0, _react.forwardRef)((_ref4, forwardedRef) => {
|
|
99
103
|
let {
|
|
100
104
|
children,
|
|
101
105
|
className,
|
|
@@ -103,7 +107,7 @@ const Row = /*#__PURE__*/(0, _react.forwardRef)((_ref5, forwardedRef) => {
|
|
|
103
107
|
status,
|
|
104
108
|
dragId,
|
|
105
109
|
...rest
|
|
106
|
-
} =
|
|
110
|
+
} = _ref4;
|
|
107
111
|
const {
|
|
108
112
|
isDraggable,
|
|
109
113
|
tableId
|
|
@@ -121,8 +125,32 @@ const Row = /*#__PURE__*/(0, _react.forwardRef)((_ref5, forwardedRef) => {
|
|
|
121
125
|
}, [forwardedRef]);
|
|
122
126
|
(0, _react.useEffect)(() => {
|
|
123
127
|
if (!isDraggable || !dragId || !internalRef.current) return;
|
|
128
|
+
// 끌고 있는 블록 안쪽은 드롭 대상이 아니다 — 거기 놓으면 병합이 쪼개진다
|
|
129
|
+
const isInsideDraggedBlock = data => {
|
|
130
|
+
const {
|
|
131
|
+
blockBody,
|
|
132
|
+
blockStart,
|
|
133
|
+
blockEnd
|
|
134
|
+
} = data;
|
|
135
|
+
if (typeof blockStart !== 'number' || typeof blockEnd !== 'number' || blockStart < 0) return false;
|
|
136
|
+
const located = internalRef.current ? (0, _dndMove.locateRow)(internalRef.current) : null;
|
|
137
|
+
if (!located || located.bodyIndex !== blockBody) return false;
|
|
138
|
+
return located.index >= blockStart && located.index <= blockEnd;
|
|
139
|
+
};
|
|
140
|
+
// 병합 블록 가운데에 놓으면 그 블록이 쪼개진다 — 받을 수 있는 변만 연다
|
|
141
|
+
const openEdges = () => {
|
|
142
|
+
const located = internalRef.current ? (0, _dndMove.locateRow)(internalRef.current) : null;
|
|
143
|
+
if (!located) return ['top', 'bottom'];
|
|
144
|
+
return (0, _rowSpanBlock.computeOpenEdges)((0, _rowSpanBlock.readRowSpans)(located.body), located.index);
|
|
145
|
+
};
|
|
124
146
|
return (0, _adapter.dropTargetForElements)({
|
|
125
147
|
element: internalRef.current,
|
|
148
|
+
canDrop: _ref5 => {
|
|
149
|
+
let {
|
|
150
|
+
source
|
|
151
|
+
} = _ref5;
|
|
152
|
+
return !isInsideDraggedBlock(source.data) && openEdges().length > 0;
|
|
153
|
+
},
|
|
126
154
|
getData: _ref6 => {
|
|
127
155
|
let {
|
|
128
156
|
input,
|
|
@@ -134,7 +162,7 @@ const Row = /*#__PURE__*/(0, _react.forwardRef)((_ref5, forwardedRef) => {
|
|
|
134
162
|
}, {
|
|
135
163
|
input,
|
|
136
164
|
element,
|
|
137
|
-
allowedEdges:
|
|
165
|
+
allowedEdges: openEdges()
|
|
138
166
|
});
|
|
139
167
|
},
|
|
140
168
|
onDragEnter: _ref7 => {
|
|
@@ -202,10 +230,28 @@ const DragHeaderCell = _ref9 => {
|
|
|
202
230
|
});
|
|
203
231
|
};
|
|
204
232
|
DragHeaderCell.displayName = 'Table.DragHeaderCell';
|
|
233
|
+
// 미리보기에 그릴 행과 배지에 적을 건수를 정한다 — 묶음은 행이 여러 개여도 한 건이다
|
|
234
|
+
const planDragPreview = (rowEl, input) => {
|
|
235
|
+
const rowRect = rowEl.getBoundingClientRect();
|
|
236
|
+
const badgeHalf = _dndPreview.PREVIEW_BADGE_SIZE / 2;
|
|
237
|
+
// 묶음마다 `tbody` 가 생기므로 표 전체에서 모은다 — 자기 `tbody` 기준이면 자기 묶음만 보인다.
|
|
238
|
+
// 대상은 `Table.Body` 가 그린 `tbody` 뿐이다 — 맨 `tbody` 의 행까지 세면 TB-10 의 단일 프리뷰가 깨진다
|
|
239
|
+
const tableEl = rowEl.classList.contains('ncua-table__row--selected') ? rowEl.closest('table') : null;
|
|
240
|
+
const selectedRows = tableEl ? Array.from(tableEl.querySelectorAll('.ncua-table__body > .ncua-table__row--selected')) : [];
|
|
241
|
+
const isMultiSelect = selectedRows.length > 1;
|
|
242
|
+
return {
|
|
243
|
+
rows: isMultiSelect ? selectedRows : (0, _dndMove.findRowSpanBlock)(rowEl)?.rows ?? [rowEl],
|
|
244
|
+
itemCount: isMultiSelect ? selectedRows.length : 1,
|
|
245
|
+
rowRect,
|
|
246
|
+
offsetX: Math.round(input.clientX - rowRect.left) + badgeHalf,
|
|
247
|
+
offsetY: Math.round(input.clientY - rowRect.top) + badgeHalf
|
|
248
|
+
};
|
|
249
|
+
};
|
|
205
250
|
const DragCell = _ref0 => {
|
|
206
251
|
let {
|
|
207
252
|
disabled,
|
|
208
253
|
className,
|
|
254
|
+
rowSpan,
|
|
209
255
|
children
|
|
210
256
|
} = _ref0;
|
|
211
257
|
const {
|
|
@@ -224,34 +270,31 @@ const DragCell = _ref0 => {
|
|
|
224
270
|
let pendingPreview = null;
|
|
225
271
|
return (0, _adapter.draggable)({
|
|
226
272
|
element: handleRef.current,
|
|
227
|
-
getInitialData: () =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
273
|
+
getInitialData: () => {
|
|
274
|
+
const rowEl = handleRef.current?.closest('tr');
|
|
275
|
+
const block = rowEl ? (0, _dndMove.findRowSpanBlock)(rowEl) : null;
|
|
276
|
+
// 블록 전체가 움직이므로 소비자에게는 블록 첫 행을 출발점으로 알린다
|
|
277
|
+
const blockId = block?.rows[0]?.getAttribute(_dndMove.DRAG_ID_ATTRIBUTE) ?? dragId;
|
|
278
|
+
return {
|
|
279
|
+
id: blockId,
|
|
280
|
+
tableId,
|
|
281
|
+
moveIds: rowEl ? (0, _dndMove.planMoveIds)(rowEl) : [],
|
|
282
|
+
blockBody: block?.bodyIndex ?? -1,
|
|
283
|
+
blockStart: block?.start ?? -1,
|
|
284
|
+
blockEnd: block?.end ?? -1
|
|
285
|
+
};
|
|
286
|
+
},
|
|
231
287
|
onGenerateDragPreview: _ref1 => {
|
|
232
288
|
let {
|
|
233
289
|
nativeSetDragImage,
|
|
234
290
|
location
|
|
235
291
|
} = _ref1;
|
|
236
|
-
|
|
237
|
-
const rowEl = handleRef.current.closest('tr');
|
|
292
|
+
const rowEl = handleRef.current?.closest('tr');
|
|
238
293
|
if (!rowEl) return;
|
|
239
294
|
(0, _disableNativeDragPreview.disableNativeDragPreview)({
|
|
240
295
|
nativeSetDragImage
|
|
241
296
|
});
|
|
242
|
-
|
|
243
|
-
const badgeHalf = _dndPreview.PREVIEW_BADGE_SIZE / 2;
|
|
244
|
-
const offsetX = Math.round(location.initial.input.clientX - rowRect.left) + badgeHalf;
|
|
245
|
-
const offsetY = Math.round(location.initial.input.clientY - rowRect.top) + badgeHalf;
|
|
246
|
-
const isSelected = rowEl.classList.contains('ncua-table__row--selected');
|
|
247
|
-
const bodyEl = rowEl.closest('.ncua-table__body');
|
|
248
|
-
const selectedRows = isSelected && bodyEl ? Array.from(bodyEl.querySelectorAll('.ncua-table__row--selected')) : [];
|
|
249
|
-
pendingPreview = {
|
|
250
|
-
rows: selectedRows.length > 1 ? selectedRows : [rowEl],
|
|
251
|
-
rowRect,
|
|
252
|
-
offsetX,
|
|
253
|
-
offsetY
|
|
254
|
-
};
|
|
297
|
+
pendingPreview = planDragPreview(rowEl, location.initial.input);
|
|
255
298
|
},
|
|
256
299
|
onDragStart: _ref10 => {
|
|
257
300
|
let {
|
|
@@ -261,11 +304,12 @@ const DragCell = _ref0 => {
|
|
|
261
304
|
if (!pendingPreview) return;
|
|
262
305
|
const {
|
|
263
306
|
rows,
|
|
307
|
+
itemCount,
|
|
264
308
|
rowRect,
|
|
265
309
|
offsetX,
|
|
266
310
|
offsetY
|
|
267
311
|
} = pendingPreview;
|
|
268
|
-
overlay = (0, _dndPreview.buildDragPreview)(rows, rowRect);
|
|
312
|
+
overlay = (0, _dndPreview.buildDragPreview)(rows, rowRect, itemCount);
|
|
269
313
|
overlay.style.left = `${Math.round(location.initial.input.clientX) - offsetX}px`;
|
|
270
314
|
overlay.style.top = `${Math.round(location.initial.input.clientY) - offsetY}px`;
|
|
271
315
|
document.body.appendChild(overlay);
|
|
@@ -289,6 +333,7 @@ const DragCell = _ref0 => {
|
|
|
289
333
|
}, [isDraggable, dragId, tableId, disabled, setIsDragging]);
|
|
290
334
|
return (0, _jsxRuntime.jsx)("td", {
|
|
291
335
|
className: (0, _classnames.default)('ncua-table__drag-cell', className),
|
|
336
|
+
rowSpan: rowSpan,
|
|
292
337
|
children: (0, _jsxRuntime.jsxs)("span", {
|
|
293
338
|
className: "ncua-table__drag-cell-inner",
|
|
294
339
|
children: [isDraggable && (0, _jsxRuntime.jsx)("button", {
|
|
@@ -461,9 +506,21 @@ const sortChildren = children => {
|
|
|
461
506
|
});
|
|
462
507
|
return result;
|
|
463
508
|
};
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
509
|
+
const readRowDrop = (sourceData, target) => {
|
|
510
|
+
if (!target) return null;
|
|
511
|
+
const fromId = sourceData.id;
|
|
512
|
+
const toId = target.data.id;
|
|
513
|
+
if (typeof fromId !== 'string' || typeof toId !== 'string' || fromId === toId) return null;
|
|
514
|
+
const isStringArray = v => Array.isArray(v) && v.every(x => typeof x === 'string');
|
|
515
|
+
const moveIds = isStringArray(sourceData.moveIds) ? sourceData.moveIds : [fromId];
|
|
516
|
+
const edge = (0, _closestEdge.extractClosestEdge)(target.data) === 'bottom' ? 'bottom' : 'top';
|
|
517
|
+
return {
|
|
518
|
+
fromId,
|
|
519
|
+
toId,
|
|
520
|
+
edge,
|
|
521
|
+
moveIds
|
|
522
|
+
};
|
|
523
|
+
};
|
|
467
524
|
const TableComponent = /*#__PURE__*/(0, _react.forwardRef)((_ref17, ref) => {
|
|
468
525
|
let {
|
|
469
526
|
type = 'horizontal',
|
|
@@ -480,6 +537,25 @@ const TableComponent = /*#__PURE__*/(0, _react.forwardRef)((_ref17, ref) => {
|
|
|
480
537
|
} = _ref17;
|
|
481
538
|
const tableIdRef = (0, _react.useRef)(`ncua-table-${Math.random().toString(_dndPreview.TABLE_DND_ID_RADIX).slice(_dndPreview.TABLE_DND_ID_SLICE_START, _dndPreview.TABLE_DND_ID_SLICE_END)}`);
|
|
482
539
|
const tableId = tableIdRef.current;
|
|
540
|
+
// 콜백이 매 렌더마다 재생성되어도 effect 가 재구독하지 않도록 ref 로 유지한다.
|
|
541
|
+
const onRowDropRef = (0, _react.useRef)(undefined);
|
|
542
|
+
(0, _react.useEffect)(() => (0, _adapter.monitorForElements)({
|
|
543
|
+
canMonitor: _ref18 => {
|
|
544
|
+
let {
|
|
545
|
+
source
|
|
546
|
+
} = _ref18;
|
|
547
|
+
return source.data.tableId === tableId;
|
|
548
|
+
},
|
|
549
|
+
onDrop: _ref19 => {
|
|
550
|
+
let {
|
|
551
|
+
source,
|
|
552
|
+
location
|
|
553
|
+
} = _ref19;
|
|
554
|
+
const drop = readRowDrop(source.data, location.current.dropTargets[0]);
|
|
555
|
+
if (!drop) return;
|
|
556
|
+
onRowDropRef.current?.(drop.fromId, drop.toId, drop.edge, drop.moveIds);
|
|
557
|
+
}
|
|
558
|
+
}), [tableId]);
|
|
483
559
|
const effectiveDraggable = draggable && type === 'horizontal';
|
|
484
560
|
const tableClasses = (0, _classnames.default)('ncua-table', className, {
|
|
485
561
|
'ncua-table--horizontal': type === 'horizontal',
|
|
@@ -568,7 +644,8 @@ const TableComponent = /*#__PURE__*/(0, _react.forwardRef)((_ref17, ref) => {
|
|
|
568
644
|
};
|
|
569
645
|
const dndContextValue = {
|
|
570
646
|
isDraggable: effectiveDraggable,
|
|
571
|
-
tableId
|
|
647
|
+
tableId,
|
|
648
|
+
onRowDropRef
|
|
572
649
|
};
|
|
573
650
|
// horizontalScroll=true 시 외곽 wrapper + FloatingProvider 부착.
|
|
574
651
|
// 핵심 — __h-scroll-container 는 <table>(또는 scroll-area) 만 감싸고, footer/pagination 은
|
|
@@ -645,6 +722,7 @@ TableComponent.displayName = 'Table';
|
|
|
645
722
|
const Table = exports.Table = Object.assign(TableComponent, {
|
|
646
723
|
Header,
|
|
647
724
|
Body,
|
|
725
|
+
RowGroup,
|
|
648
726
|
Row,
|
|
649
727
|
DragHeaderCell,
|
|
650
728
|
DragCell,
|
|
@@ -52,12 +52,17 @@ _vitest.vi.mock('@atlaskit/pragmatic-drag-and-drop-auto-scroll/element', () => (
|
|
|
52
52
|
(0, _vitest.expect)((0, _tableDndTestUtils.draggableArgsOf)('a').element).toBe(handle);
|
|
53
53
|
(0, _vitest.expect)(view.querySelectorAll('button.ncua-table__drag-handle')).toHaveLength(_tableDndTestUtils.ROW_IDS.length);
|
|
54
54
|
});
|
|
55
|
-
(0, _vitest.it)('TB-08 getInitialData 는 {id: dragId, tableId}
|
|
55
|
+
(0, _vitest.it)('TB-08 getInitialData 는 {id: dragId, tableId} 와 옮길 행·블록 좌표를 돌려준다', () => {
|
|
56
56
|
(0, _tableTestUtils.render)((0, _tableDndTestUtils.draggableTable)(_vitest.vi.fn()));
|
|
57
57
|
const tableId = (0, _tableDndTestUtils.sourceDataOf)('b').tableId;
|
|
58
|
+
// 병합이 없으면 블록은 자기 행 한 칸이고 옮길 행도 자기 자신뿐이다
|
|
58
59
|
(0, _vitest.expect)((0, _tableDndTestUtils.draggableArgsOf)('a').getInitialData?.({})).toEqual({
|
|
59
60
|
id: 'a',
|
|
60
|
-
tableId
|
|
61
|
+
tableId,
|
|
62
|
+
moveIds: ['a'],
|
|
63
|
+
blockBody: 0,
|
|
64
|
+
blockStart: 0,
|
|
65
|
+
blockEnd: 0
|
|
61
66
|
});
|
|
62
67
|
});
|
|
63
68
|
(0, _vitest.it)('TB-08 disabled 면 button 을 disabled 로 렌더하고 draggable 로 등록하지 않는다', () => {
|
|
@@ -33,14 +33,14 @@ const DRAG_OVER_BOTTOM = 'ncua-table__row--drag-over-bottom';
|
|
|
33
33
|
const source = (0, _tableDndTestUtils.sourceDataOf)('a');
|
|
34
34
|
(0, _tableDndTestUtils.dropOnMonitors)(source, [(0, _tableDndTestUtils.targetDataOf)('b', _tableDndTestUtils.LOWER_HALF_Y, source)]);
|
|
35
35
|
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledTimes(1);
|
|
36
|
-
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'b', 'bottom');
|
|
36
|
+
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'b', 'bottom', ['a']);
|
|
37
37
|
});
|
|
38
38
|
(0, _vitest.it)('TB-01 다른 행의 위쪽 절반에 놓으면 edge 는 "top" 이다', () => {
|
|
39
39
|
const onRowDrop = _vitest.vi.fn();
|
|
40
40
|
(0, _tableTestUtils.render)((0, _tableDndTestUtils.draggableTable)(onRowDrop));
|
|
41
41
|
const source = (0, _tableDndTestUtils.sourceDataOf)('c');
|
|
42
42
|
(0, _tableDndTestUtils.dropOnMonitors)(source, [(0, _tableDndTestUtils.targetDataOf)('a', _tableDndTestUtils.UPPER_HALF_Y, source)]);
|
|
43
|
-
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('c', 'a', 'top');
|
|
43
|
+
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('c', 'a', 'top', ['c']);
|
|
44
44
|
});
|
|
45
45
|
(0, _vitest.it)('TB-01 target 에 closest edge 정보가 없으면 "top" 으로 전달한다', () => {
|
|
46
46
|
const onRowDrop = _vitest.vi.fn();
|
|
@@ -50,7 +50,7 @@ const DRAG_OVER_BOTTOM = 'ncua-table__row--drag-over-bottom';
|
|
|
50
50
|
id: 'b',
|
|
51
51
|
tableId: source.tableId
|
|
52
52
|
}]);
|
|
53
|
-
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'b', 'top');
|
|
53
|
+
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'b', 'top', ['a']);
|
|
54
54
|
});
|
|
55
55
|
(0, _vitest.it)('TB-01 여러 drop target 이 겹치면 가장 안쪽(첫 번째) target 기준으로 전달한다', () => {
|
|
56
56
|
const onRowDrop = _vitest.vi.fn();
|
|
@@ -58,15 +58,20 @@ const DRAG_OVER_BOTTOM = 'ncua-table__row--drag-over-bottom';
|
|
|
58
58
|
const source = (0, _tableDndTestUtils.sourceDataOf)('a');
|
|
59
59
|
(0, _tableDndTestUtils.dropOnMonitors)(source, [(0, _tableDndTestUtils.targetDataOf)('c', _tableDndTestUtils.LOWER_HALF_Y, source), (0, _tableDndTestUtils.targetDataOf)('b', _tableDndTestUtils.UPPER_HALF_Y, source)]);
|
|
60
60
|
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledTimes(1);
|
|
61
|
-
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'c', 'bottom');
|
|
61
|
+
(0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'c', 'bottom', ['a']);
|
|
62
62
|
});
|
|
63
63
|
(0, _vitest.it)('TB-01 행의 source·target data 에 이 테이블의 tableId 가 실린다', () => {
|
|
64
64
|
(0, _tableTestUtils.render)((0, _tableDndTestUtils.draggableTable)(_vitest.vi.fn()));
|
|
65
65
|
const source = (0, _tableDndTestUtils.sourceDataOf)('a');
|
|
66
66
|
const target = (0, _tableDndTestUtils.targetDataOf)('b', _tableDndTestUtils.LOWER_HALF_Y, source);
|
|
67
|
+
// 블록 좌표는 병합 블록 드롭 판정에 쓰인다. 병합이 없으면 자기 행 한 칸이다
|
|
67
68
|
(0, _vitest.expect)(source).toEqual({
|
|
68
69
|
id: 'a',
|
|
69
|
-
tableId: _vitest.expect.stringMatching(/^ncua-table-/)
|
|
70
|
+
tableId: _vitest.expect.stringMatching(/^ncua-table-/),
|
|
71
|
+
moveIds: ['a'],
|
|
72
|
+
blockBody: 0,
|
|
73
|
+
blockStart: 0,
|
|
74
|
+
blockEnd: 0
|
|
70
75
|
});
|
|
71
76
|
(0, _vitest.expect)(target.id).toBe('b');
|
|
72
77
|
(0, _vitest.expect)(target.tableId).toBe(source.tableId);
|
|
@@ -137,7 +142,7 @@ const DRAG_OVER_BOTTOM = 'ncua-table__row--drag-over-bottom';
|
|
|
137
142
|
id: 'b',
|
|
138
143
|
tableId: source.tableId
|
|
139
144
|
}]);
|
|
140
|
-
(0, _vitest.expect)(first).toHaveBeenCalledWith('a', 'b', 'top');
|
|
145
|
+
(0, _vitest.expect)(first).toHaveBeenCalledWith('a', 'b', 'top', ['a']);
|
|
141
146
|
(0, _vitest.expect)(second).not.toHaveBeenCalled();
|
|
142
147
|
(0, _vitest.expect)((0, _tableDndTestUtils.sourceDataOf)('a', sectionOf('second')).tableId).not.toBe(source.tableId);
|
|
143
148
|
});
|
|
@@ -167,7 +172,7 @@ const DRAG_OVER_BOTTOM = 'ncua-table__row--drag-over-bottom';
|
|
|
167
172
|
id: 'c',
|
|
168
173
|
tableId: foreignTarget.tableId
|
|
169
174
|
}]);
|
|
170
|
-
(0, _vitest.expect)(first).toHaveBeenCalledWith('a', 'c', 'top');
|
|
175
|
+
(0, _vitest.expect)(first).toHaveBeenCalledWith('a', 'c', 'top', ['a']);
|
|
171
176
|
(0, _vitest.expect)(second).not.toHaveBeenCalled();
|
|
172
177
|
});
|
|
173
178
|
_vitest.it.todo('TB-04 다른 테이블의 행 위에 놓으면 어느 테이블의 onRowDrop 도 호출하지 않는다 (canDrop 또는 target tableId 검사)');
|
|
@@ -184,7 +189,7 @@ const DRAG_OVER_BOTTOM = 'ncua-table__row--drag-over-bottom';
|
|
|
184
189
|
(0, _vitest.expect)((0, _tableDndTestUtils.monitors)()).toHaveLength(1);
|
|
185
190
|
(0, _vitest.expect)((0, _tableDndTestUtils.monitors)()[0]).toBe(monitorBefore);
|
|
186
191
|
(0, _vitest.expect)(first).not.toHaveBeenCalled();
|
|
187
|
-
(0, _vitest.expect)(latest).toHaveBeenCalledWith('a', 'b', 'bottom');
|
|
192
|
+
(0, _vitest.expect)(latest).toHaveBeenCalledWith('a', 'b', 'bottom', ['a']);
|
|
188
193
|
});
|
|
189
194
|
(0, _vitest.it)('TB-05 onRowDrop 을 undefined 로 바꾸면 이후 drop 은 무시된다', () => {
|
|
190
195
|
const first = _vitest.vi.fn();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _react = require("react");
|
|
4
|
+
var _client = require("react-dom/client");
|
|
5
|
+
var _testUtils = require("react-dom/test-utils");
|
|
6
|
+
var _vitest = require("vitest");
|
|
7
|
+
var _Table = require("../Table");
|
|
8
|
+
// @vitest-environment jsdom
|
|
9
|
+
/**
|
|
10
|
+
* `Table.RowGroup` 의 DOM 계약만 다룬다.
|
|
11
|
+
* hover·selected 배경은 CSS 캐스케이드 결과이므로 jsdom 으로 판정할 수 없다 (브라우저에서 확인).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
let container;
|
|
15
|
+
let root;
|
|
16
|
+
(0, _vitest.beforeEach)(() => {
|
|
17
|
+
container = document.createElement('div');
|
|
18
|
+
document.body.appendChild(container);
|
|
19
|
+
(0, _testUtils.act)(() => {
|
|
20
|
+
root = (0, _client.createRoot)(container);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
(0, _vitest.afterEach)(() => {
|
|
24
|
+
(0, _testUtils.act)(() => {
|
|
25
|
+
root.unmount();
|
|
26
|
+
});
|
|
27
|
+
container.remove();
|
|
28
|
+
});
|
|
29
|
+
// `children` 은 TableRowGroupProps 의 필수 prop 이라 createElement 의 props 인자에 담는다.
|
|
30
|
+
const rowGroupProps = className => ({
|
|
31
|
+
className,
|
|
32
|
+
children: /*#__PURE__*/(0, _react.createElement)('tr', null, /*#__PURE__*/(0, _react.createElement)('td', null, '셀'))
|
|
33
|
+
});
|
|
34
|
+
const renderRowGroup = className => {
|
|
35
|
+
(0, _testUtils.act)(() => {
|
|
36
|
+
root.render(/*#__PURE__*/(0, _react.createElement)('table', null, /*#__PURE__*/(0, _react.createElement)(_Table.Table.RowGroup, rowGroupProps(className))));
|
|
37
|
+
});
|
|
38
|
+
const tbody = container.querySelector('tbody');
|
|
39
|
+
if (!tbody) throw new Error('tbody 가 렌더되지 않았다.');
|
|
40
|
+
return tbody;
|
|
41
|
+
};
|
|
42
|
+
(0, _vitest.describe)('Table.RowGroup', () => {
|
|
43
|
+
(0, _vitest.it)('ncua-table__body 와 ncua-table__row-group 을 가진 tbody 를 렌더한다', () => {
|
|
44
|
+
const tbody = renderRowGroup();
|
|
45
|
+
(0, _vitest.expect)(tbody.classList.contains('ncua-table__body')).toBe(true);
|
|
46
|
+
(0, _vitest.expect)(tbody.classList.contains('ncua-table__row-group')).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
(0, _vitest.it)('전달한 className 을 기본 클래스와 함께 붙인다', () => {
|
|
49
|
+
const tbody = renderRowGroup('custom-group');
|
|
50
|
+
(0, _vitest.expect)(tbody.classList.contains('custom-group')).toBe(true);
|
|
51
|
+
(0, _vitest.expect)(tbody.classList.contains('ncua-table__body')).toBe(true);
|
|
52
|
+
(0, _vitest.expect)(tbody.classList.contains('ncua-table__row-group')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|