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(timepicker): input filter to accept only numbers #3247

Merged
merged 2 commits into from Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions e2e-app/src/app/app.module.ts
Expand Up @@ -23,6 +23,7 @@ import {TooltipFocusComponent} from './tooltip/focus/tooltip-focus.component';
import {TooltipPositionComponent} from './tooltip/position/tooltip-position.component';
import {TypeaheadAutoCloseComponent} from './typeahead/autoclose/typeahead-autoclose.component';
import {TypeaheadFocusComponent} from './typeahead/focus/typeahead-focus.component';
import {TimepickerFilterComponent} from './timepicker/filter/timepicker-filter.component';
import {TimepickerNavigationComponent} from './timepicker/navigation/timepicker-navigation.component';
import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-validation.component';

Expand All @@ -47,6 +48,7 @@ import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-val
TypeaheadFocusComponent,
TypeaheadValidationComponent,
TypeaheadAutoCloseComponent,
TimepickerFilterComponent,
TimepickerNavigationComponent,
],
imports: [BrowserModule, FormsModule, ReactiveFormsModule, routing, NgbModule],
Expand Down
2 changes: 2 additions & 0 deletions e2e-app/src/app/app.routing.ts
Expand Up @@ -15,6 +15,7 @@ import {TooltipFocusComponent} from './tooltip/focus/tooltip-focus.component';
import {TooltipPositionComponent} from './tooltip/position/tooltip-position.component';
import {TypeaheadAutoCloseComponent} from './typeahead/autoclose/typeahead-autoclose.component';
import {TypeaheadFocusComponent} from './typeahead/focus/typeahead-focus.component';
import {TimepickerFilterComponent} from './timepicker/filter/timepicker-filter.component';
import {TimepickerNavigationComponent} from './timepicker/navigation/timepicker-navigation.component';
import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-validation.component';
import {DropdownPositionComponent} from './dropdown/position/dropdown-position.component';
Expand Down Expand Up @@ -63,6 +64,7 @@ export const routes: Routes = [
path: 'timepicker',
children: [
{path: 'navigation', component: TimepickerNavigationComponent},
{path: 'filter', component: TimepickerFilterComponent},
]
},
];
Expand Down
@@ -0,0 +1,4 @@
<ngb-timepicker
[seconds]="true"
[(ngModel)]="time"
></ngb-timepicker>
@@ -0,0 +1,8 @@
import {Component} from '@angular/core';

@Component({
templateUrl: './timepicker-filter.component.html',
})
export class TimepickerFilterComponent {
time = {hour: null, minute: null, second: null};
}
69 changes: 69 additions & 0 deletions e2e-app/src/app/timepicker/filter/timepicker-filter.e2e-spec.ts
@@ -0,0 +1,69 @@
import {protractor} from 'protractor';

import {openUrl} from '../../tools.po';

import {TimepickerFilterPage} from './timepicker-filter.po';

describe('Timepicker', () => {
let page: TimepickerFilterPage;

beforeAll(() => page = new TimepickerFilterPage());
beforeEach(async() => await openUrl('timepicker/filter'));

async function expectValue(expectedValue) {
const inputs = page.getFields();

const values = [];
for (let i = 0; i < inputs.length; i++) {
values[i] = await inputs[i].getAttribute('value');
}
expect(values.join(':')).toBe(expectedValue);
}

describe('filter', async() => {
it(`should accept numbers`, async() => {
await expectValue('::'); // No starting values

const inputs = page.getFields();
await inputs[0].sendKeys('1');
await inputs[1].sendKeys('2');
await inputs[2].sendKeys('3');

await inputs[0].click();
await expectValue('01:02:03');
});

it(`shouldn't accept alpha`, async() => {
await expectValue('::'); // No starting values

const inputs = page.getFields();
await inputs[0].sendKeys('A');
await inputs[1].sendKeys('A');
await inputs[2].sendKeys('A');

await inputs[0].click();
await expectValue('::');
});

it(`shouldn accept special commands`, async() => {

const inputs = page.getFields();

await inputs[0].sendKeys('1');
await inputs[0].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a'));
await inputs[0].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'c'));

await inputs[1].click();
await inputs[1].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'v'));

await inputs[2].click();
await inputs[2].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'v'));

await inputs[0].click();
await expectValue('01:01:01');

});

});

});
8 changes: 8 additions & 0 deletions e2e-app/src/app/timepicker/filter/timepicker-filter.po.ts
@@ -0,0 +1,8 @@
import {$} from 'protractor';

export type Field = 'hour' | 'minute' | 'second';

export class TimepickerFilterPage {
getField(field: Field) { return $(`.ngb-tp-${field} > input`); }
getFields() { return ['hour', 'minute', 'second'].map((field: Field) => this.getField(field)); }
}
16 changes: 12 additions & 4 deletions src/timepicker/timepicker.ts
Expand Up @@ -15,6 +15,8 @@ import {NgbTimepickerConfig} from './timepicker-config';
import {NgbTimeAdapter} from './ngb-time-adapter';
import {NgbTimepickerI18n} from './timepicker-i18n';

const FILTER_REGEX = /[^0-9]/g;

const NGB_TIMEPICKER_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgbTimepicker),
Expand All @@ -38,10 +40,12 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<span class="chevron ngb-tp-chevron"></span>
<span class="sr-only" i18n="@@ngb.timepicker.increment-hours">Increment hours</span>
</button>
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize" [class.form-control-lg]="isLargeSize"
maxlength="2" placeholder="HH" i18n-placeholder="@@ngb.timepicker.HH"
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize"
[class.form-control-lg]="isLargeSize"
maxlength="2" inputmode="numeric" placeholder="HH" i18n-placeholder="@@ngb.timepicker.HH"
[value]="formatHour(model?.hour)" (change)="updateHour($event.target.value)"
[readOnly]="readonlyInputs" [disabled]="disabled" aria-label="Hours" i18n-aria-label="@@ngb.timepicker.hours"
(input)="formatInput($event.target)"
(keydown.ArrowUp)="changeHour(hourStep); $event.preventDefault()"
(keydown.ArrowDown)="changeHour(-hourStep); $event.preventDefault()">
<button *ngIf="spinners" tabindex="-1" type="button" (click)="changeHour(-hourStep)"
Expand All @@ -60,9 +64,10 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<span class="sr-only" i18n="@@ngb.timepicker.increment-minutes">Increment minutes</span>
</button>
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize" [class.form-control-lg]="isLargeSize"
maxlength="2" placeholder="MM" i18n-placeholder="@@ngb.timepicker.MM"
maxlength="2" inputmode="numeric" placeholder="MM" i18n-placeholder="@@ngb.timepicker.MM"
[value]="formatMinSec(model?.minute)" (change)="updateMinute($event.target.value)"
[readOnly]="readonlyInputs" [disabled]="disabled" aria-label="Minutes" i18n-aria-label="@@ngb.timepicker.minutes"
(input)="formatInput($event.target)"
(keydown.ArrowUp)="changeMinute(minuteStep); $event.preventDefault()"
(keydown.ArrowDown)="changeMinute(-minuteStep); $event.preventDefault()">
<button *ngIf="spinners" tabindex="-1" type="button" (click)="changeMinute(-minuteStep)"
Expand All @@ -81,9 +86,10 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<span class="sr-only" i18n="@@ngb.timepicker.increment-seconds">Increment seconds</span>
</button>
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize" [class.form-control-lg]="isLargeSize"
maxlength="2" placeholder="SS" i18n-placeholder="@@ngb.timepicker.SS"
maxlength="2" inputmode="numeric" placeholder="SS" i18n-placeholder="@@ngb.timepicker.SS"
[value]="formatMinSec(model?.second)" (change)="updateSecond($event.target.value)"
[readOnly]="readonlyInputs" [disabled]="disabled" aria-label="Seconds" i18n-aria-label="@@ngb.timepicker.seconds"
(input)="formatInput($event.target)"
(keydown.ArrowUp)="changeSecond(secondStep); $event.preventDefault()"
(keydown.ArrowDown)="changeSecond(-secondStep); $event.preventDefault()">
<button *ngIf="spinners" tabindex="-1" type="button" (click)="changeSecond(-secondStep)"
Expand Down Expand Up @@ -245,6 +251,8 @@ export class NgbTimepicker implements ControlValueAccessor,
}
}

formatInput(input: HTMLInputElement) { input.value = input.value.replace(FILTER_REGEX, ''); }

formatHour(value: number) {
if (isNumber(value)) {
if (this.meridian) {
Expand Down