@codecademy/gamut 68.1.3-alpha.77d8dc.0 → 68.1.3-alpha.a2160b.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DatePicker/Calendar/CalendarBody.js +14 -10
- package/dist/DatePicker/Calendar/types.d.ts +4 -0
- package/dist/DatePicker/Calendar/utils/dateGrid.js +6 -6
- package/dist/DatePicker/Calendar/utils/format.d.ts +5 -9
- package/dist/DatePicker/Calendar/utils/format.js +23 -42
- package/dist/DatePicker/Calendar/utils/keyHandler.d.ts +1 -1
- package/dist/DatePicker/Calendar/utils/keyHandler.js +17 -8
- package/dist/DatePicker/DatePicker.js +7 -27
- package/dist/DatePicker/DatePickerCalendar.js +7 -2
- package/dist/DatePicker/DatePickerInput.js +4 -2
- package/dist/DatePicker/translations.js +5 -1
- package/dist/DatePicker/types.d.ts +9 -1
- package/dist/DatePicker/utils.d.ts +3 -1
- package/dist/DatePicker/utils.js +29 -10
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { TextButton } from '../../Button';
|
|
6
6
|
import { getMonthGrid, isDateDisabled, isDateInRange, isSameDay } from './utils/dateGrid';
|
|
7
|
-
import {
|
|
7
|
+
import { getWeekdayNames } from './utils/format';
|
|
8
8
|
import { getDatesWithRow, keyHandler } from './utils/keyHandler';
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
const TableHeader = /*#__PURE__*/_styled("th", {
|
|
@@ -15,7 +15,7 @@ const TableHeader = /*#__PURE__*/_styled("th", {
|
|
|
15
15
|
fontWeight: 'base',
|
|
16
16
|
color: 'text-disabled',
|
|
17
17
|
textAlign: 'center'
|
|
18
|
-
}), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,
|
|
18
|
+
}), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9EYXRlUGlja2VyL0NhbGVuZGFyL0NhbGVuZGFyQm9keS50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBZ0JvQiIsImZpbGUiOiIuLi8uLi8uLi9zcmMvRGF0ZVBpY2tlci9DYWxlbmRhci9DYWxlbmRhckJvZHkudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBzdGF0ZXMgfSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuaW1wb3J0IHsgdXNlQ2FsbGJhY2ssIHVzZUVmZmVjdCwgdXNlTWVtbywgdXNlUmVmIH0gZnJvbSAncmVhY3QnO1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnO1xuXG5pbXBvcnQgeyBUZXh0QnV0dG9uIH0gZnJvbSAnLi4vLi4vQnV0dG9uJztcbmltcG9ydCB7IENhbGVuZGFyQm9keVByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQge1xuICBnZXRNb250aEdyaWQsXG4gIGlzRGF0ZURpc2FibGVkLFxuICBpc0RhdGVJblJhbmdlLFxuICBpc1NhbWVEYXksXG59IGZyb20gJy4vdXRpbHMvZGF0ZUdyaWQnO1xuaW1wb3J0IHsgZ2V0V2Vla2RheU5hbWVzIH0gZnJvbSAnLi91dGlscy9mb3JtYXQnO1xuaW1wb3J0IHsgZ2V0RGF0ZXNXaXRoUm93LCBrZXlIYW5kbGVyIH0gZnJvbSAnLi91dGlscy9rZXlIYW5kbGVyJztcblxuY29uc3QgVGFibGVIZWFkZXIgPSBzdHlsZWQudGgoXG4gIGNzcyh7XG4gICAgZm9udFNpemU6IDE0LFxuICAgIGZvbnRXZWlnaHQ6ICdiYXNlJyxcbiAgICBjb2xvcjogJ3RleHQtZGlzYWJsZWQnLFxuICAgIHRleHRBbGlnbjogJ2NlbnRlcicsXG4gIH0pXG4pO1xuXG5jb25zdCBEYXRlQnV0dG9uID0gc3R5bGVkKFRleHRCdXR0b24pKFxuICBzdGF0ZXMoe1xuICAgIGlzVG9kYXk6IHtcbiAgICAgIHBvc2l0aW9uOiAncmVsYXRpdmUnLFxuICAgICAgJyY6OmFmdGVyJzoge1xuICAgICAgICBjb250ZW50OiAnXCJcIicsXG4gICAgICAgIHBvc2l0aW9uOiAnYWJzb2x1dGUnLFxuICAgICAgICBib3R0b206IDQsXG4gICAgICAgIHdpZHRoOiA0LFxuICAgICAgICBoZWlnaHQ6IDQsXG4gICAgICAgIGJvcmRlclJhZGl1czogJ2Z1bGwnLFxuICAgICAgICBiZzogJ2h5cGVyJyxcbiAgICAgIH0sXG4gICAgfSxcbiAgICBpc1NlbGVjdGVkOiB7XG4gICAgICBiZzogJ3RleHQnLFxuICAgICAgY29sb3I6ICdiYWNrZ3JvdW5kJyxcbiAgICAgICcmOmhvdmVyLCAmOmZvY3VzJzoge1xuICAgICAgICBiZzogJ3NlY29uZGFyeS1ob3ZlcicsXG4gICAgICAgIGNvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgICB9LFxuICAgICAgJyY6OmFmdGVyJzoge1xuICAgICAgICBiZzogJ2JhY2tncm91bmQnLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGlzSW5SYW5nZToge1xuICAgICAgYmc6ICd0ZXh0LWRpc2FibGVkJyxcbiAgICAgIGNvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgICBib3JkZXJSYWRpdXM6ICdub25lJyxcbiAgICAgICcmOmhvdmVyLCAmOmZvY3VzJzoge1xuICAgICAgICBiZzogJ3NlY29uZGFyeS1ob3ZlcicsXG4gICAgICAgIGNvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgICB9LFxuICAgICAgJyY6OmFmdGVyJzoge1xuICAgICAgICBiZzogJ2JhY2tncm91bmQnLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGRpc2FibGVkOiB7XG4gICAgICBjb2xvcjogJ3RleHQtZGlzYWJsZWQnLFxuICAgICAgdGV4dERlY29yYXRpb246ICdsaW5lLXRocm91Z2gnLFxuICAgICAgJyY6aG92ZXInOiB7XG4gICAgICAgIHRleHREZWNvcmF0aW9uOiAnbGluZS10aHJvdWdoJyxcbiAgICAgIH0sXG4gICAgfSxcbiAgfSksXG4gIGNzcyh7XG4gICAgZm9udFdlaWdodDogJ2Jhc2UnLFxuICAgIHdpZHRoOiAnMzJweCcsXG4gIH0pXG4pO1xuXG5leHBvcnQgY29uc3QgQ2FsZW5kYXJCb2R5OiBSZWFjdC5GQzxDYWxlbmRhckJvZHlQcm9wcz4gPSAoe1xuICB2aXNpYmxlRGF0ZSxcbiAgc2VsZWN0ZWREYXRlLFxuICBlbmREYXRlID0gbnVsbCxcbiAgZGlzYWJsZWREYXRlcyA9IFtdLFxuICBvbkRhdGVTZWxlY3QsXG4gIGxvY2FsZSxcbiAgd2Vla1N0YXJ0c09uID0gMCxcbiAgbGFiZWxsZWRCeUlkLFxuICBmb2N1c2VkRGF0ZSxcbiAgb25Gb2N1c2VkRGF0ZUNoYW5nZSxcbiAgb25WaXNpYmxlRGF0ZUNoYW5nZSxcbiAgb25Fc2NhcGVLZXlQcmVzcyxcbiAgaGFzQWRqYWNlbnRNb250aFJpZ2h0LFxuICBoYXNBZGphY2VudE1vbnRoTGVmdCxcbn0pID0+IHtcbiAgY29uc3QgeWVhciA9IHZpc2libGVEYXRlLmdldEZ1bGxZZWFyKCk7XG4gIGNvbnN0IG1vbnRoID0gdmlzaWJsZURhdGUuZ2V0TW9udGgoKTtcbiAgY29uc3Qgd2Vla3MgPSBnZXRNb250aEdyaWQoeWVhciwgbW9udGgsIHdlZWtTdGFydHNPbik7XG4gIGNvbnN0IHdlZWtkYXlMYWJlbHMgPSBnZXRXZWVrZGF5TmFtZXMoJ3Nob3J0JywgbG9jYWxlLCB3ZWVrU3RhcnRzT24pO1xuICBjb25zdCB3ZWVrZGF5RnVsbE5hbWVzID0gZ2V0V2Vla2RheU5hbWVzKCdsb25nJywgbG9jYWxlLCB3ZWVrU3RhcnRzT24pO1xuICBjb25zdCBidXR0b25SZWZzID0gdXNlUmVmPE1hcDxudW1iZXIsIEhUTUxFbGVtZW50Pj4obmV3IE1hcCgpKTtcblxuICBjb25zdCBkYXRlc1dpdGhSb3cgPSB1c2VNZW1vKCgpID0+IGdldERhdGVzV2l0aFJvdyh3ZWVrcyksIFt3ZWVrc10pO1xuICBjb25zdCBmb2N1c1RhcmdldCA9IGZvY3VzZWREYXRlID8/IHNlbGVjdGVkRGF0ZTtcblxuICBjb25zdCBpc1RvZGF5ID0gdXNlQ2FsbGJhY2soXG4gICAgKGRhdGU6IERhdGUgfCBudWxsKSA9PiBkYXRlICE9PSBudWxsICYmIGlzU2FtZURheShkYXRlLCBuZXcgRGF0ZSgpKSxcbiAgICBbXVxuICApO1xuXG4gIGNvbnN0IGZvY3VzQnV0dG9uID0gdXNlQ2FsbGJhY2soKGRhdGU6IERhdGUgfCBudWxsKSA9PiB7XG4gICAgaWYgKGRhdGUgPT09IG51bGwpIHJldHVybjtcbiAgICBjb25zdCBrZXkgPSBuZXcgRGF0ZShcbiAgICAgIGRhdGUuZ2V0RnVsbFllYXIoKSxcbiAgICAgIGRhdGUuZ2V0TW9udGgoKSxcbiAgICAgIGRhdGUuZ2V0RGF0ZSgpXG4gICAgKS5nZXRUaW1lKCk7XG4gICAgYnV0dG9uUmVmcy5jdXJyZW50LmdldChrZXkpPy5mb2N1cygpO1xuICB9LCBbXSk7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoZm9jdXNUYXJnZXQgIT09IG51bGwpIGZvY3VzQnV0dG9uKGZvY3VzVGFyZ2V0KTtcbiAgfSwgW2ZvY3VzVGFyZ2V0LCBmb2N1c0J1dHRvbl0pO1xuXG4gIGNvbnN0IGhhbmRsZUtleURvd24gPSB1c2VDYWxsYmFjayhcbiAgICAoZTogUmVhY3QuS2V5Ym9hcmRFdmVudCwgZGF0ZTogRGF0ZSkgPT5cbiAgICAgIGtleUhhbmRsZXIoXG4gICAgICAgIGUsXG4gICAgICAgIGRhdGUsXG4gICAgICAgIG9uRm9jdXNlZERhdGVDaGFuZ2UsXG4gICAgICAgIGRhdGVzV2l0aFJvdyxcbiAgICAgICAgbW9udGgsXG4gICAgICAgIHllYXIsXG4gICAgICAgIGRpc2FibGVkRGF0ZXMsXG4gICAgICAgIG9uRGF0ZVNlbGVjdCxcbiAgICAgICAgb25Fc2NhcGVLZXlQcmVzcyxcbiAgICAgICAgb25WaXNpYmxlRGF0ZUNoYW5nZSxcbiAgICAgICAgaGFzQWRqYWNlbnRNb250aFJpZ2h0LFxuICAgICAgICBoYXNBZGphY2VudE1vbnRoTGVmdFxuICAgICAgKSxcbiAgICBbXG4gICAgICBvbkZvY3VzZWREYXRlQ2hhbmdlLFxuICAgICAgZGF0ZXNXaXRoUm93LFxuICAgICAgbW9udGgsXG4gICAgICB5ZWFyLFxuICAgICAgZGlzYWJsZWREYXRlcyxcbiAgICAgIG9uRGF0ZVNlbGVjdCxcbiAgICAgIG9uRXNjYXBlS2V5UHJlc3MsXG4gICAgICBvblZpc2libGVEYXRlQ2hhbmdlLFxuICAgICAgaGFzQWRqYWNlbnRNb250aExlZnQsXG4gICAgICBoYXNBZGphY2VudE1vbnRoUmlnaHQsXG4gICAgXVxuICApO1xuXG4gIGNvbnN0IHNldEJ1dHRvblJlZiA9IHVzZUNhbGxiYWNrKChkYXRlOiBEYXRlLCBlbDogSFRNTEVsZW1lbnQgfCBudWxsKSA9PiB7XG4gICAgY29uc3QgayA9IG5ldyBEYXRlKFxuICAgICAgZGF0ZS5nZXRGdWxsWWVhcigpLFxuICAgICAgZGF0ZS5nZXRNb250aCgpLFxuICAgICAgZGF0ZS5nZXREYXRlKClcbiAgICApLmdldFRpbWUoKTtcbiAgICBpZiAoZWwpIGJ1dHRvblJlZnMuY3VycmVudC5zZXQoaywgZWwpO1xuICAgIGVsc2UgYnV0dG9uUmVmcy5jdXJyZW50LmRlbGV0ZShrKTtcbiAgfSwgW10pO1xuXG4gIHJldHVybiAoXG4gICAgPHRhYmxlIGFyaWEtbGFiZWxsZWRieT17bGFiZWxsZWRCeUlkfSByb2xlPVwiZ3JpZFwiIHdpZHRoPVwiMTAwJVwiPlxuICAgICAgPHRoZWFkPlxuICAgICAgICA8dHI+XG4gICAgICAgICAge3dlZWtkYXlMYWJlbHMubWFwKChsYWJlbCwgaSkgPT4gKFxuICAgICAgICAgICAgPFRhYmxlSGVhZGVyIGFiYnI9e3dlZWtkYXlGdWxsTmFtZXNbaV19IGtleT17bGFiZWx9IHNjb3BlPVwiY29sXCI+XG4gICAgICAgICAgICAgIHtsYWJlbH1cbiAgICAgICAgICAgIDwvVGFibGVIZWFkZXI+XG4gICAgICAgICAgKSl9XG4gICAgICAgIDwvdHI+XG4gICAgICA8L3RoZWFkPlxuICAgICAgPHRib2R5PlxuICAgICAgICB7d2Vla3MubWFwKCh3ZWVrLCByb3dJbmRleCkgPT4gKFxuICAgICAgICAgIDx0ciBrZXk9e3dlZWsuam9pbignLScpfT5cbiAgICAgICAgICAgIHt3ZWVrLm1hcCgoZGF0ZSwgY29sSW5kZXgpID0+IHtcbiAgICAgICAgICAgICAgaWYgKGRhdGUgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgICAgLy8gZml4IHRoaXMgZXJyb3JcbiAgICAgICAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSByZWFjdC9uby1hcnJheS1pbmRleC1rZXksIGpzeC1hMTF5L2NvbnRyb2wtaGFzLWFzc29jaWF0ZWQtbGFiZWxcbiAgICAgICAgICAgICAgICAgIDx0ZCBrZXk9e2BlbXB0eS0ke3Jvd0luZGV4fS0ke2NvbEluZGV4fWB9IHJvbGU9XCJncmlkY2VsbFwiIC8+XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBjb25zdCBzZWxlY3RlZCA9XG4gICAgICAgICAgICAgICAgaXNTYW1lRGF5KGRhdGUsIHNlbGVjdGVkRGF0ZSkgfHwgaXNTYW1lRGF5KGRhdGUsIGVuZERhdGUpO1xuICAgICAgICAgICAgICBjb25zdCBpblJhbmdlID1cbiAgICAgICAgICAgICAgICAhIXNlbGVjdGVkRGF0ZSAmJlxuICAgICAgICAgICAgICAgICEhZW5kRGF0ZSAmJlxuICAgICAgICAgICAgICAgIGlzRGF0ZUluUmFuZ2UoZGF0ZSwgc2VsZWN0ZWREYXRlLCBlbmREYXRlKTtcbiAgICAgICAgICAgICAgY29uc3QgZGlzYWJsZWQgPSBpc0RhdGVEaXNhYmxlZChkYXRlLCBkaXNhYmxlZERhdGVzKTtcbiAgICAgICAgICAgICAgY29uc3QgdG9kYXkgPSBpc1RvZGF5KGRhdGUpO1xuICAgICAgICAgICAgICAvLyB0aGlzIGlzIG1ha2luZyB0aGUgc2VsZWN0ZWQgZGF0ZSBhIGRpZmZlcm5ldCBjb2xvciBiYyBpdCBpcyBmb2N1c2VkLCBsb29rIGludG8gZnVydGhlclxuICAgICAgICAgICAgICBjb25zdCBpc0ZvY3VzZWQgPVxuICAgICAgICAgICAgICAgIGZvY3VzVGFyZ2V0ICE9PSBudWxsICYmIGlzU2FtZURheShkYXRlLCBmb2N1c1RhcmdldCk7XG5cbiAgICAgICAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgICAgICA8dGRcbiAgICAgICAgICAgICAgICAgIGFyaWEtc2VsZWN0ZWQ9e3NlbGVjdGVkfVxuICAgICAgICAgICAgICAgICAga2V5PXtkYXRlLmdldFRpbWUoKX1cbiAgICAgICAgICAgICAgICAgIHJvbGU9XCJncmlkY2VsbFwiXG4gICAgICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAgICAgPERhdGVCdXR0b25cbiAgICAgICAgICAgICAgICAgICAgZGlzYWJsZWQ9e2Rpc2FibGVkfVxuICAgICAgICAgICAgICAgICAgICBpc0luUmFuZ2U9e2luUmFuZ2V9XG4gICAgICAgICAgICAgICAgICAgIGlzU2VsZWN0ZWQ9e3NlbGVjdGVkfVxuICAgICAgICAgICAgICAgICAgICBpc1RvZGF5PXt0b2RheX1cbiAgICAgICAgICAgICAgICAgICAgcmVmPXsoZWwpID0+IHNldEJ1dHRvblJlZihkYXRlLCBlbCBhcyBIVE1MRWxlbWVudCB8IG51bGwpfVxuICAgICAgICAgICAgICAgICAgICB0YWJJbmRleD17aXNGb2N1c2VkID8gMCA6IC0xfVxuICAgICAgICAgICAgICAgICAgICB2YXJpYW50PVwic2Vjb25kYXJ5XCJcbiAgICAgICAgICAgICAgICAgICAgb25DbGljaz17KCkgPT4gb25EYXRlU2VsZWN0KGRhdGUpfVxuICAgICAgICAgICAgICAgICAgICBvbkZvY3VzPXsoKSA9PiBvbkZvY3VzZWREYXRlQ2hhbmdlPy4oZGF0ZSl9XG4gICAgICAgICAgICAgICAgICAgIG9uS2V5RG93bj17KGU6IFJlYWN0LktleWJvYXJkRXZlbnQpID0+XG4gICAgICAgICAgICAgICAgICAgICAgaGFuZGxlS2V5RG93bihlLCBkYXRlKVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICAgIHtkYXRlLmdldERhdGUoKX1cbiAgICAgICAgICAgICAgICAgIDwvRGF0ZUJ1dHRvbj5cbiAgICAgICAgICAgICAgICA8L3RkPlxuICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfSl9XG4gICAgICAgICAgPC90cj5cbiAgICAgICAgKSl9XG4gICAgICA8L3Rib2R5PlxuICAgIDwvdGFibGU+XG4gICk7XG59O1xuIl19 */");
|
|
19
19
|
const DateButton = /*#__PURE__*/_styled(TextButton, {
|
|
20
20
|
target: "e12sl4cx0",
|
|
21
21
|
label: "DateButton"
|
|
@@ -26,7 +26,6 @@ const DateButton = /*#__PURE__*/_styled(TextButton, {
|
|
|
26
26
|
content: '""',
|
|
27
27
|
position: 'absolute',
|
|
28
28
|
bottom: 4,
|
|
29
|
-
left: '50%',
|
|
30
29
|
width: 4,
|
|
31
30
|
height: 4,
|
|
32
31
|
borderRadius: 'full',
|
|
@@ -58,12 +57,15 @@ const DateButton = /*#__PURE__*/_styled(TextButton, {
|
|
|
58
57
|
},
|
|
59
58
|
disabled: {
|
|
60
59
|
color: 'text-disabled',
|
|
61
|
-
textDecoration: 'line-through'
|
|
60
|
+
textDecoration: 'line-through',
|
|
61
|
+
'&:hover': {
|
|
62
|
+
textDecoration: 'line-through'
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
}), css({
|
|
64
66
|
fontWeight: 'base',
|
|
65
67
|
width: '32px'
|
|
66
|
-
}), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,
|
|
68
|
+
}), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9EYXRlUGlja2VyL0NhbGVuZGFyL0NhbGVuZGFyQm9keS50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBeUJtQiIsImZpbGUiOiIuLi8uLi8uLi9zcmMvRGF0ZVBpY2tlci9DYWxlbmRhci9DYWxlbmRhckJvZHkudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBzdGF0ZXMgfSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuaW1wb3J0IHsgdXNlQ2FsbGJhY2ssIHVzZUVmZmVjdCwgdXNlTWVtbywgdXNlUmVmIH0gZnJvbSAncmVhY3QnO1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnO1xuXG5pbXBvcnQgeyBUZXh0QnV0dG9uIH0gZnJvbSAnLi4vLi4vQnV0dG9uJztcbmltcG9ydCB7IENhbGVuZGFyQm9keVByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQge1xuICBnZXRNb250aEdyaWQsXG4gIGlzRGF0ZURpc2FibGVkLFxuICBpc0RhdGVJblJhbmdlLFxuICBpc1NhbWVEYXksXG59IGZyb20gJy4vdXRpbHMvZGF0ZUdyaWQnO1xuaW1wb3J0IHsgZ2V0V2Vla2RheU5hbWVzIH0gZnJvbSAnLi91dGlscy9mb3JtYXQnO1xuaW1wb3J0IHsgZ2V0RGF0ZXNXaXRoUm93LCBrZXlIYW5kbGVyIH0gZnJvbSAnLi91dGlscy9rZXlIYW5kbGVyJztcblxuY29uc3QgVGFibGVIZWFkZXIgPSBzdHlsZWQudGgoXG4gIGNzcyh7XG4gICAgZm9udFNpemU6IDE0LFxuICAgIGZvbnRXZWlnaHQ6ICdiYXNlJyxcbiAgICBjb2xvcjogJ3RleHQtZGlzYWJsZWQnLFxuICAgIHRleHRBbGlnbjogJ2NlbnRlcicsXG4gIH0pXG4pO1xuXG5jb25zdCBEYXRlQnV0dG9uID0gc3R5bGVkKFRleHRCdXR0b24pKFxuICBzdGF0ZXMoe1xuICAgIGlzVG9kYXk6IHtcbiAgICAgIHBvc2l0aW9uOiAncmVsYXRpdmUnLFxuICAgICAgJyY6OmFmdGVyJzoge1xuICAgICAgICBjb250ZW50OiAnXCJcIicsXG4gICAgICAgIHBvc2l0aW9uOiAnYWJzb2x1dGUnLFxuICAgICAgICBib3R0b206IDQsXG4gICAgICAgIHdpZHRoOiA0LFxuICAgICAgICBoZWlnaHQ6IDQsXG4gICAgICAgIGJvcmRlclJhZGl1czogJ2Z1bGwnLFxuICAgICAgICBiZzogJ2h5cGVyJyxcbiAgICAgIH0sXG4gICAgfSxcbiAgICBpc1NlbGVjdGVkOiB7XG4gICAgICBiZzogJ3RleHQnLFxuICAgICAgY29sb3I6ICdiYWNrZ3JvdW5kJyxcbiAgICAgICcmOmhvdmVyLCAmOmZvY3VzJzoge1xuICAgICAgICBiZzogJ3NlY29uZGFyeS1ob3ZlcicsXG4gICAgICAgIGNvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgICB9LFxuICAgICAgJyY6OmFmdGVyJzoge1xuICAgICAgICBiZzogJ2JhY2tncm91bmQnLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGlzSW5SYW5nZToge1xuICAgICAgYmc6ICd0ZXh0LWRpc2FibGVkJyxcbiAgICAgIGNvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgICBib3JkZXJSYWRpdXM6ICdub25lJyxcbiAgICAgICcmOmhvdmVyLCAmOmZvY3VzJzoge1xuICAgICAgICBiZzogJ3NlY29uZGFyeS1ob3ZlcicsXG4gICAgICAgIGNvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgICB9LFxuICAgICAgJyY6OmFmdGVyJzoge1xuICAgICAgICBiZzogJ2JhY2tncm91bmQnLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGRpc2FibGVkOiB7XG4gICAgICBjb2xvcjogJ3RleHQtZGlzYWJsZWQnLFxuICAgICAgdGV4dERlY29yYXRpb246ICdsaW5lLXRocm91Z2gnLFxuICAgICAgJyY6aG92ZXInOiB7XG4gICAgICAgIHRleHREZWNvcmF0aW9uOiAnbGluZS10aHJvdWdoJyxcbiAgICAgIH0sXG4gICAgfSxcbiAgfSksXG4gIGNzcyh7XG4gICAgZm9udFdlaWdodDogJ2Jhc2UnLFxuICAgIHdpZHRoOiAnMzJweCcsXG4gIH0pXG4pO1xuXG5leHBvcnQgY29uc3QgQ2FsZW5kYXJCb2R5OiBSZWFjdC5GQzxDYWxlbmRhckJvZHlQcm9wcz4gPSAoe1xuICB2aXNpYmxlRGF0ZSxcbiAgc2VsZWN0ZWREYXRlLFxuICBlbmREYXRlID0gbnVsbCxcbiAgZGlzYWJsZWREYXRlcyA9IFtdLFxuICBvbkRhdGVTZWxlY3QsXG4gIGxvY2FsZSxcbiAgd2Vla1N0YXJ0c09uID0gMCxcbiAgbGFiZWxsZWRCeUlkLFxuICBmb2N1c2VkRGF0ZSxcbiAgb25Gb2N1c2VkRGF0ZUNoYW5nZSxcbiAgb25WaXNpYmxlRGF0ZUNoYW5nZSxcbiAgb25Fc2NhcGVLZXlQcmVzcyxcbiAgaGFzQWRqYWNlbnRNb250aFJpZ2h0LFxuICBoYXNBZGphY2VudE1vbnRoTGVmdCxcbn0pID0+IHtcbiAgY29uc3QgeWVhciA9IHZpc2libGVEYXRlLmdldEZ1bGxZZWFyKCk7XG4gIGNvbnN0IG1vbnRoID0gdmlzaWJsZURhdGUuZ2V0TW9udGgoKTtcbiAgY29uc3Qgd2Vla3MgPSBnZXRNb250aEdyaWQoeWVhciwgbW9udGgsIHdlZWtTdGFydHNPbik7XG4gIGNvbnN0IHdlZWtkYXlMYWJlbHMgPSBnZXRXZWVrZGF5TmFtZXMoJ3Nob3J0JywgbG9jYWxlLCB3ZWVrU3RhcnRzT24pO1xuICBjb25zdCB3ZWVrZGF5RnVsbE5hbWVzID0gZ2V0V2Vla2RheU5hbWVzKCdsb25nJywgbG9jYWxlLCB3ZWVrU3RhcnRzT24pO1xuICBjb25zdCBidXR0b25SZWZzID0gdXNlUmVmPE1hcDxudW1iZXIsIEhUTUxFbGVtZW50Pj4obmV3IE1hcCgpKTtcblxuICBjb25zdCBkYXRlc1dpdGhSb3cgPSB1c2VNZW1vKCgpID0+IGdldERhdGVzV2l0aFJvdyh3ZWVrcyksIFt3ZWVrc10pO1xuICBjb25zdCBmb2N1c1RhcmdldCA9IGZvY3VzZWREYXRlID8/IHNlbGVjdGVkRGF0ZTtcblxuICBjb25zdCBpc1RvZGF5ID0gdXNlQ2FsbGJhY2soXG4gICAgKGRhdGU6IERhdGUgfCBudWxsKSA9PiBkYXRlICE9PSBudWxsICYmIGlzU2FtZURheShkYXRlLCBuZXcgRGF0ZSgpKSxcbiAgICBbXVxuICApO1xuXG4gIGNvbnN0IGZvY3VzQnV0dG9uID0gdXNlQ2FsbGJhY2soKGRhdGU6IERhdGUgfCBudWxsKSA9PiB7XG4gICAgaWYgKGRhdGUgPT09IG51bGwpIHJldHVybjtcbiAgICBjb25zdCBrZXkgPSBuZXcgRGF0ZShcbiAgICAgIGRhdGUuZ2V0RnVsbFllYXIoKSxcbiAgICAgIGRhdGUuZ2V0TW9udGgoKSxcbiAgICAgIGRhdGUuZ2V0RGF0ZSgpXG4gICAgKS5nZXRUaW1lKCk7XG4gICAgYnV0dG9uUmVmcy5jdXJyZW50LmdldChrZXkpPy5mb2N1cygpO1xuICB9LCBbXSk7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoZm9jdXNUYXJnZXQgIT09IG51bGwpIGZvY3VzQnV0dG9uKGZvY3VzVGFyZ2V0KTtcbiAgfSwgW2ZvY3VzVGFyZ2V0LCBmb2N1c0J1dHRvbl0pO1xuXG4gIGNvbnN0IGhhbmRsZUtleURvd24gPSB1c2VDYWxsYmFjayhcbiAgICAoZTogUmVhY3QuS2V5Ym9hcmRFdmVudCwgZGF0ZTogRGF0ZSkgPT5cbiAgICAgIGtleUhhbmRsZXIoXG4gICAgICAgIGUsXG4gICAgICAgIGRhdGUsXG4gICAgICAgIG9uRm9jdXNlZERhdGVDaGFuZ2UsXG4gICAgICAgIGRhdGVzV2l0aFJvdyxcbiAgICAgICAgbW9udGgsXG4gICAgICAgIHllYXIsXG4gICAgICAgIGRpc2FibGVkRGF0ZXMsXG4gICAgICAgIG9uRGF0ZVNlbGVjdCxcbiAgICAgICAgb25Fc2NhcGVLZXlQcmVzcyxcbiAgICAgICAgb25WaXNpYmxlRGF0ZUNoYW5nZSxcbiAgICAgICAgaGFzQWRqYWNlbnRNb250aFJpZ2h0LFxuICAgICAgICBoYXNBZGphY2VudE1vbnRoTGVmdFxuICAgICAgKSxcbiAgICBbXG4gICAgICBvbkZvY3VzZWREYXRlQ2hhbmdlLFxuICAgICAgZGF0ZXNXaXRoUm93LFxuICAgICAgbW9udGgsXG4gICAgICB5ZWFyLFxuICAgICAgZGlzYWJsZWREYXRlcyxcbiAgICAgIG9uRGF0ZVNlbGVjdCxcbiAgICAgIG9uRXNjYXBlS2V5UHJlc3MsXG4gICAgICBvblZpc2libGVEYXRlQ2hhbmdlLFxuICAgICAgaGFzQWRqYWNlbnRNb250aExlZnQsXG4gICAgICBoYXNBZGphY2VudE1vbnRoUmlnaHQsXG4gICAgXVxuICApO1xuXG4gIGNvbnN0IHNldEJ1dHRvblJlZiA9IHVzZUNhbGxiYWNrKChkYXRlOiBEYXRlLCBlbDogSFRNTEVsZW1lbnQgfCBudWxsKSA9PiB7XG4gICAgY29uc3QgayA9IG5ldyBEYXRlKFxuICAgICAgZGF0ZS5nZXRGdWxsWWVhcigpLFxuICAgICAgZGF0ZS5nZXRNb250aCgpLFxuICAgICAgZGF0ZS5nZXREYXRlKClcbiAgICApLmdldFRpbWUoKTtcbiAgICBpZiAoZWwpIGJ1dHRvblJlZnMuY3VycmVudC5zZXQoaywgZWwpO1xuICAgIGVsc2UgYnV0dG9uUmVmcy5jdXJyZW50LmRlbGV0ZShrKTtcbiAgfSwgW10pO1xuXG4gIHJldHVybiAoXG4gICAgPHRhYmxlIGFyaWEtbGFiZWxsZWRieT17bGFiZWxsZWRCeUlkfSByb2xlPVwiZ3JpZFwiIHdpZHRoPVwiMTAwJVwiPlxuICAgICAgPHRoZWFkPlxuICAgICAgICA8dHI+XG4gICAgICAgICAge3dlZWtkYXlMYWJlbHMubWFwKChsYWJlbCwgaSkgPT4gKFxuICAgICAgICAgICAgPFRhYmxlSGVhZGVyIGFiYnI9e3dlZWtkYXlGdWxsTmFtZXNbaV19IGtleT17bGFiZWx9IHNjb3BlPVwiY29sXCI+XG4gICAgICAgICAgICAgIHtsYWJlbH1cbiAgICAgICAgICAgIDwvVGFibGVIZWFkZXI+XG4gICAgICAgICAgKSl9XG4gICAgICAgIDwvdHI+XG4gICAgICA8L3RoZWFkPlxuICAgICAgPHRib2R5PlxuICAgICAgICB7d2Vla3MubWFwKCh3ZWVrLCByb3dJbmRleCkgPT4gKFxuICAgICAgICAgIDx0ciBrZXk9e3dlZWsuam9pbignLScpfT5cbiAgICAgICAgICAgIHt3ZWVrLm1hcCgoZGF0ZSwgY29sSW5kZXgpID0+IHtcbiAgICAgICAgICAgICAgaWYgKGRhdGUgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgICAgLy8gZml4IHRoaXMgZXJyb3JcbiAgICAgICAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSByZWFjdC9uby1hcnJheS1pbmRleC1rZXksIGpzeC1hMTF5L2NvbnRyb2wtaGFzLWFzc29jaWF0ZWQtbGFiZWxcbiAgICAgICAgICAgICAgICAgIDx0ZCBrZXk9e2BlbXB0eS0ke3Jvd0luZGV4fS0ke2NvbEluZGV4fWB9IHJvbGU9XCJncmlkY2VsbFwiIC8+XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBjb25zdCBzZWxlY3RlZCA9XG4gICAgICAgICAgICAgICAgaXNTYW1lRGF5KGRhdGUsIHNlbGVjdGVkRGF0ZSkgfHwgaXNTYW1lRGF5KGRhdGUsIGVuZERhdGUpO1xuICAgICAgICAgICAgICBjb25zdCBpblJhbmdlID1cbiAgICAgICAgICAgICAgICAhIXNlbGVjdGVkRGF0ZSAmJlxuICAgICAgICAgICAgICAgICEhZW5kRGF0ZSAmJlxuICAgICAgICAgICAgICAgIGlzRGF0ZUluUmFuZ2UoZGF0ZSwgc2VsZWN0ZWREYXRlLCBlbmREYXRlKTtcbiAgICAgICAgICAgICAgY29uc3QgZGlzYWJsZWQgPSBpc0RhdGVEaXNhYmxlZChkYXRlLCBkaXNhYmxlZERhdGVzKTtcbiAgICAgICAgICAgICAgY29uc3QgdG9kYXkgPSBpc1RvZGF5KGRhdGUpO1xuICAgICAgICAgICAgICAvLyB0aGlzIGlzIG1ha2luZyB0aGUgc2VsZWN0ZWQgZGF0ZSBhIGRpZmZlcm5ldCBjb2xvciBiYyBpdCBpcyBmb2N1c2VkLCBsb29rIGludG8gZnVydGhlclxuICAgICAgICAgICAgICBjb25zdCBpc0ZvY3VzZWQgPVxuICAgICAgICAgICAgICAgIGZvY3VzVGFyZ2V0ICE9PSBudWxsICYmIGlzU2FtZURheShkYXRlLCBmb2N1c1RhcmdldCk7XG5cbiAgICAgICAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgICAgICA8dGRcbiAgICAgICAgICAgICAgICAgIGFyaWEtc2VsZWN0ZWQ9e3NlbGVjdGVkfVxuICAgICAgICAgICAgICAgICAga2V5PXtkYXRlLmdldFRpbWUoKX1cbiAgICAgICAgICAgICAgICAgIHJvbGU9XCJncmlkY2VsbFwiXG4gICAgICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAgICAgPERhdGVCdXR0b25cbiAgICAgICAgICAgICAgICAgICAgZGlzYWJsZWQ9e2Rpc2FibGVkfVxuICAgICAgICAgICAgICAgICAgICBpc0luUmFuZ2U9e2luUmFuZ2V9XG4gICAgICAgICAgICAgICAgICAgIGlzU2VsZWN0ZWQ9e3NlbGVjdGVkfVxuICAgICAgICAgICAgICAgICAgICBpc1RvZGF5PXt0b2RheX1cbiAgICAgICAgICAgICAgICAgICAgcmVmPXsoZWwpID0+IHNldEJ1dHRvblJlZihkYXRlLCBlbCBhcyBIVE1MRWxlbWVudCB8IG51bGwpfVxuICAgICAgICAgICAgICAgICAgICB0YWJJbmRleD17aXNGb2N1c2VkID8gMCA6IC0xfVxuICAgICAgICAgICAgICAgICAgICB2YXJpYW50PVwic2Vjb25kYXJ5XCJcbiAgICAgICAgICAgICAgICAgICAgb25DbGljaz17KCkgPT4gb25EYXRlU2VsZWN0KGRhdGUpfVxuICAgICAgICAgICAgICAgICAgICBvbkZvY3VzPXsoKSA9PiBvbkZvY3VzZWREYXRlQ2hhbmdlPy4oZGF0ZSl9XG4gICAgICAgICAgICAgICAgICAgIG9uS2V5RG93bj17KGU6IFJlYWN0LktleWJvYXJkRXZlbnQpID0+XG4gICAgICAgICAgICAgICAgICAgICAgaGFuZGxlS2V5RG93bihlLCBkYXRlKVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICAgIHtkYXRlLmdldERhdGUoKX1cbiAgICAgICAgICAgICAgICAgIDwvRGF0ZUJ1dHRvbj5cbiAgICAgICAgICAgICAgICA8L3RkPlxuICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfSl9XG4gICAgICAgICAgPC90cj5cbiAgICAgICAgKSl9XG4gICAgICA8L3Rib2R5PlxuICAgIDwvdGFibGU+XG4gICk7XG59O1xuIl19 */");
|
|
67
69
|
export const CalendarBody = ({
|
|
68
70
|
visibleDate,
|
|
69
71
|
selectedDate,
|
|
@@ -76,17 +78,19 @@ export const CalendarBody = ({
|
|
|
76
78
|
focusedDate,
|
|
77
79
|
onFocusedDateChange,
|
|
78
80
|
onVisibleDateChange,
|
|
79
|
-
onEscapeKeyPress
|
|
81
|
+
onEscapeKeyPress,
|
|
82
|
+
hasAdjacentMonthRight,
|
|
83
|
+
hasAdjacentMonthLeft
|
|
80
84
|
}) => {
|
|
81
85
|
const year = visibleDate.getFullYear();
|
|
82
86
|
const month = visibleDate.getMonth();
|
|
83
87
|
const weeks = getMonthGrid(year, month, weekStartsOn);
|
|
84
|
-
const weekdayLabels =
|
|
85
|
-
const weekdayFullNames =
|
|
88
|
+
const weekdayLabels = getWeekdayNames('short', locale, weekStartsOn);
|
|
89
|
+
const weekdayFullNames = getWeekdayNames('long', locale, weekStartsOn);
|
|
86
90
|
const buttonRefs = useRef(new Map());
|
|
87
91
|
const datesWithRow = useMemo(() => getDatesWithRow(weeks), [weeks]);
|
|
88
92
|
const focusTarget = focusedDate ?? selectedDate;
|
|
89
|
-
const isToday = useCallback(
|
|
93
|
+
const isToday = useCallback(date => date !== null && isSameDay(date, new Date()), []);
|
|
90
94
|
const focusButton = useCallback(date => {
|
|
91
95
|
if (date === null) return;
|
|
92
96
|
const key = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
@@ -95,7 +99,7 @@ export const CalendarBody = ({
|
|
|
95
99
|
useEffect(() => {
|
|
96
100
|
if (focusTarget !== null) focusButton(focusTarget);
|
|
97
101
|
}, [focusTarget, focusButton]);
|
|
98
|
-
const handleKeyDown = useCallback((e, date) => keyHandler(e, date, onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange), [onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange]);
|
|
102
|
+
const handleKeyDown = useCallback((e, date) => keyHandler(e, date, onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange, hasAdjacentMonthRight, hasAdjacentMonthLeft), [onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange, hasAdjacentMonthLeft, hasAdjacentMonthRight]);
|
|
99
103
|
const setButtonRef = useCallback((date, el) => {
|
|
100
104
|
const k = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
101
105
|
if (el) buttonRefs.current.set(k, el);else buttonRefs.current.delete(k);
|
|
@@ -42,6 +42,10 @@ export interface CalendarBodyProps {
|
|
|
42
42
|
onVisibleDateChange: (newDate: Date) => void;
|
|
43
43
|
/** Called when the escape key is pressed */
|
|
44
44
|
onEscapeKeyPress?: () => void;
|
|
45
|
+
/** When true (e.g. two-month view), arrow keys move focus to adjacent month without changing visible date. */
|
|
46
|
+
hasAdjacentMonthRight?: boolean;
|
|
47
|
+
/** When true (e.g. two-month view), arrow keys move focus to adjacent month without changing visible date. */
|
|
48
|
+
hasAdjacentMonthLeft?: boolean;
|
|
45
49
|
}
|
|
46
50
|
export interface QuickAction {
|
|
47
51
|
num: number;
|
|
@@ -8,8 +8,8 @@ const DAYS_PER_WEEK = 7;
|
|
|
8
8
|
/**
|
|
9
9
|
* Normalize to start of day in local time for comparison.
|
|
10
10
|
*/
|
|
11
|
-
const
|
|
12
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
11
|
+
const normalizeDate = date => {
|
|
12
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -69,7 +69,7 @@ export const getMonthGrid = (year, month, weekStartsOn = 0) => {
|
|
|
69
69
|
*/
|
|
70
70
|
export const isSameDay = (a, b) => {
|
|
71
71
|
if (a === null || b === null) return false;
|
|
72
|
-
return
|
|
72
|
+
return normalizeDate(a) === normalizeDate(b);
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
/**
|
|
@@ -77,9 +77,9 @@ export const isSameDay = (a, b) => {
|
|
|
77
77
|
*/
|
|
78
78
|
export const isDateInRange = (date, start, end) => {
|
|
79
79
|
if (start === null) return false;
|
|
80
|
-
const normalizedDateTime =
|
|
81
|
-
const normalizedStartDateTime =
|
|
82
|
-
const normalizedEndDateTime = end !== null ?
|
|
80
|
+
const normalizedDateTime = normalizeDate(date);
|
|
81
|
+
const normalizedStartDateTime = normalizeDate(start);
|
|
82
|
+
const normalizedEndDateTime = end !== null ? normalizeDate(end) : normalizedStartDateTime;
|
|
83
83
|
const low = Math.min(normalizedStartDateTime, normalizedEndDateTime);
|
|
84
84
|
const high = Math.max(normalizedStartDateTime, normalizedEndDateTime);
|
|
85
85
|
return normalizedDateTime > low && normalizedDateTime < high;
|
|
@@ -2,23 +2,19 @@
|
|
|
2
2
|
* Date formatting for the calendar using Intl.DateTimeFormat.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Capitalize the first character of a string; rest unchanged (e.g. "next month" → "Next month").
|
|
5
|
+
* Capitalize the first character of a string using the locale; rest unchanged (e.g. "next month" → "Next month").
|
|
6
6
|
*/
|
|
7
|
-
export declare const capitalizeFirst: (str: string) => string;
|
|
7
|
+
export declare const capitalizeFirst: (str: string, locale?: string) => string;
|
|
8
8
|
/**
|
|
9
9
|
* Format month and year for the calendar header (e.g. "February 2026").
|
|
10
10
|
*/
|
|
11
11
|
export declare const formatMonthYear: (date: Date, locale?: string) => string;
|
|
12
12
|
/**
|
|
13
|
-
* Get
|
|
13
|
+
* Get weekday names for column headers or abbr attributes.
|
|
14
14
|
* Order depends on weekStartsOn: 0 = Sunday first, 1 = Monday first.
|
|
15
|
+
* @param format - 'short' for abbreviated (e.g. "Su", "Mo"), 'long' for full (e.g. "Sunday", "Monday")
|
|
15
16
|
*/
|
|
16
|
-
export declare const
|
|
17
|
-
/**
|
|
18
|
-
* Get full weekday names for abbr attributes (e.g. "Sunday", "Monday").
|
|
19
|
-
* Same order as getWeekdayLabels.
|
|
20
|
-
*/
|
|
21
|
-
export declare const getWeekdayFullNames: (locale?: string, weekStartsOn?: 0 | 1) => string[];
|
|
17
|
+
export declare const getWeekdayNames: (format: 'short' | 'long', locale?: string, weekStartsOn?: 0 | 1) => string[];
|
|
22
18
|
/**
|
|
23
19
|
* Get localized "next month" and "previous month" labels for calendar nav.
|
|
24
20
|
* Uses Intl.RelativeTimeFormat with numeric: "auto" (e.g. "next month", "last month").
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Date formatting for the calendar using Intl.DateTimeFormat.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { isValidDate } from './validation';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
|
-
* Capitalize the first character of a string; rest unchanged (e.g. "next month" → "Next month").
|
|
8
|
+
* Capitalize the first character of a string using the locale; rest unchanged (e.g. "next month" → "Next month").
|
|
7
9
|
*/
|
|
8
|
-
export const capitalizeFirst = str => str.length === 0 ? str : str[0].
|
|
10
|
+
export const capitalizeFirst = (str, locale) => str.length === 0 ? str : str[0].toLocaleUpperCase(locale) + str.slice(1);
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Format month and year for the calendar header (e.g. "February 2026").
|
|
@@ -18,43 +20,22 @@ export const formatMonthYear = (date, locale) => {
|
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
* Get
|
|
23
|
+
* Get weekday names for column headers or abbr attributes.
|
|
22
24
|
* Order depends on weekStartsOn: 0 = Sunday first, 1 = Monday first.
|
|
25
|
+
* @param format - 'short' for abbreviated (e.g. "Su", "Mo"), 'long' for full (e.g. "Sunday", "Monday")
|
|
23
26
|
*/
|
|
24
|
-
export const
|
|
27
|
+
export const getWeekdayNames = (format, locale, weekStartsOn = 0) => {
|
|
25
28
|
const formatter = new Intl.DateTimeFormat(locale, {
|
|
26
|
-
weekday:
|
|
29
|
+
weekday: format
|
|
27
30
|
});
|
|
28
31
|
// Jan 7, 2024 is a Sunday; add 0..6 days to get Sun..Sat
|
|
29
32
|
const sunday = new Date(2024, 0, 7);
|
|
30
|
-
const labels = Array.from({
|
|
31
|
-
length: 7
|
|
32
|
-
}, (_, i) => {
|
|
33
|
-
const d = new Date(sunday);
|
|
34
|
-
d.setDate(sunday.getDate() + i);
|
|
35
|
-
return formatter.format(d);
|
|
36
|
-
});
|
|
37
|
-
if (weekStartsOn === 1) {
|
|
38
|
-
return [...labels.slice(1), labels[0]];
|
|
39
|
-
}
|
|
40
|
-
return labels;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get full weekday names for abbr attributes (e.g. "Sunday", "Monday").
|
|
45
|
-
* Same order as getWeekdayLabels.
|
|
46
|
-
*/
|
|
47
|
-
export const getWeekdayFullNames = (locale, weekStartsOn = 0) => {
|
|
48
|
-
const formatter = new Intl.DateTimeFormat(locale, {
|
|
49
|
-
weekday: 'long'
|
|
50
|
-
});
|
|
51
|
-
const sunday = new Date(2024, 0, 7);
|
|
52
33
|
const names = Array.from({
|
|
53
34
|
length: 7
|
|
54
35
|
}, (_, i) => {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
return formatter.format(
|
|
36
|
+
const date = new Date(sunday);
|
|
37
|
+
date.setDate(sunday.getDate() + i);
|
|
38
|
+
return formatter.format(date);
|
|
58
39
|
});
|
|
59
40
|
if (weekStartsOn === 1) {
|
|
60
41
|
return [...names.slice(1), names[0]];
|
|
@@ -71,8 +52,8 @@ export const getRelativeMonthLabels = locale => {
|
|
|
71
52
|
numeric: 'auto'
|
|
72
53
|
});
|
|
73
54
|
return {
|
|
74
|
-
nextMonth: capitalizeFirst(rtf.format(1, 'month')),
|
|
75
|
-
lastMonth: capitalizeFirst(rtf.format(-1, 'month'))
|
|
55
|
+
nextMonth: capitalizeFirst(rtf.format(1, 'month'), locale),
|
|
56
|
+
lastMonth: capitalizeFirst(rtf.format(-1, 'month'), locale)
|
|
76
57
|
};
|
|
77
58
|
};
|
|
78
59
|
|
|
@@ -83,7 +64,7 @@ export const getRelativeTodayLabel = locale => {
|
|
|
83
64
|
const rtf = new Intl.RelativeTimeFormat(locale, {
|
|
84
65
|
numeric: 'auto'
|
|
85
66
|
});
|
|
86
|
-
return capitalizeFirst(rtf.format(0, 'day'));
|
|
67
|
+
return capitalizeFirst(rtf.format(0, 'day'), locale);
|
|
87
68
|
};
|
|
88
69
|
|
|
89
70
|
/**
|
|
@@ -97,8 +78,8 @@ export const getDateFormatPattern = locale => {
|
|
|
97
78
|
month: '2-digit',
|
|
98
79
|
day: '2-digit'
|
|
99
80
|
}).formatToParts(new Date(2025, 0, 15));
|
|
100
|
-
return parts.map(
|
|
101
|
-
switch (
|
|
81
|
+
return parts.map(part => {
|
|
82
|
+
switch (part.type) {
|
|
102
83
|
case 'day':
|
|
103
84
|
return 'DD';
|
|
104
85
|
case 'month':
|
|
@@ -106,7 +87,7 @@ export const getDateFormatPattern = locale => {
|
|
|
106
87
|
case 'year':
|
|
107
88
|
return 'YYYY';
|
|
108
89
|
default:
|
|
109
|
-
return
|
|
90
|
+
return part.value;
|
|
110
91
|
}
|
|
111
92
|
}).join('');
|
|
112
93
|
};
|
|
@@ -133,7 +114,7 @@ export const parseDateFromInput = (value, locale) => {
|
|
|
133
114
|
const trimmed = value.trim();
|
|
134
115
|
if (!trimmed) return null;
|
|
135
116
|
const parsed = new Date(trimmed);
|
|
136
|
-
if (
|
|
117
|
+
if (!isValidDate(parsed)) return null;
|
|
137
118
|
const formatted = formatDateForInput(parsed, locale);
|
|
138
119
|
if (formatted === trimmed) return parsed;
|
|
139
120
|
const parts = trimmed.split(/[/-]/);
|
|
@@ -159,13 +140,13 @@ export const formatDateRangeForInput = (startDate, endDate, locale) => {
|
|
|
159
140
|
export const parseDateRangeFromInput = (value, locale) => {
|
|
160
141
|
const trimmed = value.trim();
|
|
161
142
|
if (!trimmed) return null;
|
|
162
|
-
const parts = trimmed.split(RANGE_SEPARATOR).map(
|
|
143
|
+
const parts = trimmed.split(RANGE_SEPARATOR).map(part => part.trim());
|
|
163
144
|
if (parts.length === 1) {
|
|
164
|
-
const
|
|
165
|
-
if (!
|
|
145
|
+
const date = parseDateFromInput(parts[0], locale);
|
|
146
|
+
if (!date) return null;
|
|
166
147
|
return {
|
|
167
|
-
startDate:
|
|
168
|
-
endDate: new Date(
|
|
148
|
+
startDate: date,
|
|
149
|
+
endDate: new Date(date)
|
|
169
150
|
};
|
|
170
151
|
}
|
|
171
152
|
if (parts.length === 2) {
|
|
@@ -10,4 +10,4 @@ export declare const getDatesWithRow: (weeks: (Date | null)[][]) => {
|
|
|
10
10
|
export declare const keyHandler: (e: React.KeyboardEvent, date: Date, onFocusedDateChange: (date: Date | null) => void, datesWithRow: {
|
|
11
11
|
date: Date;
|
|
12
12
|
rowIndex: number;
|
|
13
|
-
}[], month: number, year: number, disabledDates: Date[], onDateSelect: (date: Date) => void, onEscapeKeyPress?: () => void, onVisibleDateChange?: ((newDate: Date) => void) | undefined) => void;
|
|
13
|
+
}[], month: number, year: number, disabledDates: Date[], onDateSelect: (date: Date) => void, onEscapeKeyPress?: () => void, onVisibleDateChange?: ((newDate: Date) => void) | undefined, hasAdjacentMonthRight?: boolean, hasAdjacentMonthLeft?: boolean) => void;
|
|
@@ -21,14 +21,15 @@ export const getDatesWithRow = weeks => {
|
|
|
21
21
|
});
|
|
22
22
|
return result;
|
|
23
23
|
};
|
|
24
|
-
export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange) => {
|
|
25
|
-
const key = date.getTime();
|
|
24
|
+
export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange, hasAdjacentMonthRight, hasAdjacentMonthLeft) => {
|
|
26
25
|
const idx = datesWithRow.findIndex(({
|
|
27
|
-
date:
|
|
28
|
-
}) =>
|
|
26
|
+
date: dateWithRow
|
|
27
|
+
}) => dateWithRow.getTime() === date.getTime());
|
|
29
28
|
if (idx < 0) return;
|
|
30
29
|
const currentRow = datesWithRow[idx].rowIndex;
|
|
31
30
|
const day = date.getDate();
|
|
31
|
+
const hasRight = !!hasAdjacentMonthRight;
|
|
32
|
+
const hasLeft = !!hasAdjacentMonthLeft;
|
|
32
33
|
let newDate = null;
|
|
33
34
|
let newVisibleDate = null;
|
|
34
35
|
switch (e.key) {
|
|
@@ -39,7 +40,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
|
|
|
39
40
|
} else {
|
|
40
41
|
const lastDayPrevMonth = new Date(year, month, 0);
|
|
41
42
|
newDate = lastDayPrevMonth;
|
|
42
|
-
|
|
43
|
+
if (!hasLeft) {
|
|
44
|
+
newVisibleDate = new Date(year, month - 1, 1);
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
47
|
break;
|
|
45
48
|
case 'ArrowRight':
|
|
@@ -48,7 +51,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
|
|
|
48
51
|
newDate = datesWithRow[idx + 1].date;
|
|
49
52
|
} else {
|
|
50
53
|
newDate = new Date(year, month + 1, 1);
|
|
51
|
-
|
|
54
|
+
if (!hasRight) {
|
|
55
|
+
newVisibleDate = new Date(year, month + 1, 1);
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
break;
|
|
54
59
|
case 'ArrowUp':
|
|
@@ -56,7 +61,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
|
|
|
56
61
|
newDate = new Date(date);
|
|
57
62
|
newDate.setDate(newDate.getDate() - 7);
|
|
58
63
|
if (newDate.getMonth() !== month || newDate.getFullYear() !== year) {
|
|
59
|
-
|
|
64
|
+
if (!hasLeft) {
|
|
65
|
+
newVisibleDate = new Date(newDate.getFullYear(), newDate.getMonth(), 1);
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
68
|
break;
|
|
62
69
|
case 'ArrowDown':
|
|
@@ -64,7 +71,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
|
|
|
64
71
|
newDate = new Date(date);
|
|
65
72
|
newDate.setDate(newDate.getDate() + 7);
|
|
66
73
|
if (newDate.getMonth() !== month || newDate.getFullYear() !== year) {
|
|
67
|
-
|
|
74
|
+
if (!hasRight) {
|
|
75
|
+
newVisibleDate = new Date(newDate.getFullYear(), newDate.getMonth(), 1);
|
|
76
|
+
}
|
|
68
77
|
}
|
|
69
78
|
break;
|
|
70
79
|
case 'Home':
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MiniArrowRightIcon } from '@codecademy/gamut-icons';
|
|
2
|
-
import { useCallback, useId,
|
|
2
|
+
import { useCallback, useId, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { FlexBox } from '../Box';
|
|
4
4
|
import { PopoverContainer } from '../PopoverContainer';
|
|
5
5
|
import { DatePickerCalendar } from './DatePickerCalendar';
|
|
@@ -30,18 +30,6 @@ export const DatePicker = props => {
|
|
|
30
30
|
const inputRef = useRef(null);
|
|
31
31
|
const dialogId = useId();
|
|
32
32
|
const calendarDialogId = `datepicker-dialog-${dialogId.replace(/:/g, '')}`;
|
|
33
|
-
const popoverOffset = 4;
|
|
34
|
-
|
|
35
|
-
// Align popover left edge with input left edge. PopoverContainer's "bottom-right"
|
|
36
|
-
// sets popover left = target left + (target width + offset + x), so we pass
|
|
37
|
-
// x = -(target width + offset) to get popover left = target left.
|
|
38
|
-
const [popoverX, setPopoverX] = useState(0);
|
|
39
|
-
useLayoutEffect(() => {
|
|
40
|
-
if (isCalendarOpen && inputRef.current) {
|
|
41
|
-
const width = inputRef.current.offsetWidth;
|
|
42
|
-
setPopoverX(-(width + popoverOffset));
|
|
43
|
-
}
|
|
44
|
-
}, [isCalendarOpen, popoverOffset]);
|
|
45
33
|
const openCalendar = useCallback(() => setIsCalendarOpen(true), []);
|
|
46
34
|
const closeCalendar = useCallback(() => {
|
|
47
35
|
setIsCalendarOpen(false);
|
|
@@ -85,14 +73,6 @@ export const DatePicker = props => {
|
|
|
85
73
|
mode: 'single'
|
|
86
74
|
};
|
|
87
75
|
}, [mode, startOrSelectedDate, endDate, setSelection, activeRangePart, setActiveRangePart, isCalendarOpen, openCalendar, closeCalendar, locale, disabledDates, calendarDialogId, translationsProp]);
|
|
88
|
-
|
|
89
|
-
// what is this doing
|
|
90
|
-
// useEffect(() => {
|
|
91
|
-
// if (!isCalendarOpen) return;
|
|
92
|
-
// const id = setTimeout(() => inputRef.current?.focus(), 0);
|
|
93
|
-
// return () => clearTimeout(id);
|
|
94
|
-
// }, [isCalendarOpen]);
|
|
95
|
-
|
|
96
76
|
const content = children !== undefined ? children : /*#__PURE__*/_jsxs(_Fragment, {
|
|
97
77
|
children: [/*#__PURE__*/_jsx(FlexBox, {
|
|
98
78
|
gap: 8,
|
|
@@ -106,7 +86,7 @@ export const DatePicker = props => {
|
|
|
106
86
|
ref: inputRef
|
|
107
87
|
}), /*#__PURE__*/_jsx(MiniArrowRightIcon, {
|
|
108
88
|
alignSelf: "center"
|
|
109
|
-
}),
|
|
89
|
+
}), /*#__PURE__*/_jsx(DatePickerInput, {
|
|
110
90
|
label: props.endLabel,
|
|
111
91
|
placeholder: placeholder,
|
|
112
92
|
rangePart: "end"
|
|
@@ -118,20 +98,20 @@ export const DatePicker = props => {
|
|
|
118
98
|
ref: inputRef
|
|
119
99
|
})
|
|
120
100
|
}), /*#__PURE__*/_jsx(PopoverContainer, {
|
|
121
|
-
alignment: "bottom-
|
|
101
|
+
alignment: "bottom-left",
|
|
122
102
|
allowPageInteraction: true,
|
|
123
103
|
focusOnProps: {
|
|
124
104
|
autoFocus: false,
|
|
125
105
|
focusLock: false
|
|
126
106
|
},
|
|
107
|
+
invertAxis: "x",
|
|
127
108
|
isOpen: isCalendarOpen,
|
|
128
|
-
offset: popoverOffset,
|
|
129
109
|
targetRef: inputRef,
|
|
130
|
-
x:
|
|
131
|
-
y:
|
|
110
|
+
x: -20,
|
|
111
|
+
y: -16,
|
|
132
112
|
onRequestClose: closeCalendar,
|
|
133
113
|
children: /*#__PURE__*/_jsx("div", {
|
|
134
|
-
"aria-label":
|
|
114
|
+
"aria-label": contextValue.translations.calendarDialogAriaLabel,
|
|
135
115
|
id: calendarDialogId,
|
|
136
116
|
role: "dialog",
|
|
137
117
|
children: /*#__PURE__*/_jsx(DatePickerCalendar, {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { breakpoints } from '@codecademy/gamut-styles';
|
|
1
2
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
3
|
+
import { useMedia } from 'react-use';
|
|
2
4
|
import { Box, FlexBox } from '../Box';
|
|
3
5
|
import { Calendar, CalendarBody, CalendarFooter, CalendarHeader } from './Calendar';
|
|
4
6
|
import { useDatePicker } from './DatePickerContext';
|
|
@@ -54,7 +56,7 @@ export const DatePickerCalendar = ({
|
|
|
54
56
|
handleDateSelectSingle(date, startOrSelectedDate, setSelection);
|
|
55
57
|
} else {
|
|
56
58
|
context.setActiveRangePart(null);
|
|
57
|
-
handleDateSelectRange(date, context.activeRangePart, startOrSelectedDate, context.endDate, setSelection);
|
|
59
|
+
handleDateSelectRange(date, context.activeRangePart, startOrSelectedDate, context.endDate, setSelection, disabledDates);
|
|
58
60
|
}
|
|
59
61
|
};
|
|
60
62
|
const handleClearDate = () => {
|
|
@@ -70,6 +72,7 @@ export const DatePickerCalendar = ({
|
|
|
70
72
|
const focusTarget = focusedDate ?? startOrSelectedDate ?? endDate ?? new Date();
|
|
71
73
|
const addMonths = (date, n) => new Date(date.getFullYear(), date.getMonth() + n, 1);
|
|
72
74
|
const secondMonthDate = addMonths(visibleDate, 1);
|
|
75
|
+
const isTwoMonthsVisible = useMedia(`(min-width: ${breakpoints.xs})`);
|
|
73
76
|
return /*#__PURE__*/_jsxs(Calendar, {
|
|
74
77
|
children: [/*#__PURE__*/_jsxs(Box, {
|
|
75
78
|
p: 24,
|
|
@@ -84,6 +87,7 @@ export const DatePickerCalendar = ({
|
|
|
84
87
|
disabledDates: disabledDates,
|
|
85
88
|
endDate: endDate,
|
|
86
89
|
focusedDate: focusTarget,
|
|
90
|
+
hasAdjacentMonthRight: isTwoMonthsVisible,
|
|
87
91
|
labelledById: headingId,
|
|
88
92
|
locale: locale,
|
|
89
93
|
selectedDate: startOrSelectedDate,
|
|
@@ -106,6 +110,7 @@ export const DatePickerCalendar = ({
|
|
|
106
110
|
disabledDates: disabledDates,
|
|
107
111
|
endDate: endDate,
|
|
108
112
|
focusedDate: focusTarget,
|
|
113
|
+
hasAdjacentMonthLeft: isTwoMonthsVisible,
|
|
109
114
|
labelledById: headingId,
|
|
110
115
|
locale: locale,
|
|
111
116
|
selectedDate: startOrSelectedDate,
|
|
@@ -119,7 +124,7 @@ export const DatePickerCalendar = ({
|
|
|
119
124
|
})]
|
|
120
125
|
})]
|
|
121
126
|
}), /*#__PURE__*/_jsx(CalendarFooter, {
|
|
122
|
-
clearText: translations.
|
|
127
|
+
clearText: translations.clearText,
|
|
123
128
|
disabled: startOrSelectedDate === null && endDate === null,
|
|
124
129
|
locale: locale,
|
|
125
130
|
showClearButton: isRange,
|
|
@@ -44,7 +44,8 @@ export const DatePickerInput = /*#__PURE__*/forwardRef(({
|
|
|
44
44
|
openCalendar,
|
|
45
45
|
locale,
|
|
46
46
|
isCalendarOpen,
|
|
47
|
-
calendarDialogId
|
|
47
|
+
calendarDialogId,
|
|
48
|
+
translations
|
|
48
49
|
} = context;
|
|
49
50
|
const isRange = mode === 'range';
|
|
50
51
|
const inputID = useId();
|
|
@@ -98,12 +99,13 @@ export const DatePickerInput = /*#__PURE__*/forwardRef(({
|
|
|
98
99
|
const handleOpenCalendar = () => {
|
|
99
100
|
openCalendar();
|
|
100
101
|
};
|
|
101
|
-
const defaultLabel = isRange
|
|
102
|
+
const defaultLabel = !isRange ? translations.dateLabel : rangePart === 'end' ? translations.endDateLabel : translations.startDateLabel;
|
|
102
103
|
return /*#__PURE__*/_jsx(FormGroup, {
|
|
103
104
|
htmlFor: inputId,
|
|
104
105
|
isSoloField: true // should probaly be based on a prop
|
|
105
106
|
,
|
|
106
107
|
label: label ?? defaultLabel,
|
|
108
|
+
mb: 0,
|
|
107
109
|
pb: 0,
|
|
108
110
|
spacing: "tight",
|
|
109
111
|
width: "170px",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/** Default UI strings; pass translations prop to override. */
|
|
2
2
|
export const DEFAULT_DATE_PICKER_TRANSLATIONS = {
|
|
3
|
-
|
|
3
|
+
clearText: 'Clear',
|
|
4
|
+
dateLabel: 'Date',
|
|
5
|
+
startDateLabel: 'Start date',
|
|
6
|
+
endDateLabel: 'End date',
|
|
7
|
+
calendarDialogAriaLabel: 'Choose date'
|
|
4
8
|
};
|
|
@@ -53,7 +53,15 @@ export type ActiveRangePart = 'start' | 'end' | null;
|
|
|
53
53
|
/** Optional translations for DatePicker UI strings. Pass to override defaults. */
|
|
54
54
|
export interface DatePickerTranslations {
|
|
55
55
|
/** Label for the clear date button (default: "Clear"). */
|
|
56
|
-
|
|
56
|
+
clearText?: string;
|
|
57
|
+
/** Default label for the date input in single mode (default: "Date"). */
|
|
58
|
+
dateLabel?: string;
|
|
59
|
+
/** Default label for the start date input in range mode (default: "Start date"). */
|
|
60
|
+
startDateLabel?: string;
|
|
61
|
+
/** Default label for the end date input in range mode (default: "End date"). */
|
|
62
|
+
endDateLabel?: string;
|
|
63
|
+
/** aria-label for the calendar dialog (default: "Choose date"). */
|
|
64
|
+
calendarDialogAriaLabel?: string;
|
|
57
65
|
}
|
|
58
66
|
/** Shared state provided by DatePicker via context. */
|
|
59
67
|
export interface DatePickerBaseContextValue {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { ActiveRangePart } from './types';
|
|
2
|
+
/** True if any disabled date falls within [start, end] (inclusive, by calendar day). */
|
|
3
|
+
export declare const rangeContainsDisabled: (start: Date, end: Date, disabledDates: Date[]) => boolean;
|
|
2
4
|
export declare const handleDateSelectSingle: (date: Date, selectedDate: Date | null, setSelection: (date: Date | null) => void) => void;
|
|
3
|
-
export declare const handleDateSelectRange: (date: Date, activeRangePart: ActiveRangePart, startDate: Date | null, endDate: Date | null, setSelection: (startDate: Date | null, endDate?: Date | null) => void) => void;
|
|
5
|
+
export declare const handleDateSelectRange: (date: Date, activeRangePart: ActiveRangePart, startDate: Date | null, endDate: Date | null, setSelection: (startDate: Date | null, endDate?: Date | null) => void, disabledDates: Date[]) => void;
|
package/dist/DatePicker/utils.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { isDateInRange, isSameDay } from './Calendar/utils/dateGrid';
|
|
2
|
+
/** True if any disabled date falls within [start, end] (inclusive, by calendar day). */
|
|
3
|
+
export const rangeContainsDisabled = (start, end, disabledDates) => {
|
|
4
|
+
return disabledDates.some(date => isSameDay(date, start) || isSameDay(date, end) || isDateInRange(date, start, end));
|
|
5
|
+
};
|
|
1
6
|
export const handleDateSelectSingle = (date, selectedDate, setSelection) => {
|
|
2
7
|
// If clicked date is the same as Start Date: Clear Start Date
|
|
3
8
|
if (selectedDate && date.getTime() === selectedDate.getTime()) {
|
|
@@ -7,7 +12,15 @@ export const handleDateSelectSingle = (date, selectedDate, setSelection) => {
|
|
|
7
12
|
// If clicked date is not the same as Start Date: Set Start Date to clicked date
|
|
8
13
|
setSelection(date);
|
|
9
14
|
};
|
|
10
|
-
|
|
15
|
+
const applyRangeOrNewStart = (start, end, clickedDate, disabledDates, setSelection) => {
|
|
16
|
+
// if range contains disabled dates, set start date to clicked date and end date to null
|
|
17
|
+
if (rangeContainsDisabled(start, end, disabledDates)) {
|
|
18
|
+
setSelection(clickedDate, null);
|
|
19
|
+
} else {
|
|
20
|
+
setSelection(start, end);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const handleDateSelectRange = (date, activeRangePart, startDate, endDate, setSelection, disabledDates) => {
|
|
11
24
|
// Range mode: field targeting (start or end input was focused)
|
|
12
25
|
if (activeRangePart === 'start') {
|
|
13
26
|
if (date.getTime() === startDate?.getTime()) {
|
|
@@ -15,7 +28,11 @@ export const handleDateSelectRange = (date, activeRangePart, startDate, endDate,
|
|
|
15
28
|
return;
|
|
16
29
|
}
|
|
17
30
|
const newEnd = endDate != null && date.getTime() <= endDate.getTime() ? endDate : null;
|
|
18
|
-
|
|
31
|
+
if (newEnd != null) {
|
|
32
|
+
applyRangeOrNewStart(date, newEnd, date, disabledDates, setSelection);
|
|
33
|
+
} else {
|
|
34
|
+
setSelection(date, newEnd);
|
|
35
|
+
}
|
|
19
36
|
return;
|
|
20
37
|
}
|
|
21
38
|
if (activeRangePart === 'end') {
|
|
@@ -24,7 +41,11 @@ export const handleDateSelectRange = (date, activeRangePart, startDate, endDate,
|
|
|
24
41
|
return;
|
|
25
42
|
}
|
|
26
43
|
const newStart = startDate != null && date.getTime() >= startDate.getTime() ? startDate : null;
|
|
27
|
-
|
|
44
|
+
if (newStart != null) {
|
|
45
|
+
applyRangeOrNewStart(newStart, date, date, disabledDates, setSelection);
|
|
46
|
+
} else {
|
|
47
|
+
setSelection(newStart, date);
|
|
48
|
+
}
|
|
28
49
|
return;
|
|
29
50
|
}
|
|
30
51
|
|
|
@@ -47,12 +68,11 @@ export const handleDateSelectRange = (date, activeRangePart, startDate, endDate,
|
|
|
47
68
|
}
|
|
48
69
|
// If clicked date > Start: Updates End Date to new date (Start remains)
|
|
49
70
|
if (date.getTime() > startDate.getTime()) {
|
|
50
|
-
|
|
71
|
+
applyRangeOrNewStart(startDate, date, date, disabledDates, setSelection);
|
|
51
72
|
return;
|
|
52
73
|
}
|
|
53
74
|
// If clicked date < Start: Updates Start Date to new date (End remains) - extends range to the left
|
|
54
|
-
|
|
55
|
-
setSelection(date, endDate);
|
|
75
|
+
applyRangeOrNewStart(date, endDate, date, disabledDates, setSelection);
|
|
56
76
|
return;
|
|
57
77
|
}
|
|
58
78
|
// Start is Set, End is Empty
|
|
@@ -60,10 +80,9 @@ export const handleDateSelectRange = (date, activeRangePart, startDate, endDate,
|
|
|
60
80
|
// If clicked date < Start: Restarts selection with clicked date as new Start
|
|
61
81
|
if (date.getTime() < startDate.getTime()) {
|
|
62
82
|
setSelection(date, null);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
setSelection(startDate, date);
|
|
83
|
+
} else {
|
|
84
|
+
// If clicked date > Start: Sets it as End Date (if range valid)
|
|
85
|
+
applyRangeOrNewStart(startDate, date, date, disabledDates, setSelection);
|
|
67
86
|
}
|
|
68
87
|
return;
|
|
69
88
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecademy/gamut",
|
|
3
3
|
"description": "Styleguide & Component library for Codecademy",
|
|
4
|
-
"version": "68.1.3-alpha.
|
|
4
|
+
"version": "68.1.3-alpha.a2160b.0",
|
|
5
5
|
"author": "Codecademy Engineering <dev@codecademy.com>",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@codecademy/gamut-icons": "9.57.0",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"dist/**/[A-Z]**/[A-Z]*.js",
|
|
60
60
|
"dist/**/[A-Z]**/index.js"
|
|
61
61
|
],
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "18146c945a8c1d5fbf8a8019dbadc5b193de5c9c"
|
|
63
63
|
}
|