@react-stately/virtualizer 4.1.0 → 4.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-stately/virtualizer",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -22,14 +22,16 @@
22
22
  "url": "https://github.com/adobe/react-spectrum"
23
23
  },
24
24
  "dependencies": {
25
- "@react-aria/utils": "^3.25.3",
26
- "@react-types/shared": "^3.25.0",
25
+ "@react-aria/utils": "^3.27.0",
26
+ "@react-types/shared": "^3.27.0",
27
27
  "@swc/helpers": "^0.5.0"
28
28
  },
29
29
  "peerDependencies": {
30
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
30
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
31
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
31
32
  },
32
33
  "publishConfig": {
33
34
  "access": "public"
34
- }
35
- }
35
+ },
36
+ "gitHead": "09e7f44bebdc9d89122926b2b439a0a38a2814ea"
37
+ }
package/src/Layout.ts CHANGED
@@ -32,7 +32,7 @@ import {Virtualizer} from './Virtualizer';
32
32
  */
33
33
  export abstract class Layout<T extends object, O = any> implements LayoutDelegate {
34
34
  /** The Virtualizer the layout is currently attached to. */
35
- virtualizer: Virtualizer<T, any>;
35
+ virtualizer: Virtualizer<T, any> | null = null;
36
36
 
37
37
  /**
38
38
  * Returns whether the layout should invalidate in response to
@@ -83,11 +83,11 @@ export abstract class Layout<T extends object, O = any> implements LayoutDelegat
83
83
  */
84
84
  getDropTargetLayoutInfo?(target: ItemDropTarget): LayoutInfo;
85
85
 
86
- getItemRect(key: Key): Rect {
87
- return this.getLayoutInfo(key)?.rect;
86
+ getItemRect(key: Key): Rect | null {
87
+ return this.getLayoutInfo(key)?.rect ?? null;
88
88
  }
89
89
 
90
90
  getVisibleRect(): Rect {
91
- return this.virtualizer.visibleRect;
91
+ return this.virtualizer!.visibleRect;
92
92
  }
93
93
  }
@@ -28,23 +28,25 @@ export class ReusableView<T extends object, V> {
28
28
  layoutInfo: LayoutInfo | null;
29
29
 
30
30
  /** The content currently being displayed by this view, set by the virtualizer. */
31
- content: T;
31
+ content: T | null;
32
32
 
33
- rendered: V;
33
+ rendered: V | null;
34
34
 
35
35
  viewType: string;
36
36
  key: Key;
37
37
 
38
- parent: ReusableView<T, V> | null;
39
- children: Set<ReusableView<T, V>>;
40
- reusableViews: Map<string, ReusableView<T, V>[]>;
38
+ children: Set<ChildView<T, V>>;
39
+ reusableViews: Map<string, ChildView<T, V>[]>;
41
40
 
42
- constructor(virtualizer: Virtualizer<T, V>) {
41
+ constructor(virtualizer: Virtualizer<T, V>, viewType: string) {
43
42
  this.virtualizer = virtualizer;
44
43
  this.key = ++KEY;
45
- this.parent = null;
44
+ this.viewType = viewType;
46
45
  this.children = new Set();
47
46
  this.reusableViews = new Map();
47
+ this.layoutInfo = null;
48
+ this.content = null;
49
+ this.rendered = null;
48
50
  }
49
51
 
50
52
  /**
@@ -62,16 +64,14 @@ export class ReusableView<T extends object, V> {
62
64
  // The cells within a row are removed from their parent in order. If the row is reused, the cells
63
65
  // should be reused in the new row in the same order they were before.
64
66
  let reusable = this.reusableViews.get(reuseType);
65
- let view = reusable?.length > 0
66
- ? reusable.shift()
67
- : new ReusableView<T, V>(this.virtualizer);
67
+ let view = reusable && reusable.length > 0
68
+ ? reusable.shift()!
69
+ : new ChildView<T, V>(this.virtualizer, this, reuseType);
68
70
 
69
- view.viewType = reuseType;
70
- view.parent = this;
71
71
  return view;
72
72
  }
73
73
 
74
- reuseChild(child: ReusableView<T, V>) {
74
+ reuseChild(child: ChildView<T, V>) {
75
75
  child.prepareForReuse();
76
76
  let reusable = this.reusableViews.get(child.viewType);
77
77
  if (!reusable) {
@@ -81,3 +81,18 @@ export class ReusableView<T extends object, V> {
81
81
  reusable.push(child);
82
82
  }
83
83
  }
84
+
85
+ export class RootView<T extends object, V> extends ReusableView<T, V> {
86
+ constructor(virtualizer: Virtualizer<T, V>) {
87
+ super(virtualizer, 'root');
88
+ }
89
+ }
90
+
91
+ export class ChildView<T extends object, V> extends ReusableView<T, V> {
92
+ parent: ReusableView<T, V>;
93
+
94
+ constructor(virtualizer: Virtualizer<T, V>, parent: ReusableView<T, V>, viewType: string) {
95
+ super(virtualizer, viewType);
96
+ this.parent = parent;
97
+ }
98
+ }
@@ -10,6 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {ChildView, ReusableView, RootView} from './ReusableView';
13
14
  import {Collection, Key} from '@react-types/shared';
14
15
  import {InvalidationContext, Mutable, VirtualizerDelegate, VirtualizerRenderOptions} from './types';
15
16
  import {isSetEqual} from './utils';
@@ -18,9 +19,14 @@ import {LayoutInfo} from './LayoutInfo';
18
19
  import {OverscanManager} from './OverscanManager';
19
20
  import {Point} from './Point';
20
21
  import {Rect} from './Rect';
21
- import {ReusableView} from './ReusableView';
22
22
  import {Size} from './Size';
23
23
 
24
+ interface VirtualizerOptions<T extends object, V> {
25
+ delegate: VirtualizerDelegate<T, V>,
26
+ collection: Collection<T>,
27
+ layout: Layout<T>
28
+ }
29
+
24
30
  /**
25
31
  * The Virtualizer class renders a scrollable collection of data using customizable layouts.
26
32
  * It supports very large collections by only rendering visible views to the DOM, reusing
@@ -55,23 +61,25 @@ export class Virtualizer<T extends object, V> {
55
61
  /** The set of persisted keys that are always present in the DOM, even if not currently in view. */
56
62
  readonly persistedKeys: Set<Key>;
57
63
 
58
- private _visibleViews: Map<Key, ReusableView<T, V>>;
64
+ private _visibleViews: Map<Key, ChildView<T, V>>;
59
65
  private _renderedContent: WeakMap<T, V>;
60
- private _rootView: ReusableView<T, V>;
66
+ private _rootView: RootView<T, V>;
61
67
  private _isScrolling: boolean;
62
- private _invalidationContext: InvalidationContext | null;
68
+ private _invalidationContext: InvalidationContext;
63
69
  private _overscanManager: OverscanManager;
64
70
 
65
- constructor(delegate: VirtualizerDelegate<T, V>) {
66
- this.delegate = delegate;
71
+ constructor(options: VirtualizerOptions<T, V>) {
72
+ this.delegate = options.delegate;
73
+ this.collection = options.collection;
74
+ this.layout = options.layout;
67
75
  this.contentSize = new Size;
68
76
  this.visibleRect = new Rect;
69
77
  this.persistedKeys = new Set();
70
78
  this._visibleViews = new Map();
71
79
  this._renderedContent = new WeakMap();
72
- this._rootView = new ReusableView(this);
80
+ this._rootView = new RootView(this);
73
81
  this._isScrolling = false;
74
- this._invalidationContext = null;
82
+ this._invalidationContext = {};
75
83
  this._overscanManager = new OverscanManager();
76
84
  }
77
85
 
@@ -86,7 +94,7 @@ export class Virtualizer<T extends object, V> {
86
94
  for (let k of this.persistedKeys) {
87
95
  while (k != null) {
88
96
  let layoutInfo = this.layout.getLayoutInfo(k);
89
- if (!layoutInfo) {
97
+ if (!layoutInfo || layoutInfo.parentKey == null) {
90
98
  break;
91
99
  }
92
100
 
@@ -105,7 +113,7 @@ export class Virtualizer<T extends object, V> {
105
113
  return layoutInfo.parentKey != null ? this._visibleViews.get(layoutInfo.parentKey) : this._rootView;
106
114
  }
107
115
 
108
- private getReusableView(layoutInfo: LayoutInfo): ReusableView<T, V> {
116
+ private getReusableView(layoutInfo: LayoutInfo): ChildView<T, V> {
109
117
  let parentView = this.getParentView(layoutInfo)!;
110
118
  let view = parentView.getReusableView(layoutInfo.type);
111
119
  view.layoutInfo = layoutInfo;
@@ -114,13 +122,15 @@ export class Virtualizer<T extends object, V> {
114
122
  }
115
123
 
116
124
  private _renderView(reusableView: ReusableView<T, V>) {
117
- let {type, key, content} = reusableView.layoutInfo;
118
- reusableView.content = content || this.collection.getItem(key);
119
- reusableView.rendered = this._renderContent(type, reusableView.content);
125
+ if (reusableView.layoutInfo) {
126
+ let {type, key, content} = reusableView.layoutInfo;
127
+ reusableView.content = content || this.collection.getItem(key);
128
+ reusableView.rendered = this._renderContent(type, reusableView.content);
129
+ }
120
130
  }
121
131
 
122
- private _renderContent(type: string, content: T) {
123
- let cached = this._renderedContent.get(content);
132
+ private _renderContent(type: string, content: T | null) {
133
+ let cached = content != null ? this._renderedContent.get(content) : null;
124
134
  if (cached != null) {
125
135
  return cached;
126
136
  }
@@ -196,7 +206,7 @@ export class Virtualizer<T extends object, V> {
196
206
  private updateSubviews() {
197
207
  let visibleLayoutInfos = this.getVisibleLayoutInfos();
198
208
 
199
- let removed = new Set<ReusableView<T, V>>();
209
+ let removed = new Set<ChildView<T, V>>();
200
210
  for (let [key, view] of this._visibleViews) {
201
211
  let layoutInfo = visibleLayoutInfos.get(key);
202
212
  // If a view's parent changed, treat it as a delete and re-create in the new parent.
@@ -219,7 +229,9 @@ export class Virtualizer<T extends object, V> {
219
229
 
220
230
  let item = this.collection.getItem(layoutInfo.key);
221
231
  if (view.content !== item) {
222
- this._renderedContent.delete(view.content);
232
+ if (view.content != null) {
233
+ this._renderedContent.delete(view.content);
234
+ }
223
235
  this._renderView(view);
224
236
  }
225
237
  }
@@ -261,7 +273,7 @@ export class Virtualizer<T extends object, V> {
261
273
  needsLayout = true;
262
274
  }
263
275
 
264
- if (opts.layout !== this.layout) {
276
+ if (opts.layout !== this.layout || this.layout.virtualizer !== this) {
265
277
  if (this.layout) {
266
278
  this.layout.virtualizer = null;
267
279
  }
package/src/types.ts CHANGED
@@ -24,14 +24,14 @@ export interface InvalidationContext<O = any> {
24
24
 
25
25
  export interface VirtualizerDelegate<T extends object, V> {
26
26
  setVisibleRect(rect: Rect): void,
27
- renderView(type: string, content: T): V,
27
+ renderView(type: string, content: T | null): V,
28
28
  invalidate(ctx: InvalidationContext): void
29
29
  }
30
30
 
31
31
  export interface VirtualizerRenderOptions<T extends object, O = any> {
32
32
  layout: Layout<T>,
33
33
  collection: Collection<T>,
34
- persistedKeys?: Set<Key>,
34
+ persistedKeys?: Set<Key> | null,
35
35
  visibleRect: Rect,
36
36
  invalidationContext: InvalidationContext,
37
37
  isScrolling: boolean,
@@ -21,7 +21,7 @@ import {useLayoutEffect} from '@react-aria/utils';
21
21
  import {Virtualizer} from './Virtualizer';
22
22
 
23
23
  interface VirtualizerProps<T extends object, V, O> {
24
- renderView(type: string, content: T): V,
24
+ renderView(type: string, content: T | null): V,
25
25
  layout: Layout<T>,
26
26
  collection: Collection<T>,
27
27
  onVisibleRectChange(rect: Rect): void,
@@ -45,13 +45,17 @@ export function useVirtualizerState<T extends object, V, O = any>(opts: Virtuali
45
45
  let [invalidationContext, setInvalidationContext] = useState<InvalidationContext>({});
46
46
  let visibleRectChanged = useRef(false);
47
47
  let [virtualizer] = useState(() => new Virtualizer<T, V>({
48
- setVisibleRect(rect) {
49
- setVisibleRect(rect);
50
- visibleRectChanged.current = true;
51
- },
52
- // TODO: should changing these invalidate the entire cache?
53
- renderView: opts.renderView,
54
- invalidate: setInvalidationContext
48
+ collection: opts.collection,
49
+ layout: opts.layout,
50
+ delegate: {
51
+ setVisibleRect(rect) {
52
+ setVisibleRect(rect);
53
+ visibleRectChanged.current = true;
54
+ },
55
+ // TODO: should changing these invalidate the entire cache?
56
+ renderView: opts.renderView,
57
+ invalidate: setInvalidationContext
58
+ }
55
59
  }));
56
60
 
57
61
  // onVisibleRectChange must be called from an effect, not during render.