diff --git a/change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json b/change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json new file mode 100644 index 0000000000000..81ed563ea2612 --- /dev/null +++ b/change/@fluentui-react-motion-f4f8b668-785c-48d9-9d8f-2410472b12f4.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: handle case when Animation.persist() does not exist", + "packageName": "@fluentui/react-motion", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx b/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx index a2508262d2f20..7aa5ce73c5f76 100644 --- a/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx +++ b/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx @@ -36,6 +36,27 @@ function createElementMock() { } describe('createMotionComponent', () => { + let hasAnimation: boolean; + beforeEach(() => { + if (!global.Animation) { + hasAnimation = false; + global.Animation = { + // @ts-expect-error mock + prototype: { + persist: jest.fn(), + }, + }; + } else { + hasAnimation = true; + } + }); + + afterEach(() => { + if (!hasAnimation) { + // @ts-expect-error mock + delete global.Animation; + } + }); it('creates a motion and plays it', () => { const TestAtom = createMotionComponent(motion); const { animateMock, ElementMock } = createElementMock(); @@ -52,6 +73,24 @@ describe('createMotionComponent', () => { }); }); + it('creates a motion and plays it (without .persist())', () => { + // @ts-expect-error mock + delete global.Animation.prototype.persist; + const TestAtom = createMotionComponent(motion); + const { animateMock, ElementMock } = createElementMock(); + + render( + + + , + ); + + expect(animateMock).toHaveBeenCalledWith(motion.keyframes, { + duration: 1, + fill: 'forwards', + }); + }); + it('supports functions as motion definitions', () => { const fnMotion = jest.fn().mockImplementation(() => motion); const TestAtom = createMotionComponent(fnMotion); diff --git a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx index ad2cedcacd631..a1cd9bb2df262 100644 --- a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx +++ b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx @@ -41,6 +41,28 @@ function createElementMock() { } describe('createPresenceComponent', () => { + let hasAnimation: boolean; + beforeEach(() => { + if (!global.Animation) { + hasAnimation = false; + global.Animation = { + // @ts-expect-error mock + prototype: { + persist: jest.fn(), + }, + }; + } else { + hasAnimation = true; + } + }); + + afterEach(() => { + if (!hasAnimation) { + // @ts-expect-error mock + delete global.Animation; + } + }); + describe('appear', () => { it('does not animate by default', () => { const TestPresence = createPresenceComponent(motion); @@ -68,6 +90,24 @@ describe('createPresenceComponent', () => { expect(animateMock).toHaveBeenCalledWith(enterKeyframes, options); }); + it('animates when is "true" (without .persist())', () => { + // @ts-expect-error mock + delete window.Animation.prototype.persist; + const TestPresence = createPresenceComponent(motion); + const { animateMock, ElementMock } = createElementMock(); + + render( + + + , + ); + + expect(animateMock).toHaveBeenCalledWith(enterKeyframes, { + ...options, + duration: 1, + }); + }); + it('finishes motion when wrapped in motion behaviour context with skip behaviour', async () => { const TestPresence = createPresenceComponent(motion); const { finishMock, ElementMock } = createElementMock(); diff --git a/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts b/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts index 66ea0c35e1b6b..6fa1294484363 100644 --- a/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts +++ b/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts @@ -2,6 +2,9 @@ import * as React from 'react'; import type { AnimationHandle, AtomMotion } from '../types'; function useAnimateAtomsInSupportedEnvironment() { + // eslint-disable-next-line @nx/workspace-no-restricted-globals + const SUPPORTS_PERSIST = typeof window !== 'undefined' && typeof window.Animation?.prototype.persist === 'function'; + return React.useCallback( ( element: HTMLElement, @@ -19,10 +22,10 @@ function useAnimateAtomsInSupportedEnvironment() { fill: 'forwards', ...params, - ...(isReducedMotion && { duration: 1 }), + ...((isReducedMotion || !SUPPORTS_PERSIST) && { duration: 1 }), }); - animation.persist(); + SUPPORTS_PERSIST && animation.persist(); return animation; });