UNPKG

ldx-widgets

Version:

widgets

311 lines (287 loc) 11 kB
(function() { var DOWN_ARROW, ENTER, ESCAPE, PropTypes, React, SearchInput, SelectInputCustomOptions, Spinner, TAB, UP_ARROW, _filter, createClass, div, li, ref, ref1, ul; React = require('react'); createClass = require('create-react-class'); PropTypes = require('prop-types'); SearchInput = React.createFactory(require('./search_input')); Spinner = React.createFactory(require('./spinner')); ref = require('../constants/keyboard'), DOWN_ARROW = ref.DOWN_ARROW, UP_ARROW = ref.UP_ARROW, TAB = ref.TAB, ESCAPE = ref.ESCAPE, ENTER = ref.ENTER; _filter = require('lodash/filter'); ref1 = require('react-dom-factories'), 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 @props.searchWidth - [Number] - Optional Width of the search input. Default is 250 & */ 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, OPTION_PADDING: PropTypes.number, value: PropTypes.string, noResultsText: PropTypes.string, SelectEl: PropTypes.object, valueField: PropTypes.string, labelField: PropTypes.string, isFilter: PropTypes.bool, searchWidth: PropTypes.number }, getDefaultProps: function() { return { filter: '', optionHeight: 20, isFilter: false, searchWidth: 250 }; }, getInitialState: function() { return { filterValue: '', focusedOptionIndex: -1, options: this.props.options.slice(0) }; }, componentDidMount: function() { var SelectEl, filter, ref2, ref3; ref2 = this.props, filter = ref2.filter, SelectEl = ref2.SelectEl; if (filter) { this.handleFilterChange(filter); } else { if (SelectEl != null) { if ((ref3 = SelectEl.refs.input) != null) { ref3.blur(); } } } if (this.textInput != null) { this.textInput.focus(); } return window.addEventListener('keydown', this.handleKeyDown); }, componentWillUnmount: function() { return window.removeEventListener('keydown', this.handleKeyDown); }, render: function() { var filterValue, isFilter, labelField, noResultsText, optionListClass, options, passedOptions, placeholder, ref2, ref3, searchWidth, selectOptions, value; passedOptions = this.props.options; ref2 = this.state, filterValue = ref2.filterValue, options = ref2.options; ref3 = this.props, placeholder = ref3.placeholder, value = ref3.value, noResultsText = ref3.noResultsText, isFilter = ref3.isFilter, searchWidth = ref3.searchWidth, labelField = ref3.labelField; 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 ? SearchInput({ key: 'input', ref: (function(_this) { return function(input) { return _this.textInput = input; }; })(this), id: "filter", term: filterValue, handleChange: this.handleFilterChange, placeholder: placeholder, wrapClass: 'custom-filter-select', width: searchWidth }) : void 0, passedOptions.length === 0 ? Spinner({ key: 'spinner', length: 7, radius: 7, lines: 12, width: 2 }) : selectOptions.length === 0 ? div({ key: 'no-results', className: 'no-results' }, noResultsText != null ? noResultsText : t('No matches found')) : ul({ key: 'options', className: optionListClass, ref: (function(_this) { return function(optionsList) { return _this.optionsList = optionsList; }; })(this), onScroll: this.handleScroll }, selectOptions) ]); }, 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 (index === focusedOptionIndex) { optionClass += " is-focused"; } if (value === opt[valueField]) { optionClass += " is-selected"; } return li({ key: index, onClick: this.handleClick.bind(this, opt), onMouseOver: this.handleMouseOver.bind(this, index), onMouseOut: this.handleMouseOut.bind(this, index), 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({ refocusEl: SelectEl }); case ESCAPE: e.preventDefault(); return this.context.closeOverlay({ refocusEl: SelectEl }); case ENTER: e.preventDefault(); currentOption = options[focusedOptionIndex]; if (currentOption != null) { return this.props.onChange(currentOption[valueField], this.context.closeOverlay); } return this.context.closeOverlay({ refocusEl: 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(newIndex); } }; })(this)); }, adjustScrollPosition: function(newIndex) { var OPTION_PADDING, adjust, clientHeight, fullHeight, isOffBottom, isOffTop, newIndexBottom, newIndexTop, optionHeight, ref2, ref3, scrollTop; ref2 = this.props, optionHeight = ref2.optionHeight, OPTION_PADDING = ref2.OPTION_PADDING; fullHeight = optionHeight + OPTION_PADDING; ref3 = this.optionsList, scrollTop = ref3.scrollTop, clientHeight = ref3.clientHeight; newIndexTop = fullHeight * newIndex; newIndexBottom = newIndexTop + fullHeight; isOffBottom = newIndexBottom > scrollTop + clientHeight; isOffTop = newIndexTop < scrollTop; adjust = 0; if (isOffBottom) { adjust = newIndexBottom - (scrollTop + clientHeight); } else if (isOffTop) { adjust = 0 - (scrollTop - newIndexTop); } if (adjust !== 0) { return this.optionsList.scrollTop = scrollTop + adjust; } }, handleFilterChange: function(filterValue) { var labelField, opt, options, ref2; ref2 = this.props, options = ref2.options, labelField = ref2.labelField; options = (function() { var j, len, results; results = []; for (j = 0, len = options.length; j < len; j++) { opt = options[j]; if (opt[labelField].toLowerCase().search(filterValue.toLowerCase()) > -1) { results.push(opt); } } return results; })(); return this.setState({ filterValue: filterValue, options: options }, (function(_this) { return function() { var base; return typeof (base = _this.props).onChangeFilter === "function" ? base.onChangeFilter(filterValue) : 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); }, handleMouseOver: function(focusedOptionIndex, e) { return this.setState({ focusedOptionIndex: focusedOptionIndex }); }, handleMouseOut: function(focusedOptionIndex, e) { return this.setState({ focusedOptionIndex: -1 }); }, handleOptionsClick: function(e) { return e.stopPropagation(); }, handleScroll: function(e) { return e.stopPropagation(); } }); module.exports = SelectInputCustomOptions; }).call(this);