Repository: Hacker0x01/react-datepicker (ts)

Issue:
Keyboard accessibility not aligned with ARIA authoring practices guide
**Describe the bug**
Some of the keyboard shortcuts are not aligned to what the W3C WAI-ARIA.
[Date Picker Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/)
Like the Home and End button should go to the first and last day of the week.

**To Reproduce**
Steps to reproduce the behavior:

1. Open the calendar
2. Press Home on keyboard
3. Previous year is selected

**Expected behavior**
Should move the selected day to the first day of the week.

Keyboard accessibility of Shift+PageUp and Shift+PageDown not aligned with ARIA authoring practices guide
**Describe the bug**
Shift+PageUp and Shift+PageDown keyboard shortcuts are not aligned with the W3C WAI-ARIA.  Currently, Shift+PageUp is doing the same as the PageUp shortcut functionality and Shift+PageDown is doing the same as the PageDown shortcut functionality.

[Date Picker Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/)

![image](https://github.com/Hacker0x01/react-datepicker/assets/145029139/d878deef-e984-4b3c-a176-2e655acf3417)


**To Reproduce**
Steps to reproduce the behavior:
1. Open the calendar
2. Move the focus to the calendar
3. Press Shift + PageUp or Shift + PageDown

**Expected behavior**
- Shift + PageUp should change the grid of dates to the same month in the previous year.
- Shift + PageDown should change the grid of dates to the same month in the next year.


Pull request:
Fix #4456: Add shift+pageUp key and shift+pageDown key navigation in Calendar component
Closes #4456

## Description
This PR addresses the navigation behavior in the calendar component to enhance accessibility based on W3C WAI-ARIA principles.  Previously, the shift+pageUp and shift+pageDown key handers navigated one month before and after the selected date (which is the handler for the pageUp and pageDown), which could be confusing for users.  The update modifies the behavior to move to the previous and the next year, providing a more intuitive and accessible experience.

[Reference Link](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/#:~:text=Shift%20%2B%20Page%20Up,of%20the%20month.)

## Changes Made
- Updated shift+pageUp and shift+pageDown key handlers in the calendar component.
- Navigation now focuses on the previous and next year, improving usability.
- Added the corresponding test cases.

## Related issues
Closes #4456

Patch:
diff --git a/README.md b/README.md
index 0fe00c94..8e19a7c3 100644
--- a/README.md
+++ b/README.md
@@ -159,7 +159,9 @@ The examples are hosted within the docs folder and are ran in the simple app tha
 - _Up_: Move to the previous week.
 - _Down_: Move to the next week.
 - _PgUp_: Move to the previous month.
+- _Shift+PgUp_: Move to the same day and month of the previous year. If that day does not exist, moves focus to the last day of the month.
 - _PgDn_: Move to the next month.
+- _Shift+PgDn_: Move to the same day and month of the next year. If that day does not exist, moves focus to the last day of the month.
 - _Home_: Move to the first day (e.g Sunday) of the current week.
 - _End_: Move to the last day (e.g. Saturday) of the current week.
 - _Enter/Esc/Tab_: close the calendar. (Enter & Esc calls preventDefault)
diff --git a/src/index.jsx b/src/index.jsx
index a3700220..15b44e80 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -26,6 +26,8 @@ import {
   subDays,
   subMonths,
   subWeeks,
+  addYears,
+  subYears,
   isDayDisabled,
   isDayInRange,
   getEffectiveMinDate,
@@ -363,10 +365,10 @@ export default class DatePicker extends React.Component {
     this.props.openToDate
       ? this.props.openToDate
       : this.props.selectsEnd && this.props.startDate
-      ? this.props.startDate
-      : this.props.selectsStart && this.props.endDate
-      ? this.props.endDate
-      : newDate();
+        ? this.props.startDate
+        : this.props.selectsStart && this.props.endDate
+          ? this.props.endDate
+          : newDate();
 
   // Convert the date from string format to standard Date format
   modifyHolidays = () =>
@@ -387,8 +389,8 @@ export default class DatePicker extends React.Component {
       minDate && isBefore(defaultPreSelection, startOfDay(minDate))
         ? minDate
         : maxDate && isAfter(defaultPreSelection, endOfDay(maxDate))
-        ? maxDate
-        : defaultPreSelection;
+          ? maxDate
+          : defaultPreSelection;
     return {
       open: this.props.startOpen || false,
       preventFocus: false,
@@ -843,6 +845,7 @@ export default class DatePicker extends React.Component {
   onDayKeyDown = (event) => {
     this.props.onKeyDown(event);
     const eventKey = event.key;
+    const isShiftKeyActive = event.shiftKey;
 
     const copy = newDate(this.state.preSelection);
     if (eventKey === "Enter") {
@@ -880,10 +883,14 @@ export default class DatePicker extends React.Component {
           newSelection = addWeeks(copy, 1);
           break;
         case "PageUp":
-          newSelection = subMonths(copy, 1);
+          newSelection = isShiftKeyActive
+            ? subYears(copy, 1)
+            : subMonths(copy, 1);
           break;
         case "PageDown":
-          newSelection = addMonths(copy, 1);
+          newSelection = isShiftKeyActive
+            ? addYears(copy, 1)
+            : addMonths(copy, 1);
           break;
         case "Home":
           newSelection = getStartOfWeek(
@@ -1187,14 +1194,14 @@ export default class DatePicker extends React.Component {
       typeof this.props.value === "string"
         ? this.props.value
         : typeof this.state.inputValue === "string"
-        ? this.state.inputValue
-        : this.props.selectsRange
-        ? safeDateRangeFormat(
-            this.props.startDate,
-            this.props.endDate,
-            this.props,
-          )
-        : safeDateFormat(this.props.selected, this.props);
+          ? this.state.inputValue
+          : this.props.selectsRange
+            ? safeDateRangeFormat(
+                this.props.startDate,
+                this.props.endDate,
+                this.props,
+              )
+            : safeDateFormat(this.props.selected, this.props);
 
     return React.cloneElement(customInput, {
       [customInputRef]: (input) => {


Test patch:
diff --git a/test/datepicker_test.test.js b/test/datepicker_test.test.js
index 994fe4a6..8e13b613 100644
--- a/test/datepicker_test.test.js
+++ b/test/datepicker_test.test.js
@@ -931,6 +931,21 @@ describe("DatePicker", () => {
       utils.formatDate(data.datePicker.state.preSelection, data.testFormat),
     ).toBe(utils.formatDate(data.copyM, data.testFormat));
   });
+  it("should handle onDayKeyDown Shift+PageUp", () => {
+    const data = getOnInputKeyDownStuff();
+
+    TestUtils.Simulate.keyDown(data.nodeInput, getKey("ArrowDown"));
+    TestUtils.Simulate.keyDown(
+      getSelectedDayNode(data.datePicker),
+      getKey("PageUp", true),
+    );
+
+    data.copyM = utils.subYears(data.copyM, 1);
+
+    expect(
+      utils.formatDate(data.datePicker.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(data.copyM, data.testFormat));
+  });
   it("should handle onDayKeyDown PageDown", () => {
     var data = getOnInputKeyDownStuff();
     TestUtils.Simulate.keyDown(data.nodeInput, getKey("ArrowDown"));
@@ -943,6 +958,21 @@ describe("DatePicker", () => {
       utils.formatDate(data.datePicker.state.preSelection, data.testFormat),
     ).toBe(utils.formatDate(data.copyM, data.testFormat));
   });
+  it("should handle onDayKeyDown Shift+PageDown", () => {
+    const data = getOnInputKeyDownStuff();
+
+    TestUtils.Simulate.keyDown(data.nodeInput, getKey("ArrowDown"));
+    TestUtils.Simulate.keyDown(
+      getSelectedDayNode(data.datePicker),
+      getKey("PageDown", true),
+    );
+
+    data.copyM = utils.addYears(data.copyM, 1);
+
+    expect(
+      utils.formatDate(data.datePicker.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(data.copyM, data.testFormat));
+  });
   it("should handle onDayKeyDown End", () => {
     const data = getOnInputKeyDownStuff();
     TestUtils.Simulate.keyDown(data.nodeInput, getKey("ArrowDown"));