@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.
Files changed (56) hide show
  1. package/dist/cjs/assets/scripts/table/rowSpanBlock.js +97 -0
  2. package/dist/cjs/src/components/data-display/table/Table.js +135 -57
  3. package/dist/cjs/src/components/data-display/table/__tests__/Table.dnd.dragCell.test.js +7 -2
  4. package/dist/cjs/src/components/data-display/table/__tests__/Table.dnd.test.js +13 -8
  5. package/dist/cjs/src/components/data-display/table/__tests__/Table.rowGroup.test.js +54 -0
  6. package/dist/cjs/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.js +212 -0
  7. package/dist/cjs/src/components/data-display/table/__tests__/dnd-context.test.js +5 -2
  8. package/dist/cjs/src/components/data-display/table/__tests__/tableSelectableStyle.test.js +75 -0
  9. package/dist/cjs/src/components/data-display/table/dnd-context.js +4 -1
  10. package/dist/cjs/src/components/data-display/table/dnd-move.js +46 -0
  11. package/dist/cjs/src/components/data-display/table/dnd-preview.js +8 -3
  12. package/dist/cjs/src/generated/scoped-css.js +1 -1
  13. package/dist/esm/assets/scripts/table/rowSpanBlock.js +85 -0
  14. package/dist/esm/src/components/data-display/table/Table.js +135 -57
  15. package/dist/esm/src/components/data-display/table/__tests__/Table.dnd.dragCell.test.js +7 -2
  16. package/dist/esm/src/components/data-display/table/__tests__/Table.dnd.test.js +13 -8
  17. package/dist/esm/src/components/data-display/table/__tests__/Table.rowGroup.test.js +51 -0
  18. package/dist/esm/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.js +210 -0
  19. package/dist/esm/src/components/data-display/table/__tests__/dnd-context.test.js +5 -2
  20. package/dist/esm/src/components/data-display/table/__tests__/tableSelectableStyle.test.js +72 -0
  21. package/dist/esm/src/components/data-display/table/dnd-context.js +4 -1
  22. package/dist/esm/src/components/data-display/table/dnd-move.js +38 -0
  23. package/dist/esm/src/components/data-display/table/dnd-preview.js +8 -3
  24. package/dist/esm/src/generated/scoped-css.js +1 -1
  25. package/dist/temp/assets/scripts/table/rowSpanBlock.d.ts +29 -0
  26. package/dist/temp/assets/scripts/table/rowSpanBlock.js +77 -0
  27. package/dist/temp/src/components/data-display/table/Table.d.ts +7 -3
  28. package/dist/temp/src/components/data-display/table/Table.js +107 -47
  29. package/dist/temp/src/components/data-display/table/__tests__/Table.dnd.dragCell.test.js +10 -2
  30. package/dist/temp/src/components/data-display/table/__tests__/Table.dnd.test.js +16 -8
  31. package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroup.test.d.ts +1 -0
  32. package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroup.test.js +52 -0
  33. package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.d.ts +1 -0
  34. package/dist/temp/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.js +137 -0
  35. package/dist/temp/src/components/data-display/table/__tests__/dnd-context.test.js +2 -2
  36. package/dist/temp/src/components/data-display/table/__tests__/tableSelectableStyle.test.d.ts +1 -0
  37. package/dist/temp/src/components/data-display/table/__tests__/tableSelectableStyle.test.js +71 -0
  38. package/dist/temp/src/components/data-display/table/dnd-context.d.ts +3 -0
  39. package/dist/temp/src/components/data-display/table/dnd-context.js +1 -0
  40. package/dist/temp/src/components/data-display/table/dnd-move.d.ts +17 -0
  41. package/dist/temp/src/components/data-display/table/dnd-move.js +31 -0
  42. package/dist/temp/src/components/data-display/table/dnd-preview.d.ts +5 -1
  43. package/dist/temp/src/components/data-display/table/dnd-preview.js +7 -3
  44. package/dist/temp/src/components/data-display/table/types.d.ts +14 -5
  45. package/dist/temp/src/generated/scoped-css.js +1 -1
  46. package/dist/types/assets/scripts/table/rowSpanBlock.d.ts +29 -0
  47. package/dist/types/src/components/data-display/table/Table.d.ts +7 -3
  48. package/dist/types/src/components/data-display/table/__tests__/Table.rowGroup.test.d.ts +1 -0
  49. package/dist/types/src/components/data-display/table/__tests__/Table.rowGroupDnd.test.d.ts +1 -0
  50. package/dist/types/src/components/data-display/table/__tests__/tableSelectableStyle.test.d.ts +1 -0
  51. package/dist/types/src/components/data-display/table/dnd-context.d.ts +3 -0
  52. package/dist/types/src/components/data-display/table/dnd-move.d.ts +17 -0
  53. package/dist/types/src/components/data-display/table/dnd-preview.d.ts +5 -1
  54. package/dist/types/src/components/data-display/table/types.d.ts +14 -5
  55. package/dist/ui-admin/assets/styles/style.css +60 -4
  56. package/package.json +1 -1
@@ -0,0 +1,212 @@
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 _dndMove = require("../dnd-move");
8
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } // @vitest-environment jsdom
9
+ /**
10
+ * 묶음은 스타일 레이어이므로 드래그앤드롭 동작이 묶음 유무와 무관해야 한다.
11
+ * 드롭 자체는 실제 포인터 이벤트가 필요해 jsdom 으로 재현할 수 없어, 감시 등록 횟수와 콜백 전달만 고정한다.
12
+ */
13
+ const monitorCalls = [];
14
+ _vitest.vi.mock('@atlaskit/pragmatic-drag-and-drop/element/adapter', () => ({
15
+ dropTargetForElements: () => () => undefined,
16
+ draggable: () => () => undefined,
17
+ monitorForElements: args => {
18
+ monitorCalls.push(args);
19
+ return () => undefined;
20
+ }
21
+ }));
22
+ _vitest.vi.mock('@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview', () => ({
23
+ disableNativeDragPreview: () => undefined
24
+ }));
25
+ _vitest.vi.mock('@atlaskit/pragmatic-drag-and-drop-auto-scroll/element', () => ({
26
+ autoScrollForElements: () => () => undefined
27
+ }));
28
+ _vitest.vi.mock('@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge', () => ({
29
+ attachClosestEdge: data => data,
30
+ extractClosestEdge: () => 'bottom'
31
+ }));
32
+ const {
33
+ Table
34
+ } = await Promise.resolve().then(() => _interopRequireWildcard(require('../Table')));
35
+ let container;
36
+ let root;
37
+ (0, _vitest.beforeEach)(() => {
38
+ monitorCalls.length = 0;
39
+ container = document.createElement('div');
40
+ document.body.appendChild(container);
41
+ (0, _testUtils.act)(() => {
42
+ root = (0, _client.createRoot)(container);
43
+ });
44
+ });
45
+ (0, _vitest.afterEach)(() => {
46
+ (0, _testUtils.act)(() => {
47
+ root.unmount();
48
+ });
49
+ container.remove();
50
+ });
51
+ const rowProps = id => ({
52
+ key: id,
53
+ dragId: id,
54
+ children: /*#__PURE__*/(0, _react.createElement)(Table.Cell, null, id)
55
+ });
56
+ const row = id => /*#__PURE__*/(0, _react.createElement)(Table.Row, rowProps(id));
57
+ const groupProps = (key, onRowDrop, ids) => ({
58
+ key,
59
+ onRowDrop,
60
+ children: ids.map(row)
61
+ });
62
+ const group = (key, onRowDrop, ids) => /*#__PURE__*/(0, _react.createElement)(Table.RowGroup, groupProps(key, onRowDrop, ids));
63
+ const bodyProps = (onRowDrop, ids) => ({
64
+ onRowDrop,
65
+ children: ids.map(row)
66
+ });
67
+ const plainBodyProps = ids => ({
68
+ children: ids.map(row)
69
+ });
70
+ const tableProps = children => ({
71
+ type: 'horizontal',
72
+ draggable: true,
73
+ children
74
+ });
75
+ const renderTable = children => {
76
+ (0, _testUtils.act)(() => {
77
+ root.render(/*#__PURE__*/(0, _react.createElement)(Table, tableProps(children)));
78
+ });
79
+ };
80
+ (0, _vitest.describe)('Table.RowGroup — 드래그앤드롭', () => {
81
+ (0, _vitest.it)('묶음이 여러 개여도 드롭 감시는 한 번만 등록된다', () => {
82
+ const onRowDrop = _vitest.vi.fn();
83
+ renderTable([group('g1', onRowDrop, ['a', 'b']), group('g2', onRowDrop, ['c', 'd']), group('g3', onRowDrop, ['e'])]);
84
+ (0, _vitest.expect)(monitorCalls).toHaveLength(1);
85
+ });
86
+ (0, _vitest.it)('묶음에 넘긴 onRowDrop 이 드롭 1회에 1번만 호출된다', () => {
87
+ const onRowDrop = _vitest.vi.fn();
88
+ renderTable([group('g1', onRowDrop, ['a', 'b']), group('g2', onRowDrop, ['c', 'd'])]);
89
+ for (const call of monitorCalls) {
90
+ call.onDrop({
91
+ source: {
92
+ data: {
93
+ id: 'a'
94
+ }
95
+ },
96
+ location: {
97
+ current: {
98
+ dropTargets: [{
99
+ data: {
100
+ id: 'c'
101
+ }
102
+ }]
103
+ }
104
+ }
105
+ });
106
+ }
107
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledTimes(1);
108
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'c', 'bottom', ['a']);
109
+ });
110
+ (0, _vitest.it)('다중 선택 드래그는 고른 행과 그 병합 블록을 모두 넘긴다', () => {
111
+ const onRowDrop = _vitest.vi.fn();
112
+ renderTable(/*#__PURE__*/(0, _react.createElement)(Table.Body, bodyProps(onRowDrop, ['a', 'b', 'c', 'd'])));
113
+ const rows = Array.from(container.querySelectorAll('tbody tr'));
114
+ // a 가 b 를 덮고, c 는 혼자다 — a·c 를 고르면 b 도 따라와야 한다
115
+ rows[0].cells[0].rowSpan = 2;
116
+ rows[0].classList.add('ncua-table__row--selected');
117
+ rows[2].classList.add('ncua-table__row--selected');
118
+ monitorCalls[0].onDrop({
119
+ source: {
120
+ data: {
121
+ id: 'a',
122
+ moveIds: (0, _dndMove.planMoveIds)(rows[0])
123
+ }
124
+ },
125
+ location: {
126
+ current: {
127
+ dropTargets: [{
128
+ data: {
129
+ id: 'd'
130
+ }
131
+ }]
132
+ }
133
+ }
134
+ });
135
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'd', 'bottom', ['a', 'b', 'c']);
136
+ });
137
+ (0, _vitest.it)('고르지 않은 행을 잡으면 그 행의 블록만 넘긴다', () => {
138
+ const onRowDrop = _vitest.vi.fn();
139
+ renderTable(/*#__PURE__*/(0, _react.createElement)(Table.Body, bodyProps(onRowDrop, ['a', 'b', 'c', 'd'])));
140
+ const rows = Array.from(container.querySelectorAll('tbody tr'));
141
+ rows[0].cells[0].rowSpan = 2;
142
+ rows[2].classList.add('ncua-table__row--selected');
143
+ monitorCalls[0].onDrop({
144
+ source: {
145
+ data: {
146
+ id: 'a',
147
+ moveIds: (0, _dndMove.planMoveIds)(rows[0])
148
+ }
149
+ },
150
+ location: {
151
+ current: {
152
+ dropTargets: [{
153
+ data: {
154
+ id: 'd'
155
+ }
156
+ }]
157
+ }
158
+ }
159
+ });
160
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'd', 'bottom', ['a', 'b']);
161
+ });
162
+ (0, _vitest.it)('onRowDrop 을 거두면 드롭이 더 이상 호출되지 않는다', () => {
163
+ const onRowDrop = _vitest.vi.fn();
164
+ const drop = () => monitorCalls[0].onDrop({
165
+ source: {
166
+ data: {
167
+ id: 'a'
168
+ }
169
+ },
170
+ location: {
171
+ current: {
172
+ dropTargets: [{
173
+ data: {
174
+ id: 'b'
175
+ }
176
+ }]
177
+ }
178
+ }
179
+ });
180
+ renderTable(/*#__PURE__*/(0, _react.createElement)(Table.Body, bodyProps(onRowDrop, ['a', 'b'])));
181
+ drop();
182
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledTimes(1);
183
+ (0, _testUtils.act)(() => {
184
+ root.render(/*#__PURE__*/(0, _react.createElement)(Table, tableProps(/*#__PURE__*/(0, _react.createElement)(Table.Body, plainBodyProps(['a', 'b'])))));
185
+ });
186
+ drop();
187
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledTimes(1);
188
+ });
189
+ (0, _vitest.it)('Table.Body 의 onRowDrop 동작은 묶음 도입 전과 같다', () => {
190
+ const onRowDrop = _vitest.vi.fn();
191
+ renderTable(/*#__PURE__*/(0, _react.createElement)(Table.Body, bodyProps(onRowDrop, ['a', 'b'])));
192
+ (0, _vitest.expect)(monitorCalls).toHaveLength(1);
193
+ monitorCalls[0].onDrop({
194
+ source: {
195
+ data: {
196
+ id: 'a'
197
+ }
198
+ },
199
+ location: {
200
+ current: {
201
+ dropTargets: [{
202
+ data: {
203
+ id: 'b'
204
+ }
205
+ }]
206
+ }
207
+ }
208
+ });
209
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledTimes(1);
210
+ (0, _vitest.expect)(onRowDrop).toHaveBeenCalledWith('a', 'b', 'bottom', ['a']);
211
+ });
212
+ });
@@ -49,13 +49,16 @@ const readDefaults = () => {
49
49
  });
50
50
  (0, _vitest.expect)(() => row?.setIsDragging(true)).not.toThrow();
51
51
  });
52
- (0, _vitest.it)('RSD-05 TableDndContext 기본값은 드래그 비활성 · 빈 tableId 다', () => {
52
+ (0, _vitest.it)('RSD-05 TableDndContext 기본값은 드래그 비활성 · 빈 tableId · 비어 있는 드롭 핸들러 자리다', () => {
53
53
  const {
54
54
  table
55
55
  } = readDefaults();
56
56
  (0, _vitest.expect)(table).toEqual({
57
57
  isDraggable: false,
58
- tableId: ''
58
+ tableId: '',
59
+ onRowDropRef: {
60
+ current: undefined
61
+ }
59
62
  });
60
63
  });
61
64
  });
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ var _nodePath = _interopRequireDefault(require("node:path"));
4
+ var _sass = require("sass");
5
+ var _vitest = require("vitest");
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ // box-shadow 는 jsdom 에서 계산되지 않으므로, 컴파일된 CSS 에서 규칙을 검사한다.
8
+ // (page-title/__tests__/pageTitleStyle.test.ts 와 같은 방식)
9
+ const TABLE_SCSS_PATH = _nodePath.default.resolve(__dirname, '../../../../../assets/styles/components/table/_index.scss');
10
+ const compiledTableCss = (0, _sass.compile)(TABLE_SCSS_PATH).css;
11
+ /** 선택자 → 선언 목록. 같은 선택자가 여러 규칙에 나오면 선언을 이어 붙인다. */
12
+ const parseRules = css => {
13
+ const rules = new Map();
14
+ for (const [, selectorList, body] of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
15
+ const declarations = body.split(';').map(declaration => declaration.trim().replace(/\s+/g, ' ')).filter(Boolean);
16
+ for (const selector of selectorList.split(',').map(selector => selector.trim().replace(/\s+/g, ' '))) {
17
+ rules.set(selector, [...(rules.get(selector) ?? []), ...declarations]);
18
+ }
19
+ }
20
+ return rules;
21
+ };
22
+ const rules = parseRules(compiledTableCss);
23
+ const declarationsOf = selector => rules.get(selector) ?? [];
24
+ const SELECTABLE = '.ncua-table--selectable';
25
+ const IN_DATA_GRID = '.ncua-table--in-data-grid.ncua-table--selectable';
26
+ const UNSELECTED_ROW = '.ncua-table__body .ncua-table__row:not(.ncua-table__row--selected)';
27
+ const HEADER_FIRST_CELL = '.ncua-table__header-cell:first-child';
28
+ const COMPENSATION_LINE = 'box-shadow: inset 1px 0 0 0 var(--gray-100)';
29
+ const NO_SHADOW = 'box-shadow: none';
30
+ (0, _vitest.describe)('Table selectable — 좌측 border 보상선', () => {
31
+ (0, _vitest.it)('행(tr)이 보상선을 갖는다 — 병합 셀이 덮은 행의 첫 셀은 표 왼쪽 끝이 아니다', () => {
32
+ // Given: 컴파일된 Table CSS 에서
33
+ // When: selectable 의 보상 규칙을 찾으면
34
+ const declarations = declarationsOf(`${SELECTABLE} ${UNSELECTED_ROW}`);
35
+ // Then: 셀이 아니라 행 전체에 보상선을 둔다
36
+ (0, _vitest.expect)(declarations).toContain(COMPENSATION_LINE);
37
+ });
38
+ (0, _vitest.it)('헤더 첫 칸도 보상선을 갖는다', () => {
39
+ // Given/When: 헤더 첫 칸 규칙을 찾으면
40
+ // Then: 기존 보상선이 그대로다
41
+ (0, _vitest.expect)(declarationsOf(`${SELECTABLE} ${HEADER_FIRST_CELL}`)).toContain(COMPENSATION_LINE);
42
+ });
43
+ });
44
+ (0, _vitest.describe)('Table selectable — DataGrid 안에서는 보상선을 거둔다', () => {
45
+ (0, _vitest.it)('행(tr)의 보상선이 제거된다 — DataGrid wrapper 가 외곽 border 를 담당한다', () => {
46
+ // Given: 컴파일된 Table CSS 에서
47
+ // When: in-data-grid 조합의 행 규칙을 찾으면
48
+ const declarations = declarationsOf(`${IN_DATA_GRID} ${UNSELECTED_ROW}`);
49
+ // Then: 보상선을 지워 DataGrid 외곽선과 겹치지 않는다
50
+ (0, _vitest.expect)(declarations).toContain(NO_SHADOW);
51
+ });
52
+ (0, _vitest.it)('헤더 첫 칸의 보상선이 제거된다', () => {
53
+ // Given/When: in-data-grid 조합의 헤더 첫 칸 규칙을 찾으면
54
+ // Then: 보상선을 지운다
55
+ (0, _vitest.expect)(declarationsOf(`${IN_DATA_GRID} ${HEADER_FIRST_CELL}`)).toContain(NO_SHADOW);
56
+ });
57
+ (0, _vitest.it)('보상선을 받는 모든 자리에 제거 규칙이 대응한다 — 한쪽만 넓어지면 선이 남는다', () => {
58
+ // Given: 보상선을 받는 selectable 선택자를 모두 모으고
59
+ const compensated = [...rules].filter(_ref => {
60
+ let [selector, declarations] = _ref;
61
+ return selector.startsWith(SELECTABLE) && declarations.includes(COMPENSATION_LINE);
62
+ }).map(_ref2 => {
63
+ let [selector] = _ref2;
64
+ return selector;
65
+ });
66
+ // When: 각 선택자의 in-data-grid 대응 규칙을 찾으면
67
+ const uncovered = compensated.filter(selector => {
68
+ const counterpart = selector.replace(SELECTABLE, IN_DATA_GRID);
69
+ return !declarationsOf(counterpart).includes(NO_SHADOW);
70
+ });
71
+ // Then: 대응 없이 남는 자리가 없다
72
+ (0, _vitest.expect)(compensated.length).toBeGreaterThan(0);
73
+ (0, _vitest.expect)(uncovered).toEqual([]);
74
+ });
75
+ });
@@ -7,7 +7,10 @@ exports.TableDndContext = exports.RowDndContext = void 0;
7
7
  var _react = require("react");
8
8
  const TableDndContext = exports.TableDndContext = /*#__PURE__*/(0, _react.createContext)({
9
9
  isDraggable: false,
10
- tableId: ''
10
+ tableId: '',
11
+ onRowDropRef: {
12
+ current: undefined
13
+ }
11
14
  });
12
15
  const RowDndContext = exports.RowDndContext = /*#__PURE__*/(0, _react.createContext)({
13
16
  dragId: undefined,
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.planMoveIds = exports.locateRow = exports.findRowSpanBlock = exports.DRAG_ID_ATTRIBUTE = void 0;
7
+ var _rowSpanBlock = require("../../../../assets/scripts/table/rowSpanBlock");
8
+ const DRAG_ID_ATTRIBUTE = exports.DRAG_ID_ATTRIBUTE = 'data-draggable-id';
9
+ const toDragIds = rows => rows.map(row => row.getAttribute(DRAG_ID_ATTRIBUTE)).filter(id => id !== null);
10
+ /** 행 위치는 `tbody` 기준이라 묶음이 여러 개면 서로 겹친다 — 어느 `tbody` 인지 함께 본다 */
11
+ const locateRow = rowEl => {
12
+ const located = (0, _rowSpanBlock.locateRow)(rowEl);
13
+ const table = located?.body.closest('table');
14
+ if (!located || !table) return null;
15
+ return {
16
+ ...located,
17
+ bodyIndex: Array.from(table.tBodies).indexOf(located.body)
18
+ };
19
+ };
20
+ /** 병합이 덮는 행은 따로 옮길 수 없다. 드래그 단위를 그 행이 속한 병합 블록으로 넓힌다. */
21
+ exports.locateRow = locateRow;
22
+ const findRowSpanBlock = rowEl => {
23
+ const located = locateRow(rowEl);
24
+ if (!located) return null;
25
+ const {
26
+ body,
27
+ bodyIndex,
28
+ index
29
+ } = located;
30
+ const block = (0, _rowSpanBlock.computeRowSpanBlock)((0, _rowSpanBlock.readRowSpans)(body), index);
31
+ return {
32
+ ...block,
33
+ bodyIndex,
34
+ rows: Array.from(body.rows).slice(block.start, block.end + 1)
35
+ };
36
+ };
37
+ /** 이번 드래그로 옮겨야 하는 행 전부. 고른 행이 여럿이면 각자의 병합 블록까지 끌고 온다. */
38
+ exports.findRowSpanBlock = findRowSpanBlock;
39
+ const planMoveIds = rowEl => {
40
+ const tableEl = rowEl.closest('table');
41
+ const selectedRows = tableEl ? Array.from(tableEl.querySelectorAll('tbody > .ncua-table__row--selected')) : [];
42
+ const isMultiSelect = selectedRows.length > 1 && selectedRows.includes(rowEl);
43
+ const rows = isMultiSelect ? (0, _rowSpanBlock.expandRowsToBlocks)(selectedRows) : findRowSpanBlock(rowEl)?.rows ?? [rowEl];
44
+ return toDragIds(rows);
45
+ };
46
+ exports.planMoveIds = planMoveIds;
@@ -93,11 +93,16 @@ const buildRowCard = _ref => {
93
93
  wrapper.appendChild(table);
94
94
  return wrapper;
95
95
  };
96
- const buildDragPreview = (rows, rowRect) => {
96
+ /**
97
+ * @param rows 미리보기에 그릴 행. 병합 묶음은 한 건이라도 행이 여러 개다
98
+ * @param itemCount 옮기는 건수. 병합 묶음은 행 수와 다르므로, 생략하면 행 수를 건수로 본다
99
+ */
100
+ const buildDragPreview = function (rows, rowRect) {
101
+ let itemCount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : rows.length;
97
102
  const visibleCount = Math.min(rows.length, PREVIEW_MAX_ROWS);
98
103
  const hasSummary = rows.length > visibleCount;
99
104
  const totalCards = visibleCount + (hasSummary ? 1 : 0);
100
- const isMulti = rows.length > 1;
105
+ const isMulti = itemCount > 1;
101
106
  const parentTable = rows[0]?.closest('table') ?? null;
102
107
  const rowWidth = Math.round(rowRect.width);
103
108
  const rowHeight = Math.round(rowRect.height);
@@ -107,7 +112,7 @@ const buildDragPreview = (rows, rowRect) => {
107
112
  width: `${PREVIEW_BADGE_HALF + rowWidth}px`,
108
113
  height: `${PREVIEW_BADGE_HALF + rowHeight + PREVIEW_STACK_OFFSET * (totalCards - 1)}px`
109
114
  });
110
- if (isMulti) container.appendChild(buildCountBadge(rows.length));
115
+ if (isMulti) container.appendChild(buildCountBadge(itemCount));
111
116
  if (hasSummary) container.appendChild(buildSummaryCard(rowRect));
112
117
  // 뒤쪽 행 카드부터 렌더해서 앞쪽이 위로 올라오게
113
118
  for (let i = visibleCount - 1; i >= 0; i--) {