import { FiltersMap, KeyedItem } from '@wix/bex-core'; import { ToolbarCollectionState } from './ToolbarCollectionState'; import { action, computed, makeObservable, observable, reaction } from 'mobx'; import { ReactWindowGridRef } from '../components/VirtualGridRepeater/VirtualGridRepeaterState'; export interface TableVirtualStateParams< K extends KeyedItem, T, F extends FiltersMap, > { readonly table: { keyedItems: K[]; toolbar: ToolbarCollectionState; }; getVirtualItemKey?: (item: K) => string; } type InferItem = K extends KeyedItem ? T : never; export class TableVirtualState< K extends KeyedItem, T, F extends FiltersMap, > { gridRef?: ReactWindowGridRef | null; readonly table; readonly getVirtualItemKey; gap = 0; rowHeight?: number | ((item: InferItem, index: number) => number) = 0; estimatedRowHeight?: number = 80; rowHeights = {} as Record; get container() { return this.table.toolbar.container; } constructor(params: TableVirtualStateParams) { this.table = params.table; this.getVirtualItemKey = params.getVirtualItemKey; makeObservable(this, { rowHeight: observable.ref, estimatedRowHeight: observable.ref, rowHeights: observable.ref, calcGridProps: computed, rowHeightFunc: computed, itemsToRenderCount: computed, itemKeyRecyclerOverride: computed, init: action, setVariableRowHeight: action, }); this.init(); } get forceRenderIndexes() { return this.toolbar.forceRenderIndexes; } init() { const disposers = [ reaction( () => this.rowHeights, async () => { this.gridRef?.resetAfterIndices({ columnIndex: 0, rowIndex: 0, shouldForceUpdate: true, }); }, ), ]; return () => { disposers.forEach((disposer) => disposer?.()); }; } setVariableRowHeight = ({ size, key, }: { size: number; key: number | string; }) => { this.rowHeights = { ...this.rowHeights, [key]: size, }; }; get toolbar() { return this.table.toolbar; } get itemsContentWidth() { return this.table.toolbar.itemsContentRect.rect.width ?? 0; } get itemKeyRecyclerOverride() { const { getVirtualItemKey, table: { keyedItems }, } = this; return (index: number) => { const keyedItem = keyedItems[index]; return getVirtualItemKey?.(keyedItem) ?? keyedItem?.key ?? index; }; } get itemsToRenderCount() { return this.table.keyedItems.length; } get rowHeightFunc() { const { rowHeight: rowHeightProp, table: { keyedItems }, } = this; if (process.env.NODE_ENV === 'test' && rowHeightProp == null) { // need to provide rowHeight for tests return () => 20; } if (rowHeightProp == null) { return undefined; } if (typeof rowHeightProp === 'number') { return () => rowHeightProp as number; } return action((index: number) => rowHeightProp(keyedItems[index].item, index), ); } get calcGridProps() { const { rowHeightFunc: rowHeight, itemsContentWidth, table: { toolbar }, estimatedRowHeight, } = this; const { itemsContentHeight } = toolbar; const avgRowHeight = rowHeight?.(0) ?? estimatedRowHeight; const overscanCount = avgRowHeight == null ? 10 : Math.round((itemsContentHeight / avgRowHeight) * 1.5); return { columnCount: 1, width: itemsContentWidth, columnWidth: () => itemsContentWidth, columnWidthIncludingGap: () => itemsContentWidth, rowHeight, rowHeightIncludingGap: ({ index, key, }: { index: number; key: number | string; }) => { if (rowHeight != null) { return rowHeight(index); } return this.rowHeights[key]; }, estimatedRowHeight, overscanCount, }; } }