@instructure/ui-date-time-input 11.7.3-snapshot-29 → 11.7.3-snapshot-30

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,7 +3,7 @@
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
- ## [11.7.3-snapshot-29](https://github.com/instructure/instructure-ui/compare/v11.7.2...v11.7.3-snapshot-29) (2026-05-06)
6
+ ## [11.7.3-snapshot-30](https://github.com/instructure/instructure-ui/compare/v11.7.2...v11.7.3-snapshot-30) (2026-05-07)
7
7
 
8
8
 
9
9
  ### Bug Fixes
@@ -11,6 +11,32 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
11
11
  * **many:** update dependencies, remove lots of Babel plugins, remove Webpack 4 support ([f916fca](https://github.com/instructure/instructure-ui/commit/f916fcafdddcb2d7de401f93e8ff92cfdfa47bba))
12
12
 
13
13
 
14
+ ### Features
15
+
16
+ * **ui-select,ui-date-time-input:** rework DateTimeInput and replace DateInput v1 with DateInput v2 in DateTimeInput ([5a9405c](https://github.com/instructure/instructure-ui/commit/5a9405c0a19ea9fc6a34b2f34c60c582f96ec2af))
17
+
18
+
19
+ ### BREAKING CHANGES
20
+
21
+ * **ui-select,ui-date-time-input:** prevMonthLabel prop removed (use screenReaderLabels.prevMonthButton instead)
22
+
23
+ nextMonthLabel prop removed (use screenReaderLabels.nextMonthButton instead)
24
+
25
+ renderWeekdayLabels prop removed
26
+
27
+ dateFormat type changed: string → string
28
+
29
+ { parser: (input: string) => Date
30
+
31
+ null, formatter: (date: Date) => string }
32
+
33
+ screenReaderLabels is a new required prop
34
+
35
+ dateFormat default changed: Moment's 'LL' (long month name) → locale's default date format
36
+
37
+ INSTUI-4791
38
+
39
+
14
40
 
15
41
 
16
42
 
@@ -0,0 +1,343 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2018 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { Component } from 'react';
26
+ import { Locale, DateTime, ApplyLocaleContext } from '@instructure/ui-i18n';
27
+ import { FormFieldGroup } from '@instructure/ui-form-field/latest';
28
+ import { DateInput } from '@instructure/ui-date-input/latest';
29
+ import { TimeSelect } from '@instructure/ui-time-select/latest';
30
+ import { allowedProps } from "./props.js";
31
+ import { error } from '@instructure/console';
32
+
33
+ /**
34
+ ---
35
+ category: components
36
+ ---
37
+ **/
38
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
39
+ class DateTimeInput extends Component {
40
+ static displayName = "DateTimeInput";
41
+ // extra verbose localized date and time
42
+ static DEFAULT_MESSAGE_FORMAT = 'LLLL';
43
+ static allowedProps = allowedProps;
44
+ static defaultProps = {
45
+ layout: 'inline',
46
+ colSpacing: 'medium',
47
+ rowSpacing: 'small',
48
+ timeStep: 30,
49
+ showMessages: true,
50
+ messageFormat: DateTimeInput.DEFAULT_MESSAGE_FORMAT,
51
+ isRequired: false,
52
+ allowNonStepInput: false
53
+ };
54
+ static contextType = ApplyLocaleContext;
55
+ ref = null; // This is used by Tooltip for positioning
56
+
57
+ handleRef = el => {
58
+ this.ref = el;
59
+ };
60
+ constructor(props) {
61
+ super(props);
62
+ // State needs to be calculated because render could be called before
63
+ // componentDidMount()
64
+ this.state = this.recalculateState(props.value || props.defaultValue);
65
+ }
66
+ componentDidMount() {
67
+ // we'll need to recalculate the state because the context value is
68
+ // set at this point (and it might change locale & timezone)
69
+ const initState = this.recalculateState(this.props.value || this.props.defaultValue);
70
+ this.setState(initState);
71
+ this.props.reset?.(this.reset);
72
+ }
73
+ componentDidUpdate(prevProps) {
74
+ const valueChanged = prevProps.value !== this.props.value || prevProps.defaultValue !== this.props.defaultValue;
75
+ const isUpdated = valueChanged || prevProps.locale !== this.props.locale || prevProps.timezone !== this.props.timezone || prevProps.dateFormat !== this.props.dateFormat || prevProps.messageFormat !== this.props.messageFormat || prevProps.invalidDateTimeMessage !== this.props.invalidDateTimeMessage;
76
+ if (isUpdated) {
77
+ this.setState(_prevState => {
78
+ return {
79
+ ...this.recalculateState(this.props.value || this.props.defaultValue)
80
+ };
81
+ });
82
+ }
83
+ }
84
+ recalculateState(dateStr, doNotChangeDate = false, doNotChangeTime = false) {
85
+ let errorMsg;
86
+ if (dateStr) {
87
+ const parsed = DateTime.parse(dateStr, this.locale(), this.timezone());
88
+ if (parsed.isValid()) {
89
+ if (doNotChangeTime && this.state.timeSelectValue) {
90
+ // There is a selected time, adjust the parsed date to its value
91
+ const timeParsed = DateTime.parse(this.state.timeSelectValue, this.locale(), this.timezone());
92
+ parsed.hour(timeParsed.hour()).minute(timeParsed.minute());
93
+ }
94
+ if (doNotChangeDate && this.state.iso) {
95
+ parsed.date(this.state.iso.date()).month(this.state.iso.month()).year(this.state.iso.year());
96
+ }
97
+ if (this.props.initialTimeForNewDate && !this.state?.timeSelectValue) {
98
+ const hour = Number(this.props.initialTimeForNewDate.slice(0, 2));
99
+ const minute = Number(this.props.initialTimeForNewDate.slice(3, 5));
100
+ if (isNaN(hour) || isNaN(minute)) {
101
+ error(false, `[DateTimeInput] initialTimeForNewDate prop is not in the correct format. Please use HH:MM format.`);
102
+ } else if (hour < 0 || hour > 23 || minute > 59 || minute < 0) {
103
+ error(false, `[DateTimeInput] 0 <= hour < 24 and 0 <= minute < 60 for initialTimeForNewDate prop.`);
104
+ } else {
105
+ parsed.hour(hour).minute(minute);
106
+ }
107
+ }
108
+ const newTimeSelectValue = parsed.toISOString();
109
+ if (this.isDisabledDate(parsed)) {
110
+ let text = typeof this.props.disabledDateTimeMessage === 'function' ? this.props.disabledDateTimeMessage(parsed.toISOString(true)) : this.props.disabledDateTimeMessage;
111
+ if (!text) {
112
+ text = typeof this.props.invalidDateTimeMessage === 'function' ? this.props.invalidDateTimeMessage(parsed.toISOString(true)) : this.props.invalidDateTimeMessage;
113
+ }
114
+ errorMsg = text ? {
115
+ text,
116
+ type: 'error'
117
+ } : undefined;
118
+ return {
119
+ iso: parsed.clone(),
120
+ dateInputText: this.formatDateInput(parsed.toDate()),
121
+ message: errorMsg,
122
+ timeSelectValue: newTimeSelectValue
123
+ };
124
+ }
125
+ return {
126
+ iso: parsed.clone(),
127
+ dateInputText: this.formatDateInput(parsed.toDate()),
128
+ message: {
129
+ type: 'success',
130
+ text: parsed.format(this.props.messageFormat)
131
+ },
132
+ timeSelectValue: newTimeSelectValue
133
+ };
134
+ }
135
+ }
136
+ // if there is no date string clear TimeSelect value
137
+ const clearTimeSelect = dateStr ? {} : {
138
+ timeSelectValue: '',
139
+ message: undefined
140
+ };
141
+ return {
142
+ iso: undefined,
143
+ dateInputText: dateStr ? dateStr : '',
144
+ ...clearTimeSelect
145
+ };
146
+ }
147
+ reset = () => this.setState(this.recalculateState());
148
+ locale() {
149
+ if (this.props.locale) {
150
+ return this.props.locale;
151
+ } else if (this.context && this.context.locale) {
152
+ return this.context.locale;
153
+ }
154
+ return Locale.browserLocale();
155
+ }
156
+ timezone() {
157
+ if (this.props.timezone) {
158
+ return this.props.timezone;
159
+ } else if (this.context && this.context.timezone) {
160
+ return this.context.timezone;
161
+ }
162
+ return DateTime.browserTimeZone();
163
+ }
164
+ formatDateInput(date) {
165
+ const {
166
+ dateFormat
167
+ } = this.props;
168
+ if (typeof dateFormat !== 'string' && dateFormat?.formatter) {
169
+ return dateFormat.formatter(date);
170
+ }
171
+ return date.toLocaleDateString(typeof dateFormat === 'string' ? dateFormat : this.locale(), {
172
+ timeZone: this.timezone(),
173
+ calendar: 'gregory',
174
+ numberingSystem: 'latn'
175
+ });
176
+ }
177
+ isDisabledDate(date) {
178
+ const disabledDates = this.props.disabledDates;
179
+ if (!disabledDates) {
180
+ return false;
181
+ }
182
+ if (Array.isArray(disabledDates)) {
183
+ for (const aDisabledDate of disabledDates) {
184
+ if (date.isSame(aDisabledDate, 'day')) {
185
+ return true;
186
+ }
187
+ }
188
+ return false;
189
+ }
190
+ return disabledDates(date.toISOString());
191
+ }
192
+ handleDateTextChange = (_event, inputValue, _utcDateString) => {
193
+ this.setState({
194
+ dateInputText: inputValue
195
+ });
196
+ };
197
+ handleDateValidated = (event, _inputValue, utcDateString) => {
198
+ let newState;
199
+ if (utcDateString && this.state.timeSelectValue && (!this.state.dateInputText || this.state.dateInputText === '')) {
200
+ const timeParsed = DateTime.parse(this.state.timeSelectValue, this.locale(), this.timezone());
201
+ const dateParsed = DateTime.parse(utcDateString, this.locale(), this.timezone());
202
+ const dateParsedAdjusted = dateParsed.set({
203
+ hour: timeParsed.hour(),
204
+ minute: timeParsed.minute()
205
+ });
206
+ newState = this.recalculateState(dateParsedAdjusted.toISOString(), false, false);
207
+ } else if (!utcDateString) {
208
+ // invalid date — pass raw text so error message is shown
209
+ newState = this.recalculateState(this.state.dateInputText, false, true);
210
+ } else {
211
+ newState = this.recalculateState(utcDateString, false, true);
212
+ }
213
+ this.changeStateIfNeeded(newState, event);
214
+ };
215
+ updateStateBasedOnTimeSelect = (event, option) => {
216
+ // this.state.iso is undefined if date is invalid or not set.
217
+ // in this case recalculate with the dateInput's text which will result in
218
+ // an empty valid date (if isRequired is false) or an invalid date.
219
+ const newValue = this.state.iso ? option.value : this.state.dateInputText;
220
+ const newState = this.recalculateState(newValue, true, false);
221
+ this.changeStateIfNeeded(newState, event);
222
+ this.setState({
223
+ timeSelectValue: option.value
224
+ });
225
+ };
226
+ changeStateIfNeeded = (newState, e) => {
227
+ const dateStr = newState.dateInputText;
228
+ if (this.props.isRequired && !newState.iso || dateStr && dateStr.length > 0 && !newState.iso) {
229
+ const text = typeof this.props.invalidDateTimeMessage === 'function' ? this.props.invalidDateTimeMessage(dateStr ? dateStr : '') : this.props.invalidDateTimeMessage;
230
+ // eslint-disable-next-line no-param-reassign
231
+ newState.message = {
232
+ text: text,
233
+ type: 'error'
234
+ };
235
+ }
236
+ if (this.areDifferentDates(this.state.iso, newState.iso)) {
237
+ if (typeof this.props.onChange === 'function') {
238
+ const newDate = newState.iso?.toISOString();
239
+ // Timeout is needed here because users might change value in the
240
+ // onChange event lister, which might not execute properly
241
+ setTimeout(() => {
242
+ this.props.onChange?.(e, newDate);
243
+ }, 0);
244
+ }
245
+ }
246
+ this.setState(newState);
247
+ };
248
+ areDifferentDates = (d1, d2) => {
249
+ if (!d1 && !d2) {
250
+ return false;
251
+ }
252
+ return !d1 || !d2 || !d1.isSame(d2);
253
+ };
254
+ handleBlur = e => {
255
+ // when TABbing from the DateInput to TimeInput or visa-versa, the blur
256
+ // happens on the target before the relatedTarget gets focus.
257
+ // The timeout gives it a moment for that to happen
258
+ if (typeof this.props.onBlur === 'function') {
259
+ setTimeout(() => {
260
+ this.props.onBlur?.(e);
261
+ }, 0);
262
+ }
263
+ };
264
+ render() {
265
+ const {
266
+ description,
267
+ datePlaceholder,
268
+ timePlaceholder,
269
+ dateRenderLabel,
270
+ dateInputRef,
271
+ timeRenderLabel,
272
+ timeFormat,
273
+ timeStep,
274
+ timeInputRef,
275
+ locale,
276
+ timezone,
277
+ showMessages,
278
+ messages,
279
+ layout,
280
+ rowSpacing,
281
+ colSpacing,
282
+ isRequired,
283
+ interaction,
284
+ allowNonStepInput,
285
+ screenReaderLabels,
286
+ disabledDates,
287
+ withYearPicker
288
+ } = this.props;
289
+ const allMessages = [...(showMessages && this.state.message ? [this.state.message] : []), ...(messages || [])];
290
+ const hasError = allMessages.find(m => m.type === 'newError' || m.type === 'error');
291
+ // if the component is in error state, create an empty error message to pass down to the subcomponents (DateInput and TimeInput) so they get a red outline and red required asterisk
292
+ const subComponentMessages = hasError ? [{
293
+ type: 'error',
294
+ text: ''
295
+ }] : [];
296
+ return _jsxs(FormFieldGroup, {
297
+ description: description,
298
+ layout: layout,
299
+ rowSpacing: rowSpacing,
300
+ colSpacing: colSpacing,
301
+ vAlign: "top",
302
+ elementRef: this.handleRef,
303
+ isGroup: false,
304
+ messages: [...(showMessages && this.state.message ? [this.state.message] : []), ...(messages || [])],
305
+ "data-cid": "DateTimeInput",
306
+ children: [_jsx(DateInput, {
307
+ renderLabel: dateRenderLabel,
308
+ screenReaderLabels: screenReaderLabels,
309
+ withYearPicker: withYearPicker,
310
+ value: this.state.dateInputText,
311
+ onChange: this.handleDateTextChange,
312
+ onRequestValidateDate: this.handleDateValidated,
313
+ onBlur: e => this.handleBlur(e),
314
+ inputRef: dateInputRef,
315
+ placeholder: datePlaceholder,
316
+ isRequired: isRequired,
317
+ messages: subComponentMessages,
318
+ interaction: interaction,
319
+ locale: locale,
320
+ timezone: timezone,
321
+ disabledDates: disabledDates,
322
+ dateFormat: this.props.dateFormat
323
+ }), _jsx(TimeSelect, {
324
+ value: this.state.timeSelectValue,
325
+ onChange: this.updateStateBasedOnTimeSelect,
326
+ placeholder: timePlaceholder,
327
+ onBlur: this.handleBlur,
328
+ renderLabel: timeRenderLabel,
329
+ locale: locale,
330
+ format: timeFormat,
331
+ step: timeStep,
332
+ timezone: timezone,
333
+ inputRef: timeInputRef,
334
+ interaction: interaction,
335
+ allowNonStepInput: allowNonStepInput,
336
+ isRequired: isRequired,
337
+ messages: subComponentMessages
338
+ })]
339
+ });
340
+ }
341
+ }
342
+ export default DateTimeInput;
343
+ export { DateTimeInput };
@@ -0,0 +1,26 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ const allowedProps = ['description', 'dateRenderLabel', 'screenReaderLabels', 'datePlaceholder', 'timePlaceholder', 'dateFormat', 'interaction', 'timeRenderLabel', 'timeStep', 'timeFormat', 'locale', 'timezone', 'invalidDateTimeMessage', 'showMessages', 'messages', 'messageFormat', 'layout', 'rowSpacing', 'colSpacing', 'value', 'defaultValue', 'isRequired', 'onChange', 'dateInputRef', 'timeInputRef', 'onBlur', 'disabledDates', 'disabledDateTimeMessage', 'allowNonStepInput', 'reset', 'withYearPicker'];
26
+ export { allowedProps };
@@ -0,0 +1,24 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ export { DateTimeInput } from "../DateTimeInput/v2/index.js";