@@ -74,7 +74,7 @@ const formControlOptionalFields = new Set([
7474/**
7575 * A `TcbOp` which constructs an instance of the signal forms `Field` directive on a native element.
7676 */
77- export class TcbNativeFieldDirectiveTypeOp extends TcbOp {
77+ export class TcbNativeFieldOp extends TcbOp {
7878 /** Bindings that aren't supported on signal form fields. */
7979 protected readonly unsupportedBindingFields = new Set ( [
8080 ...formControlInputFields ,
@@ -84,27 +84,17 @@ export class TcbNativeFieldDirectiveTypeOp extends TcbOp {
8484 'minlength' ,
8585 ] ) ;
8686
87- private readonly inputType : string | null ;
88-
8987 override get optional ( ) {
9088 return false ;
9189 }
9290
9391 constructor (
94- private tcb : Context ,
95- private scope : Scope ,
96- private node : TmplAstElement ,
92+ protected tcb : Context ,
93+ protected scope : Scope ,
94+ protected node : TmplAstElement ,
95+ private inputType : string | null ,
9796 ) {
9897 super ( ) ;
99-
100- this . inputType =
101- ( node . name === 'input' && node . attributes . find ( ( attr ) => attr . name === 'type' ) ?. value ) ||
102- null ;
103-
104- // Radio controls are allowed to set the `value`.
105- if ( this . inputType === 'radio' ) {
106- this . unsupportedBindingFields . delete ( 'value' ) ;
107- }
10898 }
10999
110100 override execute ( ) : null {
@@ -120,11 +110,7 @@ export class TcbNativeFieldDirectiveTypeOp extends TcbOp {
120110
121111 checkUnsupportedFieldBindings ( this . node , this . unsupportedBindingFields , this . tcb ) ;
122112
123- const expectedType =
124- this . node instanceof TmplAstElement
125- ? this . getExpectedTypeFromDomNode ( this . node )
126- : this . getUnsupportedType ( ) ;
127-
113+ const expectedType = this . getExpectedTypeFromDomNode ( this . node ) ;
128114 const value = extractFieldValue ( fieldBinding . value , this . tcb , this . scope ) ;
129115
130116 // Create a variable with the expected type and check that the field value is assignable, e.g.
@@ -180,6 +166,38 @@ export class TcbNativeFieldDirectiveTypeOp extends TcbOp {
180166 }
181167}
182168
169+ /**
170+ * A variation of the `TcbNativeFieldOp` with specific logic for radio buttons.
171+ */
172+ export class TcbNativeRadioButtonFieldOp extends TcbNativeFieldOp {
173+ constructor ( tcb : Context , scope : Scope , node : TmplAstElement ) {
174+ super ( tcb , scope , node , 'radio' ) ;
175+ this . unsupportedBindingFields . delete ( 'value' ) ;
176+ }
177+
178+ override execute ( ) : null {
179+ super . execute ( ) ;
180+
181+ const valueBinding = this . node . inputs . find ( ( attr ) => {
182+ return attr . type === BindingType . Property && attr . name === 'value' ;
183+ } ) ;
184+
185+ if ( valueBinding !== undefined ) {
186+ // Include an additional expression to check that the `value` is a string.
187+ const id = this . tcb . allocateId ( ) ;
188+ const value = tcbExpression ( valueBinding . value , this . tcb , this . scope ) ;
189+ const assignment = ts . factory . createBinaryExpression ( id , ts . SyntaxKind . EqualsToken , value ) ;
190+ addParseSpanInfo ( assignment , valueBinding . sourceSpan ) ;
191+ this . scope . addStatement (
192+ tsDeclareVariable ( id , ts . factory . createKeywordTypeNode ( ts . SyntaxKind . StringKeyword ) ) ,
193+ ) ;
194+ this . scope . addStatement ( ts . factory . createExpressionStatement ( assignment ) ) ;
195+ }
196+
197+ return null ;
198+ }
199+ }
200+
183201/** Expands the set of bound inputs with the ones from custom field directives. */
184202export function expandBoundAttributesForField (
185203 directive : TypeCheckableDirectiveMeta ,
@@ -319,7 +337,7 @@ export function isNativeField(
319337 dir : TypeCheckableDirectiveMeta ,
320338 node : TmplAstNode ,
321339 allDirectiveMatches : TypeCheckableDirectiveMeta [ ] ,
322- ) : boolean {
340+ ) : node is TmplAstElement & { name : 'input' | 'select' | 'textarea' } {
323341 // Only applies to the `Field` directive.
324342 if ( ! isFieldDirective ( dir ) ) {
325343 return false ;
0 commit comments