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,398 +1,403 @@
1
- import type { Parchment as TypeParchment } from 'quill';
2
- import type TypeBlock from 'quill/blots/block';
3
- import type { BlockEmbed as TypeBlockEmbed } from 'quill/blots/block';
4
- import type TypeScroll from 'quill/blots/scroll';
5
- import type { TableBodyTag, TableCellValue } from '../utils';
6
- import type { TableCellFormat } from './table-cell-format';
7
- import Quill from 'quill';
8
- import { blotName, cssTextToObject, findParentBlot, findParentBlots, toCamelCase } from '../utils';
9
- import { ContainerFormat } from './container-format';
10
- import { TableBodyFormat } from './table-body-format';
11
- import { getValidCellspan, isSameCellValue } from './utils';
12
-
13
- const Block = Quill.import('blots/block') as TypeParchment.BlotConstructor;
14
- const BlockEmbed = Quill.import('blots/block/embed') as typeof TypeBlockEmbed;
15
-
16
- export class TableCellInnerFormat extends ContainerFormat {
17
- static blotName = blotName.tableCellInner;
18
- static tagName = 'div';
19
- static className = 'ql-table-cell-inner';
20
- static allowDataAttrs: Set<string> = new Set(['table-id', 'row-id', 'col-id', 'rowspan', 'colspan', 'empty-row', 'wrap-tag']);
21
- static defaultChild: TypeParchment.BlotConstructor = Block;
22
- declare parent: TableCellFormat;
23
- // keep `isAllowStyle` and `allowStyle` same with TableCellFormat
24
- static allowStyle = new Set(['background-color', 'border', 'height']);
25
- static isAllowStyle(str: string): boolean {
26
- const cssAttrName = toCamelCase(str);
27
- for (const style of this.allowStyle) {
28
- // cause `cssTextToObject` will transform css string to camel case style name
29
- if (cssAttrName.startsWith(toCamelCase(style))) {
30
- return true;
31
- }
32
- }
33
- return false;
34
- }
35
-
36
- static create(value: TableCellValue) {
37
- const {
38
- tableId,
39
- rowId,
40
- colId,
41
- rowspan,
42
- colspan,
43
- style,
44
- emptyRow,
45
- tag = 'td',
46
- wrapTag = 'tbody',
47
- } = value;
48
- const node = super.create() as HTMLElement;
49
- node.dataset.tableId = tableId;
50
- node.dataset.rowId = rowId;
51
- node.dataset.colId = colId;
52
- node.dataset.rowspan = String(getValidCellspan(rowspan));
53
- node.dataset.colspan = String(getValidCellspan(colspan));
54
- node.dataset.tag = tag;
55
- node.dataset.wrapTag = wrapTag;
56
- style && (node.dataset.style = style);
57
- try {
58
- emptyRow && (node.dataset.emptyRow = JSON.stringify(emptyRow));
59
- }
60
- catch {}
61
- return node;
62
- }
63
-
64
- static formats(domNode: HTMLElement) {
65
- const {
66
- tableId,
67
- rowId,
68
- colId,
69
- rowspan,
70
- colspan,
71
- style,
72
- emptyRow,
73
- tag = 'td',
74
- wrapTag = 'tbody',
75
- } = domNode.dataset;
76
- const value: Record<string, any> = {
77
- tableId: String(tableId),
78
- rowId: String(rowId),
79
- colId: String(colId),
80
- rowspan: Number(getValidCellspan(rowspan)),
81
- colspan: Number(getValidCellspan(colspan)),
82
- tag,
83
- wrapTag,
84
- };
85
-
86
- style && (value.style = style);
87
-
88
- try {
89
- emptyRow && (value.emptyRow = JSON.parse(emptyRow));
90
- }
91
- catch {}
92
-
93
- return value;
94
- }
95
-
96
- constructor(scroll: TypeScroll, domNode: HTMLElement, _value: TableCellValue) {
97
- super(scroll, domNode);
98
- domNode.setAttribute('contenteditable', String(scroll.isEnabled()));
99
- }
100
-
101
- setFormatValue(name: string, value?: any, isStyle: boolean = false) {
102
- if (isStyle) {
103
- if (!this.statics.isAllowStyle(name)) return;
104
- }
105
- else {
106
- super.setFormatValue(name, value);
107
- }
108
- if (this.parent && this.parent.statics.blotName === blotName.tableCell) {
109
- this.parent.setFormatValue(name, value);
110
- }
111
-
112
- this.clearCache();
113
- }
114
-
115
- clearCache() {
116
- const blocks = this.descendants(Block, 0);
117
- for (const child of blocks) {
118
- (child as TypeBlock).cache = {};
119
- }
120
- }
121
-
122
- get tableId() {
123
- return this.domNode.dataset.tableId!;
124
- }
125
-
126
- get rowId() {
127
- return this.domNode.dataset.rowId!;
128
- }
129
-
130
- set rowId(value) {
131
- this.setFormatValue('row-id', value);
132
- }
133
-
134
- get colId() {
135
- return this.domNode.dataset.colId!;
136
- }
137
-
138
- set colId(value) {
139
- this.setFormatValue('col-id', value);
140
- }
141
-
142
- get rowspan() {
143
- return Number(this.domNode.dataset.rowspan);
144
- }
145
-
146
- set rowspan(value: number) {
147
- this.setFormatValue('rowspan', value);
148
- }
149
-
150
- get colspan() {
151
- return Number(this.domNode.dataset.colspan);
152
- }
153
-
154
- set colspan(value: number) {
155
- this.setFormatValue('colspan', value);
156
- }
157
-
158
- get emptyRow(): string[] {
159
- try {
160
- return JSON.parse(this.domNode.dataset.emptyRow!);
161
- }
162
- catch {
163
- return [];
164
- }
165
- }
166
-
167
- set emptyRow(value: string[]) {
168
- // if value same as currentEmptyRow, do nothing
169
- if (this.emptyRow.toString() === value.toString()) return;
170
-
171
- try {
172
- if (value.length > 0) {
173
- this.setFormatValue('empty-row', JSON.stringify(value), false);
174
- }
175
- else {
176
- this.setFormatValue('empty-row', null, false);
177
- }
178
- }
179
- catch {
180
- this.setFormatValue('empty-row', null, false);
181
- }
182
- }
183
-
184
- set wrapTag(value: TableBodyTag) {
185
- this.setFormatValue('wrap-tag', value);
186
- }
187
-
188
- get wrapTag() {
189
- return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
190
- }
191
-
192
- getColumnIndex() {
193
- const table = findParentBlot(this, blotName.tableMain);
194
- return table.getColIds().indexOf(this.colId);
195
- }
196
-
197
- getTableBody() {
198
- let target: TypeParchment.Parent = this.parent;
199
- while (target && !(target instanceof TableBodyFormat) && target !== this.scroll) {
200
- target = target.parent;
201
- }
202
- if (target === this.scroll) {
203
- return null;
204
- }
205
- return target as TableBodyFormat;
206
- }
207
-
208
- getTableRow() {
209
- try {
210
- return findParentBlot(this, blotName.tableRow);
211
- }
212
- catch {
213
- return null;
214
- }
215
- }
216
-
217
- setStyleByString(styleStr: string) {
218
- const style = cssTextToObject(styleStr);
219
- for (const [name, value] of Object.entries(style)) {
220
- this.setFormatValue(name, value, true);
221
- }
222
- }
223
-
224
- convertTableCell() {
225
- if (this.parent.statics.blotName !== blotName.tableCell) return;
226
- this.parent.convertTableCell();
227
- this.clearCache();
228
- }
229
-
230
- formatAt(index: number, length: number, name: string, value: any) {
231
- if (this.children.length === 0) {
232
- const defaultChild = this.scroll.create(this.statics.defaultChild.blotName);
233
- this.appendChild(defaultChild);
234
- // block min length is 1
235
- length += defaultChild.length();
236
- }
237
- super.formatAt(index, length, name, value);
238
- // set style for `td`
239
- if (value && value.style) {
240
- this.setStyleByString(value.style);
241
- }
242
- }
243
-
244
- insertAt(index: number, value: string, def?: any): void {
245
- const [child] = this.children.find(index);
246
- // always keep TableCellInner not empty
247
- if (!child && this.statics.defaultChild) {
248
- const defaultChild = this.scroll.create(this.statics.defaultChild.blotName || 'block');
249
- this.appendChild(defaultChild);
250
- }
251
- super.insertAt(index, value, def);
252
- // BlockEmbed will have a \n in delta, this will effect history stack
253
- // so when insert a BlockEmbed, if current child length <= 1 then remove it
254
- const blot = def == null
255
- ? this.scroll.create('text', value)
256
- : this.scroll.create(value, def);
257
- if (blot instanceof BlockEmbed && child && child.length() <= 1) {
258
- child.remove();
259
- }
260
- }
261
-
262
- formats(): Record<string, any> {
263
- const value = this.statics.formats(this.domNode);
264
- return {
265
- [this.statics.blotName]: value,
266
- };
267
- }
268
-
269
- checkMerge(): boolean {
270
- const { colId, rowId, colspan, rowspan } = this;
271
- const next = this.next as TableCellInnerFormat;
272
- return (
273
- next !== null
274
- && next.statics.blotName === this.statics.blotName
275
- && next.rowId === rowId
276
- && next.colId === colId
277
- && next.colspan === colspan
278
- && next.rowspan === rowspan
279
- );
280
- }
281
-
282
- optimize() {
283
- const parent = this.parent;
284
- const blotValue = this.statics.formats(this.domNode);
285
- // handle BlockEmbed to insert tableCellInner when setContents
286
- if (this.prev && this.prev instanceof BlockEmbed) {
287
- const prev = this.prev;
288
- this.insertBefore(prev, this.children.head);
289
- if (this.length() <= 1) {
290
- const afterBlock = this.scroll.create('block');
291
- this.insertBefore(afterBlock, prev.next);
292
- }
293
- }
294
- const parentNotTableCell = parent !== null && parent.statics.blotName !== blotName.tableCell;
295
- if (parentNotTableCell) {
296
- this.wrap(blotName.tableCell, blotValue);
297
- // when insert delta like: [ { attributes: { 'table-up-cell-inner': { ... } }, insert: '\n' }, { attributes: { 'table-up-cell-inner': { ... } }, insert: '\n' }, ...]
298
- // that delta will create dom like: <td><div></div></td>... . that means TableCellInner will be an empty cell without 'block'
299
- // in this case, a 'block' should to inserted to makesure that the cell will not be remove
300
- if (this.children.length === 0) {
301
- const child = this.scroll.create(this.statics.defaultChild.blotName);
302
- this.appendChild(child);
303
- }
304
- }
305
-
306
- if (this.children.length > 0 && this.next != null && this.checkMerge()) {
307
- this.next.moveChildren(this);
308
- this.next.remove();
309
- }
310
- // TODO: uiNode not test, maybe have bug
311
- if (this.uiNode != null && this.uiNode !== this.domNode.firstChild) {
312
- this.domNode.insertBefore(this.uiNode, this.domNode.firstChild);
313
- }
314
- // this is necessary when redo or undo. else will delete or insert wrong index
315
- if (this.children.length === 0) {
316
- // if cellInner doesn't have child then remove it. not insert a block
317
- this.remove();
318
- }
319
- else {
320
- // update delta data
321
- if (
322
- this.domNode.dataset.style
323
- && parentNotTableCell
324
- && parent.domNode.style.cssText !== this.domNode.dataset.style
325
- ) {
326
- this.setStyleByString(this.domNode.dataset.style);
327
- }
328
- }
329
- }
330
-
331
- insertBefore(blot: TypeParchment.Blot, ref?: TypeParchment.Blot | null) {
332
- if (blot.statics.blotName === this.statics.blotName) {
333
- const cellInnerBlot = blot as TableCellInnerFormat;
334
- const cellInnerBlotValue = this.statics.formats(cellInnerBlot.domNode);
335
- const selfValue = this.statics.formats(this.domNode);
336
- const isSame = isSameCellValue(selfValue, cellInnerBlotValue);
337
-
338
- if (!isSame) {
339
- const [selfRow, selfCell] = findParentBlots(this, [blotName.tableRow, blotName.tableCell] as const);
340
- let cellRef: TypeParchment.Blot = selfCell;
341
- // split current cellInner
342
- if (ref) {
343
- const index = ref.offset();
344
- const length = this.length();
345
- if (index !== 0 && index < length) {
346
- const newCellInner = this.split(index)!;
347
- const newCell = newCellInner.wrap(blotName.tableCell, selfValue);
348
- selfRow.insertBefore(newCell, selfCell.next);
349
- cellRef = newCell;
350
- }
351
- }
352
- if (this.tableId !== cellInnerBlot.tableId) {
353
- const selfTableWrapper = findParentBlot(this, blotName.tableWrapper);
354
- const index = this.offset(selfTableWrapper);
355
- const afterSelfTableWrapper = selfTableWrapper.split(index);
356
- return selfTableWrapper.parent.insertBefore(cellInnerBlot, afterSelfTableWrapper);
357
- }
358
- // different rowId. split current row
359
- if (this.rowId !== cellInnerBlot.rowId) {
360
- let rowRef: TypeParchment.Blot | null = selfRow;
361
- const splitRef = ref;
362
- if (splitRef) {
363
- const index = splitRef.offset(selfRow);
364
- rowRef = selfRow.split(index);
365
- }
366
- const row = this.scroll.create(blotName.tableRow, cellInnerBlotValue) as TypeParchment.Parent;
367
- const cell = this.scroll.create(blotName.tableCell, cellInnerBlotValue) as TypeParchment.Parent;
368
- cell.appendChild(cellInnerBlot);
369
- row.appendChild(cell);
370
- return selfRow.parent.insertBefore(row, rowRef);
371
- }
372
- return selfRow.insertBefore(
373
- cellInnerBlot.wrap(blotName.tableCell, cellInnerBlotValue),
374
- cellRef,
375
- );
376
- }
377
- else {
378
- const next = this.split(ref ? ref.offset() : 0);
379
- return this.parent.insertBefore(cellInnerBlot, next);
380
- }
381
- }
382
- else if (blot.statics.blotName === blotName.tableCol) {
383
- try {
384
- const bodyBlot = findParentBlot(this, blotName.tableBody);
385
- const index = this.offset(bodyBlot);
386
- const next = bodyBlot.split(index);
387
- bodyBlot.parent.insertBefore(blot, next);
388
- blot.optimize({});
389
- }
390
- catch {
391
- // here should not trigger
392
- console.warn('TableCellInner not in TableBody');
393
- }
394
- return;
395
- }
396
- super.insertBefore(blot, ref);
397
- }
398
- }
1
+ import type { Parchment as TypeParchment } from 'quill';
2
+ import type TypeBlock from 'quill/blots/block';
3
+ import type { BlockEmbed as TypeBlockEmbed } from 'quill/blots/block';
4
+ import type TypeScroll from 'quill/blots/scroll';
5
+ import type { TableBodyTag, TableCellValue } from '../utils';
6
+ import type { TableCellFormat } from './table-cell-format';
7
+ import Quill from 'quill';
8
+ import { blotName, cssTextToObject, findParentBlot, findParentBlots, toCamelCase } from '../utils';
9
+ import { ContainerFormat } from './container-format';
10
+ import { TableBodyFormat } from './table-body-format';
11
+ import { getValidCellspan, isSameCellValue } from './utils';
12
+
13
+ const Block = Quill.import('blots/block') as TypeParchment.BlotConstructor;
14
+ const BlockEmbed = Quill.import('blots/block/embed') as typeof TypeBlockEmbed;
15
+
16
+ export class TableCellInnerFormat extends ContainerFormat {
17
+ static blotName = blotName.tableCellInner;
18
+ static tagName = 'div';
19
+ static className = 'ql-table-cell-inner';
20
+ static allowDataAttrs: Set<string> = new Set(['table-id', 'row-id', 'col-id', 'rowspan', 'colspan', 'empty-row', 'wrap-tag']);
21
+ static defaultChild: TypeParchment.BlotConstructor = Block;
22
+ declare parent: TableCellFormat;
23
+ // keep `isAllowStyle` and `allowStyle` same with TableCellFormat
24
+ static allowStyle = new Set(['background-color', 'border', 'height']);
25
+ static isAllowStyle(str: string): boolean {
26
+ const cssAttrName = toCamelCase(str);
27
+ for (const style of this.allowStyle) {
28
+ // cause `cssTextToObject` will transform css string to camel case style name
29
+ if (cssAttrName.startsWith(toCamelCase(style))) {
30
+ return true;
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+
36
+ static create(value: TableCellValue) {
37
+ const {
38
+ tableId,
39
+ rowId,
40
+ colId,
41
+ rowspan,
42
+ colspan,
43
+ style,
44
+ emptyRow,
45
+ tag = 'td',
46
+ wrapTag = 'tbody',
47
+ } = value;
48
+ const node = super.create() as HTMLElement;
49
+ node.dataset.tableId = tableId;
50
+ node.dataset.rowId = rowId;
51
+ node.dataset.colId = colId;
52
+ node.dataset.rowspan = String(getValidCellspan(rowspan));
53
+ node.dataset.colspan = String(getValidCellspan(colspan));
54
+ node.dataset.tag = tag;
55
+ node.dataset.wrapTag = wrapTag;
56
+ style && (node.dataset.style = style);
57
+ try {
58
+ emptyRow && (node.dataset.emptyRow = JSON.stringify(emptyRow));
59
+ }
60
+ catch {}
61
+ return node;
62
+ }
63
+
64
+ static formats(domNode: HTMLElement) {
65
+ const {
66
+ tableId,
67
+ rowId,
68
+ colId,
69
+ rowspan,
70
+ colspan,
71
+ style,
72
+ emptyRow,
73
+ tag = 'td',
74
+ wrapTag = 'tbody',
75
+ } = domNode.dataset;
76
+ const value: Record<string, any> = {
77
+ tableId: String(tableId),
78
+ rowId: String(rowId),
79
+ colId: String(colId),
80
+ rowspan: Number(getValidCellspan(rowspan)),
81
+ colspan: Number(getValidCellspan(colspan)),
82
+ tag,
83
+ wrapTag,
84
+ };
85
+
86
+ style && (value.style = style);
87
+
88
+ try {
89
+ emptyRow && (value.emptyRow = JSON.parse(emptyRow));
90
+ }
91
+ catch {}
92
+
93
+ return value;
94
+ }
95
+
96
+ constructor(scroll: TypeScroll, domNode: HTMLElement, _value: TableCellValue) {
97
+ super(scroll, domNode);
98
+ domNode.setAttribute('contenteditable', String(scroll.isEnabled()));
99
+ }
100
+
101
+ setFormatValue(name: string, value?: any, isStyle: boolean = false) {
102
+ if (isStyle) {
103
+ if (!this.statics.isAllowStyle(name)) return;
104
+ }
105
+ else {
106
+ super.setFormatValue(name, value);
107
+ }
108
+ if (this.parent && this.parent.statics.blotName === blotName.tableCell) {
109
+ this.parent.setFormatValue(name, value);
110
+ }
111
+
112
+ this.clearCache();
113
+ }
114
+
115
+ clearCache() {
116
+ const blocks = this.descendants(Block, 0);
117
+ for (const child of blocks) {
118
+ (child as TypeBlock).cache = {};
119
+ }
120
+ }
121
+
122
+ get tableId() {
123
+ return this.domNode.dataset.tableId!;
124
+ }
125
+
126
+ get rowId() {
127
+ return this.domNode.dataset.rowId!;
128
+ }
129
+
130
+ set rowId(value) {
131
+ this.setFormatValue('row-id', value);
132
+ }
133
+
134
+ get colId() {
135
+ return this.domNode.dataset.colId!;
136
+ }
137
+
138
+ set colId(value) {
139
+ this.setFormatValue('col-id', value);
140
+ }
141
+
142
+ get rowspan() {
143
+ return Number(this.domNode.dataset.rowspan);
144
+ }
145
+
146
+ set rowspan(value: number) {
147
+ this.setFormatValue('rowspan', value);
148
+ }
149
+
150
+ get colspan() {
151
+ return Number(this.domNode.dataset.colspan);
152
+ }
153
+
154
+ set colspan(value: number) {
155
+ this.setFormatValue('colspan', value);
156
+ }
157
+
158
+ get emptyRow(): string[] {
159
+ try {
160
+ return JSON.parse(this.domNode.dataset.emptyRow!);
161
+ }
162
+ catch {
163
+ return [];
164
+ }
165
+ }
166
+
167
+ set emptyRow(value: string[]) {
168
+ // if value same as currentEmptyRow, do nothing
169
+ if (this.emptyRow.toString() === value.toString()) return;
170
+
171
+ try {
172
+ if (value.length > 0) {
173
+ this.setFormatValue('empty-row', JSON.stringify(value), false);
174
+ }
175
+ else {
176
+ this.setFormatValue('empty-row', null, false);
177
+ }
178
+ }
179
+ catch {
180
+ this.setFormatValue('empty-row', null, false);
181
+ }
182
+ }
183
+
184
+ set wrapTag(value: TableBodyTag) {
185
+ this.setFormatValue('wrap-tag', value);
186
+ }
187
+
188
+ get wrapTag() {
189
+ return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
190
+ }
191
+
192
+ getColumnIndex() {
193
+ const tableBlot = findParentBlot(this, blotName.tableMain);
194
+ return tableBlot.getColIds().indexOf(this.colId);
195
+ }
196
+
197
+ getRowIndex() {
198
+ const tableBlot = findParentBlot(this, blotName.tableMain);
199
+ return tableBlot.getRowIds().indexOf(this.rowId);
200
+ }
201
+
202
+ getTableBody() {
203
+ let target: TypeParchment.Parent = this.parent;
204
+ while (target && !(target instanceof TableBodyFormat) && target !== this.scroll) {
205
+ target = target.parent;
206
+ }
207
+ if (target === this.scroll) {
208
+ return null;
209
+ }
210
+ return target as TableBodyFormat;
211
+ }
212
+
213
+ getTableRow() {
214
+ try {
215
+ return findParentBlot(this, blotName.tableRow);
216
+ }
217
+ catch {
218
+ return null;
219
+ }
220
+ }
221
+
222
+ setStyleByString(styleStr: string) {
223
+ const style = cssTextToObject(styleStr);
224
+ for (const [name, value] of Object.entries(style)) {
225
+ this.setFormatValue(name, value, true);
226
+ }
227
+ }
228
+
229
+ convertTableCell() {
230
+ if (this.parent.statics.blotName !== blotName.tableCell) return;
231
+ this.parent.convertTableCell();
232
+ this.clearCache();
233
+ }
234
+
235
+ formatAt(index: number, length: number, name: string, value: any) {
236
+ if (this.children.length === 0) {
237
+ const defaultChild = this.scroll.create(this.statics.defaultChild.blotName);
238
+ this.appendChild(defaultChild);
239
+ // block min length is 1
240
+ length += defaultChild.length();
241
+ }
242
+ super.formatAt(index, length, name, value);
243
+ // set style for `td`
244
+ if (value?.style) {
245
+ this.setStyleByString(value.style);
246
+ }
247
+ }
248
+
249
+ insertAt(index: number, value: string, def?: any): void {
250
+ const [child] = this.children.find(index);
251
+ // always keep TableCellInner not empty
252
+ if (!child && this.statics.defaultChild) {
253
+ const defaultChild = this.scroll.create(this.statics.defaultChild.blotName || 'block');
254
+ this.appendChild(defaultChild);
255
+ }
256
+ super.insertAt(index, value, def);
257
+ // BlockEmbed will have a \n in delta, this will effect history stack
258
+ // so when insert a BlockEmbed, if current child length <= 1 then remove it
259
+ const blot = def == null
260
+ ? this.scroll.create('text', value)
261
+ : this.scroll.create(value, def);
262
+ if (blot instanceof BlockEmbed && child && child.length() <= 1) {
263
+ child.remove();
264
+ }
265
+ }
266
+
267
+ formats(): Record<string, any> {
268
+ const value = this.statics.formats(this.domNode);
269
+ return {
270
+ [this.statics.blotName]: value,
271
+ };
272
+ }
273
+
274
+ checkMerge(): boolean {
275
+ const { colId, rowId, colspan, rowspan } = this;
276
+ const next = this.next as TableCellInnerFormat;
277
+ return (
278
+ next !== null
279
+ && next.statics.blotName === this.statics.blotName
280
+ && next.rowId === rowId
281
+ && next.colId === colId
282
+ && next.colspan === colspan
283
+ && next.rowspan === rowspan
284
+ );
285
+ }
286
+
287
+ optimize() {
288
+ const parent = this.parent;
289
+ const blotValue = this.statics.formats(this.domNode);
290
+ // handle BlockEmbed to insert tableCellInner when setContents
291
+ if (this.prev && this.prev instanceof BlockEmbed) {
292
+ const prev = this.prev;
293
+ this.insertBefore(prev, this.children.head);
294
+ if (this.length() <= 1) {
295
+ const afterBlock = this.scroll.create('block');
296
+ this.insertBefore(afterBlock, prev.next);
297
+ }
298
+ }
299
+ const parentNotTableCell = parent !== null && parent.statics.blotName !== blotName.tableCell;
300
+ if (parentNotTableCell) {
301
+ this.wrap(blotName.tableCell, blotValue);
302
+ // when insert delta like: [ { attributes: { 'table-up-cell-inner': { ... } }, insert: '\n' }, { attributes: { 'table-up-cell-inner': { ... } }, insert: '\n' }, ...]
303
+ // that delta will create dom like: <td><div></div></td>... . that means TableCellInner will be an empty cell without 'block'
304
+ // in this case, a 'block' should to inserted to makesure that the cell will not be remove
305
+ if (this.children.length === 0) {
306
+ const child = this.scroll.create(this.statics.defaultChild.blotName);
307
+ this.appendChild(child);
308
+ }
309
+ }
310
+
311
+ if (this.children.length > 0 && this.next != null && this.checkMerge()) {
312
+ this.next.moveChildren(this);
313
+ this.next.remove();
314
+ }
315
+ // TODO: uiNode not test, maybe have bug
316
+ if (this.uiNode != null && this.uiNode !== this.domNode.firstChild) {
317
+ this.domNode.insertBefore(this.uiNode, this.domNode.firstChild);
318
+ }
319
+ // this is necessary when redo or undo. else will delete or insert wrong index
320
+ if (this.children.length === 0) {
321
+ // if cellInner doesn't have child then remove it. not insert a block
322
+ this.remove();
323
+ }
324
+ else {
325
+ // update delta data
326
+ if (
327
+ this.domNode.dataset.style
328
+ && parentNotTableCell
329
+ && parent.domNode.style.cssText !== this.domNode.dataset.style
330
+ ) {
331
+ this.setStyleByString(this.domNode.dataset.style);
332
+ }
333
+ }
334
+ }
335
+
336
+ insertBefore(blot: TypeParchment.Blot, ref?: TypeParchment.Blot | null) {
337
+ if (blot.statics.blotName === this.statics.blotName) {
338
+ const cellInnerBlot = blot as TableCellInnerFormat;
339
+ const cellInnerBlotValue = this.statics.formats(cellInnerBlot.domNode);
340
+ const selfValue = this.statics.formats(this.domNode);
341
+ const isSame = isSameCellValue(selfValue, cellInnerBlotValue);
342
+
343
+ if (!isSame) {
344
+ const [selfRow, selfCell] = findParentBlots(this, [blotName.tableRow, blotName.tableCell] as const);
345
+ let cellRef: TypeParchment.Blot = selfCell;
346
+ // split current cellInner
347
+ if (ref) {
348
+ const index = ref.offset();
349
+ const length = this.length();
350
+ if (index !== 0 && index < length) {
351
+ const newCellInner = this.split(index)!;
352
+ const newCell = newCellInner.wrap(blotName.tableCell, selfValue);
353
+ selfRow.insertBefore(newCell, selfCell.next);
354
+ cellRef = newCell;
355
+ }
356
+ }
357
+ if (this.tableId !== cellInnerBlot.tableId) {
358
+ const selfTableWrapper = findParentBlot(this, blotName.tableWrapper);
359
+ const index = this.offset(selfTableWrapper);
360
+ const afterSelfTableWrapper = selfTableWrapper.split(index);
361
+ return selfTableWrapper.parent.insertBefore(cellInnerBlot, afterSelfTableWrapper);
362
+ }
363
+ // different rowId. split current row
364
+ if (this.rowId !== cellInnerBlot.rowId) {
365
+ let rowRef: TypeParchment.Blot | null = selfRow;
366
+ const splitRef = ref;
367
+ if (splitRef) {
368
+ const index = splitRef.offset(selfRow);
369
+ rowRef = selfRow.split(index);
370
+ }
371
+ const row = this.scroll.create(blotName.tableRow, cellInnerBlotValue) as TypeParchment.Parent;
372
+ const cell = this.scroll.create(blotName.tableCell, cellInnerBlotValue) as TypeParchment.Parent;
373
+ cell.appendChild(cellInnerBlot);
374
+ row.appendChild(cell);
375
+ return selfRow.parent.insertBefore(row, rowRef);
376
+ }
377
+ return selfRow.insertBefore(
378
+ cellInnerBlot.wrap(blotName.tableCell, cellInnerBlotValue),
379
+ cellRef,
380
+ );
381
+ }
382
+ else {
383
+ const next = this.split(ref ? ref.offset() : 0);
384
+ return this.parent.insertBefore(cellInnerBlot, next);
385
+ }
386
+ }
387
+ else if (blot.statics.blotName === blotName.tableCol) {
388
+ try {
389
+ const bodyBlot = findParentBlot(this, blotName.tableBody);
390
+ const index = this.offset(bodyBlot);
391
+ const next = bodyBlot.split(index);
392
+ bodyBlot.parent.insertBefore(blot, next);
393
+ blot.optimize({});
394
+ }
395
+ catch {
396
+ // here should not trigger
397
+ console.warn('TableCellInner not in TableBody');
398
+ }
399
+ return;
400
+ }
401
+ super.insertBefore(blot, ref);
402
+ }
403
+ }