@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.
@@ -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 type ListLayoutOptions<T> = {
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
- padding?: number,
24
- indentationForItem?: (collection: Collection<Node<T>>, key: Key) => number,
25
- loaderHeight?: number,
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>, ListLayoutProps> implements DropTargetDelegate {
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 forceSectionHeaders: boolean;
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
- protected isLoading: boolean;
70
- protected lastWidth: number;
71
- protected lastCollection: Collection<Node<T>>;
58
+ private lastCollection: Collection<Node<T>>;
59
+ private lastWidth: number;
72
60
  protected rootNodes: LayoutNode[];
73
- protected invalidateEverything: boolean;
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<T> = {}) {
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.forceSectionHeaders = options.forceSectionHeaders;
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.layoutInfos.get(key)!;
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.layoutInfos.has(key) && this.requestedRect.area < this.contentSize.area && this.lastCollection) {
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
- private isVisible(node: LayoutNode, rect: Rect) {
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<ListLayoutProps>) {
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<ListLayoutProps>) {
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 = (this.rowHeight ?? this.estimatedRowHeight);
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
- if (this.isLoading) {
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 === (cached.header || cached.layoutInfo).rect.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
- private buildSection(node: Node<T>, x: number, y: number): LayoutNode {
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
- private buildSectionHeader(node: Node<T>, x: number, y: number): LayoutNode {
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?.header || previousLayoutNode?.layoutInfo;
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
- private buildItem(node: Node<T>, x: number, y: number): LayoutNode {
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 layoutInfo = this.layoutInfos.get(key);
367
+ let layoutNode = this.layoutNodes.get(key);
435
368
  // If no layoutInfo, item has been deleted/removed.
436
- if (!layoutInfo) {
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
- this.layoutInfos.set(key, newLayoutInfo);
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.header === oldLayoutInfo) {
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
  }