fix(editor): improve link popup positioning with autoUpdate (#11510)

Closes: [BS-3038](https://linear.app/affine-design/issue/BS-3038/遇到一个新bug-抽象的一p)

Maybe the `mock selection widget` is needed.
This commit is contained in:
fundon
2025-04-07 13:33:11 +00:00
parent 1f45cc5dec
commit e927d02c96
2 changed files with 72 additions and 52 deletions

View File

@@ -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;

View File

@@ -116,8 +116,7 @@ export const linkPopupStyle = css`
}
}
.overlay-mask,
.mock-selection-container {
.overlay-root {
position: fixed;
top: 0;
left: 0;