@react-stately/layout
Advanced tools
Comparing version
@@ -56,4 +56,8 @@ var $7Tzdl$reactstatelyvirtualizer = require("@react-stately/virtualizer"); | ||
this.gap = new (0, $7Tzdl$reactstatelyvirtualizer.Size)(horizontalSpacing, minSpace.height); | ||
let rows = Math.ceil(this.virtualizer.collection.size / numColumns); | ||
let iterator = this.virtualizer.collection[Symbol.iterator](); | ||
let collection = this.virtualizer.collection; | ||
// Make sure to set rows to 0 if we performing a first time load or are rendering the empty state so that Virtualizer | ||
// won't try to render its body | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
let rows = isEmptyOrLoading ? 0 : Math.ceil(collection.size / numColumns); | ||
let iterator = collection[Symbol.iterator](); | ||
let y = rows > 0 ? minSpace.height : 0; | ||
@@ -70,2 +74,4 @@ let newLayoutInfos = new Map(); | ||
if (!node) break; | ||
// We will add the loader after the skeletons so skip here | ||
if (node.type === 'loader') continue; | ||
if (node.type === 'skeleton') skeleton = node; | ||
@@ -100,2 +106,9 @@ let key = skeleton ? `${skeleton.key}-${skeletonCount++}` : node.key; | ||
} | ||
// Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out. | ||
let lastNode = collection.getItem(collection.getLastKey()); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') { | ||
let rect = new (0, $7Tzdl$reactstatelyvirtualizer.Rect)(horizontalSpacing, y, itemWidth, 0); | ||
let layoutInfo = new (0, $7Tzdl$reactstatelyvirtualizer.LayoutInfo)('loader', lastNode.key, rect); | ||
newLayoutInfos.set(lastNode.key, layoutInfo); | ||
} | ||
this.layoutInfos = newLayoutInfos; | ||
@@ -105,3 +118,3 @@ this.contentSize = new (0, $7Tzdl$reactstatelyvirtualizer.Size)(this.virtualizer.visibleRect.width, y); | ||
getLayoutInfo(key) { | ||
return this.layoutInfos.get(key); | ||
return this.layoutInfos.get(key) || null; | ||
} | ||
@@ -113,3 +126,3 @@ getContentSize() { | ||
let layoutInfos = []; | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key)) layoutInfos.push(layoutInfo); | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') layoutInfos.push(layoutInfo); | ||
return layoutInfos; | ||
@@ -138,3 +151,3 @@ } | ||
if (this.numColumns === 1) { | ||
let searchRect = new (0, $7Tzdl$reactstatelyvirtualizer.Rect)(x, Math.max(0, y - this.gap.height), 1, this.gap.height * 2); | ||
let searchRect = new (0, $7Tzdl$reactstatelyvirtualizer.Rect)(x, Math.max(0, y - this.gap.height), 1, Math.max(1, this.gap.height * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -141,0 +154,0 @@ let minDistance = Infinity; |
@@ -50,4 +50,8 @@ import {Size as $ipgKF$Size, Rect as $ipgKF$Rect, LayoutInfo as $ipgKF$LayoutInfo, Layout as $ipgKF$Layout} from "@react-stately/virtualizer"; | ||
this.gap = new (0, $ipgKF$Size)(horizontalSpacing, minSpace.height); | ||
let rows = Math.ceil(this.virtualizer.collection.size / numColumns); | ||
let iterator = this.virtualizer.collection[Symbol.iterator](); | ||
let collection = this.virtualizer.collection; | ||
// Make sure to set rows to 0 if we performing a first time load or are rendering the empty state so that Virtualizer | ||
// won't try to render its body | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
let rows = isEmptyOrLoading ? 0 : Math.ceil(collection.size / numColumns); | ||
let iterator = collection[Symbol.iterator](); | ||
let y = rows > 0 ? minSpace.height : 0; | ||
@@ -64,2 +68,4 @@ let newLayoutInfos = new Map(); | ||
if (!node) break; | ||
// We will add the loader after the skeletons so skip here | ||
if (node.type === 'loader') continue; | ||
if (node.type === 'skeleton') skeleton = node; | ||
@@ -94,2 +100,9 @@ let key = skeleton ? `${skeleton.key}-${skeletonCount++}` : node.key; | ||
} | ||
// Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out. | ||
let lastNode = collection.getItem(collection.getLastKey()); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') { | ||
let rect = new (0, $ipgKF$Rect)(horizontalSpacing, y, itemWidth, 0); | ||
let layoutInfo = new (0, $ipgKF$LayoutInfo)('loader', lastNode.key, rect); | ||
newLayoutInfos.set(lastNode.key, layoutInfo); | ||
} | ||
this.layoutInfos = newLayoutInfos; | ||
@@ -99,3 +112,3 @@ this.contentSize = new (0, $ipgKF$Size)(this.virtualizer.visibleRect.width, y); | ||
getLayoutInfo(key) { | ||
return this.layoutInfos.get(key); | ||
return this.layoutInfos.get(key) || null; | ||
} | ||
@@ -107,3 +120,3 @@ getContentSize() { | ||
let layoutInfos = []; | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key)) layoutInfos.push(layoutInfo); | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') layoutInfos.push(layoutInfo); | ||
return layoutInfos; | ||
@@ -132,3 +145,3 @@ } | ||
if (this.numColumns === 1) { | ||
let searchRect = new (0, $ipgKF$Rect)(x, Math.max(0, y - this.gap.height), 1, this.gap.height * 2); | ||
let searchRect = new (0, $ipgKF$Rect)(x, Math.max(0, y - this.gap.height), 1, Math.max(1, this.gap.height * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -135,0 +148,0 @@ let minDistance = Infinity; |
@@ -79,3 +79,3 @@ var $iId4j$reactstatelycollections = require("@react-stately/collections"); | ||
isVisible(node, rect) { | ||
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || this.virtualizer.isPersistedKey(node.layoutInfo.key); | ||
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || node.layoutInfo.type === 'loader' || this.virtualizer.isPersistedKey(node.layoutInfo.key); | ||
} | ||
@@ -135,2 +135,4 @@ shouldInvalidateEverything(invalidationContext) { | ||
let nodes = []; | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
if (isEmptyOrLoading) y = 0; | ||
for (let node of collection){ | ||
@@ -149,3 +151,14 @@ var _this_rowHeight, _ref; | ||
if (node.type === 'item' && y > this.requestedRect.maxY) { | ||
y += (collection.size - (nodes.length + skipped)) * rowHeight; | ||
var _nodes_at; | ||
let itemsAfterRect = collection.size - (nodes.length + skipped); | ||
let lastNode = collection.getItem(collection.getLastKey()); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') itemsAfterRect--; | ||
y += itemsAfterRect * rowHeight; | ||
// Always add the loader sentinel if present. This assumes the loader is the last option/row | ||
// will need to refactor when handling multi section loading | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader' && ((_nodes_at = nodes.at(-1)) === null || _nodes_at === void 0 ? void 0 : _nodes_at.layoutInfo.type) !== 'loader') { | ||
let loader = this.buildChild(lastNode, this.padding, y, null); | ||
nodes.push(loader); | ||
y = loader.layoutInfo.rect.maxY; | ||
} | ||
break; | ||
@@ -155,3 +168,3 @@ } | ||
y -= this.gap; | ||
y += this.padding; | ||
y += isEmptyOrLoading ? 0 : this.padding; | ||
this.contentSize = new (0, $iId4j$reactstatelyvirtualizer.Size)(this.virtualizer.visibleRect.width, y); | ||
@@ -168,2 +181,3 @@ return nodes; | ||
layoutNode.layoutInfo.parentKey = parentKey !== null && parentKey !== void 0 ? parentKey : null; | ||
layoutNode.layoutInfo.allowOverflow = true; | ||
this.layoutNodes.set(node.key, layoutNode); | ||
@@ -182,2 +196,4 @@ return layoutNode; | ||
return this.buildLoader(node, x, y); | ||
case 'separator': | ||
return this.buildItem(node, x, y); | ||
default: | ||
@@ -191,3 +207,6 @@ throw new Error('Unsupported node type: ' + node.type); | ||
rect.width = this.virtualizer.contentSize.width - this.padding - x; | ||
rect.height = this.loaderHeight || this.rowHeight || this.estimatedRowHeight || $fe69e47e38ed0ac4$var$DEFAULT_HEIGHT; | ||
var _this_loaderHeight, _ref, _ref1; | ||
// Note that if the user provides isLoading to their sentinel during a case where they only want to render the emptyState, this will reserve | ||
// room for the loader alongside rendering the emptyState | ||
rect.height = node.props.isLoading ? (_ref1 = (_ref = (_this_loaderHeight = this.loaderHeight) !== null && _this_loaderHeight !== void 0 ? _this_loaderHeight : this.rowHeight) !== null && _ref !== void 0 ? _ref : this.estimatedRowHeight) !== null && _ref1 !== void 0 ? _ref1 : $fe69e47e38ed0ac4$var$DEFAULT_HEIGHT : 0; | ||
return { | ||
@@ -340,3 +359,3 @@ layoutInfo: layoutInfo, | ||
// Find the closest item within on either side of the point using the gap width. | ||
let searchRect = new (0, $iId4j$reactstatelyvirtualizer.Rect)(x, Math.max(0, y - this.gap), 1, this.gap * 2); | ||
let searchRect = new (0, $iId4j$reactstatelyvirtualizer.Rect)(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -343,0 +362,0 @@ let key = null; |
@@ -73,3 +73,3 @@ import {getChildNodes as $img26$getChildNodes} from "@react-stately/collections"; | ||
isVisible(node, rect) { | ||
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || this.virtualizer.isPersistedKey(node.layoutInfo.key); | ||
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || node.layoutInfo.type === 'loader' || this.virtualizer.isPersistedKey(node.layoutInfo.key); | ||
} | ||
@@ -129,2 +129,4 @@ shouldInvalidateEverything(invalidationContext) { | ||
let nodes = []; | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
if (isEmptyOrLoading) y = 0; | ||
for (let node of collection){ | ||
@@ -143,3 +145,14 @@ var _this_rowHeight, _ref; | ||
if (node.type === 'item' && y > this.requestedRect.maxY) { | ||
y += (collection.size - (nodes.length + skipped)) * rowHeight; | ||
var _nodes_at; | ||
let itemsAfterRect = collection.size - (nodes.length + skipped); | ||
let lastNode = collection.getItem(collection.getLastKey()); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') itemsAfterRect--; | ||
y += itemsAfterRect * rowHeight; | ||
// Always add the loader sentinel if present. This assumes the loader is the last option/row | ||
// will need to refactor when handling multi section loading | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader' && ((_nodes_at = nodes.at(-1)) === null || _nodes_at === void 0 ? void 0 : _nodes_at.layoutInfo.type) !== 'loader') { | ||
let loader = this.buildChild(lastNode, this.padding, y, null); | ||
nodes.push(loader); | ||
y = loader.layoutInfo.rect.maxY; | ||
} | ||
break; | ||
@@ -149,3 +162,3 @@ } | ||
y -= this.gap; | ||
y += this.padding; | ||
y += isEmptyOrLoading ? 0 : this.padding; | ||
this.contentSize = new (0, $img26$Size)(this.virtualizer.visibleRect.width, y); | ||
@@ -162,2 +175,3 @@ return nodes; | ||
layoutNode.layoutInfo.parentKey = parentKey !== null && parentKey !== void 0 ? parentKey : null; | ||
layoutNode.layoutInfo.allowOverflow = true; | ||
this.layoutNodes.set(node.key, layoutNode); | ||
@@ -176,2 +190,4 @@ return layoutNode; | ||
return this.buildLoader(node, x, y); | ||
case 'separator': | ||
return this.buildItem(node, x, y); | ||
default: | ||
@@ -185,3 +201,6 @@ throw new Error('Unsupported node type: ' + node.type); | ||
rect.width = this.virtualizer.contentSize.width - this.padding - x; | ||
rect.height = this.loaderHeight || this.rowHeight || this.estimatedRowHeight || $61ef60fc9b1041f4$var$DEFAULT_HEIGHT; | ||
var _this_loaderHeight, _ref, _ref1; | ||
// Note that if the user provides isLoading to their sentinel during a case where they only want to render the emptyState, this will reserve | ||
// room for the loader alongside rendering the emptyState | ||
rect.height = node.props.isLoading ? (_ref1 = (_ref = (_this_loaderHeight = this.loaderHeight) !== null && _this_loaderHeight !== void 0 ? _this_loaderHeight : this.rowHeight) !== null && _ref !== void 0 ? _ref : this.estimatedRowHeight) !== null && _ref1 !== void 0 ? _ref1 : $61ef60fc9b1041f4$var$DEFAULT_HEIGHT : 0; | ||
return { | ||
@@ -334,3 +353,3 @@ layoutInfo: layoutInfo, | ||
// Find the closest item within on either side of the point using the gap width. | ||
let searchRect = new (0, $img26$Rect)(x, Math.max(0, y - this.gap), 1, this.gap * 2); | ||
let searchRect = new (0, $img26$Rect)(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -337,0 +356,0 @@ let key = null; |
@@ -56,4 +56,6 @@ var $fe69e47e38ed0ac4$exports = require("./ListLayout.main.js"); | ||
buildCollection() { | ||
var _collection_head; | ||
this.stickyColumnIndices = []; | ||
let collection = this.virtualizer.collection; | ||
if (((_collection_head = collection.head) === null || _collection_head === void 0 ? void 0 : _collection_head.key) === -1) return []; | ||
for (let column of collection.columns)// The selection cell and any other sticky columns always need to be visible. | ||
@@ -202,3 +204,4 @@ // In addition, row headers need to be in the DOM for accessibility labeling. | ||
let rowHeight = this.getEstimatedRowHeight() + this.gap; | ||
for (let node of (0, $9lycG$reactstatelycollections.getChildNodes)(collection.body, collection)){ | ||
let childNodes = (0, $9lycG$reactstatelycollections.getChildNodes)(collection.body, collection); | ||
for (let node of childNodes){ | ||
// Skip rows before the valid rectangle unless they are already cached. | ||
@@ -217,8 +220,24 @@ if (y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) { | ||
if (y > this.requestedRect.maxY) { | ||
var _children_at; | ||
let rowsAfterRect = collection.size - (children.length + skipped); | ||
let lastNode = (0, $9lycG$reactstatelycollections.getLastItem)(childNodes); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') rowsAfterRect--; | ||
// Estimate the remaining height for rows that we don't need to layout right now. | ||
y += (collection.size - (skipped + children.length)) * rowHeight; | ||
y += rowsAfterRect * rowHeight; | ||
// Always add the loader sentinel if present. This assumes the loader is the last row in the body, | ||
// will need to refactor when handling multi section loading | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader' && ((_children_at = children.at(-1)) === null || _children_at === void 0 ? void 0 : _children_at.layoutInfo.type) !== 'loader') { | ||
let loader = this.buildChild(lastNode, this.padding, y, layoutInfo.key); | ||
loader.layoutInfo.parentKey = layoutInfo.key; | ||
loader.index = collection.size; | ||
width = Math.max(width, loader.layoutInfo.rect.width); | ||
children.push(loader); | ||
y = loader.layoutInfo.rect.maxY; | ||
} | ||
break; | ||
} | ||
} | ||
if (children.length === 0) y = this.virtualizer.visibleRect.maxY; | ||
// Make sure that the table body gets a height if empty or performing initial load | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
if (isEmptyOrLoading) y = this.virtualizer.visibleRect.maxY; | ||
else y -= this.gap; | ||
@@ -358,2 +377,5 @@ rect.width = width; | ||
} | ||
// Always include loading sentinel even when virtualized, we assume it is always the last child for now | ||
let lastRow = node.children.at(-1); | ||
if ((lastRow === null || lastRow === void 0 ? void 0 : lastRow.layoutInfo.type) === 'loader') res.push(lastRow.layoutInfo); | ||
break; | ||
@@ -433,3 +455,3 @@ } | ||
// Find the closest item within on either side of the point using the gap width. | ||
let searchRect = new (0, $9lycG$reactstatelyvirtualizer.Rect)(x, Math.max(0, y - this.gap), 1, this.gap * 2); | ||
let searchRect = new (0, $9lycG$reactstatelyvirtualizer.Rect)(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -436,0 +458,0 @@ let key = null; |
import {ListLayout as $61ef60fc9b1041f4$export$cacbb3924155d68e} from "./ListLayout.module.js"; | ||
import {getChildNodes as $bmsJv$getChildNodes} from "@react-stately/collections"; | ||
import {getChildNodes as $bmsJv$getChildNodes, getLastItem as $bmsJv$getLastItem} from "@react-stately/collections"; | ||
import {Size as $bmsJv$Size, Rect as $bmsJv$Rect, LayoutInfo as $bmsJv$LayoutInfo} from "@react-stately/virtualizer"; | ||
@@ -50,4 +50,6 @@ import {TableColumnLayout as $bmsJv$TableColumnLayout} from "@react-stately/table"; | ||
buildCollection() { | ||
var _collection_head; | ||
this.stickyColumnIndices = []; | ||
let collection = this.virtualizer.collection; | ||
if (((_collection_head = collection.head) === null || _collection_head === void 0 ? void 0 : _collection_head.key) === -1) return []; | ||
for (let column of collection.columns)// The selection cell and any other sticky columns always need to be visible. | ||
@@ -196,3 +198,4 @@ // In addition, row headers need to be in the DOM for accessibility labeling. | ||
let rowHeight = this.getEstimatedRowHeight() + this.gap; | ||
for (let node of (0, $bmsJv$getChildNodes)(collection.body, collection)){ | ||
let childNodes = (0, $bmsJv$getChildNodes)(collection.body, collection); | ||
for (let node of childNodes){ | ||
// Skip rows before the valid rectangle unless they are already cached. | ||
@@ -211,8 +214,24 @@ if (y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) { | ||
if (y > this.requestedRect.maxY) { | ||
var _children_at; | ||
let rowsAfterRect = collection.size - (children.length + skipped); | ||
let lastNode = (0, $bmsJv$getLastItem)(childNodes); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') rowsAfterRect--; | ||
// Estimate the remaining height for rows that we don't need to layout right now. | ||
y += (collection.size - (skipped + children.length)) * rowHeight; | ||
y += rowsAfterRect * rowHeight; | ||
// Always add the loader sentinel if present. This assumes the loader is the last row in the body, | ||
// will need to refactor when handling multi section loading | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader' && ((_children_at = children.at(-1)) === null || _children_at === void 0 ? void 0 : _children_at.layoutInfo.type) !== 'loader') { | ||
let loader = this.buildChild(lastNode, this.padding, y, layoutInfo.key); | ||
loader.layoutInfo.parentKey = layoutInfo.key; | ||
loader.index = collection.size; | ||
width = Math.max(width, loader.layoutInfo.rect.width); | ||
children.push(loader); | ||
y = loader.layoutInfo.rect.maxY; | ||
} | ||
break; | ||
} | ||
} | ||
if (children.length === 0) y = this.virtualizer.visibleRect.maxY; | ||
// Make sure that the table body gets a height if empty or performing initial load | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
if (isEmptyOrLoading) y = this.virtualizer.visibleRect.maxY; | ||
else y -= this.gap; | ||
@@ -352,2 +371,5 @@ rect.width = width; | ||
} | ||
// Always include loading sentinel even when virtualized, we assume it is always the last child for now | ||
let lastRow = node.children.at(-1); | ||
if ((lastRow === null || lastRow === void 0 ? void 0 : lastRow.layoutInfo.type) === 'loader') res.push(lastRow.layoutInfo); | ||
break; | ||
@@ -427,3 +449,3 @@ } | ||
// Find the closest item within on either side of the point using the gap width. | ||
let searchRect = new (0, $bmsJv$Rect)(x, Math.max(0, y - this.gap), 1, this.gap * 2); | ||
let searchRect = new (0, $bmsJv$Rect)(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -430,0 +452,0 @@ let key = null; |
@@ -51,3 +51,3 @@ import { DropTarget, DropTargetDelegate, ItemDropTarget, Key, Node, Collection, LayoutDelegate } from "@react-types/shared"; | ||
update(invalidationContext: InvalidationContext<O>): void; | ||
getLayoutInfo(key: Key): LayoutInfo; | ||
getLayoutInfo(key: Key): LayoutInfo | null; | ||
getContentSize(): Size; | ||
@@ -54,0 +54,0 @@ getVisibleLayoutInfos(rect: Rect): LayoutInfo[]; |
@@ -89,20 +89,32 @@ var $5TwTj$reactstatelyvirtualizer = require("@react-stately/virtualizer"); | ||
}; | ||
let collection = this.virtualizer.collection; | ||
let skeletonCount = 0; | ||
for (let node of this.virtualizer.collection)if (node.type === 'skeleton') { | ||
// Add skeleton cards until every column has at least one, and we fill the viewport. | ||
let startingHeights = [ | ||
...columnHeights | ||
]; | ||
while(!columnHeights.every((h, i)=>h !== startingHeights[i]) || Math.min(...columnHeights) < this.virtualizer.visibleRect.height){ | ||
var _this_layoutInfos_get; | ||
let key = `${node.key}-${skeletonCount++}`; | ||
let content = ((_this_layoutInfos_get = this.layoutInfos.get(key)) === null || _this_layoutInfos_get === void 0 ? void 0 : _this_layoutInfos_get.content) || { | ||
...node | ||
}; | ||
addNode(key, content); | ||
} | ||
break; | ||
} else addNode(node.key, node); | ||
// Reset all columns to the maximum for the next section | ||
let maxHeight = Math.max(...columnHeights); | ||
for (let node of collection){ | ||
if (node.type === 'skeleton') { | ||
// Add skeleton cards until every column has at least one, and we fill the viewport. | ||
let startingHeights = [ | ||
...columnHeights | ||
]; | ||
while(!columnHeights.every((h, i)=>h !== startingHeights[i]) || Math.min(...columnHeights) < this.virtualizer.visibleRect.height){ | ||
var _this_layoutInfos_get; | ||
let key = `${node.key}-${skeletonCount++}`; | ||
let content = ((_this_layoutInfos_get = this.layoutInfos.get(key)) === null || _this_layoutInfos_get === void 0 ? void 0 : _this_layoutInfos_get.content) || { | ||
...node | ||
}; | ||
addNode(key, content); | ||
} | ||
break; | ||
} else if (node.type !== 'loader') addNode(node.key, node); | ||
} | ||
// Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out. | ||
// Add it under the first column for simplicity | ||
let lastNode = collection.getItem(collection.getLastKey()); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') { | ||
let rect = new (0, $5TwTj$reactstatelyvirtualizer.Rect)(horizontalSpacing, columnHeights[0], itemWidth, 0); | ||
let layoutInfo = new (0, $5TwTj$reactstatelyvirtualizer.LayoutInfo)('loader', lastNode.key, rect); | ||
newLayoutInfos.set(lastNode.key, layoutInfo); | ||
} | ||
// Reset all columns to the maximum for the next section. If loading, set to 0 so virtualizer doesn't render its body since there aren't items to render | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
let maxHeight = isEmptyOrLoading ? 0 : Math.max(...columnHeights); | ||
this.contentSize = new (0, $5TwTj$reactstatelyvirtualizer.Size)(this.virtualizer.visibleRect.width, maxHeight); | ||
@@ -120,3 +132,3 @@ this.layoutInfos = newLayoutInfos; | ||
let layoutInfos = []; | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key)) layoutInfos.push(layoutInfo); | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') layoutInfos.push(layoutInfo); | ||
return layoutInfos; | ||
@@ -123,0 +135,0 @@ } |
@@ -83,20 +83,32 @@ import {LayoutInfo as $lFzwI$LayoutInfo, Size as $lFzwI$Size, Rect as $lFzwI$Rect, Point as $lFzwI$Point, Layout as $lFzwI$Layout} from "@react-stately/virtualizer"; | ||
}; | ||
let collection = this.virtualizer.collection; | ||
let skeletonCount = 0; | ||
for (let node of this.virtualizer.collection)if (node.type === 'skeleton') { | ||
// Add skeleton cards until every column has at least one, and we fill the viewport. | ||
let startingHeights = [ | ||
...columnHeights | ||
]; | ||
while(!columnHeights.every((h, i)=>h !== startingHeights[i]) || Math.min(...columnHeights) < this.virtualizer.visibleRect.height){ | ||
var _this_layoutInfos_get; | ||
let key = `${node.key}-${skeletonCount++}`; | ||
let content = ((_this_layoutInfos_get = this.layoutInfos.get(key)) === null || _this_layoutInfos_get === void 0 ? void 0 : _this_layoutInfos_get.content) || { | ||
...node | ||
}; | ||
addNode(key, content); | ||
} | ||
break; | ||
} else addNode(node.key, node); | ||
// Reset all columns to the maximum for the next section | ||
let maxHeight = Math.max(...columnHeights); | ||
for (let node of collection){ | ||
if (node.type === 'skeleton') { | ||
// Add skeleton cards until every column has at least one, and we fill the viewport. | ||
let startingHeights = [ | ||
...columnHeights | ||
]; | ||
while(!columnHeights.every((h, i)=>h !== startingHeights[i]) || Math.min(...columnHeights) < this.virtualizer.visibleRect.height){ | ||
var _this_layoutInfos_get; | ||
let key = `${node.key}-${skeletonCount++}`; | ||
let content = ((_this_layoutInfos_get = this.layoutInfos.get(key)) === null || _this_layoutInfos_get === void 0 ? void 0 : _this_layoutInfos_get.content) || { | ||
...node | ||
}; | ||
addNode(key, content); | ||
} | ||
break; | ||
} else if (node.type !== 'loader') addNode(node.key, node); | ||
} | ||
// Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out. | ||
// Add it under the first column for simplicity | ||
let lastNode = collection.getItem(collection.getLastKey()); | ||
if ((lastNode === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') { | ||
let rect = new (0, $lFzwI$Rect)(horizontalSpacing, columnHeights[0], itemWidth, 0); | ||
let layoutInfo = new (0, $lFzwI$LayoutInfo)('loader', lastNode.key, rect); | ||
newLayoutInfos.set(lastNode.key, layoutInfo); | ||
} | ||
// Reset all columns to the maximum for the next section. If loading, set to 0 so virtualizer doesn't render its body since there aren't items to render | ||
let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 || collection.size === 1 && collection.getItem(collection.getFirstKey()).type === 'loader'; | ||
let maxHeight = isEmptyOrLoading ? 0 : Math.max(...columnHeights); | ||
this.contentSize = new (0, $lFzwI$Size)(this.virtualizer.visibleRect.width, maxHeight); | ||
@@ -114,3 +126,3 @@ this.layoutInfos = newLayoutInfos; | ||
let layoutInfos = []; | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key)) layoutInfos.push(layoutInfo); | ||
for (let layoutInfo of this.layoutInfos.values())if (layoutInfo.rect.intersects(rect) || this.virtualizer.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') layoutInfos.push(layoutInfo); | ||
return layoutInfos; | ||
@@ -117,0 +129,0 @@ } |
{ | ||
"name": "@react-stately/layout", | ||
"version": "3.0.0-nightly-74cac946a-250317", | ||
"version": "3.0.0-nightly-77b3442e4-250520", | ||
"description": "Spectrum UI components in React", | ||
@@ -25,8 +25,8 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@react-stately/collections": "3.0.0-nightly-74cac946a-250317", | ||
"@react-stately/table": "3.0.0-nightly-74cac946a-250317", | ||
"@react-stately/virtualizer": "3.0.0-nightly-74cac946a-250317", | ||
"@react-types/grid": "3.0.0-nightly-74cac946a-250317", | ||
"@react-types/shared": "3.0.0-nightly-74cac946a-250317", | ||
"@react-types/table": "3.0.0-nightly-74cac946a-250317", | ||
"@react-stately/collections": "3.0.0-nightly-77b3442e4-250520", | ||
"@react-stately/table": "3.0.0-nightly-77b3442e4-250520", | ||
"@react-stately/virtualizer": "3.0.0-nightly-77b3442e4-250520", | ||
"@react-types/grid": "3.0.0-nightly-77b3442e4-250520", | ||
"@react-types/shared": "3.0.0-nightly-77b3442e4-250520", | ||
"@react-types/table": "3.0.0-nightly-77b3442e4-250520", | ||
"@swc/helpers": "^0.5.0" | ||
@@ -33,0 +33,0 @@ }, |
@@ -123,4 +123,8 @@ /* | ||
let rows = Math.ceil(this.virtualizer!.collection.size / numColumns); | ||
let iterator = this.virtualizer!.collection[Symbol.iterator](); | ||
let collection = this.virtualizer!.collection; | ||
// Make sure to set rows to 0 if we performing a first time load or are rendering the empty state so that Virtualizer | ||
// won't try to render its body | ||
let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader'); | ||
let rows = isEmptyOrLoading ? 0 : Math.ceil(collection.size / numColumns); | ||
let iterator = collection[Symbol.iterator](); | ||
let y = rows > 0 ? minSpace.height : 0; | ||
@@ -140,2 +144,7 @@ let newLayoutInfos = new Map(); | ||
// We will add the loader after the skeletons so skip here | ||
if (node.type === 'loader') { | ||
continue; | ||
} | ||
if (node.type === 'skeleton') { | ||
@@ -182,2 +191,10 @@ skeleton = node; | ||
// Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out. | ||
let lastNode = collection.getItem(collection.getLastKey()!); | ||
if (lastNode?.type === 'loader') { | ||
let rect = new Rect(horizontalSpacing, y, itemWidth, 0); | ||
let layoutInfo = new LayoutInfo('loader', lastNode.key, rect); | ||
newLayoutInfos.set(lastNode.key, layoutInfo); | ||
} | ||
this.layoutInfos = newLayoutInfos; | ||
@@ -187,4 +204,4 @@ this.contentSize = new Size(this.virtualizer!.visibleRect.width, y); | ||
getLayoutInfo(key: Key): LayoutInfo { | ||
return this.layoutInfos.get(key)!; | ||
getLayoutInfo(key: Key): LayoutInfo | null { | ||
return this.layoutInfos.get(key) || null; | ||
} | ||
@@ -199,3 +216,3 @@ | ||
for (let layoutInfo of this.layoutInfos.values()) { | ||
if (layoutInfo.rect.intersects(rect) || this.virtualizer!.isPersistedKey(layoutInfo.key)) { | ||
if (layoutInfo.rect.intersects(rect) || this.virtualizer!.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') { | ||
layoutInfos.push(layoutInfo); | ||
@@ -235,3 +252,3 @@ } | ||
if (this.numColumns === 1) { | ||
let searchRect = new Rect(x, Math.max(0, y - this.gap.height), 1, this.gap.height * 2); | ||
let searchRect = new Rect(x, Math.max(0, y - this.gap.height), 1, Math.max(1, this.gap.height * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -238,0 +255,0 @@ let minDistance = Infinity; |
@@ -32,3 +32,3 @@ /* | ||
estimatedHeadingHeight?: number, | ||
/** | ||
/** | ||
* The fixed height of a loader element in px. This loader is specifically for | ||
@@ -188,3 +188,3 @@ * "load more" elements rendered when loading more rows at the root level or inside nested row/sections. | ||
protected isVisible(node: LayoutNode, rect: Rect): boolean { | ||
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || this.virtualizer!.isPersistedKey(node.layoutInfo.key); | ||
return node.layoutInfo.rect.intersects(rect) || node.layoutInfo.isSticky || node.layoutInfo.type === 'header' || node.layoutInfo.type === 'loader' || this.virtualizer!.isPersistedKey(node.layoutInfo.key); | ||
} | ||
@@ -260,5 +260,9 @@ | ||
let nodes: LayoutNode[] = []; | ||
let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader'); | ||
if (isEmptyOrLoading) { | ||
y = 0; | ||
} | ||
for (let node of collection) { | ||
let rowHeight = (this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT) + this.gap; | ||
// Skip rows before the valid rectangle unless they are already cached. | ||
@@ -274,5 +278,18 @@ if (node.type === 'item' && y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) { | ||
nodes.push(layoutNode); | ||
if (node.type === 'item' && y > this.requestedRect.maxY) { | ||
let itemsAfterRect = collection.size - (nodes.length + skipped); | ||
let lastNode = collection.getItem(collection.getLastKey()!); | ||
if (lastNode?.type === 'loader') { | ||
itemsAfterRect--; | ||
} | ||
if (node.type === 'item' && y > this.requestedRect.maxY) { | ||
y += (collection.size - (nodes.length + skipped)) * rowHeight; | ||
y += itemsAfterRect * rowHeight; | ||
// Always add the loader sentinel if present. This assumes the loader is the last option/row | ||
// will need to refactor when handling multi section loading | ||
if (lastNode?.type === 'loader' && nodes.at(-1)?.layoutInfo.type !== 'loader') { | ||
let loader = this.buildChild(lastNode, this.padding, y, null); | ||
nodes.push(loader); | ||
y = loader.layoutInfo.rect.maxY; | ||
} | ||
break; | ||
@@ -283,3 +300,3 @@ } | ||
y -= this.gap; | ||
y += this.padding; | ||
y += isEmptyOrLoading ? 0 : this.padding; | ||
this.contentSize = new Size(this.virtualizer!.visibleRect.width, y); | ||
@@ -309,2 +326,3 @@ return nodes; | ||
layoutNode.layoutInfo.parentKey = parentKey ?? null; | ||
layoutNode.layoutInfo.allowOverflow = true; | ||
this.layoutNodes.set(node.key, layoutNode); | ||
@@ -324,2 +342,4 @@ return layoutNode; | ||
return this.buildLoader(node, x, y); | ||
case 'separator': | ||
return this.buildItem(node, x, y); | ||
default: | ||
@@ -334,3 +354,5 @@ throw new Error('Unsupported node type: ' + node.type); | ||
rect.width = this.virtualizer!.contentSize.width - this.padding - x; | ||
rect.height = this.loaderHeight || this.rowHeight || this.estimatedRowHeight || DEFAULT_HEIGHT; | ||
// Note that if the user provides isLoading to their sentinel during a case where they only want to render the emptyState, this will reserve | ||
// room for the loader alongside rendering the emptyState | ||
rect.height = node.props.isLoading ? this.loaderHeight ?? this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT : 0; | ||
@@ -517,3 +539,3 @@ return { | ||
// Find the closest item within on either side of the point using the gap width. | ||
let searchRect = new Rect(x, Math.max(0, y - this.gap), 1, this.gap * 2); | ||
let searchRect = new Rect(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -520,0 +542,0 @@ let key: Key | null = null; |
@@ -14,3 +14,3 @@ /* | ||
import {DropTarget, ItemDropTarget, Key} from '@react-types/shared'; | ||
import {getChildNodes} from '@react-stately/collections'; | ||
import {getChildNodes, getLastItem} from '@react-stately/collections'; | ||
import {GridNode} from '@react-types/grid'; | ||
@@ -89,2 +89,6 @@ import {InvalidationContext, LayoutInfo, Point, Rect, Size} from '@react-stately/virtualizer'; | ||
let collection = this.virtualizer!.collection as TableCollection<T>; | ||
if (collection.head?.key === -1) { | ||
return []; | ||
} | ||
for (let column of collection.columns) { | ||
@@ -256,3 +260,4 @@ // The selection cell and any other sticky columns always need to be visible. | ||
let rowHeight = this.getEstimatedRowHeight() + this.gap; | ||
for (let node of getChildNodes(collection.body, collection)) { | ||
let childNodes = getChildNodes(collection.body, collection); | ||
for (let node of childNodes) { | ||
// Skip rows before the valid rectangle unless they are already cached. | ||
@@ -273,4 +278,21 @@ if (y + rowHeight < this.requestedRect.y && !this.isValid(node, y)) { | ||
if (y > this.requestedRect.maxY) { | ||
let rowsAfterRect = collection.size - (children.length + skipped); | ||
let lastNode = getLastItem(childNodes); | ||
if (lastNode?.type === 'loader') { | ||
rowsAfterRect--; | ||
} | ||
// Estimate the remaining height for rows that we don't need to layout right now. | ||
y += (collection.size - (skipped + children.length)) * rowHeight; | ||
y += rowsAfterRect * rowHeight; | ||
// Always add the loader sentinel if present. This assumes the loader is the last row in the body, | ||
// will need to refactor when handling multi section loading | ||
if (lastNode?.type === 'loader' && children.at(-1)?.layoutInfo.type !== 'loader') { | ||
let loader = this.buildChild(lastNode, this.padding, y, layoutInfo.key); | ||
loader.layoutInfo.parentKey = layoutInfo.key; | ||
loader.index = collection.size; | ||
width = Math.max(width, loader.layoutInfo.rect.width); | ||
children.push(loader); | ||
y = loader.layoutInfo.rect.maxY; | ||
} | ||
break; | ||
@@ -280,3 +302,5 @@ } | ||
if (children.length === 0) { | ||
// Make sure that the table body gets a height if empty or performing initial load | ||
let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader'); | ||
if (isEmptyOrLoading) { | ||
y = this.virtualizer!.visibleRect.maxY; | ||
@@ -450,2 +474,8 @@ } else { | ||
} | ||
// Always include loading sentinel even when virtualized, we assume it is always the last child for now | ||
let lastRow = node.children.at(-1); | ||
if (lastRow?.layoutInfo.type === 'loader') { | ||
res.push(lastRow.layoutInfo); | ||
} | ||
break; | ||
@@ -552,3 +582,3 @@ } | ||
// Find the closest item within on either side of the point using the gap width. | ||
let searchRect = new Rect(x, Math.max(0, y - this.gap), 1, this.gap * 2); | ||
let searchRect = new Rect(x, Math.max(0, y - this.gap), 1, Math.max(1, this.gap * 2)); | ||
let candidates = this.getVisibleLayoutInfos(searchRect); | ||
@@ -555,0 +585,0 @@ let key: Key | null = null; |
@@ -143,4 +143,5 @@ /* | ||
let collection = this.virtualizer!.collection; | ||
let skeletonCount = 0; | ||
for (let node of this.virtualizer!.collection) { | ||
for (let node of collection) { | ||
if (node.type === 'skeleton') { | ||
@@ -158,3 +159,3 @@ // Add skeleton cards until every column has at least one, and we fill the viewport. | ||
break; | ||
} else { | ||
} else if (node.type !== 'loader') { | ||
addNode(node.key, node); | ||
@@ -164,4 +165,14 @@ } | ||
// Reset all columns to the maximum for the next section | ||
let maxHeight = Math.max(...columnHeights); | ||
// Always add the loader sentinel if present in the collection so we can make sure it is never virtualized out. | ||
// Add it under the first column for simplicity | ||
let lastNode = collection.getItem(collection.getLastKey()!); | ||
if (lastNode?.type === 'loader') { | ||
let rect = new Rect(horizontalSpacing, columnHeights[0], itemWidth, 0); | ||
let layoutInfo = new LayoutInfo('loader', lastNode.key, rect); | ||
newLayoutInfos.set(lastNode.key, layoutInfo); | ||
} | ||
// Reset all columns to the maximum for the next section. If loading, set to 0 so virtualizer doesn't render its body since there aren't items to render | ||
let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader'); | ||
let maxHeight = isEmptyOrLoading ? 0 : Math.max(...columnHeights); | ||
this.contentSize = new Size(this.virtualizer!.visibleRect.width, maxHeight); | ||
@@ -183,3 +194,3 @@ this.layoutInfos = newLayoutInfos; | ||
for (let layoutInfo of this.layoutInfos.values()) { | ||
if (layoutInfo.rect.intersects(rect) || this.virtualizer!.isPersistedKey(layoutInfo.key)) { | ||
if (layoutInfo.rect.intersects(rect) || this.virtualizer!.isPersistedKey(layoutInfo.key) || layoutInfo.type === 'loader') { | ||
layoutInfos.push(layoutInfo); | ||
@@ -186,0 +197,0 @@ } |
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
570355
6.88%6070
4.71%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated