@@ -198,9 +198,10 @@ export const CodeAutocompleteField = ({
198198 intent,
199199} : CodeAutocompleteFieldProps ) => {
200200 const value = React . useRef < string > ( initialValue ) ;
201+ const [ hasValue , setHasValue ] = React . useState ( ! ! initialValue ) ;
201202 const cursorPosition = React . useRef ( 0 ) ;
202203 const dropdownXYoffset = React . useRef < { x : number ; y : number } > ( { x : 0 , y : 0 } ) ;
203- const [ shouldShowDropdown , setShouldShowDropdown ] = React . useState ( false ) ;
204+ const [ shouldShowDropdown , setShouldShowDropdownState ] = React . useState ( false ) ;
204205 const [ suggestions , setSuggestions ] = React . useState < CodeAutocompleteFieldSuggestionWithReplacementInfo [ ] > ( [ ] ) ;
205206 const [ suggestionsPending , setSuggestionsPending ] = React . useState ( false ) ;
206207 const suggestionRequestData = React . useRef < RequestMetaData > ( { requestId : undefined } ) ;
@@ -221,6 +222,7 @@ export const CodeAutocompleteField = ({
221222 const currentCm = React . useRef < EditorView > ( undefined ) ;
222223 currentCm . current = cm ;
223224 const isFocused = React . useRef ( false ) ;
225+ const suggestionsDismissed = React . useRef ( false ) ;
224226 const autoSuggestionDivRef = React . useRef < HTMLDivElement > ( null ) ;
225227 /** Mutable editor state, since this needs to be current in scope of the SingleLineEditorComponent. */
226228 const [ editorState ] = React . useState < {
@@ -229,6 +231,15 @@ export const CodeAutocompleteField = ({
229231 cm ?: EditorView ;
230232 dropdownShown : boolean ;
231233 } > ( { index : 0 , suggestions : [ ] , dropdownShown : false } ) ;
234+
235+ const setDropdownShown = React . useCallback (
236+ ( shown : boolean ) => {
237+ editorState . dropdownShown = shown ;
238+ setShouldShowDropdownState ( shown ) ;
239+ } ,
240+ [ editorState ] ,
241+ ) ;
242+
232243 /** This is for the AutoSuggestionList component in order to re-render. */
233244 const [ focusedIndex , setFocusedIndex ] = React . useState ( 0 ) ;
234245 const selectedTextRanges = React . useRef < IRange [ ] > ( [ ] ) ;
@@ -266,10 +277,6 @@ export const CodeAutocompleteField = ({
266277 typeof editorState ?. cm ?. dispatch === "function" ? editorState ?. cm ?. dispatch : ( ) => { }
267278 ) as EditorView [ "dispatch" ] ;
268279
269- React . useEffect ( ( ) => {
270- editorState . dropdownShown = shouldShowDropdown ;
271- } , [ shouldShowDropdown , editorState ] ) ;
272-
273280 // Handle replacement highlighting
274281 useEffect ( ( ) => {
275282 if ( highlightedElement && cm ) {
@@ -331,7 +338,7 @@ export const CodeAutocompleteField = ({
331338 suggestionResponse ?. replacementResults ?. length === 1 &&
332339 ! suggestionResponse ?. replacementResults [ 0 ] ?. replacements ?. length
333340 ) {
334- setShouldShowDropdown ( false ) ;
341+ setDropdownShown ( false ) ;
335342 }
336343 if ( suggestionResponse ?. replacementResults ?. length ) {
337344 suggestionResponse . replacementResults . forEach (
@@ -352,7 +359,7 @@ export const CodeAutocompleteField = ({
352359 setSuggestions ( [ ] ) ;
353360 }
354361 setCurrentIndex ( 0 ) ;
355- } , [ suggestionResponse , editorState ] ) ;
362+ } , [ suggestionResponse , editorState , setDropdownShown ] ) ;
356363
357364 const getOffsetRange = ( cm : EditorView , from : number , to : number ) => {
358365 if ( ! cm ) return { fromOffset : 0 , toOffset : 0 } ;
@@ -365,15 +372,6 @@ export const CodeAutocompleteField = ({
365372 return { fromOffset, toOffset } ;
366373 } ;
367374
368- const inputActionsDisplayed = React . useCallback ( ( node : any ) => {
369- if ( ! node ) return ;
370- const width = node . offsetWidth ;
371- const slCodeEditor = node . parentElement . getElementsByClassName ( `${ eccgui } -singlelinecodeeditor` ) ;
372- if ( slCodeEditor . length > 0 ) {
373- slCodeEditor [ 0 ] . style . paddingRight = `${ width } px` ;
374- }
375- } , [ ] ) ;
376-
377375 const asyncCheckInput = useMemo (
378376 ( ) => async ( inputString : string ) => {
379377 if (
@@ -448,6 +446,7 @@ export const CodeAutocompleteField = ({
448446 const handleChange = React . useMemo ( ( ) => {
449447 return ( val : string ) => {
450448 value . current = val ;
449+ setHasValue ( ! ! val ) ;
451450 checkValuePathValidity . cancel ( ) ;
452451 checkValuePathValidity ( value . current ) ;
453452 onChange ( val ) ;
@@ -462,8 +461,8 @@ export const CodeAutocompleteField = ({
462461 cursorPosition . current = cursor - offsetFromFirstLine ;
463462 // cursor change is fired after onChange, so we put the auto-complete logic here
464463 //get value at line
465- if ( isFocused . current ) {
466- setShouldShowDropdown ( true ) ;
464+ if ( isFocused . current && ! suggestionsDismissed . current ) {
465+ setDropdownShown ( true ) ;
467466 handleEditorInputChange . cancel ( ) ;
468467 handleEditorInputChange ( value . current , cursorPosition . current ) ;
469468 }
@@ -480,6 +479,20 @@ export const CodeAutocompleteField = ({
480479 } ;
481480
482481 const handleInputEditorKeyPress = ( event : KeyboardEvent ) => {
482+ if ( event . key === OVERWRITTEN_KEYS . Escape ) {
483+ if ( editorState . dropdownShown ) {
484+ suggestionsDismissed . current = true ;
485+
486+ event . preventDefault ( ) ;
487+ handleEscapePressed ( ) ;
488+ }
489+ // A closed dropdown lets CodeMirror handle Escape so the next Tab can leave the editor.
490+ return true ;
491+ }
492+ if ( event . key === OVERWRITTEN_KEYS . Tab && ! editorState . dropdownShown ) {
493+ return true ;
494+ }
495+ suggestionsDismissed . current = false ;
483496 const overWrittenKeys : Array < string > = Object . values ( OVERWRITTEN_KEYS ) ;
484497 if ( overWrittenKeys . includes ( event . key ) && ( useTabForCompletions || event . key !== OVERWRITTEN_KEYS . Tab ) ) {
485498 //don't prevent when enter should create new line (multiline config) and dropdown isn't shown
@@ -498,7 +511,7 @@ export const CodeAutocompleteField = ({
498511
499512 const closeDropDown = ( ) => {
500513 setHighlightedElement ( undefined ) ;
501- setShouldShowDropdown ( false ) ;
514+ setDropdownShown ( false ) ;
502515 } ;
503516
504517 const handleDropdownChange = ( selectedSuggestion : CodeAutocompleteFieldSuggestionWithReplacementInfo ) => {
@@ -525,19 +538,20 @@ export const CodeAutocompleteField = ({
525538 }
526539 } ;
527540
528- const handleInputEditorClear = ( ) => {
529- dispatch ( {
530- changes : { from : 0 , to : cm ? .state . doc . length , insert : "" } ,
541+ const handleInputEditorClear = React . useCallback ( ( ) => {
542+ currentCm . current ?. dispatch ( {
543+ changes : { from : 0 , to : currentCm . current . state . doc . length , insert : "" } ,
531544 } ) ;
532545 cursorPosition . current = 0 ;
533546 handleChange ( "" ) ;
534- cm ?. focus ( ) ;
535- } ;
547+ currentCm . current ?. focus ( ) ;
548+ } , [ handleChange ] ) ;
536549
537550 const handleInputFocus = ( focusState : boolean ) => {
538551 onFocusChange ?.( focusState ) ;
539552 if ( focusState ) {
540- setShouldShowDropdown ( true ) ;
553+ suggestionsDismissed . current = false ;
554+ setDropdownShown ( true ) ;
541555 } else {
542556 closeDropDown ( ) ;
543557 }
@@ -556,6 +570,7 @@ export const CodeAutocompleteField = ({
556570 } ;
557571
558572 const handleInputMouseDown = React . useCallback ( ( editor : EditorView ) => {
573+ suggestionsDismissed . current = false ;
559574 const cursor = editorState . cm ?. state . selection . main . head ;
560575 const currentLine = editorState . cm ?. state . doc . lineAt ( cursor ?? 0 ) . number ;
561576 const clickedLine = editor ?. state . doc . lineAt ( cursor ?? 0 ) . number ;
@@ -658,6 +673,17 @@ export const CodeAutocompleteField = ({
658673 onMouseDown = { handleInputMouseDown }
659674 height = { height }
660675 readOnly = { readOnly }
676+ codeEditorProps = { {
677+ actions : hasValue ? (
678+ < IconButton
679+ data-test-id = "value-path-clear-btn"
680+ name = "operation-clear"
681+ text = { clearIconText }
682+ disabled = { readOnly }
683+ onClick = { handleInputEditorClear }
684+ />
685+ ) : undefined ,
686+ } }
661687 />
662688 ) ;
663689 } , [
@@ -670,7 +696,11 @@ export const CodeAutocompleteField = ({
670696 showScrollBar ,
671697 multiline ,
672698 handleInputMouseDown ,
699+ height ,
673700 readOnly ,
701+ hasValue ,
702+ clearIconText ,
703+ handleInputEditorClear ,
674704 effectiveIntent ,
675705 ] ) ;
676706 const autoSuggestionInput = (
@@ -708,17 +738,6 @@ export const CodeAutocompleteField = ({
708738 >
709739 { codeEditor }
710740 </ ContextOverlay >
711- { ! ! value . current && (
712- < span className = { BlueprintClassNames . INPUT_ACTION } ref = { inputActionsDisplayed } >
713- < IconButton
714- data-test-id = { "value-path-clear-btn" }
715- name = "operation-clear"
716- text = { clearIconText }
717- disabled = { readOnly }
718- onClick = { handleInputEditorClear }
719- />
720- </ span >
721- ) }
722741 </ div >
723742 </ div >
724743 ) ;
0 commit comments