1- import { useLayoutEffect , useState } from 'react'
1+ import { RefObject , useLayoutEffect , useState } from 'react'
22
33import { SxProp } from '../../sx'
44import { getCharacterCoordinates } from '../utils/character-coordinates'
55
66type UseDynamicTextareaHeightSettings = {
7- minHeightLines : number
8- maxHeightLines : number
9- element : HTMLTextAreaElement | null
7+ minHeightLines ? : number
8+ maxHeightLines ? : number
9+ elementRef : RefObject < HTMLTextAreaElement | null >
1010 /** The current value of the input. */
1111 value : string
1212}
@@ -16,43 +16,47 @@ type UseDynamicTextareaHeightSettings = {
1616 * resizing it as the user types. If the user manually resizes the textarea, their setting
1717 * will be respected.
1818 *
19- * Returns an object to spread to the component's `sx` prop.
19+ * Returns an object to spread to the component's `sx` prop. If you are using `Textarea`,
20+ * apply this to the child `textarea` element: `<Textarea sx={{'& textarea': resultOfThisHook}} />`.
2021 *
2122 * NOTE: for the most accurate results, be sure that the `lineHeight` of the element is
2223 * explicitly set in CSS.
2324 */
2425export const useDynamicTextareaHeight = ( {
2526 minHeightLines,
2627 maxHeightLines,
27- element ,
28+ elementRef ,
2829 value,
2930} : UseDynamicTextareaHeightSettings ) : SxProp [ 'sx' ] => {
3031 const [ height , setHeight ] = useState < string | undefined > ( undefined )
3132 const [ minHeight , setMinHeight ] = useState < string | undefined > ( undefined )
3233 const [ maxHeight , setMaxHeight ] = useState < string | undefined > ( undefined )
3334
3435 useLayoutEffect ( ( ) => {
36+ const element = elementRef . current
3537 if ( ! element ) return
3638
3739 const computedStyles = getComputedStyle ( element )
3840 const pt = computedStyles . paddingTop
39- const lastCharacterCoords = getCharacterCoordinates ( element , element . value . length )
4041
4142 // The calculator gives us the distance from the top border to the bottom of the caret, including
4243 // any top padding, so we need to delete the top padding to accurately get the height
4344 // We could also parse and subtract the top padding, but this is more reliable (no chance of NaN)
4445 element . style . paddingTop = '0'
46+
47+ const lastCharacterCoords = getCharacterCoordinates ( element , element . value . length )
48+
4549 // Somehow we come up 1 pixel too short and the scrollbar appears, so just add one
4650 setHeight ( `${ lastCharacterCoords . top + lastCharacterCoords . height + 1 } px` )
4751 element . style . paddingTop = pt
4852
4953 const lineHeight =
5054 computedStyles . lineHeight === 'normal' ? `1.2 * ${ computedStyles . fontSize } ` : computedStyles . lineHeight
5155 // Using CSS calculations is fast and prevents us from having to parse anything
52- setMinHeight ( `calc(${ minHeightLines } * ${ lineHeight } )` )
53- setMaxHeight ( `calc(${ maxHeightLines } * ${ lineHeight } )` )
56+ if ( minHeightLines !== undefined ) setMinHeight ( `calc(${ minHeightLines } * ${ lineHeight } )` )
57+ if ( maxHeightLines !== undefined ) setMaxHeight ( `calc(${ maxHeightLines } * ${ lineHeight } )` )
5458 // `value` is an unnecessary dependency but it enables us to recalculate as the user types
55- } , [ minHeightLines , maxHeightLines , element , value ] )
59+ } , [ minHeightLines , maxHeightLines , value , elementRef ] )
5660
5761 return { height, minHeight, maxHeight, boxSizing : 'content-box' }
5862}
0 commit comments