From fda242d3da40b1352298bf6ddf3fc902b7690e3b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Jun 2025 15:58:59 +0100 Subject: [PATCH] Lexical: Fixed tiny image resizer on image insert Added specific focus on image insert, and updated resize handler to watch for load events and toggle a resize once loaded. --- .../js/wysiwyg/ui/defaults/buttons/objects.ts | 1 + .../ui/framework/helpers/node-resizer.ts | 60 ++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index 4eb4c5a4e..41f6061b6 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts @@ -92,6 +92,7 @@ export const image: EditorButtonDefinition = { context.editor.update(() => { const link = $createLinkedImageNodeFromImageData(image); $insertNodes([link]); + link.select(); }); }) }); diff --git a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts index fa8ff48be..934ac5763 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts @@ -12,17 +12,21 @@ function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode { class NodeResizer { protected context: EditorUiContext; - protected dom: HTMLElement|null = null; + protected resizerDOM: HTMLElement|null = null; + protected targetDOM: HTMLElement|null = null; protected scrollContainer: HTMLElement; protected mouseTracker: MouseDragTracker|null = null; protected activeSelection: string = ''; + protected loadAbortController = new AbortController(); constructor(context: EditorUiContext) { this.context = context; this.scrollContainer = context.scrollDOM; this.onSelectionChange = this.onSelectionChange.bind(this); + this.onTargetDOMLoad = this.onTargetDOMLoad.bind(this); + context.manager.onSelectionChange(this.onSelectionChange); } @@ -47,56 +51,68 @@ class NodeResizer { } } + onTargetDOMLoad(): void { + this.updateResizerPosition(); + } + teardown() { this.context.manager.offSelectionChange(this.onSelectionChange); this.hide(); } - protected showForNode(node: NodeHasSize&LexicalNode, dom: HTMLElement) { - this.dom = this.buildDOM(); + protected showForNode(node: NodeHasSize&LexicalNode, targetDOM: HTMLElement) { + this.resizerDOM = this.buildDOM(); + this.targetDOM = targetDOM; let ghost = el('span', {class: 'editor-node-resizer-ghost'}); if ($isImageNode(node)) { - ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-node-resizer-ghost'}); + ghost = el('img', {src: targetDOM.getAttribute('src'), class: 'editor-node-resizer-ghost'}); } - this.dom.append(ghost); + this.resizerDOM.append(ghost); - this.context.scrollDOM.append(this.dom); - this.updateDOMPosition(dom); + this.context.scrollDOM.append(this.resizerDOM); + this.updateResizerPosition(); - this.mouseTracker = this.setupTracker(this.dom, node, dom); + this.mouseTracker = this.setupTracker(this.resizerDOM, node, targetDOM); this.activeSelection = node.getKey(); + + if (targetDOM.matches('img, embed, iframe, object')) { + this.loadAbortController = new AbortController(); + targetDOM.addEventListener('load', this.onTargetDOMLoad, { signal: this.loadAbortController.signal }); + } } - protected updateDOMPosition(nodeDOM: HTMLElement) { - if (!this.dom) { + protected updateResizerPosition() { + if (!this.resizerDOM || !this.targetDOM) { return; } const scrollAreaRect = this.scrollContainer.getBoundingClientRect(); - const nodeRect = nodeDOM.getBoundingClientRect(); + const nodeRect = this.targetDOM.getBoundingClientRect(); const top = nodeRect.top - (scrollAreaRect.top - this.scrollContainer.scrollTop); const left = nodeRect.left - scrollAreaRect.left; - this.dom.style.top = `${top}px`; - this.dom.style.left = `${left}px`; - this.dom.style.width = nodeRect.width + 'px'; - this.dom.style.height = nodeRect.height + 'px'; + this.resizerDOM.style.top = `${top}px`; + this.resizerDOM.style.left = `${left}px`; + this.resizerDOM.style.width = nodeRect.width + 'px'; + this.resizerDOM.style.height = nodeRect.height + 'px'; } protected updateDOMSize(width: number, height: number): void { - if (!this.dom) { + if (!this.resizerDOM) { return; } - this.dom.style.width = width + 'px'; - this.dom.style.height = height + 'px'; + this.resizerDOM.style.width = width + 'px'; + this.resizerDOM.style.height = height + 'px'; } protected hide() { this.mouseTracker?.teardown(); - this.dom?.remove(); + this.resizerDOM?.remove(); + this.targetDOM = null; this.activeSelection = ''; + this.loadAbortController.abort(); } protected buildDOM() { @@ -140,7 +156,7 @@ class NodeResizer { return new MouseDragTracker(container, '.editor-node-resizer-handle', { down(event: MouseEvent, handle: HTMLElement) { - _this.dom?.classList.add('active'); + _this.resizerDOM?.classList.add('active'); _this.context.editor.getEditorState().read(() => { const domRect = nodeDOM.getBoundingClientRect(); startingWidth = node.getWidth() || domRect.width; @@ -165,10 +181,10 @@ class NodeResizer { node.setHeight(hasHeight ? size.height : 0); _this.context.manager.triggerLayoutUpdate(); requestAnimationFrame(() => { - _this.updateDOMPosition(nodeDOM); + _this.updateResizerPosition(); }) }); - _this.dom?.classList.remove('active'); + _this.resizerDOM?.classList.remove('active'); } }); }