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

feat(input-date-picker): add focus trap support #6816

Merged
Merged
2 changes: 1 addition & 1 deletion src/components/date-picker/date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export class DatePicker implements LocalizedComponent, LoadableComponent, T9nCom
: this.maxAsDate
: this.maxAsDate;
return (
<Host onBlur={this.reset} onKeyDown={this.keyDownHandler} role="application">
<Host onBlur={this.reset} onKeyDown={this.keyDownHandler}>
{this.renderCalendar(activeDate, maxDate, minDate, date, endDate)}
</Host>
);
Expand Down
206 changes: 176 additions & 30 deletions src/components/input-date-picker/input-date-picker.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { newE2EPage } from "@stencil/core/testing";
import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing";
import {
defaults,
disabled,
Expand All @@ -12,6 +12,7 @@ import {
import { html } from "../../../support/formatting";
import { CSS } from "./resources";
import { CSS as MONTH_HEADER_CSS } from "../date-picker-month-header/resources";
import { getFocusedElementProp, skipAnimations } from "../../tests/utils";
const animationDurationInMs = 200;

describe("calcite-input-date-picker", () => {
Expand Down Expand Up @@ -47,7 +48,7 @@ describe("calcite-input-date-picker", () => {

expect(await input.getProperty("value")).toBe("");

await input.callMethod("setFocus");
await input.click();
await page.waitForChanges();
await page.waitForTimeout(animationDurationInMs);
const wrapper = (
Expand Down Expand Up @@ -117,7 +118,7 @@ describe("calcite-input-date-picker", () => {
const input = await page.find("calcite-input-date-picker");
const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

await input.callMethod("setFocus");
await input.click();
await page.waitForChanges();
await page.waitForTimeout(animationDurationInMs);

Expand Down Expand Up @@ -174,7 +175,7 @@ describe("calcite-input-date-picker", () => {
const inputDatePicker = await page.find("calcite-input-date-picker");
const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

await inputDatePicker.callMethod("setFocus");
await inputDatePicker.click();
await page.waitForChanges();
await page.keyboard.type("3/7/");
await page.keyboard.press("Enter");
Expand Down Expand Up @@ -217,18 +218,55 @@ describe("calcite-input-date-picker", () => {
expect(await element.getProperty("value")).toBe("");
});

it("displays a calendar when clicked", async () => {
const page = await newE2EPage({
html: "<calcite-input-date-picker value='2000-11-27'></calcite-input-date-picker>"
describe("toggling date picker", () => {
let page: E2EPage;
let inputDatePicker: E2EElement;

beforeEach(async () => {
page = await newE2EPage();
await page.setContent(html` <calcite-input-date-picker value="2000-11-27"></calcite-input-date-picker>`);
await skipAnimations(page);
await page.waitForChanges();
inputDatePicker = await page.find("calcite-input-date-picker");
});
await page.waitForChanges();
const date = await page.find("calcite-input-date-picker");

await date.click();
await page.waitForChanges();
const calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");
it("toggles the date picker when clicked", async () => {
let calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");

expect(await calendar.isVisible()).toBe(false);

expect(await calendar.isVisible()).toBe(true);
await inputDatePicker.click();
await page.waitForChanges();
calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");

expect(await calendar.isVisible()).toBe(true);

await inputDatePicker.click();
await page.waitForChanges();
calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");

expect(await calendar.isVisible()).toBe(false);
});

it("toggles the date picker when using arrow down/escape key", async () => {
let calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");

expect(await calendar.isVisible()).toBe(false);

await inputDatePicker.callMethod("setFocus");
await page.waitForChanges();
await page.keyboard.press("ArrowDown");
await page.waitForChanges();
calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");

expect(await calendar.isVisible()).toBe(true);

await page.keyboard.press("Escape");
await page.waitForChanges();
calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper");

expect(await calendar.isVisible()).toBe(false);
});
});

describe("localization", () => {
Expand Down Expand Up @@ -329,7 +367,7 @@ describe("calcite-input-date-picker", () => {
await page.setContent(`<calcite-input-date-picker value="2023-01-31"></calcite-input-date-picker>`);
const inputDatePicker = await page.find("calcite-input-date-picker");

await inputDatePicker.callMethod("setFocus");
await inputDatePicker.click();
await page.waitForChanges();

await page.evaluate(() => {
Expand Down Expand Up @@ -388,7 +426,7 @@ describe("calcite-input-date-picker", () => {

expect(await input.getProperty("value")).toBe("");

await component.callMethod("setFocus");
await component.click();
await page.waitForChanges();
const calendar = await page.find(`#canReadOnly >>> .${CSS.menu}`);

Expand Down Expand Up @@ -452,30 +490,18 @@ describe("calcite-input-date-picker", () => {
it("should return endDate time as 23:59:999 when end value is typed", async () => {
const page = await newE2EPage();
await page.setContent(html` <calcite-input-date-picker layout="horizontal" range></calcite-input-date-picker>`);

const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

const datepickerEl = await page.find("calcite-input-date-picker");
const datePickerEl = await page.find("calcite-input-date-picker");
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("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await page.keyboard.press("Tab");
await page.waitForChanges();
await datepickerEl.type("08/30/2022");
await datePickerEl.type("08/30/2022");
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(changeEvent).toHaveReceivedEventTimes(1);
expect(await datepickerEl.getProperty("value")).toEqual(["", "2022-08-30"]);
expect(await datePickerEl.getProperty("value")).toEqual(["", "2022-08-30"]);
});

it("should update this.value and input value when valueAsDate is set", async () => {
Expand Down Expand Up @@ -580,4 +606,124 @@ describe("calcite-input-date-picker", () => {
expect(changeEvent).toHaveReceivedEventTimes(1);
expect(await datepickerEl.getProperty("value")).toEqual(["2022-08-15", "2022-08-20"]);
});

describe("focus trapping", () => {
it("traps focus only when open", async () => {
const page = await newE2EPage();
await page.setContent(
html` <calcite-input-date-picker></calcite-input-date-picker>
<div id="next-sibling" tabindex="0">next sibling</div>`
);

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "id")).toBe("next-sibling");

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-INPUT");

const opening = page.waitForEvent("calciteInputDatePickerOpen");
await page.keyboard.press("ArrowDown");
await opening;
await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

const closing = page.waitForEvent("calciteInputDatePickerClose");
await page.keyboard.press("Escape");
await closing;
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-INPUT");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "id")).toBe("next-sibling");
});

it("traps focus only when open (range)", async () => {
const page = await newE2EPage();
await page.setContent(
html`<calcite-input-date-picker range></calcite-input-date-picker>
<div id="next-sibling" tabindex="0">next sibling</div>`
);

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "id")).toBe("next-sibling");

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-INPUT");

const startOpening = page.waitForEvent("calciteInputDatePickerOpen");
await page.keyboard.press("ArrowDown");
await startOpening;
await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

const startClosing = page.waitForEvent("calciteInputDatePickerClose");
await page.keyboard.press("Escape");
await startClosing;
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-INPUT");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-INPUT");

const endOpening = page.waitForEvent("calciteInputDatePickerOpen");
await page.keyboard.press("ArrowDown");
await endOpening;
await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-DATE-PICKER");

const endClosing = page.waitForEvent("calciteInputDatePickerClose");
await page.keyboard.press("Escape");
await endClosing;
expect(await getFocusedElementProp(page, "tagName")).toBe("CALCITE-INPUT-DATE-PICKER");
expect(await getFocusedElementProp(page, "tagName", { shadow: true })).toBe("CALCITE-INPUT");

await page.keyboard.press("Tab");
expect(await getFocusedElementProp(page, "id")).toBe("next-sibling");
});
});
});
21 changes: 21 additions & 0 deletions src/components/input-date-picker/input-date-picker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@
shadow-none;
}

:host([scale="s"]) {
--calcite-toggle-spacing: theme("spacing.2");
}

:host([scale="m"]) {
--calcite-toggle-spacing: theme("spacing.3");
}

:host([scale="l"]) {
--calcite-toggle-spacing: theme("spacing.4");
}

@include disabled();

.calendar-picker-wrapper {
Expand All @@ -22,6 +34,15 @@
@apply relative;
}

.toggle-icon {
@apply absolute flex
w-4 cursor-pointer
items-center;
inset-inline-end: 0;
inset-block: 0;
padding-inline: var(--calcite-toggle-spacing);
}

:host([range]) {
.input-container {
@apply flex;
Expand Down
Loading