"use strict";
/* eslint-disable @typescript-eslint/ban-ts-comment */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.assignRef = assignRef;
exports.createContext = createContext;
exports.mergeRefs = mergeRefs;
exports.useCallbackRef = useCallbackRef;
exports.useClickOutside = useClickOutside;
exports.useControlled = useControlled;
exports.useIds = useIds;
exports.useIsMounted = useIsMounted;
const react_1 = __importStar(require("react"));
const assertion_1 = require("./assertion");
const function_1 = require("./function");
/**
 * Assigns given value to the provided ref.
 * Supports ref as function (e.g. useState) and classic ref (e.g. useRef).
 */
function assignRef(ref, value) {
    if (ref === null || ref === undefined)
        return;
    if ((0, assertion_1.isFunction)(ref)) {
        ref(value);
        return;
    }
    try {
        // @ts-ignore - 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. */
function createContext(options = {}) {
    const { errorMessage = 'Context can only be accessed by components wrapped with the Provider.', isOptional, name, } = options;
    const Context = react_1.default.createContext(undefined);
    Context.displayName = name;
    function useContext() {
        const context = react_1.default.useContext(Context);
        if (!isOptional && context === undefined)
            throw new Error(errorMessage);
        return context;
    }
    return [Context.Provider, useContext];
}
/** Assigns a node to all provided refs. */
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. */
function useCallbackRef(callback, deps = []) {
    const ref = (0, react_1.useRef)(callback);
    ref.current = callback;
    return (0, react_1.useCallback)(((...args) => { var _a; return (_a = ref.current) === null || _a === void 0 ? void 0 : _a.call(ref, ...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'])
 */
function useClickOutside(ref, callback, events = defaultEvents) {
    const eventsString = JSON.stringify(events);
    const listener = useCallbackRef((ev) => {
        const refs = (0, assertion_1.isArray)(ref) ? ref : [ref];
        const target = ev.target;
        const isClickOutside = refs.every(ref => ref.current && !ref.current.contains(target));
        isClickOutside && (callback === null || callback === void 0 ? void 0 : callback(ev));
    });
    (0, react_1.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. */
function useControlled(props) {
    const { defaultValue, onChange: onChangeProp, value: valueProp } = props;
    const [valueState, setValue] = (0, react_1.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 = (0, function_1.runIfFn)(next);
        if (!isControlled)
            setValue(newValue);
        onChangeProp === null || onChangeProp === void 0 ? void 0 : 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. */
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.
 */
function useIsMounted() {
    const isMounted = (0, react_1.useRef)(false);
    (0, react_1.useEffect)(() => {
        isMounted.current = true;
        return () => {
            isMounted.current = false;
        };
    }, []);
    return (0, react_1.useCallback)(() => isMounted.current, []);
}
//# sourceMappingURL=react.js.map