1- import { forwardRef , useRef } from 'react' ;
1+ import { forwardRef , useEffect , useLayoutEffect , useRef } from 'react' ;
22import { useControlledState } from '@react-stately/utils' ;
33import { useTextField } from 'react-aria' ;
44
5+ import { useEvent } from '../../../_internal/index' ;
56import { CubeTextInputBaseProps , TextInputBase } from '../TextInput' ;
67import { useProviderProps } from '../../../provider' ;
78import {
89 castNullableStringValue ,
910 WithNullableValue ,
1011} from '../../../utils/react/nullableValue' ;
11- import { chain , useLayoutEffect } from '../../../utils/react' ;
12+ import { chain } from '../../../utils/react' ;
1213import { useFieldProps } from '../../form' ;
13- import { useEvent } from '../../../_internal' ;
1414
1515export interface CubeTextAreaProps extends CubeTextInputBaseProps {
16- /** Whether the textarea should change its size depends on content */
16+ /** Whether the textarea should change its size depends on the content */
1717 autoSize ?: boolean ;
18- /** The rows attribute in HTML is used to specify the number of visible text lines for the
19- * control i.e. the number of rows to display. */
18+ /** Max number of visible rows when autoSize is `true`. Defaults to 10 */
19+ maxRows ?: number ;
20+ /** The `rows` attribute in HTML is used to specify the number of visible text lines for the
21+ * control i.e. the number of rows to display. Defaults to 3 */
2022 rows ?: number ;
2123}
2224
@@ -36,42 +38,21 @@ function TextArea(props: WithNullableValue<CubeTextAreaProps>, ref) {
3638 isReadOnly = false ,
3739 isRequired = false ,
3840 onChange,
41+ maxRows = 10 ,
3942 rows = 3 ,
4043 ...otherProps
4144 } = props ;
4245
43- let [ inputValue , setInputValue ] = useControlledState (
46+ rows = Math . max ( rows , 1 ) ;
47+ maxRows = Math . max ( maxRows , rows ) ;
48+
49+ let [ inputValue , setInputValue ] = useControlledState < string > (
4450 props . value ,
4551 props . defaultValue ,
4652 ( ) => { } ,
4753 ) ;
4854 let inputRef = useRef < HTMLTextAreaElement > ( null ) ;
4955
50- let onHeightChange = useEvent ( ( ) => {
51- if ( autoSize && inputRef . current ) {
52- let input = inputRef . current ;
53- let prevAlignment = input . style . alignSelf ;
54- let computedStyle = getComputedStyle ( input ) ;
55- input . style . alignSelf = 'start' ;
56- input . style . height = 'auto' ;
57- input . style . height = input . scrollHeight
58- ? `calc(${ input . scrollHeight } px + (2 * var(--border-width)))`
59- : `${
60- parseFloat ( computedStyle . paddingTop ) +
61- parseFloat ( computedStyle . paddingBottom ) +
62- parseFloat ( computedStyle . lineHeight ) * ( rows || 3 ) +
63- 2
64- } px`;
65- input . style . alignSelf = prevAlignment ;
66- }
67- } ) ;
68-
69- useLayoutEffect ( ( ) => {
70- if ( inputRef . current ) {
71- onHeightChange ( ) ;
72- }
73- } , [ inputValue , inputRef . current ] ) ;
74-
7556 let { labelProps, inputProps } = useTextField (
7657 {
7758 ...props ,
@@ -81,6 +62,65 @@ function TextArea(props: WithNullableValue<CubeTextAreaProps>, ref) {
8162 inputRef ,
8263 ) ;
8364
65+ const adjustHeight = useEvent ( ( ) => {
66+ const textarea = inputRef . current ;
67+
68+ if ( ! textarea || ! autoSize ) return ;
69+
70+ // Reset height to get the correct scrollHeight
71+ textarea . style . height = 'auto' ;
72+
73+ // Get computed styles to account for padding
74+ const computedStyle = getComputedStyle ( textarea ) ;
75+ const paddingTop = parseFloat ( computedStyle . paddingTop ) || 0 ;
76+ const paddingBottom = parseFloat ( computedStyle . paddingBottom ) || 0 ;
77+ const borderTop = parseFloat ( computedStyle . borderTopWidth ) || 0 ;
78+ const borderBottom = parseFloat ( computedStyle . borderBottomWidth ) || 0 ;
79+
80+ // Calculate line height (approximately)
81+ const lineHeight = parseInt ( computedStyle . lineHeight ) || 20 ;
82+
83+ // Calculate content height (excluding padding and border)
84+ const contentHeight = textarea . scrollHeight - paddingTop - paddingBottom ;
85+
86+ // Calculate rows based on content height
87+ const computedRows = Math . ceil ( contentHeight / lineHeight ) ;
88+
89+ // Apply min/max constraints
90+ const targetRows = Math . max ( Math . min ( computedRows , maxRows ) , rows ) ;
91+
92+ // Set the height including padding and border
93+ const totalHeight =
94+ targetRows * lineHeight +
95+ paddingTop +
96+ paddingBottom +
97+ borderTop +
98+ borderBottom ;
99+
100+ textarea . style . height = `${ totalHeight } px` ;
101+ } ) ;
102+
103+ const useEnvironmentalEffect =
104+ typeof window !== 'undefined' ? useLayoutEffect : useEffect ;
105+
106+ // Call adjustHeight on content change
107+ useEnvironmentalEffect ( ( ) => {
108+ adjustHeight ( ) ;
109+ } , [ inputValue ] ) ;
110+
111+ // Also call it on element resize as that can affect wrapping
112+ useEnvironmentalEffect ( ( ) => {
113+ if ( ! autoSize || ! inputRef . current ) return ;
114+
115+ adjustHeight ( ) ;
116+
117+ const resizeObserver = new ResizeObserver ( adjustHeight ) ;
118+
119+ resizeObserver . observe ( inputRef ?. current ) ;
120+
121+ return ( ) => resizeObserver . disconnect ( ) ;
122+ } , [ autoSize , inputRef ?. current ] ) ;
123+
84124 return (
85125 < TextInputBase
86126 ref = { ref }
0 commit comments