@@ -13,16 +13,24 @@ import {
1313 useListNavigation ,
1414 useRole ,
1515} from "@floating-ui/react" ;
16- import { styled } from "styled-components" ;
16+ import { css , styled } from "styled-components" ;
1717import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" ;
1818import type { Option } from "./options" ;
1919import { findMatchingOptions } from "./options" ;
2020import { SuggestBoxItem } from "./SuggestBoxItem" ;
21+ import type { Diagnostic } from "./diagnostics" ;
2122
22- const Input = styled ( VSCodeTextField ) `
23+ const Input = styled ( VSCodeTextField ) < { $error : boolean } > `
2324 width: 430px;
2425
2526 font-family: var(--vscode-editor-font-family);
27+
28+ ${ ( props ) =>
29+ props . $error &&
30+ css `
31+ --dropdown-border: var(--vscode-inputValidation-errorBorder);
32+ --focus-border: var(--vscode-inputValidation-errorBorder);
33+ ` }
2634` ;
2735
2836const Container = styled . div `
@@ -50,7 +58,10 @@ const NoSuggestionsText = styled.div`
5058 padding-left: 22px;
5159` ;
5260
53- export type SuggestBoxProps < T extends Option < T > > = {
61+ export type SuggestBoxProps <
62+ T extends Option < T > ,
63+ D extends Diagnostic = Diagnostic ,
64+ > = {
5465 value ?: string ;
5566 onChange : ( value : string ) => void ;
5667 options : T [ ] ;
@@ -62,6 +73,12 @@ export type SuggestBoxProps<T extends Option<T>> = {
6273 */
6374 parseValueToTokens : ( value : string ) => string [ ] ;
6475
76+ /**
77+ * Validate the value. This is used to show syntax errors in the input.
78+ * @param value The user-entered value to validate.
79+ */
80+ validateValue ?: ( value : string ) => D [ ] ;
81+
6582 /**
6683 * Get the icon to display for an option.
6784 * @param option The option to get the icon for.
@@ -83,20 +100,29 @@ export type SuggestBoxProps<T extends Option<T>> = {
83100 * for easier testing.
84101 * @param props The props returned by `getReferenceProps` of {@link useInteractions}
85102 */
86- renderInputComponent ?: ( props : Record < string , unknown > ) => ReactNode ;
103+ renderInputComponent ?: (
104+ props : Record < string , unknown > ,
105+ hasError : boolean ,
106+ ) => ReactNode ;
87107} ;
88108
89- export const SuggestBox = < T extends Option < T > > ( {
109+ export const SuggestBox = <
110+ T extends Option < T > ,
111+ D extends Diagnostic = Diagnostic ,
112+ > ( {
90113 value = "" ,
91114 onChange,
92115 options,
93116 parseValueToTokens,
117+ validateValue,
94118 getIcon,
95119 getDetails,
96120 disabled,
97121 "aria-label" : ariaLabel ,
98- renderInputComponent = ( props ) => < Input { ...props } /> ,
99- } : SuggestBoxProps < T > ) => {
122+ renderInputComponent = ( props , hasError ) => (
123+ < Input { ...props } $error = { hasError } />
124+ ) ,
125+ } : SuggestBoxProps < T , D > ) => {
100126 const [ isOpen , setIsOpen ] = useState ( false ) ;
101127 const [ activeIndex , setActiveIndex ] = useState < number | null > ( null ) ;
102128
@@ -151,6 +177,13 @@ export const SuggestBox = <T extends Option<T>>({
151177 return findMatchingOptions ( options , parseValueToTokens ( value ) ) ;
152178 } , [ options , value , parseValueToTokens ] ) ;
153179
180+ const diagnostics = useMemo (
181+ ( ) => validateValue ?.( value ) ?? [ ] ,
182+ [ validateValue , value ] ,
183+ ) ;
184+
185+ const hasSyntaxError = diagnostics . length > 0 ;
186+
154187 useEffect ( ( ) => {
155188 if ( disabled ) {
156189 setIsOpen ( false ) ;
@@ -180,6 +213,7 @@ export const SuggestBox = <T extends Option<T>>({
180213 } ,
181214 disabled,
182215 } ) ,
216+ hasSyntaxError ,
183217 ) }
184218 { isOpen && (
185219 < FloatingPortal >
0 commit comments