From 3656c8458fa955570dff2e0d6cb076e3e5a8e7e9 Mon Sep 17 00:00:00 2001 From: leedom Date: Fri, 7 Apr 2023 12:17:37 +0800 Subject: [PATCH] feat: textarea with adaptive height --- app/calcTextareaHeight.js | 104 -------------------------------------- app/components/chat.tsx | 24 ++++++++- app/components/home.tsx | 2 - app/utils.ts | 4 ++ tsconfig.json | 2 +- 5 files changed, 28 insertions(+), 108 deletions(-) delete mode 100644 app/calcTextareaHeight.js diff --git a/app/calcTextareaHeight.js b/app/calcTextareaHeight.js deleted file mode 100644 index 2324473dc..000000000 --- a/app/calcTextareaHeight.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * fork from element-ui - * https://github.com/ElemeFE/element/blob/master/packages/input/src/calcTextareaHeight.js - */ - -let hiddenTextarea; - -const HIDDEN_STYLE = ` - height:0 !important; - visibility:hidden !important; - overflow:hidden !important; - position:absolute !important; - z-index:-1000 !important; - top:0 !important; - right:0 !important -`; - -const CONTEXT_STYLE = [ - "letter-spacing", - "line-height", - "padding-top", - "padding-bottom", - "font-family", - "font-weight", - "font-size", - "text-rendering", - "text-transform", - "width", - "text-indent", - "padding-left", - "padding-right", - "border-width", - "box-sizing", -]; - -function calculateNodeStyling(targetElement) { - const style = window.getComputedStyle(targetElement); - - const boxSizing = style.getPropertyValue("box-sizing"); - - const paddingSize = - parseFloat(style.getPropertyValue("padding-bottom")) + - parseFloat(style.getPropertyValue("padding-top")); - - const borderSize = - parseFloat(style.getPropertyValue("border-bottom-width")) + - parseFloat(style.getPropertyValue("border-top-width")); - - const contextStyle = CONTEXT_STYLE.map( - (name) => `${name}:${style.getPropertyValue(name)}`, - ).join(";"); - - return { contextStyle, paddingSize, borderSize, boxSizing }; -} - -export default function calcTextareaHeight( - targetElement, - minRows = 2, - maxRows = 4, -) { - if (!hiddenTextarea) { - hiddenTextarea = document.createElement("textarea"); - document.body.appendChild(hiddenTextarea); - } - - let { paddingSize, borderSize, boxSizing, contextStyle } = - calculateNodeStyling(targetElement); - - hiddenTextarea.setAttribute("style", `${contextStyle};${HIDDEN_STYLE}`); - hiddenTextarea.value = targetElement.value || targetElement.placeholder || ""; - - let height = hiddenTextarea.scrollHeight; - const result = {}; - - if (boxSizing === "border-box") { - height = height + borderSize; - } else if (boxSizing === "content-box") { - height = height - paddingSize; - } - - hiddenTextarea.value = ""; - let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; - - if (minRows !== null) { - let minHeight = singleRowHeight * minRows; - if (boxSizing === "border-box") { - minHeight = minHeight + paddingSize + borderSize; - } - height = Math.max(minHeight, height); - result.minHeight = `${minHeight}px`; - } - if (maxRows !== null) { - let maxHeight = singleRowHeight * maxRows; - if (boxSizing === "border-box") { - maxHeight = maxHeight + paddingSize + borderSize; - } - height = Math.min(maxHeight, height); - } - result.height = `${height}px`; - hiddenTextarea.parentNode && - hiddenTextarea.parentNode.removeChild(hiddenTextarea); - hiddenTextarea = null; - return result; -} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 4ab616444..b631d534e 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -41,6 +41,8 @@ import chatStyle from "./chat.module.scss"; import { Input, Modal, showModal, showToast } from "./ui-lib"; +import calcTextareaHeight from "../calcTextareaHeight"; + const Markdown = dynamic( async () => memo((await import("./markdown")).Markdown), { @@ -331,6 +333,10 @@ function useScrollToBottom() { export function Chat(props: { showSideBar?: () => void; sideBarShowing?: boolean; + autoSize: { + minRows: number; + maxRows?: number; + }; }) { type RenderMessage = Message & { preview?: boolean }; @@ -347,6 +353,7 @@ export function Chat(props: { const { submitKey, shouldSubmit } = useSubmitHandler(); const { scrollRef, setAutoScroll } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); + const [textareaStyle, setTextareaStyle] = useState({}); const onChatBodyScroll = (e: HTMLElement) => { const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; @@ -380,6 +387,16 @@ export function Chat(props: { dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; }; + // textarea has an adaptive height + const resizeTextarea = () => { + const dom = inputRef.current; + if (!dom) return; + const { minRows, maxRows } = props.autoSize; + setTimeout(() => { + setTextareaStyle(calcTextareaHeight(dom, minRows, maxRows)); + }, 50); + }; + // only search prompts when user input is short const SEARCH_TEXT_LIMIT = 30; const onInput = (text: string) => { @@ -504,6 +521,11 @@ export function Chat(props: { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Textarea Adaptive height + useEffect(() => { + resizeTextarea(); + }); + return (
@@ -659,8 +681,8 @@ export function Chat(props: {