@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.
- package/components/alert-service/alert-service.d.ts +2 -2
- package/components/alert-service/alert-service.js +2 -2
- package/components/button-group/button-group.js +14 -4
- package/components/date-picker/animate-date.d.ts +1 -0
- package/components/date-picker/animate-date.js +33 -0
- package/components/date-picker/consts.d.ts +21 -4
- package/components/date-picker/consts.js +16 -0
- package/components/date-picker/date-picker.css +27 -12
- package/components/date-picker/date-picker.d.ts +48 -1
- package/components/date-picker/date-picker.js +27 -1
- package/components/date-picker/date-popup.d.ts +3 -9
- package/components/date-picker/date-popup.js +27 -56
- package/components/date-picker/day.js +19 -15
- package/components/date-picker/month-names.js +28 -15
- package/components/date-picker/month-slider.d.ts +5 -20
- package/components/date-picker/month-slider.js +41 -43
- package/components/date-picker/month.d.ts +4 -0
- package/components/date-picker/month.js +34 -20
- package/components/date-picker/months.js +28 -81
- package/components/date-picker/scroll-arith.d.ts +35 -0
- package/components/date-picker/scroll-arith.js +65 -0
- package/components/date-picker/use-intersection-observer.d.ts +6 -0
- package/components/date-picker/use-intersection-observer.js +48 -0
- package/components/date-picker/use-scroll-behavior.d.ts +8 -0
- package/components/date-picker/use-scroll-behavior.js +94 -0
- package/components/date-picker/years.d.ts +1 -18
- package/components/date-picker/years.js +90 -70
- package/components/footer/footer.d.ts +1 -1
- package/components/global/dom.d.ts +1 -1
- package/components/global/dom.js +1 -1
- package/components/global/theme.js +2 -2
- package/components/heading/heading.d.ts +4 -4
- package/components/tabs/dumb-tabs.d.ts +1 -0
- package/components/tabs/dumb-tabs.js +2 -2
- package/components/util-stories.d.ts +1 -0
- package/components/util-stories.js +1 -0
- package/package.json +40 -39
- 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]:
|
|
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:
|
|
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:
|
|
94
|
+
scrollDate: ScrollDate;
|
|
91
95
|
currentRange: [Date, Date] | null;
|
|
92
96
|
activeRange: [Date, Date] | null;
|
|
93
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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:
|
|
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% +
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
this.componentRef.current.removeEventListener('wheel', this.handleWheel);
|
|
64
|
-
}
|
|
50
|
+
this.animationCleanup?.();
|
|
65
51
|
}
|
|
66
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
59
|
-
|
|
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
|
}
|