127 lines
6.6 KiB
JavaScript
127 lines
6.6 KiB
JavaScript
define(["require", "exports", "react", "@fluentui/react-hooks", "@fluentui/utilities", "./observeResize"], function (require, exports, React, react_hooks_1, utilities_1, observeResize_1) {
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.useOverflow = void 0;
|
|
/**
|
|
* Track whether any items don't fit within their container, and move them to the overflow menu.
|
|
* Items are moved into the overflow menu from back to front, excluding pinned items.
|
|
*
|
|
* The overflow menu button must be the last sibling of all of the items that can be put into the overflow, and it
|
|
* must be hooked up to the `setMenuButtonRef` setter function that's returned by `useOverflow`:
|
|
* ```ts
|
|
* const overflow = useOverflow(...);
|
|
* ```
|
|
* ```jsx
|
|
* <Container>
|
|
* <Item /> // Index 0
|
|
* <Item /> // Index 1
|
|
* ...
|
|
* <Button ref={overflow.setMenuButtonRef} /> // Can be any React.Component or HTMLElement
|
|
* </Container>
|
|
* ```
|
|
*/
|
|
var useOverflow = function (_a) {
|
|
var onOverflowItemsChanged = _a.onOverflowItemsChanged, rtl = _a.rtl, pinnedIndex = _a.pinnedIndex;
|
|
var updateOverflowRef = React.useRef();
|
|
var containerWidthRef = React.useRef();
|
|
// Attach a resize observer to the container
|
|
var containerRef = (0, react_hooks_1.useRefEffect)(function (container) {
|
|
var cleanupObserver = (0, observeResize_1.observeResize)(container, function (entries) {
|
|
containerWidthRef.current = entries ? entries[0].contentRect.width : container.clientWidth;
|
|
if (updateOverflowRef.current) {
|
|
updateOverflowRef.current();
|
|
}
|
|
});
|
|
return function () {
|
|
cleanupObserver();
|
|
containerWidthRef.current = undefined;
|
|
};
|
|
});
|
|
var menuButtonRef = (0, react_hooks_1.useRefEffect)(function (menuButton) {
|
|
containerRef(menuButton.parentElement);
|
|
return function () { return containerRef(null); };
|
|
});
|
|
(0, react_hooks_1.useIsomorphicLayoutEffect)(function () {
|
|
var container = containerRef.current;
|
|
var menuButton = menuButtonRef.current;
|
|
if (!container || !menuButton) {
|
|
return;
|
|
}
|
|
// items contains the container's children, excluding the overflow menu button itself
|
|
var items = [];
|
|
for (var i = 0; i < container.children.length; i++) {
|
|
var item = container.children[i];
|
|
if (item instanceof HTMLElement && item !== menuButton) {
|
|
items.push(item);
|
|
}
|
|
}
|
|
// Keep track of the minimum width of the container to fit each child index.
|
|
// This cache is an integral part of the algorithm and not just a performance optimization: it allows us to
|
|
// recalculate the overflowIndex on subsequent resizes even if some items are already inside the overflow.
|
|
var minContainerWidth = [];
|
|
var extraWidth = 0; // The accumulated width of items that don't move into the overflow
|
|
updateOverflowRef.current = function () {
|
|
var containerWidth = containerWidthRef.current;
|
|
if (containerWidth === undefined) {
|
|
return;
|
|
}
|
|
// Iterate the items in reverse order until we find one that fits within the bounds of the container
|
|
for (var i = items.length - 1; i >= 0; i--) {
|
|
// Calculate the min container width for this item if we haven't done so yet
|
|
if (minContainerWidth[i] === undefined) {
|
|
var itemOffsetEnd = rtl ? containerWidth - items[i].offsetLeft : items[i].offsetLeft + items[i].offsetWidth;
|
|
// If the item after this one is pinned, reserve space for it
|
|
if (i + 1 < items.length && i + 1 === pinnedIndex) {
|
|
// Use distance between the end of the previous item and this one (rather than the
|
|
// pinned item's offsetWidth), to account for any margin between the items.
|
|
extraWidth = minContainerWidth[i + 1] - itemOffsetEnd;
|
|
}
|
|
// Reserve space for the menu button after the first item was added to the overflow
|
|
if (i === items.length - 2) {
|
|
extraWidth += menuButton.offsetWidth;
|
|
}
|
|
minContainerWidth[i] = itemOffsetEnd + extraWidth;
|
|
}
|
|
if (containerWidth > minContainerWidth[i]) {
|
|
setOverflowIndex(i + 1);
|
|
return;
|
|
}
|
|
}
|
|
// If we got here, nothing fits outside the overflow
|
|
setOverflowIndex(0);
|
|
};
|
|
var prevOverflowIndex = items.length;
|
|
var setOverflowIndex = function (overflowIndex) {
|
|
if (prevOverflowIndex !== overflowIndex) {
|
|
prevOverflowIndex = overflowIndex;
|
|
onOverflowItemsChanged(overflowIndex, items.map(function (ele, index) { return ({
|
|
ele: ele,
|
|
isOverflowing: index >= overflowIndex && index !== pinnedIndex,
|
|
}); }));
|
|
}
|
|
};
|
|
var cancelAnimationFrame = undefined;
|
|
// If the container width is already known from a previous render, update the overflow with its width.
|
|
// Do this in an animation frame to avoid forcing layout to happen early.
|
|
if (containerWidthRef.current !== undefined) {
|
|
var win_1 = (0, utilities_1.getWindow)(container);
|
|
if (win_1) {
|
|
var animationFrameId_1 = win_1.requestAnimationFrame(updateOverflowRef.current);
|
|
cancelAnimationFrame = function () { return win_1.cancelAnimationFrame(animationFrameId_1); };
|
|
}
|
|
}
|
|
return function () {
|
|
if (cancelAnimationFrame) {
|
|
cancelAnimationFrame();
|
|
}
|
|
// On cleanup, need to remove all items from the overflow
|
|
// so they don't have stale properties on the next render
|
|
setOverflowIndex(items.length);
|
|
updateOverflowRef.current = undefined;
|
|
};
|
|
});
|
|
return { menuButtonRef: menuButtonRef };
|
|
};
|
|
exports.useOverflow = useOverflow;
|
|
});
|
|
//# sourceMappingURL=useOverflow.js.map
|