@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import React , { useState , ReactNode , ChangeEvent , KeyboardEvent , useEffect , useCallback , useRef } from 'react' ;
17+ import React , { useState , ReactNode , ChangeEvent , KeyboardEvent , useRef } from 'react' ;
1818import classNames from 'classnames' ;
1919
2020import Autocompleter from "../../autocomplete/AutocompleteProvider" ;
@@ -27,10 +27,10 @@ import { Icon as CheckmarkIcon } from '../../../res/img/element-icons/roomlist/c
2727interface AutocompleteInputProps {
2828 provider : Autocompleter ;
2929 placeholder : string ;
30+ selection : ICompletion [ ] ;
31+ onSelectionChange : ( selection : ICompletion [ ] ) => void ;
3032 maxSuggestions ?: number ;
3133 renderSuggestion ?: ( s : ICompletion ) => ReactNode ;
32- selection ?: ICompletion [ ] ;
33- onSelectionChange ?: ( selection : ICompletion [ ] ) => void ;
3434 renderSelection ?: ( m : ICompletion ) => ReactNode ;
3535 additionalFilter ?: ( suggestion : ICompletion ) => boolean ;
3636}
@@ -45,99 +45,74 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
4545 selection,
4646 additionalFilter,
4747} ) => {
48- const [ _selection , setSelection ] = useState < ICompletion [ ] > ( [ ] ) ;
49- const [ suggestions , setSuggestions ] = useState < ICompletion [ ] > ( [ ] ) ;
5048 const [ query , setQuery ] = useState < string > ( '' ) ;
51- const [ focusedElement , setFocusedElement ] = useState < HTMLElement > ( null ) ;
52-
49+ const [ suggestions , setSuggestions ] = useState < ICompletion [ ] > ( [ ] ) ;
50+ const [ isFocused , setFocused ] = useState < boolean > ( false ) ;
5351 const editorContainerRef = useRef < HTMLDivElement > ( ) ;
5452 const editorRef = useRef < HTMLInputElement > ( ) ;
5553
56- const getSuggestions = useCallback ( async ( ) => {
57- if ( query ) {
58- let matches = await provider . getCompletions (
59- query ,
60- { start : query . length , end : query . length } ,
61- true ,
62- maxSuggestions ,
63- ) ;
64- if ( additionalFilter ) {
65- matches = matches . filter ( additionalFilter ) ;
66- }
67- setSuggestions ( matches ) ;
68- }
69- } , [ query , maxSuggestions , provider , additionalFilter ] ) ;
70-
71- useEffect ( ( ) => {
72- getSuggestions ( ) ;
73-
74- if ( selection ) {
75- setSelection ( selection ) ;
76- }
77- } , [ getSuggestions , selection ] ) ;
78-
7954 const focusEditor = ( ) => {
8055 editorRef ?. current ?. focus ( ) ;
8156 } ;
8257
83- const removeSelection = ( t : ICompletion ) => {
84- const newSelection = _selection . map ( s => s ) ;
85- const idx = _selection . findIndex ( s => s . completionId === t . completionId ) ;
58+ const onQueryChange = async ( e : ChangeEvent < HTMLInputElement > ) => {
59+ const value = e . target . value . trim ( ) ;
8660
87- if ( idx >= 0 ) {
88- newSelection . splice ( idx , 1 ) ;
89- if ( onSelectionChange ) {
90- onSelectionChange ( newSelection ) ;
91- }
92- setSelection ( newSelection ) ;
93- }
61+ setQuery ( value ) ;
9462
95- setQuery ( '' ) ;
96- } ;
63+ let matches = await provider . getCompletions (
64+ query ,
65+ { start : query . length , end : query . length } ,
66+ true ,
67+ maxSuggestions ,
68+ ) ;
9769
98- const onFilterChange = async ( e : ChangeEvent < HTMLInputElement > ) => {
99- e . stopPropagation ( ) ;
100- e . preventDefault ( ) ;
70+ if ( additionalFilter ) {
71+ matches = matches . filter ( additionalFilter ) ;
72+ }
10173
102- setQuery ( e . target . value . trim ( ) ) ;
74+ setSuggestions ( matches ) ;
10375 } ;
10476
105- const onClickInputArea = ( e ) => {
106- e . stopPropagation ( ) ;
107- e . preventDefault ( ) ;
108-
77+ const onClickInputArea = ( ) => {
10978 focusEditor ( ) ;
11079 } ;
11180
11281 const onKeyDown = ( e : KeyboardEvent ) => {
11382 const hasModifiers = e . ctrlKey || e . shiftKey || e . metaKey ;
83+
11484 // when the field is empty and the user hits backspace remove the right-most target
115- if ( ! query && _selection . length > 0 && e . key === Key . BACKSPACE && ! hasModifiers ) {
116- e . preventDefault ( ) ;
117- removeSelection ( _selection [ _selection . length - 1 ] ) ;
85+ if ( ! query && selection . length > 0 && e . key === Key . BACKSPACE && ! hasModifiers ) {
86+ removeSelection ( selection [ selection . length - 1 ] ) ;
11887 }
11988 } ;
12089
121- const toggleSelection = ( e : ICompletion ) => {
122- const newSelection = _selection . map ( t => t ) ;
123- const idx = _selection . findIndex ( s => s . completionId === e . completionId ) ;
124- if ( idx >= 0 ) {
125- newSelection . splice ( idx , 1 ) ;
90+ const toggleSelection = ( completion : ICompletion ) => {
91+ const newSelection = [ ...selection ] ;
92+ const index = selection . findIndex ( selection => selection . completionId === completion . completionId ) ;
93+
94+ if ( index >= 0 ) {
95+ newSelection . splice ( index , 1 ) ;
12696 } else {
127- newSelection . push ( e ) ;
97+ newSelection . push ( completion ) ;
12898 }
12999
130- if ( onSelectionChange ) {
100+ onSelectionChange ( newSelection ) ;
101+ focusEditor ( ) ;
102+ } ;
103+
104+ const removeSelection = ( completion : ICompletion ) => {
105+ const newSelection = [ ...selection ] ;
106+ const index = selection . findIndex ( selection => selection . completionId === completion . completionId ) ;
107+
108+ if ( index >= 0 ) {
109+ newSelection . splice ( index , 1 ) ;
131110 onSelectionChange ( newSelection ) ;
132111 }
133- setSelection ( newSelection ) ;
134- setQuery ( '' ) ;
135-
136- focusEditor ( ) ;
137112 } ;
138113
139- const _renderSuggestion = ( s : ICompletion ) : ReactNode => {
140- const isSelected = _selection . findIndex ( selection => selection . completionId === s . completionId ) >= 0 ;
114+ const _renderSuggestion = ( completion : ICompletion ) : ReactNode => {
115+ const isSelected = selection . findIndex ( selection => selection . completionId === completion . completionId ) >= 0 ;
141116 const classes = classNames ( {
142117 'mx_AutocompleteInput_suggestion' : true ,
143118 'mx_AutocompleteInput_suggestion--selected' : isSelected ,
@@ -149,10 +124,10 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
149124 e . preventDefault ( ) ;
150125 e . stopPropagation ( ) ;
151126
152- toggleSelection ( s ) ;
127+ toggleSelection ( completion ) ;
153128 } }
154- key = { s . completionId }
155- data-testid = { `autocomplete-suggestion-item-${ s . completionId } ` }
129+ key = { completion . completionId }
130+ data-testid = { `autocomplete-suggestion-item-${ completion . completionId } ` }
156131 >
157132 < div >
158133 { children }
@@ -162,13 +137,13 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
162137 ) ;
163138
164139 if ( renderSuggestion ) {
165- return withContainer ( renderSuggestion ( s ) ) ;
140+ return withContainer ( renderSuggestion ( completion ) ) ;
166141 }
167142
168143 return withContainer (
169144 < >
170- < span className = 'mx_AutocompleteInput_suggestion_title' > { s . completion } </ span >
171- < span className = 'mx_AutocompleteInput_suggestion_description' > { s . completionId } </ span >
145+ < span className = 'mx_AutocompleteInput_suggestion_title' > { completion . completion } </ span >
146+ < span className = 'mx_AutocompleteInput_suggestion_description' > { completion . completionId } </ span >
172147 </ > ,
173148 ) ;
174149 } ;
@@ -202,7 +177,7 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
202177 ) ;
203178 } ;
204179
205- const hasPlaceholder = ( ) : boolean => _selection . length === 0 && query . length === 0 ;
180+ const hasPlaceholder = ( ) : boolean => selection . length === 0 && query . length === 0 ;
206181
207182 return (
208183 < div className = "mx_AutocompleteInput" >
@@ -212,22 +187,22 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
212187 onClick = { onClickInputArea }
213188 data-testid = "autocomplete-editor"
214189 >
215- { _selection . map ( s => _renderSelection ( s ) ) }
190+ { selection . map ( s => _renderSelection ( s ) ) }
216191 < input
192+ ref = { editorRef }
217193 type = "text"
218194 onKeyDown = { onKeyDown }
219- onChange = { onFilterChange }
195+ onChange = { onQueryChange }
220196 value = { query }
221197 autoComplete = "off"
222198 placeholder = { hasPlaceholder ( ) ? placeholder : null }
223- ref = { editorRef }
224- onFocus = { ( e ) => setFocusedElement ( e . target ) }
225- onBlur = { ( ) => setFocusedElement ( null ) }
199+ onFocus = { ( ) => setFocused ( true ) }
200+ onBlur = { ( ) => setFocused ( false ) }
226201 data-testid = "autocomplete-input"
227202 />
228203 </ div >
229204 {
230- ( focusedElement === editorRef . current && suggestions . length ) ? (
205+ ( isFocused && suggestions . length ) ? (
231206 < div
232207 className = "mx_AutocompleteInput_matches"
233208 style = { { top : editorContainerRef . current ?. clientHeight } }
0 commit comments