@instructure/ui-select 8.36.1-snapshot-0 → 8.36.1-snapshot-2

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
- ## [8.36.1-snapshot-0](https://github.com/instructure/instructure-ui/compare/v8.36.0...v8.36.1-snapshot-0) (2023-03-28)
6
+ ## [8.36.1-snapshot-2](https://github.com/instructure/instructure-ui/compare/v8.36.0...v8.36.1-snapshot-2) (2023-04-11)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-select
8
+
9
+ ### Performance Improvements
10
+
11
+ * **ui-select:** improve perf for large amount of items ([396a13b](https://github.com/instructure/instructure-ui/commit/396a13b81b0471cfd19329817c563bcddc158828))
9
12
 
10
13
 
11
14
 
@@ -28,7 +28,7 @@ var _dec, _dec2, _dec3, _class, _class2, _Options$Separator, _Options$Separator2
28
28
  */
29
29
 
30
30
  /** @jsx jsx */
31
- import React, { Children, Component } from 'react';
31
+ import React, { Children, Component, memo } from 'react';
32
32
  import { createChainedFunction } from '@instructure/ui-utils';
33
33
  import { testable } from '@instructure/ui-testable';
34
34
  import { matchComponentTypes, omitProps, getInteraction, withDeterministicId } from '@instructure/ui-react-utils';
@@ -45,6 +45,29 @@ import generateComponentTheme from './theme';
45
45
  import { Group } from './Group';
46
46
  import { Option } from './Option';
47
47
  import { allowedProps, propTypes } from './props';
48
+ // This memoed Option component is used to prevent unnecessary re-renders of
49
+ // Options.Item when the Select component is re-rendered. This is necessary
50
+ // because the Select component is re-rendered on every prop change of the <Select.Option>
51
+ // and with a large amount of options, this can cause a lot of unnecessary re-renders.
52
+ const MemoedOption = /*#__PURE__*/memo(function Opt(props) {
53
+ const optionsItemProps = props.optionsItemProps,
54
+ children = props.children;
55
+ return (
56
+ // The main <Options> that renders this is always an "ul"
57
+ jsx(Options.Item, Object.assign({
58
+ as: "li"
59
+ }, optionsItemProps), children)
60
+ );
61
+ },
62
+ // This is a custom equality function that checks if the props of the
63
+ // <Select.Option> have changed. If they haven't, then the Options.Item
64
+ // doesn't need to be re-rendered.
65
+ (prevProps, nextProps) => {
66
+ return prevProps.selectOption.props.isHighlighted === nextProps.selectOption.props.isHighlighted && prevProps.selectOption.props.isSelected === nextProps.selectOption.props.isSelected && prevProps.selectOption.props.isDisabled === nextProps.selectOption.props.isDisabled && prevProps.selectOption.props.children === nextProps.selectOption.props.children && prevProps.selectOption.props.id === nextProps.selectOption.props.id && prevProps.selectOption.props.renderBeforeLabel === nextProps.selectOption.props.renderBeforeLabel && prevProps.selectOption.props.renderAfterLabel === nextProps.selectOption.props.renderAfterLabel && prevProps.children === nextProps.children;
67
+ });
68
+ // This is needed so the propTypes in <Options> check are correct
69
+ MemoedOption.displayName = 'Item';
70
+
48
71
  /**
49
72
  ---
50
73
  category: components
@@ -98,7 +121,7 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
98
121
  (_this$props$makeStyle2 = (_this$props4 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props4);
99
122
 
100
123
  // scroll option into view if needed
101
- this.scrollToOption(this.highlightedOptionId);
124
+ requestAnimationFrame(() => this.scrollToOption(this.highlightedOptionId));
102
125
  }
103
126
  focus() {
104
127
  this._input && this._input.focus();
@@ -199,11 +222,10 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
199
222
  onRequestShowOptions = _this$props5.onRequestShowOptions,
200
223
  onRequestHideOptions = _this$props5.onRequestHideOptions,
201
224
  onRequestSelectOption = _this$props5.onRequestSelectOption;
202
- const highlightedOptionId = this.highlightedOptionId;
203
- const selectedOptionId = this.selectedOptionId;
204
225
  return this.interaction === 'enabled' ? {
205
226
  onRequestShowOptions: event => {
206
227
  onRequestShowOptions === null || onRequestShowOptions === void 0 ? void 0 : onRequestShowOptions(event);
228
+ const selectedOptionId = this.selectedOptionId;
207
229
  if (selectedOptionId && !Array.isArray(selectedOptionId)) {
208
230
  // highlight selected option on show
209
231
  this.highlightOption(event, selectedOptionId);
@@ -216,6 +238,7 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
216
238
  let id = _ref.id,
217
239
  direction = _ref.direction;
218
240
  if (!isShowingOptions) return;
241
+ const highlightedOptionId = this.highlightedOptionId;
219
242
  // if id exists, use that
220
243
  let highlightId = this._optionIds.indexOf(id) > -1 ? id : void 0;
221
244
  if (!highlightId) {
@@ -299,7 +322,10 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
299
322
  // track as valid option if not disabled
300
323
  this._optionIds.push(id);
301
324
  }
302
- return jsx(Options.Item, optionProps, children);
325
+ return jsx(MemoedOption, {
326
+ optionsItemProps: optionProps,
327
+ selectOption: option
328
+ }, children);
303
329
  }
304
330
  renderGroup(group, data) {
305
331
  const getOptionProps = data.getOptionProps,
@@ -33,6 +33,29 @@ const _excluded = ["id", "renderLabel", "children"],
33
33
  _excluded2 = ["renderLabel", "inputValue", "placeholder", "isRequired", "shouldNotWrap", "size", "isInline", "width", "htmlSize", "messages", "renderBeforeInput", "renderAfterInput", "onFocus", "onBlur", "onInputChange", "onRequestHideOptions"],
34
34
  _excluded3 = ["ref"];
35
35
  var _dec, _dec2, _dec3, _class, _class2, _Options$Separator, _Options$Separator2, _IconArrowOpenUpLine, _IconArrowOpenDownLin;
36
+ // This memoed Option component is used to prevent unnecessary re-renders of
37
+ // Options.Item when the Select component is re-rendered. This is necessary
38
+ // because the Select component is re-rendered on every prop change of the <Select.Option>
39
+ // and with a large amount of options, this can cause a lot of unnecessary re-renders.
40
+ const MemoedOption = /*#__PURE__*/(0, _react.memo)(function Opt(props) {
41
+ const optionsItemProps = props.optionsItemProps,
42
+ children = props.children;
43
+ return (
44
+ // The main <Options> that renders this is always an "ul"
45
+ (0, _emotion.jsx)(_Options.Options.Item, Object.assign({
46
+ as: "li"
47
+ }, optionsItemProps), children)
48
+ );
49
+ },
50
+ // This is a custom equality function that checks if the props of the
51
+ // <Select.Option> have changed. If they haven't, then the Options.Item
52
+ // doesn't need to be re-rendered.
53
+ (prevProps, nextProps) => {
54
+ return prevProps.selectOption.props.isHighlighted === nextProps.selectOption.props.isHighlighted && prevProps.selectOption.props.isSelected === nextProps.selectOption.props.isSelected && prevProps.selectOption.props.isDisabled === nextProps.selectOption.props.isDisabled && prevProps.selectOption.props.children === nextProps.selectOption.props.children && prevProps.selectOption.props.id === nextProps.selectOption.props.id && prevProps.selectOption.props.renderBeforeLabel === nextProps.selectOption.props.renderBeforeLabel && prevProps.selectOption.props.renderAfterLabel === nextProps.selectOption.props.renderAfterLabel && prevProps.children === nextProps.children;
55
+ });
56
+ // This is needed so the propTypes in <Options> check are correct
57
+ MemoedOption.displayName = 'Item';
58
+
36
59
  /**
37
60
  ---
38
61
  category: components
@@ -86,7 +109,7 @@ let Select = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 = (0
86
109
  (_this$props$makeStyle2 = (_this$props4 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props4);
87
110
 
88
111
  // scroll option into view if needed
89
- this.scrollToOption(this.highlightedOptionId);
112
+ requestAnimationFrame(() => this.scrollToOption(this.highlightedOptionId));
90
113
  }
91
114
  focus() {
92
115
  this._input && this._input.focus();
@@ -187,11 +210,10 @@ let Select = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 = (0
187
210
  onRequestShowOptions = _this$props5.onRequestShowOptions,
188
211
  onRequestHideOptions = _this$props5.onRequestHideOptions,
189
212
  onRequestSelectOption = _this$props5.onRequestSelectOption;
190
- const highlightedOptionId = this.highlightedOptionId;
191
- const selectedOptionId = this.selectedOptionId;
192
213
  return this.interaction === 'enabled' ? {
193
214
  onRequestShowOptions: event => {
194
215
  onRequestShowOptions === null || onRequestShowOptions === void 0 ? void 0 : onRequestShowOptions(event);
216
+ const selectedOptionId = this.selectedOptionId;
195
217
  if (selectedOptionId && !Array.isArray(selectedOptionId)) {
196
218
  // highlight selected option on show
197
219
  this.highlightOption(event, selectedOptionId);
@@ -204,6 +226,7 @@ let Select = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 = (0
204
226
  let id = _ref.id,
205
227
  direction = _ref.direction;
206
228
  if (!isShowingOptions) return;
229
+ const highlightedOptionId = this.highlightedOptionId;
207
230
  // if id exists, use that
208
231
  let highlightId = this._optionIds.indexOf(id) > -1 ? id : void 0;
209
232
  if (!highlightId) {
@@ -287,7 +310,10 @@ let Select = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 = (0
287
310
  // track as valid option if not disabled
288
311
  this._optionIds.push(id);
289
312
  }
290
- return (0, _emotion.jsx)(_Options.Options.Item, optionProps, children);
313
+ return (0, _emotion.jsx)(MemoedOption, {
314
+ optionsItemProps: optionProps,
315
+ selectOption: option
316
+ }, children);
291
317
  }
292
318
  renderGroup(group, data) {
293
319
  const getOptionProps = data.getOptionProps,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-select",
3
- "version": "8.36.1-snapshot-0",
3
+ "version": "8.36.1-snapshot-2",
4
4
  "description": "A component for select and autocomplete behavior.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,30 +23,30 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-babel-preset": "8.36.1-snapshot-0",
27
- "@instructure/ui-color-utils": "8.36.1-snapshot-0",
28
- "@instructure/ui-test-locator": "8.36.1-snapshot-0",
29
- "@instructure/ui-test-utils": "8.36.1-snapshot-0",
30
- "@instructure/ui-themes": "8.36.1-snapshot-0"
26
+ "@instructure/ui-babel-preset": "8.36.1-snapshot-2",
27
+ "@instructure/ui-color-utils": "8.36.1-snapshot-2",
28
+ "@instructure/ui-test-locator": "8.36.1-snapshot-2",
29
+ "@instructure/ui-test-utils": "8.36.1-snapshot-2",
30
+ "@instructure/ui-themes": "8.36.1-snapshot-2"
31
31
  },
32
32
  "dependencies": {
33
33
  "@babel/runtime": "^7.20.13",
34
- "@instructure/emotion": "8.36.1-snapshot-0",
35
- "@instructure/shared-types": "8.36.1-snapshot-0",
36
- "@instructure/ui-dom-utils": "8.36.1-snapshot-0",
37
- "@instructure/ui-form-field": "8.36.1-snapshot-0",
38
- "@instructure/ui-icons": "8.36.1-snapshot-0",
39
- "@instructure/ui-options": "8.36.1-snapshot-0",
40
- "@instructure/ui-popover": "8.36.1-snapshot-0",
41
- "@instructure/ui-position": "8.36.1-snapshot-0",
42
- "@instructure/ui-prop-types": "8.36.1-snapshot-0",
43
- "@instructure/ui-react-utils": "8.36.1-snapshot-0",
44
- "@instructure/ui-selectable": "8.36.1-snapshot-0",
45
- "@instructure/ui-testable": "8.36.1-snapshot-0",
46
- "@instructure/ui-text-input": "8.36.1-snapshot-0",
47
- "@instructure/ui-utils": "8.36.1-snapshot-0",
48
- "@instructure/ui-view": "8.36.1-snapshot-0",
49
- "@instructure/uid": "8.36.1-snapshot-0",
34
+ "@instructure/emotion": "8.36.1-snapshot-2",
35
+ "@instructure/shared-types": "8.36.1-snapshot-2",
36
+ "@instructure/ui-dom-utils": "8.36.1-snapshot-2",
37
+ "@instructure/ui-form-field": "8.36.1-snapshot-2",
38
+ "@instructure/ui-icons": "8.36.1-snapshot-2",
39
+ "@instructure/ui-options": "8.36.1-snapshot-2",
40
+ "@instructure/ui-popover": "8.36.1-snapshot-2",
41
+ "@instructure/ui-position": "8.36.1-snapshot-2",
42
+ "@instructure/ui-prop-types": "8.36.1-snapshot-2",
43
+ "@instructure/ui-react-utils": "8.36.1-snapshot-2",
44
+ "@instructure/ui-selectable": "8.36.1-snapshot-2",
45
+ "@instructure/ui-testable": "8.36.1-snapshot-2",
46
+ "@instructure/ui-text-input": "8.36.1-snapshot-2",
47
+ "@instructure/ui-utils": "8.36.1-snapshot-2",
48
+ "@instructure/ui-view": "8.36.1-snapshot-2",
49
+ "@instructure/uid": "8.36.1-snapshot-2",
50
50
  "prop-types": "^15.8.1"
51
51
  },
52
52
  "peerDependencies": {
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  /** @jsx jsx */
26
- import React, { Children, Component } from 'react'
26
+ import React, { Children, Component, memo } from 'react'
27
27
 
28
28
  import { createChainedFunction } from '@instructure/ui-utils'
29
29
  import { testable } from '@instructure/ui-testable'
@@ -77,6 +77,51 @@ type GroupChild = React.ComponentElement<SelectGroupProps, Group>
77
77
  type OptionChild = React.ComponentElement<SelectOptionProps, Option>
78
78
  type SelectChildren = (GroupChild | OptionChild)[]
79
79
 
80
+ type MemoedOptionProps = React.PropsWithChildren<{
81
+ selectOption: OptionChild
82
+ optionsItemProps: OptionsItemProps
83
+ }>
84
+
85
+ // This memoed Option component is used to prevent unnecessary re-renders of
86
+ // Options.Item when the Select component is re-rendered. This is necessary
87
+ // because the Select component is re-rendered on every prop change of the <Select.Option>
88
+ // and with a large amount of options, this can cause a lot of unnecessary re-renders.
89
+ const MemoedOption = memo(
90
+ function Opt(props: MemoedOptionProps) {
91
+ const { optionsItemProps, children } = props
92
+
93
+ return (
94
+ // The main <Options> that renders this is always an "ul"
95
+ <Options.Item as="li" {...optionsItemProps}>
96
+ {children}
97
+ </Options.Item>
98
+ )
99
+ },
100
+ // This is a custom equality function that checks if the props of the
101
+ // <Select.Option> have changed. If they haven't, then the Options.Item
102
+ // doesn't need to be re-rendered.
103
+ (prevProps, nextProps) => {
104
+ return (
105
+ prevProps.selectOption.props.isHighlighted ===
106
+ nextProps.selectOption.props.isHighlighted &&
107
+ prevProps.selectOption.props.isSelected ===
108
+ nextProps.selectOption.props.isSelected &&
109
+ prevProps.selectOption.props.isDisabled ===
110
+ nextProps.selectOption.props.isDisabled &&
111
+ prevProps.selectOption.props.children ===
112
+ nextProps.selectOption.props.children &&
113
+ prevProps.selectOption.props.id === nextProps.selectOption.props.id &&
114
+ prevProps.selectOption.props.renderBeforeLabel ===
115
+ nextProps.selectOption.props.renderBeforeLabel &&
116
+ prevProps.selectOption.props.renderAfterLabel ===
117
+ nextProps.selectOption.props.renderAfterLabel &&
118
+ prevProps.children === nextProps.children
119
+ )
120
+ }
121
+ )
122
+ // This is needed so the propTypes in <Options> check are correct
123
+ MemoedOption.displayName = 'Item'
124
+
80
125
  /**
81
126
  ---
82
127
  category: components
@@ -118,7 +163,7 @@ class Select extends Component<SelectProps> {
118
163
  this.props.makeStyles?.()
119
164
 
120
165
  // scroll option into view if needed
121
- this.scrollToOption(this.highlightedOptionId)
166
+ requestAnimationFrame(() => this.scrollToOption(this.highlightedOptionId))
122
167
  }
123
168
 
124
169
  state = {
@@ -273,13 +318,12 @@ class Select extends Component<SelectProps> {
273
318
  onRequestSelectOption
274
319
  } = this.props
275
320
 
276
- const highlightedOptionId = this.highlightedOptionId
277
- const selectedOptionId = this.selectedOptionId
278
-
279
321
  return this.interaction === 'enabled'
280
322
  ? {
281
323
  onRequestShowOptions: (event) => {
282
324
  onRequestShowOptions?.(event)
325
+ const selectedOptionId = this.selectedOptionId
326
+
283
327
  if (selectedOptionId && !Array.isArray(selectedOptionId)) {
284
328
  // highlight selected option on show
285
329
  this.highlightOption(event, selectedOptionId)
@@ -293,6 +337,8 @@ class Select extends Component<SelectProps> {
293
337
  { id, direction }: { id?: string; direction?: number }
294
338
  ) => {
295
339
  if (!isShowingOptions) return
340
+
341
+ const highlightedOptionId = this.highlightedOptionId
296
342
  // if id exists, use that
297
343
  let highlightId = this._optionIds.indexOf(id!) > -1 ? id : undefined
298
344
  if (!highlightId) {
@@ -389,7 +435,11 @@ class Select extends Component<SelectProps> {
389
435
  this._optionIds.push(id)
390
436
  }
391
437
 
392
- return <Options.Item {...optionProps}>{children}</Options.Item>
438
+ return (
439
+ <MemoedOption optionsItemProps={optionProps} selectOption={option}>
440
+ {children}
441
+ </MemoedOption>
442
+ )
393
443
  }
394
444
 
395
445
  renderGroup(