packages/bui-core/src/TextArea/TextArea.tsx (143 lines of code) (raw):

import { isMini, useForkRef, useValue } from '@bifrostui/utils'; import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; import { TextAreaProps } from './TextArea.types'; import './TextArea.less'; const prefixCls = 'bui-textarea'; const DEFAULT_ROWS = 2; const TextArea = React.forwardRef<HTMLDivElement, TextAreaProps>( (props, ref) => { const { className, value, defaultValue, textareaProps, textareaRef, name, placeholder, disabled, rows, maxLength, autoSize, autoFocus, showCount, onChange, ...others } = props; const [textAreaValue, triggerChange] = useValue({ value, defaultValue, onChange, }); const initLock = useRef(false); const internalRef = useRef<HTMLTextAreaElement>(null); const handleInputRef = useForkRef(internalRef, textareaRef); // autoFocus useEffect(() => { if (autoFocus && internalRef.current) { internalRef.current.focus(); } }, [internalRef]); const handleAutoHeight = (height) => { if (typeof autoSize === 'object') { const { maxHeight, minHeight } = autoSize; if (maxHeight !== undefined) { return Math.min(height, maxHeight); } if (minHeight !== undefined) { return Math.max(height, minHeight); } } return height; }; // H5 autoSize useEffect(() => { if (!autoSize || isMini) return; const textArea = internalRef.current; if (!textArea) return; textArea.style.height = 'auto'; let height = textArea.scrollHeight; height = handleAutoHeight(height); textArea.style.height = `${height}px`; }, [textAreaValue, autoSize]); // miniprogram autoSize const handleLineChange = (e) => { if (!isMini) return; const textArea = internalRef.current; const { height, lineCount } = e?.detail || {}; const line = lineCount <= 1 ? rows : lineCount; // 总高度 = 行数 * 单行高度 let textAreaHeight = line * (height / lineCount + 4); textAreaHeight = handleAutoHeight(textAreaHeight); // autoSize=false也需要初始化小程序textarea高度 if (!initLock.current && !autoSize && rows === DEFAULT_ROWS) { textArea.style.height = `${textAreaHeight}px`; initLock.current = true; return; } if (autoSize) { textArea.style.height = `${textAreaHeight}px`; } }; let nativeProps: Record<string, any> = { [isMini ? 'maxlength' : 'maxLength']: maxLength ?? -1, }; if (isMini) { nativeProps = { ...nativeProps, onLineChange: handleLineChange, placeholderClass: 'bui-mini-placeholder', autoFocus, focus: autoFocus, }; } return ( <div className={clsx( prefixCls, { [`${prefixCls}-disabled`]: disabled, }, className, )} ref={ref} {...others} > <textarea ref={handleInputRef} {...nativeProps} name={name} value={textAreaValue} placeholder={placeholder} disabled={disabled} rows={rows} {...textareaProps} onChange={(e) => { triggerChange(e, e.target.value); textareaProps?.onChange?.(e); }} onInput={(e: React.ChangeEvent<HTMLTextAreaElement>) => { // 小程序中无onChange事件,通过onInput模拟 if (isMini) { triggerChange(e, e.target.value); } textareaProps?.onInput?.(e); }} className={clsx(`${prefixCls}-content`, textareaProps?.className)} /> {showCount && ( <div className={`${prefixCls}-count`}> {maxLength === undefined ? textAreaValue.length : `${textAreaValue.length}/${maxLength}`} </div> )} </div> ); }, ); TextArea.displayName = 'BuiTextArea'; TextArea.defaultProps = { defaultValue: '', rows: DEFAULT_ROWS, autoSize: false, autoFocus: false, showCount: false, }; export default TextArea;