mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-23 19:45:05 +03:00
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import useCombinedRefs from '@/core/hooks/useCombinedRefs';
|
|
import { id } from '@/utils/utils';
|
|
|
|
interface AutoResizeInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
autoresizing?: true;
|
|
placeholderMinWidth?: boolean;
|
|
onAutosize?: (width: number) => void;
|
|
}
|
|
|
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
autoresizing?: false;
|
|
placeholderMinWidth?: false;
|
|
onAutosize?: undefined;
|
|
}
|
|
|
|
export function Input({
|
|
autoresizing = false,
|
|
placeholderMinWidth = false,
|
|
onAutosize,
|
|
style: inputStyle,
|
|
...inputProps
|
|
}: AutoResizeInputProps | InputProps) {
|
|
const [inputWidth, setInputWidth] = useState(0);
|
|
|
|
const sizerRef = useRef<HTMLDivElement>(null);
|
|
const placeholderSizerRef = useRef<HTMLDivElement>(null);
|
|
const [inputRef, inputElement] = useCombinedRefs<HTMLInputElement>();
|
|
|
|
const sizerStyle: React.CSSProperties = {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
visibility: 'hidden',
|
|
height: 0,
|
|
overflow: 'scroll',
|
|
whiteSpace: 'pre',
|
|
};
|
|
|
|
const copyStyles = useCallback((styles: CSSStyleDeclaration, node: HTMLElement) => {
|
|
node.style.fontSize = styles.fontSize;
|
|
node.style.fontFamily = styles.fontFamily;
|
|
node.style.fontWeight = styles.fontWeight;
|
|
node.style.fontStyle = styles.fontStyle;
|
|
node.style.letterSpacing = styles.letterSpacing;
|
|
node.style.textTransform = styles.textTransform;
|
|
}, []);
|
|
|
|
const updateInputWidth = useCallback(() => {
|
|
if (!sizerRef.current || typeof sizerRef.current.scrollWidth === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
let newInputWidth: number;
|
|
|
|
if (inputProps.placeholder && (!inputProps.value || (inputProps.value && placeholderMinWidth))) {
|
|
const sizerWidth = sizerRef.current.scrollWidth;
|
|
const placeholderWidth = placeholderSizerRef.current?.scrollWidth || 0;
|
|
newInputWidth = Math.max(sizerWidth, placeholderWidth) + 2;
|
|
} else {
|
|
newInputWidth = sizerRef.current.scrollWidth + 2;
|
|
}
|
|
|
|
|
|
if (newInputWidth !== inputWidth) {
|
|
setInputWidth(newInputWidth);
|
|
onAutosize?.(newInputWidth);
|
|
}
|
|
}, [inputProps.placeholder, inputProps.value, inputProps.type, placeholderMinWidth, inputWidth, onAutosize]);
|
|
|
|
const copyInputStyles = useCallback(() => {
|
|
if (!inputElement.current || !window.getComputedStyle) {
|
|
return;
|
|
}
|
|
|
|
const inputStyles = window.getComputedStyle(inputElement.current);
|
|
if (!inputStyles) {
|
|
return;
|
|
}
|
|
|
|
copyStyles(inputStyles, sizerRef.current!);
|
|
if (placeholderSizerRef.current) {
|
|
copyStyles(inputStyles, placeholderSizerRef.current);
|
|
}
|
|
}, [inputElement]);
|
|
|
|
useEffect(() => {
|
|
if (autoresizing) {
|
|
copyInputStyles();
|
|
updateInputWidth();
|
|
}
|
|
}, [autoresizing, copyInputStyles, updateInputWidth]);
|
|
|
|
useEffect(() => {
|
|
if (autoresizing) {
|
|
updateInputWidth();
|
|
}
|
|
}, [inputProps.value, inputProps.placeholder, autoresizing, updateInputWidth]);
|
|
|
|
return (
|
|
<>
|
|
<input
|
|
{...inputProps}
|
|
ref={inputRef}
|
|
style={{
|
|
boxSizing: 'content-box',
|
|
width: autoresizing ? `${inputWidth}px` : undefined,
|
|
...inputStyle,
|
|
}}
|
|
/>
|
|
{autoresizing && createPortal(
|
|
<>
|
|
<div ref={sizerRef} style={sizerStyle}>
|
|
{inputProps.defaultValue || inputProps.value || ''}
|
|
</div>
|
|
{inputProps.placeholder && (
|
|
<div ref={placeholderSizerRef} style={sizerStyle}>
|
|
{inputProps.placeholder}
|
|
</div>
|
|
)}
|
|
</>,
|
|
id("root")
|
|
)}
|
|
</>
|
|
);
|
|
}
|