@react-stately/layout 3.13.10-nightly.4685 → 3.13.10-nightly.4694
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/GridLayout.main.js +137 -0
- package/dist/GridLayout.main.js.map +1 -0
- package/dist/GridLayout.mjs +132 -0
- package/dist/GridLayout.module.js +132 -0
- package/dist/GridLayout.module.js.map +1 -0
- package/dist/ListLayout.main.js +23 -63
- package/dist/ListLayout.main.js.map +1 -1
- package/dist/ListLayout.mjs +24 -64
- package/dist/ListLayout.module.js +24 -64
- package/dist/ListLayout.module.js.map +1 -1
- package/dist/TableLayout.main.js +30 -70
- package/dist/TableLayout.main.js.map +1 -1
- package/dist/TableLayout.mjs +30 -70
- package/dist/TableLayout.module.js +30 -70
- package/dist/TableLayout.module.js.map +1 -1
- package/dist/import.mjs +3 -1
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -1
- package/dist/module.js +3 -1
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +83 -49
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/GridLayout.ts +215 -0
- package/src/ListLayout.ts +50 -104
- package/src/TableLayout.ts +48 -94
- package/src/index.ts +4 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 {DropTarget, DropTargetDelegate, ItemDropTarget, Key, Node} from '@react-types/shared';
|
|
14
|
+
import {Layout, LayoutInfo, Rect, Size} from '@react-stately/virtualizer';
|
|
15
|
+
|
|
16
|
+
export interface GridLayoutOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The minimum item size.
|
|
19
|
+
* @default 200 x 200
|
|
20
|
+
*/
|
|
21
|
+
minItemSize?: Size,
|
|
22
|
+
/**
|
|
23
|
+
* The maximum item size.
|
|
24
|
+
* @default Infinity
|
|
25
|
+
*/
|
|
26
|
+
maxItemSize?: Size,
|
|
27
|
+
/**
|
|
28
|
+
* The minimum space required between items.
|
|
29
|
+
* @default 18 x 18
|
|
30
|
+
*/
|
|
31
|
+
minSpace?: Size,
|
|
32
|
+
/**
|
|
33
|
+
* The maximum number of columns.
|
|
34
|
+
* @default Infinity
|
|
35
|
+
*/
|
|
36
|
+
maxColumns?: number,
|
|
37
|
+
/**
|
|
38
|
+
* The thickness of the drop indicator.
|
|
39
|
+
* @default 2
|
|
40
|
+
*/
|
|
41
|
+
dropIndicatorThickness?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class GridLayout<T, O = any> extends Layout<Node<T>, O> implements DropTargetDelegate {
|
|
45
|
+
protected minItemSize: Size;
|
|
46
|
+
protected maxItemSize: Size;
|
|
47
|
+
protected minSpace: Size;
|
|
48
|
+
protected maxColumns: number;
|
|
49
|
+
protected dropIndicatorThickness: number;
|
|
50
|
+
protected itemSize: Size;
|
|
51
|
+
protected numColumns: number;
|
|
52
|
+
protected horizontalSpacing: number;
|
|
53
|
+
protected layoutInfos: LayoutInfo[];
|
|
54
|
+
|
|
55
|
+
constructor(options: GridLayoutOptions) {
|
|
56
|
+
super();
|
|
57
|
+
this.minItemSize = options.minItemSize || new Size(200, 200);
|
|
58
|
+
this.maxItemSize = options.maxItemSize || new Size(Infinity, Infinity);
|
|
59
|
+
this.minSpace = options.minSpace || new Size(18, 18);
|
|
60
|
+
this.maxColumns = options.maxColumns || Infinity;
|
|
61
|
+
this.dropIndicatorThickness = options.dropIndicatorThickness || 2;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
validate(): void {
|
|
65
|
+
let visibleWidth = this.virtualizer.visibleRect.width;
|
|
66
|
+
|
|
67
|
+
// The max item width is always the entire viewport.
|
|
68
|
+
// If the max item height is infinity, scale in proportion to the max width.
|
|
69
|
+
let maxItemWidth = Math.min(this.maxItemSize.width, visibleWidth);
|
|
70
|
+
let maxItemHeight = Number.isFinite(this.maxItemSize.height)
|
|
71
|
+
? this.maxItemSize.height
|
|
72
|
+
: Math.floor((this.minItemSize.height / this.minItemSize.width) * maxItemWidth);
|
|
73
|
+
|
|
74
|
+
// Compute the number of rows and columns needed to display the content
|
|
75
|
+
let columns = Math.floor(visibleWidth / (this.minItemSize.width + this.minSpace.width));
|
|
76
|
+
this.numColumns = Math.max(1, Math.min(this.maxColumns, columns));
|
|
77
|
+
|
|
78
|
+
// Compute the available width (minus the space between items)
|
|
79
|
+
let width = visibleWidth - (this.minSpace.width * Math.max(0, this.numColumns));
|
|
80
|
+
|
|
81
|
+
// Compute the item width based on the space available
|
|
82
|
+
let itemWidth = Math.floor(width / this.numColumns);
|
|
83
|
+
itemWidth = Math.max(this.minItemSize.width, Math.min(maxItemWidth, itemWidth));
|
|
84
|
+
|
|
85
|
+
// Compute the item height, which is proportional to the item width
|
|
86
|
+
let t = ((itemWidth - this.minItemSize.width) / (maxItemWidth - this.minItemSize.width));
|
|
87
|
+
let itemHeight = this.minItemSize.height + Math.floor((maxItemHeight - this.minItemSize.height) * t);
|
|
88
|
+
itemHeight = Math.max(this.minItemSize.height, Math.min(maxItemHeight, itemHeight));
|
|
89
|
+
this.itemSize = new Size(itemWidth, itemHeight);
|
|
90
|
+
|
|
91
|
+
// Compute the horizontal spacing and content height
|
|
92
|
+
this.horizontalSpacing = Math.floor((visibleWidth - this.numColumns * this.itemSize.width) / (this.numColumns + 1));
|
|
93
|
+
|
|
94
|
+
this.layoutInfos = [];
|
|
95
|
+
for (let node of this.virtualizer.collection) {
|
|
96
|
+
this.layoutInfos.push(this.getLayoutInfoForNode(node));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getVisibleLayoutInfos(rect: Rect): LayoutInfo[] {
|
|
101
|
+
let firstVisibleItem = this.getIndexAtPoint(rect.x, rect.y);
|
|
102
|
+
let lastVisibleItem = this.getIndexAtPoint(rect.maxX, rect.maxY);
|
|
103
|
+
let result = this.layoutInfos.slice(firstVisibleItem, lastVisibleItem + 1);
|
|
104
|
+
let persistedIndices = [...this.virtualizer.persistedKeys].map(key => this.virtualizer.collection.getItem(key).index).sort((a, b) => a - b);
|
|
105
|
+
let persistedBefore = [];
|
|
106
|
+
for (let index of persistedIndices) {
|
|
107
|
+
if (index < firstVisibleItem) {
|
|
108
|
+
persistedBefore.push(this.layoutInfos[index]);
|
|
109
|
+
} else if (index > lastVisibleItem) {
|
|
110
|
+
result.push(this.layoutInfos[index]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
result.unshift(...persistedBefore);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected getIndexAtPoint(x: number, y: number) {
|
|
118
|
+
let itemHeight = this.itemSize.height + this.minSpace.height;
|
|
119
|
+
let itemWidth = this.itemSize.width + this.horizontalSpacing;
|
|
120
|
+
return Math.max(0,
|
|
121
|
+
Math.min(
|
|
122
|
+
this.virtualizer.collection.size - 1,
|
|
123
|
+
Math.floor(y / itemHeight) * this.numColumns + Math.floor((x - this.horizontalSpacing) / itemWidth)
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getLayoutInfo(key: Key): LayoutInfo | null {
|
|
129
|
+
let node = this.virtualizer.collection.getItem(key);
|
|
130
|
+
return node ? this.layoutInfos[node.index] : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected getLayoutInfoForNode(node: Node<T>): LayoutInfo {
|
|
134
|
+
let idx = node.index;
|
|
135
|
+
let row = Math.floor(idx / this.numColumns);
|
|
136
|
+
let column = idx % this.numColumns;
|
|
137
|
+
let x = this.horizontalSpacing + column * (this.itemSize.width + this.horizontalSpacing);
|
|
138
|
+
let y = this.minSpace.height + row * (this.itemSize.height + this.minSpace.height);
|
|
139
|
+
let rect = new Rect(x, y, this.itemSize.width, this.itemSize.height);
|
|
140
|
+
return new LayoutInfo(node.type, node.key, rect);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getContentSize(): Size {
|
|
144
|
+
let numRows = Math.ceil(this.virtualizer.collection.size / this.numColumns);
|
|
145
|
+
let contentHeight = this.minSpace.height + numRows * (this.itemSize.height + this.minSpace.height);
|
|
146
|
+
return new Size(this.virtualizer.visibleRect.width, contentHeight);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
|
|
150
|
+
if (this.layoutInfos.length === 0) {
|
|
151
|
+
return {type: 'root'};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
x += this.virtualizer.visibleRect.x;
|
|
155
|
+
y += this.virtualizer.visibleRect.y;
|
|
156
|
+
let index = this.getIndexAtPoint(x, y);
|
|
157
|
+
|
|
158
|
+
let layoutInfo = this.layoutInfos[index];
|
|
159
|
+
let target: DropTarget = {
|
|
160
|
+
type: 'item',
|
|
161
|
+
key: layoutInfo.key,
|
|
162
|
+
dropPosition: 'on'
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
let pos = this.numColumns === 1 ? y : x;
|
|
166
|
+
let layoutInfoPos = this.numColumns === 1 ? layoutInfo.rect.y : layoutInfo.rect.x;
|
|
167
|
+
let size = this.numColumns === 1 ? layoutInfo.rect.height : layoutInfo.rect.width;
|
|
168
|
+
if (isValidDropTarget(target)) {
|
|
169
|
+
// If dropping on the item is accepted, try the before/after positions
|
|
170
|
+
// if within 5px of the start or end of the item.
|
|
171
|
+
if (pos < layoutInfoPos + 5) {
|
|
172
|
+
target.dropPosition = 'before';
|
|
173
|
+
} else if (pos > layoutInfoPos + size - 5) {
|
|
174
|
+
target.dropPosition = 'after';
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
// If dropping on the item isn't accepted, try the target before or after depending on the position.
|
|
178
|
+
let mid = layoutInfoPos + size / 2;
|
|
179
|
+
if (pos <= mid && isValidDropTarget({...target, dropPosition: 'before'})) {
|
|
180
|
+
target.dropPosition = 'before';
|
|
181
|
+
} else if (pos >= mid && isValidDropTarget({...target, dropPosition: 'after'})) {
|
|
182
|
+
target.dropPosition = 'after';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return target;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getDropTargetLayoutInfo(target: ItemDropTarget): LayoutInfo {
|
|
190
|
+
let layoutInfo = this.getLayoutInfo(target.key);
|
|
191
|
+
let rect: Rect;
|
|
192
|
+
if (this.numColumns === 1) {
|
|
193
|
+
// Flip from vertical to horizontal if only one column is visible.
|
|
194
|
+
rect = new Rect(
|
|
195
|
+
layoutInfo.rect.x,
|
|
196
|
+
target.dropPosition === 'before'
|
|
197
|
+
? layoutInfo.rect.y - this.minSpace.height / 2 - this.dropIndicatorThickness / 2
|
|
198
|
+
: layoutInfo.rect.maxY + this.minSpace.height / 2 - this.dropIndicatorThickness / 2,
|
|
199
|
+
layoutInfo.rect.width,
|
|
200
|
+
this.dropIndicatorThickness
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
rect = new Rect(
|
|
204
|
+
target.dropPosition === 'before'
|
|
205
|
+
? layoutInfo.rect.x - this.horizontalSpacing / 2 - this.dropIndicatorThickness / 2
|
|
206
|
+
: layoutInfo.rect.maxX + this.horizontalSpacing / 2 - this.dropIndicatorThickness / 2,
|
|
207
|
+
layoutInfo.rect.y,
|
|
208
|
+
this.dropIndicatorThickness,
|
|
209
|
+
layoutInfo.rect.height
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return new LayoutInfo('dropIndicator', target.key + ':' + target.dropPosition, rect);
|
|
214
|
+
}
|
|
215
|
+
}
|
package/src/ListLayout.ts
CHANGED
|
@@ -10,70 +10,55 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {Collection, DropTarget, DropTargetDelegate, Key, Node} from '@react-types/shared';
|
|
13
|
+
import {Collection, DropTarget, DropTargetDelegate, ItemDropTarget, Key, Node} from '@react-types/shared';
|
|
14
14
|
import {getChildNodes} from '@react-stately/collections';
|
|
15
15
|
import {InvalidationContext, Layout, LayoutInfo, Point, Rect, Size} from '@react-stately/virtualizer';
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
/** The height of a row in px. */
|
|
17
|
+
export interface ListLayoutOptions {
|
|
18
|
+
/** The fixed height of a row in px. */
|
|
19
19
|
rowHeight?: number,
|
|
20
|
+
/** The estimated height of a row, when row heights are variable. */
|
|
20
21
|
estimatedRowHeight?: number,
|
|
22
|
+
/** The fixed height of a section header in px. */
|
|
21
23
|
headingHeight?: number,
|
|
24
|
+
/** The estimated height of a section header, when the height is variable. */
|
|
22
25
|
estimatedHeadingHeight?: number,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
placeholderHeight?: number,
|
|
27
|
-
forceSectionHeaders?: boolean,
|
|
28
|
-
enableEmptyState?: boolean
|
|
29
|
-
};
|
|
26
|
+
/** The thickness of the drop indicator. */
|
|
27
|
+
dropIndicatorThickness?: number
|
|
28
|
+
}
|
|
30
29
|
|
|
31
30
|
// A wrapper around LayoutInfo that supports hierarchy
|
|
32
31
|
export interface LayoutNode {
|
|
33
32
|
node?: Node<unknown>,
|
|
34
33
|
layoutInfo: LayoutInfo,
|
|
35
|
-
header?: LayoutInfo,
|
|
36
34
|
children?: LayoutNode[],
|
|
37
35
|
validRect: Rect,
|
|
38
36
|
index?: number
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
export interface ListLayoutProps {
|
|
42
|
-
isLoading?: boolean
|
|
43
|
-
}
|
|
44
|
-
|
|
45
39
|
const DEFAULT_HEIGHT = 48;
|
|
46
40
|
|
|
47
41
|
/**
|
|
48
|
-
* The ListLayout class is an implementation of a virtualizer {@link Layout}
|
|
49
|
-
* it is used for creating lists and lists with indented sub-lists.
|
|
50
|
-
*
|
|
42
|
+
* The ListLayout class is an implementation of a virtualizer {@link Layout}.
|
|
51
43
|
* To configure a ListLayout, you can use the properties to define the
|
|
52
44
|
* layouts and/or use the method for defining indentation.
|
|
53
45
|
* The {@link ListKeyboardDelegate} extends the existing virtualizer
|
|
54
46
|
* delegate with an additional method to do this (it uses the same delegate object as
|
|
55
47
|
* the virtualizer itself).
|
|
56
48
|
*/
|
|
57
|
-
export class ListLayout<T> extends Layout<Node<T>,
|
|
49
|
+
export class ListLayout<T, O = any> extends Layout<Node<T>, O> implements DropTargetDelegate {
|
|
58
50
|
protected rowHeight: number;
|
|
59
51
|
protected estimatedRowHeight: number;
|
|
60
52
|
protected headingHeight: number;
|
|
61
53
|
protected estimatedHeadingHeight: number;
|
|
62
|
-
protected
|
|
63
|
-
protected padding: number;
|
|
64
|
-
protected indentationForItem?: (collection: Collection<Node<T>>, key: Key) => number;
|
|
65
|
-
protected layoutInfos: Map<Key, LayoutInfo>;
|
|
54
|
+
protected dropIndicatorThickness: number;
|
|
66
55
|
protected layoutNodes: Map<Key, LayoutNode>;
|
|
67
56
|
protected contentSize: Size;
|
|
68
57
|
protected collection: Collection<Node<T>>;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
protected lastCollection: Collection<Node<T>>;
|
|
58
|
+
private lastCollection: Collection<Node<T>>;
|
|
59
|
+
private lastWidth: number;
|
|
72
60
|
protected rootNodes: LayoutNode[];
|
|
73
|
-
|
|
74
|
-
protected loaderHeight: number;
|
|
75
|
-
protected placeholderHeight: number;
|
|
76
|
-
protected enableEmptyState: boolean;
|
|
61
|
+
private invalidateEverything: boolean;
|
|
77
62
|
/** The rectangle containing currently valid layout infos. */
|
|
78
63
|
protected validRect: Rect;
|
|
79
64
|
/** The rectangle of requested layout infos so far. */
|
|
@@ -83,19 +68,13 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
83
68
|
* Creates a new ListLayout with options. See the list of properties below for a description
|
|
84
69
|
* of the options that can be provided.
|
|
85
70
|
*/
|
|
86
|
-
constructor(options: ListLayoutOptions
|
|
71
|
+
constructor(options: ListLayoutOptions = {}) {
|
|
87
72
|
super();
|
|
88
73
|
this.rowHeight = options.rowHeight;
|
|
89
74
|
this.estimatedRowHeight = options.estimatedRowHeight;
|
|
90
75
|
this.headingHeight = options.headingHeight;
|
|
91
76
|
this.estimatedHeadingHeight = options.estimatedHeadingHeight;
|
|
92
|
-
this.
|
|
93
|
-
this.padding = options.padding || 0;
|
|
94
|
-
this.indentationForItem = options.indentationForItem;
|
|
95
|
-
this.loaderHeight = options.loaderHeight;
|
|
96
|
-
this.placeholderHeight = options.placeholderHeight;
|
|
97
|
-
this.enableEmptyState = options.enableEmptyState || false;
|
|
98
|
-
this.layoutInfos = new Map();
|
|
77
|
+
this.dropIndicatorThickness = options.dropIndicatorThickness || 2;
|
|
99
78
|
this.layoutNodes = new Map();
|
|
100
79
|
this.rootNodes = [];
|
|
101
80
|
this.lastWidth = 0;
|
|
@@ -107,7 +86,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
107
86
|
|
|
108
87
|
getLayoutInfo(key: Key) {
|
|
109
88
|
this.ensureLayoutInfo(key);
|
|
110
|
-
return this.
|
|
89
|
+
return this.layoutNodes.get(key)?.layoutInfo || null;
|
|
111
90
|
}
|
|
112
91
|
|
|
113
92
|
getVisibleLayoutInfos(rect: Rect) {
|
|
@@ -129,9 +108,6 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
129
108
|
for (let node of nodes) {
|
|
130
109
|
if (this.isVisible(node, rect)) {
|
|
131
110
|
res.push(node.layoutInfo);
|
|
132
|
-
if (node.header) {
|
|
133
|
-
res.push(node.header);
|
|
134
|
-
}
|
|
135
111
|
|
|
136
112
|
if (node.children) {
|
|
137
113
|
addNodes(node.children);
|
|
@@ -166,7 +142,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
166
142
|
// If the layout info wasn't found, it might be outside the bounds of the area that we've
|
|
167
143
|
// computed layout for so far. This can happen when accessing a random key, e.g pressing Home/End.
|
|
168
144
|
// Compute the full layout and try again.
|
|
169
|
-
if (!this.
|
|
145
|
+
if (!this.layoutNodes.has(key) && this.requestedRect.area < this.contentSize.area && this.lastCollection) {
|
|
170
146
|
this.requestedRect = new Rect(0, 0, Infinity, Infinity);
|
|
171
147
|
this.rootNodes = this.buildCollection();
|
|
172
148
|
this.requestedRect = new Rect(0, 0, this.contentSize.width, this.contentSize.height);
|
|
@@ -176,19 +152,18 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
176
152
|
return false;
|
|
177
153
|
}
|
|
178
154
|
|
|
179
|
-
|
|
180
|
-
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || this.virtualizer.isPersistedKey(node.layoutInfo.key);
|
|
155
|
+
protected isVisible(node: LayoutNode, rect: Rect) {
|
|
156
|
+
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || this.virtualizer.isPersistedKey(node.layoutInfo.key);
|
|
181
157
|
}
|
|
182
158
|
|
|
183
|
-
protected shouldInvalidateEverything(invalidationContext: InvalidationContext<
|
|
159
|
+
protected shouldInvalidateEverything(invalidationContext: InvalidationContext<O>) {
|
|
184
160
|
// Invalidate cache if the size of the collection changed.
|
|
185
161
|
// In this case, we need to recalculate the entire layout.
|
|
186
162
|
return invalidationContext.sizeChanged;
|
|
187
163
|
}
|
|
188
164
|
|
|
189
|
-
validate(invalidationContext: InvalidationContext<
|
|
165
|
+
validate(invalidationContext: InvalidationContext<O>) {
|
|
190
166
|
this.collection = this.virtualizer.collection;
|
|
191
|
-
this.isLoading = invalidationContext.layoutOptions?.isLoading || false;
|
|
192
167
|
|
|
193
168
|
// Reset valid rect if we will have to invalidate everything.
|
|
194
169
|
// Otherwise we can reuse cached layout infos outside the current visible rect.
|
|
@@ -205,8 +180,6 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
205
180
|
if (!this.collection.getItem(key)) {
|
|
206
181
|
let layoutNode = this.layoutNodes.get(key);
|
|
207
182
|
if (layoutNode) {
|
|
208
|
-
this.layoutInfos.delete(layoutNode.layoutInfo.key);
|
|
209
|
-
this.layoutInfos.delete(layoutNode.header?.key);
|
|
210
183
|
this.layoutNodes.delete(key);
|
|
211
184
|
}
|
|
212
185
|
}
|
|
@@ -219,12 +192,11 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
219
192
|
this.validRect = this.requestedRect.copy();
|
|
220
193
|
}
|
|
221
194
|
|
|
222
|
-
protected buildCollection(): LayoutNode[] {
|
|
223
|
-
let y = this.padding;
|
|
195
|
+
protected buildCollection(y = 0): LayoutNode[] {
|
|
224
196
|
let skipped = 0;
|
|
225
197
|
let nodes = [];
|
|
226
198
|
for (let node of this.collection) {
|
|
227
|
-
let rowHeight =
|
|
199
|
+
let rowHeight = this.rowHeight ?? this.estimatedRowHeight;
|
|
228
200
|
|
|
229
201
|
// Skip rows before the valid rectangle unless they are already cached.
|
|
230
202
|
if (node.type === 'item' && y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) {
|
|
@@ -243,25 +215,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
243
215
|
}
|
|
244
216
|
}
|
|
245
217
|
|
|
246
|
-
|
|
247
|
-
let rect = new Rect(0, y, this.virtualizer.visibleRect.width,
|
|
248
|
-
this.loaderHeight ?? this.virtualizer.visibleRect.height);
|
|
249
|
-
let loader = new LayoutInfo('loader', 'loader', rect);
|
|
250
|
-
this.layoutInfos.set('loader', loader);
|
|
251
|
-
nodes.push({layoutInfo: loader});
|
|
252
|
-
y = loader.rect.maxY;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (nodes.length === 0 && this.enableEmptyState) {
|
|
256
|
-
let rect = new Rect(0, y, this.virtualizer.visibleRect.width,
|
|
257
|
-
this.placeholderHeight ?? this.virtualizer.visibleRect.height);
|
|
258
|
-
let placeholder = new LayoutInfo('placeholder', 'placeholder', rect);
|
|
259
|
-
this.layoutInfos.set('placeholder', placeholder);
|
|
260
|
-
nodes.push({layoutInfo: placeholder});
|
|
261
|
-
y = placeholder.rect.maxY;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
this.contentSize = new Size(this.virtualizer.visibleRect.width, y + this.padding);
|
|
218
|
+
this.contentSize = new Size(this.virtualizer.visibleRect.width, y);
|
|
265
219
|
return nodes;
|
|
266
220
|
}
|
|
267
221
|
|
|
@@ -271,7 +225,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
271
225
|
!this.invalidateEverything &&
|
|
272
226
|
cached &&
|
|
273
227
|
cached.node === node &&
|
|
274
|
-
y ===
|
|
228
|
+
y === cached.layoutInfo.rect.y &&
|
|
275
229
|
cached.layoutInfo.rect.intersects(this.validRect) &&
|
|
276
230
|
cached.validRect.containsRect(cached.layoutInfo.rect.intersection(this.requestedRect))
|
|
277
231
|
);
|
|
@@ -286,11 +240,6 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
286
240
|
layoutNode.node = node;
|
|
287
241
|
|
|
288
242
|
layoutNode.layoutInfo.parentKey = parentKey ?? null;
|
|
289
|
-
this.layoutInfos.set(layoutNode.layoutInfo.key, layoutNode.layoutInfo);
|
|
290
|
-
if (layoutNode.header) {
|
|
291
|
-
this.layoutInfos.set(layoutNode.header.key, layoutNode.header);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
243
|
this.layoutNodes.set(node.key, layoutNode);
|
|
295
244
|
return layoutNode;
|
|
296
245
|
}
|
|
@@ -306,17 +255,8 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
306
255
|
}
|
|
307
256
|
}
|
|
308
257
|
|
|
309
|
-
|
|
258
|
+
protected buildSection(node: Node<T>, x: number, y: number): LayoutNode {
|
|
310
259
|
let width = this.virtualizer.visibleRect.width;
|
|
311
|
-
let header = null;
|
|
312
|
-
if (node.rendered || this.forceSectionHeaders) {
|
|
313
|
-
let headerNode = this.buildSectionHeader(node, x, y);
|
|
314
|
-
header = headerNode.layoutInfo;
|
|
315
|
-
header.key += ':header';
|
|
316
|
-
header.parentKey = node.key;
|
|
317
|
-
y += header.rect.height;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
260
|
let rect = new Rect(0, y, width, 0);
|
|
321
261
|
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
322
262
|
|
|
@@ -347,14 +287,13 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
347
287
|
rect.height = y - startY;
|
|
348
288
|
|
|
349
289
|
return {
|
|
350
|
-
header,
|
|
351
290
|
layoutInfo,
|
|
352
291
|
children,
|
|
353
292
|
validRect: layoutInfo.rect.intersection(this.requestedRect)
|
|
354
293
|
};
|
|
355
294
|
}
|
|
356
295
|
|
|
357
|
-
|
|
296
|
+
protected buildSectionHeader(node: Node<T>, x: number, y: number): LayoutNode {
|
|
358
297
|
let width = this.virtualizer.visibleRect.width;
|
|
359
298
|
let rectHeight = this.headingHeight;
|
|
360
299
|
let isEstimated = false;
|
|
@@ -365,7 +304,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
365
304
|
// Mark as estimated if the size of the overall virtualizer changed,
|
|
366
305
|
// or the content of the item changed.
|
|
367
306
|
let previousLayoutNode = this.layoutNodes.get(node.key);
|
|
368
|
-
let previousLayoutInfo = previousLayoutNode?.
|
|
307
|
+
let previousLayoutInfo = previousLayoutNode?.layoutInfo;
|
|
369
308
|
if (previousLayoutInfo) {
|
|
370
309
|
let curNode = this.collection.getItem(node.key);
|
|
371
310
|
let lastNode = this.lastCollection ? this.lastCollection.getItem(node.key) : null;
|
|
@@ -391,7 +330,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
391
330
|
};
|
|
392
331
|
}
|
|
393
332
|
|
|
394
|
-
|
|
333
|
+
protected buildItem(node: Node<T>, x: number, y: number): LayoutNode {
|
|
395
334
|
let width = this.virtualizer.visibleRect.width;
|
|
396
335
|
let rectHeight = this.rowHeight;
|
|
397
336
|
let isEstimated = false;
|
|
@@ -415,14 +354,8 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
415
354
|
rectHeight = DEFAULT_HEIGHT;
|
|
416
355
|
}
|
|
417
356
|
|
|
418
|
-
if (typeof this.indentationForItem === 'function') {
|
|
419
|
-
x += this.indentationForItem(this.collection, node.key) || 0;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
357
|
let rect = new Rect(x, y, width - x, rectHeight);
|
|
423
358
|
let layoutInfo = new LayoutInfo(node.type, node.key, rect);
|
|
424
|
-
// allow overflow so the focus ring/selection ring can extend outside to overlap with the adjacent items borders
|
|
425
|
-
layoutInfo.allowOverflow = true;
|
|
426
359
|
layoutInfo.estimatedSize = isEstimated;
|
|
427
360
|
return {
|
|
428
361
|
layoutInfo,
|
|
@@ -431,18 +364,19 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
431
364
|
}
|
|
432
365
|
|
|
433
366
|
updateItemSize(key: Key, size: Size) {
|
|
434
|
-
let
|
|
367
|
+
let layoutNode = this.layoutNodes.get(key);
|
|
435
368
|
// If no layoutInfo, item has been deleted/removed.
|
|
436
|
-
if (!
|
|
369
|
+
if (!layoutNode) {
|
|
437
370
|
return false;
|
|
438
371
|
}
|
|
439
372
|
|
|
373
|
+
let layoutInfo = layoutNode.layoutInfo;
|
|
440
374
|
layoutInfo.estimatedSize = false;
|
|
441
375
|
if (layoutInfo.rect.height !== size.height) {
|
|
442
376
|
// Copy layout info rather than mutating so that later caches are invalidated.
|
|
443
377
|
let newLayoutInfo = layoutInfo.copy();
|
|
444
378
|
newLayoutInfo.rect.height = size.height;
|
|
445
|
-
|
|
379
|
+
layoutNode.layoutInfo = newLayoutInfo;
|
|
446
380
|
|
|
447
381
|
// Items after this layoutInfo will need to be repositioned to account for the new height.
|
|
448
382
|
// Adjust the validRect so that only items above remain valid.
|
|
@@ -473,9 +407,7 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
473
407
|
n.validRect = n.validRect.intersection(this.validRect);
|
|
474
408
|
|
|
475
409
|
// Replace layout info in LayoutNode
|
|
476
|
-
if (n.
|
|
477
|
-
n.header = newLayoutInfo;
|
|
478
|
-
} else if (n.layoutInfo === oldLayoutInfo) {
|
|
410
|
+
if (n.layoutInfo === oldLayoutInfo) {
|
|
479
411
|
n.layoutInfo = newLayoutInfo;
|
|
480
412
|
}
|
|
481
413
|
}
|
|
@@ -519,4 +451,18 @@ export class ListLayout<T> extends Layout<Node<T>, ListLayoutProps> implements D
|
|
|
519
451
|
|
|
520
452
|
return target;
|
|
521
453
|
}
|
|
454
|
+
|
|
455
|
+
getDropTargetLayoutInfo(target: ItemDropTarget): LayoutInfo {
|
|
456
|
+
let layoutInfo = this.getLayoutInfo(target.key);
|
|
457
|
+
let rect: Rect;
|
|
458
|
+
if (target.dropPosition === 'before') {
|
|
459
|
+
rect = new Rect(layoutInfo.rect.x, layoutInfo.rect.y - this.dropIndicatorThickness / 2, layoutInfo.rect.width, this.dropIndicatorThickness);
|
|
460
|
+
} else if (target.dropPosition === 'after') {
|
|
461
|
+
rect = new Rect(layoutInfo.rect.x, layoutInfo.rect.maxY - this.dropIndicatorThickness / 2, layoutInfo.rect.width, this.dropIndicatorThickness);
|
|
462
|
+
} else {
|
|
463
|
+
rect = layoutInfo.rect;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return new LayoutInfo('dropIndicator', target.key + ':' + target.dropPosition, rect);
|
|
467
|
+
}
|
|
522
468
|
}
|