feat: auto-show toolbar on text selection, position above selection
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user