@docsvision/material-ui-pickers 3.2.0-beta.24 → 3.2.0-beta.26
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/.size-snapshot.json +11 -11
- package/9db1cba1a61cb58db53f38425bbd1f95.7z +0 -0
- package/build/DatePicker/DatePicker.d.ts +2 -2
- package/build/DatePicker/DatePicker.js +1 -1
- package/build/DateTimePicker/DateTimePicker.js +1 -1
- package/build/Picker/Picker.js +1 -1
- package/build/TimePicker/TimePicker.js +1 -1
- package/build/_helpers/text-field-helper.d.ts +1 -1
- package/build/_shared/KeyboardDateInput.js +1 -1
- package/build/_shared/ModalDialog.d.ts +2 -2
- package/build/_shared/PureDateInput.js +1 -1
- package/build/_shared/ToolbarButton.d.ts +2 -2
- package/build/_shared/hooks/useKeyboardPickerState.js +2 -2
- package/build/constants/prop-types.js +2 -2
- package/build/dist/material-ui-pickers.esm.js +12 -12
- package/build/dist/material-ui-pickers.esm.js.map +1 -1
- package/build/dist/material-ui-pickers.js +62 -62
- package/build/dist/material-ui-pickers.js.map +1 -1
- package/build/dist/material-ui-pickers.umd.js +5790 -4511
- package/build/dist/material-ui-pickers.umd.min.js +1 -1
- package/build/package.json +1 -1
- package/build/views/Calendar/Calendar.js +2 -2
- package/build/views/Clock/Clock.d.ts +1 -1
- package/build/views/Clock/ClockPointer.d.ts +1 -1
- package/package.json +1 -1
- package/src/DatePicker/DatePicker.tsx +91 -0
- package/src/DatePicker/DatePickerToolbar.tsx +75 -0
- package/src/DatePicker/index.ts +7 -0
- package/src/DateTimePicker/DateTimePicker.tsx +72 -0
- package/src/DateTimePicker/DateTimePickerTabs.tsx +86 -0
- package/src/DateTimePicker/DateTimePickerToolbar.tsx +123 -0
- package/src/DateTimePicker/index.ts +7 -0
- package/src/MuiPickersUtilsProvider.tsx +39 -0
- package/src/Picker/Picker.tsx +275 -0
- package/src/Picker/WrappedKeyboardPicker.tsx +136 -0
- package/src/Picker/WrappedPurePicker.tsx +128 -0
- package/src/Rifm/Rifm.ts +213 -0
- package/src/TimePicker/TimePicker.tsx +67 -0
- package/src/TimePicker/TimePickerToolbar.tsx +180 -0
- package/src/TimePicker/index.tsx +7 -0
- package/src/__tests__/DatePicker/Calendar.test.tsx +51 -0
- package/src/__tests__/DatePicker/Month.test.tsx +36 -0
- package/src/__tests__/DatePicker/MonthSelection.test.tsx +37 -0
- package/src/__tests__/MuiPickersUtilsProvider.test.tsx +21 -0
- package/src/__tests__/TimePicker/Clock.test.tsx +68 -0
- package/src/__tests__/_helpers/date-utils.test.tsx +197 -0
- package/src/__tests__/_helpers/text-field-helper.test.tsx +34 -0
- package/src/__tests__/_shared/ModalDialog.test.tsx +109 -0
- package/src/__tests__/e2e/DatePicker.test.tsx +233 -0
- package/src/__tests__/e2e/DatePickerProps.test.tsx +18 -0
- package/src/__tests__/e2e/DatePickerRoot.test.tsx +154 -0
- package/src/__tests__/e2e/DateTimePicker.test.tsx +86 -0
- package/src/__tests__/e2e/DateTimePickerRoot.test.tsx +72 -0
- package/src/__tests__/e2e/KeyboardDatePicker.test.tsx +140 -0
- package/src/__tests__/e2e/KeyboardDateTimePicker.test.tsx +48 -0
- package/src/__tests__/e2e/Theme.test.tsx +30 -0
- package/src/__tests__/e2e/TimePicker.test.tsx +121 -0
- package/src/__tests__/e2e/commands.tsx +8 -0
- package/src/__tests__/setup.js +26 -0
- package/src/__tests__/test-utils.tsx +45 -0
- package/src/__tests__/tsconfig.json +5 -0
- package/src/_helpers/date-utils.ts +92 -0
- package/src/_helpers/text-field-helper.ts +239 -0
- package/src/_helpers/time-utils.ts +83 -0
- package/src/_helpers/utils.ts +8 -0
- package/src/_shared/KeyboardDateInput.tsx +161 -0
- package/src/_shared/ModalDialog.tsx +114 -0
- package/src/_shared/PickerToolbar.tsx +52 -0
- package/src/_shared/PureDateInput.tsx +45 -0
- package/src/_shared/ToolbarButton.tsx +63 -0
- package/src/_shared/ToolbarText.tsx +49 -0
- package/src/_shared/WithUtils.tsx +19 -0
- package/src/_shared/hooks/useIsLandscape.tsx +27 -0
- package/src/_shared/hooks/useKeyDown.ts +34 -0
- package/src/_shared/hooks/useKeyboardPickerState.ts +72 -0
- package/src/_shared/hooks/useOpenState.ts +22 -0
- package/src/_shared/hooks/usePickerState.ts +134 -0
- package/src/_shared/hooks/useUtils.ts +20 -0
- package/src/_shared/hooks/useViews.tsx +31 -0
- package/src/_shared/icons/ArrowLeftIcon.tsx +11 -0
- package/src/_shared/icons/ArrowRightIcon.tsx +11 -0
- package/src/_shared/icons/DateRangeIcon.tsx +11 -0
- package/src/_shared/icons/KeyboardIcon.tsx +11 -0
- package/src/_shared/icons/TimeIcon.tsx +12 -0
- package/src/constants/ClockType.ts +11 -0
- package/src/constants/dimensions.ts +5 -0
- package/src/constants/prop-types.ts +37 -0
- package/src/index.ts +48 -0
- package/src/typings/BasePicker.tsx +60 -0
- package/src/typings/date.ts +3 -0
- package/src/typings/extendMui.ts +10 -0
- package/src/typings/overrides.ts +44 -0
- package/src/views/Calendar/Calendar.tsx +361 -0
- package/src/views/Calendar/CalendarHeader.tsx +141 -0
- package/src/views/Calendar/Day.tsx +97 -0
- package/src/views/Calendar/DayWrapper.tsx +33 -0
- package/src/views/Calendar/SlideTransition.tsx +100 -0
- package/src/views/Clock/Clock.tsx +172 -0
- package/src/views/Clock/ClockNumber.tsx +91 -0
- package/src/views/Clock/ClockNumbers.tsx +76 -0
- package/src/views/Clock/ClockPointer.tsx +104 -0
- package/src/views/Clock/ClockView.tsx +105 -0
- package/src/views/Month/Month.tsx +76 -0
- package/src/views/Month/MonthView.tsx +94 -0
- package/src/views/Year/Year.tsx +77 -0
- package/src/views/Year/YearView.tsx +91 -0
- package/src/wrappers/InlineWrapper.tsx +80 -0
- package/src/wrappers/ModalWrapper.tsx +113 -0
- package/src/wrappers/StaticWrapper.tsx +22 -0
- package/src/wrappers/Wrapper.tsx +75 -0
- package/coverage/clover.xml +0 -601
- package/coverage/coverage-final.json +0 -22
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -79
- package/coverage/lcov-report/index.html +0 -201
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -170
- package/coverage/lcov-report/src/MuiPickersUtilsProvider.tsx.html +0 -186
- package/coverage/lcov-report/src/_helpers/date-utils.ts.html +0 -345
- package/coverage/lcov-report/src/_helpers/index.html +0 -123
- package/coverage/lcov-report/src/_helpers/time-utils.ts.html +0 -318
- package/coverage/lcov-report/src/_helpers/utils.ts.html +0 -93
- package/coverage/lcov-report/src/_shared/ModalDialog.tsx.html +0 -411
- package/coverage/lcov-report/src/_shared/WithUtils.tsx.html +0 -126
- package/coverage/lcov-report/src/_shared/hooks/index.html +0 -110
- package/coverage/lcov-report/src/_shared/hooks/useKeyDown.ts.html +0 -171
- package/coverage/lcov-report/src/_shared/hooks/useUtils.ts.html +0 -129
- package/coverage/lcov-report/src/_shared/icons/ArrowLeftIcon.tsx.html +0 -102
- package/coverage/lcov-report/src/_shared/icons/ArrowRightIcon.tsx.html +0 -102
- package/coverage/lcov-report/src/_shared/icons/index.html +0 -110
- package/coverage/lcov-report/src/_shared/index.html +0 -110
- package/coverage/lcov-report/src/constants/ClockType.ts.html +0 -102
- package/coverage/lcov-report/src/constants/dimensions.ts.html +0 -84
- package/coverage/lcov-report/src/constants/index.html +0 -110
- package/coverage/lcov-report/src/index.html +0 -97
- package/coverage/lcov-report/src/views/Calendar/Calendar.tsx.html +0 -1152
- package/coverage/lcov-report/src/views/Calendar/CalendarHeader.tsx.html +0 -492
- package/coverage/lcov-report/src/views/Calendar/Day.tsx.html +0 -360
- package/coverage/lcov-report/src/views/Calendar/DayWrapper.tsx.html +0 -168
- package/coverage/lcov-report/src/views/Calendar/SlideTransition.tsx.html +0 -369
- package/coverage/lcov-report/src/views/Calendar/index.html +0 -149
- package/coverage/lcov-report/src/views/Clock/Clock.tsx.html +0 -585
- package/coverage/lcov-report/src/views/Clock/ClockPointer.tsx.html +0 -381
- package/coverage/lcov-report/src/views/Clock/index.html +0 -110
- package/coverage/lcov-report/src/views/Month/Month.tsx.html +0 -297
- package/coverage/lcov-report/src/views/Month/MonthView.tsx.html +0 -351
- package/coverage/lcov-report/src/views/Month/index.html +0 -110
- package/coverage/lcov.info +0 -1094
package/src/Rifm/Rifm.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
value: string;
|
|
5
|
+
maskChar: string;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
format: (str: string, oldValue: string, currentCaretPosition?: number) => string;
|
|
8
|
+
mask?: boolean;
|
|
9
|
+
replace?: (value: string) => string;
|
|
10
|
+
accept?: RegExp;
|
|
11
|
+
letterPosition?: number;
|
|
12
|
+
children: (value: {
|
|
13
|
+
value: string;
|
|
14
|
+
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>;
|
|
15
|
+
}) => any;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Rifm = (props: Props) => {
|
|
19
|
+
const [, refresh] = React.useReducer(c => c + 1, 0);
|
|
20
|
+
const valueRef = React.useRef<any>(null);
|
|
21
|
+
const { replace, maskChar } = props;
|
|
22
|
+
const [previousValue, setPreviousValue] = React.useState({ value: '' });
|
|
23
|
+
const userValue = replace
|
|
24
|
+
? replace(props.format(props.value, previousValue.value))
|
|
25
|
+
: props.format(props.value, previousValue.value);
|
|
26
|
+
|
|
27
|
+
const currentPreviousValue = previousValue.value;
|
|
28
|
+
|
|
29
|
+
// state of delete button see comments below about inputType support
|
|
30
|
+
const isDeleleteButtonDownRef = React.useRef(false);
|
|
31
|
+
|
|
32
|
+
const onChange = (evt: any) => {
|
|
33
|
+
const eventValue = evt.target.value;
|
|
34
|
+
const selectionStart = evt.target.selectionStart;
|
|
35
|
+
|
|
36
|
+
valueRef.current = [
|
|
37
|
+
eventValue, // eventValue
|
|
38
|
+
evt.target, // input
|
|
39
|
+
eventValue.length > userValue.length, // isSizeIncreaseOperation
|
|
40
|
+
isDeleleteButtonDownRef.current, // isDeleleteButtonDown
|
|
41
|
+
selectionStart, // selectionStart
|
|
42
|
+
userValue === props.format(eventValue, currentPreviousValue, selectionStart), // isNoOperation
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// The main trick is to update underlying input with non formatted value (= eventValue)
|
|
46
|
+
// that allows us to calculate right cursor position after formatting (see getCursorPosition)
|
|
47
|
+
// then we format new value and call props.onChange with masked/formatted value
|
|
48
|
+
// and finally we are able to set cursor position into right place
|
|
49
|
+
refresh(null);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// React prints warn on server in non production mode about useLayoutEffect usage
|
|
53
|
+
// in both cases it's noop
|
|
54
|
+
if (typeof window !== 'undefined') {
|
|
55
|
+
React.useLayoutEffect(() => {
|
|
56
|
+
if (valueRef.current == null) return;
|
|
57
|
+
|
|
58
|
+
let [
|
|
59
|
+
eventValue,
|
|
60
|
+
input,
|
|
61
|
+
isSizeIncreaseOperation,
|
|
62
|
+
isDeleleteButtonDown,
|
|
63
|
+
selectionStart,
|
|
64
|
+
// No operation means that value itself hasn't been changed, BTW cursor, selection etc can be changed
|
|
65
|
+
isNoOperation,
|
|
66
|
+
] = valueRef.current;
|
|
67
|
+
valueRef.current = null;
|
|
68
|
+
|
|
69
|
+
// this usually occurs on deleting special symbols like ' here 123'123.00
|
|
70
|
+
// in case of isDeleleteButtonDown cursor should move differently vs backspace
|
|
71
|
+
const deleteWasNoOp = isDeleleteButtonDown && isNoOperation;
|
|
72
|
+
|
|
73
|
+
// Create string from only accepted symbols
|
|
74
|
+
const clean = (str: string) => (str.match(props.accept || /\d|\w/g) || []).join('');
|
|
75
|
+
|
|
76
|
+
const valueBeforeSelectionStart = clean(eventValue.substr(0, input.selectionStart));
|
|
77
|
+
|
|
78
|
+
// trying to find cursor position in formatted value having knowledge about valueBeforeSelectionStart
|
|
79
|
+
// This works because we assume that format doesn't change the order of accepted symbols.
|
|
80
|
+
// Imagine we have formatter which adds ' symbol between numbers, and by default we refuse all non numeric symbols
|
|
81
|
+
// for example we had input = 1'2|'4 (| means cursor position) then user entered '3' symbol
|
|
82
|
+
// inputValue = 1'23'|4 so valueBeforeSelectionStart = 123 and formatted value = 1'2'3'4
|
|
83
|
+
// calling getCursorPosition("1'2'3'4") will give us position after 3, 1'2'3|'4
|
|
84
|
+
// so for formatting just this function to determine cursor position after formatting is enough
|
|
85
|
+
// with masking we need to do some additional checks see `mask` below
|
|
86
|
+
const getCursorPosition = (val: any) => {
|
|
87
|
+
let start = 0;
|
|
88
|
+
let cleanPos = 0;
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i !== valueBeforeSelectionStart.length; ++i) {
|
|
91
|
+
let newPos = val.indexOf(valueBeforeSelectionStart[i], start) + 1;
|
|
92
|
+
|
|
93
|
+
let newCleanPos = clean(val).indexOf(valueBeforeSelectionStart[i], cleanPos) + 1;
|
|
94
|
+
|
|
95
|
+
// this skips position change if accepted symbols order was broken
|
|
96
|
+
// For example fixes edge case with fixed point numbers:
|
|
97
|
+
// You have '0|.00', then press 1, it becomes 01|.00 and after format 1.00, this breaks our assumption
|
|
98
|
+
// that order of accepted symbols is not changed after format,
|
|
99
|
+
// so here we don't update start position if other accepted symbols was inbetween current and new position
|
|
100
|
+
if (newCleanPos - cleanPos > 1) {
|
|
101
|
+
newPos = start;
|
|
102
|
+
newCleanPos = cleanPos;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
cleanPos = Math.max(newCleanPos, cleanPos);
|
|
106
|
+
start = Math.max(start, newPos);
|
|
107
|
+
}
|
|
108
|
+
return start;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//let start = getCursorPosition(eventValue);
|
|
112
|
+
|
|
113
|
+
// // Masking part, for masks if size of mask is above some value
|
|
114
|
+
// // we need to replace symbols instead of do nothing as like in format
|
|
115
|
+
// if (props.mask === true && isSizeIncreaseOperation && !isNoOperation) {
|
|
116
|
+
// const c = clean(eventValue.substr(start))[0];
|
|
117
|
+
// start = eventValue.indexOf(c, start);
|
|
118
|
+
|
|
119
|
+
// eventValue = `${eventValue.substr(0, start)}${eventValue.substr(
|
|
120
|
+
// start + 1
|
|
121
|
+
// )}`;
|
|
122
|
+
// }
|
|
123
|
+
|
|
124
|
+
const formattedValue = props.format(eventValue, currentPreviousValue, selectionStart);
|
|
125
|
+
let replacedValue = replace ? replace(formattedValue) : formattedValue;
|
|
126
|
+
|
|
127
|
+
let start = getCursorPosition(formattedValue);
|
|
128
|
+
let inputCharPosition = start - 1;
|
|
129
|
+
if (inputCharPosition > -1) {
|
|
130
|
+
if (
|
|
131
|
+
props.letterPosition &&
|
|
132
|
+
props.letterPosition > -1 &&
|
|
133
|
+
replacedValue[inputCharPosition].match(/[a-zA-Z]/) &&
|
|
134
|
+
(inputCharPosition < props.letterPosition || inputCharPosition > props.letterPosition + 1)
|
|
135
|
+
) {
|
|
136
|
+
replacedValue = currentPreviousValue;
|
|
137
|
+
} else if (
|
|
138
|
+
props.letterPosition &&
|
|
139
|
+
props.letterPosition > -1 &&
|
|
140
|
+
replacedValue[inputCharPosition].match(/\d/) &&
|
|
141
|
+
(inputCharPosition >= props.letterPosition &&
|
|
142
|
+
inputCharPosition <= props.letterPosition + 1)
|
|
143
|
+
) {
|
|
144
|
+
replacedValue = currentPreviousValue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (userValue === replacedValue) {
|
|
149
|
+
// if nothing changed for formatted value, just refresh so userValue will be used at render
|
|
150
|
+
refresh(null);
|
|
151
|
+
} else {
|
|
152
|
+
props.onChange(replacedValue);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return () => {
|
|
156
|
+
// Visually improves working with masked values,
|
|
157
|
+
// like cursor jumping over refused symbols
|
|
158
|
+
// as an example date mask: was "5|1-24-3" then user pressed "6"
|
|
159
|
+
// it becomes "56-|12-43" with this code, and "56|-12-43" without
|
|
160
|
+
if (
|
|
161
|
+
props.mask != null &&
|
|
162
|
+
(isSizeIncreaseOperation || (isDeleleteButtonDown && !deleteWasNoOp))
|
|
163
|
+
) {
|
|
164
|
+
while (
|
|
165
|
+
formattedValue[start] &&
|
|
166
|
+
clean(formattedValue[start]) === '' &&
|
|
167
|
+
formattedValue[start] != maskChar
|
|
168
|
+
) {
|
|
169
|
+
start += 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//input.selectionStart = input.selectionEnd = start + (deleteWasNoOp ? 1 : 0);
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
React.useEffect(() => {
|
|
179
|
+
// until https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported
|
|
180
|
+
// by all major browsers (now supported by: +chrome, +safari, ?edge, !firefox)
|
|
181
|
+
// there is no way I found to distinguish in onChange
|
|
182
|
+
// backspace or delete was called in some situations
|
|
183
|
+
// firefox track https://bugzilla.mozilla.org/show_bug.cgi?id=1447239
|
|
184
|
+
const handleKeyDown = (evt: KeyboardEvent) => {
|
|
185
|
+
if (evt.code === 'Delete') {
|
|
186
|
+
isDeleleteButtonDownRef.current = true;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleKeyUp = (evt: KeyboardEvent) => {
|
|
191
|
+
if (evt.code === 'Delete') {
|
|
192
|
+
isDeleleteButtonDownRef.current = false;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
197
|
+
document.addEventListener('keyup', handleKeyUp);
|
|
198
|
+
|
|
199
|
+
return () => {
|
|
200
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
201
|
+
document.removeEventListener('keyup', handleKeyUp);
|
|
202
|
+
};
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
let value = valueRef.current != null ? valueRef.current[0] : userValue;
|
|
206
|
+
// value = props.format(value, currentPreviousValue);
|
|
207
|
+
previousValue.value = value;
|
|
208
|
+
|
|
209
|
+
return props.children({
|
|
210
|
+
value: value,
|
|
211
|
+
onChange,
|
|
212
|
+
});
|
|
213
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import TimePickerToolbar from './TimePickerToolbar';
|
|
2
|
+
import { useUtils } from '../_shared/hooks/useUtils';
|
|
3
|
+
import { timePickerDefaultProps } from '../constants/prop-types';
|
|
4
|
+
import { pick12hOr24hFormat } from '../_helpers/text-field-helper';
|
|
5
|
+
import { WrappedPurePickerProps, makePurePicker } from '../Picker/WrappedPurePicker';
|
|
6
|
+
import { makeKeyboardPicker, WrappedKeyboardPickerProps } from '../Picker/WrappedKeyboardPicker';
|
|
7
|
+
|
|
8
|
+
type TimePickerView = 'hours' | 'minutes' | 'seconds';
|
|
9
|
+
|
|
10
|
+
export interface BaseTimePickerProps {
|
|
11
|
+
/**
|
|
12
|
+
* 12h/24h view for hour selection clock
|
|
13
|
+
* @default true
|
|
14
|
+
*/
|
|
15
|
+
ampm?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Step over minutes
|
|
18
|
+
* @default 1
|
|
19
|
+
*/
|
|
20
|
+
minutesStep?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TimePickerViewsProps extends BaseTimePickerProps {
|
|
24
|
+
/** Array of views to show */
|
|
25
|
+
views?: ('hours' | 'minutes' | 'seconds')[];
|
|
26
|
+
/** Open to timepicker */
|
|
27
|
+
openTo?: 'hours' | 'minutes' | 'seconds';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type TimePickerProps = WrappedPurePickerProps & TimePickerViewsProps;
|
|
31
|
+
|
|
32
|
+
export type KeyboardTimePickerProps = WrappedKeyboardPickerProps & TimePickerViewsProps;
|
|
33
|
+
|
|
34
|
+
const defaultProps = {
|
|
35
|
+
...timePickerDefaultProps,
|
|
36
|
+
openTo: 'hours' as TimePickerView,
|
|
37
|
+
views: ['hours', 'minutes'] as TimePickerView[],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function useOptions(props: TimePickerProps | KeyboardTimePickerProps) {
|
|
41
|
+
const utils = useUtils();
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
getDefaultFormat: () =>
|
|
45
|
+
pick12hOr24hFormat(props.format, props.ampm, {
|
|
46
|
+
'12h': utils.time12hFormat,
|
|
47
|
+
'24h': utils.time24hFormat,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const TimePicker = makePurePicker<TimePickerViewsProps>({
|
|
53
|
+
useOptions,
|
|
54
|
+
DefaultToolbarComponent: TimePickerToolbar,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const KeyboardTimePicker = makeKeyboardPicker<TimePickerViewsProps>({
|
|
58
|
+
useOptions,
|
|
59
|
+
DefaultToolbarComponent: TimePickerToolbar,
|
|
60
|
+
getCustomProps: props => ({
|
|
61
|
+
refuse: props.ampm ? /[^\dap]+/gi : /[^\d]+/gi,
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
TimePicker.defaultProps = defaultProps;
|
|
66
|
+
|
|
67
|
+
KeyboardTimePicker.defaultProps = defaultProps;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import ClockType from '../constants/ClockType';
|
|
4
|
+
import ToolbarText from '../_shared/ToolbarText';
|
|
5
|
+
import ToolbarButton from '../_shared/ToolbarButton';
|
|
6
|
+
import PickerToolbar from '../_shared/PickerToolbar';
|
|
7
|
+
import { arrayIncludes } from '../_helpers/utils';
|
|
8
|
+
import { useUtils } from '../_shared/hooks/useUtils';
|
|
9
|
+
import { MaterialUiPickersDate } from '../typings/date';
|
|
10
|
+
import { ToolbarComponentProps } from '../Picker/Picker';
|
|
11
|
+
import { useTheme, makeStyles } from '@material-ui/core/styles';
|
|
12
|
+
import { convertToMeridiem, getMeridiem } from '../_helpers/time-utils';
|
|
13
|
+
|
|
14
|
+
export const useStyles = makeStyles(
|
|
15
|
+
{
|
|
16
|
+
toolbarLandscape: {
|
|
17
|
+
flexWrap: 'wrap',
|
|
18
|
+
},
|
|
19
|
+
toolbarAmpmLeftPadding: {
|
|
20
|
+
paddingLeft: 50,
|
|
21
|
+
},
|
|
22
|
+
separator: {
|
|
23
|
+
margin: '0 4px 0 2px',
|
|
24
|
+
cursor: 'default',
|
|
25
|
+
},
|
|
26
|
+
hourMinuteLabel: {
|
|
27
|
+
display: 'flex',
|
|
28
|
+
justifyContent: 'flex-end',
|
|
29
|
+
alignItems: 'flex-end',
|
|
30
|
+
},
|
|
31
|
+
hourMinuteLabelLandscape: {
|
|
32
|
+
marginTop: 'auto',
|
|
33
|
+
},
|
|
34
|
+
hourMinuteLabelReverse: {
|
|
35
|
+
flexDirection: 'row-reverse',
|
|
36
|
+
},
|
|
37
|
+
ampmSelection: {
|
|
38
|
+
marginLeft: 20,
|
|
39
|
+
marginRight: -20,
|
|
40
|
+
display: 'flex',
|
|
41
|
+
flexDirection: 'column',
|
|
42
|
+
},
|
|
43
|
+
ampmLandscape: {
|
|
44
|
+
margin: '4px 0 auto',
|
|
45
|
+
flexDirection: 'row',
|
|
46
|
+
justifyContent: 'space-around',
|
|
47
|
+
flexBasis: '100%',
|
|
48
|
+
},
|
|
49
|
+
ampmSelectionWithSeconds: {
|
|
50
|
+
marginLeft: 15,
|
|
51
|
+
marginRight: 10,
|
|
52
|
+
},
|
|
53
|
+
ampmLabel: {
|
|
54
|
+
fontSize: 18,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{ name: 'MuiPickersTimePickerToolbar' }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export function useMeridiemMode(
|
|
61
|
+
date: MaterialUiPickersDate,
|
|
62
|
+
ampm: boolean | undefined,
|
|
63
|
+
onChange: (date: MaterialUiPickersDate, isFinished?: boolean | undefined) => void
|
|
64
|
+
) {
|
|
65
|
+
const utils = useUtils();
|
|
66
|
+
const meridiemMode = getMeridiem(date, utils);
|
|
67
|
+
|
|
68
|
+
const handleMeridiemChange = React.useCallback(
|
|
69
|
+
(mode: 'am' | 'pm') => {
|
|
70
|
+
const timeWithMeridiem = convertToMeridiem(date, mode, Boolean(ampm), utils);
|
|
71
|
+
onChange(timeWithMeridiem, false);
|
|
72
|
+
},
|
|
73
|
+
[ampm, date, onChange, utils]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return { meridiemMode, handleMeridiemChange };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TimePickerToolbar: React.FC<ToolbarComponentProps> = ({
|
|
80
|
+
date,
|
|
81
|
+
views,
|
|
82
|
+
ampm,
|
|
83
|
+
openView,
|
|
84
|
+
onChange,
|
|
85
|
+
isLandscape,
|
|
86
|
+
setOpenView,
|
|
87
|
+
}) => {
|
|
88
|
+
const utils = useUtils();
|
|
89
|
+
const theme = useTheme();
|
|
90
|
+
const classes = useStyles();
|
|
91
|
+
const { meridiemMode, handleMeridiemChange } = useMeridiemMode(date, ampm, onChange);
|
|
92
|
+
|
|
93
|
+
const clockTypographyVariant = isLandscape ? 'h3' : 'h2';
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<PickerToolbar
|
|
97
|
+
isLandscape={isLandscape}
|
|
98
|
+
className={clsx({
|
|
99
|
+
[classes.toolbarLandscape]: isLandscape,
|
|
100
|
+
[classes.toolbarAmpmLeftPadding]: ampm && !isLandscape,
|
|
101
|
+
})}
|
|
102
|
+
>
|
|
103
|
+
<div
|
|
104
|
+
className={clsx(classes.hourMinuteLabel, {
|
|
105
|
+
[classes.hourMinuteLabelLandscape]: isLandscape,
|
|
106
|
+
[classes.hourMinuteLabelReverse]: theme.direction === 'rtl',
|
|
107
|
+
})}
|
|
108
|
+
>
|
|
109
|
+
{arrayIncludes(views, 'hours') && (
|
|
110
|
+
<ToolbarButton
|
|
111
|
+
variant={clockTypographyVariant}
|
|
112
|
+
onClick={() => setOpenView(ClockType.HOURS)}
|
|
113
|
+
selected={openView === ClockType.HOURS}
|
|
114
|
+
label={utils.getHourText(date, Boolean(ampm))}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{arrayIncludes(views, ['hours', 'minutes']) && (
|
|
119
|
+
<ToolbarText
|
|
120
|
+
label=":"
|
|
121
|
+
variant={clockTypographyVariant}
|
|
122
|
+
selected={false}
|
|
123
|
+
className={classes.separator}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
{arrayIncludes(views, 'minutes') && (
|
|
128
|
+
<ToolbarButton
|
|
129
|
+
variant={clockTypographyVariant}
|
|
130
|
+
onClick={() => setOpenView(ClockType.MINUTES)}
|
|
131
|
+
selected={openView === ClockType.MINUTES}
|
|
132
|
+
label={utils.getMinuteText(date)}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{arrayIncludes(views, ['minutes', 'seconds']) && (
|
|
137
|
+
<ToolbarText variant="h2" label=":" selected={false} className={classes.separator} />
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{arrayIncludes(views, 'seconds') && (
|
|
141
|
+
<ToolbarButton
|
|
142
|
+
variant="h2"
|
|
143
|
+
onClick={() => setOpenView(ClockType.SECONDS)}
|
|
144
|
+
selected={openView === ClockType.SECONDS}
|
|
145
|
+
label={utils.getSecondText(date)}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{ampm && (
|
|
151
|
+
<div
|
|
152
|
+
className={clsx(classes.ampmSelection, {
|
|
153
|
+
[classes.ampmLandscape]: isLandscape,
|
|
154
|
+
[classes.ampmSelectionWithSeconds]: arrayIncludes(views, 'seconds'),
|
|
155
|
+
})}
|
|
156
|
+
>
|
|
157
|
+
<ToolbarButton
|
|
158
|
+
disableRipple
|
|
159
|
+
variant="subtitle1"
|
|
160
|
+
selected={meridiemMode === 'am'}
|
|
161
|
+
typographyClassName={classes.ampmLabel}
|
|
162
|
+
label={utils.getMeridiemText('am')}
|
|
163
|
+
onClick={() => handleMeridiemChange('am')}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<ToolbarButton
|
|
167
|
+
disableRipple
|
|
168
|
+
variant="subtitle1"
|
|
169
|
+
selected={meridiemMode === 'pm'}
|
|
170
|
+
typographyClassName={classes.ampmLabel}
|
|
171
|
+
label={utils.getMeridiemText('pm')}
|
|
172
|
+
onClick={() => handleMeridiemChange('pm')}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</PickerToolbar>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export default TimePickerToolbar;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ShallowWrapper } from 'enzyme';
|
|
3
|
+
import { shallowRender, utilsToUse } from '../test-utils';
|
|
4
|
+
import { Calendar, CalendarProps } from '../../views/Calendar/Calendar';
|
|
5
|
+
|
|
6
|
+
describe('Calendar', () => {
|
|
7
|
+
let component: ShallowWrapper<CalendarProps>;
|
|
8
|
+
const onChangeMock = jest.fn();
|
|
9
|
+
const onMonthChangeMock = jest.fn();
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
component = shallowRender(props => (
|
|
13
|
+
<Calendar
|
|
14
|
+
date={utilsToUse.date('01-01-2017')}
|
|
15
|
+
onMonthChange={onMonthChangeMock}
|
|
16
|
+
onChange={onChangeMock}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('Should swipe between month', () => {
|
|
23
|
+
component.find('CalendarHeader').prop<any>('onMonthChange')(utilsToUse.date());
|
|
24
|
+
expect(onMonthChangeMock).toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Calendar - disabled selected date on mount', () => {
|
|
29
|
+
let component: ShallowWrapper<any, any, any>;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
component = shallowRender(props => (
|
|
33
|
+
<Calendar
|
|
34
|
+
date={utilsToUse.date('01-01-2017')}
|
|
35
|
+
minDate={new Date('01-01-2018')}
|
|
36
|
+
onChange={jest.fn()}
|
|
37
|
+
utils={utilsToUse}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('Should dispatch onDateSelect with isFinish = false on mount', () => {
|
|
44
|
+
const { onChange } = component.instance().props;
|
|
45
|
+
if (process.env.UTILS === 'moment') {
|
|
46
|
+
return expect(onChange).toHaveBeenCalled();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
expect(onChange).toHaveBeenCalledWith(utilsToUse.date('01-01-2018'), false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ShallowWrapper } from 'enzyme';
|
|
3
|
+
import { shallow, utilsToUse } from '../test-utils';
|
|
4
|
+
import { Month, MonthProps } from '../../views/Month/Month';
|
|
5
|
+
|
|
6
|
+
describe('Month', () => {
|
|
7
|
+
let component: ShallowWrapper<MonthProps>;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
component = shallow(
|
|
11
|
+
<Month value={utilsToUse.date('01-01-2017')} onSelect={jest.fn()}>
|
|
12
|
+
Oct
|
|
13
|
+
</Month>
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('Should render', () => {
|
|
18
|
+
expect(component).toBeTruthy();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Month - disabled state', () => {
|
|
23
|
+
let component: ShallowWrapper<MonthProps>;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
component = shallow(
|
|
27
|
+
<Month disabled value={utilsToUse.date('01-01-2017')} onSelect={jest.fn()}>
|
|
28
|
+
Oct
|
|
29
|
+
</Month>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('Should render in disabled state', () => {
|
|
34
|
+
expect(component.prop('tabIndex')).toBe(-1);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import Month from '../../views/Month/Month';
|
|
3
|
+
import { ReactWrapper } from 'enzyme';
|
|
4
|
+
import { mount, utilsToUse } from '../test-utils';
|
|
5
|
+
import { MonthSelection, MonthSelectionProps } from '../../views/Month/MonthView';
|
|
6
|
+
|
|
7
|
+
describe('MonthSelection', () => {
|
|
8
|
+
let component: ReactWrapper<MonthSelectionProps>;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
component = mount(
|
|
12
|
+
<MonthSelection
|
|
13
|
+
minDate={new Date('03-01-2017')}
|
|
14
|
+
maxDate={new Date('05-01-2017')}
|
|
15
|
+
date={utilsToUse.date('04-01-2017')}
|
|
16
|
+
onChange={jest.fn()}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('Should render disabled months before min date and after max date', () => {
|
|
22
|
+
expect(component.find(Month).map(month => month.prop('disabled'))).toEqual([
|
|
23
|
+
true,
|
|
24
|
+
true,
|
|
25
|
+
false,
|
|
26
|
+
false,
|
|
27
|
+
false,
|
|
28
|
+
true,
|
|
29
|
+
true,
|
|
30
|
+
true,
|
|
31
|
+
true,
|
|
32
|
+
true,
|
|
33
|
+
true,
|
|
34
|
+
true,
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// required to use just shallow here because utils prop override
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DateFnsUtils from '@date-io/date-fns';
|
|
4
|
+
import MuiPickersUtilsProvider, { MuiPickersUtilsProviderProps } from '../MuiPickersUtilsProvider';
|
|
5
|
+
import { shallow, ShallowWrapper } from 'enzyme'; // required to use just shallow here because utils prop override
|
|
6
|
+
|
|
7
|
+
describe('MuiPickersUtilsProvider', () => {
|
|
8
|
+
let component: ShallowWrapper<MuiPickersUtilsProviderProps>;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
component = shallow(
|
|
12
|
+
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
|
13
|
+
<div />
|
|
14
|
+
</MuiPickersUtilsProvider>
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('Should render context provider', () => {
|
|
19
|
+
expect(component).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ShallowWrapper } from 'enzyme';
|
|
3
|
+
import { shallow } from '../test-utils';
|
|
4
|
+
import { Clock, ClockProps } from '../../views/Clock/Clock';
|
|
5
|
+
|
|
6
|
+
const mouseClockEvent = {
|
|
7
|
+
preventDefault: jest.fn(),
|
|
8
|
+
stopPropagation: jest.fn(),
|
|
9
|
+
buttons: 1,
|
|
10
|
+
nativeEvent: {
|
|
11
|
+
offsetX: 10,
|
|
12
|
+
offsetY: 25,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('Clock', () => {
|
|
17
|
+
let component: ShallowWrapper<ClockProps>;
|
|
18
|
+
const onChangeMock = jest.fn();
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
onChangeMock.mockReset();
|
|
22
|
+
component = shallow(
|
|
23
|
+
<Clock
|
|
24
|
+
type="minutes"
|
|
25
|
+
onChange={onChangeMock}
|
|
26
|
+
classes={{} as any}
|
|
27
|
+
value={12}
|
|
28
|
+
children={[<div key="foo">foo</div>]}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('Should renders', () => {
|
|
34
|
+
// console.log(component.debug());
|
|
35
|
+
expect(component).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('Should set time on mouse move with click', () => {
|
|
39
|
+
component.find('[role="menu"]').simulate('mouseMove', mouseClockEvent);
|
|
40
|
+
|
|
41
|
+
expect(onChangeMock).toHaveBeenCalledWith(52, false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('Should set isMoving = false on mouse up', () => {
|
|
45
|
+
(component.instance() as Clock).isMoving = true;
|
|
46
|
+
component.find('[role="menu"]').simulate('mouseUp', mouseClockEvent);
|
|
47
|
+
|
|
48
|
+
expect((component.instance() as Clock).isMoving).toBeFalsy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('Should set time on touch move', () => {
|
|
52
|
+
component.find('[role="menu"]').simulate('touchMove', {
|
|
53
|
+
preventDefault: jest.fn(),
|
|
54
|
+
stopPropagation: jest.fn(),
|
|
55
|
+
changedTouches: [{ clientX: 10, clientY: 15 }],
|
|
56
|
+
target: {
|
|
57
|
+
getBoundingClientRect: () => ({ left: 0, top: 0 }),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(onChangeMock).toHaveBeenCalledWith(52, false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('Should set isMoving = false on touch end', () => {
|
|
65
|
+
component.find('[role="menu"]').simulate('touchEnd');
|
|
66
|
+
expect((component.instance() as Clock).isMoving).toBeFalsy();
|
|
67
|
+
});
|
|
68
|
+
});
|