Private
Public Access
1
0
Files
power-apps-codeapps-blog-part2/node_modules/@fluentui/react-motion/lib/factories/createPresenceComponent.js

169 lines
7.4 KiB
JavaScript

'use client';
import { useEventCallback, useFirstMount, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
import * as React from 'react';
import { PresenceGroupChildContext } from '../contexts/PresenceGroupChildContext';
import { useAnimateAtoms } from '../hooks/useAnimateAtoms';
import { useMotionImperativeRef } from '../hooks/useMotionImperativeRef';
import { useMountedState } from '../hooks/useMountedState';
import { useIsReducedMotion } from '../hooks/useIsReducedMotion';
import { useChildElement } from '../utils/useChildElement';
import { useMotionBehaviourContext } from '../contexts/MotionBehaviourContext';
import { createMotionComponent } from './createMotionComponent';
/**
* A private symbol to store the motion definition on the component for variants.
*
* @internal
*/ export const PRESENCE_MOTION_DEFINITION = Symbol('PRESENCE_MOTION_DEFINITION');
const INTERRUPTABLE_MOTION_SYMBOL = Symbol.for('interruptablePresence');
export function createPresenceComponent(value) {
return Object.assign((props)=>{
'use no memo';
const itemContext = React.useContext(PresenceGroupChildContext);
const merged = {
...itemContext,
...props
};
const skipMotions = useMotionBehaviourContext() === 'skip';
const { appear, children, imperativeRef, onExit, onMotionFinish, onMotionStart, onMotionCancel, visible, unmountOnExit, ..._rest } = merged;
const params = _rest;
const [mounted, setMounted] = useMountedState(visible, unmountOnExit);
const [child, childRef] = useChildElement(children, mounted);
const handleRef = useMotionImperativeRef(imperativeRef);
const optionsRef = React.useRef({
appear,
params,
skipMotions
});
const animateAtoms = useAnimateAtoms();
const isFirstMount = useFirstMount();
const isReducedMotion = useIsReducedMotion();
const handleMotionStart = useEventCallback((direction)=>{
onMotionStart === null || onMotionStart === void 0 ? void 0 : onMotionStart(null, {
direction
});
});
const handleMotionFinish = useEventCallback((direction)=>{
onMotionFinish === null || onMotionFinish === void 0 ? void 0 : onMotionFinish(null, {
direction
});
if (direction === 'exit' && unmountOnExit) {
setMounted(false);
onExit === null || onExit === void 0 ? void 0 : onExit();
}
});
const handleMotionCancel = useEventCallback((direction)=>{
onMotionCancel === null || onMotionCancel === void 0 ? void 0 : onMotionCancel(null, {
direction
});
});
useIsomorphicLayoutEffect(()=>{
// Heads up!
// We store the params in a ref to avoid re-rendering the component when the params change.
optionsRef.current = {
appear,
params,
skipMotions
};
});
useIsomorphicLayoutEffect(()=>{
const element = childRef.current;
if (!element) {
return;
}
let handle;
function cleanup() {
if (!handle) {
return;
}
// Heads up!
//
// If the animation is interruptible & is running, we don't want to cancel it as it will be reversed in
// the next effect.
if (IS_EXPERIMENTAL_INTERRUPTIBLE_MOTION && handle.isRunning()) {
return;
}
handle.cancel();
handleRef.current = undefined;
}
const presenceMotion = typeof value === 'function' ? value({
element,
...optionsRef.current.params
}) : value;
const IS_EXPERIMENTAL_INTERRUPTIBLE_MOTION = presenceMotion[INTERRUPTABLE_MOTION_SYMBOL];
if (IS_EXPERIMENTAL_INTERRUPTIBLE_MOTION) {
handle = handleRef.current;
if (handle && handle.isRunning()) {
handle.reverse();
return cleanup;
}
}
const atoms = visible ? presenceMotion.enter : presenceMotion.exit;
const direction = visible ? 'enter' : 'exit';
// Heads up!
// Initial styles are applied when the component is mounted for the first time and "appear" is set to "false" (otherwise animations are triggered)
const applyInitialStyles = !optionsRef.current.appear && isFirstMount;
const skipAnimationByConfig = optionsRef.current.skipMotions;
if (!applyInitialStyles) {
handleMotionStart(direction);
}
handle = animateAtoms(element, atoms, {
isReducedMotion: isReducedMotion()
});
if (applyInitialStyles) {
// Heads up!
// .finish() is used in this case to skip animation and apply animation styles immediately
handle.finish();
return cleanup;
}
handleRef.current = handle;
handle.setMotionEndCallbacks(()=>handleMotionFinish(direction), ()=>handleMotionCancel(direction));
if (skipAnimationByConfig) {
handle.finish();
}
return cleanup;
}, // Excluding `isFirstMount` from deps to prevent re-triggering the animation on subsequent renders
// eslint-disable-next-line react-hooks/exhaustive-deps
[
animateAtoms,
childRef,
handleRef,
isReducedMotion,
handleMotionFinish,
handleMotionStart,
handleMotionCancel,
visible
]);
React.useEffect(()=>{
// Heads up!
//
// Dispose the handle when unmounting the component to clean up retained references. Doing it in a separate
// effect to ensure that the component is unmounted.
if (unmountOnExit && !mounted) {
var _handleRef_current;
(_handleRef_current = handleRef.current) === null || _handleRef_current === void 0 ? void 0 : _handleRef_current.dispose();
}
}, [
handleRef,
unmountOnExit,
mounted
]);
if (mounted) {
return child;
}
return null;
}, {
// Heads up!
// Always normalize it to a function to simplify types
[PRESENCE_MOTION_DEFINITION]: typeof value === 'function' ? value : ()=>value
}, {
// Wrap `enter` in its own motion component as a static method, e.g. <Fade.In>
In: createMotionComponent(// If we have a motion function, wrap it to forward the runtime params and pick `enter`.
// Otherwise, pass the `enter` motion object directly.
typeof value === 'function' ? (...args)=>value(...args).enter : value.enter),
// Wrap `exit` in its own motion component as a static method, e.g. <Fade.Out>
Out: createMotionComponent(// If we have a motion function, wrap it to forward the runtime params and pick `exit`.
// Otherwise, pass the `exit` motion object directly.
typeof value === 'function' ? (...args)=>value(...args).exit : value.exit)
});
}