Skip to content
This repository was archived by the owner on Apr 14, 2020. It is now read-only.

Commit 655f5ca

Browse files
authored
Merge pull request #119 from madou/self-no-store
remove need for name prop with self targetted animations
2 parents e49e474 + 85a9e03 commit 655f5ca

File tree

18 files changed

+184
-145
lines changed

18 files changed

+184
-145
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ yarn add yubaba react@^16.4.x react-dom@^16.4.x emotion@^10.x.x
4444
import Animator, { Move } from 'yubaba';
4545

4646
({ isLarge }) => (
47-
<Animator name="my-first-baba" triggerSelfKey={isLarge}>
47+
<Animator triggerSelfKey={isLarge}>
4848
<Move>{anim => <div {...anim} className={isLarge ? 'large' : 'small'} />}</Move>
4949
</Animator>
5050
);

packages/yubaba/src/Animator/__docs__/docs.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Using the `triggerSelfKey` prop to force an animation on itself over a state cha
5353
import Animator, { Move } from 'yubaba';
5454

5555
({ children, itemId }) => (
56-
<Animator triggerSelfKey={itemId} name="self-target">
56+
<Animator triggerSelfKey={itemId}>
5757
<Move>{children}</Move>
5858
</Animator>
5959
);

packages/yubaba/src/Animator/index.tsx

+62-112
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import Collector, {
66
SupplyRefHandler,
77
CollectorChildrenAsFunction,
88
CollectorData,
9-
CollectorChildrenProps,
109
CollectorActions,
11-
InlineStyles,
1210
TargetPropsFunc,
1311
AnimationData,
1412
AnimationCallback,
@@ -18,69 +16,8 @@ import defer from '../lib/defer';
1816
import noop from '../lib/noop';
1917
import { precondition, warn } from '../lib/log';
2018
import * as store from '../lib/animatorStore';
21-
import { InjectedProps, withVisibilityManagerContext } from '../VisibilityManager';
22-
23-
export type AnimationFunc = () => Promise<void>;
24-
25-
export interface MappedAnimation {
26-
animate: AnimationFunc;
27-
beforeAnimate: AnimationFunc;
28-
afterAnimate: AnimationFunc;
29-
cleanup: () => void;
30-
}
31-
32-
export type AnimationBlock = MappedAnimation[];
33-
34-
export interface ChildProps {
35-
style?: InlineStyles;
36-
className?: string;
37-
}
38-
39-
export interface AnimatorState {
40-
childProps: ChildProps;
41-
animationsMarkup: React.ReactPortal[];
42-
}
43-
44-
export interface AnimatorProps extends CollectorChildrenProps, InjectedProps {
45-
/**
46-
* Name of the animator, this should match the target animator.
47-
*/
48-
name: string;
49-
50-
/**
51-
* Will trigger animations over itself when this prop changes.
52-
*
53-
* You can't use the with the "in" prop.
54-
*/
55-
triggerSelfKey?: string;
56-
57-
/**
58-
* Use if your element is expected to persist through an animation.
59-
* When you transition to the next state set your "in" to false and vice versa.
60-
* This lets the Animator components know when to execute the animations.
61-
*
62-
* You can't use this with the "triggerSelfKey".
63-
*/
64-
in?: boolean;
65-
66-
/**
67-
* Callback called when all animations have finished and been cleaned up. Fired from the triggering Animator
68-
* component.
69-
*/
70-
onFinish: () => void;
71-
72-
/**
73-
* Time this component will wait until it throws away the animation.
74-
* Defaults to 50ms, might want to bump it up if loading something that was code split.
75-
*/
76-
timeToWaitForNextAnimator: number;
77-
78-
/**
79-
* HTMLElement container used when creating elements for animations,
80-
* generally only supporting animations will need this.
81-
*/
82-
container: HTMLElement | (() => HTMLElement);
83-
}
19+
import { withVisibilityManagerContext } from '../VisibilityManager';
20+
import { AnimatorProps, AnimatorState, AnimationBlock } from './types';
8421

8522
export default class Animator extends React.PureComponent<AnimatorProps, AnimatorState> {
8623
static displayName = 'Animator';
@@ -89,6 +26,7 @@ export default class Animator extends React.PureComponent<AnimatorProps, Animato
8926
onFinish: noop,
9027
timeToWaitForNextAnimator: 50,
9128
container: document.body,
29+
name: '',
9230
};
9331

9432
state: AnimatorState = {
@@ -111,7 +49,13 @@ export default class Animator extends React.PureComponent<AnimatorProps, Animato
11149
abortAnimations: () => void = () => undefined;
11250

11351
componentDidMount() {
114-
const { in: componentIn, name } = this.props;
52+
const { in: componentIn, name, triggerSelfKey } = this.props;
53+
54+
if (process.env.NODE_ENV === 'development' && (!triggerSelfKey && !name)) {
55+
warn(
56+
'"name" prop needs to be defined. Without it you may have problems matching up animator targets. You will not get this error when using "triggerSelfKey" prop.'
57+
);
58+
}
11559

11660
if (componentIn === undefined && store.has(name)) {
11761
// A child has already been stored, so this is probably the matching pair.
@@ -128,22 +72,23 @@ export default class Animator extends React.PureComponent<AnimatorProps, Animato
12872

12973
getSnapshotBeforeUpdate(prevProps: AnimatorProps) {
13074
if (prevProps.in === true && this.props.in === false) {
131-
this.storeDOMData();
75+
this.snapshotDOMData();
13276
this.delayedClearStore();
13377
this.abortAnimations();
13478
}
13579

13680
if (prevProps.triggerSelfKey !== this.props.triggerSelfKey) {
137-
this.storeDOMData();
138-
this.delayedClearStore();
81+
return this.snapshotDOMData('return');
13982
}
14083

141-
// we can return snapshot here to circumvent the entire storing of dom data.
142-
// would remove the need for setting a name!
14384
return null;
14485
}
14586

146-
componentDidUpdate(prevProps: AnimatorProps, _: AnimatorState) {
87+
componentDidUpdate(
88+
prevProps: AnimatorProps,
89+
_: AnimatorState,
90+
DOMSnapshot: store.AnimatorData | null
91+
) {
14792
const inPropSame = this.props.in === prevProps.in;
14893
const triggerSelfKeyPropSame = this.props.triggerSelfKey === prevProps.triggerSelfKey;
14994

@@ -192,7 +137,7 @@ export default class Animator extends React.PureComponent<AnimatorProps, Animato
192137
// Make sure to keep react state the same for any inflight animations to be captured correctly.
193138
requestAnimationFrame(() => {
194139
this.abortAnimations();
195-
this.executeAnimations();
140+
this.executeAnimations(DOMSnapshot);
196141
});
197142
}
198143
}
@@ -204,7 +149,7 @@ export default class Animator extends React.PureComponent<AnimatorProps, Animato
204149
return;
205150
}
206151

207-
this.storeDOMData();
152+
this.snapshotDOMData();
208153
this.delayedClearStore();
209154
this.abortAnimations();
210155
this.unmounting = true;
@@ -226,64 +171,69 @@ export default class Animator extends React.PureComponent<AnimatorProps, Animato
226171
setTimeout(() => store.remove(name), timeToWaitForNextAnimator);
227172
}
228173

229-
storeDOMData() {
230-
if (this.unmounting) {
231-
return;
232-
}
233-
174+
snapshotDOMData(action: 'store' | 'return' = 'store'): store.AnimatorData | null {
234175
// If there is only a Animator target and no child animations
235176
// data will be undefined, which means there are no animations to store.
236-
if (this.data) {
237-
if (process.env.NODE_ENV === 'development') {
238-
precondition(
239-
this.element,
240-
`The ref was not set when trying to store data, check that a child element has a ref passed. This needs to be set so we can take a snapshot of the origin DOM element.
177+
if (this.unmounting || !this.data) {
178+
return null;
179+
}
180+
181+
if (process.env.NODE_ENV === 'development') {
182+
precondition(
183+
this.element,
184+
`The ref was not set when trying to store data, check that a child element has a ref passed. This needs to be set so we can take a snapshot of the origin DOM element.
241185
242186
<${Animator.displayName} name="${this.props.name}">
243187
{props => <div ref={props.ref} />}
244188
</${Animator.displayName}>
245189
`
246-
);
247-
}
190+
);
191+
}
248192

249-
const elementBoundingBox = getElementBoundingBox(this.element as HTMLElement);
250-
const focalTargetElementBoundingBox = this.focalTargetElement
251-
? getElementBoundingBox(this.focalTargetElement)
252-
: undefined;
193+
const elementBoundingBox = getElementBoundingBox(this.element as HTMLElement);
194+
const focalTargetElementBoundingBox = this.focalTargetElement
195+
? getElementBoundingBox(this.focalTargetElement)
196+
: undefined;
253197

254-
if (process.env.NODE_ENV === 'development' && elementBoundingBox.size.height === 0) {
255-
warn(`Your target child had a height of zero when capturing it's DOM data. This may affect the animation.
198+
if (process.env.NODE_ENV === 'development' && elementBoundingBox.size.height === 0) {
199+
warn(`Your target child had a height of zero when capturing it's DOM data. This may affect the animation.
256200
If it's an image, try and have the image loaded before mounting, or set a static height.`);
257-
}
258-
259-
const { name } = this.props;
201+
}
260202

261-
// NOTE: Currently in react 16.3 if the parent being unmounted is a Fragment
262-
// there is a chance for sibling elements to be removed from the DOM first
263-
// resulting in inaccurate calculations of location. Watch out!
264-
const data: store.AnimatorData = {
265-
elementData: {
266-
element: this.element as HTMLElement,
267-
elementBoundingBox,
268-
focalTargetElement: this.focalTargetElement,
269-
focalTargetElementBoundingBox,
270-
render: this.renderChildren,
271-
},
272-
collectorData: this.data,
273-
};
203+
const { name } = this.props;
204+
205+
// NOTE: Currently in react 16.3 if the parent being unmounted is a Fragment
206+
// there is a chance for sibling elements to be removed from the DOM first
207+
// resulting in inaccurate calculations of location. Watch out!
208+
const data: store.AnimatorData = {
209+
elementData: {
210+
element: this.element as HTMLElement,
211+
elementBoundingBox,
212+
focalTargetElement: this.focalTargetElement,
213+
focalTargetElementBoundingBox,
214+
render: this.renderChildren,
215+
},
216+
collectorData: this.data,
217+
};
218+
219+
if (action === 'return') {
220+
return data;
221+
}
274222

223+
if (action === 'store') {
275224
store.set(name, data);
276225
}
226+
227+
return null;
277228
}
278229

279-
executeAnimations = () => {
230+
executeAnimations = (DOMSnapshot: store.AnimatorData | null = store.get(this.props.name)) => {
280231
const { name, container: getContainer, context } = this.props;
281232
const container = typeof getContainer === 'function' ? getContainer() : getContainer;
282-
const fromTarget = store.get(name);
283233
let aborted = false;
284234

285-
if (fromTarget) {
286-
const { collectorData, elementData } = fromTarget;
235+
if (DOMSnapshot) {
236+
const { collectorData, elementData } = DOMSnapshot;
287237
this.animating = true;
288238

289239
// Calculate DOM data for the executing element to then be passed to the animation/s.

packages/yubaba/src/Animator/test.tsx

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { mount, ReactWrapper } from 'enzyme'; // eslint-disable-line import/no-extraneous-dependencies
2+
import { mount, ReactWrapper, shallow } from 'enzyme'; // eslint-disable-line import/no-extraneous-dependencies
33
import { MemoryRouter, Link } from 'react-router-dom';
44
import { WrappedAnimator as Animator } from '../Animator';
55
import Target from '../FocalTarget';
@@ -33,7 +33,41 @@ const startAnimation = (wrapper: ReactWrapper) => {
3333
};
3434

3535
describe('<Animator />', () => {
36+
it('should warn if name isnt defined', () => {
37+
console.warn = jest.fn();
38+
process.env.NODE_ENV = 'development';
39+
40+
mount(<Animator>{props => <div {...props} />}</Animator>);
41+
42+
expect(console.warn).toHaveBeenCalledWith(`yubaba v0.0.0
43+
44+
"name" prop needs to be defined. Without it you may have problems matching up animator targets. You will not get this error when using "triggerSelfKey" prop.`);
45+
});
46+
47+
it('should not warn if not using name when doing self targetted animator', () => {
48+
console.warn = jest.fn();
49+
process.env.NODE_ENV = 'development';
50+
51+
shallow(<Animator triggerSelfKey="hi">{props => <div {...props} />}</Animator>);
52+
53+
expect(console.warn).not.toHaveBeenCalled();
54+
});
55+
56+
it('should not warn when using name', () => {
57+
console.warn = jest.fn();
58+
process.env.NODE_ENV = 'development';
59+
60+
shallow(
61+
<Animator name="hello" triggerSelfKey="hi">
62+
{props => <div {...props} />}
63+
</Animator>
64+
);
65+
66+
expect(console.warn).not.toHaveBeenCalled();
67+
});
68+
3669
it('should callback when animation has finished', done => {
70+
(getElementBoundingBox as jest.Mock).mockReturnValue(utils.domData());
3771
const Animation = utils.createTestAnimation();
3872
const wrapper = mount(
3973
<utils.AnimatorUnderTest
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { CollectorChildrenProps, InlineStyles } from '../Collector';
2+
import { InjectedProps } from '../VisibilityManager';
3+
4+
export type AnimationFunc = () => Promise<void>;
5+
6+
export interface MappedAnimation {
7+
animate: AnimationFunc;
8+
beforeAnimate: AnimationFunc;
9+
afterAnimate: AnimationFunc;
10+
cleanup: () => void;
11+
}
12+
13+
export type AnimationBlock = MappedAnimation[];
14+
15+
export interface ChildProps {
16+
style?: InlineStyles;
17+
className?: string;
18+
}
19+
20+
export interface AnimatorState {
21+
childProps: ChildProps;
22+
animationsMarkup: React.ReactPortal[];
23+
}
24+
25+
export interface BaseAnimatorProps extends CollectorChildrenProps, InjectedProps {
26+
/**
27+
* Callback called when all animations have finished and been cleaned up. Fired from the triggering Animator
28+
* component.
29+
*/
30+
onFinish: () => void;
31+
32+
/**
33+
* Time this component will wait until it throws away the animation.
34+
* Defaults to 50ms, might want to bump it up if loading something that was code split.
35+
*/
36+
timeToWaitForNextAnimator: number;
37+
38+
/**
39+
* HTMLElement container used when creating elements for animations,
40+
* generally only supporting animations will need this.
41+
*/
42+
container: HTMLElement | (() => HTMLElement);
43+
}
44+
45+
export interface AnimatorProps extends BaseAnimatorProps {
46+
/**
47+
* Name of the animator, this should match the target animator.
48+
*/
49+
name: string;
50+
51+
/**
52+
* Use if your element is expected to persist through an animation.
53+
* When you transition to the next state set your "in" to false and vice versa.
54+
* This lets the Animator components know when to execute the animations.
55+
*
56+
* You can't use this with the "triggerSelfKey".
57+
*/
58+
in?: boolean;
59+
60+
/**
61+
* Will trigger animations over itself when this prop changes.
62+
*
63+
* You can't use the with the "in" prop.
64+
*/
65+
triggerSelfKey?: string;
66+
}

0 commit comments

Comments
 (0)