@difizen/libro-virtualized 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/es/auto-sizer/auto-sizer.d.ts +56 -0
- package/es/auto-sizer/auto-sizer.d.ts.map +1 -0
- package/es/auto-sizer/auto-sizer.js +157 -0
- package/es/auto-sizer/index.d.ts +3 -0
- package/es/auto-sizer/index.d.ts.map +1 -0
- package/es/auto-sizer/index.js +2 -0
- package/es/cell-measurer/cell-measurer-cache.d.ts +52 -0
- package/es/cell-measurer/cell-measurer-cache.d.ts.map +1 -0
- package/es/cell-measurer/cell-measurer-cache.js +176 -0
- package/es/cell-measurer/cell-measurer.d.ts +39 -0
- package/es/cell-measurer/cell-measurer.d.ts.map +1 -0
- package/es/cell-measurer/cell-measurer.js +154 -0
- package/es/cell-measurer/index.d.ts +5 -0
- package/es/cell-measurer/index.d.ts.map +1 -0
- package/es/cell-measurer/index.js +4 -0
- package/es/cell-measurer/types.d.ts +9 -0
- package/es/cell-measurer/types.d.ts.map +1 -0
- package/es/cell-measurer/types.js +0 -0
- package/es/grid/accessibility-overscanIndices-getter.d.ts +11 -0
- package/es/grid/accessibility-overscanIndices-getter.d.ts.map +1 -0
- package/es/grid/accessibility-overscanIndices-getter.js +33 -0
- package/es/grid/default-cell-range-renderer.d.ts +10 -0
- package/es/grid/default-cell-range-renderer.d.ts.map +1 -0
- package/es/grid/default-cell-range-renderer.js +135 -0
- package/es/grid/default-overscanIndices-getter.d.ts +11 -0
- package/es/grid/default-overscanIndices-getter.d.ts.map +1 -0
- package/es/grid/default-overscanIndices-getter.js +28 -0
- package/es/grid/grid.d.ts +359 -0
- package/es/grid/grid.d.ts.map +1 -0
- package/es/grid/grid.js +1287 -0
- package/es/grid/index.d.ts +6 -0
- package/es/grid/index.d.ts.map +1 -0
- package/es/grid/index.js +4 -0
- package/es/grid/types.d.ts +87 -0
- package/es/grid/types.d.ts.map +1 -0
- package/es/grid/types.js +1 -0
- package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.d.ts +17 -0
- package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.d.ts.map +1 -0
- package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.js +25 -0
- package/es/grid/utils/cell-size-and-position-manager-row.d.ts +97 -0
- package/es/grid/utils/cell-size-and-position-manager-row.d.ts.map +1 -0
- package/es/grid/utils/cell-size-and-position-manager-row.js +297 -0
- package/es/grid/utils/cell-size-and-position-manager.d.ts +85 -0
- package/es/grid/utils/cell-size-and-position-manager.d.ts.map +1 -0
- package/es/grid/utils/cell-size-and-position-manager.js +268 -0
- package/es/grid/utils/max-element-size.d.ts +2 -0
- package/es/grid/utils/max-element-size.d.ts.map +1 -0
- package/es/grid/utils/max-element-size.js +17 -0
- package/es/grid/utils/scaling-cell-size-and-position-manager-row.d.ts +78 -0
- package/es/grid/utils/scaling-cell-size-and-position-manager-row.d.ts.map +1 -0
- package/es/grid/utils/scaling-cell-size-and-position-manager-row.js +187 -0
- package/es/grid/utils/scaling-cell-size-and-position-manager.d.ts +70 -0
- package/es/grid/utils/scaling-cell-size-and-position-manager.d.ts.map +1 -0
- package/es/grid/utils/scaling-cell-size-and-position-manager.js +187 -0
- package/es/grid/utils/update-scroll-index-helper.d.ts +24 -0
- package/es/grid/utils/update-scroll-index-helper.d.ts.map +1 -0
- package/es/grid/utils/update-scroll-index-helper.js +40 -0
- package/es/index.d.ts +5 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +4 -0
- package/es/list/index.d.ts +3 -0
- package/es/list/index.d.ts.map +1 -0
- package/es/list/index.js +1 -0
- package/es/list/list.d.ts +109 -0
- package/es/list/list.d.ts.map +1 -0
- package/es/list/list.js +261 -0
- package/es/list/types.d.ts +22 -0
- package/es/list/types.d.ts.map +1 -0
- package/es/list/types.js +1 -0
- package/es/utils/animation-frame.d.ts +7 -0
- package/es/utils/animation-frame.d.ts.map +1 -0
- package/es/utils/animation-frame.js +20 -0
- package/es/utils/create-callback-memoizer.d.ts +5 -0
- package/es/utils/create-callback-memoizer.d.ts.map +1 -0
- package/es/utils/create-callback-memoizer.js +25 -0
- package/es/utils/get-updated-offset-for-index.d.ts +14 -0
- package/es/utils/get-updated-offset-for-index.d.ts.map +1 -0
- package/es/utils/get-updated-offset-for-index.js +32 -0
- package/es/utils/init-cell-metadata.d.ts +13 -0
- package/es/utils/init-cell-metadata.d.ts.map +1 -0
- package/es/utils/init-cell-metadata.js +32 -0
- package/es/utils/request-animation-timeout.d.ts +12 -0
- package/es/utils/request-animation-timeout.d.ts.map +1 -0
- package/es/utils/request-animation-timeout.js +33 -0
- package/es/utils/test-helper.d.ts +5 -0
- package/es/utils/test-helper.d.ts.map +1 -0
- package/es/utils/test-helper.js +31 -0
- package/es/vendor/binary-search-bounds.d.ts +22 -0
- package/es/vendor/binary-search-bounds.d.ts.map +1 -0
- package/es/vendor/binary-search-bounds.js +198 -0
- package/es/vendor/detect-element-resize.d.ts +16 -0
- package/es/vendor/detect-element-resize.d.ts.map +1 -0
- package/es/vendor/detect-element-resize.js +184 -0
- package/es/vendor/interval-tree.d.ts +10 -0
- package/es/vendor/interval-tree.d.ts.map +1 -0
- package/es/vendor/interval-tree.js +359 -0
- package/package.json +59 -0
- package/src/auto-sizer/auto-sizer.tsx +187 -0
- package/src/auto-sizer/index.ts +4 -0
- package/src/cell-measurer/cell-measurer-cache.ts +220 -0
- package/src/cell-measurer/cell-measurer.ts +151 -0
- package/src/cell-measurer/index.ts +5 -0
- package/src/cell-measurer/types.ts +8 -0
- package/src/grid/accessibility-overscanIndices-getter.ts +38 -0
- package/src/grid/default-cell-range-renderer.ts +166 -0
- package/src/grid/default-overscanIndices-getter.ts +32 -0
- package/src/grid/grid.tsx +1672 -0
- package/src/grid/index.ts +14 -0
- package/src/grid/types.ts +112 -0
- package/src/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.ts +62 -0
- package/src/grid/utils/cell-size-and-position-manager-row.ts +365 -0
- package/src/grid/utils/cell-size-and-position-manager.ts +309 -0
- package/src/grid/utils/max-element-size.ts +18 -0
- package/src/grid/utils/scaling-cell-size-and-position-manager-row.ts +206 -0
- package/src/grid/utils/scaling-cell-size-and-position-manager.ts +198 -0
- package/src/grid/utils/update-scroll-index-helper.ts +96 -0
- package/src/index.spec.ts +10 -0
- package/src/index.ts +4 -0
- package/src/list/index.ts +2 -0
- package/src/list/list.tsx +292 -0
- package/src/list/types.ts +25 -0
- package/src/utils/animation-frame.ts +38 -0
- package/src/utils/create-callback-memoizer.ts +32 -0
- package/src/utils/get-updated-offset-for-index.ts +33 -0
- package/src/utils/init-cell-metadata.ts +32 -0
- package/src/utils/request-animation-timeout.ts +44 -0
- package/src/utils/test-helper.ts +20 -0
- package/src/vendor/binary-search-bounds.ts +203 -0
- package/src/vendor/detect-element-resize.ts +241 -0
- package/src/vendor/interval-tree.ts +406 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
import type { Alignment, CellSizeGetter, VisibleCellRange } from '../types.js';
|
|
3
|
+
|
|
4
|
+
import CellSizeAndPositionManager from './cell-size-and-position-manager.js';
|
|
5
|
+
import { getMaxElementSize } from './max-element-size.js';
|
|
6
|
+
|
|
7
|
+
type ContainerSizeAndOffset = {
|
|
8
|
+
containerSize: number;
|
|
9
|
+
offset: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Browsers have scroll offset limitations (eg Chrome stops scrolling at ~33.5M pixels where as Edge tops out at ~1.5M pixels).
|
|
14
|
+
* After a certain position, the browser won't allow the user to scroll further (even via JavaScript scroll offset adjustments).
|
|
15
|
+
* This util picks a lower ceiling for max size and artificially adjusts positions within to make it transparent for users.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
type Params = {
|
|
19
|
+
maxScrollSize?: number;
|
|
20
|
+
cellCount: number;
|
|
21
|
+
cellSizeGetter: CellSizeGetter;
|
|
22
|
+
estimatedCellSize: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extends CellSizeAndPositionManager and adds scaling behavior for lists that are too large to fit within a browser's native limits.
|
|
27
|
+
*/
|
|
28
|
+
export default class ScalingCellSizeAndPositionManager {
|
|
29
|
+
_cellSizeAndPositionManager: CellSizeAndPositionManager;
|
|
30
|
+
_maxScrollSize: number;
|
|
31
|
+
|
|
32
|
+
constructor({ maxScrollSize = getMaxElementSize(), ...params }: Params) {
|
|
33
|
+
// Favor composition over inheritance to simplify IE10 support
|
|
34
|
+
this._cellSizeAndPositionManager = new CellSizeAndPositionManager(params);
|
|
35
|
+
this._maxScrollSize = maxScrollSize;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
areOffsetsAdjusted(): boolean {
|
|
39
|
+
return this._cellSizeAndPositionManager.getTotalSize() > this._maxScrollSize;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
configure(params: {
|
|
43
|
+
cellCount: number;
|
|
44
|
+
estimatedCellSize: number;
|
|
45
|
+
cellSizeGetter: CellSizeGetter;
|
|
46
|
+
}) {
|
|
47
|
+
this._cellSizeAndPositionManager.configure(params);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getCellCount(): number {
|
|
51
|
+
return this._cellSizeAndPositionManager.getCellCount();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getEstimatedCellSize(): number {
|
|
55
|
+
return this._cellSizeAndPositionManager.getEstimatedCellSize();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getLastMeasuredIndex(): number {
|
|
59
|
+
return this._cellSizeAndPositionManager.getLastMeasuredIndex();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Number of pixels a cell at the given position (offset) should be shifted in order to fit within the scaled container.
|
|
64
|
+
* The offset passed to this function is scaled (safe) as well.
|
|
65
|
+
*/
|
|
66
|
+
getOffsetAdjustment({
|
|
67
|
+
containerSize,
|
|
68
|
+
offset, // safe
|
|
69
|
+
}: ContainerSizeAndOffset): number {
|
|
70
|
+
const totalSize = this._cellSizeAndPositionManager.getTotalSize();
|
|
71
|
+
const safeTotalSize = this.getTotalSize();
|
|
72
|
+
const offsetPercentage = this._getOffsetPercentage({
|
|
73
|
+
containerSize,
|
|
74
|
+
offset,
|
|
75
|
+
totalSize: safeTotalSize,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return Math.round(offsetPercentage * (safeTotalSize - totalSize));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getSizeAndPositionOfCell(index: number) {
|
|
82
|
+
return this._cellSizeAndPositionManager.getSizeAndPositionOfCell(index);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getSizeAndPositionOfLastMeasuredCell() {
|
|
86
|
+
return this._cellSizeAndPositionManager.getSizeAndPositionOfLastMeasuredCell();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** See CellSizeAndPositionManager#getTotalSize */
|
|
90
|
+
getTotalSize(): number {
|
|
91
|
+
return Math.min(
|
|
92
|
+
this._maxScrollSize,
|
|
93
|
+
this._cellSizeAndPositionManager.getTotalSize(),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** See CellSizeAndPositionManager#getUpdatedOffsetForIndex */
|
|
98
|
+
getUpdatedOffsetForIndex({
|
|
99
|
+
align = 'auto',
|
|
100
|
+
containerSize,
|
|
101
|
+
currentOffset, // safe
|
|
102
|
+
targetIndex,
|
|
103
|
+
}: {
|
|
104
|
+
align: Alignment;
|
|
105
|
+
containerSize: number;
|
|
106
|
+
currentOffset: number;
|
|
107
|
+
targetIndex: number;
|
|
108
|
+
}) {
|
|
109
|
+
currentOffset = this._safeOffsetToOffset({
|
|
110
|
+
containerSize,
|
|
111
|
+
offset: currentOffset,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const offset = this._cellSizeAndPositionManager.getUpdatedOffsetForIndex({
|
|
115
|
+
align,
|
|
116
|
+
containerSize,
|
|
117
|
+
currentOffset,
|
|
118
|
+
targetIndex,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return this._offsetToSafeOffset({
|
|
122
|
+
containerSize,
|
|
123
|
+
offset,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** See CellSizeAndPositionManager#getVisibleCellRange */
|
|
128
|
+
getVisibleCellRange({
|
|
129
|
+
containerSize,
|
|
130
|
+
offset, // safe
|
|
131
|
+
}: ContainerSizeAndOffset): VisibleCellRange {
|
|
132
|
+
offset = this._safeOffsetToOffset({
|
|
133
|
+
containerSize,
|
|
134
|
+
offset,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return this._cellSizeAndPositionManager.getVisibleCellRange({
|
|
138
|
+
containerSize,
|
|
139
|
+
offset,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
resetCell(index: number): void {
|
|
144
|
+
this._cellSizeAndPositionManager.resetCell(index);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_getOffsetPercentage({
|
|
148
|
+
containerSize,
|
|
149
|
+
offset, // safe
|
|
150
|
+
totalSize,
|
|
151
|
+
}: {
|
|
152
|
+
containerSize: number;
|
|
153
|
+
offset: number;
|
|
154
|
+
totalSize: number;
|
|
155
|
+
}) {
|
|
156
|
+
return totalSize <= containerSize ? 0 : offset / (totalSize - containerSize);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_offsetToSafeOffset({
|
|
160
|
+
containerSize,
|
|
161
|
+
offset, // unsafe
|
|
162
|
+
}: ContainerSizeAndOffset): number {
|
|
163
|
+
const totalSize = this._cellSizeAndPositionManager.getTotalSize();
|
|
164
|
+
const safeTotalSize = this.getTotalSize();
|
|
165
|
+
|
|
166
|
+
if (totalSize === safeTotalSize) {
|
|
167
|
+
return offset;
|
|
168
|
+
} else {
|
|
169
|
+
const offsetPercentage = this._getOffsetPercentage({
|
|
170
|
+
containerSize,
|
|
171
|
+
offset,
|
|
172
|
+
totalSize,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return Math.round(offsetPercentage * (safeTotalSize - containerSize));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_safeOffsetToOffset({
|
|
180
|
+
containerSize,
|
|
181
|
+
offset, // safe
|
|
182
|
+
}: ContainerSizeAndOffset): number {
|
|
183
|
+
const totalSize = this._cellSizeAndPositionManager.getTotalSize();
|
|
184
|
+
const safeTotalSize = this.getTotalSize();
|
|
185
|
+
|
|
186
|
+
if (totalSize === safeTotalSize) {
|
|
187
|
+
return offset;
|
|
188
|
+
} else {
|
|
189
|
+
const offsetPercentage = this._getOffsetPercentage({
|
|
190
|
+
containerSize,
|
|
191
|
+
offset,
|
|
192
|
+
totalSize: safeTotalSize,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return Math.round(offsetPercentage * (totalSize - containerSize));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Alignment, CellSize } from '../types.js';
|
|
2
|
+
|
|
3
|
+
import type ScalingCellSizeAndPositionManager from './scaling-cell-size-and-position-manager.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper function that determines when to update scroll offsets to ensure that a scroll-to-index remains visible.
|
|
7
|
+
* This function also ensures that the scroll ofset isn't past the last column/row of cells.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type Params = {
|
|
11
|
+
// Width or height of cells for the current axis
|
|
12
|
+
cellSize?: CellSize;
|
|
13
|
+
|
|
14
|
+
// Manages size and position metadata of cells
|
|
15
|
+
cellSizeAndPositionManager: ScalingCellSizeAndPositionManager;
|
|
16
|
+
|
|
17
|
+
// Previous number of rows or columns
|
|
18
|
+
previousCellsCount: number;
|
|
19
|
+
|
|
20
|
+
// Previous width or height of cells
|
|
21
|
+
previousCellSize: CellSize;
|
|
22
|
+
|
|
23
|
+
previousScrollToAlignment: Alignment;
|
|
24
|
+
|
|
25
|
+
// Previous scroll-to-index
|
|
26
|
+
previousScrollToIndex: number;
|
|
27
|
+
|
|
28
|
+
// Previous width or height of the virtualized container
|
|
29
|
+
previousSize: number;
|
|
30
|
+
|
|
31
|
+
// Current scrollLeft or scrollTop
|
|
32
|
+
scrollOffset: number;
|
|
33
|
+
|
|
34
|
+
scrollToAlignment: Alignment;
|
|
35
|
+
|
|
36
|
+
// Scroll-to-index
|
|
37
|
+
scrollToIndex: number;
|
|
38
|
+
|
|
39
|
+
// Width or height of the virtualized container
|
|
40
|
+
size: number;
|
|
41
|
+
|
|
42
|
+
sizeJustIncreasedFromZero: boolean;
|
|
43
|
+
|
|
44
|
+
// Callback to invoke with an scroll-to-index value
|
|
45
|
+
updateScrollIndexCallback: (index: number) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function updateScrollIndexHelper({
|
|
49
|
+
cellSize,
|
|
50
|
+
cellSizeAndPositionManager,
|
|
51
|
+
previousCellsCount,
|
|
52
|
+
previousCellSize,
|
|
53
|
+
previousScrollToAlignment,
|
|
54
|
+
previousScrollToIndex,
|
|
55
|
+
previousSize,
|
|
56
|
+
scrollOffset,
|
|
57
|
+
scrollToAlignment,
|
|
58
|
+
scrollToIndex,
|
|
59
|
+
size,
|
|
60
|
+
sizeJustIncreasedFromZero,
|
|
61
|
+
updateScrollIndexCallback,
|
|
62
|
+
}: Params) {
|
|
63
|
+
const cellCount = cellSizeAndPositionManager.getCellCount();
|
|
64
|
+
const hasScrollToIndex = scrollToIndex >= 0 && scrollToIndex < cellCount;
|
|
65
|
+
const sizeHasChanged =
|
|
66
|
+
size !== previousSize ||
|
|
67
|
+
sizeJustIncreasedFromZero ||
|
|
68
|
+
!previousCellSize ||
|
|
69
|
+
(typeof cellSize === 'number' && cellSize !== previousCellSize);
|
|
70
|
+
|
|
71
|
+
// If we have a new scroll target OR if height/row-height has changed,
|
|
72
|
+
// We should ensure that the scroll target is visible.
|
|
73
|
+
if (
|
|
74
|
+
hasScrollToIndex &&
|
|
75
|
+
(sizeHasChanged ||
|
|
76
|
+
scrollToAlignment !== previousScrollToAlignment ||
|
|
77
|
+
scrollToIndex !== previousScrollToIndex)
|
|
78
|
+
) {
|
|
79
|
+
updateScrollIndexCallback(scrollToIndex);
|
|
80
|
+
|
|
81
|
+
// If we don't have a selected item but list size or number of children have decreased,
|
|
82
|
+
// Make sure we aren't scrolled too far past the current content.
|
|
83
|
+
} else if (
|
|
84
|
+
!hasScrollToIndex &&
|
|
85
|
+
cellCount > 0 &&
|
|
86
|
+
(size < previousSize || cellCount < previousCellsCount)
|
|
87
|
+
) {
|
|
88
|
+
// We need to ensure that the current scroll offset is still within the collection's range.
|
|
89
|
+
// To do this, we don't need to measure everything; CellMeasurer would perform poorly.
|
|
90
|
+
// Just check to make sure we're still okay.
|
|
91
|
+
// Only adjust the scroll position if we've scrolled below the last set of rows.
|
|
92
|
+
if (scrollOffset > cellSizeAndPositionManager.getTotalSize() - size) {
|
|
93
|
+
updateScrollIndexCallback(cellCount - 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
Alignment,
|
|
6
|
+
CellPosition,
|
|
7
|
+
CellRendererParams,
|
|
8
|
+
CellSize,
|
|
9
|
+
NoContentRenderer,
|
|
10
|
+
OverscanIndicesGetter,
|
|
11
|
+
RenderedSection,
|
|
12
|
+
Scroll as GridScroll,
|
|
13
|
+
} from '../grid/index.js';
|
|
14
|
+
import Grid, { accessibilityOverscanIndicesGetter } from '../grid/index.js';
|
|
15
|
+
|
|
16
|
+
import type { RenderedRows, RowRenderer, Scroll } from './types.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* It is inefficient to create and manage a large list of DOM elements within a scrolling container
|
|
20
|
+
* if only a few of those elements are visible. The primary purpose of this component is to improve
|
|
21
|
+
* performance by only rendering the DOM nodes that a user is able to see based on their current
|
|
22
|
+
* scroll position.
|
|
23
|
+
*
|
|
24
|
+
* This component renders a virtualized list of elements with either fixed or dynamic heights.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
type Props = {
|
|
28
|
+
'aria-label'?: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Removes fixed height from the scrollingContainer so that the total height
|
|
32
|
+
* of rows can stretch the window. Intended for use with WindowScroller
|
|
33
|
+
*/
|
|
34
|
+
autoHeight: boolean;
|
|
35
|
+
|
|
36
|
+
/** Optional CSS class name */
|
|
37
|
+
className?: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Used to estimate the total height of a List before all of its rows have actually been measured.
|
|
41
|
+
* The estimated total height is adjusted as rows are rendered.
|
|
42
|
+
*/
|
|
43
|
+
estimatedRowSize: number;
|
|
44
|
+
|
|
45
|
+
/** Height constraint for list (determines how many actual rows are rendered) */
|
|
46
|
+
height: number;
|
|
47
|
+
|
|
48
|
+
/** Optional renderer to be used in place of rows when rowCount is 0 */
|
|
49
|
+
noRowsRenderer: NoContentRenderer;
|
|
50
|
+
|
|
51
|
+
/** Callback invoked with information about the slice of rows that were just rendered. */
|
|
52
|
+
|
|
53
|
+
onRowsRendered: (params: RenderedRows) => void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Callback invoked whenever the scroll offset changes within the inner scrollable region.
|
|
57
|
+
* This callback can be used to sync scrolling between lists, tables, or grids.
|
|
58
|
+
*/
|
|
59
|
+
onScroll: (params: Scroll) => void;
|
|
60
|
+
|
|
61
|
+
/** See Grid#overscanIndicesGetter */
|
|
62
|
+
overscanIndicesGetter: OverscanIndicesGetter;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Number of rows to render above/below the visible bounds of the list.
|
|
66
|
+
* These rows can help for smoother scrolling on touch devices.
|
|
67
|
+
*/
|
|
68
|
+
overscanRowCount: number;
|
|
69
|
+
|
|
70
|
+
/** Either a fixed row height (number) or a function that returns the height of a row given its index. */
|
|
71
|
+
rowHeight: CellSize;
|
|
72
|
+
|
|
73
|
+
/** Responsible for rendering a row given an index; ({ index: number }): node */
|
|
74
|
+
rowRenderer: RowRenderer;
|
|
75
|
+
|
|
76
|
+
/** Number of rows in list. */
|
|
77
|
+
rowCount: number;
|
|
78
|
+
|
|
79
|
+
/** See Grid#scrollToAlignment */
|
|
80
|
+
scrollToAlignment: Alignment;
|
|
81
|
+
|
|
82
|
+
/** Row index to ensure visible (by forcefully scrolling if necessary) */
|
|
83
|
+
scrollToIndex: number;
|
|
84
|
+
|
|
85
|
+
/** Vertical offset. */
|
|
86
|
+
scrollTop?: number;
|
|
87
|
+
|
|
88
|
+
/** Optional inline style */
|
|
89
|
+
style: any;
|
|
90
|
+
|
|
91
|
+
/** Tab index for focus */
|
|
92
|
+
tabIndex?: number;
|
|
93
|
+
|
|
94
|
+
/** Width of list */
|
|
95
|
+
width: number;
|
|
96
|
+
|
|
97
|
+
// 每个cell的高度
|
|
98
|
+
cellsHeight: number[];
|
|
99
|
+
|
|
100
|
+
editorsOffset: number[];
|
|
101
|
+
|
|
102
|
+
editorAreaHeight: number[];
|
|
103
|
+
|
|
104
|
+
totalSize: number;
|
|
105
|
+
|
|
106
|
+
noEditorArea: React.ReactElement;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default class List extends React.PureComponent<Props> {
|
|
110
|
+
static defaultProps = {
|
|
111
|
+
autoHeight: false,
|
|
112
|
+
estimatedRowSize: 30,
|
|
113
|
+
onScroll: () => {
|
|
114
|
+
//
|
|
115
|
+
},
|
|
116
|
+
noRowsRenderer: () => null,
|
|
117
|
+
onRowsRendered: () => {
|
|
118
|
+
//
|
|
119
|
+
},
|
|
120
|
+
overscanIndicesGetter: accessibilityOverscanIndicesGetter,
|
|
121
|
+
overscanRowCount: 10,
|
|
122
|
+
scrollToAlignment: 'auto',
|
|
123
|
+
scrollToIndex: -1,
|
|
124
|
+
style: {},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
Grid?: React.ElementRef<typeof Grid> | null;
|
|
128
|
+
|
|
129
|
+
forceUpdateGrid() {
|
|
130
|
+
if (this.Grid) {
|
|
131
|
+
this.Grid.forceUpdate();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** See Grid#getOffsetForCell */
|
|
136
|
+
getOffsetForRow({ alignment, index }: { alignment: Alignment; index: number }) {
|
|
137
|
+
if (this.Grid) {
|
|
138
|
+
const { scrollTop } = this.Grid.getOffsetForCell({
|
|
139
|
+
alignment,
|
|
140
|
+
rowIndex: index,
|
|
141
|
+
columnIndex: 0,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return scrollTop;
|
|
145
|
+
}
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** CellMeasurer compatibility */
|
|
150
|
+
invalidateCellSizeAfterRender({ columnIndex, rowIndex }: CellPosition) {
|
|
151
|
+
if (this.Grid) {
|
|
152
|
+
this.Grid.invalidateCellSizeAfterRender({
|
|
153
|
+
rowIndex,
|
|
154
|
+
columnIndex,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** See Grid#measureAllCells */
|
|
160
|
+
measureAllRows() {
|
|
161
|
+
if (this.Grid) {
|
|
162
|
+
this.Grid.measureAllCells();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** CellMeasurer compatibility */
|
|
167
|
+
recomputeGridSize(
|
|
168
|
+
{ columnIndex = 0, rowIndex = 0 }: CellPosition = { columnIndex: 0, rowIndex: 0 },
|
|
169
|
+
) {
|
|
170
|
+
if (this.Grid) {
|
|
171
|
+
this.Grid.recomputeGridSize({
|
|
172
|
+
rowIndex,
|
|
173
|
+
columnIndex,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** See Grid#recomputeGridSize */
|
|
179
|
+
recomputeRowHeights(index = 0) {
|
|
180
|
+
if (this.Grid) {
|
|
181
|
+
this.Grid.recomputeGridSize({
|
|
182
|
+
rowIndex: index,
|
|
183
|
+
columnIndex: 0,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
scrollToLine(cellIndex = 0, lineIndex = 0) {
|
|
189
|
+
if (this.Grid) {
|
|
190
|
+
this.Grid.scrollToLine({
|
|
191
|
+
cellIndex,
|
|
192
|
+
lineIndex,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** See Grid#scrollToPosition */
|
|
198
|
+
scrollToPosition(scrollTop = 0) {
|
|
199
|
+
if (this.Grid) {
|
|
200
|
+
this.Grid.scrollToPosition({ scrollTop });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** See Grid#scrollToCell */
|
|
205
|
+
scrollToRow(index = 0) {
|
|
206
|
+
if (this.Grid) {
|
|
207
|
+
this.Grid.scrollToCell({
|
|
208
|
+
columnIndex: 0,
|
|
209
|
+
rowIndex: index,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
override render() {
|
|
215
|
+
const { className, noRowsRenderer, scrollToIndex, width } = this.props;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<Grid
|
|
219
|
+
resizeObserver={{} as any}
|
|
220
|
+
{...this.props}
|
|
221
|
+
autoContainerWidth
|
|
222
|
+
cellRenderer={this._cellRenderer}
|
|
223
|
+
className={classNames('ReactVirtualized__List', className)}
|
|
224
|
+
columnWidth={width}
|
|
225
|
+
columnCount={1}
|
|
226
|
+
noContentRenderer={noRowsRenderer}
|
|
227
|
+
onScroll={this._onScroll}
|
|
228
|
+
onSectionRendered={this._onSectionRendered}
|
|
229
|
+
ref={this._setRef}
|
|
230
|
+
scrollToRow={scrollToIndex}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_cellRenderer = ({
|
|
236
|
+
parent,
|
|
237
|
+
rowIndex,
|
|
238
|
+
style,
|
|
239
|
+
isScrolling,
|
|
240
|
+
isVisible,
|
|
241
|
+
key,
|
|
242
|
+
}: CellRendererParams) => {
|
|
243
|
+
const { rowRenderer } = this.props;
|
|
244
|
+
|
|
245
|
+
// TRICKY The style object is sometimes cached by Grid.
|
|
246
|
+
// This prevents new style objects from bypassing shallowCompare().
|
|
247
|
+
// However as of React 16, style props are auto-frozen (at least in dev mode)
|
|
248
|
+
// Check to make sure we can still modify the style before proceeding.
|
|
249
|
+
// https://github.com/facebook/react/commit/977357765b44af8ff0cfea327866861073095c12#commitcomment-20648713
|
|
250
|
+
// const { writable } = Object.getOwnPropertyDescriptor(style, 'width');
|
|
251
|
+
// if (writable) {
|
|
252
|
+
// // By default, List cells should be 100% width.
|
|
253
|
+
// // This prevents them from flowing under a scrollbar (if present).
|
|
254
|
+
// style.width = '100%';
|
|
255
|
+
// }
|
|
256
|
+
|
|
257
|
+
return rowRenderer({
|
|
258
|
+
index: rowIndex,
|
|
259
|
+
style,
|
|
260
|
+
isScrolling,
|
|
261
|
+
isVisible,
|
|
262
|
+
key,
|
|
263
|
+
parent,
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
_setRef = (ref: React.ElementRef<typeof Grid> | null) => {
|
|
268
|
+
this.Grid = ref;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
_onScroll = ({ clientHeight, scrollHeight, scrollTop }: GridScroll) => {
|
|
272
|
+
const { onScroll } = this.props;
|
|
273
|
+
|
|
274
|
+
onScroll({ clientHeight, scrollHeight, scrollTop });
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
_onSectionRendered = ({
|
|
278
|
+
rowOverscanStartIndex,
|
|
279
|
+
rowOverscanStopIndex,
|
|
280
|
+
rowStartIndex,
|
|
281
|
+
rowStopIndex,
|
|
282
|
+
}: RenderedSection) => {
|
|
283
|
+
const { onRowsRendered } = this.props;
|
|
284
|
+
|
|
285
|
+
onRowsRendered({
|
|
286
|
+
overscanStartIndex: rowOverscanStartIndex,
|
|
287
|
+
overscanStopIndex: rowOverscanStopIndex,
|
|
288
|
+
startIndex: rowStartIndex,
|
|
289
|
+
stopIndex: rowStopIndex,
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type RowRendererParams = {
|
|
4
|
+
index: number;
|
|
5
|
+
isScrolling: boolean;
|
|
6
|
+
isVisible: boolean;
|
|
7
|
+
key: string;
|
|
8
|
+
parent: Record<any, any>;
|
|
9
|
+
style: Record<any, any>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type RowRenderer = (params: RowRendererParams) => React.ReactNode;
|
|
13
|
+
|
|
14
|
+
export type RenderedRows = {
|
|
15
|
+
overscanStartIndex: number;
|
|
16
|
+
overscanStopIndex: number;
|
|
17
|
+
startIndex: number;
|
|
18
|
+
stopIndex: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type Scroll = {
|
|
22
|
+
clientHeight: number;
|
|
23
|
+
scrollHeight: number;
|
|
24
|
+
scrollTop: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type Callback = (timestamp: number) => void;
|
|
2
|
+
type CancelAnimationFrame = (requestId: number) => void;
|
|
3
|
+
type RequestAnimationFrame = (callback: Callback) => number;
|
|
4
|
+
|
|
5
|
+
// Properly handle server-side rendering.
|
|
6
|
+
let win: any;
|
|
7
|
+
if (typeof window !== 'undefined') {
|
|
8
|
+
win = window;
|
|
9
|
+
} else if (typeof self !== 'undefined') {
|
|
10
|
+
win = self;
|
|
11
|
+
} else {
|
|
12
|
+
win = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// requestAnimationFrame() shim by Paul Irish
|
|
16
|
+
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
|
17
|
+
const request =
|
|
18
|
+
win.requestAnimationFrame ||
|
|
19
|
+
win.webkitRequestAnimationFrame ||
|
|
20
|
+
win.mozRequestAnimationFrame ||
|
|
21
|
+
win.oRequestAnimationFrame ||
|
|
22
|
+
win.msRequestAnimationFrame ||
|
|
23
|
+
function (callback: Callback): RequestAnimationFrame {
|
|
24
|
+
return win.setTimeout(callback, 1000 / 60);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const cancel =
|
|
28
|
+
win.cancelAnimationFrame ||
|
|
29
|
+
win.webkitCancelAnimationFrame ||
|
|
30
|
+
win.mozCancelAnimationFrame ||
|
|
31
|
+
win.oCancelAnimationFrame ||
|
|
32
|
+
win.msCancelAnimationFrame ||
|
|
33
|
+
function (id: number) {
|
|
34
|
+
win.clearTimeout(id);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const raf: RequestAnimationFrame = request;
|
|
38
|
+
export const caf: CancelAnimationFrame = cancel;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper utility that updates the specified callback whenever any of the specified indices have changed.
|
|
3
|
+
*/
|
|
4
|
+
export default function createCallbackMemoizer(requireAllKeys = true) {
|
|
5
|
+
let cachedIndices: Record<any, any> = {};
|
|
6
|
+
|
|
7
|
+
return ({ callback, indices }: any) => {
|
|
8
|
+
const keys = Object.keys(indices);
|
|
9
|
+
const allInitialized =
|
|
10
|
+
!requireAllKeys ||
|
|
11
|
+
keys.every((key) => {
|
|
12
|
+
const value = indices[key];
|
|
13
|
+
return Array.isArray(value) ? value.length > 0 : value >= 0;
|
|
14
|
+
});
|
|
15
|
+
const indexChanged =
|
|
16
|
+
keys.length !== Object.keys(cachedIndices).length ||
|
|
17
|
+
keys.some((key) => {
|
|
18
|
+
const cachedValue = cachedIndices[key];
|
|
19
|
+
const value = indices[key];
|
|
20
|
+
|
|
21
|
+
return Array.isArray(value)
|
|
22
|
+
? cachedValue.join(',') !== value.join(',')
|
|
23
|
+
: cachedValue !== value;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
cachedIndices = indices;
|
|
27
|
+
|
|
28
|
+
if (allInitialized && indexChanged) {
|
|
29
|
+
callback(indices);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|