@react-stately/layout 3.4.6-nightly.3173 → 3.4.6-nightly.3183
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/dist/main.js +298 -1
- package/dist/main.js.map +1 -1
- package/dist/module.js +298 -2
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +32 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/TableLayout.ts +13 -73
- package/src/TableLayout_DEPRECATED.ts +426 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {ColumnProps, TableCollection} from '@react-types/table';
|
|
14
|
+
import {GridNode} from '@react-types/grid';
|
|
15
|
+
import {Key} from 'react';
|
|
16
|
+
import {LayoutInfo, Point, Rect, Size} from '@react-stately/virtualizer';
|
|
17
|
+
import {LayoutNode, ListLayout, ListLayoutOptions} from './ListLayout';
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
type TableLayoutOptions<T> = ListLayoutOptions<T> & {
|
|
21
|
+
getDefaultWidth: (props) => string | number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TableLayout_DEPRECATED<T> extends ListLayout<T> {
|
|
25
|
+
collection: TableCollection<T>;
|
|
26
|
+
lastCollection: TableCollection<T>;
|
|
27
|
+
columnWidths: Map<Key, number>;
|
|
28
|
+
stickyColumnIndices: number[];
|
|
29
|
+
getDefaultWidth: (props) => string | number;
|
|
30
|
+
wasLoading = false;
|
|
31
|
+
isLoading = false;
|
|
32
|
+
|
|
33
|
+
constructor(options: TableLayoutOptions<T>) {
|
|
34
|
+
super(options);
|
|
35
|
+
this.getDefaultWidth = options.getDefaultWidth;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
buildCollection(): LayoutNode[] {
|
|
40
|
+
// If columns changed, clear layout cache.
|
|
41
|
+
if (
|
|
42
|
+
!this.lastCollection ||
|
|
43
|
+
this.collection.columns.length !== this.lastCollection.columns.length ||
|
|
44
|
+
this.collection.columns.some((c, i) => c.key !== this.lastCollection.columns[i].key)
|
|
45
|
+
) {
|
|
46
|
+
// Invalidate everything in this layout pass. Will be reset in ListLayout on the next pass.
|
|
47
|
+
this.invalidateEverything = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Track whether we were previously loading. This is used to adjust the animations of async loading vs inserts.
|
|
51
|
+
let loadingState = this.collection.body.props.loadingState;
|
|
52
|
+
this.wasLoading = this.isLoading;
|
|
53
|
+
this.isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
|
|
54
|
+
|
|
55
|
+
this.buildColumnWidths();
|
|
56
|
+
let header = this.buildHeader();
|
|
57
|
+
let body = this.buildBody(0);
|
|
58
|
+
body.layoutInfo.rect.width = Math.max(header.layoutInfo.rect.width, body.layoutInfo.rect.width);
|
|
59
|
+
this.contentSize = new Size(body.layoutInfo.rect.width, body.layoutInfo.rect.maxY);
|
|
60
|
+
return [
|
|
61
|
+
header,
|
|
62
|
+
body
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
buildColumnWidths() {
|
|
67
|
+
this.columnWidths = new Map();
|
|
68
|
+
this.stickyColumnIndices = [];
|
|
69
|
+
|
|
70
|
+
// Pass 1: set widths for all explicitly defined columns.
|
|
71
|
+
let remainingColumns = new Set<GridNode<T>>();
|
|
72
|
+
let remainingSpace = this.virtualizer.visibleRect.width;
|
|
73
|
+
for (let column of this.collection.columns) {
|
|
74
|
+
let props = column.props as ColumnProps<T>;
|
|
75
|
+
let width = props.width ?? this.getDefaultWidth(props);
|
|
76
|
+
if (width != null) {
|
|
77
|
+
let w = this.parseWidth(width);
|
|
78
|
+
this.columnWidths.set(column.key, w);
|
|
79
|
+
remainingSpace -= w;
|
|
80
|
+
} else {
|
|
81
|
+
remainingColumns.add(column);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The selection cell and any other sticky columns always need to be visible.
|
|
85
|
+
// In addition, row headers need to be in the DOM for accessibility labeling.
|
|
86
|
+
if (column.props.isSelectionCell || this.collection.rowHeaderColumnKeys.has(column.key)) {
|
|
87
|
+
this.stickyColumnIndices.push(column.index);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Pass 2: if there are remaining columns, then distribute the remaining space evenly.
|
|
92
|
+
if (remainingColumns.size > 0) {
|
|
93
|
+
let columnWidth = remainingSpace / (this.collection.columns.length - this.columnWidths.size);
|
|
94
|
+
|
|
95
|
+
for (let column of remainingColumns) {
|
|
96
|
+
let props = column.props as ColumnProps<T>;
|
|
97
|
+
let minWidth = props.minWidth != null ? this.parseWidth(props.minWidth) : 75;
|
|
98
|
+
let maxWidth = props.maxWidth != null ? this.parseWidth(props.maxWidth) : Infinity;
|
|
99
|
+
let width = Math.floor(Math.max(minWidth, Math.min(maxWidth, columnWidth)));
|
|
100
|
+
|
|
101
|
+
this.columnWidths.set(column.key, width);
|
|
102
|
+
remainingSpace -= width;
|
|
103
|
+
if (width !== columnWidth) {
|
|
104
|
+
columnWidth = remainingSpace / (this.collection.columns.length - this.columnWidths.size);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
parseWidth(width: number | string): number {
|
|
111
|
+
if (typeof width === 'string') {
|
|
112
|
+
let match = width.match(/^(\d+)%$/);
|
|
113
|
+
if (!match) {
|
|
114
|
+
throw new Error('Only percentages are supported as column widths');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return this.virtualizer.visibleRect.width * (parseInt(match[1], 10) / 100);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return width;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
buildHeader(): LayoutNode {
|
|
124
|
+
let rect = new Rect(0, 0, 0, 0);
|
|
125
|
+
let layoutInfo = new LayoutInfo('header', 'header', rect);
|
|
126
|
+
|
|
127
|
+
let y = 0;
|
|
128
|
+
let width = 0;
|
|
129
|
+
let children: LayoutNode[] = [];
|
|
130
|
+
for (let headerRow of this.collection.headerRows) {
|
|
131
|
+
let layoutNode = this.buildChild(headerRow, 0, y);
|
|
132
|
+
layoutNode.layoutInfo.parentKey = 'header';
|
|
133
|
+
y = layoutNode.layoutInfo.rect.maxY;
|
|
134
|
+
width = Math.max(width, layoutNode.layoutInfo.rect.width);
|
|
135
|
+
children.push(layoutNode);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
rect.width = width;
|
|
139
|
+
rect.height = y;
|
|
140
|
+
|
|
141
|
+
this.layoutInfos.set('header', layoutInfo);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
layoutInfo,
|
|
145
|
+
children
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
buildHeaderRow(headerRow: GridNode<T>, x: number, y: number) {
|
|
150
|
+
let rect = new Rect(0, y, 0, 0);
|
|
151
|
+
let row = new LayoutInfo('headerrow', headerRow.key, rect);
|
|
152
|
+
|
|
153
|
+
let height = 0;
|
|
154
|
+
let columns: LayoutNode[] = [];
|
|
155
|
+
for (let cell of headerRow.childNodes) {
|
|
156
|
+
let layoutNode = this.buildChild(cell, x, y);
|
|
157
|
+
layoutNode.layoutInfo.parentKey = row.key;
|
|
158
|
+
x = layoutNode.layoutInfo.rect.maxX;
|
|
159
|
+
height = Math.max(height, layoutNode.layoutInfo.rect.height);
|
|
160
|
+
columns.push(layoutNode);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.setChildHeights(columns, height);
|
|
164
|
+
|
|
165
|
+
rect.height = height;
|
|
166
|
+
rect.width = x;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
layoutInfo: row,
|
|
170
|
+
children: columns
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setChildHeights(children: LayoutNode[], height: number) {
|
|
175
|
+
for (let child of children) {
|
|
176
|
+
if (child.layoutInfo.rect.height !== height) {
|
|
177
|
+
// Need to copy the layout info before we mutate it.
|
|
178
|
+
child.layoutInfo = child.layoutInfo.copy();
|
|
179
|
+
this.layoutInfos.set(child.layoutInfo.key, child.layoutInfo);
|
|
180
|
+
|
|
181
|
+
child.layoutInfo.rect.height = height;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getColumnWidth(node: GridNode<T>) {
|
|
187
|
+
let colspan = node.colspan ?? 1;
|
|
188
|
+
let width = 0;
|
|
189
|
+
for (let i = 0; i < colspan; i++) {
|
|
190
|
+
let column = this.collection.columns[node.index + i];
|
|
191
|
+
width += this.columnWidths.get(column.key);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return width;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getEstimatedHeight(node: GridNode<T>, width: number, height: number, estimatedHeight: number) {
|
|
198
|
+
let isEstimated = false;
|
|
199
|
+
|
|
200
|
+
// If no explicit height is available, use an estimated height.
|
|
201
|
+
if (height == null) {
|
|
202
|
+
// If a previous version of this layout info exists, reuse its height.
|
|
203
|
+
// Mark as estimated if the size of the overall collection view changed,
|
|
204
|
+
// or the content of the item changed.
|
|
205
|
+
let previousLayoutNode = this.layoutNodes.get(node.key);
|
|
206
|
+
if (previousLayoutNode) {
|
|
207
|
+
let curNode = this.collection.getItem(node.key);
|
|
208
|
+
let lastNode = this.lastCollection ? this.lastCollection.getItem(node.key) : null;
|
|
209
|
+
height = previousLayoutNode.layoutInfo.rect.height;
|
|
210
|
+
isEstimated = curNode !== lastNode || width !== previousLayoutNode.layoutInfo.rect.width || previousLayoutNode.layoutInfo.estimatedSize;
|
|
211
|
+
} else {
|
|
212
|
+
height = estimatedHeight;
|
|
213
|
+
isEstimated = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {height, isEstimated};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
buildColumn(node: GridNode<T>, x: number, y: number): LayoutNode {
|
|
221
|
+
let width = this.getColumnWidth(node);
|
|
222
|
+
let {height, isEstimated} = this.getEstimatedHeight(node, width, this.headingHeight, this.estimatedHeadingHeight);
|
|
223
|
+
let rect = new Rect(x, y, width, height);
|
|
224
|
+
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
225
|
+
layoutInfo.isSticky = node.props?.isSelectionCell;
|
|
226
|
+
layoutInfo.zIndex = layoutInfo.isSticky ? 2 : 1;
|
|
227
|
+
layoutInfo.estimatedSize = isEstimated;
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
layoutInfo
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
buildBody(y: number): LayoutNode {
|
|
235
|
+
let rect = new Rect(0, y, 0, 0);
|
|
236
|
+
let layoutInfo = new LayoutInfo('rowgroup', 'body', rect);
|
|
237
|
+
|
|
238
|
+
let startY = y;
|
|
239
|
+
let width = 0;
|
|
240
|
+
let children: LayoutNode[] = [];
|
|
241
|
+
for (let node of this.collection.body.childNodes) {
|
|
242
|
+
let layoutNode = this.buildChild(node, 0, y);
|
|
243
|
+
layoutNode.layoutInfo.parentKey = 'body';
|
|
244
|
+
y = layoutNode.layoutInfo.rect.maxY;
|
|
245
|
+
width = Math.max(width, layoutNode.layoutInfo.rect.width);
|
|
246
|
+
children.push(layoutNode);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (this.isLoading) {
|
|
250
|
+
let rect = new Rect(0, y, width || this.virtualizer.visibleRect.width, children.length === 0 ? this.virtualizer.visibleRect.height : 60);
|
|
251
|
+
let loader = new LayoutInfo('loader', 'loader', rect);
|
|
252
|
+
loader.parentKey = 'body';
|
|
253
|
+
loader.isSticky = children.length === 0;
|
|
254
|
+
this.layoutInfos.set('loader', loader);
|
|
255
|
+
children.push({layoutInfo: loader});
|
|
256
|
+
y = loader.rect.maxY;
|
|
257
|
+
width = Math.max(width, rect.width);
|
|
258
|
+
} else if (children.length === 0) {
|
|
259
|
+
let rect = new Rect(0, y, this.virtualizer.visibleRect.width, this.virtualizer.visibleRect.height);
|
|
260
|
+
let empty = new LayoutInfo('empty', 'empty', rect);
|
|
261
|
+
empty.parentKey = 'body';
|
|
262
|
+
empty.isSticky = true;
|
|
263
|
+
this.layoutInfos.set('empty', empty);
|
|
264
|
+
children.push({layoutInfo: empty});
|
|
265
|
+
y = empty.rect.maxY;
|
|
266
|
+
width = Math.max(width, rect.width);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
rect.width = width;
|
|
270
|
+
rect.height = y - startY;
|
|
271
|
+
|
|
272
|
+
this.layoutInfos.set('body', layoutInfo);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
layoutInfo,
|
|
276
|
+
children
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
buildNode(node: GridNode<T>, x: number, y: number): LayoutNode {
|
|
281
|
+
switch (node.type) {
|
|
282
|
+
case 'headerrow':
|
|
283
|
+
return this.buildHeaderRow(node, x, y);
|
|
284
|
+
case 'item':
|
|
285
|
+
return this.buildRow(node, x, y);
|
|
286
|
+
case 'column':
|
|
287
|
+
case 'placeholder':
|
|
288
|
+
return this.buildColumn(node, x, y);
|
|
289
|
+
case 'cell':
|
|
290
|
+
return this.buildCell(node, x, y);
|
|
291
|
+
default:
|
|
292
|
+
throw new Error('Unknown node type ' + node.type);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
buildRow(node: GridNode<T>, x: number, y: number): LayoutNode {
|
|
297
|
+
let rect = new Rect(x, y, 0, 0);
|
|
298
|
+
let layoutInfo = new LayoutInfo('row', node.key, rect);
|
|
299
|
+
|
|
300
|
+
let children: LayoutNode[] = [];
|
|
301
|
+
let height = 0;
|
|
302
|
+
for (let child of node.childNodes) {
|
|
303
|
+
let layoutNode = this.buildChild(child, x, y);
|
|
304
|
+
x = layoutNode.layoutInfo.rect.maxX;
|
|
305
|
+
height = Math.max(height, layoutNode.layoutInfo.rect.height);
|
|
306
|
+
children.push(layoutNode);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
this.setChildHeights(children, height);
|
|
310
|
+
|
|
311
|
+
rect.width = x;
|
|
312
|
+
rect.height = height + 1; // +1 for bottom border
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
layoutInfo,
|
|
316
|
+
children
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
buildCell(node: GridNode<T>, x: number, y: number): LayoutNode {
|
|
321
|
+
let width = this.getColumnWidth(node);
|
|
322
|
+
let {height, isEstimated} = this.getEstimatedHeight(node, width, this.rowHeight, this.estimatedRowHeight);
|
|
323
|
+
let rect = new Rect(x, y, width, height);
|
|
324
|
+
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
325
|
+
layoutInfo.isSticky = node.props?.isSelectionCell;
|
|
326
|
+
layoutInfo.zIndex = layoutInfo.isSticky ? 2 : 1;
|
|
327
|
+
layoutInfo.estimatedSize = isEstimated;
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
layoutInfo
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
getVisibleLayoutInfos(rect: Rect) {
|
|
335
|
+
let res: LayoutInfo[] = [];
|
|
336
|
+
|
|
337
|
+
for (let node of this.rootNodes) {
|
|
338
|
+
res.push(node.layoutInfo);
|
|
339
|
+
this.addVisibleLayoutInfos(res, node, rect);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return res;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
addVisibleLayoutInfos(res: LayoutInfo[], node: LayoutNode, rect: Rect) {
|
|
346
|
+
if (!node.children || node.children.length === 0) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
switch (node.layoutInfo.type) {
|
|
351
|
+
case 'header': {
|
|
352
|
+
for (let child of node.children) {
|
|
353
|
+
res.push(child.layoutInfo);
|
|
354
|
+
this.addVisibleLayoutInfos(res, child, rect);
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case 'rowgroup': {
|
|
359
|
+
let firstVisibleRow = this.binarySearch(node.children, rect.topLeft, 'y');
|
|
360
|
+
let lastVisibleRow = this.binarySearch(node.children, rect.bottomRight, 'y');
|
|
361
|
+
for (let i = firstVisibleRow; i <= lastVisibleRow; i++) {
|
|
362
|
+
res.push(node.children[i].layoutInfo);
|
|
363
|
+
this.addVisibleLayoutInfos(res, node.children[i], rect);
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
case 'headerrow':
|
|
368
|
+
case 'row': {
|
|
369
|
+
let firstVisibleCell = this.binarySearch(node.children, rect.topLeft, 'x');
|
|
370
|
+
let lastVisibleCell = this.binarySearch(node.children, rect.topRight, 'x');
|
|
371
|
+
let stickyIndex = 0;
|
|
372
|
+
for (let i = firstVisibleCell; i <= lastVisibleCell; i++) {
|
|
373
|
+
// Sticky columns and row headers are always in the DOM. Interleave these
|
|
374
|
+
// with the visible range so that they are in the right order.
|
|
375
|
+
if (stickyIndex < this.stickyColumnIndices.length) {
|
|
376
|
+
let idx = this.stickyColumnIndices[stickyIndex];
|
|
377
|
+
while (idx < i) {
|
|
378
|
+
res.push(node.children[idx].layoutInfo);
|
|
379
|
+
idx = this.stickyColumnIndices[stickyIndex++];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
res.push(node.children[i].layoutInfo);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
while (stickyIndex < this.stickyColumnIndices.length) {
|
|
387
|
+
let idx = this.stickyColumnIndices[stickyIndex++];
|
|
388
|
+
res.push(node.children[idx].layoutInfo);
|
|
389
|
+
}
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
default:
|
|
393
|
+
throw new Error('Unknown node type ' + node.layoutInfo.type);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
binarySearch(items: LayoutNode[], point: Point, axis: 'x' | 'y') {
|
|
398
|
+
let low = 0;
|
|
399
|
+
let high = items.length - 1;
|
|
400
|
+
while (low <= high) {
|
|
401
|
+
let mid = (low + high) >> 1;
|
|
402
|
+
let item = items[mid];
|
|
403
|
+
|
|
404
|
+
if ((axis === 'x' && item.layoutInfo.rect.maxX < point.x) || (axis === 'y' && item.layoutInfo.rect.maxY < point.y)) {
|
|
405
|
+
low = mid + 1;
|
|
406
|
+
} else if ((axis === 'x' && item.layoutInfo.rect.x > point.x) || (axis === 'y' && item.layoutInfo.rect.y > point.y)) {
|
|
407
|
+
high = mid - 1;
|
|
408
|
+
} else {
|
|
409
|
+
return mid;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return Math.max(0, Math.min(items.length - 1, low));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
getInitialLayoutInfo(layoutInfo: LayoutInfo) {
|
|
417
|
+
let res = super.getInitialLayoutInfo(layoutInfo);
|
|
418
|
+
|
|
419
|
+
// If this insert was the result of async loading, remove the zoom effect and just keep the fade in.
|
|
420
|
+
if (this.wasLoading) {
|
|
421
|
+
res.transform = null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return res;
|
|
425
|
+
}
|
|
426
|
+
}
|
package/src/index.ts
CHANGED