react-responsive-overflow-list
Advanced tools
+2
-1
@@ -267,3 +267,4 @@ //#region rolldown:runtime | ||
| flexWrap: "wrap", | ||
| contain: "layout style" | ||
| contain: "layout style", | ||
| minWidth: 0 | ||
| }; | ||
@@ -270,0 +271,0 @@ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":["elementRef: React.RefObject<T>","flushImmediately: boolean","refValue: T","useIsoLayoutEffect: typeof useEffect","useLayoutEffect","useEffect","nodes: HTMLElement[]","result: Record<number, NodePosition>","lastRowKey: number | undefined","containerRef: React.RefObject<HTMLElement | null>","overflowRef: React.RefObject<HTMLElement | null>","DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties","DefaultOverflowElement","props: DefaultOverflowElementProps<T>","ref: React.ForwardedRef<HTMLDivElement>","OverflowList","props: OverflowListProps<T>","forwardedRef: React.Ref<HTMLElement>","containerStyles: React.CSSProperties","React","OverflowList: OverflowListComponent","DEFAULT_CONTAINER_STYLES: React.CSSProperties"],"sources":["../src/hooks/useResizeObserver.ts","../src/hooks/useForkRef.tsx","../src/hooks/useIsoLayoutEffect.ts","../src/utils/index.ts","../src/components/DefaultOverflowMenu.tsx","../src/components/OverflowList.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushSync } from \"react-dom\";\n\nexport interface ResizeObserverDimensions {\n width: number;\n height: number;\n contentWidth: number;\n contentHeight: number;\n}\n\nexport function useResizeObserver<T extends HTMLElement | null>(\n elementRef: React.RefObject<T>,\n flushImmediately: boolean = false\n): ResizeObserverDimensions | null {\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const [dimensions, setDimensions] = useState<ResizeObserverDimensions | null>(null);\n\n // Initialize resize observer\n useEffect(() => {\n if (!elementRef.current) return;\n\n // Create resize observer\n resizeObserverRef.current = new ResizeObserver((entries) => {\n if (entries[0]) {\n const entry = entries[0];\n const updateDimensions = () => {\n setDimensions({\n width: entry.borderBoxSize[0]?.inlineSize ?? entry.target.clientWidth,\n height: entry.borderBoxSize[0]?.blockSize ?? entry.target.clientHeight,\n contentWidth: entry.contentRect.width,\n contentHeight: entry.contentRect.height,\n });\n };\n\n if (flushImmediately) {\n // Update DOM immediately using flushSync\n flushSync(updateDimensions);\n } else {\n // Use requestAnimationFrame to defer this update and avoid forced reflow\n requestAnimationFrame(updateDimensions);\n }\n }\n });\n\n resizeObserverRef.current.observe(elementRef.current);\n\n return () => {\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n resizeObserverRef.current = null;\n }\n };\n }, [elementRef.current, flushImmediately]);\n\n return dimensions;\n}\n","// Utility to combine refs (similar to MUI's useForkRef but simpler)\nimport React from \"react\";\n\nexport const useForkRef = <T,>(...refs: (React.Ref<T> | null)[]): React.RefCallback<T> | null => {\n return React.useMemo(() => {\n if (refs.every((ref) => ref == null)) {\n return null;\n }\n return (refValue: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") {\n ref(refValue);\n } else if (ref) {\n (ref as React.RefObject<T>).current = refValue;\n }\n });\n };\n }, refs);\n};\n","import { useEffect, useLayoutEffect } from \"react\";\n\nexport const useIsoLayoutEffect: typeof useEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import React from \"react\";\n\n/**\n * Groups HTML elements by their vertical position (top coordinate)\n * and includes bottom position information\n */\nexport interface NodePosition {\n elements: Set<HTMLElement>;\n bottom: number;\n top: number;\n}\n\nexport function groupNodesByTopPosition(nodes: HTMLElement[]): Record<number, NodePosition> {\n if (nodes.length === 0) return {};\n\n const result: Record<number, NodePosition> = {};\n let lastRowKey: number | undefined;\n\n nodes.forEach((node) => {\n const rect = node.getBoundingClientRect();\n const top = Math.round(rect.top);\n const bottom = Math.round(rect.bottom);\n\n // Check if this element overlaps vertically with the last row\n const lastRow = lastRowKey !== undefined ? result[lastRowKey] : undefined;\n if (lastRow && top < lastRow.bottom && bottom > lastRow.top) {\n lastRow.top = Math.min(lastRow.top, top);\n lastRow.bottom = Math.max(lastRow.bottom, bottom);\n lastRow.elements.add(node);\n } else {\n result[top] = {\n elements: new Set<HTMLElement>(),\n bottom: bottom,\n top: top,\n };\n result[top].elements.add(node);\n lastRowKey = top;\n }\n });\n\n return result;\n}\n\n/**\n * Helper function to get row information from container\n * Returns itemsSizesMap, rowPositions, and children or null if the container is not available\n */\nexport function getRowPositionsData(\n containerRef: React.RefObject<HTMLElement | null>,\n overflowRef: React.RefObject<HTMLElement | null>\n): {\n itemsSizesMap: Record<number, NodePosition>;\n rowPositions: number[];\n children: HTMLElement[];\n} | null {\n if (!containerRef.current) return null;\n\n const container = containerRef.current;\n const children = Array.from(container.children).filter((child) => overflowRef.current !== child) as HTMLElement[];\n\n if (children.length === 0) return null;\n\n // Group elements by their vertical position (rows)\n const itemsSizesMap = groupNodesByTopPosition(children);\n\n // Get all the vertical positions (rows)\n const rowPositions = Object.keys(itemsSizesMap).map(Number);\n\n return { itemsSizesMap, rowPositions, children };\n}\n","import React, { useState } from \"react\";\nimport { OverflowElementProps } from \"./OverflowList\";\n\nconst DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n border: \"1px solid #ccc\",\n padding: \"4px 8px\",\n fontSize: \"12px\",\n};\n\n/**\n * Simple default overflow menu component that displays hidden items in a basic dropdown\n */\n\ninterface DefaultOverflowElementProps<T> extends OverflowElementProps<T> {}\n\nexport const DefaultOverflowElement = React.forwardRef(function DefaultOverflowElement<T>(\n props: DefaultOverflowElementProps<T>,\n ref: React.ForwardedRef<HTMLDivElement>\n) {\n const { items } = props;\n const count = items.length;\n\n return (\n <div ref={ref} style={DEFAULT_OVERFLOW_BUTTON_STYLES}>\n {`+${count} more`}\n </div>\n );\n}) as <T>(props: DefaultOverflowElementProps<T> & { ref?: React.Ref<HTMLDivElement> }) => React.ReactElement;\n","import React, { useRef, useState } from \"react\";\nimport { useForkRef, useIsoLayoutEffect, useResizeObserver } from \"../hooks\";\nimport { getRowPositionsData } from \"../utils\";\nimport { DefaultOverflowElement } from \"./DefaultOverflowMenu\";\n\ntype BaseComponentProps = React.HTMLAttributes<HTMLElement>;\n\nexport type RenderItemVisibilityMeta = {\n visible: boolean;\n index: number;\n};\n\ntype BaseOverflowListProps<T> = BaseComponentProps & {\n // Polymorphic component prop - allows changing the host element\n as?: React.ElementType;\n\n // would define the maximum number of rows that can be visible (default is 1)\n maxRows?: number;\n\n // would define the maximum number of items that can be visible (default is 100)\n maxVisibleItems?: number;\n\n // would define the overflow item renderer, applied only to overflow items (default is the same as renderItem)\n renderOverflowItem?: (item: NoInfer<T>, index: number) => React.ReactNode;\n // overflow renderer, applied only to overflow items (default is a dropdown menu - DefaultOverflowMenu component)\n renderOverflow?: (items: NoInfer<T>[]) => React.ReactNode;\n // would define the props to pass to the overflow indicator button\n renderOverflowProps?: Partial<OverflowElementProps<T>>;\n\n // after the container dimensions change, flush the state immediately (default is true)\n // if true, using flushSync to update the state immediately - this can affect performance but avoid flickering\n // if false, using requestAnimationFrame to update the state - this avoid forced reflow and improve performance\n flushImmediately?: boolean;\n\n // customize how each item is shown/hidden during measurement so you can keep custom elements mounted\n renderItemVisibility?: (node: React.ReactNode, meta: RenderItemVisibilityMeta) => React.ReactNode;\n};\n\ntype OverflowListWithItems<T> = BaseOverflowListProps<T> & {\n // would define the items to render in the list\n items: T[];\n // would define the default item renderer, applied both to visible and overflow items\n renderItem: (item: NoInfer<T>, index: number) => React.ReactNode;\n children?: never;\n};\n\ntype OverflowListWithChildren<T> = BaseOverflowListProps<T> & {\n children: React.ReactNode;\n items?: never;\n renderItem?: never;\n};\n\nexport type OverflowListProps<T> = OverflowListWithItems<T> | OverflowListWithChildren<T>;\n\nexport type OverflowListComponent = <T>(\n props: OverflowListProps<T> & { ref?: React.Ref<HTMLElement> }\n) => React.ReactElement;\n\nexport interface OverflowElementProps<T> {\n items: T[];\n}\n\n/**\n * Responsive container that shows as many items as can fit within maxRows,\n * hiding overflow items behind a configurable overflow renderer.\n * Automatically recalculates visible items on resize.\n *\n * Technical details:\n * Uses a three-phases approach:\n * 1. \"measuring\" renders all items to calculate positions,\n * 2. \"measuring overflow\" render all items fit in the container, try to add the overflow indicator item to the container. check if it opens a new row, if so, remove the last item from the last row.\n * 3. \"normal\" phase shows only what fits within constraints. (this is the stable state that we want to keep)\n */\nconst OverflowListComponent = React.memo(\n React.forwardRef(function OverflowList<T>(props: OverflowListProps<T>, forwardedRef: React.Ref<HTMLElement>) {\n const {\n as: Component = \"div\",\n children,\n // if items is not provided, use children as items\n items = React.Children.toArray(children),\n renderOverflow,\n // if renderItem is not provided, this component is used in the children pattern, means each item is simply a React.ReactNode\n renderItem = (item) => item as React.ReactNode,\n renderOverflowItem,\n renderOverflowProps,\n renderItemVisibility,\n maxRows = 1,\n maxVisibleItems = 100,\n flushImmediately = true,\n\n ...containerProps\n } = props;\n\n const [visibleCount, setVisibleCount] = useState(items.length);\n const [subtractCount, setSubtractCount] = useState(0);\n const [phase, setPhase] = useState<\"normal\" | \"measuring\" | \"measuring-overflow-indicator\">(\"normal\");\n\n const containerRef = useRef<HTMLElement>(null);\n const finalContainerRef = useForkRef(containerRef, forwardedRef);\n const finalVisibleCount = visibleCount - subtractCount;\n\n const overflowCount = items.length - finalVisibleCount;\n const showOverflow = overflowCount > 0 && phase !== \"measuring\";\n\n const finalRenderOverflow = renderOverflow?.(items.slice(finalVisibleCount) as T[]) ?? (\n <DefaultOverflowElement items={items.slice(finalVisibleCount) as T[]} {...renderOverflowProps} />\n );\n\n const overflowElement = showOverflow ? finalRenderOverflow : null;\n\n const overflowRef = useRef<HTMLElement>(null);\n // @ts-expect-error - ref is not exposed as type in jsx elements but it exists\n const finalOverflowRef = useForkRef(overflowRef, overflowElement?.ref);\n\n // Reset state when items change\n useIsoLayoutEffect(() => {\n setPhase(\"measuring\");\n setVisibleCount(items.length);\n setSubtractCount(0);\n }, [items.length, maxRows]);\n\n useIsoLayoutEffect(() => {\n // in measurement, evaluate results\n if (phase === \"measuring\") {\n countVisibleItems();\n setPhase(\"measuring-overflow-indicator\");\n }\n }, [phase]);\n\n useIsoLayoutEffect(() => {\n // After placing the overflow indicator, evaluate if it ends up opening a new row\n if (phase === \"measuring-overflow-indicator\") {\n const updateWasNeeded = updateOverflowIndicator();\n if (!updateWasNeeded) {\n setPhase(\"normal\");\n }\n }\n }, [phase, subtractCount]);\n\n // if the container dimensions change, re-measure\n const containerDims = useResizeObserver(containerRef, flushImmediately);\n useIsoLayoutEffect(() => {\n if (phase === \"normal\") {\n setPhase(\"measuring\");\n setSubtractCount(0);\n }\n }, [containerDims]);\n\n // Unified method that handles both growing and shrinking\n // this function is called in measuring phase, and it is used to measure how many items can fit in the container\n const countVisibleItems = () => {\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return;\n\n const { itemsSizesMap, rowPositions } = rowData;\n\n // edge case: if only 1 item is given, check if its width is bigger than the container width, if so set the maxRows to 0 (there is not enough space for the item, so we showing overflow indicator)\n if (items.length === 1) {\n const itemRef = itemsSizesMap[rowPositions[0]].elements.values().next().value;\n const containerWidth = containerRef.current?.getBoundingClientRect().width ?? 0;\n const itemWidth = itemRef?.getBoundingClientRect().width ?? 0;\n\n if (itemWidth > containerWidth) {\n setVisibleCount(0);\n } else setVisibleCount(1);\n return;\n }\n\n // Only take up to maxRows\n const visibleRowPositions = rowPositions.slice(0, maxRows);\n\n // only items in rows that conform to the maxRows constraint can be visible\n let fittingCount = visibleRowPositions.reduce((acc, position) => {\n return acc + itemsSizesMap[position].elements.size;\n }, 0);\n\n // Ensure we respect maxVisibleItems\n fittingCount = Math.min(fittingCount, maxVisibleItems);\n\n // Only update state if the number of visible items has changed\n setVisibleCount(fittingCount);\n };\n\n const updateOverflowIndicator = () => {\n // Nothing left to subtract—either we already hid every visible item or there were none to begin with.\n // Avoid looping indefinitely by exiting early.\n if (finalVisibleCount <= 0) {\n return false;\n }\n\n if (!overflowRef.current) return false;\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return false;\n\n const { rowPositions, itemsSizesMap } = rowData;\n\n const overflowRect = overflowRef.current.getBoundingClientRect();\n const overflowMiddleY = overflowRect.top + overflowRect.height / 2;\n const lastRowTop = rowPositions[rowPositions.length - 1];\n const lastRow = itemsSizesMap[lastRowTop];\n\n // if the overflow indicator item opens a new row(we check it by the middle of the item)\n if (overflowMiddleY > lastRow.bottom) {\n setSubtractCount((c) => c + 1);\n return true;\n }\n return false;\n };\n\n // Cloned overflow element that ensures ref is passed so we could measure dimensions on this element\n const clonedOverflowElement = overflowElement\n ? React.cloneElement(overflowElement as React.ReactElement<any>, {\n ref: finalOverflowRef,\n })\n : null;\n\n // Get the items to render based on current state\n\n // we can render only up to maxVisibleItems items and maxRows rows\n let finalItems = items;\n if (maxVisibleItems) {\n finalItems = finalItems.slice(0, maxVisibleItems);\n }\n\n const containerStyles: React.CSSProperties = {\n ...DEFAULT_CONTAINER_STYLES,\n ...containerProps.style,\n };\n\n const finalRenderItemVisibility =\n renderItemVisibility ??\n ((node, meta) => {\n // prefer react 19.2 new activity component to control the visibility of the item while don't forcing mount/unmount of the item\n // @ts-ignore\n const Activity = React?.Activity;\n if (Activity) {\n return (\n <Activity key={meta.index} mode={meta.visible ? \"visible\" : \"hidden\"}>\n {node}\n </Activity>\n );\n }\n\n // below react 19.2, simply return null if the item is not visible\n if (!meta.visible) return null;\n return <React.Fragment key={meta.index}>{node}</React.Fragment>;\n });\n\n return (\n <Component {...containerProps} ref={finalContainerRef} style={containerStyles}>\n {finalItems.map((item, index) => {\n const isVisible =\n phase ===\n // in measuring phase, show all items\n \"measuring\" ||\n // in 'normal' phase, show only the N items that fit\n index < finalVisibleCount;\n\n const itemComponent = renderItem(item as T, index);\n\n return finalRenderItemVisibility(itemComponent, { index, visible: isVisible });\n })}\n\n {clonedOverflowElement}\n </Component>\n );\n })\n);\n\nexport const OverflowList: OverflowListComponent = OverflowListComponent as OverflowListComponent;\n\nconst DEFAULT_CONTAINER_STYLES: React.CSSProperties = {\n display: \"flex\",\n flexWrap: \"wrap\",\n contain: \"layout style\",\n};\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.cjs","names":["elementRef: React.RefObject<T>","flushImmediately: boolean","refValue: T","useIsoLayoutEffect: typeof useEffect","useLayoutEffect","useEffect","nodes: HTMLElement[]","result: Record<number, NodePosition>","lastRowKey: number | undefined","containerRef: React.RefObject<HTMLElement | null>","overflowRef: React.RefObject<HTMLElement | null>","DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties","DefaultOverflowElement","props: DefaultOverflowElementProps<T>","ref: React.ForwardedRef<HTMLDivElement>","OverflowList","props: OverflowListProps<T>","forwardedRef: React.Ref<HTMLElement>","containerStyles: React.CSSProperties","React","OverflowList: OverflowListComponent","DEFAULT_CONTAINER_STYLES: React.CSSProperties"],"sources":["../src/hooks/useResizeObserver.ts","../src/hooks/useForkRef.tsx","../src/hooks/useIsoLayoutEffect.ts","../src/utils/index.ts","../src/components/DefaultOverflowMenu.tsx","../src/components/OverflowList.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushSync } from \"react-dom\";\n\nexport interface ResizeObserverDimensions {\n width: number;\n height: number;\n contentWidth: number;\n contentHeight: number;\n}\n\nexport function useResizeObserver<T extends HTMLElement | null>(\n elementRef: React.RefObject<T>,\n flushImmediately: boolean = false\n): ResizeObserverDimensions | null {\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const [dimensions, setDimensions] = useState<ResizeObserverDimensions | null>(null);\n\n // Initialize resize observer\n useEffect(() => {\n if (!elementRef.current) return;\n\n // Create resize observer\n resizeObserverRef.current = new ResizeObserver((entries) => {\n if (entries[0]) {\n const entry = entries[0];\n const updateDimensions = () => {\n setDimensions({\n width: entry.borderBoxSize[0]?.inlineSize ?? entry.target.clientWidth,\n height: entry.borderBoxSize[0]?.blockSize ?? entry.target.clientHeight,\n contentWidth: entry.contentRect.width,\n contentHeight: entry.contentRect.height,\n });\n };\n\n if (flushImmediately) {\n // Update DOM immediately using flushSync\n flushSync(updateDimensions);\n } else {\n // Use requestAnimationFrame to defer this update and avoid forced reflow\n requestAnimationFrame(updateDimensions);\n }\n }\n });\n\n resizeObserverRef.current.observe(elementRef.current);\n\n return () => {\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n resizeObserverRef.current = null;\n }\n };\n }, [elementRef.current, flushImmediately]);\n\n return dimensions;\n}\n","// Utility to combine refs (similar to MUI's useForkRef but simpler)\nimport React from \"react\";\n\nexport const useForkRef = <T,>(...refs: (React.Ref<T> | null)[]): React.RefCallback<T> | null => {\n return React.useMemo(() => {\n if (refs.every((ref) => ref == null)) {\n return null;\n }\n return (refValue: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") {\n ref(refValue);\n } else if (ref) {\n (ref as React.RefObject<T>).current = refValue;\n }\n });\n };\n }, refs);\n};\n","import { useEffect, useLayoutEffect } from \"react\";\n\nexport const useIsoLayoutEffect: typeof useEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import React from \"react\";\n\n/**\n * Groups HTML elements by their vertical position (top coordinate)\n * and includes bottom position information\n */\nexport interface NodePosition {\n elements: Set<HTMLElement>;\n bottom: number;\n top: number;\n}\n\nexport function groupNodesByTopPosition(nodes: HTMLElement[]): Record<number, NodePosition> {\n if (nodes.length === 0) return {};\n\n const result: Record<number, NodePosition> = {};\n let lastRowKey: number | undefined;\n\n nodes.forEach((node) => {\n const rect = node.getBoundingClientRect();\n const top = Math.round(rect.top);\n const bottom = Math.round(rect.bottom);\n\n // Check if this element overlaps vertically with the last row\n const lastRow = lastRowKey !== undefined ? result[lastRowKey] : undefined;\n if (lastRow && top < lastRow.bottom && bottom > lastRow.top) {\n lastRow.top = Math.min(lastRow.top, top);\n lastRow.bottom = Math.max(lastRow.bottom, bottom);\n lastRow.elements.add(node);\n } else {\n result[top] = {\n elements: new Set<HTMLElement>(),\n bottom: bottom,\n top: top,\n };\n result[top].elements.add(node);\n lastRowKey = top;\n }\n });\n\n return result;\n}\n\n/**\n * Helper function to get row information from container\n * Returns itemsSizesMap, rowPositions, and children or null if the container is not available\n */\nexport function getRowPositionsData(\n containerRef: React.RefObject<HTMLElement | null>,\n overflowRef: React.RefObject<HTMLElement | null>\n): {\n itemsSizesMap: Record<number, NodePosition>;\n rowPositions: number[];\n children: HTMLElement[];\n} | null {\n if (!containerRef.current) return null;\n\n const container = containerRef.current;\n const children = Array.from(container.children).filter((child) => overflowRef.current !== child) as HTMLElement[];\n\n if (children.length === 0) return null;\n\n // Group elements by their vertical position (rows)\n const itemsSizesMap = groupNodesByTopPosition(children);\n\n // Get all the vertical positions (rows)\n const rowPositions = Object.keys(itemsSizesMap).map(Number);\n\n return { itemsSizesMap, rowPositions, children };\n}\n","import React, { useState } from \"react\";\nimport { OverflowElementProps } from \"./OverflowList\";\n\nconst DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n border: \"1px solid #ccc\",\n padding: \"4px 8px\",\n fontSize: \"12px\",\n};\n\n/**\n * Simple default overflow menu component that displays hidden items in a basic dropdown\n */\n\ninterface DefaultOverflowElementProps<T> extends OverflowElementProps<T> {}\n\nexport const DefaultOverflowElement = React.forwardRef(function DefaultOverflowElement<T>(\n props: DefaultOverflowElementProps<T>,\n ref: React.ForwardedRef<HTMLDivElement>\n) {\n const { items } = props;\n const count = items.length;\n\n return (\n <div ref={ref} style={DEFAULT_OVERFLOW_BUTTON_STYLES}>\n {`+${count} more`}\n </div>\n );\n}) as <T>(props: DefaultOverflowElementProps<T> & { ref?: React.Ref<HTMLDivElement> }) => React.ReactElement;\n","import React, { useRef, useState } from \"react\";\nimport { useForkRef, useIsoLayoutEffect, useResizeObserver } from \"../hooks\";\nimport { getRowPositionsData } from \"../utils\";\nimport { DefaultOverflowElement } from \"./DefaultOverflowMenu\";\n\ntype BaseComponentProps = React.HTMLAttributes<HTMLElement>;\n\nexport type RenderItemVisibilityMeta = {\n visible: boolean;\n index: number;\n};\n\ntype BaseOverflowListProps<T> = BaseComponentProps & {\n // Polymorphic component prop - allows changing the host element\n as?: React.ElementType;\n\n // would define the maximum number of rows that can be visible (default is 1)\n maxRows?: number;\n\n // would define the maximum number of items that can be visible (default is 100)\n maxVisibleItems?: number;\n\n // would define the overflow item renderer, applied only to overflow items (default is the same as renderItem)\n renderOverflowItem?: (item: NoInfer<T>, index: number) => React.ReactNode;\n // overflow renderer, applied only to overflow items (default is a dropdown menu - DefaultOverflowMenu component)\n renderOverflow?: (items: NoInfer<T>[]) => React.ReactNode;\n // would define the props to pass to the overflow indicator button\n renderOverflowProps?: Partial<OverflowElementProps<T>>;\n\n // after the container dimensions change, flush the state immediately (default is true)\n // if true, using flushSync to update the state immediately - this can affect performance but avoid flickering\n // if false, using requestAnimationFrame to update the state - this avoid forced reflow and improve performance\n flushImmediately?: boolean;\n\n // customize how each item is shown/hidden during measurement so you can keep custom elements mounted\n renderItemVisibility?: (node: React.ReactNode, meta: RenderItemVisibilityMeta) => React.ReactNode;\n};\n\ntype OverflowListWithItems<T> = BaseOverflowListProps<T> & {\n // would define the items to render in the list\n items: T[];\n // would define the default item renderer, applied both to visible and overflow items\n renderItem: (item: NoInfer<T>, index: number) => React.ReactNode;\n children?: never;\n};\n\ntype OverflowListWithChildren<T> = BaseOverflowListProps<T> & {\n children: React.ReactNode;\n items?: never;\n renderItem?: never;\n};\n\nexport type OverflowListProps<T> = OverflowListWithItems<T> | OverflowListWithChildren<T>;\n\nexport type OverflowListComponent = <T>(\n props: OverflowListProps<T> & { ref?: React.Ref<HTMLElement> }\n) => React.ReactElement;\n\nexport interface OverflowElementProps<T> {\n items: T[];\n}\n\n/**\n * Responsive container that shows as many items as can fit within maxRows,\n * hiding overflow items behind a configurable overflow renderer.\n * Automatically recalculates visible items on resize.\n *\n * Technical details:\n * Uses a three-phases approach:\n * 1. \"measuring\" renders all items to calculate positions,\n * 2. \"measuring overflow\" render all items fit in the container, try to add the overflow indicator item to the container. check if it opens a new row, if so, remove the last item from the last row.\n * 3. \"normal\" phase shows only what fits within constraints. (this is the stable state that we want to keep)\n */\nconst OverflowListComponent = React.memo(\n React.forwardRef(function OverflowList<T>(props: OverflowListProps<T>, forwardedRef: React.Ref<HTMLElement>) {\n const {\n as: Component = \"div\",\n children,\n // if items is not provided, use children as items\n items = React.Children.toArray(children),\n renderOverflow,\n // if renderItem is not provided, this component is used in the children pattern, means each item is simply a React.ReactNode\n renderItem = (item) => item as React.ReactNode,\n renderOverflowItem,\n renderOverflowProps,\n renderItemVisibility,\n maxRows = 1,\n maxVisibleItems = 100,\n flushImmediately = true,\n\n ...containerProps\n } = props;\n\n const [visibleCount, setVisibleCount] = useState(items.length);\n const [subtractCount, setSubtractCount] = useState(0);\n const [phase, setPhase] = useState<\"normal\" | \"measuring\" | \"measuring-overflow-indicator\">(\"normal\");\n\n const containerRef = useRef<HTMLElement>(null);\n const finalContainerRef = useForkRef(containerRef, forwardedRef);\n const finalVisibleCount = visibleCount - subtractCount;\n\n const overflowCount = items.length - finalVisibleCount;\n const showOverflow = overflowCount > 0 && phase !== \"measuring\";\n\n const finalRenderOverflow = renderOverflow?.(items.slice(finalVisibleCount) as T[]) ?? (\n <DefaultOverflowElement items={items.slice(finalVisibleCount) as T[]} {...renderOverflowProps} />\n );\n\n const overflowElement = showOverflow ? finalRenderOverflow : null;\n\n const overflowRef = useRef<HTMLElement>(null);\n // @ts-expect-error - ref is not exposed as type in jsx elements but it exists\n const finalOverflowRef = useForkRef(overflowRef, overflowElement?.ref);\n\n // Reset state when items change\n useIsoLayoutEffect(() => {\n setPhase(\"measuring\");\n setVisibleCount(items.length);\n setSubtractCount(0);\n }, [items.length, maxRows]);\n\n useIsoLayoutEffect(() => {\n // in measurement, evaluate results\n if (phase === \"measuring\") {\n countVisibleItems();\n setPhase(\"measuring-overflow-indicator\");\n }\n }, [phase]);\n\n useIsoLayoutEffect(() => {\n // After placing the overflow indicator, evaluate if it ends up opening a new row\n if (phase === \"measuring-overflow-indicator\") {\n const updateWasNeeded = updateOverflowIndicator();\n if (!updateWasNeeded) {\n setPhase(\"normal\");\n }\n }\n }, [phase, subtractCount]);\n\n // if the container dimensions change, re-measure\n const containerDims = useResizeObserver(containerRef, flushImmediately);\n useIsoLayoutEffect(() => {\n if (phase === \"normal\") {\n setPhase(\"measuring\");\n setSubtractCount(0);\n }\n }, [containerDims]);\n\n // Unified method that handles both growing and shrinking\n // this function is called in measuring phase, and it is used to measure how many items can fit in the container\n const countVisibleItems = () => {\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return;\n\n const { itemsSizesMap, rowPositions } = rowData;\n\n // edge case: if only 1 item is given, check if its width is bigger than the container width, if so set the maxRows to 0 (there is not enough space for the item, so we showing overflow indicator)\n if (items.length === 1) {\n const itemRef = itemsSizesMap[rowPositions[0]].elements.values().next().value;\n const containerWidth = containerRef.current?.getBoundingClientRect().width ?? 0;\n const itemWidth = itemRef?.getBoundingClientRect().width ?? 0;\n\n if (itemWidth > containerWidth) {\n setVisibleCount(0);\n } else setVisibleCount(1);\n return;\n }\n\n // Only take up to maxRows\n const visibleRowPositions = rowPositions.slice(0, maxRows);\n\n // only items in rows that conform to the maxRows constraint can be visible\n let fittingCount = visibleRowPositions.reduce((acc, position) => {\n return acc + itemsSizesMap[position].elements.size;\n }, 0);\n\n // Ensure we respect maxVisibleItems\n fittingCount = Math.min(fittingCount, maxVisibleItems);\n\n // Only update state if the number of visible items has changed\n setVisibleCount(fittingCount);\n };\n\n const updateOverflowIndicator = () => {\n // Nothing left to subtract—either we already hid every visible item or there were none to begin with.\n // Avoid looping indefinitely by exiting early.\n if (finalVisibleCount <= 0) {\n return false;\n }\n\n if (!overflowRef.current) return false;\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return false;\n\n const { rowPositions, itemsSizesMap } = rowData;\n\n const overflowRect = overflowRef.current.getBoundingClientRect();\n const overflowMiddleY = overflowRect.top + overflowRect.height / 2;\n const lastRowTop = rowPositions[rowPositions.length - 1];\n const lastRow = itemsSizesMap[lastRowTop];\n\n // if the overflow indicator item opens a new row(we check it by the middle of the item)\n if (overflowMiddleY > lastRow.bottom) {\n setSubtractCount((c) => c + 1);\n return true;\n }\n return false;\n };\n\n // Cloned overflow element that ensures ref is passed so we could measure dimensions on this element\n const clonedOverflowElement = overflowElement\n ? React.cloneElement(overflowElement as React.ReactElement<any>, {\n ref: finalOverflowRef,\n })\n : null;\n\n // Get the items to render based on current state\n\n // we can render only up to maxVisibleItems items and maxRows rows\n let finalItems = items;\n if (maxVisibleItems) {\n finalItems = finalItems.slice(0, maxVisibleItems);\n }\n\n const containerStyles: React.CSSProperties = {\n ...DEFAULT_CONTAINER_STYLES,\n ...containerProps.style,\n };\n\n const finalRenderItemVisibility =\n renderItemVisibility ??\n ((node, meta) => {\n // prefer react 19.2 new activity component to control the visibility of the item while don't forcing mount/unmount of the item\n // @ts-ignore\n const Activity = React?.Activity;\n if (Activity) {\n return (\n <Activity key={meta.index} mode={meta.visible ? \"visible\" : \"hidden\"}>\n {node}\n </Activity>\n );\n }\n\n // below react 19.2, simply return null if the item is not visible\n if (!meta.visible) return null;\n return <React.Fragment key={meta.index}>{node}</React.Fragment>;\n });\n\n return (\n <Component {...containerProps} ref={finalContainerRef} style={containerStyles}>\n {finalItems.map((item, index) => {\n const isVisible =\n phase ===\n // in measuring phase, show all items\n \"measuring\" ||\n // in 'normal' phase, show only the N items that fit\n index < finalVisibleCount;\n\n const itemComponent = renderItem(item as T, index);\n\n return finalRenderItemVisibility(itemComponent, { index, visible: isVisible });\n })}\n\n {clonedOverflowElement}\n </Component>\n );\n })\n);\n\nexport const OverflowList: OverflowListComponent = OverflowListComponent as OverflowListComponent;\n\nconst DEFAULT_CONTAINER_STYLES: React.CSSProperties = {\n display: \"flex\",\n flexWrap: \"wrap\",\n contain: \"layout style\",\n minWidth: 0,\n};\n"],"mappings":"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"} |
+2
-1
@@ -244,3 +244,4 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; | ||
| flexWrap: "wrap", | ||
| contain: "layout style" | ||
| contain: "layout style", | ||
| minWidth: 0 | ||
| }; | ||
@@ -247,0 +248,0 @@ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":["elementRef: React.RefObject<T>","flushImmediately: boolean","refValue: T","useIsoLayoutEffect: typeof useEffect","nodes: HTMLElement[]","result: Record<number, NodePosition>","lastRowKey: number | undefined","containerRef: React.RefObject<HTMLElement | null>","overflowRef: React.RefObject<HTMLElement | null>","DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties","DefaultOverflowElement","props: DefaultOverflowElementProps<T>","ref: React.ForwardedRef<HTMLDivElement>","OverflowList","props: OverflowListProps<T>","forwardedRef: React.Ref<HTMLElement>","containerStyles: React.CSSProperties","OverflowList: OverflowListComponent","DEFAULT_CONTAINER_STYLES: React.CSSProperties"],"sources":["../src/hooks/useResizeObserver.ts","../src/hooks/useForkRef.tsx","../src/hooks/useIsoLayoutEffect.ts","../src/utils/index.ts","../src/components/DefaultOverflowMenu.tsx","../src/components/OverflowList.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushSync } from \"react-dom\";\n\nexport interface ResizeObserverDimensions {\n width: number;\n height: number;\n contentWidth: number;\n contentHeight: number;\n}\n\nexport function useResizeObserver<T extends HTMLElement | null>(\n elementRef: React.RefObject<T>,\n flushImmediately: boolean = false\n): ResizeObserverDimensions | null {\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const [dimensions, setDimensions] = useState<ResizeObserverDimensions | null>(null);\n\n // Initialize resize observer\n useEffect(() => {\n if (!elementRef.current) return;\n\n // Create resize observer\n resizeObserverRef.current = new ResizeObserver((entries) => {\n if (entries[0]) {\n const entry = entries[0];\n const updateDimensions = () => {\n setDimensions({\n width: entry.borderBoxSize[0]?.inlineSize ?? entry.target.clientWidth,\n height: entry.borderBoxSize[0]?.blockSize ?? entry.target.clientHeight,\n contentWidth: entry.contentRect.width,\n contentHeight: entry.contentRect.height,\n });\n };\n\n if (flushImmediately) {\n // Update DOM immediately using flushSync\n flushSync(updateDimensions);\n } else {\n // Use requestAnimationFrame to defer this update and avoid forced reflow\n requestAnimationFrame(updateDimensions);\n }\n }\n });\n\n resizeObserverRef.current.observe(elementRef.current);\n\n return () => {\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n resizeObserverRef.current = null;\n }\n };\n }, [elementRef.current, flushImmediately]);\n\n return dimensions;\n}\n","// Utility to combine refs (similar to MUI's useForkRef but simpler)\nimport React from \"react\";\n\nexport const useForkRef = <T,>(...refs: (React.Ref<T> | null)[]): React.RefCallback<T> | null => {\n return React.useMemo(() => {\n if (refs.every((ref) => ref == null)) {\n return null;\n }\n return (refValue: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") {\n ref(refValue);\n } else if (ref) {\n (ref as React.RefObject<T>).current = refValue;\n }\n });\n };\n }, refs);\n};\n","import { useEffect, useLayoutEffect } from \"react\";\n\nexport const useIsoLayoutEffect: typeof useEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import React from \"react\";\n\n/**\n * Groups HTML elements by their vertical position (top coordinate)\n * and includes bottom position information\n */\nexport interface NodePosition {\n elements: Set<HTMLElement>;\n bottom: number;\n top: number;\n}\n\nexport function groupNodesByTopPosition(nodes: HTMLElement[]): Record<number, NodePosition> {\n if (nodes.length === 0) return {};\n\n const result: Record<number, NodePosition> = {};\n let lastRowKey: number | undefined;\n\n nodes.forEach((node) => {\n const rect = node.getBoundingClientRect();\n const top = Math.round(rect.top);\n const bottom = Math.round(rect.bottom);\n\n // Check if this element overlaps vertically with the last row\n const lastRow = lastRowKey !== undefined ? result[lastRowKey] : undefined;\n if (lastRow && top < lastRow.bottom && bottom > lastRow.top) {\n lastRow.top = Math.min(lastRow.top, top);\n lastRow.bottom = Math.max(lastRow.bottom, bottom);\n lastRow.elements.add(node);\n } else {\n result[top] = {\n elements: new Set<HTMLElement>(),\n bottom: bottom,\n top: top,\n };\n result[top].elements.add(node);\n lastRowKey = top;\n }\n });\n\n return result;\n}\n\n/**\n * Helper function to get row information from container\n * Returns itemsSizesMap, rowPositions, and children or null if the container is not available\n */\nexport function getRowPositionsData(\n containerRef: React.RefObject<HTMLElement | null>,\n overflowRef: React.RefObject<HTMLElement | null>\n): {\n itemsSizesMap: Record<number, NodePosition>;\n rowPositions: number[];\n children: HTMLElement[];\n} | null {\n if (!containerRef.current) return null;\n\n const container = containerRef.current;\n const children = Array.from(container.children).filter((child) => overflowRef.current !== child) as HTMLElement[];\n\n if (children.length === 0) return null;\n\n // Group elements by their vertical position (rows)\n const itemsSizesMap = groupNodesByTopPosition(children);\n\n // Get all the vertical positions (rows)\n const rowPositions = Object.keys(itemsSizesMap).map(Number);\n\n return { itemsSizesMap, rowPositions, children };\n}\n","import React, { useState } from \"react\";\nimport { OverflowElementProps } from \"./OverflowList\";\n\nconst DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n border: \"1px solid #ccc\",\n padding: \"4px 8px\",\n fontSize: \"12px\",\n};\n\n/**\n * Simple default overflow menu component that displays hidden items in a basic dropdown\n */\n\ninterface DefaultOverflowElementProps<T> extends OverflowElementProps<T> {}\n\nexport const DefaultOverflowElement = React.forwardRef(function DefaultOverflowElement<T>(\n props: DefaultOverflowElementProps<T>,\n ref: React.ForwardedRef<HTMLDivElement>\n) {\n const { items } = props;\n const count = items.length;\n\n return (\n <div ref={ref} style={DEFAULT_OVERFLOW_BUTTON_STYLES}>\n {`+${count} more`}\n </div>\n );\n}) as <T>(props: DefaultOverflowElementProps<T> & { ref?: React.Ref<HTMLDivElement> }) => React.ReactElement;\n","import React, { useRef, useState } from \"react\";\nimport { useForkRef, useIsoLayoutEffect, useResizeObserver } from \"../hooks\";\nimport { getRowPositionsData } from \"../utils\";\nimport { DefaultOverflowElement } from \"./DefaultOverflowMenu\";\n\ntype BaseComponentProps = React.HTMLAttributes<HTMLElement>;\n\nexport type RenderItemVisibilityMeta = {\n visible: boolean;\n index: number;\n};\n\ntype BaseOverflowListProps<T> = BaseComponentProps & {\n // Polymorphic component prop - allows changing the host element\n as?: React.ElementType;\n\n // would define the maximum number of rows that can be visible (default is 1)\n maxRows?: number;\n\n // would define the maximum number of items that can be visible (default is 100)\n maxVisibleItems?: number;\n\n // would define the overflow item renderer, applied only to overflow items (default is the same as renderItem)\n renderOverflowItem?: (item: NoInfer<T>, index: number) => React.ReactNode;\n // overflow renderer, applied only to overflow items (default is a dropdown menu - DefaultOverflowMenu component)\n renderOverflow?: (items: NoInfer<T>[]) => React.ReactNode;\n // would define the props to pass to the overflow indicator button\n renderOverflowProps?: Partial<OverflowElementProps<T>>;\n\n // after the container dimensions change, flush the state immediately (default is true)\n // if true, using flushSync to update the state immediately - this can affect performance but avoid flickering\n // if false, using requestAnimationFrame to update the state - this avoid forced reflow and improve performance\n flushImmediately?: boolean;\n\n // customize how each item is shown/hidden during measurement so you can keep custom elements mounted\n renderItemVisibility?: (node: React.ReactNode, meta: RenderItemVisibilityMeta) => React.ReactNode;\n};\n\ntype OverflowListWithItems<T> = BaseOverflowListProps<T> & {\n // would define the items to render in the list\n items: T[];\n // would define the default item renderer, applied both to visible and overflow items\n renderItem: (item: NoInfer<T>, index: number) => React.ReactNode;\n children?: never;\n};\n\ntype OverflowListWithChildren<T> = BaseOverflowListProps<T> & {\n children: React.ReactNode;\n items?: never;\n renderItem?: never;\n};\n\nexport type OverflowListProps<T> = OverflowListWithItems<T> | OverflowListWithChildren<T>;\n\nexport type OverflowListComponent = <T>(\n props: OverflowListProps<T> & { ref?: React.Ref<HTMLElement> }\n) => React.ReactElement;\n\nexport interface OverflowElementProps<T> {\n items: T[];\n}\n\n/**\n * Responsive container that shows as many items as can fit within maxRows,\n * hiding overflow items behind a configurable overflow renderer.\n * Automatically recalculates visible items on resize.\n *\n * Technical details:\n * Uses a three-phases approach:\n * 1. \"measuring\" renders all items to calculate positions,\n * 2. \"measuring overflow\" render all items fit in the container, try to add the overflow indicator item to the container. check if it opens a new row, if so, remove the last item from the last row.\n * 3. \"normal\" phase shows only what fits within constraints. (this is the stable state that we want to keep)\n */\nconst OverflowListComponent = React.memo(\n React.forwardRef(function OverflowList<T>(props: OverflowListProps<T>, forwardedRef: React.Ref<HTMLElement>) {\n const {\n as: Component = \"div\",\n children,\n // if items is not provided, use children as items\n items = React.Children.toArray(children),\n renderOverflow,\n // if renderItem is not provided, this component is used in the children pattern, means each item is simply a React.ReactNode\n renderItem = (item) => item as React.ReactNode,\n renderOverflowItem,\n renderOverflowProps,\n renderItemVisibility,\n maxRows = 1,\n maxVisibleItems = 100,\n flushImmediately = true,\n\n ...containerProps\n } = props;\n\n const [visibleCount, setVisibleCount] = useState(items.length);\n const [subtractCount, setSubtractCount] = useState(0);\n const [phase, setPhase] = useState<\"normal\" | \"measuring\" | \"measuring-overflow-indicator\">(\"normal\");\n\n const containerRef = useRef<HTMLElement>(null);\n const finalContainerRef = useForkRef(containerRef, forwardedRef);\n const finalVisibleCount = visibleCount - subtractCount;\n\n const overflowCount = items.length - finalVisibleCount;\n const showOverflow = overflowCount > 0 && phase !== \"measuring\";\n\n const finalRenderOverflow = renderOverflow?.(items.slice(finalVisibleCount) as T[]) ?? (\n <DefaultOverflowElement items={items.slice(finalVisibleCount) as T[]} {...renderOverflowProps} />\n );\n\n const overflowElement = showOverflow ? finalRenderOverflow : null;\n\n const overflowRef = useRef<HTMLElement>(null);\n // @ts-expect-error - ref is not exposed as type in jsx elements but it exists\n const finalOverflowRef = useForkRef(overflowRef, overflowElement?.ref);\n\n // Reset state when items change\n useIsoLayoutEffect(() => {\n setPhase(\"measuring\");\n setVisibleCount(items.length);\n setSubtractCount(0);\n }, [items.length, maxRows]);\n\n useIsoLayoutEffect(() => {\n // in measurement, evaluate results\n if (phase === \"measuring\") {\n countVisibleItems();\n setPhase(\"measuring-overflow-indicator\");\n }\n }, [phase]);\n\n useIsoLayoutEffect(() => {\n // After placing the overflow indicator, evaluate if it ends up opening a new row\n if (phase === \"measuring-overflow-indicator\") {\n const updateWasNeeded = updateOverflowIndicator();\n if (!updateWasNeeded) {\n setPhase(\"normal\");\n }\n }\n }, [phase, subtractCount]);\n\n // if the container dimensions change, re-measure\n const containerDims = useResizeObserver(containerRef, flushImmediately);\n useIsoLayoutEffect(() => {\n if (phase === \"normal\") {\n setPhase(\"measuring\");\n setSubtractCount(0);\n }\n }, [containerDims]);\n\n // Unified method that handles both growing and shrinking\n // this function is called in measuring phase, and it is used to measure how many items can fit in the container\n const countVisibleItems = () => {\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return;\n\n const { itemsSizesMap, rowPositions } = rowData;\n\n // edge case: if only 1 item is given, check if its width is bigger than the container width, if so set the maxRows to 0 (there is not enough space for the item, so we showing overflow indicator)\n if (items.length === 1) {\n const itemRef = itemsSizesMap[rowPositions[0]].elements.values().next().value;\n const containerWidth = containerRef.current?.getBoundingClientRect().width ?? 0;\n const itemWidth = itemRef?.getBoundingClientRect().width ?? 0;\n\n if (itemWidth > containerWidth) {\n setVisibleCount(0);\n } else setVisibleCount(1);\n return;\n }\n\n // Only take up to maxRows\n const visibleRowPositions = rowPositions.slice(0, maxRows);\n\n // only items in rows that conform to the maxRows constraint can be visible\n let fittingCount = visibleRowPositions.reduce((acc, position) => {\n return acc + itemsSizesMap[position].elements.size;\n }, 0);\n\n // Ensure we respect maxVisibleItems\n fittingCount = Math.min(fittingCount, maxVisibleItems);\n\n // Only update state if the number of visible items has changed\n setVisibleCount(fittingCount);\n };\n\n const updateOverflowIndicator = () => {\n // Nothing left to subtract—either we already hid every visible item or there were none to begin with.\n // Avoid looping indefinitely by exiting early.\n if (finalVisibleCount <= 0) {\n return false;\n }\n\n if (!overflowRef.current) return false;\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return false;\n\n const { rowPositions, itemsSizesMap } = rowData;\n\n const overflowRect = overflowRef.current.getBoundingClientRect();\n const overflowMiddleY = overflowRect.top + overflowRect.height / 2;\n const lastRowTop = rowPositions[rowPositions.length - 1];\n const lastRow = itemsSizesMap[lastRowTop];\n\n // if the overflow indicator item opens a new row(we check it by the middle of the item)\n if (overflowMiddleY > lastRow.bottom) {\n setSubtractCount((c) => c + 1);\n return true;\n }\n return false;\n };\n\n // Cloned overflow element that ensures ref is passed so we could measure dimensions on this element\n const clonedOverflowElement = overflowElement\n ? React.cloneElement(overflowElement as React.ReactElement<any>, {\n ref: finalOverflowRef,\n })\n : null;\n\n // Get the items to render based on current state\n\n // we can render only up to maxVisibleItems items and maxRows rows\n let finalItems = items;\n if (maxVisibleItems) {\n finalItems = finalItems.slice(0, maxVisibleItems);\n }\n\n const containerStyles: React.CSSProperties = {\n ...DEFAULT_CONTAINER_STYLES,\n ...containerProps.style,\n };\n\n const finalRenderItemVisibility =\n renderItemVisibility ??\n ((node, meta) => {\n // prefer react 19.2 new activity component to control the visibility of the item while don't forcing mount/unmount of the item\n // @ts-ignore\n const Activity = React?.Activity;\n if (Activity) {\n return (\n <Activity key={meta.index} mode={meta.visible ? \"visible\" : \"hidden\"}>\n {node}\n </Activity>\n );\n }\n\n // below react 19.2, simply return null if the item is not visible\n if (!meta.visible) return null;\n return <React.Fragment key={meta.index}>{node}</React.Fragment>;\n });\n\n return (\n <Component {...containerProps} ref={finalContainerRef} style={containerStyles}>\n {finalItems.map((item, index) => {\n const isVisible =\n phase ===\n // in measuring phase, show all items\n \"measuring\" ||\n // in 'normal' phase, show only the N items that fit\n index < finalVisibleCount;\n\n const itemComponent = renderItem(item as T, index);\n\n return finalRenderItemVisibility(itemComponent, { index, visible: isVisible });\n })}\n\n {clonedOverflowElement}\n </Component>\n );\n })\n);\n\nexport const OverflowList: OverflowListComponent = OverflowListComponent as OverflowListComponent;\n\nconst DEFAULT_CONTAINER_STYLES: React.CSSProperties = {\n display: \"flex\",\n flexWrap: \"wrap\",\n contain: \"layout style\",\n};\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.js","names":["elementRef: React.RefObject<T>","flushImmediately: boolean","refValue: T","useIsoLayoutEffect: typeof useEffect","nodes: HTMLElement[]","result: Record<number, NodePosition>","lastRowKey: number | undefined","containerRef: React.RefObject<HTMLElement | null>","overflowRef: React.RefObject<HTMLElement | null>","DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties","DefaultOverflowElement","props: DefaultOverflowElementProps<T>","ref: React.ForwardedRef<HTMLDivElement>","OverflowList","props: OverflowListProps<T>","forwardedRef: React.Ref<HTMLElement>","containerStyles: React.CSSProperties","OverflowList: OverflowListComponent","DEFAULT_CONTAINER_STYLES: React.CSSProperties"],"sources":["../src/hooks/useResizeObserver.ts","../src/hooks/useForkRef.tsx","../src/hooks/useIsoLayoutEffect.ts","../src/utils/index.ts","../src/components/DefaultOverflowMenu.tsx","../src/components/OverflowList.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushSync } from \"react-dom\";\n\nexport interface ResizeObserverDimensions {\n width: number;\n height: number;\n contentWidth: number;\n contentHeight: number;\n}\n\nexport function useResizeObserver<T extends HTMLElement | null>(\n elementRef: React.RefObject<T>,\n flushImmediately: boolean = false\n): ResizeObserverDimensions | null {\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const [dimensions, setDimensions] = useState<ResizeObserverDimensions | null>(null);\n\n // Initialize resize observer\n useEffect(() => {\n if (!elementRef.current) return;\n\n // Create resize observer\n resizeObserverRef.current = new ResizeObserver((entries) => {\n if (entries[0]) {\n const entry = entries[0];\n const updateDimensions = () => {\n setDimensions({\n width: entry.borderBoxSize[0]?.inlineSize ?? entry.target.clientWidth,\n height: entry.borderBoxSize[0]?.blockSize ?? entry.target.clientHeight,\n contentWidth: entry.contentRect.width,\n contentHeight: entry.contentRect.height,\n });\n };\n\n if (flushImmediately) {\n // Update DOM immediately using flushSync\n flushSync(updateDimensions);\n } else {\n // Use requestAnimationFrame to defer this update and avoid forced reflow\n requestAnimationFrame(updateDimensions);\n }\n }\n });\n\n resizeObserverRef.current.observe(elementRef.current);\n\n return () => {\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n resizeObserverRef.current = null;\n }\n };\n }, [elementRef.current, flushImmediately]);\n\n return dimensions;\n}\n","// Utility to combine refs (similar to MUI's useForkRef but simpler)\nimport React from \"react\";\n\nexport const useForkRef = <T,>(...refs: (React.Ref<T> | null)[]): React.RefCallback<T> | null => {\n return React.useMemo(() => {\n if (refs.every((ref) => ref == null)) {\n return null;\n }\n return (refValue: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") {\n ref(refValue);\n } else if (ref) {\n (ref as React.RefObject<T>).current = refValue;\n }\n });\n };\n }, refs);\n};\n","import { useEffect, useLayoutEffect } from \"react\";\n\nexport const useIsoLayoutEffect: typeof useEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import React from \"react\";\n\n/**\n * Groups HTML elements by their vertical position (top coordinate)\n * and includes bottom position information\n */\nexport interface NodePosition {\n elements: Set<HTMLElement>;\n bottom: number;\n top: number;\n}\n\nexport function groupNodesByTopPosition(nodes: HTMLElement[]): Record<number, NodePosition> {\n if (nodes.length === 0) return {};\n\n const result: Record<number, NodePosition> = {};\n let lastRowKey: number | undefined;\n\n nodes.forEach((node) => {\n const rect = node.getBoundingClientRect();\n const top = Math.round(rect.top);\n const bottom = Math.round(rect.bottom);\n\n // Check if this element overlaps vertically with the last row\n const lastRow = lastRowKey !== undefined ? result[lastRowKey] : undefined;\n if (lastRow && top < lastRow.bottom && bottom > lastRow.top) {\n lastRow.top = Math.min(lastRow.top, top);\n lastRow.bottom = Math.max(lastRow.bottom, bottom);\n lastRow.elements.add(node);\n } else {\n result[top] = {\n elements: new Set<HTMLElement>(),\n bottom: bottom,\n top: top,\n };\n result[top].elements.add(node);\n lastRowKey = top;\n }\n });\n\n return result;\n}\n\n/**\n * Helper function to get row information from container\n * Returns itemsSizesMap, rowPositions, and children or null if the container is not available\n */\nexport function getRowPositionsData(\n containerRef: React.RefObject<HTMLElement | null>,\n overflowRef: React.RefObject<HTMLElement | null>\n): {\n itemsSizesMap: Record<number, NodePosition>;\n rowPositions: number[];\n children: HTMLElement[];\n} | null {\n if (!containerRef.current) return null;\n\n const container = containerRef.current;\n const children = Array.from(container.children).filter((child) => overflowRef.current !== child) as HTMLElement[];\n\n if (children.length === 0) return null;\n\n // Group elements by their vertical position (rows)\n const itemsSizesMap = groupNodesByTopPosition(children);\n\n // Get all the vertical positions (rows)\n const rowPositions = Object.keys(itemsSizesMap).map(Number);\n\n return { itemsSizesMap, rowPositions, children };\n}\n","import React, { useState } from \"react\";\nimport { OverflowElementProps } from \"./OverflowList\";\n\nconst DEFAULT_OVERFLOW_BUTTON_STYLES: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n border: \"1px solid #ccc\",\n padding: \"4px 8px\",\n fontSize: \"12px\",\n};\n\n/**\n * Simple default overflow menu component that displays hidden items in a basic dropdown\n */\n\ninterface DefaultOverflowElementProps<T> extends OverflowElementProps<T> {}\n\nexport const DefaultOverflowElement = React.forwardRef(function DefaultOverflowElement<T>(\n props: DefaultOverflowElementProps<T>,\n ref: React.ForwardedRef<HTMLDivElement>\n) {\n const { items } = props;\n const count = items.length;\n\n return (\n <div ref={ref} style={DEFAULT_OVERFLOW_BUTTON_STYLES}>\n {`+${count} more`}\n </div>\n );\n}) as <T>(props: DefaultOverflowElementProps<T> & { ref?: React.Ref<HTMLDivElement> }) => React.ReactElement;\n","import React, { useRef, useState } from \"react\";\nimport { useForkRef, useIsoLayoutEffect, useResizeObserver } from \"../hooks\";\nimport { getRowPositionsData } from \"../utils\";\nimport { DefaultOverflowElement } from \"./DefaultOverflowMenu\";\n\ntype BaseComponentProps = React.HTMLAttributes<HTMLElement>;\n\nexport type RenderItemVisibilityMeta = {\n visible: boolean;\n index: number;\n};\n\ntype BaseOverflowListProps<T> = BaseComponentProps & {\n // Polymorphic component prop - allows changing the host element\n as?: React.ElementType;\n\n // would define the maximum number of rows that can be visible (default is 1)\n maxRows?: number;\n\n // would define the maximum number of items that can be visible (default is 100)\n maxVisibleItems?: number;\n\n // would define the overflow item renderer, applied only to overflow items (default is the same as renderItem)\n renderOverflowItem?: (item: NoInfer<T>, index: number) => React.ReactNode;\n // overflow renderer, applied only to overflow items (default is a dropdown menu - DefaultOverflowMenu component)\n renderOverflow?: (items: NoInfer<T>[]) => React.ReactNode;\n // would define the props to pass to the overflow indicator button\n renderOverflowProps?: Partial<OverflowElementProps<T>>;\n\n // after the container dimensions change, flush the state immediately (default is true)\n // if true, using flushSync to update the state immediately - this can affect performance but avoid flickering\n // if false, using requestAnimationFrame to update the state - this avoid forced reflow and improve performance\n flushImmediately?: boolean;\n\n // customize how each item is shown/hidden during measurement so you can keep custom elements mounted\n renderItemVisibility?: (node: React.ReactNode, meta: RenderItemVisibilityMeta) => React.ReactNode;\n};\n\ntype OverflowListWithItems<T> = BaseOverflowListProps<T> & {\n // would define the items to render in the list\n items: T[];\n // would define the default item renderer, applied both to visible and overflow items\n renderItem: (item: NoInfer<T>, index: number) => React.ReactNode;\n children?: never;\n};\n\ntype OverflowListWithChildren<T> = BaseOverflowListProps<T> & {\n children: React.ReactNode;\n items?: never;\n renderItem?: never;\n};\n\nexport type OverflowListProps<T> = OverflowListWithItems<T> | OverflowListWithChildren<T>;\n\nexport type OverflowListComponent = <T>(\n props: OverflowListProps<T> & { ref?: React.Ref<HTMLElement> }\n) => React.ReactElement;\n\nexport interface OverflowElementProps<T> {\n items: T[];\n}\n\n/**\n * Responsive container that shows as many items as can fit within maxRows,\n * hiding overflow items behind a configurable overflow renderer.\n * Automatically recalculates visible items on resize.\n *\n * Technical details:\n * Uses a three-phases approach:\n * 1. \"measuring\" renders all items to calculate positions,\n * 2. \"measuring overflow\" render all items fit in the container, try to add the overflow indicator item to the container. check if it opens a new row, if so, remove the last item from the last row.\n * 3. \"normal\" phase shows only what fits within constraints. (this is the stable state that we want to keep)\n */\nconst OverflowListComponent = React.memo(\n React.forwardRef(function OverflowList<T>(props: OverflowListProps<T>, forwardedRef: React.Ref<HTMLElement>) {\n const {\n as: Component = \"div\",\n children,\n // if items is not provided, use children as items\n items = React.Children.toArray(children),\n renderOverflow,\n // if renderItem is not provided, this component is used in the children pattern, means each item is simply a React.ReactNode\n renderItem = (item) => item as React.ReactNode,\n renderOverflowItem,\n renderOverflowProps,\n renderItemVisibility,\n maxRows = 1,\n maxVisibleItems = 100,\n flushImmediately = true,\n\n ...containerProps\n } = props;\n\n const [visibleCount, setVisibleCount] = useState(items.length);\n const [subtractCount, setSubtractCount] = useState(0);\n const [phase, setPhase] = useState<\"normal\" | \"measuring\" | \"measuring-overflow-indicator\">(\"normal\");\n\n const containerRef = useRef<HTMLElement>(null);\n const finalContainerRef = useForkRef(containerRef, forwardedRef);\n const finalVisibleCount = visibleCount - subtractCount;\n\n const overflowCount = items.length - finalVisibleCount;\n const showOverflow = overflowCount > 0 && phase !== \"measuring\";\n\n const finalRenderOverflow = renderOverflow?.(items.slice(finalVisibleCount) as T[]) ?? (\n <DefaultOverflowElement items={items.slice(finalVisibleCount) as T[]} {...renderOverflowProps} />\n );\n\n const overflowElement = showOverflow ? finalRenderOverflow : null;\n\n const overflowRef = useRef<HTMLElement>(null);\n // @ts-expect-error - ref is not exposed as type in jsx elements but it exists\n const finalOverflowRef = useForkRef(overflowRef, overflowElement?.ref);\n\n // Reset state when items change\n useIsoLayoutEffect(() => {\n setPhase(\"measuring\");\n setVisibleCount(items.length);\n setSubtractCount(0);\n }, [items.length, maxRows]);\n\n useIsoLayoutEffect(() => {\n // in measurement, evaluate results\n if (phase === \"measuring\") {\n countVisibleItems();\n setPhase(\"measuring-overflow-indicator\");\n }\n }, [phase]);\n\n useIsoLayoutEffect(() => {\n // After placing the overflow indicator, evaluate if it ends up opening a new row\n if (phase === \"measuring-overflow-indicator\") {\n const updateWasNeeded = updateOverflowIndicator();\n if (!updateWasNeeded) {\n setPhase(\"normal\");\n }\n }\n }, [phase, subtractCount]);\n\n // if the container dimensions change, re-measure\n const containerDims = useResizeObserver(containerRef, flushImmediately);\n useIsoLayoutEffect(() => {\n if (phase === \"normal\") {\n setPhase(\"measuring\");\n setSubtractCount(0);\n }\n }, [containerDims]);\n\n // Unified method that handles both growing and shrinking\n // this function is called in measuring phase, and it is used to measure how many items can fit in the container\n const countVisibleItems = () => {\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return;\n\n const { itemsSizesMap, rowPositions } = rowData;\n\n // edge case: if only 1 item is given, check if its width is bigger than the container width, if so set the maxRows to 0 (there is not enough space for the item, so we showing overflow indicator)\n if (items.length === 1) {\n const itemRef = itemsSizesMap[rowPositions[0]].elements.values().next().value;\n const containerWidth = containerRef.current?.getBoundingClientRect().width ?? 0;\n const itemWidth = itemRef?.getBoundingClientRect().width ?? 0;\n\n if (itemWidth > containerWidth) {\n setVisibleCount(0);\n } else setVisibleCount(1);\n return;\n }\n\n // Only take up to maxRows\n const visibleRowPositions = rowPositions.slice(0, maxRows);\n\n // only items in rows that conform to the maxRows constraint can be visible\n let fittingCount = visibleRowPositions.reduce((acc, position) => {\n return acc + itemsSizesMap[position].elements.size;\n }, 0);\n\n // Ensure we respect maxVisibleItems\n fittingCount = Math.min(fittingCount, maxVisibleItems);\n\n // Only update state if the number of visible items has changed\n setVisibleCount(fittingCount);\n };\n\n const updateOverflowIndicator = () => {\n // Nothing left to subtract—either we already hid every visible item or there were none to begin with.\n // Avoid looping indefinitely by exiting early.\n if (finalVisibleCount <= 0) {\n return false;\n }\n\n if (!overflowRef.current) return false;\n const rowData = getRowPositionsData(containerRef, overflowRef);\n if (!rowData) return false;\n\n const { rowPositions, itemsSizesMap } = rowData;\n\n const overflowRect = overflowRef.current.getBoundingClientRect();\n const overflowMiddleY = overflowRect.top + overflowRect.height / 2;\n const lastRowTop = rowPositions[rowPositions.length - 1];\n const lastRow = itemsSizesMap[lastRowTop];\n\n // if the overflow indicator item opens a new row(we check it by the middle of the item)\n if (overflowMiddleY > lastRow.bottom) {\n setSubtractCount((c) => c + 1);\n return true;\n }\n return false;\n };\n\n // Cloned overflow element that ensures ref is passed so we could measure dimensions on this element\n const clonedOverflowElement = overflowElement\n ? React.cloneElement(overflowElement as React.ReactElement<any>, {\n ref: finalOverflowRef,\n })\n : null;\n\n // Get the items to render based on current state\n\n // we can render only up to maxVisibleItems items and maxRows rows\n let finalItems = items;\n if (maxVisibleItems) {\n finalItems = finalItems.slice(0, maxVisibleItems);\n }\n\n const containerStyles: React.CSSProperties = {\n ...DEFAULT_CONTAINER_STYLES,\n ...containerProps.style,\n };\n\n const finalRenderItemVisibility =\n renderItemVisibility ??\n ((node, meta) => {\n // prefer react 19.2 new activity component to control the visibility of the item while don't forcing mount/unmount of the item\n // @ts-ignore\n const Activity = React?.Activity;\n if (Activity) {\n return (\n <Activity key={meta.index} mode={meta.visible ? \"visible\" : \"hidden\"}>\n {node}\n </Activity>\n );\n }\n\n // below react 19.2, simply return null if the item is not visible\n if (!meta.visible) return null;\n return <React.Fragment key={meta.index}>{node}</React.Fragment>;\n });\n\n return (\n <Component {...containerProps} ref={finalContainerRef} style={containerStyles}>\n {finalItems.map((item, index) => {\n const isVisible =\n phase ===\n // in measuring phase, show all items\n \"measuring\" ||\n // in 'normal' phase, show only the N items that fit\n index < finalVisibleCount;\n\n const itemComponent = renderItem(item as T, index);\n\n return finalRenderItemVisibility(itemComponent, { index, visible: isVisible });\n })}\n\n {clonedOverflowElement}\n </Component>\n );\n })\n);\n\nexport const OverflowList: OverflowListComponent = OverflowListComponent as OverflowListComponent;\n\nconst DEFAULT_CONTAINER_STYLES: React.CSSProperties = {\n display: \"flex\",\n flexWrap: \"wrap\",\n contain: \"layout style\",\n minWidth: 0,\n};\n"],"mappings":"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"} |
+1
-1
| { | ||
| "name": "react-responsive-overflow-list", | ||
| "version": "0.3.2", | ||
| "version": "0.3.3", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "description": "A responsive React component that shows as many items as can fit within constraints, hiding overflow items behind a configurable overflow renderer", |
@@ -276,2 +276,3 @@ import React, { useRef, useState } from "react"; | ||
| contain: "layout style", | ||
| minWidth: 0, | ||
| }; |
99692
0.1%990
0.3%