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,304 +1,304 @@
1
- import type { Parchment as TypeParchment } from 'quill';
2
- import type { TableBodyTag, TableCellValue } from '../utils';
3
- import { blotName, ensureArray, findParentBlot, getInlineStyles, toCamelCase } from '../utils';
4
- import { ContainerFormat } from './container-format';
5
- import { TableBodyFormat } from './table-body-format';
6
- import { TableCellInnerFormat } from './table-cell-inner-format';
7
- import { TableRowFormat } from './table-row-format';
8
- import { getValidCellspan } from './utils';
9
-
10
- export class TableCellFormat extends ContainerFormat {
11
- static blotName = blotName.tableCell;
12
- static tagName = 'td';
13
- static className = 'ql-table-cell';
14
- static allowAttrs = new Set(['rowspan', 'colspan']);
15
- static allowDataAttrs = new Set(['table-id', 'row-id', 'col-id', 'empty-row', 'wrap-tag']);
16
-
17
- // keep `isAllowStyle` and `allowStyle` same with TableCellInnerFormat
18
- static allowStyle = new Set(['background-color', 'border', 'height']);
19
- static isAllowStyle(str: string): boolean {
20
- const cssAttrName = toCamelCase(str);
21
- for (const style of this.allowStyle) {
22
- // cause `cssTextToObject` will transform css string to camel case style name
23
- if (cssAttrName.startsWith(toCamelCase(style))) {
24
- return true;
25
- }
26
- }
27
- return false;
28
- }
29
-
30
- static create(value: TableCellValue) {
31
- const {
32
- tableId,
33
- rowId,
34
- colId,
35
- rowspan,
36
- colspan,
37
- style,
38
- emptyRow,
39
- tag = 'td',
40
- wrapTag = 'tbody',
41
- } = value;
42
- const node = document.createElement(tag);
43
- node.classList.add(...ensureArray(this.className));
44
- node.dataset.tableId = tableId;
45
- node.dataset.rowId = rowId;
46
- node.dataset.colId = colId;
47
- node.dataset.wrapTag = wrapTag;
48
- node.setAttribute('rowspan', String(getValidCellspan(rowspan)));
49
- node.setAttribute('colspan', String(getValidCellspan(colspan)));
50
- style && (node.style.cssText = style);
51
- try {
52
- emptyRow && (node.dataset.emptyRow = JSON.stringify(emptyRow));
53
- }
54
- catch {}
55
- return node;
56
- }
57
-
58
- static formats(domNode: HTMLElement) {
59
- const { tableId, rowId, colId, emptyRow, wrapTag = 'tbody' } = domNode.dataset;
60
- const rowspan = Number(domNode.getAttribute('rowspan'));
61
- const colspan = Number(domNode.getAttribute('colspan'));
62
- const value: Record<string, any> = {
63
- tableId,
64
- rowId,
65
- colId,
66
- rowspan: getValidCellspan(rowspan),
67
- colspan: getValidCellspan(colspan),
68
- tag: domNode.tagName.toLowerCase(),
69
- wrapTag,
70
- };
71
-
72
- const inlineStyles = getInlineStyles(domNode);
73
- const entries = Object.entries(inlineStyles).filter(([, value]) => {
74
- return !['initial', 'inherit'].includes(value);
75
- });
76
- if (entries.length > 0) {
77
- value.style = entries.map(([key, value]) => `${key}: ${value}`).join(';');
78
- }
79
-
80
- try {
81
- emptyRow && (value.emptyRow = JSON.parse(emptyRow));
82
- }
83
- catch {}
84
-
85
- return value;
86
- }
87
-
88
- isChildHeadTableCellInner() {
89
- const headChild = this.children.head;
90
- return headChild && headChild.statics.blotName === blotName.tableCellInner;
91
- }
92
-
93
- setFormatValue(name: string, value?: any) {
94
- if (this.statics.allowAttrs.has(name) || this.statics.allowDataAttrs.has(name)) {
95
- let attrName = name;
96
- if (this.statics.allowDataAttrs.has(name)) {
97
- attrName = `data-${name}`;
98
- }
99
- if (value) {
100
- this.domNode.setAttribute(attrName, value);
101
- }
102
- else {
103
- this.domNode.removeAttribute(attrName);
104
- }
105
- }
106
- else if (this.statics.isAllowStyle(name)) {
107
- Object.assign(this.domNode.style, {
108
- [name]: value,
109
- });
110
- if (name.startsWith('border')) {
111
- this.setStyleBoder(name, value);
112
- }
113
- }
114
-
115
- const headChild = this.children.head!;
116
- if (
117
- this.isChildHeadTableCellInner()
118
- && this.domNode.style.cssText
119
- // only update if data not match. avoid optimize circular updates
120
- && this.domNode.style.cssText !== (headChild.domNode as HTMLElement).dataset.style
121
- ) {
122
- (headChild.domNode as HTMLElement).dataset.style = this.domNode.style.cssText;
123
- }
124
-
125
- if (this.parent && this.parent.statics.blotName === blotName.tableRow) {
126
- (this.parent as TableRowFormat).setFormatValue(name, value);
127
- }
128
- }
129
-
130
- setStyleBoder(name: string, value?: any) {
131
- // eslint-disable-next-line no-extra-boolean-cast
132
- const setValue = Boolean(value) ? value : null;
133
- const isMergeBorder = !['left', 'right', 'top', 'bottom'].some(direction => name.includes(direction)) && name.startsWith('border-');
134
- if (!isMergeBorder) return;
135
-
136
- const leftCellInners = this.getNearByCell('left').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
137
- for (const cell of leftCellInners) {
138
- cell.setFormatValue(name.replace('border-', 'border-right-'), setValue, true);
139
- }
140
- const topCellInners = this.getNearByCell('top').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
141
- for (const cell of topCellInners) {
142
- cell.setFormatValue(name.replace('border-', 'border-bottom-'), setValue, true);
143
- }
144
- }
145
-
146
- getNearByCell(direction: 'left' | 'top'): TableCellFormat[] {
147
- const colIds: string[] = [];
148
- try {
149
- const tableMain = findParentBlot(this, blotName.tableMain);
150
- colIds.push(...tableMain.getColIds());
151
- }
152
- catch (error) {
153
- console.error(`Cell is not in table! ${error}`);
154
- }
155
- if (colIds.length === 0) return [];
156
-
157
- if (direction === 'left') {
158
- const nearByCell = new Set<TableCellFormat>();
159
- let row = this.parent;
160
- for (let i = 0; i < this.rowspan; i++) {
161
- if (!(row instanceof TableRowFormat)) break;
162
- const next = row.children.iterator();
163
- let cur: null | TableCellFormat = null;
164
- while ((cur = next())) {
165
- const i = colIds.indexOf(cur.colId) + cur.colspan;
166
- if (this.colId === colIds[i]) {
167
- nearByCell.add(cur);
168
- }
169
- }
170
- row = row.next as TableRowFormat;
171
- }
172
- return Array.from(nearByCell);
173
- }
174
- else if (direction === 'top') {
175
- if (!(this.parent instanceof TableRowFormat) || !this.parent.prev) return [];
176
- const nearByCell = new Set<TableCellFormat>();
177
-
178
- const startColIndex = this.getColumnIndex();
179
- const endColIndex = startColIndex + this.colspan;
180
- const borderColIds = new Set(colIds.filter((_, i) => i >= startColIndex && i < endColIndex));
181
-
182
- let rowspan = 1;
183
- let row = this.parent.prev as TableRowFormat;
184
- while (row) {
185
- let trReachCurrent = false;
186
- const next = row.children.iterator();
187
- let cur: null | TableCellFormat = null;
188
- let colspan = 0;
189
- while ((cur = next())) {
190
- if (borderColIds.has(cur.colId) && cur.rowspan >= rowspan) {
191
- nearByCell.add(cur);
192
- borderColIds.delete(cur.colId);
193
- }
194
- colspan += cur.colspan;
195
- cur.rowspan >= rowspan && (trReachCurrent = true);
196
- }
197
- if (!trReachCurrent && colspan === colIds.length) break;
198
- row = row.prev as TableRowFormat;
199
- rowspan += 1;
200
- }
201
- return Array.from(nearByCell);
202
- }
203
- return [];
204
- }
205
-
206
- get tableId() {
207
- return this.domNode.dataset.tableId!;
208
- }
209
-
210
- get rowId() {
211
- return this.domNode.dataset.rowId!;
212
- }
213
-
214
- get colId() {
215
- return this.domNode.dataset.colId!;
216
- }
217
-
218
- get rowspan() {
219
- return Number(this.domNode.getAttribute('rowspan'));
220
- }
221
-
222
- get colspan() {
223
- return Number(this.domNode.getAttribute('colspan'));
224
- }
225
-
226
- get emptyRow(): string[] {
227
- try {
228
- return JSON.parse(this.domNode.dataset.emptyRow!);
229
- }
230
- catch {
231
- return [];
232
- }
233
- }
234
-
235
- get wrapTag() {
236
- return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
237
- }
238
-
239
- getColumnIndex() {
240
- const table = findParentBlot(this, blotName.tableMain);
241
- return table.getColIds().indexOf(this.colId);
242
- }
243
-
244
- getCellInner() {
245
- return this.children.head as TableCellInnerFormat;
246
- }
247
-
248
- convertTableCell() {
249
- const value = this.statics.formats(this.domNode);
250
- const tag = value.tag === 'td' ? 'th' : 'td';
251
-
252
- const headChild = this.children.head!;
253
- if (
254
- this.isChildHeadTableCellInner()
255
- // only update if data not match. avoid optimize circular updates
256
- && (headChild.domNode as HTMLElement).dataset.tag !== tag
257
- ) {
258
- (headChild.domNode as HTMLElement).dataset.tag = tag;
259
- }
260
-
261
- this.replaceWith(blotName.tableCell, {
262
- ...value,
263
- tag,
264
- });
265
- }
266
-
267
- checkMerge(): boolean {
268
- const { colId, rowId, colspan, rowspan } = this;
269
- const next = this.next as TableCellFormat;
270
- return (
271
- next !== null
272
- && next.statics.blotName === this.statics.blotName
273
- && next.rowId === rowId
274
- && next.colId === colId
275
- && next.colspan === colspan
276
- && next.rowspan === rowspan
277
- );
278
- }
279
-
280
- optimize(context: Record<string, any>) {
281
- const { tableId, rowId, wrapTag } = this;
282
- if (this.parent !== null && this.parent.statics.blotName !== blotName.tableRow) {
283
- this.wrap(blotName.tableRow, { tableId, rowId, wrapTag });
284
- }
285
- // when `replaceWith` called to replace cell. wrapTag may change. so row wrapTag also need to update
286
- if (this.parent.statics.blotName === blotName.tableRow && (this.parent as TableRowFormat).wrapTag !== wrapTag) {
287
- (this.parent as TableRowFormat).setFormatValue('wrap-tag', wrapTag);
288
- }
289
-
290
- if (this.emptyRow.length > 0) {
291
- const tableBody = this.parent.parent;
292
- if (tableBody instanceof TableBodyFormat) {
293
- let insertBefore: TypeParchment.Blot | null = this.parent.next;
294
- for (const rowId of this.emptyRow) {
295
- const row = this.scroll.create(blotName.tableRow, { tableId, rowId, wrapTag }) as TableRowFormat;
296
- tableBody.insertBefore(row, insertBefore);
297
- insertBefore = row.next;
298
- }
299
- }
300
- }
301
-
302
- super.optimize(context);
303
- }
304
- }
1
+ import type { Parchment as TypeParchment } from 'quill';
2
+ import type { TableBodyTag, TableCellValue } from '../utils';
3
+ import { blotName, ensureArray, findParentBlot, getInlineStyles, toCamelCase } from '../utils';
4
+ import { ContainerFormat } from './container-format';
5
+ import { TableBodyFormat } from './table-body-format';
6
+ import { TableCellInnerFormat } from './table-cell-inner-format';
7
+ import { TableRowFormat } from './table-row-format';
8
+ import { getValidCellspan } from './utils';
9
+
10
+ export class TableCellFormat extends ContainerFormat {
11
+ static blotName = blotName.tableCell;
12
+ static tagName = 'td';
13
+ static className = 'ql-table-cell';
14
+ static allowAttrs = new Set(['rowspan', 'colspan']);
15
+ static allowDataAttrs = new Set(['table-id', 'row-id', 'col-id', 'empty-row', 'wrap-tag']);
16
+
17
+ // keep `isAllowStyle` and `allowStyle` same with TableCellInnerFormat
18
+ static allowStyle = new Set(['background-color', 'border', 'height']);
19
+ static isAllowStyle(str: string): boolean {
20
+ const cssAttrName = toCamelCase(str);
21
+ for (const style of this.allowStyle) {
22
+ // cause `cssTextToObject` will transform css string to camel case style name
23
+ if (cssAttrName.startsWith(toCamelCase(style))) {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
29
+
30
+ static create(value: TableCellValue) {
31
+ const {
32
+ tableId,
33
+ rowId,
34
+ colId,
35
+ rowspan,
36
+ colspan,
37
+ style,
38
+ emptyRow,
39
+ tag = 'td',
40
+ wrapTag = 'tbody',
41
+ } = value;
42
+ const node = document.createElement(tag);
43
+ node.classList.add(...ensureArray(this.className));
44
+ node.dataset.tableId = tableId;
45
+ node.dataset.rowId = rowId;
46
+ node.dataset.colId = colId;
47
+ node.dataset.wrapTag = wrapTag;
48
+ node.setAttribute('rowspan', String(getValidCellspan(rowspan)));
49
+ node.setAttribute('colspan', String(getValidCellspan(colspan)));
50
+ style && (node.style.cssText = style);
51
+ try {
52
+ emptyRow && (node.dataset.emptyRow = JSON.stringify(emptyRow));
53
+ }
54
+ catch {}
55
+ return node;
56
+ }
57
+
58
+ static formats(domNode: HTMLElement) {
59
+ const { tableId, rowId, colId, emptyRow, wrapTag = 'tbody' } = domNode.dataset;
60
+ const rowspan = Number(domNode.getAttribute('rowspan'));
61
+ const colspan = Number(domNode.getAttribute('colspan'));
62
+ const value: Record<string, any> = {
63
+ tableId,
64
+ rowId,
65
+ colId,
66
+ rowspan: getValidCellspan(rowspan),
67
+ colspan: getValidCellspan(colspan),
68
+ tag: domNode.tagName.toLowerCase(),
69
+ wrapTag,
70
+ };
71
+
72
+ const inlineStyles = getInlineStyles(domNode);
73
+ const entries = Object.entries(inlineStyles).filter(([, value]) => {
74
+ return !['initial', 'inherit'].includes(value);
75
+ });
76
+ if (entries.length > 0) {
77
+ value.style = entries.map(([key, value]) => `${key}: ${value}`).join(';');
78
+ }
79
+
80
+ try {
81
+ emptyRow && (value.emptyRow = JSON.parse(emptyRow));
82
+ }
83
+ catch {}
84
+
85
+ return value;
86
+ }
87
+
88
+ isChildHeadTableCellInner() {
89
+ const headChild = this.children.head;
90
+ return headChild && headChild.statics.blotName === blotName.tableCellInner;
91
+ }
92
+
93
+ setFormatValue(name: string, value?: any) {
94
+ if (this.statics.allowAttrs.has(name) || this.statics.allowDataAttrs.has(name)) {
95
+ let attrName = name;
96
+ if (this.statics.allowDataAttrs.has(name)) {
97
+ attrName = `data-${name}`;
98
+ }
99
+ if (value) {
100
+ this.domNode.setAttribute(attrName, value);
101
+ }
102
+ else {
103
+ this.domNode.removeAttribute(attrName);
104
+ }
105
+ }
106
+ else if (this.statics.isAllowStyle(name)) {
107
+ Object.assign(this.domNode.style, {
108
+ [name]: value,
109
+ });
110
+ if (name.startsWith('border')) {
111
+ this.setStyleBoder(name, value);
112
+ }
113
+ }
114
+
115
+ const headChild = this.children.head!;
116
+ if (
117
+ this.isChildHeadTableCellInner()
118
+ && this.domNode.style.cssText
119
+ // only update if data not match. avoid optimize circular updates
120
+ && this.domNode.style.cssText !== (headChild.domNode as HTMLElement).dataset.style
121
+ ) {
122
+ (headChild.domNode as HTMLElement).dataset.style = this.domNode.style.cssText;
123
+ }
124
+
125
+ if (this.parent && this.parent.statics.blotName === blotName.tableRow) {
126
+ (this.parent as TableRowFormat).setFormatValue(name, value);
127
+ }
128
+ }
129
+
130
+ setStyleBoder(name: string, value?: any) {
131
+ // eslint-disable-next-line no-extra-boolean-cast
132
+ const setValue = Boolean(value) ? value : null;
133
+ const isMergeBorder = !['left', 'right', 'top', 'bottom'].some(direction => name.includes(direction)) && name.startsWith('border-');
134
+ if (!isMergeBorder) return;
135
+
136
+ const leftCellInners = this.getNearByCell('left').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
137
+ for (const cell of leftCellInners) {
138
+ cell.setFormatValue(name.replace('border-', 'border-right-'), setValue, true);
139
+ }
140
+ const topCellInners = this.getNearByCell('top').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
141
+ for (const cell of topCellInners) {
142
+ cell.setFormatValue(name.replace('border-', 'border-bottom-'), setValue, true);
143
+ }
144
+ }
145
+
146
+ getNearByCell(direction: 'left' | 'top'): TableCellFormat[] {
147
+ const colIds: string[] = [];
148
+ try {
149
+ const tableMain = findParentBlot(this, blotName.tableMain);
150
+ colIds.push(...tableMain.getColIds());
151
+ }
152
+ catch (error) {
153
+ console.error(`Cell is not in table! ${error}`);
154
+ }
155
+ if (colIds.length === 0) return [];
156
+
157
+ if (direction === 'left') {
158
+ const nearByCell = new Set<TableCellFormat>();
159
+ let row = this.parent;
160
+ for (let i = 0; i < this.rowspan; i++) {
161
+ if (!(row instanceof TableRowFormat)) break;
162
+ const next = row.children.iterator();
163
+ let cur: null | TableCellFormat = null;
164
+ while ((cur = next())) {
165
+ const i = colIds.indexOf(cur.colId) + cur.colspan;
166
+ if (this.colId === colIds[i]) {
167
+ nearByCell.add(cur);
168
+ }
169
+ }
170
+ row = row.next as TableRowFormat;
171
+ }
172
+ return Array.from(nearByCell);
173
+ }
174
+ else if (direction === 'top') {
175
+ if (!(this.parent instanceof TableRowFormat) || !this.parent.prev) return [];
176
+ const nearByCell = new Set<TableCellFormat>();
177
+
178
+ const startColIndex = this.getColumnIndex();
179
+ const endColIndex = startColIndex + this.colspan;
180
+ const borderColIds = new Set(colIds.filter((_, i) => i >= startColIndex && i < endColIndex));
181
+
182
+ let rowspan = 1;
183
+ let row = this.parent.prev as TableRowFormat;
184
+ while (row) {
185
+ let trReachCurrent = false;
186
+ const next = row.children.iterator();
187
+ let cur: null | TableCellFormat = null;
188
+ let colspan = 0;
189
+ while ((cur = next())) {
190
+ if (borderColIds.has(cur.colId) && cur.rowspan >= rowspan) {
191
+ nearByCell.add(cur);
192
+ borderColIds.delete(cur.colId);
193
+ }
194
+ colspan += cur.colspan;
195
+ cur.rowspan >= rowspan && (trReachCurrent = true);
196
+ }
197
+ if (!trReachCurrent && colspan === colIds.length) break;
198
+ row = row.prev as TableRowFormat;
199
+ rowspan += 1;
200
+ }
201
+ return Array.from(nearByCell);
202
+ }
203
+ return [];
204
+ }
205
+
206
+ get tableId() {
207
+ return this.domNode.dataset.tableId!;
208
+ }
209
+
210
+ get rowId() {
211
+ return this.domNode.dataset.rowId!;
212
+ }
213
+
214
+ get colId() {
215
+ return this.domNode.dataset.colId!;
216
+ }
217
+
218
+ get rowspan() {
219
+ return Number(this.domNode.getAttribute('rowspan'));
220
+ }
221
+
222
+ get colspan() {
223
+ return Number(this.domNode.getAttribute('colspan'));
224
+ }
225
+
226
+ get emptyRow(): string[] {
227
+ try {
228
+ return JSON.parse(this.domNode.dataset.emptyRow!);
229
+ }
230
+ catch {
231
+ return [];
232
+ }
233
+ }
234
+
235
+ get wrapTag() {
236
+ return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
237
+ }
238
+
239
+ getColumnIndex() {
240
+ const table = findParentBlot(this, blotName.tableMain);
241
+ return table.getColIds().indexOf(this.colId);
242
+ }
243
+
244
+ getCellInner() {
245
+ return this.children.head as TableCellInnerFormat;
246
+ }
247
+
248
+ convertTableCell() {
249
+ const value = this.statics.formats(this.domNode);
250
+ const tag = value.tag === 'td' ? 'th' : 'td';
251
+
252
+ const headChild = this.children.head!;
253
+ if (
254
+ this.isChildHeadTableCellInner()
255
+ // only update if data not match. avoid optimize circular updates
256
+ && (headChild.domNode as HTMLElement).dataset.tag !== tag
257
+ ) {
258
+ (headChild.domNode as HTMLElement).dataset.tag = tag;
259
+ }
260
+
261
+ this.replaceWith(blotName.tableCell, {
262
+ ...value,
263
+ tag,
264
+ });
265
+ }
266
+
267
+ checkMerge(): boolean {
268
+ const { colId, rowId, colspan, rowspan } = this;
269
+ const next = this.next as TableCellFormat;
270
+ return (
271
+ next !== null
272
+ && next.statics.blotName === this.statics.blotName
273
+ && next.rowId === rowId
274
+ && next.colId === colId
275
+ && next.colspan === colspan
276
+ && next.rowspan === rowspan
277
+ );
278
+ }
279
+
280
+ optimize(context: Record<string, any>) {
281
+ const { tableId, rowId, wrapTag } = this;
282
+ if (this.parent !== null && this.parent.statics.blotName !== blotName.tableRow) {
283
+ this.wrap(blotName.tableRow, { tableId, rowId, wrapTag });
284
+ }
285
+ // when `replaceWith` called to replace cell. wrapTag may change. so row wrapTag also need to update
286
+ if (this.parent.statics.blotName === blotName.tableRow && (this.parent as TableRowFormat).wrapTag !== wrapTag) {
287
+ (this.parent as TableRowFormat).setFormatValue('wrap-tag', wrapTag);
288
+ }
289
+
290
+ if (this.emptyRow.length > 0) {
291
+ const tableBody = this.parent.parent;
292
+ if (tableBody instanceof TableBodyFormat) {
293
+ let insertBefore: TypeParchment.Blot | null = this.parent.next;
294
+ for (const rowId of this.emptyRow) {
295
+ const row = this.scroll.create(blotName.tableRow, { tableId, rowId, wrapTag }) as TableRowFormat;
296
+ tableBody.insertBefore(row, insertBefore);
297
+ insertBefore = row.next;
298
+ }
299
+ }
300
+ }
301
+
302
+ super.optimize(context);
303
+ }
304
+ }