@@ -34,10 +34,15 @@ import {
3434 Actions ,
3535 composeWithUi ,
3636 ControlElement ,
37+ EnumOption ,
3738 isEnumControl ,
39+ JsonFormsState ,
40+ mapStateToEnumControlProps ,
3841 OwnPropsOfControl ,
42+ OwnPropsOfEnum ,
3943 RankedTester ,
4044 rankWith ,
45+ StatePropsOfControl ,
4146} from '@jsonforms/core' ;
4247import type { Observable } from 'rxjs' ;
4348import { map , startWith } from 'rxjs/operators' ;
@@ -67,13 +72,13 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
6772 autoActiveFirstOption
6873 #auto="matAutocomplete"
6974 (optionSelected)="onSelect($event)"
75+ [displayWith]="displayFn"
7076 >
71- <mat-option
72- *ngFor="let option of filteredOptions | async"
73- [value]="option"
74- >
75- {{ option }}
77+ @for (option of filteredOptions | async; track option.value) {
78+ <mat-option [value]="option">
79+ {{ option.label }}
7680 </mat-option>
81+ }
7782 </mat-autocomplete>
7883 <mat-hint *ngIf="shouldShowUnfocusedDescription() || focused">{{
7984 description
@@ -105,16 +110,40 @@ export class AutocompleteControlRenderer
105110 extends JsonFormsControl
106111 implements OnInit
107112{
108- @Input ( ) options : string [ ] ;
109- filteredOptions : Observable < string [ ] > ;
113+ @Input ( ) options ?: EnumOption [ ] | string [ ] ;
114+ valuesToTranslatedOptions ?: Map < string , EnumOption > ;
115+ filteredOptions : Observable < EnumOption [ ] > ;
110116 shouldFilter : boolean ;
111117 focused = false ;
112118
113119 constructor ( jsonformsService : JsonFormsAngularService ) {
114120 super ( jsonformsService ) ;
115121 }
122+
123+ protected override mapToProps (
124+ state : JsonFormsState
125+ ) : StatePropsOfControl & OwnPropsOfEnum {
126+ return mapStateToEnumControlProps ( state , this . getOwnProps ( ) ) ;
127+ }
128+
116129 getEventValue = ( event : any ) => event . target . value ;
117130
131+ override onChange ( ev : any ) {
132+ const eventValue = this . getEventValue ( ev ) ;
133+ const option = Array . from (
134+ this . valuesToTranslatedOptions ?. values ( ) ?? [ ]
135+ ) . find ( ( option ) => option . label === eventValue ) ;
136+ if ( ! option ) {
137+ super . onChange ( ev ) ;
138+ return ;
139+ }
140+
141+ this . jsonFormsService . updateCore (
142+ Actions . update ( this . propsPath , ( ) => option . value )
143+ ) ;
144+ this . triggerValidation ( ) ;
145+ }
146+
118147 ngOnInit ( ) {
119148 super . ngOnInit ( ) ;
120149 this . shouldFilter = false ;
@@ -124,6 +153,12 @@ export class AutocompleteControlRenderer
124153 ) ;
125154 }
126155
156+ override mapAdditionalProps ( _props : StatePropsOfControl & OwnPropsOfEnum ) {
157+ this . valuesToTranslatedOptions = new Map (
158+ ( _props . options ?? [ ] ) . map ( ( option ) => [ option . value , option ] )
159+ ) ;
160+ }
161+
127162 updateFilter ( event : any ) {
128163 // ENTER
129164 if ( event . keyCode === 13 ) {
@@ -136,30 +171,67 @@ export class AutocompleteControlRenderer
136171 onSelect ( ev : MatAutocompleteSelectedEvent ) {
137172 const path = composeWithUi ( this . uischema as ControlElement , this . path ) ;
138173 this . shouldFilter = false ;
139- this . jsonFormsService . updateCore (
140- Actions . update ( path , ( ) => ev . option . value )
141- ) ;
174+ const option : EnumOption = ev . option . value ;
175+ this . jsonFormsService . updateCore ( Actions . update ( path , ( ) => option . value ) ) ;
142176 this . triggerValidation ( ) ;
143177 }
144178
145- filter ( val : string ) : string [ ] {
146- return ( this . options || this . scopedSchema . enum || [ ] ) . filter (
147- ( option ) =>
148- ! this . shouldFilter ||
149- ! val ||
150- option . toLowerCase ( ) . indexOf ( val . toLowerCase ( ) ) === 0
179+ // use arrow function to bind "this" reference
180+ displayFn = ( option ?: string | EnumOption ) : string => {
181+ if ( ! option ) {
182+ return '' ;
183+ }
184+
185+ if ( typeof option === 'string' ) {
186+ if ( ! this . valuesToTranslatedOptions ) {
187+ return option ; // show raw value until translations are ready
188+ }
189+
190+ // if no option matches, it is a manual input
191+ return this . valuesToTranslatedOptions . get ( option ) ?. label ?? option ;
192+ }
193+
194+ return option ?. label ?? '' ;
195+ } ;
196+
197+ filter ( val : string | EnumOption | undefined ) : EnumOption [ ] {
198+ const options = Array . from ( this . valuesToTranslatedOptions ?. values ( ) || [ ] ) ;
199+
200+ if ( ! val || ! this . shouldFilter ) {
201+ return options ;
202+ }
203+
204+ const label = typeof val === 'string' ? val : val . label ;
205+ return options . filter ( ( option ) =>
206+ option . label . toLowerCase ( ) . startsWith ( label . toLowerCase ( ) )
151207 ) ;
152208 }
153- protected getOwnProps ( ) : OwnPropsOfAutoComplete {
209+ protected getOwnProps ( ) : OwnPropsOfControl & OwnPropsOfEnum {
154210 return {
155211 ...super . getOwnProps ( ) ,
156- options : this . options ,
212+ options : this . stringOptionsToEnumOptions ( this . options ) ,
157213 } ;
158214 }
159- }
160215
161- export const enumControlTester : RankedTester = rankWith ( 2 , isEnumControl ) ;
216+ /**
217+ * For {@link options} input backwards compatibility
218+ */
219+ protected stringOptionsToEnumOptions (
220+ options : typeof this . options
221+ ) : EnumOption [ ] | undefined {
222+ if ( ! options ) {
223+ return undefined ;
224+ }
162225
163- interface OwnPropsOfAutoComplete extends OwnPropsOfControl {
164- options : string [ ] ;
226+ return options . every ( ( item ) => typeof item === 'string' )
227+ ? options . map ( ( str ) => {
228+ return {
229+ label : str ,
230+ value : str ,
231+ } satisfies EnumOption ;
232+ } )
233+ : options ;
234+ }
165235}
236+
237+ export const enumControlTester : RankedTester = rankWith ( 2 , isEnumControl ) ;
0 commit comments