@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
JavaScript
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