/* @oracle/oraclejet-preact: undefined */
'use strict';

var jsxRuntime = require('preact/jsx-runtime');
var preact = require('preact');
var compat = require('preact/compat');
var hooks = require('preact/hooks');
var mergeProps = require('./mergeProps-e3da7237.js');
var collectionUtils = require('./collectionUtils-f1d3183f.js');
var ReorderableContext = require('./ReorderableContext-16396e17.js');
var useNavigation = require('./useNavigation-fa3f5a50.js');
require('./LayerHost-3cd73603.js');
var useSelection = require('./useSelection-37939f09.js');
var clientHints = require('./clientHints-c40c5250.js');
var classNames = require('./classNames-c14c6ef3.js');


var HiddenAccessible = require('./HiddenAccessible-087eb49a.js');
var LoadMoreCollection = require('./LoadMoreCollection-eb1f2603.js');
var PRIVATE_BaseCardView_themes_BaseCardViewStyles_css = require('./PRIVATE_BaseCardView/themes/BaseCardViewStyles.css.js');
var refUtils = require('./refUtils-a9872e75.js');
var logger = require('./logger-2b636482.js');
var tabbableUtils = require('./tabbableUtils-22a50472.js');
var useId = require('./useId-6c0eeb27.js');
var useAnimation = require('./useAnimation-65c0e455.js');
var useResizeObserver = require('./useResizeObserver-a9e97180.js');
var TabbableModeContext = require('./TabbableModeContext-e99d527e.js');
var FocusTrap = require('./FocusTrap-4873a3f2.js');
var useUser = require('./useUser-1295b5cb.js');
var keys = require('./keys-25710458.js');
require('./StyledCheckbox-9047ed7d.js');

require('./CollectionInteractionContext-f84ff7a1.js');
var useReorderableItem = require('./useReorderableItem-64be8d2b.js');
var useReorderableContext = require('./useReorderableContext-92514102.js');
var Grid = require('./Grid-cef20569.js');
var Flex = require('./Flex-3a257bc3.js');
var PRIVATE_BaseCardView_themes_BaseCardViewContract_css = require('./PRIVATE_BaseCardView/themes/BaseCardViewContract.css.js');
var Skeleton = require('./Skeleton-8e35c7d1.js');

var useAddBusyState = require('./useAddBusyState-d19ae1fa.js');

/**
 * Item selector for CardView components
 */
const CARDVIEW_ITEM_SELECTOR = '[data-oj-card-item]';
const emptyKeys = { all: false, keys: new Set() };
/**
 * A mapping of gutterSize to concrete size in pixels
 */
const gutterSizeToPX = {
    xs: 12,
    sm: 16,
    md: 24,
    lg: 48,
    xl: 64
};
const getColCount = (gapWidth, cardWidth, rootWidth) => {
    if (!cardWidth || !rootWidth)
        return 0;
    // if we have n cards, we have n-1 gaps
    const colCount = Math.floor((rootWidth + gapWidth) / (gapWidth + cardWidth));
    return Math.max(1, colCount);
};

/**
 * The internal component used to render a reorderable item in CardView.
 */
function BaseCardViewReorderableItem({ children, itemKey, itemIndex, isCurrentKey, dragKey, setDragKey, onReorder, rootRef, gutterSize, columns }) {
    const itemRef = hooks.useRef(null);
    const isOneColumn = columns === 1;
    const isFirstColumn = itemIndex % columns === 0;
    const isLastColumn = itemIndex % columns === columns - 1;
    const variantClasses = PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.multiVariantStyles({
        itemOverlayFront: gutterSize
    });
    const variantClassesVertical = PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.multiVariantStyles({
        itemOverlayFrontVertical: gutterSize
    });
    const itemStyles = {
        itemOverlayFront: isOneColumn ? variantClassesVertical : variantClasses,
        itemOverlayRear: isOneColumn ? PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemOverlayRearVertical : PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemOverlayRear,
        itemDragger: PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemDragger
    };
    const getItem = hooks.useCallback(() => itemRef?.current?.closest(CARDVIEW_ITEM_SELECTOR), []);
    const setTransferData = hooks.useCallback((event) => {
        event.dataTransfer?.setData('text/cardview-items-key', JSON.stringify(itemKey));
    }, [itemKey]);
    const { rootProps, isDragged, overlayZoneBeforeProps, overlayZoneAfterProps } = useReorderableItem.useReorderableItem({
        isDisabled: onReorder ? false : true,
        dragKey,
        setDragKey,
        onReorder,
        rootRef,
        ref: itemRef,
        getItem,
        setTransferData,
        itemStyles,
        itemSelector: CARDVIEW_ITEM_SELECTOR,
        isVertical: isOneColumn
    });
    // TODO: remove this as part of JET-70023
    const { currentItemKey } = useReorderableContext.useReorderableContext();
    hooks.useEffect(() => {
        if (isCurrentKey) {
            currentItemKey.current = itemKey;
        }
    }, [isCurrentKey, itemKey, currentItemKey]);
    const { direction } = useUser.useUser();
    const isRtl = direction === 'rtl';
    const overlayZoneBeforeClass = classNames.classNames([
        overlayZoneBeforeProps?.class,
        !isOneColumn && !isRtl && isFirstColumn && PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemOverlayFrontEdge,
        !isOneColumn && isRtl && isLastColumn && PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemOverlayFrontEdge
    ]);
    const overlayZoneAfterClass = classNames.classNames([
        overlayZoneAfterProps?.class,
        !isOneColumn && !isRtl && isLastColumn && PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemOverlayRearEdge,
        !isOneColumn && isRtl && isFirstColumn && PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemOverlayRearEdge
    ]);
    return (jsxRuntime.jsxs("div", { ...rootProps, children: [children, !isDragged && keys.isKeyDefined(dragKey) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ...overlayZoneBeforeProps, class: overlayZoneBeforeClass }), jsxRuntime.jsx("div", { ...overlayZoneAfterProps, class: overlayZoneAfterClass })] }))] }));
}

/**
 * The internal component used to render a single item in CardView.
 */
function BaseCardViewItem({ children, itemKey, itemData, itemIndex, isTabbable, isCurrentKey, isFocused, isFocusRingVisible, selectedKeys, selectionMode, onSelectionChange, focusBehavior, setIsFocusBehaviorValid, initialAnimation, isGridLayout, updateCardSize, reorderItemProps, onAnimationBusyStart, onAnimationBusyEnd }) {
    const rootRef = hooks.useRef(null);
    const timeoutRef = hooks.useRef();
    const isItemFocused = isFocused && isFocusRingVisible;
    const variantClasses = PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.multiVariantStyles({
        itemFocused: focusBehavior === 'card' && isItemFocused ? 'isItemFocused' : 'isNotItemFocused',
        itemInitialOpacity: initialAnimation ? 'isTransparent' : 'isNotTransparent',
        itemInGridLayout: isGridLayout ? 'isInGridLayout' : 'isNotInGridLayout'
    });
    const classes = classNames.classNames([PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.itemStyle, variantClasses]);
    /*********************************** Tabbable Mode ***********************************/
    // don't bubble Arrow Up/Down/Left/Right events to parent when in isTabbable mode
    // this bubbling causes the outer CollectionFocus ring to change index while in the isTabbable mode.
    const handleKeyDown = (event) => {
        if (focusBehavior === 'card' &&
            isTabbable &&
            (event.key === 'ArrowDown' ||
                event.key === 'ArrowUp' ||
                event.key === 'ArrowLeft' ||
                event.key === 'ArrowRight')) {
            event.stopPropagation();
        }
    };
    /************************************ Card Size ************************************/
    const handleResize = hooks.useCallback((entry) => {
        updateCardSize?.(entry.contentRect.width, entry.contentRect.height);
    }, [updateCardSize]);
    const nullRef = hooks.useRef(null);
    useResizeObserver.useResizeObserver(updateCardSize ? rootRef : nullRef, handleResize);
    /********************************* Focus Behavior *********************************/
    hooks.useLayoutEffect(() => {
        if (focusBehavior === 'content' && isItemFocused) {
            // rootRef is cardview item, its first child is the focus trap div, so its first grand child
            // is the actual content
            const content = rootRef.current?.firstElementChild?.firstElementChild;
            const focusable = tabbableUtils.allTabbableElements(content, true, true);
            if (focusable.length === 0 || focusable.length > 1) {
                logger.warn(`${focusable.length} focusable elements are detected in the card. 'content' focusBehavior is intended only for use when the card has single focusable element. Please specify one focusable element in the card or switch the focusBehavior to 'card'.`);
                setIsFocusBehaviorValid?.(false);
            }
            else {
                focusable[0].focus();
            }
        }
    }, [focusBehavior, isItemFocused, setIsFocusBehaviorValid]);
    /******************************** Initial Animation ********************************/
    // animation for slide up/down
    const { nodeRef: nodeRefSlide, controller: controllerSlide } = useAnimation.useAnimation(initialAnimation || 'none', animationConfigSlide(itemIndex));
    // animation for opacity change
    const { nodeRef: nodeRefOpacity, controller: controllerOpacity } = useAnimation.useAnimation((initialAnimation && 'opacity') || 'none', animationConfigOpacity(itemIndex, timeoutRef, onAnimationBusyEnd));
    if (initialAnimation) {
        onAnimationBusyStart?.();
    }
    // max cap for animation is 1s
    hooks.useEffect(() => {
        if (initialAnimation !== undefined) {
            timeoutRef.current = setTimeout(() => {
                controllerSlide.cancel();
                controllerOpacity.cancel();
                if (rootRef.current) {
                    rootRef.current.style.opacity = '1';
                }
                onAnimationBusyEnd?.();
            }, 1000);
        }
    }, [controllerOpacity, controllerSlide, initialAnimation, onAnimationBusyEnd]);
    // TODO: issue track in JET-57681, we currently have two useAnimation() because the opacity and
    // slide up/down have different durations, and the current API couldn't handle this case, we will
    // update this once the API is updated
    /********************************** Child Component **********************************/
    const itemRefs = hooks.useMemo(() => {
        // NOTE: spot an issue when we click the cards during animation is processing, the cards would not finish
        // the current animations and stay as what it is, e.g. leave itself as half transparent.
        // need to remove nodeRefSlide and nodeRefOpacity to avoid this issue.
        if (initialAnimation === undefined) {
            return refUtils.mergeRefs(rootRef);
        }
        return refUtils.mergeRefs(rootRef, nodeRefSlide, nodeRefOpacity);
    }, [rootRef, nodeRefSlide, nodeRefOpacity, initialAnimation]);
    const rendererContext = collectionUtils.getItemContext({ data: { data: itemData, metadata: { key: itemKey } }, index: itemIndex }, selectedKeys, selectionMode === 'multiple', onSelectionChange);
    const childrenComponent = reorderItemProps ? (jsxRuntime.jsx(BaseCardViewReorderableItem, { ...reorderItemProps, itemKey: itemKey, itemIndex: itemIndex, isCurrentKey: isCurrentKey, children: children(rendererContext) })) : (children(rendererContext));
    return (jsxRuntime.jsx("div", { id: useId.useId(), ref: itemRefs, class: classes, role: "gridcell", "data-oj-card-item": "", "data-oj-key": itemKey, onKeyDown: handleKeyDown, "aria-selected": selectionMode === 'none' ? undefined : keys.containsKey(selectedKeys, itemKey), ...(typeof itemKey === 'number' && { 'data-oj-key-type': 'number' }), children: jsxRuntime.jsx(TabbableModeContext.TabbableModeContext.Provider, { value: { isTabbable }, children: jsxRuntime.jsx(FocusTrap.FocusTrap, { isDisabled: !isTabbable, restoreFocusRef: false, children: childrenComponent }) }) }));
}
const animationConfigSlide = (index) => {
    return {
        animationStates: {
            slideUp: {
                from: {
                    translateY: '50px'
                },
                to: {
                    translateY: '0px'
                },
                options: {
                    duration: 300,
                    delay: index * 50,
                    easing: [0, 0, 0.2, 1]
                }
            },
            slideDown: {
                from: {
                    translateY: '-50px'
                },
                to: {
                    translateY: '0'
                },
                options: {
                    duration: 300,
                    delay: index * 50,
                    easing: [0, 0, 0.2, 1]
                }
            }
        },
        isAnimatedOnMount: true
    };
};
const animationConfigOpacity = (index, timeoutRef, onAnimationBusyEnd) => {
    return {
        animationStates: {
            opacity: {
                from: {
                    opacity: 0
                },
                to: {
                    opacity: 1
                },
                options: {
                    duration: 150,
                    delay: index * 50,
                    easing: 'linear'
                }
            }
        },
        isAnimatedOnMount: true,
        onAnimationEnd: () => {
            onAnimationBusyEnd?.();
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
            }
        }
    };
};
/**
 * Compare the equality between two reorderItemProps, especially useful for memoized items
 */
const _compareReorderItemProps = (props1, props2) => {
    return (props1?.['dragKey'] === props2?.['dragKey'] &&
        props1?.['setDragKey'] === props2?.['setDragKey'] &&
        props1?.['columns'] === props2?.['columns'] &&
        props1?.['gutterSize'] === props2?.['gutterSize'] &&
        props1?.['rootRef'] === props2?.['rootRef']);
};
const MemoizeBaseCardViewItem = compat.memo(BaseCardViewItem, (prev, next) => {
    if (prev && next) {
        return (prev.children === next.children &&
            prev.itemKey === next.itemKey &&
            prev.itemData === next.itemData &&
            prev.itemIndex === next.itemIndex &&
            prev.isTabbable === next.isTabbable &&
            prev.isCurrentKey === next.isCurrentKey &&
            prev.isFocused === next.isFocused &&
            prev.isFocusRingVisible === next.isFocusRingVisible &&
            prev.initialAnimation === next.initialAnimation &&
            prev.focusBehavior === next.focusBehavior &&
            keys.isSameKey(prev.selectedKeys, next.selectedKeys) &&
            prev.selectionMode === next.selectionMode &&
            prev.onSelectionChange === next.onSelectionChange &&
            prev.setIsFocusBehaviorValid === next.setIsFocusBehaviorValid &&
            prev.isGridLayout === next.isGridLayout &&
            prev.updateCardSize === next.updateCardSize &&
            prev.onAnimationBusyStart === next.onAnimationBusyStart &&
            prev.onAnimationBusyEnd === next.onAnimationBusyEnd &&
            _compareReorderItemProps(prev.reorderItemProps, next.reorderItemProps));
    }
    return false;
});

function BaseCardViewLayout({ children, layout, gutterSize, columns }) {
    const gap = PRIVATE_BaseCardView_themes_BaseCardViewContract_css.cardViewVars.gutterSize[gutterSize];
    if (layout === 'grid') {
        return columns < 1 ? null : (jsxRuntime.jsx(Grid.Grid, { gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: gap, children: children }));
    }
    else {
        return (jsxRuntime.jsx(Flex.Flex, { wrap: "wrap", gap: gap, children: children }));
    }
}

/**
 * Allows to specify the time delay for rendering the component
 **/
const timerValue = 50;
/**
 * BaseCardViewSkeletonContainer renders skeletons after 50ms threshold defined in the Redwood spec
 **/
function BaseCardViewSkeletonContainer({ children }) {
    const [isVisible, setIsVisible] = hooks.useState(false);
    hooks.useEffect(() => {
        setTimeout(() => {
            setIsVisible(true);
        }, timerValue);
    }, []);
    return isVisible ? jsxRuntime.jsx(preact.Fragment, { children: children }) : jsxRuntime.jsx("div", { class: LoadMoreCollection.LOADMORE_STYLE_CLASS });
}

const DEFAULT_SKELETON_NUMBER = 25;
const DEFAULT_SKELETON_WIDTH = '80x';
const DEFAULT_SKELETON_HEIGHT = '85x';
/**
 * A function that returns an array of skeletons based on count
 * @param count the number of skeletons
 */
const getSkeletonsByCount = (layout, count = DEFAULT_SKELETON_NUMBER, width, height, isUnique = true) => {
    const sw = width || (layout === 'flex' ? DEFAULT_SKELETON_WIDTH : '100%');
    const sh = height || DEFAULT_SKELETON_HEIGHT;
    return [...Array(count)].map((_v, i) => (
    // create unique keys if needed, otherwise the animations won't start at the same time, because the old copy of skeletons
    // could be reused, even the key is specified by useId(), combine count with time to make the key unique.
    jsxRuntime.jsx(Skeleton.Skeleton, { width: sw, height: sh, borderRadius: "6px" }, isUnique && `${i}${new Date().getTime()}`)));
};
/**
 * When there is no data (i.e. data is an empty array or null, and hasMore is false), we will show 25 card skeletons with default size
 */
const InitialLoadSkeletons = (props) => (jsxRuntime.jsx(BaseCardViewSkeletonContainer, { children: getSkeletonsByCount(props.layout) }));
/**
 * When we are fetching the initial data (i.e. data is an empty array, and hasMore is true), we will show 25 card skeletons with default size,
 * because we don't know the card dimension at this time. Since there is more data to load, we will specify LOADMORE_STYLE_CLASS on skeletons
 */
const DefaultLoadMoreSkeletons = (props) => {
    const firstSkeleton = (jsxRuntime.jsx("div", { class: LoadMoreCollection.LOADMORE_STYLE_CLASS, children: getSkeletonsByCount(props.layout, 1) }));
    const otherSkeletons = getSkeletonsByCount(props.layout, DEFAULT_SKELETON_NUMBER - 1);
    const skeletons = [firstSkeleton, ...otherSkeletons];
    return jsxRuntime.jsx(BaseCardViewSkeletonContainer, { children: skeletons });
};
/**
 * During the render that we measure and update card dimension, we will hide skeletons temporarily
 * to avoid showing incorrect skeletons
 */
const InvisibleSkeletons = () => {
    return jsxRuntime.jsx("div", { class: LoadMoreCollection.LOADMORE_STYLE_CLASS });
};
const LoadMoreSkeletons = (props) => {
    if (props.colCount < 1 || props.cardWidth == null || props.cardHeight == null) {
        // if column count / card width / card height are invalid, we will show the invisible skeletons
        return jsxRuntime.jsx(InvisibleSkeletons, {});
    }
    else {
        // after initial renders, show skeletons with correct dimension and count
        const skeletons = [];
        const firstRowColCount = props.colCount - (props.totalCount % props.colCount);
        const isOnlyOneRow = firstRowColCount === props.colCount;
        const cardWidth = `calc(${props.cardWidth}px)`;
        const cardHeight = props.cardHeight
            ? `calc(${props.cardHeight}px)`
            : DEFAULT_SKELETON_HEIGHT;
        const width = props.layout === 'flex' ? cardWidth : '100%';
        const height = props.layout === 'flex' || isOnlyOneRow ? cardHeight : '100%';
        // first row
        const firstSkeleton = (jsxRuntime.jsx("div", { class: LoadMoreCollection.LOADMORE_STYLE_CLASS, children: getSkeletonsByCount(props.layout, 1, width, height) }));
        skeletons.push(firstSkeleton);
        skeletons.push(getSkeletonsByCount(props.layout, firstRowColCount - 1, width, height));
        // second row
        if (!isOnlyOneRow) {
            skeletons.push(getSkeletonsByCount(props.layout, props.colCount, width, cardHeight));
        }
        return jsxRuntime.jsx(BaseCardViewSkeletonContainer, { children: skeletons });
    }
};
const CustomInitialSkeletions = (props) => {
    const context = { loadingStatus: 'initial' };
    return (jsxRuntime.jsx(BaseCardViewSkeletonContainer, { children: renderMultipleSkeletons(context, 25, props.skeletonRenderer) }));
};
const CustomLoadMoreSkeletons = (props) => {
    const skeletons = [];
    if (props.isRenderAsInitial) {
        const context = { loadingStatus: 'initial' };
        skeletons.push(jsxRuntime.jsx("div", { class: LoadMoreCollection.LOADMORE_STYLE_CLASS, children: props.skeletonRenderer(context) }));
        skeletons.push(renderMultipleSkeletons(context, 24, props.skeletonRenderer));
    }
    else {
        if (props.colCount === 0) {
            return null;
        }
        const cardWidth = `calc(${props.cardWidth}px)`;
        const cardHeight = `calc(${props.cardHeight}px)`;
        const firstRowColCount = props.colCount - (props.totalCount % props.colCount);
        const isOnlyOneRow = firstRowColCount === props.colCount;
        const context = {
            loadingStatus: 'loadMore',
            height: props.layout === 'flex' || isOnlyOneRow ? cardHeight : '100%',
            width: props.layout === 'flex' ? cardWidth : '100%'
        };
        skeletons.push(jsxRuntime.jsx("div", { class: LoadMoreCollection.LOADMORE_STYLE_CLASS, children: props.skeletonRenderer(context) }));
        skeletons.push(Array(renderMultipleSkeletons(context, firstRowColCount - 1, props.skeletonRenderer)));
        if (!isOnlyOneRow) {
            skeletons.push(renderMultipleSkeletons(context, props.colCount, props.skeletonRenderer));
        }
    }
    return jsxRuntime.jsx(BaseCardViewSkeletonContainer, { children: skeletons });
};
const renderMultipleSkeletons = (context, count, skeletonRenderer) => {
    return Array(count).fill(skeletonRenderer(context));
};

const useCardViewReorder = ({ onReorder, gutterSize, columns, rootRef, scrollCurrItemIntoView }) => {
    const isReorderEnabled = onReorder != undefined;
    const [dragKey, setDragKey] = hooks.useState(undefined);
    const { reorderProps, reorderContext, reorderInstructionsId, reorderInstructions } = useReorderableItem.useReorderable({
        onReorder,
        rootRef,
        itemSelector: CARDVIEW_ITEM_SELECTOR,
        isDisabled: !isReorderEnabled,
        columns
    });
    if (!isReorderEnabled) {
        return {
            reorderProps,
            reorderContext,
            reorderInstructionsId,
            reorderInstructions
        };
    }
    const handleDragOver = (event) => {
        collectionUtils.dragToScroll(event, dragKey !== undefined, rootRef.current);
        // during mouse reordering, the drag item might not be the same as current item,
        // so we should disallow current item scroll into view
        scrollCurrItemIntoView.current = false;
    };
    const reorderEventProps = { ...reorderProps, onDragOver: handleDragOver };
    const reorderItemProps = {
        dragKey,
        setDragKey,
        onReorder,
        rootRef,
        gutterSize,
        columns
    };
    return {
        reorderProps: reorderEventProps,
        reorderItemProps,
        reorderContext,
        reorderInstructionsId,
        reorderInstructions
    };
};

const BaseCardViewImpl = compat.forwardRef(({ children, data, onLoadMore = () => { }, hasMore = false, getRowKey, currentItemOverride, onPersistCurrentItem, selectionMode = 'none', selectedKeys = emptyKeys, onSelectionChange, onReorder, viewportConfig, focusBehavior = 'card', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, initialAnimation = 'slideUp', gutterSize = 'sm', columns, layout, cardSize, updateCardSize, skeletonRenderer }, ref) => {
    const rootRef = hooks.useRef(null);
    hooks.useImperativeHandle(ref, () => rootRef.current, [rootRef]);
    // tracking the anchor key which is used for shift+click selection
    const anchorKey = hooks.useRef();
    const gutterSizePX = gutterSizeToPX[gutterSize];
    const layoutProps = { layout, columns, gutterSize };
    //setup busy start and busy end callbacks for the initial animation
    const { onBusyStart: onAnimationBusyStart, onBusyEnd: onAnimationBusyEnd } = useAddBusyState.useAddBusyState('handling initial animation');
    const isInitialAnimation = hooks.useRef(true);
    hooks.useEffect(() => {
        if (cardSize.height != undefined && cardSize.width != undefined) {
            isInitialAnimation.current = false;
        }
    }, [cardSize]);
    const ariaMultiSelectable = selectionMode === 'none' ? undefined : selectionMode === 'multiple';
    const ariaRowcount = 1;
    const ariaColcount = data ? data.length : -1;
    const [isFocusBehaviorValid, setIsFocusBehaviorValid] = hooks.useState(true);
    const validFocusBehavior = isFocusBehaviorValid ? focusBehavior : 'card';
    // ensure current item is scrolled into view when current item is updated
    // during reordering, enable that during keyboard reordering, disable that during mouse reordering
    const scrollCurrItemIntoView = hooks.useRef(false);
    const isReorderEnabled = onReorder != undefined;
    const isFetching = hooks.useRef(false);
    /*****************************************************************************************/
    /*********************** Current Key, Focus Ring and Tabbable Mode ***********************/
    /*****************************************************************************************/
    const arrowKeyAndHandlers = {
        ArrowUp: (key) => collectionUtils.getPrevNextKeyByCount(data, getRowKey, key, -columns),
        ArrowDown: (key) => collectionUtils.getPrevNextKeyByCount(data, getRowKey, key, columns),
        ArrowLeft: (key) => collectionUtils.getPrevNextKeyByCount(data, getRowKey, key, -1),
        ArrowRight: (key) => collectionUtils.getPrevNextKeyByCount(data, getRowKey, key, 1)
    };
    const { navigationProps, currentKey, isCurrentItemOverridden, showFocusRing, isCurrentTabbableKey } = useNavigation.useNavigation(data, rootRef, idExtractor, CARDVIEW_ITEM_SELECTOR, arrowKeyAndHandlers, currentItemOverride, onPersistCurrentItem, viewportConfig, undefined, selectionMode !== 'multiple', validFocusBehavior === 'card', validFocusBehavior === 'card', false);
    /*****************************************************************************************/
    /*************************************** Selection ***************************************/
    /*****************************************************************************************/
    const handleSelectionChange = hooks.useCallback((detail) => {
        if (onSelectionChange) {
            if (detail.value.all === false && detail.value.keys.size > 0) {
                // during regular selection, the last key selected by a user gesture is the anchor key
                anchorKey.current = Array.from(detail.value.keys.values()).pop();
            }
            onSelectionChange(detail);
        }
    }, [anchorKey, onSelectionChange]);
    const handleSelectionRangeChange = hooks.useCallback((detail) => {
        if (data && onSelectionChange) {
            const value = collectionUtils.handleSelectionRange(detail, data, getRowKey);
            // for non-virtualized CardView, value should always be an array
            if (Array.isArray(value)) {
                if (detail.eventType === 'shiftSpace') {
                    // during range selection by shift+space, the first key selected by a user gesture is the anchor key
                    anchorKey.current = detail.value.start;
                }
                else {
                    // during range selection by shift+click/arrow keys, the last key selected by a user gesture is the anchor key
                    anchorKey.current = detail.value.end;
                }
                onSelectionChange({ value: { all: false, keys: new Set(value) }, target: null });
            }
        }
    }, [data, onSelectionChange, getRowKey]);
    const { selectionProps } = useSelection.useSelection((element) => {
        if (element === rootRef.current) {
            return currentKey === undefined ? null : currentKey;
        }
        return collectionUtils.keyExtractor(element, CARDVIEW_ITEM_SELECTOR);
    }, selectedKeys, selectionMode, false, 'toggle', handleSelectionChange, anchorKey.current, currentKey, (currentKey, isPrev) => collectionUtils.getPrevNextKeyByCount(data, getRowKey, currentKey, isPrev ? -columns : columns), (currentKey, isPrev) => collectionUtils.getPrevNextKeyByCount(data, getRowKey, currentKey, isPrev ? -1 : 1), (key) => {
        if (rootRef.current) {
            const elem = collectionUtils.findElementByKey(rootRef.current, key, CARDVIEW_ITEM_SELECTOR);
            if (elem) {
                elem.scrollIntoView({ block: 'nearest' });
            }
        }
    }, handleSelectionRangeChange);
    /*****************************************************************************************/
    /**************************************** Reorder ****************************************/
    /*****************************************************************************************/
    const { reorderProps, reorderItemProps, reorderContext, reorderInstructionsId, reorderInstructions } = useCardViewReorder({
        onReorder,
        gutterSize,
        columns,
        rootRef,
        scrollCurrItemIntoView
    });
    const reorderAcc = isReorderEnabled && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("span", { id: reorderInstructionsId, children: jsxRuntime.jsx(HiddenAccessible.HiddenAccessible, { children: reorderInstructions }) }) }));
    const handleKeyDown = (event) => {
        // for content focusBehavior, when users tab out, we need to focus back to card grid, to ensure
        // the tabbing order works properly, otherwise the focus might get stuck at the current item
        if (validFocusBehavior === 'content' && event.key === 'Tab') {
            rootRef.current?.focus({ preventScroll: true });
        }
        // during keyboard reordering, the current item should be scrolled into view
        const keyHandlers = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
        if (isReorderEnabled &&
            (event.metaKey || event.ctrlKey) &&
            event.shiftKey &&
            keyHandlers.includes(event.key)) {
            scrollCurrItemIntoView.current = true;
        }
    };
    hooks.useEffect(() => {
        if (isReorderEnabled && currentKey != null && rootRef.current) {
            const elem = collectionUtils.findElementByKey(rootRef.current, currentKey, CARDVIEW_ITEM_SELECTOR);
            // make sure current item is visible during reordering
            // scroll the current item into view only when the current item is updated internally
            if (elem && scrollCurrItemIntoView.current && !isCurrentItemOverridden) {
                elem.scrollIntoView({ block: 'nearest' });
                scrollCurrItemIntoView.current = false;
            }
        }
        // since the currentKey will not change during keyboard reordering,
        // listen to data to ensure cardview scroll into view
    }, [currentKey, data, isReorderEnabled, isCurrentItemOverridden]);
    /*****************************************************************************************/
    /************************************* Load More *****************************************/
    /*****************************************************************************************/
    const handleLoadMore = hooks.useCallback(() => {
        // check isFetching to avoid multiple load more at the same time (details in JET-61117)
        if (!isFetching.current) {
            isFetching.current = true;
            onLoadMore();
        }
    }, [isFetching, onLoadMore]);
    hooks.useEffect(() => {
        isFetching.current = false;
        // listen to data and hasMore because when one of them gets updated, the fetching is done
    }, [data, hasMore]);
    /*****************************************************************************************/
    /************************************ Child Component ************************************/
    /*****************************************************************************************/
    const childrenComponent = (context) => {
        const itemKey = getRowKey(context.data);
        const isTabbable = isCurrentTabbableKey(itemKey);
        const isFocused = currentKey === itemKey && !isTabbable;
        const isFocusRingVisible = isFocused && showFocusRing;
        const isLastCard = data ? context.index === data.length - 1 : false;
        return (jsxRuntime.jsx(MemoizeBaseCardViewItem, { itemKey: itemKey, isCurrentKey: currentKey === itemKey, itemData: context.data, itemIndex: context.index, isTabbable: isTabbable, isFocused: isFocused, isFocusRingVisible: isFocusRingVisible, isGridLayout: layout === 'grid', selectedKeys: selectedKeys, selectionMode: selectionMode, onSelectionChange: onSelectionChange, focusBehavior: validFocusBehavior, ...(context.index === 0 && isInitialAnimation.current && { onAnimationBusyStart }), ...(isLastCard && isInitialAnimation.current && { onAnimationBusyEnd }), ...(focusBehavior === 'content' && { setIsFocusBehaviorValid }), ...(isInitialAnimation.current && { initialAnimation }), ...(context.index === 0 && !cardSize.width && { updateCardSize }), ...(isReorderEnabled && { reorderItemProps }), children: children }, itemKey));
    };
    // todo: issue track in JET-57952, need a way to show the skeletons in keyboard navigation
    const skeletons = data ? (!data.length && hasMore ? (skeletonRenderer ? (jsxRuntime.jsx(CustomLoadMoreSkeletons, { layout: layout, colCount: columns, totalCount: data.length, cardWidth: cardSize.width, cardHeight: cardSize.height, isRenderAsInitial: true, skeletonRenderer: skeletonRenderer })) : (jsxRuntime.jsx(DefaultLoadMoreSkeletons, { layout: layout }))) : skeletonRenderer ? (jsxRuntime.jsx(CustomLoadMoreSkeletons, { layout: layout, colCount: columns, totalCount: data.length, cardWidth: cardSize.width, cardHeight: cardSize.height, isRenderAsInitial: false, skeletonRenderer: skeletonRenderer })) : (jsxRuntime.jsx(LoadMoreSkeletons, { layout: layout, colCount: columns, totalCount: data.length, cardWidth: cardSize.width, cardHeight: cardSize.height }))) : null;
    viewportConfig = collectionUtils.getViewportConfig(rootRef, viewportConfig);
    const collectionComponent = (jsxRuntime.jsx("div", { role: "row", children: jsxRuntime.jsx(BaseCardViewLayout, { ...layoutProps, children: jsxRuntime.jsx(LoadMoreCollection.LoadMoreCollection, { data: data, hasMore: hasMore, onLoadMore: handleLoadMore, loadMoreIndicator: skeletons, loadMoreThreshold: gutterSizePX + 4, viewportConfig: viewportConfig, children: childrenComponent }) }) }));
    const initialLoadSkeletons = (jsxRuntime.jsx(BaseCardViewLayout, { ...layoutProps, children: skeletonRenderer ? (jsxRuntime.jsx(CustomInitialSkeletions, { skeletonRenderer: skeletonRenderer })) : (jsxRuntime.jsx(InitialLoadSkeletons, { layout: layout })) }));
    const allowBrowserContextMenu = !(clientHints.getClientHints().platform === 'ios' || clientHints.getClientHints().platform === 'android');
    const classArray = [PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.baseStyle];
    if (!allowBrowserContextMenu) {
        classArray.push(PRIVATE_BaseCardView_themes_BaseCardViewStyles_css.styles.userSelect);
    }
    const classes = classNames.classNames(classArray);
    return (jsxRuntime.jsxs("div", { ...mergeProps.mergeProps(navigationProps, selectionProps, reorderProps, {
            onKeyDown: handleKeyDown
        }), ref: rootRef, role: "grid", class: classes, tabIndex: 0, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": isReorderEnabled ? reorderInstructionsId + ' ' + ariaDescribedBy : ariaDescribedBy, "aria-multiselectable": ariaMultiSelectable, "aria-rowcount": ariaRowcount, "aria-colcount": ariaColcount, children: [jsxRuntime.jsx(ReorderableContext.ReorderableContext.Provider, { value: reorderContext, children: !data ? (initialLoadSkeletons) : data.length || hasMore ? (collectionComponent) : (jsxRuntime.jsx(preact.Fragment, {})) }), reorderAcc] }));
});
const idExtractor = (elem) => elem.id;
/**
 * Cast BaseCardViewImpl to generic type, because BaseCardViewImpl would infer the generic type to specific type,
 * i.e infer <K extends string | number, D> to <string | number, unknown>, which will cause type issues in the parent component
 */
const BaseCardView = BaseCardViewImpl;

exports.BaseCardView = BaseCardView;
exports.getColCount = getColCount;
exports.gutterSizeToPX = gutterSizeToPX;
//# sourceMappingURL=BaseCardView-ba63723a.js.map
