Added logic to show selected chips even if they don't exist in source (showSelectedWhenNotInSource). Object is now accepted in value prop and should match source. WIP.
parent
9d732f4b19
commit
e301116066
|
@ -162,6 +162,7 @@
|
|||
"location": "start"
|
||||
}],
|
||||
"no-with": [2],
|
||||
"object-shorthand": [2],
|
||||
"one-var": [0],
|
||||
"operator-assignment": [0, "always"],
|
||||
"operator-linebreak": [2, "before"],
|
||||
|
@ -200,7 +201,6 @@
|
|||
"yoda": [2, "never", {
|
||||
"exceptRange": true
|
||||
}],
|
||||
"babel/object-shorthand": [2],
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 1,
|
||||
"react/jsx-closing-bracket-location": 0,
|
||||
|
|
|
@ -21,6 +21,7 @@ const factory = (Chip, Input) => {
|
|||
direction: PropTypes.oneOf(['auto', 'up', 'down']),
|
||||
disabled: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
keepFocusOnChange: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
multiple: PropTypes.bool,
|
||||
onBlur: PropTypes.func,
|
||||
|
@ -28,6 +29,7 @@ const factory = (Chip, Input) => {
|
|||
onQueryChange: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
selectedPosition: PropTypes.oneOf(['above', 'below', 'none']),
|
||||
showSelectedWhenNotInSource: PropTypes.bool,
|
||||
showSuggestionsWhenValueIsSet: PropTypes.bool,
|
||||
source: PropTypes.any,
|
||||
suggestionMatch: PropTypes.oneOf(['start', 'anywhere', 'word']),
|
||||
|
@ -36,7 +38,6 @@ const factory = (Chip, Input) => {
|
|||
autocomplete: PropTypes.string,
|
||||
focus: PropTypes.string,
|
||||
input: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
suggestion: PropTypes.string,
|
||||
suggestions: PropTypes.string,
|
||||
up: PropTypes.string,
|
||||
|
@ -50,9 +51,11 @@ const factory = (Chip, Input) => {
|
|||
allowCreate: false,
|
||||
className: '',
|
||||
direction: 'auto',
|
||||
selectedPosition: 'above',
|
||||
keepFocusOnChange: false,
|
||||
multiple: true,
|
||||
selectedPosition: 'above',
|
||||
showSuggestionsWhenValueIsSet: false,
|
||||
showSelectedWhenNotInSource: false,
|
||||
source: {},
|
||||
suggestionMatch: 'start'
|
||||
};
|
||||
|
@ -61,15 +64,31 @@ const factory = (Chip, Input) => {
|
|||
direction: this.props.direction,
|
||||
focus: false,
|
||||
showAllSuggestions: this.props.showSuggestionsWhenValueIsSet,
|
||||
query: this.query(this.props.value)
|
||||
query: this.query(this.props.value),
|
||||
valueIsObject: false
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({
|
||||
// TODO: Move to method
|
||||
valueIsObject: !Array.isArray(this.props.value) && typeof this.props.value === 'object'
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!this.props.multiple) {
|
||||
this.setState({
|
||||
query: this.query(nextProps.value)
|
||||
});
|
||||
}
|
||||
// TODO: Better comparison?
|
||||
if (nextProps.multiple && this.props.value !== nextProps.value) {
|
||||
console.log('value has changed fo real', nextProps.value);
|
||||
this.setState({
|
||||
// TODO: Move to method
|
||||
valueIsObject: !Array.isArray(nextProps.value) && typeof nextProps.value === 'object'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
|
@ -82,14 +101,25 @@ const factory = (Chip, Input) => {
|
|||
return true;
|
||||
}
|
||||
|
||||
handleChange = (keys, event) => {
|
||||
const key = this.props.multiple ? keys : keys[0];
|
||||
const query = this.query(key);
|
||||
if (this.props.onChange) this.props.onChange(key, event);
|
||||
this.setState(
|
||||
{focus: false, query, showAllSuggestions: this.props.showSuggestionsWhenValueIsSet},
|
||||
() => { ReactDOM.findDOMNode(this).querySelector('input').blur(); }
|
||||
);
|
||||
handleChange = (values, event) => {
|
||||
console.log('handle change', values);
|
||||
const value = this.props.multiple ? values : values[0];
|
||||
console.log('new keys here in handle change', value);
|
||||
const { showSuggestionsWhenValueIsSet: showAllSuggestions } = this.props;
|
||||
const query = this.query(value);
|
||||
// TODO: Pass back key/value if object originally supplied for value
|
||||
if (this.props.onChange) this.props.onChange(value, event);
|
||||
if (this.props.keepFocusOnChange) {
|
||||
this.setState({ query, showAllSuggestions });
|
||||
} else {
|
||||
this.setState({ focus: false, query, showAllSuggestions }, () => {
|
||||
ReactDOM.findDOMNode(this).querySelector('input').blur();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown = () => {
|
||||
this.selectOrCreateActiveItem();
|
||||
};
|
||||
|
||||
handleQueryBlur = (event) => {
|
||||
|
@ -99,11 +129,11 @@ const factory = (Chip, Input) => {
|
|||
|
||||
handleQueryChange = (value) => {
|
||||
if (this.props.onQueryChange) this.props.onQueryChange(value);
|
||||
this.setState({query: value, showAllSuggestions: false});
|
||||
this.setState({query: value, showAllSuggestions: false, active: null});
|
||||
};
|
||||
|
||||
handleQueryFocus = () => {
|
||||
this.refs.suggestions.scrollTop = 0;
|
||||
this.suggestionsNode.scrollTop = 0;
|
||||
this.setState({active: '', focus: true});
|
||||
if (this.props.onFocus) this.props.onFocus();
|
||||
};
|
||||
|
@ -120,14 +150,7 @@ const factory = (Chip, Input) => {
|
|||
}
|
||||
|
||||
if (event.which === 13) {
|
||||
let target = this.state.active;
|
||||
if (!target) {
|
||||
target = this.props.allowCreate
|
||||
? this.state.query
|
||||
: [...this.suggestions().keys()][0];
|
||||
this.setState({active: target});
|
||||
}
|
||||
this.select(event, target);
|
||||
this.selectOrCreateActiveItem();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -149,7 +172,7 @@ const factory = (Chip, Input) => {
|
|||
|
||||
calculateDirection () {
|
||||
if (this.props.direction === 'auto') {
|
||||
const client = ReactDOM.findDOMNode(this.refs.input).getBoundingClientRect();
|
||||
const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect();
|
||||
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
|
||||
const up = client.top > ((screen_height / 2) + client.height);
|
||||
return up ? 'up' : 'down';
|
||||
|
@ -165,7 +188,18 @@ const factory = (Chip, Input) => {
|
|||
query_value = source_value ? source_value : key;
|
||||
}
|
||||
return query_value;
|
||||
}
|
||||
}
|
||||
|
||||
selectOrCreateActiveItem () {
|
||||
let target = this.state.active;
|
||||
if (!target) {
|
||||
target = this.props.allowCreate
|
||||
? this.state.query
|
||||
: [...this.suggestions().keys()][0];
|
||||
this.setState({active: target});
|
||||
}
|
||||
this.select(event, target);
|
||||
}
|
||||
|
||||
suggestions () {
|
||||
let suggest = new Map();
|
||||
|
@ -174,15 +208,17 @@ const factory = (Chip, Input) => {
|
|||
const values = this.values();
|
||||
const source = this.source();
|
||||
|
||||
console.log('in suggestions', values);
|
||||
// Suggest any non-set value which matches the query
|
||||
if (this.props.multiple) {
|
||||
for (const [key, value] of source) {
|
||||
if (!values.has(key) && this.matches(value.toLowerCase().trim(), query)) {
|
||||
//if ((Array.isArray(values) && !values.has(key)) && this.matches(value.toLowerCase().trim(), query)) {
|
||||
if ((!values.has(key)) && this.matches(value.toLowerCase().trim(), query)) {
|
||||
suggest.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// When multiple is false, suggest any value which matches the query if showAllSuggestions is false
|
||||
// When multipleArray is false, suggest any value which matches the query if showAllSuggestions is false
|
||||
} else if (query && !this.state.showAllSuggestions) {
|
||||
for (const [key, value] of source) {
|
||||
if (this.matches(value.toLowerCase().trim(), query)) {
|
||||
|
@ -190,7 +226,7 @@ const factory = (Chip, Input) => {
|
|||
}
|
||||
}
|
||||
|
||||
// When multiple is false, suggest all values when showAllSuggestions is true
|
||||
// When multipleArray is false, suggest all values when showAllSuggestions is true
|
||||
} else {
|
||||
suggest = source;
|
||||
}
|
||||
|
@ -223,43 +259,117 @@ const factory = (Chip, Input) => {
|
|||
}
|
||||
|
||||
values () {
|
||||
const valueMap = new Map();
|
||||
const vals = this.props.multiple ? this.props.value : [this.props.value];
|
||||
|
||||
if (this.props.showSelectedWhenNotInSource && typeof vals === 'object') {
|
||||
return new Map(Object.entries(vals));
|
||||
}
|
||||
|
||||
const valueMap = new Map();
|
||||
for (const [k, v] of this.source()) {
|
||||
if (vals.indexOf(k) !== -1) valueMap.set(k, v);
|
||||
if ((Array.isArray(vals) && vals.indexOf(k) !== -1) || (k in vals)) {
|
||||
valueMap.set(k, v);
|
||||
}
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
|
||||
select = (event, target) => {
|
||||
events.pauseEvent(event);
|
||||
const values = this.values(this.props.value);
|
||||
let values = this.values(this.props.value);
|
||||
const source = this.source();
|
||||
const newValue = target === void 0 ? event.target.id : target;
|
||||
console.log('selected', target, event.target);
|
||||
if (this.state.valueIsObject) {
|
||||
console.log('current values', values);
|
||||
console.log('new value', newValue);
|
||||
|
||||
const sourceObj = Array.from(source).reduce((obj, [k, value]) => {
|
||||
console.log('reducer', 'key', k, 'value', value);
|
||||
if (k === newValue) {
|
||||
obj[k] = value; // Be careful! ES6 Maps may have non-String keys.
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
values = Array.from(values).reduce((obj, [ke, value]) => {
|
||||
console.log('reducer', 'key', ke, 'value', value);
|
||||
obj[ke] = value; // Be careful! ES6 Maps may have non-String keys.
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
console.log('new obj', sourceObj);
|
||||
|
||||
return this.handleChange(Object.assign(values, sourceObj), event);
|
||||
}
|
||||
|
||||
this.handleChange([newValue, ...values.keys()], event);
|
||||
};
|
||||
|
||||
unselect (key, event) {
|
||||
if (!this.props.disabled) {
|
||||
const values = this.values(this.props.value);
|
||||
|
||||
console.log('unselected vals', values, 'key', key);
|
||||
/*if (typeof values === 'object') {
|
||||
delete values[key];
|
||||
|
||||
return this.handleChange(Object.keys(values), event);
|
||||
}*/
|
||||
|
||||
values.delete(key);
|
||||
|
||||
console.log('new keys', values.keys());
|
||||
|
||||
if (this.state.valueIsObject) {
|
||||
const valuesObj = Array.from(values).reduce((obj, [k, value]) => {
|
||||
obj[k] = value; // Be careful! ES6 Maps may have non-String keys.
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
return this.handleChange(valuesObj, event);
|
||||
}
|
||||
this.handleChange([...values.keys()], event);
|
||||
}
|
||||
}
|
||||
|
||||
renderSelected () {
|
||||
if (this.props.multiple) {
|
||||
const selectedItems = [...this.values()].map(([key, value]) => {
|
||||
return (
|
||||
<Chip
|
||||
key={key}
|
||||
className={this.props.theme.value}
|
||||
deletable
|
||||
onDeleteClick={this.unselect.bind(this, key)}
|
||||
>
|
||||
{value}
|
||||
</Chip>
|
||||
);
|
||||
});
|
||||
let selectedItems = [];
|
||||
|
||||
if (typeof this.values() === 'object') {
|
||||
console.log('values here', this.values().keys());
|
||||
|
||||
// TODO: Extract to renderSelectedFromObject and renderSelectedFromArray methods
|
||||
selectedItems = [...this.values()].map(([key, value]) => {
|
||||
console.log('key', key, 'value', value, 'name', this.values()[key]);
|
||||
return (
|
||||
<Chip
|
||||
key={key}
|
||||
className={this.props.theme.value}
|
||||
deletable
|
||||
onDeleteClick={this.unselect.bind(this, key)}
|
||||
>
|
||||
{value}
|
||||
</Chip>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
selectedItems = [...this.values()].map(([key, value]) => {
|
||||
return (
|
||||
<Chip
|
||||
key={key}
|
||||
className={this.props.theme.value}
|
||||
deletable
|
||||
onDeleteClick={this.unselect.bind(this, key)}
|
||||
>
|
||||
{value}
|
||||
</Chip>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('items', selectedItems);
|
||||
|
||||
return <ul className={this.props.theme.values}>{selectedItems}</ul>;
|
||||
}
|
||||
|
@ -274,7 +384,7 @@ const factory = (Chip, Input) => {
|
|||
id={key}
|
||||
key={key}
|
||||
className={className}
|
||||
onMouseDown={this.select}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseOver={this.handleSuggestionHover}
|
||||
>
|
||||
{value}
|
||||
|
@ -282,14 +392,20 @@ const factory = (Chip, Input) => {
|
|||
);
|
||||
});
|
||||
|
||||
const className = classnames(theme.suggestions, {[theme.up]: this.state.direction === 'up'});
|
||||
return <ul ref='suggestions' className={className}>{suggestions}</ul>;
|
||||
return (
|
||||
<ul
|
||||
className={classnames(theme.suggestions, {[theme.up]: this.state.direction === 'up'})}
|
||||
ref={node => { this.suggestionsNode = node; }}
|
||||
>
|
||||
{suggestions}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
allowCreate, error, label, source, suggestionMatch, //eslint-disable-line no-unused-vars
|
||||
selectedPosition, showSuggestionsWhenValueIsSet, onQueryChange, //eslint-disable-line no-unused-vars
|
||||
selectedPosition, keepFocusOnChange, showSuggestionsWhenValueIsSet, showSelectedWhenNotInSource, onQueryChange, //eslint-disable-line no-unused-vars
|
||||
theme, ...other
|
||||
} = this.props;
|
||||
const className = classnames(theme.autocomplete, {
|
||||
|
@ -301,7 +417,8 @@ const factory = (Chip, Input) => {
|
|||
{this.props.selectedPosition === 'above' ? this.renderSelected() : null}
|
||||
<Input
|
||||
{...other}
|
||||
ref='input'
|
||||
ref={node => { this.inputNode = node; }}
|
||||
autoComplete="off"
|
||||
className={theme.input}
|
||||
error={error}
|
||||
label={label}
|
||||
|
@ -310,6 +427,8 @@ const factory = (Chip, Input) => {
|
|||
onFocus={this.handleQueryFocus}
|
||||
onKeyDown={this.handleQueryKeyDown}
|
||||
onKeyUp={this.handleQueryKeyUp}
|
||||
theme={theme}
|
||||
themeNamespace="input"
|
||||
value={this.state.query}
|
||||
/>
|
||||
{this.renderSuggestions()}
|
||||
|
|
|
@ -80,7 +80,7 @@ var factory = function factory(Chip, Input) {
|
|||
showAllSuggestions: _this.props.showSuggestionsWhenValueIsSet,
|
||||
query: _this.query(_this.props.value)
|
||||
}, _this.handleChange = function (keys, event) {
|
||||
var key = _this.props.multiple ? keys : keys[0];
|
||||
var key = _this.props.multipleArray ? keys : keys[0];
|
||||
var query = _this.query(key);
|
||||
if (_this.props.onChange) _this.props.onChange(key, event);
|
||||
_this.setState({ focus: false, query: query, showAllSuggestions: _this.props.showSuggestionsWhenValueIsSet }, function () {
|
||||
|
@ -199,7 +199,7 @@ var factory = function factory(Chip, Input) {
|
|||
}
|
||||
}
|
||||
|
||||
// When multiple is false, suggest any value which matches the query if showAllSuggestions is false
|
||||
// When multipleArray is false, suggest any value which matches the query if showAllSuggestions is false
|
||||
} catch (err) {
|
||||
_didIteratorError = true;
|
||||
_iteratorError = err;
|
||||
|
@ -230,7 +230,7 @@ var factory = function factory(Chip, Input) {
|
|||
}
|
||||
}
|
||||
|
||||
// When multiple is false, suggest all values when showAllSuggestions is true
|
||||
// When multipleArray is false, suggest all values when showAllSuggestions is true
|
||||
} catch (err) {
|
||||
_didIteratorError2 = true;
|
||||
_iteratorError2 = err;
|
||||
|
@ -479,4 +479,4 @@ var factory = function factory(Chip, Input) {
|
|||
var Autocomplete = factory(_Chip2.default, _Input2.default);
|
||||
exports.default = (0, _reactCssThemr.themr)(_identifiers.AUTOCOMPLETE)(Autocomplete);
|
||||
exports.autocompleteFactory = factory;
|
||||
exports.Autocomplete = Autocomplete;
|
||||
exports.Autocomplete = Autocomplete;
|
||||
|
|
|
@ -5,15 +5,18 @@ class AutocompleteTest extends React.Component {
|
|||
state = {
|
||||
simple: 'Spain',
|
||||
simpleShowAll: 'England',
|
||||
multiple: ['ES-es', 'TH-th'],
|
||||
multipleArray: ['ES-es', 'TH-th'],
|
||||
multipleObject: {'ES-es': 'Spain', 'TH-th': 'Thailand'},
|
||||
countriesArray: ['Spain', 'England', 'USA', 'Thailand', 'Tongo', 'Slovenia'],
|
||||
countriesObject: {'ES-es': 'Spain', 'TH-th': 'Thailand', 'EN-gb': 'England',
|
||||
'EN-en': 'United States of America', 'EN-nz': 'New Zealand'}
|
||||
countriesObject: {
|
||||
'EN-gb': 'England',
|
||||
'EN-en': 'United States of America', 'EN-nz': 'New Zealand'
|
||||
}
|
||||
};
|
||||
|
||||
handleMultipleChange = (value) => {
|
||||
handleMultipleArrayChange = (value) => {
|
||||
this.setState({
|
||||
multiple: value,
|
||||
multipleArray: value,
|
||||
countriesObject: {
|
||||
...this.state.countriesObject,
|
||||
...(value[0] && !this.state.countriesObject[value[0]]) ? {[value[0]]: value[0]} : {}
|
||||
|
@ -21,6 +24,12 @@ class AutocompleteTest extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
handleMultipleObjectChange = (value) => {
|
||||
this.setState({
|
||||
multipleObject: value
|
||||
});
|
||||
};
|
||||
|
||||
handleSimpleChange = (value) => {
|
||||
this.setState({simple: value});
|
||||
};
|
||||
|
@ -29,6 +38,15 @@ class AutocompleteTest extends React.Component {
|
|||
this.setState({simpleShowAll: value});
|
||||
};
|
||||
|
||||
handleQueryChange = () => {
|
||||
/*this.setState({
|
||||
countriesObject: {
|
||||
'EN-gb': 'England',
|
||||
'EN-en': 'United States of America', 'EN-nz': 'New Zealand'
|
||||
},
|
||||
})*/
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<section>
|
||||
|
@ -38,10 +56,22 @@ class AutocompleteTest extends React.Component {
|
|||
<Autocomplete
|
||||
allowCreate
|
||||
label="Pick multiple elements..."
|
||||
onChange={this.handleMultipleChange}
|
||||
onChange={this.handleMultipleArrayChange}
|
||||
//onQueryChange={this.handleQueryChange}
|
||||
source={this.state.countriesObject}
|
||||
suggestionMatch="anywhere"
|
||||
value={this.state.multiple}
|
||||
value={this.state.multipleArray}
|
||||
/>
|
||||
|
||||
<Autocomplete
|
||||
allowCreate
|
||||
label="Pick multiple elements (showValuesWhenNotInSource example)..."
|
||||
onChange={this.handleMultipleObjectChange}
|
||||
//onQueryChange={this.handleQueryChange}
|
||||
showSelectedWhenNotInSource
|
||||
source={this.state.countriesObject}
|
||||
suggestionMatch="anywhere"
|
||||
value={this.state.multipleObject}
|
||||
/>
|
||||
|
||||
<Autocomplete
|
||||
|
|
Loading…
Reference in New Issue