@gravity-ui/date-components 1.0.0 → 1.1.1

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.
Files changed (69) hide show
  1. package/dist/cjs/components/Calendar/Calendar.css +9 -1
  2. package/dist/cjs/components/Calendar/CalendarBase.js +9 -9
  3. package/dist/cjs/components/Calendar/hooks/types.d.ts +5 -2
  4. package/dist/cjs/components/Calendar/hooks/useCalendarCellProps.js +21 -16
  5. package/dist/cjs/components/Calendar/hooks/useCalendarGridProps.js +2 -7
  6. package/dist/cjs/components/Calendar/hooks/useCalendarProps.js +6 -3
  7. package/dist/cjs/components/Calendar/hooks/useCalendarState.js +69 -26
  8. package/dist/cjs/components/Calendar/hooks/useRangeCalendarState.js +16 -11
  9. package/dist/cjs/components/Calendar/utils.d.ts +3 -1
  10. package/dist/cjs/components/Calendar/utils.js +7 -1
  11. package/dist/cjs/components/DateField/hooks/useDateFieldProps.js +23 -16
  12. package/dist/cjs/components/DateField/hooks/useDateFieldState.d.ts +2 -0
  13. package/dist/cjs/components/DateField/hooks/useDateFieldState.js +49 -166
  14. package/dist/cjs/components/DateField/types.d.ts +1 -1
  15. package/dist/cjs/components/DateField/utils.d.ts +12 -9
  16. package/dist/cjs/components/DateField/utils.js +168 -25
  17. package/dist/cjs/components/DatePicker/DatePicker.css +6 -0
  18. package/dist/cjs/components/DatePicker/DatePicker.d.ts +4 -1
  19. package/dist/cjs/components/DatePicker/DatePicker.js +6 -33
  20. package/dist/cjs/components/DatePicker/MobileCalendar.js +4 -4
  21. package/dist/cjs/components/DatePicker/hooks/useDatePickerProps.d.ts +20 -0
  22. package/dist/cjs/components/DatePicker/hooks/useDatePickerProps.js +134 -0
  23. package/dist/cjs/components/DatePicker/hooks/useDatePickerState.d.ts +4 -5
  24. package/dist/cjs/components/DatePicker/hooks/useDatePickerState.js +18 -8
  25. package/dist/cjs/components/DatePicker/index.d.ts +1 -0
  26. package/dist/cjs/components/DatePicker/index.js +1 -0
  27. package/dist/cjs/components/RelativeDatePicker/RelativeDatePicker.css +1 -0
  28. package/dist/cjs/components/RelativeDatePicker/RelativeDatePicker.d.ts +3 -0
  29. package/dist/cjs/components/RelativeDatePicker/RelativeDatePicker.js +1 -1
  30. package/dist/cjs/components/RelativeDatePicker/hooks/useRelativeDatePickerProps.js +35 -27
  31. package/dist/cjs/components/utils/dates.d.ts +9 -0
  32. package/dist/cjs/components/utils/dates.js +28 -0
  33. package/dist/esm/components/Calendar/Calendar.css +9 -1
  34. package/dist/esm/components/Calendar/CalendarBase.js +10 -10
  35. package/dist/esm/components/Calendar/hooks/types.d.ts +5 -2
  36. package/dist/esm/components/Calendar/hooks/useCalendarCellProps.js +21 -16
  37. package/dist/esm/components/Calendar/hooks/useCalendarGridProps.js +2 -7
  38. package/dist/esm/components/Calendar/hooks/useCalendarProps.js +6 -3
  39. package/dist/esm/components/Calendar/hooks/useCalendarState.js +70 -27
  40. package/dist/esm/components/Calendar/hooks/useRangeCalendarState.js +16 -11
  41. package/dist/esm/components/Calendar/utils.d.ts +3 -1
  42. package/dist/esm/components/Calendar/utils.js +6 -0
  43. package/dist/esm/components/DateField/hooks/useDateFieldProps.js +23 -16
  44. package/dist/esm/components/DateField/hooks/useDateFieldState.d.ts +2 -0
  45. package/dist/esm/components/DateField/hooks/useDateFieldState.js +41 -158
  46. package/dist/esm/components/DateField/types.d.ts +1 -1
  47. package/dist/esm/components/DateField/utils.d.ts +12 -9
  48. package/dist/esm/components/DateField/utils.js +162 -21
  49. package/dist/esm/components/DatePicker/DatePicker.css +6 -0
  50. package/dist/esm/components/DatePicker/DatePicker.d.ts +4 -1
  51. package/dist/esm/components/DatePicker/DatePicker.js +8 -35
  52. package/dist/esm/components/DatePicker/MobileCalendar.js +1 -1
  53. package/dist/esm/components/DatePicker/hooks/useDatePickerProps.d.ts +20 -0
  54. package/dist/esm/components/DatePicker/hooks/useDatePickerProps.js +127 -0
  55. package/dist/esm/components/DatePicker/hooks/useDatePickerState.d.ts +4 -5
  56. package/dist/esm/components/DatePicker/hooks/useDatePickerState.js +17 -7
  57. package/dist/esm/components/DatePicker/index.d.ts +1 -0
  58. package/dist/esm/components/DatePicker/index.js +1 -0
  59. package/dist/esm/components/RelativeDatePicker/RelativeDatePicker.css +1 -0
  60. package/dist/esm/components/RelativeDatePicker/RelativeDatePicker.d.ts +3 -0
  61. package/dist/esm/components/RelativeDatePicker/RelativeDatePicker.js +1 -1
  62. package/dist/esm/components/RelativeDatePicker/hooks/useRelativeDatePickerProps.js +35 -27
  63. package/dist/esm/components/utils/dates.d.ts +9 -0
  64. package/dist/esm/components/utils/dates.js +22 -0
  65. package/package.json +8 -7
  66. package/dist/cjs/components/DatePicker/DesktopCalendar.d.ts +0 -15
  67. package/dist/cjs/components/DatePicker/DesktopCalendar.js +0 -56
  68. package/dist/esm/components/DatePicker/DesktopCalendar.d.ts +0 -15
  69. package/dist/esm/components/DatePicker/DesktopCalendar.js +0 -48
@@ -6,7 +6,7 @@ const date_utils_1 = require("@gravity-ui/date-utils");
6
6
  const icons_1 = require("@gravity-ui/icons");
7
7
  const uikit_1 = require("@gravity-ui/uikit");
8
8
  const cn_1 = require("../../utils/cn");
9
- const utils_1 = require("../DateField/utils");
9
+ const dates_1 = require("../utils/dates");
10
10
  const getButtonSizeForInput_1 = require("../utils/getButtonSizeForInput");
11
11
  require("./MobileCalendar.css");
12
12
  const b = (0, cn_1.block)('mobile-calendar');
@@ -31,15 +31,15 @@ function MobileCalendar({ props, state }) {
31
31
  format: getDateFormat(type),
32
32
  timeZone: props.timeZone,
33
33
  })
34
- : (0, utils_1.createPlaceholderValue)({
34
+ : (0, dates_1.createPlaceholderValue)({
35
35
  placeholderValue: props.placeholderValue,
36
36
  timeZone: props.timeZone,
37
37
  });
38
38
  if (state.hasTime) {
39
- newDate = (0, utils_1.mergeDateTime)(newDate, localDate);
39
+ newDate = (0, dates_1.mergeDateTime)(newDate, localDate);
40
40
  }
41
41
  else if (state.dateValue) {
42
- newDate = (0, utils_1.mergeDateTime)(newDate, state.dateValue);
42
+ newDate = (0, dates_1.mergeDateTime)(newDate, state.dateValue);
43
43
  }
44
44
  else {
45
45
  newDate = newDate.set('hours', 0).set('minutes', 0).set('seconds', 0);
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import type { ButtonProps, PopupProps, TextInputProps } from '@gravity-ui/uikit';
3
+ import type { Calendar } from '../../Calendar';
4
+ import type { DateFieldProps } from '../../DateField';
5
+ import type { DatePickerProps } from '../DatePicker';
6
+ import type { DatePickerState } from './useDatePickerState';
7
+ interface InnerRelativeDatePickerProps {
8
+ groupProps: React.HTMLAttributes<unknown> & {
9
+ ref: React.Ref<any>;
10
+ };
11
+ fieldProps: TextInputProps;
12
+ calendarButtonProps: ButtonProps & {
13
+ ref: React.Ref<HTMLButtonElement>;
14
+ };
15
+ popupProps: PopupProps;
16
+ calendarProps: React.ComponentProps<typeof Calendar>;
17
+ timeInputProps: DateFieldProps;
18
+ }
19
+ export declare function useDatePickerProps(state: DatePickerState, { onFocus, onBlur, ...props }: DatePickerProps): InnerRelativeDatePickerProps;
20
+ export {};
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.useDatePickerProps = void 0;
18
+ const react_1 = __importDefault(require("react"));
19
+ const uikit_1 = require("@gravity-ui/uikit");
20
+ const DateField_1 = require("../../DateField");
21
+ const getButtonSizeForInput_1 = require("../../utils/getButtonSizeForInput");
22
+ const mergeProps_1 = require("../../utils/mergeProps");
23
+ const i18n_1 = require("../i18n");
24
+ function useDatePickerProps(state, _a) {
25
+ var _b;
26
+ var { onFocus, onBlur } = _a, props = __rest(_a, ["onFocus", "onBlur"]);
27
+ const [isActive, setActive] = react_1.default.useState(false);
28
+ const { focusWithinProps } = (0, uikit_1.useFocusWithin)({
29
+ onFocusWithin: onFocus,
30
+ onBlurWithin: onBlur,
31
+ onFocusWithinChange(isFocusWithin) {
32
+ setActive(isFocusWithin);
33
+ if (!isFocusWithin) {
34
+ state.setOpen(false);
35
+ }
36
+ },
37
+ });
38
+ const { inputProps } = (0, DateField_1.useDateFieldProps)(state.dateFieldState, props);
39
+ let error;
40
+ let validationState = props.validationState;
41
+ if (validationState) {
42
+ error = validationState === 'invalid' ? props.errorMessage || true : undefined;
43
+ }
44
+ else {
45
+ validationState = state.dateFieldState.validationState;
46
+ error = validationState === 'invalid';
47
+ }
48
+ const inputRef = react_1.default.useRef(null);
49
+ const handleRef = (0, uikit_1.useForkRef)(inputRef, inputProps.controlRef);
50
+ const calendarRef = react_1.default.useRef(null);
51
+ const calendarButtonRef = react_1.default.useRef(null);
52
+ const groupRef = react_1.default.useRef(null);
53
+ function focusInput() {
54
+ setTimeout(() => {
55
+ var _a;
56
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
57
+ });
58
+ }
59
+ return {
60
+ groupProps: Object.assign(Object.assign({ ref: groupRef, tabIndex: -1, role: 'group' }, focusWithinProps), { style: props.style, 'aria-disabled': state.disabled || undefined, onKeyDown: (e) => {
61
+ if (e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
62
+ e.preventDefault();
63
+ e.stopPropagation();
64
+ state.setOpen(true);
65
+ }
66
+ } }),
67
+ fieldProps: (0, mergeProps_1.mergeProps)(inputProps, state.dateFieldState.isEmpty && !isActive && props.placeholder
68
+ ? { value: '' }
69
+ : undefined, { controlRef: handleRef, error }),
70
+ calendarButtonProps: {
71
+ ref: calendarButtonRef,
72
+ size: (0, getButtonSizeForInput_1.getButtonSizeForInput)(props.size),
73
+ disabled: state.disabled,
74
+ extraProps: {
75
+ 'aria-label': (0, i18n_1.i18n)('Calendar'),
76
+ 'aria-haspopup': 'dialog',
77
+ 'aria-expanded': state.isOpen,
78
+ },
79
+ view: 'flat-secondary',
80
+ onClick: () => {
81
+ setActive(true);
82
+ state.setOpen(!state.isOpen);
83
+ },
84
+ },
85
+ popupProps: {
86
+ open: state.isOpen,
87
+ onEscapeKeyDown: () => {
88
+ state.setOpen(false);
89
+ focusInput();
90
+ },
91
+ onOutsideClick: (e) => {
92
+ var _a;
93
+ if (e.target !== calendarButtonRef.current) {
94
+ state.setOpen(false);
95
+ }
96
+ if (e.target && ((_a = groupRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
97
+ focusInput();
98
+ }
99
+ },
100
+ // @ts-expect-error focusTrap in popup was introduced in a newer version of uikit
101
+ focusTrap: true,
102
+ },
103
+ calendarProps: {
104
+ ref: calendarRef,
105
+ autoFocus: true,
106
+ size: props.size === 's' ? 'm' : props.size,
107
+ disabled: props.disabled,
108
+ readOnly: props.readOnly,
109
+ onUpdate: (d) => {
110
+ state.setDateValue(d);
111
+ if (!state.hasTime) {
112
+ focusInput();
113
+ }
114
+ },
115
+ defaultFocusedValue: (_b = state.dateValue) !== null && _b !== void 0 ? _b : undefined,
116
+ value: state.dateValue,
117
+ minValue: props.minValue,
118
+ maxValue: props.maxValue,
119
+ isDateUnavailable: props.isDateUnavailable,
120
+ timeZone: props.timeZone,
121
+ },
122
+ timeInputProps: {
123
+ value: state.timeValue,
124
+ onUpdate: state.setTimeValue,
125
+ format: state.timeFormat,
126
+ readOnly: state.readOnly,
127
+ disabled: state.disabled,
128
+ timeZone: props.timeZone,
129
+ hasClear: props.hasClear,
130
+ size: props.size,
131
+ },
132
+ };
133
+ }
134
+ exports.useDatePickerProps = useDatePickerProps;
@@ -1,5 +1,6 @@
1
1
  import type { DateTime } from '@gravity-ui/date-utils';
2
- import type { InputBase, ValueBase } from '../../types';
2
+ import type { DateFieldState } from '../../DateField';
3
+ import type { DateFieldBase } from '../../types';
3
4
  export type Granularity = 'day' | 'hour' | 'minute' | 'second';
4
5
  export interface DatePickerState {
5
6
  /** The currently selected date. */
@@ -36,10 +37,8 @@ export interface DatePickerState {
36
37
  isOpen: boolean;
37
38
  /** Sets whether the calendar popover is open. */
38
39
  setOpen(isOpen: boolean): void;
40
+ dateFieldState: DateFieldState;
39
41
  }
40
- export interface DatePickerStateOptions extends ValueBase<DateTime>, InputBase {
41
- placeholderValue?: DateTime;
42
- timeZone?: string;
43
- format?: string;
42
+ export interface DatePickerStateOptions extends DateFieldBase {
44
43
  }
45
44
  export declare function useDatePickerState(props: DatePickerStateOptions): DatePickerState;
@@ -5,8 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useDatePickerState = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
+ const DateField_1 = require("../../DateField");
8
9
  const utils_1 = require("../../DateField/utils");
9
10
  const useControlledState_1 = require("../../hooks/useControlledState");
11
+ const dates_1 = require("../../utils/dates");
10
12
  function useDatePickerState(props) {
11
13
  const { disabled, readOnly } = props;
12
14
  const [isOpen, setOpen] = react_1.default.useState(false);
@@ -44,7 +46,7 @@ function useDatePickerState(props) {
44
46
  if (disabled || readOnly) {
45
47
  return;
46
48
  }
47
- setValue(mergeDateTime(date, time));
49
+ setValue((0, dates_1.mergeDateTime)(date, time));
48
50
  setSelectedDate(null);
49
51
  setSelectedTime(null);
50
52
  };
@@ -80,6 +82,19 @@ function useDatePickerState(props) {
80
82
  setSelectedTime(newValue);
81
83
  }
82
84
  };
85
+ const dateFieldState = (0, DateField_1.useDateFieldState)({
86
+ value: value !== null && value !== void 0 ? value : null,
87
+ onUpdate: setValue,
88
+ disabled,
89
+ readOnly,
90
+ validationState: props.validationState,
91
+ minValue: props.minValue,
92
+ maxValue: props.maxValue,
93
+ isDateUnavailable: props.isDateUnavailable,
94
+ format,
95
+ placeholderValue: props.placeholderValue,
96
+ timeZone: props.timeZone,
97
+ });
83
98
  return {
84
99
  value: value !== null && value !== void 0 ? value : null,
85
100
  setValue,
@@ -100,15 +115,10 @@ function useDatePickerState(props) {
100
115
  }
101
116
  setOpen(newIsOpen);
102
117
  },
118
+ dateFieldState,
103
119
  };
104
120
  }
105
121
  exports.useDatePickerState = useDatePickerState;
106
- function mergeDateTime(date, time) {
107
- return date
108
- .set('hours', time.hour())
109
- .set('minutes', time.minute())
110
- .set('seconds', time.second());
111
- }
112
122
  function getPlaceholderTime(placeholderValue, timeZone) {
113
- return (0, utils_1.createPlaceholderValue)({ placeholderValue, timeZone });
123
+ return (0, dates_1.createPlaceholderValue)({ placeholderValue, timeZone });
114
124
  }
@@ -1,2 +1,3 @@
1
1
  export * from './DatePicker';
2
2
  export * from './hooks/useDatePickerState';
3
+ export * from './hooks/useDatePickerProps';
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./DatePicker"), exports);
18
18
  __exportStar(require("./hooks/useDatePickerState"), exports);
19
+ __exportStar(require("./hooks/useDatePickerProps"), exports);
@@ -1,6 +1,7 @@
1
1
  .g-date-relative-date-picker {
2
2
  position: relative;
3
3
  display: inline-flex;
4
+ outline: none;
4
5
  }
5
6
  .g-date-relative-date-picker__field {
6
7
  width: 100%;
@@ -1,6 +1,9 @@
1
+ import React from 'react';
2
+ import type { CalendarProps } from '../Calendar';
1
3
  import type { AccessibilityProps, DomProps, FocusableProps, KeyboardEvents, StyleProps, TextInputProps } from '../types';
2
4
  import type { RelativeDatePickerStateOptions } from './hooks/useRelativeDatePickerState';
3
5
  import './RelativeDatePicker.css';
4
6
  export interface RelativeDatePickerProps extends RelativeDatePickerStateOptions, TextInputProps, FocusableProps, KeyboardEvents, DomProps, StyleProps, AccessibilityProps {
7
+ children?: (props: CalendarProps) => React.ReactNode;
5
8
  }
6
9
  export declare function RelativeDatePicker(props: RelativeDatePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -27,6 +27,6 @@ function RelativeDatePicker(props) {
27
27
  readOnly: props.readOnly,
28
28
  placeholderValue: props.placeholderValue,
29
29
  timeZone: props.timeZone,
30
- } })), (0, jsx_runtime_1.jsx)(uikit_1.TextInput, Object.assign({}, fieldProps, { leftContent: (0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, modeSwitcherProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Function }) })), rightContent: (0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [!isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, calendarButtonProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Calendar }) }))), isMobile && state.mode === 'absolute' && ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendarIcon, { state: state.datePickerState, props: { size: props.size } }))] }) })), !isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Popup, Object.assign({}, popupProps, { anchorRef: anchorRef, children: (0, jsx_runtime_1.jsxs)("div", { className: b('popup-content'), children: [(0, jsx_runtime_1.jsx)(Calendar_1.Calendar, Object.assign({}, calendarProps)), state.datePickerState.hasTime && ((0, jsx_runtime_1.jsx)("div", { className: b('time-field-wrapper'), children: (0, jsx_runtime_1.jsx)(DateField_1.DateField, Object.assign({}, timeInputProps)) }))] }) })))] })));
30
+ } })), (0, jsx_runtime_1.jsx)(uikit_1.TextInput, Object.assign({}, fieldProps, { leftContent: (0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, modeSwitcherProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Function }) })), rightContent: (0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [!isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, calendarButtonProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Calendar }) }))), isMobile && state.mode === 'absolute' && ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendarIcon, { state: state.datePickerState, props: { size: props.size } }))] }) })), !isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Popup, Object.assign({}, popupProps, { anchorRef: anchorRef, children: (0, jsx_runtime_1.jsxs)("div", { className: b('popup-content'), children: [typeof props.children === 'function' ? (props.children(calendarProps)) : ((0, jsx_runtime_1.jsx)(Calendar_1.Calendar, Object.assign({}, calendarProps))), state.datePickerState.hasTime && ((0, jsx_runtime_1.jsx)("div", { className: b('time-field-wrapper'), children: (0, jsx_runtime_1.jsx)(DateField_1.DateField, Object.assign({}, timeInputProps)) }))] }) })))] })));
31
31
  }
32
32
  exports.RelativeDatePicker = RelativeDatePicker;
@@ -42,7 +42,7 @@ function useRelativeDatePickerProps(state, _a) {
42
42
  onBlurWithin: onBlur,
43
43
  onFocusWithinChange(isFocusWithin) {
44
44
  if (!isFocusWithin) {
45
- state.setActive(isFocusWithin);
45
+ state.setActive(false);
46
46
  }
47
47
  },
48
48
  });
@@ -50,16 +50,12 @@ function useRelativeDatePickerProps(state, _a) {
50
50
  if (!state.isActive && isOpen) {
51
51
  setOpen(false);
52
52
  }
53
- const [prevActive, setPrevActive] = react_1.default.useState(state.isActive);
54
- if (prevActive !== state.isActive) {
55
- setPrevActive(state.isActive);
56
- if (state.isActive && !isOpen) {
57
- setOpen(true);
58
- }
59
- }
60
53
  const commonInputProps = {
61
54
  onFocus: () => {
62
- state.setActive(true);
55
+ if (!state.isActive) {
56
+ state.setActive(true);
57
+ setOpen(true);
58
+ }
63
59
  },
64
60
  };
65
61
  const { inputProps } = (0, DateField_1.useDateFieldProps)(dateFieldState, Object.assign(Object.assign({}, props), { value: undefined, defaultValue: undefined, onUpdate: undefined }));
@@ -68,6 +64,7 @@ function useRelativeDatePickerProps(state, _a) {
68
64
  value: relativeDateState.text,
69
65
  onUpdate: relativeDateState.setText,
70
66
  hasClear: props.hasClear && !relativeDateState.readOnly,
67
+ placeholder: props.placeholder,
71
68
  size: props.size,
72
69
  };
73
70
  let error;
@@ -82,13 +79,33 @@ function useRelativeDatePickerProps(state, _a) {
82
79
  : dateFieldState.validationState;
83
80
  error = validationState === 'invalid';
84
81
  }
85
- const wasActiveBeforeClickRef = react_1.default.useRef(state.isActive);
86
82
  const inputRef = react_1.default.useRef(null);
87
83
  const handleRef = (0, uikit_1.useForkRef)(inputRef, mode === 'relative' ? relativeDateProps.controlRef : inputProps.controlRef);
88
84
  const calendarRef = react_1.default.useRef(null);
85
+ function focusCalendar() {
86
+ setTimeout(() => {
87
+ var _a;
88
+ (_a = calendarRef.current) === null || _a === void 0 ? void 0 : _a.focus();
89
+ });
90
+ }
91
+ function focusInput() {
92
+ setTimeout(() => {
93
+ var _a;
94
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
95
+ });
96
+ }
89
97
  return {
90
- groupProps: Object.assign({ role: 'group' }, focusWithinProps),
91
- fieldProps: (0, mergeProps_1.mergeProps)(commonInputProps, mode === 'relative' ? relativeDateProps : inputProps, { controlRef: handleRef, error }),
98
+ groupProps: Object.assign(Object.assign({ tabIndex: -1, role: 'group' }, focusWithinProps), { onKeyDown: (e) => {
99
+ if (e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
100
+ e.preventDefault();
101
+ e.stopPropagation();
102
+ setOpen(true);
103
+ focusCalendar();
104
+ }
105
+ } }),
106
+ fieldProps: (0, mergeProps_1.mergeProps)(commonInputProps, mode === 'relative' ? relativeDateProps : inputProps, mode === 'absolute' && dateFieldState.isEmpty && !state.isActive && props.placeholder
107
+ ? { value: '' }
108
+ : undefined, { controlRef: handleRef, error }),
92
109
  modeSwitcherProps: {
93
110
  size: (0, getButtonSizeForInput_1.getButtonSizeForInput)(props.size),
94
111
  disabled: state.readOnly || state.disabled,
@@ -109,7 +126,7 @@ function useRelativeDatePickerProps(state, _a) {
109
126
  else if (relativeDateState.parsedDate) {
110
127
  setFocusedDate(relativeDateState.parsedDate);
111
128
  }
112
- setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
129
+ focusInput();
113
130
  },
114
131
  },
115
132
  calendarButtonProps: {
@@ -121,29 +138,20 @@ function useRelativeDatePickerProps(state, _a) {
121
138
  'aria-expanded': isOpen,
122
139
  },
123
140
  view: 'flat-secondary',
124
- onFocus: () => {
125
- wasActiveBeforeClickRef.current = state.isActive;
126
- },
127
141
  onClick: () => {
128
142
  state.setActive(true);
129
- if (wasActiveBeforeClickRef.current) {
130
- setOpen(!isOpen);
131
- if (!isOpen) {
132
- setTimeout(() => {
133
- var _a;
134
- (_a = calendarRef.current) === null || _a === void 0 ? void 0 : _a.focus();
135
- });
136
- }
143
+ setOpen(!isOpen);
144
+ if (!isOpen) {
145
+ focusCalendar();
137
146
  }
138
- wasActiveBeforeClickRef.current = state.isActive;
139
147
  },
140
148
  },
141
149
  popupProps: {
142
150
  open: isOpen,
143
151
  onEscapeKeyDown: () => {
144
152
  setOpen(false);
153
+ focusInput();
145
154
  },
146
- restoreFocus: true,
147
155
  },
148
156
  calendarProps: {
149
157
  ref: calendarRef,
@@ -154,7 +162,7 @@ function useRelativeDatePickerProps(state, _a) {
154
162
  datePickerState.setDateValue(v);
155
163
  if (!state.datePickerState.hasTime) {
156
164
  setOpen(false);
157
- setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
165
+ focusInput();
158
166
  }
159
167
  },
160
168
  focusedValue: focusedDate,
@@ -0,0 +1,9 @@
1
+ import type { DateTime } from '@gravity-ui/date-utils';
2
+ interface PlaceholderValueOptions {
3
+ placeholderValue?: DateTime;
4
+ timeZone?: string;
5
+ }
6
+ export declare function createPlaceholderValue({ placeholderValue, timeZone }: PlaceholderValueOptions): DateTime;
7
+ export declare function isInvalid(value: DateTime | null | undefined, minValue: DateTime | undefined, maxValue: DateTime | undefined): boolean;
8
+ export declare function mergeDateTime(date: DateTime, time: DateTime): DateTime;
9
+ export {};
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeDateTime = exports.isInvalid = exports.createPlaceholderValue = void 0;
4
+ const date_utils_1 = require("@gravity-ui/date-utils");
5
+ function createPlaceholderValue({ placeholderValue, timeZone }) {
6
+ return (placeholderValue !== null && placeholderValue !== void 0 ? placeholderValue : (0, date_utils_1.dateTime)({ timeZone }).set('hour', 0).set('minute', 0).set('second', 0));
7
+ }
8
+ exports.createPlaceholderValue = createPlaceholderValue;
9
+ function isInvalid(value, minValue, maxValue) {
10
+ if (!value) {
11
+ return false;
12
+ }
13
+ if (minValue && value.isBefore(minValue)) {
14
+ return true;
15
+ }
16
+ if (maxValue && maxValue.isBefore(value)) {
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+ exports.isInvalid = isInvalid;
22
+ function mergeDateTime(date, time) {
23
+ return date
24
+ .set('hours', time.hour())
25
+ .set('minutes', time.minute())
26
+ .set('seconds', time.second());
27
+ }
28
+ exports.mergeDateTime = mergeDateTime;
@@ -82,6 +82,11 @@
82
82
  .g-date-calendar__content_animation_zoom-out.g-date-calendar__previous-state {
83
83
  animation: calendar-zoom-out-hiding 0.25s ease forwards;
84
84
  }
85
+ @media screen and (prefers-reduced-motion: reduce), (update: slow) {
86
+ .g-date-calendar__content[class] {
87
+ animation-duration: 0.001ms;
88
+ }
89
+ }
85
90
  @keyframes calendar-forward {
86
91
  from {
87
92
  transform: translateX(0);
@@ -142,10 +147,13 @@
142
147
  display: grid;
143
148
  gap: var(--_--calendar-days-gap);
144
149
  }
145
- .g-date-calendar__grid-rowgroup_mode_months, .g-date-calendar__grid-rowgroup_mode_years {
150
+ .g-date-calendar__grid-rowgroup_mode_months, .g-date-calendar__grid-rowgroup_mode_quarters, .g-date-calendar__grid-rowgroup_mode_years {
146
151
  grid-row: 1/-1;
147
152
  padding: 12px 0 0;
148
153
  }
154
+ .g-date-calendar__grid-rowgroup-header {
155
+ align-self: center;
156
+ }
149
157
  .g-date-calendar__grid-row {
150
158
  display: grid;
151
159
  grid-auto-columns: 1fr;
@@ -5,7 +5,7 @@ import { block } from '../../utils/cn';
5
5
  import { useCalendarCellProps } from './hooks/useCalendarCellProps';
6
6
  import { useCalendarGridProps } from './hooks/useCalendarGridProps';
7
7
  import { useCalendarProps } from './hooks/useCalendarProps';
8
- import { getDaysInPeriod, getWeekDays } from './utils';
8
+ import { calendarLayouts, getDaysInPeriod, getWeekDays } from './utils';
9
9
  import './Calendar.css';
10
10
  const b = block('calendar');
11
11
  export const CalendarBase = React.forwardRef(function Calendar(props, ref) {
@@ -16,7 +16,8 @@ export const CalendarBase = React.forwardRef(function Calendar(props, ref) {
16
16
  state.setFocused(true);
17
17
  },
18
18
  }));
19
- return (_jsxs("div", Object.assign({}, calendarProps, { className: b({ size: props.size }), children: [_jsxs("div", { className: b('header'), children: [_jsx(Button, Object.assign({}, modeButtonProps, { view: "flat", size: props.size, children: state.mode === 'years' ? (_jsx("span", { className: b('mode-label', b(`years-label`)), children: modeButtonProps.children }, "label")) : ([
19
+ return (_jsxs("div", Object.assign({}, calendarProps, { className: b({ size: props.size }), children: [_jsxs("div", { className: b('header'), children: [_jsx(Button, Object.assign({}, modeButtonProps, { view: "flat", size: props.size, children: state.availableModes.indexOf(state.mode) + 1 ===
20
+ state.availableModes.length ? (_jsx("span", { className: b('mode-label', b(`years-label`)), children: modeButtonProps.children }, "label")) : ([
20
21
  _jsx("span", { className: b('mode-label'), children: modeButtonProps.children }, "label"),
21
22
  _jsx(Button.Icon, { children: _jsx(ArrowToggle, { direction: "bottom" }) }, "icon"),
22
23
  ]) })), _jsxs("div", { className: b('controls'), children: [_jsx(Button, Object.assign({}, previousButtonProps, { view: "flat", size: props.size, children: _jsx(Button.Icon, { children: _jsx(ArrowToggle, { direction: "left" }) }) })), _jsx(Button, Object.assign({}, nextButtonProps, { view: "flat", size: props.size, children: _jsx(Button.Icon, { children: _jsx(ArrowToggle, { direction: "right" }) }) }))] })] }), _jsx(CalendarGrid, { state: state })] })));
@@ -27,8 +28,7 @@ function CalendarGrid({ state }) {
27
28
  const startDateChanged = !state.startDate.isSame(prevState.startDate, 'days');
28
29
  let animation;
29
30
  if (modeChanged) {
30
- if ((state.mode === 'days' && prevState.mode === 'months') ||
31
- (state.mode === 'months' && prevState.mode === 'years')) {
31
+ if (calendarLayouts.indexOf(prevState.mode) > calendarLayouts.indexOf(state.mode)) {
32
32
  animation = 'zoom-out';
33
33
  }
34
34
  else {
@@ -59,13 +59,13 @@ function Weekdays({ state }) {
59
59
  }
60
60
  function CalendarGridCells({ state }) {
61
61
  const rowsInPeriod = state.mode === 'days' ? 6 : 4;
62
- const columnsInRow = state.mode === 'days' ? 7 : 3;
62
+ const columnsInRow = state.mode === 'days' ? 7 : 3 + (state.mode === 'quarters' ? 1 : 0);
63
63
  const days = getDaysInPeriod(state.startDate, state.endDate, state.mode);
64
- return (_jsx("div", { className: b('grid-rowgroup', { mode: state.mode }), role: "rowgroup", children: [...new Array(rowsInPeriod).keys()].map((rowIndex) => (_jsx("div", { className: b('grid-row'), role: "row", children: days
65
- .slice(rowIndex * columnsInRow, (rowIndex + 1) * columnsInRow)
66
- .map((date) => {
67
- return _jsx(CalendarCell, { date: date, state: state }, date.unix());
68
- }) }, rowIndex))) }));
64
+ return (_jsx("div", { className: b('grid-rowgroup', { mode: state.mode }), role: "rowgroup", children: [...new Array(rowsInPeriod).keys()].map((rowIndex) => (_jsxs("div", { className: b('grid-row'), role: "row", children: [state.mode === 'quarters' ? (_jsx("span", { role: "rowheader", className: b('grid-rowgroup-header'), children: days[rowIndex * columnsInRow].format('YYYY') })) : null, days
65
+ .slice(rowIndex * columnsInRow, (rowIndex + 1) * columnsInRow)
66
+ .map((date) => {
67
+ return _jsx(CalendarCell, { date: date, state: state }, date.unix());
68
+ })] }, rowIndex))) }));
69
69
  }
70
70
  function CalendarCell({ date, state }) {
71
71
  const { cellProps, buttonProps, formattedDate, isDisabled, isSelected, isRangeSelection, isSelectionStart, isSelectionEnd, isOutsideCurrentRange, isUnavailable, isCurrent, isWeekend, } = useCalendarCellProps(date, state);
@@ -1,6 +1,6 @@
1
1
  import type { DateTime } from '@gravity-ui/date-utils';
2
2
  import type { InputBase, RangeValue } from '../../types';
3
- export type CalendarLayout = 'days' | 'months' | 'years';
3
+ export type CalendarLayout = 'days' | 'months' | 'quarters' | 'years';
4
4
  export interface CalendarStateOptionsBase extends InputBase {
5
5
  /** The minimum allowed date that a user may select. */
6
6
  minValue?: DateTime;
@@ -27,6 +27,8 @@ export interface CalendarStateOptionsBase extends InputBase {
27
27
  defaultMode?: CalendarLayout;
28
28
  /** Handler that is called when the mode changes */
29
29
  onUpdateMode?: (mode: CalendarLayout) => void;
30
+ /** Controls which modes to use */
31
+ modes?: Partial<Record<CalendarLayout, boolean>>;
30
32
  }
31
33
  interface CalendarStateBase {
32
34
  /** Whether the calendar is disabled. */
@@ -45,7 +47,7 @@ interface CalendarStateBase {
45
47
  /** Selects the currently focused date. */
46
48
  selectFocusedDate(): void;
47
49
  /** Selects the given date. */
48
- selectDate(date: DateTime): void;
50
+ selectDate(date: DateTime, force?: boolean): void;
49
51
  /** Moves focus to the next calendar date. */
50
52
  focusNextCell(): void;
51
53
  /** Moves focus to the previous calendar date. */
@@ -98,6 +100,7 @@ interface CalendarStateBase {
98
100
  readonly mode: CalendarLayout;
99
101
  /** Sets calendar layout */
100
102
  setMode(mode: CalendarLayout): void;
103
+ readonly availableModes: CalendarLayout[];
101
104
  readonly startDate: DateTime;
102
105
  readonly endDate: DateTime;
103
106
  }
@@ -21,16 +21,7 @@ export function useCalendarCellProps(date, state) {
21
21
  const isSelectable = !isDisabled && !isUnavailable;
22
22
  const isCurrent = dateTime().isSame(date, state.mode);
23
23
  const isWeekend = state.isWeekend(date);
24
- let label = '';
25
- if (state.mode === 'days') {
26
- label = `${date.format('dddd')}, ${date.format('LL')}`;
27
- }
28
- else if (state.mode === 'months') {
29
- label = `${date.format('MMMM YYYY')}`;
30
- }
31
- else if (state.mode === 'years') {
32
- label = `${date.format('YYYY')}`;
33
- }
24
+ const label = getDateLabel(date, state.mode);
34
25
  const cellProps = {
35
26
  role: 'gridcell',
36
27
  'aria-selected': isSelected ? 'true' : undefined,
@@ -45,12 +36,7 @@ export function useCalendarCellProps(date, state) {
45
36
  onClick: isSelectable
46
37
  ? () => {
47
38
  state.setFocusedDate(date);
48
- if (state.mode === 'days') {
49
- state.selectDate(date);
50
- }
51
- else {
52
- state.zoomIn();
53
- }
39
+ state.selectDate(date);
54
40
  }
55
41
  : undefined,
56
42
  onPointerEnter() {
@@ -71,6 +57,9 @@ export function useCalendarCellProps(date, state) {
71
57
  if (state.mode === 'months') {
72
58
  formattedDate = date.format('MMM');
73
59
  }
60
+ else if (state.mode === 'quarters') {
61
+ formattedDate = date.format('[Q]Q');
62
+ }
74
63
  else if (state.mode === 'years') {
75
64
  formattedDate = date.format('YYYY');
76
65
  }
@@ -89,3 +78,19 @@ export function useCalendarCellProps(date, state) {
89
78
  isWeekend,
90
79
  };
91
80
  }
81
+ function getDateLabel(date, mode) {
82
+ let label = '';
83
+ if (mode === 'days') {
84
+ label = `${date.format('dddd')}, ${date.format('LL')}`;
85
+ }
86
+ else if (mode === 'months') {
87
+ label = `${date.format('MMMM YYYY')}`;
88
+ }
89
+ else if (mode === 'quarters') {
90
+ label = `${date.format('[Q]Q YYYY')}`;
91
+ }
92
+ else if (mode === 'years') {
93
+ label = `${date.format('YYYY')}`;
94
+ }
95
+ return label;
96
+ }