react-widgets-up
Version:
An à la carte set of polished, extensible, and accessible inputs built for React
144 lines (139 loc) • 4.36 kB
JavaScript
import React, { useState, useEffect, useRef, useCallback } from 'react';
// Transition states
export const TRANSITION_STATES = {
ENTERING: 'entering',
ENTERED: 'entered',
EXITING: 'exiting',
EXITED: 'exited'
};
// Transition component props interface
// Internal callback interface
// Custom Transition component
export const Transition = ({
in: inProp,
timeout: _timeout = 300,
appear: _appear = false,
enter: _enter = true,
exit: _exit = true,
mountOnEnter: _mountOnEnter = false,
unmountOnExit: _unmountOnExit = false,
nodeRef,
addEndListener,
onEnter,
onEntering,
onEntered,
onExit,
onExiting,
onExited,
children
}) => {
const [status, setStatus] = useState(() => {
if (inProp) {
return _appear ? TRANSITION_STATES.EXITED : TRANSITION_STATES.ENTERED;
}
return _mountOnEnter || _unmountOnExit ? null : TRANSITION_STATES.EXITED;
});
const timeoutRef = useRef(null);
const nextCallbackRef = useRef(null);
const getNode = useCallback(() => {
return (nodeRef == null ? void 0 : nodeRef.current) || null;
}, [nodeRef]);
const getTimeout = useCallback(phase => {
if (typeof _timeout === 'number') {
return _timeout;
}
return _timeout[phase] || 0;
}, [_timeout]);
const clearTimeoutHandler = useCallback(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}, []);
const setNextCallback = useCallback(callback => {
let active = true;
const nextCallback = () => {
if (active) {
callback();
}
};
nextCallback.cancel = () => {
active = false;
};
nextCallbackRef.current = nextCallback;
return nextCallback;
}, []);
const performEnter = useCallback(() => {
const node = getNode();
if (!_enter) {
setStatus(TRANSITION_STATES.ENTERED);
onEntered == null || onEntered(node || undefined);
return;
}
onEnter == null || onEnter(node || undefined);
setStatus(TRANSITION_STATES.ENTERING);
onEntering == null || onEntering(node || undefined);
const next = setNextCallback(() => {
setStatus(TRANSITION_STATES.ENTERED);
onEntered == null || onEntered(node || undefined);
});
if (addEndListener && node) {
addEndListener(node, next);
} else {
timeoutRef.current = setTimeout(next, getTimeout('enter'));
}
}, [_enter, onEnter, onEntering, onEntered, setNextCallback, getTimeout, getNode, addEndListener]);
const performExit = useCallback(() => {
const node = getNode();
if (!_exit) {
setStatus(TRANSITION_STATES.EXITED);
onExited == null || onExited(node || undefined);
return;
}
onExit == null || onExit(node || undefined);
setStatus(TRANSITION_STATES.EXITING);
onExiting == null || onExiting(node || undefined);
const next = setNextCallback(() => {
setStatus(TRANSITION_STATES.EXITED);
onExited == null || onExited(node || undefined);
});
if (addEndListener && node) {
addEndListener(node, next);
} else {
timeoutRef.current = setTimeout(next, getTimeout('exit'));
}
}, [_exit, onExit, onExiting, onExited, setNextCallback, getTimeout, getNode, addEndListener]);
useEffect(() => {
if (inProp && (status === TRANSITION_STATES.EXITED || status === null)) {
if (status === null) {
setStatus(TRANSITION_STATES.EXITED);
}
performEnter();
} else if (!inProp && (status === TRANSITION_STATES.ENTERED || status === TRANSITION_STATES.ENTERING)) {
performExit();
}
}, [inProp, status, performEnter, performExit]);
useEffect(() => {
return () => {
var _nextCallbackRef$curr;
clearTimeoutHandler();
if ((_nextCallbackRef$curr = nextCallbackRef.current) != null && _nextCallbackRef$curr.cancel) {
nextCallbackRef.current.cancel();
}
};
}, [clearTimeoutHandler]);
// Handle mounting/unmounting logic
if (_mountOnEnter && status === null) {
return null;
}
if (_unmountOnExit && status === TRANSITION_STATES.EXITED) {
return null;
}
if (typeof children === 'function') {
return /*#__PURE__*/React.createElement(React.Fragment, null, children(status, nodeRef));
}
return /*#__PURE__*/React.cloneElement(children, {
transitionStatus: status,
ref: nodeRef
});
};