@react-stately/layout 4.2.2 → 4.3.1

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.
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import {DropTarget, ItemDropTarget, Key} from '@react-types/shared';
14
- import {getChildNodes} from '@react-stately/collections';
14
+ import {getChildNodes, getLastItem} from '@react-stately/collections';
15
15
  import {GridNode} from '@react-types/grid';
16
16
  import {InvalidationContext, LayoutInfo, Point, Rect, Size} from '@react-stately/virtualizer';
17
17
  import {LayoutNode, ListLayout, ListLayoutOptions} from './ListLayout';
@@ -85,6 +85,10 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
85
85
  this.stickyColumnIndices = [];
86
86
 
87
87
  let collection = this.virtualizer!.collection as TableCollection<T>;
88
+ if (collection.head?.key === -1) {
89
+ return [];
90
+ }
91
+
88
92
  for (let column of collection.columns) {
89
93
  // The selection cell and any other sticky columns always need to be visible.
90
94
  // In addition, row headers need to be in the DOM for accessibility labeling.
@@ -251,7 +255,8 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
251
255
  let width = 0;
252
256
  let children: LayoutNode[] = [];
253
257
  let rowHeight = this.getEstimatedRowHeight() + this.gap;
254
- for (let node of getChildNodes(collection.body, collection)) {
258
+ let childNodes = getChildNodes(collection.body, collection);
259
+ for (let node of childNodes) {
255
260
  // Skip rows before the valid rectangle unless they are already cached.
256
261
  if (y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) {
257
262
  y += rowHeight;
@@ -267,13 +272,32 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
267
272
  children.push(layoutNode);
268
273
 
269
274
  if (y > this.requestedRect.maxY) {
275
+ let rowsAfterRect = collection.size - (children.length + skipped);
276
+ let lastNode = getLastItem(childNodes);
277
+ if (lastNode?.type === 'loader') {
278
+ rowsAfterRect--;
279
+ }
280
+
270
281
  // Estimate the remaining height for rows that we don't need to layout right now.
271
- y += (collection.size - (skipped + children.length)) * rowHeight;
282
+ y += rowsAfterRect * rowHeight;
283
+
284
+ // Always add the loader sentinel if present. This assumes the loader is the last row in the body,
285
+ // will need to refactor when handling multi section loading
286
+ if (lastNode?.type === 'loader' && children.at(-1)?.layoutInfo.type !== 'loader') {
287
+ let loader = this.buildChild(lastNode, this.padding, y, layoutInfo.key);
288
+ loader.layoutInfo.parentKey = layoutInfo.key;
289
+ loader.index = collection.size;
290
+ width = Math.max(width, loader.layoutInfo.rect.width);
291
+ children.push(loader);
292
+ y = loader.layoutInfo.rect.maxY;
293
+ }
272
294
  break;
273
295
  }
274
296
  }
275
297
 
276
- if (children.length === 0) {
298
+ // Make sure that the table body gets a height if empty or performing initial load
299
+ let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader');
300
+ if (isEmptyOrLoading) {
277
301
  y = this.virtualizer!.visibleRect.maxY;
278
302
  } else {
279
303
  y -= this.gap;
@@ -442,6 +466,12 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
442
466
  this.addVisibleLayoutInfos(res, node.children[idx], rect);
443
467
  }
444
468
  }
469
+
470
+ // Always include loading sentinel even when virtualized, we assume it is always the last child for now
471
+ let lastRow = node.children.at(-1);
472
+ if (lastRow?.layoutInfo.type === 'loader') {
473
+ res.push(lastRow.layoutInfo);
474
+ }
445
475
  break;
446
476
  }
447
477
  case 'headerrow':
@@ -543,7 +573,7 @@ export class TableLayout<T, O extends TableLayoutProps = TableLayoutProps> exten
543
573
  y += this.virtualizer!.visibleRect.y;
544
574
 
545
575
  // Find the closest item within on either side of the point using the gap width.
546
- let searchRect = new Rect(x, Math.max(0, y - this.gap), 1, this.gap * 2);
576
+ let searchRect = new Rect(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2));
547
577
  let candidates = this.getVisibleLayoutInfos(searchRect);
548
578
  let key: Key | null = null;
549
579
  let minDistance = Infinity;
@@ -140,8 +140,9 @@ export class WaterfallLayout<T extends object, O extends WaterfallLayoutOptions
140
140
  columnHeights[column] += layoutInfo.rect.height + minSpace.height;
141
141
  };
142
142
 
143
+ let collection = this.virtualizer!.collection;
143
144
  let skeletonCount = 0;
144
- for (let node of this.virtualizer!.collection) {
145
+ for (let node of collection) {
145
146
  if (node.type === 'skeleton') {
146
147
  // Add skeleton cards until every column has at least one, and we fill the viewport.
147
148
  let startingHeights = [...columnHeights];
@@ -154,13 +155,23 @@ export class WaterfallLayout<T extends object, O extends WaterfallLayoutOptions
154
155
  addNode(key, content);
155
156
  }
156
157
  break;
157
- } else {
158
+ } else if (node.type !== 'loader') {
158
159
  addNode(node.key, node);
159
160
  }
160
161
  }
161
162
 
162
- // Reset all columns to the maximum for the next section
163
- let maxHeight = Math.max(...columnHeights);
163
+ // Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out.
164
+ // Add it under the first column for simplicity
165
+ let lastNode = collection.getItem(collection.getLastKey()!);
166
+ if (lastNode?.type === 'loader') {
167
+ let rect = new Rect(horizontalSpacing, columnHeights[0], itemWidth, 0);
168
+ let layoutInfo = new LayoutInfo('loader', lastNode.key, rect);
169
+ newLayoutInfos.set(lastNode.key, layoutInfo);
170
+ }
171
+
172
+ // Reset all columns to the maximum for the next section. If loading, set to 0 so virtualizer doesn't render its body since there aren't items to render
173
+ let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader');
174
+ let maxHeight = isEmptyOrLoading ? 0 : Math.max(...columnHeights);
164
175
  this.contentSize = new Size(this.virtualizer!.visibleRect.width, maxHeight);
165
176
  this.layoutInfos = newLayoutInfos;
166
177
  this.numColumns = numColumns;
@@ -177,7 +188,7 @@ export class WaterfallLayout<T extends object, O extends WaterfallLayoutOptions
177
188
  getVisibleLayoutInfos(rect: Rect): LayoutInfo[] {
178
189
  let layoutInfos: LayoutInfo[] = [];
179
190
  for (let layoutInfo of this.layoutInfos.values()) {
180
- if (layoutInfo.rect.intersects(rect) || this.virtualizer!.isPersistedKey(layoutInfo.key)) {
191
+ if (layoutInfo.rect.intersects(rect) || this.virtualizer!.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') {
181
192
  layoutInfos.push(layoutInfo);
182
193
  }
183
194
  }