feat: auto-show toolbar on text selection, position above selection

This commit is contained in:
Forge
2026-03-06 13:40:50 +08:00
parent 1aefdd18a4
commit b54cfc87cf

View File

@@ -27,7 +27,7 @@ export default defineContentScript({
const end = el.selectionEnd ?? -1;
if (end - start >= 2) {
const text = el.value.substring(start, end).trim();
if (text.length >= 2) {
if (text.length > 10) {
selectedText = text;
storedStart = start;
storedEnd = end;
@@ -43,7 +43,7 @@ export default defineContentScript({
const sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
const text = sel.toString().trim();
if (text.length >= 2) {
if (text.length > 10) {
selectedText = text;
storedRange = sel.getRangeAt(0).cloneRange(); // CLONE — selection will be lost later
storedElement = null;
@@ -98,29 +98,32 @@ export default defineContentScript({
// ─── Toolbar ──────────────────────────────────────────────────────────────
function getToolbarPosition(mouseX: number, mouseY: number) {
function getToolbarPosition(rect: DOMRect) {
const TOOLBAR_W = 280;
const TOOLBAR_H = 40;
const MARGIN = 8;
const vpW = window.innerWidth;
const vpH = window.innerHeight;
const scrollX = window.scrollX;
const scrollY = window.scrollY;
let top = mouseY + scrollY - TOOLBAR_H - MARGIN;
let left = mouseX + scrollX - TOOLBAR_W / 2;
// Center above the selection rect
let left = rect.left + scrollX + rect.width / 2 - TOOLBAR_W / 2;
let top = rect.top + scrollY - TOOLBAR_H - MARGIN;
left = Math.max(scrollX + MARGIN, Math.min(left, scrollX + vpW - TOOLBAR_W - MARGIN));
if (top < scrollY + MARGIN) top = mouseY + scrollY + MARGIN + 16;
if (top + TOOLBAR_H > scrollY + vpH - MARGIN) top = mouseY + scrollY - TOOLBAR_H - MARGIN - 16;
// Clamp horizontally to viewport
left = Math.max(scrollX + MARGIN, Math.min(left, scrollX + window.innerWidth - TOOLBAR_W - MARGIN));
// Flip below if not enough space above
if (top < scrollY + MARGIN) {
top = rect.bottom + scrollY + MARGIN;
}
return { top, left };
}
function showToolbar(mouseX: number, mouseY: number) {
function showToolbar(rect: DOMRect) {
hideToolbar();
const { top, left } = getToolbarPosition(mouseX, mouseY);
const { top, left } = getToolbarPosition(rect);
toolbar = document.createElement('div');
toolbar.id = 'lexai-toolbar';
@@ -412,16 +415,31 @@ export default defineContentScript({
if (target?.closest?.('[data-lexai="true"]')) return;
// Capture selection state IMMEDIATELY — positions are valid right now.
// The setTimeout below is only for showing the toolbar (lets browser finalise).
// The setTimeout below lets the browser finalise the selection before we read it.
const captured = captureSelectionNow();
setTimeout(() => {
if (captured && selectedText) {
showToolbar(e.clientX + window.scrollX, e.clientY + window.scrollY);
if (!captured || !selectedText || selectedText.length <= 10) {
hideToolbar();
return;
}
// Get bounding rect from the live selection (still valid inside setTimeout)
let rect: DOMRect | null = null;
const sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
rect = sel.getRangeAt(0).getBoundingClientRect();
} else if (storedElement) {
// fallback: use the element's bounding rect for textarea/input
rect = storedElement.getBoundingClientRect();
}
if (rect && rect.width > 0) {
showToolbar(rect);
} else {
hideToolbar();
}
}, 50);
}, 10);
});
// Hide toolbar when clicking elsewhere (not on our UI)