UNPKG

@momentum-ui/react

Version:

Cisco Momentum UI framework for ReactJs applications

523 lines (410 loc) 18 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _omit = _interopRequireDefault(require("lodash/omit")); var _uniqueId = _interopRequireDefault(require("lodash/uniqueId")); var _querySelectorAll = _interopRequireDefault(require("dom-helpers/query/querySelectorAll")); var _SelectableContext = _interopRequireDefault(require("../SelectableContext")); var _ListContext = _interopRequireDefault(require("../ListContext")); var _excluded = ["active", "children", "className", "role", "tabType", "wrap"]; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _extends() { _extends = Object.assign ? Object.assign.bind() : 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; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } var List = /*#__PURE__*/function (_React$Component) { _inheritsLoose(List, _React$Component); List.getDerivedStateFromProps = function getDerivedStateFromProps(_ref, state) { var active = _ref.active; return active ? _extends({}, state, { listContext: _extends({}, state.listContext, { active: active }) }) : state; }; function List(props) { var _this; _this = _React$Component.call(this, props) || this; _this.determineInitialFocus = function () { var _this$props = _this.props, focusFirstQuery = _this$props.focusFirstQuery, shouldFocusInitial = _this$props.shouldFocusInitial; var listContext = _this.state.listContext; var items = (0, _querySelectorAll.default)(_this.listNode, focusFirstQuery || ".md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)"); var focus = listContext.focus; if (items.length) { if (!focus) { focus = _this.getNextFocusedChild(items, items[0], 0); } if (focus && shouldFocusInitial) { _this.listNode.querySelector("[data-md-event-key=\"" + focus + "\"]").focus(); } } }; _this.getIncludesFirstCharacter = function (str, char) { return str.charAt(0).toLowerCase().includes(char); }; _this.getValue = function (arr, index, attribute) { return arr[index].attributes["data-md-" + attribute + "-key"] && arr[index].attributes["data-md-" + attribute + "-key"].value; }; _this.getFocusableItems = function () { if (!_this.listNode) return null; var focusQuery = _this.props.focusQuery; var defaultItems = (0, _querySelectorAll.default)(_this.listNode, ".md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)"); var customItems = focusQuery && (0, _querySelectorAll.default)(_this.listNode, focusQuery) || []; return customItems.length ? customItems.filter(function (item) { return customItems.indexOf(item) >= 0; }) : defaultItems; }; _this.handleKeyDown = function (e) { var _this$props2 = _this.props, shouldFocusActive = _this$props2.shouldFocusActive, shouldPropagateKeyDown = _this$props2.shouldPropagateKeyDown, navigationDirection = _this$props2.navigationDirection; var focus = _this.state.listContext.focus; var clickEvent; var tgt = e.currentTarget; var char = e.key; var items = _this.getFocusableItems(); var length = items.length && items.length - 1 || 0; var focusIdx = focus && items.indexOf(_this.listNode.querySelector("[data-md-event-key=\"" + focus + "\"]")) || 0; var flag = false; var isPrintableCharacter = function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); }; switch (e.which) { case 9: // TAB if (shouldFocusActive) { _this._needsRefocus = false; _this.setFocusToActive(); } break; case 32: // SPACE case 13: // ENTER try { clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); } catch (err) { if (document.createEvent) { // DOM Level 3 for IE 9+ clickEvent = document.createEvent('MouseEvents'); clickEvent.initEvent('click', true, true); } } tgt.dispatchEvent(clickEvent); flag = true; break; case 38: // UP if (navigationDirection === 'both' || navigationDirection === 'vertical') { _this.getNextFocusedChild(items, tgt, -1); _this._needsRefocus = true; if (!shouldPropagateKeyDown) flag = true; } break; case 37: // LEFT if (navigationDirection === 'both' || navigationDirection === 'horizontal') { _this.getNextFocusedChild(items, tgt, -1); _this._needsRefocus = true; if (!shouldPropagateKeyDown) flag = true; } break; case 39: // RIGHT if (navigationDirection === 'both' || navigationDirection === 'horizontal') { _this.getNextFocusedChild(items, tgt, 1); _this._needsRefocus = true; if (!shouldPropagateKeyDown) flag = true; } break; case 40: // DOWN if (navigationDirection === 'both' || navigationDirection === 'vertical') { _this.getNextFocusedChild(items, tgt, 1); _this._needsRefocus = true; if (!shouldPropagateKeyDown) flag = true; } break; case 33: // PAGE UP case 36: // HOME _this.setFocusToLimit('start', items, length); _this._needsRefocus = true; flag = true; break; case 34: // PAGE DOWN case 35: // END _this.setFocusToLimit('end', items, length); _this._needsRefocus = true; flag = true; break; default: if (isPrintableCharacter(char)) { _this.setFocusByFirstCharacter(char, focusIdx, items, length); _this._needsRefocus = true; flag = true; } break; } if (flag) { e.stopPropagation(); e.preventDefault(); } }; _this.handleSelect = function (e, opts) { var _this$props3 = _this.props, onSelect = _this$props3.onSelect, trackActive = _this$props3.trackActive; var active = _this.state.listContext.active; var eventKey = opts.eventKey, label = opts.label, value = opts.value; var items = _this.getFocusableItems(); var index = items.findIndex(function (item) { return item.getAttribute('data-md-event-key') === eventKey || item.querySelector("[data-md-event-key=\"" + eventKey + "\"]"); }); // Don't do anything if index is the same or outside of the bounds if (eventKey === active || index < 0 || index > items.length - 1) return; _this.setFocus(items, index); // Don't do anything if onSelect Event Handler is present if (onSelect) { return onSelect(e, { keyboardKey: _this.getValue(items, index, 'keyboard'), eventKey: _this.getValue(items, index, 'event'), label: label, value: value }); } // Keep reference to last index for event handler var last = active; // Call change event handler trackActive && _this.setState(function (state) { return { last: last, listContext: _extends({}, state.listContext, { active: _this.getValue(items, index, 'event') }) }; }); }; _this.setFocus = function (items, index) { _this.setState(function (state) { return { listContext: _extends({}, state.listContext, { focus: _this.getValue(items, index, 'event') }) }; }); }; _this.setActiveAndFocus = function (active, focus) { _this._needsRefocus = false; _this.setState(function (state) { return { listContext: _extends({}, state.listContext, { active: active, focus: state.shouldFocusActive && active || focus }) }; }); }; _this.setFocusByFirstCharacter = function (char, focusIdx, items, length) { var listContext = _this.state.listContext; var newIndex = items.reduce(function (agg, item, idx, arr) { var index = focusIdx + idx + 1 > length ? Math.abs(focusIdx + idx - length) : focusIdx + idx + 1; return !agg.length && _this.getValue(arr, index, 'keyboard') && _this.getIncludesFirstCharacter(_this.getValue(arr, index, 'keyboard'), char) ? agg.concat(_this.getValue(arr, index, 'event')) : agg; }, []); typeof newIndex[0] === 'string' && listContext.focus !== newIndex[0] && _this.setState(function (state) { return { listContext: _extends({}, state.listContext, { focus: newIndex[0] }) }; }); }; _this.state = { id: props.id || (0, _uniqueId.default)('md-list-'), last: 0, listContext: { active: props.active, focus: props.shouldFocusActive && props.active || props.focus, role: props.itemRole, type: props.type, ariaConfig: props.ariaConfig }, selectContext: { parentKeyDown: _this.handleKeyDown, parentOnSelect: _this.handleSelect } }; return _this; } var _proto = List.prototype; _proto.componentDidMount = function componentDidMount() { var focusFirst = this.props.focusFirst; focusFirst && this.listNode && this.determineInitialFocus(); }; _proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) { var listContext = this.state.listContext; var _this$props4 = this.props, active = _this$props4.active, focus = _this$props4.focus, shouldFocusActive = _this$props4.shouldFocusActive; if (shouldFocusActive && (prevProps.focus !== focus || prevProps.active !== active)) { this.setActiveAndFocus(active, focus); } if (!this._needsRefocus || !this.listNode) return; if (listContext.focus && prevState.listContext.focus !== listContext.focus) { this.listNode.querySelector("[data-md-event-key=\"" + listContext.focus + "\"]").focus(); } }; _proto.getNextFocusedChild = function getNextFocusedChild(items, current, offset) { if (!this.listNode) return null; var shouldLoop = this.props.shouldLoop; var listContext = this.state.listContext; var possibleIndex = items.indexOf(current) + offset; var getIndex = function getIndex() { if (possibleIndex < 0) { return shouldLoop ? items.length - 1 : 0; } else if (possibleIndex > items.length - 1) { return shouldLoop ? 0 : items.length - 1; } else return possibleIndex; }; listContext.focus !== this.getValue(items, getIndex(), 'event') && this.setState({ listContext: _extends({}, listContext, { focus: this.getValue(items, getIndex(), 'event') }) }); return this.getValue(items, getIndex(), 'event'); }; _proto.setFocusToActive = function setFocusToActive() { var focus = this.state.listContext.active; if (!focus) { var items = this.getFocusableItems(); focus = this.getValue(items, 0, 'event'); } this.setState({ listContext: _extends({}, this.state.listContext, { focus: focus }) }); }; _proto.setFocusToLimit = function setFocusToLimit(target, items, length) { var focus = this.state.listContext.focus; var index = target === 'start' ? 0 : length; var newFocusKey = this.getValue(items, index, 'event'); newFocusKey !== focus && this.setState({ listContext: _extends({}, this.state.listContext, { focus: newFocusKey }) }); }; _proto.render = function render() { var _this2 = this; var _this$props5 = this.props, active = _this$props5.active, children = _this$props5.children, className = _this$props5.className, role = _this$props5.role, tabType = _this$props5.tabType, wrap = _this$props5.wrap, props = _objectWithoutPropertiesLoose(_this$props5, _excluded); var _this$state = this.state, listContext = _this$state.listContext, selectContext = _this$state.selectContext; var otherProps = (0, _omit.default)(_extends({}, props), ['ariaConfig', 'focusFirst', 'focusFirstQuery', 'focusQuery', 'itemRole', 'navigationDirection', 'shouldPropagateKeyDown', 'shouldFocusActive', 'shouldFocusInitial', 'shouldLoop', 'trackActive', 'type']); var getActiveId = function getActiveId() { var activeNode = active && active.length && _this2.listNode && _this2.listNode.querySelector("[data-md-event-key=\"" + active[0] + "\"]"); return activeNode && activeNode.id; }; /* eslint-disable jsx-a11y/aria-activedescendant-has-tabindex */ return /*#__PURE__*/_react.default.createElement(_SelectableContext.default.Provider, { value: selectContext }, /*#__PURE__*/_react.default.createElement(_ListContext.default.Provider, { value: listContext }, /*#__PURE__*/_react.default.createElement("div", _extends({ className: 'md-list' + ("" + (tabType && " md-list--" + tabType || '')) + ("" + (wrap && " md-list--wrap" || '')) + ("" + (className && " " + className || '')), role: role, "aria-activedescendant": getActiveId(), ref: function ref(_ref2) { return _this2.listNode = _ref2; } }, otherProps), children))); /* eslint-enable*/ }; return List; }(_react.default.Component); List.propTypes = { /** @prop Optional active prop to pass active prop to children | null */ active: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.array, _propTypes.default.number]), /** @prop Optional parameter for accessibility configuration | null */ ariaConfig: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.object]), /** @prop Children nodes to render inside List | null */ children: _propTypes.default.node, /** @prop Optional css class string | '' */ className: _propTypes.default.string, /** @prop Optional focus prop to pass focus prop to children | null */ focus: _propTypes.default.string, /** @prop Sets first List item to have focus within List context | true */ focusFirst: _propTypes.default.bool, /** @prop Queries children to find matching item to have focus | '' */ focusFirstQuery: _propTypes.default.string, /** @prop Additional elements that can be focused by selector | '' */ focusQuery: _propTypes.default.string, /** @prop Optional ID value of List | null */ id: _propTypes.default.string, /** @prop Optional tabType prop type to manually set child role | 'listitem' */ itemRole: _propTypes.default.string, /** @prop Restricts the traversal of the list with either UP/DOWN, LEFT/RIGHT, or both | 'both' */ navigationDirection: _propTypes.default.oneOf(['vertical', 'horizontal', 'both']), /** @prop Callback function invoked by user selecting an interactive item within List | null */ onSelect: _propTypes.default.func, /** @prop Disables the stopPropagation() & preventDefault() for ArrowKey Events | false */ shouldPropagateKeyDown: _propTypes.default.bool, /** @prop Sets the ARIA role for the Nav, in the context of a TabContainer | 'list' */ role: _propTypes.default.string, /** @prop Invokes dom focus method on mount | true */ shouldFocusInitial: _propTypes.default.bool, /** @prop Determines if focus should revert to active on list exit | false */ shouldFocusActive: _propTypes.default.bool, /** @prop Determines if keyboard navigation should loop through list | true */ shouldLoop: _propTypes.default.bool, /** @prop Sets the orientation of the List | 'vertical' */ tabType: _propTypes.default.oneOf(['vertical', 'horizontal']), /** @prop Determines if List wrapper should track active | true */ trackActive: _propTypes.default.bool, /** @prop Sets List size | null */ type: _propTypes.default.oneOf(['small', 'large', 'space', 'xlarge']), /** @prop Optional wrap prop type to wrap items to next row */ wrap: _propTypes.default.bool }; List.defaultProps = { active: null, ariaConfig: null, children: null, className: '', id: null, itemRole: 'listitem', focus: null, focusFirst: true, focusFirstQuery: '', focusQuery: '', navigationDirection: 'both', onSelect: null, shouldPropagateKeyDown: false, role: 'list', shouldFocusActive: false, shouldFocusInitial: true, shouldLoop: true, tabType: 'vertical', trackActive: true, type: null, wrap: false }; List.displayName = 'List'; var _default = List; exports.default = _default;