quill-table-up 3.1.2 → 3.2.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.
Files changed (111) hide show
  1. package/README.md +15 -8
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.ts +168 -146
  4. package/dist/index.js +47 -47
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.umd.js +52 -52
  7. package/dist/index.umd.js.map +1 -1
  8. package/package.json +22 -24
  9. package/src/__tests__/e2e/custom-creator.test.ts +44 -44
  10. package/src/__tests__/e2e/editor-page.ts +77 -77
  11. package/src/__tests__/e2e/table-align.test.ts +104 -104
  12. package/src/__tests__/e2e/table-blots.test.ts +169 -169
  13. package/src/__tests__/e2e/table-caption.test.ts +134 -134
  14. package/src/__tests__/e2e/table-clipboard.test.ts +20 -20
  15. package/src/__tests__/e2e/table-hack.test.ts +151 -151
  16. package/src/__tests__/e2e/table-keyboard-handler.test.ts +20 -4
  17. package/src/__tests__/e2e/table-menu.test.ts +172 -172
  18. package/src/__tests__/e2e/table-resize.test.ts +654 -9
  19. package/src/__tests__/e2e/table-scrollbar.test.ts +144 -144
  20. package/src/__tests__/e2e/table-selection.test.ts +563 -563
  21. package/src/__tests__/e2e/types.d.ts +8 -7
  22. package/src/__tests__/e2e/utils.ts +52 -52
  23. package/src/__tests__/unit/table-blots.test.ts +720 -720
  24. package/src/__tests__/unit/table-caption.test.ts +234 -234
  25. package/src/__tests__/unit/table-cell-merge.test.ts +713 -724
  26. package/src/__tests__/unit/table-clipboard.test.ts +2176 -2176
  27. package/src/__tests__/unit/table-hack.test.ts +1014 -1014
  28. package/src/__tests__/unit/table-insert.test.ts +915 -926
  29. package/src/__tests__/unit/table-redo-undo.test.ts +2429 -2429
  30. package/src/__tests__/unit/table-remove.test.ts +313 -343
  31. package/src/__tests__/unit/utils.test-d.ts +49 -49
  32. package/src/__tests__/unit/utils.test.ts +711 -711
  33. package/src/__tests__/unit/utils.ts +307 -307
  34. package/src/__tests__/unit/vitest.d.ts +14 -14
  35. package/src/formats/container-format.ts +107 -107
  36. package/src/formats/overrides/block-embed.ts +72 -72
  37. package/src/formats/overrides/block.ts +95 -95
  38. package/src/formats/overrides/index.ts +3 -3
  39. package/src/formats/overrides/scroll.ts +70 -70
  40. package/src/formats/table-body-format.ts +52 -52
  41. package/src/formats/table-caption-format.ts +116 -116
  42. package/src/formats/table-cell-format.ts +304 -304
  43. package/src/formats/table-cell-inner-format.ts +403 -398
  44. package/src/formats/table-colgroup-format.ts +136 -136
  45. package/src/formats/table-foot-format.ts +7 -7
  46. package/src/formats/table-head-format.ts +7 -7
  47. package/src/formats/table-main-format.ts +1 -1
  48. package/src/formats/table-row-format.ts +218 -210
  49. package/src/formats/utils.ts +6 -6
  50. package/src/index.ts +19 -19
  51. package/src/modules/index.ts +7 -7
  52. package/src/modules/table-align.ts +131 -131
  53. package/src/modules/table-clipboard/table-clipboard.ts +6 -8
  54. package/src/modules/table-dom-selector.ts +33 -33
  55. package/src/modules/table-menu/constants.ts +223 -223
  56. package/src/modules/table-menu/index.ts +4 -4
  57. package/src/modules/table-menu/table-menu-common.ts +330 -329
  58. package/src/modules/table-menu/table-menu-contextmenu.ts +111 -118
  59. package/src/modules/table-menu/table-menu-select.ts +96 -94
  60. package/src/modules/table-resize/index.ts +5 -5
  61. package/src/modules/table-resize/table-resize-box.ts +714 -363
  62. package/src/modules/table-resize/table-resize-common.ts +246 -382
  63. package/src/modules/table-resize/table-resize-drag.ts +241 -0
  64. package/src/modules/table-resize/table-resize-line.ts +244 -182
  65. package/src/modules/table-resize/table-resize-scale.ts +174 -173
  66. package/src/modules/table-resize/utils.ts +84 -3
  67. package/src/modules/table-scrollbar.ts +292 -292
  68. package/src/modules/table-selection.ts +613 -669
  69. package/src/style/button.less +45 -45
  70. package/src/style/color-picker.less +136 -136
  71. package/src/style/dialog.less +53 -53
  72. package/src/style/functions.less +9 -9
  73. package/src/style/index.less +120 -120
  74. package/src/style/input.less +64 -64
  75. package/src/style/select-box.less +52 -52
  76. package/src/style/table-creator.less +56 -56
  77. package/src/style/table-menu.less +125 -125
  78. package/src/style/table-resize-scale.less +31 -31
  79. package/src/style/table-resize.less +249 -202
  80. package/src/style/table-scrollbar.less +49 -49
  81. package/src/style/table-selection.less +23 -23
  82. package/src/style/tooltip.less +19 -19
  83. package/src/style/variables.less +1 -1
  84. package/src/svg/arrow-up-down.svg +11 -11
  85. package/src/svg/convert-cell.svg +7 -7
  86. package/src/table-up.ts +1363 -1360
  87. package/src/types.d.ts +4 -4
  88. package/src/utils/bem.ts +23 -23
  89. package/src/utils/blot-helper.ts +101 -105
  90. package/src/utils/color.ts +109 -109
  91. package/src/utils/components/button.ts +22 -22
  92. package/src/utils/components/color-picker.ts +236 -236
  93. package/src/utils/components/dialog.ts +83 -41
  94. package/src/utils/components/index.ts +6 -6
  95. package/src/utils/components/input.ts +74 -74
  96. package/src/utils/components/table/creator.ts +89 -89
  97. package/src/utils/components/table/index.ts +2 -2
  98. package/src/utils/components/table/select-box.ts +78 -78
  99. package/src/utils/components/tooltip.ts +179 -189
  100. package/src/utils/constants.ts +125 -124
  101. package/src/utils/drag-helper.ts +112 -0
  102. package/src/utils/index.ts +15 -14
  103. package/src/utils/is.ts +9 -9
  104. package/src/utils/position.ts +60 -60
  105. package/src/utils/resize-observer-helper.ts +47 -47
  106. package/src/utils/scroll.ts +145 -47
  107. package/src/utils/style-helper.ts +47 -47
  108. package/src/utils/transformer.ts +10 -10
  109. package/src/utils/transition-event-helper.ts +8 -8
  110. package/src/utils/types.ts +156 -157
  111. package/src/utils/utils.ts +12 -12
@@ -1,363 +1,714 @@
1
- import type { TableColFormat, TableMainFormat, TableRowFormat, TableWrapperFormat } from '../../formats';
2
- import type { TableUp } from '../../table-up';
3
- import type { TableSelection } from '../table-selection';
4
- import type { sizeChangeValue } from './table-resize-common';
5
- import Quill from 'quill';
6
- import { getTableMainRect, TableCaptionFormat, TableCellInnerFormat } from '../../formats';
7
- import { addScrollEvent, clearScrollEvent, createBEM, createResizeObserver, findChildBlot } from '../../utils';
8
- import { TableResizeCommon } from './table-resize-common';
9
- import { isTableAlignRight } from './utils';
10
-
11
- interface Point {
12
- x: number;
13
- y: number;
14
- }
15
- export class TableResizeBox extends TableResizeCommon {
16
- root: HTMLElement;
17
- tableWrapperBlot?: TableWrapperFormat;
18
- resizeObserver?: ResizeObserver;
19
- tableCols: TableColFormat[] = [];
20
- tableRows: TableRowFormat[] = [];
21
- rowHeadWrapper: HTMLElement | null = null;
22
- colHeadWrapper: HTMLElement | null = null;
23
- corner: HTMLElement | null = null;
24
- scrollHandler: [HTMLElement, (e: Event) => void][] = [];
25
- lastHeaderSelect: { isX: boolean; index: number } | null = null;
26
- size: number = 12;
27
- bem = createBEM('resize-box');
28
-
29
- constructor(public tableModule: TableUp, public quill: Quill, _options: any) {
30
- super(tableModule, quill);
31
-
32
- this.root = this.tableModule.addContainer(this.bem.b());
33
- this.quill.on(Quill.events.EDITOR_CHANGE, this.updateWhenTextChange);
34
- }
35
-
36
- updateWhenTextChange = (eventName: string) => {
37
- if (eventName === Quill.events.TEXT_CHANGE) {
38
- if (this.table && !this.quill.root.contains(this.table)) {
39
- this.setSelectionTable(undefined);
40
- }
41
- else {
42
- this.update();
43
- }
44
- }
45
- };
46
-
47
- setSelectionTable(table: HTMLTableElement | undefined) {
48
- if (this.table === table) return;
49
- this.hide();
50
- this.table = table;
51
- if (this.table) {
52
- const newTableBlot = Quill.find(this.table) as TableMainFormat;
53
- if (newTableBlot) {
54
- this.tableBlot = newTableBlot;
55
- this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
56
- }
57
- this.show();
58
- }
59
- this.update();
60
- }
61
-
62
- handleResizerHeader(isX: boolean, index: number, e: MouseEvent) {
63
- if (!this.table) return;
64
- const { clientX, clientY } = e;
65
- const tableRect = this.table.getBoundingClientRect();
66
- if (!e.shiftKey) {
67
- this.lastHeaderSelect = null;
68
- }
69
- const currentBoundary: [Point, Point] = [
70
- { x: isX ? tableRect.left : clientX, y: isX ? clientY : tableRect.top },
71
- { x: isX ? tableRect.right : clientX, y: isX ? clientY : tableRect.bottom },
72
- ];
73
- if (this.lastHeaderSelect) {
74
- // find last click head
75
- let lastX: number;
76
- let lastY: number;
77
- if (this.lastHeaderSelect.isX) {
78
- const tableRowHeads = Array.from(this.root.getElementsByClassName(this.bem.be('row-header'))) as HTMLElement[];
79
- const rect = tableRowHeads[this.lastHeaderSelect.index].getBoundingClientRect();
80
- lastX = Math.min(rect.left, tableRect.left);
81
- lastY = rect.top + rect.height / 2;
82
- }
83
- else {
84
- const tableColHeads = Array.from(this.root.getElementsByClassName(this.bem.be('col-header'))) as HTMLElement[];
85
- const rect = tableColHeads[this.lastHeaderSelect.index].getBoundingClientRect();
86
- lastX = rect.left + rect.width / 2;
87
- lastY = Math.min(rect.top, tableRect.top);
88
- }
89
-
90
- if (this.lastHeaderSelect.isX !== isX) {
91
- currentBoundary[1] = {
92
- x: Math.max(currentBoundary[0].x, lastX),
93
- y: Math.max(currentBoundary[0].y, lastY),
94
- };
95
- currentBoundary[0] = {
96
- x: Math.min(currentBoundary[0].x, lastX),
97
- y: Math.min(currentBoundary[0].y, lastY),
98
- };
99
- }
100
- else if (isX) {
101
- currentBoundary[0].y = Math.min(currentBoundary[0].y, lastY);
102
- currentBoundary[1].y = Math.max(currentBoundary[1].y, lastY);
103
- }
104
- else {
105
- currentBoundary[0].x = Math.min(currentBoundary[0].x, lastX);
106
- currentBoundary[1].x = Math.max(currentBoundary[1].x, lastX);
107
- }
108
- }
109
- else {
110
- this.lastHeaderSelect = { isX, index };
111
- }
112
-
113
- const tableSelection = this.tableModule.getModule<TableSelection>('table-selection');
114
- if (tableSelection) {
115
- tableSelection.table = this.table;
116
- tableSelection.setSelectedTds(tableSelection.computeSelectedTds(...currentBoundary));
117
- tableSelection.show();
118
- }
119
- }
120
-
121
- findCurrentColIndex(e: MouseEvent): number {
122
- return Array.from(this.root.getElementsByClassName(this.bem.be('col-separator'))).indexOf(e.target as HTMLElement);
123
- }
124
-
125
- colWidthChange(i: number, w: sizeChangeValue, isFull: boolean) {
126
- const tableColHeads = Array.from(this.root.getElementsByClassName(this.bem.be('col-header'))) as HTMLElement[];
127
- tableColHeads[i].style.width = isFull ? `${w.pre}%` : `${w.px}px`;
128
- }
129
-
130
- handleColMouseDownFunc = function (this: TableResizeBox, e: MouseEvent) {
131
- const value = this.handleColMouseDown(e);
132
- if (value && this.dragColBreak && this.tableBlot) {
133
- const [tableCaptionBlot] = findChildBlot(this.tableBlot, TableCaptionFormat);
134
- const offset = tableCaptionBlot && tableCaptionBlot.side === 'top' ? 0 : this.size;
135
- Object.assign(this.dragColBreak.style, {
136
- top: `${value.top - offset}px`,
137
- left: `${value.left}px`,
138
- height: `${value.height + this.size}px`,
139
- });
140
- }
141
- return value;
142
- }.bind(this);
143
-
144
- bindColEvents() {
145
- const tableColHeads = Array.from(this.root.getElementsByClassName(this.bem.be('col-header'))) as HTMLElement[];
146
- const tableColHeadSeparators = Array.from(this.root.getElementsByClassName(this.bem.be('col-separator'))) as HTMLElement[];
147
-
148
- addScrollEvent.call(this, this.tableWrapperBlot!.domNode, () => {
149
- this.colHeadWrapper!.scrollLeft = this.tableWrapperBlot!.domNode.scrollLeft;
150
- });
151
-
152
- for (const [i, el] of tableColHeads.entries()) {
153
- el.addEventListener('click', this.handleResizerHeader.bind(this, false, i));
154
- }
155
- for (const el of tableColHeadSeparators) {
156
- el.addEventListener('mousedown', this.handleColMouseDownFunc);
157
- // prevent drag
158
- el.addEventListener('dragstart', e => e.preventDefault());
159
- }
160
- }
161
-
162
- findCurrentRowIndex(e: MouseEvent): number {
163
- return Array.from(this.root.getElementsByClassName(this.bem.be('row-separator'))).indexOf(e.target as HTMLElement);
164
- }
165
-
166
- rowHeightChange(i: number, h: number) {
167
- const tableRowHeads = Array.from(this.root.getElementsByClassName(this.bem.be('row-header'))) as HTMLElement[];
168
- tableRowHeads[i].style.height = `${h}px`;
169
- }
170
-
171
- handleRowMouseDownFunc = function (this: TableResizeBox, e: MouseEvent) {
172
- const value = this.handleRowMouseDown(e);
173
- if (value && this.dragRowBreak) {
174
- Object.assign(this.dragRowBreak.style, {
175
- top: `${value.top}px`,
176
- left: `${value.left - this.size}px`,
177
- width: `${value.width + this.size}px`,
178
- });
179
- }
180
- return value;
181
- }.bind(this);
182
-
183
- bindRowEvents() {
184
- const tableRowHeads = Array.from(this.root.getElementsByClassName(this.bem.be('row-header'))) as HTMLElement[];
185
- const tableRowHeadSeparators = Array.from(this.root.getElementsByClassName(this.bem.be('row-separator'))) as HTMLElement[];
186
-
187
- addScrollEvent.call(this, this.tableWrapperBlot!.domNode, () => {
188
- this.rowHeadWrapper!.scrollTop = this.tableWrapperBlot!.domNode.scrollTop;
189
- });
190
-
191
- for (const [i, el] of tableRowHeads.entries()) {
192
- el.addEventListener('click', this.handleResizerHeader.bind(this, true, i));
193
- }
194
- for (const el of tableRowHeadSeparators) {
195
- el.addEventListener('mousedown', this.handleRowMouseDownFunc);
196
- // prevent drag
197
- el.addEventListener('dragstart', e => e.preventDefault());
198
- }
199
- }
200
-
201
- update() {
202
- if (!this.tableBlot || !this.tableWrapperBlot) return;
203
- const { rect: tableRect } = getTableMainRect(this.tableBlot);
204
- if (!tableRect) return;
205
-
206
- this.root.innerHTML = '';
207
-
208
- this.tableCols = this.tableBlot.getCols();
209
- this.tableRows = this.tableBlot.getRows();
210
- const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
211
- const rootRect = this.quill.root.getBoundingClientRect();
212
- Object.assign(this.root.style, {
213
- top: `${Math.max(tableRect.y, tableWrapperRect.y) - rootRect.y}px`,
214
- left: `${Math.max(tableRect.x, tableWrapperRect.x) - rootRect.x}px`,
215
- });
216
-
217
- if (this.tableCols.length > 0 && this.tableRows.length > 0) {
218
- this.corner = document.createElement('div');
219
- this.corner.classList.add(this.bem.be('corner'));
220
- Object.assign(this.corner.style, {
221
- width: `${this.size}px`,
222
- height: `${this.size}px`,
223
- });
224
- this.corner.addEventListener('click', () => {
225
- const tableSelection = this.tableModule.getModule<TableSelection>('table-selection');
226
- if (tableSelection && this.tableBlot) {
227
- tableSelection.setSelectedTds(this.tableBlot.descendants(TableCellInnerFormat));
228
- tableSelection.show();
229
- tableSelection.updateWithSelectedTds();
230
- }
231
- });
232
- this.root.appendChild(this.corner);
233
- }
234
-
235
- if (this.tableCols.length > 0) {
236
- let colHeadStr = '';
237
- for (const [, col] of this.tableCols.entries()) {
238
- let width = col.domNode.getBoundingClientRect().width;
239
- if (width === 0) {
240
- width = Number.parseInt(col.domNode.getAttribute('width')!, 10);
241
- }
242
- colHeadStr += `<div class="${this.bem.be('col-header')}" style="width: ${width}px">
243
- <div class="${this.bem.be('col-separator')}" style="height: ${tableRect.height + this.size - 3}px"></div>
244
- </div>`;
245
- }
246
- const colHeadWrapper = document.createElement('div');
247
- colHeadWrapper.classList.add(this.bem.be('col'));
248
- const colHead = document.createElement('div');
249
- colHead.classList.add(this.bem.be('col-wrapper'));
250
- Object.assign(colHeadWrapper.style, {
251
- transform: `translateY(-${this.size}px)`,
252
- maxWidth: `${tableWrapperRect.width}px`,
253
- height: `${this.size}px`,
254
- });
255
- Object.assign(colHead.style, {
256
- width: `${tableRect.width}px`,
257
- });
258
- colHead.innerHTML = colHeadStr;
259
- colHeadWrapper.appendChild(colHead);
260
- this.root.appendChild(colHeadWrapper);
261
- colHeadWrapper.scrollLeft = this.tableWrapperBlot.domNode.scrollLeft;
262
- this.colHeadWrapper = colHeadWrapper;
263
- this.bindColEvents();
264
- }
265
-
266
- if (this.tableRows.length > 0) {
267
- let rowHeadStr = '';
268
- for (const [, row] of this.tableRows.entries()) {
269
- const height = `${row.domNode.getBoundingClientRect().height}px`;
270
- rowHeadStr += `<div class="${this.bem.be('row-header')}" style="height: ${Number.parseFloat(height)}px">
271
- <div class="${this.bem.be('row-separator')}" style="width: ${tableRect.width + this.size - 3}px"></div>
272
- </div>`;
273
- }
274
- const rowHeadWrapper = document.createElement('div');
275
- rowHeadWrapper.classList.add(this.bem.be('row'));
276
- const rowHead = document.createElement('div');
277
- rowHead.classList.add(this.bem.be('row-wrapper'));
278
- Object.assign(rowHeadWrapper.style, {
279
- transform: `translateX(-${this.size}px)`,
280
- width: `${this.size}px`,
281
- maxHeight: `${tableWrapperRect.height}px`,
282
- });
283
- Object.assign(rowHead.style, {
284
- height: `${tableRect.height}px`,
285
- });
286
- rowHead.innerHTML = rowHeadStr;
287
- rowHeadWrapper.appendChild(rowHead);
288
- this.root.appendChild(rowHeadWrapper);
289
- rowHeadWrapper.scrollTop = this.tableWrapperBlot.domNode.scrollTop;
290
- this.rowHeadWrapper = rowHeadWrapper;
291
- this.bindRowEvents();
292
- }
293
-
294
- // computed about `caption`
295
- const [tableCaptionBlot] = findChildBlot(this.tableBlot, TableCaptionFormat);
296
- const tableCaptionIsTop = !tableCaptionBlot || !(tableCaptionBlot && tableCaptionBlot.side === 'top');
297
- if (tableCaptionIsTop) {
298
- this.root.classList.remove(this.bem.is('caption-bottom'));
299
- }
300
- else {
301
- this.root.classList.add(this.bem.is('caption-bottom'));
302
- }
303
- let cornerTranslateX = -1 * this.size;
304
- let rowHeadWrapperTranslateX = -1 * this.size;
305
- if (isTableAlignRight(this.tableBlot)) {
306
- this.root.classList.add(this.bem.is('align-right'));
307
- cornerTranslateX = Math.min(tableWrapperRect.width, tableRect.width);
308
- rowHeadWrapperTranslateX = Math.min(tableWrapperRect.width, tableRect.width);
309
- }
310
- else {
311
- this.root.classList.remove(this.bem.is('align-right'));
312
- }
313
- if (this.corner) {
314
- Object.assign(this.corner.style, {
315
- transform: `translateY(${-1 * this.size}px) translateX(${cornerTranslateX}px)`,
316
- top: `${tableCaptionIsTop ? 0 : tableRect.height + this.size}px`,
317
- });
318
- }
319
- if (this.rowHeadWrapper) {
320
- Object.assign(this.rowHeadWrapper.style, {
321
- transform: `translateX(${rowHeadWrapperTranslateX}px)`,
322
- maxHeight: `${tableWrapperRect.height}px`,
323
- });
324
- }
325
- if (this.colHeadWrapper) {
326
- Object.assign(this.colHeadWrapper.style, {
327
- top: `${tableCaptionIsTop ? 0 : tableRect.height + this.size}px`,
328
- maxWidth: `${tableWrapperRect.width}px`,
329
- });
330
- }
331
- }
332
-
333
- show() {
334
- if (!this.table || !this.tableBlot || !this.tableWrapperBlot) return;
335
-
336
- this.root.classList.remove(this.bem.is('hidden'));
337
- this.resizeObserver = createResizeObserver(() => this.update(), { ignoreFirstBind: true });
338
- this.resizeObserver.observe(this.table);
339
-
340
- this.update();
341
- addScrollEvent.call(this, this.quill.root, () => {
342
- this.update();
343
- });
344
- }
345
-
346
- hide() {
347
- this.root.classList.add(this.bem.is('hidden'));
348
- if (this.resizeObserver) {
349
- this.resizeObserver.disconnect();
350
- this.resizeObserver = undefined;
351
- }
352
- }
353
-
354
- destroy() {
355
- this.hide();
356
- clearScrollEvent.call(this);
357
- this.quill.off(Quill.events.EDITOR_CHANGE, this.updateWhenTextChange);
358
- for (const [dom, handle] of this.scrollHandler) {
359
- dom.removeEventListener('scroll', handle);
360
- }
361
- this.root.remove();
362
- }
363
- }
1
+ import type { TableMainFormat, TableWrapperFormat } from '../../formats';
2
+ import type { TableUp } from '../../table-up';
3
+ import type { DragElementOptions, Position, TableResizeBoxOptions } from '../../utils';
4
+ import type { TableSelection } from '../table-selection';
5
+ import Quill from 'quill';
6
+ import { getTableMainRect, TableCaptionFormat, TableCellInnerFormat } from '../../formats';
7
+ import { addScrollEvent, clearScrollEvent, createBEM, createResizeObserver, dragElement, findChildBlot, removeScrollEvent, tableUpEvent, tableUpInternal } from '../../utils';
8
+ import { TableResizeCommon } from './table-resize-common';
9
+ import { DragTableHelper, TableAutoScroller } from './table-resize-drag';
10
+ import { isCellsSpan, isTableAlignRight } from './utils';
11
+
12
+ export class TableResizeBox extends TableResizeCommon {
13
+ static moduleName = 'table-resize-box';
14
+
15
+ options: TableResizeBoxOptions;
16
+ root: HTMLElement;
17
+ tableWrapperBlot?: TableWrapperFormat;
18
+ resizeObserver?: ResizeObserver;
19
+ rowHeadWrapper: HTMLElement | null = null;
20
+ colHeadWrapper: HTMLElement | null = null;
21
+ corner: HTMLElement | null = null;
22
+ scrollHandler: [HTMLElement, (e: Event) => void][] = [];
23
+ lastHeaderSelect: { isX: boolean; index: number } | null = null;
24
+ bem = createBEM('resize-box');
25
+ draggingColIndex = -1;
26
+ draggingRowIndex = -1;
27
+ stopColDrag: (() => void)[] = [];
28
+ stopRowDrag: (() => void)[] = [];
29
+ dragWrapper: HTMLElement | null = null;
30
+ dragPlaceholder: HTMLElement | null = null;
31
+ markIndicator: HTMLElement | null = null;
32
+ dragTip: HTMLElement | null = null;
33
+ stopColMoveDrag: (() => void)[] = [];
34
+ stopRowMoveDrag: (() => void)[] = [];
35
+ autoScroller: TableAutoScroller | null = null;
36
+ updateContentDraggingPosition: () => void;
37
+ cellSpanIndex: Set<number> = new Set();
38
+ dragPlaceholderStartPosition = { x: 0, y: 0 };
39
+
40
+ constructor(public tableModule: TableUp, public quill: Quill, options: Partial<TableResizeBoxOptions>) {
41
+ super(tableModule, quill);
42
+ this.options = this.resolveOptions(options);
43
+
44
+ this.updateContentDraggingPosition = () => this.updateContentDraggerPosition(null as any);
45
+ this.root = this.tableModule.addContainer(this.bem.b());
46
+ this.quill.on(Quill.events.EDITOR_CHANGE, this.updateWhenTextChange);
47
+ this.quill.on(tableUpEvent.TABLE_SELECTION_CHANGE, this.updateWrapperHead);
48
+ }
49
+
50
+ resolveOptions(options: Partial<TableResizeBoxOptions>) {
51
+ return Object.assign({
52
+ size: 16,
53
+ draggable: true,
54
+ }, options);
55
+ }
56
+
57
+ updateWrapperHead = () => {
58
+ if (!this.options.draggable) return;
59
+ const tableSelection = this.tableModule.getModule<TableSelection>(tableUpInternal.tableSelectionName);
60
+ if (!tableSelection || !this.tableBlot) return;
61
+ const { isSpan: isSpanX, cellIndex: cellXIndex } = isCellsSpan(true, this.tableBlot, tableSelection.selectedTds);
62
+ const { isSpan: isSpanY, cellIndex: cellYIndex } = isCellsSpan(false, this.tableBlot, tableSelection.selectedTds);
63
+ // add cursor drag style
64
+ // but if select all table, style will not be added
65
+ if (isSpanX) {
66
+ const tableColHeads = Array.from(this.root.getElementsByClassName(this.bem.be('col-header'))) as HTMLElement[];
67
+ for (const el of tableColHeads) el.classList.remove(this.bem.is('selected'));
68
+ if (!isSpanY) {
69
+ for (const i of Array.from(cellXIndex).slice(0, -1)) {
70
+ tableColHeads[i].classList.add(this.bem.is('selected'));
71
+ }
72
+ }
73
+ }
74
+ if (isSpanY) {
75
+ const tableRowHeads = Array.from(this.root.getElementsByClassName(this.bem.be('row-header'))) as HTMLElement[];
76
+ const tableRowHeadsSorted: HTMLElement[] = [];
77
+ for (const el of tableRowHeads) {
78
+ el.classList.remove(this.bem.is('selected'));
79
+ tableRowHeadsSorted[Number(el.dataset.index)] = el;
80
+ }
81
+ if (!isSpanX) {
82
+ for (const i of Array.from(cellYIndex).slice(0, -1)) {
83
+ if (tableRowHeadsSorted[i]) {
84
+ tableRowHeadsSorted[i].classList.add(this.bem.is('selected'));
85
+ }
86
+ }
87
+ }
88
+ }
89
+ };
90
+
91
+ updateWhenTextChange = (eventName: string) => {
92
+ if (eventName === Quill.events.TEXT_CHANGE) {
93
+ if (this.table && !this.quill.root.contains(this.table)) {
94
+ this.setSelectionTable(undefined);
95
+ }
96
+ else {
97
+ this.update();
98
+ }
99
+ }
100
+ };
101
+
102
+ setSelectionTable(table: HTMLTableElement | undefined) {
103
+ if (this.table === table) return;
104
+ this.hide();
105
+ this.table = table;
106
+ if (this.table) {
107
+ const newTableBlot = Quill.find(this.table) as TableMainFormat;
108
+ if (newTableBlot) {
109
+ this.tableBlot = newTableBlot;
110
+ this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
111
+ }
112
+ this.show();
113
+ }
114
+ this.update();
115
+ }
116
+
117
+ handleResizerHeaderClick(isX: boolean, index: number, e: MouseEvent) {
118
+ if (!this.table) return;
119
+ const { clientX, clientY } = e;
120
+ const tableRect = this.table.getBoundingClientRect();
121
+ if (!e.shiftKey) {
122
+ this.lastHeaderSelect = null;
123
+ }
124
+ const currentBoundary: [Position, Position] = [
125
+ { x: isX ? tableRect.left : clientX, y: isX ? clientY : tableRect.top },
126
+ { x: isX ? tableRect.right : clientX, y: isX ? clientY : tableRect.bottom },
127
+ ];
128
+ if (this.lastHeaderSelect) {
129
+ // find last click head
130
+ let lastX: number;
131
+ let lastY: number;
132
+ if (this.lastHeaderSelect.isX) {
133
+ const tableRowHeads = Array.from(this.root.getElementsByClassName(this.bem.be('row-header'))) as HTMLElement[];
134
+ const rect = tableRowHeads[this.lastHeaderSelect.index].getBoundingClientRect();
135
+ lastX = Math.min(rect.left, tableRect.left);
136
+ lastY = rect.top + rect.height / 2;
137
+ }
138
+ else {
139
+ const tableColHeads = Array.from(this.root.getElementsByClassName(this.bem.be('col-header'))) as HTMLElement[];
140
+ const rect = tableColHeads[this.lastHeaderSelect.index].getBoundingClientRect();
141
+ lastX = rect.left + rect.width / 2;
142
+ lastY = Math.min(rect.top, tableRect.top);
143
+ }
144
+
145
+ if (this.lastHeaderSelect.isX !== isX) {
146
+ currentBoundary[1] = {
147
+ x: Math.max(currentBoundary[0].x, lastX),
148
+ y: Math.max(currentBoundary[0].y, lastY),
149
+ };
150
+ currentBoundary[0] = {
151
+ x: Math.min(currentBoundary[0].x, lastX),
152
+ y: Math.min(currentBoundary[0].y, lastY),
153
+ };
154
+ }
155
+ else if (isX) {
156
+ currentBoundary[0].y = Math.min(currentBoundary[0].y, lastY);
157
+ currentBoundary[1].y = Math.max(currentBoundary[1].y, lastY);
158
+ }
159
+ else {
160
+ currentBoundary[0].x = Math.min(currentBoundary[0].x, lastX);
161
+ currentBoundary[1].x = Math.max(currentBoundary[1].x, lastX);
162
+ }
163
+ }
164
+ else {
165
+ this.lastHeaderSelect = { isX, index };
166
+ }
167
+
168
+ const tableSelection = this.tableModule.getModule<TableSelection>(tableUpInternal.tableSelectionName);
169
+ if (tableSelection) {
170
+ tableSelection.table = this.table;
171
+ tableSelection.setSelectedTds(tableSelection.computeSelectedTds(...currentBoundary));
172
+ tableSelection.show();
173
+ }
174
+ }
175
+
176
+ findDragColIndex() {
177
+ return this.draggingColIndex;
178
+ }
179
+
180
+ findDragRowIndex() {
181
+ return this.draggingRowIndex;
182
+ }
183
+
184
+ updateContentDraggerPosition(dragHelper: DragTableHelper) {
185
+ if (!dragHelper || !this.dragWrapper || !this.markIndicator || !this.tableBlot || !this.tableWrapperBlot) return;
186
+ const { rect: tableRect } = getTableMainRect(this.tableBlot);
187
+ if (!tableRect || dragHelper.moveToIndex < 0) return;
188
+ const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
189
+ const rootRect = this.quill.root.getBoundingClientRect();
190
+ Object.assign(this.dragWrapper.style, {
191
+ top: `${Math.max(tableRect.y, tableWrapperRect.y) - rootRect.y}px`,
192
+ left: `${Math.max(tableRect.x, tableWrapperRect.x) - rootRect.x}px`,
193
+ });
194
+
195
+ const { position } = dragHelper.startPosition[dragHelper.moveToIndex] || {};
196
+ const offsetX = this.dragXCommon.getOffsetFromStart(this.tableBlot);
197
+ const offsetY = this.dragYCommon.getOffsetFromStart(this.tableBlot);
198
+ const markIndicatorStyle = dragHelper.isDragX
199
+ ? {
200
+ top: `${Math.max(tableRect.y, tableWrapperRect.y) - rootRect.y}px`,
201
+ left: `${position - rootRect.left + offsetX}px`,
202
+ }
203
+ : {
204
+ top: `${position - rootRect.top + offsetY}px`,
205
+ left: `${Math.max(tableRect.x, tableWrapperRect.x) - rootRect.x}px`,
206
+ };
207
+ Object.assign(this.markIndicator.style, markIndicatorStyle);
208
+ }
209
+
210
+ createContentDragger(e: PointerEvent, isX: boolean, dragHelper: DragTableHelper) {
211
+ if (!this.tableBlot) return;
212
+ const tableSelection = this.tableModule.getModule<TableSelection>(tableUpInternal.tableSelectionName);
213
+ if (!tableSelection || !this.tableWrapperBlot) return;
214
+ tableSelection.updateWithSelectedTds();
215
+ const placeholderWidth = tableSelection.boundary!.width;
216
+ const placeholderHeight = tableSelection.boundary!.height;
217
+
218
+ const rootRect = this.quill.root.getBoundingClientRect();
219
+ const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
220
+ const dragBEM = createBEM('drag');
221
+ this.dragWrapper = this.tableModule.addContainer(dragBEM.b());
222
+ const wrapLeft = tableWrapperRect.x - rootRect.x;
223
+ const wrapTop = tableWrapperRect.y - rootRect.y;
224
+ Object.assign(this.dragWrapper.style, {
225
+ left: `${wrapLeft}px`,
226
+ top: `${wrapTop}px`,
227
+ width: `${tableWrapperRect.width}px`,
228
+ height: `${tableWrapperRect.height}px`,
229
+ });
230
+
231
+ this.dragPlaceholder = document.createElement('div');
232
+ this.dragPlaceholder.classList.add(dragBEM.be('placeholder'), dragBEM.is('hidden'));
233
+ this.dragWrapper.appendChild(this.dragPlaceholder);
234
+ this.dragPlaceholderStartPosition = {
235
+ x: isX ? tableSelection.boundary!.x - wrapLeft : 0,
236
+ y: isX ? 0 : tableSelection.boundary!.y - wrapTop,
237
+ };
238
+ Object.assign(this.dragPlaceholder.style, {
239
+ left: `${this.dragPlaceholderStartPosition.x}px`,
240
+ top: `${this.dragPlaceholderStartPosition.y}px`,
241
+ width: `${placeholderWidth}px`,
242
+ height: `${placeholderHeight}px`,
243
+ });
244
+
245
+ this.markIndicator = this.tableModule.addContainer(dragBEM.be('indicator'));
246
+ const markIndicatorStyle = isX
247
+ ? {
248
+ top: `${wrapTop}px`,
249
+ height: `${Math.min(tableSelection.boundary!.height, tableWrapperRect.height)}px`,
250
+ }
251
+ : {
252
+ left: `${wrapLeft}px`,
253
+ width: `${Math.min(tableSelection.boundary!.width, tableWrapperRect.width)}px`,
254
+ };
255
+ Object.assign(this.markIndicator.style, markIndicatorStyle);
256
+ this.updateContentDraggingPosition = () => this.updateContentDraggerPosition(dragHelper);
257
+ addScrollEvent.call(this, this.quill.root, this.updateContentDraggingPosition);
258
+ addScrollEvent.call(this, this.tableWrapperBlot.domNode, this.updateContentDraggingPosition);
259
+
260
+ this.dragTip = this.tableModule.addContainer(dragBEM.be('tip'));
261
+ const dragTipContent = document.createElement('div');
262
+ dragTipContent.classList.add(dragBEM.be('tip-content'));
263
+ this.dragTip.appendChild(dragTipContent);
264
+
265
+ // absolute position. range in tableWrapper viewport
266
+ if (isX) {
267
+ this.dragXCommon.minRange = 0;
268
+ this.dragXCommon.maxRange = tableWrapperRect.width - placeholderWidth;
269
+ }
270
+ else {
271
+ this.dragYCommon.minRange = 0;
272
+ this.dragYCommon.maxRange = tableWrapperRect.height - placeholderHeight;
273
+ }
274
+ }
275
+
276
+ bindColEvents() {
277
+ if (!this.tableWrapperBlot) return;
278
+ const tableColHeads = Array.from(this.root.getElementsByClassName(this.bem.be('col-header'))) as HTMLElement[];
279
+ const tableColHeadSeparators = Array.from(this.root.getElementsByClassName(this.bem.be('col-separator'))) as HTMLElement[];
280
+
281
+ addScrollEvent.call(this, this.tableWrapperBlot.domNode, () => {
282
+ this.colHeadWrapper!.scrollLeft = this.tableWrapperBlot!.domNode.scrollLeft;
283
+ });
284
+
285
+ if (this.stopColMoveDrag.length > 0) {
286
+ for (const stop of this.stopColMoveDrag) stop();
287
+ this.stopColMoveDrag = [];
288
+ }
289
+ const dragHelper = new DragTableHelper(this.tableModule, this.tableBlot!, this.dragXCommon, {
290
+ isDragX: true,
291
+ allowMoveToIndex: index => this.allowMoveToIndex(index),
292
+ });
293
+ for (const [index, el] of tableColHeads.entries()) {
294
+ el.addEventListener('click', this.handleResizerHeaderClick.bind(this, false, index));
295
+ if (this.options.draggable) {
296
+ const { stop } = dragElement(el, this.dragHeadOptions(true, { index, dragHelper }));
297
+ this.stopColMoveDrag.push(stop);
298
+ }
299
+ }
300
+
301
+ if (this.stopColDrag.length > 0) {
302
+ for (const stop of this.stopColDrag) stop();
303
+ this.stopColDrag = [];
304
+ }
305
+ for (const [i, el] of tableColHeadSeparators.entries()) {
306
+ const { stop } = dragElement(el, {
307
+ axis: 'x',
308
+ onStart: (position, e) => {
309
+ this.dragging = true;
310
+
311
+ this.draggingColIndex = i;
312
+ this.calculateColDragRange();
313
+ this.dragXCommon.createBreak();
314
+ if (!this.tableBlot) return;
315
+ const tableWrapperRect = this.tableBlot.domNode.parentElement!.getBoundingClientRect();
316
+ const { rect: tableRect } = getTableMainRect(this.tableBlot);
317
+ if (!tableRect) return;
318
+ // record current tablb rect to calculate the offset if have scroll when dragging
319
+ this.dragXCommon.startValue = tableRect.x;
320
+ const rootRect = this.quill.root.getBoundingClientRect();
321
+ Object.assign(this.dragXCommon.dragBreak!.style, {
322
+ top: `${Math.max(tableWrapperRect.y, tableRect.y) - rootRect.y}px`,
323
+ left: `${e.clientX - rootRect.x}px`,
324
+ height: `${Math.min(tableWrapperRect.height, tableRect.height)}px`,
325
+ });
326
+ },
327
+ onMove: ({ position }) => {
328
+ if (!this.dragXCommon.dragBreak) return;
329
+ const resultX = this.dragXCommon.limitRange(this.tableBlot, position.x, true);
330
+ const rootRect = this.quill.root.getBoundingClientRect();
331
+ this.dragXCommon.dragBreak.style.left = `${resultX - rootRect.x}px`;
332
+ },
333
+ onEnd: ({ position }) => {
334
+ this.dragging = false;
335
+
336
+ this.updateTableCol(position.x);
337
+ this.removeBreak();
338
+ },
339
+ });
340
+ this.stopColDrag.push(stop);
341
+
342
+ el.addEventListener('dragstart', e => e.preventDefault());
343
+ }
344
+ }
345
+
346
+ bindRowEvents() {
347
+ const tableRowHeads = Array.from(this.root.getElementsByClassName(this.bem.be('row-header'))) as HTMLElement[];
348
+ const tableRowHeadSeparators = Array.from(this.root.getElementsByClassName(this.bem.be('row-separator'))) as HTMLElement[];
349
+
350
+ addScrollEvent.call(this, this.tableWrapperBlot!.domNode, () => {
351
+ this.rowHeadWrapper!.scrollTop = this.tableWrapperBlot!.domNode.scrollTop;
352
+ });
353
+
354
+ if (this.stopRowMoveDrag.length > 0) {
355
+ for (const stop of this.stopRowMoveDrag) stop();
356
+ this.stopRowMoveDrag = [];
357
+ }
358
+ const dragHelper = new DragTableHelper(this.tableModule, this.tableBlot!, this.dragYCommon, {
359
+ isDragX: false,
360
+ allowMoveToIndex: index => this.allowMoveToIndex(index),
361
+ });
362
+ for (const [i, el] of tableRowHeads.entries()) {
363
+ // emptyRow doesn't generate head. logic need row index, not head inedx
364
+ const index = Number(el.dataset.index || i);
365
+ el.addEventListener('click', this.handleResizerHeaderClick.bind(this, true, i));
366
+ if (this.options.draggable) {
367
+ const { stop } = dragElement(el, this.dragHeadOptions(false, { index, dragHelper }));
368
+ this.stopRowMoveDrag.push(stop);
369
+ }
370
+ }
371
+
372
+ if (this.stopRowDrag.length > 0) {
373
+ for (const stop of this.stopRowDrag) stop();
374
+ this.stopRowDrag = [];
375
+ }
376
+ for (const [i, el] of tableRowHeadSeparators.entries()) {
377
+ const { stop } = dragElement(el, {
378
+ axis: 'y',
379
+ onStart: (ops, e) => {
380
+ this.dragging = true;
381
+
382
+ this.draggingRowIndex = i;
383
+ this.calculateRowDragRange();
384
+ this.dragYCommon.createBreak();
385
+ if (!this.tableBlot) return;
386
+ const tableWrapperRect = this.tableBlot.domNode.parentElement!.getBoundingClientRect();
387
+ const { rect: tableRect } = getTableMainRect(this.tableBlot);
388
+ if (!tableRect) return;
389
+ // record current tablb rect to calculate the offset if have scroll when dragging
390
+ this.dragYCommon.startValue = tableRect.y;
391
+ const rootRect = this.quill.root.getBoundingClientRect();
392
+ Object.assign(this.dragYCommon.dragBreak!.style, {
393
+ top: `${e.clientY - rootRect.y}px`,
394
+ left: `${Math.max(tableWrapperRect.x, tableRect.x) - rootRect.x}px`,
395
+ width: `${Math.min(tableWrapperRect.width, tableRect.width)}px`,
396
+ });
397
+ },
398
+ onMove: ({ position }) => {
399
+ if (!this.dragYCommon.dragBreak || !this.table) return;
400
+ const resultY = this.dragYCommon.limitRange(this.tableBlot, position.y, true);
401
+ const rootRect = this.quill.root.getBoundingClientRect();
402
+ this.dragYCommon.dragBreak.style.top = `${resultY - rootRect.y}px`;
403
+ },
404
+ onEnd: ({ position }) => {
405
+ this.dragging = false;
406
+
407
+ this.updateTableRow(position.y);
408
+ this.removeBreak();
409
+ },
410
+ });
411
+ this.stopRowDrag.push(stop);
412
+
413
+ el.addEventListener('dragstart', e => e.preventDefault());
414
+ }
415
+ }
416
+
417
+ allowMoveToIndex(index: number) {
418
+ return !this.cellSpanIndex.has(index);
419
+ }
420
+
421
+ recordCellSpan(isX: boolean) {
422
+ // record colspan or rowspan cell index when dragging content
423
+ // drag content not allow to insert on these index
424
+ const cellIndex = new Set<number>();
425
+ if (!this.tableBlot) return cellIndex;
426
+ const cells = this.tableBlot.descendants(TableCellInnerFormat);
427
+ const ids: string[] = isX ? this.tableBlot.getColIds() : this.tableBlot.getRowIds();
428
+ const spanAttr = isX ? 'colspan' : 'rowspan';
429
+ for (const cell of cells) {
430
+ if (cell[spanAttr] <= 1) continue;
431
+ const index = ids.indexOf(isX ? cell.colId : cell.rowId);
432
+ if (index === -1) continue;
433
+ for (let i = index + 1; i < index + cell[spanAttr] && i < ids.length; i++) {
434
+ cellIndex.add(i);
435
+ }
436
+ }
437
+ return cellIndex;
438
+ }
439
+
440
+ dragHeadOptions(isX: boolean, context: { index: number; dragHelper: DragTableHelper }): Partial<DragElementOptions> {
441
+ const { dragHelper, index } = context;
442
+ return {
443
+ axis: isX ? 'x' : 'y',
444
+ onStart: (positionInfo, e) => {
445
+ let prevent = false;
446
+ dragHelper.onStart(positionInfo, e, () => {
447
+ if (!this.tableBlot) return;
448
+ const count = (isX ? this.tableBlot.getCols() : this.tableBlot.getRows()).length;
449
+ if (dragHelper.selectedIndex.size > count) {
450
+ prevent = false;
451
+ return;
452
+ }
453
+ const selectedIndex = new Set(Array.from(dragHelper.selectedIndex).slice(0, -1));
454
+ prevent = selectedIndex.has(index);
455
+ if (!selectedIndex.has(index)) {
456
+ prevent = false;
457
+ return;
458
+ }
459
+ this.dragging = true;
460
+ if (isX) {
461
+ this.draggingColIndex = index;
462
+ }
463
+ else {
464
+ this.draggingRowIndex = index;
465
+ }
466
+ this.createContentDragger(e, isX, dragHelper);
467
+ this.cellSpanIndex = this.recordCellSpan(isX);
468
+ if (!this.tableWrapperBlot) return;
469
+ this.autoScroller = new TableAutoScroller(50, 40);
470
+ this.autoScroller.minusY = this.options.size;
471
+ this.autoScroller.minusX = this.options.size;
472
+ this.autoScroller.updateMousePosition(e.clientX, e.clientY);
473
+ this.autoScroller.start(this.tableWrapperBlot.domNode);
474
+ });
475
+ return prevent;
476
+ },
477
+ onMove: (positionInfo, e) => {
478
+ dragHelper.onMove(positionInfo, e, (helper) => {
479
+ const { movePosition } = positionInfo;
480
+ this.autoScroller?.updateMousePosition(e.clientX, e.clientY);
481
+ if (!this.dragPlaceholder || !this.markIndicator || !this.dragTip || !this.tableWrapperBlot) return;
482
+
483
+ this.dragPlaceholder.classList.remove(this.bem.is('hidden'));
484
+ const resultPosition = helper.dragCommon.limitRange(
485
+ this.tableBlot,
486
+ this.dragPlaceholderStartPosition[isX ? 'x' : 'y'] + movePosition[isX ? 'x' : 'y'],
487
+ false,
488
+ );
489
+ this.dragPlaceholder.style[isX ? 'left' : 'top'] = `${resultPosition}px`;
490
+ Object.assign(this.dragTip.style, {
491
+ left: `${e.clientX - 10}px`,
492
+ top: `${e.clientY - 10}px`,
493
+ });
494
+ if (helper.moveToIndex < 0) {
495
+ Object.assign(this.markIndicator.style, {
496
+ opacity: '0',
497
+ });
498
+ return;
499
+ }
500
+ const rootRect = this.quill.root.getBoundingClientRect();
501
+ const isBeyond = helper.moveToIndex >= helper.startPosition.length;
502
+ const item = helper.startPosition[isBeyond ? helper.moveToIndex - 1 : helper.moveToIndex];
503
+ const indicatorPosition = item.position + (isBeyond ? item.size : 0);
504
+ const offset = helper.dragCommon.getOffsetFromStart(this.tableBlot);
505
+ Object.assign(this.markIndicator.style, {
506
+ opacity: '1',
507
+ [isX ? 'left' : 'top']: `${indicatorPosition - (isX ? rootRect.left : rootRect.top) + offset}px`,
508
+ });
509
+ });
510
+ },
511
+ onEnd: (positionInfo, e) => {
512
+ dragHelper.onEnd(positionInfo, e, (helper) => {
513
+ const changeDelta = helper.updateTableStructure(
514
+ this.quill.getContents(),
515
+ (isX ? this.draggingColIndex : this.draggingRowIndex) > helper.moveToIndex,
516
+ );
517
+ this.quill.updateContents(changeDelta);
518
+ this.dragging = false;
519
+ this.cellSpanIndex = new Set();
520
+ this.autoScroller?.stop();
521
+ removeScrollEvent.call(this, this.quill.root, this.updateContentDraggingPosition);
522
+ removeScrollEvent.call(this, this.tableWrapperBlot!.domNode, this.updateContentDraggingPosition);
523
+ if (this.dragWrapper) {
524
+ this.dragWrapper.remove();
525
+ this.dragWrapper = null;
526
+ }
527
+ if (this.markIndicator) {
528
+ this.markIndicator.remove();
529
+ this.markIndicator = null;
530
+ }
531
+ if (this.dragTip) {
532
+ this.dragTip.remove();
533
+ this.dragTip = null;
534
+ }
535
+ });
536
+ },
537
+ };
538
+ }
539
+
540
+ update() {
541
+ if (!this.tableBlot || !this.tableWrapperBlot) return;
542
+ const { rect: tableRect } = getTableMainRect(this.tableBlot);
543
+ if (!tableRect) return;
544
+
545
+ this.root.innerHTML = '';
546
+
547
+ const tableCols = this.tableBlot.getCols();
548
+ const tableRows = this.tableBlot.getRows();
549
+ const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
550
+ const rootRect = this.quill.root.getBoundingClientRect();
551
+ Object.assign(this.root.style, {
552
+ top: `${Math.max(tableRect.y, tableWrapperRect.y) - rootRect.y}px`,
553
+ left: `${Math.max(tableRect.x, tableWrapperRect.x) - rootRect.x}px`,
554
+ });
555
+
556
+ if (tableCols.length > 0 && tableRows.length > 0) {
557
+ this.corner = document.createElement('div');
558
+ this.corner.classList.add(this.bem.be('corner'));
559
+ Object.assign(this.corner.style, {
560
+ width: `${this.options.size}px`,
561
+ height: `${this.options.size}px`,
562
+ });
563
+ this.corner.addEventListener('click', () => {
564
+ const tableSelection = this.tableModule.getModule<TableSelection>(tableUpInternal.tableSelectionName);
565
+ if (tableSelection && this.tableBlot) {
566
+ tableSelection.setSelectedTds(this.tableBlot.descendants(TableCellInnerFormat));
567
+ tableSelection.show();
568
+ tableSelection.updateWithSelectedTds();
569
+ }
570
+ });
571
+ this.root.appendChild(this.corner);
572
+ }
573
+
574
+ if (tableCols.length > 0) {
575
+ let colHeadStr = '';
576
+ for (const col of tableCols) {
577
+ let width = col.domNode.getBoundingClientRect().width;
578
+ if (width === 0) {
579
+ width = Number.parseInt(col.domNode.getAttribute('width')!, 10);
580
+ }
581
+ colHeadStr += `<div class="${this.bem.be('col-header')}" style="width: ${width}px">
582
+ <div class="${this.bem.be('col-separator')}" style="height: ${tableRect.height + this.options.size - 3}px"></div>
583
+ </div>`;
584
+ }
585
+ const colHeadWrapper = document.createElement('div');
586
+ colHeadWrapper.classList.add(this.bem.be('col'));
587
+ const colHead = document.createElement('div');
588
+ colHead.classList.add(this.bem.be('col-wrapper'));
589
+ Object.assign(colHeadWrapper.style, {
590
+ transform: `translateY(-${this.options.size}px)`,
591
+ maxWidth: `${tableWrapperRect.width}px`,
592
+ height: `${this.options.size}px`,
593
+ });
594
+ Object.assign(colHead.style, {
595
+ width: `${tableRect.width}px`,
596
+ });
597
+ colHead.innerHTML = colHeadStr;
598
+ colHeadWrapper.appendChild(colHead);
599
+ this.root.appendChild(colHeadWrapper);
600
+ colHeadWrapper.scrollLeft = this.tableWrapperBlot.domNode.scrollLeft;
601
+ this.colHeadWrapper = colHeadWrapper;
602
+ this.bindColEvents();
603
+ }
604
+
605
+ if (tableRows.length > 0) {
606
+ let rowHeadStr = '';
607
+ for (let i = 0; i < tableRows.length; i++) {
608
+ const index = i;
609
+ const row = tableRows[i];
610
+ let height = row.domNode.getBoundingClientRect().height;
611
+ // empty row have different height in chrome and firefox
612
+ // count total height to set
613
+ if (row.children.length === 1 && (row.children.head?.emptyRow.length || 0) > 0) {
614
+ const length = row.children.head!.emptyRow.length;
615
+ for (let start = i + 1; start < tableRows.length && start <= i + length; start++) {
616
+ const nextRow = tableRows[start];
617
+ height += nextRow.domNode.getBoundingClientRect().height;
618
+ }
619
+ i += length;
620
+ }
621
+ rowHeadStr += `<div class="${this.bem.be('row-header')}" data-index="${index}" style="height: ${height}px">
622
+ <div class="${this.bem.be('row-separator')}" style="width: ${tableRect.width + this.options.size - 3}px"></div>
623
+ </div>`;
624
+ }
625
+ const rowHeadWrapper = document.createElement('div');
626
+ rowHeadWrapper.classList.add(this.bem.be('row'));
627
+ const rowHead = document.createElement('div');
628
+ rowHead.classList.add(this.bem.be('row-wrapper'));
629
+ Object.assign(rowHeadWrapper.style, {
630
+ transform: `translateX(-${this.options.size}px)`,
631
+ width: `${this.options.size}px`,
632
+ maxHeight: `${tableWrapperRect.height}px`,
633
+ });
634
+ Object.assign(rowHead.style, {
635
+ height: `${tableRect.height}px`,
636
+ });
637
+ rowHead.innerHTML = rowHeadStr;
638
+ rowHeadWrapper.appendChild(rowHead);
639
+ this.root.appendChild(rowHeadWrapper);
640
+ rowHeadWrapper.scrollTop = this.tableWrapperBlot.domNode.scrollTop;
641
+ this.rowHeadWrapper = rowHeadWrapper;
642
+ this.bindRowEvents();
643
+ }
644
+
645
+ // computed about `caption`
646
+ const [tableCaptionBlot] = findChildBlot(this.tableBlot, TableCaptionFormat);
647
+ const tableCaptionIsTop = !tableCaptionBlot || !(tableCaptionBlot?.side === 'top');
648
+ if (tableCaptionIsTop) {
649
+ this.root.classList.remove(this.bem.is('caption-bottom'));
650
+ }
651
+ else {
652
+ this.root.classList.add(this.bem.is('caption-bottom'));
653
+ }
654
+ let cornerTranslateX = -1 * this.options.size;
655
+ let rowHeadWrapperTranslateX = -1 * this.options.size;
656
+ if (isTableAlignRight(this.tableBlot)) {
657
+ this.root.classList.add(this.bem.is('align-right'));
658
+ cornerTranslateX = Math.min(tableWrapperRect.width, tableRect.width);
659
+ rowHeadWrapperTranslateX = Math.min(tableWrapperRect.width, tableRect.width);
660
+ }
661
+ else {
662
+ this.root.classList.remove(this.bem.is('align-right'));
663
+ }
664
+ if (this.corner) {
665
+ Object.assign(this.corner.style, {
666
+ transform: `translateY(${-1 * this.options.size}px) translateX(${cornerTranslateX}px)`,
667
+ top: `${tableCaptionIsTop ? 0 : tableRect.height + this.options.size}px`,
668
+ });
669
+ }
670
+ if (this.rowHeadWrapper) {
671
+ Object.assign(this.rowHeadWrapper.style, {
672
+ transform: `translateX(${rowHeadWrapperTranslateX}px)`,
673
+ maxHeight: `${tableWrapperRect.height}px`,
674
+ });
675
+ }
676
+ if (this.colHeadWrapper) {
677
+ Object.assign(this.colHeadWrapper.style, {
678
+ top: `${tableCaptionIsTop ? 0 : tableRect.height + this.options.size}px`,
679
+ maxWidth: `${tableWrapperRect.width}px`,
680
+ });
681
+ }
682
+ }
683
+
684
+ show() {
685
+ if (!this.table || !this.tableBlot) return;
686
+
687
+ this.root.classList.remove(this.bem.is('hidden'));
688
+ this.resizeObserver = createResizeObserver(() => this.update(), { ignoreFirstBind: true });
689
+ this.resizeObserver.observe(this.table);
690
+
691
+ this.update();
692
+ addScrollEvent.call(this, this.quill.root, () => {
693
+ this.update();
694
+ });
695
+ }
696
+
697
+ hide() {
698
+ this.root.classList.add(this.bem.is('hidden'));
699
+ if (this.resizeObserver) {
700
+ this.resizeObserver.disconnect();
701
+ this.resizeObserver = undefined;
702
+ }
703
+ }
704
+
705
+ destroy() {
706
+ this.hide();
707
+ clearScrollEvent.call(this);
708
+ this.quill.off(Quill.events.EDITOR_CHANGE, this.updateWhenTextChange);
709
+ for (const [dom, handle] of this.scrollHandler) {
710
+ dom.removeEventListener('scroll', handle);
711
+ }
712
+ this.root.remove();
713
+ }
714
+ }