npm audit fix

This commit is contained in:
andrelandgraf 2020-04-28 05:43:21 +02:00
commit e1bf2be3ad
7 changed files with 7353 additions and 165 deletions

View File

@ -112,7 +112,7 @@ match = (currentInput, item) => {
***onDropdownClose***
- The callback function that will be called after closing the drop down menu.
***placeholder***
- The placeholder that will be shown inside the input field.
@ -167,8 +167,26 @@ match = (currentInput, item) => {
***initialValue***
- Specigy an initial value for the input field.
- Specify an initial value for the input field.
- For example, `initialValue={'hello world'}` will print `hello world` into the input field on first render.
- Default is empty string.
***debounceTime***
- Use `debounceTime` to define a debounce timeout time (in milliseconds) before the matching algorithm should be called
- New user input will trigger a new call to the debounce step and will clear every unresolved timeout
- For example, `debounceTime={1000}` will call the matching algorithm one second after the last user input
- This is useful if `items` is very large and/or the `match`-algorithm is doing some heavier operations
- `debounceTime` may improve the user experience by reducing lag times as it reduces the calls to the matching and rendering of the dropdown.
- Be careful, using too much debounceTime will slow down the response time of this component.
- If you still have performance issues even when using a `debounceTime={3000}` or higher, you might want to consider using another package / user input instead. Think about a "search/look-up"-button next to your input field or even consider running the search functionality in a dedicated backend.
- Default is zero which means no timeout/debouncing is used.
***debounceLoader***
- Only in use if debounceTime is set
- Of type node which can be anything that react can render and will be shown as a loading bar
- Default is string "loading...".

2
index.d.ts vendored
View File

@ -23,6 +23,8 @@ declare module 'react-datalist-input' {
initialValue?: string;
onDropdownOpen?: () => void;
onDropdownClose?: () => void;
debounceTime?: number;
debounceLoader?: React.ReactNode;
}
export default class DataListInput extends React.Component<DataListInputProperties> {

View File

@ -1,6 +1,6 @@
{
"name": "react-datalist-input",
"version": "1.2.11",
"version": "1.2.13",
"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.",
"main": "./lib/DataListInput.js",
"license": "MIT",

View File

@ -20,20 +20,25 @@ class DataListInput extends React.Component {
visible: false,
/* index of the currently focused item in the drop down menu */
focusIndex: 0,
/* cleaner click events */
/* cleaner click events, click interaction within dropdown menu */
interactionHappened: false,
/* show loader if still matching in debounced mode */
isMatchingDebounced: false,
};
/* to manage debouncing of matching, typing input into the input field */
this.inputHappenedTimeout = undefined;
window.addEventListener( 'click', this.onClickCloseMenu, false );
}
componentDidUpdate = () => {
const { currentInput, visible } = this.state;
const { currentInput, visible, isMatchingDebounced } = this.state;
const { initialValue } = this.props;
// if we have an initialValue, we want to reset it everytime we update and are empty
// also setting a new initialValue will trigger this
if ( !currentInput && initialValue && !visible ) {
if ( !currentInput && initialValue && !visible && !isMatchingDebounced ) {
this.setState( { currentInput: initialValue } );
}
}
@ -73,75 +78,6 @@ class DataListInput extends React.Component {
}
}
onClickInput = () => {
const { visible, lastValidItem } = this.state;
let { currentInput } = this.state;
const {
requiredInputLength, dropDownLength, items, match,
clearInputOnSelect, initialValue, onDropdownOpen,
} = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength;
// if user clicks on input field with initialValue,
// the user most likely wants to clear the input field
if ( initialValue && currentInput === initialValue ) {
this.setState( { currentInput: '' } );
currentInput = '';
}
if ( reachedRequiredLength && !visible ) {
const matchingItems = items.filter( ( item ) => {
if ( typeof ( match ) === typeof ( Function ) ) {
return match( currentInput, item );
}
return this.match( currentInput, item );
} );
const currentInputIsLastItem = !clearInputOnSelect && lastValidItem
&& lastValidItem.label === currentInput;
const displayableItems = matchingItems.length && !currentInputIsLastItem
? matchingItems.slice( 0, dropDownLength ) : items.slice( 0, dropDownLength );
let index = lastValidItem && !clearInputOnSelect
? this.indexOfItem( lastValidItem, displayableItems ) : 0;
index = index > 0 ? index : 0;
this.setState( { visible: true, matchingItems: displayableItems, focusIndex: index },
onDropdownOpen );
}
}
/**
* gets called when someone starts to write in the input field
* @param value
*/
onHandleInput = ( event ) => {
const currentInput = event.target.value;
const {
items, match, dropDownLength, onDropdownOpen, onDropdownClose,
} = this.props;
const matchingItems = items.filter( ( item ) => {
if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
return this.match( currentInput, item );
} );
const displayableItems = matchingItems.slice( 0, dropDownLength );
if ( matchingItems.length > 0 ) {
this.setState( {
currentInput,
matchingItems: displayableItems,
focusIndex: 0,
visible: true,
}, onDropdownOpen );
} else {
this.setState( {
currentInput,
matchingItems: displayableItems,
visible: false,
focusIndex: -1,
}, onDropdownClose );
}
};
/**
* default function for matching the current input value (needle)
* and the values of the items array
@ -152,6 +88,18 @@ class DataListInput extends React.Component {
match = ( currentInput, item ) => item
.label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
/**
* matching process to find matching entries in items array
* @param currentInput
* @param item
* @param match
* @returns {Array}
*/
matching = ( currentInput, items, match ) => items.filter( ( item ) => {
if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
return this.match( currentInput, item );
} );
/**
* function for getting the index of the currentValue inside a value of the values array
* @param currentInput
@ -163,6 +111,87 @@ class DataListInput extends React.Component {
indexOfItem = ( item, items ) => items.indexOf( items.find( i => i.key === item.key ) )
/**
* runs the matching process of the current input
* and handles debouncing the different callback calls to reduce lag time
* for bigger datasets or heavier matching algorithms
* @param currentInput
*/
debouncedMatchingUpdateStep = ( currentInput ) => {
const { lastValidItem } = this.state;
const {
items, match, debounceTime, dropDownLength, requiredInputLength,
clearInputOnSelect, onDropdownOpen, onDropdownClose,
} = this.props;
// cleanup waiting update step
if ( this.inputHappenedTimeout ) {
clearTimeout( this.inputHappenedTimeout );
}
// set currentInput into input field and show loading if debounced mode is on
const reachedRequiredLength = currentInput.length >= requiredInputLength;
const showMatchingStillLoading = debounceTime >= 0 && reachedRequiredLength;
this.setState( { currentInput, isMatchingDebounced: showMatchingStillLoading } );
// no matching if we do not reach required input length
if ( !reachedRequiredLength ) return;
const updateMatchingItems = () => {
const matchingItems = this.matching( currentInput, items, match );
const displayableItems = matchingItems.slice( 0, dropDownLength );
const showDragIndex = lastValidItem && !clearInputOnSelect;
const index = showDragIndex ? this.indexOfItem( lastValidItem, displayableItems ) : 0;
if ( matchingItems.length > 0 ) {
this.setState( {
matchingItems: displayableItems,
focusIndex: index > 0 ? index : 0,
visible: true,
isMatchingDebounced: false,
}, onDropdownOpen );
} else {
this.setState( {
matchingItems: displayableItems,
visible: false,
focusIndex: -1,
isMatchingDebounced: false,
}, onDropdownClose );
}
};
if ( debounceTime <= 0 ) {
updateMatchingItems();
} else {
this.inputHappenedTimeout = setTimeout( updateMatchingItems, debounceTime );
}
}
/**
* gets called when someone starts to write in the input field
* @param value
*/
onHandleInput = ( event ) => {
const currentInput = event.target.value;
this.debouncedMatchingUpdateStep( currentInput );
};
onClickInput = () => {
const { visible } = this.state;
let { currentInput } = this.state;
const { requiredInputLength, initialValue } = this.props;
// if user clicks on input field with initialValue,
// the user most likely wants to clear the input field
if ( initialValue && currentInput === initialValue ) {
this.setState( { currentInput: '' } );
currentInput = '';
}
const reachedRequiredLength = currentInput.length >= requiredInputLength;
if ( reachedRequiredLength && !visible ) {
this.debouncedMatchingUpdateStep( currentInput );
}
}
/**
* handle key events
* @param event
@ -226,7 +255,9 @@ class DataListInput extends React.Component {
*/
onSelect = ( selectedItem ) => {
const { suppressReselect, clearInputOnSelect, onDropdownClose } = this.props;
const { lastValidItem } = this.state;
const { lastValidItem, isMatchingDebounced } = this.state;
// block select call until last matching went through
if ( isMatchingDebounced ) return;
if ( suppressReselect && lastValidItem && selectedItem.key === lastValidItem.key ) {
// do not trigger the callback function
// but still change state to fit new selection
@ -297,6 +328,12 @@ class DataListInput extends React.Component {
</div>
);
renderLoader = ( debounceLoader, dropdownClassName, itemClassName ) => (
<div className={`datalist-items ${ dropdownClassName || 'default-datalist-items' }`}>
<div className={itemClassName}>{debounceLoader || 'loading...'}</div>
</div>
)
renderInputField = ( placeholder, currentInput, inputClassName ) => (
<input
onChange={this.onHandleInput}
@ -311,20 +348,26 @@ class DataListInput extends React.Component {
render() {
const {
currentInput, matchingItems, focusIndex, visible,
currentInput, matchingItems, focusIndex, visible, isMatchingDebounced,
} = this.state;
const {
placeholder, inputClassName, activeItemClassName,
itemClassName, requiredInputLength, dropdownClassName,
placeholder, inputClassName, activeItemClassName, itemClassName,
requiredInputLength, dropdownClassName, debounceLoader,
} = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength;
let renderedResults;
if ( reachedRequiredLength && isMatchingDebounced ) {
renderedResults = this.renderLoader( debounceLoader, itemClassName, dropdownClassName );
} else if ( reachedRequiredLength && visible ) {
renderedResults = this.renderItems( currentInput, matchingItems, focusIndex,
activeItemClassName, itemClassName, dropdownClassName );
}
return (
<div className="datalist-input">
{ this.renderInputField( placeholder, currentInput, inputClassName ) }
{ reachedRequiredLength && visible
&& this.renderItems( currentInput, matchingItems, focusIndex,
activeItemClassName, itemClassName, dropdownClassName )
}
{ renderedResults }
</div>
);
}
@ -354,6 +397,8 @@ DataListInput.propTypes = {
suppressReselect: PropTypes.bool,
dropDownLength: PropTypes.number,
initialValue: PropTypes.string,
debounceTime: PropTypes.number,
debounceLoader: PropTypes.node,
};
DataListInput.defaultProps = {
@ -368,6 +413,8 @@ DataListInput.defaultProps = {
suppressReselect: true,
dropDownLength: Infinity,
initialValue: '',
debounceTime: 0,
debounceLoader: undefined,
onDropdownOpen: () => {},
onDropdownClose: () => {},
};

View File

@ -1,8 +1,10 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import csvFile from './data.csv';
import './App.css';
import DataListInput from './DataListInput';
// eslint-disable-next-line no-unused-vars
const data = [
{
key: '0',
@ -18,8 +20,57 @@ const data = [
},
];
async function getAndParseData( filename ) {
const response = await fetch( filename );
const reader = response.body.getReader();
const decoder = new TextDecoder( 'utf-8' );
const result = await reader.read();
const allText = decoder.decode( result.value );
const allTextLines = allText.split( /\r\n|\n/ );
const headers = allTextLines[ 0 ].split( ',' );
const lines = [];
for ( let i = 1; i < allTextLines.length; i += 1 ) {
const set = allTextLines[ i ].split( ',' );
if ( set.length === headers.length ) {
const tarr = {};
for ( let j = 0; j < headers.length; j += 1 ) {
tarr[ headers[ j ] ] = set[ j ];
}
lines.push( tarr );
}
}
return lines;
}
// eslint-disable-next-line no-unused-vars
function annoyinglySlowMatchingAlg( currentInput, item ) {
for ( let i = 0; i < 100000; i += 1 ) {
i += 1;
i -= 1;
// eslint-disable-next-line no-unused-expressions
( currentInput.length + item.label.length ) % 2;
}
return item.label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
}
function App() {
const [ item, setItem ] = useState();
const [ items, setItems ] = useState( [] );
useEffect( () => {
getAndParseData( csvFile ).then( obj => setItems( obj
.concat( obj )
.map( ( row, i ) => (
{
...row,
label: row.vorname,
key: i,
}
) ) ) );
}, [] );
return (
<div className="App">
<div className="content">
@ -32,12 +83,14 @@ function App() {
}
<div className="wrapper">
<DataListInput
items={data}
items={items}
onSelect={i => setItem( i )}
placeholder="Select a ingredient"
clearInputOnSelect={false}
suppressReselect={false}
initialValue={item ? item.label : ''}
debounceTime={1000}
debounceLoader={<>Hello</>}
/>
</div>
</div>

View File

@ -20,20 +20,25 @@ class DataListInput extends React.Component {
visible: false,
/* index of the currently focused item in the drop down menu */
focusIndex: 0,
/* cleaner click events */
/* cleaner click events, click interaction within dropdown menu */
interactionHappened: false,
/* show loader if still matching in debounced mode */
isMatchingDebounced: false,
};
/* to manage debouncing of matching, typing input into the input field */
this.inputHappenedTimeout = undefined;
window.addEventListener( 'click', this.onClickCloseMenu, false );
}
componentDidUpdate = () => {
const { currentInput, visible } = this.state;
const { currentInput, visible, isMatchingDebounced } = this.state;
const { initialValue } = this.props;
// if we have an initialValue, we want to reset it everytime we update and are empty
// also setting a new initialValue will trigger this
if ( !currentInput && initialValue && !visible ) {
if ( !currentInput && initialValue && !visible && !isMatchingDebounced ) {
this.setState( { currentInput: initialValue } );
}
}
@ -73,75 +78,6 @@ class DataListInput extends React.Component {
}
}
onClickInput = () => {
const { visible, lastValidItem } = this.state;
let { currentInput } = this.state;
const {
requiredInputLength, dropDownLength, items, match,
clearInputOnSelect, initialValue, onDropdownOpen,
} = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength;
// if user clicks on input field with initialValue,
// the user most likely wants to clear the input field
if ( initialValue && currentInput === initialValue ) {
this.setState( { currentInput: '' } );
currentInput = '';
}
if ( reachedRequiredLength && !visible ) {
const matchingItems = items.filter( ( item ) => {
if ( typeof ( match ) === typeof ( Function ) ) {
return match( currentInput, item );
}
return this.match( currentInput, item );
} );
const currentInputIsLastItem = !clearInputOnSelect && lastValidItem
&& lastValidItem.label === currentInput;
const displayableItems = matchingItems.length && !currentInputIsLastItem
? matchingItems.slice( 0, dropDownLength ) : items.slice( 0, dropDownLength );
let index = lastValidItem && !clearInputOnSelect
? this.indexOfItem( lastValidItem, displayableItems ) : 0;
index = index > 0 ? index : 0;
this.setState( { visible: true, matchingItems: displayableItems, focusIndex: index },
onDropdownOpen );
}
}
/**
* gets called when someone starts to write in the input field
* @param value
*/
onHandleInput = ( event ) => {
const currentInput = event.target.value;
const {
items, match, dropDownLength, onDropdownOpen, onDropdownClose,
} = this.props;
const matchingItems = items.filter( ( item ) => {
if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
return this.match( currentInput, item );
} );
const displayableItems = matchingItems.slice( 0, dropDownLength );
if ( matchingItems.length > 0 ) {
this.setState( {
currentInput,
matchingItems: displayableItems,
focusIndex: 0,
visible: true,
}, onDropdownOpen );
} else {
this.setState( {
currentInput,
matchingItems: displayableItems,
visible: false,
focusIndex: -1,
}, onDropdownClose );
}
};
/**
* default function for matching the current input value (needle)
* and the values of the items array
@ -152,6 +88,18 @@ class DataListInput extends React.Component {
match = ( currentInput, item ) => item
.label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
/**
* matching process to find matching entries in items array
* @param currentInput
* @param item
* @param match
* @returns {Array}
*/
matching = ( currentInput, items, match ) => items.filter( ( item ) => {
if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
return this.match( currentInput, item );
} );
/**
* function for getting the index of the currentValue inside a value of the values array
* @param currentInput
@ -163,6 +111,87 @@ class DataListInput extends React.Component {
indexOfItem = ( item, items ) => items.indexOf( items.find( i => i.key === item.key ) )
/**
* runs the matching process of the current input
* and handles debouncing the different callback calls to reduce lag time
* for bigger datasets or heavier matching algorithms
* @param currentInput
*/
debouncedMatchingUpdateStep = ( currentInput ) => {
const { lastValidItem } = this.state;
const {
items, match, debounceTime, dropDownLength, requiredInputLength,
clearInputOnSelect, onDropdownOpen, onDropdownClose,
} = this.props;
// cleanup waiting update step
if ( this.inputHappenedTimeout ) {
clearTimeout( this.inputHappenedTimeout );
}
// set currentInput into input field and show loading if debounced mode is on
const reachedRequiredLength = currentInput.length >= requiredInputLength;
const showMatchingStillLoading = debounceTime >= 0 && reachedRequiredLength;
this.setState( { currentInput, isMatchingDebounced: showMatchingStillLoading } );
// no matching if we do not reach required input length
if ( !reachedRequiredLength ) return;
const updateMatchingItems = () => {
const matchingItems = this.matching( currentInput, items, match );
const displayableItems = matchingItems.slice( 0, dropDownLength );
const showDragIndex = lastValidItem && !clearInputOnSelect;
const index = showDragIndex ? this.indexOfItem( lastValidItem, displayableItems ) : 0;
if ( matchingItems.length > 0 ) {
this.setState( {
matchingItems: displayableItems,
focusIndex: index > 0 ? index : 0,
visible: true,
isMatchingDebounced: false,
}, onDropdownOpen );
} else {
this.setState( {
matchingItems: displayableItems,
visible: false,
focusIndex: -1,
isMatchingDebounced: false,
}, onDropdownClose );
}
};
if ( debounceTime <= 0 ) {
updateMatchingItems();
} else {
this.inputHappenedTimeout = setTimeout( updateMatchingItems, debounceTime );
}
}
/**
* gets called when someone starts to write in the input field
* @param value
*/
onHandleInput = ( event ) => {
const currentInput = event.target.value;
this.debouncedMatchingUpdateStep( currentInput );
};
onClickInput = () => {
const { visible } = this.state;
let { currentInput } = this.state;
const { requiredInputLength, initialValue } = this.props;
// if user clicks on input field with initialValue,
// the user most likely wants to clear the input field
if ( initialValue && currentInput === initialValue ) {
this.setState( { currentInput: '' } );
currentInput = '';
}
const reachedRequiredLength = currentInput.length >= requiredInputLength;
if ( reachedRequiredLength && !visible ) {
this.debouncedMatchingUpdateStep( currentInput );
}
}
/**
* handle key events
* @param event
@ -226,7 +255,9 @@ class DataListInput extends React.Component {
*/
onSelect = ( selectedItem ) => {
const { suppressReselect, clearInputOnSelect, onDropdownClose } = this.props;
const { lastValidItem } = this.state;
const { lastValidItem, isMatchingDebounced } = this.state;
// block select call until last matching went through
if ( isMatchingDebounced ) return;
if ( suppressReselect && lastValidItem && selectedItem.key === lastValidItem.key ) {
// do not trigger the callback function
// but still change state to fit new selection
@ -297,6 +328,12 @@ class DataListInput extends React.Component {
</div>
);
renderLoader = ( debounceLoader, dropdownClassName, itemClassName ) => (
<div className={`datalist-items ${ dropdownClassName || 'default-datalist-items' }`}>
<div className={itemClassName}>{debounceLoader || 'loading...'}</div>
</div>
)
renderInputField = ( placeholder, currentInput, inputClassName ) => (
<input
onChange={this.onHandleInput}
@ -311,20 +348,26 @@ class DataListInput extends React.Component {
render() {
const {
currentInput, matchingItems, focusIndex, visible,
currentInput, matchingItems, focusIndex, visible, isMatchingDebounced,
} = this.state;
const {
placeholder, inputClassName, activeItemClassName,
itemClassName, requiredInputLength, dropdownClassName,
placeholder, inputClassName, activeItemClassName, itemClassName,
requiredInputLength, dropdownClassName, debounceLoader,
} = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength;
let renderedResults;
if ( reachedRequiredLength && isMatchingDebounced ) {
renderedResults = this.renderLoader( debounceLoader, itemClassName, dropdownClassName );
} else if ( reachedRequiredLength && visible ) {
renderedResults = this.renderItems( currentInput, matchingItems, focusIndex,
activeItemClassName, itemClassName, dropdownClassName );
}
return (
<div className="datalist-input">
{ this.renderInputField( placeholder, currentInput, inputClassName ) }
{ reachedRequiredLength && visible
&& this.renderItems( currentInput, matchingItems, focusIndex,
activeItemClassName, itemClassName, dropdownClassName )
}
{ renderedResults }
</div>
);
}
@ -354,6 +397,8 @@ DataListInput.propTypes = {
suppressReselect: PropTypes.bool,
dropDownLength: PropTypes.number,
initialValue: PropTypes.string,
debounceTime: PropTypes.number,
debounceLoader: PropTypes.node,
};
DataListInput.defaultProps = {
@ -368,6 +413,8 @@ DataListInput.defaultProps = {
suppressReselect: true,
dropDownLength: Infinity,
initialValue: '',
debounceTime: 0,
debounceLoader: undefined,
onDropdownOpen: () => {},
onDropdownClose: () => {},
};

File diff suppressed because it is too large Load Diff