1 import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical";
2 import {el} from "../../../helpers";
3 import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
4 import {$getTableColumnWidth, $setTableColumnWidth, CustomTableNode} from "../../../nodes/custom-table";
6 type MarkerDomRecord = {x: HTMLElement, y: HTMLElement};
9 protected editor: LexicalEditor;
10 protected editArea: HTMLElement;
11 protected markerDom: MarkerDomRecord|null = null;
12 protected mouseTracker: MouseDragTracker|null = null;
13 protected dragging: boolean = false;
14 protected targetCell: HTMLElement|null = null;
15 protected xMarkerAtStart : boolean = false;
16 protected yMarkerAtStart : boolean = false;
18 constructor(editor: LexicalEditor, editArea: HTMLElement) {
20 this.editArea = editArea;
21 this.setupListeners();
25 this.editArea.addEventListener('mousemove', event => {
26 const cell = (event.target as HTMLElement).closest('td,th');
27 if (cell && !this.dragging) {
28 this.onCellMouseMove(cell as HTMLElement, event);
33 onCellMouseMove(cell: HTMLElement, event: MouseEvent) {
34 const rect = cell.getBoundingClientRect();
35 const midX = rect.left + (rect.width / 2);
36 const midY = rect.top + (rect.height / 2);
38 this.targetCell = cell;
39 this.xMarkerAtStart = event.clientX <= midX;
40 this.yMarkerAtStart = event.clientY <= midY;
42 const xMarkerPos = this.xMarkerAtStart ? rect.left : rect.right;
43 const yMarkerPos = this.yMarkerAtStart ? rect.top : rect.bottom;
44 this.updateMarkersTo(cell, xMarkerPos, yMarkerPos);
47 updateMarkersTo(cell: HTMLElement, xPos: number, yPos: number) {
48 const markers: MarkerDomRecord = this.getMarkers();
49 const table = cell.closest('table') as HTMLElement;
50 const tableRect = table.getBoundingClientRect();
52 markers.x.style.left = xPos + 'px';
53 markers.x.style.height = tableRect.height + 'px';
54 markers.x.style.top = tableRect.top + 'px';
56 markers.y.style.top = yPos + 'px';
57 markers.y.style.left = tableRect.left + 'px';
58 markers.y.style.width = tableRect.width + 'px';
61 getMarkers(): MarkerDomRecord {
62 if (!this.markerDom) {
64 x: el('div', {class: 'editor-table-marker editor-table-marker-column'}),
65 y: el('div', {class: 'editor-table-marker editor-table-marker-row'}),
67 const wrapper = el('div', {
68 class: 'editor-table-marker-wrap',
69 }, [this.markerDom.x, this.markerDom.y]);
70 this.editArea.after(wrapper);
71 this.watchMarkerMouseDrags(wrapper);
74 return this.markerDom;
77 watchMarkerMouseDrags(wrapper: HTMLElement) {
79 let markerStart: number = 0;
80 let markerProp: 'left' | 'top' = 'left';
82 this.mouseTracker = new MouseDragTracker(wrapper, '.editor-table-marker', {
83 down(event: MouseEvent, marker: HTMLElement) {
84 marker.classList.add('active');
85 _this.dragging = true;
87 markerProp = marker.classList.contains('editor-table-marker-column') ? 'left' : 'top';
88 markerStart = Number(marker.style[markerProp].replace('px', ''));
90 move(event: MouseEvent, marker: HTMLElement, distance: MouseDragTrackerDistance) {
91 marker.style[markerProp] = (markerStart + distance[markerProp === 'left' ? 'x' : 'y']) + 'px';
93 up(event: MouseEvent, marker: HTMLElement, distance: MouseDragTrackerDistance) {
94 marker.classList.remove('active');
95 marker.style.left = '0';
96 marker.style.top = '0';
98 _this.dragging = false;
99 console.log('up', distance, marker, markerProp, _this.targetCell);
100 const parentTable = _this.targetCell?.closest('table');
102 if (markerProp === 'left' && _this.targetCell && parentTable) {
103 const cellIndex = _this.getTargetCellColumnIndex();
104 _this.editor.update(() => {
105 const table = $getNearestNodeFromDOMNode(parentTable);
106 if (table instanceof CustomTableNode) {
107 const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex);
108 const newWidth = Math.max(originalWidth + distance.x, 10);
109 $setTableColumnWidth(table, cellIndex, newWidth);
117 getTargetCellColumnIndex(): number {
118 const cell = this.targetCell;
124 const row = cell.parentElement;
125 for (const rowCell of row?.children || []) {
126 let size = Number(rowCell.getAttribute('colspan'));
127 if (Number.isNaN(size) || size < 1) {
133 if (rowCell === cell) {
143 export function registerTableResizer(editor: LexicalEditor, editorArea: HTMLElement): (() => void) {
144 const resizer = new TableResizer(editor, editorArea);
146 // TODO - Strip/close down resizer