@@ -11,6 +11,7 @@ import { styles as shared } from './themes/shared/validator.common.css.js';
1111import { all } from './themes/themes.js' ;
1212import { styles } from './themes/validator.base.css.js' ;
1313
14+ /** Configuration for the validation container. */
1415interface ValidationContainerConfig {
1516 /** The id attribute for the validation container. */
1617 id ?: string ;
@@ -22,29 +23,39 @@ interface ValidationContainerConfig {
2223 hasHelperText ?: boolean ;
2324}
2425
25- function getValidationSlots ( element : IgcValidationContainerComponent ) {
26+ const VALIDATION_SLOTS_SELECTOR = 'slot:not([name="helper-text"])' ;
27+ const ALL_SLOTS_SELECTOR = 'slot' ;
28+
29+ function getValidationSlots (
30+ element : IgcValidationContainerComponent
31+ ) : NodeListOf < HTMLSlotElement > {
2632 return element . renderRoot . querySelectorAll < HTMLSlotElement > (
27- "slot:not([name='helper-text'])"
33+ VALIDATION_SLOTS_SELECTOR
2834 ) ;
2935}
3036
31- function hasProjection ( element : IgcValidationContainerComponent ) {
32- return Array . from (
33- element . renderRoot . querySelectorAll < HTMLSlotElement > ( 'slot' )
34- ) . every ( ( slot ) => isEmpty ( slot . assignedElements ( { flatten : true } ) ) ) ;
37+ function hasProjection ( element : IgcValidationContainerComponent ) : boolean {
38+ const allSlots =
39+ element . renderRoot . querySelectorAll < HTMLSlotElement > ( ALL_SLOTS_SELECTOR ) ;
40+ return Array . from ( allSlots ) . every ( ( slot ) =>
41+ isEmpty ( slot . assignedElements ( { flatten : true } ) )
42+ ) ;
3543}
3644
3745function hasProjectedValidation (
3846 element : IgcValidationContainerComponent ,
3947 slotName ?: string
40- ) {
41- const config : AssignedNodesOptions = { flatten : true } ;
48+ ) : boolean {
4249 const slots = Array . from ( getValidationSlots ( element ) ) ;
43- return slotName
44- ? slots
45- . filter ( ( slot ) => slot . name === slotName )
46- . some ( ( slot ) => slot . assignedElements ( config ) . length > 0 )
47- : slots . some ( ( slot ) => slot . assignedElements ( config ) . length > 0 ) ;
50+ const config : AssignedNodesOptions = { flatten : true } ;
51+
52+ if ( slotName ) {
53+ return slots
54+ . filter ( ( slot ) => slot . name === slotName )
55+ . some ( ( slot ) => ! isEmpty ( slot . assignedElements ( config ) ) ) ;
56+ }
57+
58+ return slots . some ( ( slot ) => ! isEmpty ( slot . assignedElements ( config ) ) ) ;
4859}
4960
5061/* blazorSuppress */
@@ -60,7 +71,7 @@ export default class IgcValidationContainerComponent extends LitElement {
6071 public static override styles = [ styles , shared ] ;
6172
6273 /* blazorSuppress */
63- public static register ( ) {
74+ public static register ( ) : void {
6475 registerComponent ( IgcValidationContainerComponent , IgcIconComponent ) ;
6576 }
6677
@@ -71,21 +82,26 @@ export default class IgcValidationContainerComponent extends LitElement {
7182 hasHelperText : true ,
7283 }
7384 ) : TemplateResult {
74- const { renderValidationSlots } = IgcValidationContainerComponent . prototype ;
7585 const helperText = config . hasHelperText
7686 ? html `< slot name ="helper-text " slot ="helper-text "> </ slot > `
77- : null ;
87+ : nothing ;
88+
89+ const validationSlots =
90+ IgcValidationContainerComponent . prototype . _renderValidationSlots (
91+ host . validity ,
92+ true
93+ ) ;
7894
7995 return html `
8096 < igc-validator
8197 id =${ ifDefined ( config . id ) }
8298 part =${ ifDefined ( config . part ) }
8399 slot=${ ifDefined ( config . slot ) }
84- .target=${ host }
85100 ?invalid=${ host . invalid }
101+ .target=${ host }
86102 exportparts="helper-text validation-message validation-icon"
87103 >
88- ${ helperText } ${ renderValidationSlots ( host . validity , true ) }
104+ ${ helperText } ${ validationSlots }
89105 </ igc-validator >
90106 ` ;
91107 }
@@ -109,7 +125,7 @@ export default class IgcValidationContainerComponent extends LitElement {
109125 this . _target . addEventListener ( 'invalid' , this ) ;
110126 }
111127
112- public get target ( ) {
128+ public get target ( ) : IgcFormControl {
113129 return this . _target ;
114130 }
115131
@@ -118,73 +134,87 @@ export default class IgcValidationContainerComponent extends LitElement {
118134 addThemingController ( this , all ) ;
119135 }
120136
121- protected override createRenderRoot ( ) {
137+ protected override createRenderRoot ( ) : HTMLElement | DocumentFragment {
122138 const root = super . createRenderRoot ( ) ;
123139 root . addEventListener ( 'slotchange' , this ) ;
124140 return root ;
125141 }
126142
127- public handleEvent ( { type } : Event ) {
128- const isInvalid = type === 'invalid' ;
129- const isSlotChange = type === 'slotchange' ;
130-
131- if ( isInvalid || isSlotChange ) {
132- this . invalid = isInvalid ? true : this . invalid ;
133- this . _hasSlottedContent = hasProjectedValidation ( this ) ;
143+ /** @internal */
144+ public handleEvent ( event : Event ) : void {
145+ switch ( event . type ) {
146+ case 'invalid' :
147+ if ( ! this . invalid ) {
148+ this . invalid = true ;
149+ }
150+ break ;
151+ case 'slotchange' : {
152+ const newHasSlottedContent = hasProjectedValidation ( this ) ;
153+ if ( this . _hasSlottedContent !== newHasSlottedContent ) {
154+ this . _hasSlottedContent = newHasSlottedContent ;
155+ }
156+ break ;
157+ }
134158 }
135159
136160 this . requestUpdate ( ) ;
137161 }
138162
139- protected renderValidationMessage ( slotName : string ) {
140- const icon = hasProjectedValidation ( this , slotName )
163+ protected _renderValidationMessage ( slotName : string ) : TemplateResult {
164+ const hasProjectedIcon = hasProjectedValidation ( this , slotName ) ;
165+ const parts = { 'validation-message' : true , empty : ! hasProjectedIcon } ;
166+ const icon = hasProjectedIcon
141167 ? html `
142168 < igc-icon
143169 aria-hidden ="true "
144170 name ="error "
145- collection ="default "
146171 part ="validation-icon "
147172 > </ igc-icon >
148173 `
149- : null ;
174+ : nothing ;
150175
151176 return html `
152- < div part =${ partMap ( { 'validation-message' : true , empty : ! icon } ) } >
177+ < div part =${ partMap ( parts ) } >
153178 ${ icon }
154179 < slot name =${ slotName } > </ slot >
155180 </ div >
156181 ` ;
157182 }
158183
159- protected * renderValidationSlots ( validity : ValidityState , projected = false ) {
184+ protected * _renderValidationSlots (
185+ validity : ValidityState ,
186+ projected = false
187+ ) : Generator < TemplateResult > {
188+ if ( ! validity . valid ) {
189+ yield projected
190+ ? html `< slot name ="invalid " slot ="invalid "> </ slot > `
191+ : this . _renderValidationMessage ( 'invalid' ) ;
192+ }
193+
160194 for ( const key in validity ) {
161- if ( key === 'valid' && ! validity [ key ] ) {
162- yield projected
163- ? html `< slot name ="invalid " slot ="invalid "> </ slot > `
164- : this . renderValidationMessage ( 'invalid' ) ;
165- } else if ( validity [ key as keyof ValidityState ] ) {
195+ if ( key !== 'valid' && validity [ key as keyof ValidityState ] ) {
166196 const name = toKebabCase ( key ) ;
167-
168197 yield projected
169198 ? html `< slot name =${ name } slot =${ name } > </ slot > `
170- : this . renderValidationMessage ( name ) ;
199+ : this . _renderValidationMessage ( name ) ;
171200 }
172201 }
173202 }
174203
175- protected renderHelper ( ) {
204+ protected _renderHelper ( ) : TemplateResult | typeof nothing {
176205 return this . invalid && this . _hasSlottedContent
177206 ? nothing
178207 : html `< slot name ="helper-text "> </ slot > ` ;
179208 }
180209
181- protected override render ( ) {
210+ protected override render ( ) : TemplateResult {
211+ const slots = this . invalid
212+ ? this . _renderValidationSlots ( this . target . validity )
213+ : nothing ;
214+
182215 return html `
183216 < div part =${ partMap ( { 'helper-text' : true , empty : hasProjection ( this ) } ) } >
184- ${ this . invalid
185- ? this . renderValidationSlots ( this . target . validity )
186- : nothing }
187- ${ this . renderHelper ( ) }
217+ ${ slots } ${ this . _renderHelper ( ) }
188218 </ div >
189219 ` ;
190220 }
0 commit comments