@jetbrains/ring-ui 7.0.106 → 7.0.108

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 (38) hide show
  1. package/components/alert-service/alert-service.d.ts +2 -2
  2. package/components/alert-service/alert-service.js +2 -2
  3. package/components/button-group/button-group.js +14 -4
  4. package/components/date-picker/animate-date.d.ts +1 -0
  5. package/components/date-picker/animate-date.js +33 -0
  6. package/components/date-picker/consts.d.ts +21 -4
  7. package/components/date-picker/consts.js +16 -0
  8. package/components/date-picker/date-picker.css +27 -12
  9. package/components/date-picker/date-picker.d.ts +48 -1
  10. package/components/date-picker/date-picker.js +27 -1
  11. package/components/date-picker/date-popup.d.ts +3 -9
  12. package/components/date-picker/date-popup.js +27 -56
  13. package/components/date-picker/day.js +19 -15
  14. package/components/date-picker/month-names.js +28 -15
  15. package/components/date-picker/month-slider.d.ts +5 -20
  16. package/components/date-picker/month-slider.js +41 -43
  17. package/components/date-picker/month.d.ts +4 -0
  18. package/components/date-picker/month.js +34 -20
  19. package/components/date-picker/months.js +28 -81
  20. package/components/date-picker/scroll-arith.d.ts +35 -0
  21. package/components/date-picker/scroll-arith.js +65 -0
  22. package/components/date-picker/use-intersection-observer.d.ts +6 -0
  23. package/components/date-picker/use-intersection-observer.js +48 -0
  24. package/components/date-picker/use-scroll-behavior.d.ts +8 -0
  25. package/components/date-picker/use-scroll-behavior.js +94 -0
  26. package/components/date-picker/years.d.ts +1 -18
  27. package/components/date-picker/years.js +90 -70
  28. package/components/footer/footer.d.ts +1 -1
  29. package/components/global/dom.d.ts +1 -1
  30. package/components/global/dom.js +1 -1
  31. package/components/global/theme.js +2 -2
  32. package/components/heading/heading.d.ts +4 -4
  33. package/components/tabs/dumb-tabs.d.ts +1 -0
  34. package/components/tabs/dumb-tabs.js +2 -2
  35. package/components/util-stories.d.ts +1 -0
  36. package/components/util-stories.js +1 -0
  37. package/package.json +40 -39
  38. package/typings.d.ts +5 -0
@@ -12,8 +12,8 @@ export declare const DEFAULT_ALERT_TIMEOUT = 10000;
12
12
  export declare class AlertService {
13
13
  defaultTimeout: number;
14
14
  showingAlerts: AlertItem[];
15
- containerElement: HTMLDivElement;
16
- reactRoot: import("react-dom/client").Root;
15
+ containerElement: HTMLDivElement | undefined;
16
+ reactRoot: import("react-dom/client").Root | undefined;
17
17
  _getShowingAlerts(): AlertItem[];
18
18
  renderAlertContainer(alerts: readonly AlertItem[]): import("react").JSX.Element;
19
19
  /**
@@ -9,8 +9,8 @@ export class AlertService {
9
9
  defaultTimeout = DEFAULT_ALERT_TIMEOUT;
10
10
  // This alerts are stored in inverse order (last shown is first in array)
11
11
  showingAlerts = [];
12
- containerElement = document.createElement('div');
13
- reactRoot = createRoot(this.containerElement);
12
+ containerElement = typeof document !== 'undefined' ? document.createElement('div') : undefined;
13
+ reactRoot = this.containerElement && createRoot(this.containerElement);
14
14
  _getShowingAlerts() {
15
15
  return [...this.showingAlerts];
16
16
  }
@@ -5,6 +5,19 @@ import ControlLabel from '../control-label/control-label';
5
5
  import ControlHelp from '../control-help/control-help';
6
6
  import Caption from './caption';
7
7
  import styles from './button-group.css';
8
+ function allChildrenDisabled(children) {
9
+ if (!children || typeof children !== 'object')
10
+ return false;
11
+ if ('props' in children) {
12
+ const { props } = children;
13
+ if ('disabled' in props) {
14
+ return !!props.disabled;
15
+ }
16
+ return allChildrenDisabled(props.children);
17
+ }
18
+ const real = Children.toArray(children);
19
+ return real.length > 0 && real.every(allChildrenDisabled);
20
+ }
8
21
  /**
9
22
  * @name Button Group
10
23
  */
@@ -12,10 +25,7 @@ export default class ButtonGroup extends PureComponent {
12
25
  render() {
13
26
  const { className, split, 'data-test': dataTest, label, help, ...restProps } = this.props;
14
27
  const classes = classNames(split ? styles.split : styles.buttonGroup, className, {
15
- [styles.disabled]: Children.toArray(this.props.children).every(child => !!child &&
16
- typeof child === 'object' &&
17
- 'props' in child &&
18
- child.props.disabled),
28
+ [styles.disabled]: allChildrenDisabled(this.props.children),
19
29
  });
20
30
  return (<>
21
31
  {label && <ControlLabel>{label}</ControlLabel>}
@@ -0,0 +1 @@
1
+ export declare function animateDate(initial: number | Date, target: number | Date, onUpdate: (date: Date) => void, durationMs?: number): () => void;
@@ -0,0 +1,33 @@
1
+ import { dateAnimationDuration } from './consts';
2
+ // Animate via an ascending fragment of a sine wave
3
+ // eslint-disable-next-line prettier/prettier, no-magic-numbers
4
+ const xFrom = -0.5 * Math.PI / 2;
5
+ const xTo = Math.PI / 2;
6
+ const yFrom = Math.sin(xFrom);
7
+ const yTo = Math.sin(xTo);
8
+ export function animateDate(initial, target, onUpdate, durationMs = dateAnimationDuration) {
9
+ let requestId = null;
10
+ const startTime = performance.now();
11
+ function frame(now) {
12
+ const durationFraction = (now - startTime) / durationMs;
13
+ // eslint-disable-next-line no-magic-numbers
14
+ if (durationFraction >= 0.97) {
15
+ onUpdate(new Date(target));
16
+ requestId = null;
17
+ return;
18
+ }
19
+ const x = xFrom + (xTo - xFrom) * durationFraction;
20
+ const y = Math.sin(x);
21
+ const yFraction = (y - yFrom) / (yTo - yFrom);
22
+ const date = Number(initial) + (Number(target) - Number(initial)) * yFraction;
23
+ onUpdate(new Date(date));
24
+ requestId = requestAnimationFrame(frame);
25
+ }
26
+ requestId = requestAnimationFrame(frame);
27
+ return () => {
28
+ if (requestId != null) {
29
+ cancelAnimationFrame(requestId);
30
+ requestId = null;
31
+ }
32
+ };
33
+ }
@@ -55,11 +55,15 @@ export interface RangeSpecificPopupProps {
55
55
  range: true;
56
56
  onChange: (change: DatePickerChange) => void;
57
57
  }
58
+ export interface ScrollDate {
59
+ date: number | Date;
60
+ source: 'monthsScroll' | 'yearsScroll' | 'other';
61
+ }
58
62
  export interface DatePopupState {
59
63
  active: Field;
60
64
  text: string | null;
61
65
  hoverDate: Date | null;
62
- scrollDate: number | Date | null;
66
+ scrollDate: ScrollDate | null;
63
67
  }
64
68
  export interface DatePopupBaseProps {
65
69
  date?: Date | number | string | null | undefined;
@@ -87,14 +91,27 @@ export interface Dates {
87
91
  }
88
92
  export interface CalendarProps extends Omit<DatePopupBaseProps, 'date' | 'from' | 'to' | 'time'>, Dates {
89
93
  activeDate: Date | null;
90
- scrollDate: number | Date;
94
+ scrollDate: ScrollDate;
91
95
  currentRange: [Date, Date] | null;
92
96
  activeRange: [Date, Date] | null;
93
- onScroll: (to: number) => void;
94
- onScrollChange: (date: number) => void;
97
+ setScrollDate: (scrollDate: ScrollDate) => void;
95
98
  }
96
99
  export interface MonthsProps extends CalendarProps {
97
100
  onSelect: (date: Date) => void;
98
101
  onHover: (date: Date) => void;
99
102
  }
100
103
  export type Field = 'date' | 'time' | 'from' | 'to';
104
+ /**
105
+ * Safari on iPhone doesn't allow setting scrollTop while a scroll is in progress.
106
+ * If you do, the browser will overwrite it during the next scroll event.
107
+ * This behavior occurs both during inertia scrolling and when the user is actively scrolling with a finger.
108
+ *
109
+ * In this environment, we:
110
+ * 1) only re-render when the scroll position is within a few pixels of the edge, and
111
+ * 2) only act after scrolling has ended.
112
+ */
113
+ export declare const isSafariOnIPhone: boolean;
114
+ export declare const scrollerReRenderDelayIPhone = 100;
115
+ export declare const calendarSyncOnYearScrollUpdateDelay: number;
116
+ export declare const dateAnimationDuration = 150;
117
+ export declare const yearsAnimationDuration = 200;
@@ -1,4 +1,5 @@
1
1
  import { add } from 'date-fns/add';
2
+ import sniffer from '../global/sniffer';
2
3
  const unit = 8; // px;
3
4
  const units = {
4
5
  unit,
@@ -48,3 +49,18 @@ export function getDayNumInWeek(locale, day) {
48
49
  const weekDays = shiftWeekArray(Object.values(weekdays), getWeekStartsOn(locale));
49
50
  return weekDays.indexOf(day);
50
51
  }
52
+ /**
53
+ * Safari on iPhone doesn't allow setting scrollTop while a scroll is in progress.
54
+ * If you do, the browser will overwrite it during the next scroll event.
55
+ * This behavior occurs both during inertia scrolling and when the user is actively scrolling with a finger.
56
+ *
57
+ * In this environment, we:
58
+ * 1) only re-render when the scroll position is within a few pixels of the edge, and
59
+ * 2) only act after scrolling has ended.
60
+ */
61
+ export const isSafariOnIPhone = sniffer.browser.name === 'safari' && sniffer.device.name === 'iphone';
62
+ export const scrollerReRenderDelayIPhone = 100;
63
+ // eslint-disable-next-line no-magic-numbers
64
+ export const calendarSyncOnYearScrollUpdateDelay = isSafariOnIPhone ? 130 : 100;
65
+ export const dateAnimationDuration = 150;
66
+ export const yearsAnimationDuration = 200;
@@ -224,15 +224,20 @@
224
224
 
225
225
  .months.months {
226
226
  position: absolute;
227
+
227
228
  top: 0;
228
- right: var(--ring-date-picker-year-width);
229
229
  bottom: 0;
230
230
  left: 0;
231
- }
232
231
 
233
- .days {
234
- position: relative;
235
- left: 0;
232
+ overflow-x: hidden;
233
+ overflow-y: scroll;
234
+ overscroll-behavior-y: none;
235
+
236
+ scrollbar-width: none;
237
+
238
+ &::-webkit-scrollbar {
239
+ display: none;
240
+ }
236
241
  }
237
242
 
238
243
  .month.month {
@@ -497,13 +502,13 @@
497
502
  .monthNames {
498
503
  position: absolute;
499
504
  top: 0;
500
- right: 0;
501
- bottom: 0;
505
+ right: var(--ring-date-picker-year-width);
502
506
 
503
- width: calc(var(--ring-unit) * 6);
507
+ width: calc(var(--ring-unit) * 6 - 1px);
508
+ margin-right: 1px;
504
509
 
505
510
  background-color: var(--ring-content-background-color);
506
- box-shadow: -1px 0 var(--ring-line-color);
511
+ box-shadow: -1px 0 var(--ring-line-color), 1px 0 var(--ring-line-color);
507
512
  }
508
513
 
509
514
  .monthName {
@@ -512,8 +517,6 @@
512
517
  }
513
518
 
514
519
  .monthName.monthName {
515
- position: relative;
516
-
517
520
  width: 100%;
518
521
 
519
522
  height: var(--ring-date-picker-cell-size);
@@ -534,7 +537,7 @@
534
537
  right: 0;
535
538
  left: -1px;
536
539
 
537
- width: calc(100% + 1px);
540
+ width: calc(100% + 2px);
538
541
 
539
542
  height: calc(var(--ring-unit) * 6);
540
543
 
@@ -542,6 +545,8 @@
542
545
 
543
546
  opacity: 0.17;
544
547
  background-color: var(--ring-main-color);
548
+
549
+ touch-action: none;
545
550
  }
546
551
 
547
552
  .monthSlider:hover {
@@ -568,12 +573,22 @@
568
573
  top: 0;
569
574
  right: 0;
570
575
 
576
+ overflow-y: scroll;
577
+
571
578
  width: var(--ring-date-picker-year-width);
579
+ height: 100%;
572
580
 
573
581
  background-color: var(--ring-content-background-color);
574
582
  box-shadow: -1px 0 var(--ring-line-color);
575
583
 
576
584
  font-size: var(--ring-font-size-smaller);
585
+ overscroll-behavior-y: none;
586
+
587
+ scrollbar-width: none;
588
+
589
+ &::-webkit-scrollbar {
590
+ display: none;
591
+ }
577
592
  }
578
593
 
579
594
  .year {
@@ -12,11 +12,29 @@ export interface DatePickerTranslations extends Partial<DateInputTranslations> {
12
12
  setPeriod: string;
13
13
  }
14
14
  export type DatePickerProps = Omit<DatePopupProps, 'translations' | 'parseDateInput' | 'onComplete' | 'hidden'> & {
15
+ /**
16
+ * Class name added to the root element of the control that activates the popup.
17
+ */
15
18
  className: string;
19
+ /**
20
+ * Adds a "Clear" button to the popup, which resets `date` (or `from` and `to`) to `null` when clicked.
21
+ */
16
22
  clear: boolean;
23
+ /**
24
+ * Displays the popup trigger as text, similar to a link, instead of a button.
25
+ */
17
26
  inline: boolean;
27
+ /**
28
+ * Class name added to the root element of the popup.
29
+ */
18
30
  popupClassName?: string | null | undefined;
31
+ /**
32
+ * Additional props for the Dropdown component. See **Components/Dropdown**.
33
+ */
19
34
  dropdownProps?: Partial<DropdownAttrs>;
35
+ /**
36
+ * Object with custom popup text values.
37
+ */
20
38
  translations?: DatePickerTranslations | null | undefined;
21
39
  displayMonthFormat: (date: Date, locale: Locale | undefined) => string;
22
40
  displayDayFormat: (date: Date, locale: Locale | undefined) => string;
@@ -27,11 +45,40 @@ export type DatePickerProps = Omit<DatePopupProps, 'translations' | 'parseDateIn
27
45
  rangePlaceholder?: string;
28
46
  disabled?: boolean | null | undefined;
29
47
  parseDateInput: (input: string | null | undefined) => Date | null;
48
+ /**
49
+ * Horizontal size of the popup trigger control. Only applies when `inline` is `false`.
50
+ */
30
51
  size?: Size;
31
52
  buttonAttributes?: Pick<ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label'>;
32
53
  };
33
54
  /**
34
- * @name Date Picker
55
+ * Date Picker lets users select a date, a date and time, or a date range.
56
+ * In the simplest mode, with a single unbounded date, the component needs only two props:
57
+ *
58
+ * - `date` to set the current date, and
59
+ * - `onChange` to be invoked when the user selects the date.
60
+ *
61
+ * To limit the selected date, use one or both of the following props:
62
+ *
63
+ * - `minDate`
64
+ * - `maxDate`
65
+ *
66
+ * To enable time input, use the `withTime` prop.
67
+ *
68
+ * To enable range selection, use the `range` prop. In this mode, use the following props
69
+ * instead of `date`:
70
+ *
71
+ * - `from`
72
+ * - `to`
73
+ *
74
+ * By default, the control that activates the popup is rendered as a button. If you want
75
+ * text instead, similar to a link, use the `inline` prop.
76
+ *
77
+ * The component supports internationalization. For example, in the `en-US` locale,
78
+ * the calendar starts on Sunday.
79
+ *
80
+ * There are also multiple ways to provide custom text and placeholders. See the props
81
+ * interfaces for details.
35
82
  */
36
83
  export default class DatePicker extends PureComponent<DatePickerProps> {
37
84
  static defaultProps: DatePickerProps;
@@ -30,7 +30,33 @@ const PopupComponent = ({ hidden = false, className, popupRef, onClear, datePopu
30
30
  <DatePopup onClear={onClear} {...datePopupProps} onComplete={onComplete}/>
31
31
  </Popup>);
32
32
  /**
33
- * @name Date Picker
33
+ * Date Picker lets users select a date, a date and time, or a date range.
34
+ * In the simplest mode, with a single unbounded date, the component needs only two props:
35
+ *
36
+ * - `date` to set the current date, and
37
+ * - `onChange` to be invoked when the user selects the date.
38
+ *
39
+ * To limit the selected date, use one or both of the following props:
40
+ *
41
+ * - `minDate`
42
+ * - `maxDate`
43
+ *
44
+ * To enable time input, use the `withTime` prop.
45
+ *
46
+ * To enable range selection, use the `range` prop. In this mode, use the following props
47
+ * instead of `date`:
48
+ *
49
+ * - `from`
50
+ * - `to`
51
+ *
52
+ * By default, the control that activates the popup is rendered as a button. If you want
53
+ * text instead, similar to a link, use the `inline` prop.
54
+ *
55
+ * The component supports internationalization. For example, in the `en-US` locale,
56
+ * the calendar starts on Sunday.
57
+ *
58
+ * There are also multiple ways to provide custom text and placeholders. See the props
59
+ * interfaces for details.
34
60
  */
35
61
  export default class DatePicker extends PureComponent {
36
62
  static defaultProps = {
@@ -1,34 +1,28 @@
1
1
  import { Component } from 'react';
2
2
  import * as React from 'react';
3
- import { type DatePickerChange, type DatePopupBaseProps, type DateSpecificPopupProps, type DatePopupState, type RangeSpecificPopupProps, type TimeSpecificPopupProps, type Field } from './consts';
3
+ import { type DatePickerChange, type DatePopupBaseProps, type DateSpecificPopupProps, type DatePopupState, type RangeSpecificPopupProps, type TimeSpecificPopupProps, type Field, type ScrollDate } from './consts';
4
4
  export type DatePopupProps = DatePopupBaseProps & (DateSpecificPopupProps | TimeSpecificPopupProps | RangeSpecificPopupProps);
5
5
  export default class DatePopup extends Component<DatePopupProps, DatePopupState> {
6
- static sameDay(next: Date | number | null, prev: Date | number | null): boolean;
7
6
  static defaultProps: {
8
7
  onChange(): void;
9
8
  };
10
9
  constructor(props: DatePopupProps);
11
- componentDidMount(): void;
12
10
  componentDidUpdate(prevProps: DatePopupBaseProps, prevState: DatePopupState): void;
13
11
  componentWillUnmount(): void;
14
- private _scrollDate?;
15
- private _scrollTS?;
12
+ private animationCleanup;
16
13
  isInTimeMode: () => boolean;
17
14
  componentRef: React.RefObject<HTMLDivElement | null>;
18
- handleWheel: (e: WheelEvent) => void;
19
15
  parse(text: string | null | undefined, type: 'time'): string;
20
16
  parse(text: Date | number | string | null | undefined, type?: 'date' | 'from' | 'to'): Date;
21
17
  select(changes: DatePickerChange): void;
22
18
  confirm(name: Field): void;
23
19
  isValidDate: (parsedText: Date) => boolean;
24
- scheduleScroll: () => void;
25
- scrollTo: (scrollDate: number) => void;
26
20
  hoverHandler: (hoverDate: Date) => void;
27
21
  handleActivate: (arg: Field) => () => void;
28
22
  handleInput: (text: string, name: Field) => void;
29
23
  handleConfirm: (arg: Field) => () => void;
30
24
  selectHandler: (date: Date) => void;
31
- handleScroll: (scrollDate: number) => void;
25
+ setScrollDate: (scrollDate: ScrollDate) => void;
32
26
  onClear: (e: React.MouseEvent<HTMLButtonElement>) => void;
33
27
  render(): React.JSX.Element;
34
28
  }
@@ -1,9 +1,7 @@
1
- /* eslint-disable max-lines */
2
1
  import { Component } from 'react';
3
2
  import * as React from 'react';
4
3
  import { isAfter } from 'date-fns/isAfter';
5
4
  import { isBefore } from 'date-fns/isBefore';
6
- import { isSameDay } from 'date-fns/isSameDay';
7
5
  import { startOfDay } from 'date-fns/startOfDay';
8
6
  import { set } from 'date-fns';
9
7
  import memoize from '../global/memoize';
@@ -12,15 +10,10 @@ import Months from './months';
12
10
  import Years from './years';
13
11
  import Weekdays from './weekdays';
14
12
  import { parseTime, } from './consts';
13
+ import { animateDate } from './animate-date';
14
+ import MonthNames from './month-names';
15
15
  import styles from './date-picker.css';
16
- const scrollExpDelay = 10;
17
16
  export default class DatePopup extends Component {
18
- static sameDay(next, prev) {
19
- if (next && prev) {
20
- return isSameDay(next, prev);
21
- }
22
- return next === prev;
23
- }
24
17
  static defaultProps = {
25
18
  onChange() { },
26
19
  };
@@ -35,21 +28,16 @@ export default class DatePopup extends Component {
35
28
  if (!range) {
36
29
  const parsedDate = this.parse(props.date, 'date');
37
30
  const active = withTime && parsedDate && !props.time ? 'time' : 'date';
38
- this.state = { ...defaultState, active, scrollDate: parsedDate };
31
+ this.state = { ...defaultState, active, scrollDate: { date: parsedDate, source: 'other' } };
39
32
  }
40
33
  else {
41
34
  this.state = {
42
35
  ...defaultState,
43
36
  active: props.from && !props.to ? 'to' : 'from',
44
- scrollDate: this.parse(props.from, 'from'),
37
+ scrollDate: { date: this.parse(props.from, 'from'), source: 'other' },
45
38
  };
46
39
  }
47
40
  }
48
- componentDidMount() {
49
- if (this.componentRef.current) {
50
- this.componentRef.current.addEventListener('wheel', this.handleWheel);
51
- }
52
- }
53
41
  componentDidUpdate(prevProps, prevState) {
54
42
  if (this.state.active !== prevState.active) {
55
43
  if (this.state.text && prevState.active) {
@@ -59,19 +47,11 @@ export default class DatePopup extends Component {
59
47
  }
60
48
  }
61
49
  componentWillUnmount() {
62
- if (this.componentRef.current) {
63
- this.componentRef.current.removeEventListener('wheel', this.handleWheel);
64
- }
50
+ this.animationCleanup?.();
65
51
  }
66
- _scrollDate;
67
- _scrollTS;
52
+ animationCleanup = null;
68
53
  isInTimeMode = () => (!this.props.range && this.props.withTime) || false;
69
54
  componentRef = React.createRef();
70
- handleWheel = (e) => {
71
- if (e.cancelable) {
72
- e.preventDefault();
73
- }
74
- };
75
55
  parse(text, type) {
76
56
  if (type === 'time') {
77
57
  return parseTime(String(text));
@@ -176,38 +156,27 @@ export default class DatePopup extends Component {
176
156
  }
177
157
  return false;
178
158
  };
179
- scheduleScroll = () => {
180
- const current = (this.state.scrollDate && this.parse(this.state.scrollDate, 'date')) ||
181
- this.parse(this.props[this.state.active], 'date') ||
182
- new Date();
183
- const goal = this._scrollDate;
184
- if (!current || !goal || DatePopup.sameDay(goal, current)) {
185
- this._scrollDate = null;
186
- this._scrollTS = null;
187
- return;
188
- }
189
- if (this._scrollTS) {
190
- const diff = goal - Number(current);
191
- const dt = Date.now() - this._scrollTS;
192
- const next = goal - diff * Math.E ** (-dt / scrollExpDelay);
193
- this.setState({ scrollDate: next });
194
- }
195
- this._scrollTS = Date.now();
196
- window.requestAnimationFrame(this.scheduleScroll);
197
- };
198
- scrollTo = (scrollDate) => {
199
- this._scrollDate = scrollDate;
200
- if (!this._scrollTS) {
201
- this.scheduleScroll();
202
- }
203
- };
204
159
  hoverHandler = (hoverDate) => this.setState({ hoverDate });
205
160
  handleActivate = memoize((name) => () => this.setState({ active: name }));
206
161
  handleInput = (text, name) => {
207
162
  if (name !== 'time') {
208
163
  const parsed = this.parse(text, name);
209
164
  if (this.isValidDate(parsed)) {
210
- this.scrollTo(Number(parsed));
165
+ this.animationCleanup?.();
166
+ const currentScrollDate = this.state.scrollDate?.date;
167
+ if (currentScrollDate != null) {
168
+ this.animationCleanup = animateDate(currentScrollDate, parsed, date => {
169
+ this.setState({
170
+ scrollDate: {
171
+ date,
172
+ source: 'other',
173
+ },
174
+ });
175
+ });
176
+ }
177
+ else {
178
+ this.setScrollDate({ date: parsed, source: 'other' });
179
+ }
211
180
  }
212
181
  }
213
182
  this.setState({
@@ -226,7 +195,9 @@ export default class DatePopup extends Component {
226
195
  this.select({ [this.state.active]: date });
227
196
  }
228
197
  };
229
- handleScroll = (scrollDate) => this.setState({ scrollDate });
198
+ setScrollDate = (scrollDate) => {
199
+ this.setState({ scrollDate });
200
+ };
230
201
  onClear = (e) => {
231
202
  let changes;
232
203
  if (this.props.range) {
@@ -285,7 +256,7 @@ export default class DatePopup extends Component {
285
256
  break;
286
257
  }
287
258
  }
288
- const scrollDate = this.state.scrollDate || new Date();
259
+ const scrollDate = this.state.scrollDate || { date: new Date(), source: 'other' };
289
260
  const calendarProps = {
290
261
  ...restProps,
291
262
  ...dates,
@@ -293,8 +264,7 @@ export default class DatePopup extends Component {
293
264
  activeDate,
294
265
  currentRange,
295
266
  activeRange,
296
- onScroll: this.handleScroll,
297
- onScrollChange: this.scrollTo,
267
+ setScrollDate: this.setScrollDate,
298
268
  };
299
269
  const clearable = Boolean(this.props.onClear);
300
270
  return (<div className={styles.datePopup} data-test='ring-date-popup' ref={this.componentRef}>
@@ -312,6 +282,7 @@ export default class DatePopup extends Component {
312
282
  <Weekdays locale={locale}/>
313
283
  <div className={styles.calendar}>
314
284
  <Months {...calendarProps} onHover={this.hoverHandler} onSelect={this.selectHandler} locale={locale}/>
285
+ <MonthNames {...calendarProps} onHover={this.hoverHandler} onSelect={this.selectHandler}/>
315
286
  <Years {...calendarProps}/>
316
287
  </div>
317
288
 
@@ -53,23 +53,27 @@ export default class Day extends Component {
53
53
  const spreadRange = makeSpreadRange(currentRange);
54
54
  const disabled = this.isDisabled(day);
55
55
  const activeSpreadRange = makeSpreadRange(activeRange);
56
+ const className = classNames(styles.day, dayInWeekClass, {
57
+ [styles.current]: ['date', 'from', 'to'].some(this.is),
58
+ [styles.active]: !disabled && this.is('activeDate'),
59
+ [styles.weekend]: [weekdays.SA, weekdays.SU].includes(getDay(day)),
60
+ [styles.empty]: empty,
61
+ [styles.from]: (currentRange && this.isDay(currentRange[0]) && !reverse) || (activeRange && this.isDay(activeRange[0])),
62
+ [styles.to]: (currentRange && this.isDay(currentRange[1])) || (activeRange && this.isDay(activeRange[1])),
63
+ [styles.between]: this.inRange(currentRange),
64
+ [styles.activeBetween]: !disabled && this.inRange(activeRange),
65
+ [styles.first]: getDate(day) === 1,
66
+ [styles.spread]: this.inRange(spreadRange),
67
+ [styles.activeSpread]: !disabled && this.inRange(activeSpreadRange),
68
+ [styles.disabled]: disabled,
69
+ });
70
+ if (empty) {
71
+ return <div className={className}/>;
72
+ }
56
73
  return (
57
74
  // TODO make keyboard navigation actually work
58
- <button type='button' className={classNames(styles.day, dayInWeekClass, {
59
- [styles.current]: ['date', 'from', 'to'].some(this.is),
60
- [styles.active]: !disabled && this.is('activeDate'),
61
- [styles.weekend]: [weekdays.SA, weekdays.SU].includes(getDay(day)),
62
- [styles.empty]: empty,
63
- [styles.from]: (currentRange && this.isDay(currentRange[0]) && !reverse) || (activeRange && this.isDay(activeRange[0])),
64
- [styles.to]: (currentRange && this.isDay(currentRange[1])) || (activeRange && this.isDay(activeRange[1])),
65
- [styles.between]: this.inRange(currentRange),
66
- [styles.activeBetween]: !disabled && this.inRange(activeRange),
67
- [styles.first]: getDate(day) === 1,
68
- [styles.spread]: this.inRange(spreadRange),
69
- [styles.activeSpread]: !disabled && this.inRange(activeSpreadRange),
70
- [styles.disabled]: disabled,
71
- })} onClick={this.handleClick} onMouseOver={this.handleMouseOver} onFocus={this.handleMouseOver} onMouseOut={this.handleMouseOut} onBlur={this.handleMouseOut} disabled={disabled}>
72
- {empty || <span className={classNames({ [styles.today]: isToday(day) })}>{format(day, 'd')}</span>}
75
+ <button type='button' className={className} onClick={this.handleClick} onMouseOver={this.handleMouseOver} onFocus={this.handleMouseOver} onMouseOut={this.handleMouseOut} onBlur={this.handleMouseOut} disabled={disabled}>
76
+ <span className={classNames({ [styles.today]: isToday(day) })}>{format(day, 'd')}</span>
73
77
  </button>);
74
78
  }
75
79
  }