UNPKG

@wix/design-system

Version:

@wix/design-system

90 lines 4.87 kB
import React, { useCallback, useMemo, useState } from 'react'; import classNames from 'classnames'; import Item from './Item'; import { flattenVisible, getDepth } from './utils'; // Resolve childrenStyle.marginLeft to a numeric per-depth indent for the // absolute layout. Accepts both numbers and "{n}px" strings. function parseIndentPx(childrenStyle) { const ml = childrenStyle && childrenStyle.marginLeft; if (typeof ml === 'number' && Number.isFinite(ml)) { return ml; } if (typeof ml === 'string') { const n = parseInt(ml, 10); return Number.isFinite(n) ? n : 0; } return 0; } const VirtualContainer = React.forwardRef(function VirtualContainer({ items, childrenProperty, childrenStyle, renderAction, isRenderDraggingChildren, theme, itemHeight, viewportHeight, offscreenRowCount = 5, pinnedItemId, }, containerRef) { const [scrollTop, setScrollTop] = useState(0); // Memoizing the row stream is what makes scrolling cheap. The action-row // entries store truthiness only (no rendered React node), so this cache // stays valid across drag-state changes — action-row styling depends on // isDragging, but action-row *existence* depends only on stable props // (actions/addItemLabel/maxDepth). We re-invoke renderAction at render // time per visible action row to pick up live styling. const rows = useMemo(() => flattenVisible({ items, renderAction, childrenProperty }), [items, renderAction, childrenProperty]); const indentPx = parseIndentPx(childrenStyle); const totalHeight = rows.length * itemHeight; const firstWindowIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - offscreenRowCount); const lastWindowIndex = Math.min(rows.length, Math.ceil((scrollTop + viewportHeight) / itemHeight) + offscreenRowCount); const handleScroll = useCallback(e => { setScrollTop(e.currentTarget.scrollTop); }, []); const containerClass = classNames('nestable-container', 'nestable-top-container', theme && theme.topContainer); const visible = []; for (let i = firstWindowIndex; i < lastWindowIndex; i++) { visible.push({ row: rows[i], absoluteIndex: i }); } // Pin the focused/keyboard-moved row in the slice so its keyboard handlers // survive scroll. Without this, arrow-key reorder across viewport edge // unmounts the focused Item and onKeyUp dies. if (pinnedItemId) { const alreadyVisible = visible.some(v => v.row.kind === 'item' && v.row.item.id === pinnedItemId); if (!alreadyVisible) { const pinnedIdx = rows.findIndex(r => r.kind === 'item' && r.item.id === pinnedItemId); if (pinnedIdx !== -1) { visible.push({ row: rows[pinnedIdx], absoluteIndex: pinnedIdx }); } } } return (React.createElement("div", { ref: containerRef, className: containerClass, onScroll: handleScroll, style: { height: viewportHeight, overflowY: 'auto', position: 'relative', } }, React.createElement("div", { style: { height: totalHeight, position: 'relative' } }, visible.map(({ row, absoluteIndex }) => { const top = absoluteIndex * itemHeight; const left = (row.kind === 'item' ? row.treeDepth - 1 : row.depth - 1) * indentPx; const positionStyle = { position: 'absolute', top, left, right: 0, minHeight: itemHeight, }; if (row.kind === 'item') { const itemForRender = row.isParentLocked ? { ...row.item, isParentLocked: true, draggable: false } : row.item; return (React.createElement("div", { key: `item-${row.item.id}`, style: positionStyle }, React.createElement(Item, { id: row.item.id, item: itemForRender, index: row.index, isVeryLastItem: row.isVeryLastItem, siblings: row.siblings, isRenderDraggingChildren: isRenderDraggingChildren, position: row.position, depth: getDepth(row.item, childrenProperty), theme: theme }))); } // Re-invoke renderAction at render time to pick up live styling // (depends on state.isDragging in the consumer). const actionNode = renderAction({ siblings: row.siblings, item: row.parentItem, isRootAction: row.isRootAction, veryLastItem: row.veryLastItem, depth: row.depth, }); if (!actionNode) { return null; } return (React.createElement("div", { key: `action-${row.parentItem.id}`, style: positionStyle }, actionNode)); })))); }); export default VirtualContainer; //# sourceMappingURL=VirtualContainer.js.map