react-datalist-input
Advanced tools
+106
-67
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import './DataListInput.css'; | ||
| import './input.scss'; | ||
| class DataListInput extends React.Component { | ||
| constructor( props ) { | ||
| super( props ); | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
@@ -15,3 +14,3 @@ /* last valid item that was selected from the drop down menu */ | ||
| /* current input text */ | ||
| currentInput: "", | ||
| currentInput: '', | ||
| /* current set of matching items */ | ||
@@ -30,9 +29,10 @@ matchingItems: [], | ||
| */ | ||
| onHandleInput = (currentInput) => { | ||
| const matchingItems = this.props.items.filter((item) => { | ||
| if (typeof(this.props.match) === typeof(Function)) | ||
| return this.props.match(currentInput, item); | ||
| return this.match(currentInput, item); | ||
| }); | ||
| this.setState({ | ||
| onHandleInput = ( event ) => { | ||
| const currentInput = event.target.value; | ||
| const { items, match } = this.props; | ||
| const matchingItems = items.filter( ( item ) => { | ||
| if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); } | ||
| return this.match( currentInput, item ); | ||
| } ); | ||
| this.setState( { | ||
| currentInput, | ||
@@ -42,7 +42,8 @@ matchingItems, | ||
| visible: true, | ||
| }); | ||
| } ); | ||
| }; | ||
| /** | ||
| * default function for matching the current input value (needle) and the values of the items array | ||
| * default function for matching the current input value (needle) | ||
| * and the values of the items array | ||
| * @param currentInput | ||
@@ -52,5 +53,4 @@ * @param item | ||
| */ | ||
| match = (currentInput, item) => { | ||
| return item.label.substr(0, currentInput.length).toUpperCase() === currentInput.toUpperCase(); | ||
| }; | ||
| match = ( currentInput, item ) => item | ||
| .label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase(); | ||
@@ -63,5 +63,4 @@ /** | ||
| */ | ||
| indexOfMatch = (currentInput, item) => { | ||
| return item.label.toUpperCase().indexOf(currentInput.toUpperCase()); | ||
| }; | ||
| indexOfMatch = ( currentInput, item ) => item | ||
| .label.toUpperCase().indexOf( currentInput.toUpperCase() ); | ||
@@ -72,28 +71,29 @@ /** | ||
| */ | ||
| onHandleKeydown = (event) => { | ||
| onHandleKeydown = ( event ) => { | ||
| const { visible, focusIndex, matchingItems } = this.state; | ||
| // only do something if drop-down div is visible | ||
| if (!this.state.visible) return; | ||
| let currentFocusIndex = this.state.focusIndex; | ||
| if (event.keyCode === 40 || event.keyCode === 9) { | ||
| if ( !visible ) return; | ||
| let currentFocusIndex = focusIndex; | ||
| if ( event.keyCode === 40 || event.keyCode === 9 ) { | ||
| // If the arrow DOWN key or tab is pressed increase the currentFocus variable: | ||
| currentFocusIndex += 1; | ||
| if (currentFocusIndex >= this.state.matchingItems.length) currentFocusIndex = 0; | ||
| this.setState({ | ||
| if ( currentFocusIndex >= matchingItems.length ) currentFocusIndex = 0; | ||
| this.setState( { | ||
| focusIndex: currentFocusIndex, | ||
| }); | ||
| } ); | ||
| // prevent tab to jump to the next input field if drop down is still open | ||
| event.preventDefault(); | ||
| } else if (event.keyCode === 38) { | ||
| } else if ( event.keyCode === 38 ) { | ||
| // If the arrow UP key is pressed, decrease the currentFocus variable: | ||
| currentFocusIndex -= 1; | ||
| if (currentFocusIndex <= -1) currentFocusIndex = this.state.matchingItems.length - 1; | ||
| this.setState({ | ||
| if ( currentFocusIndex <= -1 ) currentFocusIndex = matchingItems.length - 1; | ||
| this.setState( { | ||
| focusIndex: currentFocusIndex, | ||
| }); | ||
| } else if (event.keyCode === 13) { | ||
| } ); | ||
| } else if ( event.keyCode === 13 ) { | ||
| // Enter pressed, similar to onClickItem | ||
| if (this.state.focusIndex > -1) { | ||
| if ( focusIndex > -1 ) { | ||
| // Simulate a click on the "active" item: | ||
| const selectedItem = this.state.matchingItems[currentFocusIndex]; | ||
| this.onSelect(selectedItem); | ||
| const selectedItem = matchingItems[ currentFocusIndex ]; | ||
| this.onSelect( selectedItem ); | ||
| } | ||
@@ -107,7 +107,15 @@ } | ||
| */ | ||
| onClickItem = (event) => { | ||
| onClickItem = ( event ) => { | ||
| const { matchingItems } = this.state; | ||
| // update the input value and close the dropdown again | ||
| const selectedKey = event.currentTarget.children[1].value; | ||
| const selectedItem = this.state.matchingItems.find(item => item.key === selectedKey); | ||
| this.onSelect(selectedItem); | ||
| const elements = event.currentTarget.children; | ||
| let selectedKey; | ||
| for ( let i = 0; i < elements.length; i += 1 ) { | ||
| if ( elements[ i ].tagName === 'INPUT' ) { | ||
| selectedKey = Number( elements[ i ].value ); | ||
| break; | ||
| } | ||
| } | ||
| const selectedItem = matchingItems.find( item => item.key === selectedKey ); | ||
| this.onSelect( selectedItem ); | ||
| }; | ||
@@ -120,15 +128,17 @@ | ||
| */ | ||
| onSelect = (selectedItem) => { | ||
| if (this.state.lastValidItem !== undefined && selectedItem.key === this.state.lastValidItem.key){ | ||
| onSelect = ( selectedItem ) => { | ||
| console.log( selectedItem ); | ||
| const { lastValidItem } = this.state; | ||
| if ( lastValidItem && selectedItem.key === lastValidItem.key ) { | ||
| // do not trigger the callback function | ||
| // but still change state to fit new selection | ||
| this.setState({ | ||
| this.setState( { | ||
| currentInput: selectedItem.label, | ||
| visible: false, | ||
| focusIndex: -1, | ||
| }); | ||
| } ); | ||
| return; | ||
| } | ||
| // change state to fit new selection | ||
| this.setState({ | ||
| this.setState( { | ||
| currentInput: selectedItem.label, | ||
@@ -138,24 +148,38 @@ lastValidItem: selectedItem, | ||
| focusIndex: -1, | ||
| }); | ||
| } ); | ||
| // callback function onSelect | ||
| this.props.onSelect(selectedItem); | ||
| const { onSelect } = this.props; | ||
| onSelect( selectedItem ); | ||
| }; | ||
| renderItems = ( items, focusIndex, activeItemClassName, itemClassName) => ( | ||
| renderItemLabel = ( currentInput, item ) => ( | ||
| <React.Fragment> | ||
| {item.label.substr( 0, this.indexOfMatch( currentInput, item ) )} | ||
| <strong> | ||
| {item.label.substr( this.indexOfMatch( currentInput, item ), currentInput.length )} | ||
| </strong> | ||
| {item.label.substr( this.indexOfMatch( currentInput, item ) + currentInput.length )} | ||
| </React.Fragment> | ||
| ) | ||
| renderItems = ( currentInput, items, focusIndex, activeItemClassName, itemClassName ) => ( | ||
| <div className="datalist-items"> | ||
| {items.map((item, i) => { | ||
| {items.map( ( item, i ) => { | ||
| const isActive = focusIndex === i; | ||
| const itemActiveClasses = isActive ? `datalist-active-item ${activeItemClassName}` : '' | ||
| const itemClasses = `${itemClassName} ${itemActiveClasses};` | ||
| const itemActiveClasses = isActive ? `datalist-active-item ${ activeItemClassName }` : ''; | ||
| const itemClasses = `${ itemClassName } ${ itemActiveClasses };`; | ||
| return ( | ||
| <div onClick={this.onClickItem} | ||
| className={itemClasses} | ||
| key={item.key}> | ||
| {item.label.substr(0, this.indexOfMatch(currentInput, item))} | ||
| <strong>{item.label.substr(this.indexOfMatch(currentInput, item), currentInput.length)}</strong> | ||
| {item.label.substr(this.indexOfMatch(currentInput, item) + currentInput.length)} | ||
| <input type='hidden' value={item.key}/> | ||
| <div | ||
| onClick={this.onClickItem} | ||
| className={itemClasses} | ||
| key={item.key} | ||
| tabIndex={0} | ||
| role="button" | ||
| onKeyUp={event => event.preventDefault()} | ||
| > | ||
| { this.renderItemLabel( currentInput, item )} | ||
| <input type="hidden" value={item.key} /> | ||
| </div> | ||
| ) | ||
| })} | ||
| ); | ||
| } )} | ||
| </div> | ||
@@ -165,10 +189,19 @@ ); | ||
| renderInputField = ( placeholder, currentInput, inputClassName ) => ( | ||
| <input onKeyDown={this.onHandleKeydown} onChange={this.onHandleInput} type="text" | ||
| className={ `autocomplete-input ${inputClassName}` } | ||
| placeholder={placeholder} value={currentInput}/> | ||
| <input | ||
| onChange={this.onHandleInput} | ||
| onKeyDown={this.onHandleKeydown} | ||
| type="text" | ||
| className={`autocomplete-input ${ inputClassName }`} | ||
| placeholder={placeholder} | ||
| value={currentInput} | ||
| /> | ||
| ) | ||
| render() { | ||
| const { currentInput, matchingItems, focusIndex, visible } = this.state; | ||
| const { placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength } = this.props; | ||
| const { | ||
| currentInput, matchingItems, focusIndex, visible, | ||
| } = this.state; | ||
| const { | ||
| placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength, | ||
| } = this.props; | ||
| const reachedRequiredLength = currentInput.length >= requiredInputLength; | ||
@@ -178,4 +211,5 @@ return ( | ||
| { this.renderInputField( placeholder, currentInput, inputClassName ) } | ||
| { reachedRequiredLength && visible && | ||
| this.renderItems( matchingItems, focusIndex, activeItemClassName, itemClassName ) | ||
| { reachedRequiredLength && visible | ||
| && this.renderItems( currentInput, matchingItems, focusIndex, | ||
| activeItemClassName, itemClassName ) | ||
| } | ||
@@ -188,3 +222,8 @@ </div> | ||
| DataListInput.propTypes = { | ||
| items: PropTypes.array.isRequired, | ||
| items: PropTypes.arrayOf( | ||
| PropTypes.shape( { | ||
| label: PropTypes.string.isRequired, | ||
| key: PropTypes.number.isRequired, | ||
| } ), | ||
| ).isRequired, | ||
| placeholder: PropTypes.string, | ||
@@ -191,0 +230,0 @@ onSelect: PropTypes.func.isRequired, |
+1
-1
| { | ||
| "name": "react-datalist-input", | ||
| "version": "1.0.10", | ||
| "version": "1.0.11", | ||
| "description": "This package provides a react component as follows: an input field with a drop down menu to pick a possible option based on the current input.", | ||
@@ -5,0 +5,0 @@ "main": "build/index.js", |
+112
-67
@@ -32,2 +32,3 @@ import React from 'react'; | ||
| this.renderInputField = this.renderInputField.bind(this); | ||
| this.renderItemLabel = this.renderItemLabel.bind(this); | ||
| } | ||
@@ -37,11 +38,12 @@ | ||
| * gets called when someone starts to write in the input field | ||
| * @param event | ||
| * @param value | ||
| */ | ||
| onHandleInput(currentInput) { | ||
| const matchingItems = this.props.items.filter((item) => { | ||
| if (typeof(this.props.match) === typeof(Function)) | ||
| return this.props.match(currentInput, item); | ||
| return this.match(currentInput, item); | ||
| }); | ||
| this.setState({ | ||
| onHandleInput( event ){ | ||
| const currentInput = event.target.value; | ||
| const { items, match } = this.props; | ||
| const matchingItems = items.filter( ( item ) => { | ||
| if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); } | ||
| return this.match( currentInput, item ); | ||
| } ); | ||
| this.setState( { | ||
| currentInput, | ||
@@ -51,7 +53,8 @@ matchingItems, | ||
| visible: true, | ||
| }); | ||
| } ); | ||
| } | ||
| /** | ||
| * default function for matching the current input value (needle) and the values of the items array | ||
| * default function for matching the current input value (needle) | ||
| * and the values of the items array | ||
| * @param currentInput | ||
@@ -61,4 +64,4 @@ * @param item | ||
| */ | ||
| match(currentInput, item) { | ||
| return item.label.substr(0, currentInput.length).toUpperCase() === currentInput.toUpperCase(); | ||
| match( currentInput, item ) { | ||
| return item.label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase(); | ||
| } | ||
@@ -72,4 +75,4 @@ | ||
| */ | ||
| indexOfMatch(currentInput, item) { | ||
| return item.label.toUpperCase().indexOf(currentInput.toUpperCase()); | ||
| indexOfMatch( currentInput, item ) { | ||
| return item.label.toUpperCase().indexOf( currentInput.toUpperCase() ); | ||
| } | ||
@@ -81,28 +84,29 @@ | ||
| */ | ||
| onHandleKeydown(event) { | ||
| onHandleKeydown( event ) { | ||
| const { visible, focusIndex, matchingItems } = this.state; | ||
| // only do something if drop-down div is visible | ||
| if (!this.state.visible) return; | ||
| let currentFocusIndex = this.state.focusIndex; | ||
| if (event.keyCode === 40 || event.keyCode === 9) { | ||
| if ( !visible ) return; | ||
| let currentFocusIndex = focusIndex; | ||
| if ( event.keyCode === 40 || event.keyCode === 9 ) { | ||
| // If the arrow DOWN key or tab is pressed increase the currentFocus variable: | ||
| currentFocusIndex += 1; | ||
| if (currentFocusIndex >= this.state.matchingItems.length) currentFocusIndex = 0; | ||
| this.setState({ | ||
| if ( currentFocusIndex >= matchingItems.length ) currentFocusIndex = 0; | ||
| this.setState( { | ||
| focusIndex: currentFocusIndex, | ||
| }); | ||
| } ); | ||
| // prevent tab to jump to the next input field if drop down is still open | ||
| event.preventDefault(); | ||
| } else if (event.keyCode === 38) { | ||
| } else if ( event.keyCode === 38 ) { | ||
| // If the arrow UP key is pressed, decrease the currentFocus variable: | ||
| currentFocusIndex -= 1; | ||
| if (currentFocusIndex <= -1) currentFocusIndex = this.state.matchingItems.length - 1; | ||
| this.setState({ | ||
| if ( currentFocusIndex <= -1 ) currentFocusIndex = matchingItems.length - 1; | ||
| this.setState( { | ||
| focusIndex: currentFocusIndex, | ||
| }); | ||
| } else if (event.keyCode === 13) { | ||
| } ); | ||
| } else if ( event.keyCode === 13 ) { | ||
| // Enter pressed, similar to onClickItem | ||
| if (this.state.focusIndex > -1) { | ||
| if ( focusIndex > -1 ) { | ||
| // Simulate a click on the "active" item: | ||
| const selectedItem = this.state.matchingItems[currentFocusIndex]; | ||
| this.onSelect(selectedItem); | ||
| const selectedItem = matchingItems[ currentFocusIndex ]; | ||
| this.onSelect( selectedItem ); | ||
| } | ||
@@ -116,7 +120,15 @@ } | ||
| */ | ||
| onClickItem(event) { | ||
| onClickItem( event ) { | ||
| const { matchingItems } = this.state; | ||
| // update the input value and close the dropdown again | ||
| const selectedKey = event.currentTarget.children[1].value; | ||
| const selectedItem = this.state.matchingItems.find(item => item.key === selectedKey); | ||
| this.onSelect(selectedItem); | ||
| const elements = event.currentTarget.children; | ||
| let selectedKey; | ||
| for ( let i = 0; i < elements.length; i += 1 ) { | ||
| if ( elements[ i ].tagName === 'INPUT' ) { | ||
| selectedKey = Number( elements[ i ].value ); | ||
| break; | ||
| } | ||
| } | ||
| const selectedItem = matchingItems.find( item => item.key === selectedKey ); | ||
| this.onSelect( selectedItem ); | ||
| } | ||
@@ -129,15 +141,16 @@ | ||
| */ | ||
| onSelect(selectedItem) { | ||
| if (this.state.lastValidItem !== undefined && selectedItem.key === this.state.lastValidItem.key){ | ||
| onSelect( selectedItem ) { | ||
| const { lastValidItem } = this.state; | ||
| if ( lastValidItem && selectedItem.key === lastValidItem.key ) { | ||
| // do not trigger the callback function | ||
| // but still change state to fit new selection | ||
| this.setState({ | ||
| this.setState( { | ||
| currentInput: selectedItem.label, | ||
| visible: false, | ||
| focusIndex: -1, | ||
| }); | ||
| } ); | ||
| return; | ||
| } | ||
| // change state to fit new selection | ||
| this.setState({ | ||
| this.setState( { | ||
| currentInput: selectedItem.label, | ||
@@ -147,38 +160,64 @@ lastValidItem: selectedItem, | ||
| focusIndex: -1, | ||
| }); | ||
| } ); | ||
| // callback function onSelect | ||
| this.props.onSelect(selectedItem); | ||
| const { onSelect } = this.props; | ||
| onSelect( selectedItem ); | ||
| } | ||
| renderItems( items, focusIndex, activeItemClassName, itemClassName) { | ||
| renderItemLabel( currentInput, item ) { | ||
| return ( | ||
| <div className="datalist-items"> | ||
| {items.map((item, i) => { | ||
| const isActive = focusIndex === i; | ||
| const itemActiveClasses = isActive ? `datalist-active-item ${activeItemClassName}` : '' | ||
| const itemClasses = `${itemClassName} ${itemActiveClasses};` | ||
| return ( | ||
| <div onClick={this.onClickItem} | ||
| className={itemClasses} | ||
| key={item.key}> | ||
| {item.label.substr(0, this.indexOfMatch(currentInput, item))} | ||
| <strong>{item.label.substr(this.indexOfMatch(currentInput, item), currentInput.length)}</strong> | ||
| {item.label.substr(this.indexOfMatch(currentInput, item) + currentInput.length)} | ||
| <input type='hidden' value={item.key}/> | ||
| </div> | ||
| ) | ||
| })} | ||
| </div> ); | ||
| <React.Fragment> | ||
| {item.label.substr( 0, this.indexOfMatch( currentInput, item ) )} | ||
| <strong> | ||
| {item.label.substr( this.indexOfMatch( currentInput, item ), currentInput.length )} | ||
| </strong> | ||
| {item.label.substr( this.indexOfMatch( currentInput, item ) + currentInput.length )} | ||
| </React.Fragment> | ||
| ); | ||
| } | ||
| renderItems( currentInput, items, focusIndex, activeItemClassName, itemClassName ) { | ||
| return ( | ||
| <div className="datalist-items"> | ||
| {items.map( ( item, i ) => { | ||
| const isActive = focusIndex === i; | ||
| const itemActiveClasses = isActive ? `datalist-active-item ${ activeItemClassName }` : ''; | ||
| const itemClasses = `${ itemClassName } ${ itemActiveClasses };`; | ||
| return ( | ||
| <div | ||
| onClick={this.onClickItem} | ||
| className={itemClasses} | ||
| key={item.key} | ||
| tabIndex={0} | ||
| role="button" | ||
| onKeyUp={event => event.preventDefault()} | ||
| > | ||
| { this.renderItemLabel( currentInput, item )} | ||
| <input type="hidden" value={item.key} /> | ||
| </div> | ||
| ); | ||
| } )} | ||
| </div> ); | ||
| } | ||
| renderInputField( placeholder, currentInput, inputClassName ) { | ||
| return ( | ||
| <input onKeyDown={this.onHandleKeydown} onChange={this.onHandleInput} type="text" | ||
| className={ `autocomplete-input ${inputClassName}` } | ||
| placeholder={placeholder} value={currentInput} /> ); | ||
| <input | ||
| onChange={this.onHandleInput} | ||
| onKeyDown={this.onHandleKeydown} | ||
| type="text" | ||
| className={`autocomplete-input ${ inputClassName }`} | ||
| placeholder={placeholder} | ||
| value={currentInput} | ||
| /> | ||
| ); | ||
| } | ||
| render() { | ||
| const { currentInput, matchingItems, focusIndex, visible } = this.state; | ||
| const { placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength } = this.props; | ||
| const { | ||
| currentInput, matchingItems, focusIndex, visible, | ||
| } = this.state; | ||
| const { | ||
| placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength, | ||
| } = this.props; | ||
| const reachedRequiredLength = currentInput.length >= requiredInputLength; | ||
@@ -188,4 +227,5 @@ return ( | ||
| { this.renderInputField( placeholder, currentInput, inputClassName ) } | ||
| { reachedRequiredLength && visible && | ||
| this.renderItems( matchingItems, focusIndex, activeItemClassName, itemClassName ) | ||
| { reachedRequiredLength && visible | ||
| && this.renderItems( currentInput, matchingItems, focusIndex, | ||
| activeItemClassName, itemClassName ) | ||
| } | ||
@@ -198,3 +238,8 @@ </div> | ||
| DataListInput.propTypes = { | ||
| items: PropTypes.array.isRequired, | ||
| items: PropTypes.arrayOf( | ||
| PropTypes.shape( { | ||
| label: PropTypes.string.isRequired, | ||
| key: PropTypes.number.isRequired, | ||
| } ), | ||
| ).isRequired, | ||
| placeholder: PropTypes.string, | ||
@@ -218,2 +263,2 @@ onSelect: PropTypes.func.isRequired, | ||
| export default DataListInput; | ||
| export default DataListInput; |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
107055
2.17%2601
3.26%