@react-stately/layout 3.13.10-nightly.4673 → 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);
@@ -262,6 +217,7 @@ export class TableLayout<T> extends ListLayout<T> {
262
217
  layoutInfo.isSticky = !this.disableSticky && (node.props?.isDragButtonCell || node.props?.isSelectionCell);
263
218
  layoutInfo.zIndex = layoutInfo.isSticky ? 2 : 1;
264
219
  layoutInfo.estimatedSize = isEstimated;
220
+ layoutInfo.allowOverflow = true;
265
221
 
266
222
  return {
267
223
  layoutInfo,
@@ -269,15 +225,15 @@ export class TableLayout<T> extends ListLayout<T> {
269
225
  };
270
226
  }
271
227
 
272
- buildBody(y: number): LayoutNode {
228
+ private buildBody(y: number): LayoutNode {
273
229
  let rect = new Rect(0, y, 0, 0);
274
- let layoutInfo = new LayoutInfo('rowgroup', 'body', rect);
230
+ let layoutInfo = new LayoutInfo('rowgroup', this.collection.body.key, rect);
275
231
 
276
232
  let startY = y;
277
233
  let skipped = 0;
278
234
  let width = 0;
279
235
  let children: LayoutNode[] = [];
280
- for (let [i, node] of [...this.collection].entries()) {
236
+ for (let [i, node] of [...getChildNodes(this.collection.body, this.collection)].entries()) {
281
237
  let rowHeight = (this.rowHeight ?? this.estimatedRowHeight) + 1;
282
238
 
283
239
  // Skip rows before the valid rectangle unless they are already cached.
@@ -287,8 +243,8 @@ export class TableLayout<T> extends ListLayout<T> {
287
243
  continue;
288
244
  }
289
245
 
290
- let layoutNode = this.buildChild(node, 0, y);
291
- layoutNode.layoutInfo.parentKey = 'body';
246
+ let layoutNode = this.buildChild(node, 0, y, layoutInfo.key);
247
+ layoutNode.layoutInfo.parentKey = layoutInfo.key;
292
248
  layoutNode.index = i;
293
249
  y = layoutNode.layoutInfo.rect.maxY;
294
250
  width = Math.max(width, layoutNode.layoutInfo.rect.width);
@@ -305,27 +261,31 @@ export class TableLayout<T> extends ListLayout<T> {
305
261
  // Add some margin around the loader to ensure that scrollbars don't flicker in and out.
306
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);
307
263
  let loader = new LayoutInfo('loader', 'loader', rect);
308
- loader.parentKey = 'body';
264
+ loader.parentKey = layoutInfo.key;
309
265
  loader.isSticky = !this.disableSticky && children.length === 0;
310
266
  this.layoutInfos.set('loader', loader);
311
267
  children.push({layoutInfo: loader, validRect: loader.rect});
312
268
  y = loader.rect.maxY;
313
269
  width = Math.max(width, rect.width);
314
270
  } else if (children.length === 0) {
315
- let rect = new Rect(40, Math.max(y, 40), this.virtualizer.visibleRect.width - 80, this.virtualizer.visibleRect.height - 80);
316
- let empty = new LayoutInfo('empty', 'empty', rect);
317
- empty.parentKey = 'body';
318
- empty.isSticky = !this.disableSticky;
319
- this.layoutInfos.set('empty', empty);
320
- children.push({layoutInfo: empty, validRect: empty.rect});
321
- y = empty.rect.maxY;
322
- 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
+ }
323
283
  }
324
284
 
325
285
  rect.width = width;
326
286
  rect.height = y - startY;
327
287
 
328
- this.layoutInfos.set('body', layoutInfo);
288
+ this.layoutInfos.set(layoutInfo.key, layoutInfo);
329
289
 
330
290
  return {
331
291
  layoutInfo,
@@ -334,7 +294,7 @@ export class TableLayout<T> extends ListLayout<T> {
334
294
  };
335
295
  }
336
296
 
337
- buildNode(node: GridNode<T>, x: number, y: number): LayoutNode {
297
+ protected buildNode(node: GridNode<T>, x: number, y: number): LayoutNode {
338
298
  switch (node.type) {
339
299
  case 'headerrow':
340
300
  return this.buildHeaderRow(node, x, y);
@@ -350,7 +310,7 @@ export class TableLayout<T> extends ListLayout<T> {
350
310
  }
351
311
  }
352
312
 
353
- buildRow(node: GridNode<T>, x: number, y: number): LayoutNode {
313
+ private buildRow(node: GridNode<T>, x: number, y: number): LayoutNode {
354
314
  let rect = new Rect(x, y, 0, 0);
355
315
  let layoutInfo = new LayoutInfo('row', node.key, rect);
356
316
 
@@ -367,7 +327,7 @@ export class TableLayout<T> extends ListLayout<T> {
367
327
  x += layoutNode.layoutInfo.rect.width;
368
328
  }
369
329
  } else {
370
- let layoutNode = this.buildChild(child, x, y);
330
+ let layoutNode = this.buildChild(child, x, y, layoutInfo.key);
371
331
  x = layoutNode.layoutInfo.rect.maxX;
372
332
  height = Math.max(height, layoutNode.layoutInfo.rect.height);
373
333
  layoutNode.index = i;
@@ -378,7 +338,7 @@ export class TableLayout<T> extends ListLayout<T> {
378
338
 
379
339
  this.setChildHeights(children, height);
380
340
 
381
- rect.width = this.layoutInfos.get('header').rect.width;
341
+ rect.width = this.layoutInfos.get(this.collection.head?.key ?? 'header').rect.width;
382
342
  rect.height = height + 1; // +1 for bottom border
383
343
 
384
344
  return {
@@ -388,7 +348,7 @@ export class TableLayout<T> extends ListLayout<T> {
388
348
  };
389
349
  }
390
350
 
391
- buildCell(node: GridNode<T>, x: number, y: number): LayoutNode {
351
+ private buildCell(node: GridNode<T>, x: number, y: number): LayoutNode {
392
352
  let width = this.getRenderedColumnWidth(node);
393
353
  let {height, isEstimated} = this.getEstimatedHeight(node, width, this.rowHeight, this.estimatedRowHeight);
394
354
  let rect = new Rect(x, y, width, height);
@@ -414,11 +374,7 @@ export class TableLayout<T> extends ListLayout<T> {
414
374
 
415
375
  // If layout hasn't yet been done for the requested rect, union the
416
376
  // new rect with the existing valid rect, and recompute.
417
- if (!this.validRect.containsRect(rect) && this.lastCollection) {
418
- this.lastValidRect = this.validRect;
419
- this.validRect = this.validRect.union(rect);
420
- this.rootNodes = this.buildCollection();
421
- }
377
+ this.layoutIfNeeded(rect);
422
378
 
423
379
  let res: LayoutInfo[] = [];
424
380
 
@@ -431,7 +387,7 @@ export class TableLayout<T> extends ListLayout<T> {
431
387
  return res;
432
388
  }
433
389
 
434
- addVisibleLayoutInfos(res: LayoutInfo[], node: LayoutNode, rect: Rect) {
390
+ private addVisibleLayoutInfos(res: LayoutInfo[], node: LayoutNode, rect: Rect) {
435
391
  if (!node.children || node.children.length === 0) {
436
392
  return;
437
393
  }
@@ -523,7 +479,7 @@ export class TableLayout<T> extends ListLayout<T> {
523
479
  }
524
480
  }
525
481
 
526
- binarySearch(items: LayoutNode[], point: Point, axis: 'x' | 'y') {
482
+ private binarySearch(items: LayoutNode[], point: Point, axis: 'x' | 'y') {
527
483
  let low = 0;
528
484
  let high = items.length - 1;
529
485
  while (low <= high) {
@@ -542,7 +498,7 @@ export class TableLayout<T> extends ListLayout<T> {
542
498
  return Math.max(0, Math.min(items.length - 1, low));
543
499
  }
544
500
 
545
- buildPersistedIndices() {
501
+ private buildPersistedIndices() {
546
502
  if (this.virtualizer.persistedKeys === this.lastPersistedKeys) {
547
503
  return;
548
504
  }
@@ -560,7 +516,7 @@ export class TableLayout<T> extends ListLayout<T> {
560
516
  let indices = this.persistedIndices.get(layoutInfo.parentKey);
561
517
  if (!indices) {
562
518
  // stickyColumnIndices are always persisted along with any cells from persistedKeys.
563
- indices = collectionNode.type === 'cell' || collectionNode.type === 'column' ? [...this.stickyColumnIndices] : [];
519
+ indices = collectionNode?.type === 'cell' || collectionNode?.type === 'column' ? [...this.stickyColumnIndices] : [];
564
520
  this.persistedIndices.set(layoutInfo.parentKey, indices);
565
521
  }
566
522
 
@@ -596,6 +552,7 @@ export class TableLayout<T> extends ListLayout<T> {
596
552
 
597
553
  return isChrome105;
598
554
  }
555
+
599
556
  getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
600
557
  x += this.virtualizer.visibleRect.x;
601
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';