@react-stately/virtualizer
Advanced tools
Comparing version
@@ -28,14 +28,25 @@ | ||
/** | ||
* Returns whether the layout should invalidate when the layout options change. | ||
* By default it invalidates when the object identity changes. Override this | ||
* method to optimize layout updates based on specific option changes. | ||
*/ shouldInvalidateLayoutOptions(newOptions, oldOptions) { | ||
return newOptions !== oldOptions; | ||
} | ||
/** | ||
* This method allows the layout to perform any pre-computation | ||
* it needs to in order to prepare {@link LayoutInfo}s for retrieval. | ||
* Called by the virtualizer before {@link getVisibleLayoutInfos} | ||
* or {@link getLayoutInfo} are called. | ||
* it needs to in order to prepare LayoutInfos for retrieval. | ||
* Called by the virtualizer before `getVisibleLayoutInfos` | ||
* or `getLayoutInfo` are called. | ||
*/ update(invalidationContext) {} | ||
getItemRect(key) { | ||
/** @private */ getItemRect(key) { | ||
var _this_getLayoutInfo; | ||
return (_this_getLayoutInfo = this.getLayoutInfo(key)) === null || _this_getLayoutInfo === void 0 ? void 0 : _this_getLayoutInfo.rect; | ||
var _this_getLayoutInfo_rect; | ||
return (_this_getLayoutInfo_rect = (_this_getLayoutInfo = this.getLayoutInfo(key)) === null || _this_getLayoutInfo === void 0 ? void 0 : _this_getLayoutInfo.rect) !== null && _this_getLayoutInfo_rect !== void 0 ? _this_getLayoutInfo_rect : null; | ||
} | ||
getVisibleRect() { | ||
/** @private */ getVisibleRect() { | ||
return this.virtualizer.visibleRect; | ||
} | ||
constructor(){ | ||
/** The Virtualizer the layout is currently attached to. */ this.virtualizer = null; | ||
} | ||
} | ||
@@ -42,0 +53,0 @@ |
@@ -22,14 +22,25 @@ /* | ||
/** | ||
* Returns whether the layout should invalidate when the layout options change. | ||
* By default it invalidates when the object identity changes. Override this | ||
* method to optimize layout updates based on specific option changes. | ||
*/ shouldInvalidateLayoutOptions(newOptions, oldOptions) { | ||
return newOptions !== oldOptions; | ||
} | ||
/** | ||
* This method allows the layout to perform any pre-computation | ||
* it needs to in order to prepare {@link LayoutInfo}s for retrieval. | ||
* Called by the virtualizer before {@link getVisibleLayoutInfos} | ||
* or {@link getLayoutInfo} are called. | ||
* it needs to in order to prepare LayoutInfos for retrieval. | ||
* Called by the virtualizer before `getVisibleLayoutInfos` | ||
* or `getLayoutInfo` are called. | ||
*/ update(invalidationContext) {} | ||
getItemRect(key) { | ||
/** @private */ getItemRect(key) { | ||
var _this_getLayoutInfo; | ||
return (_this_getLayoutInfo = this.getLayoutInfo(key)) === null || _this_getLayoutInfo === void 0 ? void 0 : _this_getLayoutInfo.rect; | ||
var _this_getLayoutInfo_rect; | ||
return (_this_getLayoutInfo_rect = (_this_getLayoutInfo = this.getLayoutInfo(key)) === null || _this_getLayoutInfo === void 0 ? void 0 : _this_getLayoutInfo.rect) !== null && _this_getLayoutInfo_rect !== void 0 ? _this_getLayoutInfo_rect : null; | ||
} | ||
getVisibleRect() { | ||
/** @private */ getVisibleRect() { | ||
return this.virtualizer.visibleRect; | ||
} | ||
constructor(){ | ||
/** The Virtualizer the layout is currently attached to. */ this.virtualizer = null; | ||
} | ||
} | ||
@@ -36,0 +47,0 @@ |
@@ -33,6 +33,5 @@ | ||
/** | ||
* @param type A string representing the view type. Should be `'item'` for item views. | ||
Other types are used by supplementary views. | ||
* @param key The unique key for this view. | ||
* @param rect The rectangle describing the size and position of this view. | ||
* @param type The type of element represented by this LayoutInfo. Should match the `type` of the corresponding collection node. | ||
* @param key A unique key for this LayoutInfo. Should match the `key` of the corresponding collection node. | ||
* @param rect The rectangle describing the size and position of this element. | ||
*/ constructor(type, key, rect){ | ||
@@ -39,0 +38,0 @@ this.type = type; |
@@ -27,6 +27,5 @@ /* | ||
/** | ||
* @param type A string representing the view type. Should be `'item'` for item views. | ||
Other types are used by supplementary views. | ||
* @param key The unique key for this view. | ||
* @param rect The rectangle describing the size and position of this view. | ||
* @param type The type of element represented by this LayoutInfo. Should match the `type` of the corresponding collection node. | ||
* @param key A unique key for this LayoutInfo. Should match the `key` of the corresponding collection node. | ||
* @param rect The rectangle describing the size and position of this element. | ||
*/ constructor(type, key, rect){ | ||
@@ -33,0 +32,0 @@ this.type = type; |
@@ -7,2 +7,3 @@ | ||
$parcel$export(module.exports, "ReusableView", () => $197a1781bd47f5b9$export$1a5223887c560441); | ||
$parcel$export(module.exports, "RootView", () => $197a1781bd47f5b9$export$e21886a4eef6b29a); | ||
/* | ||
@@ -33,5 +34,3 @@ * Copyright 2020 Adobe. All rights reserved. | ||
let reusable = this.reusableViews.get(reuseType); | ||
let view = (reusable === null || reusable === void 0 ? void 0 : reusable.length) > 0 ? reusable.shift() : new $197a1781bd47f5b9$export$1a5223887c560441(this.virtualizer); | ||
view.viewType = reuseType; | ||
view.parent = this; | ||
let view = reusable && reusable.length > 0 ? reusable.shift() : new $197a1781bd47f5b9$export$7a41b6f219e61634(this.virtualizer, this, reuseType); | ||
return view; | ||
@@ -48,12 +47,26 @@ } | ||
} | ||
constructor(virtualizer){ | ||
constructor(virtualizer, viewType){ | ||
this.virtualizer = virtualizer; | ||
this.key = ++$197a1781bd47f5b9$var$KEY; | ||
this.parent = null; | ||
this.viewType = viewType; | ||
this.children = new Set(); | ||
this.reusableViews = new Map(); | ||
this.layoutInfo = null; | ||
this.content = null; | ||
this.rendered = null; | ||
} | ||
} | ||
class $197a1781bd47f5b9$export$e21886a4eef6b29a extends $197a1781bd47f5b9$export$1a5223887c560441 { | ||
constructor(virtualizer){ | ||
super(virtualizer, 'root'); | ||
} | ||
} | ||
class $197a1781bd47f5b9$export$7a41b6f219e61634 extends $197a1781bd47f5b9$export$1a5223887c560441 { | ||
constructor(virtualizer, parent, viewType){ | ||
super(virtualizer, viewType); | ||
this.parent = parent; | ||
} | ||
} | ||
//# sourceMappingURL=ReusableView.main.js.map |
@@ -26,5 +26,3 @@ /* | ||
let reusable = this.reusableViews.get(reuseType); | ||
let view = (reusable === null || reusable === void 0 ? void 0 : reusable.length) > 0 ? reusable.shift() : new $ad1d98aa8f0c31b4$export$1a5223887c560441(this.virtualizer); | ||
view.viewType = reuseType; | ||
view.parent = this; | ||
let view = reusable && reusable.length > 0 ? reusable.shift() : new $ad1d98aa8f0c31b4$export$7a41b6f219e61634(this.virtualizer, this, reuseType); | ||
return view; | ||
@@ -41,13 +39,27 @@ } | ||
} | ||
constructor(virtualizer){ | ||
constructor(virtualizer, viewType){ | ||
this.virtualizer = virtualizer; | ||
this.key = ++$ad1d98aa8f0c31b4$var$KEY; | ||
this.parent = null; | ||
this.viewType = viewType; | ||
this.children = new Set(); | ||
this.reusableViews = new Map(); | ||
this.layoutInfo = null; | ||
this.content = null; | ||
this.rendered = null; | ||
} | ||
} | ||
class $ad1d98aa8f0c31b4$export$e21886a4eef6b29a extends $ad1d98aa8f0c31b4$export$1a5223887c560441 { | ||
constructor(virtualizer){ | ||
super(virtualizer, 'root'); | ||
} | ||
} | ||
class $ad1d98aa8f0c31b4$export$7a41b6f219e61634 extends $ad1d98aa8f0c31b4$export$1a5223887c560441 { | ||
constructor(virtualizer, parent, viewType){ | ||
super(virtualizer, viewType); | ||
this.parent = parent; | ||
} | ||
} | ||
export {$ad1d98aa8f0c31b4$export$1a5223887c560441 as ReusableView}; | ||
export {$ad1d98aa8f0c31b4$export$1a5223887c560441 as ReusableView, $ad1d98aa8f0c31b4$export$7a41b6f219e61634 as ChildView, $ad1d98aa8f0c31b4$export$e21886a4eef6b29a as RootView}; | ||
//# sourceMappingURL=ReusableView.module.js.map |
@@ -1,2 +0,2 @@ | ||
import { Key, Collection, ItemDropTarget, LayoutDelegate } from "@react-types/shared"; | ||
import { Key, Collection, ItemDropTarget, LayoutDelegate, Node } from "@react-types/shared"; | ||
export class Point { | ||
@@ -119,27 +119,26 @@ /** The x-coordinate of the point. */ | ||
/** | ||
* Instances of this lightweight class are created by {@link Layout} subclasses | ||
* to represent each view in the {@link Virtualizer}. LayoutInfo objects describe | ||
* various properties of a view, such as its position and size, and style information. | ||
* The virtualizer uses this information when creating actual views to display. | ||
* Instances of this lightweight class are created by `Layout` subclasses | ||
* to represent each item in the `Virtualizer`. LayoutInfo objects describe | ||
* various properties of an item, such as its position and size, and style information. | ||
* The virtualizer uses this information when creating actual DOM elements to display. | ||
*/ | ||
export class LayoutInfo { | ||
/** | ||
* A string representing the view type. Should be `'item'` for item views. | ||
* Other types are used by supplementary views. | ||
* The type of element represented by this LayoutInfo. Should match the `type` of the corresponding collection node. | ||
*/ | ||
type: string; | ||
/** | ||
* A unique key for this view. For item views, it should match the content key. | ||
* A unique key for this LayoutInfo. Should match the `key` of the corresponding collection node. | ||
*/ | ||
key: Key; | ||
/** | ||
* The key for a parent layout info, if any. | ||
* The key for a parent LayoutInfo, if any. | ||
*/ | ||
parentKey: Key | null; | ||
/** | ||
* Content for this view if it was generated by the layout rather than coming from the Collection. | ||
* Content for this item if it was generated by the layout rather than coming from the Collection. | ||
*/ | ||
content: any | null; | ||
/** | ||
* The rectangle describing the size and position of this view. | ||
* The rectangle describing the size and position of this element. | ||
*/ | ||
@@ -149,2 +148,5 @@ rect: Rect; | ||
* Whether the size is estimated. `false` by default. | ||
* Items with estimated sizes will be measured the first time they are added to the DOM. | ||
* The estimated size is used to calculate the size and position of the scrollbar. | ||
* @default false | ||
*/ | ||
@@ -154,18 +156,20 @@ estimatedSize: boolean; | ||
* Whether the layout info sticks to the viewport when scrolling. | ||
* @default false | ||
*/ | ||
isSticky: boolean; | ||
/** | ||
* The view's opacity. 1 by default. | ||
* The element's opacity. | ||
* @default 1 | ||
*/ | ||
opacity: number; | ||
/** | ||
* A CSS transform string to apply to the view. `null` by default. | ||
* A CSS transform string to apply to the element. `null` by default. | ||
*/ | ||
transform: string | null; | ||
/** | ||
* The z-index of the view. 0 by default. | ||
* The z-index of the element. 0 by default. | ||
*/ | ||
zIndex: number; | ||
/** | ||
* Whether the layout info allows its contents to overflow its container. | ||
* Whether the element allows its contents to overflow its container. | ||
* @default false | ||
@@ -175,6 +179,5 @@ */ | ||
/** | ||
* @param type A string representing the view type. Should be `'item'` for item views. | ||
Other types are used by supplementary views. | ||
* @param key The unique key for this view. | ||
* @param rect The rectangle describing the size and position of this view. | ||
* @param type The type of element represented by this LayoutInfo. Should match the `type` of the corresponding collection node. | ||
* @param key A unique key for this LayoutInfo. Should match the `key` of the corresponding collection node. | ||
* @param rect The rectangle describing the size and position of this element. | ||
*/ | ||
@@ -188,3 +191,3 @@ constructor(type: string, key: Key, rect: Rect); | ||
/** | ||
* [Virtualizer]{@link Virtualizer} creates instances of the [ReusableView]{@link ReusableView} class to | ||
* `Virtualizer` creates instances of the `ReusableView` class to | ||
* represent views currently being displayed. | ||
@@ -198,10 +201,9 @@ */ | ||
/** The content currently being displayed by this view, set by the virtualizer. */ | ||
content: T; | ||
rendered: V; | ||
content: T | null; | ||
rendered: V | null; | ||
viewType: string; | ||
key: Key; | ||
parent: ReusableView<T, V> | null; | ||
children: Set<ReusableView<T, V>>; | ||
reusableViews: Map<string, ReusableView<T, V>[]>; | ||
constructor(virtualizer: Virtualizer<T, V>); | ||
children: Set<ChildView<T, V>>; | ||
reusableViews: Map<string, ChildView<T, V>[]>; | ||
constructor(virtualizer: Virtualizer<T, V>, viewType: string); | ||
/** | ||
@@ -211,5 +213,14 @@ * Prepares the view for reuse. Called just before the view is removed from the DOM. | ||
prepareForReuse(): void; | ||
getReusableView(reuseType: string): ReusableView<T, V>; | ||
reuseChild(child: ReusableView<T, V>): void; | ||
getReusableView(reuseType: string): ChildView<T, V>; | ||
reuseChild(child: ChildView<T, V>): void; | ||
} | ||
declare class ChildView<T extends object, V> extends ReusableView<T, V> { | ||
parent: ReusableView<T, V>; | ||
constructor(virtualizer: Virtualizer<T, V>, parent: ReusableView<T, V>, viewType: string); | ||
} | ||
interface VirtualizerOptions<T extends object, V> { | ||
delegate: VirtualizerDelegate<T, V>; | ||
collection: Collection<T>; | ||
layout: Layout<T>; | ||
} | ||
/** | ||
@@ -221,3 +232,3 @@ * The Virtualizer class renders a scrollable collection of data using customizable layouts. | ||
* | ||
* Virtualizer uses {@link Layout} objects to compute what views should be visible, and how | ||
* Virtualizer uses `Layout` objects to compute what views should be visible, and how | ||
* to position and style them. This means that virtualizer can have its items arranged in | ||
@@ -228,4 +239,4 @@ * a stack, a grid, a circle, or any other layout you can think of. The layout can be changed | ||
* Layouts produce information on what views should appear in the virtualizer, but do not create | ||
* the views themselves directly. It is the responsibility of the {@link VirtualizerDelegate} object | ||
* to render elements for each layout info. The virtualizer manages a set of {@link ReusableView} objects, | ||
* the views themselves directly. It is the responsibility of the `VirtualizerDelegate` object | ||
* to render elements for each layout info. The virtualizer manages a set of `ReusableView` objects, | ||
* which are reused as the user scrolls by swapping their content with cached elements returned by the delegate. | ||
@@ -249,3 +260,3 @@ */ | ||
readonly persistedKeys: Set<Key>; | ||
constructor(delegate: VirtualizerDelegate<T, V>); | ||
constructor(options: VirtualizerOptions<T, V>); | ||
/** Returns whether the given key, or an ancestor, is persisted. */ | ||
@@ -257,3 +268,3 @@ isPersistedKey(key: Key): boolean; | ||
keyAtPoint(point: Point): Key | null; | ||
getVisibleLayoutInfos(): Map<any, any>; | ||
getVisibleLayoutInfos(): Map<Key, LayoutInfo>; | ||
/** Performs layout and updates visible views as needed. */ | ||
@@ -266,33 +277,16 @@ render(opts: VirtualizerRenderOptions<T>): ReusableView<T, V>[]; | ||
/** | ||
* [Virtualizer]{@link Virtualizer} supports arbitrary layout objects, which compute what views are visible, and how | ||
* to position and style them. However, layouts do not create the views themselves directly. Instead, | ||
* layouts produce lightweight {@link LayoutInfo} objects which describe various properties of a view, | ||
* such as its position and size. The {@link Virtualizer} is then responsible for creating the actual | ||
* Virtualizer supports arbitrary layout objects, which compute what items are visible, and how | ||
* to position and style them. However, layouts do not render items directly. Instead, | ||
* layouts produce lightweight LayoutInfo objects which describe various properties of an item, | ||
* such as its position and size. The Virtualizer is then responsible for creating the actual | ||
* views as needed, based on this layout information. | ||
* | ||
* Every layout extends from the {@link Layout} abstract base class. Layouts must implement a minimum of the | ||
* two methods listed below. All other methods can be optionally overridden to implement custom behavior. | ||
* | ||
* @see {@link getVisibleLayoutInfos} | ||
* @see {@link getLayoutInfo} | ||
* Every layout extends from the Layout abstract base class. Layouts must implement the `getVisibleLayoutInfos`, | ||
* `getLayoutInfo`, and `getContentSize` methods. All other methods can be optionally overridden to implement custom behavior. | ||
*/ | ||
export abstract class Layout<T extends object, O = any> implements LayoutDelegate { | ||
export abstract class Layout<T extends object = Node<any>, O = any> implements LayoutDelegate { | ||
/** The Virtualizer the layout is currently attached to. */ | ||
virtualizer: Virtualizer<T, any>; | ||
virtualizer: Virtualizer<T, any> | null; | ||
/** | ||
* Returns whether the layout should invalidate in response to | ||
* visible rectangle changes. By default, it only invalidates | ||
* when the virtualizer's size changes. Return true always | ||
* to make the layout invalidate while scrolling (e.g. sticky headers). | ||
*/ | ||
shouldInvalidate(newRect: Rect, oldRect: Rect): boolean; | ||
/** | ||
* This method allows the layout to perform any pre-computation | ||
* it needs to in order to prepare {@link LayoutInfo}s for retrieval. | ||
* Called by the virtualizer before {@link getVisibleLayoutInfos} | ||
* or {@link getLayoutInfo} are called. | ||
*/ | ||
update(invalidationContext: InvalidationContext<O>): void; | ||
/** | ||
* Returns an array of {@link LayoutInfo} objects which are inside the given rectangle. | ||
* Returns an array of `LayoutInfo` objects which are inside the given rectangle. | ||
* Should be implemented by subclasses. | ||
@@ -303,3 +297,3 @@ * @param rect The rectangle that should contain the returned LayoutInfo objects. | ||
/** | ||
* Returns a {@link LayoutInfo} for the given key. | ||
* Returns a `LayoutInfo` for the given key. | ||
* Should be implemented by subclasses. | ||
@@ -310,6 +304,26 @@ * @param key The key of the LayoutInfo to retrieve. | ||
/** | ||
* Returns size of the content. By default, it returns collectionView's size. | ||
* Returns size of the content. By default, it returns virtualizer's size. | ||
*/ | ||
abstract getContentSize(): Size; | ||
/** | ||
* Returns whether the layout should invalidate in response to | ||
* visible rectangle changes. By default, it only invalidates | ||
* when the virtualizer's size changes. Return true always | ||
* to make the layout invalidate while scrolling (e.g. sticky headers). | ||
*/ | ||
shouldInvalidate(newRect: Rect, oldRect: Rect): boolean; | ||
/** | ||
* Returns whether the layout should invalidate when the layout options change. | ||
* By default it invalidates when the object identity changes. Override this | ||
* method to optimize layout updates based on specific option changes. | ||
*/ | ||
shouldInvalidateLayoutOptions(newOptions: O, oldOptions: O): boolean; | ||
/** | ||
* This method allows the layout to perform any pre-computation | ||
* it needs to in order to prepare LayoutInfos for retrieval. | ||
* Called by the virtualizer before `getVisibleLayoutInfos` | ||
* or `getLayoutInfo` are called. | ||
*/ | ||
update(invalidationContext: InvalidationContext<O>): void; | ||
/** | ||
* Updates the size of the given item. | ||
@@ -319,6 +333,8 @@ */ | ||
/** | ||
* Returns a LayoutInfo for the given drop target. | ||
* Returns a `LayoutInfo` for the given drop target. | ||
*/ | ||
getDropTargetLayoutInfo?(target: ItemDropTarget): LayoutInfo; | ||
getItemRect(key: Key): Rect; | ||
/** @private */ | ||
getItemRect(key: Key): Rect | null; | ||
/** @private */ | ||
getVisibleRect(): Rect; | ||
@@ -331,2 +347,3 @@ } | ||
itemSizeChanged?: boolean; | ||
layoutOptionsChanged?: boolean; | ||
layoutOptions?: O; | ||
@@ -336,3 +353,3 @@ } | ||
setVisibleRect(rect: Rect): void; | ||
renderView(type: string, content: T): V; | ||
renderView(type: string, content: T | null): V; | ||
invalidate(ctx: InvalidationContext): void; | ||
@@ -343,3 +360,3 @@ } | ||
collection: Collection<T>; | ||
persistedKeys?: Set<Key>; | ||
persistedKeys?: Set<Key> | null; | ||
visibleRect: Rect; | ||
@@ -351,3 +368,3 @@ invalidationContext: InvalidationContext; | ||
interface VirtualizerProps<T extends object, V, O> { | ||
renderView(type: string, content: T): V; | ||
renderView(type: string, content: T | null): V; | ||
layout: Layout<T>; | ||
@@ -354,0 +371,0 @@ collection: Collection<T>; |
@@ -32,9 +32,13 @@ var $41b7691783731623$exports = require("./Rect.main.js"); | ||
let [virtualizer] = (0, $amfZP$react.useState)(()=>new (0, $e1bc15d49d21df0e$exports.Virtualizer)({ | ||
setVisibleRect (rect) { | ||
setVisibleRect(rect); | ||
visibleRectChanged.current = true; | ||
}, | ||
// TODO: should changing these invalidate the entire cache? | ||
renderView: opts.renderView, | ||
invalidate: setInvalidationContext | ||
collection: opts.collection, | ||
layout: opts.layout, | ||
delegate: { | ||
setVisibleRect (rect) { | ||
setVisibleRect(rect); | ||
visibleRectChanged.current = true; | ||
}, | ||
// TODO: should changing these invalidate the entire cache? | ||
renderView: opts.renderView, | ||
invalidate: setInvalidationContext | ||
} | ||
})); | ||
@@ -41,0 +45,0 @@ // onVisibleRectChange must be called from an effect, not during render. |
@@ -26,9 +26,13 @@ import {Rect as $60423f92c7f9ad87$export$c79fc6492f3af13d} from "./Rect.module.js"; | ||
let [virtualizer] = (0, $3Fik3$useState)(()=>new (0, $38b9490c1cca8fc4$export$89be5a243e59c4b2)({ | ||
setVisibleRect (rect) { | ||
setVisibleRect(rect); | ||
visibleRectChanged.current = true; | ||
}, | ||
// TODO: should changing these invalidate the entire cache? | ||
renderView: opts.renderView, | ||
invalidate: setInvalidationContext | ||
collection: opts.collection, | ||
layout: opts.layout, | ||
delegate: { | ||
setVisibleRect (rect) { | ||
setVisibleRect(rect); | ||
visibleRectChanged.current = true; | ||
}, | ||
// TODO: should changing these invalidate the entire cache? | ||
renderView: opts.renderView, | ||
invalidate: setInvalidationContext | ||
} | ||
})); | ||
@@ -35,0 +39,0 @@ // onVisibleRectChange must be called from an effect, not during render. |
@@ -0,5 +1,5 @@ | ||
var $197a1781bd47f5b9$exports = require("./ReusableView.main.js"); | ||
var $abed55ea619a7a17$exports = require("./utils.main.js"); | ||
var $191a033606d4fda1$exports = require("./OverscanManager.main.js"); | ||
var $41b7691783731623$exports = require("./Rect.main.js"); | ||
var $197a1781bd47f5b9$exports = require("./ReusableView.main.js"); | ||
var $064492b79924894c$exports = require("./Size.main.js"); | ||
@@ -35,3 +35,3 @@ | ||
let layoutInfo = this.layout.getLayoutInfo(k); | ||
if (!layoutInfo) break; | ||
if (!layoutInfo || layoutInfo.parentKey == null) break; | ||
k = layoutInfo.parentKey; | ||
@@ -53,8 +53,10 @@ if (k === key) return true; | ||
_renderView(reusableView) { | ||
let { type: type, key: key, content: content } = reusableView.layoutInfo; | ||
reusableView.content = content || this.collection.getItem(key); | ||
reusableView.rendered = this._renderContent(type, reusableView.content); | ||
if (reusableView.layoutInfo) { | ||
let { type: type, key: key, content: content } = reusableView.layoutInfo; | ||
reusableView.content = content || this.collection.getItem(key); | ||
reusableView.rendered = this._renderContent(type, reusableView.content); | ||
} | ||
} | ||
_renderContent(type, content) { | ||
let cached = this._renderedContent.get(content); | ||
let cached = content != null ? this._renderedContent.get(content) : null; | ||
if (cached != null) return cached; | ||
@@ -129,3 +131,3 @@ let rendered = this.delegate.renderView(type, content); | ||
if (view.content !== item) { | ||
this._renderedContent.delete(view.content); | ||
if (view.content != null) this._renderedContent.delete(view.content); | ||
this._renderView(view); | ||
@@ -159,2 +161,3 @@ } | ||
let itemSizeChanged = false; | ||
let layoutOptionsChanged = false; | ||
let needsUpdate = false; | ||
@@ -165,3 +168,3 @@ if (opts.collection !== this.collection) { | ||
} | ||
if (opts.layout !== this.layout) { | ||
if (opts.layout !== this.layout || this.layout.virtualizer !== this) { | ||
if (this.layout) this.layout.virtualizer = null; | ||
@@ -191,4 +194,4 @@ opts.layout.virtualizer = this; | ||
itemSizeChanged || (itemSizeChanged = opts.invalidationContext.itemSizeChanged || false); | ||
needsLayout || (needsLayout = itemSizeChanged || sizeChanged || offsetChanged); | ||
needsLayout || (needsLayout = opts.invalidationContext.layoutOptions !== this._invalidationContext.layoutOptions); | ||
layoutOptionsChanged || (layoutOptionsChanged = opts.invalidationContext.layoutOptions != null && this._invalidationContext.layoutOptions != null && opts.invalidationContext.layoutOptions !== this._invalidationContext.layoutOptions && this.layout.shouldInvalidateLayoutOptions(opts.invalidationContext.layoutOptions, this._invalidationContext.layoutOptions)); | ||
needsLayout || (needsLayout = itemSizeChanged || sizeChanged || offsetChanged || layoutOptionsChanged); | ||
} | ||
@@ -206,2 +209,3 @@ this._invalidationContext = opts.invalidationContext; | ||
itemSizeChanged: itemSizeChanged, | ||
layoutOptionsChanged: layoutOptionsChanged, | ||
layoutOptions: this._invalidationContext.layoutOptions | ||
@@ -225,4 +229,6 @@ }); | ||
} | ||
constructor(delegate){ | ||
this.delegate = delegate; | ||
constructor(options){ | ||
this.delegate = options.delegate; | ||
this.collection = options.collection; | ||
this.layout = options.layout; | ||
this.contentSize = new (0, $064492b79924894c$exports.Size); | ||
@@ -233,5 +239,5 @@ this.visibleRect = new (0, $41b7691783731623$exports.Rect); | ||
this._renderedContent = new WeakMap(); | ||
this._rootView = new (0, $197a1781bd47f5b9$exports.ReusableView)(this); | ||
this._rootView = new (0, $197a1781bd47f5b9$exports.RootView)(this); | ||
this._isScrolling = false; | ||
this._invalidationContext = null; | ||
this._invalidationContext = {}; | ||
this._overscanManager = new (0, $191a033606d4fda1$exports.OverscanManager)(); | ||
@@ -238,0 +244,0 @@ } |
@@ -0,5 +1,5 @@ | ||
import {RootView as $ad1d98aa8f0c31b4$export$e21886a4eef6b29a} from "./ReusableView.module.js"; | ||
import {isSetEqual as $fc36f9a046a9ce79$export$a8d0d0c8d1c5df64} from "./utils.module.js"; | ||
import {OverscanManager as $364191b3decf3697$export$4455ee6afb38dcbb} from "./OverscanManager.module.js"; | ||
import {Rect as $60423f92c7f9ad87$export$c79fc6492f3af13d} from "./Rect.module.js"; | ||
import {ReusableView as $ad1d98aa8f0c31b4$export$1a5223887c560441} from "./ReusableView.module.js"; | ||
import {Size as $ee1bfa90a957fb8a$export$cb6da89c6af1a8ec} from "./Size.module.js"; | ||
@@ -29,3 +29,3 @@ | ||
let layoutInfo = this.layout.getLayoutInfo(k); | ||
if (!layoutInfo) break; | ||
if (!layoutInfo || layoutInfo.parentKey == null) break; | ||
k = layoutInfo.parentKey; | ||
@@ -47,8 +47,10 @@ if (k === key) return true; | ||
_renderView(reusableView) { | ||
let { type: type, key: key, content: content } = reusableView.layoutInfo; | ||
reusableView.content = content || this.collection.getItem(key); | ||
reusableView.rendered = this._renderContent(type, reusableView.content); | ||
if (reusableView.layoutInfo) { | ||
let { type: type, key: key, content: content } = reusableView.layoutInfo; | ||
reusableView.content = content || this.collection.getItem(key); | ||
reusableView.rendered = this._renderContent(type, reusableView.content); | ||
} | ||
} | ||
_renderContent(type, content) { | ||
let cached = this._renderedContent.get(content); | ||
let cached = content != null ? this._renderedContent.get(content) : null; | ||
if (cached != null) return cached; | ||
@@ -123,3 +125,3 @@ let rendered = this.delegate.renderView(type, content); | ||
if (view.content !== item) { | ||
this._renderedContent.delete(view.content); | ||
if (view.content != null) this._renderedContent.delete(view.content); | ||
this._renderView(view); | ||
@@ -153,2 +155,3 @@ } | ||
let itemSizeChanged = false; | ||
let layoutOptionsChanged = false; | ||
let needsUpdate = false; | ||
@@ -159,3 +162,3 @@ if (opts.collection !== this.collection) { | ||
} | ||
if (opts.layout !== this.layout) { | ||
if (opts.layout !== this.layout || this.layout.virtualizer !== this) { | ||
if (this.layout) this.layout.virtualizer = null; | ||
@@ -185,4 +188,4 @@ opts.layout.virtualizer = this; | ||
itemSizeChanged || (itemSizeChanged = opts.invalidationContext.itemSizeChanged || false); | ||
needsLayout || (needsLayout = itemSizeChanged || sizeChanged || offsetChanged); | ||
needsLayout || (needsLayout = opts.invalidationContext.layoutOptions !== this._invalidationContext.layoutOptions); | ||
layoutOptionsChanged || (layoutOptionsChanged = opts.invalidationContext.layoutOptions != null && this._invalidationContext.layoutOptions != null && opts.invalidationContext.layoutOptions !== this._invalidationContext.layoutOptions && this.layout.shouldInvalidateLayoutOptions(opts.invalidationContext.layoutOptions, this._invalidationContext.layoutOptions)); | ||
needsLayout || (needsLayout = itemSizeChanged || sizeChanged || offsetChanged || layoutOptionsChanged); | ||
} | ||
@@ -200,2 +203,3 @@ this._invalidationContext = opts.invalidationContext; | ||
itemSizeChanged: itemSizeChanged, | ||
layoutOptionsChanged: layoutOptionsChanged, | ||
layoutOptions: this._invalidationContext.layoutOptions | ||
@@ -219,4 +223,6 @@ }); | ||
} | ||
constructor(delegate){ | ||
this.delegate = delegate; | ||
constructor(options){ | ||
this.delegate = options.delegate; | ||
this.collection = options.collection; | ||
this.layout = options.layout; | ||
this.contentSize = new (0, $ee1bfa90a957fb8a$export$cb6da89c6af1a8ec); | ||
@@ -227,5 +233,5 @@ this.visibleRect = new (0, $60423f92c7f9ad87$export$c79fc6492f3af13d); | ||
this._renderedContent = new WeakMap(); | ||
this._rootView = new (0, $ad1d98aa8f0c31b4$export$1a5223887c560441)(this); | ||
this._rootView = new (0, $ad1d98aa8f0c31b4$export$e21886a4eef6b29a)(this); | ||
this._isScrolling = false; | ||
this._invalidationContext = null; | ||
this._invalidationContext = {}; | ||
this._overscanManager = new (0, $364191b3decf3697$export$4455ee6afb38dcbb)(); | ||
@@ -232,0 +238,0 @@ } |
{ | ||
"name": "@react-stately/virtualizer", | ||
"version": "3.0.0-nightly-dcc0752f8-241031", | ||
"version": "3.0.0-nightly-e00dc8373-250318", | ||
"description": "Spectrum UI components in React", | ||
@@ -25,13 +25,13 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@react-aria/utils": "^3.0.0-nightly-dcc0752f8-241031", | ||
"@react-types/shared": "^3.0.0-nightly-dcc0752f8-241031", | ||
"@react-aria/utils": "3.0.0-nightly-e00dc8373-250318", | ||
"@react-types/shared": "3.0.0-nightly-e00dc8373-250318", | ||
"@swc/helpers": "^0.5.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", | ||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"stableVersion": "4.1.0" | ||
} | ||
} |
@@ -14,3 +14,3 @@ /* | ||
import {InvalidationContext} from './types'; | ||
import {ItemDropTarget, Key, LayoutDelegate} from '@react-types/shared'; | ||
import {ItemDropTarget, Key, LayoutDelegate, Node} from '@react-types/shared'; | ||
import {LayoutInfo} from './LayoutInfo'; | ||
@@ -22,19 +22,35 @@ import {Rect} from './Rect'; | ||
/** | ||
* [Virtualizer]{@link Virtualizer} supports arbitrary layout objects, which compute what views are visible, and how | ||
* to position and style them. However, layouts do not create the views themselves directly. Instead, | ||
* layouts produce lightweight {@link LayoutInfo} objects which describe various properties of a view, | ||
* such as its position and size. The {@link Virtualizer} is then responsible for creating the actual | ||
* Virtualizer supports arbitrary layout objects, which compute what items are visible, and how | ||
* to position and style them. However, layouts do not render items directly. Instead, | ||
* layouts produce lightweight LayoutInfo objects which describe various properties of an item, | ||
* such as its position and size. The Virtualizer is then responsible for creating the actual | ||
* views as needed, based on this layout information. | ||
* | ||
* Every layout extends from the {@link Layout} abstract base class. Layouts must implement a minimum of the | ||
* two methods listed below. All other methods can be optionally overridden to implement custom behavior. | ||
* | ||
* @see {@link getVisibleLayoutInfos} | ||
* @see {@link getLayoutInfo} | ||
* Every layout extends from the Layout abstract base class. Layouts must implement the `getVisibleLayoutInfos`, | ||
* `getLayoutInfo`, and `getContentSize` methods. All other methods can be optionally overridden to implement custom behavior. | ||
*/ | ||
export abstract class Layout<T extends object, O = any> implements LayoutDelegate { | ||
export abstract class Layout<T extends object = Node<any>, O = any> implements LayoutDelegate { | ||
/** The Virtualizer the layout is currently attached to. */ | ||
virtualizer: Virtualizer<T, any>; | ||
virtualizer: Virtualizer<T, any> | null = null; | ||
/** | ||
* Returns an array of `LayoutInfo` objects which are inside the given rectangle. | ||
* Should be implemented by subclasses. | ||
* @param rect The rectangle that should contain the returned LayoutInfo objects. | ||
*/ | ||
abstract getVisibleLayoutInfos(rect: Rect): LayoutInfo[]; | ||
/** | ||
* Returns a `LayoutInfo` for the given key. | ||
* Should be implemented by subclasses. | ||
* @param key The key of the LayoutInfo to retrieve. | ||
*/ | ||
abstract getLayoutInfo(key: Key): LayoutInfo | null; | ||
/** | ||
* Returns size of the content. By default, it returns virtualizer's size. | ||
*/ | ||
abstract getContentSize(): Size; | ||
/** | ||
* Returns whether the layout should invalidate in response to | ||
@@ -52,29 +68,19 @@ * visible rectangle changes. By default, it only invalidates | ||
/** | ||
* This method allows the layout to perform any pre-computation | ||
* it needs to in order to prepare {@link LayoutInfo}s for retrieval. | ||
* Called by the virtualizer before {@link getVisibleLayoutInfos} | ||
* or {@link getLayoutInfo} are called. | ||
* Returns whether the layout should invalidate when the layout options change. | ||
* By default it invalidates when the object identity changes. Override this | ||
* method to optimize layout updates based on specific option changes. | ||
*/ | ||
update(invalidationContext: InvalidationContext<O>) {} // eslint-disable-line @typescript-eslint/no-unused-vars | ||
shouldInvalidateLayoutOptions(newOptions: O, oldOptions: O): boolean { | ||
return newOptions !== oldOptions; | ||
} | ||
/** | ||
* Returns an array of {@link LayoutInfo} objects which are inside the given rectangle. | ||
* Should be implemented by subclasses. | ||
* @param rect The rectangle that should contain the returned LayoutInfo objects. | ||
* This method allows the layout to perform any pre-computation | ||
* it needs to in order to prepare LayoutInfos for retrieval. | ||
* Called by the virtualizer before `getVisibleLayoutInfos` | ||
* or `getLayoutInfo` are called. | ||
*/ | ||
abstract getVisibleLayoutInfos(rect: Rect): LayoutInfo[]; | ||
update(invalidationContext: InvalidationContext<O>): void {} // eslint-disable-line @typescript-eslint/no-unused-vars | ||
/** | ||
* Returns a {@link LayoutInfo} for the given key. | ||
* Should be implemented by subclasses. | ||
* @param key The key of the LayoutInfo to retrieve. | ||
*/ | ||
abstract getLayoutInfo(key: Key): LayoutInfo | null; | ||
/** | ||
* Returns size of the content. By default, it returns collectionView's size. | ||
*/ | ||
abstract getContentSize(): Size; | ||
/** | ||
* Updates the size of the given item. | ||
@@ -85,13 +91,15 @@ */ | ||
/** | ||
* Returns a LayoutInfo for the given drop target. | ||
* Returns a `LayoutInfo` for the given drop target. | ||
*/ | ||
getDropTargetLayoutInfo?(target: ItemDropTarget): LayoutInfo; | ||
getItemRect(key: Key): Rect { | ||
return this.getLayoutInfo(key)?.rect; | ||
/** @private */ | ||
getItemRect(key: Key): Rect | null { | ||
return this.getLayoutInfo(key)?.rect ?? null; | ||
} | ||
/** @private */ | ||
getVisibleRect(): Rect { | ||
return this.virtualizer.visibleRect; | ||
return this.virtualizer!.visibleRect; | ||
} | ||
} |
@@ -17,11 +17,10 @@ /* | ||
/** | ||
* Instances of this lightweight class are created by {@link Layout} subclasses | ||
* to represent each view in the {@link Virtualizer}. LayoutInfo objects describe | ||
* various properties of a view, such as its position and size, and style information. | ||
* The virtualizer uses this information when creating actual views to display. | ||
* Instances of this lightweight class are created by `Layout` subclasses | ||
* to represent each item in the `Virtualizer`. LayoutInfo objects describe | ||
* various properties of an item, such as its position and size, and style information. | ||
* The virtualizer uses this information when creating actual DOM elements to display. | ||
*/ | ||
export class LayoutInfo { | ||
/** | ||
* A string representing the view type. Should be `'item'` for item views. | ||
* Other types are used by supplementary views. | ||
* The type of element represented by this LayoutInfo. Should match the `type` of the corresponding collection node. | ||
*/ | ||
@@ -31,3 +30,3 @@ type: string; | ||
/** | ||
* A unique key for this view. For item views, it should match the content key. | ||
* A unique key for this LayoutInfo. Should match the `key` of the corresponding collection node. | ||
*/ | ||
@@ -37,3 +36,3 @@ key: Key; | ||
/** | ||
* The key for a parent layout info, if any. | ||
* The key for a parent LayoutInfo, if any. | ||
*/ | ||
@@ -43,3 +42,3 @@ parentKey: Key | null; | ||
/** | ||
* Content for this view if it was generated by the layout rather than coming from the Collection. | ||
* Content for this item if it was generated by the layout rather than coming from the Collection. | ||
*/ | ||
@@ -49,3 +48,3 @@ content: any | null; | ||
/** | ||
* The rectangle describing the size and position of this view. | ||
* The rectangle describing the size and position of this element. | ||
*/ | ||
@@ -56,2 +55,5 @@ rect: Rect; | ||
* Whether the size is estimated. `false` by default. | ||
* Items with estimated sizes will be measured the first time they are added to the DOM. | ||
* The estimated size is used to calculate the size and position of the scrollbar. | ||
* @default false | ||
*/ | ||
@@ -62,2 +64,3 @@ estimatedSize: boolean; | ||
* Whether the layout info sticks to the viewport when scrolling. | ||
* @default false | ||
*/ | ||
@@ -67,3 +70,4 @@ isSticky: boolean; | ||
/** | ||
* The view's opacity. 1 by default. | ||
* The element's opacity. | ||
* @default 1 | ||
*/ | ||
@@ -73,3 +77,3 @@ opacity: number; | ||
/** | ||
* A CSS transform string to apply to the view. `null` by default. | ||
* A CSS transform string to apply to the element. `null` by default. | ||
*/ | ||
@@ -79,3 +83,3 @@ transform: string | null; | ||
/** | ||
* The z-index of the view. 0 by default. | ||
* The z-index of the element. 0 by default. | ||
*/ | ||
@@ -85,3 +89,3 @@ zIndex: number; | ||
/** | ||
* Whether the layout info allows its contents to overflow its container. | ||
* Whether the element allows its contents to overflow its container. | ||
* @default false | ||
@@ -92,6 +96,5 @@ */ | ||
/** | ||
* @param type A string representing the view type. Should be `'item'` for item views. | ||
Other types are used by supplementary views. | ||
* @param key The unique key for this view. | ||
* @param rect The rectangle describing the size and position of this view. | ||
* @param type The type of element represented by this LayoutInfo. Should match the `type` of the corresponding collection node. | ||
* @param key A unique key for this LayoutInfo. Should match the `key` of the corresponding collection node. | ||
* @param rect The rectangle describing the size and position of this element. | ||
*/ | ||
@@ -98,0 +101,0 @@ constructor(type: string, key: Key, rect: Rect) { |
@@ -21,3 +21,3 @@ /* | ||
setVisibleRect(rect: Rect) { | ||
setVisibleRect(rect: Rect): void { | ||
let time = performance.now() - this.startTime; | ||
@@ -38,3 +38,3 @@ if (time < 500) { | ||
getOverscannedRect() { | ||
getOverscannedRect(): Rect { | ||
let overscanned = this.visibleRect.copy(); | ||
@@ -41,0 +41,0 @@ |
@@ -138,3 +138,3 @@ /* | ||
equals(rect: Rect) { | ||
equals(rect: Rect): boolean { | ||
return rect.x === this.x | ||
@@ -146,3 +146,3 @@ && rect.y === this.y | ||
pointEquals(point: Point | Rect) { | ||
pointEquals(point: Point | Rect): boolean { | ||
return this.x === point.x | ||
@@ -152,3 +152,3 @@ && this.y === point.y; | ||
sizeEquals(size: Size | Rect) { | ||
sizeEquals(size: Size | Rect): boolean { | ||
return this.width === size.width | ||
@@ -161,3 +161,3 @@ && this.height === size.height; | ||
*/ | ||
union(other: Rect) { | ||
union(other: Rect): Rect { | ||
let x = Math.min(this.x, other.x); | ||
@@ -164,0 +164,0 @@ let y = Math.min(this.y, other.y); |
@@ -20,3 +20,3 @@ /* | ||
/** | ||
* [Virtualizer]{@link Virtualizer} creates instances of the [ReusableView]{@link ReusableView} class to | ||
* `Virtualizer` creates instances of the `ReusableView` class to | ||
* represent views currently being displayed. | ||
@@ -32,5 +32,5 @@ */ | ||
/** The content currently being displayed by this view, set by the virtualizer. */ | ||
content: T; | ||
content: T | null; | ||
rendered: V; | ||
rendered: V | null; | ||
@@ -40,12 +40,14 @@ viewType: string; | ||
parent: ReusableView<T, V> | null; | ||
children: Set<ReusableView<T, V>>; | ||
reusableViews: Map<string, ReusableView<T, V>[]>; | ||
children: Set<ChildView<T, V>>; | ||
reusableViews: Map<string, ChildView<T, V>[]>; | ||
constructor(virtualizer: Virtualizer<T, V>) { | ||
constructor(virtualizer: Virtualizer<T, V>, viewType: string) { | ||
this.virtualizer = virtualizer; | ||
this.key = ++KEY; | ||
this.parent = null; | ||
this.viewType = viewType; | ||
this.children = new Set(); | ||
this.reusableViews = new Map(); | ||
this.layoutInfo = null; | ||
this.content = null; | ||
this.rendered = null; | ||
} | ||
@@ -56,3 +58,3 @@ | ||
*/ | ||
prepareForReuse() { | ||
prepareForReuse(): void { | ||
this.content = null; | ||
@@ -63,3 +65,3 @@ this.rendered = null; | ||
getReusableView(reuseType: string) { | ||
getReusableView(reuseType: string): ChildView<T, V> { | ||
// Reusable view queue should be FIFO so that DOM order remains consistent during scrolling. | ||
@@ -70,12 +72,10 @@ // For example, cells within a row should remain in the same order even if the row changes contents. | ||
let reusable = this.reusableViews.get(reuseType); | ||
let view = reusable?.length > 0 | ||
? reusable.shift() | ||
: new ReusableView<T, V>(this.virtualizer); | ||
let view = reusable && reusable.length > 0 | ||
? reusable.shift()! | ||
: new ChildView<T, V>(this.virtualizer, this, reuseType); | ||
view.viewType = reuseType; | ||
view.parent = this; | ||
return view; | ||
} | ||
reuseChild(child: ReusableView<T, V>) { | ||
reuseChild(child: ChildView<T, V>): void { | ||
child.prepareForReuse(); | ||
@@ -90,1 +90,16 @@ let reusable = this.reusableViews.get(child.viewType); | ||
} | ||
export class RootView<T extends object, V> extends ReusableView<T, V> { | ||
constructor(virtualizer: Virtualizer<T, V>) { | ||
super(virtualizer, 'root'); | ||
} | ||
} | ||
export class ChildView<T extends object, V> extends ReusableView<T, V> { | ||
parent: ReusableView<T, V>; | ||
constructor(virtualizer: Virtualizer<T, V>, parent: ReusableView<T, V>, viewType: string) { | ||
super(virtualizer, viewType); | ||
this.parent = parent; | ||
} | ||
} |
@@ -40,5 +40,5 @@ /* | ||
*/ | ||
get area() { | ||
get area(): number { | ||
return this.width * this.height; | ||
} | ||
} |
@@ -22,2 +22,3 @@ /* | ||
itemSizeChanged?: boolean, | ||
layoutOptionsChanged?: boolean, | ||
layoutOptions?: O | ||
@@ -28,3 +29,3 @@ } | ||
setVisibleRect(rect: Rect): void, | ||
renderView(type: string, content: T): V, | ||
renderView(type: string, content: T | null): V, | ||
invalidate(ctx: InvalidationContext): void | ||
@@ -36,3 +37,3 @@ } | ||
collection: Collection<T>, | ||
persistedKeys?: Set<Key>, | ||
persistedKeys?: Set<Key> | null, | ||
visibleRect: Rect, | ||
@@ -39,0 +40,0 @@ invalidationContext: InvalidationContext, |
@@ -24,3 +24,3 @@ /* | ||
interface VirtualizerProps<T extends object, V, O> { | ||
renderView(type: string, content: T): V, | ||
renderView(type: string, content: T | null): V, | ||
layout: Layout<T>, | ||
@@ -49,9 +49,13 @@ collection: Collection<T>, | ||
let [virtualizer] = useState(() => new Virtualizer<T, V>({ | ||
setVisibleRect(rect) { | ||
setVisibleRect(rect); | ||
visibleRectChanged.current = true; | ||
}, | ||
// TODO: should changing these invalidate the entire cache? | ||
renderView: opts.renderView, | ||
invalidate: setInvalidationContext | ||
collection: opts.collection, | ||
layout: opts.layout, | ||
delegate: { | ||
setVisibleRect(rect) { | ||
setVisibleRect(rect); | ||
visibleRectChanged.current = true; | ||
}, | ||
// TODO: should changing these invalidate the entire cache? | ||
renderView: opts.renderView, | ||
invalidate: setInvalidationContext | ||
} | ||
})); | ||
@@ -58,0 +62,0 @@ |
@@ -13,2 +13,3 @@ /* | ||
import {ChildView, ReusableView, RootView} from './ReusableView'; | ||
import {Collection, Key} from '@react-types/shared'; | ||
@@ -22,5 +23,10 @@ import {InvalidationContext, Mutable, VirtualizerDelegate, VirtualizerRenderOptions} from './types'; | ||
import {Rect} from './Rect'; | ||
import {ReusableView} from './ReusableView'; | ||
import {Size} from './Size'; | ||
interface VirtualizerOptions<T extends object, V> { | ||
delegate: VirtualizerDelegate<T, V>, | ||
collection: Collection<T>, | ||
layout: Layout<T> | ||
} | ||
/** | ||
@@ -32,3 +38,3 @@ * The Virtualizer class renders a scrollable collection of data using customizable layouts. | ||
* | ||
* Virtualizer uses {@link Layout} objects to compute what views should be visible, and how | ||
* Virtualizer uses `Layout` objects to compute what views should be visible, and how | ||
* to position and style them. This means that virtualizer can have its items arranged in | ||
@@ -39,4 +45,4 @@ * a stack, a grid, a circle, or any other layout you can think of. The layout can be changed | ||
* Layouts produce information on what views should appear in the virtualizer, but do not create | ||
* the views themselves directly. It is the responsibility of the {@link VirtualizerDelegate} object | ||
* to render elements for each layout info. The virtualizer manages a set of {@link ReusableView} objects, | ||
* the views themselves directly. It is the responsibility of the `VirtualizerDelegate` object | ||
* to render elements for each layout info. The virtualizer manages a set of `ReusableView` objects, | ||
* which are reused as the user scrolls by swapping their content with cached elements returned by the delegate. | ||
@@ -62,11 +68,13 @@ */ | ||
private _visibleViews: Map<Key, ReusableView<T, V>>; | ||
private _visibleViews: Map<Key, ChildView<T, V>>; | ||
private _renderedContent: WeakMap<T, V>; | ||
private _rootView: ReusableView<T, V>; | ||
private _rootView: RootView<T, V>; | ||
private _isScrolling: boolean; | ||
private _invalidationContext: InvalidationContext | null; | ||
private _invalidationContext: InvalidationContext; | ||
private _overscanManager: OverscanManager; | ||
constructor(delegate: VirtualizerDelegate<T, V>) { | ||
this.delegate = delegate; | ||
constructor(options: VirtualizerOptions<T, V>) { | ||
this.delegate = options.delegate; | ||
this.collection = options.collection; | ||
this.layout = options.layout; | ||
this.contentSize = new Size; | ||
@@ -77,5 +85,5 @@ this.visibleRect = new Rect; | ||
this._renderedContent = new WeakMap(); | ||
this._rootView = new ReusableView(this); | ||
this._rootView = new RootView(this); | ||
this._isScrolling = false; | ||
this._invalidationContext = null; | ||
this._invalidationContext = {}; | ||
this._overscanManager = new OverscanManager(); | ||
@@ -85,3 +93,3 @@ } | ||
/** Returns whether the given key, or an ancestor, is persisted. */ | ||
isPersistedKey(key: Key) { | ||
isPersistedKey(key: Key): boolean { | ||
// Quick check if the key is directly in the set of persisted keys. | ||
@@ -96,3 +104,3 @@ if (this.persistedKeys.has(key)) { | ||
let layoutInfo = this.layout.getLayoutInfo(k); | ||
if (!layoutInfo) { | ||
if (!layoutInfo || layoutInfo.parentKey == null) { | ||
break; | ||
@@ -116,3 +124,3 @@ } | ||
private getReusableView(layoutInfo: LayoutInfo): ReusableView<T, V> { | ||
private getReusableView(layoutInfo: LayoutInfo): ChildView<T, V> { | ||
let parentView = this.getParentView(layoutInfo)!; | ||
@@ -126,9 +134,11 @@ let view = parentView.getReusableView(layoutInfo.type); | ||
private _renderView(reusableView: ReusableView<T, V>) { | ||
let {type, key, content} = reusableView.layoutInfo; | ||
reusableView.content = content || this.collection.getItem(key); | ||
reusableView.rendered = this._renderContent(type, reusableView.content); | ||
if (reusableView.layoutInfo) { | ||
let {type, key, content} = reusableView.layoutInfo; | ||
reusableView.content = content || this.collection.getItem(key); | ||
reusableView.rendered = this._renderContent(type, reusableView.content); | ||
} | ||
} | ||
private _renderContent(type: string, content: T) { | ||
let cached = this._renderedContent.get(content); | ||
private _renderContent(type: string, content: T | null) { | ||
let cached = content != null ? this._renderedContent.get(content) : null; | ||
if (cached != null) { | ||
@@ -185,3 +195,3 @@ return cached; | ||
getVisibleLayoutInfos() { | ||
getVisibleLayoutInfos(): Map<Key, LayoutInfo> { | ||
let isTestEnv = process.env.NODE_ENV === 'test' && !process.env.VIRT_ON; | ||
@@ -210,3 +220,3 @@ let isClientWidthMocked = isTestEnv && typeof HTMLElement !== 'undefined' && Object.getOwnPropertyNames(HTMLElement.prototype).includes('clientWidth'); | ||
let removed = new Set<ReusableView<T, V>>(); | ||
let removed = new Set<ChildView<T, V>>(); | ||
for (let [key, view] of this._visibleViews) { | ||
@@ -234,3 +244,5 @@ let layoutInfo = visibleLayoutInfos.get(key); | ||
if (view.content !== item) { | ||
this._renderedContent.delete(view.content); | ||
if (view.content != null) { | ||
this._renderedContent.delete(view.content); | ||
} | ||
this._renderView(view); | ||
@@ -270,2 +282,3 @@ } | ||
let itemSizeChanged = false; | ||
let layoutOptionsChanged = false; | ||
let needsUpdate = false; | ||
@@ -278,3 +291,3 @@ | ||
if (opts.layout !== this.layout) { | ||
if (opts.layout !== this.layout || this.layout.virtualizer !== this) { | ||
if (this.layout) { | ||
@@ -314,4 +327,7 @@ this.layout.virtualizer = null; | ||
itemSizeChanged ||= opts.invalidationContext.itemSizeChanged || false; | ||
needsLayout ||= itemSizeChanged || sizeChanged || offsetChanged; | ||
needsLayout ||= opts.invalidationContext.layoutOptions !== this._invalidationContext.layoutOptions; | ||
layoutOptionsChanged ||= opts.invalidationContext.layoutOptions != null | ||
&& this._invalidationContext.layoutOptions != null | ||
&& opts.invalidationContext.layoutOptions !== this._invalidationContext.layoutOptions | ||
&& this.layout.shouldInvalidateLayoutOptions(opts.invalidationContext.layoutOptions, this._invalidationContext.layoutOptions); | ||
needsLayout ||= itemSizeChanged || sizeChanged || offsetChanged || layoutOptionsChanged; | ||
} | ||
@@ -334,2 +350,3 @@ this._invalidationContext = opts.invalidationContext; | ||
itemSizeChanged, | ||
layoutOptionsChanged, | ||
layoutOptions: this._invalidationContext.layoutOptions | ||
@@ -348,7 +365,7 @@ }); | ||
invalidate(context: InvalidationContext) { | ||
invalidate(context: InvalidationContext): void { | ||
this.delegate.invalidate(context); | ||
} | ||
updateItemSize(key: Key, size: Size) { | ||
updateItemSize(key: Key, size: Size): void { | ||
if (!this.layout.updateItemSize) { | ||
@@ -355,0 +372,0 @@ return; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
283464
5.77%3
-25%3695
4.53%5
25%+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed