@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.
@@ -1,10 +1,10 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
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 { createAndFireEvent, withAnalyticsContext, withAnalyticsEvents } from '@atlaskit/analytics-next';
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.2";
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 timePickerDefaultProps = {
28
- appearance: 'default',
29
- autoFocus: false,
30
- defaultIsOpen: false,
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 = withAnalyticsContext({
319
- componentName: 'timePicker',
320
- packageName,
321
- packageVersion
322
- })(withAnalyticsEvents({
323
- onChange: createAndFireEvent('atlaskit')({
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
- actionSubject: 'timePicker',
326
- attributes: {
327
- componentName: 'timePicker',
328
- packageName,
329
- packageVersion
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
- })(TimePickerComponent));
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
+ };