56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
import type { ForwardedRef } from 'react';
|
|
import React, { forwardRef, useEffect, useLayoutEffect, useRef } from 'react';
|
|
import { mergeRefs } from '@react-aria/utils';
|
|
import { strictAssert } from '../util/assert';
|
|
import type { PropsType } from './Input';
|
|
import { Input } from './Input';
|
|
|
|
export const AutoSizeTextArea = forwardRef(function AutoSizeTextArea(
|
|
props: PropsType,
|
|
ref: ForwardedRef<HTMLTextAreaElement>
|
|
): JSX.Element {
|
|
const ownRef = useRef<HTMLTextAreaElement | null>(null);
|
|
const textareaRef = mergeRefs(ownRef, ref);
|
|
|
|
function update(textarea: HTMLTextAreaElement) {
|
|
const styles = window.getComputedStyle(textarea);
|
|
const { scrollHeight } = textarea;
|
|
let height = 'calc(';
|
|
height += `${scrollHeight}px`;
|
|
if (styles.boxSizing === 'border-box') {
|
|
height += ` + ${styles.borderTopWidth} + ${styles.borderBottomWidth}`;
|
|
} else {
|
|
height += ` - ${styles.paddingTop} - ${styles.paddingBottom}`;
|
|
}
|
|
height += ')';
|
|
Object.assign(textarea.style, {
|
|
height,
|
|
overflow: 'hidden',
|
|
resize: 'none',
|
|
});
|
|
}
|
|
|
|
useEffect(() => {
|
|
strictAssert(ownRef.current, 'inputRef.current should be defined');
|
|
const textarea = ownRef.current;
|
|
function onInput() {
|
|
textarea.style.height = 'auto';
|
|
requestAnimationFrame(() => update(textarea));
|
|
}
|
|
textarea.addEventListener('input', onInput);
|
|
return () => {
|
|
textarea.removeEventListener('input', onInput);
|
|
};
|
|
}, []);
|
|
|
|
useLayoutEffect(() => {
|
|
strictAssert(ownRef.current, 'inputRef.current should be defined');
|
|
const textarea = ownRef.current;
|
|
textarea.style.height = 'auto';
|
|
update(textarea);
|
|
}, [props.value]);
|
|
|
|
return <Input ref={textareaRef} {...props} forceTextarea />;
|
|
});
|