UNPKG

wix-style-react

Version:
577 lines (492 loc) • 21.6 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.DIVIDER_OPTION_VALUE = undefined; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; exports.optionValidator = optionValidator; var _DropdownLayout = require('./DropdownLayout.scss'); var _DropdownLayout2 = _interopRequireDefault(_DropdownLayout); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _WixComponent2 = require('../BaseComponents/WixComponent'); var _WixComponent3 = _interopRequireDefault(_WixComponent2); var _scrollIntoView = require('../utils/scrollIntoView'); var _scrollIntoView2 = _interopRequireDefault(_scrollIntoView); var _InfiniteScroll = require('../utils/InfiniteScroll'); var _InfiniteScroll2 = _interopRequireDefault(_InfiniteScroll); var _Loader = require('../Loader/Loader'); var _Loader2 = _interopRequireDefault(_Loader); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var modulu = function modulu(n, m) { var remain = n % m; return remain >= 0 ? remain : remain + m; }; var NOT_HOVERED_INDEX = -1; var DIVIDER_OPTION_VALUE = exports.DIVIDER_OPTION_VALUE = '-'; var DropdownLayout = function (_WixComponent) { _inherits(DropdownLayout, _WixComponent); function DropdownLayout(props) { _classCallCheck(this, DropdownLayout); var _this = _possibleConstructorReturn(this, (DropdownLayout.__proto__ || Object.getPrototypeOf(DropdownLayout)).call(this, props)); _this._wrapWithInfiniteScroll = function (scrollableElement) { return _react2.default.createElement( _InfiniteScroll2.default, { useWindow: true, scrollElement: _this.options, loadMore: _this.props.loadMore, hasMore: _this.props.hasMore, loader: _react2.default.createElement( 'div', { className: _DropdownLayout2.default.loader }, _react2.default.createElement(_Loader2.default, { dataHook: 'dropdownLayout-loader', size: 'small' }) ) }, scrollableElement ); }; _this.state = { hovered: NOT_HOVERED_INDEX, selectedId: props.selectedId }; _this._onSelect = _this._onSelect.bind(_this); _this._onMouseLeave = _this._onMouseLeave.bind(_this); _this._onMouseEnter = _this._onMouseEnter.bind(_this); _this._onKeyDown = _this._onKeyDown.bind(_this); _this._onClose = _this._onClose.bind(_this); _this.onClickOutside = _this.onClickOutside.bind(_this); return _this; } _createClass(DropdownLayout, [{ key: '_isControlled', value: function _isControlled() { return typeof this.props.selectedId !== 'undefined' && typeof this.props.onSelect !== 'undefined'; } }, { key: 'componentDidMount', value: function componentDidMount() { _get(DropdownLayout.prototype.__proto__ || Object.getPrototypeOf(DropdownLayout.prototype), 'componentDidMount', this).call(this); if (this.props.focusOnSelectedOption) { this._focusOnSelectedOption(); } } }, { key: '_focusOnSelectedOption', value: function _focusOnSelectedOption() { if (this.selectedOption) { this.options.scrollTop = Math.max(this.selectedOption.offsetTop - this.selectedOption.offsetHeight, 0); } } }, { key: '_setSelectedOptionNode', value: function _setSelectedOptionNode(optionNode, option) { if (option.id === this.state.selectedId) { this.selectedOption = optionNode; } } }, { key: 'onClickOutside', value: function onClickOutside(event) { var _props = this.props, visible = _props.visible, onClickOutside = _props.onClickOutside; if (visible && onClickOutside) { onClickOutside(event); } } }, { key: '_onSelect', value: function _onSelect(index) { var _props2 = this.props, options = _props2.options, onSelect = _props2.onSelect; var chosenOption = options[index]; var newState = { hovered: NOT_HOVERED_INDEX }; if (chosenOption) { var sameOptionWasPicked = chosenOption.id === this.state.selectedId; if (onSelect) { onSelect(chosenOption, sameOptionWasPicked); } } if (!this._isControlled()) { newState.selectedId = chosenOption && chosenOption.id; } this.setState(newState); return !!onSelect && chosenOption; } }, { key: '_onMouseEnter', value: function _onMouseEnter(index) { if (this._isSelectableOption(this.props.options[index])) { this.setState({ hovered: index }); } } }, { key: '_onMouseLeave', value: function _onMouseLeave() { this.setState({ hovered: NOT_HOVERED_INDEX }); } }, { key: '_getMarkedIndex', value: function _getMarkedIndex() { var _this2 = this; var options = this.props.options; var useHoverIndex = this.state.hovered > NOT_HOVERED_INDEX; var useSelectedIdIndex = typeof this.state.selectedId !== 'undefined'; var markedIndex = void 0; if (useHoverIndex) { markedIndex = this.state.hovered; } else if (useSelectedIdIndex) { markedIndex = options.findIndex(function (option) { return option.id === _this2.state.selectedId; }); } else { markedIndex = NOT_HOVERED_INDEX; } return markedIndex; } }, { key: '_markNextStep', value: function _markNextStep(step) { var options = this.props.options; if (!options.some(this._isSelectableOption)) { return; } var markedIndex = this._getMarkedIndex(); do { markedIndex = Math.abs(modulu(Math.max(markedIndex + step, -1), options.length)); } while (!this._isSelectableOption(options[markedIndex])); this.setState({ hovered: markedIndex }); var menuElement = this.options; var hoveredElement = this.options.childNodes[markedIndex]; (0, _scrollIntoView2.default)(menuElement, hoveredElement); } /** * Handle keydown events for the DropdownLayout, mostly for accessibility * * @param {SyntheticEvent} event - The keydown event triggered by React * @returns {boolean} - Whether the event was handled by the component */ }, { key: '_onKeyDown', value: function _onKeyDown(event) { if (!this.props.visible || this.props.isComposing) { return false; } switch (event.key) { case 'ArrowDown': { this._markNextStep(1); break; } case 'ArrowUp': { this._markNextStep(-1); break; } case ' ': case 'Spacebar': case 'Enter': { if (!this._onSelect(this.state.hovered)) { return false; } break; } case 'Tab': { if (this.props.closeOnSelect) { return this._onSelect(this.state.hovered); } else { event.preventDefault(); if (!this._onSelect(this.state.hovered)) { return false; } } break; } case 'Escape': { this._onClose(); break; } default: { return false; } } event.preventDefault(); event.stopPropagation(); return true; } }, { key: '_onClose', value: function _onClose() { this.setState({ hovered: NOT_HOVERED_INDEX }); if (this.props.onClose) { this.props.onClose(); } } }, { key: '_renderNode', value: function _renderNode(node) { return node ? _react2.default.createElement( 'div', { className: _DropdownLayout2.default.node }, node ) : null; } }, { key: 'render', value: function render() { var _this3 = this, _classNames; var _props3 = this.props, options = _props3.options, visible = _props3.visible, dropDirectionUp = _props3.dropDirectionUp, tabIndex = _props3.tabIndex, onMouseEnter = _props3.onMouseEnter, onMouseLeave = _props3.onMouseLeave, fixedHeader = _props3.fixedHeader, withArrow = _props3.withArrow, fixedFooter = _props3.fixedFooter, inContainer = _props3.inContainer; var renderedOptions = options.map(function (option, idx) { return _this3._renderOption({ option: option, idx: idx }); }); var contentContainerClassName = (0, _classnames2.default)((_classNames = {}, _defineProperty(_classNames, _DropdownLayout2.default.contentContainer, true), _defineProperty(_classNames, _DropdownLayout2.default.shown, visible), _defineProperty(_classNames, _DropdownLayout2.default.up, dropDirectionUp), _defineProperty(_classNames, _DropdownLayout2.default.down, !dropDirectionUp), _defineProperty(_classNames, _DropdownLayout2.default.withArrow, withArrow), _defineProperty(_classNames, _DropdownLayout2.default.containerStyles, !inContainer), _classNames)); return _react2.default.createElement( 'div', { tabIndex: tabIndex, className: (0, _classnames2.default)(_DropdownLayout2.default.wrapper, _DropdownLayout2.default['theme-' + this.props.theme]), onKeyDown: this._onKeyDown, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave }, _react2.default.createElement( 'div', { className: contentContainerClassName, style: { maxHeight: this.props.maxHeightPixels + 'px', minWidth: this.props.minWidthPixels ? this.props.minWidthPixels + 'px' : undefined } }, this._renderNode(fixedHeader), _react2.default.createElement( 'div', { className: _DropdownLayout2.default.options, style: { maxHeight: this.props.maxHeightPixels - 35 + 'px' }, ref: function ref(_options) { return _this3.options = _options; }, 'data-hook': 'dropdown-layout-options' }, this.props.infiniteScroll ? this._wrapWithInfiniteScroll(renderedOptions) : renderedOptions ), this._renderNode(fixedFooter) ), this._renderTopArrow() ); } }, { key: '_renderOption', value: function _renderOption(_ref) { var option = _ref.option, idx = _ref.idx; var value = option.value, id = option.id, disabled = option.disabled, title = option.title, overrideStyle = option.overrideStyle, linkTo = option.linkTo; if (value === DIVIDER_OPTION_VALUE) { return this._renderDivider(idx, 'dropdown-divider-' + (id || idx)); } var content = this._renderItem({ option: option, idx: idx, selected: id === this.state.selectedId, hovered: idx === this.state.hovered, disabled: disabled || title, title: title, overrideStyle: overrideStyle, dataHook: 'dropdown-item-' + id }); return linkTo ? _react2.default.createElement( 'a', { key: idx, 'data-hook': 'link-item', href: linkTo }, content ) : content; } }, { key: '_renderDivider', value: function _renderDivider(idx, dataHook) { return _react2.default.createElement('div', { key: idx, className: _DropdownLayout2.default.divider, 'data-hook': dataHook }); } }, { key: '_renderItem', value: function _renderItem(_ref2) { var _classNames2, _this4 = this; var option = _ref2.option, idx = _ref2.idx, selected = _ref2.selected, hovered = _ref2.hovered, disabled = _ref2.disabled, title = _ref2.title, overrideStyle = _ref2.overrideStyle, dataHook = _ref2.dataHook; var _props4 = this.props, itemHeight = _props4.itemHeight, selectedHighlight = _props4.selectedHighlight; var optionClassName = (0, _classnames2.default)((_classNames2 = {}, _defineProperty(_classNames2, _DropdownLayout2.default.option, !overrideStyle), _defineProperty(_classNames2, _DropdownLayout2.default.selected, selected && !overrideStyle && selectedHighlight), _defineProperty(_classNames2, 'wixstylereactSelected', selected && overrideStyle), _defineProperty(_classNames2, _DropdownLayout2.default.hovered, hovered && !overrideStyle), _defineProperty(_classNames2, 'wixstylereactHovered', hovered && overrideStyle), _defineProperty(_classNames2, _DropdownLayout2.default.disabled, disabled), _defineProperty(_classNames2, _DropdownLayout2.default.title, title), _defineProperty(_classNames2, _DropdownLayout2.default.smallHeight, itemHeight === 'small'), _defineProperty(_classNames2, _DropdownLayout2.default.bigHeight, itemHeight === 'big'), _classNames2)); return _react2.default.createElement( 'div', { className: optionClassName, ref: function ref(node) { return _this4._setSelectedOptionNode(node, option); }, onMouseDown: !disabled ? function () { return _this4._onSelect(idx); } : null, key: idx, onMouseEnter: function onMouseEnter() { return _this4._onMouseEnter(idx); }, onMouseLeave: this._onMouseLeave, 'data-hook': dataHook }, typeof option.value === 'function' ? option.value({ selected: selected }) : option.value ); } }, { key: '_renderTopArrow', value: function _renderTopArrow() { var _classNames3; var _props5 = this.props, withArrow = _props5.withArrow, visible = _props5.visible, dropDirectionUp = _props5.dropDirectionUp; var arrowClassName = (0, _classnames2.default)((_classNames3 = {}, _defineProperty(_classNames3, _DropdownLayout2.default.arrow, true), _defineProperty(_classNames3, _DropdownLayout2.default.up, dropDirectionUp), _defineProperty(_classNames3, _DropdownLayout2.default.down, !dropDirectionUp), _classNames3)); return withArrow && visible ? _react2.default.createElement('div', { className: arrowClassName }) : null; } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { var _this5 = this; if (this.props.visible !== nextProps.visible) { this.setState({ hovered: NOT_HOVERED_INDEX }); } if (this.props.selectedId !== nextProps.selectedId) { this.setState({ selectedId: nextProps.selectedId }); } // make sure the same item is hovered if options changed if (this.state.hovered !== NOT_HOVERED_INDEX && (!nextProps.options[this.state.hovered] || this.props.options[this.state.hovered].id !== nextProps.options[this.state.hovered].id)) { this.setState({ hovered: this.findIndex(nextProps.options, function (item) { return item.id === _this5.props.options[_this5.state.hovered].id; }) }); } } }, { key: 'findIndex', value: function findIndex(arr, predicate) { return (Array.isArray(arr) ? arr : []).findIndex(predicate); } }, { key: '_isSelectableOption', value: function _isSelectableOption(option) { return option && option.value !== '-' && !option.disabled && !option.title; } }]); return DropdownLayout; }(_WixComponent3.default); var optionPropTypes = _propTypes2.default.shape({ id: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]).isRequired, value: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.string, _propTypes2.default.func]).isRequired, disabled: _propTypes2.default.bool, overrideStyle: _propTypes2.default.bool }); function optionValidator(props, propName, componentName) { var option = props[propName]; // Notice: We don't use Proptypes.oneOf() to check for either option OR divider, because then the failure message would be less informative. if ((typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object' && option.value === DIVIDER_OPTION_VALUE) { return; } var optionError = _propTypes2.default.checkPropTypes({ option: optionPropTypes }, { option: option }, 'option', componentName); if (optionError) { return optionError; } if (option.id && option.id.toString().trim().length === 0) { return new Error('Warning: Failed option type: The option `option.id` should be non-empty after trimming in `DropdownLayout`.'); } if (option.value && option.value.toString().trim().length === 0) { return new Error('Warning: Failed option type: The option `option.value` should be non-empty after trimming in `DropdownLayout`.'); } } DropdownLayout.propTypes = { dropDirectionUp: _propTypes2.default.bool, focusOnSelectedOption: _propTypes2.default.bool, onClose: _propTypes2.default.func, /** Callback function called whenever the user selects a different option in the list */ onSelect: _propTypes2.default.func, visible: _propTypes2.default.bool, /** Array of objects. Objects must have an Id and can can include value and node. If value is '-', a divider will be rendered instead (dividers do not require and id). */ options: _propTypes2.default.arrayOf(optionValidator), /** The id of the selected option in the list */ selectedId: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), tabIndex: _propTypes2.default.number, theme: _propTypes2.default.string, onClickOutside: _propTypes2.default.func, /** A fixed header to the list */ fixedHeader: _propTypes2.default.node, /** A fixed footer to the list */ fixedFooter: _propTypes2.default.node, maxHeightPixels: _propTypes2.default.number, minWidthPixels: _propTypes2.default.number, withArrow: _propTypes2.default.bool, closeOnSelect: _propTypes2.default.bool, onMouseEnter: _propTypes2.default.func, onMouseLeave: _propTypes2.default.func, itemHeight: _propTypes2.default.oneOf(['small', 'big']), selectedHighlight: _propTypes2.default.bool, inContainer: _propTypes2.default.bool, infiniteScroll: _propTypes2.default.bool, loadMore: _propTypes2.default.func, hasMore: _propTypes2.default.bool }; DropdownLayout.defaultProps = { options: [], tabIndex: 0, maxHeightPixels: 260, closeOnSelect: true, itemHeight: 'small', selectedHighlight: true, inContainer: false, infiniteScroll: false, loadMore: null, hasMore: false }; DropdownLayout.NONE_SELECTED_ID = NOT_HOVERED_INDEX; exports.default = DropdownLayout;