Skip to content
This repository has been archived by the owner on Dec 23, 2022. It is now read-only.

Commit

Permalink
Merge 81b1f68 into 899e5f5
Browse files Browse the repository at this point in the history
  • Loading branch information
joelvh committed Feb 7, 2019
2 parents 899e5f5 + 81b1f68 commit 4ec80ff
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 72 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import ChipInput from 'material-ui-chip-input'
|dataSource|`array`||Data source for auto complete. This should be an array of strings or objects.|
|dataSourceConfig|`shape`||Config for objects list dataSource, e.g. `{ text: 'text', value: 'value' }`. If not specified, the `dataSource` must be a flat array of strings or a custom `chipRenderer` must be set to handle the objects.|
|defaultValue|`array`||The chips to display by default (for uncontrolled mode).|
|delayBeforeAdd|`bool`|`false`|Use `setTimeout` 150ms delay before adding chips in case other input callbacks like `onSelection` need to fire first.|
|disabled|`bool`||Disables the chip input if set to true.|
|FormHelperTextProps|`object`||Props to pass through to the `FormHelperText` component.|
|fullWidth|`bool`||If true, the chip input will fill the available width.|
Expand Down
150 changes: 90 additions & 60 deletions src/ChipInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ const styles = (theme) => {
}
}

const keyCodes = {
BACKSPACE: 8,
DELETE: 46,
LEFT_ARROW: 37,
RIGHT_ARROW: 39
}

class ChipInput extends React.Component {
state = {
chips: [],
Expand Down Expand Up @@ -223,22 +230,29 @@ class ChipInput extends React.Component {
if (this.state.focusedChip != null) {
this.setState({ focusedChip: null })
}
if (this.props.blurBehavior === 'add') {
// Lets assume that we only want to add the existing content as chip, when
// another event has not added a chip within 200ms .
// e.g. onSelection Callback in Autocomplete case
let numChipsBefore = (this.props.value || this.state.chips).length
let value = event.target.value
this.inputBlurTimeout = setTimeout(() => {
let numChipsAfter = (this.props.value || this.state.chips).length
if (numChipsBefore === numChipsAfter) {
this.handleAddChip(value)
const value = event.target.value
switch (this.props.blurBehavior) {
case 'add':
if (this.props.delayBeforeAdd) {
// Lets assume that we only want to add the existing content as chip, when
// another event has not added a chip within 200ms .
// e.g. onSelection Callback in Autocomplete case
let numChipsBefore = (this.props.value || this.state.chips).length
this.inputBlurTimeout = setTimeout(() => {
let numChipsAfter = (this.props.value || this.state.chips).length
if (numChipsBefore === numChipsAfter) {
this.handleAddChip(value)
} else {
this.clearInput()
}
}, 150)
} else {
this.clearInput()
this.handleAddChip(value)
}
}, 150)
} else if (this.props.blurBehavior === 'clear') {
this.clearInput()
break
case 'clear':
this.clearInput()
break
}
}

Expand All @@ -262,53 +276,61 @@ class ChipInput extends React.Component {
return
}
}

const chips = this.props.value || this.state.chips
if (this.props.newChipKeyCodes.indexOf(event.keyCode) >= 0) {
let result = this.handleAddChip(event.target.value)
const result = this.handleAddChip(event.target.value)
if (result !== false) {
event.preventDefault()
}
} else if (event.keyCode === 8 || event.keyCode === 46) {
if (event.target.value === '') {
const chips = this.props.value || this.state.chips
if (focusedChip == null && event.keyCode === 8) {
this.setState({ focusedChip: chips.length - 1 })
} else if (focusedChip != null) {
const chips = this.props.value || this.state.chips
const value = chips[focusedChip]
this.handleDeleteChip(value, focusedChip)
if (event.keyCode === 8 && focusedChip > 0) {
this.setState({ focusedChip: focusedChip - 1 })
} else if (event.keyCode === 46 && focusedChip <= chips.length - 1) {
return
}

switch (event.keyCode) {
case keyCodes.BACKSPACE:
if (event.target.value === '') {
if (focusedChip != null) {
this.handleDeleteChip(chips[focusedChip], focusedChip)
if (focusedChip > 0) {
this.setState({ focusedChip: focusedChip - 1 })
}
} else {
this.setState({ focusedChip: chips.length - 1 })
}
}
break
case keyCodes.DELETE:
if (event.target.value === '' && focusedChip != null) {
this.handleDeleteChip(chips[focusedChip], focusedChip)
if (focusedChip <= chips.length - 1) {
this.setState({ focusedChip })
}
}
}
} else if (event.keyCode === 37) {
const chips = this.props.value || this.state.chips
if (focusedChip == null && event.target.value === '' && chips.length) {
return this.setState({ focusedChip: chips.length - 1 })
}
if (focusedChip != null && focusedChip > 0) {
this.setState({ focusedChip: focusedChip - 1 })
}
} else if (event.keyCode === 39) {
const chips = this.props.value || this.state.chips
if (focusedChip != null && focusedChip < chips.length - 1) {
this.setState({ focusedChip: focusedChip + 1 })
} else {
break
case keyCodes.LEFT_ARROW:
if (focusedChip == null && event.target.value === '' && chips.length) {
this.setState({ focusedChip: chips.length - 1 })
} else if (focusedChip != null && focusedChip > 0) {
this.setState({ focusedChip: focusedChip - 1 })
}
break
case keyCodes.RIGHT_ARROW:
if (focusedChip != null && focusedChip < chips.length - 1) {
this.setState({ focusedChip: focusedChip + 1 })
} else {
this.setState({ focusedChip: null })
}
break
default:
this.setState({ focusedChip: null })
}
} else {
this.setState({ focusedChip: null })
break
}
}

handleKeyUp = (event) => {
if (!this._preventChipCreation && this.props.newChipKeyCodes.indexOf(event.keyCode) > 0 && this._keyPressed) {
this.clearInput()
} else {
this.setState({ inputValue: event.target.value })
this.updateInput(event.target.value)
}
if (this.props.onKeyUp) { this.props.onKeyUp(event) }
}
Expand All @@ -320,7 +342,7 @@ class ChipInput extends React.Component {

handleUpdateInput = (e) => {
if (this.props.inputValue == null) {
this.setState({ inputValue: e.target.value })
this.updateInput(e.target.value)
}

if (this.props.onUpdateInput) {
Expand All @@ -338,7 +360,7 @@ class ChipInput extends React.Component {
this._preventChipCreation = true
return false
}
this.setState({ inputValue: '' })
this.clearInput()
const chips = this.props.value || this.state.chips

if (this.props.dataSourceConfig) {
Expand All @@ -356,26 +378,24 @@ class ChipInput extends React.Component {
this.updateChips([ ...this.state.chips, chip ])
}
}
} else if (chip.trim().length > 0) {
return true
}

if (chip.trim().length > 0) {
if (this.props.allowDuplicates || chips.indexOf(chip) === -1) {
if (this.props.value && this.props.onAdd) {
this.props.onAdd(chip)
} else {
this.updateChips([ ...this.state.chips, chip ])
}
}
} else {
return false
return true
}
return true
return false
}

handleDeleteChip (chip, i) {
if (this.props.value) {
if (this.props.onDelete) {
this.props.onDelete(chip, i)
}
} else {
if (!this.props.value) {
const chips = this.state.chips.slice()
const changed = chips.splice(i, 1) // remove the chip at index i
if (changed) {
Expand All @@ -387,6 +407,8 @@ class ChipInput extends React.Component {
}
this.updateChips(chips, { focusedChip })
}
} else if (this.props.onDelete) {
this.props.onDelete(chip, i)
}
}

Expand All @@ -403,7 +425,11 @@ class ChipInput extends React.Component {
* @public
*/
clearInput () {
this.setState({ inputValue: '' })
this.updateInput('')
}

updateInput (value) {
this.setState({ inputValue: value })
}

/**
Expand All @@ -427,9 +453,10 @@ class ChipInput extends React.Component {
classes,
className,
clearInputValueOnChange,
defaultValue,
dataSource,
dataSourceConfig,
defaultValue,
delayBeforeAdd,
disabled,
disableUnderline,
error,
Expand Down Expand Up @@ -521,7 +548,7 @@ class ChipInput extends React.Component {
{label && (
<InputLabel
htmlFor={id}
classes={{root: cx(classes[variant], classes.label), shrink: classes.labelShrink}}
classes={{ root: cx(classes[variant], classes.label), shrink: classes.labelShrink }}
shrink={shrinkFloatingLabel}
focused={this.state.isFocused}
variant={variant}
Expand Down Expand Up @@ -600,6 +627,8 @@ ChipInput.propTypes = {
}),
/** The chips to display by default (for uncontrolled mode). */
defaultValue: PropTypes.array,
/** Whether to use `setTimeout` to delay adding chips in case other input events like `onSelection` need to fire first */
delayBeforeAdd: PropTypes.bool,
/** Disables the chip input if set to true. */
disabled: PropTypes.bool,
/** Disable the input underline. Only valid for 'standard' variant */
Expand Down Expand Up @@ -646,6 +675,7 @@ ChipInput.defaultProps = {
allowDuplicates: false,
blurBehavior: 'clear',
clearInputValueOnChange: false,
delayBeforeAdd: false,
disableUnderline: false,
newChipKeyCodes: [13],
variant: 'standard'
Expand Down
32 changes: 26 additions & 6 deletions src/ChipInput.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ describe('uncontrolled mode', () => {
tree.find('input').getDOMNode().value = 'foo'
tree.find('input').simulate('change', { target: tree.find('input').getDOMNode() })
expect(handleUpdateInput).toBeCalledWith(
expect.objectContaining({
target: expect.anything()
})
expect.objectContaining({
target: expect.anything()
})
)
})
})
Expand Down Expand Up @@ -392,11 +392,11 @@ describe('blurBehavior modes', () => {
expect(tree.find('input').getDOMNode().value).toBe('foo')
})

it('adds the input on blur with blurBehavior set to add', () => {
it('adds the input on blur with blurBehavior set to add with timeout', () => {
const handleChange = jest.fn()
jest.useFakeTimers()
const tree = mount(
<ChipInput defaultValue={['a', 'b']} blurBehavior='add' onChange={handleChange} />
<ChipInput defaultValue={['a', 'b']} blurBehavior='add' delayBeforeAdd onChange={handleChange} />
)
tree.find('input').getDOMNode().value = 'blur'
tree.find('input').simulate('blur')
Expand All @@ -411,6 +411,26 @@ describe('blurBehavior modes', () => {
tree.update()
expect(tree.find('Chip').map((chip) => chip.text())).toEqual(['a', 'b', 'blur'])
})

it('adds the input on blur with blurBehavior set to add without timeout', () => {
const handleChange = jest.fn()
jest.useFakeTimers()
const tree = mount(
<ChipInput defaultValue={['a', 'b']} blurBehavior='add' onChange={handleChange} />
)
tree.find('input').getDOMNode().value = 'blur'
tree.find('input').simulate('blur')

jest.runAllTimers()

expect(tree.find('input').getDOMNode().value).toBe('')
expect(setTimeout).toHaveBeenCalledTimes(0)

expect(handleChange.mock.calls[0][0]).toEqual(['a', 'b', 'blur'])

tree.update()
expect(tree.find('Chip').map((chip) => chip.text())).toEqual(['a', 'b', 'blur'])
})
})

describe('keys', () => {
Expand Down Expand Up @@ -500,7 +520,7 @@ describe('keys', () => {
metaKey: false,
ctrlKey: false,
altKey: false,
target: {value: 'non-empty'},
target: { value: 'non-empty' },
preventDefault: () => {
prevented = true
}
Expand Down
1 change: 1 addition & 0 deletions src/__snapshots__/ChipInput.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ exports[`uncontrolled mode matches the snapshot 1`] = `
"bar",
]
}
delayBeforeAdd={false}
disableUnderline={false}
newChipKeyCodes={
Array [
Expand Down
12 changes: 6 additions & 6 deletions stories/examples/react-autosuggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function renderInput (inputProps) {
value={chips}
inputRef={ref}
{...other}
/>
/>
)
}

Expand Down Expand Up @@ -183,7 +183,7 @@ class ReactAutosuggestExample extends React.Component {
handleDeleteChip (chip, index) {
let temp = this.state.value
temp.splice(index, 1)
this.setState({value: temp})
this.setState({ value: temp })
}

render () {
Expand All @@ -204,7 +204,7 @@ class ReactAutosuggestExample extends React.Component {
renderSuggestionsContainer={renderSuggestionsContainer}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
onSuggestionSelected={(e, {suggestionValue}) => { this.handleAddChip(suggestionValue); e.preventDefault() }}
onSuggestionSelected={(e, { suggestionValue }) => { this.handleAddChip(suggestionValue); e.preventDefault() }}
focusInputOnSuggestionClick={false}
inputProps={{
classes,
Expand Down Expand Up @@ -259,12 +259,12 @@ class ReactAutosuggestRemoteExample extends React.Component {
};

handleAddChip (chip) {
this.setState({value: this.state.value.concat([chip])})
this.setState({ value: this.state.value.concat([chip]) })
}
handleDeleteChip (chip, index) {
let temp = this.state.value
temp.splice(index, 1)
this.setState({value: temp})
this.setState({ value: temp })
}

render () {
Expand All @@ -285,7 +285,7 @@ class ReactAutosuggestRemoteExample extends React.Component {
renderSuggestionsContainer={renderSuggestionsContainer}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
onSuggestionSelected={(e, {suggestionValue}) => { this.handleAddChip(suggestionValue); e.preventDefault() }}
onSuggestionSelected={(e, { suggestionValue }) => { this.handleAddChip(suggestionValue); e.preventDefault() }}
focusInputOnSuggestionClick={false}
inputProps={{
classes,
Expand Down

0 comments on commit 4ec80ff

Please sign in to comment.