Lexical: Improved table resize bars

Added scoll & page resize handling.
Added cropping/limiting to edit area.
This commit is contained in:
Dan Brown 2024-07-23 12:45:58 +01:00
parent b618287585
commit 2cab778f19
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
4 changed files with 42 additions and 13 deletions

View File

@ -44,7 +44,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
mergeRegister( mergeRegister(
registerRichText(editor), registerRichText(editor),
registerHistory(editor, createEmptyHistoryState(), 300), registerHistory(editor, createEmptyHistoryState(), 300),
registerTableResizer(editor, editArea), registerTableResizer(editor, editWrap),
); );
setEditorContentFromHtml(editor, htmlContent); setEditorContentFromHtml(editor, htmlContent);

View File

@ -36,5 +36,5 @@
## Bugs ## Bugs
- Image resizing currently bugged, maybe change to ghost resizer in decorator instead of updating core node. - Image resizing currently bugged, maybe change to ghost resizer in decorator instead of updating core node.
- Table resize bars often floating around in wrong place, and shows on hover or interrupts mouse actions.
- Removing link around image via button deletes image, not just link - Removing link around image via button deletes image, not just link
- `SELECTION_CHANGE_COMMAND` not fired when clicking out of a table cell. Prevents toolbar hiding on table unselect.

View File

@ -8,7 +8,7 @@ type MarkerDomRecord = {x: HTMLElement, y: HTMLElement};
class TableResizer { class TableResizer {
protected editor: LexicalEditor; protected editor: LexicalEditor;
protected editArea: HTMLElement; protected editScrollContainer: HTMLElement;
protected markerDom: MarkerDomRecord|null = null; protected markerDom: MarkerDomRecord|null = null;
protected mouseTracker: MouseDragTracker|null = null; protected mouseTracker: MouseDragTracker|null = null;
protected dragging: boolean = false; protected dragging: boolean = false;
@ -16,15 +16,17 @@ class TableResizer {
protected xMarkerAtStart : boolean = false; protected xMarkerAtStart : boolean = false;
protected yMarkerAtStart : boolean = false; protected yMarkerAtStart : boolean = false;
constructor(editor: LexicalEditor, editArea: HTMLElement) { constructor(editor: LexicalEditor, editScrollContainer: HTMLElement) {
this.editor = editor; this.editor = editor;
this.editArea = editArea; this.editScrollContainer = editScrollContainer;
this.setupListeners(); this.setupListeners();
} }
teardown() { teardown() {
this.editArea.removeEventListener('mousemove', this.onCellMouseMove); this.editScrollContainer.removeEventListener('mousemove', this.onCellMouseMove);
window.removeEventListener('scroll', this.onScrollOrResize, {capture: true});
window.removeEventListener('resize', this.onScrollOrResize);
if (this.mouseTracker) { if (this.mouseTracker) {
this.mouseTracker.teardown(); this.mouseTracker.teardown();
} }
@ -32,7 +34,14 @@ class TableResizer {
protected setupListeners() { protected setupListeners() {
this.onCellMouseMove = this.onCellMouseMove.bind(this); this.onCellMouseMove = this.onCellMouseMove.bind(this);
this.editArea.addEventListener('mousemove', this.onCellMouseMove); this.onScrollOrResize = this.onScrollOrResize.bind(this);
this.editScrollContainer.addEventListener('mousemove', this.onCellMouseMove);
window.addEventListener('scroll', this.onScrollOrResize, {capture: true, passive: true});
window.addEventListener('resize', this.onScrollOrResize, {passive: true});
}
protected onScrollOrResize(): void {
this.updateCurrentMarkerTargetPosition();
} }
protected onCellMouseMove(event: MouseEvent) { protected onCellMouseMove(event: MouseEvent) {
@ -58,14 +67,33 @@ class TableResizer {
const markers: MarkerDomRecord = this.getMarkers(); const markers: MarkerDomRecord = this.getMarkers();
const table = cell.closest('table') as HTMLElement; const table = cell.closest('table') as HTMLElement;
const tableRect = table.getBoundingClientRect(); const tableRect = table.getBoundingClientRect();
const editBounds = this.editScrollContainer.getBoundingClientRect();
const maxTop = Math.max(tableRect.top, editBounds.top);
const maxBottom = Math.min(tableRect.bottom, editBounds.bottom);
const maxHeight = maxBottom - maxTop;
markers.x.style.left = xPos + 'px'; markers.x.style.left = xPos + 'px';
markers.x.style.height = tableRect.height + 'px'; markers.x.style.top = maxTop + 'px';
markers.x.style.top = tableRect.top + 'px'; markers.x.style.height = maxHeight + 'px';
markers.y.style.top = yPos + 'px'; markers.y.style.top = yPos + 'px';
markers.y.style.left = tableRect.left + 'px'; markers.y.style.left = tableRect.left + 'px';
markers.y.style.width = tableRect.width + 'px'; markers.y.style.width = tableRect.width + 'px';
// Hide markers when out of bounds
markers.y.hidden = yPos < editBounds.top || yPos > editBounds.bottom;
markers.x.hidden = tableRect.top > editBounds.bottom || tableRect.bottom < editBounds.top;
}
protected updateCurrentMarkerTargetPosition(): void {
if (!this.targetCell) {
return;
}
const rect = this.targetCell.getBoundingClientRect();
const xMarkerPos = this.xMarkerAtStart ? rect.left : rect.right;
const yMarkerPos = this.yMarkerAtStart ? rect.top : rect.bottom;
this.updateMarkersTo(this.targetCell, xMarkerPos, yMarkerPos);
} }
protected getMarkers(): MarkerDomRecord { protected getMarkers(): MarkerDomRecord {
@ -77,7 +105,7 @@ class TableResizer {
const wrapper = el('div', { const wrapper = el('div', {
class: 'editor-table-marker-wrap', class: 'editor-table-marker-wrap',
}, [this.markerDom.x, this.markerDom.y]); }, [this.markerDom.x, this.markerDom.y]);
this.editArea.after(wrapper); this.editScrollContainer.after(wrapper);
this.watchMarkerMouseDrags(wrapper); this.watchMarkerMouseDrags(wrapper);
} }
@ -180,8 +208,8 @@ class TableResizer {
} }
export function registerTableResizer(editor: LexicalEditor, editorArea: HTMLElement): (() => void) { export function registerTableResizer(editor: LexicalEditor, editScrollContainer: HTMLElement): (() => void) {
const resizer = new TableResizer(editor, editorArea); const resizer = new TableResizer(editor, editScrollContainer);
return () => { return () => {
resizer.teardown(); resizer.teardown();

View File

@ -175,6 +175,7 @@ export class EditorUIManager {
protected setupEditor(editor: LexicalEditor) { protected setupEditor(editor: LexicalEditor) {
// Update button states on editor selection change // Update button states on editor selection change
editor.registerCommand(SELECTION_CHANGE_COMMAND, () => { editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
console.log('select change', arguments);
this.triggerStateUpdate({ this.triggerStateUpdate({
editor: editor, editor: editor,
selection: $getSelection(), selection: $getSelection(),