Skip to content

Commit

Permalink
fix(date-picker): restore focus on date when navigating month with ar…
Browse files Browse the repository at this point in the history
…row/page keys (#9063)

**Related Issue:** #9062 

## Summary

Restores focus on the `activeDate` when navigating month with
`arrowDown/arrowUp` or `pageUp/pageDown` keys.
  • Loading branch information
anveshmekala committed Apr 10, 2024
1 parent 4dc1706 commit db109e0
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 21 deletions.
4 changes: 4 additions & 0 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,10 @@ export namespace Components {
* When `true`, the component is selected.
*/
"selected": boolean;
/**
* Sets focus on the component.
*/
"setFocus": () => Promise<void>;
/**
* Date is the start of date range.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Listen,
Prop,
VNode,
Method,
} from "@stencil/core";
import { dateToISO } from "../../utils/date";
import { closestElementCrossShadowBoundary, toAriaBoolean } from "../../utils/dom";
Expand All @@ -21,13 +22,19 @@ import {
import { isActivationKey } from "../../utils/key";
import { numberStringFormatter } from "../../utils/locale";
import { Scale } from "../interfaces";
import {
componentFocusable,
LoadableComponent,
setComponentLoaded,
setUpLoadableComponent,
} from "../../utils/loadable";

@Component({
tag: "calcite-date-picker-day",
styleUrl: "date-picker-day.scss",
shadow: true,
})
export class DatePickerDay implements InteractiveComponent {
export class DatePickerDay implements InteractiveComponent, LoadableComponent {
//--------------------------------------------------------------------------
//
// Properties
Expand Down Expand Up @@ -138,13 +145,31 @@ export class DatePickerDay implements InteractiveComponent {
//
//--------------------------------------------------------------------------

componentWillLoad(): void {
async componentWillLoad(): Promise<void> {
setUpLoadableComponent(this);
this.parentDatePickerEl = closestElementCrossShadowBoundary(
this.el,
"calcite-date-picker",
) as HTMLCalciteDatePickerElement;
}

componentDidLoad(): void {
setComponentLoaded(this);
}

// --------------------------------------------------------------------------
//
// Methods
//
// --------------------------------------------------------------------------

/** Sets focus on the component. */
@Method()
async setFocus(): Promise<void> {
await componentFocusable(this);
this.el.focus();
}

render(): VNode {
const dayId = dateToISO(this.value).replaceAll("-", "");
if (this.parentDatePickerEl) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
@apply flex
min-w-0
justify-center;
inline-size: calc(100% / 7);

inline-size: 100%;
calcite-date-picker-day {
@apply w-full;
}
Expand All @@ -45,9 +44,10 @@
}

.week-days {
@apply flex
flex-row
py-0;
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-auto-rows: 1fr;
padding-block: 0;
padding-inline: 6px;
&:focus {
@apply outline-none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,8 @@ export class DatePickerMonth {
}),
];

const weeks: Day[][] = [];
for (let i = 0; i < days.length; i += 7) {
weeks.push(days.slice(i, i + 7));
}

return (
<Host onFocusOut={this.disableActiveFocus} onKeyDown={this.keyDownHandler}>
<Host onFocusout={this.disableActiveFocus} onKeyDown={this.keyDownHandler}>
<div class="calendar" role="grid">
<div class="week-headers" role="row">
{adjustedWeekDays.map((weekday) => (
Expand All @@ -244,11 +239,10 @@ export class DatePickerMonth {
</span>
))}
</div>
{weeks.map((days) => (
<div class="week-days" role="row">
{days.map((day) => this.renderDateDay(day))}
</div>
))}

<div class="week-days" role="row">
{days.map((day, index) => this.renderDateDay(day, index))}
</div>
</div>
</Host>
);
Expand Down Expand Up @@ -440,15 +434,16 @@ export class DatePickerMonth {
* @param active.day
* @param active.dayInWeek
* @param active.ref
* @param key
*/
private renderDateDay({ active, currentMonth, date, day, dayInWeek, ref }: Day) {
private renderDateDay({ active, currentMonth, date, day, dayInWeek, ref }: Day, key: number) {
const isFocusedOnStart = this.isFocusedOnStart();
const isHoverInRange =
this.isHoverInRange() ||
(!this.endDate && this.hoverRange && sameDate(this.hoverRange?.end, this.startDate));

return (
<div class="day" key={date.toDateString()} role="gridcell">
<div class="day" key={key} role="gridcell">
<calcite-date-picker-day
active={active}
class={{
Expand Down Expand Up @@ -476,7 +471,7 @@ export class DatePickerMonth {
ref={(el: HTMLCalciteDatePickerDayElement) => {
// when moving via keyboard, focus must be updated on active date
if (ref && active && this.activeFocus) {
el?.focus();
el?.setFocus();
}
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,200 @@ describe("calcite-date-picker", () => {
describe("translation support", () => {
t9n("calcite-date-picker");
});

describe("ArrowKeys and PageKeys", () => {
async function setActiveDate(page: E2EPage, date: string): Promise<void> {
await page.evaluate((date) => {
const datePicker = document.querySelector("calcite-date-picker");
datePicker.activeDate = new Date(date);
}, date);
await page.waitForChanges();
}

it("should be able to navigate between months and select date using arrow keys and page keys", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-date-picker></calcite-date-picker>`);
await page.waitForChanges();

const datePicker = await page.find("calcite-date-picker");
await setActiveDate(page, "01-01-2024");

await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();

await page.keyboard.press("ArrowUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual("2023-12-25");

await page.keyboard.press("PageUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();
expect(await datePicker.getProperty("value")).toEqual("2023-11-25");

await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual("2024-01-25");

await page.keyboard.press("ArrowDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual("2024-02-01");
});

it("should be able to navigate between months and select date using arrow keys and page keys when value is parsed", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-date-picker value="2024-01-01"></calcite-date-picker>`);
const datePicker = await page.find("calcite-date-picker");

await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("ArrowUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual("2023-12-25");

await page.keyboard.press("ArrowDown");
await page.waitForChanges();
await page.keyboard.press("ArrowDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual("2024-01-08");

await page.keyboard.press("PageUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();
expect(await datePicker.getProperty("value")).toEqual("2023-12-08");

await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual("2024-02-08");
});

it("should be able to navigate between months and select date using arrow keys and page keys in range", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-date-picker range></calcite-date-picker>`);
await page.waitForChanges();

const datePicker = await page.find("calcite-date-picker");
await setActiveDate(page, "01-01-2024");

await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();

await page.keyboard.press("ArrowUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual(["2023-12-25", ""]);

await page.keyboard.press("PageUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();
await page.waitForTimeout(4000);
expect(await datePicker.getProperty("value")).toEqual(["2023-11-25", "2023-12-25"]);

await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();
await page.waitForTimeout(4000);
expect(await datePicker.getProperty("value")).toEqual(["2023-11-25", "2024-01-25"]);

await page.keyboard.press("ArrowDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual(["2023-11-25", "2024-02-01"]);
});

it("should be able to navigate between months and select date using arrow keys and page keys in range when value is parsed", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-date-picker range></calcite-date-picker>`);
const datePicker = await page.find("calcite-date-picker");
datePicker.setProperty("value", ["2024-01-01", "2024-02-10"]);

await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("ArrowUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual(["2023-12-25", "2024-02-10"]);

await page.keyboard.press("ArrowDown");
await page.waitForChanges();
await page.keyboard.press("ArrowDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual(["2023-12-25", "2024-01-08"]);

await page.keyboard.press("PageUp");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();
expect(await datePicker.getProperty("value")).toEqual(["2023-12-08", "2024-01-08"]);

await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("PageDown");
await page.waitForChanges();
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await datePicker.getProperty("value")).toEqual(["2023-12-08", "2024-02-08"]);
});
});
});

0 comments on commit db109e0

Please sign in to comment.