mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Fix highlight offset by ensuring line height consistency
Fixed the issue where highlights (spell check, selection) were offset from text due to inconsistent line height calculations between layers: 1. Dynamic line height calculation: - Replaced hardcoded 21px with computed line height from actual textarea - Uses window.getComputedStyle() to get real rendered line height - Handles both 'normal' and explicit line height values properly 2. Explicit line height synchronization: - Sets both textarea and display layers to use identical pixel line heights - Added ensureLineHeightConsistency() function for ongoing sync - Called after every height sync to maintain consistency 3. Consistent cursor positioning: - Updated resetHeight method to use calculated line height - All line-based calculations now use the same computed value - Eliminates discrepancies between layers This ensures that spell check highlights, selection highlights, and cursor positioning are perfectly aligned with the actual rendered text, regardless of font size, browser differences, or CSS inheritance.
This commit is contained in:
@@ -200,7 +200,11 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
const display = displayRef.current;
|
||||
|
||||
// Calculate line height more accurately based on actual font metrics
|
||||
const lineHeight = 21; // 14px * 1.5 line-height = 21px
|
||||
// Calculate actual line height from the textarea
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const fontSize = parseFloat(computedStyle.fontSize);
|
||||
const lineHeightValue = computedStyle.lineHeight;
|
||||
const lineHeight = lineHeightValue === "normal" ? fontSize * 1.2 : parseFloat(lineHeightValue);
|
||||
const minHeight = rows * lineHeight;
|
||||
const maxHeight = 300;
|
||||
|
||||
@@ -218,10 +222,12 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
|
||||
// Always update heights to ensure consistency
|
||||
textarea.style.height = `${finalHeight}px`;
|
||||
textarea.style.lineHeight = `${lineHeight}px`;
|
||||
textarea.style.minHeight = `${minHeight}px`;
|
||||
textarea.style.maxHeight = `${maxHeight}px`;
|
||||
|
||||
display.style.height = `${finalHeight}px`;
|
||||
display.style.lineHeight = `${lineHeight}px`;
|
||||
display.style.minHeight = `${minHeight}px`;
|
||||
display.style.maxHeight = `${maxHeight}px`;
|
||||
|
||||
@@ -284,6 +290,21 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
}
|
||||
}, [rows]);
|
||||
|
||||
// Ensure line height consistency between layers
|
||||
const ensureLineHeightConsistency = useCallback(() => {
|
||||
if (hiddenTextareaRef.current && displayRef.current) {
|
||||
const textarea = hiddenTextareaRef.current;
|
||||
const display = displayRef.current;
|
||||
|
||||
// Get the computed line height from textarea
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const lineHeightValue = computedStyle.lineHeight;
|
||||
|
||||
// Apply the same line height to display layer
|
||||
display.style.lineHeight = lineHeightValue;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Monitor textarea for any changes that might affect height
|
||||
const monitorTextareaChanges = useCallback(() => {
|
||||
if (hiddenTextareaRef.current) {
|
||||
@@ -292,6 +313,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
// Use ResizeObserver to detect when textarea dimensions change
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
});
|
||||
|
||||
resizeObserver.observe(textarea);
|
||||
@@ -302,6 +324,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
if (textarea.scrollHeight !== lastScrollHeight) {
|
||||
lastScrollHeight = textarea.scrollHeight;
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
}
|
||||
requestAnimationFrame(checkScrollHeight);
|
||||
};
|
||||
@@ -349,7 +372,11 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
if (hiddenTextareaRef.current && displayRef.current) {
|
||||
const textarea = hiddenTextareaRef.current;
|
||||
const display = displayRef.current;
|
||||
const lineHeight = 21;
|
||||
// Calculate actual line height from the textarea
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const fontSize = parseFloat(computedStyle.fontSize);
|
||||
const lineHeightValue = computedStyle.lineHeight;
|
||||
const lineHeight = lineHeightValue === "normal" ? fontSize * 1.2 : parseFloat(lineHeightValue);
|
||||
const minHeight = rows * lineHeight;
|
||||
|
||||
textarea.style.height = "auto";
|
||||
@@ -357,6 +384,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
display.style.height = `${minHeight}px`;
|
||||
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
}
|
||||
},
|
||||
}), []);
|
||||
@@ -816,6 +844,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
// Sync display height immediately for better responsiveness
|
||||
// Use both immediate sync and deferred sync for reliability
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
requestAnimationFrame(() => syncDisplayHeight());
|
||||
}, [onChange, syncDisplayHeight]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user