UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

422 lines 19.9 kB
/** @component list-item */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import React from 'react'; import PropTypes from 'prop-types'; import omit from 'lodash/omit'; import uniqueId from 'lodash/uniqueId'; import qsa from 'dom-helpers/query/querySelectorAll'; import SelectableContext from '../SelectableContext'; import ListContext from '../ListContext'; /** * @deprecated - Components in the legacy folder (/src/legacy) are deprecated. Please use a component from the components folder (/src/components) instead. Legacy components may not follow accessibility standards. **/ var List = /** @class */ (function (_super) { __extends(List, _super); function List(props) { var _this = _super.call(this, props) || this; _this.determineInitialFocus = function () { var _a; var _b = _this.props, focusFirstQuery = _b.focusFirstQuery, shouldFocusInitial = _b.shouldFocusInitial; var listContext = _this.state.listContext; var items = qsa(_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) { (_a = _this.listNode.querySelector("[data-md-event-key=\"".concat(focus, "\"]"))) === null || _a === void 0 ? void 0 : _a.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-".concat(attribute, "-key")] && arr[index].attributes["data-md-".concat(attribute, "-key")].value; }; _this.getFocusableItems = function () { if (!_this.listNode) return null; var focusQuery = _this.props.focusQuery; var defaultItems = qsa(_this.listNode, ".md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)"); var customItems = (focusQuery && qsa(_this.listNode, focusQuery)) || []; return customItems.length ? customItems.filter(function (item) { return customItems.indexOf(item) >= 0; }) : defaultItems; }; _this.handleKeyDown = function (e) { var _a = _this.props, shouldFocusActive = _a.shouldFocusActive, shouldPropagateKeyDown = _a.shouldPropagateKeyDown, navigationDirection = _a.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=\"".concat(focus, "\"]")))) || 0; var flag = false; var isPrintableCharacter = function (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 _a = _this.props, onSelect = _a.onSelect, trackActive = _a.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=\"".concat(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: __assign(__assign({}, state.listContext), { active: _this.getValue(items, index, 'event') }), }); }); }; _this.setFocus = function (items, index) { _this.setState(function (state) { return ({ listContext: __assign(__assign({}, state.listContext), { focus: _this.getValue(items, index, 'event') }), }); }); }; _this.setActiveAndFocus = function (active, focus) { _this._needsRefocus = false; _this.setState(function (state) { return ({ listContext: __assign(__assign({}, 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: __assign(__assign({}, state.listContext), { focus: newIndex[0] }), }); }); }; _this.state = { id: props.id || uniqueId('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; } List.getDerivedStateFromProps = function (_a, state) { var active = _a.active; return active ? __assign(__assign({}, state), { listContext: __assign(__assign({}, state.listContext), { active: active }) }) : state; }; List.prototype.componentDidMount = function () { var focusFirst = this.props.focusFirst; focusFirst && this.listNode && this.determineInitialFocus(); }; List.prototype.componentDidUpdate = function (prevProps, prevState) { var _a; var listContext = this.state.listContext; var _b = this.props, active = _b.active, focus = _b.focus, shouldFocusActive = _b.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) { (_a = this.listNode.querySelector("[data-md-event-key=\"".concat(listContext.focus, "\"]"))) === null || _a === void 0 ? void 0 : _a.focus(); } }; List.prototype.getNextFocusedChild = function (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 () { 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: __assign(__assign({}, listContext), { focus: this.getValue(items, getIndex(), 'event') }), }); return this.getValue(items, getIndex(), 'event'); }; List.prototype.setFocusToActive = function () { var focus = this.state.listContext.active; if (!focus) { var items = this.getFocusableItems(); focus = this.getValue(items, 0, 'event'); } this.setState({ listContext: __assign(__assign({}, this.state.listContext), { focus: focus }), }); }; List.prototype.setFocusToLimit = function (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: __assign(__assign({}, this.state.listContext), { focus: newFocusKey }), }); }; List.prototype.render = function () { var _this = this; var _a = this.props, active = _a.active, children = _a.children, className = _a.className, role = _a.role, tabType = _a.tabType, wrap = _a.wrap, props = __rest(_a, ["active", "children", "className", "role", "tabType", "wrap"]); var _b = this.state, listContext = _b.listContext, selectContext = _b.selectContext; var otherProps = omit(__assign({}, props), [ 'ariaConfig', 'focusFirst', 'focusFirstQuery', 'focusQuery', 'itemRole', 'navigationDirection', 'shouldPropagateKeyDown', 'shouldFocusActive', 'shouldFocusInitial', 'shouldLoop', 'trackActive', 'type', ]); var getActiveId = function () { var activeNode = active && active.length && _this.listNode && _this.listNode.querySelector("[data-md-event-key=\"".concat(active[0], "\"]")); return activeNode && activeNode.id; }; /* eslint-disable jsx-a11y/aria-activedescendant-has-tabindex */ return (React.createElement(SelectableContext.Provider, { value: selectContext }, React.createElement(ListContext.Provider, { value: listContext }, React.createElement("div", __assign({ className: 'md-list' + "".concat((tabType && " md-list--".concat(tabType)) || '') + "".concat((wrap && " md-list--wrap") || '') + "".concat((className && " ".concat(className)) || ''), role: role, "aria-activedescendant": getActiveId(), ref: function (ref) { return (_this.listNode = ref); } }, otherProps), children)))); /* eslint-enable*/ }; return List; }(React.Component)); List.propTypes = { /** @prop Optional active prop to pass active prop to children | null */ active: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.number]), /** @prop Optional parameter for accessibility configuration | null */ ariaConfig: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), /** @prop Children nodes to render inside List | null */ children: PropTypes.node, /** @prop Optional css class string | '' */ className: PropTypes.string, /** @prop Optional focus prop to pass focus prop to children | null */ focus: PropTypes.string, /** @prop Sets first List item to have focus within List context | true */ focusFirst: PropTypes.bool, /** @prop Queries children to find matching item to have focus | '' */ focusFirstQuery: PropTypes.string, /** @prop Additional elements that can be focused by selector | '' */ focusQuery: PropTypes.string, /** @prop Optional ID value of List | null */ id: PropTypes.string, /** @prop Optional tabType prop type to manually set child role | 'listitem' */ itemRole: PropTypes.string, /** @prop Restricts the traversal of the list with either UP/DOWN, LEFT/RIGHT, or both | 'both' */ navigationDirection: PropTypes.oneOf(['vertical', 'horizontal', 'both']), /** @prop Callback function invoked by user selecting an interactive item within List | null */ onSelect: PropTypes.func, /** @prop Disables the stopPropagation() & preventDefault() for ArrowKey Events | false */ shouldPropagateKeyDown: PropTypes.bool, /** @prop Sets the ARIA role for the Nav, in the context of a TabContainer | 'list' */ role: PropTypes.string, /** @prop Invokes dom focus method on mount | true */ shouldFocusInitial: PropTypes.bool, /** @prop Determines if focus should revert to active on list exit | false */ shouldFocusActive: PropTypes.bool, /** @prop Determines if keyboard navigation should loop through list | true */ shouldLoop: PropTypes.bool, /** @prop Sets the orientation of the List | 'vertical' */ tabType: PropTypes.oneOf(['vertical', 'horizontal']), /** @prop Determines if List wrapper should track active | true */ trackActive: PropTypes.bool, /** @prop Sets List size | null */ type: PropTypes.oneOf(['small', 'large', 'space', 'xlarge']), /** @prop Optional wrap prop type to wrap items to next row */ wrap: PropTypes.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'; export default List; //# sourceMappingURL=index.js.map