diff --git a/dist/default.css b/dist/default.css index ca6d4fe16c..d687b66be1 100644 --- a/dist/default.css +++ b/dist/default.css @@ -25,21 +25,25 @@ .Select-control:hover { box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); } +.is-searchable.is-open > .Select-control { + cursor: text; +} .is-open > .Select-control { border-bottom-right-radius: 0; border-bottom-left-radius: 0; background: white; border-color: #b3b3b3 #cccccc #d9d9d9; - cursor: text; } .is-open > .Select-control > .Select-arrow { border-color: transparent transparent #999999; border-width: 0 5px 5px; } +.is-searchable.is-focused:not(.is-open) > .Select-control { + cursor: text; +} .is-focused:not(.is-open) > .Select-control { border-color: #0088cc #0099e6 #0099e6; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px rgba(0, 136, 204, 0.5); - cursor: text; } .Select-placeholder { color: #aaaaaa; @@ -49,7 +53,7 @@ left: 0; max-width: 100%; overflow: hidden; - text-overflow: ellipses; + text-overflow: ellipsis; white-space: nowrap; } .has-value > .Select-control > .Select-placeholder { @@ -70,6 +74,9 @@ .is-focused .Select-input > input { cursor: text; } +.Select-control:not(.is-searchable) > .Select-input { + outline: none; +} .Select-loading { -webkit-animation: spin 400ms infinite linear; -o-animation: spin 400ms infinite linear; @@ -187,6 +194,10 @@ border-top-right-radius: 2px; padding: 3px 5px; } +.Select-item-label .Select-item-label__a { + color: #0088cc; + cursor: pointer; +} .Select-item-icon { cursor: pointer; border-bottom-left-radius: 2px; diff --git a/dist/react-select.js b/dist/react-select.js index 7c5fbe9288..44e7c789e8 100644 --- a/dist/react-select.js +++ b/dist/react-select.js @@ -17,6 +17,7 @@ var Select = React.createClass({ propTypes: { value: React.PropTypes.any, // initial field value multi: React.PropTypes.bool, // multi-value input + disabled: React.PropTypes.bool, // whether the Select is disabled or not options: React.PropTypes.array, // array of options delimiter: React.PropTypes.string, // delimiter to use to join multiple values asyncOptions: React.PropTypes.func, // function to call to get options @@ -26,6 +27,7 @@ var Select = React.createClass({ clearable: React.PropTypes.bool, // should it be possible to reset value clearValueText: React.PropTypes.string, // title for the "clear" control clearAllText: React.PropTypes.string, // title for the "clear" control when multi: true + searchable: React.PropTypes.bool, // whether to enable searching feature or not searchPromptText: React.PropTypes.string, // label to prompt for search input name: React.PropTypes.string, // field name, for hidden tag onChange: React.PropTypes.func, // onChange handler: function(newValue) {} @@ -33,13 +35,23 @@ var Select = React.createClass({ filterOption: React.PropTypes.func, // method to filter a single option: function(option, filterString) filterOptions: React.PropTypes.func, // method to filter the options array: function([options], filterString, [values]) matchPos: React.PropTypes.string, // (any|start) match the start or entire string when filtering - matchProp: React.PropTypes.string // (any|label|value) which option property to filter on + matchProp: React.PropTypes.string, // (any|label|value) which option property to filter on + + /* + + * Allow user to make option label clickable. When this handler is defined we should + * wrap label into label tag. + * + * onOptionLabelClick handler: function (value, event) {} + * */ + onOptionLabelClick: React.PropTypes.func }, getDefaultProps: function () { return { value: undefined, options: [], + disabled: false, delimiter: ",", asyncOptions: undefined, autoload: true, @@ -48,12 +60,15 @@ var Select = React.createClass({ clearable: true, clearValueText: "Clear value", clearAllText: "Clear all", + searchable: true, searchPromptText: "Type to search", name: undefined, onChange: undefined, className: undefined, matchPos: "any", - matchProp: "any" + matchProp: "any", + + onOptionLabelClick: undefined }; }, @@ -106,7 +121,7 @@ var Select = React.createClass({ if (this._focusAfterUpdate) { clearTimeout(this._blurTimeout); this._focusTimeout = setTimeout((function () { - this.refs.input.focus(); + this.getInputNode().focus(); this._focusAfterUpdate = false; }).bind(this), 50); } @@ -205,6 +220,11 @@ var Select = React.createClass({ this.setValue(this.state.value); }, + getInputNode: function () { + var input = this.refs.input; + return this.props.searchable ? input : input.getDOMNode(); + }, + fireChangeEvent: function (newState) { if (newState.value !== this.state.value && this.props.onChange) { this.props.onChange(newState.value, newState.values); @@ -213,10 +233,11 @@ var Select = React.createClass({ handleMouseDown: function (event) { // if the event was triggered by a mousedown and not the primary - // button, ignore it. - if (event.type == "mousedown" && event.button !== 0) { + // button, or if the component is disabled, ignore it. + if (this.props.disabled || event.type == "mousedown" && event.button !== 0) { return; } + event.stopPropagation(); event.preventDefault(); if (this.state.isFocused) { @@ -225,7 +246,7 @@ var Select = React.createClass({ }); } else { this._openAfterFocus = true; - this.refs.input.focus(); + this.getInputNode().focus(); } }, @@ -248,6 +269,8 @@ var Select = React.createClass({ }, handleKeyDown: function (event) { + if (this.state.disabled) return; + switch (event.keyCode) { case 8: @@ -356,6 +379,10 @@ var Select = React.createClass({ }, filterOptions: function (options, values) { + if (!this.props.searchable) { + return options; + } + var filterValue = this._optionsFilterString; var exclude = (values || this.state.values).map(function (i) { return i.value; @@ -473,12 +500,22 @@ var Select = React.createClass({ ); }, + handleOptionLabelClick: function (value, event) { + var handler = this.props.onOptionLabelClick; + + if (handler) { + handler(value, event); + } + }, + render: function () { var selectClass = classes("Select", this.props.className, { "is-multi": this.props.multi, + "is-searchable": this.props.searchable, "is-open": this.state.isOpen, "is-focused": this.state.isFocused, "is-loading": this.state.isLoading, + "is-disabled": this.props.disabled, "has-value": this.state.value }); @@ -488,13 +525,15 @@ var Select = React.createClass({ this.state.values.forEach(function (val) { var props = _.extend({ key: val.value, + optionLabelClick: !!this.props.onOptionLabelClick, + onOptionLabelClick: this.handleOptionLabelClick.bind(this, val), onRemove: this.removeValue.bind(this, val) }, val); value.push(React.createElement(Value, props)); }, this); } - if (!this.state.inputValue && (!this.props.multi || !value.length)) { + if (this.props.disabled || !this.state.inputValue && (!this.props.multi || !value.length)) { value.push(React.createElement( "div", { className: "Select-placeholder", key: "placeholder" }, @@ -503,22 +542,41 @@ var Select = React.createClass({ } var loading = this.state.isLoading ? React.createElement("span", { className: "Select-loading", "aria-hidden": "true" }) : null; - var clear = this.props.clearable && this.state.value ? React.createElement("span", { className: "Select-clear", title: this.props.multi ? this.props.clearAllText : this.props.clearValueText, "aria-label": this.props.multi ? this.props.clearAllText : this.props.clearValueText, onMouseDown: this.clearValue, onClick: this.clearValue, dangerouslySetInnerHTML: { __html: "×" } }) : null; + var clear = this.props.clearable && this.state.value && !this.props.disabled ? React.createElement("span", { className: "Select-clear", title: this.props.multi ? this.props.clearAllText : this.props.clearValueText, "aria-label": this.props.multi ? this.props.clearAllText : this.props.clearValueText, onMouseDown: this.clearValue, onClick: this.clearValue, dangerouslySetInnerHTML: { __html: "×" } }) : null; var menu = this.state.isOpen ? React.createElement( "div", { ref: "menu", onMouseDown: this.handleMouseDown, className: "Select-menu" }, this.buildMenu() ) : null; + var commonProps = { + ref: "input", + className: "Select-input", + tabIndex: this.props.tabIndex || 0, + onFocus: this.handleInputFocus, + onBlur: this.handleInputBlur + }; + var input; + + if (this.props.searchable && !this.props.disabled) { + input = React.createElement(Input, React.__spread({ value: this.state.inputValue, onChange: this.handleInputChange, minWidth: "5" }, commonProps)); + } else { + input = React.createElement( + "div", + commonProps, + " " + ); + } + return React.createElement( "div", { ref: "wrapper", className: selectClass }, - React.createElement("input", { type: "hidden", ref: "value", name: this.props.name, value: this.state.value }), + React.createElement("input", { type: "hidden", ref: "value", name: this.props.name, value: this.state.value, disabled: this.props.disabled }), React.createElement( "div", { className: "Select-control", ref: "control", onKeyDown: this.handleKeyDown, onMouseDown: this.handleMouseDown, onTouchEnd: this.handleMouseDown }, value, - React.createElement(Input, { className: "Select-input", tabIndex: this.props.tabIndex, ref: "input", value: this.state.inputValue, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, onChange: this.handleInputChange, minWidth: "5" }), + input, React.createElement("span", { className: "Select-arrow" }), loading, clear @@ -533,29 +591,41 @@ module.exports = Select; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./Value":3,"classnames":2}],2:[function(require,module,exports){ -function classnames() { - var args = arguments, classes = []; +function classNames() { + var args = arguments; + var classes = []; + for (var i = 0; i < args.length; i++) { - if (args[i] && 'string' === typeof args[i]) { - classes.push(args[i]); - } else if ('object' === typeof args[i]) { - classes = classes.concat(Object.keys(args[i]).filter(function(cls) { - return args[i][cls]; - })); + var arg = args[i]; + if (!arg) { + continue; + } + + if ('string' === typeof arg || 'number' === typeof arg) { + classes.push(arg); + } else if ('object' === typeof arg) { + for (var key in arg) { + if (!arg.hasOwnProperty(key) || !arg[key]) { + continue; + } + classes.push(key); + } } } - return classes.join(' ') || undefined; + return classes.join(' '); } -module.exports = classnames; +// safely export classNames in case the script is included directly on a page +if (typeof module !== 'undefined' && module.exports) { + module.exports = classNames; +} },{}],3:[function(require,module,exports){ (function (global){ "use strict"; var _ = (typeof window !== "undefined" ? window._ : typeof global !== "undefined" ? global._ : null), - React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null), - classes = require("classnames"); + React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var Option = React.createClass({ @@ -570,18 +640,34 @@ var Option = React.createClass({ }, render: function () { + var label = this.props.label; + + if (this.props.optionLabelClick) { + label = React.createElement( + "a", + { className: "Select-item-label__a", + onMouseDown: this.blockEvent, + onTouchEnd: this.props.onOptionLabelClick, + onClick: this.props.onOptionLabelClick }, + label + ); + } + return React.createElement( "div", { className: "Select-item" }, React.createElement( "span", - { className: "Select-item-icon", onMouseDown: this.blockEvent, onClick: this.props.onRemove, onTouchEnd: this.props.onRemove }, + { className: "Select-item-icon", + onMouseDown: this.blockEvent, + onClick: this.props.onRemove, + onTouchEnd: this.props.onRemove }, "×" ), React.createElement( "span", { className: "Select-item-label" }, - this.props.label + label ) ); } @@ -591,5 +677,5 @@ var Option = React.createClass({ module.exports = Option; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"classnames":2}]},{},[1])(1) +},{}]},{},[1])(1) }); \ No newline at end of file diff --git a/dist/react-select.min.js b/dist/react-select.min.js index bc7f9be2b2..8aacebf452 100644 --- a/dist/react-select.min.js +++ b/dist/react-select.min.js @@ -1 +1 @@ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.Select=t()}}(function(){return function t(e,s,i){function n(a,l){if(!s[a]){if(!e[a]){var u="function"==typeof require&&require;if(!l&&u)return u(a,!0);if(o)return o(a,!0);var r=new Error("Cannot find module '"+a+"'");throw r.code="MODULE_NOT_FOUND",r}var p=s[a]={exports:{}};e[a][0].call(p.exports,function(t){var s=e[a][1][t];return n(s?s:t)},p,p.exports,t,e,s,i)}return s[a].exports}for(var o="function"==typeof require&&require,a=0;ai.bottom||s.top=0||"value"!==this.props.matchProp&&t.label.toLowerCase().indexOf(s.toLowerCase())>=0:"label"!==this.props.matchProp&&t.value.toLowerCase().substr(0,s.length)===s||"value"!==this.props.matchProp&&t.label.toLowerCase().substr(0,s.length)===s};return i.filter(t,o,this)},selectFocusedOption:function(){return this.selectValue(this.state.focusedOption)},focusOption:function(t){this.setState({focusedOption:t})},focusNextOption:function(){this.focusAdjacentOption("next")},focusPreviousOption:function(){this.focusAdjacentOption("previous")},focusAdjacentOption:function(t){this._focusedOptionReveal=!0;var e=this.state.filteredOptions;if(!this.state.isOpen)return void this.setState({isOpen:!0,inputValue:"",focusedOption:this.state.focusedOption||e["next"===t?0:e.length-1]});if(e.length){for(var s=-1,i=0;i-1&&s0?e[s-1]:e[e.length-1]),this.setState({focusedOption:n})}},unfocusOption:function(t){this.state.focusedOption===t&&this.setState({focusedOption:null})},buildMenu:function(){var t=this.state.focusedOption?this.state.focusedOption.value:null,e=i.map(this.state.filteredOptions,function(e){var s=t===e.value,i=a({"Select-option":!0,"is-focused":s}),o=s?"focused":null,l=this.focusOption.bind(this,e),u=this.unfocusOption.bind(this,e),r=this.selectValue.bind(this,e);return n.createElement("div",{ref:o,key:"option-"+e.value,className:i,onMouseEnter:l,onMouseLeave:u,onMouseDown:r,onClick:r},e.label)},this);return e.length?e:n.createElement("div",{className:"Select-noresults"},this.props.asyncOptions&&!this.state.inputValue?this.props.searchPromptText:this.props.noResultsText)},render:function(){var t=a("Select",this.props.className,{"is-multi":this.props.multi,"is-open":this.state.isOpen,"is-focused":this.state.isFocused,"is-loading":this.state.isLoading,"has-value":this.state.value}),e=[];this.props.multi&&this.state.values.forEach(function(t){var s=i.extend({key:t.value,onRemove:this.removeValue.bind(this,t)},t);e.push(n.createElement(l,s))},this),this.state.inputValue||this.props.multi&&e.length||e.push(n.createElement("div",{className:"Select-placeholder",key:"placeholder"},this.state.placeholder));var s=this.state.isLoading?n.createElement("span",{className:"Select-loading","aria-hidden":"true"}):null,u=this.props.clearable&&this.state.value?n.createElement("span",{className:"Select-clear",title:this.props.multi?this.props.clearAllText:this.props.clearValueText,"aria-label":this.props.multi?this.props.clearAllText:this.props.clearValueText,onMouseDown:this.clearValue,onClick:this.clearValue,dangerouslySetInnerHTML:{__html:"×"}}):null,r=this.state.isOpen?n.createElement("div",{ref:"menu",onMouseDown:this.handleMouseDown,className:"Select-menu"},this.buildMenu()):null;return n.createElement("div",{ref:"wrapper",className:t},n.createElement("input",{type:"hidden",ref:"value",name:this.props.name,value:this.state.value}),n.createElement("div",{className:"Select-control",ref:"control",onKeyDown:this.handleKeyDown,onMouseDown:this.handleMouseDown,onTouchEnd:this.handleMouseDown},e,n.createElement(o,{className:"Select-input",tabIndex:this.props.tabIndex,ref:"input",value:this.state.inputValue,onFocus:this.handleInputFocus,onBlur:this.handleInputBlur,onChange:this.handleInputChange,minWidth:"5"}),n.createElement("span",{className:"Select-arrow"}),s,u),r)}});e.exports=r}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./Value":3,classnames:2}],2:[function(t,e){function s(){for(var t=arguments,e=[],s=0;si.bottom||s.top=0||"value"!==this.props.matchProp&&e.label.toLowerCase().indexOf(s.toLowerCase())>=0:"label"!==this.props.matchProp&&e.value.toLowerCase().substr(0,s.length)===s||"value"!==this.props.matchProp&&e.label.toLowerCase().substr(0,s.length)===s};return i.filter(e,n,this)},selectFocusedOption:function(){return this.selectValue(this.state.focusedOption)},focusOption:function(e){this.setState({focusedOption:e})},focusNextOption:function(){this.focusAdjacentOption("next")},focusPreviousOption:function(){this.focusAdjacentOption("previous")},focusAdjacentOption:function(e){this._focusedOptionReveal=!0;var t=this.state.filteredOptions;if(!this.state.isOpen)return void this.setState({isOpen:!0,inputValue:"",focusedOption:this.state.focusedOption||t["next"===e?0:t.length-1]});if(t.length){for(var s=-1,i=0;i-1&&s0?t[s-1]:t[t.length-1]),this.setState({focusedOption:o})}},unfocusOption:function(e){this.state.focusedOption===e&&this.setState({focusedOption:null})},buildMenu:function(){var e=this.state.focusedOption?this.state.focusedOption.value:null,t=i.map(this.state.filteredOptions,function(t){var s=e===t.value,i=a({"Select-option":!0,"is-focused":s}),n=s?"focused":null,l=this.focusOption.bind(this,t),r=this.unfocusOption.bind(this,t),p=this.selectValue.bind(this,t);return o.createElement("div",{ref:n,key:"option-"+t.value,className:i,onMouseEnter:l,onMouseLeave:r,onMouseDown:p,onClick:p},t.label)},this);return t.length?t:o.createElement("div",{className:"Select-noresults"},this.props.asyncOptions&&!this.state.inputValue?this.props.searchPromptText:this.props.noResultsText)},handleOptionLabelClick:function(e,t){var s=this.props.onOptionLabelClick;s&&s(e,t)},render:function(){var e=a("Select",this.props.className,{"is-multi":this.props.multi,"is-searchable":this.props.searchable,"is-open":this.state.isOpen,"is-focused":this.state.isFocused,"is-loading":this.state.isLoading,"is-disabled":this.props.disabled,"has-value":this.state.value}),t=[];this.props.multi&&this.state.values.forEach(function(e){var s=i.extend({key:e.value,optionLabelClick:!!this.props.onOptionLabelClick,onOptionLabelClick:this.handleOptionLabelClick.bind(this,e),onRemove:this.removeValue.bind(this,e)},e);t.push(o.createElement(l,s))},this),!this.props.disabled&&(this.state.inputValue||this.props.multi&&t.length)||t.push(o.createElement("div",{className:"Select-placeholder",key:"placeholder"},this.state.placeholder));var s,r=this.state.isLoading?o.createElement("span",{className:"Select-loading","aria-hidden":"true"}):null,p=this.props.clearable&&this.state.value&&!this.props.disabled?o.createElement("span",{className:"Select-clear",title:this.props.multi?this.props.clearAllText:this.props.clearValueText,"aria-label":this.props.multi?this.props.clearAllText:this.props.clearValueText,onMouseDown:this.clearValue,onClick:this.clearValue,dangerouslySetInnerHTML:{__html:"×"}}):null,u=this.state.isOpen?o.createElement("div",{ref:"menu",onMouseDown:this.handleMouseDown,className:"Select-menu"},this.buildMenu()):null,c={ref:"input",className:"Select-input",tabIndex:this.props.tabIndex||0,onFocus:this.handleInputFocus,onBlur:this.handleInputBlur};return s=this.props.searchable&&!this.props.disabled?o.createElement(n,o.__spread({value:this.state.inputValue,onChange:this.handleInputChange,minWidth:"5"},c)):o.createElement("div",c," "),o.createElement("div",{ref:"wrapper",className:e},o.createElement("input",{type:"hidden",ref:"value",name:this.props.name,value:this.state.value,disabled:this.props.disabled}),o.createElement("div",{className:"Select-control",ref:"control",onKeyDown:this.handleKeyDown,onMouseDown:this.handleMouseDown,onTouchEnd:this.handleMouseDown},t,s,o.createElement("span",{className:"Select-arrow"}),r,p),u)}});t.exports=p}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./Value":3,classnames:2}],2:[function(e,t){function s(){for(var e=arguments,t=[],s=0;s