wix-style-react
Version:
wix-style-react
577 lines (492 loc) • 21.6 kB
JavaScript
'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;