@instructure/ui-time-select 10.10.1-snapshot-5 → 10.10.1-snapshot-7

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,9 +3,12 @@
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
- ## [10.10.1-snapshot-5](https://github.com/instructure/instructure-ui/compare/v10.10.0...v10.10.1-snapshot-5) (2025-01-29)
6
+ ## [10.10.1-snapshot-7](https://github.com/instructure/instructure-ui/compare/v10.10.0...v10.10.1-snapshot-7) (2025-02-03)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-time-select
8
+
9
+ ### Bug Fixes
10
+
11
+ * **ui-time-select:** clear input field after setting an empty value ([1993282](https://github.com/instructure/instructure-ui/commit/19932824ebbcc5c927d000d353405ff72c4bf264))
9
12
 
10
13
 
11
14
 
@@ -73,7 +73,8 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
73
73
  // needs not to lose selectedOptionId in controlled mode otherwise it'd
74
74
  // revert to the default or '' instead of the set value
75
75
  selectedOptionId: this.isControlled ? this.state.selectedOptionId : void 0,
76
- fireChangeOnBlur: void 0
76
+ fireChangeOnBlur: void 0,
77
+ isInputCleared: this.props.allowClearingSelection && value === ''
77
78
  });
78
79
  }
79
80
  this.setState({
@@ -113,10 +114,11 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
113
114
  // when pressing ESC. NOT called when an item is selected via Enter/click,
114
115
  // (but in this case it will be called later when the input is blurred.)
115
116
  this.handleBlurOrEsc = event => {
116
- var _this$props$onHideOpt, _this$props7;
117
+ var _this$props$onHideOpt, _this$props8;
117
118
  const _this$state = this.state,
118
119
  selectedOptionId = _this$state.selectedOptionId,
119
- inputValue = _this$state.inputValue;
120
+ inputValue = _this$state.inputValue,
121
+ isInputCleared = _this$state.isInputCleared;
120
122
  let defaultValue = '';
121
123
  if (this.props.defaultValue) {
122
124
  const date = DateTime.parse(this.props.defaultValue, this.locale(), this.timezone());
@@ -124,14 +126,17 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
124
126
  }
125
127
  const selectedOption = this.getOption('id', selectedOptionId);
126
128
  let newInputValue = defaultValue;
127
- if (selectedOption) {
128
- // If there is a selected option use its value in the input field.
129
- newInputValue = selectedOption.label;
130
- }
131
129
  // if input was completely cleared, ensure it stays clear
132
130
  // e.g. defaultValue defined, but no selection yet made
133
- else if (inputValue === '') {
131
+ if (inputValue === '' && this.props.allowClearingSelection) {
134
132
  newInputValue = '';
133
+ } else if (selectedOption) {
134
+ // If there is a selected option use its value in the input field.
135
+ newInputValue = selectedOption.label;
136
+ } else if (this.props.value) {
137
+ // If controlled and input is cleared and blurred after the first render, it should revert to value
138
+ const date = DateTime.parse(this.props.value, this.locale(), this.timezone());
139
+ newInputValue = this.props.format ? date.format(this.props.format) : date.toISOString();
135
140
  }
136
141
  this.setState(() => ({
137
142
  isShowingOptions: false,
@@ -148,13 +153,22 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
148
153
  value: this.state.fireChangeOnBlur.value.toISOString(),
149
154
  inputText: this.state.fireChangeOnBlur.label
150
155
  });
156
+ } else if (isInputCleared && event.key !== 'Escape' && this.props.allowClearingSelection) {
157
+ var _this$props$onChange2, _this$props7;
158
+ this.setState(() => ({
159
+ isInputCleared: false
160
+ }));
161
+ (_this$props$onChange2 = (_this$props7 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props7, event, {
162
+ value: '',
163
+ inputText: ''
164
+ });
151
165
  }
152
166
  // TODO only fire this if handleSelectOption was not called before.
153
- (_this$props$onHideOpt = (_this$props7 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props7, event);
167
+ (_this$props$onHideOpt = (_this$props8 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props8, event);
154
168
  };
155
169
  // Called when an option is selected via mouse click or Enter.
156
170
  this.handleSelectOption = (event, data) => {
157
- var _this$props$onHideOpt2, _this$props9;
171
+ var _this$props$onHideOpt2, _this$props10;
158
172
  if (data.id === this._emptyOptionId) {
159
173
  this.setState({
160
174
  isShowingOptions: false
@@ -182,13 +196,13 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
182
196
  });
183
197
  }
184
198
  if (data.id !== currentSelectedOptionId) {
185
- var _this$props$onChange2, _this$props8;
186
- (_this$props$onChange2 = (_this$props8 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props8, event, {
199
+ var _this$props$onChange3, _this$props9;
200
+ (_this$props$onChange3 = (_this$props9 = this.props).onChange) === null || _this$props$onChange3 === void 0 ? void 0 : _this$props$onChange3.call(_this$props9, event, {
187
201
  value: selectedOption.value.toISOString(),
188
202
  inputText: newInputValue
189
203
  });
190
204
  }
191
- (_this$props$onHideOpt2 = (_this$props9 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props9, event);
205
+ (_this$props$onHideOpt2 = (_this$props10 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props10, event);
192
206
  };
193
207
  this.handleHighlightOption = (event, data) => {
194
208
  if (data.id === this._emptyOptionId) return;
@@ -306,15 +320,16 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
306
320
  filteredOptions: initialOptions.length > 288 ? initialOptions.filter(opt => opt.value.minute() % this.props.step === 0) : initialOptions,
307
321
  isShowingOptions: false,
308
322
  highlightedOptionId: initialSelection ? initialSelection.id : void 0,
309
- selectedOptionId: initialSelection ? initialSelection.id : void 0
323
+ selectedOptionId: initialSelection ? initialSelection.id : void 0,
324
+ isInputCleared: false
310
325
  };
311
326
  }
312
327
  getInitialOption(options) {
313
- const _this$props10 = this.props,
314
- value = _this$props10.value,
315
- defaultValue = _this$props10.defaultValue,
316
- defaultToFirstOption = _this$props10.defaultToFirstOption,
317
- format = _this$props10.format;
328
+ const _this$props11 = this.props,
329
+ value = _this$props11.value,
330
+ defaultValue = _this$props11.defaultValue,
331
+ defaultToFirstOption = _this$props11.defaultToFirstOption,
332
+ format = _this$props11.format;
318
333
  const initialValue = value || defaultValue;
319
334
  if (typeof initialValue === 'string') {
320
335
  const date = DateTime.parse(initialValue, this.locale(), this.timezone());
@@ -423,33 +438,33 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
423
438
  }, callRenderProp(this.props.renderEmptyOption));
424
439
  }
425
440
  render() {
426
- const _this$props11 = this.props,
427
- value = _this$props11.value,
428
- defaultValue = _this$props11.defaultValue,
429
- placeholder = _this$props11.placeholder,
430
- renderLabel = _this$props11.renderLabel,
431
- inputRef = _this$props11.inputRef,
432
- id = _this$props11.id,
433
- listRef = _this$props11.listRef,
434
- renderBeforeInput = _this$props11.renderBeforeInput,
435
- renderAfterInput = _this$props11.renderAfterInput,
436
- isRequired = _this$props11.isRequired,
437
- isInline = _this$props11.isInline,
438
- width = _this$props11.width,
439
- format = _this$props11.format,
440
- step = _this$props11.step,
441
- optionsMaxWidth = _this$props11.optionsMaxWidth,
442
- visibleOptionsCount = _this$props11.visibleOptionsCount,
443
- messages = _this$props11.messages,
444
- placement = _this$props11.placement,
445
- constrain = _this$props11.constrain,
446
- onFocus = _this$props11.onFocus,
447
- onShowOptions = _this$props11.onShowOptions,
448
- onHideOptions = _this$props11.onHideOptions,
449
- onInputChange = _this$props11.onInputChange,
450
- onKeyDown = _this$props11.onKeyDown,
451
- mountNode = _this$props11.mountNode,
452
- rest = _objectWithoutProperties(_this$props11, _excluded);
441
+ const _this$props12 = this.props,
442
+ value = _this$props12.value,
443
+ defaultValue = _this$props12.defaultValue,
444
+ placeholder = _this$props12.placeholder,
445
+ renderLabel = _this$props12.renderLabel,
446
+ inputRef = _this$props12.inputRef,
447
+ id = _this$props12.id,
448
+ listRef = _this$props12.listRef,
449
+ renderBeforeInput = _this$props12.renderBeforeInput,
450
+ renderAfterInput = _this$props12.renderAfterInput,
451
+ isRequired = _this$props12.isRequired,
452
+ isInline = _this$props12.isInline,
453
+ width = _this$props12.width,
454
+ format = _this$props12.format,
455
+ step = _this$props12.step,
456
+ optionsMaxWidth = _this$props12.optionsMaxWidth,
457
+ visibleOptionsCount = _this$props12.visibleOptionsCount,
458
+ messages = _this$props12.messages,
459
+ placement = _this$props12.placement,
460
+ constrain = _this$props12.constrain,
461
+ onFocus = _this$props12.onFocus,
462
+ onShowOptions = _this$props12.onShowOptions,
463
+ onHideOptions = _this$props12.onHideOptions,
464
+ onInputChange = _this$props12.onInputChange,
465
+ onKeyDown = _this$props12.onKeyDown,
466
+ mountNode = _this$props12.mountNode,
467
+ rest = _objectWithoutProperties(_this$props12, _excluded);
453
468
  const _this$state5 = this.state,
454
469
  inputValue = _this$state5.inputValue,
455
470
  isShowingOptions = _this$state5.isShowingOptions;
@@ -495,7 +510,8 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
495
510
  placement: 'bottom stretch',
496
511
  constrain: 'window',
497
512
  renderEmptyOption: '---',
498
- allowNonStepInput: false
513
+ allowNonStepInput: false,
514
+ allowClearingSelection: false
499
515
  }, _TimeSelect.contextType = ApplyLocaleContext, _TimeSelect)) || _class) || _class);
500
516
  export { TimeSelect };
501
517
  export default TimeSelect;
@@ -59,7 +59,8 @@ const propTypes = {
59
59
  locale: PropTypes.string,
60
60
  timezone: PropTypes.string,
61
61
  allowNonStepInput: PropTypes.bool,
62
- onInputChange: PropTypes.func
62
+ onInputChange: PropTypes.func,
63
+ allowClearingSelection: PropTypes.bool
63
64
  };
64
- const allowedProps = ['renderLabel', 'defaultToFirstOption', 'value', 'defaultValue', 'id', 'format', 'step', 'interaction', 'placeholder', 'isRequired', 'isInline', 'width', 'optionsMaxWidth', 'mountNode', 'visibleOptionsCount', 'messages', 'placement', 'constrain', 'onChange', 'onFocus', 'onBlur', 'onShowOptions', 'onHideOptions', 'inputRef', 'listRef', 'renderEmptyOption', 'renderBeforeInput', 'renderAfterInput', 'locale', 'timezone', 'allowNonStepInput', 'onInputChange'];
65
+ const allowedProps = ['renderLabel', 'defaultToFirstOption', 'value', 'defaultValue', 'id', 'format', 'step', 'interaction', 'placeholder', 'isRequired', 'isInline', 'width', 'optionsMaxWidth', 'mountNode', 'visibleOptionsCount', 'messages', 'placement', 'constrain', 'onChange', 'onFocus', 'onBlur', 'onShowOptions', 'onHideOptions', 'inputRef', 'listRef', 'renderEmptyOption', 'renderBeforeInput', 'renderAfterInput', 'locale', 'timezone', 'allowNonStepInput', 'onInputChange', 'allowClearingSelection'];
65
66
  export { propTypes, allowedProps };
@@ -85,7 +85,8 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
85
85
  // needs not to lose selectedOptionId in controlled mode otherwise it'd
86
86
  // revert to the default or '' instead of the set value
87
87
  selectedOptionId: this.isControlled ? this.state.selectedOptionId : void 0,
88
- fireChangeOnBlur: void 0
88
+ fireChangeOnBlur: void 0,
89
+ isInputCleared: this.props.allowClearingSelection && value === ''
89
90
  });
90
91
  }
91
92
  this.setState({
@@ -125,10 +126,11 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
125
126
  // when pressing ESC. NOT called when an item is selected via Enter/click,
126
127
  // (but in this case it will be called later when the input is blurred.)
127
128
  this.handleBlurOrEsc = event => {
128
- var _this$props$onHideOpt, _this$props7;
129
+ var _this$props$onHideOpt, _this$props8;
129
130
  const _this$state = this.state,
130
131
  selectedOptionId = _this$state.selectedOptionId,
131
- inputValue = _this$state.inputValue;
132
+ inputValue = _this$state.inputValue,
133
+ isInputCleared = _this$state.isInputCleared;
132
134
  let defaultValue = '';
133
135
  if (this.props.defaultValue) {
134
136
  const date = _DateTime.DateTime.parse(this.props.defaultValue, this.locale(), this.timezone());
@@ -136,14 +138,17 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
136
138
  }
137
139
  const selectedOption = this.getOption('id', selectedOptionId);
138
140
  let newInputValue = defaultValue;
139
- if (selectedOption) {
140
- // If there is a selected option use its value in the input field.
141
- newInputValue = selectedOption.label;
142
- }
143
141
  // if input was completely cleared, ensure it stays clear
144
142
  // e.g. defaultValue defined, but no selection yet made
145
- else if (inputValue === '') {
143
+ if (inputValue === '' && this.props.allowClearingSelection) {
146
144
  newInputValue = '';
145
+ } else if (selectedOption) {
146
+ // If there is a selected option use its value in the input field.
147
+ newInputValue = selectedOption.label;
148
+ } else if (this.props.value) {
149
+ // If controlled and input is cleared and blurred after the first render, it should revert to value
150
+ const date = _DateTime.DateTime.parse(this.props.value, this.locale(), this.timezone());
151
+ newInputValue = this.props.format ? date.format(this.props.format) : date.toISOString();
147
152
  }
148
153
  this.setState(() => ({
149
154
  isShowingOptions: false,
@@ -160,13 +165,22 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
160
165
  value: this.state.fireChangeOnBlur.value.toISOString(),
161
166
  inputText: this.state.fireChangeOnBlur.label
162
167
  });
168
+ } else if (isInputCleared && event.key !== 'Escape' && this.props.allowClearingSelection) {
169
+ var _this$props$onChange2, _this$props7;
170
+ this.setState(() => ({
171
+ isInputCleared: false
172
+ }));
173
+ (_this$props$onChange2 = (_this$props7 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props7, event, {
174
+ value: '',
175
+ inputText: ''
176
+ });
163
177
  }
164
178
  // TODO only fire this if handleSelectOption was not called before.
165
- (_this$props$onHideOpt = (_this$props7 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props7, event);
179
+ (_this$props$onHideOpt = (_this$props8 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props8, event);
166
180
  };
167
181
  // Called when an option is selected via mouse click or Enter.
168
182
  this.handleSelectOption = (event, data) => {
169
- var _this$props$onHideOpt2, _this$props9;
183
+ var _this$props$onHideOpt2, _this$props10;
170
184
  if (data.id === this._emptyOptionId) {
171
185
  this.setState({
172
186
  isShowingOptions: false
@@ -194,13 +208,13 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
194
208
  });
195
209
  }
196
210
  if (data.id !== currentSelectedOptionId) {
197
- var _this$props$onChange2, _this$props8;
198
- (_this$props$onChange2 = (_this$props8 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props8, event, {
211
+ var _this$props$onChange3, _this$props9;
212
+ (_this$props$onChange3 = (_this$props9 = this.props).onChange) === null || _this$props$onChange3 === void 0 ? void 0 : _this$props$onChange3.call(_this$props9, event, {
199
213
  value: selectedOption.value.toISOString(),
200
214
  inputText: newInputValue
201
215
  });
202
216
  }
203
- (_this$props$onHideOpt2 = (_this$props9 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props9, event);
217
+ (_this$props$onHideOpt2 = (_this$props10 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props10, event);
204
218
  };
205
219
  this.handleHighlightOption = (event, data) => {
206
220
  if (data.id === this._emptyOptionId) return;
@@ -318,15 +332,16 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
318
332
  filteredOptions: initialOptions.length > 288 ? initialOptions.filter(opt => opt.value.minute() % this.props.step === 0) : initialOptions,
319
333
  isShowingOptions: false,
320
334
  highlightedOptionId: initialSelection ? initialSelection.id : void 0,
321
- selectedOptionId: initialSelection ? initialSelection.id : void 0
335
+ selectedOptionId: initialSelection ? initialSelection.id : void 0,
336
+ isInputCleared: false
322
337
  };
323
338
  }
324
339
  getInitialOption(options) {
325
- const _this$props10 = this.props,
326
- value = _this$props10.value,
327
- defaultValue = _this$props10.defaultValue,
328
- defaultToFirstOption = _this$props10.defaultToFirstOption,
329
- format = _this$props10.format;
340
+ const _this$props11 = this.props,
341
+ value = _this$props11.value,
342
+ defaultValue = _this$props11.defaultValue,
343
+ defaultToFirstOption = _this$props11.defaultToFirstOption,
344
+ format = _this$props11.format;
330
345
  const initialValue = value || defaultValue;
331
346
  if (typeof initialValue === 'string') {
332
347
  const date = _DateTime.DateTime.parse(initialValue, this.locale(), this.timezone());
@@ -435,33 +450,33 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
435
450
  }, (0, _callRenderProp.callRenderProp)(this.props.renderEmptyOption));
436
451
  }
437
452
  render() {
438
- const _this$props11 = this.props,
439
- value = _this$props11.value,
440
- defaultValue = _this$props11.defaultValue,
441
- placeholder = _this$props11.placeholder,
442
- renderLabel = _this$props11.renderLabel,
443
- inputRef = _this$props11.inputRef,
444
- id = _this$props11.id,
445
- listRef = _this$props11.listRef,
446
- renderBeforeInput = _this$props11.renderBeforeInput,
447
- renderAfterInput = _this$props11.renderAfterInput,
448
- isRequired = _this$props11.isRequired,
449
- isInline = _this$props11.isInline,
450
- width = _this$props11.width,
451
- format = _this$props11.format,
452
- step = _this$props11.step,
453
- optionsMaxWidth = _this$props11.optionsMaxWidth,
454
- visibleOptionsCount = _this$props11.visibleOptionsCount,
455
- messages = _this$props11.messages,
456
- placement = _this$props11.placement,
457
- constrain = _this$props11.constrain,
458
- onFocus = _this$props11.onFocus,
459
- onShowOptions = _this$props11.onShowOptions,
460
- onHideOptions = _this$props11.onHideOptions,
461
- onInputChange = _this$props11.onInputChange,
462
- onKeyDown = _this$props11.onKeyDown,
463
- mountNode = _this$props11.mountNode,
464
- rest = (0, _objectWithoutProperties2.default)(_this$props11, _excluded);
453
+ const _this$props12 = this.props,
454
+ value = _this$props12.value,
455
+ defaultValue = _this$props12.defaultValue,
456
+ placeholder = _this$props12.placeholder,
457
+ renderLabel = _this$props12.renderLabel,
458
+ inputRef = _this$props12.inputRef,
459
+ id = _this$props12.id,
460
+ listRef = _this$props12.listRef,
461
+ renderBeforeInput = _this$props12.renderBeforeInput,
462
+ renderAfterInput = _this$props12.renderAfterInput,
463
+ isRequired = _this$props12.isRequired,
464
+ isInline = _this$props12.isInline,
465
+ width = _this$props12.width,
466
+ format = _this$props12.format,
467
+ step = _this$props12.step,
468
+ optionsMaxWidth = _this$props12.optionsMaxWidth,
469
+ visibleOptionsCount = _this$props12.visibleOptionsCount,
470
+ messages = _this$props12.messages,
471
+ placement = _this$props12.placement,
472
+ constrain = _this$props12.constrain,
473
+ onFocus = _this$props12.onFocus,
474
+ onShowOptions = _this$props12.onShowOptions,
475
+ onHideOptions = _this$props12.onHideOptions,
476
+ onInputChange = _this$props12.onInputChange,
477
+ onKeyDown = _this$props12.onKeyDown,
478
+ mountNode = _this$props12.mountNode,
479
+ rest = (0, _objectWithoutProperties2.default)(_this$props12, _excluded);
465
480
  const _this$state5 = this.state,
466
481
  inputValue = _this$state5.inputValue,
467
482
  isShowingOptions = _this$state5.isShowingOptions;
@@ -507,6 +522,7 @@ let TimeSelect = exports.TimeSelect = (_dec = (0, _withDeterministicId.withDeter
507
522
  placement: 'bottom stretch',
508
523
  constrain: 'window',
509
524
  renderEmptyOption: '---',
510
- allowNonStepInput: false
525
+ allowNonStepInput: false,
526
+ allowClearingSelection: false
511
527
  }, _TimeSelect.contextType = _ApplyLocaleContext.ApplyLocaleContext, _TimeSelect)) || _class) || _class);
512
528
  var _default = exports.default = TimeSelect;
@@ -66,6 +66,7 @@ const propTypes = exports.propTypes = {
66
66
  locale: _propTypes.default.string,
67
67
  timezone: _propTypes.default.string,
68
68
  allowNonStepInput: _propTypes.default.bool,
69
- onInputChange: _propTypes.default.func
69
+ onInputChange: _propTypes.default.func,
70
+ allowClearingSelection: _propTypes.default.bool
70
71
  };
71
- const allowedProps = exports.allowedProps = ['renderLabel', 'defaultToFirstOption', 'value', 'defaultValue', 'id', 'format', 'step', 'interaction', 'placeholder', 'isRequired', 'isInline', 'width', 'optionsMaxWidth', 'mountNode', 'visibleOptionsCount', 'messages', 'placement', 'constrain', 'onChange', 'onFocus', 'onBlur', 'onShowOptions', 'onHideOptions', 'inputRef', 'listRef', 'renderEmptyOption', 'renderBeforeInput', 'renderAfterInput', 'locale', 'timezone', 'allowNonStepInput', 'onInputChange'];
72
+ const allowedProps = exports.allowedProps = ['renderLabel', 'defaultToFirstOption', 'value', 'defaultValue', 'id', 'format', 'step', 'interaction', 'placeholder', 'isRequired', 'isInline', 'width', 'optionsMaxWidth', 'mountNode', 'visibleOptionsCount', 'messages', 'placement', 'constrain', 'onChange', 'onFocus', 'onBlur', 'onShowOptions', 'onHideOptions', 'inputRef', 'listRef', 'renderEmptyOption', 'renderBeforeInput', 'renderAfterInput', 'locale', 'timezone', 'allowNonStepInput', 'onInputChange', 'allowClearingSelection'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-time-select",
3
- "version": "10.10.1-snapshot-5",
3
+ "version": "10.10.1-snapshot-7",
4
4
  "description": "A component for selecting time values.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -24,21 +24,21 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.26.0",
27
- "@instructure/shared-types": "10.10.1-snapshot-5",
28
- "@instructure/ui-form-field": "10.10.1-snapshot-5",
29
- "@instructure/ui-i18n": "10.10.1-snapshot-5",
30
- "@instructure/ui-position": "10.10.1-snapshot-5",
31
- "@instructure/ui-prop-types": "10.10.1-snapshot-5",
32
- "@instructure/ui-react-utils": "10.10.1-snapshot-5",
33
- "@instructure/ui-select": "10.10.1-snapshot-5",
34
- "@instructure/ui-testable": "10.10.1-snapshot-5",
35
- "@instructure/ui-utils": "10.10.1-snapshot-5",
27
+ "@instructure/shared-types": "10.10.1-snapshot-7",
28
+ "@instructure/ui-form-field": "10.10.1-snapshot-7",
29
+ "@instructure/ui-i18n": "10.10.1-snapshot-7",
30
+ "@instructure/ui-position": "10.10.1-snapshot-7",
31
+ "@instructure/ui-prop-types": "10.10.1-snapshot-7",
32
+ "@instructure/ui-react-utils": "10.10.1-snapshot-7",
33
+ "@instructure/ui-select": "10.10.1-snapshot-7",
34
+ "@instructure/ui-testable": "10.10.1-snapshot-7",
35
+ "@instructure/ui-utils": "10.10.1-snapshot-7",
36
36
  "prop-types": "^15.8.1"
37
37
  },
38
38
  "devDependencies": {
39
- "@instructure/ui-axe-check": "10.10.1-snapshot-5",
40
- "@instructure/ui-babel-preset": "10.10.1-snapshot-5",
41
- "@instructure/ui-test-utils": "10.10.1-snapshot-5",
39
+ "@instructure/ui-axe-check": "10.10.1-snapshot-7",
40
+ "@instructure/ui-babel-preset": "10.10.1-snapshot-7",
41
+ "@instructure/ui-test-utils": "10.10.1-snapshot-7",
42
42
  "@testing-library/jest-dom": "^6.6.3",
43
43
  "@testing-library/react": "^16.0.1",
44
44
  "@testing-library/user-event": "^14.5.2",
@@ -74,7 +74,8 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
74
74
  placement: 'bottom stretch',
75
75
  constrain: 'window',
76
76
  renderEmptyOption: '---',
77
- allowNonStepInput: false
77
+ allowNonStepInput: false,
78
+ allowClearingSelection: false
78
79
  }
79
80
  static contextType = ApplyLocaleContext
80
81
 
@@ -220,7 +221,8 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
220
221
  : initialOptions,
221
222
  isShowingOptions: false,
222
223
  highlightedOptionId: initialSelection ? initialSelection.id : undefined,
223
- selectedOptionId: initialSelection ? initialSelection.id : undefined
224
+ selectedOptionId: initialSelection ? initialSelection.id : undefined,
225
+ isInputCleared: false
224
226
  }
225
227
  }
226
228
 
@@ -338,7 +340,8 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
338
340
  selectedOptionId: this.isControlled
339
341
  ? this.state.selectedOptionId
340
342
  : undefined,
341
- fireChangeOnBlur: undefined
343
+ fireChangeOnBlur: undefined,
344
+ isInputCleared: this.props.allowClearingSelection && value === ''
342
345
  })
343
346
  }
344
347
  this.setState({
@@ -386,7 +389,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
386
389
  // when pressing ESC. NOT called when an item is selected via Enter/click,
387
390
  // (but in this case it will be called later when the input is blurred.)
388
391
  handleBlurOrEsc: SelectProps['onRequestHideOptions'] = (event) => {
389
- const { selectedOptionId, inputValue } = this.state
392
+ const { selectedOptionId, inputValue, isInputCleared } = this.state
390
393
  let defaultValue = ''
391
394
  if (this.props.defaultValue) {
392
395
  const date = DateTime.parse(
@@ -400,14 +403,23 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
400
403
  }
401
404
  const selectedOption = this.getOption('id', selectedOptionId)
402
405
  let newInputValue = defaultValue
403
- if (selectedOption) {
404
- // If there is a selected option use its value in the input field.
405
- newInputValue = selectedOption.label
406
- }
407
406
  // if input was completely cleared, ensure it stays clear
408
407
  // e.g. defaultValue defined, but no selection yet made
409
- else if (inputValue === '') {
408
+ if (inputValue === '' && this.props.allowClearingSelection) {
410
409
  newInputValue = ''
410
+ } else if (selectedOption) {
411
+ // If there is a selected option use its value in the input field.
412
+ newInputValue = selectedOption.label
413
+ } else if (this.props.value) {
414
+ // If controlled and input is cleared and blurred after the first render, it should revert to value
415
+ const date = DateTime.parse(
416
+ this.props.value,
417
+ this.locale(),
418
+ this.timezone()
419
+ )
420
+ newInputValue = this.props.format
421
+ ? date.format(this.props.format)
422
+ : date.toISOString()
411
423
  }
412
424
  this.setState(() => ({
413
425
  isShowingOptions: false,
@@ -421,6 +433,16 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
421
433
  value: this.state.fireChangeOnBlur.value.toISOString(),
422
434
  inputText: this.state.fireChangeOnBlur.label
423
435
  })
436
+ } else if (
437
+ isInputCleared &&
438
+ (event as any).key !== 'Escape' &&
439
+ this.props.allowClearingSelection
440
+ ) {
441
+ this.setState(() => ({ isInputCleared: false }))
442
+ this.props.onChange?.(event, {
443
+ value: '',
444
+ inputText: ''
445
+ })
424
446
  }
425
447
  // TODO only fire this if handleSelectOption was not called before.
426
448
  this.props.onHideOptions?.(event)
@@ -244,6 +244,11 @@ type TimeSelectOwnProps = {
244
244
  */
245
245
  valueAsISOString?: string
246
246
  ) => void
247
+ /**
248
+ * Whether to allow for the user to clear the selected option in the input field.
249
+ * If `false`, the input field will return the last selected option after the input is cleared and loses focus.
250
+ */
251
+ allowClearingSelection: boolean
247
252
  }
248
253
 
249
254
  const propTypes: PropValidators<PropKeys> = {
@@ -278,7 +283,8 @@ const propTypes: PropValidators<PropKeys> = {
278
283
  locale: PropTypes.string,
279
284
  timezone: PropTypes.string,
280
285
  allowNonStepInput: PropTypes.bool,
281
- onInputChange: PropTypes.func
286
+ onInputChange: PropTypes.func,
287
+ allowClearingSelection: PropTypes.bool
282
288
  }
283
289
 
284
290
  const allowedProps: AllowedPropKeys = [
@@ -313,7 +319,8 @@ const allowedProps: AllowedPropKeys = [
313
319
  'locale',
314
320
  'timezone',
315
321
  'allowNonStepInput',
316
- 'onInputChange'
322
+ 'onInputChange',
323
+ 'allowClearingSelection'
317
324
  ]
318
325
 
319
326
  type TimeSelectOptions = {
@@ -356,6 +363,10 @@ type TimeSelectState = {
356
363
  * fire onChange event when the popup closes?
357
364
  */
358
365
  fireChangeOnBlur?: TimeSelectOptions
366
+ /**
367
+ * Whether to selected option is cleared
368
+ */
369
+ isInputCleared: boolean
359
370
  }
360
371
 
361
372
  export type { TimeSelectProps, TimeSelectState, TimeSelectOptions }