import React, { useCallback, useEffect, useRef, useState, } from 'react';
import { isArray, isFunction } from './assertion';
import { runIfFn } from './function';
/**
 * Assigns given value to the provided ref.
 * Supports ref as function (e.g. useState) and classic ref (e.g. useRef).
 */
export function assignRef(ref, value) {
    if (ref === null || ref === undefined)
        return;
    if (isFunction(ref)) {
        ref(value);
        return;
    }
    try {
        // @ts-expect-error TS complains about 'current' being read-only
        ref.current = value;
    }
    catch (error) {
        throw new Error(`Cannot assign value '${value}' to ref '${ref}'`);
    }
}
/** Generic function to create new Context and context access hook. */
export function createContext(options = {}) {
    const { errorMessage = 'Context can only be accessed by components wrapped with the Provider.', isOptional, name, } = options;
    const Context = React.createContext(undefined);
    Context.displayName = name;
    function useContext() {
        const context = React.useContext(Context);
        if (!isOptional && context === undefined)
            throw new Error(errorMessage);
        return context;
    }
    return [Context.Provider, useContext];
}
/** Assigns a node to all provided refs. */
export function mergeRefs(...refs) {
    return (node) => {
        refs.forEach(ref => assignRef(ref, node));
    };
}
// TODO: Bring it from UI-Utils
/** Returns given callback with a stable reference, but keeps it updated internally. */
export function useCallbackRef(callback, deps = []) {
    const ref = useRef(callback);
    ref.current = callback;
    return useCallback(((...args) => ref.current?.(...args)), deps);
}
const defaultEvents = ['mousedown', 'touchstart'];
// TODO: Bring it from UI-Utils
/**
 * Triggers given callback when an event happens outside of a given element.
 * Event listeners are attached to the window object.
 * @param ref - element to check click position for
 * @param callback - function triggered on outside click
 * @param events - array of events listened for (default: ['mousedown', 'touchstart'])
 */
export function useClickOutside(ref, callback, events = defaultEvents) {
    const eventsString = JSON.stringify(events);
    const listener = useCallbackRef((ev) => {
        const refs = isArray(ref) ? ref : [ref];
        const target = ev.target;
        const isClickOutside = refs.every(ref => ref.current && !ref.current.contains(target));
        isClickOutside && callback?.(ev);
    });
    useEffect(() => {
        const events = JSON.parse(eventsString);
        events.forEach(event => window.addEventListener(event, listener));
        return () => {
            events.forEach(event => window.removeEventListener(event, listener));
        };
    }, [eventsString, ref]);
}
// TODO: Move to ui-utils
/** Supports state management in components that can be controlled or uncontrolled. */
export function useControlled(props) {
    const { defaultValue, onChange: onChangeProp, value: valueProp } = props;
    const [valueState, setValue] = useState(defaultValue);
    const isControlled = valueProp !== undefined;
    const value = isControlled ? valueProp : valueState;
    /** Triggers the external callback with the new value. In uncontrolled mode, updates state. */
    const onChange = useCallbackRef((next) => {
        const newValue = runIfFn(next);
        if (!isControlled)
            setValue(newValue);
        onChangeProp?.(newValue);
    });
    /** In uncontrolled mode, sets the state to the default value. */
    const reset = useCallbackRef(() => {
        !isControlled && defaultValue && onChange(defaultValue);
    });
    return [value, onChange, { isControlled, reset }];
}
/** Returns an array with each prefix added to the id. Useful for aria attributes in compound components. */
export function useIds(id, prefixes) {
    return prefixes.map(prefix => `${prefix}-${id}`);
}
// TODO: Bring it from ui-utils
/**
 * Provides a function, which can be called to check if component using the hook is mounted or unmounted.
 */
export function useIsMounted() {
    const isMounted = useRef(false);
    useEffect(() => {
        isMounted.current = true;
        return () => {
            isMounted.current = false;
        };
    }, []);
    return useCallback(() => isMounted.current, []);
}
//# sourceMappingURL=react.js.map