/** * Helpers for working with refs, elements, and props. * @module General utilities */ import { hasOwnProperty } from "@ariakit/utils"; import type { HTMLAttributes, MutableRefObject, ReactElement, Ref, RefCallback, } from "react"; import { isValidElement } from "react"; /** * Sets both a function and object React ref. * * Returns a callback ref cleanup function when one is provided. */ export function setRef( ref: RefCallback | MutableRefObject | null | undefined, value: T, ): void | (() => void) { if (typeof ref === "function") { const cleanup = ref(value); if (typeof cleanup === "function") { return cleanup; } } else if (ref) { ref.current = value; } } /** * Checks if an element is a valid React element with a ref. */ export function isValidElementWithRef

}>( element: unknown, ): element is ReactElement

& { ref?: Ref } { if (!element) return false; if (!isValidElement<{ ref?: Ref }>(element)) return false; if ("ref" in element.props) return true; if ("ref" in element) return true; return false; } /** * Gets the ref property from a React element. */ export function getRefProperty(element: unknown) { if (!isValidElementWithRef(element)) return null; const props = { ...element.props }; return props.ref || element.ref; } /** * Merges two sets of props. */ export function mergeProps>( base: T, overrides: T, ) { const props = { ...base }; for (const key in overrides) { if (!hasOwnProperty(overrides, key)) continue; if (key === "__proto__") continue; if (key === "className") { const prop = "className"; const baseClass = base[prop]; const overrideClass = overrides[prop]; if (baseClass && overrideClass) { props[prop] = `${baseClass} ${overrideClass}`; } else { props[prop] = overrideClass || baseClass; } continue; } if (key === "style") { const prop = "style"; props[prop] = base[prop] ? { ...base[prop], ...overrides[prop] } : overrides[prop]; continue; } const overrideValue = overrides[key]; if (overrideValue === undefined) continue; if (key.startsWith("on")) { if (typeof overrideValue !== "function") { // Skip non-function event handler overrides to preserve base handlers. continue; } const baseValue = base[key]; if (typeof baseValue === "function") { type EventKey = Extract, `on${string}`>; props[key as EventKey] = (...args) => { overrideValue(...args); baseValue(...args); }; continue; } } props[key] = overrideValue; } return props; }