Skip to content

Commit

Permalink
fix(form controls): mark as touched (#864)
Browse files Browse the repository at this point in the history
  • Loading branch information
yggg authored and nnixaa committed Oct 11, 2018
1 parent 9a65b94 commit e06d3a7
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 17 deletions.
15 changes: 11 additions & 4 deletions src/framework/theme/components/checkbox/checkbox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Component, Input, HostBinding, forwardRef } from '@angular/core';
import { Component, Input, HostBinding, forwardRef, ChangeDetectorRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { convertToBoolProperty } from '../helpers';

Expand Down Expand Up @@ -58,7 +58,8 @@ import { convertToBoolProperty } from '../helpers';
<input type="checkbox" class="customised-control-input"
[disabled]="disabled"
[checked]="value"
(change)="value = !value">
(change)="value = !value"
(blur)="setTouched()">
<span class="customised-control-indicator"></span>
<span class="customised-control-description">
<ng-content></ng-content>
Expand Down Expand Up @@ -123,9 +124,10 @@ export class NbCheckboxComponent implements ControlValueAccessor {
set value(val) {
this._value = val;
this.onChange(val);
this.onTouched();
}

constructor(private changeDetector: ChangeDetectorRef) {}

registerOnChange(fn: any) {
this.onChange = fn;
}
Expand All @@ -135,10 +137,15 @@ export class NbCheckboxComponent implements ControlValueAccessor {
}

writeValue(val: any) {
this.value = val;
this._value = val;
this.changeDetector.detectChanges();
}

setDisabledState(val: boolean) {
this.disabled = convertToBoolProperty(val);
}

setTouched() {
this.onTouched();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
* */
protected queue: T;

protected blur$: Subject<void> = new Subject<void>();

constructor(@Inject(NB_DOCUMENT) protected document,
protected positionBuilder: NbPositionBuilderService,
protected overlay: NbOverlayService,
Expand All @@ -172,6 +174,17 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
return this.onChange$.asObservable();
}

get isShown(): boolean {
return this.ref && this.ref.hasAttached();
}

/**
* Emits when datepicker looses focus.
*/
get blur(): Observable<void> {
return this.blur$.asObservable();
}

protected abstract get pickerValueChange(): Observable<T>;

ngOnDestroy() {
Expand Down Expand Up @@ -250,7 +263,10 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
protected subscribeOnTriggers() {
const triggerStrategy = this.createTriggerStrategy();
triggerStrategy.show$.pipe(takeWhile(() => this.alive)).subscribe(() => this.show());
triggerStrategy.hide$.pipe(takeWhile(() => this.alive)).subscribe(() => this.hide());
triggerStrategy.hide$.pipe(takeWhile(() => this.alive)).subscribe(() => {
this.blur$.next();
this.hide();
});
}

protected instantiatePicker() {
Expand Down
24 changes: 20 additions & 4 deletions src/framework/theme/components/datepicker/datepicker.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
Validators,
} from '@angular/forms';
import { Type } from '@angular/core/src/type';
import { fromEvent, Observable } from 'rxjs';
import { map, takeWhile } from 'rxjs/operators';
import { fromEvent, Observable, merge } from 'rxjs';
import { map, takeWhile, filter, take } from 'rxjs/operators';

import { NB_DOCUMENT } from '../../theme.options';
import { NbDateService } from '../calendar-kit';
Expand Down Expand Up @@ -99,6 +99,10 @@ export abstract class NbDatepicker<T> {
abstract hide();

abstract shouldHide(): boolean;

abstract get isShown(): boolean;

abstract get blur(): Observable<void>;
}

export const NB_DATE_ADAPTER = new InjectionToken<NbDatepickerAdapter<any>>('Datepicker Adapter');
Expand Down Expand Up @@ -225,8 +229,8 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
* */
protected picker: NbDatepicker<D>;
protected alive: boolean = true;
protected onChange: (D) => void = () => {
};
protected onChange: (D) => void = () => {};
protected onTouched: () => void = () => {};

/**
* Form control validators will be called in validators context, so, we need to bind them.
Expand Down Expand Up @@ -276,9 +280,11 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

setDisabledState(isDisabled: boolean): void {
this.input.disabled = isDisabled;
}

/**
Expand Down Expand Up @@ -367,6 +373,16 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
this.hidePicker();
}
});

merge(
this.picker.blur,
fromEvent(this.input, 'blur').pipe(
filter(() => !this.picker.isShown && this.document.activeElement !== this.input),
),
).pipe(
takeWhile(() => this.alive),
take(1),
).subscribe(() => this.onTouched());
}

protected writePicker(value: D) {
Expand Down
40 changes: 36 additions & 4 deletions src/framework/theme/components/radio/radio-group.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ import {
OnDestroy,
Output,
QueryList,
PLATFORM_ID,
Inject,
ElementRef,
} from '@angular/core';
import { NbRadioComponent } from './radio.component';
import { merge } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
import { isPlatformBrowser } from '@angular/common';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent, merge } from 'rxjs';
import { filter, switchMap, take, takeWhile } from 'rxjs/operators';
import { convertToBoolProperty } from '../helpers';
import { NB_DOCUMENT } from '../../theme.options';
import { NbRadioComponent } from './radio.component';


/**
Expand Down Expand Up @@ -104,14 +109,21 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
protected name: string;
protected alive: boolean = true;
protected onChange = (value: any) => {};
protected onTouched = () => {};

constructor(protected cd: ChangeDetectorRef) {}
constructor(
protected cd: ChangeDetectorRef,
protected hostElement: ElementRef<HTMLElement>,
@Inject(PLATFORM_ID) protected platformId,
@Inject(NB_DOCUMENT) protected document,
) {}

ngAfterContentInit() {
this.updateNames();
this.updateValues();
this.updateDisabled();
this.subscribeOnRadiosValueChange();
this.subscribeOnRadiosBlur();
}

ngOnDestroy() {
Expand All @@ -123,6 +135,7 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

writeValue(value: any): void {
Expand Down Expand Up @@ -171,4 +184,23 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
protected markRadiosForCheck() {
this.radios.forEach((radio: NbRadioComponent) => radio.markForCheck());
}

protected subscribeOnRadiosBlur() {
if (!isPlatformBrowser(this.platformId)) {
return;
}

const hostElement = this.hostElement.nativeElement;
fromEvent<Event>(hostElement, 'focusin')
.pipe(
filter(event => hostElement.contains(event.target as Node)),
switchMap(() => merge(
fromEvent<Event>(this.document, 'focusin'),
fromEvent<Event>(this.document, 'click'),
)),
filter(event => !hostElement.contains(event.target as Node)),
take(1),
)
.subscribe(() => this.onTouched());
}
}
3 changes: 3 additions & 0 deletions src/framework/theme/components/radio/radio.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { convertToBoolProperty } from '../helpers';
styleUrls: ['./radio.component.scss'],
})
export class NbRadioComponent {

@Input() name: string;

@Input() checked: boolean;
Expand All @@ -100,6 +101,8 @@ export class NbRadioComponent {

@Output() valueChange: EventEmitter<any> = new EventEmitter();

@Output() blur: EventEmitter<void> = new EventEmitter();

disabled: boolean;

constructor(protected cd: ChangeDetectorRef) {}
Expand Down
2 changes: 2 additions & 0 deletions src/framework/theme/components/radio/radio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';

import { NbRadioModule } from './radio.module';
import { NbRadioComponent } from './radio.component';
import { NB_DOCUMENT } from '../../theme.options';

import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
import { By } from '@angular/platform-browser';
Expand Down Expand Up @@ -35,6 +36,7 @@ describe('radio', () => {
TestBed.configureTestingModule({
imports: [NbRadioModule],
declarations: [NbRadioTestComponent],
providers: [ { provide: NB_DOCUMENT, useValue: document } ],
});

fixture = TestBed.createComponent(NbRadioTestComponent);
Expand Down
3 changes: 2 additions & 1 deletion src/framework/theme/components/select/select.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
[fullWidth]="fullWidth"
[outline]="outline"
[class.opened]="isOpened"
[ngClass]="overlayPosition">
[ngClass]="overlayPosition"
(blur)="trySetTouched()">

<ng-container *ngIf="selectionModel?.length">

Expand Down
34 changes: 31 additions & 3 deletions src/framework/theme/components/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
NbTriggerStrategyBuilder,
} from '../cdk';
import { NbOptionComponent } from './option.component';
import { NbButtonComponent } from '../button/button.component';
import { NB_DOCUMENT } from '../../theme.options';
import { convertToBoolProperty } from '../helpers';

Expand Down Expand Up @@ -112,7 +113,7 @@ export class NbSelectLabelComponent {
*
* @stacked-example(Select statuses, select/select-status.component)
*
* There are three select sizes:
* There are four select sizes:
*
* @stacked-example(Select sizes, select/select-sizes.component)
*
Expand Down Expand Up @@ -233,6 +234,8 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
* */
@ViewChild(NbPortalDirective) portal: NbPortalDirective;

@ViewChild(NbButtonComponent, { read: ElementRef }) button: ElementRef<HTMLButtonElement>;

multiple: boolean = false;

/**
Expand Down Expand Up @@ -270,10 +273,11 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
* Function passed through control value accessor to propagate changes.
* */
protected onChange: Function = () => {};
protected onTouched: Function = () => {};

constructor(@Inject(NB_DOCUMENT) protected document,
protected overlay: NbOverlayService,
protected hostRef: ElementRef,
protected hostRef: ElementRef<HTMLElement>,
protected positionBuilder: NbPositionBuilderService,
protected cd: ChangeDetectorRef) {
}
Expand Down Expand Up @@ -349,9 +353,12 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this.cd.detectChanges();
}

writeValue(value: T | T[]): void {
Expand Down Expand Up @@ -386,6 +393,7 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
this.selectionModel.forEach((option: NbOptionComponent<T>) => option.deselect());
this.selectionModel = [];
this.hide();
this.button.nativeElement.focus();
this.emitSelected(null);
}

Expand Down Expand Up @@ -413,6 +421,7 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
this.selectionModel = [option];
option.select();
this.hide();
this.button.nativeElement.focus();

this.emitSelected(option.value);
}
Expand Down Expand Up @@ -464,7 +473,12 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent

triggerStrategy.hide$
.pipe(takeWhile(() => this.alive))
.subscribe(() => this.hide());
.subscribe(($event: Event) => {
this.hide();
if (!this.isClickedWithinComponent($event)) {
this.onTouched();
}
});
}

protected subscribeOnPositionChange() {
Expand Down Expand Up @@ -541,4 +555,18 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
this.selectionModel.push(corresponding);
}
}

/**
* Sets touched if focus moved outside of button and overlay,
* ignoring the case when focus moved to options overlay.
*/
trySetTouched() {
if (this.isHidden) {
this.onTouched();
}
}

protected isClickedWithinComponent($event: Event) {
return this.hostRef.nativeElement === $event.target || this.hostRef.nativeElement.contains($event.target as Node);
}
}

0 comments on commit e06d3a7

Please sign in to comment.