📄 WaterfallLayout.js
/home/palash/git/site/node_modules/react-stately/dist/private/layout/WaterfallLayout.js
Language: js • Lines: 240
import {Layout as $bcf18cb0a16f75f6$export$c84671f46d6a1ca} from "../virtualizer/Layout.js";
import {LayoutInfo as $dcdbc700dd92e06f$export$7e0eeb9da702a085} from "../virtualizer/LayoutInfo.js";
import {Point as $1bbfd9c42d9344e6$export$baf26146a414f24a} from "../virtualizer/Point.js";
import {Rect as $546350db2c358941$export$c79fc6492f3af13d} from "../virtualizer/Rect.js";
import {Size as $266cbd2785be3a2c$export$cb6da89c6af1a8ec} from "../virtualizer/Size.js";

/*
 * Copyright 2024 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */ 




class $5f92551cd3597eb4$var$WaterfallLayoutInfo extends (0, $dcdbc700dd92e06f$export$7e0eeb9da702a085) {
    copy() {
        let res = super.copy();
        res.column = this.column;
        res.index = this.index;
        return res;
    }
    constructor(...args){
        super(...args), this.column = 0, this.index = 0;
    }
}
const $5f92551cd3597eb4$var$DEFAULT_OPTIONS = {
    minItemSize: new (0, $266cbd2785be3a2c$export$cb6da89c6af1a8ec)(200, 200),
    maxItemSize: new (0, $266cbd2785be3a2c$export$cb6da89c6af1a8ec)(Infinity, Infinity),
    minSpace: new (0, $266cbd2785be3a2c$export$cb6da89c6af1a8ec)(18, 18),
    maxSpace: Infinity,
    maxColumns: Infinity,
    dropIndicatorThickness: 2,
    loaderHeight: 48
};
class $5f92551cd3597eb4$export$e9f7cda058ba8df8 extends (0, $bcf18cb0a16f75f6$export$c84671f46d6a1ca) {
    shouldInvalidateLayoutOptions(newOptions, oldOptions) {
        return newOptions.maxColumns !== oldOptions.maxColumns || newOptions.dropIndicatorThickness !== oldOptions.dropIndicatorThickness || !(newOptions.minItemSize || $5f92551cd3597eb4$var$DEFAULT_OPTIONS.minItemSize).equals(oldOptions.minItemSize || $5f92551cd3597eb4$var$DEFAULT_OPTIONS.minItemSize) || !(newOptions.maxItemSize || $5f92551cd3597eb4$var$DEFAULT_OPTIONS.maxItemSize).equals(oldOptions.maxItemSize || $5f92551cd3597eb4$var$DEFAULT_OPTIONS.maxItemSize) || !(newOptions.minSpace || $5f92551cd3597eb4$var$DEFAULT_OPTIONS.minSpace).equals(oldOptions.minSpace || $5f92551cd3597eb4$var$DEFAULT_OPTIONS.minSpace) || newOptions.maxHorizontalSpace !== oldOptions.maxHorizontalSpace || newOptions.loaderHeight !== oldOptions.loaderHeight;
    }
    update(invalidationContext) {
        var _collection_getItem;
        let { minItemSize: minItemSize = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.minItemSize, maxItemSize: maxItemSize = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.maxItemSize, minSpace: minSpace = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.minSpace, maxHorizontalSpace: maxHorizontalSpace = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.maxSpace, maxColumns: maxColumns = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.maxColumns, dropIndicatorThickness: dropIndicatorThickness = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.dropIndicatorThickness, loaderHeight: loaderHeight = $5f92551cd3597eb4$var$DEFAULT_OPTIONS.loaderHeight } = invalidationContext.layoutOptions || {};
        this.dropIndicatorThickness = dropIndicatorThickness;
        let virtualizerWidth = this.virtualizer.size.width;
        // The max item width is always the entire viewport.
        // If the max item height is infinity, scale in proportion to the max width.
        let maxItemWidth = Math.min(maxItemSize.width, virtualizerWidth);
        let maxItemHeight = Number.isFinite(maxItemSize.height) ? maxItemSize.height : Math.floor(minItemSize.height / minItemSize.width * maxItemWidth);
        // Compute the number of rows and columns needed to display the content
        let columns = Math.floor(virtualizerWidth / (minItemSize.width + minSpace.width));
        let numColumns = Math.max(1, Math.min(maxColumns, columns));
        // Compute the available width (minus the space between items)
        let width = virtualizerWidth - minSpace.width * Math.max(0, numColumns);
        // Compute the item width based on the space available
        let itemWidth = Math.floor(width / numColumns);
        itemWidth = Math.max(minItemSize.width, Math.min(maxItemWidth, itemWidth));
        // Compute the item height, which is proportional to the item width
        let t = (itemWidth - minItemSize.width) / Math.max(1, maxItemWidth - minItemSize.width);
        let itemHeight = minItemSize.height + Math.floor((maxItemHeight - minItemSize.height) * t);
        itemHeight = Math.max(minItemSize.height, Math.min(maxItemHeight, itemHeight));
        // Compute the horizontal spacing, content height and horizontal margin
        let horizontalSpacing = Math.min(Math.max(maxHorizontalSpace, minSpace.width), Math.floor((virtualizerWidth - numColumns * itemWidth) / (numColumns + 1)));
        this.margin = Math.floor((virtualizerWidth - numColumns * itemWidth - horizontalSpacing * (numColumns + 1)) / 2);
        // Setup an array of column heights
        let columnHeights = Array(numColumns).fill(minSpace.height);
        let newLayoutInfos = new Map();
        let index = 0;
        let addNode = (key, node)=>{
            let oldLayoutInfo = this.layoutInfos.get(key);
            let height = itemHeight;
            let estimatedSize = true;
            if (oldLayoutInfo) {
                height = oldLayoutInfo.rect.height / oldLayoutInfo.rect.width * itemWidth;
                estimatedSize = invalidationContext.sizeChanged || oldLayoutInfo.estimatedSize || oldLayoutInfo.content !== node;
            }
            // Figure out which column to place the item in, and compute its position.
            // Preserve the previous column index so items don't jump around during resizing unless the number of columns changed.
            let prevColumn = numColumns === this.numColumns && oldLayoutInfo && oldLayoutInfo.index === index && oldLayoutInfo.rect.y < this.virtualizer.visibleRect.maxY ? oldLayoutInfo.column : undefined;
            let column = prevColumn !== null && prevColumn !== void 0 ? prevColumn : columnHeights.reduce((minIndex, h, i)=>h < columnHeights[minIndex] ? i : minIndex, 0);
            let x = horizontalSpacing + column * (itemWidth + horizontalSpacing) + this.margin;
            let y = columnHeights[column];
            let rect = new (0, $546350db2c358941$export$c79fc6492f3af13d)(x, y, itemWidth, height);
            let layoutInfo = new $5f92551cd3597eb4$var$WaterfallLayoutInfo(node.type, key, rect);
            layoutInfo.estimatedSize = estimatedSize;
            layoutInfo.allowOverflow = true;
            layoutInfo.content = node;
            layoutInfo.column = column;
            layoutInfo.index = index++;
            newLayoutInfos.set(key, layoutInfo);
            columnHeights[column] += layoutInfo.rect.height + minSpace.height;
        };
        let collection = this.virtualizer.collection;
        let skeletonCount = 0;
        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.size.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);
        }
        // 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,
        // except if we are performing skeleton loading
        let isEmptyOrLoading = (collection === null || collection === void 0 ? void 0 : collection.size) === 0 && ((_collection_getItem = collection.getItem(collection.getFirstKey())) === null || _collection_getItem === void 0 ? void 0 : _collection_getItem.type) !== 'skeleton';
        let maxHeight = isEmptyOrLoading ? 0 : 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 === null || lastNode === void 0 ? void 0 : lastNode.type) === 'loader') {
            if (skeletonCount > 0 || !lastNode.props.isLoading) loaderHeight = 0;
            const loaderWidth = virtualizerWidth - horizontalSpacing * 2;
            // 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
            let rect = new (0, $546350db2c358941$export$c79fc6492f3af13d)(horizontalSpacing, maxHeight, loaderWidth, loaderHeight);
            let layoutInfo = new (0, $dcdbc700dd92e06f$export$7e0eeb9da702a085)('loader', lastNode.key, rect);
            newLayoutInfos.set(lastNode.key, layoutInfo);
            maxHeight = layoutInfo.rect.maxY;
        }
        this.contentSize = new (0, $266cbd2785be3a2c$export$cb6da89c6af1a8ec)(this.virtualizer.size.width, maxHeight);
        this.layoutInfos = newLayoutInfos;
        this.numColumns = numColumns;
    }
    getLayoutInfo(key) {
        return this.layoutInfos.get(key);
    }
    getContentSize() {
        return this.contentSize;
    }
    getVisibleLayoutInfos(rect) {
        let layoutInfos = [];
        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;
    }
    updateItemSize(key, size) {
        let layoutInfo = this.layoutInfos.get(key);
        if (!size || !layoutInfo) return false;
        if (size.height !== layoutInfo.rect.height) {
            let newLayoutInfo = layoutInfo.copy();
            newLayoutInfo.rect.height = size.height;
            newLayoutInfo.estimatedSize = false;
            this.layoutInfos.set(key, newLayoutInfo);
            return true;
        }
        return false;
    }
    // Override keyboard navigation to work spatially.
    getKeyRightOf(key) {
        let layoutInfo = this.getLayoutInfo(key);
        if (!layoutInfo) return null;
        let rect = new (0, $546350db2c358941$export$c79fc6492f3af13d)(layoutInfo.rect.maxX, layoutInfo.rect.y, this.virtualizer.visibleRect.maxX - layoutInfo.rect.maxX, layoutInfo.rect.height);
        let layoutInfos = this.getVisibleLayoutInfos(rect);
        let bestKey = null;
        let bestDistance = Infinity;
        for (let candidate of layoutInfos){
            if (candidate.key === key) continue;
            // Find the closest item in the x direction with the most overlap in the y direction.
            let deltaX = candidate.rect.x - rect.x;
            let overlapY = Math.min(candidate.rect.maxY, rect.maxY) - Math.max(candidate.rect.y, rect.y);
            let distance = deltaX - overlapY;
            if (distance < bestDistance) {
                bestDistance = distance;
                bestKey = candidate.key;
            }
        }
        return bestKey;
    }
    getKeyLeftOf(key) {
        let layoutInfo = this.getLayoutInfo(key);
        if (!layoutInfo) return null;
        let rect = new (0, $546350db2c358941$export$c79fc6492f3af13d)(0, layoutInfo.rect.y, layoutInfo.rect.x, layoutInfo.rect.height);
        let layoutInfos = this.getVisibleLayoutInfos(rect);
        let bestKey = null;
        let bestDistance = Infinity;
        for (let candidate of layoutInfos){
            if (candidate.key === key) continue;
            // Find the closest item in the x direction with the most overlap in the y direction.
            let deltaX = rect.maxX - candidate.rect.maxX;
            let overlapY = Math.min(candidate.rect.maxY, rect.maxY) - Math.max(candidate.rect.y, rect.y);
            let distance = deltaX - overlapY;
            if (distance < bestDistance) {
                bestDistance = distance;
                bestKey = candidate.key;
            }
        }
        return bestKey;
    }
    // This overrides the default behavior of shift selection to work spatially
    // rather than following the order of the items in the collection (which may appear unpredictable).
    getKeyRange(from, to) {
        let fromLayoutInfo = this.getLayoutInfo(from);
        let toLayoutInfo = this.getLayoutInfo(to);
        if (!fromLayoutInfo || !toLayoutInfo) return [];
        // Find items where half of the area intersects the rectangle
        // formed from the first item to the last item in the range.
        let rect = fromLayoutInfo.rect.union(toLayoutInfo.rect);
        let keys = [];
        for (let layoutInfo of this.layoutInfos.values())if (rect.intersection(layoutInfo.rect).area > layoutInfo.rect.area / 2) keys.push(layoutInfo.key);
        return keys;
    }
    getDropTargetFromPoint(x, y) {
        if (this.layoutInfos.size === 0) return {
            type: 'root'
        };
        x += this.virtualizer.visibleRect.x;
        y += this.virtualizer.visibleRect.y;
        let key = this.virtualizer.keyAtPoint(new (0, $1bbfd9c42d9344e6$export$baf26146a414f24a)(x, y));
        if (key == null) return {
            type: 'root'
        };
        // Only support "on" drop position in waterfall layout.
        // Reordering doesn't make sense because the items don't have a deterministic order.
        return {
            type: 'item',
            key: key,
            dropPosition: 'on'
        };
    }
    constructor(...args){
        super(...args), this.contentSize = new (0, $266cbd2785be3a2c$export$cb6da89c6af1a8ec)(), this.layoutInfos = new Map(), this.numColumns = 0, this.dropIndicatorThickness = 2, this.margin = 0;
    }
}


export {$5f92551cd3597eb4$export$e9f7cda058ba8df8 as WaterfallLayout};
//# sourceMappingURL=WaterfallLayout.js.map