@spark-web/utils
Version:
--- title: Utilities isExperimentalPackage: true ---
147 lines (138 loc) • 4.99 kB
JavaScript
import { useCallback, useState, useLayoutEffect, useRef } from 'react';
import { i as isFunction } from './type-check-d8c35507.esm.js';
import _slicedToArray from '@babel/runtime/helpers/esm/slicedToArray';
/**
* Sometimes we need both a local ref _and_ a forwarded ref for the same element.
* This utility merges them for us (as React doesn't offer this natively).
*
* @see https://github.com/gregberge/react-merge-refs/blob/main/src/index.tsx
*/
function mergeRefs(refs) {
return function (value) {
refs.forEach(function (ref) {
if (typeof ref === 'function') {
ref(value);
} else if (ref != null) {
ref.current = value;
}
});
};
}
/**
* Passes or assigns an arbitrary value to a ref function or object.
*
* @param ref
* @param value
*/
function assignRef(ref, value) {
if (ref == null) return;
if (isFunction(ref)) {
ref(value);
} else {
try {
ref.current = value;
} catch (error) {
throw new Error("Cannot assign value \"".concat(value, "\" to ref \"").concat(ref, "\""));
}
}
}
/**
* Passes or assigns a value to multiple refs (typically a DOM node). Useful for
* dealing with components that need an explicit ref for DOM calculations but
* also forwards refs assigned by an app.
*
* @param refs Refs to fork
*
* @example
* const internalRef = useRef<HTMLSpanElement>(null);
* const composedRef = useComposedRefs(internalRef, forwardedRef);
*/
function useComposedRefs() {
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
refs[_key] = arguments[_key];
}
return useCallback(function (node) {
for (var _i = 0, _refs = refs; _i < _refs.length; _i++) {
var ref = _refs[_i];
assignRef(ref, node);
}
// `refs` is already an array. If we do what ESLint wants us to do
// and wrap `refs` in square brackets, then the useCallback will fire
// on every render (not just when the dependencies change).
// eslint-disable-next-line react-hooks/exhaustive-deps
}, refs);
}
/**
* `useDisclosure` is a custom hook used to help handle common open, close, or toggle scenarios.
*/
function useDisclosure() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
defaultIsOpen = _ref.defaultIsOpen;
var _useState = useState(defaultIsOpen !== null && defaultIsOpen !== void 0 ? defaultIsOpen : false),
_useState2 = _slicedToArray(_useState, 2),
isOpen = _useState2[0],
setIsOpen = _useState2[1];
var onClose = function onClose() {
return setIsOpen(false);
};
var onOpen = function onOpen() {
return setIsOpen(true);
};
var onToggle = function onToggle() {
return setIsOpen(function (prevState) {
return !prevState;
});
};
return {
isOpen: isOpen,
onOpen: onOpen,
onClose: onClose,
onToggle: onToggle
};
}
/**
* On the server, React emits a warning when calling `useLayoutEffect`.
* This is because neither `useLayoutEffect` nor `useEffect` run on the server.
* We use this safe version which suppresses the warning by replacing it with a noop on the server.
*
* @see https://reactjs.org/docs/hooks-reference.html#uselayouteffect
*/
var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : function () {};
var stashedTime;
/**
* Keeps all instances of the same animation in sync.
* Taken from Sam Selikoff's example post:
* @see https://github.com/samselikoff/2022-02-24-use-synchronized-animation
*/
function useSynchronizedAnimation(animationName) {
var ref = useRef(null);
useIsomorphicLayoutEffect(function () {
var _document$getAnimatio, _document;
var animations = ((_document$getAnimatio = (_document = document).getAnimations) === null || _document$getAnimatio === void 0 ? void 0 : _document$getAnimatio.call(_document)
// @ts-expect-error: Property 'animationName' does not exist on type 'Animation'.
.filter(function (animation) {
return animation.animationName === animationName;
})) || [];
var animationTarget = animations.find(
// @ts-expect-error: Property 'target' does not exist on type 'AnimationEffect'.
function (animation) {
var _animation$effect;
return ((_animation$effect = animation.effect) === null || _animation$effect === void 0 ? void 0 : _animation$effect.target) === ref.current;
});
if (animationTarget) {
if (animationTarget === animations[0] && stashedTime) {
animationTarget.currentTime = stashedTime;
}
if (animationTarget && animationTarget !== animations[0]) {
animationTarget.currentTime = animations[0].currentTime;
}
return function () {
if (animationTarget === animations[0]) {
stashedTime = animationTarget.currentTime;
}
};
}
}, [animationName]);
return ref;
}
export { assignRef, mergeRefs, useComposedRefs, useDisclosure, useIsomorphicLayoutEffect, useSynchronizedAnimation };