UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

163 lines (154 loc) 5.81 kB
/** * 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. */ /** * 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; } /** * Options for updating the overflow handler. * Determines which items should be visible and which should be hidden * based on the container size, item sizes, and other constraints. */ /** * 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.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(_ref) { let { container, items, offset, sizes, fixedSizes, offsetSize, maxVisibleItems, dimension, onChange, previousHiddenItems = [] } = _ref; 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; let accumulated = 0; for (let i = 0; i < items.length; i++) { const size = sizes[i]; if (accumulated + size + totalFixedSize <= available && (!maxVisibleItems || visibleItems.length < maxVisibleItems)) { visibleItems.push(items[i]); accumulated += size; } else { hiddenItems.push(items[i]); } } } 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; } /** * Options for initializing an overflow handler. */ /** * Represents an instance of an overflow handler. */ function createOverflowHandler(_ref2) { let { container, maxVisibleItems, onChange, dimension = 'width' } = _ref2; // 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 }); } const resizeObserver = new ResizeObserver(() => { requestAnimationFrame(update); }); resizeObserver.observe(container); requestAnimationFrame(update); // Initial update return { disconnect() { resizeObserver.disconnect(); } }; } export { createOverflowHandler, getSize, updateOverflowHandler };