@react-stately/layout 4.0.3 → 4.1.0

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/src/ListLayout.ts CHANGED
@@ -51,16 +51,15 @@ const DEFAULT_HEIGHT = 48;
51
51
  * the virtualizer itself).
52
52
  */
53
53
  export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTargetDelegate {
54
- protected rowHeight: number;
55
- protected estimatedRowHeight: number;
56
- protected headingHeight: number;
57
- protected estimatedHeadingHeight: number;
58
- protected loaderHeight: number;
54
+ protected rowHeight: number | null;
55
+ protected estimatedRowHeight: number | null;
56
+ protected headingHeight: number | null;
57
+ protected estimatedHeadingHeight: number | null;
58
+ protected loaderHeight: number | null;
59
59
  protected dropIndicatorThickness: number;
60
60
  protected layoutNodes: Map<Key, LayoutNode>;
61
61
  protected contentSize: Size;
62
- protected collection: Collection<Node<T>>;
63
- private lastCollection: Collection<Node<T>>;
62
+ protected lastCollection: Collection<Node<T>> | null;
64
63
  private lastWidth: number;
65
64
  protected rootNodes: LayoutNode[];
66
65
  private invalidateEverything: boolean;
@@ -75,21 +74,27 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
75
74
  */
76
75
  constructor(options: ListLayoutOptions = {}) {
77
76
  super();
78
- this.rowHeight = options.rowHeight;
79
- this.estimatedRowHeight = options.estimatedRowHeight;
80
- this.headingHeight = options.headingHeight;
81
- this.estimatedHeadingHeight = options.estimatedHeadingHeight;
82
- this.loaderHeight = options.loaderHeight;
77
+ this.rowHeight = options.rowHeight ?? null;
78
+ this.estimatedRowHeight = options.estimatedRowHeight ?? null;
79
+ this.headingHeight = options.headingHeight ?? null;
80
+ this.estimatedHeadingHeight = options.estimatedHeadingHeight ?? null;
81
+ this.loaderHeight = options.loaderHeight ?? null;
83
82
  this.dropIndicatorThickness = options.dropIndicatorThickness || 2;
84
83
  this.layoutNodes = new Map();
85
84
  this.rootNodes = [];
86
85
  this.lastWidth = 0;
87
86
  this.lastCollection = null;
87
+ this.invalidateEverything = false;
88
88
  this.validRect = new Rect();
89
89
  this.requestedRect = new Rect();
90
90
  this.contentSize = new Size();
91
91
  }
92
92
 
93
+ // Backward compatibility for subclassing.
94
+ protected get collection(): Collection<Node<T>> {
95
+ return this.virtualizer!.collection;
96
+ }
97
+
93
98
  getLayoutInfo(key: Key) {
94
99
  this.ensureLayoutInfo(key);
95
100
  return this.layoutNodes.get(key)?.layoutInfo || null;
@@ -99,7 +104,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
99
104
  // Adjust rect to keep number of visible rows consistent.
100
105
  // (only if height > 1 for getDropTargetFromPoint)
101
106
  if (rect.height > 1) {
102
- let rowHeight = (this.rowHeight ?? this.estimatedRowHeight);
107
+ let rowHeight = this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT;
103
108
  rect.y = Math.floor(rect.y / rowHeight) * rowHeight;
104
109
  rect.height = Math.ceil(rect.height / rowHeight) * rowHeight;
105
110
  }
@@ -137,7 +142,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
137
142
  }
138
143
 
139
144
  // Ensure all of the persisted keys are available.
140
- for (let key of this.virtualizer.persistedKeys) {
145
+ for (let key of this.virtualizer!.persistedKeys) {
141
146
  if (this.ensureLayoutInfo(key)) {
142
147
  return;
143
148
  }
@@ -159,32 +164,32 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
159
164
  }
160
165
 
161
166
  protected isVisible(node: LayoutNode, rect: Rect) {
162
- return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || this.virtualizer.isPersistedKey(node.layoutInfo.key);
167
+ return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || this.virtualizer!.isPersistedKey(node.layoutInfo.key);
163
168
  }
164
169
 
165
170
  protected shouldInvalidateEverything(invalidationContext: InvalidationContext<O>) {
166
171
  // Invalidate cache if the size of the collection changed.
167
172
  // In this case, we need to recalculate the entire layout.
168
- return invalidationContext.sizeChanged;
173
+ return invalidationContext.sizeChanged || false;
169
174
  }
170
175
 
171
176
  update(invalidationContext: InvalidationContext<O>) {
172
- this.collection = this.virtualizer.collection;
177
+ let collection = this.virtualizer!.collection;
173
178
 
174
179
  // Reset valid rect if we will have to invalidate everything.
175
180
  // Otherwise we can reuse cached layout infos outside the current visible rect.
176
181
  this.invalidateEverything = this.shouldInvalidateEverything(invalidationContext);
177
182
  if (this.invalidateEverything) {
178
- this.requestedRect = this.virtualizer.visibleRect.copy();
183
+ this.requestedRect = this.virtualizer!.visibleRect.copy();
179
184
  this.layoutNodes.clear();
180
185
  }
181
186
 
182
187
  this.rootNodes = this.buildCollection();
183
188
 
184
189
  // Remove deleted layout nodes
185
- if (this.lastCollection && this.collection !== this.lastCollection) {
190
+ if (this.lastCollection && collection !== this.lastCollection) {
186
191
  for (let key of this.lastCollection.getKeys()) {
187
- if (!this.collection.getItem(key)) {
192
+ if (!collection.getItem(key)) {
188
193
  let layoutNode = this.layoutNodes.get(key);
189
194
  if (layoutNode) {
190
195
  this.layoutNodes.delete(key);
@@ -193,17 +198,18 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
193
198
  }
194
199
  }
195
200
 
196
- this.lastWidth = this.virtualizer.visibleRect.width;
197
- this.lastCollection = this.collection;
201
+ this.lastWidth = this.virtualizer!.visibleRect.width;
202
+ this.lastCollection = collection;
198
203
  this.invalidateEverything = false;
199
204
  this.validRect = this.requestedRect.copy();
200
205
  }
201
206
 
202
207
  protected buildCollection(y = 0): LayoutNode[] {
208
+ let collection = this.virtualizer!.collection;
203
209
  let skipped = 0;
204
- let nodes = [];
205
- for (let node of this.collection) {
206
- let rowHeight = this.rowHeight ?? this.estimatedRowHeight;
210
+ let nodes: LayoutNode[] = [];
211
+ for (let node of collection) {
212
+ let rowHeight = this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT;
207
213
 
208
214
  // Skip rows before the valid rectangle unless they are already cached.
209
215
  if (node.type === 'item' && y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) {
@@ -217,12 +223,12 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
217
223
  nodes.push(layoutNode);
218
224
 
219
225
  if (node.type === 'item' && y > this.requestedRect.maxY) {
220
- y += (this.collection.size - (nodes.length + skipped)) * rowHeight;
226
+ y += (collection.size - (nodes.length + skipped)) * rowHeight;
221
227
  break;
222
228
  }
223
229
  }
224
230
 
225
- this.contentSize = new Size(this.virtualizer.visibleRect.width, y);
231
+ this.contentSize = new Size(this.virtualizer!.visibleRect.width, y);
226
232
  return nodes;
227
233
  }
228
234
 
@@ -240,7 +246,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
240
246
 
241
247
  protected buildChild(node: Node<T>, x: number, y: number, parentKey: Key | null): LayoutNode {
242
248
  if (this.isValid(node, y)) {
243
- return this.layoutNodes.get(node.key);
249
+ return this.layoutNodes.get(node.key)!;
244
250
  }
245
251
 
246
252
  let layoutNode = this.buildNode(node, x, y);
@@ -260,14 +266,16 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
260
266
  return this.buildSectionHeader(node, x, y);
261
267
  case 'loader':
262
268
  return this.buildLoader(node, x, y);
269
+ default:
270
+ throw new Error('Unsupported node type: ' + node.type);
263
271
  }
264
272
  }
265
273
 
266
274
  protected buildLoader(node: Node<T>, x: number, y: number): LayoutNode {
267
275
  let rect = new Rect(x, y, 0, 0);
268
276
  let layoutInfo = new LayoutInfo('loader', node.key, rect);
269
- rect.width = this.virtualizer.contentSize.width;
270
- rect.height = this.loaderHeight || this.rowHeight || this.estimatedRowHeight;
277
+ rect.width = this.virtualizer!.contentSize.width;
278
+ rect.height = this.loaderHeight || this.rowHeight || this.estimatedRowHeight || DEFAULT_HEIGHT;
271
279
 
272
280
  return {
273
281
  layoutInfo,
@@ -276,15 +284,16 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
276
284
  }
277
285
 
278
286
  protected buildSection(node: Node<T>, x: number, y: number): LayoutNode {
279
- let width = this.virtualizer.visibleRect.width;
287
+ let collection = this.virtualizer!.collection;
288
+ let width = this.virtualizer!.visibleRect.width;
280
289
  let rect = new Rect(0, y, width, 0);
281
290
  let layoutInfo = new LayoutInfo(node.type, node.key, rect);
282
291
 
283
292
  let startY = y;
284
293
  let skipped = 0;
285
- let children = [];
286
- for (let child of getChildNodes(node, this.collection)) {
287
- let rowHeight = (this.rowHeight ?? this.estimatedRowHeight);
294
+ let children: LayoutNode[] = [];
295
+ for (let child of getChildNodes(node, collection)) {
296
+ let rowHeight = (this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT);
288
297
 
289
298
  // Skip rows before the valid rectangle unless they are already cached.
290
299
  if (y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) {
@@ -299,7 +308,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
299
308
 
300
309
  if (y > this.requestedRect.maxY) {
301
310
  // Estimate the remaining height for rows that we don't need to layout right now.
302
- y += ([...getChildNodes(node, this.collection)].length - (children.length + skipped)) * rowHeight;
311
+ y += ([...getChildNodes(node, collection)].length - (children.length + skipped)) * rowHeight;
303
312
  break;
304
313
  }
305
314
  }
@@ -315,7 +324,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
315
324
  }
316
325
 
317
326
  protected buildSectionHeader(node: Node<T>, x: number, y: number): LayoutNode {
318
- let width = this.virtualizer.visibleRect.width;
327
+ let width = this.virtualizer!.visibleRect.width;
319
328
  let rectHeight = this.headingHeight;
320
329
  let isEstimated = false;
321
330
 
@@ -327,7 +336,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
327
336
  let previousLayoutNode = this.layoutNodes.get(node.key);
328
337
  let previousLayoutInfo = previousLayoutNode?.layoutInfo;
329
338
  if (previousLayoutInfo) {
330
- let curNode = this.collection.getItem(node.key);
339
+ let curNode = this.virtualizer!.collection.getItem(node.key);
331
340
  let lastNode = this.lastCollection ? this.lastCollection.getItem(node.key) : null;
332
341
  rectHeight = previousLayoutInfo.rect.height;
333
342
  isEstimated = width !== this.lastWidth || curNode !== lastNode || previousLayoutInfo.estimatedSize;
@@ -353,7 +362,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
353
362
  }
354
363
 
355
364
  protected buildItem(node: Node<T>, x: number, y: number): LayoutNode {
356
- let width = this.virtualizer.visibleRect.width;
365
+ let width = this.virtualizer!.visibleRect.width;
357
366
  let rectHeight = this.rowHeight;
358
367
  let isEstimated = false;
359
368
 
@@ -394,6 +403,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
394
403
  return false;
395
404
  }
396
405
 
406
+ let collection = this.virtualizer!.collection;
397
407
  let layoutInfo = layoutNode.layoutInfo;
398
408
  layoutInfo.estimatedSize = false;
399
409
  if (layoutInfo.rect.height !== size.height) {
@@ -412,10 +422,10 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
412
422
  // Invalidate layout for this layout node and all parents
413
423
  this.updateLayoutNode(key, layoutInfo, newLayoutInfo);
414
424
 
415
- let node = this.collection.getItem(layoutInfo.parentKey);
425
+ let node = layoutInfo.parentKey != null ? collection.getItem(layoutInfo.parentKey) : null;
416
426
  while (node) {
417
427
  this.updateLayoutNode(node.key, layoutInfo, newLayoutInfo);
418
- node = this.collection.getItem(node.parentKey);
428
+ node = node.parentKey != null ? collection.getItem(node.parentKey) : null;
419
429
  }
420
430
 
421
431
  return true;
@@ -441,16 +451,20 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
441
451
  return this.contentSize;
442
452
  }
443
453
 
444
- getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
445
- x += this.virtualizer.visibleRect.x;
446
- y += this.virtualizer.visibleRect.y;
454
+ getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget | null {
455
+ x += this.virtualizer!.visibleRect.x;
456
+ y += this.virtualizer!.visibleRect.y;
447
457
 
448
- let key = this.virtualizer.keyAtPoint(new Point(x, y));
449
- if (key == null || this.collection.size === 0) {
458
+ let key = this.virtualizer!.keyAtPoint(new Point(x, y));
459
+ if (key == null || this.virtualizer!.collection.size === 0) {
450
460
  return {type: 'root'};
451
461
  }
452
462
 
453
463
  let layoutInfo = this.getLayoutInfo(key);
464
+ if (!layoutInfo) {
465
+ return null;
466
+ }
467
+
454
468
  let rect = layoutInfo.rect;
455
469
  let target: DropTarget = {
456
470
  type: 'item',
@@ -477,7 +491,7 @@ export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTa
477
491
  }
478
492
 
479
493
  getDropTargetLayoutInfo(target: ItemDropTarget): LayoutInfo {
480
- let layoutInfo = this.getLayoutInfo(target.key);
494
+ let layoutInfo = this.getLayoutInfo(target.key)!;
481
495
  let rect: Rect;
482
496
  if (target.dropPosition === 'before') {
483
497
  rect = new Rect(layoutInfo.rect.x, layoutInfo.rect.y - this.dropIndicatorThickness / 2, layoutInfo.rect.width, this.dropIndicatorThickness);
@@ -22,11 +22,13 @@ export interface TableLayoutProps {
22
22
  columnWidths?: Map<Key, number>
23
23
  }
24
24
 
25
+ const DEFAULT_ROW_HEIGHT = 48;
26
+
25
27
  export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> extends ListLayout<T, O> {
26
- protected collection: TableCollection<T>;
27
- private columnWidths: Map<Key, number>;
28
+ protected lastCollection: TableCollection<T> | null = null;
29
+ private columnWidths: Map<Key, number> = new Map();
28
30
  private stickyColumnIndices: number[];
29
- private lastPersistedKeys: Set<Key> = null;
31
+ private lastPersistedKeys: Set<Key> | null = null;
30
32
  private persistedIndices: Map<Key, number[]> = new Map();
31
33
 
32
34
  constructor(options: ListLayoutOptions) {
@@ -34,6 +36,11 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
34
36
  this.stickyColumnIndices = [];
35
37
  }
36
38
 
39
+ // Backward compatibility for subclassing.
40
+ protected get collection(): TableCollection<T> {
41
+ return this.virtualizer!.collection as TableCollection<T>;
42
+ }
43
+
37
44
  private columnsChanged(newCollection: TableCollection<T>, oldCollection: TableCollection<T> | null) {
38
45
  return !oldCollection ||
39
46
  newCollection.columns !== oldCollection.columns &&
@@ -47,7 +54,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
47
54
  }
48
55
 
49
56
  update(invalidationContext: InvalidationContext<O>): void {
50
- let newCollection = this.virtualizer.collection as TableCollection<T>;
57
+ let newCollection = this.virtualizer!.collection as TableCollection<T>;
51
58
 
52
59
  // If columnWidths were provided via layoutOptions, update those.
53
60
  // Otherwise, calculate column widths ourselves.
@@ -56,9 +63,9 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
56
63
  this.columnWidths = invalidationContext.layoutOptions.columnWidths;
57
64
  invalidationContext.sizeChanged = true;
58
65
  }
59
- } else if (invalidationContext.sizeChanged || this.columnsChanged(newCollection, this.collection)) {
66
+ } else if (invalidationContext.sizeChanged || this.columnsChanged(newCollection, this.lastCollection)) {
60
67
  let columnLayout = new TableColumnLayout({});
61
- this.columnWidths = columnLayout.buildColumnWidths(this.virtualizer.visibleRect.width, newCollection, new Map());
68
+ this.columnWidths = columnLayout.buildColumnWidths(this.virtualizer!.visibleRect.width, newCollection, new Map());
62
69
  invalidationContext.sizeChanged = true;
63
70
  }
64
71
 
@@ -68,10 +75,11 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
68
75
  protected buildCollection(): LayoutNode[] {
69
76
  this.stickyColumnIndices = [];
70
77
 
71
- for (let column of this.collection.columns) {
78
+ let collection = this.virtualizer!.collection as TableCollection<T>;
79
+ for (let column of collection.columns) {
72
80
  // The selection cell and any other sticky columns always need to be visible.
73
81
  // In addition, row headers need to be in the DOM for accessibility labeling.
74
- if (this.isStickyColumn(column) || this.collection.rowHeaderColumnKeys.has(column.key)) {
82
+ if (this.isStickyColumn(column) || collection.rowHeaderColumnKeys.has(column.key)) {
75
83
  this.stickyColumnIndices.push(column.index);
76
84
  }
77
85
  }
@@ -90,15 +98,16 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
90
98
  }
91
99
 
92
100
  protected buildTableHeader(): LayoutNode {
101
+ let collection = this.virtualizer!.collection as TableCollection<T>;
93
102
  let rect = new Rect(0, 0, 0, 0);
94
- let layoutInfo = new LayoutInfo('header', this.collection.head?.key ?? 'header', rect);
103
+ let layoutInfo = new LayoutInfo('header', collection.head?.key ?? 'header', rect);
95
104
  layoutInfo.isSticky = true;
96
105
  layoutInfo.zIndex = 1;
97
106
 
98
107
  let y = 0;
99
108
  let width = 0;
100
109
  let children: LayoutNode[] = [];
101
- for (let headerRow of this.collection.headerRows) {
110
+ for (let headerRow of collection.headerRows) {
102
111
  let layoutNode = this.buildChild(headerRow, 0, y, layoutInfo.key);
103
112
  layoutNode.layoutInfo.parentKey = layoutInfo.key;
104
113
  y = layoutNode.layoutInfo.rect.maxY;
@@ -114,7 +123,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
114
123
  layoutInfo,
115
124
  children,
116
125
  validRect: layoutInfo.rect,
117
- node: this.collection.head
126
+ node: collection.head
118
127
  };
119
128
  }
120
129
 
@@ -124,7 +133,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
124
133
 
125
134
  let height = 0;
126
135
  let columns: LayoutNode[] = [];
127
- for (let cell of getChildNodes(headerRow, this.collection)) {
136
+ for (let cell of getChildNodes(headerRow, this.virtualizer!.collection)) {
128
137
  let layoutNode = this.buildChild(cell, x, y, row.key);
129
138
  layoutNode.layoutInfo.parentKey = row.key;
130
139
  x = layoutNode.layoutInfo.rect.maxX;
@@ -161,20 +170,21 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
161
170
 
162
171
  // used to get the column widths when rendering to the DOM
163
172
  private getRenderedColumnWidth(node: GridNode<T>) {
173
+ let collection = this.virtualizer!.collection as TableCollection<T>;
164
174
  let colspan = node.colspan ?? 1;
165
175
  let colIndex = node.colIndex ?? node.index;
166
176
  let width = 0;
167
177
  for (let i = colIndex; i < colIndex + colspan; i++) {
168
- let column = this.collection.columns[i];
178
+ let column = collection.columns[i];
169
179
  if (column?.key != null) {
170
- width += this.columnWidths.get(column.key);
180
+ width += this.columnWidths.get(column.key) ?? 0;
171
181
  }
172
182
  }
173
183
 
174
184
  return width;
175
185
  }
176
186
 
177
- private getEstimatedHeight(node: GridNode<T>, width: number, height: number, estimatedHeight: number) {
187
+ private getEstimatedHeight(node: GridNode<T>, width: number, height: number | null, estimatedHeight: number | null) {
178
188
  let isEstimated = false;
179
189
 
180
190
  // If no explicit height is available, use an estimated height.
@@ -187,7 +197,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
187
197
  height = previousLayoutNode.layoutInfo.rect.height;
188
198
  isEstimated = node !== previousLayoutNode.node || width !== previousLayoutNode.layoutInfo.rect.width || previousLayoutNode.layoutInfo.estimatedSize;
189
199
  } else {
190
- height = estimatedHeight;
200
+ height = estimatedHeight ?? DEFAULT_ROW_HEIGHT;
191
201
  isEstimated = true;
192
202
  }
193
203
  }
@@ -196,12 +206,12 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
196
206
  }
197
207
 
198
208
  protected getEstimatedRowHeight(): number {
199
- return this.rowHeight ?? this.estimatedRowHeight;
209
+ return this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_ROW_HEIGHT;
200
210
  }
201
211
 
202
212
  protected buildColumn(node: GridNode<T>, x: number, y: number): LayoutNode {
203
213
  let width = this.getRenderedColumnWidth(node);
204
- let {height, isEstimated} = this.getEstimatedHeight(node, width, this.headingHeight, this.estimatedHeadingHeight);
214
+ let {height, isEstimated} = this.getEstimatedHeight(node, width, this.headingHeight ?? this.rowHeight, this.estimatedHeadingHeight ?? this.estimatedRowHeight);
205
215
  let rect = new Rect(x, y, width, height);
206
216
  let layoutInfo = new LayoutInfo(node.type, node.key, rect);
207
217
  layoutInfo.isSticky = this.isStickyColumn(node);
@@ -223,15 +233,16 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
223
233
  }
224
234
 
225
235
  protected buildBody(y: number): LayoutNode {
236
+ let collection = this.virtualizer!.collection as TableCollection<T>;
226
237
  let rect = new Rect(0, y, 0, 0);
227
- let layoutInfo = new LayoutInfo('rowgroup', this.collection.body.key, rect);
238
+ let layoutInfo = new LayoutInfo('rowgroup', collection.body.key, rect);
228
239
 
229
240
  let startY = y;
230
241
  let skipped = 0;
231
242
  let width = 0;
232
243
  let children: LayoutNode[] = [];
233
244
  let rowHeight = this.getEstimatedRowHeight();
234
- for (let node of getChildNodes(this.collection.body, this.collection)) {
245
+ for (let node of getChildNodes(collection.body, collection)) {
235
246
  // Skip rows before the valid rectangle unless they are already cached.
236
247
  if (y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) {
237
248
  y += rowHeight;
@@ -248,13 +259,13 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
248
259
 
249
260
  if (y > this.requestedRect.maxY) {
250
261
  // Estimate the remaining height for rows that we don't need to layout right now.
251
- y += (this.collection.size - (skipped + children.length)) * rowHeight;
262
+ y += (collection.size - (skipped + children.length)) * rowHeight;
252
263
  break;
253
264
  }
254
265
  }
255
266
 
256
267
  if (children.length === 0) {
257
- y = this.virtualizer.visibleRect.maxY;
268
+ y = this.virtualizer!.visibleRect.maxY;
258
269
  }
259
270
 
260
271
  rect.width = width;
@@ -264,7 +275,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
264
275
  layoutInfo,
265
276
  children,
266
277
  validRect: layoutInfo.rect.intersection(this.requestedRect),
267
- node: this.collection.body
278
+ node: collection.body
268
279
  };
269
280
  }
270
281
 
@@ -287,12 +298,13 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
287
298
  }
288
299
 
289
300
  protected buildRow(node: GridNode<T>, x: number, y: number): LayoutNode {
301
+ let collection = this.virtualizer!.collection as TableCollection<T>;
290
302
  let rect = new Rect(x, y, 0, 0);
291
303
  let layoutInfo = new LayoutInfo('row', node.key, rect);
292
304
 
293
305
  let children: LayoutNode[] = [];
294
306
  let height = 0;
295
- for (let child of getChildNodes(node, this.collection)) {
307
+ for (let child of getChildNodes(node, collection)) {
296
308
  if (child.type === 'cell') {
297
309
  if (x > this.requestedRect.maxX) {
298
310
  // Adjust existing cached layoutInfo to ensure that it is out of view.
@@ -316,7 +328,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
316
328
 
317
329
  this.setChildHeights(children, height);
318
330
 
319
- rect.width = this.layoutNodes.get(this.collection.head?.key ?? 'header').layoutInfo.rect.width;
331
+ rect.width = this.layoutNodes.get(collection.head?.key ?? 'header')!.layoutInfo.rect.width;
320
332
  rect.height = height;
321
333
 
322
334
  return {
@@ -480,20 +492,20 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
480
492
  }
481
493
 
482
494
  private buildPersistedIndices() {
483
- if (this.virtualizer.persistedKeys === this.lastPersistedKeys) {
495
+ if (this.virtualizer!.persistedKeys === this.lastPersistedKeys) {
484
496
  return;
485
497
  }
486
498
 
487
- this.lastPersistedKeys = this.virtualizer.persistedKeys;
499
+ this.lastPersistedKeys = this.virtualizer!.persistedKeys;
488
500
  this.persistedIndices.clear();
489
501
 
490
502
  // Build a map of parentKey => indices of children to persist.
491
- for (let key of this.virtualizer.persistedKeys) {
503
+ for (let key of this.virtualizer!.persistedKeys) {
492
504
  let layoutInfo = this.layoutNodes.get(key)?.layoutInfo;
493
505
 
494
506
  // Walk up ancestors so parents are also persisted if children are.
495
507
  while (layoutInfo && layoutInfo.parentKey) {
496
- let collectionNode = this.collection.getItem(layoutInfo.key);
508
+ let collectionNode = this.virtualizer!.collection.getItem(layoutInfo.key);
497
509
  let indices = this.persistedIndices.get(layoutInfo.parentKey);
498
510
  if (!indices) {
499
511
  // stickyColumnIndices are always persisted along with any cells from persistedKeys.
@@ -501,9 +513,8 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
501
513
  this.persistedIndices.set(layoutInfo.parentKey, indices);
502
514
  }
503
515
 
504
- let index = this.layoutNodes.get(layoutInfo.key).index;
505
-
506
- if (!indices.includes(index)) {
516
+ let index = this.layoutNodes.get(layoutInfo.key)?.index;
517
+ if (index != null && !indices.includes(index)) {
507
518
  indices.push(index);
508
519
  }
509
520
 
@@ -516,15 +527,15 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
516
527
  }
517
528
  }
518
529
 
519
- getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
520
- x += this.virtualizer.visibleRect.x;
521
- y += this.virtualizer.visibleRect.y;
530
+ getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget | null {
531
+ x += this.virtualizer!.visibleRect.x;
532
+ y += this.virtualizer!.visibleRect.y;
522
533
 
523
534
  // Custom variation of this.virtualizer.keyAtPoint that ignores body
524
- let key: Key;
535
+ let key: Key | null = null;
525
536
  let point = new Point(x, y);
526
537
  let rectAtPoint = new Rect(point.x, point.y, 1, 1);
527
- let layoutInfos = this.virtualizer.layout.getVisibleLayoutInfos(rectAtPoint).filter(info => info.type === 'row');
538
+ let layoutInfos = this.virtualizer!.layout.getVisibleLayoutInfos(rectAtPoint).filter(info => info.type === 'row');
528
539
 
529
540
  // Layout may return multiple layout infos in the case of
530
541
  // persisted keys, so find the first one that actually intersects.
@@ -534,11 +545,15 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
534
545
  }
535
546
  }
536
547
 
537
- if (key == null || this.collection.size === 0) {
548
+ if (key == null || this.virtualizer!.collection.size === 0) {
538
549
  return {type: 'root'};
539
550
  }
540
551
 
541
552
  let layoutInfo = this.getLayoutInfo(key);
553
+ if (!layoutInfo) {
554
+ return null;
555
+ }
556
+
542
557
  let rect = layoutInfo.rect;
543
558
  let target: DropTarget = {
544
559
  type: 'item',
@@ -566,7 +581,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
566
581
 
567
582
  getDropTargetLayoutInfo(target: ItemDropTarget): LayoutInfo {
568
583
  let layoutInfo = super.getDropTargetLayoutInfo(target);
569
- layoutInfo.parentKey = this.collection.body.key;
584
+ layoutInfo.parentKey = (this.virtualizer!.collection as TableCollection<T>).body.key;
570
585
  return layoutInfo;
571
586
  }
572
587
  }