quill-table-up 2.0.1 → 2.0.3

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 (99) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/index.umd.js.map +1 -1
  6. package/dist/table-creator.css +1 -1
  7. package/package.json +9 -14
  8. package/src/__tests__/e2e/custom-creator.test.ts +44 -0
  9. package/src/__tests__/e2e/table-align.test.ts +39 -0
  10. package/src/__tests__/e2e/table-resize.test.ts +152 -0
  11. package/src/__tests__/e2e/table-scrollbar.test.ts +31 -0
  12. package/src/__tests__/e2e/table-selection.test.ts +83 -0
  13. package/src/__tests__/e2e/utils.ts +6 -0
  14. package/src/__tests__/unit/table-insert-blot.test.ts +464 -0
  15. package/src/__tests__/unit/table-insert-remove-merge.test.ts +1270 -0
  16. package/src/__tests__/unit/table-redo-undo.test.ts +909 -0
  17. package/src/__tests__/unit/utils.test-d.ts +49 -0
  18. package/src/__tests__/unit/utils.test.ts +715 -0
  19. package/src/__tests__/unit/utils.ts +216 -0
  20. package/src/__tests__/unit/vitest.d.ts +12 -0
  21. package/src/formats/container-format.ts +52 -0
  22. package/src/formats/index.ts +10 -0
  23. package/src/formats/overrides/block.ts +93 -0
  24. package/src/formats/overrides/blockquote.ts +8 -0
  25. package/src/formats/overrides/code.ts +8 -0
  26. package/src/formats/overrides/header.ts +8 -0
  27. package/src/formats/overrides/index.ts +6 -0
  28. package/src/formats/overrides/list.ts +10 -0
  29. package/src/formats/overrides/scroll.ts +51 -0
  30. package/src/formats/table-body-format.ts +92 -0
  31. package/src/formats/table-cell-format.ts +139 -0
  32. package/src/formats/table-cell-inner-format.ts +251 -0
  33. package/src/formats/table-col-format.ts +174 -0
  34. package/src/formats/table-colgroup-format.ts +133 -0
  35. package/src/formats/table-main-format.ts +143 -0
  36. package/src/formats/table-row-format.ts +147 -0
  37. package/src/formats/table-wrapper-format.ts +55 -0
  38. package/src/formats/utils.ts +3 -0
  39. package/src/index.ts +1157 -0
  40. package/src/modules/index.ts +5 -0
  41. package/src/modules/table-align.ts +116 -0
  42. package/src/modules/table-menu/constants.ts +140 -0
  43. package/src/modules/table-menu/index.ts +3 -0
  44. package/src/modules/table-menu/table-menu-common.ts +249 -0
  45. package/src/modules/table-menu/table-menu-contextmenu.ts +94 -0
  46. package/src/modules/table-menu/table-menu-select.ts +28 -0
  47. package/src/modules/table-resize/index.ts +5 -0
  48. package/src/modules/table-resize/table-resize-box.ts +293 -0
  49. package/src/modules/table-resize/table-resize-common.ts +343 -0
  50. package/src/modules/table-resize/table-resize-line.ts +163 -0
  51. package/src/modules/table-resize/table-resize-scale.ts +154 -0
  52. package/src/modules/table-resize/utils.ts +3 -0
  53. package/src/modules/table-scrollbar.ts +255 -0
  54. package/src/modules/table-selection.ts +262 -0
  55. package/src/style/button.less +45 -0
  56. package/src/style/color-picker.less +134 -0
  57. package/src/style/dialog.less +53 -0
  58. package/src/style/functions.less +9 -0
  59. package/src/style/index.less +89 -0
  60. package/src/style/input.less +64 -0
  61. package/src/style/select-box.less +51 -0
  62. package/src/style/table-creator.less +68 -0
  63. package/src/style/table-menu.less +122 -0
  64. package/src/style/table-resize-scale.less +31 -0
  65. package/src/style/table-resize.less +183 -0
  66. package/src/style/table-scrollbar.less +49 -0
  67. package/src/style/table-selection.less +15 -0
  68. package/src/style/tooltip.less +19 -0
  69. package/src/style/variables.less +1 -0
  70. package/src/svg/background.svg +1 -0
  71. package/src/svg/border.svg +1 -0
  72. package/src/svg/color.svg +1 -0
  73. package/src/svg/insert-bottom.svg +1 -0
  74. package/src/svg/insert-left.svg +1 -0
  75. package/src/svg/insert-right.svg +1 -0
  76. package/src/svg/insert-top.svg +1 -0
  77. package/src/svg/merge-cell.svg +1 -0
  78. package/src/svg/remove-column.svg +1 -0
  79. package/src/svg/remove-row.svg +1 -0
  80. package/src/svg/remove-table.svg +1 -0
  81. package/src/svg/split-cell.svg +1 -0
  82. package/src/types.d.ts +4 -0
  83. package/src/utils/bem.ts +23 -0
  84. package/src/utils/color.ts +109 -0
  85. package/src/utils/components/button.ts +22 -0
  86. package/src/utils/components/color-picker.ts +236 -0
  87. package/src/utils/components/dialog.ts +41 -0
  88. package/src/utils/components/index.ts +6 -0
  89. package/src/utils/components/input.ts +74 -0
  90. package/src/utils/components/table/creator.ts +86 -0
  91. package/src/utils/components/table/index.ts +2 -0
  92. package/src/utils/components/table/select-box.ts +83 -0
  93. package/src/utils/components/tooltip.ts +186 -0
  94. package/src/utils/constants.ts +99 -0
  95. package/src/utils/index.ts +7 -0
  96. package/src/utils/is.ts +6 -0
  97. package/src/utils/position.ts +21 -0
  98. package/src/utils/types.ts +131 -0
  99. package/src/utils/utils.ts +139 -0
@@ -0,0 +1,139 @@
1
+ import type { TableCellValue } from '../utils';
2
+ import type { TableCellInnerFormat } from './table-cell-inner-format';
3
+ import type { TableRowFormat } from './table-row-format';
4
+ import { blotName } from '../utils';
5
+ import { ContainerFormat } from './container-format';
6
+ import { getValidCellspan } from './utils';
7
+
8
+ export class TableCellFormat extends ContainerFormat {
9
+ static blotName = blotName.tableCell;
10
+ static tagName = 'td';
11
+ static className = 'ql-table-cell';
12
+ static allowDataAttrs = new Set(['table-id', 'row-id', 'col-id']);
13
+ static allowAttrs = new Set(['rowspan', 'colspan']);
14
+
15
+ // keep `isAllowStyle` and `allowStyle` same with TableCellInnerFormat
16
+ static allowStyle = new Set(['background-color', 'border', 'height']);
17
+ static isAllowStyle(str: string): boolean {
18
+ for (const style of this.allowStyle) {
19
+ if (str.startsWith(style)) {
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+
26
+ static create(value: TableCellValue) {
27
+ const {
28
+ tableId,
29
+ rowId,
30
+ colId,
31
+ rowspan,
32
+ colspan,
33
+ style,
34
+ } = value;
35
+ const node = super.create() as HTMLElement;
36
+ node.dataset.tableId = tableId;
37
+ node.dataset.rowId = rowId;
38
+ node.dataset.colId = colId;
39
+ node.setAttribute('rowspan', String(getValidCellspan(rowspan)));
40
+ node.setAttribute('colspan', String(getValidCellspan(colspan)));
41
+ style && (node.style.cssText = style);
42
+ return node;
43
+ }
44
+
45
+ static formats(domNode: HTMLElement) {
46
+ const { tableId, rowId, colId } = domNode.dataset;
47
+ const rowspan = Number(domNode.getAttribute('rowspan'));
48
+ const colspan = Number(domNode.getAttribute('colspan'));
49
+ const value: Record<string, any> = {
50
+ tableId,
51
+ rowId,
52
+ colId,
53
+ rowspan: getValidCellspan(rowspan),
54
+ colspan: getValidCellspan(colspan),
55
+ };
56
+
57
+ const inlineStyles: Record<string, any> = {};
58
+ for (let i = 0; i < domNode.style.length; i++) {
59
+ const property = domNode.style[i];
60
+ const value = domNode.style[property as keyof CSSStyleDeclaration] as string;
61
+ if (this.isAllowStyle(String(property)) && !['initial', 'inherit'].includes(value)) {
62
+ inlineStyles[property] = value;
63
+ }
64
+ }
65
+ const entries = Object.entries(inlineStyles);
66
+ if (entries.length > 0) {
67
+ value.style = entries.map(([key, value]) => `${key}:${value}`).join(';');
68
+ }
69
+
70
+ return value;
71
+ }
72
+
73
+ setFormatValue(name: string, value?: any) {
74
+ if (this.statics.allowAttrs.has(name) || this.statics.allowDataAttrs.has(name)) {
75
+ let attrName = name;
76
+ if (this.statics.allowDataAttrs.has(name)) {
77
+ attrName = `data-${name}`;
78
+ }
79
+ if (value) {
80
+ this.domNode.setAttribute(attrName, value);
81
+ }
82
+ else {
83
+ this.domNode.removeAttribute(attrName);
84
+ }
85
+ }
86
+ else if (this.statics.isAllowStyle(name)) {
87
+ Object.assign(this.domNode.style, {
88
+ [name]: value,
89
+ });
90
+ }
91
+ }
92
+
93
+ get tableId() {
94
+ return this.domNode.dataset.tableId!;
95
+ }
96
+
97
+ get rowId() {
98
+ return this.domNode.dataset.rowId!;
99
+ }
100
+
101
+ get colId() {
102
+ return this.domNode.dataset.colId!;
103
+ }
104
+
105
+ get rowspan() {
106
+ return Number(this.domNode.getAttribute('rowspan'));
107
+ }
108
+
109
+ get colspan() {
110
+ return Number(this.domNode.getAttribute('colspan'));
111
+ }
112
+
113
+ getCellInner() {
114
+ return this.children.head as TableCellInnerFormat;
115
+ }
116
+
117
+ checkMerge(): boolean {
118
+ const { colId, rowId, colspan, rowspan } = this;
119
+ const next = this.next as TableCellFormat;
120
+ return (
121
+ next !== null
122
+ && next.statics.blotName === this.statics.blotName
123
+ && next.rowId === rowId
124
+ && next.colId === colId
125
+ && next.colspan === colspan
126
+ && next.rowspan === rowspan
127
+ );
128
+ }
129
+
130
+ optimize(context: Record<string, any>) {
131
+ const parent = this.parent as TableRowFormat;
132
+ const { tableId, rowId } = this;
133
+ if (parent !== null && parent.statics.blotName !== blotName.tableRow) {
134
+ this.wrap(blotName.tableRow, { tableId, rowId });
135
+ }
136
+
137
+ super.optimize(context);
138
+ }
139
+ }
@@ -0,0 +1,251 @@
1
+ import type { Parchment as TypeParchment } from 'quill';
2
+ import type TypeBlock from 'quill/blots/block';
3
+ import type { TableCellValue } from '../utils';
4
+ import type { TableCellFormat } from './table-cell-format';
5
+ import Quill from 'quill';
6
+ import { blotName, findParentBlot, findParentBlots } from '../utils';
7
+ import { ContainerFormat } from './container-format';
8
+ import { getValidCellspan } from './utils';
9
+
10
+ const Block = Quill.import('blots/block') as TypeParchment.BlotConstructor;
11
+ const BlockEmbed = Quill.import('blots/block/embed') as TypeParchment.BlotConstructor;
12
+
13
+ export class TableCellInnerFormat extends ContainerFormat {
14
+ static blotName = blotName.tableCellInner;
15
+ static tagName = 'div';
16
+ static className = 'ql-table-cell-inner';
17
+ static allowDataAttrs: Set<string> = new Set(['table-id', 'row-id', 'col-id', 'rowspan', 'colspan']);
18
+ static defaultChild: TypeParchment.BlotConstructor = Block;
19
+ declare parent: TableCellFormat;
20
+ // keep `isAllowStyle` and `allowStyle` same with TableCellFormat
21
+ static allowStyle = new Set(['background-color', 'border', 'height']);
22
+ static isAllowStyle(str: string): boolean {
23
+ for (const style of this.allowStyle) {
24
+ if (str.startsWith(style)) {
25
+ return true;
26
+ }
27
+ }
28
+ return false;
29
+ }
30
+
31
+ static create(value: TableCellValue) {
32
+ const {
33
+ tableId,
34
+ rowId,
35
+ colId,
36
+ rowspan,
37
+ colspan,
38
+ style,
39
+ } = value;
40
+ const node = super.create() as HTMLElement;
41
+ node.dataset.tableId = tableId;
42
+ node.dataset.rowId = rowId;
43
+ node.dataset.colId = colId;
44
+ node.dataset.rowspan = String(getValidCellspan(rowspan));
45
+ node.dataset.colspan = String(getValidCellspan(colspan));
46
+ style && (node.dataset.style = style);
47
+ return node;
48
+ }
49
+
50
+ static formats(domNode: HTMLElement) {
51
+ const { tableId, rowId, colId, rowspan, colspan, style } = domNode.dataset;
52
+ const value: Record<string, any> = {
53
+ tableId,
54
+ rowId,
55
+ colId,
56
+ rowspan: Number(getValidCellspan(rowspan)),
57
+ colspan: Number(getValidCellspan(colspan)),
58
+ };
59
+ style && (value.style = style);
60
+ return value;
61
+ }
62
+
63
+ setFormatValue(name: string, value: any, isStyle: boolean = false) {
64
+ if (isStyle) {
65
+ if (!this.statics.isAllowStyle(name)) return;
66
+ if (this.parent) {
67
+ this.parent.setFormatValue(name, value);
68
+ this.domNode.dataset.style = this.parent.domNode.style.cssText;
69
+ }
70
+ }
71
+ else {
72
+ if (!this.statics.allowDataAttrs.has(name)) return;
73
+ const attrName = `data-${name}`;
74
+ if (value) {
75
+ this.domNode.setAttribute(attrName, value);
76
+ }
77
+ else {
78
+ this.domNode.removeAttribute(attrName);
79
+ }
80
+ if (this.parent) {
81
+ this.parent.setFormatValue(name, value);
82
+ }
83
+ }
84
+
85
+ const blocks = this.descendants(Block, 0);
86
+ for (const child of blocks) {
87
+ (child as TypeBlock).cache = {};
88
+ }
89
+ }
90
+
91
+ get tableId() {
92
+ return this.domNode.dataset.tableId!;
93
+ }
94
+
95
+ get rowId() {
96
+ return this.domNode.dataset.rowId!;
97
+ }
98
+
99
+ set rowId(value) {
100
+ this.setFormatValue('row-id', value);
101
+ }
102
+
103
+ get colId() {
104
+ return this.domNode.dataset.colId!;
105
+ }
106
+
107
+ set colId(value) {
108
+ this.setFormatValue('col-id', value);
109
+ }
110
+
111
+ get rowspan() {
112
+ return Number(this.domNode.dataset.rowspan);
113
+ }
114
+
115
+ set rowspan(value: number) {
116
+ this.setFormatValue('rowspan', value);
117
+ }
118
+
119
+ get colspan() {
120
+ return Number(this.domNode.dataset.colspan);
121
+ }
122
+
123
+ set colspan(value: number) {
124
+ this.setFormatValue('colspan', value);
125
+ }
126
+
127
+ getColumnIndex() {
128
+ const table = findParentBlot(this, blotName.tableMain);
129
+ return table.getColIds().indexOf(this.colId);
130
+ }
131
+
132
+ formatAt(index: number, length: number, name: string, value: any) {
133
+ if (this.children.length === 0) {
134
+ this.appendChild(this.scroll.create(this.statics.defaultChild.blotName));
135
+ // block min length is 1
136
+ length += 1;
137
+ }
138
+ super.formatAt(index, length, name, value);
139
+ }
140
+
141
+ formats(): Record<string, any> {
142
+ const value = this.statics.formats(this.domNode);
143
+ return {
144
+ [this.statics.blotName]: value,
145
+ };
146
+ }
147
+
148
+ checkMerge(): boolean {
149
+ const { colId, rowId, colspan, rowspan } = this;
150
+ const next = this.next as TableCellInnerFormat;
151
+ return (
152
+ next !== null
153
+ && next.statics.blotName === this.statics.blotName
154
+ && next.rowId === rowId
155
+ && next.colId === colId
156
+ && next.colspan === colspan
157
+ && next.rowspan === rowspan
158
+ );
159
+ }
160
+
161
+ optimize() {
162
+ const parent = this.parent;
163
+ const blotValue = this.statics.formats(this.domNode);
164
+ // handle BlockEmbed to insert tableCellInner when setContents
165
+ if (this.prev && this.prev instanceof BlockEmbed) {
166
+ const afterBlock = this.scroll.create('block');
167
+ this.appendChild(this.prev);
168
+ this.appendChild(afterBlock);
169
+ }
170
+ if (parent !== null && parent.statics.blotName !== blotName.tableCell) {
171
+ this.wrap(blotName.tableCell, blotValue);
172
+ // when insert delta like: [ { attributes: { 'table-up-cell-inner': { ... } }, insert: '\n' }, { attributes: { 'table-up-cell-inner': { ... } }, insert: '\n' }, ...]
173
+ // that delta will create dom like: <td><div></div></td>... . that means TableCellInner will be an empty cell without 'block'
174
+ // in this case, a 'block' should to inserted to makesure that the cell will not be remove
175
+ if (this.children.length === 0) {
176
+ const child = this.scroll.create(this.statics.defaultChild.blotName);
177
+ this.appendChild(child);
178
+ }
179
+ }
180
+
181
+ if (this.children.length > 0 && this.next != null && this.checkMerge()) {
182
+ this.next.moveChildren(this);
183
+ this.next.remove();
184
+ }
185
+ // TODO: uiNode not test, maybe have bug
186
+ if (this.uiNode != null && this.uiNode !== this.domNode.firstChild) {
187
+ this.domNode.insertBefore(this.uiNode, this.domNode.firstChild);
188
+ }
189
+ // this is necessary when redo or undo. else will delete or insert wrong index
190
+ if (this.children.length === 0) {
191
+ // if cellInner doesn't have child then remove it. not insert a block
192
+ this.remove();
193
+ }
194
+ }
195
+
196
+ insertBefore(blot: TypeParchment.Blot, ref?: TypeParchment.Blot | null) {
197
+ if (blot.statics.blotName === this.statics.blotName) {
198
+ const cellInnerBlot = blot as TableCellInnerFormat;
199
+ const cellInnerBlotValue = this.statics.formats(cellInnerBlot.domNode);
200
+ const selfValue = this.statics.formats(this.domNode);
201
+ const isSame = Object.entries(selfValue).every(([key, value]) => value === cellInnerBlotValue[key]);
202
+
203
+ if (!isSame) {
204
+ const [selfRow, selfCell] = findParentBlots(this, [blotName.tableRow, blotName.tableCell] as const);
205
+ // split current cellInner
206
+ if (ref) {
207
+ const index = ref.offset();
208
+ const length = this.length();
209
+ if (index + 1 < length) {
210
+ const newCellInner = this.scroll.create(blotName.tableCellInner, selfValue) as TypeParchment.Parent;
211
+ this.children.forEachAt(index + 1, this.length(), (block) => {
212
+ newCellInner.appendChild(block);
213
+ });
214
+ selfRow.insertBefore(newCellInner.wrap(blotName.tableCell, selfValue), selfCell.next);
215
+
216
+ if (this.children.length === 0) {
217
+ this.remove();
218
+ if (this.parent.children.length === 0) {
219
+ this.parent.remove();
220
+ }
221
+ }
222
+ }
223
+ }
224
+ // different rowId. split current row. move lines which after ref to next row
225
+ if (this.rowId !== cellInnerBlot.rowId) {
226
+ if (ref) {
227
+ const index = ref.offset(selfRow);
228
+ selfRow.split(index);
229
+ }
230
+ else if (selfCell.next) {
231
+ const index = selfCell.next.offset(selfRow);
232
+ selfRow.split(index);
233
+ }
234
+ const row = this.scroll.create(blotName.tableRow, cellInnerBlotValue) as TypeParchment.Parent;
235
+ const cell = this.scroll.create(blotName.tableCell, cellInnerBlotValue) as TypeParchment.Parent;
236
+ cell.appendChild(cellInnerBlot);
237
+ row.appendChild(cell);
238
+ return selfRow.parent.insertBefore(row, selfRow.next);
239
+ }
240
+ return selfRow.insertBefore(
241
+ cellInnerBlot.wrap(blotName.tableCell, cellInnerBlotValue),
242
+ ref ? selfCell : selfCell.next,
243
+ );
244
+ }
245
+ else {
246
+ return this.parent.insertBefore(cellInnerBlot, this.next);
247
+ }
248
+ }
249
+ super.insertBefore(blot, ref);
250
+ }
251
+ }
@@ -0,0 +1,174 @@
1
+ import type { Parchment as TypeParchment } from 'quill';
2
+ import type { BlockEmbed as TypeBlockEmbed } from 'quill/blots/block';
3
+ import type { TableColValue } from '../utils';
4
+ import Quill from 'quill';
5
+ import { blotName, findParentBlot, findParentBlots, tableUpSize } from '../utils';
6
+ import { TableCellInnerFormat } from './table-cell-inner-format';
7
+
8
+ const BlockEmbed = Quill.import('blots/block/embed') as typeof TypeBlockEmbed;
9
+
10
+ export class TableColFormat extends BlockEmbed {
11
+ static blotName = blotName.tableCol;
12
+ static tagName = 'col';
13
+
14
+ static validWidth(width: string | number, full: boolean) {
15
+ let widthNumber = Number.parseFloat(String(width));
16
+ if (Number.isNaN(widthNumber)) {
17
+ widthNumber = tableUpSize[full ? 'colMinWidthPre' : 'colMinWidthPx'];
18
+ }
19
+ return `${widthNumber}${full ? '%' : 'px'}`;
20
+ }
21
+
22
+ static create(value: TableColValue) {
23
+ const { width, tableId, colId, full, align } = value;
24
+ const node = super.create() as HTMLElement;
25
+ node.setAttribute('width', this.validWidth(width, !!full));
26
+ full && (node.dataset.full = String(full));
27
+ if (align && align !== 'left') {
28
+ node.dataset.align = align;
29
+ }
30
+ node.dataset.tableId = tableId;
31
+ node.dataset.colId = colId;
32
+ return node;
33
+ }
34
+
35
+ static value(domNode: HTMLElement) {
36
+ const { tableId, colId } = domNode.dataset;
37
+ const width = domNode.getAttribute('width') || tableUpSize.colDefaultWidth;
38
+ const align = domNode.dataset.align;
39
+ const full = Object.hasOwn(domNode.dataset, 'full');
40
+ const value: Record<string, any> = {
41
+ tableId,
42
+ colId,
43
+ full,
44
+ };
45
+ width && (value.width = Number.parseFloat(width));
46
+ align && (value.align = align);
47
+ return value;
48
+ }
49
+
50
+ constructor(
51
+ public scroll: TypeParchment.Root,
52
+ public domNode: HTMLElement,
53
+ ) {
54
+ super(scroll, domNode);
55
+ }
56
+
57
+ get width(): number {
58
+ let width: number | string | null = this.domNode.getAttribute('width');
59
+ if (!width) {
60
+ width = this.domNode.getBoundingClientRect().width;
61
+ if (this.full) {
62
+ const table = this.domNode.closest('table');
63
+ if (!table) return tableUpSize[this.full ? 'colMinWidthPre' : 'colMinWidthPx'];
64
+ return width / 100 * table.getBoundingClientRect().width;
65
+ }
66
+ return width;
67
+ }
68
+ return Number.parseFloat(String(width));
69
+ }
70
+
71
+ set width(value: string | number) {
72
+ let width = Number.parseFloat(String(value));
73
+ if (Number.isNaN(width)) {
74
+ width = tableUpSize[this.full ? 'colMinWidthPre' : 'colMinWidthPx'];
75
+ }
76
+ this.domNode.setAttribute('width', this.statics.validWidth(width, !!this.full));
77
+ }
78
+
79
+ get tableId() {
80
+ return this.domNode.dataset.tableId!;
81
+ }
82
+
83
+ get colId() {
84
+ return this.domNode.dataset.colId!;
85
+ }
86
+
87
+ get full() {
88
+ return Object.hasOwn(this.domNode.dataset, 'full');
89
+ }
90
+
91
+ get align() {
92
+ return this.domNode.dataset.align || '';
93
+ }
94
+
95
+ set align(value: string) {
96
+ if (value === 'right' || value === 'center') {
97
+ this.domNode.dataset.align = value;
98
+ }
99
+ else {
100
+ this.domNode.removeAttribute('data-align');
101
+ }
102
+ }
103
+
104
+ checkMerge(): boolean {
105
+ const next = this.next as TableColFormat;
106
+ const { tableId, colId } = this;
107
+ return (
108
+ next !== null
109
+ && next.statics.blotName === this.statics.blotName
110
+ && next.tableId === tableId
111
+ && next.colId === colId
112
+ );
113
+ }
114
+
115
+ optimize(context: Record< string, any>) {
116
+ const parent = this.parent;
117
+ if (parent != null && parent.statics.blotName !== blotName.tableColgroup) {
118
+ const value = this.statics.value(this.domNode);
119
+ this.wrap(blotName.tableColgroup, value);
120
+ }
121
+
122
+ const tableColgroup = findParentBlot(this, blotName.tableColgroup);
123
+ tableColgroup.align = this.align;
124
+
125
+ super.optimize(context);
126
+ }
127
+
128
+ insertAt(index: number, value: string, def?: any): void {
129
+ if (def != null) {
130
+ super.insertAt(index, value, def);
131
+ return;
132
+ }
133
+ const lines = value.split('\n');
134
+ const text = lines.pop();
135
+ const blocks = lines.map((line) => {
136
+ const block = this.scroll.create('block');
137
+ block.insertAt(0, line);
138
+ return block;
139
+ });
140
+ const ref = this.split(index);
141
+ const [tableColgroupBlot, tableMainBlot] = findParentBlots(this, [blotName.tableColgroup, blotName.tableMain] as const);
142
+ const tableBodyBlot = tableColgroupBlot.next;
143
+ if (ref) {
144
+ const index = ref.offset(tableColgroupBlot);
145
+ tableColgroupBlot.split(index);
146
+ }
147
+ // create tbody
148
+ let insertBlot = tableMainBlot.parent.parent;
149
+ let nextBlotRef: TypeParchment.Blot | null = tableMainBlot.parent.next;
150
+ if (tableBodyBlot) {
151
+ const cellInners = tableBodyBlot.descendants(TableCellInnerFormat);
152
+ if (cellInners.length > 0) {
153
+ const cellInnerBlot = cellInners[0];
154
+ const value = TableCellInnerFormat.formats(cellInnerBlot.domNode);
155
+ const newBlock = this.scroll.create('block') as TypeParchment.BlockBlot;
156
+ const newTableCellInner = newBlock.wrap(blotName.tableCellInner, value);
157
+ const newTableCell = newTableCellInner.wrap(blotName.tableCell, value);
158
+ const newTableRow = newTableCell.wrap(blotName.tableRow, value);
159
+ const newTableBody = newTableRow.wrap(blotName.tableBody, value.tableId);
160
+ tableColgroupBlot.parent.insertBefore(newTableBody, tableColgroupBlot.next);
161
+
162
+ insertBlot = newBlock;
163
+ nextBlotRef = newBlock.next;
164
+ }
165
+ }
166
+
167
+ for (const block of blocks) {
168
+ insertBlot.insertBefore(block, nextBlotRef);
169
+ }
170
+ if (text) {
171
+ insertBlot.insertBefore(this.scroll.create('text', text), nextBlotRef);
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,133 @@
1
+ import type { Parchment as TypeParchment } from 'quill';
2
+ import type { TableColValue, TableValue } from '../utils';
3
+ import type { TableColFormat } from './table-col-format';
4
+ import { blotName, findParentBlot, tableUpSize } from '../utils';
5
+ import { ContainerFormat } from './container-format';
6
+ import { TableMainFormat } from './table-main-format';
7
+
8
+ export class TableColgroupFormat extends ContainerFormat {
9
+ static blotName = blotName.tableColgroup;
10
+ static tagName = 'colgroup';
11
+ declare children: TypeParchment.LinkedList<TableColFormat>;
12
+
13
+ static create(value: TableValue) {
14
+ const node = super.create() as HTMLElement;
15
+ node.dataset.tableId = value.tableId;
16
+ value.full && (node.dataset.full = String(value.full));
17
+ if (value.align && value.align !== 'left') {
18
+ node.dataset.align = value.align;
19
+ }
20
+ node.setAttribute('contenteditable', 'false');
21
+ return node;
22
+ }
23
+
24
+ get tableId() {
25
+ return this.domNode.dataset.tableId!;
26
+ }
27
+
28
+ get full() {
29
+ return Object.hasOwn(this.domNode.dataset, 'full');
30
+ }
31
+
32
+ set full(value: boolean) {
33
+ if (value) {
34
+ this.domNode.dataset.full = 'true';
35
+ }
36
+ else {
37
+ this.domNode.removeAttribute('data-full');
38
+ }
39
+ }
40
+
41
+ get align() {
42
+ return this.domNode.dataset.align || '';
43
+ }
44
+
45
+ set align(value: string) {
46
+ if (value === 'right' || value === 'center') {
47
+ this.domNode.dataset.align = value;
48
+ }
49
+ else {
50
+ this.domNode.removeAttribute('data-align');
51
+ }
52
+ }
53
+
54
+ findCol(index: number) {
55
+ const next = this.children.iterator();
56
+ let i = 0;
57
+ let cur: TableColFormat | null;
58
+ while ((cur = next())) {
59
+ if (i === index) {
60
+ break;
61
+ }
62
+ i++;
63
+ }
64
+ return cur;
65
+ }
66
+
67
+ insertColByIndex(index: number, value: TableColValue) {
68
+ const table = this.parent;
69
+ if (!(table instanceof TableMainFormat)) {
70
+ throw new TypeError('TableColgroupFormat should be child of TableFormat');
71
+ }
72
+ const col = this.findCol(index);
73
+ const tableCellInner = this.scroll.create(blotName.tableCol, value) as TableColFormat;
74
+ if (table.full) {
75
+ // TODO: first minus column should be near by
76
+ const next = this.children.iterator();
77
+ let cur: TableColFormat | null;
78
+ while ((cur = next())) {
79
+ if (cur.width - tableCellInner.width >= tableUpSize.colMinWidthPre) {
80
+ cur.width -= tableCellInner.width;
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ this.insertBefore(tableCellInner, col);
86
+ }
87
+
88
+ removeColByIndex(index: number) {
89
+ const table = this.parent;
90
+ if (!(table instanceof TableMainFormat)) {
91
+ throw new TypeError('TableColgroupFormat should be child of TableMainFormat');
92
+ }
93
+ const col = this.findCol(index);
94
+ if (col) {
95
+ if (table.full) {
96
+ if (col.next) {
97
+ (col.next as TableColFormat).width += col.width;
98
+ }
99
+ else if (col.prev) {
100
+ (col.prev as TableColFormat).width += col.width;
101
+ }
102
+ }
103
+ col.remove();
104
+ table.colWidthFillTable();
105
+ }
106
+ }
107
+
108
+ checkMerge(): boolean {
109
+ const next = this.next as TableColgroupFormat;
110
+ const tableMain = this.parent;
111
+ if ((tableMain instanceof TableMainFormat) && !tableMain.full) {
112
+ tableMain.colWidthFillTable();
113
+ }
114
+ return (
115
+ next !== null
116
+ && next.statics.blotName === this.statics.blotName
117
+ && next.tableId === this.tableId
118
+ );
119
+ }
120
+
121
+ optimize(context: Record<string, any>) {
122
+ const parent = this.parent;
123
+ const { tableId, full, align } = this;
124
+ if (parent != null && parent.statics.blotName !== blotName.tableMain) {
125
+ this.wrap(blotName.tableMain, { tableId, full, align });
126
+ }
127
+
128
+ const tableMain = findParentBlot(this, blotName.tableMain);
129
+ tableMain.align = align;
130
+
131
+ super.optimize(context);
132
+ }
133
+ }