import styled from "styled-components"; import { RefObject } from "preact"; import { useEffect, useLayoutEffect, useRef } from "preact/hooks"; import TextArea, { TextAreaProps } from "../components/ui/TextArea"; import { internalSubscribe } from "./eventEmitter"; import { isTouchscreenDevice } from "./isTouchscreenDevice"; type TextAreaAutoSizeProps = Omit< JSX.HTMLAttributes, "style" | "value" | "onChange" > & TextAreaProps & { forceFocus?: boolean; autoFocus?: boolean; minHeight?: number; maxRows?: number; value: string; id?: string; onChange?: (ev: JSX.TargetedEvent) => void; }; const Container = styled.div` flex-grow: 1; display: flex; flex-direction: column; `; const Ghost = styled.div<{ lineHeight: string; maxRows: number }>` flex: 0; width: 100%; overflow: hidden; visibility: hidden; position: relative; > div { width: 100%; white-space: pre-wrap; word-break: break-all; top: 0; position: absolute; font-size: var(--text-size); line-height: ${(props) => props.lineHeight}; max-height: calc( calc(${(props) => props.lineHeight} * ${(props) => props.maxRows}) ); } `; export default function TextAreaAutoSize(props: TextAreaAutoSizeProps) { const { autoFocus, minHeight, maxRows, value, padding, lineHeight, hideBorder, forceFocus, children, as, onChange, ...textAreaProps } = props; const ref = useRef() as RefObject; const ghost = useRef() as RefObject; useLayoutEffect(() => { if (ref.current && ghost.current) { ref.current.style.height = ghost.current.clientHeight + "px"; } }, [ghost, props.value]); useEffect(() => { if (isTouchscreenDevice) return; autoFocus && ref.current && ref.current.focus(); }, [value]); const inputSelected = () => ["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? ""); useEffect(() => { if (!ref.current) return; 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(() => { if (!ref.current) return; function focus(id: string) { if (id === props.id) { ref.current!.focus(); } } return internalSubscribe("TextArea", "focus", focus); }, [ref]); return (