@instructure/ui-time-select 8.14.1-snapshot.21 → 8.14.1-snapshot.31

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.
@@ -22,7 +22,7 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import React, { BaseSyntheticEvent, Component } from 'react'
25
+ import React, { Component } from 'react'
26
26
  import { Moment } from 'moment-timezone'
27
27
 
28
28
  import { ApplyLocaleContext, Locale, DateTime } from '@instructure/ui-i18n'
@@ -37,29 +37,27 @@ import { testable } from '@instructure/ui-testable'
37
37
  import { Select } from '@instructure/ui-select'
38
38
 
39
39
  import type { SelectProps } from '@instructure/ui-select'
40
- import type { TimeSelectProps } from './props'
40
+ import type {
41
+ TimeSelectProps,
42
+ TimeSelectState,
43
+ TimeSelectOptions
44
+ } from './props'
41
45
 
42
46
  import { allowedProps, propTypes } from './props'
43
47
 
44
- type TimeSelectOptions = {
45
- id: string
46
- value: string
47
- label: string
48
- }
49
-
50
- type TimeSelectState = {
51
- inputValue: string
52
- options: TimeSelectOptions[]
53
- filteredOptions: TimeSelectOptions[]
54
- isShowingOptions: boolean
55
- highlightedOptionId?: string
56
- selectedOptionId?: string
57
- }
48
+ type GetOption = <F extends keyof TimeSelectOptions>(
49
+ field: F,
50
+ value?: TimeSelectOptions[F],
51
+ options?: TimeSelectOptions[]
52
+ ) => TimeSelectOptions | undefined
58
53
 
59
54
  /**
60
55
  ---
61
56
  category: components
62
57
  ---
58
+ @tsProps
59
+
60
+ A component used to select a time value.
63
61
  **/
64
62
  @testable()
65
63
  class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
@@ -70,7 +68,6 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
70
68
 
71
69
  static defaultProps = {
72
70
  defaultToFirstOption: false,
73
- id: undefined,
74
71
  format: 'LT', // see https://momentjs.com/docs/#/displaying/
75
72
  step: 30,
76
73
  isRequired: false,
@@ -78,27 +75,14 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
78
75
  visibleOptionsCount: 8,
79
76
  placement: 'bottom stretch',
80
77
  constrain: 'window',
81
- // @ts-expect-error ts-migrate(6133) FIXME: 'event' is declared but its value is never read.
82
- onChange: (event, data) => {},
83
- // @ts-expect-error ts-migrate(6133) FIXME: 'event' is declared but its value is never read.
84
- onFocus: (event) => {},
85
- // @ts-expect-error ts-migrate(6133) FIXME: 'event' is declared but its value is never read.
86
- onBlur: (event) => {},
87
- // @ts-expect-error ts-migrate(6133) FIXME: 'event' is declared but its value is never read.
88
- onShowOptions: (event) => {},
89
- // @ts-expect-error ts-migrate(6133) FIXME: 'event' is declared but its value is never read.
90
- onHideOptions: (event) => {},
91
- // @ts-expect-error ts-migrate(6133) FIXME: 'node' is declared but its value is never read.
92
- inputRef: (node) => {},
93
- // @ts-expect-error ts-migrate(6133) FIXME: 'node' is declared but its value is never read.
94
- listRef: (node) => {},
95
- renderEmptyOption: '---',
96
- renderBeforeInput: null,
97
- renderAfterInput: null
78
+ renderEmptyOption: '---'
98
79
  }
99
80
 
100
81
  static contextType = ApplyLocaleContext
101
82
 
83
+ ref: Select | null = null
84
+ private readonly _emptyOptionId = uid('Select-EmptyOption')
85
+
102
86
  constructor(props: TimeSelectProps) {
103
87
  super(props)
104
88
  this.state = this.getInitialState()
@@ -110,21 +94,17 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
110
94
  this.setState(this.getInitialState())
111
95
  }
112
96
 
113
- ref: Element | null = null
114
- _emptyOptionId = uid('Select-EmptyOption')
115
-
116
97
  focus() {
117
- // @ts-expect-error ts-migrate(2339) FIXME: Property 'ref' does not exist on type 'TimeSel... Remove this comment to see the full error message
118
- this.ref && this.ref.focus()
98
+ this.ref?.focus()
119
99
  }
120
100
 
121
101
  get _select() {
122
102
  console.warn(
123
103
  '_select property is deprecated and will be removed in v9, please use ref instead'
124
104
  )
125
-
126
105
  return this.ref
127
106
  }
107
+
128
108
  get isControlled() {
129
109
  return typeof this.props.value !== 'undefined'
130
110
  }
@@ -134,7 +114,6 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
134
114
  }
135
115
 
136
116
  get focused() {
137
- // @ts-expect-error ts-migrate(2339) FIXME: Property 'ref' does not exist on type 'TimeSel... Remove this comment to see the full error message
138
117
  return this.ref && this.ref.focused
139
118
  }
140
119
 
@@ -185,7 +164,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
185
164
  }
186
165
  }
187
166
 
188
- getInitialState() {
167
+ getInitialState(): TimeSelectState {
189
168
  const initialOptions = this.generateOptions()
190
169
  const initialSelection = this.getInitialOption(initialOptions)
191
170
  return {
@@ -193,16 +172,14 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
193
172
  options: initialOptions,
194
173
  filteredOptions: initialOptions,
195
174
  isShowingOptions: false,
196
- highlightedOptionId: initialSelection
197
- ? (initialSelection as any).id
198
- : undefined,
199
- selectedOptionId: initialSelection
200
- ? (initialSelection as any).id
201
- : undefined
175
+ highlightedOptionId: initialSelection ? initialSelection.id : undefined,
176
+ selectedOptionId: initialSelection ? initialSelection.id : undefined
202
177
  }
203
178
  }
204
179
 
205
- getInitialOption(options: TimeSelectOptions[]) {
180
+ getInitialOption(
181
+ options: TimeSelectOptions[]
182
+ ): TimeSelectOptions | undefined {
206
183
  const { value, defaultValue, defaultToFirstOption, format } = this.props
207
184
  const initialValue = value || defaultValue
208
185
 
@@ -217,22 +194,21 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
217
194
  const date = DateTime.parse(initialValue, this.locale(), this.timezone())
218
195
  return { label: format ? date.format(format) : date.toISOString() }
219
196
  }
220
- // otherwise return first option, if desired
197
+ // otherwise, return first option, if desired
221
198
  if (defaultToFirstOption) {
222
199
  return options[0]
223
200
  }
224
201
  return undefined
225
202
  }
226
203
 
227
- getOption(field: string, value: unknown, options = this.state.options) {
228
- return options.find((option: any) => option[field] === value)
204
+ getOption: GetOption = (field, value, options = this.state.options) => {
205
+ return options.find((option) => option[field] === value)
229
206
  }
230
207
 
231
208
  getFormattedId(date: Moment) {
232
209
  // ISO8601 strings may contain a space. Remove any spaces before using the
233
210
  // date as the id.
234
- const dateStr = date.toISOString()
235
- return dateStr.replace(/\s/g, '')
211
+ return date.toISOString().replace(/\s/g, '')
236
212
  }
237
213
 
238
214
  getBaseDate() {
@@ -251,10 +227,8 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
251
227
  const options = []
252
228
 
253
229
  for (let hour = 0; hour < 24; hour++) {
254
- // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.
255
- for (let minute = 0; minute < 60 / this.props.step; minute++) {
256
- // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.
257
- const minutes = minute * this.props.step
230
+ for (let minute = 0; minute < 60 / this.props.step!; minute++) {
231
+ const minutes = minute * this.props.step!
258
232
  const newDate = date.set({ hour: hour, minute: minutes })
259
233
  // store time options
260
234
  options.push({
@@ -317,7 +291,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
317
291
  return undefined
318
292
  }
319
293
 
320
- handleRef = (node: any) => {
294
+ handleRef = (node: Select) => {
321
295
  this.ref = node
322
296
  }
323
297
 
@@ -326,16 +300,16 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
326
300
  this.props.onBlur?.(event)
327
301
  }
328
302
 
329
- handleInputChange = (event: BaseSyntheticEvent) => {
303
+ handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
330
304
  const value = event.target.value
331
305
  const newOptions = this.filterOptions(value)
332
306
 
333
- this.setState((_state: TimeSelectState) => ({
307
+ this.setState({
334
308
  inputValue: value,
335
309
  filteredOptions: newOptions,
336
310
  highlightedOptionId: newOptions.length > 0 ? newOptions[0].id : undefined,
337
311
  isShowingOptions: true
338
- }))
312
+ })
339
313
 
340
314
  if (!this.state.isShowingOptions) {
341
315
  this.props.onShowOptions?.(event)
@@ -368,7 +342,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
368
342
  : date.toISOString()
369
343
  }
370
344
 
371
- this.setState((_state: TimeSelectState) => ({
345
+ this.setState(() => ({
372
346
  isShowingOptions: false,
373
347
  highlightedOptionId: undefined,
374
348
  inputValue: selectedOptionId ? option!.label : prevValue,
@@ -400,7 +374,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
400
374
  this.setState({ isShowingOptions: false })
401
375
  return
402
376
  }
403
- const option = this.getOption('id', id)
377
+ const option = this.getOption('id', id)!
404
378
 
405
379
  let newInputValue: string
406
380
  if (this.isControlled) {
@@ -412,7 +386,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
412
386
  filteredOptions: this.filterOptions('')
413
387
  })
414
388
  } else {
415
- newInputValue = option!.label
389
+ newInputValue = option.label
416
390
  this.setState({
417
391
  isShowingOptions: false,
418
392
  selectedOptionId: id,
@@ -423,7 +397,7 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
423
397
 
424
398
  if (id !== this.state.selectedOptionId) {
425
399
  this.props.onChange?.(event, {
426
- value: option!.value,
400
+ value: option.value,
427
401
  inputText: newInputValue
428
402
  })
429
403
  }
@@ -438,11 +412,11 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
438
412
  return this.renderEmptyOption()
439
413
  }
440
414
 
441
- return filteredOptions.map((option: any) => {
415
+ return filteredOptions.map((option: TimeSelectOptions) => {
442
416
  const { id, label } = option
443
417
  return (
444
418
  <Select.Option
445
- id={id}
419
+ id={id!}
446
420
  key={id}
447
421
  isHighlighted={id === highlightedOptionId}
448
422
  isSelected={id === selectedOptionId}
@@ -493,7 +467,6 @@ class TimeSelect extends Component<TimeSelectProps, TimeSelectState> {
493
467
  } = this.props
494
468
 
495
469
  const { inputValue, isShowingOptions } = this.state
496
-
497
470
  return (
498
471
  <Select
499
472
  renderLabel={renderLabel}
@@ -40,38 +40,6 @@ import type {
40
40
  PositionConstraint
41
41
  } from '@instructure/ui-position'
42
42
 
43
- type TimeSelectOwnProps = {
44
- renderLabel: React.ReactNode | ((...args: any[]) => any)
45
- defaultToFirstOption?: boolean
46
- value?: any // TODO: controllable(I18nPropTypes.iso8601, 'onChange'),
47
- defaultValue?: string
48
- id?: string
49
- format?: string
50
- step?: 5 | 10 | 15 | 20 | 30 | 60
51
- interaction?: 'enabled' | 'disabled' | 'readonly'
52
- placeholder?: string
53
- isRequired?: boolean
54
- isInline?: boolean
55
- width?: string
56
- optionsMaxWidth?: string
57
- visibleOptionsCount?: number
58
- messages?: FormMessage[]
59
- placement?: PlacementPropValues
60
- constrain?: PositionConstraint
61
- onChange?: (...args: any[]) => any
62
- onFocus?: (...args: any[]) => any
63
- onBlur?: (...args: any[]) => any
64
- onShowOptions?: (...args: any[]) => any
65
- onHideOptions?: (...args: any[]) => any
66
- inputRef?: (...args: any[]) => any
67
- listRef?: (...args: any[]) => any
68
- renderEmptyOption?: React.ReactNode | ((...args: any[]) => any)
69
- renderBeforeInput?: React.ReactNode | ((...args: any[]) => any)
70
- renderAfterInput?: React.ReactNode | ((...args: any[]) => any)
71
- locale?: string
72
- timezone?: string
73
- }
74
-
75
43
  type PropKeys = keyof TimeSelectOwnProps
76
44
 
77
45
  type AllowedPropKeys = Readonly<Array<PropKeys>>
@@ -82,136 +50,133 @@ type TimeSelectProps = TimeSelectOwnProps &
82
50
  InputHTMLAttributes<TimeSelectOwnProps>
83
51
  >
84
52
 
85
- const propTypes: PropValidators<PropKeys> = {
53
+ type TimeSelectOwnProps = {
86
54
  /**
87
55
  * The form field label.
88
56
  */
89
- renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
57
+ renderLabel: React.ReactNode | (() => React.ReactNode)
90
58
  /**
91
59
  * Whether to default to the first option when `defaultValue` hasn't been specified.
92
60
  */
93
- defaultToFirstOption: PropTypes.bool,
61
+ defaultToFirstOption?: boolean
94
62
  /**
95
63
  * An ISO 8601 formatted date string representing the current selected value. If defined,
96
64
  * the component will act controlled and will not manage its own state.
97
65
  */
98
- value: controllable(I18nPropTypes.iso8601, 'onChange'),
66
+ value?: string // TODO: controllable(I18nPropTypes.iso8601, 'onChange'),
99
67
  /**
100
68
  * An ISO 8601 formatted date string to use if `value` isn't provided.
101
69
  */
102
- defaultValue: I18nPropTypes.iso8601,
70
+ defaultValue?: string
103
71
  /**
104
72
  * The id of the text input. One is generated if not supplied.
105
73
  */
106
- id: PropTypes.string,
74
+ id?: string
107
75
  /**
108
76
  * The format to use when displaying the possible and currently selected options.
109
77
  *
110
78
  * See [moment](https://momentjs.com/docs/#/displaying/format/) for the list
111
79
  * of available formats.
112
80
  */
113
- format: PropTypes.string,
81
+ format?: string
114
82
  /**
115
83
  * The number of minutes to increment by when generating the allowable options.
116
84
  */
117
- step: PropTypes.oneOf([5, 10, 15, 20, 30, 60]),
85
+ step?: 5 | 10 | 15 | 20 | 30 | 60
118
86
  /**
119
87
  * Specifies if interaction with the input is enabled, disabled, or readonly.
120
88
  * When "disabled", the input changes visibly to indicate that it cannot
121
89
  * receive user interactions. When "readonly" the input still cannot receive
122
- * user interactions but it keeps the same styles as if it were enabled.
90
+ * user interactions, but it keeps the same styles as if it were enabled.
123
91
  */
124
- interaction: PropTypes.oneOf(['enabled', 'disabled', 'readonly']),
92
+ interaction?: 'enabled' | 'disabled' | 'readonly'
125
93
  /**
126
94
  * Html placeholder text to display when the input has no value. This should
127
95
  * be hint text, not a label replacement.
128
96
  */
129
- placeholder: PropTypes.string,
130
- /**
131
- * Whether or not the text input is required.
132
- */
133
- isRequired: PropTypes.bool,
97
+ placeholder?: string
98
+ isRequired?: boolean
134
99
  /**
135
100
  * Whether the input is rendered inline with other elements or if it
136
101
  * is rendered as a block level element.
137
102
  */
138
- isInline: PropTypes.bool,
103
+ isInline?: boolean
139
104
  /**
140
105
  * The width of the text input.
141
106
  */
142
- width: PropTypes.string,
107
+ width?: string
143
108
  /**
144
109
  * The max width the options list can be before option text wraps. If not
145
110
  * set, the list will only display as wide as the text input.
146
111
  */
147
- optionsMaxWidth: PropTypes.string,
112
+ optionsMaxWidth?: string
148
113
  /**
149
114
  * The number of options that should be visible before having to scroll.
150
115
  */
151
- visibleOptionsCount: PropTypes.number,
116
+ visibleOptionsCount?: number
152
117
  /**
153
- * Displays messages and validation for the input. It should be an object
154
- * with the following shape:
118
+ * Displays messages and validation for the input. It should be an array of
119
+ * objects with the following shape:
155
120
  * `{
156
- * text: PropTypes.node,
157
- * type: PropTypes.oneOf(['error', 'hint', 'success', 'screenreader-only'])
121
+ * text: ReactNode,
122
+ * type: One of: ['error', 'hint', 'success', 'screenreader-only']
158
123
  * }`
159
124
  */
160
- messages: PropTypes.arrayOf(FormPropTypes.message),
125
+ messages?: FormMessage[]
161
126
  /**
162
127
  * The placement of the options list.
163
128
  */
164
- placement: PositionPropTypes.placement,
129
+ placement?: PlacementPropValues
165
130
  /**
166
131
  * The parent in which to constrain the placement.
167
132
  */
168
- constrain: PositionPropTypes.constrain,
133
+ constrain?: PositionConstraint
169
134
  /**
170
135
  * Callback fired when a new option is selected.
171
- * @param {Object} event - the event object
172
- * @param {Object} data - additional data
173
- * @param data.value - the value of selected option
136
+ * @param event - the event object
137
+ * @param data - additional data
174
138
  */
175
- onChange: PropTypes.func,
139
+ onChange?: (
140
+ event: React.SyntheticEvent,
141
+ data: { value?: string; inputText: string }
142
+ ) => void
176
143
  /**
177
144
  * Callback fired when text input receives focus.
178
145
  */
179
- onFocus: PropTypes.func,
146
+ onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void
180
147
  /**
181
148
  * Callback fired when text input loses focus.
182
149
  */
183
- onBlur: PropTypes.func,
150
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void
184
151
  /**
185
152
  * Callback fired when the options list is shown.
186
153
  */
187
- onShowOptions: PropTypes.func,
154
+ onShowOptions?: (event: React.SyntheticEvent) => void
188
155
  /**
189
156
  * Callback fired when the options list is hidden.
190
157
  */
191
- onHideOptions: PropTypes.func,
158
+ onHideOptions?: (event: React.SyntheticEvent) => void
192
159
  /**
193
160
  * A ref to the html `input` element.
194
161
  */
195
- inputRef: PropTypes.func,
162
+ inputRef?: (inputElement: HTMLInputElement | null) => void
196
163
  /**
197
164
  * A ref to the html `ul` element.
198
165
  */
199
- listRef: PropTypes.func,
166
+ listRef?: (listElement: HTMLUListElement | null) => void
200
167
  /**
201
168
  * Content to display in the list when no options are available.
202
169
  */
203
- renderEmptyOption: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
170
+ renderEmptyOption?: React.ReactNode | (() => React.ReactNode)
204
171
  /**
205
172
  * Content to display before the text input. This will commonly be an icon.
206
173
  */
207
- renderBeforeInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
174
+ renderBeforeInput?: React.ReactNode | (() => React.ReactNode)
208
175
  /**
209
176
  * Content to display after the text input. This content will replace the
210
177
  * default arrow icons.
211
178
  */
212
- renderAfterInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
213
-
214
- /* eslint-disable react/require-default-props */
179
+ renderAfterInput?: React.ReactNode | (() => React.ReactNode)
215
180
  /**
216
181
  * A standard language identifier.
217
182
  *
@@ -223,7 +188,7 @@ const propTypes: PropValidators<PropKeys> = {
223
188
  * The web browser's locale will be used if no value is set via a component property or a context
224
189
  * property.
225
190
  */
226
- locale: PropTypes.string,
191
+ locale?: string
227
192
  /**
228
193
  * A timezone identifier in the format: Area/Location
229
194
  *
@@ -236,8 +201,39 @@ const propTypes: PropValidators<PropKeys> = {
236
201
  * The web browser's timezone will be used if no value is set via a component property or a context
237
202
  * property.
238
203
  */
204
+ timezone?: string
205
+ }
206
+
207
+ const propTypes: PropValidators<PropKeys> = {
208
+ renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
209
+ defaultToFirstOption: PropTypes.bool,
210
+ value: controllable(I18nPropTypes.iso8601, 'onChange'),
211
+ defaultValue: I18nPropTypes.iso8601,
212
+ id: PropTypes.string,
213
+ format: PropTypes.string,
214
+ step: PropTypes.oneOf([5, 10, 15, 20, 30, 60]),
215
+ interaction: PropTypes.oneOf(['enabled', 'disabled', 'readonly']),
216
+ placeholder: PropTypes.string,
217
+ isRequired: PropTypes.bool,
218
+ isInline: PropTypes.bool,
219
+ width: PropTypes.string,
220
+ optionsMaxWidth: PropTypes.string,
221
+ visibleOptionsCount: PropTypes.number,
222
+ messages: PropTypes.arrayOf(FormPropTypes.message),
223
+ placement: PositionPropTypes.placement,
224
+ constrain: PositionPropTypes.constrain,
225
+ onChange: PropTypes.func,
226
+ onFocus: PropTypes.func,
227
+ onBlur: PropTypes.func,
228
+ onShowOptions: PropTypes.func,
229
+ onHideOptions: PropTypes.func,
230
+ inputRef: PropTypes.func,
231
+ listRef: PropTypes.func,
232
+ renderEmptyOption: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
233
+ renderBeforeInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
234
+ renderAfterInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
235
+ locale: PropTypes.string,
239
236
  timezone: PropTypes.string
240
- /* eslint-enable react/require-default-props */
241
237
  }
242
238
 
243
239
  const allowedProps: AllowedPropKeys = [
@@ -272,5 +268,20 @@ const allowedProps: AllowedPropKeys = [
272
268
  'timezone'
273
269
  ]
274
270
 
275
- export type { TimeSelectProps }
271
+ type TimeSelectOptions = {
272
+ id?: string
273
+ value?: string
274
+ label: string
275
+ }
276
+
277
+ type TimeSelectState = {
278
+ inputValue: string
279
+ options: TimeSelectOptions[]
280
+ filteredOptions: TimeSelectOptions[]
281
+ isShowingOptions: boolean
282
+ highlightedOptionId?: string
283
+ selectedOptionId?: string
284
+ }
285
+
286
+ export type { TimeSelectProps, TimeSelectState, TimeSelectOptions }
276
287
  export { propTypes, allowedProps }