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.
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/table-creator.css +1 -1
- package/package.json +9 -14
- package/src/__tests__/e2e/custom-creator.test.ts +44 -0
- package/src/__tests__/e2e/table-align.test.ts +39 -0
- package/src/__tests__/e2e/table-resize.test.ts +152 -0
- package/src/__tests__/e2e/table-scrollbar.test.ts +31 -0
- package/src/__tests__/e2e/table-selection.test.ts +83 -0
- package/src/__tests__/e2e/utils.ts +6 -0
- package/src/__tests__/unit/table-insert-blot.test.ts +464 -0
- package/src/__tests__/unit/table-insert-remove-merge.test.ts +1270 -0
- package/src/__tests__/unit/table-redo-undo.test.ts +909 -0
- package/src/__tests__/unit/utils.test-d.ts +49 -0
- package/src/__tests__/unit/utils.test.ts +715 -0
- package/src/__tests__/unit/utils.ts +216 -0
- package/src/__tests__/unit/vitest.d.ts +12 -0
- package/src/formats/container-format.ts +52 -0
- package/src/formats/index.ts +10 -0
- package/src/formats/overrides/block.ts +93 -0
- package/src/formats/overrides/blockquote.ts +8 -0
- package/src/formats/overrides/code.ts +8 -0
- package/src/formats/overrides/header.ts +8 -0
- package/src/formats/overrides/index.ts +6 -0
- package/src/formats/overrides/list.ts +10 -0
- package/src/formats/overrides/scroll.ts +51 -0
- package/src/formats/table-body-format.ts +92 -0
- package/src/formats/table-cell-format.ts +139 -0
- package/src/formats/table-cell-inner-format.ts +251 -0
- package/src/formats/table-col-format.ts +174 -0
- package/src/formats/table-colgroup-format.ts +133 -0
- package/src/formats/table-main-format.ts +143 -0
- package/src/formats/table-row-format.ts +147 -0
- package/src/formats/table-wrapper-format.ts +55 -0
- package/src/formats/utils.ts +3 -0
- package/src/index.ts +1157 -0
- package/src/modules/index.ts +5 -0
- package/src/modules/table-align.ts +116 -0
- package/src/modules/table-menu/constants.ts +140 -0
- package/src/modules/table-menu/index.ts +3 -0
- package/src/modules/table-menu/table-menu-common.ts +249 -0
- package/src/modules/table-menu/table-menu-contextmenu.ts +94 -0
- package/src/modules/table-menu/table-menu-select.ts +28 -0
- package/src/modules/table-resize/index.ts +5 -0
- package/src/modules/table-resize/table-resize-box.ts +293 -0
- package/src/modules/table-resize/table-resize-common.ts +343 -0
- package/src/modules/table-resize/table-resize-line.ts +163 -0
- package/src/modules/table-resize/table-resize-scale.ts +154 -0
- package/src/modules/table-resize/utils.ts +3 -0
- package/src/modules/table-scrollbar.ts +255 -0
- package/src/modules/table-selection.ts +262 -0
- package/src/style/button.less +45 -0
- package/src/style/color-picker.less +134 -0
- package/src/style/dialog.less +53 -0
- package/src/style/functions.less +9 -0
- package/src/style/index.less +89 -0
- package/src/style/input.less +64 -0
- package/src/style/select-box.less +51 -0
- package/src/style/table-creator.less +68 -0
- package/src/style/table-menu.less +122 -0
- package/src/style/table-resize-scale.less +31 -0
- package/src/style/table-resize.less +183 -0
- package/src/style/table-scrollbar.less +49 -0
- package/src/style/table-selection.less +15 -0
- package/src/style/tooltip.less +19 -0
- package/src/style/variables.less +1 -0
- package/src/svg/background.svg +1 -0
- package/src/svg/border.svg +1 -0
- package/src/svg/color.svg +1 -0
- package/src/svg/insert-bottom.svg +1 -0
- package/src/svg/insert-left.svg +1 -0
- package/src/svg/insert-right.svg +1 -0
- package/src/svg/insert-top.svg +1 -0
- package/src/svg/merge-cell.svg +1 -0
- package/src/svg/remove-column.svg +1 -0
- package/src/svg/remove-row.svg +1 -0
- package/src/svg/remove-table.svg +1 -0
- package/src/svg/split-cell.svg +1 -0
- package/src/types.d.ts +4 -0
- package/src/utils/bem.ts +23 -0
- package/src/utils/color.ts +109 -0
- package/src/utils/components/button.ts +22 -0
- package/src/utils/components/color-picker.ts +236 -0
- package/src/utils/components/dialog.ts +41 -0
- package/src/utils/components/index.ts +6 -0
- package/src/utils/components/input.ts +74 -0
- package/src/utils/components/table/creator.ts +86 -0
- package/src/utils/components/table/index.ts +2 -0
- package/src/utils/components/table/select-box.ts +83 -0
- package/src/utils/components/tooltip.ts +186 -0
- package/src/utils/constants.ts +99 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/is.ts +6 -0
- package/src/utils/position.ts +21 -0
- package/src/utils/types.ts +131 -0
- package/src/utils/utils.ts +139 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type TableUp from '..';
|
|
2
|
+
import type { TableMainFormat, TableWrapperFormat } from '../formats';
|
|
3
|
+
import { autoUpdate, computePosition, flip, limitShift, offset, shift } from '@floating-ui/dom';
|
|
4
|
+
import Quill from 'quill';
|
|
5
|
+
import { createBEM } from '../utils';
|
|
6
|
+
|
|
7
|
+
export class TableAlign {
|
|
8
|
+
tableBlot: TableMainFormat;
|
|
9
|
+
tableWrapperBlot: TableWrapperFormat;
|
|
10
|
+
alignBox?: HTMLElement;
|
|
11
|
+
cleanup?: () => void;
|
|
12
|
+
bem = createBEM('align');
|
|
13
|
+
resizeObserver = new ResizeObserver(() => this.hide());
|
|
14
|
+
constructor(public tableModule: TableUp, public table: HTMLElement, public quill: Quill) {
|
|
15
|
+
this.tableBlot = Quill.find(table)! as TableMainFormat;
|
|
16
|
+
this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
|
|
17
|
+
|
|
18
|
+
this.alignBox = this.buildTool();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
buildTool() {
|
|
22
|
+
const alignBox = this.tableModule.addContainer(this.bem.b());
|
|
23
|
+
const icons = Quill.import('ui/icons') as Record<string, any>;
|
|
24
|
+
const alignIcons = {
|
|
25
|
+
left: icons.align[''],
|
|
26
|
+
center: icons.align.center,
|
|
27
|
+
right: icons.align.right,
|
|
28
|
+
};
|
|
29
|
+
for (const [align, iconStr] of Object.entries(alignIcons)) {
|
|
30
|
+
const item = document.createElement('span');
|
|
31
|
+
item.dataset.align = align;
|
|
32
|
+
item.classList.add(this.bem.be('item'));
|
|
33
|
+
item.innerHTML = `<i class="icon">${iconStr}</i>`;
|
|
34
|
+
item.addEventListener('click', () => {
|
|
35
|
+
const value = item.dataset.align;
|
|
36
|
+
if (value) {
|
|
37
|
+
this.setTableAlign(this.tableBlot, value);
|
|
38
|
+
|
|
39
|
+
this.quill.once(Quill.events.SCROLL_OPTIMIZE, () => {
|
|
40
|
+
if (this.tableModule.tableSelection) {
|
|
41
|
+
this.tableModule.tableSelection.hide();
|
|
42
|
+
}
|
|
43
|
+
if (this.tableModule.tableResize) {
|
|
44
|
+
this.tableModule.tableResize.update();
|
|
45
|
+
}
|
|
46
|
+
if (this.tableModule.tableResizeScale) {
|
|
47
|
+
this.tableModule.tableResizeScale.update();
|
|
48
|
+
}
|
|
49
|
+
if (this.tableModule.tableScrollbar) {
|
|
50
|
+
this.tableModule.tableScrollbar.update();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
alignBox.appendChild(item);
|
|
56
|
+
}
|
|
57
|
+
if (!this.cleanup) {
|
|
58
|
+
this.cleanup = autoUpdate(
|
|
59
|
+
this.tableWrapperBlot.domNode,
|
|
60
|
+
alignBox,
|
|
61
|
+
() => this.update(),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return alignBox;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setTableAlign(tableBlot: TableMainFormat, align: string) {
|
|
68
|
+
const cols = tableBlot.getCols();
|
|
69
|
+
for (const col of cols) {
|
|
70
|
+
col.align = align;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
show() {
|
|
75
|
+
if (!this.alignBox) return;
|
|
76
|
+
this.alignBox.classList.add(this.bem.bm('active'));
|
|
77
|
+
this.resizeObserver.observe(this.table);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hide() {
|
|
81
|
+
if (!this.alignBox) return;
|
|
82
|
+
this.alignBox.classList.remove(this.bem.bm('active'));
|
|
83
|
+
if (this.cleanup) {
|
|
84
|
+
this.cleanup();
|
|
85
|
+
this.cleanup = undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
update() {
|
|
90
|
+
if (!this.alignBox) return;
|
|
91
|
+
if (this.tableBlot.full || this.tableBlot.domNode.offsetWidth >= this.quill.root.offsetWidth) {
|
|
92
|
+
this.hide();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.show();
|
|
97
|
+
computePosition(this.tableWrapperBlot.domNode, this.alignBox, {
|
|
98
|
+
placement: 'top',
|
|
99
|
+
middleware: [flip(), shift({ limiter: limitShift() }), offset(16)],
|
|
100
|
+
}).then(({ x, y }) => {
|
|
101
|
+
Object.assign(this.alignBox!.style, {
|
|
102
|
+
left: `${x}px`,
|
|
103
|
+
top: `${y}px`,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
destroy() {
|
|
109
|
+
this.hide();
|
|
110
|
+
this.resizeObserver.disconnect();
|
|
111
|
+
if (this.alignBox) {
|
|
112
|
+
this.alignBox.remove();
|
|
113
|
+
this.alignBox = undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Tool } from '../../utils';
|
|
2
|
+
import Background from '../../svg/background.svg';
|
|
3
|
+
import Border from '../../svg/border.svg';
|
|
4
|
+
import InsertBottom from '../../svg/insert-bottom.svg';
|
|
5
|
+
import InsertLeft from '../../svg/insert-left.svg';
|
|
6
|
+
import InsertRight from '../../svg/insert-right.svg';
|
|
7
|
+
import InsertTop from '../../svg/insert-top.svg';
|
|
8
|
+
import MergeCell from '../../svg/merge-cell.svg';
|
|
9
|
+
import RemoveColumn from '../../svg/remove-column.svg';
|
|
10
|
+
import RemoveRow from '../../svg/remove-row.svg';
|
|
11
|
+
import RemoveTable from '../../svg/remove-table.svg';
|
|
12
|
+
import SplitCell from '../../svg/split-cell.svg';
|
|
13
|
+
import { createBEM } from '../../utils';
|
|
14
|
+
|
|
15
|
+
export const menuColorSelectClassName = 'color-selector';
|
|
16
|
+
export const defaultTools: Tool[] = [
|
|
17
|
+
{
|
|
18
|
+
name: 'InsertTop',
|
|
19
|
+
icon: InsertTop,
|
|
20
|
+
tip: 'Insert row above',
|
|
21
|
+
handle: (tableModule) => {
|
|
22
|
+
tableModule.appendRow(false);
|
|
23
|
+
tableModule.hideTableTools();
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'InsertRight',
|
|
28
|
+
icon: InsertRight,
|
|
29
|
+
tip: 'Insert column right',
|
|
30
|
+
handle: (tableModule) => {
|
|
31
|
+
tableModule.appendCol(true);
|
|
32
|
+
tableModule.hideTableTools();
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'InsertBottom',
|
|
37
|
+
icon: InsertBottom,
|
|
38
|
+
tip: 'Insert row below',
|
|
39
|
+
handle: (tableModule) => {
|
|
40
|
+
tableModule.appendRow(true);
|
|
41
|
+
tableModule.hideTableTools();
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'InsertLeft',
|
|
46
|
+
icon: InsertLeft,
|
|
47
|
+
tip: 'Insert column Left',
|
|
48
|
+
handle: (tableModule) => {
|
|
49
|
+
tableModule.appendCol(false);
|
|
50
|
+
tableModule.hideTableTools();
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'break',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
|
|
58
|
+
name: 'MergeCell',
|
|
59
|
+
icon: MergeCell,
|
|
60
|
+
tip: 'Merge Cell',
|
|
61
|
+
handle: (tableModule) => {
|
|
62
|
+
tableModule.mergeCells();
|
|
63
|
+
tableModule.hideTableTools();
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
|
|
68
|
+
name: 'SplitCell',
|
|
69
|
+
icon: SplitCell,
|
|
70
|
+
tip: 'Split Cell',
|
|
71
|
+
handle: (tableModule) => {
|
|
72
|
+
tableModule.splitCell();
|
|
73
|
+
tableModule.hideTableTools();
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'break',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'DeleteRow',
|
|
81
|
+
icon: RemoveRow,
|
|
82
|
+
tip: 'Delete Row',
|
|
83
|
+
handle: (tableModule) => {
|
|
84
|
+
tableModule.removeRow();
|
|
85
|
+
tableModule.hideTableTools();
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'DeleteColumn',
|
|
90
|
+
icon: RemoveColumn,
|
|
91
|
+
tip: 'Delete Column',
|
|
92
|
+
handle: (tableModule) => {
|
|
93
|
+
tableModule.removeCol();
|
|
94
|
+
tableModule.hideTableTools();
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'DeleteTable',
|
|
99
|
+
icon: RemoveTable,
|
|
100
|
+
tip: 'Delete table',
|
|
101
|
+
handle: (tableModule) => {
|
|
102
|
+
tableModule.deleteTable();
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'break',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'BackgroundColor',
|
|
110
|
+
icon: Background,
|
|
111
|
+
isColorChoose: true,
|
|
112
|
+
tip: 'Set background color',
|
|
113
|
+
key: 'background-color',
|
|
114
|
+
handle: (tableModule, selectedTds, color) => {
|
|
115
|
+
tableModule.setCellAttrs(selectedTds, 'background-color', color, true);
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'BorderColor',
|
|
120
|
+
icon: Border,
|
|
121
|
+
isColorChoose: true,
|
|
122
|
+
tip: 'Set border color',
|
|
123
|
+
key: 'border-color',
|
|
124
|
+
handle: (tableModule, selectedTds, color) => {
|
|
125
|
+
tableModule.setCellAttrs(selectedTds, 'border-color', color, true);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
export const maxSaveColorCount = 10;
|
|
131
|
+
export const usedColors = new Set<string>();
|
|
132
|
+
const bem = createBEM('color-map');
|
|
133
|
+
export const colorClassName = {
|
|
134
|
+
selectWrapper: bem.b(),
|
|
135
|
+
used: bem.bm('used'),
|
|
136
|
+
item: bem.be('item'),
|
|
137
|
+
btn: bem.be('btn'),
|
|
138
|
+
map: bem.be('content'),
|
|
139
|
+
mapRow: bem.be('content-row'),
|
|
140
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type Quill from 'quill';
|
|
2
|
+
import type { TableUp } from '../..';
|
|
3
|
+
import type { TableMenuOptions, ToolOption, TooltipInstance, ToolTipOptions } from '../../utils';
|
|
4
|
+
import { createBEM, createColorPicker, createTooltip, debounce, defaultColorMap, isArray, isFunction, randomId } from '../../utils';
|
|
5
|
+
import { colorClassName, defaultTools, maxSaveColorCount, menuColorSelectClassName, usedColors } from './constants';
|
|
6
|
+
|
|
7
|
+
export type TableMenuOptionsInput = Partial<Omit<TableMenuOptions, 'texts'>>;
|
|
8
|
+
export class TableMenuCommon {
|
|
9
|
+
options: TableMenuOptions;
|
|
10
|
+
menu: HTMLElement | null = null;
|
|
11
|
+
updateUsedColor: (this: any, color?: string) => void;
|
|
12
|
+
tooltipItem: TooltipInstance[] = [];
|
|
13
|
+
bem = createBEM('menu');
|
|
14
|
+
colorItemClass = `color-${randomId()}`;
|
|
15
|
+
colorChooseTooltipOption: ToolTipOptions = {
|
|
16
|
+
direction: 'top',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
constructor(public tableModule: TableUp, public quill: Quill, options: TableMenuOptionsInput) {
|
|
20
|
+
this.options = this.resolveOptions(options);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const storageValue = localStorage.getItem(this.options.localstorageKey) || '[]';
|
|
24
|
+
let colorValue = JSON.parse(storageValue);
|
|
25
|
+
if (!isArray(colorValue)) {
|
|
26
|
+
colorValue = [];
|
|
27
|
+
}
|
|
28
|
+
colorValue.slice(-1 * maxSaveColorCount).map((c: string) => usedColors.add(c));
|
|
29
|
+
}
|
|
30
|
+
catch {}
|
|
31
|
+
|
|
32
|
+
this.updateUsedColor = debounce((color?: string) => {
|
|
33
|
+
if (!color) return;
|
|
34
|
+
usedColors.add(color);
|
|
35
|
+
if (usedColors.size > maxSaveColorCount) {
|
|
36
|
+
const saveColors = Array.from(usedColors).slice(-1 * maxSaveColorCount);
|
|
37
|
+
usedColors.clear();
|
|
38
|
+
saveColors.map(v => usedColors.add(v));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
localStorage.setItem(this.options.localstorageKey, JSON.stringify(Array.from(usedColors)));
|
|
42
|
+
const usedColorWrappers = Array.from(document.querySelectorAll(`.${this.colorItemClass}.${colorClassName.used}`));
|
|
43
|
+
for (const usedColorWrapper of usedColorWrappers) {
|
|
44
|
+
const newColorItem = document.createElement('div');
|
|
45
|
+
newColorItem.classList.add(colorClassName.item);
|
|
46
|
+
newColorItem.style.backgroundColor = String(color);
|
|
47
|
+
// if already have same color item. doesn't need insert
|
|
48
|
+
const sameColorItem = Array.from(usedColorWrapper.querySelectorAll(`.${colorClassName.item}[style*="background-color: ${newColorItem.style.backgroundColor}"]`));
|
|
49
|
+
if (sameColorItem.length <= 0) {
|
|
50
|
+
usedColorWrapper.appendChild(newColorItem);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const colorItem = Array.from(usedColorWrapper.querySelectorAll(`.${colorClassName.item}`)).slice(0, -1 * maxSaveColorCount);
|
|
54
|
+
for (const item of colorItem) {
|
|
55
|
+
item.remove();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, 1000);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resolveOptions(options: TableMenuOptionsInput) {
|
|
62
|
+
const value = Object.assign({
|
|
63
|
+
tipText: true,
|
|
64
|
+
tipTexts: {},
|
|
65
|
+
tools: defaultTools,
|
|
66
|
+
localstorageKey: '__table-bg-used-color',
|
|
67
|
+
defaultColorMap,
|
|
68
|
+
}, options);
|
|
69
|
+
return value as TableMenuOptions;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
getUsedColors() {
|
|
73
|
+
return usedColors;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
buildTools(): HTMLElement {
|
|
77
|
+
const toolBox = document.createElement('div');
|
|
78
|
+
toolBox.classList.add(this.bem.b());
|
|
79
|
+
Object.assign(toolBox.style, { display: 'flex' });
|
|
80
|
+
for (const tool of this.options.tools) {
|
|
81
|
+
const { name, icon, handle, isColorChoose, key: attrKey, tip = '' } = tool as ToolOption;
|
|
82
|
+
const item = document.createElement('span');
|
|
83
|
+
item.classList.add(this.bem.be('item'));
|
|
84
|
+
if (name === 'break') {
|
|
85
|
+
item.classList.add(this.bem.is('break'));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// add icon
|
|
89
|
+
const iconDom = document.createElement('i');
|
|
90
|
+
iconDom.classList.add('icon');
|
|
91
|
+
if (isFunction(icon)) {
|
|
92
|
+
iconDom.appendChild(icon(this.tableModule));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
iconDom.innerHTML = icon;
|
|
96
|
+
}
|
|
97
|
+
item.appendChild(iconDom);
|
|
98
|
+
|
|
99
|
+
if (isColorChoose && attrKey) {
|
|
100
|
+
const tooltipItem = this.createColorChoose(item, { name, icon, handle, isColorChoose, key: attrKey, tip });
|
|
101
|
+
this.tooltipItem.push(tooltipItem);
|
|
102
|
+
item.classList.add(menuColorSelectClassName);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
isFunction(handle) && item.addEventListener('click', (e) => {
|
|
106
|
+
this.quill.focus();
|
|
107
|
+
handle(this.tableModule, this.getSelectedTds(), e);
|
|
108
|
+
}, false);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// add text
|
|
112
|
+
const tipText = this.options.tipTexts[name] || tip;
|
|
113
|
+
if (this.options.tipText && tipText && tip) {
|
|
114
|
+
this.createTipText(item, tipText);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
toolBox.appendChild(item);
|
|
118
|
+
}
|
|
119
|
+
return toolBox;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
createColorChoose(item: HTMLElement, { handle, key }: ToolOption) {
|
|
123
|
+
const colorSelectWrapper = document.createElement('div');
|
|
124
|
+
colorSelectWrapper.classList.add(colorClassName.selectWrapper);
|
|
125
|
+
|
|
126
|
+
if (this.options.defaultColorMap.length > 0) {
|
|
127
|
+
const colorMap = document.createElement('div');
|
|
128
|
+
colorMap.classList.add(colorClassName.map);
|
|
129
|
+
for (const colors of this.options.defaultColorMap) {
|
|
130
|
+
const colorMapRow = document.createElement('div');
|
|
131
|
+
colorMapRow.classList.add(colorClassName.mapRow);
|
|
132
|
+
for (const color of colors) {
|
|
133
|
+
const colorItem = document.createElement('div');
|
|
134
|
+
colorItem.classList.add(colorClassName.item);
|
|
135
|
+
colorItem.style.backgroundColor = color;
|
|
136
|
+
colorMapRow.appendChild(colorItem);
|
|
137
|
+
}
|
|
138
|
+
colorMap.appendChild(colorMapRow);
|
|
139
|
+
}
|
|
140
|
+
colorSelectWrapper.appendChild(colorMap);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const colorMapRow = document.createElement('div');
|
|
144
|
+
colorMapRow.classList.add(colorClassName.mapRow);
|
|
145
|
+
Object.assign(colorMapRow.style, {
|
|
146
|
+
marginTop: '4px',
|
|
147
|
+
});
|
|
148
|
+
const transparentColor = document.createElement('div');
|
|
149
|
+
transparentColor.classList.add(colorClassName.btn, 'transparent');
|
|
150
|
+
transparentColor.textContent = this.tableModule.options.texts.transparent;
|
|
151
|
+
transparentColor.addEventListener('click', () => {
|
|
152
|
+
handle(this.tableModule, this.getSelectedTds(), 'transparent');
|
|
153
|
+
});
|
|
154
|
+
const clearColor = document.createElement('div');
|
|
155
|
+
clearColor.classList.add(colorClassName.btn, 'clear');
|
|
156
|
+
clearColor.textContent = this.tableModule.options.texts.clear;
|
|
157
|
+
clearColor.addEventListener('click', () => {
|
|
158
|
+
handle(this.tableModule, this.getSelectedTds(), null);
|
|
159
|
+
});
|
|
160
|
+
const customColor = document.createElement('div');
|
|
161
|
+
customColor.classList.add(colorClassName.btn, 'custom');
|
|
162
|
+
customColor.textContent = this.tableModule.options.texts.custom;
|
|
163
|
+
const colorPicker = createColorPicker({
|
|
164
|
+
onChange: (color) => {
|
|
165
|
+
handle(this.tableModule, this.getSelectedTds(), color);
|
|
166
|
+
this.updateUsedColor(color);
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const { hide: hideColorPicker, destroy: destroyColorPicker } = createTooltip(customColor, {
|
|
170
|
+
direction: 'right',
|
|
171
|
+
type: 'click',
|
|
172
|
+
content: colorPicker,
|
|
173
|
+
container: customColor,
|
|
174
|
+
})!;
|
|
175
|
+
|
|
176
|
+
colorMapRow.appendChild(transparentColor);
|
|
177
|
+
colorMapRow.appendChild(clearColor);
|
|
178
|
+
colorMapRow.appendChild(customColor);
|
|
179
|
+
colorSelectWrapper.appendChild(colorMapRow);
|
|
180
|
+
|
|
181
|
+
if (usedColors.size > 0) {
|
|
182
|
+
const usedColorWrap = document.createElement('div');
|
|
183
|
+
usedColorWrap.classList.add(colorClassName.used, this.colorItemClass);
|
|
184
|
+
for (const recordColor of usedColors) {
|
|
185
|
+
const colorItem = document.createElement('div');
|
|
186
|
+
colorItem.classList.add(colorClassName.item);
|
|
187
|
+
colorItem.style.backgroundColor = recordColor;
|
|
188
|
+
usedColorWrap.appendChild(colorItem);
|
|
189
|
+
}
|
|
190
|
+
colorSelectWrapper.appendChild(usedColorWrap);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
colorSelectWrapper.addEventListener('click', (e) => {
|
|
194
|
+
e.stopPropagation();
|
|
195
|
+
hideColorPicker();
|
|
196
|
+
const item = e.target as HTMLElement;
|
|
197
|
+
const color = item.style.backgroundColor;
|
|
198
|
+
const selectedTds = this.getSelectedTds();
|
|
199
|
+
if (item && color && selectedTds.length > 0) {
|
|
200
|
+
this.tableModule.setCellAttrs(selectedTds, key!, color, true);
|
|
201
|
+
if (!item.closest(`.${colorClassName.item}`)) return;
|
|
202
|
+
this.updateUsedColor(color);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return createTooltip(item, {
|
|
207
|
+
content: colorSelectWrapper,
|
|
208
|
+
onClose(force) {
|
|
209
|
+
const isChild = colorSelectWrapper.contains(colorPicker);
|
|
210
|
+
if (force && isChild) {
|
|
211
|
+
hideColorPicker();
|
|
212
|
+
}
|
|
213
|
+
return isChild;
|
|
214
|
+
},
|
|
215
|
+
onDestroy() {
|
|
216
|
+
destroyColorPicker();
|
|
217
|
+
},
|
|
218
|
+
...this.colorChooseTooltipOption,
|
|
219
|
+
})!;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
getSelectedTds() {
|
|
223
|
+
return this.tableModule.tableSelection?.selectedTds || [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
createTipText(item: HTMLElement, text: string) {
|
|
227
|
+
const tipTextDom = createTooltip(item, { msg: text });
|
|
228
|
+
tipTextDom && this.tooltipItem.push(tipTextDom);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
update() {
|
|
232
|
+
if (!this.menu || !this.tableModule.tableSelection || !this.tableModule.tableSelection.boundary) return;
|
|
233
|
+
Object.assign(this.menu.style, { display: 'flex' });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
hide() {
|
|
237
|
+
this.menu && Object.assign(this.menu.style, { display: 'none' });
|
|
238
|
+
for (const tooltip of this.tooltipItem) {
|
|
239
|
+
tooltip.hide(true);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
destroy() {
|
|
244
|
+
for (const tooltip of this.tooltipItem) tooltip.destroy();
|
|
245
|
+
if (!this.menu) return;
|
|
246
|
+
this.menu.remove();
|
|
247
|
+
this.menu = null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type Quill from 'quill';
|
|
2
|
+
import type TableUp from '../..';
|
|
3
|
+
import type { TableMenuOptions, ToolTipOptions } from '../../utils';
|
|
4
|
+
import { limitDomInViewPort } from '../../utils';
|
|
5
|
+
import { menuColorSelectClassName } from './constants';
|
|
6
|
+
import { TableMenuCommon } from './table-menu-common';
|
|
7
|
+
|
|
8
|
+
type TableMenuOptionsInput = Partial<Omit<TableMenuOptions, 'texts'>>;
|
|
9
|
+
export class TableMenuContextmenu extends TableMenuCommon {
|
|
10
|
+
colorChooseTooltipOption: ToolTipOptions = {
|
|
11
|
+
direction: 'right',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
constructor(public tableModule: TableUp, public quill: Quill, options: TableMenuOptionsInput) {
|
|
15
|
+
super(tableModule, quill, options);
|
|
16
|
+
|
|
17
|
+
this.quill.root.addEventListener('contextmenu', this.listenContextmenu);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
listenContextmenu = (e: MouseEvent) => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
|
|
23
|
+
const path = e.composedPath() as HTMLElement[];
|
|
24
|
+
if (!path || path.length <= 0) return;
|
|
25
|
+
|
|
26
|
+
const tableNode = path.find(node => node.tagName && node.tagName.toUpperCase() === 'TABLE' && node.classList.contains('ql-table'));
|
|
27
|
+
|
|
28
|
+
if (tableNode && this.tableModule.tableSelection?.selectedTds?.length) {
|
|
29
|
+
if (!this.menu) {
|
|
30
|
+
this.menu = this.buildTools();
|
|
31
|
+
}
|
|
32
|
+
this.update({ x: e.clientX, y: e.clientY });
|
|
33
|
+
document.addEventListener('click', () => {
|
|
34
|
+
this.hide();
|
|
35
|
+
}, { once: true });
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.hide();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
buildTools(): HTMLElement {
|
|
43
|
+
const menu = super.buildTools();
|
|
44
|
+
menu.classList.add(this.bem.is('contextmenu'));
|
|
45
|
+
const items = menu.getElementsByClassName(menuColorSelectClassName);
|
|
46
|
+
for (const item of Array.from(items)) {
|
|
47
|
+
item.addEventListener('click', e => e.stopPropagation());
|
|
48
|
+
}
|
|
49
|
+
document.body.appendChild(menu);
|
|
50
|
+
return menu;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
createTipText(item: HTMLElement, text: string): void {
|
|
54
|
+
const tipTextDom = document.createElement('span');
|
|
55
|
+
tipTextDom.textContent = text;
|
|
56
|
+
item.appendChild(tipTextDom);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
update(position?: { x: number; y: number }) {
|
|
60
|
+
if (!this.menu || !this.tableModule.tableSelection || !this.tableModule.tableSelection.boundary) return;
|
|
61
|
+
super.update();
|
|
62
|
+
const style: Record<string, any> = {
|
|
63
|
+
display: 'flex',
|
|
64
|
+
left: 0,
|
|
65
|
+
top: 0,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (!position) {
|
|
69
|
+
return this.hide();
|
|
70
|
+
}
|
|
71
|
+
const { x, y } = position;
|
|
72
|
+
style.left = x;
|
|
73
|
+
style.top = y;
|
|
74
|
+
|
|
75
|
+
Object.assign(this.menu.style, {
|
|
76
|
+
...style,
|
|
77
|
+
left: `${style.left + window.scrollX}px`,
|
|
78
|
+
top: `${style.top + window.scrollY}px`,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// limit menu in viewport
|
|
82
|
+
const menuRect = this.menu.getBoundingClientRect();
|
|
83
|
+
const { left: limitLeft, top: limitTop } = limitDomInViewPort(menuRect);
|
|
84
|
+
Object.assign(this.menu.style, {
|
|
85
|
+
left: `${limitLeft + window.scrollX}px`,
|
|
86
|
+
top: `${limitTop + window.scrollY}px`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
destroy() {
|
|
91
|
+
this.quill.root.removeEventListener('contextmenu', this.listenContextmenu);
|
|
92
|
+
super.destroy();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type Quill from 'quill';
|
|
2
|
+
import type { TableMenuOptionsInput, TableUp } from '../..';
|
|
3
|
+
import { computePosition, flip, limitShift, offset, shift } from '@floating-ui/dom';
|
|
4
|
+
import { TableMenuCommon } from './table-menu-common';
|
|
5
|
+
|
|
6
|
+
export class TableMenuSelect extends TableMenuCommon {
|
|
7
|
+
constructor(public tableModule: TableUp, public quill: Quill, options: TableMenuOptionsInput) {
|
|
8
|
+
super(tableModule, quill, options);
|
|
9
|
+
|
|
10
|
+
this.menu = this.buildTools();
|
|
11
|
+
this.tableModule.addContainer(this.menu);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
update() {
|
|
15
|
+
if (!this.menu || !this.tableModule.tableSelection || !this.tableModule.tableSelection.boundary) return;
|
|
16
|
+
super.update();
|
|
17
|
+
|
|
18
|
+
computePosition(this.tableModule.tableSelection.cellSelect, this.menu, {
|
|
19
|
+
placement: 'bottom',
|
|
20
|
+
middleware: [flip(), shift({ limiter: limitShift() }), offset(8)],
|
|
21
|
+
}).then(({ x, y }) => {
|
|
22
|
+
Object.assign(this.menu!.style, {
|
|
23
|
+
left: `${x}px`,
|
|
24
|
+
top: `${y}px`,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|