Skip to content

Commit

Permalink
feat: 🎸 Add checkbox and group components and directives
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom-Rune Bornholdt committed Jan 17, 2023
1 parent 0078825 commit 52e6a2a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-shrimps-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ethlete/components': minor
---

Add checkbox and group components and directives
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import {
AfterContentInit,
ChangeDetectionStrategy,
Component,
ContentChild,
ContentChildren,
QueryList,
ViewEncapsulation,
} from '@angular/core';
import { CheckboxGroupControlDirective, CHECKBOX_GROUP_CONTROL_TOKEN, CHECKBOX_TOKEN } from '../../directives';
import { CheckboxComponent } from '../checkbox';
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { DestroyService } from '@ethlete/core';
import { CheckboxGroupDirective } from '../../directives';

@Component({
selector: 'et-checkbox-group',
Expand All @@ -20,16 +12,7 @@ import { CheckboxComponent } from '../checkbox';
host: {
class: 'et-checkbox-group',
},
providers: [DestroyService],
hostDirectives: [CheckboxGroupDirective],
})
export class CheckboxGroupComponent implements AfterContentInit {
@ContentChildren(CHECKBOX_TOKEN)
checkboxes?: QueryList<CheckboxComponent>;

@ContentChild(CHECKBOX_GROUP_CONTROL_TOKEN)
groupControl?: CheckboxGroupControlDirective;

ngAfterContentInit(): void {
console.log(this.checkboxes);
console.log(this.groupControl);
}
}
export class CheckboxGroupComponent {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,94 @@
import { Directive } from '@angular/core';
import { AfterContentInit, ContentChild, ContentChildren, Directive, inject, QueryList } from '@angular/core';
import { DestroyService } from '@ethlete/core';
import { combineLatest, map, Observable, startWith, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs';
import {
CheckboxGroupControlDirective,
CHECKBOX_GROUP_CONTROL_TOKEN,
} from '../checkbox-group-control/checkbox-group-control.directive';
import { CheckboxDirective, CHECKBOX_TOKEN } from '../checkbox/checkbox.directive';

@Directive({
standalone: true,
})
export class CheckboxGroupDirective {}
export class CheckboxGroupDirective implements AfterContentInit {
private readonly _destroy$ = inject(DestroyService).destroy$;

@ContentChildren(CHECKBOX_TOKEN)
checkboxes?: QueryList<CheckboxDirective>;

@ContentChild(CHECKBOX_GROUP_CONTROL_TOKEN)
groupControl?: CheckboxGroupControlDirective;

checkboxesWithoutGroupCtrl$?: Observable<CheckboxDirective[]>;

ngAfterContentInit(): void {
if (!this.groupControl) {
console.warn('A checkbox group without a group control is totally useless.');
return;
}

this.checkboxesWithoutGroupCtrl$ = this.checkboxes?.changes.pipe(
startWith(this.checkboxes),
map(() => {
const cbs = this.checkboxes?.toArray() ?? [];

return cbs.filter((cb) => cb.uniqueId !== this.groupControl?.checkbox.uniqueId);
}),
);

this._monitorCheckboxes();
}

private _monitorCheckboxes(): void {
if (!this.checkboxesWithoutGroupCtrl$) {
return;
}

this.checkboxesWithoutGroupCtrl$
.pipe(
takeUntil(this._destroy$),
switchMap((checkboxes) =>
combineLatest(checkboxes.map((checkbox) => checkbox.change.pipe(startWith(checkbox.checked)))).pipe(
tap((checkStates) => {
if (!this.groupControl) {
return;
}

const allChecked = checkStates.every((checked) => checked);
const allUnchecked = checkStates.every((checked) => !checked);

if (allChecked) {
this.groupControl.checkbox.writeValue(true);
} else {
this.groupControl.checkbox.writeValue(false);
}

this.groupControl.checkbox.indeterminate = !allChecked && !allUnchecked;
this.groupControl.checkbox._markForCheck();
}),
),
),
)
.subscribe();

this.groupControl?.checkbox.change
.pipe(
startWith(this.groupControl?.checkbox.checked),
withLatestFrom(this.checkboxesWithoutGroupCtrl$),
takeUntil(this._destroy$),
tap(([checked, checkboxes]) => {
for (const checkbox of checkboxes ?? []) {
if (checkbox.uniqueId !== this.groupControl?.checkbox.uniqueId) {
checkbox.writeValue(checked);
checkbox._markForCheck();
}
}

for (const checkbox of checkboxes ?? []) {
checkbox._emitChangeEvent();
}
}),
)
.subscribe();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const CHECKBOX_VALUE_ACCESSOR = {

export const CHECKBOX_TOKEN = new InjectionToken<CheckboxDirective>('ET_CHECKBOX_DIRECTIVE_TOKEN');

let nextUniqueId = 0;

@Directive({
standalone: true,
providers: [CHECKBOX_VALUE_ACCESSOR, { provide: CHECKBOX_TOKEN, useExisting: CheckboxDirective }],
Expand All @@ -28,6 +30,12 @@ export const CHECKBOX_TOKEN = new InjectionToken<CheckboxDirective>('ET_CHECKBOX
export class CheckboxDirective implements ControlValueAccessor {
private readonly _cdr = inject(ChangeDetectorRef);

private readonly _uniqueId = `et-checkbox-${++nextUniqueId}`;

get uniqueId() {
return this._uniqueId;
}

@Input()
get checked(): boolean {
return this._checked;
Expand Down Expand Up @@ -120,7 +128,11 @@ export class CheckboxDirective implements ControlValueAccessor {
this._controlValueAccessorChangeFn(this.checked);
}

private _emitChangeEvent(): void {
_markForCheck() {
this._cdr.markForCheck();
}

_emitChangeEvent(): void {
this.change.emit(this.checked);
}
}

0 comments on commit 52e6a2a

Please sign in to comment.