UNPKG

@atlaskit/atlassian-navigation

Version:

A horizontal navigation component for Atlassian products.

155 lines (149 loc) 7.51 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useOverflowStatus = exports.useOverflowController = exports.default = exports.OverflowProvider = void 0; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _throttle = _interopRequireDefault(require("lodash/throttle")); var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // Prevent width detector from triggering too many re-renders var THROTTLE_INTERVAL = 16 * 4; // Approx min width of items (based of "More" size) var ITEM_APPROX_MINWIDTH = 70; var calculateHash = function calculateHash(w, n) { return w + '#' + n; }; var updateHashRef = function updateHashRef(currentRef, value) { currentRef.unshift(value); currentRef.length = 3; }; var OverflowContext = /*#__PURE__*/(0, _react.createContext)({ isVisible: true, openOverflowMenu: _noop.default, closeOverflowMenu: _noop.default }); // eslint-disable-next-line @repo/internal/react/require-jsdoc var OverflowProvider = function OverflowProvider(_ref) { var children = _ref.children, isVisible = _ref.isVisible, openOverflowMenu = _ref.openOverflowMenu, closeOverflowMenu = _ref.closeOverflowMenu; var Provider = OverflowContext.Provider; var value = (0, _react.useMemo)(function () { return { isVisible: isVisible, openOverflowMenu: openOverflowMenu, closeOverflowMenu: closeOverflowMenu }; }, [isVisible, openOverflowMenu, closeOverflowMenu]); return /*#__PURE__*/_react.default.createElement(Provider, { value: value }, children); }; /** * __useOverFlowStatus__ * * Returns the current context value for the nearest OverflowProvider. * * - [Example](https://atlassian.design/components/atlassian-navigation/examples#responsive) */ exports.OverflowProvider = OverflowProvider; var useOverflowStatus = function useOverflowStatus() { return (0, _react.useContext)(OverflowContext); }; exports.useOverflowStatus = useOverflowStatus; var useOverflowController = function useOverflowController(nodes) { var items = _react.default.Children.toArray(nodes); var _useState = (0, _react.useState)(9999), _useState2 = (0, _slicedToArray2.default)(_useState, 2), width = _useState2[0], setWidth = _useState2[1]; var _useState3 = (0, _react.useState)(items.length), _useState4 = (0, _slicedToArray2.default)(_useState3, 2), itemsLimit = _useState4[0], setItemsLimit = _useState4[1]; var _useState5 = (0, _react.useState)({}), _useState6 = (0, _slicedToArray2.default)(_useState5, 2), forceEffectValue = _useState6[0], triggerForceEffect = _useState6[1]; // Storing items approximate width so we can try expanding when there is enough room var itemsWidths = (0, _react.useRef)([]).current; // Storing a couple of width + items count in order to stabilize var hashRef = (0, _react.useRef)([]); // AFP-2511 TODO: Fix automatic suppressions below // eslint-disable-next-line react-hooks/exhaustive-deps var throttleSetWidth = (0, _react.useCallback)((0, _throttle.default)(setWidth, THROTTLE_INTERVAL), [setWidth]); (0, _react.useEffect)(function () { var lastItemWidth = itemsWidths[itemsLimit] || 0; var wasJustLimited = lastItemWidth < 0; var currentHash = calculateHash(width, itemsLimit); if (hashRef.current[0] === currentHash) { // After removing an item, if width has not changed yet we schedule a force update // to handle case where removing an item does not actually trigger width change var t = setTimeout(function () { updateHashRef(hashRef.current, ''); triggerForceEffect({}); }, THROTTLE_INTERVAL * 1.5); return function () { return clearTimeout(t); }; } if (wasJustLimited) { // Width was updated either via resize or after changing the limit // we cap the width between ITEM_APPROX_MINWIDTH and 2*ITEM_APPROX_MINWIDTH // because width is throttled as when fast expanding/resizing partialWidth // will not be reliable (edge case) var partialWidth = Math.max(Math.min(width + lastItemWidth, ITEM_APPROX_MINWIDTH * 2), ITEM_APPROX_MINWIDTH); itemsWidths[itemsLimit] = partialWidth; } if (width < ITEM_APPROX_MINWIDTH * 0.9 && itemsLimit) { // If current width is less than an item approx width we remove an item // marking the width as negative so we will calculate it on width update // plus we set the hash to stabilise and not removing more than one element // until we are sure width was updated var nextHash = calculateHash(width, itemsLimit - 1); if (hashRef.current.indexOf(nextHash) === -1) { setItemsLimit(itemsLimit - 1); itemsWidths[itemsLimit - 1] = -(width || 1); updateHashRef(hashRef.current, nextHash); } return; } /** * This is not necessarily equal to `lastItemWidth` because the * ```js * if (wasJustLimited) {} * ``` * branch above modifies `itemsWidths`. * * Using `lastItemWidth` here can cause collapsing behavior to fail, * such as the issue reported in DSP-7329. */ var currentLastItemWidth = itemsWidths[itemsLimit] || 0; if (width - currentLastItemWidth > ITEM_APPROX_MINWIDTH * 1.1 && itemsLimit < items.length) { // If we have enough room to accomodate next item width we increase the limit // unless it has been recently removed var _nextHash = calculateHash(width, itemsLimit + 1); if (hashRef.current.indexOf(_nextHash) === -1) { setItemsLimit(itemsLimit + 1); updateHashRef(hashRef.current, _nextHash); } return; } }, [width, hashRef, itemsLimit, itemsWidths, forceEffectValue, items.length]); return { visibleItems: items.slice(0, itemsLimit), overflowItems: items.slice(itemsLimit), updateWidth: throttleSetWidth }; }; // Used to extract props for useOverflowStatus(); // eslint-disable-next-line @repo/internal/react/use-noop exports.useOverflowController = useOverflowController; var _default = function _default(props) {}; exports.default = _default;