@workflo/components
Version:
373 lines (309 loc) • 15.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
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 _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _sectionIterator = require('section-iterator');
var _sectionIterator2 = _interopRequireDefault(_sectionIterator);
var _reactThemeable = require('react-themeable');
var _reactThemeable2 = _interopRequireDefault(_reactThemeable);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: 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; } // 100 line dependency
function noop() {}
var Autowhatever = function (_Component) {
_inherits(Autowhatever, _Component);
function Autowhatever(props) {
_classCallCheck(this, Autowhatever);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Autowhatever).call(this, props));
_this.onKeyDown = _this.onKeyDown.bind(_this);
return _this;
} // Styles. See: https://github.com/markdalgleish/react-themeable
_createClass(Autowhatever, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.ensureFocusedSuggestionIsVisible();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this.ensureFocusedSuggestionIsVisible();
}
}, {
key: 'getItemId',
value: function getItemId(sectionIndex, itemIndex) {
if (itemIndex === null) {
return null;
}
var id = this.props.id;
var section = sectionIndex === null ? '' : 'section-' + sectionIndex;
return 'react-autowhatever-' + id + '-' + section + '-item-' + itemIndex;
}
}, {
key: 'getItemsContainerId',
value: function getItemsContainerId() {
var id = this.props.id;
return 'react-whatever-' + id;
}
}, {
key: 'renderItemsList',
value: function renderItemsList(theme, items, sectionIndex) {
var _this2 = this;
var _props = this.props;
var id = _props.id;
var renderItem = _props.renderItem;
var focusedSectionIndex = _props.focusedSectionIndex;
var focusedItemIndex = _props.focusedItemIndex;
var isItemPropsFunction = typeof this.props.itemProps === 'function';
return items.map(function (item, itemIndex) {
var itemPropsObj = isItemPropsFunction ? _this2.props.itemProps({ sectionIndex: sectionIndex, itemIndex: itemIndex }) : _this2.props.itemProps;
var onMouseEnter = itemPropsObj.onMouseEnter;
var onMouseLeave = itemPropsObj.onMouseLeave;
var onMouseDown = itemPropsObj.onMouseDown;
var onClick = itemPropsObj.onClick;
var onMouseEnterFn = onMouseEnter ? function (event) {
return onMouseEnter(event, { sectionIndex: sectionIndex, itemIndex: itemIndex });
} : noop;
var onMouseLeaveFn = onMouseLeave ? function (event) {
return onMouseLeave(event, { sectionIndex: sectionIndex, itemIndex: itemIndex });
} : noop;
var onMouseDownFn = onMouseDown ? function (event) {
return onMouseDown(event, { sectionIndex: sectionIndex, itemIndex: itemIndex });
} : noop;
var onClickFn = onClick ? function (event) {
return onClick(event, { sectionIndex: sectionIndex, itemIndex: itemIndex });
} : noop;
var sectionPrefix = sectionIndex === null ? '' : 'section-' + sectionIndex + '-';
var itemKey = 'react-autowhatever-' + id + '-' + sectionPrefix + 'item-' + itemIndex;
var isFocused = sectionIndex === focusedSectionIndex && itemIndex === focusedItemIndex;
var itemProps = _extends({
id: _this2.getItemId(sectionIndex, itemIndex),
ref: isFocused ? 'focusedItem' : null,
role: 'option'
}, theme(itemKey, 'item', isFocused && 'itemFocused'), itemPropsObj, {
onMouseEnter: onMouseEnterFn,
onMouseLeave: onMouseLeaveFn,
onMouseDown: onMouseDownFn,
onClick: onClickFn
});
return _react2.default.createElement(
'li',
itemProps,
renderItem(item)
);
});
}
}, {
key: 'renderSections',
value: function renderSections(theme) {
var _this3 = this;
var _props2 = this.props;
var items = _props2.items;
var getSectionItems = _props2.getSectionItems;
var sectionItemsArray = items.map(function (section) {
return getSectionItems(section);
});
var noItemsExist = sectionItemsArray.every(function (sectionItems) {
return sectionItems.length === 0;
});
if (noItemsExist) {
return null;
}
var _props3 = this.props;
var id = _props3.id;
var shouldRenderSection = _props3.shouldRenderSection;
var renderSectionTitle = _props3.renderSectionTitle;
return _react2.default.createElement(
'div',
_extends({ id: this.getItemsContainerId(),
ref: 'itemsContainer',
role: 'listbox'
}, theme('react-autowhatever-' + id + '-items-container', 'itemsContainer')),
items.map(function (section, sectionIndex) {
if (!shouldRenderSection(section)) {
return null;
}
var sectionTitle = renderSectionTitle(section);
return _react2.default.createElement(
'div',
theme('react-autowhatever-' + id + '-section-' + sectionIndex + '-container', 'sectionContainer'),
sectionTitle && _react2.default.createElement(
'div',
theme('react-autowhatever-' + id + '-section-' + sectionIndex + '-title', 'sectionTitle'),
sectionTitle
),
_react2.default.createElement(
'ul',
theme('react-autowhatever-' + id + '-section-' + sectionIndex + '-items-container', 'sectionItemsContainer'),
_this3.renderItemsList(theme, sectionItemsArray[sectionIndex], sectionIndex)
)
);
})
);
}
}, {
key: 'renderItems',
value: function renderItems(theme) {
var items = this.props.items;
if (items.length === 0) {
return null;
}
var id = this.props;
return _react2.default.createElement(
'ul',
_extends({ id: this.getItemsContainerId(),
ref: 'itemsContainer',
role: 'listbox'
}, theme('react-autowhatever-' + id + '-items-container', 'itemsContainer')),
this.renderItemsList(theme, items, null)
);
}
}, {
key: 'onKeyDown',
value: function onKeyDown(event) {
var _this4 = this;
var _props4 = this.props;
var inputProps = _props4.inputProps;
var focusedSectionIndex = _props4.focusedSectionIndex;
var focusedItemIndex = _props4.focusedItemIndex;
var onKeyDownFn = inputProps.onKeyDown; // Babel is throwing:
// "onKeyDown" is read-only
// on:
// const { onKeyDown } = inputProps
switch (event.key) {
case 'ArrowDown':
case 'ArrowUp':
{
var _ret = function () {
var _props5 = _this4.props;
var multiSection = _props5.multiSection;
var items = _props5.items;
var getSectionItems = _props5.getSectionItems;
var sectionIterator = (0, _sectionIterator2.default)({
multiSection: multiSection,
data: multiSection ? items.map(function (section) {
return getSectionItems(section).length;
}) : items.length
});
var nextPrev = event.key === 'ArrowDown' ? 'next' : 'prev';
var _sectionIterator$next = sectionIterator[nextPrev]([focusedSectionIndex, focusedItemIndex]);
var _sectionIterator$next2 = _slicedToArray(_sectionIterator$next, 2);
var newFocusedSectionIndex = _sectionIterator$next2[0];
var newFocusedItemIndex = _sectionIterator$next2[1];
onKeyDownFn(event, { newFocusedSectionIndex: newFocusedSectionIndex, newFocusedItemIndex: newFocusedItemIndex });
return 'break';
}();
if (_ret === 'break') break;
}
default:
onKeyDownFn(event, { focusedSectionIndex: focusedSectionIndex, focusedItemIndex: focusedItemIndex });
}
}
}, {
key: 'ensureFocusedSuggestionIsVisible',
value: function ensureFocusedSuggestionIsVisible() {
if (!this.refs.focusedItem) {
return;
}
var _refs = this.refs;
var focusedItem = _refs.focusedItem;
var itemsContainer = _refs.itemsContainer;
var itemOffsetRelativeToContainer = focusedItem.offsetParent === itemsContainer ? focusedItem.offsetTop : focusedItem.offsetTop - itemsContainer.offsetTop;
var scrollTop = itemsContainer.scrollTop; // Top of the visible area
if (itemOffsetRelativeToContainer < scrollTop) {
// Item is off the top of the visible area
scrollTop = itemOffsetRelativeToContainer;
} else if (itemOffsetRelativeToContainer + focusedItem.offsetHeight > scrollTop + itemsContainer.offsetHeight) {
// Item is off the bottom of the visible area
scrollTop = itemOffsetRelativeToContainer + focusedItem.offsetHeight - itemsContainer.offsetHeight;
}
if (scrollTop !== itemsContainer.scrollTop) {
itemsContainer.scrollTop = scrollTop;
}
}
}, {
key: 'render',
value: function render() {
var _props6 = this.props;
var id = _props6.id;
var multiSection = _props6.multiSection;
var focusedSectionIndex = _props6.focusedSectionIndex;
var focusedItemIndex = _props6.focusedItemIndex;
var theme = (0, _reactThemeable2.default)(this.props.theme);
var renderedItems = multiSection ? this.renderSections(theme) : this.renderItems(theme);
var isOpen = renderedItems !== null;
var ariaActivedescendant = this.getItemId(focusedSectionIndex, focusedItemIndex);
var inputProps = _extends({
type: 'text',
value: '',
autoComplete: 'off',
role: 'combobox',
ref: 'input',
'aria-autocomplete': 'list',
'aria-owns': this.getItemsContainerId(),
'aria-expanded': isOpen,
'aria-activedescendant': ariaActivedescendant
}, theme('react-autowhatever-' + id + '-input', 'input'), this.props.inputProps, {
onKeyDown: this.props.inputProps.onKeyDown && this.onKeyDown
});
return _react2.default.createElement(
'div',
theme('react-autowhatever-' + id + '-container', 'container', isOpen && 'containerOpen'),
_react2.default.createElement('input', inputProps),
renderedItems
);
}
}]);
return Autowhatever;
}(_react.Component);
Autowhatever.propTypes = {
id: _react.PropTypes.string, // Used in aria-* attributes. If multiple Autowhatever's are rendered on a page, they must have unique ids.
multiSection: _react.PropTypes.bool, // Indicates whether a multi section layout should be rendered.
items: _react.PropTypes.array.isRequired, // Array of items or sections to render.
renderItem: _react.PropTypes.func, // This function renders a single item.
shouldRenderSection: _react.PropTypes.func, // This function gets a section and returns whether it should be rendered, or not.
renderSectionTitle: _react.PropTypes.func, // This function gets a section and renders its title.
getSectionItems: _react.PropTypes.func, // This function gets a section and returns its items, which will be passed into `renderItem` for rendering.
inputProps: _react.PropTypes.object, // Arbitrary input props
itemProps: _react.PropTypes.oneOfType([// Arbitrary item props
_react.PropTypes.object, _react.PropTypes.func]),
focusedSectionIndex: _react.PropTypes.number, // Section index of the focused item
focusedItemIndex: _react.PropTypes.number, // Focused item index (within a section)
theme: _react.PropTypes.object };
Autowhatever.defaultProps = {
id: '1',
multiSection: false,
shouldRenderSection: function shouldRenderSection() {
return true;
},
renderItem: function renderItem() {
throw new Error('`renderItem` must be provided');
},
renderSectionTitle: function renderSectionTitle() {
throw new Error('`renderSectionTitle` must be provided');
},
getSectionItems: function getSectionItems() {
throw new Error('`getSectionItems` must be provided');
},
inputProps: {},
itemProps: {},
focusedSectionIndex: null,
focusedItemIndex: null,
theme: {
container: 'react-autowhatever__container',
containerOpen: 'react-autowhatever__container--open',
input: 'react-autowhatever__input',
itemsContainer: 'react-autowhatever__items-container',
item: 'react-autowhatever__item',
itemFocused: 'react-autowhatever__item--focused',
sectionContainer: 'react-autowhatever__section-container',
sectionTitle: 'react-autowhatever__section-title',
sectionItemsContainer: 'react-autowhatever__section-items-container'
}
};
exports.default = Autowhatever;