@@ -9,11 +9,13 @@ import {
99 linkedSignal ,
1010 model ,
1111 output ,
12+ viewChild ,
1213} from '@angular/core' ;
1314import { type ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
1415import { NgIcon , provideIcons } from '@ng-icons/core' ;
1516import { lucideCheck } from '@ng-icons/lucide' ;
1617import { BrnCheckbox } from '@spartan-ng/brain/checkbox' ;
18+ import { BrnFieldControlDescribedBy } from '@spartan-ng/brain/field' ;
1719import type { ChangeFn , TouchFn } from '@spartan-ng/brain/forms' ;
1820import { HlmIcon } from '@spartan-ng/helm/icon' ;
1921import { hlm } from '@spartan-ng/helm/utils' ;
@@ -31,18 +33,17 @@ export const HLM_CHECKBOX_VALUE_ACCESSOR = {
3133 providers : [ HLM_CHECKBOX_VALUE_ACCESSOR ] ,
3234 viewProviders : [ provideIcons ( { lucideCheck } ) ] ,
3335 changeDetection : ChangeDetectionStrategy . OnPush ,
36+ hostDirectives : [ BrnFieldControlDescribedBy ] ,
3437 host : {
3538 class : 'contents peer' ,
3639 'data-slot' : 'checkbox' ,
37- '[attr.id]' : 'null' ,
3840 '[attr.aria-label]' : 'null' ,
3941 '[attr.aria-labelledby]' : 'null' ,
40- '[attr.aria-describedby]' : 'null' ,
4142 '[attr.data-disabled]' : '_disabled() ? "" : null' ,
4243 } ,
4344 template : `
4445 <brn-checkbox
45- [id]="id ()"
46+ [id]="inputId ()"
4647 [name]="name()"
4748 [class]="_computedClass()"
4849 [checked]="checked()"
@@ -52,6 +53,7 @@ export const HLM_CHECKBOX_VALUE_ACCESSOR = {
5253 [aria-label]="ariaLabel()"
5354 [aria-labelledby]="ariaLabelledby()"
5455 [aria-describedby]="ariaDescribedby()"
56+ [forceInvalid]="forceInvalid()"
5557 (checkedChange)="_handleChange($event)"
5658 (touched)="_onTouched?.()"
5759 >
@@ -68,14 +70,15 @@ export class HlmCheckbox implements ControlValueAccessor {
6870
6971 protected readonly _computedClass = computed ( ( ) =>
7072 hlm (
71- 'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive peer size-4 shrink-0 cursor-default rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50' ,
73+ 'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 peer size-4 shrink-0 cursor-default rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50' ,
7274 this . userClass ( ) ,
7375 this . _disabled ( ) ? 'cursor-not-allowed opacity-50' : '' ,
76+ this . _errorStateClass ( ) ,
7477 ) ,
7578 ) ;
7679
7780 /** Used to set the id on the underlying brn element. */
78- public readonly id = input < string | null > ( null ) ;
81+ public readonly inputId = input < string | null > ( null ) ;
7982
8083 /** Used to set the aria-label attribute on the underlying brn element. */
8184 public readonly ariaLabel = input < string | null > ( null , { alias : 'aria-label' } ) ;
@@ -107,8 +110,20 @@ export class HlmCheckbox implements ControlValueAccessor {
107110 /** Whether the checkbox is disabled. */
108111 public readonly disabled = input < boolean , BooleanInput > ( false , { transform : booleanAttribute } ) ;
109112
113+ /** Whether to force the checkbox into an invalid state. */
114+ public readonly forceInvalid = input < boolean , BooleanInput > ( false , { transform : booleanAttribute } ) ;
115+
110116 protected readonly _disabled = linkedSignal ( this . disabled ) ;
111117
118+ private readonly _brnCheckbox = viewChild . required ( BrnCheckbox ) ;
119+
120+ private readonly _spartanInvalid = computed ( ( ) => this . forceInvalid ( ) || this . _brnCheckbox ( ) . spartanInvalid ?.( ) ) ;
121+ protected readonly _errorStateClass = computed ( ( ) =>
122+ this . _spartanInvalid ( )
123+ ? 'border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40'
124+ : '' ,
125+ ) ;
126+
112127 protected _onChange ?: ChangeFn < boolean > ;
113128 protected _onTouched ?: TouchFn ;
114129
0 commit comments