diff --git a/packages/calcite-components/src/components.d.ts b/packages/calcite-components/src/components.d.ts index 0cb35ed0e3c..d3c6b0e1643 100644 --- a/packages/calcite-components/src/components.d.ts +++ b/packages/calcite-components/src/components.d.ts @@ -595,7 +595,7 @@ export namespace Components { */ "heading": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -1311,7 +1311,7 @@ export namespace Components { */ "activeRange": "start" | "end"; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -1420,6 +1420,10 @@ export namespace Components { * When `true`, the component is selected. */ "selected": boolean; + /** + * Sets focus on the component. + */ + "setFocus": () => Promise; /** * Date is the start of date range. */ @@ -1771,7 +1775,7 @@ export namespace Components { */ "heading": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -2144,7 +2148,7 @@ export namespace Components { */ "form": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -3486,7 +3490,7 @@ export namespace Components { */ "heading": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -3556,7 +3560,7 @@ export namespace Components { */ "getSelectedItems": () => Promise>; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -3586,7 +3590,7 @@ export namespace Components { */ "groupTitle": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; } @@ -3680,7 +3684,7 @@ export namespace Components { */ "heading": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -5176,7 +5180,7 @@ export namespace Components { */ "heading": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -5204,7 +5208,7 @@ export namespace Components { */ "closed": boolean; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel": HeadingLevel; /** @@ -7974,7 +7978,7 @@ declare namespace LocalJSX { */ "heading": string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -8743,7 +8747,7 @@ declare namespace LocalJSX { */ "activeRange"?: "start" | "end"; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -9225,7 +9229,7 @@ declare namespace LocalJSX { */ "heading"?: string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -9617,7 +9621,7 @@ declare namespace LocalJSX { */ "form"?: string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -11045,7 +11049,7 @@ declare namespace LocalJSX { */ "heading"?: string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -11112,7 +11116,7 @@ declare namespace LocalJSX { */ "filteredItems"?: HTMLCalcitePickListItemElement[]; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -11145,7 +11149,7 @@ declare namespace LocalJSX { */ "groupTitle"?: string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; } @@ -11254,7 +11258,7 @@ declare namespace LocalJSX { */ "heading"?: string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -12788,7 +12792,7 @@ declare namespace LocalJSX { */ "heading"?: string; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** @@ -12820,7 +12824,7 @@ declare namespace LocalJSX { */ "closed"?: boolean; /** - * Specifies the number at which section headings should start. + * Specifies the heading level of the component's `heading` for proper document structure, without affecting visual styling. */ "headingLevel"?: HeadingLevel; /** diff --git a/packages/calcite-components/src/components/date-picker-day/date-picker-day.tsx b/packages/calcite-components/src/components/date-picker-day/date-picker-day.tsx index 76a075e4152..bfaa5943e63 100644 --- a/packages/calcite-components/src/components/date-picker-day/date-picker-day.tsx +++ b/packages/calcite-components/src/components/date-picker-day/date-picker-day.tsx @@ -8,6 +8,7 @@ import { Listen, Prop, VNode, + Method, } from "@stencil/core"; import { dateToISO } from "../../utils/date"; @@ -22,13 +23,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 @@ -139,13 +146,31 @@ export class DatePickerDay implements InteractiveComponent { // //-------------------------------------------------------------------------- - componentWillLoad(): void { + async componentWillLoad(): Promise { + 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 { + await componentFocusable(this); + this.el.focus(); + } + render(): VNode { const dayId = dateToISO(this.value).replaceAll("-", ""); if (this.parentDatePickerEl) { diff --git a/packages/calcite-components/src/components/date-picker-month/date-picker-month.scss b/packages/calcite-components/src/components/date-picker-month/date-picker-month.scss index 60c1e853b2a..a98c3797a0e 100644 --- a/packages/calcite-components/src/components/date-picker-month/date-picker-month.scss +++ b/packages/calcite-components/src/components/date-picker-month/date-picker-month.scss @@ -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; } @@ -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; diff --git a/packages/calcite-components/src/components/date-picker-month/date-picker-month.tsx b/packages/calcite-components/src/components/date-picker-month/date-picker-month.tsx index e57ba274ced..e449034642f 100644 --- a/packages/calcite-components/src/components/date-picker-month/date-picker-month.tsx +++ b/packages/calcite-components/src/components/date-picker-month/date-picker-month.tsx @@ -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 ( - +
{adjustedWeekDays.map((weekday) => ( @@ -244,11 +239,10 @@ export class DatePickerMonth { ))}
- {weeks.map((days) => ( -
- {days.map((day) => this.renderDateDay(day))} -
- ))} + +
+ {days.map((day, index) => this.renderDateDay(day, index))} +
); @@ -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 ( -
+
{ // when moving via keyboard, focus must be updated on active date if (ref && active && this.activeFocus) { - el?.focus(); + el?.setFocus(); } }} /> diff --git a/packages/calcite-components/src/components/date-picker/date-picker.e2e.ts b/packages/calcite-components/src/components/date-picker/date-picker.e2e.ts index 31071ab4860..547f3bed2fb 100644 --- a/packages/calcite-components/src/components/date-picker/date-picker.e2e.ts +++ b/packages/calcite-components/src/components/date-picker/date-picker.e2e.ts @@ -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 { + 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``); + 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``); + 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``); + 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``); + 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"]); + }); + }); });