Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ export const DatePickerOverlayContentMixin = (superClass) =>
calendar.addEventListener('selected-date-changed', (e) => {
this.selectedDate = e.detail.value;
});

calendar.addEventListener('date-focus', (e) => {
this.__onDateFocus(e.detail.date);
});
});

this.calendars = calendars;
Expand Down Expand Up @@ -911,6 +915,25 @@ export const DatePickerOverlayContentMixin = (superClass) =>
}
}

/**
* Follows focus that something other than the keyboard handling moved to a date,
* such as a screen reader swiping to it, so that the arrow keys, the `focused` part
* and the day of month kept for PageUp / PageDown continue from that date.
* @private
*/
__onDateFocus(date) {
// The calendar focuses the date this element already tracks, nothing to follow.
if (dateEquals(date, this.focusedDate)) {
return;
}
// Same rule as the arrow keys: a disabled date may take focus, a date outside min / max may not.
if (!this._dateAllowed(date, undefined, undefined, () => false)) {
return;
}
this.focusedDate = date;
this._focusedMonthDate = date.getDate();
}

async focusDate(date, keepMonth) {
const dateToFocus = date || this.selectedDate || this.initialPosition || new Date();
this.focusedDate = dateToFocus;
Expand Down
16 changes: 16 additions & 0 deletions packages/date-picker/src/vaadin-month-calendar-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,22 @@ export const MonthCalendarMixin = (superClass) =>
this.setAttribute('role', 'application');

addListener(this.$.monthGrid, 'tap', this._handleTap.bind(this));
this.$.monthGrid.addEventListener('focusin', (e) => this._onDateFocusIn(e));
}

/**
* Reports the date whose button received focus, so the overlay can follow
* focus that was moved by something other than its own keyboard handling,
* such as a screen reader swiping to a date.
* @protected
*/
_onDateFocusIn(e) {
const cell = e.target.closest('[part~=date]');
if (cell?.date) {
this.dispatchEvent(
new CustomEvent('date-focus', { detail: { date: cell.date }, bubbles: true, composed: true }),
);
}
}

/** @override */
Expand Down
61 changes: 60 additions & 1 deletion packages/date-picker/test/keyboard-navigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
import { aTimeout, fixtureSync, nextRender } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import '../src/vaadin-date-picker.js';
import { getDefaultI18n, getFocusedCell, open, untilOverlayRendered, untilOverlayScrolled } from './helpers.js';
import {
getDateButton,
getDateCell,
getDefaultI18n,
getFocusedCell,
getMonthCalendar,
open,
untilOverlayRendered,
untilOverlayScrolled,
} from './helpers.js';

describe('keyboard navigation', () => {
describe('date-picker', () => {
Expand Down Expand Up @@ -79,6 +88,13 @@

expect(content.focusedDate.getTime()).to.equal(focused.getTime());
});

it('should show a date focused from outside in the input', async () => {
await open(datePicker);
getDateButton(getDateCell(getMonthCalendar(datePicker, 2001, 0), 15)).focus();
await nextRender();
expect(input.value).to.equal('1/15/2001');
});
});

describe('initial position', () => {
Expand Down Expand Up @@ -334,6 +350,49 @@
expect(overlay.focusedDate).to.eql(new Date(2000, 2, 31));
});

it('should follow focus moved to another date', async () => {
getDateButton(getDateCell(getMonthCalendar(overlay, 2000, 0), 15)).focus();
await nextRender();
expect(overlay.focusedDate).to.eql(new Date(2000, 0, 15));
const cell = getFocusedCell(overlay);
expect(cell.date).to.eql(new Date(2000, 0, 15));
expect(cell.getAttribute('part')).to.contain('focused');
});

it('should not notify when the focused date button regains focus', async () => {

Check failure on line 362 in packages/date-picker/test/keyboard-navigation.test.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=vaadin_web-components&issues=AaCLCiAKXsO918V9SrSo&open=AaCLCiAKXsO918V9SrSo&pullRequest=12722
const spy = sinon.spy();
overlay.addEventListener('focused-date-changed', spy);
const button = getDateButton(getDateCell(getMonthCalendar(overlay, 2000, 0), 1));
button.blur();
button.focus();
await nextRender();
expect(spy.called).to.be.false;
});

it('should not follow focus moved to a date outside max', async () => {
overlay.maxDate = new Date(2000, 0, 10);
await nextRender();
getDateButton(getDateCell(getMonthCalendar(overlay, 2000, 0), 15)).focus();
await nextRender();
expect(overlay.focusedDate).to.eql(new Date(2000, 0, 1));
});

it('should follow focus moved to a disabled date inside the range', async () => {
overlay.isDateDisabled = (date) => date.day === 15;
await nextRender();
getDateButton(getDateCell(getMonthCalendar(overlay, 2000, 0), 15)).focus();
await nextRender();
expect(overlay.focusedDate).to.eql(new Date(2000, 0, 15));
});

it('should keep the day of month of a date focused from outside on pagedown', async () => {
getDateButton(getDateCell(getMonthCalendar(overlay, 2000, 0), 15)).focus();
await nextRender();
await sendKeys({ press: 'PageDown' });
await untilOverlayScrolled(overlay);
expect(getFocusedCell(overlay).date).to.eql(new Date(2000, 1, 15));
});

it('should focus next year with shift and pagedown', async () => {
await sendKeys({ press: 'Shift+PageDown' });
await untilOverlayScrolled(overlay);
Expand Down