@atlaskit/datetime-picker 14.0.2 → 14.0.4
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/CHANGELOG.md +17 -0
- package/dist/cjs/components/date-picker.js +45 -92
- package/dist/cjs/components/date-time-picker.js +1 -1
- package/dist/cjs/components/time-picker.js +284 -312
- package/dist/cjs/internal/date-picker-migration.js +92 -0
- package/dist/es2019/components/date-picker.js +24 -76
- package/dist/es2019/components/date-time-picker.js +1 -1
- package/dist/es2019/components/time-picker.js +246 -297
- package/dist/es2019/internal/date-picker-migration.js +90 -0
- package/dist/esm/components/date-picker.js +47 -94
- package/dist/esm/components/date-time-picker.js +1 -1
- package/dist/esm/components/time-picker.js +283 -313
- package/dist/esm/internal/date-picker-migration.js +85 -0
- package/dist/types/components/date-picker.d.ts +2 -19
- package/dist/types/components/time-picker.d.ts +2 -104
- package/dist/types/internal/date-picker-migration.d.ts +51 -0
- package/dist/types/types.d.ts +4 -0
- package/dist/types-ts4.5/components/date-picker.d.ts +2 -19
- package/dist/types-ts4.5/components/time-picker.d.ts +2 -104
- package/dist/types-ts4.5/internal/date-picker-migration.d.ts +51 -0
- package/dist/types-ts4.5/types.d.ts +4 -0
- package/package.json +5 -5
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
-
import
|
|
3
|
-
import React from 'react';
|
|
2
|
+
import React, { forwardRef, useReducer, useState } from 'react';
|
|
4
3
|
|
|
5
4
|
// eslint-disable-next-line no-restricted-imports
|
|
6
5
|
import { format, isValid } from 'date-fns';
|
|
7
|
-
import {
|
|
6
|
+
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next';
|
|
7
|
+
import __noop from '@atlaskit/ds-lib/noop';
|
|
8
8
|
import { createLocalizationProvider } from '@atlaskit/locale';
|
|
9
9
|
import Select, { CreatableSelect, mergeStyles } from '@atlaskit/select';
|
|
10
10
|
// eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
|
|
@@ -15,7 +15,7 @@ import parseTime from '../internal/parse-time';
|
|
|
15
15
|
import { convertTokens } from '../internal/parse-tokens';
|
|
16
16
|
import { makeSingleValue } from '../internal/single-value';
|
|
17
17
|
const packageName = "@atlaskit/datetime-picker";
|
|
18
|
-
const packageVersion = "14.0.
|
|
18
|
+
const packageVersion = "14.0.4";
|
|
19
19
|
const menuStyles = {
|
|
20
20
|
/* Need to remove default absolute positioning as that causes issues with position fixed */
|
|
21
21
|
position: 'static',
|
|
@@ -24,287 +24,11 @@ const menuStyles = {
|
|
|
24
24
|
/* React-Popper has already offset the menu so we need to reset the margin, otherwise the offset value is doubled */
|
|
25
25
|
margin: 0
|
|
26
26
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
defaultValue: '',
|
|
32
|
-
hideIcon: false,
|
|
33
|
-
id: '',
|
|
34
|
-
innerProps: {},
|
|
35
|
-
isDisabled: false,
|
|
36
|
-
isInvalid: false,
|
|
37
|
-
label: '',
|
|
38
|
-
name: '',
|
|
39
|
-
// These disables are here for proper typing when used as defaults. They
|
|
40
|
-
// should *not* use the `noop` function.
|
|
41
|
-
/* eslint-disable @repo/internal/react/use-noop */
|
|
42
|
-
onBlur: _event => {},
|
|
43
|
-
onChange: _value => {},
|
|
44
|
-
onFocus: _event => {},
|
|
45
|
-
/* eslint-enable @repo/internal/react/use-noop */
|
|
46
|
-
parseInputValue: (time, _timeFormat) => parseTime(time),
|
|
47
|
-
selectProps: {},
|
|
48
|
-
spacing: 'default',
|
|
49
|
-
times: defaultTimes,
|
|
50
|
-
timeIsEditable: false,
|
|
51
|
-
locale: 'en-US'
|
|
52
|
-
// Not including a default prop for value as it will
|
|
53
|
-
// Make the component a controlled component
|
|
27
|
+
const analyticsAttributes = {
|
|
28
|
+
componentName: 'timePicker',
|
|
29
|
+
packageName,
|
|
30
|
+
packageVersion
|
|
54
31
|
};
|
|
55
|
-
class TimePickerComponent extends React.Component {
|
|
56
|
-
constructor(...args) {
|
|
57
|
-
super(...args);
|
|
58
|
-
_defineProperty(this, "containerRef", null);
|
|
59
|
-
_defineProperty(this, "state", {
|
|
60
|
-
isOpen: this.props.defaultIsOpen,
|
|
61
|
-
clearingFromIcon: false,
|
|
62
|
-
value: this.props.defaultValue,
|
|
63
|
-
isFocused: false
|
|
64
|
-
});
|
|
65
|
-
// All state needs to be accessed via this function so that the state is mapped from props
|
|
66
|
-
// correctly to allow controlled/uncontrolled usage.
|
|
67
|
-
_defineProperty(this, "getValue", () => {
|
|
68
|
-
var _this$props$value;
|
|
69
|
-
return (_this$props$value = this.props.value) !== null && _this$props$value !== void 0 ? _this$props$value : this.state.value;
|
|
70
|
-
});
|
|
71
|
-
_defineProperty(this, "getIsOpen", () => {
|
|
72
|
-
var _this$props$isOpen;
|
|
73
|
-
return (_this$props$isOpen = this.props.isOpen) !== null && _this$props$isOpen !== void 0 ? _this$props$isOpen : this.state.isOpen;
|
|
74
|
-
});
|
|
75
|
-
_defineProperty(this, "onChange", (newValue, action) => {
|
|
76
|
-
const rawValue = newValue ? newValue.value || newValue : '';
|
|
77
|
-
const value = rawValue.toString();
|
|
78
|
-
let changedState = {
|
|
79
|
-
value
|
|
80
|
-
};
|
|
81
|
-
if (action && action.action === 'clear') {
|
|
82
|
-
changedState = {
|
|
83
|
-
...changedState,
|
|
84
|
-
clearingFromIcon: true
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
this.setState(changedState);
|
|
88
|
-
this.props.onChange(value);
|
|
89
|
-
});
|
|
90
|
-
/**
|
|
91
|
-
* Only allow custom times if timeIsEditable prop is true
|
|
92
|
-
*/
|
|
93
|
-
_defineProperty(this, "onCreateOption", inputValue => {
|
|
94
|
-
if (this.props.timeIsEditable) {
|
|
95
|
-
const {
|
|
96
|
-
parseInputValue,
|
|
97
|
-
timeFormat
|
|
98
|
-
} = this.props;
|
|
99
|
-
let sanitizedInput;
|
|
100
|
-
try {
|
|
101
|
-
sanitizedInput = parseInputValue(inputValue, timeFormat || defaultTimeFormat);
|
|
102
|
-
} catch (e) {
|
|
103
|
-
return; // do nothing, the main validation should happen in the form
|
|
104
|
-
}
|
|
105
|
-
const includesSeconds = !!(timeFormat && /[:.]?(s|ss)/.test(timeFormat));
|
|
106
|
-
const formatFormat = includesSeconds ? 'HH:mm:ss' : 'HH:mm';
|
|
107
|
-
const formattedValue = format(sanitizedInput, formatFormat) || '';
|
|
108
|
-
this.setState({
|
|
109
|
-
value: formattedValue
|
|
110
|
-
});
|
|
111
|
-
this.props.onChange(formattedValue);
|
|
112
|
-
} else {
|
|
113
|
-
this.onChange(inputValue);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
_defineProperty(this, "onMenuOpen", () => {
|
|
117
|
-
// Don't open menu after the user has clicked clear
|
|
118
|
-
if (this.state.clearingFromIcon) {
|
|
119
|
-
this.setState({
|
|
120
|
-
clearingFromIcon: false
|
|
121
|
-
});
|
|
122
|
-
} else {
|
|
123
|
-
this.setState({
|
|
124
|
-
isOpen: true
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
_defineProperty(this, "onMenuClose", () => {
|
|
129
|
-
// Don't close menu after the user has clicked clear
|
|
130
|
-
if (this.state.clearingFromIcon) {
|
|
131
|
-
this.setState({
|
|
132
|
-
clearingFromIcon: false
|
|
133
|
-
});
|
|
134
|
-
} else {
|
|
135
|
-
this.setState({
|
|
136
|
-
isOpen: false
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
_defineProperty(this, "setContainerRef", ref => {
|
|
141
|
-
const oldRef = this.containerRef;
|
|
142
|
-
this.containerRef = ref;
|
|
143
|
-
// Cause a re-render if we're getting the container ref for the first time
|
|
144
|
-
// as the layered menu requires it for dimension calculation
|
|
145
|
-
if (oldRef == null && ref != null) {
|
|
146
|
-
this.forceUpdate();
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
_defineProperty(this, "onBlur", event => {
|
|
150
|
-
this.setState({
|
|
151
|
-
isFocused: false
|
|
152
|
-
});
|
|
153
|
-
this.props.onBlur(event);
|
|
154
|
-
});
|
|
155
|
-
_defineProperty(this, "onFocus", event => {
|
|
156
|
-
this.setState({
|
|
157
|
-
isFocused: true
|
|
158
|
-
});
|
|
159
|
-
this.props.onFocus(event);
|
|
160
|
-
});
|
|
161
|
-
_defineProperty(this, "onSelectKeyDown", event => {
|
|
162
|
-
const {
|
|
163
|
-
key
|
|
164
|
-
} = event;
|
|
165
|
-
const keyPressed = key.toLowerCase();
|
|
166
|
-
if (this.state.clearingFromIcon && (keyPressed === 'backspace' || keyPressed === 'delete')) {
|
|
167
|
-
// If being cleared from keyboard, don't change behaviour
|
|
168
|
-
this.setState({
|
|
169
|
-
clearingFromIcon: false
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
render() {
|
|
175
|
-
const {
|
|
176
|
-
appearance,
|
|
177
|
-
'aria-describedby': ariaDescribedBy,
|
|
178
|
-
autoFocus,
|
|
179
|
-
formatDisplayLabel,
|
|
180
|
-
hideIcon,
|
|
181
|
-
id,
|
|
182
|
-
innerProps,
|
|
183
|
-
isDisabled,
|
|
184
|
-
label,
|
|
185
|
-
locale,
|
|
186
|
-
name,
|
|
187
|
-
placeholder,
|
|
188
|
-
selectProps,
|
|
189
|
-
spacing,
|
|
190
|
-
testId,
|
|
191
|
-
isInvalid,
|
|
192
|
-
timeIsEditable,
|
|
193
|
-
timeFormat,
|
|
194
|
-
times
|
|
195
|
-
} = this.props;
|
|
196
|
-
const ICON_PADDING = 2;
|
|
197
|
-
const l10n = createLocalizationProvider(locale);
|
|
198
|
-
const value = this.getValue() || '';
|
|
199
|
-
const isOpen = this.getIsOpen();
|
|
200
|
-
const {
|
|
201
|
-
styles: selectStyles = {},
|
|
202
|
-
...otherSelectProps
|
|
203
|
-
} = selectProps;
|
|
204
|
-
const SelectComponent = timeIsEditable ? CreatableSelect : Select;
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* There are multiple props that can change how the time is formatted.
|
|
208
|
-
* The priority of props used is:
|
|
209
|
-
* 1. formatDisplayLabel
|
|
210
|
-
* 2. timeFormat
|
|
211
|
-
* 3. locale
|
|
212
|
-
*/
|
|
213
|
-
const formatTime = time => {
|
|
214
|
-
if (formatDisplayLabel) {
|
|
215
|
-
return formatDisplayLabel(time, timeFormat || defaultTimeFormat);
|
|
216
|
-
}
|
|
217
|
-
const date = parseTime(time);
|
|
218
|
-
if (!(date instanceof Date)) {
|
|
219
|
-
return '';
|
|
220
|
-
}
|
|
221
|
-
if (!isValid(date)) {
|
|
222
|
-
return time;
|
|
223
|
-
}
|
|
224
|
-
if (timeFormat) {
|
|
225
|
-
return format(date, convertTokens(timeFormat));
|
|
226
|
-
}
|
|
227
|
-
return l10n.formatTime(date);
|
|
228
|
-
};
|
|
229
|
-
const options = times.map(time => {
|
|
230
|
-
return {
|
|
231
|
-
label: formatTime(time),
|
|
232
|
-
value: time
|
|
233
|
-
};
|
|
234
|
-
});
|
|
235
|
-
const initialValue = value ? {
|
|
236
|
-
label: formatTime(value),
|
|
237
|
-
value
|
|
238
|
-
} : null;
|
|
239
|
-
const SingleValue = makeSingleValue({
|
|
240
|
-
lang: this.props.locale
|
|
241
|
-
});
|
|
242
|
-
const selectComponents = {
|
|
243
|
-
DropdownIndicator: EmptyComponent,
|
|
244
|
-
Menu: FixedLayerMenu,
|
|
245
|
-
SingleValue,
|
|
246
|
-
...(hideIcon && {
|
|
247
|
-
ClearIndicator: EmptyComponent
|
|
248
|
-
})
|
|
249
|
-
};
|
|
250
|
-
const renderIconContainer = Boolean(!hideIcon && value);
|
|
251
|
-
const mergedStyles = mergeStyles(selectStyles, {
|
|
252
|
-
control: base => ({
|
|
253
|
-
...base
|
|
254
|
-
}),
|
|
255
|
-
menu: base => ({
|
|
256
|
-
...base,
|
|
257
|
-
...menuStyles,
|
|
258
|
-
// Fixed positioned elements no longer inherit width from their parent, so we must explicitly set the
|
|
259
|
-
// menu width to the width of our container
|
|
260
|
-
width: this.containerRef ? this.containerRef.getBoundingClientRect().width : 'auto'
|
|
261
|
-
}),
|
|
262
|
-
indicatorsContainer: base => ({
|
|
263
|
-
...base,
|
|
264
|
-
paddingLeft: renderIconContainer ? ICON_PADDING : 0,
|
|
265
|
-
paddingRight: renderIconContainer ? gridSize() - ICON_PADDING : 0
|
|
266
|
-
})
|
|
267
|
-
});
|
|
268
|
-
return /*#__PURE__*/React.createElement("div", _extends({}, innerProps, {
|
|
269
|
-
ref: this.setContainerRef,
|
|
270
|
-
"data-testid": testId && `${testId}--container`
|
|
271
|
-
}), /*#__PURE__*/React.createElement("input", {
|
|
272
|
-
name: name,
|
|
273
|
-
type: "hidden",
|
|
274
|
-
value: value,
|
|
275
|
-
"data-testid": testId && `${testId}--input`,
|
|
276
|
-
onKeyDown: this.onSelectKeyDown
|
|
277
|
-
}), /*#__PURE__*/React.createElement(SelectComponent, _extends({
|
|
278
|
-
"aria-describedby": ariaDescribedBy,
|
|
279
|
-
"aria-label": label || undefined,
|
|
280
|
-
appearance: appearance,
|
|
281
|
-
autoFocus: autoFocus,
|
|
282
|
-
components: selectComponents,
|
|
283
|
-
inputId: id,
|
|
284
|
-
isClearable: true,
|
|
285
|
-
isDisabled: isDisabled,
|
|
286
|
-
menuIsOpen: isOpen && !isDisabled,
|
|
287
|
-
menuPlacement: "auto",
|
|
288
|
-
openMenuOnFocus: true,
|
|
289
|
-
onBlur: this.onBlur,
|
|
290
|
-
onCreateOption: this.onCreateOption,
|
|
291
|
-
onChange: this.onChange,
|
|
292
|
-
options: options,
|
|
293
|
-
onFocus: this.onFocus,
|
|
294
|
-
onMenuOpen: this.onMenuOpen,
|
|
295
|
-
onMenuClose: this.onMenuClose,
|
|
296
|
-
placeholder: placeholder || l10n.formatTime(placeholderDatetime),
|
|
297
|
-
styles: mergedStyles,
|
|
298
|
-
value: initialValue,
|
|
299
|
-
spacing: spacing,
|
|
300
|
-
fixedLayerRef: this.containerRef,
|
|
301
|
-
isInvalid: isInvalid,
|
|
302
|
-
testId: testId
|
|
303
|
-
}, otherSelectProps)));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
_defineProperty(TimePickerComponent, "defaultProps", timePickerDefaultProps);
|
|
307
|
-
export { TimePickerComponent as TimePickerWithoutAnalytics };
|
|
308
32
|
|
|
309
33
|
/**
|
|
310
34
|
* __Time picker__
|
|
@@ -315,19 +39,244 @@ export { TimePickerComponent as TimePickerWithoutAnalytics };
|
|
|
315
39
|
* - [Code](https://atlassian.design/components/datetime-picker/time-picker/code)
|
|
316
40
|
* - [Usage](https://atlassian.design/components/datetime-picker/time-picker/usage)
|
|
317
41
|
*/
|
|
318
|
-
const TimePicker =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
42
|
+
const TimePicker = /*#__PURE__*/forwardRef(({
|
|
43
|
+
'aria-describedby': ariaDescribedBy,
|
|
44
|
+
analyticsContext,
|
|
45
|
+
appearance = 'default',
|
|
46
|
+
autoFocus = false,
|
|
47
|
+
defaultIsOpen = false,
|
|
48
|
+
defaultValue = '',
|
|
49
|
+
formatDisplayLabel,
|
|
50
|
+
hideIcon = false,
|
|
51
|
+
id = '',
|
|
52
|
+
innerProps = {},
|
|
53
|
+
isDisabled = false,
|
|
54
|
+
isInvalid = false,
|
|
55
|
+
isOpen: providedIsOpen,
|
|
56
|
+
label = '',
|
|
57
|
+
locale = 'en-US',
|
|
58
|
+
name = '',
|
|
59
|
+
onBlur: providedOnBlur = __noop,
|
|
60
|
+
onChange: providedOnChange = __noop,
|
|
61
|
+
onFocus: providedOnFocus = __noop,
|
|
62
|
+
parseInputValue = (time, _timeFormat) => parseTime(time),
|
|
63
|
+
placeholder,
|
|
64
|
+
selectProps = {},
|
|
65
|
+
spacing = 'default',
|
|
66
|
+
testId,
|
|
67
|
+
timeFormat,
|
|
68
|
+
timeIsEditable = false,
|
|
69
|
+
times = defaultTimes,
|
|
70
|
+
value: providedValue
|
|
71
|
+
}, ref) => {
|
|
72
|
+
const [containerRef, setContainerRef] = useState(null);
|
|
73
|
+
/**
|
|
74
|
+
* When being cleared from the icon the TimePicker is blurred.
|
|
75
|
+
* This variable defines whether the default onMenuOpen or onMenuClose
|
|
76
|
+
* events should behave as normal
|
|
77
|
+
*/
|
|
78
|
+
const [clearingFromIcon, setClearingFromIcon] = useState(false);
|
|
79
|
+
// TODO: Remove isFocused? Does it do anything?
|
|
80
|
+
const [_, setIsFocused] = useState(false);
|
|
81
|
+
const [isOpen, setIsOpen] = useState(providedIsOpen || defaultIsOpen);
|
|
82
|
+
const [value, setValue] = useState(providedValue || defaultValue);
|
|
83
|
+
|
|
84
|
+
// Hack to force update: https://legacy.reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate
|
|
85
|
+
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
86
|
+
const providedOnChangeWithAnalytics = usePlatformLeafEventHandler({
|
|
87
|
+
fn: providedOnChange,
|
|
324
88
|
action: 'selectedTime',
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
89
|
+
analyticsData: analyticsContext,
|
|
90
|
+
...analyticsAttributes
|
|
91
|
+
});
|
|
92
|
+
const onChange = (newValue, action) => {
|
|
93
|
+
const rawValue = newValue ? newValue.value || newValue : '';
|
|
94
|
+
const finalValue = rawValue.toString();
|
|
95
|
+
setValue(finalValue);
|
|
96
|
+
if (action && action.action === 'clear') {
|
|
97
|
+
setClearingFromIcon(true);
|
|
98
|
+
}
|
|
99
|
+
providedOnChangeWithAnalytics(finalValue);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Only allow custom times if timeIsEditable prop is true
|
|
104
|
+
*/
|
|
105
|
+
const onCreateOption = inputValue => {
|
|
106
|
+
if (timeIsEditable) {
|
|
107
|
+
let sanitizedInput;
|
|
108
|
+
try {
|
|
109
|
+
sanitizedInput = parseInputValue(inputValue, timeFormat || defaultTimeFormat);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return; // do nothing, the main validation should happen in the form
|
|
112
|
+
}
|
|
113
|
+
const includesSeconds = !!(timeFormat && /[:.]?(s|ss)/.test(timeFormat));
|
|
114
|
+
const formatFormat = includesSeconds ? 'HH:mm:ss' : 'HH:mm';
|
|
115
|
+
const formattedValue = format(sanitizedInput, formatFormat) || '';
|
|
116
|
+
setValue(formattedValue);
|
|
117
|
+
providedOnChange(formattedValue);
|
|
118
|
+
} else {
|
|
119
|
+
providedOnChange(inputValue);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const onMenuOpen = () => {
|
|
123
|
+
// Don't open menu after the user has clicked clear
|
|
124
|
+
if (clearingFromIcon) {
|
|
125
|
+
setClearingFromIcon(false);
|
|
126
|
+
} else {
|
|
127
|
+
setIsOpen(true);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const onMenuClose = () => {
|
|
131
|
+
// Don't close menu after the user has clicked clear
|
|
132
|
+
if (clearingFromIcon) {
|
|
133
|
+
setClearingFromIcon(false);
|
|
134
|
+
} else {
|
|
135
|
+
setIsOpen(false);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const setInternalContainerRef = ref => {
|
|
139
|
+
const oldRef = containerRef;
|
|
140
|
+
setContainerRef(ref);
|
|
141
|
+
// Cause a re-render if we're getting the container ref for the first time
|
|
142
|
+
// as the layered menu requires it for dimension calculation
|
|
143
|
+
if (oldRef === null && ref !== null) {
|
|
144
|
+
forceUpdate();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const onBlur = event => {
|
|
148
|
+
setIsFocused(false);
|
|
149
|
+
providedOnBlur(event);
|
|
150
|
+
};
|
|
151
|
+
const onFocus = event => {
|
|
152
|
+
setIsFocused(true);
|
|
153
|
+
providedOnFocus(event);
|
|
154
|
+
};
|
|
155
|
+
const onSelectKeyDown = event => {
|
|
156
|
+
const {
|
|
157
|
+
key
|
|
158
|
+
} = event;
|
|
159
|
+
const keyPressed = key.toLowerCase();
|
|
160
|
+
if (clearingFromIcon && (keyPressed === 'backspace' || keyPressed === 'delete')) {
|
|
161
|
+
// If being cleared from keyboard, don't change behaviour
|
|
162
|
+
setClearingFromIcon(false);
|
|
330
163
|
}
|
|
331
|
-
}
|
|
332
|
-
|
|
164
|
+
};
|
|
165
|
+
const ICON_PADDING = 2;
|
|
166
|
+
const l10n = createLocalizationProvider(locale);
|
|
167
|
+
const {
|
|
168
|
+
styles: selectStyles = {},
|
|
169
|
+
...otherSelectProps
|
|
170
|
+
} = selectProps;
|
|
171
|
+
const SelectComponent = timeIsEditable ? CreatableSelect : Select;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* There are multiple props that can change how the time is formatted.
|
|
175
|
+
* The priority of props used is:
|
|
176
|
+
* 1. formatDisplayLabel
|
|
177
|
+
* 2. timeFormat
|
|
178
|
+
* 3. locale
|
|
179
|
+
*/
|
|
180
|
+
const formatTime = time => {
|
|
181
|
+
if (formatDisplayLabel) {
|
|
182
|
+
return formatDisplayLabel(time, timeFormat || defaultTimeFormat);
|
|
183
|
+
}
|
|
184
|
+
const date = parseTime(time);
|
|
185
|
+
if (!(date instanceof Date)) {
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
if (!isValid(date)) {
|
|
189
|
+
return time;
|
|
190
|
+
}
|
|
191
|
+
if (timeFormat) {
|
|
192
|
+
return format(date, convertTokens(timeFormat));
|
|
193
|
+
}
|
|
194
|
+
return l10n.formatTime(date);
|
|
195
|
+
};
|
|
196
|
+
const options = times.map(time => {
|
|
197
|
+
return {
|
|
198
|
+
label: formatTime(time),
|
|
199
|
+
value: time
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
let initialValue;
|
|
203
|
+
if (providedValue !== null && providedValue !== undefined && providedValue !== '') {
|
|
204
|
+
initialValue = {
|
|
205
|
+
label: formatTime(providedValue),
|
|
206
|
+
value: providedValue
|
|
207
|
+
};
|
|
208
|
+
} else if (providedValue !== '' && value) {
|
|
209
|
+
initialValue = {
|
|
210
|
+
label: formatTime(value),
|
|
211
|
+
value: value
|
|
212
|
+
};
|
|
213
|
+
} else {
|
|
214
|
+
initialValue = null;
|
|
215
|
+
}
|
|
216
|
+
const SingleValue = makeSingleValue({
|
|
217
|
+
lang: locale
|
|
218
|
+
});
|
|
219
|
+
const selectComponents = {
|
|
220
|
+
DropdownIndicator: EmptyComponent,
|
|
221
|
+
Menu: FixedLayerMenu,
|
|
222
|
+
SingleValue,
|
|
223
|
+
...(hideIcon && {
|
|
224
|
+
ClearIndicator: EmptyComponent
|
|
225
|
+
})
|
|
226
|
+
};
|
|
227
|
+
const renderIconContainer = Boolean(!hideIcon && value);
|
|
228
|
+
const mergedStyles = mergeStyles(selectStyles, {
|
|
229
|
+
control: base => ({
|
|
230
|
+
...base
|
|
231
|
+
}),
|
|
232
|
+
menu: base => ({
|
|
233
|
+
...base,
|
|
234
|
+
...menuStyles,
|
|
235
|
+
// Fixed positioned elements no longer inherit width from their parent, so we must explicitly set the
|
|
236
|
+
// menu width to the width of our container
|
|
237
|
+
width: containerRef ? containerRef.getBoundingClientRect().width : 'auto'
|
|
238
|
+
}),
|
|
239
|
+
indicatorsContainer: base => ({
|
|
240
|
+
...base,
|
|
241
|
+
paddingLeft: renderIconContainer ? ICON_PADDING : 0,
|
|
242
|
+
paddingRight: renderIconContainer ? gridSize() - ICON_PADDING : 0
|
|
243
|
+
})
|
|
244
|
+
});
|
|
245
|
+
return /*#__PURE__*/React.createElement("div", _extends({}, innerProps, {
|
|
246
|
+
ref: setInternalContainerRef,
|
|
247
|
+
"data-testid": testId && `${testId}--container`
|
|
248
|
+
}), /*#__PURE__*/React.createElement("input", {
|
|
249
|
+
name: name,
|
|
250
|
+
type: "hidden",
|
|
251
|
+
value: value,
|
|
252
|
+
"data-testid": testId && `${testId}--input`,
|
|
253
|
+
onKeyDown: onSelectKeyDown
|
|
254
|
+
}), /*#__PURE__*/React.createElement(SelectComponent, _extends({
|
|
255
|
+
"aria-describedby": ariaDescribedBy,
|
|
256
|
+
"aria-label": label || undefined,
|
|
257
|
+
appearance: appearance,
|
|
258
|
+
autoFocus: autoFocus,
|
|
259
|
+
components: selectComponents,
|
|
260
|
+
inputId: id,
|
|
261
|
+
isClearable: true,
|
|
262
|
+
isDisabled: isDisabled,
|
|
263
|
+
menuIsOpen: isOpen && !isDisabled,
|
|
264
|
+
menuPlacement: "auto",
|
|
265
|
+
openMenuOnFocus: true,
|
|
266
|
+
onBlur: onBlur,
|
|
267
|
+
onCreateOption: onCreateOption,
|
|
268
|
+
onChange: onChange,
|
|
269
|
+
options: options,
|
|
270
|
+
onFocus: onFocus,
|
|
271
|
+
onMenuOpen: onMenuOpen,
|
|
272
|
+
onMenuClose: onMenuClose,
|
|
273
|
+
placeholder: placeholder || l10n.formatTime(placeholderDatetime),
|
|
274
|
+
styles: mergedStyles,
|
|
275
|
+
value: initialValue,
|
|
276
|
+
spacing: spacing,
|
|
277
|
+
fixedLayerRef: containerRef,
|
|
278
|
+
isInvalid: isInvalid,
|
|
279
|
+
testId: testId
|
|
280
|
+
}, otherSelectProps)));
|
|
281
|
+
});
|
|
333
282
|
export default TimePicker;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything in this file is to smooth out the migration of the new date picker
|
|
3
|
+
* (https://product-fabric.atlassian.net/browse/DSP-20682). When that ticket is
|
|
4
|
+
* complete, all of these functions will ilkely be merged back into the date
|
|
5
|
+
* picker. Please do not pre-optimize and put these back into the date picker
|
|
6
|
+
* unless you are working on the DTP Refresh and you have a good reason to do
|
|
7
|
+
* so, thank you!
|
|
8
|
+
*
|
|
9
|
+
* All variables within the `di` objects are dependency injections. They should
|
|
10
|
+
* be read from within the component at the end of the day. But because we are
|
|
11
|
+
* extracting them, we have to inject them in every place manually. When we
|
|
12
|
+
* re-introduce them to the components, we can likely remove the `di` variables
|
|
13
|
+
* and instead use internal variables.
|
|
14
|
+
*
|
|
15
|
+
* If component _only_ has injected variables, it is fully internal and was
|
|
16
|
+
* broken out to be it's own function.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { format, lastDayOfMonth, parseISO } from 'date-fns';
|
|
20
|
+
import { convertTokens } from './parse-tokens';
|
|
21
|
+
import { defaultDateFormat, padToTwo, placeholderDatetime } from './index';
|
|
22
|
+
export const isDateDisabled = (date, di) => {
|
|
23
|
+
const {
|
|
24
|
+
disabled
|
|
25
|
+
} = di;
|
|
26
|
+
return disabled.indexOf(date) > -1;
|
|
27
|
+
};
|
|
28
|
+
export const getParsedISO = di => {
|
|
29
|
+
const {
|
|
30
|
+
iso
|
|
31
|
+
} = di;
|
|
32
|
+
const [year, month, date] = iso.split('-');
|
|
33
|
+
let newIso = iso;
|
|
34
|
+
const parsedDate = parseInt(date, 10);
|
|
35
|
+
const parsedMonth = parseInt(month, 10);
|
|
36
|
+
const parsedYear = parseInt(year, 10);
|
|
37
|
+
const lastDayInMonth = lastDayOfMonth(new Date(parsedYear, parsedMonth - 1) // This needs to be -1, because the Date constructor expects an index of the given month
|
|
38
|
+
).getDate();
|
|
39
|
+
if (lastDayInMonth < parsedDate) {
|
|
40
|
+
newIso = `${year}-${padToTwo(parsedMonth)}-${padToTwo(lastDayInMonth)}`;
|
|
41
|
+
} else {
|
|
42
|
+
newIso = `${year}-${padToTwo(parsedMonth)}-${padToTwo(parsedDate)}`;
|
|
43
|
+
}
|
|
44
|
+
return newIso;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* There are two props that can change how the date is parsed.
|
|
49
|
+
* The priority of props used is:
|
|
50
|
+
* 1. `parseInputValue`
|
|
51
|
+
* 2. `locale`
|
|
52
|
+
*/
|
|
53
|
+
export const parseDate = (date, di) => {
|
|
54
|
+
const {
|
|
55
|
+
parseInputValue,
|
|
56
|
+
dateFormat,
|
|
57
|
+
l10n
|
|
58
|
+
} = di;
|
|
59
|
+
if (parseInputValue) {
|
|
60
|
+
return parseInputValue(date, dateFormat || defaultDateFormat);
|
|
61
|
+
}
|
|
62
|
+
return l10n.parseDate(date);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* There are multiple props that can change how the date is formatted.
|
|
67
|
+
* The priority of props used is:
|
|
68
|
+
* 1. `formatDisplayLabel`
|
|
69
|
+
* 2. `dateFormat`
|
|
70
|
+
* 3. `locale`
|
|
71
|
+
*/
|
|
72
|
+
export const formatDate = (value, di) => {
|
|
73
|
+
const {
|
|
74
|
+
formatDisplayLabel,
|
|
75
|
+
dateFormat,
|
|
76
|
+
l10n
|
|
77
|
+
} = di;
|
|
78
|
+
if (formatDisplayLabel) {
|
|
79
|
+
return formatDisplayLabel(value, dateFormat || defaultDateFormat);
|
|
80
|
+
}
|
|
81
|
+
const date = parseISO(value);
|
|
82
|
+
return dateFormat ? format(date, convertTokens(dateFormat)) : l10n.formatDate(date);
|
|
83
|
+
};
|
|
84
|
+
export const getPlaceholder = di => {
|
|
85
|
+
const {
|
|
86
|
+
placeholder,
|
|
87
|
+
l10n
|
|
88
|
+
} = di;
|
|
89
|
+
return placeholder || l10n.formatDate(placeholderDatetime);
|
|
90
|
+
};
|