Skip to content

fix: Firefox textarea scrolling issues due to incorrect height calculation #1114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 2, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 25 additions & 41 deletions packages/web/src/components/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useLayoutEffect, useRef } from 'react';
import RowItem, { RowItemProps } from './RowItem';
import Help from './Help';
import { useTranslation } from 'react-i18next';
Expand All @@ -25,48 +25,26 @@ const MAX_HEIGHT = 300;
const Textarea: React.FC<Props> = (props) => {
const { t } = useTranslation();
const ref = useRef<HTMLTextAreaElement>(null);
const [isMax, setIsMax] = useState(false);
const _maxHeight = props.maxHeight || MAX_HEIGHT;

useEffect(() => {
if (!ref.current) {
return;
}
const maxHeight = props.maxHeight || MAX_HEIGHT;

useLayoutEffect(() => {
if (!ref.current) return;
// Reset the height to auto to calculate the scroll height
ref.current.style.height = 'auto';
ref.current.style.overflowY = 'hidden';

if (_maxHeight > 0 && ref.current.scrollHeight > _maxHeight) {
ref.current.style.height = _maxHeight + 'px';
setIsMax(true);
} else {
ref.current.style.height = ref.current.scrollHeight + 'px';
setIsMax(false);
}
}, [props.value, _maxHeight]);

useEffect(() => {
const current = ref.current;
if (!current) {
return;
}

const listener = (e: DocumentEventMap['keypress']) => {
if (props.onEnter) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
props.onEnter();
}
}
};
// Ensure the layout is updated before calculating the scroll height
// due to the bug in Firefox:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1795904
// https://bugzilla.mozilla.org/show_bug.cgi?id=1787062
void ref.current.scrollHeight;

current.addEventListener('keypress', listener);

return () => {
if (current) {
current.removeEventListener('keypress', listener);
}
};
}, [ref, props]);
// Set the height to match content, up to max height
const scrollHeight = ref.current.scrollHeight;
const isMax = maxHeight > 0 && scrollHeight > maxHeight;
ref.current.style.height = (isMax ? maxHeight : scrollHeight) + 'px';
ref.current.style.overflowY = isMax ? 'auto' : 'hidden';
}, [props.value, maxHeight]);

return (
<RowItem notItem={props.notItem}>
Expand All @@ -93,13 +71,19 @@ const Textarea: React.FC<Props> = (props) => {
className={`${
props.className ?? ''
} w-full resize-none rounded p-1.5 outline-none ${
isMax ? 'overflow-y-auto' : 'overflow-hidden'
} ${
props.noBorder ? 'border-0 focus:ring-0 ' : 'border border-black/30'
} ${props.disabled ? 'bg-gray-200 ' : ''}`}
rows={props.rows ?? 1}
placeholder={props.placeholder || t('common.enter_text')}
value={props.value}
onKeyDown={(e) => {
// keyCode is deprecated, but used for some browsers to handle IME input
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
if (props.onEnter && e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
props.onEnter();
}
}}
onChange={(e) => {
props.onChange(e.target.value);
}}
Expand Down
Loading