// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; import keycode from 'keycode'; import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui'; import { PopoverAnimationVertical } from 'material-ui/Popover'; import { isEqual } from 'lodash'; import styles from './autocomplete.css'; // Hack to prevent "Unknown prop `disableFocusRipple` on
tag" error class Divider extends Component { static muiName = MUIDivider.muiName; render () { return (
); } } export default class AutoComplete extends Component { static propTypes = { onChange: PropTypes.func.isRequired, onUpdateInput: PropTypes.func, disabled: PropTypes.bool, label: PropTypes.string, hint: PropTypes.string, error: PropTypes.string, value: PropTypes.string, className: PropTypes.string, filter: PropTypes.func, renderItem: PropTypes.func, entry: PropTypes.object, entries: PropTypes.oneOfType([ PropTypes.array, PropTypes.object ]) }; state = { lastChangedValue: undefined, entry: null, open: false, dataSource: [], dividerBreaks: [] }; dividersVisibility = {}; componentWillMount () { const dataSource = this.getDataSource(); this.setState({ dataSource }); } componentWillReceiveProps (nextProps) { const prevEntries = Object.keys(this.props.entries || {}).sort(); const nextEntries = Object.keys(nextProps.entries || {}).sort(); if (!isEqual(prevEntries, nextEntries)) { const dataSource = this.getDataSource(nextProps); this.setState({ dataSource }); } } render () { const { disabled, error, hint, label, value, className, onUpdateInput } = this.props; const { open, dataSource } = this.state; return ( ); } getDataSource (props = this.props) { const { renderItem, entries } = props; const entriesArray = (entries instanceof Array) ? entries : Object.values(entries); let currentDivider = 0; let firstSet = false; const dataSource = entriesArray.map((entry, index) => { // Render divider if (typeof entry === 'string' && entry.toLowerCase() === 'divider') { // Don't add divider if nothing before if (!firstSet) { return undefined; } const item = { text: '', divider: currentDivider, isDivider: true, value: ( ) }; currentDivider++; return item; } let item; if (renderItem && typeof renderItem === 'function') { item = renderItem(entry); // Add the item class to the entry const classNames = [ styles.item ].concat(item.value.props.className); item.value = React.cloneElement(item.value, { className: classNames.join(' ') }); } else { item = { text: entry, value: ( ) }; } if (!firstSet) { item.first = true; firstSet = true; } item.divider = currentDivider; item.entry = entry; return item; }).filter((item) => item !== undefined); return dataSource; } handleFilter = (searchText, name, item) => { if (item.isDivider) { return this.dividersVisibility[item.divider]; } if (item.first) { this.dividersVisibility = {}; } const { filter } = this.props; const show = filter(searchText, name, item); // Show the related divider if (show) { this.dividersVisibility[item.divider] = true; } return show; } onKeyDown = (event) => { const { muiAutocomplete } = this.refs; switch (keycode(event)) { case 'down': const { menu } = muiAutocomplete.refs; menu && menu.handleKeyDown(event); break; case 'enter': case 'tab': event.preventDefault(); event.stopPropagation(); event.which = 'useless'; const e = new CustomEvent('down'); e.which = 40; muiAutocomplete && muiAutocomplete.handleKeyDown(e); break; } } onChange = (item, idx) => { if (idx === -1) { return; } const { dataSource } = this.state; const { entry } = dataSource[idx]; this.handleOnChange(entry); this.setState({ entry, open: false }); } onClose = (event) => { const { onUpdateInput } = this.props; if (!onUpdateInput) { const { entry } = this.state; this.handleOnChange(entry); } } onFocus = () => { const { entry } = this.props; this.setState({ entry, open: true }, () => { this.handleOnChange(null, true); }); } handleOnChange = (value, empty) => { const { lastChangedValue } = this.state; if (value !== lastChangedValue) { this.setState({ lastChangedValue: value }); this.props.onChange(value, empty); } } }