UNPKG

@atlaskit/breadcrumbs

Version:

Breadcrumbs are a navigation system used to show a user's location in a site or app.

198 lines (191 loc) • 7.96 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useOverflowCollapse; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = require("react"); function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } // The ellipsis trigger is a compact IconButton (24px) plus the trailing separator (24px). var ELLIPSIS_WIDTH_ESTIMATE = 48; // Measurements need layout timing in the browser, but should be a no-op for SSR. var useLayoutEffect = typeof window === 'undefined' ? _react.useEffect : _react.useLayoutEffect; function setsEqual(a, b) { if (a.size !== b.size) { return false; } var _iterator = _createForOfIteratorHelper(a), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var v = _step.value; if (!b.has(v)) { return false; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return true; } function getMiddleOutCollapseOrder(itemCount) { var lastIndex = itemCount - 1; var middleIndex = Math.floor((itemCount - 1) / 2); var collapseOrder = []; if (middleIndex > 0 && middleIndex < lastIndex) { collapseOrder.push(middleIndex); } for (var distance = 1; distance < itemCount; distance++) { var leftIndex = middleIndex - distance; var rightIndex = middleIndex + distance; if (leftIndex > 0) { collapseOrder.push(leftIndex); } if (rightIndex < lastIndex) { collapseOrder.push(rightIndex); } } return collapseOrder; } // eslint-disable-next-line @repo/internal/react/require-jsdoc function useOverflowCollapse(itemCount) { var itemRefs = (0, _react.useRef)([]); var containerRef = (0, _react.useRef)(null); var naturalWidths = (0, _react.useRef)([]); var naturalWidthsReady = (0, _react.useRef)(false); var _useState = (0, _react.useState)(false), _useState2 = (0, _slicedToArray2.default)(_useState, 2), naturalWidthsReadyState = _useState2[0], setNaturalWidthsReadyState = _useState2[1]; var _useState3 = (0, _react.useState)(new Set()), _useState4 = (0, _slicedToArray2.default)(_useState3, 2), collapsedIndices = _useState4[0], setCollapsedIndices = _useState4[1]; // Intentionally runs after each render until refs have committed and natural widths are measured. // eslint-disable-next-line react-hooks/exhaustive-deps useLayoutEffect(function () { if (naturalWidthsReady.current) { return; } var widths = itemRefs.current.map(function (el) { var _el$offsetWidth; return (_el$offsetWidth = el === null || el === void 0 ? void 0 : el.offsetWidth) !== null && _el$offsetWidth !== void 0 ? _el$offsetWidth : 0; }); if (widths.some(function (w) { return w > 0; })) { naturalWidths.current = widths; naturalWidthsReady.current = true; setNaturalWidthsReadyState(true); } }); var recalculate = (0, _react.useCallback)(function () { var container = containerRef.current; if (!container || !naturalWidthsReady.current) { return; } var availableWidth = container.offsetWidth; var widths = naturalWidths.current; if (itemCount <= 2) { setCollapsedIndices(function (prev) { return prev.size === 0 ? prev : new Set(); }); return; } var totalWidth = widths.reduce(function (sum, w) { return sum + w; }, 0); if (totalWidth <= availableWidth) { setCollapsedIndices(function (prev) { return prev.size === 0 ? prev : new Set(); }); return; } var usedWidth = totalWidth; var newCollapsed = new Set(); var _iterator2 = _createForOfIteratorHelper(getMiddleOutCollapseOrder(itemCount)), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _widths$index; var index = _step2.value; if (newCollapsed.size === 0) { usedWidth += ELLIPSIS_WIDTH_ESTIMATE; } newCollapsed.add(index); usedWidth -= (_widths$index = widths[index]) !== null && _widths$index !== void 0 ? _widths$index : 0; if (usedWidth <= availableWidth) { break; } } // At the smallest sizes, even first + ellipsis + last may not fit. // In that case, also collapse the first item so only ellipsis + last is shown. // Note: this branch is only reachable when itemCount > 2 (the early return above // handles itemCount <= 2), so collapsing index 0 here always leaves at least one // visible item (the last) plus the ellipsis trigger. } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (usedWidth > availableWidth) { newCollapsed.add(0); } setCollapsedIndices(function (prev) { return setsEqual(prev, newCollapsed) ? prev : newCollapsed; }); }, [itemCount]); var registerItem = (0, _react.useCallback)(function (index) { return function (el) { itemRefs.current[index] = el; }; }, []); var observerRef = (0, _react.useRef)(null); var registerContainer = (0, _react.useCallback)(function (el) { var _observerRef$current; // Disconnect any existing observer (_observerRef$current = observerRef.current) === null || _observerRef$current === void 0 || _observerRef$current.disconnect(); containerRef.current = el; if (!el) { return; } if (typeof ResizeObserver === 'undefined') { return; } // Set up ResizeObserver immediately when container mounts var observer = new ResizeObserver(function () { recalculate(); }); observer.observe(el); observerRef.current = observer; }, [recalculate]); (0, _react.useEffect)(function () { if (naturalWidthsReadyState) { recalculate(); } }, [naturalWidthsReadyState, recalculate]); // Cleanup observer on unmount (0, _react.useEffect)(function () { return function () { var _observerRef$current2; (_observerRef$current2 = observerRef.current) === null || _observerRef$current2 === void 0 || _observerRef$current2.disconnect(); }; }, []); (0, _react.useEffect)(function () { naturalWidthsReady.current = false; naturalWidths.current = []; setNaturalWidthsReadyState(false); setCollapsedIndices(new Set()); }, [itemCount]); return { collapsedIndices: collapsedIndices, naturalWidthsReady: naturalWidthsReadyState, registerItem: registerItem, registerContainer: registerContainer }; }