From 55bd1f749bac01cc691e16283728c42e755cc706 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 6 Jul 2021 15:56:50 -0400 Subject: [PATCH] fix(datetime): add keyboard year navigation (#23585) resolves #21553 resolves #18122 --- core/src/components/datetime/datetime.tsx | 6 ++- core/src/components/datetime/readme.md | 27 ++++++++++++ .../datetime/test/manipulation.spec.ts | 44 +++++++++++++++++++ .../components/datetime/utils/manipulation.ts | 24 ++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 553948ac15..bb17bd873b 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -39,9 +39,11 @@ import { getNextDay, getNextMonth, getNextWeek, + getNextYear, getPreviousDay, getPreviousMonth, getPreviousWeek, + getPreviousYear, getStartOfWeek } from './utils/manipulation'; import { @@ -488,11 +490,11 @@ export class Datetime implements ComponentInterface { break; case 'PageUp': ev.preventDefault(); - partsToFocus = getPreviousMonth(parts); + partsToFocus = ev.shiftKey ? getPreviousYear(parts) : getPreviousMonth(parts); break; case 'PageDown': ev.preventDefault(); - partsToFocus = getNextMonth(parts); + partsToFocus = ev.shiftKey ? getNextYear(parts) : getNextMonth(parts); break; /** * Do not preventDefault here diff --git a/core/src/components/datetime/readme.md b/core/src/components/datetime/readme.md index e30fe0c1f9..8a66b12c53 100644 --- a/core/src/components/datetime/readme.md +++ b/core/src/components/datetime/readme.md @@ -109,6 +109,33 @@ subtracting 30 minutes, etc.), or even formatting data to a specific locale, then we highly recommend using [date-fns](https://date-fns.org) to work with dates in JavaScript. +## Accessibility + +### Keyboard Navigation + +`ion-datetime` has full keyboard support for navigating between focusable elements inside of the component. The following table details what each key does: + +| Key | Function | +| ------------------ | ------------------------------------------------------------ | +| `Tab` | Moves focus to the next focusable element. | +| `Shift` + `Tab` | Moves focus to the previous focusable element. | +| `Space` or `Enter` | Clicks the focusable element. | + +#### Date Grid + +| Key | Function | +| ------------------ | ------------------------------------------------------------ | +| `ArrowUp` | Moves focus to the same day of the previous week. | +| `ArrowDown` | Moves focus to the same day of the next week. | +| `ArrowRight` | Moves focus to the next day. | +| `ArrowLeft` | Moves focus to the previous day. | +| `Home` | Moves focus to the first day of the current week. | +| `End` | Moves focus to the last day of the current week. | +| `PageUp` | Changes the grid of dates to the previous month. | +| `PageDown` | Changes the grid of dates to the next month. | +| `Shift` + `PageUp` | Changes the grid of dates to the previous year. | +| `Shift` + `PageDown` | Changes the grid of dates to the next year. | + diff --git a/core/src/components/datetime/test/manipulation.spec.ts b/core/src/components/datetime/test/manipulation.spec.ts index 56661b1375..34bc8761b5 100644 --- a/core/src/components/datetime/test/manipulation.spec.ts +++ b/core/src/components/datetime/test/manipulation.spec.ts @@ -1,4 +1,6 @@ import { + getPreviousYear, + getNextYear, getPreviousMonth, getNextMonth, getPreviousDay, @@ -388,3 +390,45 @@ describe('getPreviousMonth()', () => { }); }); }); + +describe('getNextYear()', () => { + it('should return correct next year', () => { + expect(getNextYear({ month: 5, year: 2021, day: 1 })).toEqual({ + month: 5, + year: 2022, + day: 1 + }); + expect(getNextYear({ month: 12, year: 1999, day: 30 })).toEqual({ + month: 12, + year: 2000, + day: 30 + }); + // Leap year + expect(getNextYear({ month: 2, year: 2024, day: 29 })).toEqual({ + month: 2, + year: 2025, + day: 28 + }); + }); +}); + +describe('getPreviousYear()', () => { + it('should return correct next year', () => { + expect(getPreviousYear({ month: 5, year: 2021, day: 1 })).toEqual({ + month: 5, + year: 2020, + day: 1 + }); + expect(getPreviousYear({ month: 12, year: 1999, day: 30 })).toEqual({ + month: 12, + year: 1998, + day: 30 + }); + // Leap year + expect(getPreviousYear({ month: 2, year: 2024, day: 29 })).toEqual({ + month: 2, + year: 2023, + day: 28 + }); + }); +}); diff --git a/core/src/components/datetime/utils/manipulation.ts b/core/src/components/datetime/utils/manipulation.ts index 685aab6866..77fa381ae7 100644 --- a/core/src/components/datetime/utils/manipulation.ts +++ b/core/src/components/datetime/utils/manipulation.ts @@ -256,6 +256,30 @@ export const getNextMonth = (refParts: DatetimeParts) => { return { month, year, day }; } +const changeYear = (refParts: DatetimeParts, yearDelta: number) => { + const month = refParts.month; + const year = refParts.year + yearDelta; + + const numDaysInMonth = getNumDaysInMonth(month, year); + const day = (numDaysInMonth < refParts.day!) ? numDaysInMonth : refParts.day; + + return { month, year, day } +} + +/** + * Given DatetimeParts, generate the previous year. + */ +export const getPreviousYear = (refParts: DatetimeParts) => { + return changeYear(refParts, -1); +} + +/** + * Given DatetimeParts, generate the next year. + */ +export const getNextYear = (refParts: DatetimeParts) => { + return changeYear(refParts, 1); +} + /** * If PM, then internal value should * be converted to 24-hr time.