{"version":3,"file":"Group.mjs","sources":["../../../../src/components/Reorder/Group.tsx"],"sourcesContent":["\"use client\"\n\nimport { invariant, moveItem } from \"motion-utils\"\nimport * as React from \"react\"\nimport {\n    forwardRef,\n    FunctionComponent,\n    JSX,\n    useEffect,\n    useRef,\n    useState,\n} from \"react\"\nimport { ReorderContext } from \"../../context/ReorderContext\"\nimport type { MotionProxy } from \"../../render/components/create-proxy\"\nimport { HTMLMotionProps } from \"../../render/html/types\"\nimport { useConstant } from \"../../utils/use-constant\"\nimport {\n    DefaultGroupElement,\n    ItemData,\n    ReorderAxis,\n    ReorderContextProps,\n    ReorderElementTag,\n} from \"./types\"\nimport { checkReorder } from \"./utils/check-reorder\"\nimport { detectAxis } from \"./utils/detect-axis\"\n\nexport interface Props<\n    V,\n    TagName extends ReorderElementTag = DefaultGroupElement\n> {\n    /**\n     * A HTML element to render this component as. Defaults to `\"ul\"`.\n     *\n     * @public\n     */\n    as?: TagName\n\n    /**\n     * The axis to reorder along. By default, this is detected from the item\n     * layout. Use `\"xy\"` to explicitly enable wrapped layout reordering.\n     *\n     * @public\n     */\n    axis?: ReorderAxis\n\n    /**\n     * A callback to fire with the new value order. For instance, if the values\n     * are provided as a state from `useState`, this could be the set state function.\n     *\n     * The second argument is the value of the item that moved. Its old and\n     * new positions are `values.indexOf(moved)` and `newOrder.indexOf(moved)`.\n     * This fires for each step of a drag, not once when it ends, so these\n     * describe that step's move.\n     *\n     * @public\n     */\n    onReorder: (newOrder: V[], moved: V) => void\n\n    /**\n     * The latest values state.\n     *\n     * ```jsx\n     * function Component() {\n     *   const [items, setItems] = useState([0, 1, 2])\n     *\n     *   return (\n     *     <Reorder.Group values={items} onReorder={setItems}>\n     *         {items.map((item) => <Reorder.Item key={item} value={item} />)}\n     *     </Reorder.Group>\n     *   )\n     * }\n     * ```\n     *\n     * @public\n     */\n    values: V[]\n}\n\ntype ReorderGroupProps<\n    V,\n    TagName extends ReorderElementTag = DefaultGroupElement\n> = Props<V, TagName> &\n    Omit<HTMLMotionProps<TagName>, \"values\"> &\n    React.PropsWithChildren<{}>\n\nexport function ReorderGroupComponent<\n    V,\n    TagName extends ReorderElementTag = DefaultGroupElement\n>(\n    {\n        children,\n        as = \"ul\" as TagName,\n        axis: axisOverride,\n        onReorder,\n        values,\n        onPointerDownCapture,\n        onClickCapture,\n        ...props\n    }: ReorderGroupProps<V, TagName>,\n    externalRef: React.ForwardedRef<any>,\n    proxy: MotionProxy\n): JSX.Element {\n    const Component = useConstant(\n        () => proxy[as as keyof MotionProxy]\n    ) as FunctionComponent<\n        React.PropsWithChildren<HTMLMotionProps<any> & { ref?: React.Ref<any> }>\n    >\n\n    const itemLayouts = useRef(new Map<V, ItemData<V>[\"layout\"]>())\n    const [detectedAxis, setDetectedAxis] = useState<ReorderAxis>(\"y\")\n    const isReordering = useRef(false)\n    const hasReordered = useRef(false)\n    const groupRef = useRef<Element>(null)\n    const axis = axisOverride || detectedAxis\n\n    process.env.NODE_ENV !== \"production\" &&\n        invariant(\n            Boolean(values),\n            \"Reorder.Group must be provided a values prop\",\n            \"reorder-values\"\n        )\n\n    const valuesSet = new Set(values)\n    itemLayouts.current.forEach((_, value) => {\n        if (!valuesSet.has(value)) itemLayouts.current.delete(value)\n    })\n\n    const getOrder = () =>\n        values.flatMap((value) => {\n            const layout = itemLayouts.current.get(value)\n            return layout ? [{ value, layout }] : []\n        })\n\n    const isRTL = () =>\n        groupRef.current?.ownerDocument.defaultView?.getComputedStyle(\n            groupRef.current\n        ).direction === \"rtl\"\n\n    /**\n     * Write the new order of the measured items back into the slots they\n     * hold in `values`, leaving unmeasured (e.g. virtualized) values in place.\n     */\n    const commit = (newOrder: ItemData<V>[], moved: V) => {\n        let i = 0\n        const newValues = values.map((value) =>\n            itemLayouts.current.has(value) ? newOrder[i++].value : value\n        )\n        newValues.some((value, j) => value !== values[j]) &&\n            onReorder(newValues, moved)\n    }\n\n    const context: ReorderContextProps<V> = {\n        axis,\n        proxy,\n        groupRef,\n        hasReordered,\n        /**\n         * Space grabs and drops the item, arrows along `axis` move\n         * the grabbed item one place (Left and Right swap in RTL) and Escape\n         * restores the order in `from`, the values when it was grabbed. Keys\n         * from children or with modifiers (Alt+Left etc.) are left alone.\n         */\n        onKeyDown: (event, item, from, grab) => {\n            const { key, currentTarget } = event\n            if (\n                event.defaultPrevented ||\n                event.target !== currentTarget ||\n                event.altKey ||\n                event.ctrlKey ||\n                event.metaKey\n            ) {\n                return\n            }\n\n            const order = getOrder()\n            const keyIndex = [\n                \"ArrowUp\",\n                \"ArrowLeft\",\n                \"ArrowDown\",\n                \"ArrowRight\",\n            ].indexOf(key)\n            const isX = keyIndex % 2\n            if (key === \" \") {\n                event.repeat || grab(currentTarget, from ? undefined : values)\n            } else if (!from) {\n                return\n            } else if (key === \"Escape\") {\n                /**\n                 * Values added since the grab keep their slots.\n                 */\n                const kept = from.flatMap((value) =>\n                    order.filter((data) => data.value === value)\n                )\n                commit(\n                    order.map((data) =>\n                        from.includes(data.value) ? kept.shift()! : data\n                    ),\n                    item\n                )\n                grab(currentTarget)\n            } else if (\n                keyIndex < 0 ||\n                (axis !== \"xy\" && axis !== (isX ? \"x\" : \"y\"))\n            ) {\n                return\n            } else {\n                const index = order.findIndex(({ value }) => value === item)\n                const target =\n                    index + (keyIndex > 1 !== !!(isX && isRTL()) ? 1 : -1)\n                index > -1 &&\n                    order[target] &&\n                    commit(moveItem(order, index, target), item)\n            }\n            event.preventDefault()\n        },\n        registerItem: (value, layout) => {\n            itemLayouts.current.set(value, layout)\n\n            if (!axisOverride) {\n                const nextAxis = detectAxis(\n                    values.flatMap((itemValue) => {\n                        const itemLayout = itemLayouts.current.get(itemValue)\n                        return itemLayout ? [itemLayout] : []\n                    })\n                )\n\n                if (nextAxis !== detectedAxis) setDetectedAxis(nextAxis)\n            }\n        },\n        updateOrder: (item, offset, velocity) => {\n            if (isReordering.current) return\n\n            const order = getOrder()\n            const newOrder = checkReorder(\n                order,\n                item,\n                offset,\n                velocity,\n                axis,\n                isRTL() ? \"rtl\" : \"ltr\"\n            )\n\n            if (order !== newOrder) {\n                isReordering.current = true\n                hasReordered.current = true\n                commit(newOrder, item)\n            }\n        },\n    }\n\n    /**\n     * Only clear the guard once the new order has been rendered. Unrelated\n     * commits (useTransition's isPending, stale renders in front of\n     * useDeferredValue) would otherwise re-enable onReorder while the\n     * previous order is still pending.\n     */\n    useEffect(() => {\n        isReordering.current = false\n    }, [values])\n\n    // Combine refs if external ref is provided\n    const setRef = (element: Element | null) => {\n        ;(groupRef as React.MutableRefObject<Element | null>).current = element\n        if (typeof externalRef === \"function\") {\n            externalRef(element)\n        } else if (externalRef) {\n            ;(externalRef as React.MutableRefObject<Element | null>).current =\n                element\n        }\n    }\n\n    /**\n     * Disable browser scroll anchoring on the group container.\n     * When items reorder, scroll anchoring can cause the browser to adjust\n     * the scroll position, which interferes with drag position calculations.\n     */\n    const groupStyle = {\n        overflowAnchor: \"none\" as const,\n        ...props.style,\n    }\n\n    return (\n        <Component\n            {...props}\n            style={groupStyle}\n            ref={setRef}\n            /**\n             * A release that ends a reordering drag isn't a click. Browsers\n             * only click an element holding both the press and release\n             * targets, so whether it was clicked depended on which nodes\n             * React moved, and so on drag direction (#2682): Chrome drops\n             * the click if the dragged item's node moved, others may click\n             * an ancestor. Swallow it here, above every item, either way.\n             * The flag is cleared when the drag ends, a frame after the\n             * release's click, or on the next press. Clicks with no\n             * pointer press behind them (detail 0, e.g. keyboard) are left\n             * alone.\n             */\n            onPointerDownCapture={(event: React.PointerEvent<any>) => {\n                hasReordered.current = false\n                onPointerDownCapture?.(event)\n            }}\n            onClickCapture={(event: React.MouseEvent<any>) => {\n                if (hasReordered.current && event.detail) {\n                    hasReordered.current = false\n                    event.stopPropagation()\n                    event.preventDefault()\n                }\n                onClickCapture?.(event)\n            }}\n            ignoreStrict\n        >\n            <ReorderContext.Provider value={context}>\n                {children}\n            </ReorderContext.Provider>\n        </Component>\n    )\n}\n\n/**\n * Creates `Reorder.Group` rendering the components of `proxy`: `motion`,\n * or `m` to get its features from `LazyMotion`. Its items render the same.\n */\nexport const createReorderGroup = (proxy: MotionProxy) =>\n    forwardRef((props: ReorderGroupProps<any, any>, ref) =>\n        ReorderGroupComponent(props, ref, proxy)\n    ) as <\n        Values extends any[],\n        TagName extends ReorderElementTag = DefaultGroupElement\n    >(\n        props: Omit<\n            ReorderGroupProps<Values[number], TagName>,\n            \"values\" | \"onReorder\"\n        > & {\n            values: Values\n            onReorder: (newOrder: Values, moved: Values[number]) => void\n        } & { ref?: React.ForwardedRef<any> }\n    ) => ReturnType<typeof ReorderGroupComponent>\n"],"names":[],"mappings":";;;;;;;;;AAqFM;AAiBF;;;AAQA;AACA;AACA;AACA;AAEA;;AAOA;;AAEI;AAA2B;AAC/B;AAEA;;AAGQ;AACJ;;AAOJ;;;AAGG;AACH;;AAEI;AAGA;AACI;AACR;AAEA;;;;;AAKI;;;;;AAKG;;AAEC;;;AAII;AACA;;;;AAMJ;AACA;;;;;AAKC;AACD;AACA;AACI;;;;;AAGG;AACH;;AAEG;;AAIH;;;;AASA;;;;AAIA;;;;AAKI;;;;AAIZ;;;;;;;;;;;;;;AAiBI;;AAUA;AACI;AACA;AACA;;;;AAKZ;;;;;AAKG;;AAEC;AACJ;;AAGA;AACM;AACF;;;;AAGM;AACE;;AAEZ;AAEA;;;;AAIG;AACH;AACI;;;AAIJ;AAKQ;;;;;;;;;;;AAWG;AACH;AACI;AACA;AACJ;;AAGQ;;;;AAIJ;AACJ;AAQZ;AAEA;;;AAGG;AACI;;"}