2021-07-06 06:04:51 -04:00
|
|
|
|
import styled from "styled-components";
|
|
|
|
|
|
2021-07-06 14:29:27 -04:00
|
|
|
|
import { RefObject } from "preact";
|
2021-07-06 06:04:51 -04:00
|
|
|
|
import { useEffect, useLayoutEffect, useRef } from "preact/hooks";
|
2021-07-05 06:23:23 -04:00
|
|
|
|
|
2021-07-06 11:07:04 -04:00
|
|
|
|
import TextArea, { TextAreaProps } from "../components/ui/TextArea";
|
2021-07-05 06:23:23 -04:00
|
|
|
|
|
2021-06-22 05:59:06 -04:00
|
|
|
|
import { internalSubscribe } from "./eventEmitter";
|
2021-07-03 10:23:29 -04:00
|
|
|
|
import { isTouchscreenDevice } from "./isTouchscreenDevice";
|
2021-06-21 09:20:29 -04:00
|
|
|
|
|
2021-07-05 06:23:23 -04:00
|
|
|
|
type TextAreaAutoSizeProps = Omit<
|
2021-07-05 06:25:20 -04:00
|
|
|
|
JSX.HTMLAttributes<HTMLTextAreaElement>,
|
2021-08-05 09:47:00 -04:00
|
|
|
|
"style" | "value" | "onChange" | "children" | "as"
|
2021-07-05 06:23:23 -04:00
|
|
|
|
> &
|
2021-07-05 06:25:20 -04:00
|
|
|
|
TextAreaProps & {
|
|
|
|
|
forceFocus?: boolean;
|
|
|
|
|
autoFocus?: boolean;
|
|
|
|
|
minHeight?: number;
|
|
|
|
|
maxRows?: number;
|
|
|
|
|
value: string;
|
2021-06-22 05:59:06 -04:00
|
|
|
|
|
2021-07-05 06:25:20 -04:00
|
|
|
|
id?: string;
|
2021-07-06 06:04:51 -04:00
|
|
|
|
|
|
|
|
|
onChange?: (ev: JSX.TargetedEvent<HTMLTextAreaElement, Event>) => void;
|
2021-07-05 06:25:20 -04:00
|
|
|
|
};
|
2021-06-21 09:20:29 -04:00
|
|
|
|
|
2021-07-06 06:04:51 -04:00
|
|
|
|
const Container = styled.div`
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
`;
|
|
|
|
|
|
2021-07-06 14:29:27 -04:00
|
|
|
|
const Ghost = styled.div<{ lineHeight: string; maxRows: number }>`
|
2021-07-06 06:04:51 -04:00
|
|
|
|
flex: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
> div {
|
|
|
|
|
width: 100%;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-break: break-all;
|
2021-07-06 14:29:27 -04:00
|
|
|
|
|
2021-07-06 06:04:51 -04:00
|
|
|
|
top: 0;
|
|
|
|
|
position: absolute;
|
|
|
|
|
font-size: var(--text-size);
|
|
|
|
|
line-height: ${(props) => props.lineHeight};
|
|
|
|
|
|
2021-07-06 14:29:27 -04:00
|
|
|
|
max-height: calc(
|
|
|
|
|
calc(${(props) => props.lineHeight} * ${(props) => props.maxRows})
|
|
|
|
|
);
|
2021-07-06 06:04:51 -04:00
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
2021-06-21 09:20:29 -04:00
|
|
|
|
export default function TextAreaAutoSize(props: TextAreaAutoSizeProps) {
|
2021-07-05 06:25:20 -04:00
|
|
|
|
const {
|
|
|
|
|
autoFocus,
|
|
|
|
|
minHeight,
|
|
|
|
|
maxRows,
|
|
|
|
|
value,
|
|
|
|
|
padding,
|
|
|
|
|
lineHeight,
|
|
|
|
|
hideBorder,
|
|
|
|
|
forceFocus,
|
2021-07-06 03:56:30 -04:00
|
|
|
|
onChange,
|
2021-07-05 06:25:20 -04:00
|
|
|
|
...textAreaProps
|
|
|
|
|
} = props;
|
|
|
|
|
|
2021-07-06 11:07:04 -04:00
|
|
|
|
const ref = useRef<HTMLTextAreaElement>() as RefObject<HTMLTextAreaElement>;
|
|
|
|
|
const ghost = useRef<HTMLDivElement>() as RefObject<HTMLDivElement>;
|
2021-07-06 06:04:51 -04:00
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
2021-07-06 11:07:04 -04:00
|
|
|
|
if (ref.current && ghost.current) {
|
2021-07-10 10:57:29 -04:00
|
|
|
|
ref.current.style.height = `${ghost.current.clientHeight}px`;
|
2021-07-06 11:07:04 -04:00
|
|
|
|
}
|
2021-07-06 06:04:51 -04:00
|
|
|
|
}, [ghost, props.value]);
|
2021-07-05 06:25:20 -04:00
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isTouchscreenDevice) return;
|
2021-07-06 11:07:04 -04:00
|
|
|
|
autoFocus && ref.current && ref.current.focus();
|
2021-08-05 09:47:00 -04:00
|
|
|
|
}, [value, autoFocus]);
|
2021-07-05 06:25:20 -04:00
|
|
|
|
|
|
|
|
|
const inputSelected = () =>
|
|
|
|
|
["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? "");
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2021-07-06 11:07:04 -04:00
|
|
|
|
if (!ref.current) return;
|
2021-07-05 06:25:20 -04:00
|
|
|
|
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
|
2021-08-30 13:01:32 -04:00
|
|
|
|
// ref.current.value = value;
|
|
|
|
|
// * commented out of 30-08-21
|
|
|
|
|
// * hopefully nothing breaks :v
|
2021-07-05 06:25:20 -04:00
|
|
|
|
|
|
|
|
|
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()) {
|
2021-07-06 11:07:04 -04:00
|
|
|
|
ref.current!.focus();
|
2021-07-05 06:25:20 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.body.addEventListener("keydown", keyDown);
|
|
|
|
|
return () => document.body.removeEventListener("keydown", keyDown);
|
2021-08-05 09:47:00 -04:00
|
|
|
|
}, [ref, autoFocus, forceFocus, value]);
|
2021-07-05 06:25:20 -04:00
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2021-07-06 11:07:04 -04:00
|
|
|
|
if (!ref.current) return;
|
2021-07-05 06:25:20 -04:00
|
|
|
|
function focus(id: string) {
|
|
|
|
|
if (id === props.id) {
|
2021-07-06 11:07:04 -04:00
|
|
|
|
ref.current!.focus();
|
2021-07-05 06:25:20 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 09:47:00 -04:00
|
|
|
|
return internalSubscribe(
|
|
|
|
|
"TextArea",
|
|
|
|
|
"focus",
|
|
|
|
|
focus as (...args: unknown[]) => void,
|
|
|
|
|
);
|
|
|
|
|
}, [props.id, ref]);
|
2021-07-05 06:25:20 -04:00
|
|
|
|
|
|
|
|
|
return (
|
2021-07-06 06:04:51 -04:00
|
|
|
|
<Container>
|
|
|
|
|
<TextArea
|
|
|
|
|
ref={ref}
|
|
|
|
|
value={value}
|
|
|
|
|
padding={padding}
|
2021-07-06 09:58:54 -04:00
|
|
|
|
style={{ minHeight }}
|
2021-07-06 06:04:51 -04:00
|
|
|
|
hideBorder={hideBorder}
|
|
|
|
|
lineHeight={lineHeight}
|
|
|
|
|
onChange={(ev) => {
|
|
|
|
|
onChange && onChange(ev);
|
|
|
|
|
}}
|
|
|
|
|
{...textAreaProps}
|
|
|
|
|
/>
|
2021-07-06 14:29:27 -04:00
|
|
|
|
<Ghost
|
|
|
|
|
lineHeight={lineHeight ?? "var(--textarea-line-height)"}
|
|
|
|
|
maxRows={maxRows ?? 5}>
|
2021-07-06 06:04:51 -04:00
|
|
|
|
<div ref={ghost} style={{ padding }}>
|
|
|
|
|
{props.value
|
|
|
|
|
? props.value
|
|
|
|
|
.split("\n")
|
|
|
|
|
.map((x) => `\u200e${x}`)
|
|
|
|
|
.join("\n")
|
|
|
|
|
: undefined ?? "\n"}
|
|
|
|
|
</div>
|
|
|
|
|
</Ghost>
|
|
|
|
|
</Container>
|
2021-07-05 06:25:20 -04:00
|
|
|
|
);
|
2021-06-21 09:20:29 -04:00
|
|
|
|
}
|