UNPKG

ldx-widgets

Version:

widgets

270 lines (248 loc) 9.31 kB
(function() { var DOWN_ARROW, ENTER, ESCAPE, PropTypes, React, SelectInputCustomOptions, TAB, TextInput, UP_ARROW, _, createClass, div, li, ref, ref1, ul; React = require('react'); createClass = require('create-react-class'); PropTypes = require('prop-types'); TextInput = React.createFactory(require('./text_input_2')); ref = require('../constants/keyboard'), DOWN_ARROW = ref.DOWN_ARROW, UP_ARROW = ref.UP_ARROW, TAB = ref.TAB, ESCAPE = ref.ESCAPE, ENTER = ref.ENTER; _ = require('lodash'); ref1 = React.DOM, div = ref1.div, ul = ref1.ul, li = ref1.li; /*& @general Filter select options component. This component lives on the overlay layer, and requires integrated context methods closeOverlay and openOverlay within the application. @props.filter - [String] - Optional Initialize the component overlay with a filter value. This will start filtering option labels based on this value. @props.placeholder - [String] - Optional Placeholder value for the filter input @props.onChange - [Function] - Required Function that is fired when a selection change is made @props.options - [Array] - Optional Array of options to render in the select @props.optionHeight - [Number] - Optional The fixed height of each menu option @props.value - [String|Number] - Optional The value of the currently selected option object @props.noResultsText - [String] - Optional Text displayed in the menu when no results match the filter input value @props.SelectEl - [Function] - Optional Reference to the select menu component that opens this overlay. If provided, focus will be directed back to the input when closing the overlay @props.onChangeFilter - [Function] - Optional Function fired when the filter input changes & */ SelectInputCustomOptions = createClass({ displayName: 'SelectInputCustomOptions', contextTypes: { closeOverlay: PropTypes.func }, propTypes: { filter: PropTypes.string, placeholder: PropTypes.string, onChange: PropTypes.func.isRequired, options: PropTypes.array, optionHeight: PropTypes.number, value: PropTypes.string, noResultsText: PropTypes.string, SelectEl: PropTypes.object, valueField: PropTypes.string, labelField: PropTypes.string, isFilter: PropTypes.bool }, getDefaultProps: function() { return { filter: '', optionHeight: 20, noResultsText: 'No results found', isFilter: false }; }, getInitialState: function() { return { filterValue: '', focusedOptionIndex: 0, options: this.props.options }; }, componentDidMount: function() { var filter; filter = this.props.filter; if (filter) { this.setState({ filterValue: filter }); } if (this.textInput != null) { this.textInput.focus(); } return window.addEventListener('keydown', this.handleKeyDown); }, componentWillUnmount: function() { return window.removeEventListener('keydown', this.handleKeyDown); }, componentWillUpdate: function(nextProps, nextState) { var filterValue, labelField, options, opts; filterValue = nextState.filterValue; options = nextProps.options, labelField = nextProps.labelField; if ((filterValue != null) && this.state.filterValue !== filterValue) { opts = _.filter(options, (function(_this) { return function(o) { return o[labelField].toLowerCase().search(filterValue.toLowerCase()) > -1; }; })(this)); return this.setState({ options: opts }); } }, render: function() { var filterValue, isFilter, noResultsText, optionListClass, options, placeholder, ref2, ref3, selectOptions, value; ref2 = this.state, filterValue = ref2.filterValue, options = ref2.options; ref3 = this.props, placeholder = ref3.placeholder, value = ref3.value, noResultsText = ref3.noResultsText, isFilter = ref3.isFilter; selectOptions = []; optionListClass = isFilter ? 'options-list' : 'options-list no-filter'; options.forEach((function(_this) { return function(o, i) { return selectOptions.push(_this.processOption(o, i)); }; })(this)); return div({ className: "select-options", onClick: this.handleOptionsClick }, [ isFilter ? TextInput({ key: 'input', ref: (function(_this) { return function(input) { return _this.textInput = input; }; })(this), id: "filter", value: filterValue, onChange: this.handleFilterChange, placeholder: placeholder }) : void 0, selectOptions.length ? ul({ key: 'options', className: optionListClass, ref: (function(_this) { return function(optionsList) { return _this.optionsList = optionsList; }; })(this), onScroll: this.handleScroll }, selectOptions) : div({ key: 'no-results', className: 'no-results' }, noResultsText) ]); }, processOption: function(opt, index) { var focusedOptionIndex, labelField, optionClass, optionHeight, options, ref2, ref3, value, valueField; ref2 = this.state, options = ref2.options, focusedOptionIndex = ref2.focusedOptionIndex; ref3 = this.props, value = ref3.value, optionHeight = ref3.optionHeight, labelField = ref3.labelField, valueField = ref3.valueField; optionClass = "option"; if (options[focusedOptionIndex] === opt) { optionClass += " is-focused"; } if (value === opt[valueField]) { optionClass += " is-selected"; } return li({ key: index, onClick: this.handleClick.bind(this, opt), className: optionClass, title: opt[labelField], style: { height: optionHeight, lineHeight: optionHeight + "px" } }, opt[labelField]); }, handleKeyDown: function(e) { var SelectEl, adjust, currentOption, focusedOptionIndex, isFilter, labelField, newIndex, options, ref2, ref3, valueField; ref2 = this.state, options = ref2.options, focusedOptionIndex = ref2.focusedOptionIndex, isFilter = ref2.isFilter; ref3 = this.props, SelectEl = ref3.SelectEl, labelField = ref3.labelField, valueField = ref3.valueField; adjust = 0; switch (e.keyCode) { case UP_ARROW: e.preventDefault(); adjust = -1; break; case DOWN_ARROW: e.preventDefault(); adjust = 1; break; case TAB: e.preventDefault(); return this.context.closeOverlay(SelectEl); case ESCAPE: e.preventDefault(); return this.context.closeOverlay(SelectEl); case ENTER: e.preventDefault(); currentOption = options[focusedOptionIndex]; if (currentOption != null) { return this.props.onChange(currentOption[valueField], this.context.closeOverlay); } return this.context.closeOverlay(SelectEl); default: e.stopPropagation(); break; } newIndex = focusedOptionIndex + adjust; if (newIndex < 0) { newIndex = 0; } else if (newIndex >= options.length - 1) { newIndex = options.length - 1; } return this.setState({ focusedOptionIndex: newIndex }, (function(_this) { return function() { if (isFilter) { _this.textInput.focus(); } if (_this.optionsList != null) { return _this.adjustScrollPosition(adjust); } }; })(this)); }, adjustScrollPosition: function(adjust) { var adjustTop, maxHeight, optionHeight, options, scrollAdjust, scrollTop; optionHeight = this.props.optionHeight; options = this.state.options; scrollAdjust = adjust * optionHeight; scrollTop = this.optionsList.scrollTop; adjustTop = this.optionsList.scrollTop + scrollAdjust; maxHeight = optionHeight * options.length; if (adjustTop < 0) { adjustTop = 0; } else if (adjustTop > maxHeight) { adjustTop = maxHeight; } return this.optionsList.scrollTop = adjustTop; }, handleFilterChange: function(e) { return this.setState({ filterValue: e }, (function(_this) { return function() { var base; return typeof (base = _this.props).onChangeFilter === "function" ? base.onChangeFilter(e) : void 0; }; })(this)); }, handleClick: function(option, e) { var onChange, ref2, valueField; ref2 = this.props, valueField = ref2.valueField, onChange = ref2.onChange; return onChange(option[valueField], this.context.closeOverlay); }, handleOptionsClick: function(e) { return e.stopPropagation(); }, handleScroll: function(e) { return e.stopPropagation(); } }); module.exports = SelectInputCustomOptions; }).call(this);