1+ import { useEffect , useRef , useState , useCallback } from 'react' ;
2+
3+ // Media Query for detecting "large" screens (matching Tailwind's lg: breakpoint)
4+ const LARGE_SCREEN_MQ = '(min-width: 1024px)' ;
5+
6+ // Calculates and sets the textarea height based on its scrollHeight
7+ const adjustTextareaHeight = ( textarea : HTMLTextAreaElement | null ) => {
8+ if ( ! textarea ) return ;
9+
10+ // Only perform auto-sizing on large screens
11+ if ( ! window . matchMedia ( LARGE_SCREEN_MQ ) . matches ) {
12+ // On small screens, reset inline height and max-height styles.
13+ // This allows CSS (e.g., `rows` attribute or classes) to control the height,
14+ // and enables manual resizing if `resize-vertical` is set.
15+ textarea . style . height = '' ; // Use 'auto' or '' to reset
16+ textarea . style . maxHeight = '' ;
17+ return ; // Do not adjust height programmatically on small screens
18+ }
19+
20+ const computedStyle = window . getComputedStyle ( textarea ) ;
21+ // Get the max-height specified by CSS (e.g., from `lg:max-h-48`)
22+ const currentMaxHeight = computedStyle . maxHeight ;
23+
24+ // Temporarily remove max-height to allow scrollHeight to be calculated correctly
25+ textarea . style . maxHeight = 'none' ;
26+ // Reset height to 'auto' to measure the actual scrollHeight needed
27+ textarea . style . height = 'auto' ;
28+ // Set the height to the calculated scrollHeight
29+ textarea . style . height = `${ textarea . scrollHeight } px` ;
30+ // Re-apply the original max-height from CSS to enforce the limit
31+ textarea . style . maxHeight = currentMaxHeight ;
32+ } ;
33+
34+ // Interface describing the API returned by the hook
35+ export interface AutosizeTextareaApi {
36+ value : ( ) => string ;
37+ setValue : ( value : string ) => void ;
38+ focus : ( ) => void ;
39+ ref : React . RefObject < HTMLTextAreaElement > ;
40+ onInput : ( event : React . FormEvent < HTMLTextAreaElement > ) => void ; // Input handler
41+ }
42+
43+ // This is a workaround to prevent the textarea from re-rendering when the inner content changes
44+ // See https://github.com/ggml-org/llama.cpp/pull/12299
45+ // combined now with auto-sizing logic.
46+ export function useChatTextarea ( initValue : string ) : AutosizeTextareaApi {
47+ const [ savedInitValue , setSavedInitValue ] = useState < string > ( initValue ) ;
48+ const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
49+
50+ // Effect to set initial value and height on mount or when initValue changes
51+ useEffect ( ( ) => {
52+ const textarea = textareaRef . current ;
53+ if ( textarea ) {
54+ if ( typeof savedInitValue === 'string' && savedInitValue . length > 0 ) {
55+ textarea . value = savedInitValue ;
56+ // Call adjustTextareaHeight - it will check screen size internally
57+ setTimeout ( ( ) => adjustTextareaHeight ( textarea ) , 0 ) ;
58+ setSavedInitValue ( '' ) ; // Reset after applying
59+ } else {
60+ // Adjust height even if there's no initial value (for initial render)
61+ setTimeout ( ( ) => adjustTextareaHeight ( textarea ) , 0 ) ;
62+ }
63+ }
64+ } , [ textareaRef , savedInitValue ] ) ; // Depend on ref and savedInitValue
65+
66+ const handleInput = useCallback ( ( event : React . FormEvent < HTMLTextAreaElement > ) => {
67+ // Call adjustTextareaHeight on every input - it will decide whether to act
68+ adjustTextareaHeight ( event . currentTarget ) ;
69+ } , [ ] ) ;
70+
71+ return {
72+ // Method to get the current value directly from the textarea
73+ value : ( ) => {
74+ return textareaRef . current ?. value ?? '' ;
75+ } ,
76+ // Method to programmatically set the value and trigger height adjustment
77+ setValue : ( value : string ) => {
78+ const textarea = textareaRef . current ;
79+ if ( textarea ) {
80+ textarea . value = value ;
81+ // Call adjustTextareaHeight - it will check screen size internally
82+ setTimeout ( ( ) => adjustTextareaHeight ( textarea ) , 0 ) ;
83+ }
84+ } ,
85+ focus : ( ) => {
86+ if ( textareaRef . current ) {
87+ textareaRef . current . focus ( ) ;
88+ }
89+ } ,
90+ ref : textareaRef ,
91+ onInput : handleInput ,
92+ } ;
93+ }
0 commit comments