quill-table-up 2.4.2 → 3.0.0

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 (66) hide show
  1. package/README.md +96 -40
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.ts +172 -110
  4. package/dist/index.js +29 -28
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.umd.js +29 -28
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/table-creator.css +1 -1
  9. package/package.json +1 -1
  10. package/src/__tests__/e2e/table-align.test.ts +1 -2
  11. package/src/__tests__/e2e/table-blots.test.ts +45 -35
  12. package/src/__tests__/e2e/table-clipboard.test.ts +20 -0
  13. package/src/__tests__/e2e/table-hack.test.ts +5 -5
  14. package/src/__tests__/e2e/table-keyboard-handler.test.ts +6 -6
  15. package/src/__tests__/e2e/table-menu.test.ts +23 -0
  16. package/src/__tests__/e2e/table-selection.test.ts +3 -3
  17. package/src/__tests__/unit/table-blots.test.ts +26 -26
  18. package/src/__tests__/unit/table-caption.test.ts +33 -36
  19. package/src/__tests__/unit/table-cell-merge.test.ts +114 -114
  20. package/src/__tests__/unit/table-clipboard.test.ts +383 -19
  21. package/src/__tests__/unit/table-hack.test.ts +202 -144
  22. package/src/__tests__/unit/table-insert.test.ts +79 -79
  23. package/src/__tests__/unit/table-redo-undo.test.ts +495 -64
  24. package/src/__tests__/unit/table-remove.test.ts +8 -11
  25. package/src/__tests__/unit/utils.test.ts +4 -4
  26. package/src/__tests__/unit/utils.ts +4 -3
  27. package/src/formats/container-format.ts +25 -2
  28. package/src/formats/index.ts +54 -8
  29. package/src/formats/overrides/block.ts +35 -30
  30. package/src/formats/table-body-format.ts +18 -58
  31. package/src/formats/table-cell-format.ts +296 -286
  32. package/src/formats/table-cell-inner-format.ts +384 -358
  33. package/src/formats/table-foot-format.ts +7 -0
  34. package/src/formats/table-head-format.ts +7 -0
  35. package/src/formats/table-main-format.ts +84 -5
  36. package/src/formats/table-row-format.ts +44 -14
  37. package/src/modules/index.ts +1 -0
  38. package/src/modules/table-align.ts +59 -53
  39. package/src/modules/table-clipboard.ts +60 -39
  40. package/src/modules/table-dom-selector.ts +33 -0
  41. package/src/modules/table-menu/constants.ts +21 -31
  42. package/src/modules/table-menu/table-menu-common.ts +72 -51
  43. package/src/modules/table-menu/table-menu-contextmenu.ts +22 -6
  44. package/src/modules/table-menu/table-menu-select.ts +75 -12
  45. package/src/modules/table-resize/table-resize-box.ts +148 -128
  46. package/src/modules/table-resize/table-resize-common.ts +37 -32
  47. package/src/modules/table-resize/table-resize-line.ts +53 -36
  48. package/src/modules/table-resize/table-resize-scale.ts +32 -27
  49. package/src/modules/table-scrollbar.ts +58 -32
  50. package/src/modules/table-selection.ts +102 -79
  51. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  52. package/src/style/index.less +0 -1
  53. package/src/style/select-box.less +1 -0
  54. package/src/style/table-menu.less +4 -4
  55. package/src/style/table-resize.less +1 -0
  56. package/src/style/table-scrollbar.less +2 -2
  57. package/src/table-up.ts +161 -194
  58. package/src/utils/components/table/creator.ts +4 -1
  59. package/src/utils/components/tooltip.ts +6 -1
  60. package/src/utils/constants.ts +8 -2
  61. package/src/utils/index.ts +2 -1
  62. package/src/utils/scroll.ts +47 -0
  63. package/src/utils/style-helper.ts +47 -0
  64. package/src/utils/transformer.ts +0 -25
  65. package/src/utils/types.ts +15 -15
  66. package/src/utils/scroll-event-helper.ts +0 -22
@@ -0,0 +1,7 @@
1
+ import { blotName } from '../utils';
2
+ import { TableBodyFormat } from './table-body-format';
3
+
4
+ export class TableFootFormat extends TableBodyFormat {
5
+ static blotName = blotName.tableFoot;
6
+ static tagName = 'tfoot';
7
+ }
@@ -0,0 +1,7 @@
1
+ import { blotName } from '../utils';
2
+ import { TableBodyFormat } from './table-body-format';
3
+
4
+ export class TableHeadFormat extends TableBodyFormat {
5
+ static blotName = blotName.tableHead;
6
+ static tagName = 'thead';
7
+ }
@@ -1,6 +1,7 @@
1
1
  import type TypeScroll from 'quill/blots/scroll';
2
2
  import type { TableValue } from '../utils';
3
- import { blotName, tableUpSize } from '../utils';
3
+ import type { TableBodyFormat } from './table-body-format';
4
+ import { blotName, randomId, tableUpSize } from '../utils';
4
5
  import { ContainerFormat } from './container-format';
5
6
  import { TableCellInnerFormat } from './table-cell-inner-format';
6
7
  import { TableColFormat } from './table-col-format';
@@ -128,8 +129,14 @@ export class TableMainFormat extends ContainerFormat {
128
129
  Object.assign(this.domNode.style, style);
129
130
  }
130
131
 
132
+ getBodys() {
133
+ return Array.from(this.domNode.querySelectorAll('thead, tbody, tfoot'))
134
+ .map(el => this.scroll.find(el) as TableBodyFormat)
135
+ .filter(Boolean);
136
+ }
137
+
131
138
  getRows() {
132
- return Array.from(this.domNode.querySelectorAll(`${TableRowFormat.tagName}`))
139
+ return Array.from(this.domNode.querySelectorAll('tr'))
133
140
  .map(el => this.scroll.find(el) as TableRowFormat)
134
141
  .filter(Boolean);
135
142
  }
@@ -228,7 +235,7 @@ export class TableMainFormat extends ContainerFormat {
228
235
  else {
229
236
  if (row.children.length === 0 && row.prev) {
230
237
  // find the not empty row
231
- let prev: TableRowFormat = row.prev as TableRowFormat;
238
+ let prev = row.prev as TableRowFormat;
232
239
  while (prev && prev.children.length === 0) {
233
240
  prev = prev.prev as TableRowFormat;
234
241
  }
@@ -257,7 +264,9 @@ export class TableMainFormat extends ContainerFormat {
257
264
  const childs: Record<string, ContainerFormat[]> = {
258
265
  [blotName.tableCaption]: [],
259
266
  [blotName.tableColgroup]: [],
267
+ [blotName.tableHead]: [],
260
268
  [blotName.tableBody]: [],
269
+ [blotName.tableFoot]: [],
261
270
  };
262
271
  // eslint-disable-next-line unicorn/no-array-for-each
263
272
  this.children.forEach((child) => {
@@ -274,23 +283,93 @@ export class TableMainFormat extends ContainerFormat {
274
283
  // check sort child
275
284
  const tableCaption = childs[blotName.tableCaption][0];
276
285
  const tableColgroup = childs[blotName.tableColgroup][0];
286
+ const tableHead = childs[blotName.tableHead][0];
277
287
  const tableBody = childs[blotName.tableBody][0];
288
+ const tableFoot = childs[blotName.tableFoot][0];
278
289
 
279
290
  const isCaptionFirst = tableCaption && this.children.head !== tableCaption;
280
291
  const isColgroupSecond = tableColgroup && tableCaption && tableCaption.next !== tableColgroup;
281
292
  const isColgroupFirst = tableColgroup && !tableCaption && this.children.head !== tableColgroup;
282
- const isBodyLast = tableBody && this.children.tail !== tableBody;
293
+ const isHeadLast = tableHead && !tableBody && !tableFoot && this.children.tail !== tableHead;
294
+ const isBodyAfterHead = tableBody && tableHead && tableBody.prev !== tableHead;
295
+ const isBodyLast = tableBody && !tableFoot && this.children.tail !== tableBody;
296
+ const isBodyBeforeFoot = tableBody && tableFoot && tableBody.next !== tableFoot;
297
+ const isFootLast = tableFoot && this.children.tail !== tableFoot;
283
298
 
284
299
  // sort child
285
- if (isCaptionFirst || isColgroupSecond || isColgroupFirst || isBodyLast) {
300
+ if (isCaptionFirst || isColgroupSecond || isColgroupFirst || isHeadLast || isBodyAfterHead || isBodyLast || isBodyBeforeFoot || isFootLast) {
286
301
  const tableMain = this.clone() as TableMainFormat;
287
302
  tableCaption && tableMain.appendChild(tableCaption);
288
303
  tableColgroup && tableMain.appendChild(tableColgroup);
304
+ tableHead && tableMain.appendChild(tableHead);
289
305
  tableBody && tableMain.appendChild(tableBody);
306
+ tableFoot && tableMain.appendChild(tableFoot);
290
307
 
291
308
  // eslint-disable-next-line unicorn/no-array-for-each
292
309
  this.children.forEach(child => child.remove());
293
310
  tableMain.moveChildren(this);
294
311
  }
295
312
  }
313
+
314
+ // insert row at index
315
+ insertRow(targetIndex: number) {
316
+ // get all column id. exclude the columns of the target index row with rowspan
317
+ const colIds = this.getColIds();
318
+ const rows = this.descendants(TableRowFormat);
319
+ const insertColIds = new Set(colIds);
320
+ let index = 0;
321
+ for (const row of rows) {
322
+ if (index === targetIndex) break;
323
+ row.foreachCellInner((cell) => {
324
+ if (index + cell.rowspan > targetIndex) {
325
+ cell.rowspan += 1;
326
+ insertColIds.delete(cell.colId);
327
+ // colspan cell need remove all includes colId
328
+ if (cell.colspan !== 1) {
329
+ const colIndex = colIds.indexOf(cell.colId);
330
+ for (let i = 0; i < cell.colspan - 1; i++) {
331
+ insertColIds.delete(colIds[colIndex + i + 1]);
332
+ }
333
+ }
334
+ }
335
+ });
336
+ index += 1;
337
+ }
338
+ // append new row
339
+ const tableId = this.tableId;
340
+ const rowId = randomId();
341
+ const tableRow = this.scroll.create(blotName.tableRow, {
342
+ tableId,
343
+ rowId,
344
+ }) as ContainerFormat;
345
+ for (const colId of insertColIds) {
346
+ const breakBlot = this.scroll.create('break');
347
+ const block = breakBlot.wrap('block');
348
+ const tableCellInner = block.wrap(blotName.tableCellInner, {
349
+ tableId,
350
+ rowId,
351
+ colId,
352
+ rowspan: 1,
353
+ colspan: 1,
354
+ });
355
+ const tableCell = tableCellInner.wrap(blotName.tableCell, {
356
+ tableId,
357
+ rowId,
358
+ colId,
359
+ rowspan: 1,
360
+ colspan: 1,
361
+ });
362
+ tableRow.appendChild(tableCell);
363
+ }
364
+
365
+ // insert the new row at the target index
366
+ // if you insert a row at the thead, then the new row will be inserted in thead. Similarly for tbody and tfoot
367
+ const refRow = rows[targetIndex] || null;
368
+ if (!refRow) {
369
+ rows[rows.length - 1].parent.appendChild(tableRow);
370
+ }
371
+ else {
372
+ refRow.parent.insertBefore(tableRow, refRow);
373
+ }
374
+ }
296
375
  }
@@ -1,5 +1,5 @@
1
1
  import type { Parchment as TypeParchment } from 'quill';
2
- import type { TableCellValue, TableRowValue } from '../utils';
2
+ import type { TableBodyTag, TableCellValue, TableRowValue } from '../utils';
3
3
  import type { TableCellFormat } from './table-cell-format';
4
4
  import { blotName, findParentBlot } from '../utils';
5
5
  import { ContainerFormat } from './container-format';
@@ -10,11 +10,21 @@ export class TableRowFormat extends ContainerFormat {
10
10
  static blotName = blotName.tableRow;
11
11
  static tagName = 'tr';
12
12
  static className = 'ql-table-row';
13
+ static allowDataAttrs = new Set(['table-id', 'row-id', 'wrap-tag']);
14
+ static allowDataAttrsChangeHandler: Record<string, keyof TableRowFormat> = {
15
+ 'wrap-tag': 'wrapParentTag',
16
+ };
13
17
 
14
18
  static create(value: TableRowValue) {
19
+ const {
20
+ tableId,
21
+ rowId,
22
+ wrapTag = 'tbody',
23
+ } = value;
15
24
  const node = super.create() as HTMLElement;
16
- node.dataset.tableId = value.tableId;
17
- node.dataset.rowId = value.rowId;
25
+ node.dataset.tableId = tableId;
26
+ node.dataset.rowId = rowId;
27
+ node.dataset.wrapTag = wrapTag;
18
28
  return node;
19
29
  }
20
30
 
@@ -28,6 +38,10 @@ export class TableRowFormat extends ContainerFormat {
28
38
  return this.domNode.dataset.tableId!;
29
39
  }
30
40
 
41
+ get wrapTag() {
42
+ return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
43
+ }
44
+
31
45
  setHeight(value: string) {
32
46
  this.foreachCellInner((cellInner) => {
33
47
  cellInner.setFormatValue('height', value, true);
@@ -144,7 +158,7 @@ export class TableRowFormat extends ContainerFormat {
144
158
  let cur: TableCellFormat | null;
145
159
  while ((cur = next())) {
146
160
  const [tableCell] = cur.descendants(TableCellInnerFormat);
147
- if (func(tableCell, i++)) break;
161
+ if (tableCell && func(tableCell, i++)) break;
148
162
  }
149
163
  }
150
164
 
@@ -157,19 +171,35 @@ export class TableRowFormat extends ContainerFormat {
157
171
  );
158
172
  }
159
173
 
160
- optimize(_context: Record<string, any>) {
174
+ wrapParentTag() {
175
+ const tableBodyBlotNameMap: Record<string, string> = {
176
+ thead: blotName.tableHead,
177
+ tbody: blotName.tableBody,
178
+ tfoot: blotName.tableFoot,
179
+ };
180
+
161
181
  const parent = this.parent;
162
- const { tableId } = this;
163
- if (parent !== null && parent.statics.blotName !== blotName.tableBody) {
164
- this.wrap(blotName.tableBody, tableId);
182
+ if (parent !== null && parent.statics.blotName !== tableBodyBlotNameMap[this.wrapTag]) {
183
+ if (Object.values(tableBodyBlotNameMap).includes(parent.statics.blotName)) {
184
+ const index = this.offset(this.parent);
185
+ const newParent = this.parent.split(index);
186
+ if (newParent && newParent.length() <= 0) {
187
+ newParent.remove();
188
+ }
189
+ const afterParent = (this.parent as any).splitAfter(this);
190
+ if (afterParent && afterParent.length() <= 0) {
191
+ afterParent.remove();
192
+ }
193
+ this.parent.replaceWith(tableBodyBlotNameMap[this.wrapTag], this.tableId);
194
+ }
195
+ else {
196
+ this.wrap(tableBodyBlotNameMap[this.wrapTag], this.tableId);
197
+ }
165
198
  }
199
+ }
166
200
 
167
- if (
168
- this.statics.requiredContainer
169
- && !(this.parent instanceof this.statics.requiredContainer)
170
- ) {
171
- this.wrap(this.statics.requiredContainer.blotName);
172
- }
201
+ optimize(_context: Record<string, any>) {
202
+ this.wrapParentTag();
173
203
 
174
204
  this.enforceAllowedChildren();
175
205
  if (this.children.length > 0 && this.next != null && this.checkMerge()) {
@@ -1,5 +1,6 @@
1
1
  export * from './table-align';
2
2
  export * from './table-clipboard';
3
+ export * from './table-dom-selector';
3
4
  export * from './table-menu';
4
5
  export * from './table-resize';
5
6
  export * from './table-scrollbar';
@@ -3,31 +3,38 @@ import type { TableUp } from '../table-up';
3
3
  import { autoUpdate, computePosition, flip, limitShift, offset, shift } from '@floating-ui/dom';
4
4
  import Quill from 'quill';
5
5
  import { createBEM, createResizeObserver } from '../utils';
6
+ import { TableDomSelector } from './table-dom-selector';
6
7
 
7
- export class TableAlign {
8
- tableBlot: TableMainFormat;
9
- tableWrapperBlot: TableWrapperFormat;
10
- alignBox?: HTMLElement;
8
+ export class TableAlign extends TableDomSelector {
9
+ static moduleName: string = 'table-align';
10
+
11
+ tableBlot?: TableMainFormat;
12
+ tableWrapperBlot?: TableWrapperFormat;
13
+ alignBox: HTMLElement | null;
11
14
  cleanup?: () => void;
12
15
  bem = createBEM('align');
13
- resizeObserver = createResizeObserver(() => this.update(), { ignoreFirstBind: true });
16
+ resizeObserver?: ResizeObserver;
14
17
 
15
- constructor(public tableModule: TableUp, public table: HTMLElement, public quill: Quill) {
16
- this.tableBlot = Quill.find(table)! as TableMainFormat;
17
- this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
18
+ constructor(public tableModule: TableUp, public quill: Quill, _options: any) {
19
+ super(tableModule, quill);
18
20
 
19
- this.alignBox = this.buildTool();
20
- this.resizeObserver.observe(this.table);
21
- this.quill.on(Quill.events.TEXT_CHANGE, this.updateWhenTextChange);
22
-
23
- this.show();
21
+ this.alignBox = this.buildTools();
22
+ this.hide();
23
+ this.quill.on(Quill.events.EDITOR_CHANGE, this.updateWhenTextChange);
24
24
  }
25
25
 
26
- updateWhenTextChange = () => {
27
- this.update();
26
+ updateWhenTextChange = (eventName: string) => {
27
+ if (eventName === Quill.events.TEXT_CHANGE) {
28
+ if (this.table && !this.quill.root.contains(this.table)) {
29
+ this.setSelectionTable(undefined);
30
+ }
31
+ else {
32
+ this.update();
33
+ }
34
+ }
28
35
  };
29
36
 
30
- buildTool() {
37
+ buildTools() {
31
38
  const alignBox = this.tableModule.addContainer(this.bem.b());
32
39
  const icons = Quill.import('ui/icons') as Record<string, any>;
33
40
  const alignIcons = {
@@ -40,39 +47,21 @@ export class TableAlign {
40
47
  item.dataset.align = align;
41
48
  item.classList.add(this.bem.be('item'));
42
49
  item.innerHTML = `<i class="icon">${iconStr}</i>`;
43
- item.addEventListener('click', () => {
44
- const value = item.dataset.align;
45
- if (value) {
46
- this.setTableAlign(this.tableBlot, value);
47
-
48
- this.quill.once(Quill.events.SCROLL_OPTIMIZE, () => {
49
- if (this.tableModule.tableSelection) {
50
- this.tableModule.tableSelection.hide();
51
- }
52
- if (this.tableModule.tableResize) {
53
- this.tableModule.tableResize.update();
54
- }
55
- if (this.tableModule.tableResizeScale) {
56
- this.tableModule.tableResizeScale.update();
57
- }
58
- if (this.tableModule.tableScrollbar) {
59
- this.tableModule.tableScrollbar.update();
60
- }
61
- });
62
- }
63
- });
50
+ item.addEventListener('click', this.handleAlignItemClick.bind(this));
64
51
  alignBox.appendChild(item);
65
52
  }
66
- if (!this.cleanup) {
67
- this.cleanup = autoUpdate(
68
- this.tableWrapperBlot.domNode,
69
- alignBox,
70
- () => this.update(),
71
- );
72
- }
73
53
  return alignBox;
74
54
  }
75
55
 
56
+ handleAlignItemClick(e: MouseEvent) {
57
+ const item = e.currentTarget;
58
+ if (!item) return;
59
+ const value = (item as HTMLElement).dataset.align;
60
+ if (value && this.tableBlot) {
61
+ this.setTableAlign(this.tableBlot, value);
62
+ }
63
+ }
64
+
76
65
  setTableAlign(tableBlot: TableMainFormat, align: string) {
77
66
  const cols = tableBlot.getCols();
78
67
  for (const col of cols) {
@@ -81,14 +70,28 @@ export class TableAlign {
81
70
  }
82
71
 
83
72
  show() {
84
- if (!this.alignBox) return;
85
- this.alignBox.classList.add(this.bem.bm('active'));
86
- this.update();
73
+ if (!this.table || !this.alignBox) return;
74
+ this.tableBlot = Quill.find(this.table) as TableMainFormat;
75
+ this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
76
+ this.alignBox.classList.remove(this.bem.is('hidden'));
77
+ this.resizeObserver = createResizeObserver(() => this.update(), { ignoreFirstBind: true });
78
+ this.resizeObserver.observe(this.table);
79
+ if (this.cleanup) {
80
+ this.cleanup();
81
+ }
82
+ this.cleanup = autoUpdate(
83
+ this.tableWrapperBlot.domNode,
84
+ this.alignBox,
85
+ () => this.update(),
86
+ );
87
87
  }
88
88
 
89
89
  hide() {
90
- if (!this.alignBox) return;
91
- this.alignBox.classList.remove(this.bem.bm('active'));
90
+ this.tableBlot = undefined;
91
+ this.tableWrapperBlot = undefined;
92
+ if (this.alignBox) {
93
+ this.alignBox.classList.add(this.bem.is('hidden'));
94
+ }
92
95
  if (this.cleanup) {
93
96
  this.cleanup();
94
97
  this.cleanup = undefined;
@@ -96,8 +99,8 @@ export class TableAlign {
96
99
  }
97
100
 
98
101
  update() {
99
- if (!this.alignBox) return;
100
- if (this.tableBlot.full || this.tableBlot.domNode.offsetWidth >= this.quill.root.offsetWidth) {
102
+ if (!this.alignBox || !this.tableBlot || !this.tableWrapperBlot) return;
103
+ if (!this.table || this.tableBlot.full || this.tableBlot.domNode.offsetWidth >= this.quill.root.offsetWidth) {
101
104
  this.hide();
102
105
  return;
103
106
  }
@@ -115,11 +118,14 @@ export class TableAlign {
115
118
 
116
119
  destroy() {
117
120
  this.hide();
118
- this.resizeObserver.disconnect();
121
+ if (this.resizeObserver) {
122
+ this.resizeObserver.disconnect();
123
+ this.resizeObserver = undefined;
124
+ }
119
125
  this.quill.off(Quill.events.TEXT_CHANGE, this.updateWhenTextChange);
120
126
  if (this.alignBox) {
121
127
  this.alignBox.remove();
122
- this.alignBox = undefined;
128
+ this.alignBox = null;
123
129
  }
124
130
  }
125
131
  }
@@ -53,7 +53,9 @@ export class TableClipboard extends Clipboard {
53
53
  constructor(public quill: Quill, options: Partial<ClipboardOptions>) {
54
54
  super(quill, options);
55
55
  this.addMatcher('table', this.matchTable.bind(this));
56
+ this.addMatcher('thead', this.matchThead.bind(this));
56
57
  this.addMatcher('tbody', this.matchTbody.bind(this));
58
+ this.addMatcher('tfoot', this.matchTfoot.bind(this));
57
59
  this.addMatcher('colgroup', this.matchColgroup.bind(this));
58
60
  this.addMatcher('col', this.matchCol.bind(this));
59
61
  this.addMatcher('tr', this.matchTr.bind(this));
@@ -64,6 +66,22 @@ export class TableClipboard extends Clipboard {
64
66
  this.addMatcher(Node.ELEMENT_NODE, this.matchTdAttributor.bind(this));
65
67
  }
66
68
 
69
+ getStyleBackgroundColor(node: Node, delta: TypeDelta) {
70
+ const backgroundColor = (node as HTMLElement).style.backgroundColor;
71
+ if (backgroundColor) {
72
+ for (const op of delta.ops) {
73
+ if (op.attributes?.[blotName.tableCellInner]) {
74
+ const { style, ...value } = op.attributes[blotName.tableCellInner] as TableCellValue;
75
+ const styleObj = cssTextToObject(style || '');
76
+ if (!styleObj.backgroundColor) {
77
+ styleObj.backgroundColor = backgroundColor;
78
+ op.attributes[blotName.tableCellInner] = { ...value, style: objectToCssText(styleObj) };
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+
67
85
  matchTable(node: Node, delta: TypeDelta) {
68
86
  if (delta.ops.length === 0) return delta;
69
87
 
@@ -73,7 +91,7 @@ export class TableClipboard extends Clipboard {
73
91
  for (let i = 0; i < delta.ops.length; i++) {
74
92
  const { attributes, insert } = delta.ops[i];
75
93
  // if attribute doesn't have tableCellInner, treat it as a blank line(emptyRow)
76
- if (!isObject(insert) && attributes && !attributes[blotName.tableCellInner] && !attributes[blotName.tableCaption]) {
94
+ if (!isObject(insert) && (!attributes || (!attributes[blotName.tableCellInner] && !attributes[blotName.tableCaption]))) {
77
95
  delta.ops.splice(i, 1);
78
96
  i -= 1;
79
97
  continue;
@@ -120,54 +138,66 @@ export class TableClipboard extends Clipboard {
120
138
  }, [] as Record<string, any>[]);
121
139
  ops.splice(bodyStartIndex + 1, 0, ...newCols);
122
140
 
141
+ const resultDelta = new Delta(ops);
142
+ this.getStyleBackgroundColor(node, resultDelta);
123
143
  // reset variable to avoid conflict with other table
124
144
  this.tableId = randomId();
125
145
  this.colIds = [];
126
146
  this.rowspanCount = [];
127
147
  this.cellCount = 0;
128
148
  this.colCount = 0;
129
- return new Delta(ops);
149
+ return resultDelta;
130
150
  }
131
151
 
132
152
  matchTbody(node: Node, delta: TypeDelta) {
133
- const backgroundColor = (node as HTMLElement).style.backgroundColor;
134
- if (backgroundColor) {
135
- for (const op of delta.ops) {
136
- if (op.attributes?.[blotName.tableCellInner]) {
137
- const { style, ...value } = op.attributes[blotName.tableCellInner] as TableCellValue;
138
- const styleObj = cssTextToObject(style || '');
139
- if (!styleObj.backgroundColor) {
140
- styleObj.backgroundColor = backgroundColor;
141
- op.attributes[blotName.tableCellInner] = { ...value, style: objectToCssText(styleObj) };
142
- }
143
- }
144
- }
145
- }
153
+ this.getStyleBackgroundColor(node, delta);
146
154
  // add `emptyRow`
147
155
  let emptyRows = [];
148
156
  for (let i = delta.ops.length - 1; i >= 0; i--) {
149
157
  const op = delta.ops[i];
150
- if (op.attributes) {
151
- if (!op.attributes[blotName.tableCellInner]) {
158
+ if (!op.attributes?.[blotName.tableCellInner]) {
159
+ emptyRows = [];
160
+ emptyRows.push(randomId());
161
+ }
162
+ else if (op.attributes) {
163
+ if ((op.attributes[blotName.tableCellInner] as TableCellValue).rowspan === 1) {
152
164
  emptyRows = [];
153
- emptyRows.push(randomId());
154
165
  }
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);
166
+ else if (emptyRows.length > 0) {
167
+ if (!(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow) {
168
+ (op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow = [];
164
169
  }
170
+ (op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow!.push(...emptyRows);
165
171
  }
166
172
  }
167
173
  }
174
+ // clear rowspan. thead/tbody/tfoot will not share rowspan
175
+ this.rowspanCount = [];
168
176
  return delta;
169
177
  }
170
178
 
179
+ matchThead(node: Node, delta: TypeDelta) {
180
+ const deltaData = this.matchTbody(node, delta);
181
+ for (const op of deltaData.ops) {
182
+ if (op.attributes && op.attributes[blotName.tableCellInner]) {
183
+ const tableCellInner = op.attributes[blotName.tableCellInner] as TableCellValue;
184
+ tableCellInner.wrapTag = 'thead';
185
+ }
186
+ }
187
+ return deltaData;
188
+ }
189
+
190
+ matchTfoot(node: Node, delta: TypeDelta) {
191
+ const deltaData = this.matchTbody(node, delta);
192
+ for (const op of deltaData.ops) {
193
+ if (op.attributes && op.attributes[blotName.tableCellInner]) {
194
+ const tableCellInner = op.attributes[blotName.tableCellInner] as TableCellValue;
195
+ tableCellInner.wrapTag = 'tfoot';
196
+ }
197
+ }
198
+ return deltaData;
199
+ }
200
+
171
201
  matchColgroup(node: Node, delta: TypeDelta) {
172
202
  const ops: Record<string, any>[] = [];
173
203
  for (let i = 0; i < delta.ops.length; i++) {
@@ -213,18 +243,9 @@ export class TableClipboard extends Clipboard {
213
243
  this.rowspanCount[i] = { rowspan: 0, colspan: 0 };
214
244
  }
215
245
  }
216
- for (const op of delta.ops) {
217
- if (op.attributes) {
218
- const { background, [blotName.tableCellInner]: tableCellInner } = op.attributes;
219
- if (tableCellInner && background) {
220
- const { style = '' } = tableCellInner as TableCellValue;
221
- const styleObj = cssTextToObject(style);
222
- styleObj.backgroundColor = background as string;
223
- (op.attributes![blotName.tableCellInner] as TableCellValue).style = objectToCssText(styleObj);
224
- }
225
- }
226
- }
227
- return delta;
246
+ this.getStyleBackgroundColor(node, delta);
247
+ // if delta.ops is empty, return a new line. make sure emptyRow parse correctly in `matchTbody`
248
+ return delta.ops.length === 0 ? new Delta([{ insert: '\n' }]) : delta;
228
249
  }
229
250
 
230
251
  matchTd(node: Node, delta: TypeDelta) {
@@ -0,0 +1,33 @@
1
+ import type Quill from 'quill';
2
+ import type { TableUp } from '../table-up';
3
+
4
+ export class TableDomSelector {
5
+ table?: HTMLTableElement;
6
+
7
+ constructor(public tableModule: TableUp, public quill: Quill) {
8
+ this.quill.root.addEventListener('mousedown', this.tableSelectHandler.bind(this));
9
+ }
10
+
11
+ tableSelectHandler(event: MouseEvent) {
12
+ const path = event.composedPath() as HTMLElement[];
13
+ if (event.button !== 0 || !path || path.length <= 0) return;
14
+ const tableNode = path.find(node => node.tagName && node.tagName.toUpperCase() === 'TABLE');
15
+ this.setSelectionTable(tableNode as HTMLTableElement);
16
+ }
17
+
18
+ setSelectionTable(table: HTMLTableElement | undefined) {
19
+ if (this.table === table) return;
20
+ this.hide();
21
+ this.table = table;
22
+ if (this.table) {
23
+ this.show();
24
+ }
25
+ this.update();
26
+ }
27
+
28
+ hide() {}
29
+
30
+ show() {}
31
+
32
+ update() {}
33
+ }