Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(datepicker): improve date and time inputs combination #3117

Merged
merged 13 commits into from
Oct 3, 2022
24 changes: 24 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,24 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'DateTimepickerShowcaseComponent',
name: 'Date Timepicker Showcase',
},
{
path: 'date-timepicker-dynamic-inputs-showcase.component',
link: '/datepicker/date-timepicker-dynamic-inputs-showcase.component',
component: 'DateTimepickerDynamicInputsShowcaseComponent',
name: 'Date Timepicker Dynamic Inputs Showcase',
},
{
path: 'datepicker-dynamic-inputs-showcase.component',
link: '/datepicker/datepicker-dynamic-inputs-showcase.component',
component: 'DatepickerDynamicInputsShowcaseComponent',
name: 'Datepicker Dynamic Inputs Showcase',
},
{
path: 'range-picker-dynamic-inputs-showcase.component',
link: '/datepicker/rangepicker-dynamic-inputs-showcase.component',
component: 'RangeickerDynamicInputsShowcaseComponent',
name: 'Range Picker Dynamic Inputs Showcase',
},
{
path: 'date-timepicker-single-column.component',
link: '/datepicker/date-timepicker-single-column.component',
Expand Down Expand Up @@ -597,6 +615,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'TimepickerWithSecondsComponent',
name: 'Timepicker With Seconds',
},
{
path: 'timepicker-dynamic-inputs-showcase.component',
link: '/timepicker/timepicker-dynamic-inputs-showcase.component',
component: 'TimepickerDynamicInputsShowcaseComponent',
name: 'Timepicker Dynamic Inputs Showcase',
},
{
path: 'timepicker-form-control.component',
link: '/timepicker/timepicker-form-control.component',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,26 @@ import { NbTimePickerComponent } from '../timepicker/timepicker.component';
[showNavigation]="showNavigation"
[showWeekNumber]="showWeekNumber"
[weekNumberSymbol]="weekNumberSymbol"
(dateChange)="onDateValueChange($event)">
(dateChange)="onDateValueChange($event)"
>
</nb-base-calendar>
<div class="timepicker-section"
[class.size-large]="isLarge()"
[class.timepicker-single-column-width]="singleColumn"
[class.timepicker-multiple-column-width]="!singleColumn">
<div
class="timepicker-section"
[class.size-large]="isLarge()"
[class.timepicker-single-column-width]="singleColumn"
[class.timepicker-multiple-column-width]="!singleColumn"
>
<div class="picker-title">{{ title }}</div>
<nb-timepicker
(onSelectTime)="onTimeChange($event)"
[date]="date"
[twelveHoursFormat]="twelveHoursFormat"
[showAmPmLabel]="showAmPmLabel"
[withSeconds]="showSeconds()"
[showFooter]="false"
[singleColumn]="singleColumn"
[step]="step">
[step]="step"
>
</nb-timepicker>
<ng-container nbPortalOutlet></ng-container>
</div>
Expand Down Expand Up @@ -79,6 +84,11 @@ export class NbCalendarWithTimeComponent<D> extends NbCalendarComponent<D> imple
* */
@Input() twelveHoursFormat: boolean;

/**
* Defines should show am/pm label if twelveHoursFormat enabled.
* */
@Input() showAmPmLabel: boolean;

/**
* Show seconds in timepicker.
* Ignored when singleColumn is true.
Expand Down Expand Up @@ -115,9 +125,10 @@ export class NbCalendarWithTimeComponent<D> extends NbCalendarComponent<D> imple
@ViewChild(NbPortalOutletDirective) portalOutlet: NbPortalOutletDirective;
@ViewChild(NbTimePickerComponent) timepicker: NbTimePickerComponent<D>;

constructor(protected dateService: NbDateService<D>,
public cd: ChangeDetectorRef,
protected calendarTimeModelService: NbCalendarTimeModelService<D>,
constructor(
protected dateService: NbDateService<D>,
public cd: ChangeDetectorRef,
protected calendarTimeModelService: NbCalendarTimeModelService<D>,
) {
super();
}
Expand Down Expand Up @@ -166,11 +177,8 @@ export class NbCalendarWithTimeComponent<D> extends NbCalendarComponent<D> imple
this.dateChange.emit(this.dateService.today());
}

/**
* We don't show seconds with twelve hours format
* */
showSeconds(): boolean {
return this.withSeconds && !this.twelveHoursFormat;
return this.withSeconds && !this.singleColumn;
}

isLarge(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import { NB_DATE_SERVICE_OPTIONS } from './datepicker.directive';
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, NbCalendarWithTimeComponent<D>>
implements OnInit {

export class NbDateTimePickerComponent<D>
extends NbBasePickerComponent<D, D, NbCalendarWithTimeComponent<D>>
implements OnInit
{
protected pickerClass: Type<NbCalendarWithTimeComponent<D>> = NbCalendarWithTimeComponent;

get value(): any {
Expand Down Expand Up @@ -78,6 +79,19 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
_twelveHoursFormat: boolean;
static ngAcceptInputType_twelveHoursFormat: NbBooleanInput;

/**
* Defines should show am/pm label if twelveHoursFormat enabled.
* */
@Input()
get showAmPmLabel(): boolean {
return this._showAmPmLabel;
}
set showAmPmLabel(value: boolean) {
this._showAmPmLabel = convertToBoolProperty(value);
}
protected _showAmPmLabel: boolean = true;
static ngAcceptInputType_showAmPmLabel: NbBooleanInput;

/**
* Show seconds in timepicker.
* Ignored when singleColumn is true.
Expand Down Expand Up @@ -112,14 +126,16 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
return this.valueChange as EventEmitter<D>;
}

constructor(@Inject(NB_DOCUMENT) document,
positionBuilder: NbPositionBuilderService,
triggerStrategyBuilder: NbTriggerStrategyBuilderService,
overlay: NbOverlayService,
cfr: ComponentFactoryResolver,
dateService: NbDateService<D>,
@Optional() @Inject(NB_DATE_SERVICE_OPTIONS) dateServiceOptions,
protected calendarWithTimeModelService: NbCalendarTimeModelService<D>) {
constructor(
@Inject(NB_DOCUMENT) document,
positionBuilder: NbPositionBuilderService,
triggerStrategyBuilder: NbTriggerStrategyBuilderService,
overlay: NbOverlayService,
cfr: ComponentFactoryResolver,
dateService: NbDateService<D>,
@Optional() @Inject(NB_DATE_SERVICE_OPTIONS) dateServiceOptions,
protected calendarWithTimeModelService: NbCalendarTimeModelService<D>,
) {
super(document, positionBuilder, triggerStrategyBuilder, overlay, cfr, dateService, dateServiceOptions);
}

Expand All @@ -131,6 +147,7 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
protected patchWithInputs() {
this.picker.singleColumn = this.singleColumn;
this.picker.twelveHoursFormat = this.twelveHoursFormat;
this.picker.showAmPmLabel = this.showAmPmLabel;
this.picker.withSeconds = this.withSeconds;
this.picker.step = this.step;
this.picker.title = this.title;
Expand All @@ -141,8 +158,10 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
if (this.twelveHoursFormat) {
this.picker.timeFormat = this.dateService.getTwelveHoursFormat();
} else {
this.picker.timeFormat = this.withSeconds ? this.dateService.getTwentyFourHoursFormatWithSeconds() :
this.dateService.getTwentyFourHoursFormat();
this.picker.timeFormat =
this.withSeconds && !this.singleColumn
? this.dateService.getTwentyFourHoursFormatWithSeconds()
: this.dateService.getTwentyFourHoursFormat();
}
super.patchWithInputs();

Expand All @@ -169,4 +188,3 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
}
}
}

12 changes: 10 additions & 2 deletions src/framework/theme/components/datepicker/datepicker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T, D> {
* */
abstract showWeekNumber: boolean;

readonly formatChanged$: Subject<void> = new Subject();

/**
* Calendar component class that has to be instantiated inside overlay.
* */
Expand Down Expand Up @@ -489,8 +491,14 @@ export class NbBasePickerComponent<D, T, P> extends NbBasePicker<D, T, P> implem
}

ngOnChanges(changes: SimpleChanges) {
if (changes.format && !changes.format.isFirstChange()) {
this.checkFormat();
if (changes.format) {
if (!changes.format.isFirstChange()) {
this.checkFormat();
}
this.formatChanged$.next();
}
if (this.picker) {
this.patchWithInputs();
}
}

Expand Down
24 changes: 22 additions & 2 deletions src/framework/theme/components/datepicker/datepicker.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {
ValidatorFn,
Validators,
} from '@angular/forms';
import { fromEvent, merge, Observable, Subject } from 'rxjs';
import { filter, map, take, takeUntil, tap } from 'rxjs/operators';
import { fromEvent, merge, Observable, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, map, pairwise, startWith, take, takeUntil, tap } from 'rxjs/operators';

import { NB_DOCUMENT } from '../../theme.options';
import { NbDateService } from '../calendar-kit/services/date.service';
Expand Down Expand Up @@ -113,6 +113,8 @@ export abstract class NbDatepicker<T, D = T> {
abstract get isShown(): boolean;

abstract get blur(): Observable<void>;

abstract get formatChanged$(): Observable<void>;
}

export const NB_DATE_ADAPTER = new InjectionToken<NbDatepickerAdapter<any>>('Datepicker Adapter');
Expand Down Expand Up @@ -285,6 +287,8 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
this.setupPicker();
}

protected pickerInputsChangedSubscription: Subscription | undefined;

/**
* Datepicker adapter.
* */
Expand Down Expand Up @@ -444,6 +448,22 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
this.picker.value = this.datepickerAdapter.parse(this.inputValue, this.picker.format);
}

this.pickerInputsChangedSubscription?.unsubscribe();
this.pickerInputsChangedSubscription = this.picker.formatChanged$
.pipe(
map(() => this.picker.format),
startWith(this.picker.format),
distinctUntilChanged(),
pairwise(),
takeUntil(this.destroy$),
)
.subscribe(([prevFormat, nextFormat]) => {
if (this.inputValue) {
const date = this.datepickerAdapter.parse(this.inputValue, prevFormat);
this.writeInput(date);
}
});

// In case datepicker component placed after the input with datepicker directive,
// we can't read `this.picker.format` on first change detection run,
// since it's not bound yet, so we have to wait for datepicker component initialization.
Expand Down
38 changes: 24 additions & 14 deletions src/framework/theme/components/timepicker/timepicker.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<nb-card *nbPortal
[class.supports-scrollbar-theming]="!isFirefox()"
class="nb-timepicker-container">
<nb-card *nbPortal [class.supports-scrollbar-theming]="!isFirefox()" class="nb-timepicker-container">
<nb-card-header class="column-header">
<ng-container *ngIf="singleColumn; else fullTimeHeadersBlock">
<div class="header-cell">Time</div>
Expand All @@ -9,7 +7,9 @@
<div class="header-cell">{{ hoursText }}</div>
<div class="header-cell">{{ minutesText }}</div>
<div *ngIf="withSeconds" class="header-cell">{{ secondsText }}</div>
<div *ngIf="twelveHoursFormat" class="header-cell">{{ ampmText }}</div>
<div *ngIf="twelveHoursFormat" class="header-cell">
<ng-template [ngIf]="showAmPmLabel">{{ ampmText }}</ng-template>
</div>
</ng-template>
</nb-card-header>

Expand All @@ -19,11 +19,13 @@
<nb-list-item
class="list-item"
[class.selected]="isSelectedFullTimeValue(item)"
*ngFor="let item of fullTimeOptions; trackBy: trackBySingleColumnValue.bind(this)">
*ngFor="let item of fullTimeOptions; trackBy: trackBySingleColumnValue.bind(this)"
>
<nb-timepicker-cell
[value]="getFullTimeString(item)"
[selected]="isSelectedFullTimeValue(item)"
(select)="selectFullTime(item)">
(select)="selectFullTime(item)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
Expand All @@ -34,47 +36,55 @@
<nb-list-item
class="list-item"
[class.selected]="isSelectedHour(item.value)"
*ngFor="let item of hoursColumnOptions; trackBy: trackByTimeValues">
*ngFor="let item of hoursColumnOptions; trackBy: trackByTimeValues"
>
<nb-timepicker-cell
[value]="item.text"
[selected]="isSelectedHour(item.value)"
(select)="setHour(item.value)">
(select)="setHour(item.value)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
<nb-list class="values-list">
<nb-list-item
class="list-item"
[class.selected]="isSelectedMinute(item.value)"
*ngFor="let item of minutesColumnOptions; trackBy: trackByTimeValues">
*ngFor="let item of minutesColumnOptions; trackBy: trackByTimeValues"
>
<nb-timepicker-cell
[value]="item.text"
[selected]="isSelectedMinute(item.value)"
(select)="setMinute(item.value)">
(select)="setMinute(item.value)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
<nb-list *ngIf="showSeconds()" class="values-list">
<nb-list-item
class="list-item"
[class.selected]="isSelectedSecond(item.value)"
*ngFor="let item of secondsColumnOptions; trackBy: trackByTimeValues">
*ngFor="let item of secondsColumnOptions; trackBy: trackByTimeValues"
>
<nb-timepicker-cell
[value]="item.text"
[selected]="isSelectedSecond(item.value)"
(select)="setSecond(item.value)">
(select)="setSecond(item.value)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
<nb-list *ngIf="twelveHoursFormat" class="values-list">
<nb-list-item
class="list-item am-pm-item"
[class.selected]="isSelectedDayPeriod(dayPeriod)"
*ngFor="let dayPeriod of dayPeriodColumnOptions; trackBy: trackByDayPeriod">
*ngFor="let dayPeriod of dayPeriodColumnOptions; trackBy: trackByDayPeriod"
>
<nb-timepicker-cell
[value]="dayPeriod"
[selected]="isSelectedDayPeriod(dayPeriod)"
(select)="changeDayPeriod(dayPeriod)">
(select)="changeDayPeriod(dayPeriod)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
Expand Down
Loading