From 3a52fd2a0bd78c4c5ff0dab4355456808d81129f Mon Sep 17 00:00:00 2001 From: zipg Date: Thu, 3 Sep 2026 17:51:26 +0800 Subject: [PATCH] fix(grid): keep caret aligned after filter condition wraps --- .../grid/DataGridConditionEditor.vue | 19 +++++++++++-------- .../__tests__/DataGridConditionEditor.spec.ts | 15 ++++++++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGridConditionEditor.vue b/apps/desktop/src/components/grid/DataGridConditionEditor.vue index 2532a70cea..3f8b709a2d 100644 --- a/apps/desktop/src/components/grid/DataGridConditionEditor.vue +++ b/apps/desktop/src/components/grid/DataGridConditionEditor.vue @@ -120,13 +120,14 @@ const previewStyle = computed(() => { }); const previewArrowStyle = computed(() => ({ top: `${historyPreview.value?.arrowTop ?? 0}px` })); -function createTextProbe(input: HTMLTextAreaElement, wrap: boolean, options: { width?: number; textIndent?: number } = {}) { +function createTextProbe(input: HTMLTextAreaElement, wrap: boolean, options: { width?: number; paddingLeft?: number; paddingRight?: number } = {}) { const probe = document.createElement(wrap ? "div" : "span"); const style = window.getComputedStyle(input); const width = options.width ?? input.clientWidth; - const textIndent = options.textIndent !== undefined ? `${options.textIndent}px` : style.textIndent; + const paddingLeft = options.paddingLeft !== undefined ? `${options.paddingLeft}px` : style.paddingLeft; + const paddingRight = options.paddingRight !== undefined ? `${options.paddingRight}px` : style.paddingRight; probe.textContent = input.value || input.placeholder || ""; - probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;${wrap ? `width:${width}px;white-space:pre-wrap;overflow-wrap:anywhere;padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};text-indent:${textIndent};` : "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:${width}px;white-space:pre-wrap;overflow-wrap:anywhere;padding:${style.paddingTop} ${paddingRight} ${style.paddingBottom} ${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; } @@ -140,7 +141,11 @@ function shouldExpand(input: HTMLTextAreaElement) { } function measureExpandedHeight(input: HTMLTextAreaElement, rect: typeof expandedRect.value) { - const probe = createTextProbe(input, true, { width: Math.max(1, rect.width - 8), textIndent: rect.prefix }); + const probe = createTextProbe(input, true, { + width: Math.max(1, rect.width - 8), + paddingLeft: rect.prefix + 2, + paddingRight: rect.suffix + 8, + }); const style = window.getComputedStyle(input); const lineHeight = Number.parseFloat(style.lineHeight) || 24; const contentHeight = probe.scrollHeight; @@ -843,8 +848,7 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH width: calc(100% - 1rem + var(--data-grid-expanded-scrollbar-offset)); max-width: none; margin-right: calc(-1 * var(--data-grid-expanded-scrollbar-offset)); - padding: 0 calc(var(--data-grid-condition-suffix-width) + 0.5rem) 0.0625rem 0.125rem; - text-indent: var(--data-grid-condition-prefix-indent); + padding: 0 calc(var(--data-grid-condition-suffix-width) + 0.5rem) 0.0625rem calc(var(--data-grid-condition-prefix-indent) + 0.125rem); overflow-x: hidden; overflow-y: auto; white-space: pre-wrap; @@ -908,8 +912,7 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH min-width: 0; height: auto; margin-right: calc(-1 * var(--data-grid-expanded-scrollbar-offset)); - padding: 0 calc(var(--data-grid-condition-suffix-width) + 0.5rem) 0.0625rem 0.125rem; - text-indent: var(--data-grid-condition-prefix-indent); + padding: 0 calc(var(--data-grid-condition-suffix-width) + 0.5rem) 0.0625rem calc(var(--data-grid-condition-prefix-indent) + 0.125rem); white-space: pre-wrap; overflow-wrap: anywhere; } diff --git a/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts index c3890cfb2a..47889a45b8 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts @@ -258,17 +258,22 @@ describe("DataGridConditionEditor quote completion", () => { expect(document.querySelector('[role="listbox"]')).toBeNull(); }); - it("keeps expanded input first-line indent and wraps long tokens", () => { + it("keeps the wrapped input caret aligned with its syntax highlight layer", () => { const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8"); const expandedInputCss = source.match(/\.data-grid-topbar-condition-input--expanded\s*\{(?[\s\S]*?)\n\}/)?.groups?.body; + const expandedHighlightCss = source.match(/\.data-grid-condition-highlight--expanded\s*\{(?[\s\S]*?)\n\}/)?.groups?.body; + const prefixPadding = "calc(var(--data-grid-condition-prefix-indent) + 0.125rem)"; expect(expandedInputCss).toContain("padding:"); - expect(expandedInputCss).toContain("0.0625rem 0.125rem"); - expect(expandedInputCss).toContain("text-indent: var(--data-grid-condition-prefix-indent)"); + expect(expandedInputCss).toContain(prefixPadding); + expect(expandedHighlightCss).toContain(prefixPadding); + expect(expandedInputCss).not.toContain("text-indent:"); + expect(expandedHighlightCss).not.toContain("text-indent:"); expect(expandedInputCss).toContain("overflow-wrap: anywhere"); expect(source).toContain("white-space:pre-wrap;overflow-wrap:anywhere;"); - expect(source).toContain("text-indent:${style.textIndent};"); - expect(source).toContain("textIndent: rect.prefix"); + expect(source).not.toContain("textIndent: rect.prefix"); + expect(source).toContain("paddingLeft: rect.prefix + 2"); + expect(source).toContain("paddingRight: rect.suffix + 8"); expect(source).toContain("width: Math.max(1, rect.width - 8)"); expect(source).toContain("function fitExpandedHeightToOverlay()"); expect(source).toContain("expandedHeight.value + overflow");