framer-motion
Version:
A simple and powerful JavaScript animation library
1 lines • 6.96 kB
Source Map (JSON)
{"version":3,"file":"Group.mjs","sources":["../../../../src/components/Reorder/Group.tsx"],"sourcesContent":["\"use client\"\n\nimport { invariant } 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 { motion } from \"../../render/components/motion/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 * @public\n */\n onReorder: (newOrder: 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 ...props\n }: ReorderGroupProps<V, TagName>,\n externalRef?: React.ForwardedRef<any>\n): JSX.Element {\n const Component = useConstant(\n () => motion[as as keyof typeof motion]\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 groupRef = useRef<Element>(null)\n const axis = axisOverride || detectedAxis\n\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 context: ReorderContextProps<V> = {\n axis,\n groupRef,\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 = values.flatMap((value) => {\n const layout = itemLayouts.current.get(value)\n return layout ? [{ value, layout }] : []\n })\n const direction =\n groupRef.current?.ownerDocument.defaultView?.getComputedStyle(\n groupRef.current\n ).direction === \"rtl\"\n ? \"rtl\"\n : \"ltr\"\n const newOrder = checkReorder(\n order,\n item,\n offset,\n velocity,\n axis,\n direction\n )\n\n if (order !== newOrder) {\n isReordering.current = true\n\n const newValues = [...values]\n const measuredIndexes = order.map(({ value }) =>\n values.indexOf(value)\n )\n newOrder.forEach(({ value }, index) => {\n newValues[measuredIndexes[index]] = value\n })\n onReorder(newValues)\n }\n },\n }\n\n useEffect(() => {\n isReordering.current = false\n })\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 {...props} style={groupStyle} ref={setRef} ignoreStrict>\n <ReorderContext.Provider value={context}>\n {children}\n </ReorderContext.Provider>\n </Component>\n )\n}\n\nexport const ReorderGroup = /*@__PURE__*/ forwardRef(ReorderGroupComponent) 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) => void\n } & { ref?: React.ForwardedRef<any> }\n) => ReturnType<typeof ReorderGroupComponent>\n"],"names":[],"mappings":";;;;;;;;;;AAgFM;AAcF;;;AAQA;AACA;AACA;;AAQA;;AAEI;AAA2B;AAC/B;AAEA;;;AAGI;;;;;;;;;;;;;;;;AAmBQ;AACJ;AACA;AAIQ;;AAER;AASA;AACI;AAEA;;;;AAMA;;;;;;AAOR;AACJ;;AAGA;AACM;AACF;;;;AAGM;AACE;;AAEZ;AAEA;;;;AAIG;AACH;AACI;;;AAIJ;AAOJ;AAEO;;"}