@instructure/ui-date-time-input 9.10.1 → 9.11.0

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,26 @@
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.11.0](https://github.com/instructure/instructure-ui/compare/v9.10.2...v9.11.0) (2025-02-12)
7
+
8
+ **Note:** Version bump only for package @instructure/ui-date-time-input
9
+
10
+
11
+
12
+
13
+
14
+ ## [9.10.2](https://github.com/instructure/instructure-ui/compare/v9.10.1...v9.10.2) (2024-12-19)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * **ui-date-time-input,ui-form-field:** make DateTimeInput compatible with the new error format ([064a1a9](https://github.com/instructure/instructure-ui/commit/064a1a9e581660dfd2804d35f11d6ae514875614)), closes [#1826](https://github.com/instructure/instructure-ui/issues/1826)
20
+ * **ui-date-time-input:** fix DateTimeInput displaying wrong value of its value is changed in a onChange callback ([5cc4692](https://github.com/instructure/instructure-ui/commit/5cc4692a0273aa4a0f1c9e46cb170b3e825eaefb)), closes [#1829](https://github.com/instructure/instructure-ui/issues/1829)
21
+
22
+
23
+
24
+
25
+
6
26
  ## [9.10.1](https://github.com/instructure/instructure-ui/compare/v9.10.0...v9.10.1) (2024-12-09)
7
27
 
8
28
  **Note:** Version bump only for package @instructure/ui-date-time-input
@@ -143,7 +143,7 @@ describe('<DateTimeInput />', () => {
143
143
  invalidDateTimeMessage: invalidDateTimeMessage
144
144
  })),
145
145
  container = _render.container;
146
- const timeInput = screen.getByLabelText('time-input');
146
+ const timeInput = screen.getByLabelText('time-input *');
147
147
  await userEvent.type(timeInput, '1:00 PM');
148
148
  fireEvent.blur(timeInput);
149
149
  await waitFor(() => {
@@ -75,7 +75,7 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
75
75
  event.persist();
76
76
  // timeout is needed here because handleDayClick could be called in the same
77
77
  // frame, and it updates calendarSelectedDate which is read in here.
78
- window.setTimeout(() => {
78
+ setTimeout(() => {
79
79
  if (event.key === 'Enter') {
80
80
  // user pressed enter, use the selected value in the calendar
81
81
  this.updateStateBasedOnDateInput(this.state.calendarSelectedDate, event);
@@ -108,12 +108,21 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
108
108
  // eslint-disable-next-line no-param-reassign
109
109
  newState.message = {
110
110
  text: text,
111
- type: 'error'
111
+ type: 'newError'
112
112
  };
113
113
  }
114
114
  if (this.areDifferentDates(this.state.iso, newState.iso)) {
115
- var _this$props$onChange, _this$props, _newState$iso;
116
- (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, e, (_newState$iso = newState.iso) === null || _newState$iso === void 0 ? void 0 : _newState$iso.toISOString());
115
+ if (typeof this.props.onChange === 'function') {
116
+ var _newState$iso;
117
+ const newDate = (_newState$iso = newState.iso) === null || _newState$iso === void 0 ? void 0 : _newState$iso.toISOString();
118
+ // Timeout is needed here because users might change value in the
119
+ // onChange event lister, which might not execute properly because
120
+ // the state change is not complete
121
+ setTimeout(() => {
122
+ var _this$props$onChange, _this$props;
123
+ (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, e, newDate);
124
+ }, 0);
125
+ }
117
126
  }
118
127
  this.setState(newState);
119
128
  };
@@ -128,7 +137,7 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
128
137
  // happens on the target before the relatedTarget gets focus.
129
138
  // The timeout gives it a moment for that to happen
130
139
  if (typeof this.props.onBlur === 'function') {
131
- window.setTimeout(() => {
140
+ setTimeout(() => {
132
141
  var _this$props$onBlur, _this$props2;
133
142
  (_this$props$onBlur = (_this$props2 = this.props).onBlur) === null || _this$props$onBlur === void 0 ? void 0 : _this$props$onBlur.call(_this$props2, e);
134
143
  }, 0);
@@ -241,7 +250,7 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
241
250
  }
242
251
  errorMsg = text ? {
243
252
  text,
244
- type: 'error'
253
+ type: 'newError'
245
254
  } : void 0;
246
255
  return {
247
256
  iso: parsed.clone(),
@@ -435,6 +444,13 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
435
444
  interaction = _this$props4.interaction,
436
445
  renderWeekdayLabels = _this$props4.renderWeekdayLabels,
437
446
  allowNonStepInput = _this$props4.allowNonStepInput;
447
+ const allMessages = [...(showMessages && this.state.message ? [this.state.message] : []), ...(messages || [])];
448
+ const hasError = allMessages.find(m => m.type === 'newError');
449
+ // 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
450
+ const subComponentMessages = hasError ? [{
451
+ type: 'newError',
452
+ text: ''
453
+ }] : [];
438
454
  return /*#__PURE__*/React.createElement(FormFieldGroup, {
439
455
  description: description,
440
456
  layout: layout,
@@ -442,6 +458,7 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
442
458
  colSpacing: colSpacing,
443
459
  vAlign: "top",
444
460
  elementRef: this.handleRef,
461
+ isGroup: false,
445
462
  messages: [...(showMessages && this.state.message ? [this.state.message] : []), ...(messages || [])]
446
463
  }, /*#__PURE__*/React.createElement(DateInput, {
447
464
  display: "block",
@@ -462,6 +479,7 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
462
479
  onRequestRenderNextMonth: this.handleRenderNextMonth,
463
480
  onRequestRenderPrevMonth: this.handleRenderPrevMonth,
464
481
  isRequired: isRequired,
482
+ messages: subComponentMessages,
465
483
  interaction: interaction,
466
484
  renderNavigationLabel: /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("div", null, this.state.renderedDate.format('MMMM')), /*#__PURE__*/React.createElement("div", null, this.state.renderedDate.format('y')))
467
485
  }, this.renderDays()), /*#__PURE__*/React.createElement(TimeSelect, {
@@ -476,7 +494,9 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_DateTimeInput = class Da
476
494
  timezone: timezone,
477
495
  inputRef: timeInputRef,
478
496
  interaction: interaction,
479
- allowNonStepInput: allowNonStepInput
497
+ allowNonStepInput: allowNonStepInput,
498
+ isRequired: isRequired,
499
+ messages: subComponentMessages
480
500
  }));
481
501
  }
482
502
  }, _DateTimeInput.displayName = "DateTimeInput", _DateTimeInput.DEFAULT_MESSAGE_FORMAT = 'LLLL', _DateTimeInput.propTypes = propTypes, _DateTimeInput.allowedProps = allowedProps, _DateTimeInput.defaultProps = {
@@ -146,7 +146,7 @@ describe('<DateTimeInput />', () => {
146
146
  invalidDateTimeMessage: invalidDateTimeMessage
147
147
  })),
148
148
  container = _render.container;
149
- const timeInput = _react2.screen.getByLabelText('time-input');
149
+ const timeInput = _react2.screen.getByLabelText('time-input *');
150
150
  await _userEvent.default.type(timeInput, '1:00 PM');
151
151
  _react2.fireEvent.blur(timeInput);
152
152
  await (0, _react2.waitFor)(() => {
@@ -83,7 +83,7 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
83
83
  event.persist();
84
84
  // timeout is needed here because handleDayClick could be called in the same
85
85
  // frame, and it updates calendarSelectedDate which is read in here.
86
- window.setTimeout(() => {
86
+ setTimeout(() => {
87
87
  if (event.key === 'Enter') {
88
88
  // user pressed enter, use the selected value in the calendar
89
89
  this.updateStateBasedOnDateInput(this.state.calendarSelectedDate, event);
@@ -116,12 +116,21 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
116
116
  // eslint-disable-next-line no-param-reassign
117
117
  newState.message = {
118
118
  text: text,
119
- type: 'error'
119
+ type: 'newError'
120
120
  };
121
121
  }
122
122
  if (this.areDifferentDates(this.state.iso, newState.iso)) {
123
- var _this$props$onChange, _this$props, _newState$iso;
124
- (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, e, (_newState$iso = newState.iso) === null || _newState$iso === void 0 ? void 0 : _newState$iso.toISOString());
123
+ if (typeof this.props.onChange === 'function') {
124
+ var _newState$iso;
125
+ const newDate = (_newState$iso = newState.iso) === null || _newState$iso === void 0 ? void 0 : _newState$iso.toISOString();
126
+ // Timeout is needed here because users might change value in the
127
+ // onChange event lister, which might not execute properly because
128
+ // the state change is not complete
129
+ setTimeout(() => {
130
+ var _this$props$onChange, _this$props;
131
+ (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, e, newDate);
132
+ }, 0);
133
+ }
125
134
  }
126
135
  this.setState(newState);
127
136
  };
@@ -136,7 +145,7 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
136
145
  // happens on the target before the relatedTarget gets focus.
137
146
  // The timeout gives it a moment for that to happen
138
147
  if (typeof this.props.onBlur === 'function') {
139
- window.setTimeout(() => {
148
+ setTimeout(() => {
140
149
  var _this$props$onBlur, _this$props2;
141
150
  (_this$props$onBlur = (_this$props2 = this.props).onBlur) === null || _this$props$onBlur === void 0 ? void 0 : _this$props$onBlur.call(_this$props2, e);
142
151
  }, 0);
@@ -249,7 +258,7 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
249
258
  }
250
259
  errorMsg = text ? {
251
260
  text,
252
- type: 'error'
261
+ type: 'newError'
253
262
  } : void 0;
254
263
  return {
255
264
  iso: parsed.clone(),
@@ -443,6 +452,13 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
443
452
  interaction = _this$props4.interaction,
444
453
  renderWeekdayLabels = _this$props4.renderWeekdayLabels,
445
454
  allowNonStepInput = _this$props4.allowNonStepInput;
455
+ const allMessages = [...(showMessages && this.state.message ? [this.state.message] : []), ...(messages || [])];
456
+ const hasError = allMessages.find(m => m.type === 'newError');
457
+ // 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
458
+ const subComponentMessages = hasError ? [{
459
+ type: 'newError',
460
+ text: ''
461
+ }] : [];
446
462
  return /*#__PURE__*/_react.default.createElement(_FormFieldGroup.FormFieldGroup, {
447
463
  description: description,
448
464
  layout: layout,
@@ -450,6 +466,7 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
450
466
  colSpacing: colSpacing,
451
467
  vAlign: "top",
452
468
  elementRef: this.handleRef,
469
+ isGroup: false,
453
470
  messages: [...(showMessages && this.state.message ? [this.state.message] : []), ...(messages || [])]
454
471
  }, /*#__PURE__*/_react.default.createElement(_DateInput.DateInput, {
455
472
  display: "block",
@@ -470,6 +487,7 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
470
487
  onRequestRenderNextMonth: this.handleRenderNextMonth,
471
488
  onRequestRenderPrevMonth: this.handleRenderPrevMonth,
472
489
  isRequired: isRequired,
490
+ messages: subComponentMessages,
473
491
  interaction: interaction,
474
492
  renderNavigationLabel: /*#__PURE__*/_react.default.createElement("span", null, /*#__PURE__*/_react.default.createElement("div", null, this.state.renderedDate.format('MMMM')), /*#__PURE__*/_react.default.createElement("div", null, this.state.renderedDate.format('y')))
475
493
  }, this.renderDays()), /*#__PURE__*/_react.default.createElement(_TimeSelect.TimeSelect, {
@@ -484,7 +502,9 @@ let DateTimeInput = exports.DateTimeInput = (_dec = (0, _testable.testable)(), _
484
502
  timezone: timezone,
485
503
  inputRef: timeInputRef,
486
504
  interaction: interaction,
487
- allowNonStepInput: allowNonStepInput
505
+ allowNonStepInput: allowNonStepInput,
506
+ isRequired: isRequired,
507
+ messages: subComponentMessages
488
508
  }));
489
509
  }
490
510
  }, _DateTimeInput.displayName = "DateTimeInput", _DateTimeInput.DEFAULT_MESSAGE_FORMAT = 'LLLL', _DateTimeInput.propTypes = _props.propTypes, _DateTimeInput.allowedProps = _props.allowedProps, _DateTimeInput.defaultProps = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-date-time-input",
3
- "version": "9.10.1",
3
+ "version": "9.11.0",
4
4
  "description": "A UI component that allows to select date and time.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-babel-preset": "9.10.1",
27
- "@instructure/ui-test-utils": "9.10.1",
26
+ "@instructure/ui-babel-preset": "9.11.0",
27
+ "@instructure/ui-test-utils": "9.11.0",
28
28
  "@testing-library/jest-dom": "^6.4.6",
29
29
  "@testing-library/react": "^15.0.7",
30
30
  "@testing-library/user-event": "^14.5.2",
@@ -32,20 +32,20 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@babel/runtime": "^7.24.5",
35
- "@instructure/console": "9.10.1",
36
- "@instructure/shared-types": "9.10.1",
37
- "@instructure/ui-a11y-content": "9.10.1",
38
- "@instructure/ui-buttons": "9.10.1",
39
- "@instructure/ui-calendar": "9.10.1",
40
- "@instructure/ui-date-input": "9.10.1",
41
- "@instructure/ui-form-field": "9.10.1",
42
- "@instructure/ui-i18n": "9.10.1",
43
- "@instructure/ui-icons": "9.10.1",
44
- "@instructure/ui-prop-types": "9.10.1",
45
- "@instructure/ui-react-utils": "9.10.1",
46
- "@instructure/ui-testable": "9.10.1",
47
- "@instructure/ui-time-select": "9.10.1",
48
- "@instructure/ui-utils": "9.10.1",
35
+ "@instructure/console": "9.11.0",
36
+ "@instructure/shared-types": "9.11.0",
37
+ "@instructure/ui-a11y-content": "9.11.0",
38
+ "@instructure/ui-buttons": "9.11.0",
39
+ "@instructure/ui-calendar": "9.11.0",
40
+ "@instructure/ui-date-input": "9.11.0",
41
+ "@instructure/ui-form-field": "9.11.0",
42
+ "@instructure/ui-i18n": "9.11.0",
43
+ "@instructure/ui-icons": "9.11.0",
44
+ "@instructure/ui-prop-types": "9.11.0",
45
+ "@instructure/ui-react-utils": "9.11.0",
46
+ "@instructure/ui-testable": "9.11.0",
47
+ "@instructure/ui-time-select": "9.11.0",
48
+ "@instructure/ui-utils": "9.11.0",
49
49
  "prop-types": "^15.8.1"
50
50
  },
51
51
  "peerDependencies": {
@@ -173,7 +173,7 @@ describe('<DateTimeInput />', () => {
173
173
  invalidDateTimeMessage={invalidDateTimeMessage}
174
174
  />
175
175
  )
176
- const timeInput = screen.getByLabelText('time-input')
176
+ const timeInput = screen.getByLabelText('time-input *')
177
177
 
178
178
  await userEvent.type(timeInput, '1:00 PM')
179
179
  fireEvent.blur(timeInput)
@@ -165,7 +165,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
165
165
  ? this.props.invalidDateTimeMessage(parsed.toISOString(true))
166
166
  : this.props.invalidDateTimeMessage
167
167
  }
168
- errorMsg = text ? { text, type: 'error' } : undefined
168
+ errorMsg = text ? { text, type: 'newError' } : undefined
169
169
  return {
170
170
  iso: parsed.clone(),
171
171
  calendarSelectedDate: parsed.clone(),
@@ -277,7 +277,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
277
277
  event.persist()
278
278
  // timeout is needed here because handleDayClick could be called in the same
279
279
  // frame, and it updates calendarSelectedDate which is read in here.
280
- window.setTimeout(() => {
280
+ setTimeout(() => {
281
281
  if ((event as React.KeyboardEvent).key === 'Enter') {
282
282
  // user pressed enter, use the selected value in the calendar
283
283
  this.updateStateBasedOnDateInput(this.state.calendarSelectedDate, event)
@@ -348,10 +348,18 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
348
348
  ? this.props.invalidDateTimeMessage(dateStr ? dateStr : '')
349
349
  : this.props.invalidDateTimeMessage
350
350
  // eslint-disable-next-line no-param-reassign
351
- newState.message = { text: text, type: 'error' }
351
+ newState.message = { text: text, type: 'newError' }
352
352
  }
353
353
  if (this.areDifferentDates(this.state.iso, newState.iso)) {
354
- this.props.onChange?.(e, newState.iso?.toISOString())
354
+ if (typeof this.props.onChange === 'function') {
355
+ const newDate = newState.iso?.toISOString()
356
+ // Timeout is needed here because users might change value in the
357
+ // onChange event lister, which might not execute properly because
358
+ // the state change is not complete
359
+ setTimeout(() => {
360
+ this.props.onChange?.(e, newDate)
361
+ }, 0)
362
+ }
355
363
  }
356
364
  this.setState(newState)
357
365
  }
@@ -368,7 +376,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
368
376
  // happens on the target before the relatedTarget gets focus.
369
377
  // The timeout gives it a moment for that to happen
370
378
  if (typeof this.props.onBlur === 'function') {
371
- window.setTimeout(() => {
379
+ setTimeout(() => {
372
380
  this.props.onBlur?.(e)
373
381
  }, 0)
374
382
  }
@@ -547,6 +555,17 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
547
555
  allowNonStepInput
548
556
  } = this.props
549
557
 
558
+ const allMessages = [
559
+ ...(showMessages && this.state.message ? [this.state.message] : []),
560
+ ...(messages || [])
561
+ ]
562
+
563
+ const hasError = allMessages.find((m) => m.type === 'newError')
564
+ // 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
565
+ const subComponentMessages: FormMessage[] = hasError
566
+ ? [{ type: 'newError', text: '' }]
567
+ : []
568
+
550
569
  return (
551
570
  <FormFieldGroup
552
571
  description={description}
@@ -555,6 +574,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
555
574
  colSpacing={colSpacing}
556
575
  vAlign="top"
557
576
  elementRef={this.handleRef}
577
+ isGroup={false}
558
578
  messages={[
559
579
  ...(showMessages && this.state.message ? [this.state.message] : []),
560
580
  ...(messages || [])
@@ -581,6 +601,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
581
601
  onRequestRenderNextMonth={this.handleRenderNextMonth}
582
602
  onRequestRenderPrevMonth={this.handleRenderPrevMonth}
583
603
  isRequired={isRequired}
604
+ messages={subComponentMessages}
584
605
  interaction={interaction}
585
606
  renderNavigationLabel={
586
607
  <span>
@@ -604,6 +625,8 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
604
625
  inputRef={timeInputRef}
605
626
  interaction={interaction}
606
627
  allowNonStepInput={allowNonStepInput}
628
+ isRequired={isRequired}
629
+ messages={subComponentMessages}
607
630
  />
608
631
  </FormFieldGroup>
609
632
  )