@fluentui/react-motion
Advanced tools
Comparing version 9.0.0 to 9.1.0
# Change Log - @fluentui/react-motion | ||
This log was last generated on Thu, 06 Jun 2024 15:22:17 GMT and should not be manually modified. | ||
This log was last generated on Wed, 12 Jun 2024 13:16:08 GMT and should not be manually modified. | ||
<!-- Start content --> | ||
## [9.1.0](https://github.com/microsoft/fluentui/tree/@fluentui/react-motion_v9.1.0) | ||
Wed, 12 Jun 2024 13:16:08 GMT | ||
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-motion_v9.0.0..@fluentui/react-motion_v9.1.0) | ||
### Minor changes | ||
- feat: add support for params ([PR #31566](https://github.com/microsoft/fluentui/pull/31566) by olfedias@microsoft.com) | ||
- feat: Add consistent start and finish lifecycle callbacks ([PR #31644](https://github.com/microsoft/fluentui/pull/31644) by lingfangao@hotmail.com) | ||
### Patches | ||
- fix(motions): improve compat for jsdom & jest ([PR #31602](https://github.com/microsoft/fluentui/pull/31602) by olfedias@microsoft.com) | ||
## [9.0.0](https://github.com/microsoft/fluentui/tree/@fluentui/react-motion_v9.0.0) | ||
Thu, 06 Jun 2024 15:22:17 GMT | ||
Thu, 06 Jun 2024 15:26:35 GMT | ||
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-motions-preview_v0.3.2..@fluentui/react-motion_v9.0.0) | ||
@@ -11,0 +25,0 @@ |
@@ -7,5 +7,5 @@ import * as React_2 from 'react'; | ||
export declare type AtomMotionFn = (params: { | ||
export declare type AtomMotionFn<MotionParams extends Record<string, MotionParam> = {}> = (params: { | ||
element: HTMLElement; | ||
}) => AtomMotion | AtomMotion[]; | ||
} & MotionParams) => AtomMotion | AtomMotion[]; | ||
@@ -17,5 +17,5 @@ /** | ||
*/ | ||
export declare function createMotionComponent(value: AtomMotion | AtomMotion[] | AtomMotionFn): React_2.FC<MotionComponentProps>; | ||
export declare function createMotionComponent<MotionParams extends Record<string, MotionParam> = {}>(value: AtomMotion | AtomMotion[] | AtomMotionFn<MotionParams>): React_2.FC<MotionComponentProps & MotionParams>; | ||
export declare function createPresenceComponent(value: PresenceMotion | PresenceMotionFn): React_2.FC<PresenceComponentProps>; | ||
export declare function createPresenceComponent<MotionParams extends Record<string, MotionParam> = {}>(value: PresenceMotion | PresenceMotionFn<MotionParams>): React_2.FC<PresenceComponentProps & MotionParams>; | ||
@@ -49,2 +49,17 @@ export declare const curves: { | ||
imperativeRef?: React_2.Ref<MotionImperativeRef | undefined>; | ||
/** | ||
* Callback that is called when the whole motion finishes. | ||
* | ||
* A motion definition can contain multiple animations and therefore multiple "finish" events. The callback is | ||
* triggered once all animations have finished with "null" instead of an event object to avoid ambiguity. | ||
*/ | ||
onMotionFinish?: (ev: null) => void; | ||
/** | ||
* Callback that is called when the whole motion starts. | ||
* | ||
* A motion definition can contain multiple animations and therefore multiple "start" events. The callback is | ||
* triggered when the first animation is started. There is no official "start" event with the Web Animations API. | ||
* so the callback is triggered with "null". | ||
*/ | ||
onMotionStart?: (ev: null) => void; | ||
}; | ||
@@ -59,2 +74,10 @@ | ||
/** | ||
* @internal | ||
* | ||
* A motion param should be a primitive value that can be serialized to JSON and could be potentially used a plain | ||
* dependency for React hooks. | ||
*/ | ||
declare type MotionParam = boolean | number | string; | ||
export declare const motionTokens: { | ||
@@ -99,2 +122,12 @@ curveAccelerateMax: "cubic-bezier(0.9,0.1,1,0.2)"; | ||
}) => void; | ||
/** | ||
* Callback that is called when the whole motion starts. | ||
* | ||
* A motion definition can contain multiple animations and therefore multiple "start" events. The callback is | ||
* triggered when the first animation is started. There is no official "start" event with the Web Animations API. | ||
* so the callback is triggered with "null". | ||
*/ | ||
onMotionStart?: (ev: null, data: { | ||
direction: 'enter' | 'exit'; | ||
}) => void; | ||
/** Defines whether a component is visible; triggers the "enter" or "exit" motions. */ | ||
@@ -145,6 +178,6 @@ visible?: boolean; | ||
export declare type PresenceMotionFn = (params: { | ||
export declare type PresenceMotionFn<MotionParams extends Record<string, MotionParam> = {}> = (params: { | ||
element: HTMLElement; | ||
}) => PresenceMotion; | ||
} & MotionParams) => PresenceMotion; | ||
export { } |
@@ -20,16 +20,32 @@ "use strict"; | ||
const Atom = (props)=>{ | ||
const { children, imperativeRef } = props; | ||
const { children, imperativeRef, onMotionFinish: onMotionFinishProp, onMotionStart: onMotionStartProp, ..._rest } = props; | ||
const params = _rest; | ||
const child = (0, _getChildElement.getChildElement)(children); | ||
const handleRef = (0, _useMotionImperativeRef.useMotionImperativeRef)(imperativeRef); | ||
const elementRef = _react.useRef(); | ||
const paramsRef = _react.useRef(params); | ||
const isReducedMotion = (0, _useIsReducedMotion.useIsReducedMotion)(); | ||
const onMotionStart = (0, _reactutilities.useEventCallback)(()=>{ | ||
onMotionStartProp === null || onMotionStartProp === void 0 ? void 0 : onMotionStartProp(null); | ||
}); | ||
const onMotionFinish = (0, _reactutilities.useEventCallback)(()=>{ | ||
onMotionFinishProp === null || onMotionFinishProp === void 0 ? void 0 : onMotionFinishProp(null); | ||
}); | ||
(0, _reactutilities.useIsomorphicLayoutEffect)(()=>{ | ||
// Heads up! | ||
// We store the params in a ref to avoid re-rendering the component when the params change. | ||
paramsRef.current = params; | ||
}); | ||
(0, _reactutilities.useIsomorphicLayoutEffect)(()=>{ | ||
const element = elementRef.current; | ||
if (element) { | ||
const atoms = typeof value === 'function' ? value({ | ||
element | ||
element, | ||
...paramsRef.current | ||
}) : value; | ||
onMotionStart(); | ||
const handle = (0, _animateAtoms.animateAtoms)(element, atoms, { | ||
isReducedMotion: isReducedMotion() | ||
}); | ||
handle.onfinish = onMotionFinish; | ||
handleRef.current = handle; | ||
@@ -42,3 +58,5 @@ return ()=>{ | ||
handleRef, | ||
isReducedMotion | ||
isReducedMotion, | ||
onMotionFinish, | ||
onMotionStart | ||
]); | ||
@@ -45,0 +63,0 @@ return /*#__PURE__*/ _react.cloneElement(children, { |
@@ -21,3 +21,3 @@ "use strict"; | ||
function shouldSkipAnimation(appear, isFirstMount, visible) { | ||
return !appear && isFirstMount && visible; | ||
return !appear && isFirstMount && !!visible; | ||
} | ||
@@ -27,6 +27,8 @@ function createPresenceComponent(value) { | ||
const itemContext = _react.useContext(_PresenceGroupChildContext.PresenceGroupChildContext); | ||
const { appear, children, imperativeRef, onMotionFinish, visible, unmountOnExit } = { | ||
const merged = { | ||
...itemContext, | ||
...props | ||
}; | ||
const { appear, children, imperativeRef, onExit, onMotionFinish, onMotionStart, visible, unmountOnExit, ..._rest } = merged; | ||
const params = _rest; | ||
const [mounted, setMounted] = (0, _useMountedState.useMountedState)(visible, unmountOnExit); | ||
@@ -37,22 +39,28 @@ const child = (0, _getChildElement.getChildElement)(children); | ||
const ref = (0, _reactutilities.useMergedRefs)(elementRef, child.ref); | ||
const optionsRef = _react.useRef({}); | ||
const optionsRef = _react.useRef({ | ||
appear, | ||
params | ||
}); | ||
const isFirstMount = (0, _reactutilities.useFirstMount)(); | ||
const isReducedMotion = (0, _useIsReducedMotion.useIsReducedMotion)(); | ||
const onEnterFinish = (0, _reactutilities.useEventCallback)(()=>{ | ||
onMotionFinish === null || onMotionFinish === void 0 ? void 0 : onMotionFinish(null, { | ||
direction: 'enter' | ||
const handleMotionStart = (0, _reactutilities.useEventCallback)((direction)=>{ | ||
onMotionStart === null || onMotionStart === void 0 ? void 0 : onMotionStart(null, { | ||
direction | ||
}); | ||
}); | ||
const onExitFinish = (0, _reactutilities.useEventCallback)(()=>{ | ||
const handleMotionFinish = (0, _reactutilities.useEventCallback)((direction)=>{ | ||
onMotionFinish === null || onMotionFinish === void 0 ? void 0 : onMotionFinish(null, { | ||
direction: 'exit' | ||
direction | ||
}); | ||
if (unmountOnExit) { | ||
if (direction === 'exit' && unmountOnExit) { | ||
setMounted(false); | ||
itemContext === null || itemContext === void 0 ? void 0 : itemContext.onExit(); | ||
onExit === null || onExit === void 0 ? void 0 : onExit(); | ||
} | ||
}); | ||
(0, _reactutilities.useIsomorphicLayoutEffect)(()=>{ | ||
// Heads up! | ||
// We store the params in a ref to avoid re-rendering the component when the params change. | ||
optionsRef.current = { | ||
appear | ||
appear, | ||
params | ||
}; | ||
@@ -66,9 +74,15 @@ }); | ||
const presenceMotion = typeof value === 'function' ? value({ | ||
element | ||
element, | ||
...optionsRef.current.params | ||
}) : value; | ||
const atoms = visible ? presenceMotion.enter : presenceMotion.exit; | ||
const direction = visible ? 'enter' : 'exit'; | ||
const forceFinishMotion = !visible && isFirstMount; | ||
if (!forceFinishMotion) { | ||
handleMotionStart(direction); | ||
} | ||
const handle = (0, _animateAtoms.animateAtoms)(element, atoms, { | ||
isReducedMotion: isReducedMotion() | ||
}); | ||
if (!visible && isFirstMount) { | ||
if (forceFinishMotion) { | ||
// Heads up! | ||
@@ -80,3 +94,5 @@ // .finish() is used there to skip animation on first mount, but apply animation styles immediately | ||
handleRef.current = handle; | ||
handle.onfinish = visible ? onEnterFinish : onExitFinish; | ||
handle.onfinish = ()=>{ | ||
handleMotionFinish(direction); | ||
}; | ||
return ()=>{ | ||
@@ -89,4 +105,4 @@ handle.cancel(); | ||
isReducedMotion, | ||
onEnterFinish, | ||
onExitFinish, | ||
handleMotionFinish, | ||
handleMotionStart, | ||
visible | ||
@@ -93,0 +109,0 @@ ]); |
@@ -39,2 +39,14 @@ "use strict"; | ||
set onfinish (callback){ | ||
// Heads up! | ||
// Jest uses jsdom as the default environment, which doesn't support the Web Animations API. This no-op is | ||
// necessary to avoid errors in tests. | ||
// | ||
// See https://github.com/microsoft/fluentui/issues/31593 | ||
// See https://github.com/jsdom/jsdom/issues/3429 | ||
if (process.env.NODE_ENV === 'test') { | ||
if (animations.length === 0) { | ||
callback(); | ||
return; | ||
} | ||
} | ||
Promise.all(animations.map((animation)=>animation.finished)).then(()=>{ | ||
@@ -41,0 +53,0 @@ callback(); |
@@ -1,2 +0,2 @@ | ||
import { useIsomorphicLayoutEffect, useMergedRefs } from '@fluentui/react-utilities'; | ||
import { useEventCallback, useIsomorphicLayoutEffect, useMergedRefs } from '@fluentui/react-utilities'; | ||
import * as React from 'react'; | ||
@@ -13,16 +13,32 @@ import { useIsReducedMotion } from '../hooks/useIsReducedMotion'; | ||
const Atom = (props)=>{ | ||
const { children, imperativeRef } = props; | ||
const { children, imperativeRef, onMotionFinish: onMotionFinishProp, onMotionStart: onMotionStartProp, ..._rest } = props; | ||
const params = _rest; | ||
const child = getChildElement(children); | ||
const handleRef = useMotionImperativeRef(imperativeRef); | ||
const elementRef = React.useRef(); | ||
const paramsRef = React.useRef(params); | ||
const isReducedMotion = useIsReducedMotion(); | ||
const onMotionStart = useEventCallback(()=>{ | ||
onMotionStartProp === null || onMotionStartProp === void 0 ? void 0 : onMotionStartProp(null); | ||
}); | ||
const onMotionFinish = useEventCallback(()=>{ | ||
onMotionFinishProp === null || onMotionFinishProp === void 0 ? void 0 : onMotionFinishProp(null); | ||
}); | ||
useIsomorphicLayoutEffect(()=>{ | ||
// Heads up! | ||
// We store the params in a ref to avoid re-rendering the component when the params change. | ||
paramsRef.current = params; | ||
}); | ||
useIsomorphicLayoutEffect(()=>{ | ||
const element = elementRef.current; | ||
if (element) { | ||
const atoms = typeof value === 'function' ? value({ | ||
element | ||
element, | ||
...paramsRef.current | ||
}) : value; | ||
onMotionStart(); | ||
const handle = animateAtoms(element, atoms, { | ||
isReducedMotion: isReducedMotion() | ||
}); | ||
handle.onfinish = onMotionFinish; | ||
handleRef.current = handle; | ||
@@ -35,3 +51,5 @@ return ()=>{ | ||
handleRef, | ||
isReducedMotion | ||
isReducedMotion, | ||
onMotionFinish, | ||
onMotionStart | ||
]); | ||
@@ -38,0 +56,0 @@ return React.cloneElement(children, { |
@@ -10,3 +10,3 @@ import { useEventCallback, useFirstMount, useIsomorphicLayoutEffect, useMergedRefs } from '@fluentui/react-utilities'; | ||
function shouldSkipAnimation(appear, isFirstMount, visible) { | ||
return !appear && isFirstMount && visible; | ||
return !appear && isFirstMount && !!visible; | ||
} | ||
@@ -16,6 +16,8 @@ export function createPresenceComponent(value) { | ||
const itemContext = React.useContext(PresenceGroupChildContext); | ||
const { appear, children, imperativeRef, onMotionFinish, visible, unmountOnExit } = { | ||
const merged = { | ||
...itemContext, | ||
...props | ||
}; | ||
const { appear, children, imperativeRef, onExit, onMotionFinish, onMotionStart, visible, unmountOnExit, ..._rest } = merged; | ||
const params = _rest; | ||
const [mounted, setMounted] = useMountedState(visible, unmountOnExit); | ||
@@ -26,22 +28,28 @@ const child = getChildElement(children); | ||
const ref = useMergedRefs(elementRef, child.ref); | ||
const optionsRef = React.useRef({}); | ||
const optionsRef = React.useRef({ | ||
appear, | ||
params | ||
}); | ||
const isFirstMount = useFirstMount(); | ||
const isReducedMotion = useIsReducedMotion(); | ||
const onEnterFinish = useEventCallback(()=>{ | ||
onMotionFinish === null || onMotionFinish === void 0 ? void 0 : onMotionFinish(null, { | ||
direction: 'enter' | ||
const handleMotionStart = useEventCallback((direction)=>{ | ||
onMotionStart === null || onMotionStart === void 0 ? void 0 : onMotionStart(null, { | ||
direction | ||
}); | ||
}); | ||
const onExitFinish = useEventCallback(()=>{ | ||
const handleMotionFinish = useEventCallback((direction)=>{ | ||
onMotionFinish === null || onMotionFinish === void 0 ? void 0 : onMotionFinish(null, { | ||
direction: 'exit' | ||
direction | ||
}); | ||
if (unmountOnExit) { | ||
if (direction === 'exit' && unmountOnExit) { | ||
setMounted(false); | ||
itemContext === null || itemContext === void 0 ? void 0 : itemContext.onExit(); | ||
onExit === null || onExit === void 0 ? void 0 : onExit(); | ||
} | ||
}); | ||
useIsomorphicLayoutEffect(()=>{ | ||
// Heads up! | ||
// We store the params in a ref to avoid re-rendering the component when the params change. | ||
optionsRef.current = { | ||
appear | ||
appear, | ||
params | ||
}; | ||
@@ -55,9 +63,15 @@ }); | ||
const presenceMotion = typeof value === 'function' ? value({ | ||
element | ||
element, | ||
...optionsRef.current.params | ||
}) : value; | ||
const atoms = visible ? presenceMotion.enter : presenceMotion.exit; | ||
const direction = visible ? 'enter' : 'exit'; | ||
const forceFinishMotion = !visible && isFirstMount; | ||
if (!forceFinishMotion) { | ||
handleMotionStart(direction); | ||
} | ||
const handle = animateAtoms(element, atoms, { | ||
isReducedMotion: isReducedMotion() | ||
}); | ||
if (!visible && isFirstMount) { | ||
if (forceFinishMotion) { | ||
// Heads up! | ||
@@ -69,3 +83,5 @@ // .finish() is used there to skip animation on first mount, but apply animation styles immediately | ||
handleRef.current = handle; | ||
handle.onfinish = visible ? onEnterFinish : onExitFinish; | ||
handle.onfinish = ()=>{ | ||
handleMotionFinish(direction); | ||
}; | ||
return ()=>{ | ||
@@ -79,4 +95,4 @@ handle.cancel(); | ||
isReducedMotion, | ||
onEnterFinish, | ||
onExitFinish, | ||
handleMotionFinish, | ||
handleMotionStart, | ||
visible | ||
@@ -83,0 +99,0 @@ ]); |
@@ -29,2 +29,14 @@ export function animateAtoms(element, value, options) { | ||
set onfinish (callback){ | ||
// Heads up! | ||
// Jest uses jsdom as the default environment, which doesn't support the Web Animations API. This no-op is | ||
// necessary to avoid errors in tests. | ||
// | ||
// See https://github.com/microsoft/fluentui/issues/31593 | ||
// See https://github.com/jsdom/jsdom/issues/3429 | ||
if (process.env.NODE_ENV === 'test') { | ||
if (animations.length === 0) { | ||
callback(); | ||
return; | ||
} | ||
} | ||
Promise.all(animations.map((animation)=>animation.finished)).then(()=>{ | ||
@@ -31,0 +43,0 @@ callback(); |
{ | ||
"name": "@fluentui/react-motion", | ||
"version": "9.0.0", | ||
"version": "9.1.0", | ||
"description": "A package with utilities & motion definitions using Web Animations API", | ||
@@ -26,6 +26,5 @@ "main": "lib-commonjs/index.js", | ||
"start": "yarn storybook", | ||
"storybook": "start-storybook", | ||
"storybook": "yarn --cwd ../stories storybook", | ||
"test": "jest --passWithNoTests", | ||
"test-ssr": "test-ssr \"./stories/**/*.stories.tsx\"", | ||
"type-check": "tsc -b tsconfig.json" | ||
"type-check": "just-scripts type-check" | ||
}, | ||
@@ -32,0 +31,0 @@ "devDependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
149593
1428