Rework & simplify autocomplete code
parent
daf6bac933
commit
283e7e2ef3
|
@ -42,11 +42,6 @@ export interface AutocompleteTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutocompleteProps extends InputProps {
|
export interface AutocompleteProps extends InputProps {
|
||||||
/**
|
|
||||||
* Determines if user can create a new option with the current typed value.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
allowCreate?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Determines the opening direction. It can be auto, up or down.
|
* Determines the opening direction. It can be auto, up or down.
|
||||||
* @default auto
|
* @default auto
|
||||||
|
@ -105,16 +100,6 @@ export interface AutocompleteProps extends InputProps {
|
||||||
* @default above
|
* @default above
|
||||||
*/
|
*/
|
||||||
selectedPosition?: "above" | "below" | "none";
|
selectedPosition?: "above" | "below" | "none";
|
||||||
/**
|
|
||||||
* Determines if the selected list is shown if the `value` keys don't exist in the source. Only works if passing the `value` prop as an Object.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
showSelectedWhenNotInSource?: boolean;
|
|
||||||
/**
|
|
||||||
* If true, the list of suggestions will not be filtered when a value is selected.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
showSuggestionsWhenValueIsSet?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Object of key/values or array representing all items suggested.
|
* Object of key/values or array representing all items suggested.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { themr } from 'react-css-themr';
|
import { themr } from 'react-css-themr';
|
||||||
import { isValuePresent } from '../utils/utils';
|
|
||||||
import { AUTOCOMPLETE } from '../identifiers.js';
|
import { AUTOCOMPLETE } from '../identifiers.js';
|
||||||
import InjectChip from '../chip/Chip.js';
|
import InjectChip from '../chip/Chip.js';
|
||||||
import InjectInput from '../input/Input.js';
|
import InjectInput from '../input/Input.js';
|
||||||
|
@ -24,7 +23,6 @@ const factory = (Chip, Input) => {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
allowClear: PropTypes.bool,
|
allowClear: PropTypes.bool,
|
||||||
clearTooltip: PropTypes.string,
|
clearTooltip: PropTypes.string,
|
||||||
allowCreate: PropTypes.bool,
|
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
direction: PropTypes.oneOf(['auto', 'up', 'down']),
|
direction: PropTypes.oneOf(['auto', 'up', 'down']),
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
@ -37,6 +35,8 @@ const factory = (Chip, Input) => {
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.node,
|
PropTypes.node,
|
||||||
]),
|
]),
|
||||||
|
labelKey: PropTypes.string,
|
||||||
|
valueKey: PropTypes.string,
|
||||||
multiple: PropTypes.bool,
|
multiple: PropTypes.bool,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
@ -46,9 +46,8 @@ const factory = (Chip, Input) => {
|
||||||
onQueryChange: PropTypes.func,
|
onQueryChange: PropTypes.func,
|
||||||
query: PropTypes.string,
|
query: PropTypes.string,
|
||||||
selectedPosition: PropTypes.oneOf(['above', 'below', 'none']),
|
selectedPosition: PropTypes.oneOf(['above', 'below', 'none']),
|
||||||
showSelectedWhenNotInSource: PropTypes.bool,
|
|
||||||
showSuggestionsWhenValueIsSet: PropTypes.bool,
|
|
||||||
source: PropTypes.any,
|
source: PropTypes.any,
|
||||||
|
minWidth: PropTypes.number,
|
||||||
suggestionMatch: PropTypes.oneOf(['disabled', 'start', 'anywhere', 'word', 'none']),
|
suggestionMatch: PropTypes.oneOf(['disabled', 'start', 'anywhere', 'word', 'none']),
|
||||||
theme: PropTypes.shape({
|
theme: PropTypes.shape({
|
||||||
active: PropTypes.string,
|
active: PropTypes.string,
|
||||||
|
@ -67,29 +66,23 @@ const factory = (Chip, Input) => {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
allowClear: false,
|
allowClear: false,
|
||||||
clearTooltip: 'Clear',
|
clearTooltip: 'Clear',
|
||||||
allowCreate: false,
|
|
||||||
className: '',
|
className: '',
|
||||||
direction: 'auto',
|
direction: 'auto',
|
||||||
keepFocusOnChange: false,
|
keepFocusOnChange: false,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
selectedPosition: 'above',
|
selectedPosition: 'above',
|
||||||
showSelectedWhenNotInSource: false,
|
|
||||||
showSuggestionsWhenValueIsSet: false,
|
|
||||||
source: {},
|
source: {},
|
||||||
suggestionMatch: 'start',
|
suggestionMatch: 'start',
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
focus: false,
|
focus: false,
|
||||||
showAllSuggestions: this.props.showSuggestionsWhenValueIsSet,
|
query: this.props.query,
|
||||||
query: this.props.query ? this.props.query : this.query(this.props.value),
|
|
||||||
isValueAnObject: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (!this.props.multiple) {
|
if (this.props.query !== nextProps.query) {
|
||||||
const query = nextProps.query ? nextProps.query : this.query(nextProps.value);
|
this.setState({ query: nextProps.query });
|
||||||
this.updateQuery(query, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,57 +93,48 @@ const factory = (Chip, Input) => {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange = (values, event) => {
|
handleChange = (value, event) => {
|
||||||
const value = this.props.multiple ? values : values[0];
|
if (this.props.onChange) {
|
||||||
const { showSuggestionsWhenValueIsSet: showAllSuggestions } = this.props;
|
this.props.onChange(value, event);
|
||||||
const query = this.query(value);
|
}
|
||||||
if (this.props.onChange) this.props.onChange(value, event);
|
if (!this.props.keepFocusOnChange) {
|
||||||
if (this.props.keepFocusOnChange) {
|
this.updateQuery(undefined);
|
||||||
this.setState({ query, showAllSuggestions });
|
this.setState({ query: undefined, focus: false }, () => {
|
||||||
} else {
|
|
||||||
this.setState({ focus: false, query, showAllSuggestions }, () => {
|
|
||||||
ReactDOM.findDOMNode(this).querySelector('input').blur();
|
ReactDOM.findDOMNode(this).querySelector('input').blur();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.updateQuery(query, this.props.query);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMouseDown = (event) => {
|
handleMouseDown = (event) => {
|
||||||
this.selectOrCreateActiveItem(event);
|
this.selectOrCreateActiveItem(event);
|
||||||
}
|
};
|
||||||
|
|
||||||
handleQueryBlur = (event) => {
|
handleQueryBlur = (event) => {
|
||||||
if (this.state.focus) this.setState({ focus: false });
|
if (this.state.focus) this.setState({ focus: false });
|
||||||
if (this.props.onBlur) this.props.onBlur(event, this.state.active);
|
if (this.props.onBlur) this.props.onBlur(event, this.state.active);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateQuery = (query, notify) => {
|
updateQuery = (query) => {
|
||||||
if (notify && this.props.onQueryChange) this.props.onQueryChange(query);
|
if (this.props.onQueryChange) this.props.onQueryChange(query);
|
||||||
this.setState({ query });
|
this.setState({ query });
|
||||||
}
|
};
|
||||||
|
|
||||||
handleQueryChange = (value) => {
|
handleQueryChange = (value, event) => {
|
||||||
const query = this.clearQuery ? '' : value;
|
if (value === '' && !this.props.multiple &&
|
||||||
this.clearQuery = false;
|
(this.props.allowBlank || this.props.allowClear) && this.props.onChange) {
|
||||||
|
this.props.onChange(null, event);
|
||||||
this.updateQuery(query, true);
|
}
|
||||||
this.setState({ showAllSuggestions: query ? false : this.props.showSuggestionsWhenValueIsSet, active: null });
|
this.updateQuery(value);
|
||||||
|
this.setState({ active: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleQueryFocus = (event) => {
|
handleQueryFocus = (event) => {
|
||||||
event.target.scrollTop = 0;
|
event.target.scrollTop = 0;
|
||||||
this.setState({ active: '', focus: true });
|
this.setState({ active: null, focus: true });
|
||||||
if (this.props.onFocus) this.props.onFocus(event);
|
if (this.props.onFocus) this.props.onFocus(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleQueryKeyDown = (event) => {
|
handleQueryKeyDown = (event) => {
|
||||||
// Mark query for clearing in handleQueryChange when pressing backspace and showing all suggestions.
|
|
||||||
this.clearQuery = (
|
|
||||||
event.which === 8
|
|
||||||
&& this.props.showSuggestionsWhenValueIsSet
|
|
||||||
&& this.state.showAllSuggestions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (event.which === 13) {
|
if (event.which === 13) {
|
||||||
this.selectOrCreateActiveItem(event);
|
this.selectOrCreateActiveItem(event);
|
||||||
}
|
}
|
||||||
|
@ -173,25 +157,40 @@ const factory = (Chip, Input) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSuggestionHover = (event) => {
|
handleSuggestionHover = (event) => {
|
||||||
this.setState({ active: event.target.id });
|
let t = event.target;
|
||||||
|
while (t && !t.id) {
|
||||||
|
t = t.parentNode;
|
||||||
|
}
|
||||||
|
this.setState({ active: t && t.id || '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
calculateDirection() {
|
calculateDirection() {
|
||||||
const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect();
|
const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect();
|
||||||
|
const screen_width = window.innerWidth || document.documentElement.offsetWidth;
|
||||||
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
|
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
|
||||||
let direction = this.props.direction;
|
let direction = this.props.direction;
|
||||||
if (this.props.direction === 'auto') {
|
if (this.props.direction === 'auto') {
|
||||||
const up = client.top > ((screen_height / 2) + client.height);
|
const up = client.top > ((screen_height / 2) + client.height);
|
||||||
direction = up ? 'up' : 'down';
|
direction = up ? 'up' : 'down';
|
||||||
}
|
}
|
||||||
const top = direction == 'down' ? (client.top+client.height)+'px' : client.top+'px';
|
let top = client.top
|
||||||
|
+ (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop)
|
||||||
|
- (document.documentElement.clientTop || document.body.clientTop || 0);
|
||||||
|
top = direction == 'down' ? top + client.height + 'px' : top + 'px';
|
||||||
const bottom = direction == 'up' ? '0px' : undefined;
|
const bottom = direction == 'up' ? '0px' : undefined;
|
||||||
const left = client.left+'px';
|
let left = (client.left
|
||||||
const width = client.width+'px';
|
+ (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft)
|
||||||
|
- (document.documentElement.clientLeft || document.body.clientLeft || 0));
|
||||||
|
let width = (this.props.minWidth && client.width < this.props.minWidth ? this.props.minWidth : client.width);
|
||||||
|
if (left + width >= screen_width) {
|
||||||
|
left = screen_width - width;
|
||||||
|
}
|
||||||
let maxHeight = direction == 'down' ? (screen_height-client.top-client.height) : client.top;
|
let maxHeight = direction == 'down' ? (screen_height-client.top-client.height) : client.top;
|
||||||
if (maxHeight > screen_height*0.45) {
|
if (maxHeight > screen_height*0.45) {
|
||||||
maxHeight = Math.floor(screen_height*0.45);
|
maxHeight = Math.floor(screen_height*0.45);
|
||||||
}
|
}
|
||||||
|
left = left+'px';
|
||||||
|
width = width+'px';
|
||||||
maxHeight = maxHeight+'px';
|
maxHeight = maxHeight+'px';
|
||||||
if (this.state.top !== top || this.state.left !== left ||
|
if (this.state.top !== top || this.state.left !== left ||
|
||||||
this.state.width !== width || this.state.bottom !== bottom ||
|
this.state.width !== width || this.state.bottom !== bottom ||
|
||||||
|
@ -200,24 +199,16 @@ const factory = (Chip, Input) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query(key) {
|
|
||||||
let query_value = '';
|
|
||||||
if (!this.props.multiple && isValuePresent(key)) {
|
|
||||||
const source_value = this.source().get(`${key}`);
|
|
||||||
query_value = source_value || key;
|
|
||||||
}
|
|
||||||
return query_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectOrCreateActiveItem(event) {
|
selectOrCreateActiveItem(event) {
|
||||||
let target = this.state.active;
|
let target = this.state.active;
|
||||||
if (!target) {
|
if (target == null) {
|
||||||
target = this.props.allowCreate
|
for (let k of this.suggestions().keys()) {
|
||||||
? this.state.query
|
this.setState({ active: k });
|
||||||
: [...this.suggestions().keys()][0];
|
break;
|
||||||
this.setState({ active: target });
|
}
|
||||||
|
} else {
|
||||||
|
this.select(event, target);
|
||||||
}
|
}
|
||||||
this.select(event, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
normalise(value) {
|
normalise(value) {
|
||||||
|
@ -238,30 +229,23 @@ const factory = (Chip, Input) => {
|
||||||
|
|
||||||
suggestions() {
|
suggestions() {
|
||||||
let suggest = new Map();
|
let suggest = new Map();
|
||||||
const rawQuery = this.state.query || (this.props.multiple ? '' : this.props.value);
|
|
||||||
const query = this.normalise((`${rawQuery}`));
|
|
||||||
const values = this.values();
|
|
||||||
const source = this.source();
|
const source = this.source();
|
||||||
|
const query = this.normalise(this.state.query == null ? '' : this.state.query+'');
|
||||||
|
|
||||||
// Suggest any non-set value which matches the query
|
if (query !== '' && this.props.suggestionMatch !== 'disabled' && this.props.suggestionMatch !== 'none') {
|
||||||
if (this.props.multiple) {
|
|
||||||
for (const [key, value] of source) {
|
|
||||||
if (!values.has(key) && this.matches(this.normalise(value), query)) {
|
|
||||||
suggest.set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When multiple 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) {
|
for (const [key, value] of source) {
|
||||||
if (this.matches(this.normalise(value), query)) {
|
if (this.matches(this.normalise(value), query)) {
|
||||||
suggest.set(key, value);
|
suggest.set(''+key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When multiple is false, suggest all values when showAllSuggestions is true
|
|
||||||
} else {
|
} else {
|
||||||
suggest = source;
|
suggest = this.props.multiple ? new Map(source) : source;
|
||||||
|
}
|
||||||
|
if (this.props.multiple) {
|
||||||
|
const values = this.isValueAnObject() ? Object.keys(this.props.value) : this.props.value||[];
|
||||||
|
for (const k of values) {
|
||||||
|
suggest.delete(''+k);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return suggest;
|
return suggest;
|
||||||
|
@ -270,108 +254,88 @@ const factory = (Chip, Input) => {
|
||||||
matches(value, query) {
|
matches(value, query) {
|
||||||
const { suggestionMatch } = this.props;
|
const { suggestionMatch } = this.props;
|
||||||
|
|
||||||
if (suggestionMatch === 'disabled') {
|
if (suggestionMatch === 'start') {
|
||||||
return true;
|
|
||||||
} else if (suggestionMatch === 'start') {
|
|
||||||
return value.startsWith(query);
|
return value.startsWith(query);
|
||||||
} else if (suggestionMatch === 'anywhere') {
|
} else if (suggestionMatch === 'anywhere') {
|
||||||
return value.includes(query);
|
return value.includes(query);
|
||||||
} else if (suggestionMatch === 'word') {
|
} else if (suggestionMatch === 'word') {
|
||||||
const re = new RegExp(`\\b${query}`, 'g');
|
const re = new RegExp(`\\b${query}`, 'g');
|
||||||
return re.test(value);
|
return re.test(value);
|
||||||
}else if(suggestionMatch === 'none'){
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
source() {
|
source() {
|
||||||
const { source: src } = this.props;
|
const src = this.props.source;
|
||||||
if (src.hasOwnProperty('length')) {
|
if (this._cachedSource != src) {
|
||||||
return new Map(src.map(item => Array.isArray(item) ? [...item] : [item, item]));
|
this._cachedSource = src;
|
||||||
|
const valueKey = this.props.valueKey || 'value';
|
||||||
|
const labelKey = this.props.labelKey || 'label';
|
||||||
|
if (src.hasOwnProperty('length')) {
|
||||||
|
this._source = new Map(src.map(item => {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
return [''+item[0], item[1]];
|
||||||
|
} else if (typeof item != 'object') {
|
||||||
|
return [''+item, item];
|
||||||
|
} else {
|
||||||
|
return [''+item[valueKey], item[labelKey]];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
this._source = new Map(Object.keys(src).map(key => [`${key}`, src[key]]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new Map(Object.keys(src).map(key => [`${key}`, src[key]]));
|
return this._source;
|
||||||
}
|
|
||||||
|
|
||||||
values() {
|
|
||||||
let vals = this.props.multiple ? this.props.value : [this.props.value];
|
|
||||||
|
|
||||||
if (!vals) vals = [];
|
|
||||||
|
|
||||||
if (this.props.showSelectedWhenNotInSource && this.isValueAnObject()) {
|
|
||||||
return new Map(Object.entries(vals));
|
|
||||||
}
|
|
||||||
|
|
||||||
const valueMap = new Map();
|
|
||||||
|
|
||||||
const stringVals = vals.map(v => `${v}`);
|
|
||||||
for (const [k, v] of this.source()) {
|
|
||||||
if (stringVals.indexOf(k) !== -1) valueMap.set(k, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return valueMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select = (event, target) => {
|
select = (event, target) => {
|
||||||
events.pauseEvent(event);
|
events.pauseEvent(event);
|
||||||
const values = this.values(this.props.value);
|
let newValue = target === void 0 ? event.target.id : target;
|
||||||
const source = this.source();
|
|
||||||
const newValue = target === void 0 ? event.target.id : target;
|
|
||||||
|
|
||||||
if (this.isValueAnObject()) {
|
if (this.isValueAnObject()) {
|
||||||
const newItem = Array.from(source).reduce((obj, [k, value]) => {
|
newValue = { ...(this.props.value||{}), [newValue]: true };
|
||||||
if (k === newValue) {
|
} else if (this.props.multiple) {
|
||||||
obj[k] = value;
|
newValue = [ ...(this.props.value||[]), newValue ];
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
if (Object.keys(newItem).length === 0 && newValue) {
|
|
||||||
newItem[newValue] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleChange(Object.assign(this.mapToObject(values), newItem), event);
|
|
||||||
}
|
}
|
||||||
|
this.handleChange(newValue, event);
|
||||||
this.handleChange([newValue, ...values.keys()], event);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
unselect(key, event) {
|
unselect(key, event) {
|
||||||
if (!this.props.disabled) {
|
if (!this.props.disabled) {
|
||||||
const values = this.values(this.props.value);
|
let newValue;
|
||||||
|
|
||||||
values.delete(key);
|
|
||||||
|
|
||||||
if (this.isValueAnObject()) {
|
if (this.isValueAnObject()) {
|
||||||
return this.handleChange(this.mapToObject(values), event);
|
newValue = { ...this.props.value };
|
||||||
|
delete newValue[key];
|
||||||
|
} else if (this.props.multiple) {
|
||||||
|
newValue = (this.props.value||[]).filter(v => v != key);
|
||||||
}
|
}
|
||||||
|
this.handleChange(newValue, event);
|
||||||
this.handleChange([...values.keys()], event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isValueAnObject() {
|
isValueAnObject() {
|
||||||
return !Array.isArray(this.props.value) && typeof this.props.value === 'object';
|
return this.props.value && !Array.isArray(this.props.value) && typeof this.props.value === 'object';
|
||||||
}
|
}
|
||||||
|
|
||||||
mapToObject(map) {
|
mapToObject(array) {
|
||||||
return Array.from(map).reduce((obj, [k, value]) => {
|
return array.reduce((obj, k) => {
|
||||||
obj[k] = value;
|
obj[k] = true;
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSelected() {
|
renderSelected() {
|
||||||
if (this.props.multiple) {
|
if (this.props.multiple) {
|
||||||
const selectedItems = [...this.values()].map(([key, value]) => (
|
const values = this.isValueAnObject() ? Object.keys(this.props.value) : this.props.value||[];
|
||||||
|
const source = this.source();
|
||||||
|
const selectedItems = values.map(key => (
|
||||||
<Chip
|
<Chip
|
||||||
key={key}
|
key={key}
|
||||||
className={this.props.theme.value}
|
className={this.props.theme.value}
|
||||||
deletable
|
deletable
|
||||||
onDeleteClick={this.unselect.bind(this, key)}
|
onDeleteClick={this.unselect.bind(this, key)}
|
||||||
>
|
>
|
||||||
{value}
|
{source.get(''+key)}
|
||||||
</Chip>
|
</Chip>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -415,26 +379,30 @@ const factory = (Chip, Input) => {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
allowClear, allowCreate, clearTooltip, error, label, source, suggestionMatch, query, // eslint-disable-line no-unused-vars
|
allowClear, clearTooltip, error, label, source, suggestionMatch, query, // eslint-disable-line no-unused-vars
|
||||||
selectedPosition, keepFocusOnChange, showSuggestionsWhenValueIsSet, showSelectedWhenNotInSource, onQueryChange, // eslint-disable-line no-unused-vars
|
selectedPosition, keepFocusOnChange, onQueryChange, // eslint-disable-line no-unused-vars
|
||||||
theme, ...other
|
theme, multiple, minWidth, ...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
// outdated properties
|
||||||
|
delete other.showSelectedWhenNotInSource;
|
||||||
|
delete other.allowCreate;
|
||||||
const className = classnames(theme.autocomplete, {
|
const className = classnames(theme.autocomplete, {
|
||||||
[theme.focus]: this.state.focus,
|
[theme.focus]: this.state.focus,
|
||||||
}, this.props.className);
|
}, this.props.className);
|
||||||
|
|
||||||
|
const withClear = allowClear && (this.props.multiple ? this.props.value && Object.keys(this.props.value).length > 0 : this.props.value != null);
|
||||||
return (
|
return (
|
||||||
<div data-react-toolbox="autocomplete" className={className}>
|
<div data-react-toolbox="autocomplete" className={className}>
|
||||||
{selectedPosition === 'above' ? this.renderSelected() : null}
|
{selectedPosition === 'above' ? this.renderSelected() : null}
|
||||||
{allowClear && this.state.query != '' ? <span
|
{withClear ? <span
|
||||||
className={'material-icons '+theme.clear}
|
className={'material-icons '+theme.clear}
|
||||||
title={clearTooltip}
|
title={clearTooltip}
|
||||||
onClick={(e) => this.handleChange([], e)}>clear</span> : null}
|
onClick={e => this.handleChange(multiple ? [] : null, e)}>clear</span> : null}
|
||||||
<Input
|
<Input
|
||||||
{...other}
|
{...other}
|
||||||
ref={(node) => { this.inputNode = node; }}
|
ref={(node) => { this.inputNode = node; }}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={theme.input+(allowClear && this.state.query != '' ? ' '+theme.withclear : '')}
|
className={theme.input+(withClear ? ' '+theme.withclear : '')}
|
||||||
error={error}
|
error={error}
|
||||||
label={label}
|
label={label}
|
||||||
onBlur={this.handleQueryBlur}
|
onBlur={this.handleQueryBlur}
|
||||||
|
@ -444,7 +412,7 @@ const factory = (Chip, Input) => {
|
||||||
onKeyUp={this.handleQueryKeyUp}
|
onKeyUp={this.handleQueryKeyUp}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
themeNamespace="input"
|
themeNamespace="input"
|
||||||
value={this.state.query}
|
value={this.state.query == null ? (this.props.multiple || !this.props.value ? '' : this.source().get(''+this.props.value)) : this.state.query}
|
||||||
/>
|
/>
|
||||||
<Portal>
|
<Portal>
|
||||||
{this.state.focus ? this.renderSuggestionList() : null}
|
{this.state.focus ? this.renderSuggestionList() : null}
|
||||||
|
|
|
@ -43,7 +43,6 @@ If you want to provide a theme via context, the component key is `RTAutocomplete
|
||||||
|
|
||||||
| Name | Type | Default | Description|
|
| Name | Type | Default | Description|
|
||||||
|:-----|:-----|:-----|:-----|
|
|:-----|:-----|:-----|:-----|
|
||||||
| `allowCreate` | `Bool` | `false` | Determines if user can create a new option with the current typed value |
|
|
||||||
| `className` | `String` | `''` | Sets a class to style of the Component.|
|
| `className` | `String` | `''` | Sets a class to style of the Component.|
|
||||||
| `direction` | `String` | `auto` | Determines the opening direction. It can be `auto`, `up` or `down`. |
|
| `direction` | `String` | `auto` | Determines the opening direction. It can be `auto`, `up` or `down`. |
|
||||||
| `disabled` | `Bool` | `false` | If true, component will be disabled. |
|
| `disabled` | `Bool` | `false` | If true, component will be disabled. |
|
||||||
|
@ -60,8 +59,6 @@ If you want to provide a theme via context, the component key is `RTAutocomplete
|
||||||
| `query` | `String` | | This property has to be used in case the `source` is not static and will be changing during search for `multiple={false}` autocomplete, content of the `query` has to be managed by the `onQueryChange` callback. |
|
| `query` | `String` | | This property has to be used in case the `source` is not static and will be changing during search for `multiple={false}` autocomplete, content of the `query` has to be managed by the `onQueryChange` callback. |
|
||||||
| `source` | `Object` or `Array` | | Object of key/values or array representing all items suggested. |
|
| `source` | `Object` or `Array` | | Object of key/values or array representing all items suggested. |
|
||||||
| `selectedPosition` | `String` | `above` | Determines if the selected list is shown above or below input. It can be `above`, `below` or `none`. |
|
| `selectedPosition` | `String` | `above` | Determines if the selected list is shown above or below input. It can be `above`, `below` or `none`. |
|
||||||
| `showSelectedWhenNotInSource` | `Bool` | `false` | Determines if the selected list is shown if the `value` keys don't exist in the source. Only works if passing the `value` prop as an Object. |
|
|
||||||
| `showSuggestionsWhenValueIsSet` | `Bool` | `false` | If true, the list of suggestions will not be filtered when a value is selected, until the query is modified. |
|
|
||||||
| `suggestionMatch` | `String` | `start` | Determines how suggestions are supplied. It can be `start` (query matches the start of a suggestion), `anywhere` (query matches anywhere inside the suggestion), `word` (query matches the start of a word in the suggestion) or `disabled` (disable filtering of provided source, all items are shown). |
|
| `suggestionMatch` | `String` | `start` | Determines how suggestions are supplied. It can be `start` (query matches the start of a suggestion), `anywhere` (query matches anywhere inside the suggestion), `word` (query matches the start of a word in the suggestion) or `disabled` (disable filtering of provided source, all items are shown). |
|
||||||
| `value` | `String`, `Array` or `Object` | | Value or array of values currently selected component. |
|
| `value` | `String`, `Array` or `Object` | | Value or array of values currently selected component. |
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,18 @@
|
||||||
text-indent: 28px !important;
|
text-indent: 28px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.withclear label {
|
||||||
|
transition-property: top, left, font-size, color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputFilled {
|
||||||
|
composes: filled from '../input/theme.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.withclear input:not(:focus):not(.inputFilled) ~ label {
|
||||||
|
left: 28px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.values {
|
.values {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
Loading…
Reference in New Issue