quill-table-up 2.3.1 → 2.4.1
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/README.md +1 -0
- package/dist/index.d.ts +35 -12
- package/dist/index.js +26 -25
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +35 -31
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/e2e/table-blots.test.ts +22 -0
- package/src/__tests__/e2e/table-keyboard-handler.test.ts +218 -0
- package/src/__tests__/e2e/table-selection.test.ts +137 -131
- package/src/__tests__/unit/table-cell-merge.test.ts +261 -1
- package/src/__tests__/unit/table-clipboard.test.ts +62 -44
- package/src/__tests__/unit/table-insert.test.ts +39 -2
- package/src/__tests__/unit/table-redo-undo.test.ts +69 -0
- package/src/__tests__/unit/table-remove.test.ts +4 -2
- package/src/__tests__/unit/utils.ts +22 -4
- package/src/__tests__/unit/vitest.d.ts +4 -1
- package/src/formats/index.ts +6 -0
- package/src/formats/overrides/block-embed.ts +54 -0
- package/src/formats/overrides/block.ts +23 -4
- package/src/formats/overrides/index.ts +1 -0
- package/src/formats/table-cell-format.ts +39 -6
- package/src/formats/table-cell-inner-format.ts +70 -21
- package/src/formats/table-main-format.ts +92 -1
- package/src/formats/table-row-format.ts +13 -2
- package/src/modules/table-clipboard.ts +30 -35
- package/src/modules/table-resize/table-resize-box.ts +2 -2
- package/src/modules/table-resize/table-resize-common.ts +3 -5
- package/src/modules/table-resize/table-resize-line.ts +1 -1
- package/src/modules/table-resize/table-resize-scale.ts +1 -1
- package/src/modules/table-scrollbar.ts +9 -10
- package/src/modules/table-selection.ts +52 -31
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/table-up.ts +57 -23
- package/src/utils/blot-helper.ts +7 -4
- package/src/utils/index.ts +1 -1
- package/src/utils/{scroll-event-handle.ts → scroll-event-helper.ts} +7 -0
- package/src/utils/types.ts +2 -0
|
@@ -2,6 +2,7 @@ import type TypeScroll from 'quill/blots/scroll';
|
|
|
2
2
|
import type { TableValue } from '../utils';
|
|
3
3
|
import { blotName, tableUpSize } from '../utils';
|
|
4
4
|
import { ContainerFormat } from './container-format';
|
|
5
|
+
import { TableCellInnerFormat } from './table-cell-inner-format';
|
|
5
6
|
import { TableColFormat } from './table-col-format';
|
|
6
7
|
import { TableRowFormat } from './table-row-format';
|
|
7
8
|
|
|
@@ -128,7 +129,9 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
getRows() {
|
|
131
|
-
return this.
|
|
132
|
+
return Array.from(this.domNode.querySelectorAll(`${TableRowFormat.tagName}`))
|
|
133
|
+
.map(el => this.scroll.find(el) as TableRowFormat)
|
|
134
|
+
.filter(Boolean);
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
getRowIds() {
|
|
@@ -159,6 +162,94 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
super.optimize(context);
|
|
165
|
+
this.mergeRow();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ensure row id unique in same table
|
|
169
|
+
mergeRow() {
|
|
170
|
+
if (!this.parent) return;
|
|
171
|
+
const rows = this.getRows();
|
|
172
|
+
const rowGroup: Record<string, TableRowFormat[]> = {};
|
|
173
|
+
for (const row of rows) {
|
|
174
|
+
if (!rowGroup[row.rowId]) rowGroup[row.rowId] = [];
|
|
175
|
+
rowGroup[row.rowId].push(row);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const rowList of Object.values(rowGroup)) {
|
|
179
|
+
for (let i = 1; i < rowList.length; i++) {
|
|
180
|
+
const row = rowList[i];
|
|
181
|
+
row.moveChildren(rowList[0]);
|
|
182
|
+
row.remove();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
checkEmptyCol(autoMerge: boolean) {
|
|
188
|
+
if (autoMerge) {
|
|
189
|
+
const rowCount = this.getRows().length;
|
|
190
|
+
const cols = this.getCols();
|
|
191
|
+
const cells = this.descendants(TableCellInnerFormat);
|
|
192
|
+
for (const cell of cells) {
|
|
193
|
+
if (cell.colspan > 1 && cell.rowspan >= rowCount) {
|
|
194
|
+
const index = cols.findIndex(col => col.colId === cell.colId);
|
|
195
|
+
const currentCol = cols[index];
|
|
196
|
+
for (let i = index + 1; i < index + cell.colspan; i++) {
|
|
197
|
+
cols[i].remove();
|
|
198
|
+
currentCol.width += cols[i].width;
|
|
199
|
+
}
|
|
200
|
+
cell.colspan = 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
checkEmptyRow(autoMerge: boolean) {
|
|
207
|
+
const rows = this.getRows();
|
|
208
|
+
const rowIds = new Set(rows.map(row => row.rowId));
|
|
209
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
210
|
+
const row = rows[i];
|
|
211
|
+
if (autoMerge) {
|
|
212
|
+
// reduce rowspan previou row
|
|
213
|
+
if (row.children.length === 0) {
|
|
214
|
+
for (let gap = 1, j = i - 1; j >= 0; j--, gap++) {
|
|
215
|
+
const prev = rows[j];
|
|
216
|
+
prev.foreachCellInner((cell) => {
|
|
217
|
+
if (cell.rowspan > gap) {
|
|
218
|
+
cell.rowspan -= 1;
|
|
219
|
+
const emptyRow = new Set(cell.emptyRow);
|
|
220
|
+
emptyRow.delete(row.rowId);
|
|
221
|
+
cell.emptyRow = Array.from(emptyRow);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
row.remove();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
if (row.children.length === 0 && row.prev) {
|
|
230
|
+
// find the not empty row
|
|
231
|
+
let prev: TableRowFormat = row.prev as TableRowFormat;
|
|
232
|
+
while (prev && prev.children.length === 0) {
|
|
233
|
+
prev = prev.prev as TableRowFormat;
|
|
234
|
+
}
|
|
235
|
+
prev.foreachCellInner((cell) => {
|
|
236
|
+
const emptyRowIds = new Set(cell.emptyRow);
|
|
237
|
+
// prevent order change. like currnet emptyRow ['1', '2'] add rowId '2' will be ['2', '1']
|
|
238
|
+
if (!emptyRowIds.has(row.rowId)) {
|
|
239
|
+
// the loop is from back to front, and the rowId should be added to the head
|
|
240
|
+
cell.emptyRow = [row.rowId, ...emptyRowIds];
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
row.foreachCellInner((cell) => {
|
|
245
|
+
for (const emptyRow of cell.emptyRow) {
|
|
246
|
+
if (!rowIds.has(emptyRow)) {
|
|
247
|
+
row.parent.insertBefore(this.scroll.create(blotName.tableRow, { tableId: this.tableId, rowId: emptyRow }), row.next);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
162
253
|
}
|
|
163
254
|
|
|
164
255
|
sortMergeChildren() {
|
|
@@ -157,13 +157,24 @@ export class TableRowFormat extends ContainerFormat {
|
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
optimize(
|
|
160
|
+
optimize(_context: Record<string, any>) {
|
|
161
161
|
const parent = this.parent;
|
|
162
162
|
const { tableId } = this;
|
|
163
163
|
if (parent !== null && parent.statics.blotName !== blotName.tableBody) {
|
|
164
164
|
this.wrap(blotName.tableBody, tableId);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
if (
|
|
168
|
+
this.statics.requiredContainer
|
|
169
|
+
&& !(this.parent instanceof this.statics.requiredContainer)
|
|
170
|
+
) {
|
|
171
|
+
this.wrap(this.statics.requiredContainer.blotName);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.enforceAllowedChildren();
|
|
175
|
+
if (this.children.length > 0 && this.next != null && this.checkMerge()) {
|
|
176
|
+
this.next.moveChildren(this);
|
|
177
|
+
this.next.remove();
|
|
178
|
+
}
|
|
168
179
|
}
|
|
169
180
|
}
|
|
@@ -46,9 +46,7 @@ function calculateCols(tableNode: HTMLElement, colNums: number): number[] {
|
|
|
46
46
|
export class TableClipboard extends Clipboard {
|
|
47
47
|
tableId = randomId();
|
|
48
48
|
rowId = randomId();
|
|
49
|
-
rowIds: string[] = [];
|
|
50
49
|
colIds: string[] = [];
|
|
51
|
-
emptyRowCount: number[] = [];
|
|
52
50
|
rowspanCount: { rowspan: number; colspan: number }[] = [];
|
|
53
51
|
cellCount = 0;
|
|
54
52
|
colCount = 0;
|
|
@@ -69,16 +67,17 @@ export class TableClipboard extends Clipboard {
|
|
|
69
67
|
matchTable(node: Node, delta: TypeDelta) {
|
|
70
68
|
if (delta.ops.length === 0) return delta;
|
|
71
69
|
|
|
72
|
-
const preSumEmptyRowCount = this.emptyRowCount.reduce((acc, cur) => {
|
|
73
|
-
acc.push((acc[acc.length - 1] ?? 0) + cur);
|
|
74
|
-
return acc;
|
|
75
|
-
}, [] as number[]);
|
|
76
|
-
|
|
77
70
|
const ops: Record<string, any>[] = [];
|
|
78
71
|
const cols: Record<string, any>[] = [];
|
|
79
72
|
let bodyStartIndex = -1;
|
|
80
73
|
for (let i = 0; i < delta.ops.length; i++) {
|
|
81
74
|
const { attributes, insert } = delta.ops[i];
|
|
75
|
+
// if attribute doesn't have tableCellInner, treat it as a blank line(emptyRow)
|
|
76
|
+
if (!isObject(insert) && attributes && !attributes[blotName.tableCellInner] && !attributes[blotName.tableCaption]) {
|
|
77
|
+
delta.ops.splice(i, 1);
|
|
78
|
+
i -= 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
82
81
|
// remove quill origin table format and tableCell format
|
|
83
82
|
const { table, [blotName.tableCell]: tableCell, ...attrs } = attributes || {};
|
|
84
83
|
const hasCol = isObject(insert) && insert[blotName.tableCol];
|
|
@@ -86,19 +85,7 @@ export class TableClipboard extends Clipboard {
|
|
|
86
85
|
cols.push({ insert });
|
|
87
86
|
}
|
|
88
87
|
else {
|
|
89
|
-
|
|
90
|
-
const tableCellInner = attrs[blotName.tableCellInner] as TableCellValue;
|
|
91
|
-
const rowNum = this.rowIds.indexOf(tableCellInner.rowId);
|
|
92
|
-
// reduce cell rowspan by counting empty rows
|
|
93
|
-
if (rowNum !== -1) {
|
|
94
|
-
const rowspan = tableCellInner.rowspan;
|
|
95
|
-
tableCellInner.rowspan -= (preSumEmptyRowCount[rowNum + rowspan - 1] - preSumEmptyRowCount[rowNum]);
|
|
96
|
-
}
|
|
97
|
-
ops.push({ attributes: { ...attrs, [blotName.tableCellInner]: tableCellInner }, insert });
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
ops.push({ attributes: attrs, insert });
|
|
101
|
-
}
|
|
88
|
+
ops.push({ attributes: attrs, insert });
|
|
102
89
|
}
|
|
103
90
|
// record col insert index
|
|
104
91
|
if (
|
|
@@ -135,9 +122,7 @@ export class TableClipboard extends Clipboard {
|
|
|
135
122
|
|
|
136
123
|
// reset variable to avoid conflict with other table
|
|
137
124
|
this.tableId = randomId();
|
|
138
|
-
this.rowIds = [];
|
|
139
125
|
this.colIds = [];
|
|
140
|
-
this.emptyRowCount = [];
|
|
141
126
|
this.rowspanCount = [];
|
|
142
127
|
this.cellCount = 0;
|
|
143
128
|
this.colCount = 0;
|
|
@@ -158,6 +143,28 @@ export class TableClipboard extends Clipboard {
|
|
|
158
143
|
}
|
|
159
144
|
}
|
|
160
145
|
}
|
|
146
|
+
// add `emptyRow`
|
|
147
|
+
let emptyRows = [];
|
|
148
|
+
for (let i = delta.ops.length - 1; i >= 0; i--) {
|
|
149
|
+
const op = delta.ops[i];
|
|
150
|
+
if (op.attributes) {
|
|
151
|
+
if (!op.attributes[blotName.tableCellInner]) {
|
|
152
|
+
emptyRows = [];
|
|
153
|
+
emptyRows.push(randomId());
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
if ((op.attributes[blotName.tableCellInner] as TableCellValue).rowspan === 1) {
|
|
157
|
+
emptyRows = [];
|
|
158
|
+
}
|
|
159
|
+
else if (emptyRows.length > 0) {
|
|
160
|
+
if (!(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow) {
|
|
161
|
+
(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow = [];
|
|
162
|
+
}
|
|
163
|
+
(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow!.push(...emptyRows);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
161
168
|
return delta;
|
|
162
169
|
}
|
|
163
170
|
|
|
@@ -173,6 +180,7 @@ export class TableClipboard extends Clipboard {
|
|
|
173
180
|
}
|
|
174
181
|
|
|
175
182
|
matchCol(node: Node, _delta: TypeDelta) {
|
|
183
|
+
// split col by span
|
|
176
184
|
let span = Number((node as HTMLElement).getAttribute('span') || 1);
|
|
177
185
|
if (Number.isNaN(span)) span = 1;
|
|
178
186
|
|
|
@@ -194,7 +202,6 @@ export class TableClipboard extends Clipboard {
|
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
matchTr(node: Node, delta: TypeDelta) {
|
|
197
|
-
this.rowIds.push(this.rowId);
|
|
198
205
|
this.rowId = randomId();
|
|
199
206
|
this.cellCount = 0;
|
|
200
207
|
// minus rowspan
|
|
@@ -206,11 +213,9 @@ export class TableClipboard extends Clipboard {
|
|
|
206
213
|
this.rowspanCount[i] = { rowspan: 0, colspan: 0 };
|
|
207
214
|
}
|
|
208
215
|
}
|
|
209
|
-
let hasTd = true;
|
|
210
216
|
for (const op of delta.ops) {
|
|
211
217
|
if (op.attributes) {
|
|
212
218
|
const { background, [blotName.tableCellInner]: tableCellInner } = op.attributes;
|
|
213
|
-
if (!tableCellInner) hasTd = false;
|
|
214
219
|
if (tableCellInner && background) {
|
|
215
220
|
const { style = '' } = tableCellInner as TableCellValue;
|
|
216
221
|
const styleObj = cssTextToObject(style);
|
|
@@ -218,17 +223,7 @@ export class TableClipboard extends Clipboard {
|
|
|
218
223
|
(op.attributes![blotName.tableCellInner] as TableCellValue).style = objectToCssText(styleObj);
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
|
-
else {
|
|
222
|
-
hasTd = false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// record if row doesn't have any cell. It's happen in excel. https://github.com/zzxming/quill-table-up/issues/165
|
|
226
|
-
let rowspanCount = 0;
|
|
227
|
-
if (!hasTd) {
|
|
228
|
-
rowspanCount = 1;
|
|
229
|
-
delta = new Delta();
|
|
230
226
|
}
|
|
231
|
-
this.emptyRowCount.push(rowspanCount);
|
|
232
227
|
return delta;
|
|
233
228
|
}
|
|
234
229
|
|
|
@@ -187,7 +187,7 @@ export class TableResizeBox extends TableResizeCommon {
|
|
|
187
187
|
|
|
188
188
|
update() {
|
|
189
189
|
const { rect: tableRect, body: tableBodyBlot } = getTableMainRect(this.tableMain);
|
|
190
|
-
if (!tableBodyBlot) return;
|
|
190
|
+
if (!tableBodyBlot || !tableRect) return;
|
|
191
191
|
const tableWrapperRect = this.tableWrapper.domNode.getBoundingClientRect();
|
|
192
192
|
const rootRect = this.quill.root.getBoundingClientRect();
|
|
193
193
|
Object.assign(this.root.style, {
|
|
@@ -238,7 +238,7 @@ export class TableResizeBox extends TableResizeCommon {
|
|
|
238
238
|
this.tableRows = this.tableMain.getRows();
|
|
239
239
|
this.root.innerHTML = '';
|
|
240
240
|
const { rect: tableRect, body: tableBodyBlot } = getTableMainRect(this.tableMain);
|
|
241
|
-
if (!tableBodyBlot) return;
|
|
241
|
+
if (!tableBodyBlot || !tableRect) return;
|
|
242
242
|
const tableWrapperRect = this.tableWrapper.domNode.getBoundingClientRect();
|
|
243
243
|
|
|
244
244
|
if (this.tableCols.length > 0 && this.tableRows.length > 0) {
|
|
@@ -255,7 +255,7 @@ export class TableResizeCommon {
|
|
|
255
255
|
e.preventDefault();
|
|
256
256
|
if (!this.tableMain) return;
|
|
257
257
|
const { rect: tableRect, body: tableBodyBlot } = getTableMainRect(this.tableMain);
|
|
258
|
-
if (!tableBodyBlot) return;
|
|
258
|
+
if (!tableBodyBlot || !tableRect) return;
|
|
259
259
|
// set drag init width
|
|
260
260
|
const cols = this.tableMain.getCols();
|
|
261
261
|
this.colIndex = this.findCurrentColIndex(e);
|
|
@@ -269,8 +269,7 @@ export class TableResizeCommon {
|
|
|
269
269
|
this.dragging = true;
|
|
270
270
|
|
|
271
271
|
const divDom = document.createElement('div');
|
|
272
|
-
divDom.classList.add(this.dragBEM.b());
|
|
273
|
-
divDom.classList.add(this.dragBEM.is('col'));
|
|
272
|
+
divDom.classList.add(this.dragBEM.b(), this.dragBEM.is('col'));
|
|
274
273
|
divDom.dataset.w = String(width);
|
|
275
274
|
|
|
276
275
|
const styleValue = {
|
|
@@ -350,8 +349,7 @@ export class TableResizeCommon {
|
|
|
350
349
|
const tableMainRect = this.tableMain.domNode.getBoundingClientRect();
|
|
351
350
|
|
|
352
351
|
const divDom = document.createElement('div');
|
|
353
|
-
divDom.classList.add(this.dragBEM.b());
|
|
354
|
-
divDom.classList.add(this.dragBEM.is('row'));
|
|
352
|
+
divDom.classList.add(this.dragBEM.b(), this.dragBEM.is('row'));
|
|
355
353
|
divDom.dataset.h = String(height);
|
|
356
354
|
|
|
357
355
|
const styleValue = {
|
|
@@ -64,7 +64,7 @@ export class TableResizeLine extends TableResizeCommon {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
findCurrentColIndex() {
|
|
67
|
-
return this.curColIndex;
|
|
67
|
+
return this.curColIndex + (this.tableCellBlot?.colspan || 1) - 1;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
handleColMouseUpFunc = async function (this: TableResizeLine) {
|
|
@@ -116,7 +116,7 @@ export class TableResizeScale {
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
const { rect: tableRect, body: tableBodyBlot } = getTableMainRect(this.tableMainBlot);
|
|
119
|
-
if (!tableBodyBlot) return;
|
|
119
|
+
if (!tableBodyBlot || !tableRect) return;
|
|
120
120
|
const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
|
|
121
121
|
const editorRect = this.quill.root.getBoundingClientRect();
|
|
122
122
|
const { scrollTop, scrollLeft } = this.tableWrapperBlot.domNode;
|
|
@@ -19,9 +19,9 @@ export class Scrollbar {
|
|
|
19
19
|
X: number;
|
|
20
20
|
Y: number;
|
|
21
21
|
} = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
X: 0,
|
|
23
|
+
Y: 0,
|
|
24
|
+
};
|
|
25
25
|
|
|
26
26
|
ob: ResizeObserver;
|
|
27
27
|
container: HTMLElement;
|
|
@@ -70,8 +70,8 @@ export class Scrollbar {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
setScrollbarPosition() {
|
|
73
|
-
const { rect:
|
|
74
|
-
if (!tableBodyBlot) return;
|
|
73
|
+
const { rect: tableRect, body: tableBodyBlot } = getTableMainRect(this.tableMainBlot);
|
|
74
|
+
if (!tableBodyBlot || !tableRect) return;
|
|
75
75
|
const { scrollLeft: editorScrollX, scrollTop: editorScrollY, offsetLeft: rootOffsetLeft, offsetTop: rootOffsetTop } = this.quill.root;
|
|
76
76
|
const { offsetLeft: containerOffsetLeft, offsetTop: containerOffsetTop } = this.container;
|
|
77
77
|
const { offsetLeft: bodyOffsetLeft, offsetTop: bodyOffsetTop } = tableBodyBlot.domNode;
|
|
@@ -80,10 +80,10 @@ export class Scrollbar {
|
|
|
80
80
|
let x = containerOffsetLeft + bodyOffsetLeft - rootOffsetLeft;
|
|
81
81
|
let y = containerOffsetTop + bodyOffsetTop - rootOffsetTop;
|
|
82
82
|
if (this.isVertical) {
|
|
83
|
-
x += Math.min(containerWidth,
|
|
83
|
+
x += Math.min(containerWidth, tableRect.width);
|
|
84
84
|
}
|
|
85
85
|
else {
|
|
86
|
-
y += Math.min(containerHeight,
|
|
86
|
+
y += Math.min(containerHeight, tableRect.height);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// table align right effect
|
|
@@ -92,7 +92,7 @@ export class Scrollbar {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
Object.assign(this.scrollbar.style, {
|
|
95
|
-
[this.propertyMap.size]: `${this.isVertical ? Math.min(containerHeight,
|
|
95
|
+
[this.propertyMap.size]: `${this.isVertical ? Math.min(containerHeight, tableRect.height) : containerWidth}px`,
|
|
96
96
|
transform: `translate(${x - editorScrollX}px, ${y - editorScrollY}px)`,
|
|
97
97
|
});
|
|
98
98
|
this.containerScrollHandler(this.container);
|
|
@@ -115,8 +115,7 @@ export class Scrollbar {
|
|
|
115
115
|
|
|
116
116
|
createScrollbar() {
|
|
117
117
|
const scrollbar = document.createElement('div');
|
|
118
|
-
scrollbar.classList.add(this.bem.b());
|
|
119
|
-
scrollbar.classList.add(this.isVertical ? this.bem.is('vertical') : this.bem.is('horizontal'), this.bem.is('transparent'));
|
|
118
|
+
scrollbar.classList.add(this.bem.b(), this.isVertical ? this.bem.is('vertical') : this.bem.is('horizontal'), this.bem.is('transparent'));
|
|
120
119
|
Object.assign(scrollbar.style, {
|
|
121
120
|
display: 'none',
|
|
122
121
|
});
|
|
@@ -4,7 +4,7 @@ import type { TableUp } from '../table-up';
|
|
|
4
4
|
import type { InternalTableMenuModule, RelactiveRect, TableSelectionOptions } from '../utils';
|
|
5
5
|
import Quill from 'quill';
|
|
6
6
|
import { getTableMainRect, TableBodyFormat, TableCellFormat, TableCellInnerFormat } from '../formats';
|
|
7
|
-
import { addScrollEvent, blotName, clearScrollEvent, createBEM, createResizeObserver, findAllParentBlot, findParentBlot, getRelativeRect, isRectanglesIntersect, tableUpEvent } from '../utils';
|
|
7
|
+
import { addScrollEvent, blotName, clearScrollEvent, createBEM, createResizeObserver, findAllParentBlot, findParentBlot, getElementScroll, getRelativeRect, isRectanglesIntersect, tableUpEvent } from '../utils';
|
|
8
8
|
|
|
9
9
|
const ERROR_LIMIT = 0;
|
|
10
10
|
|
|
@@ -18,8 +18,8 @@ export interface SelectionData {
|
|
|
18
18
|
export class TableSelection {
|
|
19
19
|
options: TableSelectionOptions;
|
|
20
20
|
boundary: RelactiveRect | null = null;
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
scrollRecordEls: HTMLElement[] = [];
|
|
22
|
+
startScrollRecordPosition: { x: number; y: number }[] = [];
|
|
23
23
|
selectedTableScrollX: number = 0;
|
|
24
24
|
selectedTableScrollY: number = 0;
|
|
25
25
|
selectedEditorScrollX: number = 0;
|
|
@@ -43,6 +43,7 @@ export class TableSelection {
|
|
|
43
43
|
|
|
44
44
|
constructor(public tableModule: TableUp, public quill: Quill, options: Partial<TableSelectionOptions> = {}) {
|
|
45
45
|
this.options = this.resolveOptions(options);
|
|
46
|
+
this.scrollRecordEls = [this.quill.root, document.documentElement];
|
|
46
47
|
|
|
47
48
|
this.cellSelectWrap = tableModule.addContainer(this.bem.b());
|
|
48
49
|
this.cellSelect = this.helpLinesInitial();
|
|
@@ -310,17 +311,12 @@ export class TableSelection {
|
|
|
310
311
|
}),
|
|
311
312
|
);
|
|
312
313
|
|
|
313
|
-
const
|
|
314
|
-
const { x: editorScrollX, y: editorScrollY } = this.getQuillViewScroll();
|
|
315
|
-
this.selectedTableScrollX = tableScrollX;
|
|
316
|
-
this.selectedTableScrollY = tableScrollY;
|
|
317
|
-
this.selectedEditorScrollX = editorScrollX;
|
|
318
|
-
this.selectedEditorScrollY = editorScrollY;
|
|
319
|
-
|
|
314
|
+
const scrollDiff = this.getScrollPositionDiff();
|
|
320
315
|
// set boundary to initially mouse move rectangle
|
|
321
316
|
const { rect: tableRect } = getTableMainRect(tableMainBlot);
|
|
322
|
-
|
|
323
|
-
const
|
|
317
|
+
if (!tableRect) return [];
|
|
318
|
+
const startPointX = startPoint.x + scrollDiff.x;
|
|
319
|
+
const startPointY = startPoint.y + scrollDiff.y;
|
|
324
320
|
let boundary = {
|
|
325
321
|
x: Math.max(tableRect.left, Math.min(endPoint.x, startPointX)),
|
|
326
322
|
y: Math.max(tableRect.top, Math.min(endPoint.y, startPointY)),
|
|
@@ -379,15 +375,44 @@ export class TableSelection {
|
|
|
379
375
|
});
|
|
380
376
|
}
|
|
381
377
|
|
|
378
|
+
getScrollPositionDiff() {
|
|
379
|
+
const { x: tableScrollX, y: tableScrollY } = this.getTableViewScroll();
|
|
380
|
+
const { x: editorScrollX, y: editorScrollY } = getElementScroll(this.quill.root);
|
|
381
|
+
this.selectedTableScrollX = tableScrollX;
|
|
382
|
+
this.selectedTableScrollY = tableScrollY;
|
|
383
|
+
this.selectedEditorScrollX = editorScrollX;
|
|
384
|
+
this.selectedEditorScrollY = editorScrollY;
|
|
385
|
+
|
|
386
|
+
return this.startScrollRecordPosition.reduce((pre, { x, y }, i) => {
|
|
387
|
+
const { x: currentX, y: currentY } = getElementScroll(this.scrollRecordEls[i]);
|
|
388
|
+
pre.x += x - currentX;
|
|
389
|
+
pre.y += y - currentY;
|
|
390
|
+
return pre;
|
|
391
|
+
}, { x: 0, y: 0 });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
recordScrollPosition() {
|
|
395
|
+
this.clearRecordScrollPosition();
|
|
396
|
+
for (const el of this.scrollRecordEls) {
|
|
397
|
+
this.startScrollRecordPosition.push(getElementScroll(el));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
clearRecordScrollPosition() {
|
|
402
|
+
this.startScrollRecordPosition = [];
|
|
403
|
+
}
|
|
404
|
+
|
|
382
405
|
mouseDownHandler = (mousedownEvent: MouseEvent) => {
|
|
383
406
|
const { button, target, clientX, clientY } = mousedownEvent;
|
|
384
407
|
const closestTable = (target as HTMLElement).closest<HTMLTableElement>('.ql-table');
|
|
385
|
-
const
|
|
386
|
-
if (button !== 0 || !closestTable ||
|
|
408
|
+
const closestTableCaption = (target as HTMLElement).closest('caption');
|
|
409
|
+
if (button !== 0 || !closestTable || closestTableCaption) return;
|
|
387
410
|
|
|
388
411
|
this.setSelectionTable(closestTable);
|
|
389
412
|
const startTableId = closestTable.dataset.tableId;
|
|
390
413
|
const startPoint = { x: clientX, y: clientY };
|
|
414
|
+
|
|
415
|
+
this.recordScrollPosition();
|
|
391
416
|
this.selectedTds = this.computeSelectedTds(startPoint, startPoint);
|
|
392
417
|
this.dragging = true;
|
|
393
418
|
this.show();
|
|
@@ -400,11 +425,12 @@ export class TableSelection {
|
|
|
400
425
|
this.tableModule.tableResize.hide();
|
|
401
426
|
}
|
|
402
427
|
const { button, target, clientX, clientY } = mousemoveEvent;
|
|
403
|
-
const
|
|
428
|
+
const closestTable = (target as HTMLElement).closest<HTMLTableElement>('.ql-table');
|
|
429
|
+
const closestTableCaption = (target as HTMLElement).closest('caption');
|
|
404
430
|
if (
|
|
405
431
|
button !== 0
|
|
406
|
-
||
|
|
407
|
-
||
|
|
432
|
+
|| closestTableCaption
|
|
433
|
+
|| !closestTable || closestTable.dataset.tableId !== startTableId
|
|
408
434
|
) {
|
|
409
435
|
return;
|
|
410
436
|
}
|
|
@@ -420,6 +446,7 @@ export class TableSelection {
|
|
|
420
446
|
document.body.removeEventListener('mousemove', mouseMoveHandler, false);
|
|
421
447
|
document.body.removeEventListener('mouseup', mouseUpHandler, false);
|
|
422
448
|
this.dragging = false;
|
|
449
|
+
this.clearRecordScrollPosition();
|
|
423
450
|
if (this.tableMenu && this.selectedTds.length > 0) {
|
|
424
451
|
this.tableMenu.show();
|
|
425
452
|
}
|
|
@@ -451,14 +478,12 @@ export class TableSelection {
|
|
|
451
478
|
|
|
452
479
|
update() {
|
|
453
480
|
if (this.selectedTds.length === 0 || !this.boundary || !this.table) return;
|
|
454
|
-
const { x: editorScrollX, y: editorScrollY } = this.
|
|
481
|
+
const { x: editorScrollX, y: editorScrollY } = getElementScroll(this.quill.root);
|
|
455
482
|
const { x: tableScrollX, y: tableScrollY } = this.getTableViewScroll();
|
|
456
483
|
const tableWrapperRect = this.table.parentElement!.getBoundingClientRect();
|
|
457
484
|
const rootRect = this.quill.root.getBoundingClientRect();
|
|
458
485
|
const wrapLeft = tableWrapperRect.x - rootRect.x;
|
|
459
486
|
const wrapTop = tableWrapperRect.y - rootRect.y;
|
|
460
|
-
this.startScrollX = tableScrollX;
|
|
461
|
-
this.startScrollY = tableScrollY;
|
|
462
487
|
|
|
463
488
|
Object.assign(this.cellSelect.style, {
|
|
464
489
|
left: `${this.selectedEditorScrollX * 2 - editorScrollX + this.boundary.x + this.selectedTableScrollX - tableScrollX - wrapLeft}px`,
|
|
@@ -477,13 +502,6 @@ export class TableSelection {
|
|
|
477
502
|
}
|
|
478
503
|
}
|
|
479
504
|
|
|
480
|
-
getQuillViewScroll() {
|
|
481
|
-
return {
|
|
482
|
-
x: this.quill.root.scrollLeft,
|
|
483
|
-
y: this.quill.root.scrollTop,
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
|
|
487
505
|
getTableViewScroll() {
|
|
488
506
|
if (!this.table) {
|
|
489
507
|
return {
|
|
@@ -491,15 +509,18 @@ export class TableSelection {
|
|
|
491
509
|
y: 0,
|
|
492
510
|
};
|
|
493
511
|
}
|
|
494
|
-
return
|
|
495
|
-
x: this.table.parentElement!.scrollLeft,
|
|
496
|
-
y: this.table.parentElement!.scrollTop,
|
|
497
|
-
};
|
|
512
|
+
return getElementScroll(this.table.parentElement!);
|
|
498
513
|
}
|
|
499
514
|
|
|
500
515
|
setSelectionTable(table: HTMLTableElement | undefined) {
|
|
501
516
|
if (this.table === table) return;
|
|
502
517
|
this.table = table;
|
|
518
|
+
if (this.table) {
|
|
519
|
+
this.scrollRecordEls.push(this.table.parentElement!);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
this.scrollRecordEls.pop();
|
|
523
|
+
}
|
|
503
524
|
}
|
|
504
525
|
|
|
505
526
|
removeCell = (e: KeyboardEvent) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.3","results":[[":__tests__/unit/utils.test-d.ts",{"duration":0,"failed":false}],[":__tests__/unit/table-clipboard.test.ts",{"duration":
|
|
1
|
+
{"version":"3.2.3","results":[[":__tests__/unit/utils.test-d.ts",{"duration":0,"failed":false}],[":__tests__/unit/table-clipboard.test.ts",{"duration":3198.3800000000047,"failed":false}],[":__tests__/unit/table-redo-undo.test.ts",{"duration":8473.984600000002,"failed":false}],[":__tests__/unit/table-hack.test.ts",{"duration":2830.0276,"failed":false}],[":__tests__/unit/table-insert.test.ts",{"duration":4750.2608,"failed":false}],[":__tests__/unit/table-cell-merge.test.ts",{"duration":3491.4125,"failed":false}],[":__tests__/unit/table-blots.test.ts",{"duration":2240.7780000000002,"failed":false}],[":__tests__/unit/utils.test.ts",{"duration":640.7419,"failed":false}],[":__tests__/unit/table-remove.test.ts",{"duration":2719.4262,"failed":false}],[":__tests__/unit/table-caption.test.ts",{"duration":1264.7592,"failed":false}]]}
|