UNPKG

react-phone-number-input

Version:
1,316 lines (1,068 loc) 38.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _taggedTemplateLiteral2 = require('babel-runtime/helpers/taggedTemplateLiteral'); var _taggedTemplateLiteral3 = _interopRequireDefault(_taggedTemplateLiteral2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _templateObject = (0, _taggedTemplateLiteral3.default)(['\n\twrapper\n\t\t// Sometimes (e.g. when using mobile dropdown menus)\n\t\t// "position: relative" could be overridden to "static"\n\t\t// to allow for the menu stretching to full screen width.\n\t\t// Therefore it was moved to CSS from inline styles.\n\n\t\t-webkit-user-select : none\n\t\t-moz-user-select : none\n\t\t-ms-user-select : none\n\t\tuser-select : none\n\n\tlist\n\t\tlist-style-type : none\n\t\toverflow-x : hidden\n\n\tselected\n\t\tbox-sizing : border-box\n\n\tselected_flex_wrapper\n\t\tdisplay : flex\n\t\talign-items : center\n\n\tselected_label\n\t\tflex : 1\n\t\toverflow : hidden\n\t\ttext-overflow : ellipsis\n\n\tarrow\n\n\tseparator\n\t\tpadding : 0\n\t\tline-height : 0\n\t\tfont-size : 0\n\n\tlabel\n\t\tposition : absolute\n\t\twhite-space : nowrap\n\n\t\t-webkit-user-select : none\n\t\t-moz-user-select : none\n\t\t-ms-user-select : none\n\t\tuser-select : none\n\n\t\t// Vertically align\n\t\tdisplay : flex\n\t\talign-items : center\n\t\theight : 100%\n'], ['\n\twrapper\n\t\t// Sometimes (e.g. when using mobile dropdown menus)\n\t\t// "position: relative" could be overridden to "static"\n\t\t// to allow for the menu stretching to full screen width.\n\t\t// Therefore it was moved to CSS from inline styles.\n\n\t\t-webkit-user-select : none\n\t\t-moz-user-select : none\n\t\t-ms-user-select : none\n\t\tuser-select : none\n\n\tlist\n\t\tlist-style-type : none\n\t\toverflow-x : hidden\n\n\tselected\n\t\tbox-sizing : border-box\n\n\tselected_flex_wrapper\n\t\tdisplay : flex\n\t\talign-items : center\n\n\tselected_label\n\t\tflex : 1\n\t\toverflow : hidden\n\t\ttext-overflow : ellipsis\n\n\tarrow\n\n\tseparator\n\t\tpadding : 0\n\t\tline-height : 0\n\t\tfont-size : 0\n\n\tlabel\n\t\tposition : absolute\n\t\twhite-space : nowrap\n\n\t\t-webkit-user-select : none\n\t\t-moz-user-select : none\n\t\t-ms-user-select : none\n\t\tuser-select : none\n\n\t\t// Vertically align\n\t\tdisplay : flex\n\t\talign-items : center\n\t\theight : 100%\n']); // https://github.com/halt-hammerzeit/react-responsive-ui/blob/master/source/select.js var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _reactStyling = require('react-styling'); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _dom = require('./misc/dom'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Possible enhancements: // // * If the menu is close to a screen edge, // automatically reposition it so that it fits on the screen // * Maybe show menu immediately above the toggler // (like in Material design), not below it. // // https://material.google.com/components/menus.html var value_prop_type = _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.number, _react2.default.PropTypes.bool]); var Select = function (_PureComponent) { (0, _inherits3.default)(Select, _PureComponent); function Select(props) { (0, _classCallCheck3.default)(this, Select); // Shouldn't memory leak because // the set of options is assumed to be constant. var _this = (0, _possibleConstructorReturn3.default)(this, (Select.__proto__ || (0, _getPrototypeOf2.default)(Select)).call(this, props)); _this.state = {}; _this.options = {}; _this.toggle = _this.toggle.bind(_this); _this.document_clicked = _this.document_clicked.bind(_this); _this.on_key_down = _this.on_key_down.bind(_this); _this.on_autocomplete_input_change = _this.on_autocomplete_input_change.bind(_this); _this.on_key_down_in_container = _this.on_key_down_in_container.bind(_this); var value = props.value; var autocomplete = props.autocomplete; var options = props.options; var children = props.children; var menu = props.menu; var toggler = props.toggler; var onChange = props.onChange; if (autocomplete) { if (!options) { throw new Error('"options" property is required for an "autocomplete" select'); } _this.state.matching_options = _this.get_matching_options(options, value); } if (children && !menu) { _react2.default.Children.forEach(children, function (element) { if (!element.props.value) { throw new Error('You must specify "value" prop on each child of <Select/>'); } if (!element.props.label) { throw new Error('You must specify "label" prop on each child of <Select/>'); } }); } if (menu && !toggler) { throw new Error('Supply a "toggler" component when enabling "menu" in <Select/>'); } if (!menu && !onChange) { throw new Error('"onChange" property must be specified for <Select/>'); } return _this; } // Client side rendering, javascript is enabled (0, _createClass3.default)(Select, [{ key: 'componentDidMount', value: function componentDidMount() { document.addEventListener('click', this.document_clicked); var fallback = this.props.fallback; if (fallback) { this.setState({ javascript: true }); } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(previous_props, previous_state) { var _state = this.state; var expanded = _state.expanded; var height = _state.height; if (expanded !== previous_state.expanded) { if (expanded && this.should_animate()) { if (height === undefined) { this.calculate_height(); } } } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { document.removeEventListener('click', this.document_clicked); } }, { key: 'render', value: function render() { var _this2 = this; var _props = this.props; var id = _props.id; var upward = _props.upward; var scroll = _props.scroll; var children = _props.children; var menu = _props.menu; var toggler = _props.toggler; var alignment = _props.alignment; var autocomplete = _props.autocomplete; var saveOnIcons = _props.saveOnIcons; var fallback = _props.fallback; var disabled = _props.disabled; var placeholder = _props.placeholder; var label = _props.label; var error = _props.error; var indicateInvalid = _props.indicateInvalid; var style = _props.style; var className = _props.className; var _state2 = this.state; var expanded = _state2.expanded; var list_height = _state2.list_height; var options = this.get_options(); var list_style = styles.list; // Makes the options list scrollable (only when not in `autocomplete` mode). if (this.is_scrollable() && this.state.list_height !== undefined) { list_style = (0, _extends3.default)({}, list_style, { maxHeight: list_height + 'px' }); } var overflow = scroll && options && this.overflown(); var list_items = void 0; // If a list of options is supplied as an array of `{ value, label }`, // then transform those elements to <buttons/> if (options) { list_items = options.map(function (_ref, index) { var value = _ref.value; var label = _ref.label; var icon = _ref.icon; return _this2.render_list_item({ index: index, value: value, label: label, icon: !saveOnIcons && icon, overflow: overflow }); }); } // Else, if a list of options is supplied as a set of child React elements, // then render those elements. else { list_items = _react2.default.Children.map(children, function (element, index) { if (!element) { return; } return _this2.render_list_item({ index: index, element: element }); }); } var wrapper_style = (0, _extends3.default)({}, styles.wrapper, { textAlign: alignment }); var selected = this.get_selected_option(); var markup = _react2.default.createElement( 'div', { ref: function ref(_ref4) { return _this2.select = _ref4; }, onKeyDown: this.on_key_down_in_container, style: style ? (0, _extends3.default)({}, wrapper_style, style) : wrapper_style, className: (0, _classnames2.default)('rrui__select', { 'rrui__rich': fallback, 'rrui__select--upward': upward, 'rrui__select--expanded': expanded, 'rrui__select--collapsed': !expanded, 'rrui__select--disabled': disabled }, className) }, !menu && this.render_selected_item(), label && (this.get_selected_option() || placeholder) && _react2.default.createElement( 'label', { htmlFor: id, className: (0, _classnames2.default)('rrui__input-label', { 'rrui__input-label--invalid': error && indicateInvalid }), style: styles.label }, label ), menu && _react2.default.createElement( 'div', { ref: function ref(_ref2) { return _this2.menu_toggler; }, className: 'rrui__select__toggler' }, _react2.default.cloneElement(toggler, { onClick: this.toggle }) ), _react2.default.createElement( 'ul', { ref: function ref(_ref3) { return _this2.list = _ref3; }, style: list_style, className: (0, _classnames2.default)('rrui__expandable', 'rrui__expandable--overlay', 'rrui__select__options', 'rrui__shadow', { 'rrui__expandable--expanded': expanded, 'rrui__select__options--expanded': expanded, 'rrui__select__options--left-aligned': alignment === 'left', 'rrui__select__options--right-aligned': alignment === 'right', 'rrui__select__options--simple-left-aligned': !children && alignment === 'left', 'rrui__select__options--simple-right-aligned': !children && alignment === 'right', // CSS selector performance optimization 'rrui__select__options--upward': upward, 'rrui__select__options--downward': !upward }) }, list_items ), fallback && !this.state.javascript && this.render_static(), error && indicateInvalid && _react2.default.createElement( 'div', { className: 'rrui__input-error' }, error ) ); return markup; } }, { key: 'render_list_item', value: function render_list_item(_ref5) // , first, last { var _this3 = this; var index = _ref5.index; var element = _ref5.element; var value = _ref5.value; var label = _ref5.label; var icon = _ref5.icon; var overflow = _ref5.overflow; var _props2 = this.props; var disabled = _props2.disabled; var menu = _props2.menu; var scrollbarPadding = _props2.scrollbarPadding; var _state3 = this.state; var focused_option_value = _state3.focused_option_value; var expanded = _state3.expanded; // If a list of options is supplied as a set of child React elements, // then extract values from their props. if (element) { value = element.props.value; } var is_focused = !menu && value === focused_option_value; var item_style = void 0; // on overflow the vertical scrollbar will take up space // reducing padding-right and the only way to fix that // is to add additional padding-right // // a hack to restore padding-right taken up by a vertical scrollbar if (overflow && scrollbarPadding) { item_style = { marginRight: (0, _dom.get_scrollbar_width)() + 'px' }; } var button = void 0; // If a list of options is supplied as a set of child React elements, // then enhance those elements with extra props. if (element) { (function () { var extra_props = { style: item_style ? (0, _extends3.default)({}, item_style, element.props.style) : element.props.style, className: (0, _classnames2.default)('rrui__select__option', { 'rrui__select__option--focused': is_focused }, element.props.className) }; var onClick = element.props.onClick; extra_props.onClick = function (event) { if (menu) { _this3.toggle(); } else { _this3.item_clicked(value, event); } if (onClick) { onClick(event); } }; button = _react2.default.cloneElement(element, extra_props); })(); } // Else, if a list of options is supplied as an array of `{ value, label }`, // then transform those options to <buttons/> else { button = _react2.default.createElement( 'button', { type: 'button', onClick: function onClick(event) { return _this3.item_clicked(value, event); }, disabled: disabled, tabIndex: '-1', className: (0, _classnames2.default)('rrui__select__option', { 'rrui__select__option--focused': is_focused, // CSS selector performance optimization 'rrui__select__option--disabled': disabled }), style: item_style }, icon && _react2.default.cloneElement(icon, { className: (0, _classnames2.default)(icon.props.className, 'rrui__select__option-icon') }), label ); } var markup = _react2.default.createElement( 'li', { key: get_option_key(value), ref: function ref(_ref6) { return _this3.options[get_option_key(value)] = _ref6; }, className: (0, _classnames2.default)('rrui__expandable__content', 'rrui__select__options-list-item', { 'rrui__select__separator-option': element && element.type === Select.Separator, 'rrui__expandable__content--expanded': expanded, // CSS selector performance optimization 'rrui__select__options-list-item--expanded': expanded }) }, button ); return markup; } }, { key: 'render_selected_item', value: function render_selected_item() { var _this4 = this; var _props3 = this.props; var children = _props3.children; var value = _props3.value; var placeholder = _props3.placeholder; var label = _props3.label; var disabled = _props3.disabled; var autocomplete = _props3.autocomplete; var concise = _props3.concise; var _state4 = this.state; var expanded = _state4.expanded; var autocomplete_width = _state4.autocomplete_width; var autocomplete_input_value = _state4.autocomplete_input_value; var selected = this.get_selected_option(); var selected_label = this.get_selected_option_label(); var selected_text = selected ? selected_label : placeholder || label; var style = styles.selected; if (autocomplete && expanded) { // style = { ...style, width: autocomplete_width + 'px' } var _markup = _react2.default.createElement('input', { type: 'text', ref: function ref(_ref7) { return _this4.autocomplete = _ref7; }, placeholder: selected_text, value: autocomplete_input_value, onChange: this.on_autocomplete_input_change, onKeyDown: this.on_key_down, style: style, className: (0, _classnames2.default)('rrui__input', 'rrui__select__selected', 'rrui__select__selected--autocomplete', { 'rrui__select__selected--nothing': !selected_label, // CSS selector performance optimization 'rrui__select__selected--expanded': expanded, 'rrui__select__selected--disabled': disabled }) }); return _markup; } var markup = _react2.default.createElement( 'button', { ref: function ref(_ref8) { return _this4.selected = _ref8; }, type: 'button', disabled: disabled, onClick: this.toggle, onKeyDown: this.on_key_down, style: style, className: (0, _classnames2.default)('rrui__input', 'rrui__select__selected', { 'rrui__select__selected--nothing': !selected_label }) }, _react2.default.createElement( 'div', { style: styles.selected_flex_wrapper, className: 'rrui__select__selected-content' }, _react2.default.createElement( 'div', { style: styles.selected_label, className: 'rrui__select__selected-label' }, concise && selected && selected.icon ? _react2.default.cloneElement(selected.icon, { title: selected_label }) : selected_text ), _react2.default.createElement('div', { className: (0, _classnames2.default)('rrui__select__arrow', { // CSS selector performance optimization 'rrui__select__arrow--expanded': expanded }), style: styles.arrow }) ) ); return markup; } // supports disabled javascript }, { key: 'render_static', value: function render_static() { var _props4 = this.props; var id = _props4.id; var name = _props4.name; var value = _props4.value; var label = _props4.label; var disabled = _props4.disabled; var options = _props4.options; var menu = _props4.menu; var toggler = _props4.toggler; var style = _props4.style; var className = _props4.className; var children = _props4.children; if (menu) { return _react2.default.createElement( 'div', { className: 'rrui__rich__fallback' }, toggler ); } var markup = _react2.default.createElement( 'div', { className: 'rrui__rich__fallback' }, _react2.default.createElement( 'select', { id: id, name: name, value: value === null ? undefined : value, disabled: disabled, onChange: function onChange(event) {}, style: style ? (0, _extends3.default)({}, style, { width: 'auto' }) : { width: 'auto' }, className: className }, options ? options.map(function (item, i) { return _react2.default.createElement( 'option', { className: 'rrui__select__option', key: i + ' ' + item.value, value: item.value }, item.label ); }) : _react2.default.Children.map(children, function (child) { if (!child) { return; } return _react2.default.createElement( 'option', { className: 'rrui__select__option', key: child.props.value, value: child.props.value }, child.props.label ); }) ) ); return markup; } }, { key: 'get_selected_option', value: function get_selected_option() { var value = this.props.value; return this.get_option(value); } }, { key: 'get_option', value: function get_option(value) { var _props5 = this.props; var options = _props5.options; var children = _props5.children; if (options) { return options.filter(function (x) { return x.value === value; })[0]; } var option = void 0; _react2.default.Children.forEach(children, function (child) { if (child.props.value === value) { option = child; } }); return option; } }, { key: 'get_option_index', value: function get_option_index(option) { var _props6 = this.props; var options = _props6.options; var children = _props6.children; if (options) { return options.indexOf(option); } var option_index = void 0; _react2.default.Children.forEach(children, function (child, index) { if (child.props.value === option.value) { option_index = index; } }); return option_index; } }, { key: 'get_selected_option_label', value: function get_selected_option_label() { var options = this.props.options; var selected = this.get_selected_option(); if (!selected) { return; } if (options) { return selected.label; } return selected.props.label; } }, { key: 'overflown', value: function overflown() { var _props7 = this.props; var options = _props7.options; var maxItems = _props7.maxItems; return options.length > maxItems; } }, { key: 'scrollable_list_height', value: function scrollable_list_height() { var state = arguments.length <= 0 || arguments[0] === undefined ? this.state : arguments[0]; var maxItems = this.props.maxItems; // (Adding vertical padding so that it shows these `maxItems` options fully) return (state.height - 2 * state.vertical_padding) * (maxItems / this.get_options().length) + state.vertical_padding; } }, { key: 'should_animate', value: function should_animate() { return true; // return this.props.options.length >= this.props.transition_item_count_min } }, { key: 'toggle', value: function toggle(event) { var _this5 = this; var toggle_options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; if (event) { // Don't navigate away when clicking links event.preventDefault(); // Not discarding the click event because // other expanded selects may be listening to it. // // Discard the click event so that it won't reach `document` click listener // event.stopPropagation() // doesn't work // event.nativeEvent.stopImmediatePropagation() } var _props8 = this.props; var disabled = _props8.disabled; var autocomplete = _props8.autocomplete; var options = _props8.options; var value = _props8.value; var focusUponSelection = _props8.focusUponSelection; var onToggle = _props8.onToggle; if (disabled) { return; } var expanded = this.state.expanded; if (!expanded && autocomplete) { var autocomplete_value = this.get_selected_option_label() || ''; this.setState({ autocomplete_input_value: autocomplete_value, matching_options: this.get_matching_options(options, autocomplete_value) }); // if (!this.state.autocomplete_width) // { // this.setState({ autocomplete_width: this.get_widest_label_width() }) // } } // Deferring expanding the select upon click // because document.onClick should finish first, // otherwise `event.target` may be detached from the DOM // and it would immediately toggle back to collapsed state. setTimeout(function () { _this5.setState({ expanded: !expanded }); if (!expanded && options) { // Focus either the selected option // or the first option in the list. var focused_option_value = value || options[0].value; _this5.setState({ focused_option_value: focused_option_value }); // Scroll down to the focused option _this5.scroll_to(focused_option_value); } // If it's autocomplete, then focus <input/> field // upon toggling the select component. if (autocomplete && !toggle_options.dont_focus_after_toggle) { if (!expanded || expanded && focusUponSelection) { setTimeout(function () { // Focus the toggler if (expanded) { _this5.selected.focus(); } else { _this5.autocomplete.focus(); } }, 0); } } if (onToggle) { onToggle(!expanded); } if (toggle_options.callback) { toggle_options.callback(); } }, 0); } }, { key: 'item_clicked', value: function item_clicked(value, event) { if (event) { event.preventDefault(); } var _props9 = this.props; var disabled = _props9.disabled; var onChange = _props9.onChange; var autocomplete = _props9.autocomplete; var focusUponSelection = _props9.focusUponSelection; if (disabled) { return; } // Focus the toggler if (focusUponSelection) { if (autocomplete) { this.autocomplete.focus(); } else { this.selected.focus(); } } this.toggle(undefined, { callback: function callback() { return onChange(value); } }); } }, { key: 'document_clicked', value: function document_clicked(event) { var autocomplete = _reactDom2.default.findDOMNode(this.autocomplete); var selected_option = _reactDom2.default.findDOMNode(this.selected); var options_list = _reactDom2.default.findDOMNode(this.list); // Don't close the select if its expander button has been clicked, // or if autocomplete has been clicked, // or if an option was selected from the list. if (options_list.contains(event.target) || autocomplete && autocomplete.contains(event.target) || selected_option && selected_option.contains(event.target)) { return; } this.setState({ expanded: false }); var onToggle = this.props.onToggle; if (onToggle) { onToggle(false); } } // Would have used `onBlur()` handler here // with `container.contains(event.relatedTarget)`, // but it has an IE bug in React. // https://github.com/facebook/react/issues/3751 // // Therefore, using the hacky `document.onClick` handlers // and this `onKeyDown` Tab handler // until `event.relatedTarget` support is consistent in React. // }, { key: 'on_key_down_in_container', value: function on_key_down_in_container(event) { if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) { return; } var expanded = this.state.expanded; switch (event.keyCode) { // Toggle on Tab out case 9: if (expanded) { this.toggle(undefined, { dont_focus_after_toggle: true }); var onTabOut = this.props.onTabOut; if (onTabOut) { onTabOut(event); } } return; } } }, { key: 'on_key_down', value: function on_key_down(event) { var _this6 = this; if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) { return; } var _props10 = this.props; var options = _props10.options; var value = _props10.value; var autocomplete = _props10.autocomplete; var _state5 = this.state; var expanded = _state5.expanded; var focused_option_value = _state5.focused_option_value; // Maybe add support for `children` arrow navigation in future if (options) { switch (event.keyCode) { // Select the previous option (if present) on up arrow case 38: event.preventDefault(); var previous = this.previous_focusable_option(); if (previous) { this.show_option(previous.value, 'top'); return this.setState({ focused_option_value: previous.value }); } return; // Select the next option (if present) on down arrow case 40: event.preventDefault(); var next = this.next_focusable_option(); if (next) { this.show_option(next.value, 'bottom'); return this.setState({ focused_option_value: next.value }); } return; // Collapse on Escape // // Maybe add this kind of support for "Escape" key in some future: // hiding the item list, cancelling current item selection process // and restoring the selection present before the item list was toggled. // case 27: // Collapse the list if it's expanded if (this.state.expanded) { this.toggle(); // Restore focus when the list is collapsed setTimeout(function () { _this6.selected.focus(); }, 0); } return; // on Enter case 13: // Choose the focused item on Enter if (expanded) { event.preventDefault(); // If an item is focused // (which may not be a case // when autocomplete is matching no items) // (still for non-autocomplete select // it is valid to have a default option) if (this.get_options() && this.get_options().length > 0) { // Choose the focused item this.item_clicked(focused_option_value); // And collapse the select this.toggle(); } } // Else it should have just submitted the form on Enter, // but it wouldn't because the select element activator is a <button/> // therefore hitting Enter while being focused on it just pushes that button. // So submit the enclosing form manually. else { if ((0, _dom.submit_parent_form)(_reactDom2.default.findDOMNode(this.select))) { event.preventDefault(); } } return; // on Spacebar case 32: // Choose the focused item on Enter if (expanded) { // only if it it's an `options` select // and also if it's not an autocomplete if (this.get_options() && !autocomplete) { event.preventDefault(); // `focused_option_value` could be non-existent // in case of `autocomplete`, but since // we're explicitly not handling autocomplete here // it is valid to select any options including the default ones. this.item_clicked(focused_option_value); this.toggle(); } } // Expand the select otherwise else { event.preventDefault(); this.toggle(); } return; } } } }, { key: 'get_options', value: function get_options() { var _props11 = this.props; var autocomplete = _props11.autocomplete; var autocompleteShowAll = _props11.autocompleteShowAll; var maxItems = _props11.maxItems; var options = _props11.options; var matching_options = this.state.matching_options; if (!autocomplete) { return options; } if (autocompleteShowAll) { return matching_options; } return matching_options.slice(0, maxItems); } // Get the previous option (relative to the currently focused option) }, { key: 'previous_focusable_option', value: function previous_focusable_option() { var options = this.get_options(); var focused_option_value = this.state.focused_option_value; var i = 0; while (i < options.length) { if (options[i].value === focused_option_value) { if (i - 1 >= 0) { return options[i - 1]; } } i++; } } // Get the next option (relative to the currently focused option) }, { key: 'next_focusable_option', value: function next_focusable_option() { var options = this.get_options(); var focused_option_value = this.state.focused_option_value; var i = 0; while (i < options.length) { if (options[i].value === focused_option_value) { if (i + 1 < options.length) { return options[i + 1]; } } i++; } } // Scrolls to an option having the value }, { key: 'scroll_to', value: function scroll_to(value) { var option_element = _reactDom2.default.findDOMNode(this.options[get_option_key(value)]); // If this option isn't even shown // (e.g. autocomplete) // then don't scroll to it because there's nothing to scroll to. if (!option_element) { return; } _reactDom2.default.findDOMNode(this.list).scrollTop = option_element.offsetTop; } // Fully shows an option having the `value` (scrolls to it if neccessary) }, { key: 'show_option', value: function show_option(value, gravity) { var option_element = _reactDom2.default.findDOMNode(this.options[get_option_key(value)]); var list = _reactDom2.default.findDOMNode(this.list); switch (gravity) { case 'top': if (option_element.offsetTop < list.scrollTop) { list.scrollTop = option_element.offsetTop; } return; case 'bottom': if (option_element.offsetTop + option_element.offsetHeight > list.scrollTop + list.offsetHeight) { list.scrollTop = option_element.offsetTop + option_element.offsetHeight - list.offsetHeight; } return; } } // Calculates height of the expanded item list }, { key: 'calculate_height', value: function calculate_height() { var options = this.props.options; var list_dom_node = _reactDom2.default.findDOMNode(this.list); var border = parseInt(window.getComputedStyle(list_dom_node).borderTopWidth); var height = list_dom_node.scrollHeight; // + 2 * border // inner height + 2 * border var vertical_padding = parseInt(window.getComputedStyle(list_dom_node.firstChild).paddingTop); // For things like "accordeon". // // const images = list_dom_node.querySelectorAll('img') // // if (images.length > 0) // { // return this.preload_images(list_dom_node, images) // } var state = { height: height, vertical_padding: vertical_padding, border: border }; if (this.is_scrollable() && options && this.overflown()) { state.list_height = this.scrollable_list_height(state); } this.setState(state); } }, { key: 'is_scrollable', value: function is_scrollable() { var _props12 = this.props; var menu = _props12.menu; var autocomplete = _props12.autocomplete; var autocompleteShowAll = _props12.autocompleteShowAll; var scroll = _props12.scroll; return !menu && (autocomplete && autocompleteShowAll || !autocomplete) && scroll; } // This turned out not to work for `autocomplete` // because not all options are ever shown. // get_widest_label_width() // { // // <ul/> -> <li/> -> <button/> // const label = ReactDOM.findDOMNode(this.list).firstChild.firstChild // // const style = getComputedStyle(label) // // const width = parseFloat(style.width) // const side_padding = parseFloat(style.paddingLeft) // // return width - 2 * side_padding // } }, { key: 'get_matching_options', value: function get_matching_options(options, value) { // If the autocomplete value is `undefined` or empty if (!value) { return options; } value = value.toLowerCase(); return options.filter(function (_ref9) { var label = _ref9.label; var verbose = _ref9.verbose; return (verbose || label).toLowerCase().indexOf(value) >= 0; }); } }, { key: 'on_autocomplete_input_change', value: function on_autocomplete_input_change(event) { var options = this.props.options; var input = event.target.value; var matching_options = this.get_matching_options(options, input); this.setState({ autocomplete_input_value: input, matching_options: matching_options, focused_option_value: matching_options.length > 0 ? matching_options[0].value : undefined }); } // // https://github.com/daviferreira/react-sanfona/blob/master/src/AccordionItem/index.jsx#L54 // // Wait for images to load before calculating maxHeight // preload_images(node, images) // { // let images_loaded = 0 // // const image_loaded = () => // { // images_loaded++ // // if (images_loaded === images.length) // { // this.setState // ({ // height: this.props.expanded ? node.scrollHeight : 0 // }) // } // } // // for (let i = 0; i < images.length; i += 1) // { // const image = new Image() // image.src = images[i].src // image.onload = image.onerror = image_loaded // } // } }]); return Select; }(_react.PureComponent); Select.propTypes = { // A list of selectable options options: _react.PropTypes.arrayOf(_react.PropTypes.shape({ // Option value (may be `undefined`) value: value_prop_type, // Option label (may be `undefined`) label: _react2.default.PropTypes.string, // Option icon icon: _react2.default.PropTypes.node })), // HTML form input `name` attribute name: _react.PropTypes.string, // Label which is placed above the select label: _react.PropTypes.string, // Placeholder (like "Choose") placeholder: _react.PropTypes.string, // Show icon only for selected item, // and only if `concise` is `true`. saveOnIcons: _react.PropTypes.bool, // Disables this control disabled: _react.PropTypes.bool, // Selected option value value: value_prop_type, // Is called when an option is selected onChange: _react.PropTypes.func, // (exotic use case) // Falls back to a plain HTML input // when javascript is disabled (e.g. Tor) fallback: _react.PropTypes.bool.isRequired, // CSS class className: _react.PropTypes.string, // CSS style object style: _react.PropTypes.object, // If this flag is set to `true`, // and `icon` is specified for a selected option, // then the selected option will be displayed // as icon only, without the label. concise: _react.PropTypes.bool, // If set to `true`, autocompletion is available // upon expanding the options list. autocomplete: _react.PropTypes.bool, // If set to `true`, autocomple will show all // matching options instead of just `maxItems`. autocompleteShowAll: _react.PropTypes.bool, // Options list alignment ("left", "right") alignment: _react.PropTypes.oneOf(['left', 'right']), // If `menu` flag is set to `true` // then it's gonna be a dropdown menu // with `children` elements inside. menu: _react.PropTypes.bool, // If `menu` flag is set to `true` // then `toggler` is the dropdown menu button. toggler: _react.PropTypes.element, // If `scroll` is `false`, then options list // is not limited in height. // Is `true` by default (scrollable). scroll: _react.PropTypes.bool.isRequired, // If this flag is set to `true`, // then the dropdown expands itself upward. // (as opposed to the default downward) upward: _react.PropTypes.bool, // Maximum items fitting the options list height (scrollable). // In case of `autocomplete` that's the maximum number of matched items shown. // Is `6` by default. maxItems: _react.PropTypes.number.isRequired, // Is `true` by default (only when the list of options is scrollable) scrollbarPadding: _react.PropTypes.bool, focusUponSelection: _react.PropTypes.bool.isRequired, onTabOut: _react.PropTypes.func, onToggle: _react.PropTypes.func // transition_item_count_min : PropTypes.number, // transition_duration_min : PropTypes.number, // transition_duration_max : PropTypes.number }; Select.defaultProps = { alignment: 'left', scroll: true, maxItems: 6, scrollbarPadding: true, focusUponSelection: true, fallback: false }; exports.default = Select; Select.Separator = function (props) { return _react2.default.createElement('div', { className: 'rrui__select__separator', style: styles.separator }); }; var styles = (0, _reactStyling.flat)(_templateObject); // There can be an `undefined` value, // so just `{ value }` won't do here. function get_option_key(value) { return value === undefined ? '@@rrui/select/undefined' : value; } //# sourceMappingURL=select.js.map