2021-06-21 21:11:53 +01:00
|
|
|
import { useEffect, useRef } from "preact/hooks";
|
2021-07-05 11:23:23 +01:00
|
|
|
|
|
|
|
import TextArea, {
|
|
|
|
DEFAULT_LINE_HEIGHT,
|
|
|
|
DEFAULT_TEXT_AREA_PADDING,
|
|
|
|
TextAreaProps,
|
|
|
|
TEXT_AREA_BORDER_WIDTH,
|
|
|
|
} from "../components/ui/TextArea";
|
|
|
|
|
2021-06-22 10:59:06 +01:00
|
|
|
import { internalSubscribe } from "./eventEmitter";
|
2021-07-03 15:23:29 +01:00
|
|
|
import { isTouchscreenDevice } from "./isTouchscreenDevice";
|
2021-06-21 14:20:29 +01:00
|
|
|
|
2021-07-05 11:23:23 +01:00
|
|
|
type TextAreaAutoSizeProps = Omit<
|
|
|
|
JSX.HTMLAttributes<HTMLTextAreaElement>,
|
|
|
|
"style" | "value"
|
|
|
|
> &
|
|
|
|
TextAreaProps & {
|
|
|
|
forceFocus?: boolean;
|
|
|
|
autoFocus?: boolean;
|
|
|
|
minHeight?: number;
|
|
|
|
maxRows?: number;
|
|
|
|
value: string;
|
2021-06-22 10:59:06 +01:00
|
|
|
|
2021-07-05 11:23:23 +01:00
|
|
|
id?: string;
|
|
|
|
};
|
2021-06-21 14:20:29 +01:00
|
|
|
|
|
|
|
export default function TextAreaAutoSize(props: TextAreaAutoSizeProps) {
|
2021-07-05 11:23:23 +01:00
|
|
|
const {
|
|
|
|
autoFocus,
|
|
|
|
minHeight,
|
|
|
|
maxRows,
|
|
|
|
value,
|
|
|
|
padding,
|
|
|
|
lineHeight,
|
|
|
|
hideBorder,
|
|
|
|
forceFocus,
|
|
|
|
children,
|
|
|
|
as,
|
|
|
|
...textAreaProps
|
|
|
|
} = props;
|
|
|
|
const line = lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
|
|
|
|
|
|
const heightPadding =
|
|
|
|
((padding ?? DEFAULT_TEXT_AREA_PADDING) +
|
|
|
|
(hideBorder ? 0 : TEXT_AREA_BORDER_WIDTH)) *
|
|
|
|
2;
|
|
|
|
const height = Math.max(
|
|
|
|
Math.min(value.split("\n").length, maxRows ?? Infinity) * line +
|
|
|
|
heightPadding,
|
|
|
|
minHeight ?? 0,
|
|
|
|
);
|
|
|
|
|
|
|
|
const ref = useRef<HTMLTextAreaElement>();
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (isTouchscreenDevice) return;
|
|
|
|
autoFocus && ref.current.focus();
|
|
|
|
}, [value]);
|
|
|
|
|
|
|
|
const inputSelected = () =>
|
|
|
|
["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? "");
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (forceFocus) {
|
|
|
|
ref.current.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isTouchscreenDevice) return;
|
|
|
|
if (autoFocus && !inputSelected()) {
|
|
|
|
ref.current.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// ? if you are wondering what this is
|
|
|
|
// ? it is a quick and dirty hack to fix
|
|
|
|
// ? value not setting correctly
|
|
|
|
// ? I have no clue what's going on
|
|
|
|
ref.current.value = value;
|
|
|
|
|
|
|
|
if (!autoFocus) return;
|
|
|
|
function keyDown(e: KeyboardEvent) {
|
|
|
|
if ((e.ctrlKey && e.key !== "v") || e.altKey || e.metaKey) return;
|
|
|
|
if (e.key.length !== 1) return;
|
|
|
|
if (ref && !inputSelected()) {
|
|
|
|
ref.current.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
document.body.addEventListener("keydown", keyDown);
|
|
|
|
return () => document.body.removeEventListener("keydown", keyDown);
|
|
|
|
}, [ref]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
function focus(id: string) {
|
|
|
|
if (id === props.id) {
|
|
|
|
ref.current.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return internalSubscribe("TextArea", "focus", focus);
|
|
|
|
}, [ref]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<TextArea
|
|
|
|
ref={ref}
|
|
|
|
value={value}
|
|
|
|
padding={padding}
|
|
|
|
style={{ height }}
|
|
|
|
hideBorder={hideBorder}
|
|
|
|
lineHeight={lineHeight}
|
|
|
|
{...textAreaProps}
|
|
|
|
/>
|
|
|
|
);
|
2021-06-21 14:20:29 +01:00
|
|
|
}
|