@react-stately/layout 3.13.10-nightly.4674 → 3.13.10-nightly.4681

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.
@@ -10,114 +10,74 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {ColumnSize, TableCollection} from '@react-types/table';
14
13
  import {DropTarget, Key} from '@react-types/shared';
15
14
  import {getChildNodes} from '@react-stately/collections';
16
15
  import {GridNode} from '@react-types/grid';
17
16
  import {InvalidationContext, LayoutInfo, Point, Rect, Size} from '@react-stately/virtualizer';
18
- import {LayoutNode, ListLayout, ListLayoutOptions} from './ListLayout';
17
+ import {LayoutNode, ListLayout, ListLayoutOptions, ListLayoutProps} from './ListLayout';
18
+ import {TableCollection} from '@react-types/table';
19
19
  import {TableColumnLayout} from '@react-stately/table';
20
20
 
21
- type TableLayoutOptions<T> = ListLayoutOptions<T> & {
22
- columnLayout: TableColumnLayout<T>,
23
- initialCollection: TableCollection<T>
21
+ export interface TableLayoutOptions<T> extends ListLayoutOptions<T> {
22
+ scrollContainer?: 'table' | 'body'
23
+ }
24
+
25
+ export interface TableLayoutProps extends ListLayoutProps {
26
+ columnWidths?: Map<Key, number>
24
27
  }
25
28
 
26
29
  export class TableLayout<T> extends ListLayout<T> {
27
30
  collection: TableCollection<T>;
28
31
  lastCollection: TableCollection<T>;
29
- columnWidths: Map<Key, number> = new Map();
32
+ columnWidths: Map<Key, number>;
30
33
  stickyColumnIndices: number[];
31
- wasLoading = false;
32
34
  isLoading = false;
33
35
  lastPersistedKeys: Set<Key> = null;
34
36
  persistedIndices: Map<Key, number[]> = new Map();
37
+ scrollContainer: 'table' | 'body';
35
38
  private disableSticky: boolean;
36
- columnLayout: TableColumnLayout<T>;
37
- controlledColumns: Map<Key, GridNode<unknown>>;
38
- uncontrolledColumns: Map<Key, GridNode<unknown>>;
39
- uncontrolledWidths: Map<Key, ColumnSize>;
40
- resizingColumn: Key | null;
41
39
 
42
40
  constructor(options: TableLayoutOptions<T>) {
43
41
  super(options);
44
- this.collection = options.initialCollection;
42
+ this.scrollContainer = options.scrollContainer || 'table';
45
43
  this.stickyColumnIndices = [];
46
44
  this.disableSticky = this.checkChrome105();
47
- this.columnLayout = options.columnLayout;
48
- let [controlledColumns, uncontrolledColumns] = this.columnLayout.splitColumnsIntoControlledAndUncontrolled(this.collection.columns);
49
- this.controlledColumns = controlledColumns;
50
- this.uncontrolledColumns = uncontrolledColumns;
51
- this.uncontrolledWidths = this.columnLayout.getInitialUncontrolledWidths(uncontrolledColumns);
52
- }
53
-
54
- protected shouldInvalidateEverything(invalidationContext: InvalidationContext): boolean {
55
- // If columns changed, clear layout cache.
56
- return super.shouldInvalidateEverything(invalidationContext) || (
57
- !this.lastCollection ||
58
- this.collection.columns.length !== this.lastCollection.columns.length ||
59
- this.collection.columns.some((c, i) =>
60
- c.key !== this.lastCollection.columns[i].key ||
61
- c.props.width !== this.lastCollection.columns[i].props.width ||
62
- c.props.minWidth !== this.lastCollection.columns[i].props.minWidth ||
63
- c.props.maxWidth !== this.lastCollection.columns[i].props.maxWidth
64
- )
65
- );
66
- }
67
-
68
- getResizerPosition(): Key {
69
- return this.getLayoutInfo(this.resizingColumn)?.rect.maxX;
70
- }
71
-
72
- getColumnWidth(key: Key): number {
73
- return this.columnLayout.getColumnWidth(key) ?? 0;
74
- }
75
-
76
- getColumnMinWidth(key: Key): number {
77
- let column = this.collection.columns.find(col => col.key === key);
78
- if (!column) {
79
- return 0;
80
- }
81
- return this.columnLayout.getColumnMinWidth(key);
82
45
  }
83
46
 
84
- getColumnMaxWidth(key: Key): number {
85
- let column = this.collection.columns.find(col => col.key === key);
86
- if (!column) {
87
- return 0;
88
- }
89
- return this.columnLayout.getColumnMaxWidth(key);
47
+ private columnsChanged(newCollection: TableCollection<T>, oldCollection: TableCollection<T> | null) {
48
+ return !oldCollection ||
49
+ newCollection.columns !== oldCollection.columns &&
50
+ newCollection.columns.length !== oldCollection.columns.length ||
51
+ newCollection.columns.some((c, i) =>
52
+ c.key !== oldCollection.columns[i].key ||
53
+ c.props.width !== oldCollection.columns[i].props.width ||
54
+ c.props.minWidth !== oldCollection.columns[i].props.minWidth ||
55
+ c.props.maxWidth !== oldCollection.columns[i].props.maxWidth
56
+ );
90
57
  }
91
58
 
92
- // outside, where this is called, should call props.onColumnResizeStart...
93
- startResize(key: Key): void {
94
- this.resizingColumn = key;
95
- }
59
+ validate(invalidationContext: InvalidationContext<TableLayoutProps>): void {
60
+ let newCollection = this.virtualizer.collection as TableCollection<T>;
96
61
 
97
- // only way to call props.onColumnResize with the new size outside of Layout is to send the result back
98
- updateResizedColumns(key: Key, width: number): Map<Key, ColumnSize> {
99
- let newControlled = new Map(Array.from(this.controlledColumns).map(([key, entry]) => [key, entry.props.width]));
100
- let newSizes = this.columnLayout.resizeColumnWidth(this.virtualizer.visibleRect.width, this.collection, newControlled, this.uncontrolledWidths, key, width);
101
-
102
- let map = new Map(Array.from(this.uncontrolledColumns).map(([key]) => [key, newSizes.get(key)]));
103
- map.set(key, width);
104
- this.uncontrolledWidths = map;
105
- // invalidate still uses setState, should happen at the same time the parent
106
- // component's state is processed as a result of props.onColumnResize
107
- if (this.uncontrolledWidths.size > 0) {
108
- this.virtualizer.invalidate({sizeChanged: true});
62
+ // If columnWidths were provided via layoutOptions, update those.
63
+ // Otherwise, calculate column widths ourselves.
64
+ if (invalidationContext.layoutOptions?.columnWidths) {
65
+ if (invalidationContext.layoutOptions.columnWidths !== this.columnWidths) {
66
+ this.columnWidths = invalidationContext.layoutOptions.columnWidths;
67
+ invalidationContext.sizeChanged = true;
68
+ }
69
+ } else if (invalidationContext.sizeChanged || this.columnsChanged(newCollection, this.collection)) {
70
+ let columnLayout = new TableColumnLayout({});
71
+ this.columnWidths = columnLayout.buildColumnWidths(this.virtualizer.visibleRect.width, newCollection, new Map());
72
+ invalidationContext.sizeChanged = true;
109
73
  }
110
- return newSizes;
111
- }
112
74
 
113
- endResize(): void {
114
- this.resizingColumn = null;
75
+ super.validate(invalidationContext);
115
76
  }
116
77
 
117
- buildCollection(): LayoutNode[] {
78
+ protected buildCollection(): LayoutNode[] {
118
79
  // Track whether we were previously loading. This is used to adjust the animations of async loading vs inserts.
119
80
  let loadingState = this.collection.body.props.loadingState;
120
- this.wasLoading = this.isLoading;
121
81
  this.isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
122
82
  this.stickyColumnIndices = [];
123
83
 
@@ -129,15 +89,8 @@ export class TableLayout<T> extends ListLayout<T> {
129
89
  }
130
90
  }
131
91
 
132
- let [controlledColumns, uncontrolledColumns] = this.columnLayout.splitColumnsIntoControlledAndUncontrolled(this.collection.columns);
133
- this.controlledColumns = controlledColumns;
134
- this.uncontrolledColumns = uncontrolledColumns;
135
- let colWidths = this.columnLayout.recombineColumns(this.collection.columns, this.uncontrolledWidths, uncontrolledColumns, controlledColumns);
136
-
137
- this.columnWidths = this.columnLayout.buildColumnWidths(this.virtualizer.visibleRect.width, this.collection, colWidths);
138
-
139
- let header = this.buildHeader();
140
- let body = this.buildBody(0);
92
+ let header = this.buildColumnHeader();
93
+ let body = this.buildBody(this.scrollContainer === 'body' ? 0 : header.layoutInfo.rect.height);
141
94
  this.lastPersistedKeys = null;
142
95
 
143
96
  body.layoutInfo.rect.width = Math.max(header.layoutInfo.rect.width, body.layoutInfo.rect.width);
@@ -148,16 +101,18 @@ export class TableLayout<T> extends ListLayout<T> {
148
101
  ];
149
102
  }
150
103
 
151
- buildHeader(): LayoutNode {
104
+ private buildColumnHeader(): LayoutNode {
152
105
  let rect = new Rect(0, 0, 0, 0);
153
- let layoutInfo = new LayoutInfo('header', 'header', rect);
106
+ let layoutInfo = new LayoutInfo('header', this.collection.head?.key ?? 'header', rect);
107
+ layoutInfo.isSticky = true;
108
+ layoutInfo.zIndex = 1;
154
109
 
155
110
  let y = 0;
156
111
  let width = 0;
157
112
  let children: LayoutNode[] = [];
158
113
  for (let headerRow of this.collection.headerRows) {
159
- let layoutNode = this.buildChild(headerRow, 0, y);
160
- layoutNode.layoutInfo.parentKey = 'header';
114
+ let layoutNode = this.buildChild(headerRow, 0, y, layoutInfo.key);
115
+ layoutNode.layoutInfo.parentKey = layoutInfo.key;
161
116
  y = layoutNode.layoutInfo.rect.maxY;
162
117
  width = Math.max(width, layoutNode.layoutInfo.rect.width);
163
118
  layoutNode.index = children.length;
@@ -167,7 +122,7 @@ export class TableLayout<T> extends ListLayout<T> {
167
122
  rect.width = width;
168
123
  rect.height = y;
169
124
 
170
- this.layoutInfos.set('header', layoutInfo);
125
+ this.layoutInfos.set(layoutInfo.key, layoutInfo);
171
126
 
172
127
  return {
173
128
  layoutInfo,
@@ -176,14 +131,14 @@ export class TableLayout<T> extends ListLayout<T> {
176
131
  };
177
132
  }
178
133
 
179
- buildHeaderRow(headerRow: GridNode<T>, x: number, y: number): LayoutNode {
134
+ private buildHeaderRow(headerRow: GridNode<T>, x: number, y: number): LayoutNode {
180
135
  let rect = new Rect(0, y, 0, 0);
181
136
  let row = new LayoutInfo('headerrow', headerRow.key, rect);
182
137
 
183
138
  let height = 0;
184
139
  let columns: LayoutNode[] = [];
185
140
  for (let cell of getChildNodes(headerRow, this.collection)) {
186
- let layoutNode = this.buildChild(cell, x, y);
141
+ let layoutNode = this.buildChild(cell, x, y, row.key);
187
142
  layoutNode.layoutInfo.parentKey = row.key;
188
143
  x = layoutNode.layoutInfo.rect.maxX;
189
144
  height = Math.max(height, layoutNode.layoutInfo.rect.height);
@@ -206,7 +161,7 @@ export class TableLayout<T> extends ListLayout<T> {
206
161
  };
207
162
  }
208
163
 
209
- setChildHeights(children: LayoutNode[], height: number) {
164
+ private setChildHeights(children: LayoutNode[], height: number) {
210
165
  for (let child of children) {
211
166
  if (child.layoutInfo.rect.height !== height) {
212
167
  // Need to copy the layout info before we mutate it.
@@ -219,7 +174,7 @@ export class TableLayout<T> extends ListLayout<T> {
219
174
  }
220
175
 
221
176
  // used to get the column widths when rendering to the DOM
222
- getRenderedColumnWidth(node: GridNode<T>) {
177
+ private getRenderedColumnWidth(node: GridNode<T>) {
223
178
  let colspan = node.colspan ?? 1;
224
179
  let colIndex = node.colIndex ?? node.index;
225
180
  let width = 0;
@@ -233,7 +188,7 @@ export class TableLayout<T> extends ListLayout<T> {
233
188
  return width;
234
189
  }
235
190
 
236
- getEstimatedHeight(node: GridNode<T>, width: number, height: number, estimatedHeight: number) {
191
+ private getEstimatedHeight(node: GridNode<T>, width: number, height: number, estimatedHeight: number) {
237
192
  let isEstimated = false;
238
193
 
239
194
  // If no explicit height is available, use an estimated height.
@@ -254,7 +209,7 @@ export class TableLayout<T> extends ListLayout<T> {
254
209
  return {height, isEstimated};
255
210
  }
256
211
 
257
- buildColumn(node: GridNode<T>, x: number, y: number): LayoutNode {
212
+ private buildColumn(node: GridNode<T>, x: number, y: number): LayoutNode {
258
213
  let width = this.getRenderedColumnWidth(node);
259
214
  let {height, isEstimated} = this.getEstimatedHeight(node, width, this.headingHeight, this.estimatedHeadingHeight);
260
215
  let rect = new Rect(x, y, width, height);
@@ -270,15 +225,15 @@ export class TableLayout<T> extends ListLayout<T> {
270
225
  };
271
226
  }
272
227
 
273
- buildBody(y: number): LayoutNode {
228
+ private buildBody(y: number): LayoutNode {
274
229
  let rect = new Rect(0, y, 0, 0);
275
- let layoutInfo = new LayoutInfo('rowgroup', 'body', rect);
230
+ let layoutInfo = new LayoutInfo('rowgroup', this.collection.body.key, rect);
276
231
 
277
232
  let startY = y;
278
233
  let skipped = 0;
279
234
  let width = 0;
280
235
  let children: LayoutNode[] = [];
281
- for (let [i, node] of [...this.collection].entries()) {
236
+ for (let [i, node] of [...getChildNodes(this.collection.body, this.collection)].entries()) {
282
237
  let rowHeight = (this.rowHeight ?? this.estimatedRowHeight) + 1;
283
238
 
284
239
  // Skip rows before the valid rectangle unless they are already cached.
@@ -288,8 +243,8 @@ export class TableLayout<T> extends ListLayout<T> {
288
243
  continue;
289
244
  }
290
245
 
291
- let layoutNode = this.buildChild(node, 0, y);
292
- layoutNode.layoutInfo.parentKey = 'body';
246
+ let layoutNode = this.buildChild(node, 0, y, layoutInfo.key);
247
+ layoutNode.layoutInfo.parentKey = layoutInfo.key;
293
248
  layoutNode.index = i;
294
249
  y = layoutNode.layoutInfo.rect.maxY;
295
250
  width = Math.max(width, layoutNode.layoutInfo.rect.width);
@@ -306,27 +261,31 @@ export class TableLayout<T> extends ListLayout<T> {
306
261
  // Add some margin around the loader to ensure that scrollbars don't flicker in and out.
307
262
  let rect = new Rect(40, Math.max(y, 40), (width || this.virtualizer.visibleRect.width) - 80, children.length === 0 ? this.virtualizer.visibleRect.height - 80 : 60);
308
263
  let loader = new LayoutInfo('loader', 'loader', rect);
309
- loader.parentKey = 'body';
264
+ loader.parentKey = layoutInfo.key;
310
265
  loader.isSticky = !this.disableSticky && children.length === 0;
311
266
  this.layoutInfos.set('loader', loader);
312
267
  children.push({layoutInfo: loader, validRect: loader.rect});
313
268
  y = loader.rect.maxY;
314
269
  width = Math.max(width, rect.width);
315
270
  } else if (children.length === 0) {
316
- let rect = new Rect(40, Math.max(y, 40), this.virtualizer.visibleRect.width - 80, this.virtualizer.visibleRect.height - 80);
317
- let empty = new LayoutInfo('empty', 'empty', rect);
318
- empty.parentKey = 'body';
319
- empty.isSticky = !this.disableSticky;
320
- this.layoutInfos.set('empty', empty);
321
- children.push({layoutInfo: empty, validRect: empty.rect});
322
- y = empty.rect.maxY;
323
- width = Math.max(width, rect.width);
271
+ if (this.enableEmptyState) {
272
+ let rect = new Rect(40, Math.max(y, 40), this.virtualizer.visibleRect.width - 80, this.virtualizer.visibleRect.height - 80);
273
+ let empty = new LayoutInfo('empty', 'empty', rect);
274
+ empty.parentKey = layoutInfo.key;
275
+ empty.isSticky = !this.disableSticky;
276
+ this.layoutInfos.set('empty', empty);
277
+ children.push({layoutInfo: empty, validRect: empty.rect});
278
+ y = empty.rect.maxY;
279
+ width = Math.max(width, rect.width);
280
+ } else {
281
+ y = this.virtualizer.visibleRect.maxY;
282
+ }
324
283
  }
325
284
 
326
285
  rect.width = width;
327
286
  rect.height = y - startY;
328
287
 
329
- this.layoutInfos.set('body', layoutInfo);
288
+ this.layoutInfos.set(layoutInfo.key, layoutInfo);
330
289
 
331
290
  return {
332
291
  layoutInfo,
@@ -335,7 +294,7 @@ export class TableLayout<T> extends ListLayout<T> {
335
294
  };
336
295
  }
337
296
 
338
- buildNode(node: GridNode<T>, x: number, y: number): LayoutNode {
297
+ protected buildNode(node: GridNode<T>, x: number, y: number): LayoutNode {
339
298
  switch (node.type) {
340
299
  case 'headerrow':
341
300
  return this.buildHeaderRow(node, x, y);
@@ -351,7 +310,7 @@ export class TableLayout<T> extends ListLayout<T> {
351
310
  }
352
311
  }
353
312
 
354
- buildRow(node: GridNode<T>, x: number, y: number): LayoutNode {
313
+ private buildRow(node: GridNode<T>, x: number, y: number): LayoutNode {
355
314
  let rect = new Rect(x, y, 0, 0);
356
315
  let layoutInfo = new LayoutInfo('row', node.key, rect);
357
316
 
@@ -368,7 +327,7 @@ export class TableLayout<T> extends ListLayout<T> {
368
327
  x += layoutNode.layoutInfo.rect.width;
369
328
  }
370
329
  } else {
371
- let layoutNode = this.buildChild(child, x, y);
330
+ let layoutNode = this.buildChild(child, x, y, layoutInfo.key);
372
331
  x = layoutNode.layoutInfo.rect.maxX;
373
332
  height = Math.max(height, layoutNode.layoutInfo.rect.height);
374
333
  layoutNode.index = i;
@@ -379,7 +338,7 @@ export class TableLayout<T> extends ListLayout<T> {
379
338
 
380
339
  this.setChildHeights(children, height);
381
340
 
382
- rect.width = this.layoutInfos.get('header').rect.width;
341
+ rect.width = this.layoutInfos.get(this.collection.head?.key ?? 'header').rect.width;
383
342
  rect.height = height + 1; // +1 for bottom border
384
343
 
385
344
  return {
@@ -389,7 +348,7 @@ export class TableLayout<T> extends ListLayout<T> {
389
348
  };
390
349
  }
391
350
 
392
- buildCell(node: GridNode<T>, x: number, y: number): LayoutNode {
351
+ private buildCell(node: GridNode<T>, x: number, y: number): LayoutNode {
393
352
  let width = this.getRenderedColumnWidth(node);
394
353
  let {height, isEstimated} = this.getEstimatedHeight(node, width, this.rowHeight, this.estimatedRowHeight);
395
354
  let rect = new Rect(x, y, width, height);
@@ -415,11 +374,7 @@ export class TableLayout<T> extends ListLayout<T> {
415
374
 
416
375
  // If layout hasn't yet been done for the requested rect, union the
417
376
  // new rect with the existing valid rect, and recompute.
418
- if (!this.validRect.containsRect(rect) && this.lastCollection) {
419
- this.lastValidRect = this.validRect;
420
- this.validRect = this.validRect.union(rect);
421
- this.rootNodes = this.buildCollection();
422
- }
377
+ this.layoutIfNeeded(rect);
423
378
 
424
379
  let res: LayoutInfo[] = [];
425
380
 
@@ -432,7 +387,7 @@ export class TableLayout<T> extends ListLayout<T> {
432
387
  return res;
433
388
  }
434
389
 
435
- addVisibleLayoutInfos(res: LayoutInfo[], node: LayoutNode, rect: Rect) {
390
+ private addVisibleLayoutInfos(res: LayoutInfo[], node: LayoutNode, rect: Rect) {
436
391
  if (!node.children || node.children.length === 0) {
437
392
  return;
438
393
  }
@@ -524,7 +479,7 @@ export class TableLayout<T> extends ListLayout<T> {
524
479
  }
525
480
  }
526
481
 
527
- binarySearch(items: LayoutNode[], point: Point, axis: 'x' | 'y') {
482
+ private binarySearch(items: LayoutNode[], point: Point, axis: 'x' | 'y') {
528
483
  let low = 0;
529
484
  let high = items.length - 1;
530
485
  while (low <= high) {
@@ -543,7 +498,7 @@ export class TableLayout<T> extends ListLayout<T> {
543
498
  return Math.max(0, Math.min(items.length - 1, low));
544
499
  }
545
500
 
546
- buildPersistedIndices() {
501
+ private buildPersistedIndices() {
547
502
  if (this.virtualizer.persistedKeys === this.lastPersistedKeys) {
548
503
  return;
549
504
  }
@@ -561,7 +516,7 @@ export class TableLayout<T> extends ListLayout<T> {
561
516
  let indices = this.persistedIndices.get(layoutInfo.parentKey);
562
517
  if (!indices) {
563
518
  // stickyColumnIndices are always persisted along with any cells from persistedKeys.
564
- indices = collectionNode.type === 'cell' || collectionNode.type === 'column' ? [...this.stickyColumnIndices] : [];
519
+ indices = collectionNode?.type === 'cell' || collectionNode?.type === 'column' ? [...this.stickyColumnIndices] : [];
565
520
  this.persistedIndices.set(layoutInfo.parentKey, indices);
566
521
  }
567
522
 
@@ -597,6 +552,7 @@ export class TableLayout<T> extends ListLayout<T> {
597
552
 
598
553
  return isChrome105;
599
554
  }
555
+
600
556
  getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
601
557
  x += this.virtualizer.visibleRect.x;
602
558
  y += this.virtualizer.visibleRect.y;
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- export type {ListLayoutOptions, LayoutNode} from './ListLayout';
12
+ export type {ListLayoutOptions, ListLayoutProps, LayoutNode} from './ListLayout';
13
+ export type {TableLayoutOptions, TableLayoutProps} from './TableLayout';
13
14
  export {ListLayout} from './ListLayout';
14
15
  export {TableLayout} from './TableLayout';