@carbon/ibm-products
Version:
Carbon for IBM Products
114 lines (109 loc) • 3.9 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
;
var React = require('react');
var useResizeObserver = require('../useResizeObserver.js');
function useOverflowItems() {
let items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let containerRef = arguments.length > 1 ? arguments[1] : undefined;
let offsetRef = arguments.length > 2 ? arguments[2] : undefined;
let maxItems = arguments.length > 3 ? arguments[3] : undefined;
let onChange = arguments.length > 4 ? arguments[4] : undefined;
const [remainingWidth, setRemainingWidth] = React.useState(0);
const offsetWidthRef = React.useRef(0);
const itemsRef = React.useRef(null);
const visibleItemCount = React.useRef(0);
const minWidthRef = React.useRef(0);
const requiredWidthRef = React.useRef(0);
const handleResize = () => {
if (containerRef?.current) {
offsetWidthRef.current = offsetRef?.current?.offsetWidth || 0;
const usableWidth = containerRef.current.offsetWidth - offsetWidthRef.current;
setRemainingWidth(usableWidth);
}
};
const offsetRefHandler = node => {
if (node && containerRef?.current) {
offsetWidthRef.current = node.offsetWidth;
const usableWidth = containerRef.current.offsetWidth - offsetWidthRef.current;
setRemainingWidth(usableWidth);
}
return node;
};
useResizeObserver.useResizeObserver(containerRef, handleResize);
const getMap = () => {
if (!itemsRef.current) {
itemsRef.current = new Map();
}
return itemsRef.current;
};
const requiredWidth = items?.slice(0, maxItems)?.reduce((acc, item) => acc + (getMap().get(item.id) || 0), 0);
const itemRefHandler = (id, node) => {
const map = getMap();
if (node) {
const style = getComputedStyle?.(node);
const totalWidth = node.offsetWidth + parseInt(style.marginLeft) + parseInt(style.marginRight);
map.set(id, totalWidth);
}
return () => {
map.delete(id);
};
};
const getVisibleItems = () => {
if (!containerRef) {
return items;
}
const map = getMap();
let maxReached = false;
let accumulatedWidth = 0;
let includeOffset = false;
if (maxItems) {
includeOffset = requiredWidth + offsetWidthRef?.current > requiredWidth;
} else {
includeOffset = requiredWidth > remainingWidth + offsetWidthRef?.current;
}
return items.slice(0, maxItems).reduce((prev, cur) => {
if (maxReached) {
return prev;
}
const itemWidth = map.get(cur.id) || 0;
let willFit = accumulatedWidth + itemWidth <= remainingWidth;
if (!includeOffset) {
willFit = accumulatedWidth + itemWidth <= remainingWidth + offsetWidthRef?.current;
}
if (willFit) {
accumulatedWidth += itemWidth;
prev.push(cur);
} else {
maxReached = true;
}
return prev;
}, []);
};
const visibleItems = getVisibleItems();
const hiddenItems = items.slice(visibleItems?.length);
// only call the change handler when the number of visible items has changed
if (visibleItems?.length !== visibleItemCount.current || remainingWidth !== minWidthRef.current || requiredWidth !== requiredWidthRef.current) {
visibleItemCount.current = visibleItems?.length;
minWidthRef.current = remainingWidth;
requiredWidthRef.current = requiredWidth;
const firstItemKey = getMap()?.keys()?.next()?.value || '';
const firstItemWidth = getMap()?.get(firstItemKey) || 0;
onChange?.({
hiddenItems,
minWidth: remainingWidth,
maxWidth: requiredWidth + firstItemWidth
});
}
return {
visibleItems,
itemRefHandler,
hiddenItems,
offsetRefHandler
};
}
exports.useOverflowItems = useOverflowItems;