mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Fix cumulative highlight drift with comprehensive style sync
Fixed the gradual highlight misalignment that gets worse with each line by ensuring perfect style synchronization between layers: 1. Comprehensive style synchronization: - Replaced simple line height sync with full style consistency - Copies all computed styles that affect text layout from textarea - Includes fontSize, fontFamily, letterSpacing, wordSpacing - Ensures identical padding values to prevent cumulative drift 2. Box model consistency: - Added explicit boxSizing: 'border-box' to display layer - Matches textarea box model to prevent layout differences - Eliminates padding/border calculation discrepancies 3. Rounded line height calculations: - Uses Math.round() to prevent fractional pixel accumulation - Eliminates subpixel differences that compound over multiple lines - Ensures consistent line spacing throughout the text 4. Text rendering consistency: - Copies textRendering, webkitFontSmoothing, mozOsxFontSmoothing - Prevents browser-specific text rendering differences - Eliminates subpixel positioning variations This should eliminate the gradual 'walking away' of highlights and fix the bottom text cutoff issue by ensuring perfect layer alignment.
This commit is contained in:
@@ -204,7 +204,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const fontSize = parseFloat(computedStyle.fontSize);
|
||||
const lineHeightValue = computedStyle.lineHeight;
|
||||
const lineHeight = lineHeightValue === "normal" ? fontSize * 1.2 : parseFloat(lineHeightValue);
|
||||
const lineHeight = Math.round(lineHeightValue === "normal" ? fontSize * 1.2 : parseFloat(lineHeightValue));
|
||||
const minHeight = rows * lineHeight;
|
||||
const maxHeight = 300;
|
||||
|
||||
@@ -291,17 +291,31 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
}, [rows]);
|
||||
|
||||
// Ensure line height consistency between layers
|
||||
const ensureLineHeightConsistency = useCallback(() => {
|
||||
// Comprehensive style synchronization between layers
|
||||
const ensureStyleConsistency = useCallback(() => {
|
||||
if (hiddenTextareaRef.current && displayRef.current) {
|
||||
const textarea = hiddenTextareaRef.current;
|
||||
const display = displayRef.current;
|
||||
|
||||
// Get the computed line height from textarea
|
||||
// Get all computed styles that affect text layout
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const lineHeightValue = computedStyle.lineHeight;
|
||||
|
||||
// Apply the same line height to display layer
|
||||
display.style.lineHeight = lineHeightValue;
|
||||
// Apply critical layout styles to ensure perfect alignment
|
||||
display.style.lineHeight = computedStyle.lineHeight;
|
||||
display.style.fontSize = computedStyle.fontSize;
|
||||
display.style.fontFamily = computedStyle.fontFamily;
|
||||
display.style.letterSpacing = computedStyle.letterSpacing;
|
||||
display.style.wordSpacing = computedStyle.wordSpacing;
|
||||
// Ensure identical padding (critical for alignment)
|
||||
display.style.paddingTop = computedStyle.paddingTop;
|
||||
display.style.paddingRight = computedStyle.paddingRight;
|
||||
display.style.paddingBottom = computedStyle.paddingBottom;
|
||||
display.style.paddingLeft = computedStyle.paddingLeft;
|
||||
|
||||
// Ensure identical text rendering to prevent subpixel drift
|
||||
display.style.textRendering = computedStyle.textRendering;
|
||||
display.style.webkitFontSmoothing = computedStyle.webkitFontSmoothing;
|
||||
display.style.mozOsxFontSmoothing = computedStyle.mozOsxFontSmoothing;
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -313,7 +327,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
// Use ResizeObserver to detect when textarea dimensions change
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
ensureStyleConsistency();
|
||||
});
|
||||
|
||||
resizeObserver.observe(textarea);
|
||||
@@ -324,7 +338,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
if (textarea.scrollHeight !== lastScrollHeight) {
|
||||
lastScrollHeight = textarea.scrollHeight;
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
ensureStyleConsistency();
|
||||
}
|
||||
requestAnimationFrame(checkScrollHeight);
|
||||
};
|
||||
@@ -376,7 +390,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const fontSize = parseFloat(computedStyle.fontSize);
|
||||
const lineHeightValue = computedStyle.lineHeight;
|
||||
const lineHeight = lineHeightValue === "normal" ? fontSize * 1.2 : parseFloat(lineHeightValue);
|
||||
const lineHeight = Math.round(lineHeightValue === "normal" ? fontSize * 1.2 : parseFloat(lineHeightValue));
|
||||
const minHeight = rows * lineHeight;
|
||||
|
||||
textarea.style.height = "auto";
|
||||
@@ -384,7 +398,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
display.style.height = `${minHeight}px`;
|
||||
|
||||
syncDisplayHeight();
|
||||
ensureLineHeightConsistency();
|
||||
ensureStyleConsistency();
|
||||
}
|
||||
},
|
||||
}), []);
|
||||
@@ -844,7 +858,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();
|
||||
ensureStyleConsistency();
|
||||
requestAnimationFrame(() => syncDisplayHeight());
|
||||
}, [onChange, syncDisplayHeight]);
|
||||
|
||||
@@ -1089,6 +1103,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
lineHeight: '1.5', // Match leading-relaxed
|
||||
padding: '12px 80px 12px 12px', // Match top and bottom padding: 12px each
|
||||
margin: '0',
|
||||
boxSizing: "border-box", // Match textarea box model
|
||||
boxSizing: 'border-box',
|
||||
whiteSpace: 'pre-wrap', // Match visual display
|
||||
wordWrap: 'break-word',
|
||||
@@ -1114,6 +1129,7 @@ export const RichChatInput = forwardRef<RichChatInputRef, RichChatInputProps>(({
|
||||
lineHeight: '1.5', // Match textarea line height
|
||||
padding: '12px 80px 12px 12px', // Match textarea padding: 12px top and bottom
|
||||
margin: '0',
|
||||
boxSizing: "border-box", // Match textarea box model
|
||||
whiteSpace: 'pre-wrap', // Match textarea
|
||||
wordWrap: 'break-word',
|
||||
// Hide scrollbars but keep scrolling functionality
|
||||
|
||||
Reference in New Issue
Block a user