1- import { forwardRef , useRef } from 'react' ;
1+ import { forwardRef , 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- /** Max number of visible rows when autoSize is `true` */
19- maxVisibleRows ?: 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. */
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 */
2222 rows ?: number ;
2323}
2424
@@ -38,62 +38,85 @@ function TextArea(props: WithNullableValue<CubeTextAreaProps>, ref) {
3838 isReadOnly = false ,
3939 isRequired = false ,
4040 onChange,
41- maxVisibleRows ,
41+ maxRows = 10 ,
4242 rows = 3 ,
4343 ...otherProps
4444 } = props ;
4545
46- let [ inputValue , setInputValue ] = useControlledState (
46+ rows = Math . max ( rows , 1 ) ;
47+ maxRows = Math . max ( maxRows , rows ) ;
48+
49+ let [ inputValue , setInputValue ] = useControlledState < string > (
4750 props . value ,
4851 props . defaultValue ,
4952 ( ) => { } ,
5053 ) ;
5154 let inputRef = useRef < HTMLTextAreaElement > ( null ) ;
5255
53- let onHeightChange = useEvent ( ( ) => {
54- if ( autoSize && inputRef . current ) {
55- let input = inputRef . current ;
56- const prevAlignment = input . style . alignSelf ;
57- const computedStyle = getComputedStyle ( input ) ;
56+ let { labelProps, inputProps } = useTextField (
57+ {
58+ ...props ,
59+ onChange : chain ( onChange , setInputValue ) ,
60+ inputElementType : 'textarea' ,
61+ } ,
62+ inputRef ,
63+ ) ;
64+
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 ;
5879
59- const lineHeight = parseFloat ( computedStyle . lineHeight ) ;
60- const paddingTop = parseFloat ( computedStyle . paddingTop ) ;
61- const paddingBottom = parseFloat ( computedStyle . paddingBottom ) ;
62- const borderTop = parseFloat ( computedStyle . borderTopWidth ) ;
63- const borderBottom = parseFloat ( computedStyle . borderBottomWidth ) ;
80+ // Calculate line height (approximately)
81+ const lineHeight = parseInt ( computedStyle . lineHeight ) || 20 ;
6482
65- input . style . alignSelf = 'start' ;
66- input . style . height = 'auto' ;
83+ // Calculate content height (excluding padding and border)
84+ const contentHeight = textarea . scrollHeight - paddingTop - paddingBottom ;
6785
68- const scrollHeight = input . scrollHeight ;
69- input . style . height = `calc( ${ scrollHeight } px + (2 * var(--border-width)))` ;
86+ // Calculate rows based on content height
87+ const computedRows = Math . ceil ( contentHeight / lineHeight ) ;
7088
71- input . style . alignSelf = prevAlignment ;
89+ // Apply min/max constraints
90+ const targetRows = Math . max ( Math . min ( computedRows , maxRows ) , rows ) ;
7291
73- const contentHeight =
74- scrollHeight - paddingTop - paddingBottom - borderTop - borderBottom ;
75- const rowCount = Math . round ( contentHeight / lineHeight ) ;
92+ // Set the height including padding and border
93+ const totalHeight =
94+ targetRows * lineHeight +
95+ paddingTop +
96+ paddingBottom +
97+ borderTop +
98+ borderBottom ;
7699
77- if ( autoSize && maxVisibleRows != null && rowCount > maxVisibleRows ) {
78- input . style . height = `${ maxVisibleRows * lineHeight + paddingTop + paddingBottom } px` ;
79- }
80- }
100+ textarea . style . height = `${ totalHeight } px` ;
81101 } ) ;
82102
103+ // Call adjustHeight on content change
83104 useLayoutEffect ( ( ) => {
84- if ( inputRef . current ) {
85- onHeightChange ( ) ;
86- }
87- } , [ inputValue , inputRef . current ] ) ;
105+ adjustHeight ( ) ;
106+ } , [ inputValue ] ) ;
88107
89- let { labelProps, inputProps } = useTextField (
90- {
91- ...props ,
92- onChange : chain ( onChange , setInputValue ) ,
93- inputElementType : 'textarea' ,
94- } ,
95- inputRef ,
96- ) ;
108+ // Also call it on window resize as that can affect wrapping
109+ useLayoutEffect ( ( ) => {
110+ if ( ! autoSize ) return ;
111+
112+ const handleResize = ( ) => {
113+ adjustHeight ( ) ;
114+ } ;
115+
116+ window . addEventListener ( 'resize' , handleResize ) ;
117+
118+ return ( ) => window . removeEventListener ( 'resize' , handleResize ) ;
119+ } , [ adjustHeight , autoSize ] ) ;
97120
98121 return (
99122 < TextInputBase
0 commit comments