UNPKG

retabbed

Version:

Primitive to build simple, flexible, WAI-ARIA compliant React tab components

534 lines (432 loc) 16.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = _interopDefault(require('react')); var PropTypes = _interopDefault(require('prop-types')); var idCounter = 0; /** * This generates a unique ID for an instance of Downshift * @return {String} the unique ID */ function generateId() { return String(idCounter++); } /** * Resets idCounter to 0. Used for SSR. */ function resetIdCounter() { idCounter = 0; } function noop() {} /** * This is intended to be used to compose event handlers. * They are executed in order until one of them sets * `event.preventDownshiftDefault = true`. * @param {...Function} fns the event handler functions * @return {Function} the event handler to add to an element */ function callAllEventHandlers() { for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) { fns[_key] = arguments[_key]; } return function (event) { for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } return fns.some(function (fn) { fn && fn.apply(undefined, [event].concat(args)); return event.preventTabsDefault || event.hasOwnProperty('nativeEvent') && event.nativeEvent.preventTabsDefault; }); }; } /** * Takes an argument and if it's an array, returns the first item in the array * otherwise returns the argument * @param {*} arg the maybe-array * @param {*} defaultValue the value if arg is falsey not defined * @return {*} the arg or it's first item */ function unwrapArray(arg, defaultValue) { var _arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */arg[0] : arg; if (!_arg && defaultValue) { return defaultValue; } else { return _arg; } } var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 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 inherits = function (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 objectWithoutProperties = function (obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }; var possibleConstructorReturn = function (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; }; var ControlledTabs = function (_React$Component) { inherits(ControlledTabs, _React$Component); function ControlledTabs() { var _temp, _this, _ret; classCallCheck(this, ControlledTabs); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.tabs = [], _this.tabPanels = [], _this.id = _this.props.id || 'retabbed-' + generateId(), _this.focus = _this.props.defaultFocus, _this.clearTabs = function () { _this.tabs = []; }, _this.clearTabPanels = function () { _this.tabPanels = []; }, _this.handleClickAtTabIndex = function (index, event) { event.preventDefault(); var _this$props = _this.props, onSelect = _this$props.onSelect, selectedIndex = _this$props.selectedIndex; if (selectedIndex !== index) { _this.focus = true; onSelect(index, selectedIndex, event); } }, _this.handleKeyPressAtTabIndex = function (index, event) { var _this$props2 = _this.props, onSelect = _this$props2.onSelect, selectedIndex = _this$props2.selectedIndex; var keyCode = event.keyCode; var _this2 = _this, getPrevTab = _this2.getPrevTab, getNextTab = _this2.getNextTab, getFirstTab = _this2.getFirstTab, getLastTab = _this2.getLastTab; var fn = { 37: getPrevTab, // left 39: getNextTab, // right 38: getPrevTab, // up 40: getNextTab, // down 36: getFirstTab, // home 35: getLastTab // end }[keyCode]; if (fn) { _this.focus = true; event.preventDefault(); var newSelectedIndex = fn(index, event); onSelect && onSelect(newSelectedIndex, selectedIndex, event); } }, _this.getPrevTab = function (index) { var i = index; // Look for non-disabled tab from index to first tab on the left while (i--) { if (!_this.getTab(i).disabled) { return i; } } // If no tab found, continue searching from last tab on right to index i = _this.getTabsCount(); while (i-- > index + 1) { if (!_this.getTab(i).disabled) { return i; } } // No tabs are enabled, return index // istanbul ignore next return index; }, _this.getNextTab = function (index) { var count = _this.getTabsCount(); // Look for non-disabled tab from index to the last tab on the right for (var i = index + 1; i < count; i++) { if (!_this.getTab(i).disabled) { return i; } } // If no tab found, continue searching from first on left to index for (var _i = 0; _i < index; _i++) { if (!_this.getTab(_i).disabled) { return _i; } } // No tabs are enabled, return index return index; }, _this.getFirstTab = function () { var count = _this.getTabsCount(); // Look for non disabled tab from the first tab for (var i = 0; i < count; i++) { // istanbul ignore else if (!_this.getTab(i).disabled) { return i; } } // istanbul ignore next return null; }, _this.getLastTab = function () { var i = _this.getTabsCount(); // Look for non disabled tab from the last tab while (i--) { if (!_this.getTab(i).disabled) { return i; } } // istanbul ignore next return null; }, _this.getTabListProps = function () { var rest = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return _extends({ role: 'tablist' }, rest); }, _this.getTabPanelProps = function () { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var id = _ref.id, tabId = _ref.tabId, _index = _ref.index, rest = objectWithoutProperties(_ref, ['id', 'tabId', 'index']); var item = {}; var index = _index; if (index === undefined) { _this.tabPanels.push(item); index = _this.tabPanels.indexOf(item); } else { _this.tabPanels[index] = item; } return _extends({ role: 'tabpanel', id: id || _this.id + '-tabpanel-' + index, 'aria-hidden': index !== _this.props.selectedIndex, 'aria-labelledby': tabId || _this.id + '-tab-' + index }, rest); }, _this.getTabProps = function () { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var id = _ref2.id, panelId = _ref2.panelId, _index = _ref2.index, onKeyDown = _ref2.onKeyDown, onClick = _ref2.onClick, rest = objectWithoutProperties(_ref2, ['id', 'panelId', 'index', 'onKeyDown', 'onClick']); var selectedIndex = _this.props.selectedIndex; var index = _index; var item = { disabled: rest.disabled }; if (index === undefined) { _this.tabs.push(item); index = _this.tabs.indexOf(item); } else { _this.tabs[index] = item; } var selected = !rest.disabled && selectedIndex === index; var eventHandlers = selected ? { onKeyDown: callAllEventHandlers(onKeyDown, _this.handleKeyPressAtTabIndex.bind(_this, index)) } : {}; if (!rest.disabled) { eventHandlers.onClick = callAllEventHandlers(onClick, _this.handleClickAtTabIndex.bind(_this, index)); } // eslint-disable-next-line var tabIndex = selected ? { tabIndex: 0 } : !rest.disabled ? { tabIndex: -1 } : {}; return _extends({ role: 'tab', id: id || _this.id + '-tab-' + index, focus: selected && _this.focus, 'aria-selected': selected, 'aria-disabled': rest.disabled, 'aria-controls': panelId || _this.id + '-tab-' + index }, eventHandlers, tabIndex, rest); }, _temp), possibleConstructorReturn(_this, _ret); } ControlledTabs.prototype.getTab = function getTab(i) { return this.tabs[i]; }; ControlledTabs.prototype.getTabsCount = function getTabsCount() { return this.tabs.length; }; ControlledTabs.prototype.getStateAndHelpers = function getStateAndHelpers() { var id = this.id, getTabListProps = this.getTabListProps, getTabPanelProps = this.getTabPanelProps, getTabProps = this.getTabProps; var selectedIndex = this.props.selectedIndex; return { id: id, getTabListProps: getTabListProps, getTabPanelProps: getTabPanelProps, getTabProps: getTabProps, selectedIndex: selectedIndex }; }; ControlledTabs.prototype.render = function render() { var children = unwrapArray(this.props.children, noop); // because the tabs are rerendered every time we call the children // we clear this out each render and it will be populated again as // getTabProps is called. this.clearTabs(); // because the tabs are rerendered every time we call the children // we clear this out each render and it will be populated again as // getTabPanelProps is called. this.clearTabPanels(); var element = unwrapArray(children(this.getStateAndHelpers())); return element; }; return ControlledTabs; }(React.Component); process.env.NODE_ENV !== "production" ? ControlledTabs.propTypes = { id: PropTypes.string, defaultFocus: PropTypes.bool, children: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired, selectedIndex: PropTypes.number.isRequired } : void 0; var UncontrolledTabs = function (_React$Component) { inherits(UncontrolledTabs, _React$Component); function UncontrolledTabs() { var _temp, _this, _ret; classCallCheck(this, UncontrolledTabs); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = { selectedIndex: _this.props.defaultIndex }, _this.handleSelect = function (index, prevIndex, event) { var onSelect = _this.props.onSelect; onSelect(index, prevIndex, event); _this.setState({ selectedIndex: index }); }, _temp), possibleConstructorReturn(_this, _ret); } UncontrolledTabs.prototype.render = function render() { return React.createElement(ControlledTabs, _extends({}, this.props, { onSelect: this.handleSelect, selectedIndex: this.state.selectedIndex })); }; return UncontrolledTabs; }(React.Component); process.env.NODE_ENV !== "production" ? UncontrolledTabs.propTypes = { id: PropTypes.string, defaultFocus: PropTypes.bool, children: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired, defaultIndex: PropTypes.number } : void 0; UncontrolledTabs.defaultProps = { defaultIndex: 0 }; var Tabs = function (_React$Component) { inherits(Tabs, _React$Component); function Tabs() { classCallCheck(this, Tabs); return possibleConstructorReturn(this, _React$Component.apply(this, arguments)); } Tabs.inUncontrolledMode = function inUncontrolledMode(props) { return typeof props.selectedIndex === 'undefined'; }; Tabs.prototype.componentDidUpdate = function componentDidUpdate(prevProps) { if (process.env.NODE_ENV !== 'production' && this.uncontrolledMode !== Tabs.inUncontrolledMode(prevProps)) { throw new Error('Switching between controlled mode (by using `selectedIndex`) and uncontrolled mode is not supported in `Tabs`.\nFor more information about controlled and uncontrolled mode of react-tabs see the README.'); } }; Tabs.prototype.render = function render() { this.uncontrolledMode = Tabs.inUncontrolledMode(this.props); return this.uncontrolledMode ? React.createElement(UncontrolledTabs, this.props) : React.createElement(ControlledTabs, this.props); }; return Tabs; }(React.Component); process.env.NODE_ENV !== "production" ? Tabs.propTypes = { id: PropTypes.string, defaultFocus: PropTypes.bool, children: PropTypes.func.isRequired, onSelect: PropTypes.func, defaultIndex: PropTypes.number, selectedIndex: PropTypes.number } : void 0; Tabs.defaultProps = { onSelect: noop }; function unwrapExports (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var getDisplayName_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); exports.default = getDisplayName; function getDisplayName(Component) { return Component.displayName || Component.name || (typeof Component === 'string' && Component.length > 0 ? Component : 'Unknown'); } }); var getDisplayName = unwrapExports(getDisplayName_1); var focusablePropTypes = { focus: PropTypes.bool }; function createFocusableComponent(BaseComponent) { var refKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'ref'; var FocusableComponent = function (_React$Component) { inherits(FocusableComponent, _React$Component); function FocusableComponent() { var _temp, _this, _ret; classCallCheck(this, FocusableComponent); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.node = null, _this.setRef = function (ref) { _this.node = ref; }, _temp), possibleConstructorReturn(_this, _ret); } FocusableComponent.prototype.checkFocus = function checkFocus() { if (this.props.focus) { this.node.focus(); } }; FocusableComponent.prototype.componentDidMount = function componentDidMount() { this.checkFocus(); }; FocusableComponent.prototype.componentDidUpdate = function componentDidUpdate() { this.checkFocus(); }; FocusableComponent.prototype.render = function render() { var _refProps; var _props = this.props, focus = _props.focus, props = objectWithoutProperties(_props, ['focus']); var refProps = (_refProps = {}, _refProps[refKey] = this.setRef, _refProps); return React.createElement(BaseComponent, _extends({}, refProps, props)); }; return FocusableComponent; }(React.Component); process.env.NODE_ENV !== "production" ? FocusableComponent.propTypes = focusablePropTypes : void 0; FocusableComponent.displayName = 'Focusable(' + getDisplayName(BaseComponent); return FocusableComponent; } exports.default = Tabs; exports.createFocusableComponent = createFocusableComponent; exports.resetIdCounter = resetIdCounter;