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

fix(datepicker): focus on multiple instances (#3494) #3549

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 @@ -10,6 +10,7 @@ import {NavigationComponent} from './navigation.component';

import {DatepickerAutoCloseComponent} from './datepicker/autoclose/datepicker-autoclose.component';
import {DatepickerFocusComponent} from './datepicker/focus/datepicker-focus.component';
import {DatepickerMultipleComponent} from './datepicker/multiple/datepicker-multiple.component';
import {DropdownAutoCloseComponent} from './dropdown/autoclose/dropdown-autoclose.component';
import {DropdownFocusComponent} from './dropdown/focus/dropdown-focus.component';
import {DropdownPositionComponent} from './dropdown/position/dropdown-position.component';
Expand All @@ -35,6 +36,7 @@ import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-val
NavigationComponent,
DatepickerAutoCloseComponent,
DatepickerFocusComponent,
DatepickerMultipleComponent,
DropdownAutoCloseComponent,
DropdownFocusComponent,
DropdownPositionComponent,
Expand Down
4 changes: 3 additions & 1 deletion e2e-app/src/app/app.routing.ts
Expand Up @@ -3,6 +3,7 @@ import {RouterModule, Routes} from '@angular/router';

import {DatepickerAutoCloseComponent} from './datepicker/autoclose/datepicker-autoclose.component';
import {DatepickerFocusComponent} from './datepicker/focus/datepicker-focus.component';
import {DatepickerMultipleComponent} from './datepicker/multiple/datepicker-multiple.component';
import {DropdownAutoCloseComponent} from './dropdown/autoclose/dropdown-autoclose.component';
import {DropdownFocusComponent} from './dropdown/focus/dropdown-focus.component';
import {DropdownPositionComponent} from './dropdown/position/dropdown-position.component';
Expand All @@ -27,7 +28,8 @@ export const routes: Routes = [
path: 'datepicker',
children: [
{path: 'focus', component: DatepickerFocusComponent},
{path: 'autoclose', component: DatepickerAutoCloseComponent}
{path: 'autoclose', component: DatepickerAutoCloseComponent},
{path: 'multiple', component: DatepickerMultipleComponent}
]
},
{
Expand Down
4 changes: 2 additions & 2 deletions e2e-app/src/app/datepicker/datepicker.po.ts
Expand Up @@ -7,9 +7,9 @@ export class DatepickerPage {

getToggle() { return $('#toggle'); }

getDayElement(date: Date) {
getDayElement(date: Date, datepicker = this.getDatepicker()) {
const ariaLabel = date.toLocaleString('en', {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'});
return this.getDatepicker().$(`div.ngb-dp-day[aria-label="${ariaLabel}"]`);
return datepicker.$(`div.ngb-dp-day[aria-label="${ariaLabel}"]`);
}

getWeekdayElements() { return this.getDatepicker().$$('div.ngb-dp-weekday'); }
Expand Down
@@ -0,0 +1,14 @@
<h3>Datepicker focus tests</h3>

<form>
<div>
<ngb-datepicker id="dp1" [startDate]="startDate"></ngb-datepicker>
<ngb-datepicker id="dp2" [startDate]="startDate"></ngb-datepicker>

<span class="ml-3">startDate is
<span id="start-date">{{ startDate | json }}</span>
</span>

</div>

</form>
@@ -0,0 +1,6 @@
import {Component} from '@angular/core';

@Component({templateUrl: './datepicker-multiple.component.html'})
export class DatepickerMultipleComponent {
startDate = {year: 2016, month: 8};
}
@@ -0,0 +1,22 @@
import {Key} from 'protractor';
import {sendKey, openUrl} from '../../tools.po';
import {DatepickerMultiplePage} from './datepicker-multiple.po';

describe('Datepicker multiple instances', () => {
let page: DatepickerMultiplePage;

beforeAll(() => page = new DatepickerMultiplePage());

beforeEach(async() => await openUrl('datepicker/multiple'));

it('the instance tapped should gain focus', async() => {
const dp1 = await page.getDatepicker('#dp1');
const dp2 = await page.getDatepicker('#dp2');
await page.getDayElement(new Date(2016, 7, 1), dp1).click();
await sendKey(Key.ARROW_DOWN);
await page.expectActive(new Date(2016, 7, 8), dp1);
await page.getDayElement(new Date(2016, 7, 1), dp2).click();
await sendKey(Key.ARROW_DOWN);
await page.expectActive(new Date(2016, 7, 8), dp2);
});
});
10 changes: 10 additions & 0 deletions e2e-app/src/app/datepicker/multiple/datepicker-multiple.po.ts
@@ -0,0 +1,10 @@
import {DatepickerPage} from './../datepicker.po';
import {expectFocused} from '../../tools.po';

export class DatepickerMultiplePage extends DatepickerPage {
async expectActive(date: Date, datepicker) {
const dateElement = this.getDayElement(date, datepicker);
expect(await dateElement.$('.active').isPresent()).toBeTruthy(`The date should be active`);
await expectFocused(dateElement, `active date should be focused`);
}
}
2 changes: 2 additions & 0 deletions src/datepicker/datepicker-input.spec.ts
Expand Up @@ -364,6 +364,7 @@ describe('NgbInputDatepicker', () => {
<input ngbDatepicker [startDate]="{year: 2018, month: 3}" [(ngModel)]="date" #d="ngbDatepicker"
[ngModelOptions]="{updateOn: 'blur'}">`);

fixture.detectChanges();
const inputDebugEl = fixture.debugElement.query(By.css('input'));
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);

Expand Down Expand Up @@ -877,6 +878,7 @@ describe('NgbInputDatepicker', () => {

it('should add .ngb-dp-body class when attached to body', () => {
const fixture = createTestCmpt(`<input ngbDatepicker #d="ngbDatepicker" [container]="container">`);
fixture.detectChanges();
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);

// No container specified
Expand Down
4 changes: 3 additions & 1 deletion src/datepicker/datepicker.ts
Expand Up @@ -397,14 +397,16 @@ export class NgbDatepicker implements OnDestroy,
this._ngZone.runOutsideAngular(() => {
const focusIns$ = fromEvent<FocusEvent>(this._monthsEl.nativeElement, 'focusin');
const focusOuts$ = fromEvent<FocusEvent>(this._monthsEl.nativeElement, 'focusout');
const {nativeElement} = this._elementRef;

// we're changing 'focusVisible' only when entering or leaving months view
// and ignoring all focus events where both 'target' and 'related' target are day cells
merge(focusIns$, focusOuts$)
.pipe(
filter(
({target, relatedTarget}) =>
!(hasClassName(target, 'ngb-dp-day') && hasClassName(relatedTarget, 'ngb-dp-day'))),
!(hasClassName(target, 'ngb-dp-day') && hasClassName(relatedTarget, 'ngb-dp-day') &&
nativeElement.contains(target as Node) && nativeElement.contains(relatedTarget as Node))),
takeUntil(this._destroyed$))
.subscribe(({type}) => this._ngZone.run(() => this._service.focusVisible = type === 'focusin'));
});
Expand Down