@workday/canvas-kit-labs-react-menu
Version:
A container for navigation or action items
253 lines (252 loc) • 11.2 kB
JavaScript
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 (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
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 * as React from 'react';
import styled from '@emotion/styled';
import uuid from 'uuid/v4';
import { Card } from '@workday/canvas-kit-react-card';
import { commonColors, spacing, borderRadius } from '@workday/canvas-kit-react-core';
import { hideMouseFocus } from '@workday/canvas-kit-react-common';
var List = styled('ul')(__assign({ background: commonColors.background, borderRadius: borderRadius.m, padding: 0, margin: spacing.xxs + " 0", '&:focus': {
outline: 'none',
} }, hideMouseFocus));
var Menu = (function (_super) {
__extends(Menu, _super);
function Menu(props) {
var _this = _super.call(this, props) || this;
_this.id = uuid();
_this.getNormalizedItemIndex = function (index) {
var itemCount = React.Children.count(_this.props.children);
var firstItem = 0;
var lastItem = itemCount - 1;
if (!index) {
return firstItem;
}
return index < 0 ? firstItem : index >= itemCount ? lastItem : index;
};
_this.setNormalizedItemIndex = function (index) {
_this.setState({ selectedItemIndex: _this.getNormalizedItemIndex(index) });
};
_this.handleKeyboardShortcuts = function (event) {
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
var children = React.Children.toArray(_this.props.children);
var nextSelectedIndex = 0;
var isShortcut = false;
var itemCount = children.length;
var firstItem = 0;
var lastItem = itemCount - 1;
if (event.key.length === 1 && event.key.match(/\S/)) {
var start = _this.state.selectedItemIndex + 1;
var searchIndex = void 0;
if (start === children.length) {
start = 0;
}
searchIndex = _this.getIndexFirstChars(start, event.key.toLowerCase());
if (searchIndex === -1) {
searchIndex = _this.getIndexFirstChars(0, event.key.toLowerCase(), start);
}
if (searchIndex > -1) {
isShortcut = true;
nextSelectedIndex = searchIndex;
}
}
else {
switch (event.key) {
case 'ArrowUp':
case 'ArrowDown':
case 'Down':
case 'Up':
var direction = event.key === 'ArrowUp' ? -1 : 1;
isShortcut = true;
var nextIndex = _this.state.selectedItemIndex + direction;
nextSelectedIndex =
nextIndex < 0 ? lastItem : nextIndex >= itemCount ? firstItem : nextIndex;
break;
case 'Home':
case 'End':
var skipTo = event.key === 'Home' ? firstItem : lastItem;
isShortcut = true;
nextSelectedIndex = skipTo;
break;
case 'Tab':
if (_this.props.onClose) {
_this.props.onClose();
}
break;
case 'Escape':
case 'Esc':
isShortcut = true;
if (_this.props.onClose) {
_this.props.onClose();
}
break;
case 'Spacebar':
case ' ':
case 'Enter':
nextSelectedIndex = _this.state.selectedItemIndex;
var child = children[_this.state.selectedItemIndex];
_this.handleClick(event, child.props);
isShortcut = true;
break;
default:
}
}
if (isShortcut) {
_this.setNormalizedItemIndex(nextSelectedIndex);
event.stopPropagation();
event.preventDefault();
}
};
_this.handleClick = function (event, menuItemProps) {
if (menuItemProps.isDisabled) {
return;
}
if (menuItemProps.onClick) {
menuItemProps.onClick(event);
}
if (_this.props.onSelect) {
_this.props.onSelect();
}
if (_this.props.onClose) {
if (menuItemProps.shouldClose) {
_this.props.onClose();
}
}
};
_this.getIndexFirstChars = function (startIndex, character, lastIndex) {
if (lastIndex === void 0) { lastIndex = _this.firstCharacters.length; }
for (var i = startIndex; i < lastIndex; i++) {
if (character === _this.firstCharacters[i]) {
return i;
}
}
return -1;
};
_this.setFirstCharacters = function () {
var getFirstCharacter = function (child) {
var character = '';
if (!child || typeof child === 'boolean' || child === {}) {
character = '';
}
else if (typeof child === 'string' || typeof child === 'number') {
character = child
.toString()
.trim()
.substring(0, 1)
.toLowerCase();
}
else if (Array.isArray(child) && child[0]) {
character = getFirstCharacter(child[0]);
}
else if ('props' in child) {
var children = child.props.children;
if (Array.isArray(children)) {
character = getFirstCharacter(children[0]);
}
else {
character = getFirstCharacter(children);
}
}
return character;
};
var firstCharacters = React.Children.map(_this.props.children, function (child) {
return getFirstCharacter(child);
});
_this.firstCharacters = firstCharacters;
};
_this.getInitialSelectedItem = function () {
var selected = _this.props.initialSelectedItem || 0;
selected = selected < 0 ? React.Children.count(_this.props.children) + selected : selected;
if (selected < 0) {
selected = 0;
}
else if (selected > React.Children.count(_this.props.children) - 1) {
selected = React.Children.count(_this.props.children) - 1;
}
return selected;
};
_this.setInitialSelectedItem = function () {
var selected = _this.getInitialSelectedItem();
_this.setState({ selectedItemIndex: selected });
};
_this.menuRef = React.createRef();
var selected = _this.getInitialSelectedItem();
_this.state = {
selectedItemIndex: selected,
};
return _this;
}
Menu.prototype.componentDidUpdate = function (prevProps) {
var _this = this;
if (this.props.children !== prevProps.children) {
this.setFirstCharacters();
this.setInitialSelectedItem();
}
if (this.props.isOpen && !prevProps.isOpen) {
this.setInitialSelectedItem();
}
this.animateId = requestAnimationFrame(function () {
if (_this.props.isOpen && _this.menuRef.current) {
_this.menuRef.current.focus();
}
});
};
Menu.prototype.componentDidMount = function () {
this.setFirstCharacters();
this.setInitialSelectedItem();
};
Menu.prototype.componentWillUnmount = function () {
cancelAnimationFrame(this.animateId);
};
Menu.prototype.render = function () {
var _this = this;
var _a = this.props, _b = _a.id, id = _b === void 0 ? this.id : _b, _c = _a.isOpen, isOpen = _c === void 0 ? true : _c, children = _a.children, ariaLabelledby = _a["aria-labelledby"], grow = _a.grow, width = _a.width, onSelect = _a.onSelect, onClose = _a.onClose, initialSelectedItem = _a.initialSelectedItem, elemProps = __rest(_a, ["id", "isOpen", "children", 'aria-labelledby', "grow", "width", "onSelect", "onClose", "initialSelectedItem"]);
var selectedItemIndex = this.state.selectedItemIndex;
var cardWidth = grow ? '100%' : width;
return (React.createElement(Card, { style: { display: 'inline-block' }, padding: spacing.zero, width: cardWidth },
React.createElement(List, __assign({ role: "menu", tabIndex: 0, id: id, "aria-labelledby": ariaLabelledby, "aria-activedescendant": id + "-" + selectedItemIndex, onKeyDown: this.handleKeyboardShortcuts, ref: this.menuRef }, elemProps), React.Children.map(children, function (menuItem, index) {
var itemId = id + "-" + index;
return (React.createElement(React.Fragment, { key: itemId }, React.cloneElement(menuItem, {
onClick: function (event) { return _this.handleClick(event, menuItem.props); },
id: itemId,
isFocused: selectedItemIndex === index,
})));
}))));
};
return Menu;
}(React.Component));
export default Menu;