quill-table-up 2.0.2 → 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 +3 -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,163 @@
|
|
|
1
|
+
import type TableUp from '../..';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { type TableCellFormat, TableRowFormat } from '../../formats';
|
|
4
|
+
import { blotName, createBEM, findParentBlot, findParentBlots } from '../../utils';
|
|
5
|
+
import { TableResizeCommon } from './table-resize-common';
|
|
6
|
+
import { isTableAlignRight } from './utils';
|
|
7
|
+
|
|
8
|
+
export class TableResizeLine extends TableResizeCommon {
|
|
9
|
+
colResizer: HTMLElement;
|
|
10
|
+
rowResizer: HTMLElement;
|
|
11
|
+
currentTableCell?: HTMLElement;
|
|
12
|
+
dragging = false;
|
|
13
|
+
|
|
14
|
+
curColIndex: number = -1;
|
|
15
|
+
curRowIndex: number = -1;
|
|
16
|
+
tableCellBlot?: TableCellFormat;
|
|
17
|
+
|
|
18
|
+
bem = createBEM('resize-line');
|
|
19
|
+
constructor(public tableModule: TableUp, public table: HTMLElement, quill: Quill) {
|
|
20
|
+
super(tableModule, quill);
|
|
21
|
+
this.colResizer = this.tableModule.addContainer(this.bem.be('col'));
|
|
22
|
+
this.rowResizer = this.tableModule.addContainer(this.bem.be('row'));
|
|
23
|
+
|
|
24
|
+
this.table.addEventListener('mousemove', this.mousemoveHandler);
|
|
25
|
+
this.quill.on(Quill.events.TEXT_CHANGE, this.hideWhenTextChange);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
mousemoveHandler = (e: MouseEvent) => {
|
|
29
|
+
if (this.dragging) return;
|
|
30
|
+
const tableCell = this.findTableCell(e);
|
|
31
|
+
if (!tableCell) {
|
|
32
|
+
return this.hide();
|
|
33
|
+
}
|
|
34
|
+
const tableCellBlot = Quill.find(tableCell) as TableCellFormat;
|
|
35
|
+
if (!tableCellBlot) return;
|
|
36
|
+
if (this.currentTableCell !== tableCell) {
|
|
37
|
+
this.show();
|
|
38
|
+
this.currentTableCell = tableCell;
|
|
39
|
+
this.tableCellBlot = tableCellBlot;
|
|
40
|
+
this.tableMain = findParentBlot(tableCellBlot, blotName.tableMain);
|
|
41
|
+
if (this.tableMain.getCols().length > 0) {
|
|
42
|
+
this.updateColResizer();
|
|
43
|
+
}
|
|
44
|
+
this.updateRowResizer();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
hideWhenTextChange = () => {
|
|
49
|
+
this.hide();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
findTableCell(e: MouseEvent) {
|
|
53
|
+
for (const el of e.composedPath()) {
|
|
54
|
+
if (el instanceof HTMLElement && el.tagName === 'TD') {
|
|
55
|
+
return el;
|
|
56
|
+
}
|
|
57
|
+
if (el === document.body) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
findCurrentColIndex() {
|
|
65
|
+
return this.curColIndex;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
handleColMouseUpFunc = async function (this: TableResizeLine) {
|
|
69
|
+
await this.handleColMouseUp();
|
|
70
|
+
this.updateColResizer();
|
|
71
|
+
}.bind(this);
|
|
72
|
+
|
|
73
|
+
updateColResizer() {
|
|
74
|
+
if (!this.tableMain || !this.tableCellBlot) return;
|
|
75
|
+
const tableCellBlot = this.tableCellBlot;
|
|
76
|
+
this.tableModule.toolBox.removeChild(this.colResizer);
|
|
77
|
+
this.colResizer = this.tableModule.addContainer(this.bem.be('col'));
|
|
78
|
+
|
|
79
|
+
const [tableBodyBlot] = findParentBlots(tableCellBlot, [blotName.tableBody] as const);
|
|
80
|
+
const tableBodyect = tableBodyBlot.domNode.getBoundingClientRect();
|
|
81
|
+
const tableCellRect = tableCellBlot.domNode.getBoundingClientRect();
|
|
82
|
+
const rootRect = this.quill.root.getBoundingClientRect();
|
|
83
|
+
let left = tableCellRect.right - rootRect.x;
|
|
84
|
+
if (isTableAlignRight(this.tableMain)) {
|
|
85
|
+
left = tableCellRect.left - rootRect.x;
|
|
86
|
+
}
|
|
87
|
+
Object.assign(this.colResizer.style, {
|
|
88
|
+
top: `${tableBodyect.y - rootRect.y}px`,
|
|
89
|
+
left: `${left}px`,
|
|
90
|
+
height: `${tableBodyect.height}px`,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const cols = this.tableMain.getCols();
|
|
94
|
+
this.curColIndex = cols.findIndex(col => col.colId === tableCellBlot.colId);
|
|
95
|
+
|
|
96
|
+
this.colResizer.addEventListener('mousedown', this.handleColMouseDownFunc);
|
|
97
|
+
this.colResizer.addEventListener('dragstart', (e) => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
findCurrentRowIndex() {
|
|
103
|
+
return this.curRowIndex;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
handleRowMouseUpFunc = function (this: TableResizeLine) {
|
|
107
|
+
this.handleRowMouseUp();
|
|
108
|
+
this.updateRowResizer();
|
|
109
|
+
}.bind(this);
|
|
110
|
+
|
|
111
|
+
updateRowResizer() {
|
|
112
|
+
if (!this.tableMain || !this.tableCellBlot) return;
|
|
113
|
+
const tableCellBlot = this.tableCellBlot;
|
|
114
|
+
this.tableModule.toolBox.removeChild(this.rowResizer);
|
|
115
|
+
this.rowResizer = this.tableModule.addContainer(this.bem.be('row'));
|
|
116
|
+
const currentRow = tableCellBlot.parent;
|
|
117
|
+
if (!(currentRow instanceof TableRowFormat)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const [tableBodyBlot] = findParentBlots(tableCellBlot, [blotName.tableBody] as const);
|
|
122
|
+
const tableBodynRect = tableBodyBlot.domNode.getBoundingClientRect();
|
|
123
|
+
const tableCellRect = tableCellBlot.domNode.getBoundingClientRect();
|
|
124
|
+
const rootRect = this.quill.root.getBoundingClientRect();
|
|
125
|
+
Object.assign(this.rowResizer.style, {
|
|
126
|
+
top: `${tableCellRect.bottom - rootRect.y}px`,
|
|
127
|
+
left: `${tableBodynRect.x - rootRect.x}px`,
|
|
128
|
+
width: `${tableBodynRect.width}px`,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const rows = this.tableMain.getRows();
|
|
132
|
+
this.curRowIndex = rows.indexOf(currentRow);
|
|
133
|
+
|
|
134
|
+
this.rowResizer.addEventListener('mousedown', this.handleRowMouseDownFunc);
|
|
135
|
+
this.rowResizer.addEventListener('dragstart', (e) => {
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
show() {
|
|
141
|
+
Object.assign(this.colResizer.style, { display: null });
|
|
142
|
+
Object.assign(this.rowResizer.style, { display: null });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
hide() {
|
|
146
|
+
this.currentTableCell = undefined;
|
|
147
|
+
this.rowResizer.style.display = 'none';
|
|
148
|
+
this.colResizer.style.display = 'none';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
update() {
|
|
152
|
+
this.updateColResizer();
|
|
153
|
+
this.updateRowResizer();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
destroy(): void {
|
|
157
|
+
this.colResizer.remove();
|
|
158
|
+
this.rowResizer.remove();
|
|
159
|
+
|
|
160
|
+
this.table.removeEventListener('mousemove', this.mousemoveHandler);
|
|
161
|
+
this.quill.off(Quill.events.TEXT_CHANGE, this.hideWhenTextChange);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type TableUp from '../..';
|
|
2
|
+
import type { TableColFormat, TableMainFormat, TableRowFormat, TableWrapperFormat } from '../../formats';
|
|
3
|
+
import type { TableResizeScaleOptions } from '../../utils';
|
|
4
|
+
import Quill from 'quill';
|
|
5
|
+
import { addScrollEvent, clearScrollEvent, createBEM, tableUpSize } from '../../utils';
|
|
6
|
+
import { isTableAlignRight } from './utils';
|
|
7
|
+
|
|
8
|
+
export class TableResizeScale {
|
|
9
|
+
scrollHandler: [HTMLElement, (e: Event) => void][] = [];
|
|
10
|
+
tableMainBlot: TableMainFormat | null = null;
|
|
11
|
+
tableWrapperBlot: TableWrapperFormat | null = null;
|
|
12
|
+
bem = createBEM('scale');
|
|
13
|
+
startX: number = 0;
|
|
14
|
+
startY: number = 0;
|
|
15
|
+
options: TableResizeScaleOptions;
|
|
16
|
+
root?: HTMLElement;
|
|
17
|
+
block?: HTMLElement;
|
|
18
|
+
resizeobserver: ResizeObserver = new ResizeObserver(() => this.update());
|
|
19
|
+
constructor(public tableModule: TableUp, public table: HTMLElement, public quill: Quill, options: Partial<TableResizeScaleOptions>) {
|
|
20
|
+
this.options = this.resolveOptions(options);
|
|
21
|
+
this.tableMainBlot = Quill.find(table) as TableMainFormat;
|
|
22
|
+
|
|
23
|
+
if (this.tableMainBlot && !this.tableMainBlot.full) {
|
|
24
|
+
this.tableWrapperBlot = this.tableMainBlot.parent as TableWrapperFormat;
|
|
25
|
+
this.buildResizer();
|
|
26
|
+
this.show();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
resolveOptions(options: Partial<TableResizeScaleOptions>) {
|
|
31
|
+
return Object.assign({
|
|
32
|
+
blockSize: 12,
|
|
33
|
+
}, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
buildResizer() {
|
|
37
|
+
if (!this.tableMainBlot || !this.tableWrapperBlot) return;
|
|
38
|
+
this.root = this.tableModule.addContainer(this.bem.b());
|
|
39
|
+
this.root.classList.add(this.bem.is('hidden'));
|
|
40
|
+
this.block = document.createElement('div');
|
|
41
|
+
this.block.classList.add(this.bem.be('block'));
|
|
42
|
+
Object.assign(this.block.style, {
|
|
43
|
+
width: `${this.options.blockSize}px`,
|
|
44
|
+
height: `${this.options.blockSize}px`,
|
|
45
|
+
});
|
|
46
|
+
this.root.appendChild(this.block);
|
|
47
|
+
|
|
48
|
+
let originColWidth: { blot: TableColFormat; width: number }[] = [];
|
|
49
|
+
let originRowHeight: { blot: TableRowFormat; height: number }[] = [];
|
|
50
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
51
|
+
if (!this.tableMainBlot) return;
|
|
52
|
+
// divide equally by col count/row count
|
|
53
|
+
const isRight = isTableAlignRight(this.tableMainBlot) ? -1 : 1;
|
|
54
|
+
const diffX = (e.clientX - this.startX) * isRight;
|
|
55
|
+
const diffY = e.clientY - this.startY;
|
|
56
|
+
const itemWidth = Math.floor(diffX / originColWidth.length);
|
|
57
|
+
const itemHeight = Math.floor(diffY / originRowHeight.length);
|
|
58
|
+
|
|
59
|
+
for (const { blot, width } of originColWidth) {
|
|
60
|
+
blot.width = Math.max(width + itemWidth, tableUpSize.colMinWidthPx);
|
|
61
|
+
}
|
|
62
|
+
for (const { blot, height } of originRowHeight) {
|
|
63
|
+
blot.setHeight(`${Math.max(height + itemHeight, tableUpSize.rowMinHeightPx)}px`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const handleMouseUp = () => {
|
|
67
|
+
originColWidth = [];
|
|
68
|
+
originRowHeight = [];
|
|
69
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
70
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
71
|
+
};
|
|
72
|
+
this.block.addEventListener('mousedown', (e) => {
|
|
73
|
+
if (!this.tableMainBlot || this.isTableOutofEditor()) return;
|
|
74
|
+
this.startX = e.clientX;
|
|
75
|
+
this.startY = e.clientY;
|
|
76
|
+
// save the origin width and height to calculate result width and height
|
|
77
|
+
originColWidth = this.tableMainBlot.getCols().map(col => ({ blot: col, width: Math.floor(col.width) }));
|
|
78
|
+
originRowHeight = this.tableMainBlot.getRows().map(row => ({ blot: row, height: Math.floor(row.domNode.getBoundingClientRect().height) }));
|
|
79
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
80
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
81
|
+
});
|
|
82
|
+
this.block.addEventListener('dragstart', e => e.preventDefault());
|
|
83
|
+
|
|
84
|
+
this.resizeobserver.observe(this.tableMainBlot.domNode);
|
|
85
|
+
addScrollEvent.call(this, this.quill.root, () => this.update());
|
|
86
|
+
addScrollEvent.call(this, this.tableWrapperBlot.domNode, () => this.update());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
isTableOutofEditor(): boolean {
|
|
90
|
+
if (!this.tableMainBlot || !this.tableWrapperBlot || this.tableMainBlot.full) return false;
|
|
91
|
+
// if tableMain width larger than tableWrapper. reset tableMain width equal editor width
|
|
92
|
+
const tableRect = this.tableMainBlot.domNode.getBoundingClientRect();
|
|
93
|
+
const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
|
|
94
|
+
// equal scale
|
|
95
|
+
if (tableRect.width > tableWrapperRect.width) {
|
|
96
|
+
for (const col of this.tableMainBlot.getCols()) {
|
|
97
|
+
col.width = Math.floor((col.width / tableRect.width) * tableWrapperRect.width);
|
|
98
|
+
}
|
|
99
|
+
this.tableMainBlot.colWidthFillTable();
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
update() {
|
|
106
|
+
if (!this.block || !this.root || !this.tableMainBlot || !this.tableWrapperBlot) return false;
|
|
107
|
+
const tableRect = this.tableMainBlot.domNode.getBoundingClientRect();
|
|
108
|
+
const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
|
|
109
|
+
const editorRect = this.quill.root.getBoundingClientRect();
|
|
110
|
+
const { scrollTop, scrollLeft } = this.tableWrapperBlot.domNode;
|
|
111
|
+
const blockSize = this.options.blockSize * 2;
|
|
112
|
+
const rootWidth = Math.min(tableRect.width, tableWrapperRect.width) + blockSize;
|
|
113
|
+
const rootHeight = Math.min(tableRect.height, tableWrapperRect.height) + blockSize;
|
|
114
|
+
Object.assign(this.root.style, {
|
|
115
|
+
width: `${rootWidth}px`,
|
|
116
|
+
height: `${rootHeight}px`,
|
|
117
|
+
left: `${Math.max(tableRect.x, tableWrapperRect.x) - editorRect.x - this.options.blockSize}px`,
|
|
118
|
+
top: `${Math.max(tableRect.y, tableWrapperRect.y) - editorRect.y - this.options.blockSize}px`,
|
|
119
|
+
});
|
|
120
|
+
const blockStyle = {
|
|
121
|
+
left: `${tableRect.width + blockSize - scrollLeft}px`,
|
|
122
|
+
top: `${rootHeight - scrollTop}px`,
|
|
123
|
+
};
|
|
124
|
+
if (isTableAlignRight(this.tableMainBlot)) {
|
|
125
|
+
this.root.classList.add(this.bem.is('align-right'));
|
|
126
|
+
blockStyle.left = `${this.options.blockSize + -1 * scrollLeft}px`;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.root.classList.remove(this.bem.is('align-right'));
|
|
130
|
+
}
|
|
131
|
+
Object.assign(this.block.style, blockStyle);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
show() {
|
|
135
|
+
if (this.root) {
|
|
136
|
+
this.root.classList.remove(this.bem.is('hidden'));
|
|
137
|
+
this.update();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
hide() {
|
|
142
|
+
if (this.root) {
|
|
143
|
+
this.root.classList.add(this.bem.is('hidden'));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
destroy() {
|
|
148
|
+
this.hide();
|
|
149
|
+
if (this.root) {
|
|
150
|
+
this.root.remove();
|
|
151
|
+
}
|
|
152
|
+
clearScrollEvent.call(this);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type TableUp from '..';
|
|
2
|
+
import type { TableMainFormat } from '../formats';
|
|
3
|
+
import Quill from 'quill';
|
|
4
|
+
import { addScrollEvent, clearScrollEvent, createBEM, debounce } from '../utils';
|
|
5
|
+
|
|
6
|
+
export class Scrollbar {
|
|
7
|
+
minSize: number = 20;
|
|
8
|
+
gap: number = 4;
|
|
9
|
+
move: number = 0;
|
|
10
|
+
cursorDown: boolean = false;
|
|
11
|
+
cursorLeave: boolean = false;
|
|
12
|
+
ratioY: number = 1;
|
|
13
|
+
ratioX: number = 1;
|
|
14
|
+
sizeWidth: string = '';
|
|
15
|
+
sizeHeight: string = '';
|
|
16
|
+
size: string = '';
|
|
17
|
+
thumbState: {
|
|
18
|
+
X: number;
|
|
19
|
+
Y: number;
|
|
20
|
+
} = {
|
|
21
|
+
X: 0,
|
|
22
|
+
Y: 0,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
ob: ResizeObserver;
|
|
26
|
+
container: HTMLElement;
|
|
27
|
+
scrollbar: HTMLElement;
|
|
28
|
+
thumb: HTMLElement = document.createElement('div');
|
|
29
|
+
scrollHandler: [HTMLElement, (e: Event) => void][] = [];
|
|
30
|
+
propertyMap: { readonly size: 'height'; readonly offset: 'offsetHeight'; readonly scrollDirection: 'scrollTop'; readonly scrollSize: 'scrollHeight'; readonly axis: 'Y'; readonly direction: 'top'; readonly client: 'clientY' } | { readonly size: 'width'; readonly offset: 'offsetWidth'; readonly scrollDirection: 'scrollLeft'; readonly scrollSize: 'scrollWidth'; readonly axis: 'X'; readonly direction: 'left'; readonly client: 'clientX' };
|
|
31
|
+
bem = createBEM('scrollbar');
|
|
32
|
+
constructor(public quill: Quill, public isVertical: boolean, public table: HTMLElement, public scrollbarContainer: HTMLElement) {
|
|
33
|
+
this.container = table.parentElement!;
|
|
34
|
+
this.propertyMap = this.isVertical
|
|
35
|
+
? {
|
|
36
|
+
size: 'height',
|
|
37
|
+
offset: 'offsetHeight',
|
|
38
|
+
scrollDirection: 'scrollTop',
|
|
39
|
+
scrollSize: 'scrollHeight',
|
|
40
|
+
axis: 'Y',
|
|
41
|
+
direction: 'top',
|
|
42
|
+
client: 'clientY',
|
|
43
|
+
} as const
|
|
44
|
+
: {
|
|
45
|
+
size: 'width',
|
|
46
|
+
offset: 'offsetWidth',
|
|
47
|
+
scrollDirection: 'scrollLeft',
|
|
48
|
+
scrollSize: 'scrollWidth',
|
|
49
|
+
axis: 'X',
|
|
50
|
+
direction: 'left',
|
|
51
|
+
client: 'clientX',
|
|
52
|
+
} as const;
|
|
53
|
+
this.calculateSize();
|
|
54
|
+
this.ob = new ResizeObserver(() => {
|
|
55
|
+
this.update();
|
|
56
|
+
});
|
|
57
|
+
this.ob.observe(table);
|
|
58
|
+
this.scrollbar = this.createScrollbar();
|
|
59
|
+
this.setScrollbarPosition();
|
|
60
|
+
addScrollEvent.call(this, this.quill.root, () => this.setScrollbarPosition());
|
|
61
|
+
this.showScrollbar();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
update() {
|
|
65
|
+
this.calculateSize();
|
|
66
|
+
this.setScrollbarPosition();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setScrollbarPosition() {
|
|
70
|
+
const { scrollLeft: editorScrollX, scrollTop: editorScrollY } = this.quill.root;
|
|
71
|
+
const { offsetLeft: containerOffsetLeft, offsetTop: containerOffsetTop } = this.container;
|
|
72
|
+
const { width: containerWidth, height: containerHeight } = this.container.getBoundingClientRect();
|
|
73
|
+
const { width: tableWidth, height: tableHeight } = this.table.getBoundingClientRect();
|
|
74
|
+
|
|
75
|
+
let x = containerOffsetLeft;
|
|
76
|
+
let y = containerOffsetTop;
|
|
77
|
+
if (this.isVertical) {
|
|
78
|
+
x += Math.min(containerWidth, tableWidth);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
y += Math.min(containerHeight, tableHeight);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// table align right effect
|
|
85
|
+
const tableMainBlot = Quill.find(this.table) as TableMainFormat | null;
|
|
86
|
+
if (tableMainBlot && tableMainBlot.align !== 'left') {
|
|
87
|
+
x += this.table.offsetLeft - containerOffsetLeft;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Object.assign(this.scrollbar.style, {
|
|
91
|
+
[this.propertyMap.size]: `${this.isVertical ? containerHeight : containerWidth}px`,
|
|
92
|
+
transform: `translate(${x - editorScrollX}px, ${y - editorScrollY}px)`,
|
|
93
|
+
});
|
|
94
|
+
this.containerScrollHandler(this.container);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
calculateSize() {
|
|
98
|
+
const offsetHeight = this.container.offsetHeight - this.gap;
|
|
99
|
+
const offsetWidth = this.container.offsetWidth - this.gap;
|
|
100
|
+
const originalHeight = offsetHeight ** 2 / this.container.scrollHeight;
|
|
101
|
+
const originalWidth = offsetWidth ** 2 / this.container.scrollWidth;
|
|
102
|
+
const height = Math.max(originalHeight, this.minSize);
|
|
103
|
+
const width = Math.max(originalWidth, this.minSize);
|
|
104
|
+
this.ratioY = originalHeight / (offsetHeight - originalHeight) / (height / (offsetHeight - height));
|
|
105
|
+
this.ratioX = originalWidth / (offsetWidth - originalWidth) / (width / (offsetWidth - width));
|
|
106
|
+
|
|
107
|
+
this.sizeWidth = width + this.gap < offsetWidth ? `${width}px` : '';
|
|
108
|
+
this.sizeHeight = height + this.gap < offsetHeight ? `${height}px` : '';
|
|
109
|
+
this.size = this.isVertical ? this.sizeHeight : this.sizeWidth;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
createScrollbar() {
|
|
113
|
+
const scrollbar = document.createElement('div');
|
|
114
|
+
scrollbar.classList.add(this.bem.b());
|
|
115
|
+
scrollbar.classList.add(this.isVertical ? this.bem.is('vertical') : this.bem.is('horizontal'), this.bem.is('transparent'));
|
|
116
|
+
Object.assign(scrollbar.style, {
|
|
117
|
+
display: 'none',
|
|
118
|
+
});
|
|
119
|
+
this.thumb.classList.add(this.bem.be('thumb'));
|
|
120
|
+
|
|
121
|
+
const mouseMoveDocumentHandler = (e: MouseEvent) => {
|
|
122
|
+
if (this.cursorDown === false) return;
|
|
123
|
+
const prevPage = this.thumbState[this.propertyMap.axis];
|
|
124
|
+
if (!prevPage) return;
|
|
125
|
+
|
|
126
|
+
const offsetRatio = this.scrollbar[this.propertyMap.offset] ** 2
|
|
127
|
+
/ this.container[this.propertyMap.scrollSize] / (this.isVertical ? this.ratioY : this.ratioX)
|
|
128
|
+
/ this.thumb[this.propertyMap.offset];
|
|
129
|
+
const offset = (this.scrollbar.getBoundingClientRect()[this.propertyMap.direction] - e[this.propertyMap.client]) * -1;
|
|
130
|
+
const thumbClickPosition = this.thumb[this.propertyMap.offset] - prevPage;
|
|
131
|
+
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 * offsetRatio) / this.scrollbar[this.propertyMap.offset];
|
|
132
|
+
this.container[this.propertyMap.scrollDirection] = (thumbPositionPercentage * this.container[this.propertyMap.scrollSize]) / 100;
|
|
133
|
+
};
|
|
134
|
+
const mouseUpDocumentHandler = () => {
|
|
135
|
+
this.thumbState[this.propertyMap.axis] = 0;
|
|
136
|
+
this.cursorDown = false;
|
|
137
|
+
document.removeEventListener('mousemove', mouseMoveDocumentHandler);
|
|
138
|
+
document.removeEventListener('mouseup', mouseUpDocumentHandler);
|
|
139
|
+
if (this.cursorLeave) {
|
|
140
|
+
this.hideScrollbar();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const startDrag = (e: MouseEvent) => {
|
|
144
|
+
e.stopImmediatePropagation();
|
|
145
|
+
this.cursorDown = true;
|
|
146
|
+
document.addEventListener('mousemove', mouseMoveDocumentHandler);
|
|
147
|
+
document.addEventListener('mouseup', mouseUpDocumentHandler);
|
|
148
|
+
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
|
149
|
+
document.onselectstart = () => false;
|
|
150
|
+
};
|
|
151
|
+
this.thumb.addEventListener('mousedown', (e: MouseEvent) => {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
if (e.ctrlKey || [1, 2].includes(e.button)) return;
|
|
154
|
+
|
|
155
|
+
window.getSelection()?.removeAllRanges();
|
|
156
|
+
startDrag(e);
|
|
157
|
+
|
|
158
|
+
const el = e.currentTarget as HTMLElement;
|
|
159
|
+
if (!el) return;
|
|
160
|
+
this.thumbState[this.propertyMap.axis] = el[this.propertyMap.offset] - (e[this.propertyMap.client] - el.getBoundingClientRect()[this.propertyMap.direction]);
|
|
161
|
+
});
|
|
162
|
+
const displayListener = [this.table, scrollbar];
|
|
163
|
+
for (const el of displayListener) {
|
|
164
|
+
el.addEventListener('mouseenter', this.showScrollbar);
|
|
165
|
+
el.addEventListener('mouseleave', this.hideScrollbar);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
addScrollEvent.call(this, this.container, () => {
|
|
169
|
+
this.containerScrollHandler(this.container);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
scrollbar.appendChild(this.thumb);
|
|
173
|
+
return scrollbar;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
containerScrollHandler(wrap: HTMLElement) {
|
|
177
|
+
const offset = wrap[this.propertyMap.offset] - this.gap;
|
|
178
|
+
this.move = wrap[this.propertyMap.scrollDirection] * 100 / offset * (this.isVertical ? this.ratioY : this.ratioX);
|
|
179
|
+
Object.assign(this.thumb.style, {
|
|
180
|
+
[this.propertyMap.size]: this.size,
|
|
181
|
+
transform: `translate${this.propertyMap.axis}(${this.move}%)`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
showScrollbar = debounce(() => {
|
|
186
|
+
this.cursorLeave = false;
|
|
187
|
+
this.scrollbar.removeEventListener('transitionend', this.hideScrollbarTransitionend);
|
|
188
|
+
this.scrollbar.style.display = this.size ? 'block' : 'none';
|
|
189
|
+
requestAnimationFrame(() => {
|
|
190
|
+
this.scrollbar.classList.remove(this.bem.is('transparent'));
|
|
191
|
+
});
|
|
192
|
+
}, 200);
|
|
193
|
+
|
|
194
|
+
hideScrollbar = debounce(() => {
|
|
195
|
+
this.cursorLeave = true;
|
|
196
|
+
if (this.cursorDown) return;
|
|
197
|
+
this.scrollbar.removeEventListener('transitionend', this.hideScrollbarTransitionend);
|
|
198
|
+
this.scrollbar.addEventListener('transitionend', this.hideScrollbarTransitionend, { once: true });
|
|
199
|
+
this.scrollbar.classList.add(this.bem.is('transparent'));
|
|
200
|
+
}, 200);
|
|
201
|
+
|
|
202
|
+
hideScrollbarTransitionend = () => {
|
|
203
|
+
this.scrollbar.style.display = (this.cursorDown && this.size) ? 'block' : 'none';
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
destroy() {
|
|
207
|
+
this.ob.disconnect();
|
|
208
|
+
clearScrollEvent.call(this);
|
|
209
|
+
this.table.removeEventListener('mouseenter', this.showScrollbar);
|
|
210
|
+
this.table.removeEventListener('mouseleave', this.hideScrollbar);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export class TableVirtualScrollbar {
|
|
214
|
+
scrollbarContainer: HTMLElement;
|
|
215
|
+
scrollbar: Scrollbar[];
|
|
216
|
+
bem = createBEM('scrollbar');
|
|
217
|
+
constructor(public tableModule: TableUp, public table: HTMLElement, public quill: Quill) {
|
|
218
|
+
this.scrollbarContainer = this.tableModule.addContainer(this.bem.be('container'));
|
|
219
|
+
|
|
220
|
+
this.scrollbar = [
|
|
221
|
+
new Scrollbar(quill, true, table, this.scrollbarContainer),
|
|
222
|
+
new Scrollbar(quill, false, table, this.scrollbarContainer),
|
|
223
|
+
];
|
|
224
|
+
for (const item of this.scrollbar) {
|
|
225
|
+
this.scrollbarContainer.appendChild(item.scrollbar);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
hide() {
|
|
230
|
+
for (const scrollbar of this.scrollbar) {
|
|
231
|
+
scrollbar.hideScrollbar();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
show() {
|
|
236
|
+
for (const scrollbar of this.scrollbar) {
|
|
237
|
+
scrollbar.showScrollbar();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
update() {
|
|
242
|
+
for (const scrollbar of this.scrollbar) {
|
|
243
|
+
scrollbar.calculateSize();
|
|
244
|
+
scrollbar.setScrollbarPosition();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
destroy() {
|
|
249
|
+
this.scrollbarContainer.remove();
|
|
250
|
+
for (const scrollbar of this.scrollbar) {
|
|
251
|
+
scrollbar.destroy();
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|