1
- import { useLayoutEffect , useState } from 'react'
1
+ import { RefObject , useLayoutEffect , useState } from 'react'
2
2
3
3
import { SxProp } from '../../sx'
4
4
import { getCharacterCoordinates } from '../utils/character-coordinates'
5
5
6
6
type UseDynamicTextareaHeightSettings = {
7
- minHeightLines : number
8
- maxHeightLines : number
9
- element : HTMLTextAreaElement | null
7
+ minHeightLines ? : number
8
+ maxHeightLines ? : number
9
+ elementRef : RefObject < HTMLTextAreaElement | null >
10
10
/** The current value of the input. */
11
11
value : string
12
12
}
@@ -16,43 +16,47 @@ type UseDynamicTextareaHeightSettings = {
16
16
* resizing it as the user types. If the user manually resizes the textarea, their setting
17
17
* will be respected.
18
18
*
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}} />`.
20
21
*
21
22
* NOTE: for the most accurate results, be sure that the `lineHeight` of the element is
22
23
* explicitly set in CSS.
23
24
*/
24
25
export const useDynamicTextareaHeight = ( {
25
26
minHeightLines,
26
27
maxHeightLines,
27
- element ,
28
+ elementRef ,
28
29
value,
29
30
} : UseDynamicTextareaHeightSettings ) : SxProp [ 'sx' ] => {
30
31
const [ height , setHeight ] = useState < string | undefined > ( undefined )
31
32
const [ minHeight , setMinHeight ] = useState < string | undefined > ( undefined )
32
33
const [ maxHeight , setMaxHeight ] = useState < string | undefined > ( undefined )
33
34
34
35
useLayoutEffect ( ( ) => {
36
+ const element = elementRef . current
35
37
if ( ! element ) return
36
38
37
39
const computedStyles = getComputedStyle ( element )
38
40
const pt = computedStyles . paddingTop
39
- const lastCharacterCoords = getCharacterCoordinates ( element , element . value . length )
40
41
41
42
// The calculator gives us the distance from the top border to the bottom of the caret, including
42
43
// any top padding, so we need to delete the top padding to accurately get the height
43
44
// We could also parse and subtract the top padding, but this is more reliable (no chance of NaN)
44
45
element . style . paddingTop = '0'
46
+
47
+ const lastCharacterCoords = getCharacterCoordinates ( element , element . value . length )
48
+
45
49
// Somehow we come up 1 pixel too short and the scrollbar appears, so just add one
46
50
setHeight ( `${ lastCharacterCoords . top + lastCharacterCoords . height + 1 } px` )
47
51
element . style . paddingTop = pt
48
52
49
53
const lineHeight =
50
54
computedStyles . lineHeight === 'normal' ? `1.2 * ${ computedStyles . fontSize } ` : computedStyles . lineHeight
51
55
// 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 } )` )
54
58
// `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 ] )
56
60
57
61
return { height, minHeight, maxHeight, boxSizing : 'content-box' }
58
62
}
0 commit comments