UNPKG

16.4 kBTypeScriptView Raw
1import { Validator, Requireable, PureComponent, Component } from 'react';
2import { List } from './List';
3import { Table } from './Table';
4import { CellMeasurerCache, MeasuredCellParent } from './CellMeasurer';
5import { Index, Map, Alignment, OverscanIndexRange } from '../../index';
6
7export type RenderedSection = {
8 columnOverscanStartIndex: number;
9 columnOverscanStopIndex: number;
10 columnStartIndex: number;
11 columnStopIndex: number;
12 rowOverscanStartIndex: number;
13 rowOverscanStopIndex: number;
14 rowStartIndex: number;
15 rowStopIndex: number;
16};
17
18export type GridCellProps = {
19 columnIndex: number;
20 isScrolling: boolean;
21 isVisible: boolean;
22 key: string;
23 parent: React.Component<GridCoreProps> & MeasuredCellParent;
24 rowIndex: number;
25 style: React.CSSProperties;
26};
27export type GridCellRenderer = (props: GridCellProps) => React.ReactNode;
28
29export type ConfigureParams = {
30 cellCount: number;
31 estimatedCellSize: number;
32};
33export type ContainerSizeAndOffset = {
34 containerSize: number;
35 offset: number;
36};
37export type SizeAndPositionData = {
38 offset: number;
39 size: number;
40};
41export type GetVisibleCellRangeParams = {
42 containerSize: number;
43 offset: number;
44};
45export type VisibleCellRange = {
46 start: number;
47 stop: number;
48};
49export type ScrollParams = {
50 clientHeight: number;
51 clientWidth: number;
52 scrollHeight: number;
53 scrollLeft: number;
54 scrollTop: number;
55 scrollWidth: number;
56};
57export type ScrollbarPresenceParams = {
58 horizontal: boolean;
59 size: number;
60 vertical: boolean;
61};
62export type SectionRenderedParams = RenderedSection;
63export type SCROLL_DIRECTION_HORIZONTAL = 'horizontal';
64export type SCROLL_DIRECTION_VERTICAL = 'vertical';
65export type OverscanIndicesGetterParams = {
66 direction?: SCROLL_DIRECTION_HORIZONTAL | SCROLL_DIRECTION_VERTICAL;
67 cellCount: number;
68 overscanCellsCount: number;
69 scrollDirection: SCROLL_DIRECTION_HORIZONTAL | SCROLL_DIRECTION_VERTICAL;
70 startIndex: number;
71 stopIndex: number;
72};
73
74export type OverscanIndices = OverscanIndexRange;
75
76export type OverscanIndicesGetter = (params: OverscanIndicesGetterParams) => OverscanIndices;
77
78export type ScrollOffset = {
79 scrollLeft: number;
80 scrollTop: number;
81};
82
83export type CellSizeAndPositionManager = {
84 areOffsetsAdjusted(): boolean;
85 configure({ cellCount, estimatedCellSize }: ConfigureParams): void;
86 getCellCount(): number;
87 getEstimatedCellSize(): number;
88 getLastMeasuredIndex(): number;
89 getOffsetAdjustment({ containerSize, offset /*safe*/ }: ContainerSizeAndOffset): number;
90 /**
91 * This method returns the size and position for the cell at the specified index.
92 * It just-in-time calculates (or used cached values) for cells leading up to the index.
93 */
94 getSizeAndPositionOfCell(index: number): SizeAndPositionData;
95 getSizeAndPositionOfLastMeasuredCell(): SizeAndPositionData;
96 /**
97 * Total size of all cells being measured.
98 * This value will be completedly estimated initially.
99 * As cells as measured the estimate will be updated.
100 */
101 getTotalSize(): number;
102 /**
103 * Determines a new offset that ensures a certain cell is visible, given the current offset.
104 * If the cell is already visible then the current offset will be returned.
105 * If the current offset is too great or small, it will be adjusted just enough to ensure the specified index is visible.
106 *
107 * @param align Desired alignment within container; one of "auto" (default), "start", or "end"
108 * @param containerSize Size (width or height) of the container viewport
109 * @param currentOffset Container's current (x or y) offset
110 * @param totalSize Total size (width or height) of all cells
111 * @return Offset to use to ensure the specified cell is visible
112 */
113 getUpdatedOffsetForIndex(params: {
114 align: string;
115 containerSize: number;
116 currentOffset: number;
117 targetIndex: number;
118 }): number;
119 getVisibleCellRange(params: GetVisibleCellRangeParams): VisibleCellRange;
120 /**
121 * Clear all cached values for cells after the specified index.
122 * This method should be called for any cell that has changed its size.
123 * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionOfCell() is called.
124 */
125 resetCell(index: number): void;
126};
127
128export type GridCellRangeProps = {
129 cellCache: Map<any>;
130 cellRenderer: GridCellRenderer;
131 columnSizeAndPositionManager: CellSizeAndPositionManager;
132 columnStartIndex: number;
133 columnStopIndex: number;
134 isScrolling: boolean;
135 isScrollingOptOut: boolean;
136 rowSizeAndPositionManager: CellSizeAndPositionManager;
137 rowStartIndex: number;
138 rowStopIndex: number;
139 scrollLeft: number;
140 scrollTop: number;
141 deferredMeasurementCache: CellMeasurerCache;
142 horizontalOffsetAdjustment: number;
143 parent: React.Component<GridCoreProps> & MeasuredCellParent;
144 styleCache: Map<React.CSSProperties>;
145 verticalOffsetAdjustment: number;
146 visibleColumnIndices: VisibleCellRange;
147 visibleRowIndices: VisibleCellRange;
148};
149export type GridCellRangeRenderer = (params: GridCellRangeProps) => React.ReactNode[];
150
151export type GridCoreProps = {
152 'aria-label'?: string;
153 'aria-readonly'?: boolean;
154 /**
155 * Set the width of the inner scrollable container to 'auto'.
156 * This is useful for single-column Grids to ensure that the column doesn't extend below a vertical scrollbar.
157 */
158 autoContainerWidth?: boolean;
159 /**
160 * Removes fixed height from the scrollingContainer so that the total height of rows can stretch the window.
161 * Intended for use with WindowScroller
162 */
163 autoHeight?: boolean;
164 /**
165 * Removes fixed width from the scrollingContainer so that the total width of rows can stretch the window.
166 * Intended for use with WindowScroller
167 */
168 autoWidth?: boolean;
169 /**
170 * Responsible for rendering a group of cells given their index ranges.
171 * Should implement the following interface: ({
172 * cellCache: Map,
173 * cellRenderer: Function,
174 * columnSizeAndPositionManager: CellSizeAndPositionManager,
175 * columnStartIndex: number,
176 * columnStopIndex: number,
177 * isScrolling: boolean,
178 * rowSizeAndPositionManager: CellSizeAndPositionManager,
179 * rowStartIndex: number,
180 * rowStopIndex: number,
181 * scrollLeft: number,
182 * scrollTop: number
183 * }): Array<PropTypes.node>
184 */
185 cellRangeRenderer?: GridCellRangeRenderer;
186 /**
187 * Optional custom CSS class name to attach to root Grid element.
188 */
189 className?: string;
190 /** Unfiltered props for the Grid container. */
191 containerProps?: object;
192 /** ARIA role for the cell-container. */
193 containerRole?: string;
194 /** Optional inline style applied to inner cell-container */
195 containerStyle?: React.CSSProperties;
196 /**
197 * If CellMeasurer is used to measure this Grid's children, this should be a pointer to its CellMeasurerCache.
198 * A shared CellMeasurerCache reference enables Grid and CellMeasurer to share measurement data.
199 */
200 deferredMeasurementCache?: CellMeasurerCache;
201 /**
202 * Used to estimate the total width of a Grid before all of its columns have actually been measured.
203 * The estimated total width is adjusted as columns are rendered.
204 */
205 estimatedColumnSize?: number;
206 /**
207 * Used to estimate the total height of a Grid before all of its rows have actually been measured.
208 * The estimated total height is adjusted as rows are rendered.
209 */
210 estimatedRowSize?: number;
211 /**
212 * Exposed for testing purposes only.
213 */
214 getScrollbarSize?: () => number;
215 /**
216 * Height of Grid; this property determines the number of visible (vs virtualized) rows.
217 */
218 height: number;
219 /**
220 * Optional custom id to attach to root Grid element.
221 */
222 id?: string;
223 /**
224 * Override internal is-scrolling state tracking.
225 * This property is primarily intended for use with the WindowScroller component.
226 */
227 isScrolling?: boolean;
228 /**
229 * Optional renderer to be used in place of rows when either :rowCount or :columnCount is 0.
230 */
231 noContentRenderer?: () => React.ReactNode;
232 /**
233 * Callback invoked whenever the scroll offset changes within the inner scrollable region.
234 * This callback can be used to sync scrolling between lists, tables, or grids.
235 * ({ clientHeight, clientWidth, scrollHeight, scrollLeft, scrollTop, scrollWidth }): void
236 */
237 onScroll?: (params: ScrollParams) => any;
238 /**
239 * Called whenever a horizontal or vertical scrollbar is added or removed.
240 * ({ horizontal: boolean, size: number, vertical: boolean }): void
241 */
242 onScrollbarPresenceChange?: (params: ScrollbarPresenceParams) => any;
243 /**
244 * Callback invoked with information about the section of the Grid that was just rendered.
245 * ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }): void
246 */
247 onSectionRendered?: (params: SectionRenderedParams) => any;
248 /**
249 * Number of columns to render before/after the visible section of the grid.
250 * These columns can help for smoother scrolling on touch devices or browsers that send scroll events infrequently.
251 */
252 overscanColumnCount?: number;
253 /**
254 * Calculates the number of cells to overscan before and after a specified range.
255 * This function ensures that overscanning doesn't exceed the available cells.
256 * Should implement the following interface: ({
257 * cellCount: number,
258 * overscanCellsCount: number,
259 * scrollDirection: number,
260 * startIndex: number,
261 * stopIndex: number
262 * }): {overscanStartIndex: number, overscanStopIndex: number}
263 */
264 overscanIndicesGetter?: OverscanIndicesGetter;
265 /**
266 * Number of rows to render above/below the visible section of the grid.
267 * These rows can help for smoother scrolling on touch devices or browsers that send scroll events infrequently.
268 */
269 overscanRowCount?: number;
270 /**
271 * ARIA role for the grid element.
272 */
273 role?: string;
274 /**
275 * Either a fixed row height (number) or a function that returns the height of a row given its index.
276 * Should implement the following interface: ({ index: number }): number
277 */
278 rowHeight: number | ((params: Index) => number);
279 /**
280 * Number of rows in grid.
281 */
282 rowCount: number;
283 /** Wait this amount of time after the last scroll event before resetting Grid `pointer-events`. */
284 scrollingResetTimeInterval?: number;
285 /** Horizontal offset. */
286 scrollLeft?: number;
287 /**
288 * Controls scroll-to-cell behavior of the Grid.
289 * The default ("auto") scrolls the least amount possible to ensure that the specified cell is fully visible.
290 * Use "start" to align cells to the top/left of the Grid and "end" to align bottom/right.
291 */
292 scrollToAlignment?: Alignment;
293 /**
294 * Column index to ensure visible (by forcefully scrolling if necessary)
295 */
296 scrollToColumn?: number;
297 /** Vertical offset. */
298 scrollTop?: number;
299 /**
300 * Row index to ensure visible (by forcefully scrolling if necessary)
301 */
302 scrollToRow?: number;
303 /** Optional inline style */
304 style?: React.CSSProperties;
305 /** Tab index for focus */
306 tabIndex?: number | null;
307 /**
308 * Width of Grid; this property determines the number of visible (vs virtualized) columns.
309 */
310 width: number;
311 /**
312 * PLEASE NOTE
313 * The [key: string]: any; line is here on purpose
314 * This is due to the need of force re-render of PureComponent
315 * Check the following link if you want to know more
316 * https://github.com/bvaughn/react-virtualized#pass-thru-props
317 */
318 [key: string]: any;
319};
320
321export type GridProps = GridCoreProps & {
322 /**
323 * Responsible for rendering a cell given an row and column index.
324 * Should implement the following interface: ({ columnIndex: number, rowIndex: number }): PropTypes.node
325 */
326 cellRenderer: GridCellRenderer;
327 /**
328 * Number of columns in grid.
329 */
330 columnCount: number;
331 /**
332 * Either a fixed column width (number) or a function that returns the width of a column given its index.
333 * Should implement the following interface: (index: number): number
334 */
335 columnWidth: number | ((params: Index) => number);
336};
337
338export type ScrollDirection = 'horizontal' | 'vertical';
339
340export type GridState = {
341 isScrolling: boolean;
342 scrollDirectionHorizontal: ScrollDirection;
343 scrollDirectionVertical: ScrollDirection;
344 scrollLeft: number;
345 scrollTop: number;
346};
347
348/**
349 * Specifies the number of miliseconds during which to disable pointer events while a scroll is in progress.
350 * This improves performance and makes scrolling smoother.
351 */
352export const DEFAULT_SCROLLING_RESET_TIME_INTERVAL = 150;
353
354/**
355 * Renders tabular data with virtualization along the vertical and horizontal axes.
356 * Row heights and column widths must be known ahead of time and specified as properties.
357 */
358export class Grid extends PureComponent<GridProps, GridState> {
359 static defaultProps: {
360 'aria-label': 'grid';
361 'aria-readonly': true;
362 autoContainerWidth: false;
363 autoHeight: false;
364 autoWidth: false;
365 cellRangeRenderer: GridCellRangeRenderer;
366 containerRole: 'rowgroup';
367 containerStyle: {};
368 estimatedColumnSize: 100;
369 estimatedRowSize: 30;
370 getScrollbarSize: () => number;
371 noContentRenderer: () => React.ReactNode;
372 onScroll: () => void;
373 onScrollbarPresenceChange: () => void;
374 onSectionRendered: () => void;
375 overscanColumnCount: 0;
376 overscanIndicesGetter: OverscanIndicesGetter;
377 overscanRowCount: 10;
378 role: 'grid';
379 scrollingResetTimeInterval: typeof DEFAULT_SCROLLING_RESET_TIME_INTERVAL;
380 scrollToAlignment: 'auto';
381 scrollToColumn: -1;
382 scrollToRow: -1;
383 style: {};
384 tabIndex: 0;
385 };
386
387 /**
388 * Gets offsets for a given cell and alignment.
389 */
390 getOffsetForCell(params?: { alignment?: Alignment; columnIndex?: number; rowIndex?: number }): ScrollOffset;
391
392 /**
393 * This method handles a scroll event originating from an external scroll control.
394 * It's an advanced method and should probably not be used unless you're implementing a custom scroll-bar solution.
395 */
396 handleScrollEvent(params: Partial<ScrollOffset>): void;
397
398 /**
399 * Invalidate Grid size and recompute visible cells.
400 * This is a deferred wrapper for recomputeGridSize().
401 * It sets a flag to be evaluated on cDM/cDU to avoid unnecessary renders.
402 * This method is intended for advanced use-cases like CellMeasurer.
403 */
404 // @TODO (bvaughn) Add automated test coverage for this.
405 invalidateCellSizeAfterRender(params: { columnIndex: number; rowIndex: number }): void;
406
407 /**
408 * Pre-measure all columns and rows in a Grid.
409 * Typically cells are only measured as needed and estimated sizes are used for cells that have not yet been measured.
410 * This method ensures that the next call to getTotalSize() returns an exact size (as opposed to just an estimated one).
411 */
412 measureAllCells(): void;
413
414 /**
415 * Forced recompute of row heights and column widths.
416 * This function should be called if dynamic column or row sizes have changed but nothing else has.
417 * Since Grid only receives :columnCount and :rowCount it has no way of detecting when the underlying data changes.
418 */
419 recomputeGridSize(params?: { columnIndex?: number; rowIndex?: number }): void;
420
421 /**
422 * Ensure column and row are visible.
423 */
424 scrollToCell(params: { columnIndex: number; rowIndex: number }): void;
425
426 /**
427 * Scroll to the specified offset(s).
428 * Useful for animating position changes.
429 */
430 scrollToPosition(params?: { scrollLeft: number; scrollTop: number }): void;
431}
432
433export default Grid;
434
435export const defaultCellRangeRenderer: GridCellRangeRenderer;
436
437export const accessibilityOverscanIndicesGetter: OverscanIndicesGetter;
438
439export const defaultOverscanIndicesGetter: OverscanIndicesGetter;