@instructure/ui-time-select 8.34.1-snapshot-1 → 8.35.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,13 +3,11 @@
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
- ## [8.34.1-snapshot-1](https://github.com/instructure/instructure-ui/compare/v8.34.0...v8.34.1-snapshot-1) (2023-02-17)
7
-
8
- **Note:** Version bump only for package @instructure/ui-time-select
9
-
10
-
6
+ # [8.35.0](https://github.com/instructure/instructure-ui/compare/v8.34.0...v8.35.0) (2023-02-17)
11
7
 
8
+ ### Features
12
9
 
10
+ - **ui-time-select:** allow non-step values with allowNonStepInput ([6dace32](https://github.com/instructure/instructure-ui/commit/6dace32d7fbb4d13227ac988b83f521308541204))
13
11
 
14
12
  # [8.34.0](https://github.com/instructure/instructure-ui/compare/v8.33.2...v8.34.0) (2023-02-10)
15
13
 
@@ -1,5 +1,5 @@
1
1
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
2
- const _excluded = ["value", "defaultValue", "placeholder", "renderLabel", "inputRef", "id", "listRef", "renderBeforeInput", "renderAfterInput", "isRequired", "isInline", "width", "format", "step", "optionsMaxWidth", "visibleOptionsCount", "messages", "placement", "constrain", "onFocus", "onShowOptions", "onHideOptions", "mountNode"];
2
+ const _excluded = ["value", "defaultValue", "placeholder", "renderLabel", "inputRef", "id", "listRef", "renderBeforeInput", "renderAfterInput", "isRequired", "isInline", "width", "format", "step", "optionsMaxWidth", "visibleOptionsCount", "messages", "placement", "constrain", "onFocus", "onShowOptions", "onHideOptions", "onInputChange", "onKeyDown", "mountNode"];
3
3
  var _dec, _dec2, _class, _class2;
4
4
  /*
5
5
  * The MIT License (MIT)
@@ -61,6 +61,7 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
61
61
  (_this$props$onBlur = (_this$props = this.props).onBlur) === null || _this$props$onBlur === void 0 ? void 0 : _this$props$onBlur.call(_this$props, event);
62
62
  };
63
63
  this.handleInputChange = event => {
64
+ var _this$props$onInputCh, _this$props3;
64
65
  const value = event.target.value;
65
66
  const newOptions = this.filterOptions(value);
66
67
  this.setState({
@@ -73,32 +74,44 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
73
74
  var _this$props$onShowOpt, _this$props2;
74
75
  (_this$props$onShowOpt = (_this$props2 = this.props).onShowOptions) === null || _this$props$onShowOpt === void 0 ? void 0 : _this$props$onShowOpt.call(_this$props2, event);
75
76
  }
77
+ const inputAsDate = this.parseInputText(value);
78
+ (_this$props$onInputCh = (_this$props3 = this.props).onInputChange) === null || _this$props$onInputCh === void 0 ? void 0 : _this$props$onInputCh.call(_this$props3, event, value, inputAsDate.isValid() ? inputAsDate.toISOString() : void 0);
76
79
  };
77
80
  this.handleShowOptions = event => {
78
- var _this$props$onShowOpt2, _this$props3;
81
+ var _this$props$onShowOpt2, _this$props4;
79
82
  this.setState({
80
83
  isShowingOptions: true,
81
84
  highlightedOptionId: this.state.selectedOptionId
82
85
  });
83
- (_this$props$onShowOpt2 = (_this$props3 = this.props).onShowOptions) === null || _this$props$onShowOpt2 === void 0 ? void 0 : _this$props$onShowOpt2.call(_this$props3, event);
86
+ (_this$props$onShowOpt2 = (_this$props4 = this.props).onShowOptions) === null || _this$props$onShowOpt2 === void 0 ? void 0 : _this$props$onShowOpt2.call(_this$props4, event);
84
87
  };
85
88
  this.handleHideOptions = event => {
86
- var _this$props$onHideOpt, _this$props4;
87
- const selectedOptionId = this.state.selectedOptionId;
88
- const option = this.getOption('id', selectedOptionId);
89
- let prevValue = '';
89
+ var _this$props$onHideOpt, _this$props5;
90
+ const input = this.parseInputText(this.state.inputValue);
91
+ let defaultValue = '';
90
92
  if (this.props.defaultValue) {
91
93
  const date = DateTime.parse(this.props.defaultValue, this.locale(), this.timezone());
92
- prevValue = this.props.format ? date.format(this.props.format) : date.toISOString();
94
+ defaultValue = this.props.format ? date.format(this.props.format) : date.toISOString();
93
95
  }
94
- this.setState(() => ({
95
- isShowingOptions: false,
96
- highlightedOptionId: void 0,
97
- inputValue: selectedOptionId ? option.label : prevValue,
98
- filteredOptions: this.filterOptions(''),
99
- ...this.matchValue()
100
- }));
101
- (_this$props$onHideOpt = (_this$props4 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props4, event);
96
+ if (!this.props.allowNonStepInput || this.props.allowNonStepInput && input.isValid() && input.minute() % this.props.step === 0) {
97
+ const selectedOptionId = this.state.selectedOptionId;
98
+ const option = this.getOption('id', selectedOptionId);
99
+ this.setState(() => ({
100
+ isShowingOptions: false,
101
+ highlightedOptionId: void 0,
102
+ inputValue: selectedOptionId ? option.label : defaultValue,
103
+ filteredOptions: this.filterOptions(''),
104
+ ...this.matchValue()
105
+ }));
106
+ } else {
107
+ this.setState(() => ({
108
+ isShowingOptions: false,
109
+ highlightedOptionId: void 0,
110
+ inputValue: this.state.lastValidInput || defaultValue || '',
111
+ filteredOptions: this.filterOptions('')
112
+ }));
113
+ }
114
+ (_this$props$onHideOpt = (_this$props5 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props5, event);
102
115
  };
103
116
  this.handleHighlightOption = (event, _ref) => {
104
117
  let id = _ref.id;
@@ -111,7 +124,7 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
111
124
  }));
112
125
  };
113
126
  this.handleSelectOption = (event, _ref2) => {
114
- var _this$props$onHideOpt2, _this$props6;
127
+ var _this$props$onHideOpt2, _this$props7;
115
128
  let id = _ref2.id;
116
129
  if (id === this._emptyOptionId) {
117
130
  this.setState({
@@ -139,13 +152,45 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
139
152
  });
140
153
  }
141
154
  if (id !== this.state.selectedOptionId) {
142
- var _this$props$onChange, _this$props5;
143
- (_this$props$onChange = (_this$props5 = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props5, event, {
155
+ var _this$props$onChange, _this$props6;
156
+ this.setState({
157
+ lastValidInput: newInputValue
158
+ });
159
+ (_this$props$onChange = (_this$props6 = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props6, event, {
144
160
  value: option.value,
145
161
  inputText: newInputValue
146
162
  });
147
163
  }
148
- (_this$props$onHideOpt2 = (_this$props6 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props6, event);
164
+ (_this$props$onHideOpt2 = (_this$props7 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props7, event);
165
+ };
166
+ this.onKeyDown = event => {
167
+ var _this$props$onKeyDown, _this$props9;
168
+ const input = this.parseInputText(this.state.inputValue);
169
+ // validate input and if OK close dropdown
170
+ if (event.key === 'Enter' && this.props.allowNonStepInput && input.isValid() && input.minute() % this.props.step !== 0 && this.state.lastValidInput != this.state.inputValue) {
171
+ var _this$props$onChange2, _this$props8;
172
+ this.setState(() => ({
173
+ isShowingOptions: false,
174
+ highlightedOptionId: void 0,
175
+ filteredOptions: this.filterOptions(''),
176
+ lastValidInput: this.state.inputValue
177
+ }));
178
+ (_this$props$onChange2 = (_this$props8 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props8, event, {
179
+ value: input.toISOString(),
180
+ inputText: this.state.inputValue
181
+ });
182
+ // others are set in OnHideOptions
183
+ }
184
+
185
+ (_this$props$onKeyDown = (_this$props9 = this.props).onKeyDown) === null || _this$props$onKeyDown === void 0 ? void 0 : _this$props$onKeyDown.call(_this$props9, event);
186
+ };
187
+ this.parseInputText = inputValue => {
188
+ const input = DateTime.parse(inputValue, this.locale(), this.timezone(), [this.props.format], true);
189
+ const baseDate = this.getBaseDate();
190
+ input.year(baseDate.year());
191
+ input.month(baseDate.month());
192
+ input.date(baseDate.date());
193
+ return input;
149
194
  };
150
195
  this.state = this.getInitialState();
151
196
  }
@@ -212,7 +257,10 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
212
257
  const outsideVal = this.props.value ? this.props.value : '';
213
258
  // value does not match an existing option
214
259
  const date = DateTime.parse(outsideVal, this.locale(), this.timezone());
215
- const label = this.props.format ? date.format(this.props.format) : date.toISOString();
260
+ let label = '';
261
+ if (date.isValid()) {
262
+ label = this.props.format ? date.format(this.props.format) : date.toISOString();
263
+ }
216
264
  this.setState({
217
265
  inputValue: option ? option.label : label,
218
266
  selectedOptionId: option ? option.id : void 0
@@ -241,11 +289,11 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
241
289
  };
242
290
  }
243
291
  getInitialOption(options) {
244
- const _this$props7 = this.props,
245
- value = _this$props7.value,
246
- defaultValue = _this$props7.defaultValue,
247
- defaultToFirstOption = _this$props7.defaultToFirstOption,
248
- format = _this$props7.format;
292
+ const _this$props10 = this.props,
293
+ value = _this$props10.value,
294
+ defaultValue = _this$props10.defaultValue,
295
+ defaultToFirstOption = _this$props10.defaultToFirstOption,
296
+ format = _this$props10.format;
249
297
  const initialValue = value || defaultValue;
250
298
  if (typeof initialValue === 'string') {
251
299
  // get option based on value or defaultValue, if provided
@@ -380,31 +428,33 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
380
428
  }, callRenderProp(this.props.renderEmptyOption));
381
429
  }
382
430
  render() {
383
- const _this$props8 = this.props,
384
- value = _this$props8.value,
385
- defaultValue = _this$props8.defaultValue,
386
- placeholder = _this$props8.placeholder,
387
- renderLabel = _this$props8.renderLabel,
388
- inputRef = _this$props8.inputRef,
389
- id = _this$props8.id,
390
- listRef = _this$props8.listRef,
391
- renderBeforeInput = _this$props8.renderBeforeInput,
392
- renderAfterInput = _this$props8.renderAfterInput,
393
- isRequired = _this$props8.isRequired,
394
- isInline = _this$props8.isInline,
395
- width = _this$props8.width,
396
- format = _this$props8.format,
397
- step = _this$props8.step,
398
- optionsMaxWidth = _this$props8.optionsMaxWidth,
399
- visibleOptionsCount = _this$props8.visibleOptionsCount,
400
- messages = _this$props8.messages,
401
- placement = _this$props8.placement,
402
- constrain = _this$props8.constrain,
403
- onFocus = _this$props8.onFocus,
404
- onShowOptions = _this$props8.onShowOptions,
405
- onHideOptions = _this$props8.onHideOptions,
406
- mountNode = _this$props8.mountNode,
407
- rest = _objectWithoutProperties(_this$props8, _excluded);
431
+ const _this$props11 = this.props,
432
+ value = _this$props11.value,
433
+ defaultValue = _this$props11.defaultValue,
434
+ placeholder = _this$props11.placeholder,
435
+ renderLabel = _this$props11.renderLabel,
436
+ inputRef = _this$props11.inputRef,
437
+ id = _this$props11.id,
438
+ listRef = _this$props11.listRef,
439
+ renderBeforeInput = _this$props11.renderBeforeInput,
440
+ renderAfterInput = _this$props11.renderAfterInput,
441
+ isRequired = _this$props11.isRequired,
442
+ isInline = _this$props11.isInline,
443
+ width = _this$props11.width,
444
+ format = _this$props11.format,
445
+ step = _this$props11.step,
446
+ optionsMaxWidth = _this$props11.optionsMaxWidth,
447
+ visibleOptionsCount = _this$props11.visibleOptionsCount,
448
+ messages = _this$props11.messages,
449
+ placement = _this$props11.placement,
450
+ constrain = _this$props11.constrain,
451
+ onFocus = _this$props11.onFocus,
452
+ onShowOptions = _this$props11.onShowOptions,
453
+ onHideOptions = _this$props11.onHideOptions,
454
+ onInputChange = _this$props11.onInputChange,
455
+ onKeyDown = _this$props11.onKeyDown,
456
+ mountNode = _this$props11.mountNode,
457
+ rest = _objectWithoutProperties(_this$props11, _excluded);
408
458
  const _this$state4 = this.state,
409
459
  inputValue = _this$state4.inputValue,
410
460
  isShowingOptions = _this$state4.isShowingOptions;
@@ -435,7 +485,8 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
435
485
  onRequestHideOptions: this.handleHideOptions,
436
486
  onRequestHighlightOption: this.handleHighlightOption,
437
487
  onRequestSelectOption: this.handleSelectOption,
438
- onInputChange: this.handleInputChange
488
+ onInputChange: this.handleInputChange,
489
+ onKeyDown: this.onKeyDown
439
490
  }, passthroughProps(rest)), isShowingOptions && this.renderOptions());
440
491
  }
441
492
  }, _class2.displayName = "TimeSelect", _class2.componentId = 'TimeSelect', _class2.allowedProps = allowedProps, _class2.propTypes = propTypes, _class2.defaultProps = {
@@ -448,7 +499,8 @@ let TimeSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_class
448
499
  visibleOptionsCount: 8,
449
500
  placement: 'bottom stretch',
450
501
  constrain: 'window',
451
- renderEmptyOption: '---'
502
+ renderEmptyOption: '---',
503
+ allowNonStepInput: false
452
504
  }, _class2.contextType = ApplyLocaleContext, _class2)) || _class) || _class);
453
505
  export { TimeSelect };
454
506
  export default TimeSelect;
@@ -57,7 +57,9 @@ const propTypes = {
57
57
  renderBeforeInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
58
58
  renderAfterInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
59
59
  locale: PropTypes.string,
60
- timezone: PropTypes.string
60
+ timezone: PropTypes.string,
61
+ allowNonStepInput: PropTypes.bool,
62
+ onInputChange: PropTypes.func
61
63
  };
62
- 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'];
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'];
63
65
  export { propTypes, allowedProps };
@@ -18,7 +18,7 @@ var _withDeterministicId = require("@instructure/ui-react-utils/lib/Deterministi
18
18
  var _testable = require("@instructure/ui-testable/lib/testable.js");
19
19
  var _Select = require("@instructure/ui-select/lib/Select");
20
20
  var _props = require("./props");
21
- const _excluded = ["value", "defaultValue", "placeholder", "renderLabel", "inputRef", "id", "listRef", "renderBeforeInput", "renderAfterInput", "isRequired", "isInline", "width", "format", "step", "optionsMaxWidth", "visibleOptionsCount", "messages", "placement", "constrain", "onFocus", "onShowOptions", "onHideOptions", "mountNode"];
21
+ const _excluded = ["value", "defaultValue", "placeholder", "renderLabel", "inputRef", "id", "listRef", "renderBeforeInput", "renderAfterInput", "isRequired", "isInline", "width", "format", "step", "optionsMaxWidth", "visibleOptionsCount", "messages", "placement", "constrain", "onFocus", "onShowOptions", "onHideOptions", "onInputChange", "onKeyDown", "mountNode"];
22
22
  var _dec, _dec2, _class, _class2;
23
23
  /**
24
24
  ---
@@ -50,6 +50,7 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
50
50
  (_this$props$onBlur = (_this$props = this.props).onBlur) === null || _this$props$onBlur === void 0 ? void 0 : _this$props$onBlur.call(_this$props, event);
51
51
  };
52
52
  this.handleInputChange = event => {
53
+ var _this$props$onInputCh, _this$props3;
53
54
  const value = event.target.value;
54
55
  const newOptions = this.filterOptions(value);
55
56
  this.setState({
@@ -62,32 +63,44 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
62
63
  var _this$props$onShowOpt, _this$props2;
63
64
  (_this$props$onShowOpt = (_this$props2 = this.props).onShowOptions) === null || _this$props$onShowOpt === void 0 ? void 0 : _this$props$onShowOpt.call(_this$props2, event);
64
65
  }
66
+ const inputAsDate = this.parseInputText(value);
67
+ (_this$props$onInputCh = (_this$props3 = this.props).onInputChange) === null || _this$props$onInputCh === void 0 ? void 0 : _this$props$onInputCh.call(_this$props3, event, value, inputAsDate.isValid() ? inputAsDate.toISOString() : void 0);
65
68
  };
66
69
  this.handleShowOptions = event => {
67
- var _this$props$onShowOpt2, _this$props3;
70
+ var _this$props$onShowOpt2, _this$props4;
68
71
  this.setState({
69
72
  isShowingOptions: true,
70
73
  highlightedOptionId: this.state.selectedOptionId
71
74
  });
72
- (_this$props$onShowOpt2 = (_this$props3 = this.props).onShowOptions) === null || _this$props$onShowOpt2 === void 0 ? void 0 : _this$props$onShowOpt2.call(_this$props3, event);
75
+ (_this$props$onShowOpt2 = (_this$props4 = this.props).onShowOptions) === null || _this$props$onShowOpt2 === void 0 ? void 0 : _this$props$onShowOpt2.call(_this$props4, event);
73
76
  };
74
77
  this.handleHideOptions = event => {
75
- var _this$props$onHideOpt, _this$props4;
76
- const selectedOptionId = this.state.selectedOptionId;
77
- const option = this.getOption('id', selectedOptionId);
78
- let prevValue = '';
78
+ var _this$props$onHideOpt, _this$props5;
79
+ const input = this.parseInputText(this.state.inputValue);
80
+ let defaultValue = '';
79
81
  if (this.props.defaultValue) {
80
82
  const date = _DateTime.DateTime.parse(this.props.defaultValue, this.locale(), this.timezone());
81
- prevValue = this.props.format ? date.format(this.props.format) : date.toISOString();
83
+ defaultValue = this.props.format ? date.format(this.props.format) : date.toISOString();
82
84
  }
83
- this.setState(() => ({
84
- isShowingOptions: false,
85
- highlightedOptionId: void 0,
86
- inputValue: selectedOptionId ? option.label : prevValue,
87
- filteredOptions: this.filterOptions(''),
88
- ...this.matchValue()
89
- }));
90
- (_this$props$onHideOpt = (_this$props4 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props4, event);
85
+ if (!this.props.allowNonStepInput || this.props.allowNonStepInput && input.isValid() && input.minute() % this.props.step === 0) {
86
+ const selectedOptionId = this.state.selectedOptionId;
87
+ const option = this.getOption('id', selectedOptionId);
88
+ this.setState(() => ({
89
+ isShowingOptions: false,
90
+ highlightedOptionId: void 0,
91
+ inputValue: selectedOptionId ? option.label : defaultValue,
92
+ filteredOptions: this.filterOptions(''),
93
+ ...this.matchValue()
94
+ }));
95
+ } else {
96
+ this.setState(() => ({
97
+ isShowingOptions: false,
98
+ highlightedOptionId: void 0,
99
+ inputValue: this.state.lastValidInput || defaultValue || '',
100
+ filteredOptions: this.filterOptions('')
101
+ }));
102
+ }
103
+ (_this$props$onHideOpt = (_this$props5 = this.props).onHideOptions) === null || _this$props$onHideOpt === void 0 ? void 0 : _this$props$onHideOpt.call(_this$props5, event);
91
104
  };
92
105
  this.handleHighlightOption = (event, _ref) => {
93
106
  let id = _ref.id;
@@ -100,7 +113,7 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
100
113
  }));
101
114
  };
102
115
  this.handleSelectOption = (event, _ref2) => {
103
- var _this$props$onHideOpt2, _this$props6;
116
+ var _this$props$onHideOpt2, _this$props7;
104
117
  let id = _ref2.id;
105
118
  if (id === this._emptyOptionId) {
106
119
  this.setState({
@@ -128,13 +141,45 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
128
141
  });
129
142
  }
130
143
  if (id !== this.state.selectedOptionId) {
131
- var _this$props$onChange, _this$props5;
132
- (_this$props$onChange = (_this$props5 = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props5, event, {
144
+ var _this$props$onChange, _this$props6;
145
+ this.setState({
146
+ lastValidInput: newInputValue
147
+ });
148
+ (_this$props$onChange = (_this$props6 = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props6, event, {
133
149
  value: option.value,
134
150
  inputText: newInputValue
135
151
  });
136
152
  }
137
- (_this$props$onHideOpt2 = (_this$props6 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props6, event);
153
+ (_this$props$onHideOpt2 = (_this$props7 = this.props).onHideOptions) === null || _this$props$onHideOpt2 === void 0 ? void 0 : _this$props$onHideOpt2.call(_this$props7, event);
154
+ };
155
+ this.onKeyDown = event => {
156
+ var _this$props$onKeyDown, _this$props9;
157
+ const input = this.parseInputText(this.state.inputValue);
158
+ // validate input and if OK close dropdown
159
+ if (event.key === 'Enter' && this.props.allowNonStepInput && input.isValid() && input.minute() % this.props.step !== 0 && this.state.lastValidInput != this.state.inputValue) {
160
+ var _this$props$onChange2, _this$props8;
161
+ this.setState(() => ({
162
+ isShowingOptions: false,
163
+ highlightedOptionId: void 0,
164
+ filteredOptions: this.filterOptions(''),
165
+ lastValidInput: this.state.inputValue
166
+ }));
167
+ (_this$props$onChange2 = (_this$props8 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props8, event, {
168
+ value: input.toISOString(),
169
+ inputText: this.state.inputValue
170
+ });
171
+ // others are set in OnHideOptions
172
+ }
173
+
174
+ (_this$props$onKeyDown = (_this$props9 = this.props).onKeyDown) === null || _this$props$onKeyDown === void 0 ? void 0 : _this$props$onKeyDown.call(_this$props9, event);
175
+ };
176
+ this.parseInputText = inputValue => {
177
+ const input = _DateTime.DateTime.parse(inputValue, this.locale(), this.timezone(), [this.props.format], true);
178
+ const baseDate = this.getBaseDate();
179
+ input.year(baseDate.year());
180
+ input.month(baseDate.month());
181
+ input.date(baseDate.date());
182
+ return input;
138
183
  };
139
184
  this.state = this.getInitialState();
140
185
  }
@@ -201,7 +246,10 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
201
246
  const outsideVal = this.props.value ? this.props.value : '';
202
247
  // value does not match an existing option
203
248
  const date = _DateTime.DateTime.parse(outsideVal, this.locale(), this.timezone());
204
- const label = this.props.format ? date.format(this.props.format) : date.toISOString();
249
+ let label = '';
250
+ if (date.isValid()) {
251
+ label = this.props.format ? date.format(this.props.format) : date.toISOString();
252
+ }
205
253
  this.setState({
206
254
  inputValue: option ? option.label : label,
207
255
  selectedOptionId: option ? option.id : void 0
@@ -230,11 +278,11 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
230
278
  };
231
279
  }
232
280
  getInitialOption(options) {
233
- const _this$props7 = this.props,
234
- value = _this$props7.value,
235
- defaultValue = _this$props7.defaultValue,
236
- defaultToFirstOption = _this$props7.defaultToFirstOption,
237
- format = _this$props7.format;
281
+ const _this$props10 = this.props,
282
+ value = _this$props10.value,
283
+ defaultValue = _this$props10.defaultValue,
284
+ defaultToFirstOption = _this$props10.defaultToFirstOption,
285
+ format = _this$props10.format;
238
286
  const initialValue = value || defaultValue;
239
287
  if (typeof initialValue === 'string') {
240
288
  // get option based on value or defaultValue, if provided
@@ -369,31 +417,33 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
369
417
  }, (0, _callRenderProp.callRenderProp)(this.props.renderEmptyOption));
370
418
  }
371
419
  render() {
372
- const _this$props8 = this.props,
373
- value = _this$props8.value,
374
- defaultValue = _this$props8.defaultValue,
375
- placeholder = _this$props8.placeholder,
376
- renderLabel = _this$props8.renderLabel,
377
- inputRef = _this$props8.inputRef,
378
- id = _this$props8.id,
379
- listRef = _this$props8.listRef,
380
- renderBeforeInput = _this$props8.renderBeforeInput,
381
- renderAfterInput = _this$props8.renderAfterInput,
382
- isRequired = _this$props8.isRequired,
383
- isInline = _this$props8.isInline,
384
- width = _this$props8.width,
385
- format = _this$props8.format,
386
- step = _this$props8.step,
387
- optionsMaxWidth = _this$props8.optionsMaxWidth,
388
- visibleOptionsCount = _this$props8.visibleOptionsCount,
389
- messages = _this$props8.messages,
390
- placement = _this$props8.placement,
391
- constrain = _this$props8.constrain,
392
- onFocus = _this$props8.onFocus,
393
- onShowOptions = _this$props8.onShowOptions,
394
- onHideOptions = _this$props8.onHideOptions,
395
- mountNode = _this$props8.mountNode,
396
- rest = (0, _objectWithoutProperties2.default)(_this$props8, _excluded);
420
+ const _this$props11 = this.props,
421
+ value = _this$props11.value,
422
+ defaultValue = _this$props11.defaultValue,
423
+ placeholder = _this$props11.placeholder,
424
+ renderLabel = _this$props11.renderLabel,
425
+ inputRef = _this$props11.inputRef,
426
+ id = _this$props11.id,
427
+ listRef = _this$props11.listRef,
428
+ renderBeforeInput = _this$props11.renderBeforeInput,
429
+ renderAfterInput = _this$props11.renderAfterInput,
430
+ isRequired = _this$props11.isRequired,
431
+ isInline = _this$props11.isInline,
432
+ width = _this$props11.width,
433
+ format = _this$props11.format,
434
+ step = _this$props11.step,
435
+ optionsMaxWidth = _this$props11.optionsMaxWidth,
436
+ visibleOptionsCount = _this$props11.visibleOptionsCount,
437
+ messages = _this$props11.messages,
438
+ placement = _this$props11.placement,
439
+ constrain = _this$props11.constrain,
440
+ onFocus = _this$props11.onFocus,
441
+ onShowOptions = _this$props11.onShowOptions,
442
+ onHideOptions = _this$props11.onHideOptions,
443
+ onInputChange = _this$props11.onInputChange,
444
+ onKeyDown = _this$props11.onKeyDown,
445
+ mountNode = _this$props11.mountNode,
446
+ rest = (0, _objectWithoutProperties2.default)(_this$props11, _excluded);
397
447
  const _this$state4 = this.state,
398
448
  inputValue = _this$state4.inputValue,
399
449
  isShowingOptions = _this$state4.isShowingOptions;
@@ -424,7 +474,8 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
424
474
  onRequestHideOptions: this.handleHideOptions,
425
475
  onRequestHighlightOption: this.handleHighlightOption,
426
476
  onRequestSelectOption: this.handleSelectOption,
427
- onInputChange: this.handleInputChange
477
+ onInputChange: this.handleInputChange,
478
+ onKeyDown: this.onKeyDown
428
479
  }, (0, _passthroughProps.passthroughProps)(rest)), isShowingOptions && this.renderOptions());
429
480
  }
430
481
  }, _class2.displayName = "TimeSelect", _class2.componentId = 'TimeSelect', _class2.allowedProps = _props.allowedProps, _class2.propTypes = _props.propTypes, _class2.defaultProps = {
@@ -437,7 +488,8 @@ let TimeSelect = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2
437
488
  visibleOptionsCount: 8,
438
489
  placement: 'bottom stretch',
439
490
  constrain: 'window',
440
- renderEmptyOption: '---'
491
+ renderEmptyOption: '---',
492
+ allowNonStepInput: false
441
493
  }, _class2.contextType = _ApplyLocaleContext.ApplyLocaleContext, _class2)) || _class) || _class);
442
494
  exports.TimeSelect = TimeSelect;
443
495
  var _default = TimeSelect;
@@ -64,8 +64,10 @@ const propTypes = {
64
64
  renderBeforeInput: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.func]),
65
65
  renderAfterInput: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.func]),
66
66
  locale: _propTypes.default.string,
67
- timezone: _propTypes.default.string
67
+ timezone: _propTypes.default.string,
68
+ allowNonStepInput: _propTypes.default.bool,
69
+ onInputChange: _propTypes.default.func
68
70
  };
69
71
  exports.propTypes = propTypes;
70
- 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'];
72
+ 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'];
71
73
  exports.allowedProps = allowedProps;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-time-select",
3
- "version": "8.34.1-snapshot-1",
3
+ "version": "8.35.0",
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,20 +24,21 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.20.13",
27
- "@instructure/shared-types": "8.34.1-snapshot-1",
28
- "@instructure/ui-form-field": "8.34.1-snapshot-1",
29
- "@instructure/ui-i18n": "8.34.1-snapshot-1",
30
- "@instructure/ui-position": "8.34.1-snapshot-1",
31
- "@instructure/ui-prop-types": "8.34.1-snapshot-1",
32
- "@instructure/ui-react-utils": "8.34.1-snapshot-1",
33
- "@instructure/ui-select": "8.34.1-snapshot-1",
34
- "@instructure/ui-test-locator": "8.34.1-snapshot-1",
35
- "@instructure/ui-testable": "8.34.1-snapshot-1",
27
+ "@instructure/shared-types": "8.35.0",
28
+ "@instructure/ui-form-field": "8.35.0",
29
+ "@instructure/ui-i18n": "8.35.0",
30
+ "@instructure/ui-position": "8.35.0",
31
+ "@instructure/ui-prop-types": "8.35.0",
32
+ "@instructure/ui-react-utils": "8.35.0",
33
+ "@instructure/ui-select": "8.35.0",
34
+ "@instructure/ui-test-locator": "8.35.0",
35
+ "@instructure/ui-testable": "8.35.0",
36
+ "@instructure/ui-utils": "8.35.0",
36
37
  "prop-types": "^15.8.1"
37
38
  },
38
39
  "devDependencies": {
39
- "@instructure/ui-babel-preset": "8.34.1-snapshot-1",
40
- "@instructure/ui-test-utils": "8.34.1-snapshot-1",
40
+ "@instructure/ui-babel-preset": "8.35.0",
41
+ "@instructure/ui-test-utils": "8.35.0",
41
42
  "moment-timezone": "^0.5.40"
42
43
  },
43
44
  "peerDependencies": {
@@ -5,6 +5,7 @@ describes: TimeSelect
5
5
  `TimeSelect` component is a higher level abstraction of [Select](#Select) specifically for selecting time values. The list of possible values can be configured via the component's props. If you need to select times and dates, be sure to check out the documentation around [Time and Date patterns](#TimeDate).
6
6
 
7
7
  ### Uncontrolled
8
+
8
9
  For the most basic implementations, `TimeSelect` can be uncontrolled. If desired, the `defaultValue` prop can be used to set the initial selection.
9
10
 
10
11
  ```javascript
@@ -15,11 +16,13 @@ render: true
15
16
  <TimeSelect
16
17
  renderLabel="Choose a time"
17
18
  onChange={(e, { value }) => console.log(value)}
19
+ onHideOptions={(e)=> console.log("hide opts")}
18
20
  // defaultValue={new Date().toISOString()}
19
21
  />
20
22
  ```
21
23
 
22
24
  ### Controlled
25
+
23
26
  To use `TimeSelect` controlled, simply provide the `value` prop an ISO string. The `onChange` callback provides the ISO value of the corresponding option that was selected. Use this value to update the state.
24
27
 
25
28
  ```javascript
@@ -54,7 +57,29 @@ render(
54
57
  )
55
58
  ```
56
59
 
60
+ ### Freeform input
61
+
62
+ By default, the user can only set a value that is divisible by `step` (although the component allows to set any valid time value programmatically). You can allow the user to set any valid value with typing in by setting `allowNonStepInput` to `true`. You can use the `onInputChange` event to see whether the current input is valid and its current value.
63
+ Note that the exact value needed to be typed by the user depends on their `locale`:
64
+
65
+ ```javascript
66
+ ---
67
+ example: true
68
+ render: true
69
+ ---
70
+ <TimeSelect
71
+ renderLabel="Choose a time"
72
+ onChange={(e, { value }) => console.log("change",value)}
73
+ onInputChange={(e, value, isoValue)=> console.log("inputChange", value, isoValue)}
74
+ defaultValue="2022-05-12T05:30:00.000Z"
75
+ locale="en_AU"
76
+ timezone='US/Eastern'
77
+ allowNonStepInput
78
+ />
79
+ ```
80
+
57
81
  ### Guidelines
82
+
58
83
  ```js
59
84
  ---
60
85
  guidelines: true
@@ -62,6 +87,7 @@ guidelines: true
62
87
  <Guidelines>
63
88
  <Figure recommendation="yes" title="Do">
64
89
  <Figure.Item>Use a default value of 11:59 pm for implementations that have to do with due dates</Figure.Item>
90
+ <Figure.Item>Respect the user's `locale` and `timezone` browser settings (the component does this by itself when not setting `locale` or `timezone`).</Figure.Item>
65
91
  </Figure>
66
92
  </Guidelines>
67
- ```
93
+ ```