Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@instructure/ui-select

Package Overview
Dependencies
Maintainers
32
Versions
1417
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@instructure/ui-select - npm Package Compare versions

Comparing version 6.4.1-rc.8 to 6.4.1-rc.9

15

es/Select/index.js

@@ -158,11 +158,19 @@ import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";

}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
// scroll option into view if needed
this.scrollToOption(this.highlightedOptionId);
}
}, {
key: "scrollToOption",
value: function scrollToOption(id) {
if (this._listView) {
var item = this._listView.querySelector("[id=\"".concat(id, "\"]")).parentNode;
var option = this._listView.querySelector("[id=\"".concat(id, "\"]"));
if (!option) return;
var listItem = option.parentNode;
var parentTop = getBoundingClientRect(this._listView).top;
var elemTop = getBoundingClientRect(item).top;
var elemTop = getBoundingClientRect(listItem).top;
var parentBottom = parentTop + this._listView.clientHeight;
var elemBottom = elemTop + item.clientHeight;
var elemBottom = elemTop + listItem.clientHeight;

@@ -185,3 +193,2 @@ if (elemBottom > parentBottom) {

});
this.scrollToOption(id); // scroll into view if needed
}

@@ -188,0 +195,0 @@ }

@@ -181,11 +181,19 @@ "use strict";

}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
// scroll option into view if needed
this.scrollToOption(this.highlightedOptionId);
}
}, {
key: "scrollToOption",
value: function scrollToOption(id) {
if (this._listView) {
var item = this._listView.querySelector("[id=\"".concat(id, "\"]")).parentNode;
var option = this._listView.querySelector("[id=\"".concat(id, "\"]"));
if (!option) return;
var listItem = option.parentNode;
var parentTop = (0, _getBoundingClientRect.getBoundingClientRect)(this._listView).top;
var elemTop = (0, _getBoundingClientRect.getBoundingClientRect)(item).top;
var elemTop = (0, _getBoundingClientRect.getBoundingClientRect)(listItem).top;
var parentBottom = parentTop + this._listView.clientHeight;
var elemBottom = elemTop + item.clientHeight;
var elemBottom = elemTop + listItem.clientHeight;

@@ -208,3 +216,2 @@ if (elemBottom > parentBottom) {

});
this.scrollToOption(id); // scroll into view if needed
}

@@ -211,0 +218,0 @@ }

{
"name": "@instructure/ui-select",
"version": "6.4.1-rc.8+3b91145d",
"version": "6.4.1-rc.9+fd917148",
"description": "A component for select and autocomplete behavior.",

@@ -23,23 +23,23 @@ "author": "Instructure, Inc. Engineering and Product Design",

"devDependencies": {
"@instructure/ui-babel-preset": "6.4.1-rc.8+3b91145d",
"@instructure/ui-color-utils": "6.4.1-rc.8+3b91145d",
"@instructure/ui-test-utils": "6.4.1-rc.8+3b91145d"
"@instructure/ui-babel-preset": "6.4.1-rc.9+fd917148",
"@instructure/ui-color-utils": "6.4.1-rc.9+fd917148",
"@instructure/ui-test-utils": "6.4.1-rc.9+fd917148"
},
"dependencies": {
"@babel/runtime": "^7",
"@instructure/ui-dom-utils": "6.4.1-rc.8+3b91145d",
"@instructure/ui-elements": "6.4.1-rc.8+3b91145d",
"@instructure/ui-form-field": "6.4.1-rc.8+3b91145d",
"@instructure/ui-icons": "6.4.1-rc.8+3b91145d",
"@instructure/ui-layout": "6.4.1-rc.8+3b91145d",
"@instructure/ui-options": "6.4.1-rc.8+3b91145d",
"@instructure/ui-overlays": "6.4.1-rc.8+3b91145d",
"@instructure/ui-prop-types": "6.4.1-rc.8+3b91145d",
"@instructure/ui-react-utils": "6.4.1-rc.8+3b91145d",
"@instructure/ui-selectable": "6.4.1-rc.8+3b91145d",
"@instructure/ui-testable": "6.4.1-rc.8+3b91145d",
"@instructure/ui-text-input": "6.4.1-rc.8+3b91145d",
"@instructure/ui-themeable": "6.4.1-rc.8+3b91145d",
"@instructure/ui-utils": "6.4.1-rc.8+3b91145d",
"@instructure/uid": "6.4.1-rc.8+3b91145d",
"@instructure/ui-dom-utils": "6.4.1-rc.9+fd917148",
"@instructure/ui-elements": "6.4.1-rc.9+fd917148",
"@instructure/ui-form-field": "6.4.1-rc.9+fd917148",
"@instructure/ui-icons": "6.4.1-rc.9+fd917148",
"@instructure/ui-layout": "6.4.1-rc.9+fd917148",
"@instructure/ui-options": "6.4.1-rc.9+fd917148",
"@instructure/ui-overlays": "6.4.1-rc.9+fd917148",
"@instructure/ui-prop-types": "6.4.1-rc.9+fd917148",
"@instructure/ui-react-utils": "6.4.1-rc.9+fd917148",
"@instructure/ui-selectable": "6.4.1-rc.9+fd917148",
"@instructure/ui-testable": "6.4.1-rc.9+fd917148",
"@instructure/ui-text-input": "6.4.1-rc.9+fd917148",
"@instructure/ui-themeable": "6.4.1-rc.9+fd917148",
"@instructure/ui-utils": "6.4.1-rc.9+fd917148",
"@instructure/uid": "6.4.1-rc.9+fd917148",
"classnames": "^2",

@@ -53,3 +53,3 @@ "prop-types": "^15",

"sideEffects": false,
"gitHead": "3b91145d394306bb2ee84cac56259d81f3de9b1d"
"gitHead": "fd9171482e89c4ebd549bac7d1388d043b6d4352"
}

@@ -315,13 +315,21 @@ /*

handleInputContainerRef= (node) => {
handleInputContainerRef = (node) => {
this._inputContainer = node
}
componentDidUpdate () {
// scroll option into view if needed
this.scrollToOption(this.highlightedOptionId)
}
scrollToOption (id) {
if (this._listView) {
const item = this._listView.querySelector(`[id="${id}"]`).parentNode
const option = this._listView.querySelector(`[id="${id}"]`)
if (!option) return
const listItem = option.parentNode
const parentTop = getBoundingClientRect(this._listView).top
const elemTop = getBoundingClientRect(item).top
const elemTop = getBoundingClientRect(listItem).top
const parentBottom = parentTop + this._listView.clientHeight
const elemBottom = elemTop + item.clientHeight
const elemBottom = elemTop + listItem.clientHeight

@@ -340,3 +348,2 @@ if (elemBottom > parentBottom) {

onRequestHighlightOption(event, { id })
this.scrollToOption(id) // scroll into view if needed
}

@@ -343,0 +350,0 @@ }

@@ -153,2 +153,4 @@ ---

> Note: Select makes some conditional assumptions about keyboard behavior. For example, if the list is NOT showing, up/down arrow keys and the space key, will show the list. Otherwise, the arrows will navigate options and the space key will type a space character.
```javascript

@@ -259,7 +261,8 @@ ---

event.persist()
const option = this.getOptionById(id).label
const option = this.getOptionById(id)
if (!option) return // prevent highlighting of empty option
this.setState((state) => ({
highlightedOptionId: id,
inputValue: event.type === 'keydown' ? option : state.inputValue,
announcement: option
inputValue: event.type === 'keydown' ? option.label : state.inputValue,
announcement: option.label
}))

@@ -269,9 +272,10 @@ }

handleSelectOption = (event, { id }) => {
const option = this.getOptionById(id).label
const option = this.getOptionById(id)
if (!option) return // prevent selecting of empty option
this.setState({
selectedOptionId: id,
inputValue: option,
inputValue: option.label,
isShowingOptions: false,
filteredOptions: this.props.options,
announcement: `${option} selected. List collapsed.`
announcement: `${option.label} selected. List collapsed.`
})

@@ -340,3 +344,2 @@ }

key="empty-option"
isDisabled
>

@@ -481,7 +484,8 @@ ---

event.persist()
const option = this.getOptionById(id).label
const option = this.getOptionById(id)
if (!option) return // prevent highlighting empty option
this.setState((state) => ({
highlightedOptionId: id,
inputValue: event.type === 'keydown' ? option : state.inputValue,
announcement: option
inputValue: event.type === 'keydown' ? option.label : state.inputValue,
announcement: option.label
}))

@@ -491,2 +495,4 @@ }

handleSelectOption = (event, { id }) => {
const option = this.getOptionById(id)
if (!option) return // prevent selecting of empty option
this.setState((state) => ({

@@ -498,3 +504,3 @@ selectedOptionId: [...state.selectedOptionId, id],

isShowingOptions: false,
announcement: `${this.getOptionById(id).label} selected. List collapsed.`
announcement: `${option.label} selected. List collapsed.`
}))

@@ -600,3 +606,2 @@ }

key="empty-option"
isDisabled
>

@@ -836,3 +841,226 @@ ---

#### Asynchronous option loading
If no results match the user's search, it's recommended to leave `isShowingOptions` as `true` and to display an "empty option" as a way of communicating that there are no matches. Similarly, it's helpful to display a [Spinner](#Spinner) in an empty option while options load.
```javascript
---
example: true
render: false
---
class AsyncExample extends React.Component {
state = {
inputValue: '',
isShowingOptions: false,
isLoading: false,
highlightedOptionId: null,
selectedOptionId: null,
selectedOptionLabel: '',
filteredOptions: [],
announcement: null
}
timeoutId = null
getOptionById (queryId) {
return this.state.filteredOptions.find(({ id }) => id === queryId)
}
filterOptions = (value) => {
return this.props.options.filter(option => (
option.label.toLowerCase().startsWith(value.toLowerCase())
))
}
matchValue () {
const {
filteredOptions,
inputValue,
selectedOptionId,
selectedOptionLabel
} = this.state
// an option matching user input exists
if (filteredOptions.length === 1) {
const onlyOption = filteredOptions[0]
// automatically select the matching option
if (onlyOption.label.toLowerCase() === inputValue.toLowerCase()) {
return {
inputValue: onlyOption.label,
selectedOptionId: onlyOption.id
}
}
}
// allow user to return to empty input and no selection
if (inputValue.length === 0) {
return { selectedOptionId: null, filteredOptions: [] }
}
// no match found, return selected option label to input
if (selectedOptionId) {
return { inputValue: selectedOptionLabel }
}
}
handleShowOptions = (event) => {
this.setState(({ filteredOptions }) => ({
isShowingOptions: true
}))
}
handleHideOptions = (event) => {
const { selectedOptionId, inputValue } = this.state
this.setState({
isShowingOptions: false,
highlightedOptionId: null,
announcement: 'List collapsed.',
...this.matchValue()
})
}
handleBlur = (event) => {
this.setState({ highlightedOptionId: null })
}
handleHighlightOption = (event, { id }) => {
event.persist()
const option = this.getOptionById(id)
if (!option) return // prevent highlighting of empty option
this.setState((state) => ({
highlightedOptionId: id,
inputValue: event.type === 'keydown' ? option.label : state.inputValue,
announcement: option.label
}))
}
handleSelectOption = (event, { id }) => {
const option = this.getOptionById(id)
if (!option) return // prevent selecting of empty option
this.setState({
selectedOptionId: id,
selectedOptionLabel: option.label,
inputValue: option.label,
isShowingOptions: false,
announcement: `${option.label} selected. List collapsed.`,
filteredOptions: [this.getOptionById(id)]
})
}
handleInputChange = (event) => {
const value = event.target.value
clearTimeout(this.timeoutId)
if (!value || value === '') {
this.setState({
isLoading: false,
inputValue: value,
isShowingOptions: true,
selectedOptionId: null,
selectedOptionLabel: null,
filteredOptions: [],
})
} else {
this.setState({
isLoading: true,
inputValue: value,
isShowingOptions: true,
filteredOptions: [],
highlightedOptionId: null,
announcement: 'Loading options.'
})
this.timeoutId = setTimeout(() => {
const newOptions = this.filterOptions(value)
this.setState({
filteredOptions: newOptions,
isLoading: false,
announcement: `${newOptions.length} options available.`
})
}, 1500)
}
}
render () {
const {
inputValue,
isShowingOptions,
isLoading,
highlightedOptionId,
selectedOptionId,
filteredOptions,
announcement
} = this.state
return (
<div>
<Select
renderLabel="Async Select"
assistiveText="Type to search"
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onBlur={this.handleBlur}
onInputChange={this.handleInputChange}
onRequestShowOptions={this.handleShowOptions}
onRequestHideOptions={this.handleHideOptions}
onRequestHighlightOption={this.handleHighlightOption}
onRequestSelectOption={this.handleSelectOption}
>
{filteredOptions.length > 0 ? filteredOptions.map((option) => {
return (
<Select.Option
id={option.id}
key={option.id}
isHighlighted={option.id === highlightedOptionId}
isSelected={option.id === selectedOptionId}
isDisabled={option.disabled}
renderBeforeLabel={!option.disabled ? IconUserSolid : IconUserLine}
>
{option.label}
</Select.Option>
)
}) : (
<Select.Option id="empty-option" key="empty-option">
{isLoading
? <Spinner renderTitle="Loading" size="x-small" />
: inputValue !== '' ? 'No results' : 'Type to search'}
</Select.Option>
)}
</Select>
<Alert
liveRegion={() => document.getElementById('flash-messages')}
liveRegionPoliteness="assertive"
screenReaderOnly
>
{ announcement }
</Alert>
</div>
)
}
}
render(
<View>
<AsyncExample
options={[
{ id: '0', label: 'Aaron Aaronson' },
{ id: '1', label: 'Amber Murphy' },
{ id: '2', label: 'Andrew Miller' },
{ id: '3', label: 'Barbara Ward' },
{ id: '4', label: 'Byron Cranston', disabled: true },
{ id: '5', label: 'Dennis Reynolds' },
{ id: '6', label: 'Dee Reynolds' },
{ id: '7', label: 'Ezra Betterthan' },
{ id: '8', label: 'Jeff Spicoli' },
{ id: '9', label: 'Joseph Smith' },
{ id: '10', label: 'Jasmine Diaz' },
{ id: '11', label: 'Martin Harris' },
{ id: '12', label: 'Michael Morgan', disabled: true },
{ id: '13', label: 'Michelle Rodriguez' },
{ id: '14', label: 'Ziggy Stardust' },
]}
/>
</View>
)
```
#### Providing assistive text for screen readers
It's important to ensure screen reader users receive instruction and feedback while interacting with a `Select`, but screen reader support for the `combobox` role varies. The `assistiveText` prop should always be used to explain how a keyboard user can make a selection. Additionally, a live region should be updated with feedback as the component is interacted with, such as when options are filtered or highlighted. Using an [Alert](#Alert) with the `screenReaderOnly` prop is the easiest way to do this.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc