Skip to content

Commit

Permalink
Feature/issue51 (#54)
Browse files Browse the repository at this point in the history
* feature/issue51: add minutesGap input

* feature/issue51: add minutesGap tests

* feature/issue51: add minutes formatter pipe

* ref(timepicker clock face): move logic from component template to ts

* feature/issue51: add minutesGap feature to examples and update version
  • Loading branch information
Agranom authored Oct 23, 2018
1 parent e1dc2b7 commit a4d4fcd
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 25 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file

## 2.8.0 (2018-10-23)

### Features

* feat(ngx-material-timepicker): add option `minutesGap` that defines a gap between minutes, closes [(#51)](https://github.com/Agranom/ngx-material-timepicker/issues/51)

## 2.7.0 (2018-10-21)

### Features
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Selector: `ngx-material-timepicker`
ESC: boolean | Disable or enable closing timepicker by ESC. |
| @Input()
enableKeyboardInput: boolean | To disable or enable changing time through a keyboard on the timepicker dial without interaction with a clock face. Set `false` by default |
| @Input()
minutesGap: number | To define a gap between minutes. Set `1` by default |
| @Output()
timeSet: EventEmitter\<string\> | Emits time when that was set. |
| @Output()
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ngx-material-timepicker",
"description": "Handy material design timepicker for angular",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"author": "Vitalii Boiko <boyko330@gmail.com>",
"keywords": [
Expand Down
21 changes: 20 additions & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ <h3 class="ngx-material-timepicker-examples__title">Examples</h3>
</div>
</div>
</section>
<section class="ngx-material-timepicker-examples__container ngx-material-timepicker-example">
<app-example-source-code [sourceCode]="minutesGapCode">Minutes Gap
</app-example-source-code>
<div class="ngx-material-timepicker-example__body">
<p class="ngx-material-timepicker-example__description">
You can set up a gap for minutes. It is '1' by default.
</p>
<div class="ngx-material-timepicker-example__form-group">
<input placeholder="Default gap"
[ngxTimepicker]="defaultGap" readonly>
<ngx-material-timepicker #defaultGap></ngx-material-timepicker>
</div>
<div class="ngx-material-timepicker-example__form-group">
<input placeholder="Gap with 5"
[ngxTimepicker]="with5Gap" readonly>
<ngx-material-timepicker #with5Gap [minutesGap]="5"></ngx-material-timepicker>
</div>
</div>
</section>
<section class="ngx-material-timepicker-examples__container ngx-material-timepicker-example">
<app-example-source-code [sourceCode]="customSettings">Custom settings examples
</app-example-source-code>
Expand All @@ -156,7 +175,7 @@ <h3 class="ngx-material-timepicker-examples__title">Examples</h3>
</main>
<footer class="footer">
<div class="container">
<p class="footer__version">Current version 2.7.0</p>
<p class="footer__version">Current version 2.8.0</p>
</div>
</footer>
</div>
14 changes: 14 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ export class AppComponent {
</div>
`;

minutesGapCode = `
<div class="minutes-gap-example">
<input placeholder="Default gap"
[ngxTimepicker]="defaultGap" readonly>
<ngx-material-timepicker #defaultGap></ngx-material-timepicker>
</div>
<div class="minutes-gap-example">
<input placeholder="Gap with 5"
[ngxTimepicker]="with5Gap" readonly>
<ngx-material-timepicker #with5Gap [minutesGap]="5"></ngx-material-timepicker>
</div>
`;

customSettings = `
<div class="custom-buttons-example">
<input placeholder="Custom buttons" aria-label="Custom buttons" [ngxTimepicker]="timepickerWithButtons" readonly>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class NgxMaterialTimepickerDialComponent implements OnChanges {
@Input() minTime: Moment;
@Input() maxTime: Moment;
@Input() isEditable: boolean;
@Input() minutesGap: number;

@Output() periodChanged = new EventEmitter<TimePeriod>();
@Output() timeUnitChanged = new EventEmitter<TimeUnit>();
Expand All @@ -47,7 +48,7 @@ export class NgxMaterialTimepickerDialComponent implements OnChanges {
}
if (changes['period'] && changes['period'].currentValue
|| changes['hour'] && changes['hour'].currentValue) {
const minutes = TimepickerTime.getMinutes();
const minutes = TimepickerTime.getMinutes(this.minutesGap);

this.minutes = TimepickerTime.disableMinutes(minutes, +this.hour, {
min: this.minTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[style.transform]="'rotateZ('+ time.angle +'deg) translateX(-50%)' | styleSanitizer"
*ngFor="let time of faceTime.slice(0, 12); trackBy: trackByTime">
<span [style.transform]="'rotateZ(-'+ time.angle +'deg)' | styleSanitizer"
[ngClass]="{'active': (time.time === selectedTime.time) && !isClockFaceDisabled, 'disabled': time.disabled}">{{time.time}}</span>
[ngClass]="{'active': isHourSelected(time.time), 'disabled': time.disabled}">{{time.time}}</span>
</div>
<div class="clock-face__inner" *ngIf="faceTime.length > 12"
[style.top]="'calc(50% - ' + innerClockFaceSize + 'px)'">
Expand All @@ -13,7 +13,7 @@
[style.height.px]="innerClockFaceSize"
*ngFor="let time of faceTime.slice(12, 24); trackBy: trackByTime">
<span [style.transform]="'rotateZ(-'+ time.angle +'deg)' | styleSanitizer"
[ngClass]="{'active': (time.time === selectedTime.time) && !isClockFaceDisabled, 'disabled': time.disabled}">
[ngClass]="{'active': isHourSelected(time.time), 'disabled': time.disabled}">
{{time.time}}</span>
</div>
</div>
Expand All @@ -28,8 +28,8 @@
[style.transform]="'rotateZ('+ time.angle +'deg) translateX(-50%)' | styleSanitizer"
*ngFor="let time of faceTime; trackBy: trackByTime">
<span [style.transform]="'rotateZ(-'+ time.angle +'deg)' | styleSanitizer"
[ngClass]="{'active': ((selectedTime.time === time.time) && time.time % 5 === 0) && !isClockFaceDisabled, 'disabled': time.disabled}">
{{time.time % 5 === 0 ? time.time : ''}}</span>
[ngClass]="{'active': isMinuteSelected(time.time), 'disabled': time.disabled}">
{{time.time | minutesFormatter: minutesGap}}</span>
</div>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {ElementRef, NO_ERRORS_SCHEMA, SimpleChanges} from '@angular/core';
import {ClockFaceTime} from '../../models/clock-face-time.interface';
import {StyleSanitizerPipe} from '../../pipes/style-sanitizer.pipe';
import {TimeUnit} from '../../models/time-unit.enum';
import {MinutesFormatterPipe} from '../../pipes/minutes-formatter.pipe';


describe('NgxMaterialTimepickerFaceComponent', () => {
Expand All @@ -12,7 +13,11 @@ describe('NgxMaterialTimepickerFaceComponent', () => {

beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [NgxMaterialTimepickerFaceComponent, StyleSanitizerPipe],
declarations: [
NgxMaterialTimepickerFaceComponent,
StyleSanitizerPipe,
MinutesFormatterPipe
],
schemas: [NO_ERRORS_SCHEMA]
}).createComponent(NgxMaterialTimepickerFaceComponent);

Expand Down Expand Up @@ -284,6 +289,35 @@ describe('NgxMaterialTimepickerFaceComponent', () => {
tick();
expect(counter).toBe(1);
}));

it(`should return 'true' or 'false' whether hour is selected or not`, () => {
component.selectedTime = {time: 1, angle: 30};
component.isClockFaceDisabled = false;

expect(component.isHourSelected(1)).toBeTruthy();
expect(component.isHourSelected(2)).toBeFalsy();

component.isClockFaceDisabled = true;
expect(component.isHourSelected(1)).toBeFalsy();
});

it(`should return 'true' or 'false' whether minute is selected or not`, () => {
const minute = 10;
component.selectedTime = {time: minute, angle: 30};
component.isClockFaceDisabled = false;
component.minutesGap = 10;

expect(component.isMinuteSelected(minute)).toBeTruthy();
expect(component.isMinuteSelected(5)).toBeFalsy();

component.isClockFaceDisabled = true;
expect(component.isMinuteSelected(minute)).toBeFalsy();

component.isClockFaceDisabled = false;
component.minutesGap = undefined;
component.selectedTime.time = 5;
expect(component.isMinuteSelected(5)).toBeTruthy();
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export class NgxMaterialTimepickerFaceComponent implements AfterViewInit, OnChan
@Input() selectedTime: ClockFaceTime;
@Input() unit: TimeUnit;
@Input() format: number;
@Input() minutesGap: number;

@Output() timeChange = new EventEmitter<ClockFaceTime>();
@Output() timeSelected = new EventEmitter<null>();

Expand Down Expand Up @@ -128,6 +130,14 @@ export class NgxMaterialTimepickerFaceComponent implements AfterViewInit, OnChan
this.isStarted = false;
}

isHourSelected(hour: number): boolean {
return (hour === this.selectedTime.time) && !this.isClockFaceDisabled;
}

isMinuteSelected(minute: number): boolean {
return ((this.selectedTime.time === minute) && (minute % (this.minutesGap || 5) === 0)) && !this.isClockFaceDisabled;
}

private setClockHandPosition(): void {
if (this.format === 24) {
if (this.selectedTime.time > 12 || this.selectedTime.time === '00') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<ngx-material-timepicker-face [faceTime]="minutesList" [selectedTime]="selectedMinute"
[minutesGap]="minutesGap"
(timeChange)="minuteChange.next($event)" [unit]="timeUnit.MINUTE"></ngx-material-timepicker-face>
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ export class NgxMaterialTimepickerMinutesFaceComponent implements OnChanges {
@Input() minTime: Moment;
@Input() maxTime: Moment;
@Input() format: number;
@Output() minuteChange = new EventEmitter<ClockFaceTime>();
@Input() minutesGap: number;

constructor() {
this.minutesList = TimepickerTime.getMinutes();
}
@Output() minuteChange = new EventEmitter<ClockFaceTime>();

ngOnChanges(changes: SimpleChanges) {
if (changes['period'] && changes['period'].currentValue) {
this.minutesList = TimepickerTime.disableMinutes(this.minutesList, this.selectedHour, {
const minutes = TimepickerTime.getMinutes(this.minutesGap);
this.minutesList = TimepickerTime.disableMinutes(minutes, this.selectedHour, {
min: this.minTime,
max: this.maxTime,
format: this.format,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[period]="selectedPeriod" [activeTimeUnit]="activeTimeUnit"
[minTime]="minTime" [maxTime]="maxTime"
[isEditable]="enableKeyboardInput"
[minutesGap]="minutesGap"
(periodChanged)="changePeriod($event)"
(timeUnitChanged)="changeTimeUnit($event)"
(hourChanged)="onHourChange($event)"
Expand Down Expand Up @@ -41,6 +42,7 @@
[maxTime]="maxTime"
[format]="format"
[period]="selectedPeriod"
[minutesGap]="minutesGap"
(minuteChange)="onMinuteChange($event)"></ngx-material-timepicker-minutes-face>
</div>
<div class="timepicker__actions">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,26 @@ describe('NgxMaterialTimepickerComponent', () => {
expect(spy).toHaveBeenCalledTimes(0);
});

it('should set minutesGap to 5', () => {
expect(component.minutesGap).toBeUndefined();
component.minutesGap = 5;

expect(component.minutesGap).toBe(5);
});

it('should set minutesGap to 1', () => {
expect(component.minutesGap).toBeUndefined();
component.minutesGap = 65;

expect(component.minutesGap).toBe(1);
});

it('should convert minutesGap to int', () => {
component.minutesGap = 6.5;

expect(component.minutesGap).toBe(6);
});

describe('Timepicker subscriptions', () => {
const hour = {time: 11, angle: 360};
const minute = {time: 44, angle: 36};
Expand Down
12 changes: 11 additions & 1 deletion src/app/material-timepicker/ngx-material-timepicker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export class NgxMaterialTimepickerComponent implements OnInit, OnDestroy {
selectedMinute: ClockFaceTime;
selectedPeriod: TimePeriod;

timePeriod = TimePeriod;
timeUnit = TimeUnit;
activeTimeUnit = TimeUnit.HOUR;

Expand All @@ -53,9 +52,20 @@ export class NgxMaterialTimepickerComponent implements OnInit, OnDestroy {
@Input('ESC') isEsc = true;
@Input() enableKeyboardInput: boolean;

@Input()
set minutesGap(gap: number) {
gap = Math.floor(gap);
this._minutesGap = gap <= 59 ? gap : 1;
}

get minutesGap(): number {
return this._minutesGap;
}

@Output() timeSet = new EventEmitter<string>();
@Output() closed = new EventEmitter<null>();

private _minutesGap: number;
private timepickerInput: TimepickerDirective;
private subscriptions: Subscription[] = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {NgxMaterialTimepickerDialComponent} from './components/timepicker-dial/n
import {
NgxMaterialTimepickerDialControlComponent
} from './components/timepicker-dial-control/ngx-material-timepicker-dial-control.component';
import { MinutesFormatterPipe } from './pipes/minutes-formatter.pipe';

@NgModule({
imports: [
Expand Down Expand Up @@ -57,7 +58,8 @@ import {
TimepickerDirective,
OverlayDirective,
FocusAnchorDirective,
NgxMaterialTimepickerToggleIconDirective
NgxMaterialTimepickerToggleIconDirective,
MinutesFormatterPipe
]
})
export class NgxMaterialTimepickerModule {
Expand Down
26 changes: 26 additions & 0 deletions src/app/material-timepicker/pipes/minutes-formatter.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {MinutesFormatterPipe} from './minutes-formatter.pipe';

describe('MinutesFormatterPipe', () => {
let pipe: MinutesFormatterPipe;

beforeEach(() => {
pipe = new MinutesFormatterPipe();
});

it('should create an instance', () => {
expect(pipe).toBeTruthy();
});

it('should return undefined or null without formatting', () => {
expect(pipe.transform(undefined)).toBeUndefined();
expect(pipe.transform(null)).toBeNull();
});

it('should return minute', () => {
expect(pipe.transform(3, 3)).toBe(3);
});

it('should return empty string', () => {
expect(pipe.transform(3)).toBe('');
});
});
16 changes: 16 additions & 0 deletions src/app/material-timepicker/pipes/minutes-formatter.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
name: 'minutesFormatter'
})
export class MinutesFormatterPipe implements PipeTransform {

transform(minute: number, gap = 5): number | string {
if (!minute) {
return minute;
}

return minute % gap === 0 ? minute : '';
}

}
Loading

0 comments on commit a4d4fcd

Please sign in to comment.