diff --git a/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.component.spec.ts b/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.component.spec.ts index d99a43be5..3dc8a0dcd 100644 --- a/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.component.spec.ts +++ b/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.component.spec.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, input, model } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { BrnCheckboxComponent } from './brn-checkbox.component'; @@ -7,15 +7,14 @@ import { BrnCheckboxComponent } from './brn-checkbox.component'; standalone: true, template: ` `, imports: [BrnCheckboxComponent, FormsModule], }) export class BrnCheckboxNgModelSpecComponent { - @Input() - public disabled = false; - @Input() - public airplaneMode = false; + public readonly disabled = input(false); + + public readonly airplaneMode = model(false); } diff --git a/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.spec.ts b/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.spec.ts index c32ff5f39..b8f3d9e3d 100644 --- a/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.spec.ts +++ b/libs/ui/checkbox/brain/src/lib/brn-checkbox-ng-model.spec.ts @@ -24,7 +24,7 @@ describe('BrnCheckboxComponentNgModelIntegration', () => { expect(labelElement).toBeInTheDocument(); await user.click(labelElement); await screen.findByDisplayValue('on'); - expect(container.fixture.componentInstance.airplaneMode).toBe(true); + expect(container.fixture.componentInstance.airplaneMode()).toBe(true); }); it('should set input as default correctly and click should toggle then', async () => { @@ -32,21 +32,21 @@ describe('BrnCheckboxComponentNgModelIntegration', () => { await user.click(labelElement); await screen.findByDisplayValue('off'); - expect(container.fixture.componentInstance.airplaneMode).toBe(false); + expect(container.fixture.componentInstance.airplaneMode()).toBe(false); await user.click(labelElement); await screen.findByDisplayValue('on'); - expect(container.fixture.componentInstance.airplaneMode).toBe(true); + expect(container.fixture.componentInstance.airplaneMode()).toBe(true); }); it('should set input as default correctly and enter should toggle then', async () => { const { user, container } = await setup(true); await user.keyboard('[Tab][Enter]'); - expect(container.fixture.componentInstance.airplaneMode).toBe(false); + expect(container.fixture.componentInstance.airplaneMode()).toBe(false); await user.keyboard('[Enter]'); - expect(container.fixture.componentInstance.airplaneMode).toBe(true); + expect(container.fixture.componentInstance.airplaneMode()).toBe(true); }); it('should do nothing when disabled', async () => { @@ -54,10 +54,10 @@ describe('BrnCheckboxComponentNgModelIntegration', () => { await user.click(labelElement); await screen.findByDisplayValue('off'); - expect(container.fixture.componentInstance.airplaneMode).toBe(false); + expect(container.fixture.componentInstance.airplaneMode()).toBe(false); await user.click(labelElement); await screen.findByDisplayValue('off'); - expect(container.fixture.componentInstance.airplaneMode).toBe(false); + expect(container.fixture.componentInstance.airplaneMode()).toBe(false); }); }); diff --git a/libs/ui/checkbox/brain/src/lib/brn-checkbox.component.ts b/libs/ui/checkbox/brain/src/lib/brn-checkbox.component.ts index b06f47456..eb0fe23a5 100644 --- a/libs/ui/checkbox/brain/src/lib/brn-checkbox.component.ts +++ b/libs/ui/checkbox/brain/src/lib/brn-checkbox.component.ts @@ -5,13 +5,10 @@ import { ChangeDetectionStrategy, Component, ElementRef, - EventEmitter, HostListener, type OnDestroy, - Output, PLATFORM_ID, Renderer2, - ViewChild, ViewEncapsulation, booleanAttribute, computed, @@ -20,7 +17,9 @@ import { inject, input, model, + output, signal, + viewChild, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { ChangeFn, TouchFn } from '@spartan-ng/ui-forms-brain'; @@ -73,11 +72,11 @@ const CONTAINER_POST_FIX = '-checkbox'; `, host: { - '[attr.tabindex]': '_disabled() ? "-1" : "0"', + '[attr.tabindex]': 'state().disabled() ? "-1" : "0"', '[attr.data-state]': '_dataState()', '[attr.data-focus-visible]': 'focusVisible()', '[attr.data-focus]': 'focused()', - '[attr.data-disabled]': '_disabled()', + '[attr.data-disabled]': 'state().disabled()', '[attr.aria-labelledby]': 'null', '[attr.aria-label]': 'null', '[attr.aria-describedby]': 'null', @@ -136,20 +135,20 @@ export class BrnCheckboxComponent implements AfterContentInit, OnDestroy { public readonly required = input(false, { transform: booleanAttribute }); - private readonly _disabled = signal(false); - /** Only used as input */ public readonly disabled = input(false, { transform: booleanAttribute }); + protected readonly state = computed(() => ({ + disabled: signal(this.disabled()), + })); + // eslint-disable-next-line @typescript-eslint/no-empty-function protected _onChange: ChangeFn = () => {}; // eslint-disable-next-line @typescript-eslint/no-empty-function private _onTouched: TouchFn = () => {}; - @ViewChild('checkBox', { static: true }) - public checkbox?: ElementRef; + public readonly checkbox = viewChild.required>('checkBox'); - @Output() - public readonly changed = new EventEmitter(); + public readonly changed = output(); constructor() { effect(() => { @@ -157,30 +156,22 @@ export class BrnCheckboxComponent implements AfterContentInit, OnDestroy { if (!parent) return; // check if parent is a label and assume it is for this checkbox if (parent?.tagName === 'LABEL') { - this._renderer.setAttribute(parent, 'data-disabled', this._disabled() ? 'true' : 'false'); + this._renderer.setAttribute(parent, 'data-disabled', this.state().disabled() ? 'true' : 'false'); return; } if (!this._isBrowser) return; const label = parent?.querySelector(`label[for="${this.id()}"]`); if (!label) return; - this._renderer.setAttribute(label, 'data-disabled', this._disabled() ? 'true' : 'false'); + this._renderer.setAttribute(label, 'data-disabled', this.state().disabled() ? 'true' : 'false'); }); - - effect( - () => { - // sync disabled input - this._disabled.set(this.disabled()); - }, - { allowSignalWrites: true }, - ); } @HostListener('click', ['$event']) @HostListener('keyup.space', ['$event']) @HostListener('keyup.enter', ['$event']) toggle(event: Event) { - if (this._disabled()) return; + if (this.state().disabled()) return; event.preventDefault(); const previousChecked = this.checked(); this.checked.set(previousChecked === 'indeterminate' ? true : !previousChecked); @@ -208,15 +199,13 @@ export class BrnCheckboxComponent implements AfterContentInit, OnDestroy { } }); - if (!this.checkbox) return; - - this.checkbox.nativeElement.indeterminate = this.checked() === 'indeterminate'; - if (this.checkbox.nativeElement.indeterminate) { - this.checkbox.nativeElement.value = 'indeterminate'; + this.checkbox().nativeElement.indeterminate = this.checked() === 'indeterminate'; + if (this.checkbox().nativeElement.indeterminate) { + this.checkbox().nativeElement.value = 'indeterminate'; } else { - this.checkbox.nativeElement.value = this.checked() ? 'on' : 'off'; + this.checkbox().nativeElement.value = this.checked() ? 'on' : 'off'; } - this.checkbox.nativeElement.dispatchEvent(new Event('change')); + this.checkbox().nativeElement.dispatchEvent(new Event('change')); } ngOnDestroy() { @@ -241,7 +230,7 @@ export class BrnCheckboxComponent implements AfterContentInit, OnDestroy { /** Implemented as a part of ControlValueAccessor. */ setDisabledState(isDisabled: boolean): void { - this._disabled.set(isDisabled); + this.state().disabled.set(isDisabled); } /** diff --git a/libs/ui/checkbox/helm/src/lib/hlm-checkbox.component.ts b/libs/ui/checkbox/helm/src/lib/hlm-checkbox.component.ts index 32a8d66c3..6cb73dac4 100644 --- a/libs/ui/checkbox/helm/src/lib/hlm-checkbox.component.ts +++ b/libs/ui/checkbox/helm/src/lib/hlm-checkbox.component.ts @@ -1,15 +1,4 @@ -import { - Component, - EventEmitter, - Output, - booleanAttribute, - computed, - effect, - forwardRef, - input, - model, - signal, -} from '@angular/core'; +import { Component, booleanAttribute, computed, forwardRef, input, model, output, signal } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { BrnCheckboxComponent } from '@spartan-ng/ui-checkbox-brain'; import { hlm } from '@spartan-ng/ui-core'; @@ -32,7 +21,7 @@ export const HLM_CHECKBOX_VALUE_ACCESSOR = { [name]="name()" [class]="_computedClass()" [checked]="checked()" - [disabled]="_disabled()" + [disabled]="state().disabled()" [required]="required()" [aria-label]="ariaLabel()" [aria-labelledby]="ariaLabelledby()" @@ -59,7 +48,7 @@ export class HlmCheckboxComponent { 'group inline-flex border border-foreground shrink-0 cursor-pointer items-center rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring' + ' focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[state=checked]:text-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-background', this.userClass(), - this._disabled() ? 'cursor-not-allowed opacity-50' : '', + this.state().disabled() ? 'cursor-not-allowed opacity-50' : '', ), ); @@ -80,25 +69,20 @@ export class HlmCheckboxComponent { public readonly name = input(null); public readonly required = input(false, { transform: booleanAttribute }); - protected readonly _disabled = signal(false); public readonly disabled = input(false, { transform: booleanAttribute }); - private disableInput = effect( - () => { - this._disabled.set(this.disabled()); - }, - { allowSignalWrites: true }, - ); + protected readonly state = computed(() => ({ + disabled: signal(this.disabled()), + })); // icon inputs public readonly checkIconName = input('lucideCheck'); - public readonly checkIconClass = input(''); + public readonly checkIconClass = input(''); - @Output() - public changed = new EventEmitter(); + public readonly changed = output(); protected _handleChange(): void { - if (this._disabled()) return; + if (this.state().disabled()) return; const previousChecked = this.checked(); this.checked.set(previousChecked === 'indeterminate' ? true : !previousChecked); @@ -128,6 +112,6 @@ export class HlmCheckboxComponent { } setDisabledState(isDisabled: boolean): void { - this._disabled.set(isDisabled); + this.state().disabled.set(isDisabled); } }