From e927d02c96d915e482ce20da5628ff267f97ec50 Mon Sep 17 00:00:00 2001 From: fundon Date: Mon, 7 Apr 2025 13:33:11 +0000 Subject: [PATCH] fix(editor): improve link popup positioning with autoUpdate (#11510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: [BS-3038](https://linear.app/affine-design/issue/BS-3038/遇到一个新bug-抽象的一p) Maybe the `mock selection widget` is needed. --- .../src/link-node/link-popup/link-popup.ts | 121 ++++++++++-------- .../link/src/link-node/link-popup/styles.ts | 3 +- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/blocksuite/affine/inlines/link/src/link-node/link-popup/link-popup.ts b/blocksuite/affine/inlines/link/src/link-node/link-popup/link-popup.ts index 5bf30119f..194977feb 100644 --- a/blocksuite/affine/inlines/link/src/link-node/link-popup/link-popup.ts +++ b/blocksuite/affine/inlines/link/src/link-node/link-popup/link-popup.ts @@ -13,7 +13,13 @@ import { TextSelection, } from '@blocksuite/std'; import type { InlineRange } from '@blocksuite/std/inline'; -import { computePosition, inline, offset, shift } from '@floating-ui/dom'; +import { + autoUpdate, + computePosition, + inline, + offset, + shift, +} from '@floating-ui/dom'; import { html } from 'lit'; import { property, query } from 'lit/decorators.js'; import { choose } from 'lit/directives/choose.js'; @@ -184,6 +190,36 @@ export class LinkPopup extends WithDisposable(ShadowlessElement) { this.confirmButton.requestUpdate(); } + private updateMockSelection(rects: DOMRect[]) { + if (!this.mockSelectionContainer) { + return; + } + + this.mockSelectionContainer + .querySelectorAll('div') + .forEach(e => e.remove()); + + const fragment = document.createDocumentFragment(); + + rects.forEach(domRect => { + const mockSelection = document.createElement('div'); + mockSelection.classList.add('mock-selection'); + + // Get the container's bounding rect to account for its position + const containerRect = this.mockSelectionContainer.getBoundingClientRect(); + + // Adjust the position by subtracting the container's offset + mockSelection.style.left = `${domRect.left - containerRect.left}px`; + mockSelection.style.top = `${domRect.top - containerRect.top}px`; + mockSelection.style.width = `${domRect.width}px`; + mockSelection.style.height = `${domRect.height}px`; + + fragment.append(mockSelection); + }); + + this.mockSelectionContainer.append(fragment); + } + override connectedCallback() { super.connectedCallback(); @@ -213,6 +249,40 @@ export class LinkPopup extends WithDisposable(ShadowlessElement) { this.std.host.selection.setGroup('note', []); this.abortController.abort(); }); + + const range = this.inlineEditor.toDomRange(this.targetInlineRange); + if (!range) { + return; + } + + const visualElement = { + getBoundingClientRect: () => range.getBoundingClientRect(), + getClientRects: () => range.getClientRects(), + }; + const popover = this.popoverContainer; + + this.disposables.add( + autoUpdate(visualElement, popover, () => { + computePosition(visualElement, popover, { + middleware: [ + offset(10), + inline(), + shift({ + padding: 6, + }), + ], + }) + .then(({ x, y }) => { + popover.style.left = `${x}px`; + popover.style.top = `${y}px`; + + this.updateMockSelection( + Array.from(visualElement.getClientRects()) + ); + }) + .catch(console.error); + }) + ); } override render() { @@ -230,55 +300,6 @@ export class LinkPopup extends WithDisposable(ShadowlessElement) { `; } - override updated() { - const range = this.inlineEditor.toDomRange(this.targetInlineRange); - if (!range) { - return; - } - - const domRects = range.getClientRects(); - - Object.values(domRects).forEach(domRect => { - if (!this.mockSelectionContainer) { - return; - } - const mockSelection = document.createElement('div'); - mockSelection.classList.add('mock-selection'); - - // Get the container's bounding rect to account for its position - const containerRect = this.mockSelectionContainer.getBoundingClientRect(); - - // Adjust the position by subtracting the container's offset - mockSelection.style.left = `${domRect.left - containerRect.left}px`; - mockSelection.style.top = `${domRect.top - containerRect.top}px`; - mockSelection.style.width = `${domRect.width}px`; - mockSelection.style.height = `${domRect.height}px`; - - this.mockSelectionContainer.append(mockSelection); - }); - - const visualElement = { - getBoundingClientRect: () => range.getBoundingClientRect(), - getClientRects: () => range.getClientRects(), - }; - const popover = this.popoverContainer; - - computePosition(visualElement, popover, { - middleware: [ - offset(10), - inline(), - shift({ - padding: 6, - }), - ], - }) - .then(({ x, y }) => { - popover.style.left = `${x}px`; - popover.style.top = `${y}px`; - }) - .catch(console.error); - } - @property({ attribute: false }) accessor abortController!: AbortController; diff --git a/blocksuite/affine/inlines/link/src/link-node/link-popup/styles.ts b/blocksuite/affine/inlines/link/src/link-node/link-popup/styles.ts index ad7ea885a..bc0cf795c 100644 --- a/blocksuite/affine/inlines/link/src/link-node/link-popup/styles.ts +++ b/blocksuite/affine/inlines/link/src/link-node/link-popup/styles.ts @@ -116,8 +116,7 @@ export const linkPopupStyle = css` } } - .overlay-mask, - .mock-selection-container { + .overlay-root { position: fixed; top: 0; left: 0;