{ "name": "editable", "type": "registry:ui", "dependencies": [ "radix-ui" ], "registryDependencies": [ "@diceui/use-as-ref", "@diceui/use-isomorphic-layout-effect", "@diceui/use-lazy-ref" ], "files": [ { "path": "ui/editable.tsx", "type": "registry:ui", "content": "\"use client\";\n\nimport {\n Direction as DirectionPrimitive,\n Slot as SlotPrimitive,\n} from \"radix-ui\";\nimport * as React from \"react\";\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\nimport { VisuallyHiddenInput } from \"@/registry/bases/radix/components/visually-hidden-input\";\nimport { useAsRef } from \"@/registry/bases/radix/hooks/use-as-ref\";\nimport { useIsomorphicLayoutEffect } from \"@/registry/bases/radix/hooks/use-isomorphic-layout-effect\";\nimport { useLazyRef } from \"@/registry/bases/radix/hooks/use-lazy-ref\";\n\nconst ROOT_NAME = \"Editable\";\nconst LABEL_NAME = \"EditableLabel\";\nconst AREA_NAME = \"EditableArea\";\nconst PREVIEW_NAME = \"EditablePreview\";\nconst INPUT_NAME = \"EditableInput\";\nconst TRIGGER_NAME = \"EditableTrigger\";\nconst TOOLBAR_NAME = \"EditableToolbar\";\nconst CANCEL_NAME = \"EditableCancel\";\nconst SUBMIT_NAME = \"EditableSubmit\";\n\ntype Direction = \"ltr\" | \"rtl\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype RootElement = React.ComponentRef;\ntype PreviewElement = React.ComponentRef;\ntype SubmitElement = React.ComponentRef;\ntype InputElement = React.ComponentRef;\n\ninterface StoreState {\n value: string;\n editing: boolean;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: (key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore(\n selector: (state: StoreState) => T,\n ogStore?: Store | null,\n): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(\n () => selector(store.getState()),\n [store, selector],\n );\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface EditableContextValue {\n rootId: string;\n inputId: string;\n labelId: string;\n defaultValue: string;\n onCancel: () => void;\n onEdit: () => void;\n onSubmit: (value: string) => void;\n onEnterKeyDown?: (event: KeyboardEvent) => void;\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n dir?: Direction;\n maxLength?: number;\n placeholder?: string;\n triggerMode: \"click\" | \"dblclick\" | \"focus\";\n autosize: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n}\n\nconst EditableContext = React.createContext(null);\n\nfunction useEditableContext(consumerName: string) {\n const context = React.useContext(EditableContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface EditableProps extends Omit {\n id?: string;\n defaultValue?: string;\n value?: string;\n onValueChange?: (value: string) => void;\n defaultEditing?: boolean;\n editing?: boolean;\n onEditingChange?: (editing: boolean) => void;\n onCancel?: () => void;\n onEdit?: () => void;\n onSubmit?: (value: string) => void;\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n onEnterKeyDown?: (event: KeyboardEvent) => void;\n dir?: Direction;\n maxLength?: number;\n name?: string;\n placeholder?: string;\n triggerMode?: EditableContextValue[\"triggerMode\"];\n autosize?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n}\n\nfunction Editable(props: EditableProps) {\n const {\n value: valueProp,\n defaultValue = \"\",\n defaultEditing,\n editing: editingProp,\n onValueChange,\n onEditingChange,\n onCancel: onCancelProp,\n onEdit: onEditProp,\n onSubmit: onSubmitProp,\n onEscapeKeyDown,\n onEnterKeyDown,\n dir: dirProp,\n maxLength,\n name,\n placeholder,\n triggerMode = \"click\",\n asChild,\n autosize = false,\n disabled,\n required,\n readOnly,\n invalid,\n className,\n id,\n ref,\n ...rootProps\n } = props;\n\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const inputId = React.useId();\n const labelId = React.useId();\n\n const dir = DirectionPrimitive.useDirection(dirProp);\n\n const previousValueRef = React.useRef(defaultValue);\n\n const [formTrigger, setFormTrigger] = React.useState(\n null,\n );\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef(() => ({\n value: valueProp ?? defaultValue,\n editing: editingProp ?? defaultEditing ?? false,\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onEditingChange,\n onCancel: onCancelProp,\n onEdit: onEditProp,\n onSubmit: onSubmitProp,\n onEscapeKeyDown,\n onEnterKeyDown,\n });\n\n const store = React.useMemo(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"string\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"editing\" && typeof value === \"boolean\") {\n stateRef.current.editing = value;\n propsRef.current.onEditingChange?.(value);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n };\n }, [listenersRef, stateRef, propsRef]);\n\n const value = useStore((state) => state.value, store);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n useIsomorphicLayoutEffect(() => {\n if (editingProp !== undefined) {\n store.setState(\"editing\", editingProp);\n }\n }, [editingProp]);\n\n const onCancel = React.useCallback(() => {\n const prevValue = previousValueRef.current;\n store.setState(\"value\", prevValue);\n store.setState(\"editing\", false);\n propsRef.current.onCancel?.();\n }, [store, propsRef]);\n\n const onEdit = React.useCallback(() => {\n const currentValue = store.getState().value;\n previousValueRef.current = currentValue;\n store.setState(\"editing\", true);\n propsRef.current.onEdit?.();\n }, [store, propsRef]);\n\n const onSubmit = React.useCallback(\n (newValue: string) => {\n store.setState(\"value\", newValue);\n store.setState(\"editing\", false);\n propsRef.current.onSubmit?.(newValue);\n },\n [store, propsRef],\n );\n\n const contextValue = React.useMemo(\n () => ({\n rootId,\n inputId,\n labelId,\n defaultValue,\n onSubmit,\n onEdit,\n onCancel,\n onEscapeKeyDown,\n onEnterKeyDown,\n dir,\n maxLength,\n placeholder,\n triggerMode,\n autosize,\n disabled,\n readOnly,\n required,\n invalid,\n }),\n [\n rootId,\n inputId,\n labelId,\n defaultValue,\n onSubmit,\n onCancel,\n onEdit,\n onEscapeKeyDown,\n onEnterKeyDown,\n dir,\n maxLength,\n placeholder,\n triggerMode,\n autosize,\n disabled,\n required,\n readOnly,\n invalid,\n ],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n return (\n \n \n \n {isFormControl && (\n \n )}\n \n \n );\n}\n\ninterface EditableLabelProps extends React.ComponentProps<\"label\"> {\n asChild?: boolean;\n}\n\nfunction EditableLabel(props: EditableLabelProps) {\n const { asChild, className, children, ref, ...labelProps } = props;\n const context = useEditableContext(LABEL_NAME);\n\n const LabelPrimitive = asChild ? SlotPrimitive.Slot : \"label\";\n\n return (\n \n {children}\n \n );\n}\n\ninterface EditableAreaProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction EditableArea(props: EditableAreaProps) {\n const { asChild, className, ref, ...areaProps } = props;\n const context = useEditableContext(AREA_NAME);\n const editing = useStore((state) => state.editing);\n\n const AreaPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n return (\n \n );\n}\n\ninterface EditablePreviewProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction EditablePreview(props: EditablePreviewProps) {\n const {\n onClick: onClickProp,\n onDoubleClick: onDoubleClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n asChild,\n className,\n ref,\n ...previewProps\n } = props;\n\n const context = useEditableContext(PREVIEW_NAME);\n const value = useStore((state) => state.value);\n const editing = useStore((state) => state.editing);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onDoubleClick: onDoubleClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n });\n\n const onTrigger = React.useCallback(() => {\n if (context.disabled || context.readOnly) return;\n context.onEdit();\n }, [context.onEdit, context.disabled, context.readOnly]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented || context.triggerMode !== \"click\") return;\n\n onTrigger();\n },\n [propsRef, onTrigger, context.triggerMode],\n );\n\n const onDoubleClick = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onDoubleClick?.(event);\n if (event.defaultPrevented || context.triggerMode !== \"dblclick\") return;\n\n onTrigger();\n },\n [propsRef, onTrigger, context.triggerMode],\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented || context.triggerMode !== \"focus\") return;\n\n onTrigger();\n },\n [propsRef, onTrigger, context.triggerMode],\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if (event.key === \"Enter\") {\n const nativeEvent = event.nativeEvent;\n if (context.onEnterKeyDown) {\n context.onEnterKeyDown(nativeEvent);\n if (nativeEvent.defaultPrevented) return;\n }\n onTrigger();\n }\n },\n [propsRef, onTrigger, context.onEnterKeyDown],\n );\n\n const PreviewPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n if (editing || context.readOnly) return null;\n\n return (\n \n {value || context.placeholder}\n \n );\n}\n\ninterface EditableInputProps extends React.ComponentProps<\"input\"> {\n asChild?: boolean;\n maxLength?: number;\n}\n\nfunction EditableInput(props: EditableInputProps) {\n const {\n onBlur: onBlurProp,\n onChange: onChangeProp,\n onKeyDown: onKeyDownProp,\n asChild,\n className,\n disabled,\n readOnly,\n required,\n maxLength,\n ref,\n ...inputProps\n } = props;\n\n const context = useEditableContext(INPUT_NAME);\n const store = useStoreContext(INPUT_NAME);\n const value = useStore((state) => state.value);\n const editing = useStore((state) => state.editing);\n const inputRef = React.useRef(null);\n const composedRef = useComposedRefs(ref, inputRef);\n\n const propsRef = useAsRef({\n onBlur: onBlurProp,\n onChange: onChangeProp,\n onKeyDown: onKeyDownProp,\n });\n\n const isDisabled = disabled || context.disabled;\n const isReadOnly = readOnly || context.readOnly;\n const isRequired = required || context.required;\n\n const onAutosize = React.useCallback(\n (target: InputElement) => {\n if (!context.autosize) return;\n\n if (target instanceof HTMLTextAreaElement) {\n target.style.height = \"0\";\n target.style.height = `${target.scrollHeight}px`;\n } else {\n target.style.width = \"0\";\n target.style.width = `${target.scrollWidth + 4}px`;\n }\n },\n [context.autosize],\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent) => {\n if (isDisabled || isReadOnly) return;\n\n propsRef.current.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n const relatedTarget = event.relatedTarget;\n\n const isAction =\n relatedTarget instanceof HTMLElement &&\n (relatedTarget.closest(`[data-slot=\"editable-trigger\"]`) ||\n relatedTarget.closest(`[data-slot=\"editable-cancel\"]`));\n\n if (!isAction) {\n context.onSubmit(value);\n }\n },\n [value, context.onSubmit, propsRef, isDisabled, isReadOnly],\n );\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent) => {\n if (isDisabled || isReadOnly) return;\n\n propsRef.current.onChange?.(event);\n if (event.defaultPrevented) return;\n\n store.setState(\"value\", event.target.value);\n onAutosize(event.target);\n },\n [store, propsRef, onAutosize, isDisabled, isReadOnly],\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (isDisabled || isReadOnly) return;\n\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if (event.key === \"Escape\") {\n const nativeEvent = event.nativeEvent;\n if (context.onEscapeKeyDown) {\n context.onEscapeKeyDown(nativeEvent);\n if (nativeEvent.defaultPrevented) return;\n }\n context.onCancel();\n } else if (event.key === \"Enter\") {\n context.onSubmit(value);\n }\n },\n [\n value,\n context.onSubmit,\n context.onCancel,\n context.onEscapeKeyDown,\n propsRef,\n isDisabled,\n isReadOnly,\n ],\n );\n\n useIsomorphicLayoutEffect(() => {\n if (!editing || isDisabled || isReadOnly || !inputRef.current) return;\n\n const frameId = window.requestAnimationFrame(() => {\n if (!inputRef.current) return;\n\n inputRef.current.focus();\n inputRef.current.select();\n onAutosize(inputRef.current);\n });\n\n return () => {\n window.cancelAnimationFrame(frameId);\n };\n }, [editing, onAutosize, isDisabled, isReadOnly]);\n\n const InputPrimitive = asChild ? SlotPrimitive.Slot : \"input\";\n\n if (!editing && !isReadOnly) return null;\n\n return (\n \n );\n}\n\ninterface EditableTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n forceMount?: boolean;\n}\n\nfunction EditableTrigger(props: EditableTriggerProps) {\n const { asChild, forceMount = false, ref, ...triggerProps } = props;\n const context = useEditableContext(TRIGGER_NAME);\n const editing = useStore((state) => state.editing);\n\n const onTrigger = React.useCallback(() => {\n if (context.disabled || context.readOnly) return;\n context.onEdit();\n }, [context.disabled, context.readOnly, context.onEdit]);\n\n const TriggerPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n if (!forceMount && (editing || context.readOnly)) return null;\n\n return (\n \n );\n}\n\ninterface EditableToolbarProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n orientation?: \"horizontal\" | \"vertical\";\n}\n\nfunction EditableToolbar(props: EditableToolbarProps) {\n const {\n asChild,\n className,\n orientation = \"horizontal\",\n ref,\n ...toolbarProps\n } = props;\n const context = useEditableContext(TOOLBAR_NAME);\n\n const ToolbarPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n return (\n \n );\n}\n\ninterface EditableCancelProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nfunction EditableCancel(props: EditableCancelProps) {\n const { onClick: onClickProp, asChild, ref, ...cancelProps } = props;\n const context = useEditableContext(CANCEL_NAME);\n const editing = useStore((state) => state.editing);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n });\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n if (context.disabled || context.readOnly) return;\n\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n context.onCancel();\n },\n [propsRef, context.onCancel, context.disabled, context.readOnly],\n );\n\n const CancelPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n if (!editing && !context.readOnly) return null;\n\n return (\n \n );\n}\n\ninterface EditableSubmitProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nfunction EditableSubmit(props: EditableSubmitProps) {\n const { onClick: onClickProp, asChild, ref, ...submitProps } = props;\n const context = useEditableContext(SUBMIT_NAME);\n const value = useStore((state) => state.value);\n const editing = useStore((state) => state.editing);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n });\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n if (context.disabled || context.readOnly) return;\n\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n context.onSubmit(value);\n },\n [propsRef, context.onSubmit, value, context.disabled, context.readOnly],\n );\n\n const SubmitPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n if (!editing && !context.readOnly) return null;\n\n return (\n \n );\n}\n\nexport {\n Editable,\n EditableArea,\n EditableCancel,\n EditableInput,\n EditableLabel,\n EditablePreview,\n type EditableProps,\n EditableSubmit,\n EditableToolbar,\n EditableTrigger,\n useStore as useEditable,\n};\n", "target": "" }, { "path": "components/visually-hidden-input.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport * as React from \"react\";\n\ntype InputValue = string[] | string;\n\ninterface VisuallyHiddenInputProps\n extends Omit<\n React.InputHTMLAttributes,\n \"value\" | \"checked\" | \"onReset\"\n > {\n value?: T;\n checked?: boolean;\n control: HTMLElement | null;\n bubbles?: boolean;\n}\n\nfunction VisuallyHiddenInput(\n props: VisuallyHiddenInputProps,\n) {\n const {\n control,\n value,\n checked,\n bubbles = true,\n type = \"hidden\",\n style,\n ...inputProps\n } = props;\n\n const isCheckInput = React.useMemo(\n () => type === \"checkbox\" || type === \"radio\" || type === \"switch\",\n [type],\n );\n const inputRef = React.useRef(null);\n\n const prevValueRef = React.useRef<{\n value: T | boolean | undefined;\n previous: T | boolean | undefined;\n }>({\n value: isCheckInput ? checked : value,\n previous: isCheckInput ? checked : value,\n });\n\n const prevValue = React.useMemo(() => {\n const currentValue = isCheckInput ? checked : value;\n if (prevValueRef.current.value !== currentValue) {\n prevValueRef.current.previous = prevValueRef.current.value;\n prevValueRef.current.value = currentValue;\n }\n return prevValueRef.current.previous;\n }, [isCheckInput, value, checked]);\n\n const [controlSize, setControlSize] = React.useState<{\n width?: number;\n height?: number;\n }>({});\n\n React.useLayoutEffect(() => {\n if (!control) {\n setControlSize({});\n return;\n }\n\n setControlSize({\n width: control.offsetWidth,\n height: control.offsetHeight,\n });\n\n if (typeof window === \"undefined\") return;\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!Array.isArray(entries) || !entries.length) return;\n\n const entry = entries[0];\n if (!entry) return;\n\n let width: number;\n let height: number;\n\n if (\"borderBoxSize\" in entry) {\n const borderSizeEntry = entry.borderBoxSize;\n const borderSize = Array.isArray(borderSizeEntry)\n ? borderSizeEntry[0]\n : borderSizeEntry;\n width = borderSize.inlineSize;\n height = borderSize.blockSize;\n } else {\n width = control.offsetWidth;\n height = control.offsetHeight;\n }\n\n setControlSize({ width, height });\n });\n\n resizeObserver.observe(control, { box: \"border-box\" });\n return () => {\n resizeObserver.disconnect();\n };\n }, [control]);\n\n React.useEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n\n const inputProto = window.HTMLInputElement.prototype;\n const propertyKey = isCheckInput ? \"checked\" : \"value\";\n const eventType = isCheckInput ? \"click\" : \"input\";\n const currentValue = isCheckInput ? checked : value;\n\n const serializedCurrentValue = isCheckInput\n ? checked\n : typeof value === \"object\" && value !== null\n ? JSON.stringify(value)\n : value;\n\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey);\n\n const setter = descriptor?.set;\n\n if (prevValue !== currentValue && setter) {\n const event = new Event(eventType, { bubbles });\n setter.call(input, serializedCurrentValue);\n input.dispatchEvent(event);\n }\n }, [prevValue, value, checked, bubbles, isCheckInput]);\n\n const composedStyle = React.useMemo(() => {\n return {\n ...style,\n ...(controlSize.width !== undefined && controlSize.height !== undefined\n ? controlSize\n : {}),\n border: 0,\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n height: \"1px\",\n margin: \"-1px\",\n overflow: \"hidden\",\n padding: 0,\n position: \"absolute\",\n whiteSpace: \"nowrap\",\n width: \"1px\",\n };\n }, [style, controlSize]);\n\n return (\n \n );\n}\n\nexport { VisuallyHiddenInput };\n", "target": "" }, { "path": "lib/compose-refs.ts", "type": "registry:lib", "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef = React.Ref | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef(ref: PossibleRef, value: T) {\n if (typeof ref === \"function\") {\n return ref(value);\n }\n\n if (ref !== null && ref !== undefined) {\n ref.current = value;\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs(...refs: PossibleRef[]): React.RefCallback {\n return (node) => {\n let hasCleanup = false;\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node);\n if (!hasCleanup && typeof cleanup === \"function\") {\n hasCleanup = true;\n }\n return cleanup;\n });\n\n // React <19 will log an error to the console if a callback ref returns a\n // value. We don't use ref cleanups internally so this will only happen if a\n // user's ref callback returns a value, which we only expect if they are\n // using the cleanup functionality added in React 19.\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < cleanups.length; i++) {\n const cleanup = cleanups[i];\n if (typeof cleanup === \"function\") {\n cleanup();\n } else {\n setRef(refs[i], null);\n }\n }\n };\n }\n };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs(...refs: PossibleRef[]): React.RefCallback {\n // biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values\n return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n", "target": "" } ] }