瀏覽代碼

feat: textarea with adaptive height

leedom 2 年之前
父節點
當前提交
3656c8458f
共有 5 個文件被更改,包括 28 次插入108 次删除
  1. 0 104
      app/calcTextareaHeight.js
  2. 23 1
      app/components/chat.tsx
  3. 0 2
      app/components/home.tsx
  4. 4 0
      app/utils.ts
  5. 1 1
      tsconfig.json

+ 0 - 104
app/calcTextareaHeight.js

@@ -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;
-}

+ 23 - 1
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 (
     <div className={styles.chat} key={session.id}>
       <div className={styles["window-header"]}>
@@ -659,8 +681,8 @@ export function Chat(props: {
           <textarea
             ref={inputRef}
             className={styles["chat-input"]}
+            style={textareaStyle}
             placeholder={Locale.Chat.Input(submitKey)}
-            rows={2}
             onInput={(e) => onInput(e.currentTarget.value)}
             value={userInput}
             onKeyDown={onInputKeyDown}

+ 0 - 2
app/components/home.tsx

@@ -25,8 +25,6 @@ import dynamic from "next/dynamic";
 import { REPO_URL } from "../constant";
 import { ErrorBoundary } from "./error";
 
-import calcTextareaHeight from "../calcTextareaHeight";
-
 export function Loading(props: { noLogo?: boolean }) {
   return (
     <div className={styles["loading-content"]}>

+ 4 - 0
app/utils.ts

@@ -50,6 +50,10 @@ export function isMobileScreen() {
   return window.innerWidth <= 600;
 }
 
+export function isFirefox() {
+  return /firefox/i.test(navigator.userAgent);
+}
+
 export function selectOrCopy(el: HTMLElement, content: string) {
   const currentSelection = window.getSelection();
 

+ 1 - 1
tsconfig.json

@@ -23,6 +23,6 @@
       "@/*": ["./*"]
     }
   },
-  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/calcTextareaHeight.ts"],
   "exclude": ["node_modules"]
 }