@instructure/ui-select 8.36.1-snapshot-0 → 8.36.1-snapshot-1
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 +5 -2
- package/es/Select/index.js +31 -5
- package/lib/Select/index.js +30 -4
- package/package.json +22 -22
- package/src/Select/index.tsx +56 -6
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Select/index.d.ts.map +1 -1
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-
|
|
6
|
+
## [8.36.1-snapshot-1](https://github.com/instructure/instructure-ui/compare/v8.36.0...v8.36.1-snapshot-1) (2023-03-29)
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
package/es/Select/index.js
CHANGED
|
@@ -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(
|
|
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,
|
package/lib/Select/index.js
CHANGED
|
@@ -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)(
|
|
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-
|
|
3
|
+
"version": "8.36.1-snapshot-1",
|
|
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-
|
|
27
|
-
"@instructure/ui-color-utils": "8.36.1-snapshot-
|
|
28
|
-
"@instructure/ui-test-locator": "8.36.1-snapshot-
|
|
29
|
-
"@instructure/ui-test-utils": "8.36.1-snapshot-
|
|
30
|
-
"@instructure/ui-themes": "8.36.1-snapshot-
|
|
26
|
+
"@instructure/ui-babel-preset": "8.36.1-snapshot-1",
|
|
27
|
+
"@instructure/ui-color-utils": "8.36.1-snapshot-1",
|
|
28
|
+
"@instructure/ui-test-locator": "8.36.1-snapshot-1",
|
|
29
|
+
"@instructure/ui-test-utils": "8.36.1-snapshot-1",
|
|
30
|
+
"@instructure/ui-themes": "8.36.1-snapshot-1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/runtime": "^7.20.13",
|
|
34
|
-
"@instructure/emotion": "8.36.1-snapshot-
|
|
35
|
-
"@instructure/shared-types": "8.36.1-snapshot-
|
|
36
|
-
"@instructure/ui-dom-utils": "8.36.1-snapshot-
|
|
37
|
-
"@instructure/ui-form-field": "8.36.1-snapshot-
|
|
38
|
-
"@instructure/ui-icons": "8.36.1-snapshot-
|
|
39
|
-
"@instructure/ui-options": "8.36.1-snapshot-
|
|
40
|
-
"@instructure/ui-popover": "8.36.1-snapshot-
|
|
41
|
-
"@instructure/ui-position": "8.36.1-snapshot-
|
|
42
|
-
"@instructure/ui-prop-types": "8.36.1-snapshot-
|
|
43
|
-
"@instructure/ui-react-utils": "8.36.1-snapshot-
|
|
44
|
-
"@instructure/ui-selectable": "8.36.1-snapshot-
|
|
45
|
-
"@instructure/ui-testable": "8.36.1-snapshot-
|
|
46
|
-
"@instructure/ui-text-input": "8.36.1-snapshot-
|
|
47
|
-
"@instructure/ui-utils": "8.36.1-snapshot-
|
|
48
|
-
"@instructure/ui-view": "8.36.1-snapshot-
|
|
49
|
-
"@instructure/uid": "8.36.1-snapshot-
|
|
34
|
+
"@instructure/emotion": "8.36.1-snapshot-1",
|
|
35
|
+
"@instructure/shared-types": "8.36.1-snapshot-1",
|
|
36
|
+
"@instructure/ui-dom-utils": "8.36.1-snapshot-1",
|
|
37
|
+
"@instructure/ui-form-field": "8.36.1-snapshot-1",
|
|
38
|
+
"@instructure/ui-icons": "8.36.1-snapshot-1",
|
|
39
|
+
"@instructure/ui-options": "8.36.1-snapshot-1",
|
|
40
|
+
"@instructure/ui-popover": "8.36.1-snapshot-1",
|
|
41
|
+
"@instructure/ui-position": "8.36.1-snapshot-1",
|
|
42
|
+
"@instructure/ui-prop-types": "8.36.1-snapshot-1",
|
|
43
|
+
"@instructure/ui-react-utils": "8.36.1-snapshot-1",
|
|
44
|
+
"@instructure/ui-selectable": "8.36.1-snapshot-1",
|
|
45
|
+
"@instructure/ui-testable": "8.36.1-snapshot-1",
|
|
46
|
+
"@instructure/ui-text-input": "8.36.1-snapshot-1",
|
|
47
|
+
"@instructure/ui-utils": "8.36.1-snapshot-1",
|
|
48
|
+
"@instructure/ui-view": "8.36.1-snapshot-1",
|
|
49
|
+
"@instructure/uid": "8.36.1-snapshot-1",
|
|
50
50
|
"prop-types": "^15.8.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
package/src/Select/index.tsx
CHANGED
|
@@ -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
|
|
438
|
+
return (
|
|
439
|
+
<MemoedOption optionsItemProps={optionProps} selectOption={option}>
|
|
440
|
+
{children}
|
|
441
|
+
</MemoedOption>
|
|
442
|
+
)
|
|
393
443
|
}
|
|
394
444
|
|
|
395
445
|
renderGroup(
|