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.
Files changed (99) hide show
  1. package/dist/index.d.ts +3 -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,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,3 @@
1
+ import type { TableMainFormat } from '../../formats';
2
+
3
+ export const isTableAlignRight = (tableMainBlot: TableMainFormat) => !tableMainBlot.full && tableMainBlot.align === 'right';
@@ -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
+ }