UNPKG

@veracity/vui

Version:

Veracity UI is a React component library crafted for use within Veracity applications and pages. Based on Styled Components and @xstyled.

113 lines (111 loc) 4.02 kB
import { isArray, isFunction } from "./assertion.js"; import { runIfFn } from "./function.js"; import React, { useCallback, useEffect, useRef, useState } from "react"; //#region src/utils/react.ts /** * 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 === void 0) return; if (isFunction(ref)) { ref(value); return; } try { ref.current = value; } catch (error) { throw new Error(`Cannot assign value '${value}' to ref '${ref}'`, { cause: error }); } } /** Generic function to create new Context and context access hook. */ function createContext$1(options = {}) { const { errorMessage = "Context can only be accessed by components wrapped with the Provider.", isOptional, name } = options; const Context = React.createContext(void 0); Context.displayName = name; function useContext() { const context = React.useContext(Context); if (!isOptional && context === void 0) 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)); }; } /** Returns given callback with a stable reference, but keeps it updated internally. */ function useCallbackRef(callback, deps = []) { const innerRef = useRef(callback); innerRef.current = callback; return useCallback(((...args) => innerRef.current?.(...args)), deps); } const defaultEvents = ["mousedown", "touchstart"]; /** * 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 = isArray(ref) ? ref : [ref]; const target = ev.target; refs.every((ref) => ref.current && !ref.current.contains(target)) && 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]); } /** Supports state management in components that can be controlled or uncontrolled. */ function useControlled(props) { const { defaultValue, onChange: onChangeProp, value: valueProp } = props; const [valueState, setValueState] = useState(defaultValue); const isControlled = valueProp !== void 0; 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) setValueState(newValue); onChangeProp?.(newValue); }); return [ value, onChange, { isControlled, reset: useCallbackRef(() => { !isControlled && defaultValue && onChange(defaultValue); }) } ]; } /** 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}`); } /** * Provides a function, which can be called to check if component using the hook is mounted or unmounted. */ function useIsMounted() { const isMountedRef = useRef(false); useEffect(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; }; }, []); return useCallback(() => isMountedRef.current, []); } //#endregion export { assignRef, createContext$1 as createContext, mergeRefs, useCallbackRef, useClickOutside, useControlled, useIds, useIsMounted }; globalThis.__vuiVersion__ = "5.3.1" //# sourceMappingURL=react.js.map