From c8ec5a95ac393edab92ce8b0293ae8e251a77274 Mon Sep 17 00:00:00 2001 From: Rong Sen Ng Date: Mon, 24 May 2021 01:20:10 +0800 Subject: [PATCH] refactor: add keyboard support in year grid --- src/constants.ts | 11 +++ ...-date.ts => compute-next-selected-date.ts} | 0 src/month-calendar/month-calendar.ts | 2 +- src/year-grid/ to-next-selected-year.ts | 29 +++++++ src/year-grid/stylings.ts | 3 +- src/year-grid/typings.ts | 11 ++- src/year-grid/year-grid.ts | 76 +++++++++++++++---- 7 files changed, 113 insertions(+), 19 deletions(-) rename src/helpers/{compute-next-focused-date.ts => compute-next-selected-date.ts} (100%) create mode 100644 src/year-grid/ to-next-selected-year.ts diff --git a/src/constants.ts b/src/constants.ts index 211b4fa9..15448f28 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -64,3 +64,14 @@ export const calendarKeyCodeSet = new Set([ keyCodesRecord.ENTER, keyCodesRecord.SPACE, ]); + +export const yearGridKeyCodeSet = new Set([ + keyCodesRecord.ARROW_DOWN, + keyCodesRecord.ARROW_LEFT, + keyCodesRecord.ARROW_RIGHT, + keyCodesRecord.ARROW_UP, + keyCodesRecord.END, + keyCodesRecord.ENTER, + keyCodesRecord.HOME, + keyCodesRecord.SPACE, +]); diff --git a/src/helpers/compute-next-focused-date.ts b/src/helpers/compute-next-selected-date.ts similarity index 100% rename from src/helpers/compute-next-focused-date.ts rename to src/helpers/compute-next-selected-date.ts diff --git a/src/month-calendar/month-calendar.ts b/src/month-calendar/month-calendar.ts index 1db0bf9a..2d087ca7 100644 --- a/src/month-calendar/month-calendar.ts +++ b/src/month-calendar/month-calendar.ts @@ -8,7 +8,7 @@ import { classMap } from 'lit/directives/class-map.js'; import { resetShadowRoot } from '../ stylings.js'; import type { navigationKeyCodeSet } from '../constants.js'; import { calendarKeyCodeSet, keyCodesRecord } from '../constants.js'; -import { computeNextSelectedDate } from '../helpers/compute-next-focused-date.js'; +import { computeNextSelectedDate } from '../helpers/compute-next-selected-date.js'; import { dispatchCustomEvent } from '../helpers/dispatch-custom-event.js'; import { isInTargetMonth } from '../helpers/is-in-current-month.js'; import { toClosestTarget } from '../helpers/to-closest-target.js'; diff --git a/src/year-grid/ to-next-selected-year.ts b/src/year-grid/ to-next-selected-year.ts new file mode 100644 index 00000000..5a22f1c9 --- /dev/null +++ b/src/year-grid/ to-next-selected-year.ts @@ -0,0 +1,29 @@ +import { keyCodesRecord } from '../constants.js'; +import type { ToNextSelectableYearInit } from './typings.js'; + +const { + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, + ARROW_UP, + END, + HOME, +} = keyCodesRecord; + +export function toNextSelectedYear({ + keyCode, + max, + min, + year, +}: ToNextSelectableYearInit): number { + switch (keyCode) { + case ARROW_UP: return year - 4; + case ARROW_DOWN: return year + 4; + case ARROW_LEFT: return year - 1; + case ARROW_RIGHT: return year + 1; + case END: return max.getUTCFullYear(); + case HOME: return min.getUTCFullYear(); + default: + return year; + } +} diff --git a/src/year-grid/stylings.ts b/src/year-grid/stylings.ts index f15b812e..1b3c5876 100644 --- a/src/year-grid/stylings.ts +++ b/src/year-grid/stylings.ts @@ -6,6 +6,7 @@ export const yearGridStyling = css` grid-auto-flow: row; grid-template-columns: repeat(4, minmax(1px, 56px)); grid-template-rows: repeat(auto-fit, 32px); - place-items: center; + align-items: center; + justify-items: center; } `; diff --git a/src/year-grid/typings.ts b/src/year-grid/typings.ts index b7e8ec5e..3f977bdb 100644 --- a/src/year-grid/typings.ts +++ b/src/year-grid/typings.ts @@ -1,4 +1,13 @@ -import type { Formatters } from '../typings.js'; +import type { ChangedProperties, Formatters, SupportedKeyCode } from '../typings.js'; + +export interface ToNextSelectableYearInit { + year: number; + keyCode: SupportedKeyCode; + max: Date; + min: Date; +} + +export type YearGridChangedProperties = ChangedProperties; export interface YearGridData { date: Date; diff --git a/src/year-grid/year-grid.ts b/src/year-grid/year-grid.ts index ae21f159..f7d944bd 100644 --- a/src/year-grid/year-grid.ts +++ b/src/year-grid/year-grid.ts @@ -8,14 +8,15 @@ import { html, LitElement } from 'lit'; import { toUTCDate } from 'nodemod/dist/calendar/helpers/to-utc-date.js'; import { resetShadowRoot } from '../ stylings.js'; -import { MAX_DATE } from '../constants.js'; +import { keyCodesRecord, MAX_DATE, yearGridKeyCodeSet } from '../constants.js'; import { dispatchCustomEvent } from '../helpers/dispatch-custom-event.js'; import { toClosestTarget } from '../helpers/to-closest-target.js'; import { toResolvedDate } from '../helpers/to-resolved-date.js'; import { toYearList } from '../helpers/to-year-list.js'; import { APP_YEAR_GRID_BUTTON_NAME } from '../year-grid-button/constants.js'; +import { toNextSelectedYear } from './ to-next-selected-year.js'; import { yearGridStyling } from './stylings.js'; -import type { YearGridData, YearGridProperties } from './typings.js'; +import type { YearGridChangedProperties, YearGridData, YearGridProperties } from './typings.js'; export class YearGrid extends LitElement implements YearGridProperties { @property({ attribute: false }) @@ -29,6 +30,8 @@ export class YearGrid extends LitElement implements YearGridProperties { yearGridStyling, ]; + #selectedYear: number; + constructor() { super(); @@ -40,13 +43,14 @@ export class YearGrid extends LitElement implements YearGridProperties { max: MAX_DATE, min: todayDate, }; + this.#selectedYear = todayDate.getUTCFullYear(); } protected shouldUpdate(): boolean { return this.data.formatters != null; } - protected async updated(): Promise { + protected async firstUpdated(): Promise { const selectedYearGridButton = await this.selectedYearGridButton; if (selectedYearGridButton) { @@ -54,6 +58,14 @@ export class YearGrid extends LitElement implements YearGridProperties { } } + protected update(changedProperties: YearGridChangedProperties): void { + super.update(changedProperties); + + if (changedProperties.has('data')) { + this.#selectedYear = this.data.date.getUTCFullYear(); + } + } + protected render(): TemplateResult | typeof nothing { const { date, @@ -72,11 +84,14 @@ export class YearGrid extends LitElement implements YearGridProperties { yearList.map((year) => { const yearDate = toUTCDate(year, 1, 1); const yearLabel = yearFormat(yearDate); - const isYearSelected = yearDate.getUTCFullYear() === date.getUTCFullYear(); + const fullYear = yearDate.getUTCFullYear(); + // FIXME: To update tabindex + const isYearSelected = fullYear === date.getUTCFullYear(); return html` @@ -87,20 +102,49 @@ export class YearGrid extends LitElement implements YearGridProperties { } #updateYear = (ev: MouseEvent | KeyboardEvent): void => { - /** Do nothing when keyup.key is neither Enter nor ' ' (or Spacebar on older browsers) */ - if ( - (ev as KeyboardEvent).type === 'keyup' && - !['Enter', ' ', 'Spacebar'].includes((ev as KeyboardEvent).key) - ) return; + const { + date, + max, + min, + } = this.data; + + let year = date.getUTCFullYear(); + + if (ev.type === 'keyup') { + const { keyCode } = ev as KeyboardEvent; + const keyCodeNum = keyCode as typeof keyCode extends Set ? U : never; + + if (keyCodeNum === keyCodesRecord.TAB) return; + + if (yearGridKeyCodeSet.has(keyCodeNum)) { + const selectedYear = toNextSelectedYear({ + keyCode: keyCodeNum, + max, + min, + year: this.#selectedYear, + }); - const selectedYearGridButton = toClosestTarget(ev, `${APP_YEAR_GRID_BUTTON_NAME}[data-year]`); + const selectedYearGridButton = this.shadowRoot?.querySelector( + `${APP_YEAR_GRID_BUTTON_NAME}[data-year="${selectedYear}"]` + ); - /** Do nothing when not tapping on the year button */ - if (selectedYearGridButton == null) return; + if (selectedYearGridButton) { + selectedYearGridButton.focus(); + selectedYearGridButton.scrollIntoView(); + } - const year = Number( - selectedYearGridButton.getAttribute('data-year') - ); + this.#selectedYear = selectedYear; + + return; + } + } else { + const selectedYearGridButton = toClosestTarget(ev, `${APP_YEAR_GRID_BUTTON_NAME}[data-year]`); + + /** Do nothing when not tapping on the year button */ + if (selectedYearGridButton == null) return; + + year = Number(selectedYearGridButton.getAttribute('data-year')); + } dispatchCustomEvent(this, 'year-updated', { year }); };