@instructure/ui-date-input 9.5.2 → 9.6.1-pr-snapshot-1726659472372

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 CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [9.6.1-pr-snapshot-1726659472372](https://github.com/instructure/instructure-ui/compare/v9.6.0...v9.6.1-pr-snapshot-1726659472372) (2024-09-18)
7
+
8
+
9
+ ### Features
10
+
11
+ * **ui-date-input:** improve DateInput2 api, extend docs ([27bc27f](https://github.com/instructure/instructure-ui/commit/27bc27f8a5e18cf3cabee8b5ea18f75629728ab6))
12
+
13
+
14
+
15
+
16
+
17
+ # [9.6.0](https://github.com/instructure/instructure-ui/compare/v9.5.2...v9.6.0) (2024-08-14)
18
+
19
+
20
+ ### Features
21
+
22
+ * **ui-calendar,ui-date-input:** improve DateInput2 onChange callback, add date formatting option, extend docs ([4e2c23c](https://github.com/instructure/instructure-ui/commit/4e2c23c3288885e49030f1f471d61b2fed29b54c))
23
+
24
+
25
+
26
+
27
+
6
28
  ## [9.5.2](https://github.com/instructure/instructure-ui/compare/v9.5.1...v9.5.2) (2024-08-05)
7
29
 
8
30
 
@@ -35,23 +35,85 @@ import { passthroughProps } from '@instructure/ui-react-utils';
35
35
  import { ApplyLocaleContext, Locale } from '@instructure/ui-i18n';
36
36
  import { jsx } from '@instructure/emotion';
37
37
  import { propTypes } from './props';
38
- function parseDate(dateString) {
39
- const date = new Date(dateString);
40
- // return empty string if not a valid date
41
- return isNaN(date.getTime()) ? '' : date.toISOString();
38
+ function parseLocaleDate(dateString = '', locale, timeZone) {
39
+ // This function may seem complicated but it basically does one thing:
40
+ // Given a dateString, a locale and a timeZone. The dateString is assumed to be formatted according
41
+ // to the locale. So if the locale is `en-us` the dateString is expected to be in the format of M/D/YYYY.
42
+ // The dateString is also assumed to be in the given timeZone, so "1/1/2020" in "America/Los_Angeles" timezone is
43
+ // expected to be "2020-01-01T08:00:00.000Z" in UTC time.
44
+ // This function tries to parse the dateString taking these variables into account and return a javascript Date object
45
+ // that is adjusted to be in UTC.
46
+
47
+ // Split string on '.', whitespace, '/', ',' or '-' using regex: /[.\s/.-]+/.
48
+ // The '+' allows splitting on consecutive delimiters.
49
+ // `.filter(Boolean)` is needed because some locales have a delimeter at the end (e.g.: "hu-hu")
50
+ const splitDate = dateString.split(/[,.\s/.-]+/).filter(Boolean);
51
+
52
+ // create a locale formatted new date to later extract the order and delimeter information
53
+ const localeDate = new Intl.DateTimeFormat(locale).formatToParts(new Date());
54
+ let index = 0;
55
+ let day, month, year;
56
+ localeDate.forEach(part => {
57
+ if (part.type === 'month') {
58
+ month = parseInt(splitDate[index], 10);
59
+ index++;
60
+ } else if (part.type === 'day') {
61
+ day = parseInt(splitDate[index], 10);
62
+ index++;
63
+ } else if (part.type === 'year') {
64
+ year = parseInt(splitDate[index], 10);
65
+ index++;
66
+ }
67
+ });
68
+
69
+ // sensible year limitations
70
+ if (!year || !month || !day || year < 1000 || year > 9999) return null;
71
+
72
+ // create utc date from year, month (zero indexed) and day
73
+ const date = new Date(Date.UTC(year, month - 1, day));
74
+
75
+ // Format date string in the provided timezone
76
+ const parts = new Intl.DateTimeFormat('en-US', {
77
+ timeZone,
78
+ year: 'numeric',
79
+ month: '2-digit',
80
+ day: '2-digit',
81
+ hour: '2-digit',
82
+ minute: '2-digit',
83
+ second: '2-digit',
84
+ hour12: false
85
+ }).formatToParts(date);
86
+
87
+ // Extract the date and time parts from the formatted string
88
+ const dateStringInTimezone = parts.reduce((acc, part) => {
89
+ return part.type === 'literal' ? acc : {
90
+ ...acc,
91
+ [part.type]: part.value
92
+ };
93
+ }, {});
94
+
95
+ // Create a date string in the format 'YYYY-MM-DDTHH:mm:ss'
96
+ const dateInTimezone = `${dateStringInTimezone.year}-${dateStringInTimezone.month}-${dateStringInTimezone.day}T${dateStringInTimezone.hour}:${dateStringInTimezone.minute}:${dateStringInTimezone.second}`;
97
+
98
+ // Calculate time difference for timezone offset
99
+ const timeDiff = new Date(dateInTimezone + 'Z').getTime() - date.getTime();
100
+ const newTime = new Date(date.getTime() - timeDiff);
101
+ // Return the UTC Date corresponding to the time in the specified timezone
102
+ return newTime;
42
103
  }
43
104
 
44
105
  /**
45
106
  ---
46
107
  category: components
47
108
  ---
109
+
110
+ @module experimental
48
111
  **/
49
112
  const DateInput2 = ({
50
113
  renderLabel,
51
114
  screenReaderLabels,
52
115
  isRequired = false,
53
116
  interaction = 'enabled',
54
- size = 'medium',
55
117
  isInline = false,
56
118
  value,
57
119
  messages,
@@ -59,76 +121,23 @@ const DateInput2 = ({
59
121
  onChange,
60
122
  onBlur,
61
123
  withYearPicker,
62
- onRequestValidateDate,
63
124
  invalidDateErrorMessage,
64
125
  locale,
65
126
  timezone,
66
127
  placeholder,
128
+ dateFormat,
129
+ onRequestValidateDate,
130
+ // margin, TODO enable this prop
67
131
  ...rest
68
132
  }) => {
69
- const _useState = useState(''),
70
- _useState2 = _slicedToArray(_useState, 2),
71
- selectedDate = _useState2[0],
72
- setSelectedDate = _useState2[1];
73
- const _useState3 = useState(messages || []),
74
- _useState4 = _slicedToArray(_useState3, 2),
75
- inputMessages = _useState4[0],
76
- setInputMessages = _useState4[1];
77
- const _useState5 = useState(false),
78
- _useState6 = _slicedToArray(_useState5, 2),
79
- showPopover = _useState6[0],
80
- setShowPopover = _useState6[1];
81
133
  const localeContext = useContext(ApplyLocaleContext);
82
- useEffect(() => {
83
- // when `value` is changed, validation runs again and removes the error message if validation passes
84
- // but it's NOT adding error message if validation fails for better UX
85
- validateInput(true);
86
- }, [value]);
87
- useEffect(() => {
88
- setInputMessages(messages || []);
89
- }, [messages]);
90
- const handleInputChange = (e, value) => {
91
- onChange === null || onChange === void 0 ? void 0 : onChange(e, value);
92
- // blur event formats the input which should trigger parsing
93
- if (e.type !== 'blur') {
94
- setSelectedDate(parseDate(value));
95
- }
96
- };
97
- const handleDateSelected = (dateString, _momentDate, e) => {
98
- const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
99
- month: 'long',
100
- year: 'numeric',
101
- day: 'numeric',
102
- timeZone: getTimezone()
103
- });
104
- handleInputChange(e, formattedDate);
105
- setSelectedDate(dateString);
106
- setShowPopover(false);
107
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
108
- };
109
-
110
- // onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
111
- const validateInput = (onlyRemoveError = false) => {
112
- // don't validate empty input
113
- if (!value || parseDate(value) || selectedDate) {
114
- setInputMessages(messages || []);
115
- return true;
116
- }
117
- // only show error if there is no user provided validation callback
118
- if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string' && !onRequestValidateDate) {
119
- setInputMessages([{
120
- type: 'error',
121
- text: invalidDateErrorMessage
122
- }]);
123
- }
124
- return false;
125
- };
126
134
  const getLocale = () => {
127
135
  if (locale) {
128
136
  return locale;
129
137
  } else if (localeContext.locale) {
130
138
  return localeContext.locale;
131
139
  }
140
+ // default to the system's locale
132
141
  return Locale.browserLocale();
133
142
  };
134
143
  const getTimezone = () => {
@@ -140,21 +149,86 @@ const DateInput2 = ({
140
149
  // default to the system's timezone
141
150
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
142
151
  };
152
+ const _useState = useState(messages || []),
153
+ _useState2 = _slicedToArray(_useState, 2),
154
+ inputMessages = _useState2[0],
155
+ setInputMessages = _useState2[1];
156
+ const _useState3 = useState(false),
157
+ _useState4 = _slicedToArray(_useState3, 2),
158
+ showPopover = _useState4[0],
159
+ setShowPopover = _useState4[1];
160
+ useEffect(() => {
161
+ setInputMessages(messages || []);
162
+ }, [messages]);
163
+ useEffect(() => {
164
+ const _parseDate = parseDate(value),
165
+ _parseDate2 = _slicedToArray(_parseDate, 2),
166
+ utcIsoDate = _parseDate2[1];
167
+ // clear error messages if date becomes valid
168
+ if (utcIsoDate || !value) {
169
+ setInputMessages(messages || []);
170
+ }
171
+ }, [value]);
172
+ const parseDate = (dateString = '') => {
173
+ let date = null;
174
+ if (dateFormat) {
175
+ if (typeof dateFormat === 'string') {
176
+ // use dateFormat instead of the user locale
177
+ date = parseLocaleDate(dateString, dateFormat, getTimezone());
178
+ } else if (dateFormat.parser) {
179
+ date = dateFormat.parser(dateString);
180
+ }
181
+ } else {
182
+ // no dateFormat prop passed, use locale for formatting
183
+ date = parseLocaleDate(dateString, getLocale(), getTimezone());
184
+ }
185
+ return date ? [formatDate(date), date.toISOString()] : ['', ''];
186
+ };
187
+ const formatDate = date => {
188
+ // use formatter function if provided
189
+ if (typeof dateFormat !== 'string' && dateFormat !== null && dateFormat !== void 0 && dateFormat.formatter) {
190
+ return dateFormat.formatter(date);
191
+ }
192
+ // if dateFormat set to a locale, use that, otherwise default to the user's locale
193
+ return date.toLocaleDateString(typeof dateFormat === 'string' ? dateFormat : getLocale(), {
194
+ timeZone: getTimezone(),
195
+ calendar: 'gregory',
196
+ numberingSystem: 'latn'
197
+ });
198
+ };
199
+ const handleInputChange = (e, newValue) => {
200
+ const _parseDate3 = parseDate(newValue),
201
+ _parseDate4 = _slicedToArray(_parseDate3, 2),
202
+ utcIsoDate = _parseDate4[1];
203
+ onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, utcIsoDate);
204
+ };
205
+ const handleDateSelected = (dateString, _momentDate, e) => {
206
+ setShowPopover(false);
207
+ const newValue = formatDate(new Date(dateString));
208
+ onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, dateString);
209
+ onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, newValue, dateString);
210
+ };
143
211
  const handleBlur = e => {
144
- const isInputValid = validateInput(false);
145
- if (isInputValid && selectedDate) {
146
- const formattedDate = new Date(selectedDate).toLocaleDateString(getLocale(), {
147
- month: 'long',
148
- year: 'numeric',
149
- day: 'numeric',
150
- timeZone: getTimezone()
151
- });
152
- handleInputChange(e, formattedDate);
212
+ const _parseDate5 = parseDate(value),
213
+ _parseDate6 = _slicedToArray(_parseDate5, 2),
214
+ localeDate = _parseDate6[0],
215
+ utcIsoDate = _parseDate6[1];
216
+ if (localeDate) {
217
+ if (localeDate !== value) {
218
+ onChange === null || onChange === void 0 ? void 0 : onChange(e, localeDate, utcIsoDate);
219
+ }
220
+ } else if (value && invalidDateErrorMessage) {
221
+ setInputMessages([{
222
+ type: 'error',
223
+ text: invalidDateErrorMessage
224
+ }]);
153
225
  }
154
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value, isInputValid);
155
- onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
226
+ onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, value || '', utcIsoDate);
227
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(e, value || '', utcIsoDate);
156
228
  };
229
+ const selectedDate = parseDate(value)[1];
157
230
  return jsx(TextInput, Object.assign({}, passthroughProps(rest), {
231
+ // margin={'large'} TODO add this prop to TextInput
158
232
  renderLabel: renderLabel,
159
233
  onChange: handleInputChange,
160
234
  onBlur: handleBlur,
@@ -162,7 +236,6 @@ const DateInput2 = ({
162
236
  value: value,
163
237
  placeholder: placeholder,
164
238
  width: width,
165
- size: size,
166
239
  display: isInline ? 'inline-block' : 'block',
167
240
  messages: inputMessages,
168
241
  interaction: interaction,
@@ -172,7 +245,6 @@ const DateInput2 = ({
172
245
  withBorder: false,
173
246
  screenReaderLabel: screenReaderLabels.calendarIcon,
174
247
  shape: "circle",
175
- size: size,
176
248
  interaction: interaction
177
249
  }, _IconCalendarMonthLin || (_IconCalendarMonthLin = jsx(IconCalendarMonthLine, null))),
178
250
  isShowingContent: showPopover,
@@ -187,8 +259,8 @@ const DateInput2 = ({
187
259
  onDateSelected: handleDateSelected,
188
260
  selectedDate: selectedDate,
189
261
  visibleMonth: selectedDate,
190
- locale: locale,
191
- timezone: timezone,
262
+ locale: getLocale(),
263
+ timezone: getTimezone(),
192
264
  role: "listbox",
193
265
  renderNextMonthButton: jsx(IconButton, {
194
266
  size: "small",
@@ -29,7 +29,6 @@ const propTypes = {
29
29
  renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
30
30
  screenReaderLabels: PropTypes.object.isRequired,
31
31
  value: controllable(PropTypes.string),
32
- size: PropTypes.oneOf(['small', 'medium', 'large']),
33
32
  placeholder: PropTypes.string,
34
33
  onChange: PropTypes.func,
35
34
  onBlur: PropTypes.func,
@@ -38,12 +37,11 @@ const propTypes = {
38
37
  isInline: PropTypes.bool,
39
38
  width: PropTypes.string,
40
39
  messages: PropTypes.arrayOf(FormPropTypes.message),
41
- onRequestShowCalendar: PropTypes.func,
42
- onRequestHideCalendar: PropTypes.func,
43
- onRequestValidateDate: PropTypes.func,
44
40
  invalidDateErrorMessage: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
45
41
  locale: PropTypes.string,
46
42
  timezone: PropTypes.string,
47
- withYearPicker: PropTypes.object
43
+ withYearPicker: PropTypes.object,
44
+ dateFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
45
+ onRequestValidateDate: PropTypes.func
48
46
  };
49
47
  export { propTypes };
@@ -44,23 +44,85 @@ var _IconCalendarMonthLin, _IconArrowOpenEndSoli, _IconArrowOpenStartSo;
44
44
  * SOFTWARE.
45
45
  */
46
46
  /** @jsx jsx */
47
- function parseDate(dateString) {
48
- const date = new Date(dateString);
49
- // return empty string if not a valid date
50
- return isNaN(date.getTime()) ? '' : date.toISOString();
47
+ function parseLocaleDate(dateString = '', locale, timeZone) {
48
+ // This function may seem complicated but it basically does one thing:
49
+ // Given a dateString, a locale and a timeZone. The dateString is assumed to be formatted according
50
+ // to the locale. So if the locale is `en-us` the dateString is expected to be in the format of M/D/YYYY.
51
+ // The dateString is also assumed to be in the given timeZone, so "1/1/2020" in "America/Los_Angeles" timezone is
52
+ // expected to be "2020-01-01T08:00:00.000Z" in UTC time.
53
+ // This function tries to parse the dateString taking these variables into account and return a javascript Date object
54
+ // that is adjusted to be in UTC.
55
+
56
+ // Split string on '.', whitespace, '/', ',' or '-' using regex: /[.\s/.-]+/.
57
+ // The '+' allows splitting on consecutive delimiters.
58
+ // `.filter(Boolean)` is needed because some locales have a delimeter at the end (e.g.: "hu-hu")
59
+ const splitDate = dateString.split(/[,.\s/.-]+/).filter(Boolean);
60
+
61
+ // create a locale formatted new date to later extract the order and delimeter information
62
+ const localeDate = new Intl.DateTimeFormat(locale).formatToParts(new Date());
63
+ let index = 0;
64
+ let day, month, year;
65
+ localeDate.forEach(part => {
66
+ if (part.type === 'month') {
67
+ month = parseInt(splitDate[index], 10);
68
+ index++;
69
+ } else if (part.type === 'day') {
70
+ day = parseInt(splitDate[index], 10);
71
+ index++;
72
+ } else if (part.type === 'year') {
73
+ year = parseInt(splitDate[index], 10);
74
+ index++;
75
+ }
76
+ });
77
+
78
+ // sensible year limitations
79
+ if (!year || !month || !day || year < 1000 || year > 9999) return null;
80
+
81
+ // create utc date from year, month (zero indexed) and day
82
+ const date = new Date(Date.UTC(year, month - 1, day));
83
+
84
+ // Format date string in the provided timezone
85
+ const parts = new Intl.DateTimeFormat('en-US', {
86
+ timeZone,
87
+ year: 'numeric',
88
+ month: '2-digit',
89
+ day: '2-digit',
90
+ hour: '2-digit',
91
+ minute: '2-digit',
92
+ second: '2-digit',
93
+ hour12: false
94
+ }).formatToParts(date);
95
+
96
+ // Extract the date and time parts from the formatted string
97
+ const dateStringInTimezone = parts.reduce((acc, part) => {
98
+ return part.type === 'literal' ? acc : {
99
+ ...acc,
100
+ [part.type]: part.value
101
+ };
102
+ }, {});
103
+
104
+ // Create a date string in the format 'YYYY-MM-DDTHH:mm:ss'
105
+ const dateInTimezone = `${dateStringInTimezone.year}-${dateStringInTimezone.month}-${dateStringInTimezone.day}T${dateStringInTimezone.hour}:${dateStringInTimezone.minute}:${dateStringInTimezone.second}`;
106
+
107
+ // Calculate time difference for timezone offset
108
+ const timeDiff = new Date(dateInTimezone + 'Z').getTime() - date.getTime();
109
+ const newTime = new Date(date.getTime() - timeDiff);
110
+ // Return the UTC Date corresponding to the time in the specified timezone
111
+ return newTime;
51
112
  }
52
113
 
53
114
  /**
54
115
  ---
55
116
  category: components
56
117
  ---
118
+
119
+ @module experimental
57
120
  **/
58
121
  const DateInput2 = ({
59
122
  renderLabel,
60
123
  screenReaderLabels,
61
124
  isRequired = false,
62
125
  interaction = 'enabled',
63
- size = 'medium',
64
126
  isInline = false,
65
127
  value,
66
128
  messages,
@@ -68,76 +130,23 @@ const DateInput2 = ({
68
130
  onChange,
69
131
  onBlur,
70
132
  withYearPicker,
71
- onRequestValidateDate,
72
133
  invalidDateErrorMessage,
73
134
  locale,
74
135
  timezone,
75
136
  placeholder,
137
+ dateFormat,
138
+ onRequestValidateDate,
139
+ // margin, TODO enable this prop
76
140
  ...rest
77
141
  }) => {
78
- const _useState = (0, _react.useState)(''),
79
- _useState2 = (0, _slicedToArray2.default)(_useState, 2),
80
- selectedDate = _useState2[0],
81
- setSelectedDate = _useState2[1];
82
- const _useState3 = (0, _react.useState)(messages || []),
83
- _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
84
- inputMessages = _useState4[0],
85
- setInputMessages = _useState4[1];
86
- const _useState5 = (0, _react.useState)(false),
87
- _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
88
- showPopover = _useState6[0],
89
- setShowPopover = _useState6[1];
90
142
  const localeContext = (0, _react.useContext)(_ApplyLocaleContext.ApplyLocaleContext);
91
- (0, _react.useEffect)(() => {
92
- // when `value` is changed, validation runs again and removes the error message if validation passes
93
- // but it's NOT adding error message if validation fails for better UX
94
- validateInput(true);
95
- }, [value]);
96
- (0, _react.useEffect)(() => {
97
- setInputMessages(messages || []);
98
- }, [messages]);
99
- const handleInputChange = (e, value) => {
100
- onChange === null || onChange === void 0 ? void 0 : onChange(e, value);
101
- // blur event formats the input which should trigger parsing
102
- if (e.type !== 'blur') {
103
- setSelectedDate(parseDate(value));
104
- }
105
- };
106
- const handleDateSelected = (dateString, _momentDate, e) => {
107
- const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
108
- month: 'long',
109
- year: 'numeric',
110
- day: 'numeric',
111
- timeZone: getTimezone()
112
- });
113
- handleInputChange(e, formattedDate);
114
- setSelectedDate(dateString);
115
- setShowPopover(false);
116
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
117
- };
118
-
119
- // onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
120
- const validateInput = (onlyRemoveError = false) => {
121
- // don't validate empty input
122
- if (!value || parseDate(value) || selectedDate) {
123
- setInputMessages(messages || []);
124
- return true;
125
- }
126
- // only show error if there is no user provided validation callback
127
- if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string' && !onRequestValidateDate) {
128
- setInputMessages([{
129
- type: 'error',
130
- text: invalidDateErrorMessage
131
- }]);
132
- }
133
- return false;
134
- };
135
143
  const getLocale = () => {
136
144
  if (locale) {
137
145
  return locale;
138
146
  } else if (localeContext.locale) {
139
147
  return localeContext.locale;
140
148
  }
149
+ // default to the system's locale
141
150
  return _Locale.Locale.browserLocale();
142
151
  };
143
152
  const getTimezone = () => {
@@ -149,21 +158,86 @@ const DateInput2 = ({
149
158
  // default to the system's timezone
150
159
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
151
160
  };
161
+ const _useState = (0, _react.useState)(messages || []),
162
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
163
+ inputMessages = _useState2[0],
164
+ setInputMessages = _useState2[1];
165
+ const _useState3 = (0, _react.useState)(false),
166
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
167
+ showPopover = _useState4[0],
168
+ setShowPopover = _useState4[1];
169
+ (0, _react.useEffect)(() => {
170
+ setInputMessages(messages || []);
171
+ }, [messages]);
172
+ (0, _react.useEffect)(() => {
173
+ const _parseDate = parseDate(value),
174
+ _parseDate2 = (0, _slicedToArray2.default)(_parseDate, 2),
175
+ utcIsoDate = _parseDate2[1];
176
+ // clear error messages if date becomes valid
177
+ if (utcIsoDate || !value) {
178
+ setInputMessages(messages || []);
179
+ }
180
+ }, [value]);
181
+ const parseDate = (dateString = '') => {
182
+ let date = null;
183
+ if (dateFormat) {
184
+ if (typeof dateFormat === 'string') {
185
+ // use dateFormat instead of the user locale
186
+ date = parseLocaleDate(dateString, dateFormat, getTimezone());
187
+ } else if (dateFormat.parser) {
188
+ date = dateFormat.parser(dateString);
189
+ }
190
+ } else {
191
+ // no dateFormat prop passed, use locale for formatting
192
+ date = parseLocaleDate(dateString, getLocale(), getTimezone());
193
+ }
194
+ return date ? [formatDate(date), date.toISOString()] : ['', ''];
195
+ };
196
+ const formatDate = date => {
197
+ // use formatter function if provided
198
+ if (typeof dateFormat !== 'string' && dateFormat !== null && dateFormat !== void 0 && dateFormat.formatter) {
199
+ return dateFormat.formatter(date);
200
+ }
201
+ // if dateFormat set to a locale, use that, otherwise default to the user's locale
202
+ return date.toLocaleDateString(typeof dateFormat === 'string' ? dateFormat : getLocale(), {
203
+ timeZone: getTimezone(),
204
+ calendar: 'gregory',
205
+ numberingSystem: 'latn'
206
+ });
207
+ };
208
+ const handleInputChange = (e, newValue) => {
209
+ const _parseDate3 = parseDate(newValue),
210
+ _parseDate4 = (0, _slicedToArray2.default)(_parseDate3, 2),
211
+ utcIsoDate = _parseDate4[1];
212
+ onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, utcIsoDate);
213
+ };
214
+ const handleDateSelected = (dateString, _momentDate, e) => {
215
+ setShowPopover(false);
216
+ const newValue = formatDate(new Date(dateString));
217
+ onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, dateString);
218
+ onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, newValue, dateString);
219
+ };
152
220
  const handleBlur = e => {
153
- const isInputValid = validateInput(false);
154
- if (isInputValid && selectedDate) {
155
- const formattedDate = new Date(selectedDate).toLocaleDateString(getLocale(), {
156
- month: 'long',
157
- year: 'numeric',
158
- day: 'numeric',
159
- timeZone: getTimezone()
160
- });
161
- handleInputChange(e, formattedDate);
221
+ const _parseDate5 = parseDate(value),
222
+ _parseDate6 = (0, _slicedToArray2.default)(_parseDate5, 2),
223
+ localeDate = _parseDate6[0],
224
+ utcIsoDate = _parseDate6[1];
225
+ if (localeDate) {
226
+ if (localeDate !== value) {
227
+ onChange === null || onChange === void 0 ? void 0 : onChange(e, localeDate, utcIsoDate);
228
+ }
229
+ } else if (value && invalidDateErrorMessage) {
230
+ setInputMessages([{
231
+ type: 'error',
232
+ text: invalidDateErrorMessage
233
+ }]);
162
234
  }
163
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value, isInputValid);
164
- onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
235
+ onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, value || '', utcIsoDate);
236
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(e, value || '', utcIsoDate);
165
237
  };
238
+ const selectedDate = parseDate(value)[1];
166
239
  return (0, _emotion.jsx)(_TextInput.TextInput, Object.assign({}, (0, _passthroughProps.passthroughProps)(rest), {
240
+ // margin={'large'} TODO add this prop to TextInput
167
241
  renderLabel: renderLabel,
168
242
  onChange: handleInputChange,
169
243
  onBlur: handleBlur,
@@ -171,7 +245,6 @@ const DateInput2 = ({
171
245
  value: value,
172
246
  placeholder: placeholder,
173
247
  width: width,
174
- size: size,
175
248
  display: isInline ? 'inline-block' : 'block',
176
249
  messages: inputMessages,
177
250
  interaction: interaction,
@@ -181,7 +254,6 @@ const DateInput2 = ({
181
254
  withBorder: false,
182
255
  screenReaderLabel: screenReaderLabels.calendarIcon,
183
256
  shape: "circle",
184
- size: size,
185
257
  interaction: interaction
186
258
  }, _IconCalendarMonthLin || (_IconCalendarMonthLin = (0, _emotion.jsx)(_IconCalendarMonthLine.IconCalendarMonthLine, null))),
187
259
  isShowingContent: showPopover,
@@ -196,8 +268,8 @@ const DateInput2 = ({
196
268
  onDateSelected: handleDateSelected,
197
269
  selectedDate: selectedDate,
198
270
  visibleMonth: selectedDate,
199
- locale: locale,
200
- timezone: timezone,
271
+ locale: getLocale(),
272
+ timezone: getTimezone(),
201
273
  role: "listbox",
202
274
  renderNextMonthButton: (0, _emotion.jsx)(_IconButton.IconButton, {
203
275
  size: "small",
@@ -36,7 +36,6 @@ const propTypes = exports.propTypes = {
36
36
  renderLabel: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.func]).isRequired,
37
37
  screenReaderLabels: _propTypes.default.object.isRequired,
38
38
  value: (0, _controllable.controllable)(_propTypes.default.string),
39
- size: _propTypes.default.oneOf(['small', 'medium', 'large']),
40
39
  placeholder: _propTypes.default.string,
41
40
  onChange: _propTypes.default.func,
42
41
  onBlur: _propTypes.default.func,
@@ -45,11 +44,10 @@ const propTypes = exports.propTypes = {
45
44
  isInline: _propTypes.default.bool,
46
45
  width: _propTypes.default.string,
47
46
  messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message),
48
- onRequestShowCalendar: _propTypes.default.func,
49
- onRequestHideCalendar: _propTypes.default.func,
50
- onRequestValidateDate: _propTypes.default.func,
51
47
  invalidDateErrorMessage: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
52
48
  locale: _propTypes.default.string,
53
49
  timezone: _propTypes.default.string,
54
- withYearPicker: _propTypes.default.object
50
+ withYearPicker: _propTypes.default.object,
51
+ dateFormat: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]),
52
+ onRequestValidateDate: _propTypes.default.func
55
53
  };