Skip to content

Commit db109e0

Browse files
authored
fix(date-picker): restore focus on date when navigating month with arrow/page keys (#9063)
**Related Issue:** #9062 ## Summary Restores focus on the `activeDate` when navigating month with `arrowDown/arrowUp` or `pageUp/pageDown` keys.
1 parent 4dc1706 commit db109e0

File tree

5 files changed

+241
-21
lines changed

5 files changed

+241
-21
lines changed

packages/calcite-components/src/components.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,10 @@ export namespace Components {
14201420
* When `true`, the component is selected.
14211421
*/
14221422
"selected": boolean;
1423+
/**
1424+
* Sets focus on the component.
1425+
*/
1426+
"setFocus": () => Promise<void>;
14231427
/**
14241428
* Date is the start of date range.
14251429
*/

packages/calcite-components/src/components/date-picker-day/date-picker-day.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Listen,
99
Prop,
1010
VNode,
11+
Method,
1112
} from "@stencil/core";
1213
import { dateToISO } from "../../utils/date";
1314
import { closestElementCrossShadowBoundary, toAriaBoolean } from "../../utils/dom";
@@ -21,13 +22,19 @@ import {
2122
import { isActivationKey } from "../../utils/key";
2223
import { numberStringFormatter } from "../../utils/locale";
2324
import { Scale } from "../interfaces";
25+
import {
26+
componentFocusable,
27+
LoadableComponent,
28+
setComponentLoaded,
29+
setUpLoadableComponent,
30+
} from "../../utils/loadable";
2431

2532
@Component({
2633
tag: "calcite-date-picker-day",
2734
styleUrl: "date-picker-day.scss",
2835
shadow: true,
2936
})
30-
export class DatePickerDay implements InteractiveComponent {
37+
export class DatePickerDay implements InteractiveComponent, LoadableComponent {
3138
//--------------------------------------------------------------------------
3239
//
3340
// Properties
@@ -138,13 +145,31 @@ export class DatePickerDay implements InteractiveComponent {
138145
//
139146
//--------------------------------------------------------------------------
140147

141-
componentWillLoad(): void {
148+
async componentWillLoad(): Promise<void> {
149+
setUpLoadableComponent(this);
142150
this.parentDatePickerEl = closestElementCrossShadowBoundary(
143151
this.el,
144152
"calcite-date-picker",
145153
) as HTMLCalciteDatePickerElement;
146154
}
147155

156+
componentDidLoad(): void {
157+
setComponentLoaded(this);
158+
}
159+
160+
// --------------------------------------------------------------------------
161+
//
162+
// Methods
163+
//
164+
// --------------------------------------------------------------------------
165+
166+
/** Sets focus on the component. */
167+
@Method()
168+
async setFocus(): Promise<void> {
169+
await componentFocusable(this);
170+
this.el.focus();
171+
}
172+
148173
render(): VNode {
149174
const dayId = dateToISO(this.value).replaceAll("-", "");
150175
if (this.parentDatePickerEl) {

packages/calcite-components/src/components/date-picker-month/date-picker-month.scss

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
@apply flex
2626
min-w-0
2727
justify-center;
28-
inline-size: calc(100% / 7);
29-
28+
inline-size: 100%;
3029
calcite-date-picker-day {
3130
@apply w-full;
3231
}
@@ -45,9 +44,10 @@
4544
}
4645

4746
.week-days {
48-
@apply flex
49-
flex-row
50-
py-0;
47+
display: grid;
48+
grid-template-columns: repeat(7, 1fr);
49+
grid-auto-rows: 1fr;
50+
padding-block: 0;
5151
padding-inline: 6px;
5252
&:focus {
5353
@apply outline-none;

packages/calcite-components/src/components/date-picker-month/date-picker-month.tsx

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,8 @@ export class DatePickerMonth {
229229
}),
230230
];
231231

232-
const weeks: Day[][] = [];
233-
for (let i = 0; i < days.length; i += 7) {
234-
weeks.push(days.slice(i, i + 7));
235-
}
236-
237232
return (
238-
<Host onFocusOut={this.disableActiveFocus} onKeyDown={this.keyDownHandler}>
233+
<Host onFocusout={this.disableActiveFocus} onKeyDown={this.keyDownHandler}>
239234
<div class="calendar" role="grid">
240235
<div class="week-headers" role="row">
241236
{adjustedWeekDays.map((weekday) => (
@@ -244,11 +239,10 @@ export class DatePickerMonth {
244239
</span>
245240
))}
246241
</div>
247-
{weeks.map((days) => (
248-
<div class="week-days" role="row">
249-
{days.map((day) => this.renderDateDay(day))}
250-
</div>
251-
))}
242+
243+
<div class="week-days" role="row">
244+
{days.map((day, index) => this.renderDateDay(day, index))}
245+
</div>
252246
</div>
253247
</Host>
254248
);
@@ -440,15 +434,16 @@ export class DatePickerMonth {
440434
* @param active.day
441435
* @param active.dayInWeek
442436
* @param active.ref
437+
* @param key
443438
*/
444-
private renderDateDay({ active, currentMonth, date, day, dayInWeek, ref }: Day) {
439+
private renderDateDay({ active, currentMonth, date, day, dayInWeek, ref }: Day, key: number) {
445440
const isFocusedOnStart = this.isFocusedOnStart();
446441
const isHoverInRange =
447442
this.isHoverInRange() ||
448443
(!this.endDate && this.hoverRange && sameDate(this.hoverRange?.end, this.startDate));
449444

450445
return (
451-
<div class="day" key={date.toDateString()} role="gridcell">
446+
<div class="day" key={key} role="gridcell">
452447
<calcite-date-picker-day
453448
active={active}
454449
class={{
@@ -476,7 +471,7 @@ export class DatePickerMonth {
476471
ref={(el: HTMLCalciteDatePickerDayElement) => {
477472
// when moving via keyboard, focus must be updated on active date
478473
if (ref && active && this.activeFocus) {
479-
el?.focus();
474+
el?.setFocus();
480475
}
481476
}}
482477
/>

packages/calcite-components/src/components/date-picker/date-picker.e2e.ts

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,200 @@ describe("calcite-date-picker", () => {
330330
describe("translation support", () => {
331331
t9n("calcite-date-picker");
332332
});
333+
334+
describe("ArrowKeys and PageKeys", () => {
335+
async function setActiveDate(page: E2EPage, date: string): Promise<void> {
336+
await page.evaluate((date) => {
337+
const datePicker = document.querySelector("calcite-date-picker");
338+
datePicker.activeDate = new Date(date);
339+
}, date);
340+
await page.waitForChanges();
341+
}
342+
343+
it("should be able to navigate between months and select date using arrow keys and page keys", async () => {
344+
const page = await newE2EPage();
345+
await page.setContent(html`<calcite-date-picker></calcite-date-picker>`);
346+
await page.waitForChanges();
347+
348+
const datePicker = await page.find("calcite-date-picker");
349+
await setActiveDate(page, "01-01-2024");
350+
351+
await page.keyboard.press("Tab");
352+
await page.waitForChanges();
353+
await page.keyboard.press("Tab");
354+
await page.waitForChanges();
355+
await page.keyboard.press("Tab");
356+
await page.waitForChanges();
357+
await page.keyboard.press("Tab");
358+
await page.waitForChanges();
359+
360+
await page.keyboard.press("ArrowUp");
361+
await page.waitForChanges();
362+
await page.keyboard.press("Enter");
363+
await page.waitForChanges();
364+
365+
expect(await datePicker.getProperty("value")).toEqual("2023-12-25");
366+
367+
await page.keyboard.press("PageUp");
368+
await page.waitForChanges();
369+
await page.keyboard.press("Enter");
370+
await page.waitForChanges();
371+
expect(await datePicker.getProperty("value")).toEqual("2023-11-25");
372+
373+
await page.keyboard.press("PageDown");
374+
await page.waitForChanges();
375+
await page.keyboard.press("PageDown");
376+
await page.waitForChanges();
377+
await page.keyboard.press("Enter");
378+
await page.waitForChanges();
379+
380+
expect(await datePicker.getProperty("value")).toEqual("2024-01-25");
381+
382+
await page.keyboard.press("ArrowDown");
383+
await page.waitForChanges();
384+
await page.keyboard.press("Enter");
385+
await page.waitForChanges();
386+
387+
expect(await datePicker.getProperty("value")).toEqual("2024-02-01");
388+
});
389+
390+
it("should be able to navigate between months and select date using arrow keys and page keys when value is parsed", async () => {
391+
const page = await newE2EPage();
392+
await page.setContent(html`<calcite-date-picker value="2024-01-01"></calcite-date-picker>`);
393+
const datePicker = await page.find("calcite-date-picker");
394+
395+
await page.keyboard.press("Tab");
396+
await page.waitForChanges();
397+
await page.keyboard.press("Tab");
398+
await page.waitForChanges();
399+
await page.keyboard.press("Tab");
400+
await page.waitForChanges();
401+
await page.keyboard.press("Tab");
402+
await page.waitForChanges();
403+
await page.keyboard.press("ArrowUp");
404+
await page.waitForChanges();
405+
await page.keyboard.press("Enter");
406+
await page.waitForChanges();
407+
408+
expect(await datePicker.getProperty("value")).toEqual("2023-12-25");
409+
410+
await page.keyboard.press("ArrowDown");
411+
await page.waitForChanges();
412+
await page.keyboard.press("ArrowDown");
413+
await page.waitForChanges();
414+
await page.keyboard.press("Enter");
415+
await page.waitForChanges();
416+
417+
expect(await datePicker.getProperty("value")).toEqual("2024-01-08");
418+
419+
await page.keyboard.press("PageUp");
420+
await page.waitForChanges();
421+
await page.keyboard.press("Enter");
422+
await page.waitForChanges();
423+
expect(await datePicker.getProperty("value")).toEqual("2023-12-08");
424+
425+
await page.keyboard.press("PageDown");
426+
await page.waitForChanges();
427+
await page.keyboard.press("PageDown");
428+
await page.waitForChanges();
429+
await page.keyboard.press("Enter");
430+
await page.waitForChanges();
431+
432+
expect(await datePicker.getProperty("value")).toEqual("2024-02-08");
433+
});
434+
435+
it("should be able to navigate between months and select date using arrow keys and page keys in range", async () => {
436+
const page = await newE2EPage();
437+
await page.setContent(html`<calcite-date-picker range></calcite-date-picker>`);
438+
await page.waitForChanges();
439+
440+
const datePicker = await page.find("calcite-date-picker");
441+
await setActiveDate(page, "01-01-2024");
442+
443+
await page.keyboard.press("Tab");
444+
await page.waitForChanges();
445+
await page.keyboard.press("Tab");
446+
await page.waitForChanges();
447+
await page.keyboard.press("Tab");
448+
await page.waitForChanges();
449+
await page.keyboard.press("Tab");
450+
await page.waitForChanges();
451+
452+
await page.keyboard.press("ArrowUp");
453+
await page.waitForChanges();
454+
await page.keyboard.press("Enter");
455+
await page.waitForChanges();
456+
457+
expect(await datePicker.getProperty("value")).toEqual(["2023-12-25", ""]);
458+
459+
await page.keyboard.press("PageUp");
460+
await page.waitForChanges();
461+
await page.keyboard.press("Enter");
462+
await page.waitForChanges();
463+
await page.waitForTimeout(4000);
464+
expect(await datePicker.getProperty("value")).toEqual(["2023-11-25", "2023-12-25"]);
465+
466+
await page.keyboard.press("PageDown");
467+
await page.waitForChanges();
468+
await page.keyboard.press("PageDown");
469+
await page.waitForChanges();
470+
await page.keyboard.press("Enter");
471+
await page.waitForChanges();
472+
await page.waitForTimeout(4000);
473+
expect(await datePicker.getProperty("value")).toEqual(["2023-11-25", "2024-01-25"]);
474+
475+
await page.keyboard.press("ArrowDown");
476+
await page.waitForChanges();
477+
await page.keyboard.press("Enter");
478+
await page.waitForChanges();
479+
480+
expect(await datePicker.getProperty("value")).toEqual(["2023-11-25", "2024-02-01"]);
481+
});
482+
483+
it("should be able to navigate between months and select date using arrow keys and page keys in range when value is parsed", async () => {
484+
const page = await newE2EPage();
485+
await page.setContent(html`<calcite-date-picker range></calcite-date-picker>`);
486+
const datePicker = await page.find("calcite-date-picker");
487+
datePicker.setProperty("value", ["2024-01-01", "2024-02-10"]);
488+
489+
await page.keyboard.press("Tab");
490+
await page.waitForChanges();
491+
await page.keyboard.press("Tab");
492+
await page.waitForChanges();
493+
await page.keyboard.press("Tab");
494+
await page.waitForChanges();
495+
await page.keyboard.press("Tab");
496+
await page.waitForChanges();
497+
await page.keyboard.press("ArrowUp");
498+
await page.waitForChanges();
499+
await page.keyboard.press("Enter");
500+
await page.waitForChanges();
501+
502+
expect(await datePicker.getProperty("value")).toEqual(["2023-12-25", "2024-02-10"]);
503+
504+
await page.keyboard.press("ArrowDown");
505+
await page.waitForChanges();
506+
await page.keyboard.press("ArrowDown");
507+
await page.waitForChanges();
508+
await page.keyboard.press("Enter");
509+
await page.waitForChanges();
510+
511+
expect(await datePicker.getProperty("value")).toEqual(["2023-12-25", "2024-01-08"]);
512+
513+
await page.keyboard.press("PageUp");
514+
await page.waitForChanges();
515+
await page.keyboard.press("Enter");
516+
await page.waitForChanges();
517+
expect(await datePicker.getProperty("value")).toEqual(["2023-12-08", "2024-01-08"]);
518+
519+
await page.keyboard.press("PageDown");
520+
await page.waitForChanges();
521+
await page.keyboard.press("PageDown");
522+
await page.waitForChanges();
523+
await page.keyboard.press("Enter");
524+
await page.waitForChanges();
525+
526+
expect(await datePicker.getProperty("value")).toEqual(["2023-12-08", "2024-02-08"]);
527+
});
528+
});
333529
});

0 commit comments

Comments
 (0)