@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.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/auto-sizer/auto-sizer.d.ts +56 -0
  4. package/es/auto-sizer/auto-sizer.d.ts.map +1 -0
  5. package/es/auto-sizer/auto-sizer.js +157 -0
  6. package/es/auto-sizer/index.d.ts +3 -0
  7. package/es/auto-sizer/index.d.ts.map +1 -0
  8. package/es/auto-sizer/index.js +2 -0
  9. package/es/cell-measurer/cell-measurer-cache.d.ts +52 -0
  10. package/es/cell-measurer/cell-measurer-cache.d.ts.map +1 -0
  11. package/es/cell-measurer/cell-measurer-cache.js +176 -0
  12. package/es/cell-measurer/cell-measurer.d.ts +39 -0
  13. package/es/cell-measurer/cell-measurer.d.ts.map +1 -0
  14. package/es/cell-measurer/cell-measurer.js +154 -0
  15. package/es/cell-measurer/index.d.ts +5 -0
  16. package/es/cell-measurer/index.d.ts.map +1 -0
  17. package/es/cell-measurer/index.js +4 -0
  18. package/es/cell-measurer/types.d.ts +9 -0
  19. package/es/cell-measurer/types.d.ts.map +1 -0
  20. package/es/cell-measurer/types.js +0 -0
  21. package/es/grid/accessibility-overscanIndices-getter.d.ts +11 -0
  22. package/es/grid/accessibility-overscanIndices-getter.d.ts.map +1 -0
  23. package/es/grid/accessibility-overscanIndices-getter.js +33 -0
  24. package/es/grid/default-cell-range-renderer.d.ts +10 -0
  25. package/es/grid/default-cell-range-renderer.d.ts.map +1 -0
  26. package/es/grid/default-cell-range-renderer.js +135 -0
  27. package/es/grid/default-overscanIndices-getter.d.ts +11 -0
  28. package/es/grid/default-overscanIndices-getter.d.ts.map +1 -0
  29. package/es/grid/default-overscanIndices-getter.js +28 -0
  30. package/es/grid/grid.d.ts +359 -0
  31. package/es/grid/grid.d.ts.map +1 -0
  32. package/es/grid/grid.js +1287 -0
  33. package/es/grid/index.d.ts +6 -0
  34. package/es/grid/index.d.ts.map +1 -0
  35. package/es/grid/index.js +4 -0
  36. package/es/grid/types.d.ts +87 -0
  37. package/es/grid/types.d.ts.map +1 -0
  38. package/es/grid/types.js +1 -0
  39. package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.d.ts +17 -0
  40. package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.d.ts.map +1 -0
  41. package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.js +25 -0
  42. package/es/grid/utils/cell-size-and-position-manager-row.d.ts +97 -0
  43. package/es/grid/utils/cell-size-and-position-manager-row.d.ts.map +1 -0
  44. package/es/grid/utils/cell-size-and-position-manager-row.js +297 -0
  45. package/es/grid/utils/cell-size-and-position-manager.d.ts +85 -0
  46. package/es/grid/utils/cell-size-and-position-manager.d.ts.map +1 -0
  47. package/es/grid/utils/cell-size-and-position-manager.js +268 -0
  48. package/es/grid/utils/max-element-size.d.ts +2 -0
  49. package/es/grid/utils/max-element-size.d.ts.map +1 -0
  50. package/es/grid/utils/max-element-size.js +17 -0
  51. package/es/grid/utils/scaling-cell-size-and-position-manager-row.d.ts +78 -0
  52. package/es/grid/utils/scaling-cell-size-and-position-manager-row.d.ts.map +1 -0
  53. package/es/grid/utils/scaling-cell-size-and-position-manager-row.js +187 -0
  54. package/es/grid/utils/scaling-cell-size-and-position-manager.d.ts +70 -0
  55. package/es/grid/utils/scaling-cell-size-and-position-manager.d.ts.map +1 -0
  56. package/es/grid/utils/scaling-cell-size-and-position-manager.js +187 -0
  57. package/es/grid/utils/update-scroll-index-helper.d.ts +24 -0
  58. package/es/grid/utils/update-scroll-index-helper.d.ts.map +1 -0
  59. package/es/grid/utils/update-scroll-index-helper.js +40 -0
  60. package/es/index.d.ts +5 -0
  61. package/es/index.d.ts.map +1 -0
  62. package/es/index.js +4 -0
  63. package/es/list/index.d.ts +3 -0
  64. package/es/list/index.d.ts.map +1 -0
  65. package/es/list/index.js +1 -0
  66. package/es/list/list.d.ts +109 -0
  67. package/es/list/list.d.ts.map +1 -0
  68. package/es/list/list.js +261 -0
  69. package/es/list/types.d.ts +22 -0
  70. package/es/list/types.d.ts.map +1 -0
  71. package/es/list/types.js +1 -0
  72. package/es/utils/animation-frame.d.ts +7 -0
  73. package/es/utils/animation-frame.d.ts.map +1 -0
  74. package/es/utils/animation-frame.js +20 -0
  75. package/es/utils/create-callback-memoizer.d.ts +5 -0
  76. package/es/utils/create-callback-memoizer.d.ts.map +1 -0
  77. package/es/utils/create-callback-memoizer.js +25 -0
  78. package/es/utils/get-updated-offset-for-index.d.ts +14 -0
  79. package/es/utils/get-updated-offset-for-index.d.ts.map +1 -0
  80. package/es/utils/get-updated-offset-for-index.js +32 -0
  81. package/es/utils/init-cell-metadata.d.ts +13 -0
  82. package/es/utils/init-cell-metadata.d.ts.map +1 -0
  83. package/es/utils/init-cell-metadata.js +32 -0
  84. package/es/utils/request-animation-timeout.d.ts +12 -0
  85. package/es/utils/request-animation-timeout.d.ts.map +1 -0
  86. package/es/utils/request-animation-timeout.js +33 -0
  87. package/es/utils/test-helper.d.ts +5 -0
  88. package/es/utils/test-helper.d.ts.map +1 -0
  89. package/es/utils/test-helper.js +31 -0
  90. package/es/vendor/binary-search-bounds.d.ts +22 -0
  91. package/es/vendor/binary-search-bounds.d.ts.map +1 -0
  92. package/es/vendor/binary-search-bounds.js +198 -0
  93. package/es/vendor/detect-element-resize.d.ts +16 -0
  94. package/es/vendor/detect-element-resize.d.ts.map +1 -0
  95. package/es/vendor/detect-element-resize.js +184 -0
  96. package/es/vendor/interval-tree.d.ts +10 -0
  97. package/es/vendor/interval-tree.d.ts.map +1 -0
  98. package/es/vendor/interval-tree.js +359 -0
  99. package/package.json +59 -0
  100. package/src/auto-sizer/auto-sizer.tsx +187 -0
  101. package/src/auto-sizer/index.ts +4 -0
  102. package/src/cell-measurer/cell-measurer-cache.ts +220 -0
  103. package/src/cell-measurer/cell-measurer.ts +151 -0
  104. package/src/cell-measurer/index.ts +5 -0
  105. package/src/cell-measurer/types.ts +8 -0
  106. package/src/grid/accessibility-overscanIndices-getter.ts +38 -0
  107. package/src/grid/default-cell-range-renderer.ts +166 -0
  108. package/src/grid/default-overscanIndices-getter.ts +32 -0
  109. package/src/grid/grid.tsx +1672 -0
  110. package/src/grid/index.ts +14 -0
  111. package/src/grid/types.ts +112 -0
  112. package/src/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.ts +62 -0
  113. package/src/grid/utils/cell-size-and-position-manager-row.ts +365 -0
  114. package/src/grid/utils/cell-size-and-position-manager.ts +309 -0
  115. package/src/grid/utils/max-element-size.ts +18 -0
  116. package/src/grid/utils/scaling-cell-size-and-position-manager-row.ts +206 -0
  117. package/src/grid/utils/scaling-cell-size-and-position-manager.ts +198 -0
  118. package/src/grid/utils/update-scroll-index-helper.ts +96 -0
  119. package/src/index.spec.ts +10 -0
  120. package/src/index.ts +4 -0
  121. package/src/list/index.ts +2 -0
  122. package/src/list/list.tsx +292 -0
  123. package/src/list/types.ts +25 -0
  124. package/src/utils/animation-frame.ts +38 -0
  125. package/src/utils/create-callback-memoizer.ts +32 -0
  126. package/src/utils/get-updated-offset-for-index.ts +33 -0
  127. package/src/utils/init-cell-metadata.ts +32 -0
  128. package/src/utils/request-animation-timeout.ts +44 -0
  129. package/src/utils/test-helper.ts +20 -0
  130. package/src/vendor/binary-search-bounds.ts +203 -0
  131. package/src/vendor/detect-element-resize.ts +241 -0
  132. package/src/vendor/interval-tree.ts +406 -0
@@ -0,0 +1,14 @@
1
+ export { default as accessibilityOverscanIndicesGetter } from './accessibility-overscanIndices-getter.js';
2
+ export { default as defaultCellRangeRenderer } from './default-cell-range-renderer.js';
3
+ export { default as defaultOverscanIndicesGetter } from './default-overscanIndices-getter.js';
4
+ export { default, default as Grid } from './grid.js';
5
+ export type {
6
+ Alignment,
7
+ CellPosition,
8
+ CellRendererParams,
9
+ CellSize,
10
+ NoContentRenderer,
11
+ OverscanIndicesGetter,
12
+ RenderedSection,
13
+ Scroll,
14
+ } from './types.js';
@@ -0,0 +1,112 @@
1
+ import type * as React from 'react';
2
+
3
+ import type ScalingCellSizeAndPositionManager from './utils/scaling-cell-size-and-position-manager.js';
4
+
5
+ export type CellPosition = { columnIndex: number; rowIndex: number };
6
+
7
+ export type CellRendererParams = {
8
+ columnIndex: number;
9
+ isScrolling: boolean;
10
+ isVisible: boolean;
11
+ key: string;
12
+ parent: object;
13
+ rowIndex: number;
14
+ style: object;
15
+ };
16
+
17
+ export type CellRenderer = (props: CellRendererParams) => React.ReactNode;
18
+
19
+ export type CellCache = Record<string, React.ReactNode>;
20
+ export type StyleCache = Record<string, object>;
21
+
22
+ export type CellRangeRendererParams = {
23
+ cellCache: CellCache;
24
+ cellRenderer: CellRenderer;
25
+ columnSizeAndPositionManager: ScalingCellSizeAndPositionManager;
26
+ columnStartIndex: number;
27
+ columnStopIndex: number;
28
+ deferredMeasurementCache?: object;
29
+ horizontalOffsetAdjustment: number;
30
+ isScrolling: boolean;
31
+ isScrollingOptOut: boolean;
32
+ parent: object;
33
+ rowSizeAndPositionManager: ScalingCellSizeAndPositionManager;
34
+ rowStartIndex: number;
35
+ rowStopIndex: number;
36
+ scrollLeft: number;
37
+ scrollTop: number;
38
+ styleCache: StyleCache;
39
+ verticalOffsetAdjustment: number;
40
+ visibleColumnIndices: Record<any, any>;
41
+ visibleRowIndices: Record<any, any>;
42
+ };
43
+
44
+ export type CellRangeRenderer = (params: CellRangeRendererParams) => React.ReactNode[];
45
+
46
+ export type CellSizeGetter = (params: { index: number }) => number;
47
+
48
+ export type CellSize = CellSizeGetter | number;
49
+
50
+ export type NoContentRenderer = () => React.ReactNode | null;
51
+
52
+ export type Scroll = {
53
+ clientHeight: number;
54
+ clientWidth: number;
55
+ scrollHeight: number;
56
+ scrollLeft: number;
57
+ scrollTop: number;
58
+ scrollWidth: number;
59
+ };
60
+
61
+ export type ScrollbarPresenceChange = {
62
+ horizontal: boolean;
63
+ vertical: boolean;
64
+ size: number;
65
+ };
66
+
67
+ export type RenderedSection = {
68
+ columnOverscanStartIndex: number;
69
+ columnOverscanStopIndex: number;
70
+ columnStartIndex: number;
71
+ columnStopIndex: number;
72
+ rowOverscanStartIndex: number;
73
+ rowOverscanStopIndex: number;
74
+ rowStartIndex: number;
75
+ rowStopIndex: number;
76
+ };
77
+
78
+ export type OverscanIndicesGetterParams = {
79
+ // One of SCROLL_DIRECTION_HORIZONTAL or SCROLL_DIRECTION_VERTICAL
80
+ direction: 'horizontal' | 'vertical';
81
+
82
+ // One of SCROLL_DIRECTION_BACKWARD or SCROLL_DIRECTION_FORWARD
83
+ scrollDirection: -1 | 1;
84
+
85
+ // Number of rows or columns in the current axis
86
+ cellCount: number;
87
+
88
+ // Maximum number of cells to over-render in either direction
89
+ overscanCellsCount: number;
90
+
91
+ // Begin of range of visible cells
92
+ startIndex: number;
93
+
94
+ // End of range of visible cells
95
+ stopIndex: number;
96
+ };
97
+
98
+ export type OverscanIndices = {
99
+ overscanStartIndex: number;
100
+ overscanStopIndex: number;
101
+ };
102
+
103
+ export type OverscanIndicesGetter = (
104
+ params: OverscanIndicesGetterParams,
105
+ ) => OverscanIndices;
106
+
107
+ export type Alignment = 'auto' | 'end' | 'start' | 'center';
108
+
109
+ export type VisibleCellRange = {
110
+ start?: number;
111
+ stop?: number;
112
+ };
@@ -0,0 +1,62 @@
1
+ // @flow
2
+
3
+ /**
4
+ * Helper method that determines when to recalculate row or column metadata.
5
+ */
6
+
7
+ type Params<T> = {
8
+ // Number of rows or columns in the current axis
9
+ cellCount: number;
10
+
11
+ // Width or height of cells for the current axis
12
+ cellSize?: number | null;
13
+
14
+ // Method to invoke if cell metadata should be recalculated
15
+ computeMetadataCallback: (props: T) => void;
16
+
17
+ // Parameters to pass to :computeMetadataCallback
18
+ computeMetadataCallbackProps: T;
19
+
20
+ // Newly updated number of rows or columns in the current axis
21
+ nextCellsCount: number;
22
+
23
+ // Newly updated width or height of cells for the current axis
24
+ nextCellSize?: number | null;
25
+
26
+ // Newly updated scroll-to-index
27
+ nextScrollToIndex: number;
28
+
29
+ // Scroll-to-index
30
+ scrollToIndex: number;
31
+
32
+ // Callback to invoke if the scroll position should be recalculated
33
+ updateScrollOffsetForScrollToIndex: () => void;
34
+ };
35
+
36
+ export default function calculateSizeAndPositionDataAndUpdateScrollOffset({
37
+ cellCount,
38
+ cellSize,
39
+ computeMetadataCallback,
40
+ computeMetadataCallbackProps,
41
+ nextCellsCount,
42
+ nextCellSize,
43
+ nextScrollToIndex,
44
+ scrollToIndex,
45
+ updateScrollOffsetForScrollToIndex,
46
+ }: Params<any>) {
47
+ // Don't compare cell sizes if they are functions because inline functions would cause infinite loops.
48
+ // In that event users should use the manual recompute methods to inform of changes.
49
+ if (
50
+ cellCount !== nextCellsCount ||
51
+ ((typeof cellSize === 'number' || typeof nextCellSize === 'number') &&
52
+ cellSize !== nextCellSize)
53
+ ) {
54
+ computeMetadataCallback(computeMetadataCallbackProps);
55
+
56
+ // Updated cell metadata may have hidden the previous scrolled-to item.
57
+ // In this case we should also update the scrollTop to ensure it stays visible.
58
+ if (scrollToIndex >= 0 && scrollToIndex === nextScrollToIndex) {
59
+ updateScrollOffsetForScrollToIndex();
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,365 @@
1
+ /* eslint-disable prefer-const */
2
+ /* eslint-disable @typescript-eslint/no-unused-vars */
3
+ /* eslint-disable no-param-reassign */
4
+
5
+ import type { Alignment, CellSizeGetter, VisibleCellRange } from '../types.js';
6
+
7
+ type CellSizeAndPositionManagerParams = {
8
+ cellCount: number;
9
+ cellSizeGetter: CellSizeGetter;
10
+ estimatedCellSize: number;
11
+ cellsHeight: number[];
12
+ editorAreaHeight: number[];
13
+ totalSize: number;
14
+ editorsOffset: number[];
15
+ };
16
+
17
+ type ConfigureParams = {
18
+ cellCount: number;
19
+ estimatedCellSize: number;
20
+ cellSizeGetter: CellSizeGetter;
21
+ cellsHeight: number[];
22
+ editorAreaHeight: number[];
23
+ totalSize: number;
24
+ editorsOffset: number[];
25
+ };
26
+
27
+ type GetUpdatedOffsetForIndex = {
28
+ align: Alignment;
29
+ containerSize: number;
30
+ currentOffset: number;
31
+ targetIndex: number;
32
+ };
33
+
34
+ type GetVisibleCellRangeParams = {
35
+ containerSize: number;
36
+ offset: number;
37
+ };
38
+
39
+ type SizeAndPositionData = {
40
+ offset: number;
41
+ size: number;
42
+ };
43
+
44
+ /**
45
+ * Just-in-time calculates and caches size and position information for a collection of cells.
46
+ */
47
+
48
+ export default class RowCellSizeAndPositionManager {
49
+ // Cache of size and position data for cells, mapped by cell index.
50
+ // Note that invalid values may exist in this map so only rely on cells up to this._lastMeasuredIndex
51
+ _cellSizeAndPositionData: any = {};
52
+
53
+ // Measurements for cells up to this index can be trusted; cells afterward should be estimated.
54
+ _lastMeasuredIndex = -1;
55
+
56
+ // Used in deferred mode to track which cells have been queued for measurement.
57
+ _lastBatchedIndex = -1;
58
+
59
+ _cellCount: number;
60
+ _cellSizeGetter: CellSizeGetter;
61
+ _estimatedCellSize: number;
62
+
63
+ _cellsHeight: number[];
64
+
65
+ _editorAreaHeight: number[];
66
+
67
+ _totalSize: number;
68
+
69
+ _editorsOffset: number[];
70
+
71
+ constructor({
72
+ cellCount,
73
+ cellSizeGetter,
74
+ estimatedCellSize,
75
+ cellsHeight,
76
+ editorAreaHeight,
77
+ totalSize,
78
+ editorsOffset,
79
+ }: CellSizeAndPositionManagerParams) {
80
+ // this._cellSizeGetter = cellSizeGetter;
81
+ this._cellCount = cellCount;
82
+ this._estimatedCellSize = estimatedCellSize;
83
+ this._cellsHeight = cellsHeight;
84
+ this._editorAreaHeight = editorAreaHeight;
85
+ this._editorsOffset = editorsOffset;
86
+
87
+ this._totalSize = totalSize;
88
+
89
+ this._lastMeasuredIndex = -1;
90
+ this._cellSizeAndPositionData = {};
91
+
92
+ // this._cellSizeGetter = (params: { index: number }) => {
93
+ // // return this._cellsHeight[params.index];
94
+ // return this._editorAreaHeight[params.index];
95
+ // };
96
+ }
97
+
98
+ areOffsetsAdjusted() {
99
+ return false;
100
+ }
101
+
102
+ configure({
103
+ cellCount,
104
+ estimatedCellSize,
105
+ cellSizeGetter,
106
+ cellsHeight,
107
+ editorAreaHeight,
108
+ totalSize,
109
+ editorsOffset,
110
+ }: ConfigureParams) {
111
+ this._cellCount = cellCount;
112
+ this._estimatedCellSize = estimatedCellSize;
113
+ // this._cellSizeGetter = cellSizeGetter;
114
+ this._cellsHeight = cellsHeight;
115
+
116
+ this._editorAreaHeight = editorAreaHeight;
117
+ this._editorsOffset = editorsOffset;
118
+
119
+ this._totalSize = totalSize;
120
+
121
+ this._lastMeasuredIndex = -1;
122
+ this._cellSizeAndPositionData = {};
123
+ }
124
+
125
+ getCellCount(): number {
126
+ return this._cellCount;
127
+ }
128
+
129
+ getEstimatedCellSize(): number {
130
+ return this._estimatedCellSize;
131
+ }
132
+
133
+ getLastMeasuredIndex(): number {
134
+ return this._lastMeasuredIndex;
135
+ }
136
+
137
+ getOffsetAdjustment() {
138
+ return 0;
139
+ }
140
+
141
+ /**
142
+ * This method returns the size and position for the cell at the specified index.
143
+ * It just-in-time calculates (or used cached values) for cells leading up to the index.
144
+ */
145
+ getSizeAndPositionOfCell(index: number): SizeAndPositionData {
146
+ if (index < 0 || index >= this._cellCount) {
147
+ throw Error(`Requested index ${index} is outside of range 0..${this._cellCount}`);
148
+ }
149
+
150
+ if (index > this._lastMeasuredIndex) {
151
+ const lastMeasuredCellSizeAndPosition =
152
+ this.getSizeAndPositionOfLastMeasuredCell();
153
+ let offset = lastMeasuredCellSizeAndPosition.offset; // + lastMeasuredCellSizeAndPosition.size;
154
+
155
+ for (let i = this._lastMeasuredIndex + 1; i <= index; i++) {
156
+ // const size = this._cellSizeGetter({ index: i });
157
+ const size = this._editorAreaHeight[i];
158
+ offset = this._editorsOffset[i]; // 上border高度
159
+
160
+ // undefined or NaN probably means a logic error in the size getter.
161
+ // null means we're using CellMeasurer and haven't yet measured a given index.
162
+ if (size === undefined || isNaN(size)) {
163
+ throw Error(`Invalid size returned for cell ${i} of value ${size}`);
164
+ } else if (size === null) {
165
+ this._cellSizeAndPositionData[i] = {
166
+ offset,
167
+ size: 0,
168
+ };
169
+
170
+ this._lastBatchedIndex = index;
171
+ } else {
172
+ this._cellSizeAndPositionData[i] = {
173
+ offset,
174
+ size,
175
+ };
176
+
177
+ this._lastMeasuredIndex = index;
178
+ }
179
+ }
180
+ }
181
+
182
+ return this._cellSizeAndPositionData[index];
183
+ }
184
+
185
+ getSizeAndPositionOfLastMeasuredCell(): SizeAndPositionData {
186
+ return this._lastMeasuredIndex >= 0
187
+ ? this._cellSizeAndPositionData[this._lastMeasuredIndex]
188
+ : {
189
+ offset: 0,
190
+ size: 0,
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Total size of all cells being measured.
196
+ * This value will be completely estimated initially.
197
+ * As cells are measured, the estimate will be updated.
198
+ */
199
+ getTotalSize(): number {
200
+ return this._totalSize;
201
+
202
+ // const lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
203
+ // const totalSizeOfMeasuredCells =
204
+ // lastMeasuredCellSizeAndPosition.offset + lastMeasuredCellSizeAndPosition.size;
205
+ // const numUnmeasuredCells = this._cellCount - this._lastMeasuredIndex - 1;
206
+ // const totalSizeOfUnmeasuredCells = numUnmeasuredCells * this._estimatedCellSize;
207
+ // return totalSizeOfMeasuredCells + totalSizeOfUnmeasuredCells;
208
+ }
209
+
210
+ /**
211
+ * Determines a new offset that ensures a certain cell is visible, given the current offset.
212
+ * If the cell is already visible then the current offset will be returned.
213
+ * If the current offset is too great or small, it will be adjusted just enough to ensure the specified index is visible.
214
+ *
215
+ * @param align Desired alignment within container; one of "auto" (default), "start", or "end"
216
+ * @param containerSize Size (width or height) of the container viewport
217
+ * @param currentOffset Container's current (x or y) offset
218
+ * @param totalSize Total size (width or height) of all cells
219
+ * @return Offset to use to ensure the specified cell is visible
220
+ */
221
+ getUpdatedOffsetForIndex({
222
+ align = 'auto',
223
+ containerSize,
224
+ currentOffset,
225
+ targetIndex,
226
+ }: GetUpdatedOffsetForIndex): number {
227
+ if (containerSize <= 0) {
228
+ return 0;
229
+ }
230
+
231
+ const datum = this.getSizeAndPositionOfCell(targetIndex);
232
+ const minOffset = datum.offset;
233
+
234
+ const maxOffset = datum.offset + datum.size - containerSize;
235
+
236
+ let idealOffset;
237
+
238
+ switch (align) {
239
+ case 'start':
240
+ idealOffset = minOffset;
241
+ break;
242
+ case 'end':
243
+ idealOffset = maxOffset;
244
+ break;
245
+ case 'center':
246
+ idealOffset = minOffset + datum.size / 2;
247
+ break;
248
+ default:
249
+ idealOffset = minOffset;
250
+ break;
251
+ }
252
+
253
+ const totalSize = this.getTotalSize();
254
+
255
+ return Math.max(0, Math.min(totalSize - containerSize, idealOffset));
256
+ }
257
+
258
+ getVisibleCellRange(params: GetVisibleCellRangeParams): VisibleCellRange {
259
+ let { containerSize, offset } = params;
260
+
261
+ const totalSize = this.getTotalSize();
262
+
263
+ if (totalSize === 0) {
264
+ return {};
265
+ }
266
+
267
+ const maxOffset = offset + containerSize;
268
+ const start = this._findNearestCell(offset);
269
+
270
+ const datum = this.getSizeAndPositionOfCell(start);
271
+ offset = datum.offset;
272
+
273
+ let stop = start;
274
+
275
+ while (offset < maxOffset && stop < this._cellCount - 1) {
276
+ stop++;
277
+
278
+ offset = this.getSizeAndPositionOfCell(stop).offset;
279
+ }
280
+
281
+ return {
282
+ start,
283
+ stop,
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Clear all cached values for cells after the specified index.
289
+ * This method should be called for any cell that has changed its size.
290
+ * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionOfCell() is called.
291
+ */
292
+ resetCell(index: number): void {
293
+ this._lastMeasuredIndex = Math.min(this._lastMeasuredIndex, index - 1);
294
+ }
295
+
296
+ _binarySearch(high: number, low: number, offset: number): number {
297
+ while (low <= high) {
298
+ const middle = low + Math.floor((high - low) / 2);
299
+ const currentOffset =
300
+ this.getSizeAndPositionOfCell(middle).offset ||
301
+ (middle > 0 ? this.getSizeAndPositionOfCell(middle - 1).offset || 0 : 0);
302
+
303
+ if (currentOffset === offset) {
304
+ return middle;
305
+ } else if (currentOffset < offset) {
306
+ low = middle + 1;
307
+ } else if (currentOffset > offset) {
308
+ high = middle - 1;
309
+ }
310
+ }
311
+
312
+ if (low > 0) {
313
+ return low - 1;
314
+ } else {
315
+ return 0;
316
+ }
317
+ }
318
+
319
+ _exponentialSearch(index: number, offset: number): number {
320
+ let interval = 1;
321
+
322
+ while (
323
+ index < this._cellCount &&
324
+ this.getSizeAndPositionOfCell(index).offset < offset
325
+ ) {
326
+ index += interval;
327
+ interval *= 2;
328
+ }
329
+
330
+ return this._binarySearch(
331
+ Math.min(index, this._cellCount - 1),
332
+ Math.floor(index / 2),
333
+ offset,
334
+ );
335
+ }
336
+
337
+ /**
338
+ * Searches for the cell (index) nearest the specified offset.
339
+ *
340
+ * If no exact match is found the next lowest cell index will be returned.
341
+ * This allows partially visible cells (with offsets just before/above the fold) to be visible.
342
+ */
343
+ _findNearestCell(offset: number): number {
344
+ if (isNaN(offset)) {
345
+ throw Error(`Invalid offset ${offset} specified`);
346
+ }
347
+
348
+ // Our search algorithms find the nearest match at or below the specified offset.
349
+ // So make sure the offset is at least 0 or no match will be found.
350
+ offset = Math.max(0, offset);
351
+
352
+ const lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
353
+ const lastMeasuredIndex = Math.max(0, this._lastMeasuredIndex);
354
+
355
+ if (lastMeasuredCellSizeAndPosition.offset >= offset) {
356
+ // If we've already measured cells within this range just use a binary search as it's faster.
357
+ return this._binarySearch(lastMeasuredIndex, 0, offset);
358
+ } else {
359
+ // If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
360
+ // The exponential search avoids pre-computing sizes for the full set of cells as a binary search would.
361
+ // The overall complexity for this approach is O(log n).
362
+ return this._exponentialSearch(lastMeasuredIndex, offset);
363
+ }
364
+ }
365
+ }