@carbon/ibm-products-web-components
Version:
Carbon for IBM Products Web Components
157 lines (154 loc) • 6.43 kB
JavaScript
/**
* Copyright IBM Corp. 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Copyright IBM Corp. 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.
*/
// TODO: delete this file after overflowHandler from carbon/utils gets updated
/**
* Calculates the size (width or height) of a given HTML element.
*
* This function performs an expensive calculation by temporarily changing the
* display style of the element if it is not currently visible. It then uses
* `getBoundingClientRect` to retrieve the size of the element.
*
* @param el - The HTML element whose size is to be calculated.
* @param dimension - The dimension to measure ('width' or 'height').
* @returns The size of the element in pixels. Returns 0 if the element is not provided.
*/
function getSize(el, dimension) {
if (!el) {
return 0;
}
const originalDisplay = el.style.display;
if (!el.offsetParent && getComputedStyle(el).display === 'none') {
el.style.display = 'inline-block';
}
let size = el.getBoundingClientRect()[dimension];
el.style.display = originalDisplay;
const computedStyles = getComputedStyle(el);
size =
dimension === 'width'
? size +
parseInt(computedStyles.paddingLeft) +
parseInt(computedStyles.paddingRight) +
parseInt(computedStyles.marginLeft) +
parseInt(computedStyles.marginRight)
: size +
parseInt(computedStyles.paddingTop) +
parseInt(computedStyles.paddingBottom) +
parseInt(computedStyles.marginTop) +
parseInt(computedStyles.marginBottom);
return size;
}
/**
* Updates the overflow handler by determining which items should be visible and which should be hidden.
*
* @param options - Configuration options for updating the overflow handler.
* @param options.container - Container for overflowing
* @param options.items - Child elements within container
* @param options.offset - Children with data-offset attribute
* @param options.sizes - Sizes of child elements
* @param options.fixedSizes - Fixed sizes of child elements with data-fixed attribute
* @param options.offsetSize - Total offset size
* @param options.offsetValue - Additional offset
* @param options.maxVisibleItems - Max visible items
* @param options.dimension - width | height used to measure overflow
* @param options.onChange - onChange callback
* @param options.previousHiddenItems - Array of previously hidden items
* @returns An array of hidden items after the update.
*/
function updateOverflowHandler({ container, items, offset, sizes, fixedSizes, offsetSize, maxVisibleItems, dimension, onChange, previousHiddenItems = [], offsetValue = 0, }) {
const containerSize = dimension === 'width' ? container.clientWidth : container.clientHeight;
let visibleItems = [];
let hiddenItems = [];
const totalSize = sizes.reduce((sum, size) => sum + size, 0);
const totalFixedSize = fixedSizes.reduce((sum, size) => sum + size, 0);
if (totalSize + totalFixedSize <= containerSize) {
visibleItems = maxVisibleItems
? items.slice(0, maxVisibleItems)
: [...items];
hiddenItems = maxVisibleItems ? items.slice(maxVisibleItems) : [];
}
else {
const available = containerSize - offsetSize - totalFixedSize - offsetValue;
let accumulated = 0;
let breakIndex = items.length;
for (let i = 0; i < items.length; i++) {
const size = sizes[i];
if (accumulated + size <= available &&
(!maxVisibleItems || visibleItems.length < maxVisibleItems)) {
visibleItems.push(items[i]);
accumulated += size;
}
else {
breakIndex = i;
break;
}
}
hiddenItems = items.slice(breakIndex);
}
if (previousHiddenItems.length === hiddenItems.length &&
previousHiddenItems.every((item, index) => item === hiddenItems[index])) {
return previousHiddenItems;
}
visibleItems.forEach((item) => item.removeAttribute('data-hidden'));
hiddenItems.forEach((item) => item.setAttribute('data-hidden', ''));
if (offset) {
offset.toggleAttribute('data-hidden', hiddenItems.length === 0);
}
onChange(visibleItems, hiddenItems);
return hiddenItems;
}
function createOverflowHandler({ container, maxVisibleItems, onChange, dimension = 'width', offsetValue = 0, }) {
// Error handling
if (!(container instanceof HTMLElement)) {
throw new Error('container must be an HTMLElement');
}
if (typeof onChange !== 'function') {
throw new Error('onChange must be a function');
}
if (maxVisibleItems !== undefined &&
(!Number.isInteger(maxVisibleItems) || maxVisibleItems <= 0)) {
throw new Error('maxVisibleItems must be a positive integer');
}
const children = Array.from(container.children);
const offset = children.find((item) => item.hasAttribute('data-offset'));
const fixedItems = children.filter((item) => item.hasAttribute('data-fixed'));
const items = children.filter((item) => item !== offset && !fixedItems.includes(item));
const fixedSizes = fixedItems.map((item) => getSize(item, dimension));
const sizes = items.map((item) => getSize(item, dimension));
const offsetSize = getSize(offset, dimension);
let previousHiddenItems = [];
function update() {
previousHiddenItems = updateOverflowHandler({
container,
items,
offset,
sizes,
fixedSizes,
offsetSize,
maxVisibleItems,
dimension,
onChange,
previousHiddenItems,
offsetValue,
});
}
const resizeObserver = new ResizeObserver(() => requestAnimationFrame(update));
resizeObserver.observe(container);
requestAnimationFrame(update); // Initial update
return {
disconnect() {
resizeObserver.disconnect();
},
};
}
export { createOverflowHandler, getSize, updateOverflowHandler };
//# sourceMappingURL=overflowHandler.js.map