diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index a01e7d641..94d5dc507 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1976,7 +1976,9 @@ async function initApp() { try { await settingsStore.initEditorSettings(); console.log(`[STARTUP] settingsStore.initEditorSettings: ${(performance.now() - t0).toFixed(0)}ms`); - await queryStore.initOpenTabs(); + await connectionStore.initFromDisk(); + console.log(`[STARTUP] connectionStore.initFromDisk: ${(performance.now() - t0).toFixed(0)}ms`); + await queryStore.initOpenTabs({ validConnectionIds: connectionStore.connections.map((connection) => connection.id) }); console.log(`[STARTUP] queryStore.initOpenTabs: ${(performance.now() - t0).toFixed(0)}ms`); await settingsStore.initDesktopSettings().catch(() => {}); @@ -1991,8 +1993,6 @@ async function initApp() { toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000); }); - await connectionStore.initFromDisk(); - console.log(`[STARTUP] connectionStore.initFromDisk: ${(performance.now() - t0).toFixed(0)}ms`); restoreActiveConnectionContext(); } catch (e: any) { toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000); diff --git a/apps/desktop/src/components/grid/DataGridConditionEditor.vue b/apps/desktop/src/components/grid/DataGridConditionEditor.vue index e81bbfa4d..0bb3137a7 100644 --- a/apps/desktop/src/components/grid/DataGridConditionEditor.vue +++ b/apps/desktop/src/components/grid/DataGridConditionEditor.vue @@ -98,7 +98,7 @@ function createTextProbe(input: HTMLTextAreaElement, wrap: boolean) { const probe = document.createElement(wrap ? "div" : "span"); const style = window.getComputedStyle(input); probe.textContent = input.value || input.placeholder || ""; - probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;${wrap ? `width:${input.clientWidth}px;white-space:pre-wrap;overflow-wrap:anywhere;padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};` : "white-space:pre;"}font:${style.font};font-size:${style.fontSize};font-family:${style.fontFamily};font-weight:${style.fontWeight};line-height:${style.lineHeight};letter-spacing:${style.letterSpacing};`; + probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;${wrap ? `width:${input.clientWidth}px;white-space:pre-wrap;overflow-wrap:normal;padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};` : "white-space:pre;"}font:${style.font};font-size:${style.fontSize};font-family:${style.fontFamily};font-weight:${style.fontWeight};line-height:${style.lineHeight};letter-spacing:${style.letterSpacing};`; document.body.appendChild(probe); return probe; } @@ -142,7 +142,15 @@ function updateSuggestionPosition() { void nextTick(() => { const target = activeEditor.value; if (!target) return; - suggestionPosition.value = getDataGridConditionSuggestionPosition(target.getBoundingClientRect(), { + const targetRect = target.getBoundingClientRect(); + const suggestionAnchor = expanded.value + ? { + left: targetRect.left, + bottom: expandedRect.value.top + expandedHeight.value, + width: targetRect.width, + } + : targetRect; + suggestionPosition.value = getDataGridConditionSuggestionPosition(suggestionAnchor, { viewportWidth: window.innerWidth, preferredWidth: suggestionPreferredWidth.value, maxWidth: suggestionPreferredWidth.value === undefined ? undefined : 520, @@ -160,7 +168,8 @@ function resizeEditor(forceExpand = false) { expandAfterComposition = true; return; } - const focused = document.activeElement === input || document.activeElement === overlayRef.value; + const overlayFocused = document.activeElement === overlayRef.value; + const focused = document.activeElement === input || overlayFocused; const nextExpanded = focused && shouldExpand(input) && (forceExpand || expanded.value); if (nextExpanded) { expandedRect.value = measureExpandedRect(input); @@ -177,6 +186,12 @@ function resizeEditor(forceExpand = false) { overlay.setSelectionRange(selectionStart.value, selectionEnd.value); }); } + if (!nextExpanded && overlayFocused && !composing.value) { + void nextTick(() => { + input.focus(); + input.setSelectionRange(selectionStart.value, selectionEnd.value); + }); + } }); } @@ -203,6 +218,36 @@ function focus(select = false) { resizeEditor(true); } +function scrollCaretIntoView() { + const target = activeEditor.value; + if (!target || target.scrollHeight <= target.clientHeight) return; + const style = window.getComputedStyle(target); + const probe = document.createElement("div"); + const caretMarker = document.createElement("span"); + const caret = Math.min(Math.max(selectionStart.value, 0), target.value.length); + probe.textContent = target.value.slice(0, caret) || " "; + caretMarker.textContent = "\u200b"; + probe.appendChild(caretMarker); + probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;width:${target.clientWidth}px;white-space:${style.whiteSpace};overflow-wrap:${style.overflowWrap};padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};font:${style.font};font-size:${style.fontSize};font-family:${style.fontFamily};font-weight:${style.fontWeight};line-height:${style.lineHeight};letter-spacing:${style.letterSpacing};text-indent:${style.textIndent};`; + document.body.appendChild(probe); + const lineHeight = Number.parseFloat(style.lineHeight) || 24; + const caretTop = caretMarker.offsetTop; + const topPadding = Number.parseFloat(style.paddingTop) || 0; + const bottomPadding = Number.parseFloat(style.paddingBottom) || 0; + const visibleTop = target.scrollTop + topPadding; + const visibleBottom = target.scrollTop + target.clientHeight - bottomPadding; + if (caretTop < visibleTop) target.scrollTop = Math.max(0, caretTop - topPadding); + else if (caretTop + lineHeight > visibleBottom) target.scrollTop = caretTop + lineHeight + bottomPadding - target.clientHeight; + probe.remove(); +} + +function focusAfterAccept() { + void nextTick(() => { + focus(); + void nextTick(scrollCaretIntoView); + }); +} + function syncSelection(target: HTMLTextAreaElement) { selectionStart.value = target.selectionStart; selectionEnd.value = target.selectionEnd; @@ -255,7 +300,7 @@ function onKeydown(event: KeyboardEvent) { if (completeQuote(event)) return; const action = editor.handleKeydown(event); if (action === "apply") void applyCondition(); - if (action === "accept") void nextTick(() => focus()); + if (action === "accept") focusAfterAccept(); } function completeQuote(event: KeyboardEvent) { @@ -283,7 +328,7 @@ function openHistory() { function acceptSuggestion(index: number) { editor.accept(index); - void nextTick(() => focus()); + focusAfterAccept(); } function eventInside(event: Event, element?: HTMLElement) { @@ -433,8 +478,8 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH @input="onInput" @keydown="onKeydown" /> -