-
Notifications
You must be signed in to change notification settings - Fork 257
/
Copy pathFlipMove.js
775 lines (655 loc) · 24.9 KB
/
FlipMove.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
// @flow
/**
* React Flip Move
* (c) 2016-present Joshua Comeau
*
* For information on how this code is laid out, check out CODE_TOUR.md
*/
/* eslint-disable react/prop-types */
import { Children, cloneElement, createElement, Component } from 'react';
import ReactDOM from 'react-dom';
// eslint-disable-next-line no-duplicate-imports
import type { Element, ElementRef, Key, ChildrenArray } from 'react';
import { parentNodePositionStatic, childIsDisabled } from './error-messages';
import propConverter from './prop-converter';
import {
applyStylesToDOMNode,
createTransitionString,
getNativeNode,
getPositionDelta,
getRelativeBoundingBox,
removeNodeFromDOMFlow,
updateHeightPlaceholder,
whichTransitionEvent,
} from './dom-manipulation';
import { arraysEqual, find } from './helpers';
import type {
Child,
ConvertedProps,
FlipMoveState,
ElementShape,
ChildrenHook,
ChildData,
NodeData,
DelegatedProps,
Styles,
} from './typings';
const transitionEnd = whichTransitionEvent();
const noBrowserSupport = !transitionEnd;
function getKey(childData: ChildData): Key {
return childData.key || '';
}
function getElementChildren(children: ChildrenArray<Child>): Array<Element<*>> {
// Fix incomplete typing of Children.toArray
// eslint-disable-next-line flowtype/no-weak-types
return (Children.toArray(children): any);
}
class FlipMove extends Component<ConvertedProps, FlipMoveState> {
// Copy props.children into state.
// To understand why this is important (and not an anti-pattern), consider
// how "leave" animations work. An item has "left" when the component
// receives a new set of props that do NOT contain the item.
// If we just render the props as-is, the item would instantly disappear.
// We want to keep the item rendered for a little while, until its animation
// can complete. Because we cannot mutate props, we make `state` the source
// of truth.
state = {
children: getElementChildren(
// `this.props` ought to always be defined at this point, but a report
// was made about it not being defined in IE10.
// TODO: Test in IE10, to see if there's an underlying cause that can
// be addressed.
this.props ? this.props.children : [],
).map((element: Element<*>) => ({
...element,
element,
appearing: true,
})),
};
// FlipMove needs to know quite a bit about its children in order to do
// its job. We store these as a property on the instance. We're not using
// state, because we don't want changes to trigger re-renders, we just
// need a place to keep the data for reference, when changes happen.
// This field should not be accessed directly. Instead, use getChildData,
// putChildData, etc...
childrenData: {
/* Populated via callback refs on render. eg
userSpecifiedKey1: {
domNode: <domNode>,
boundingBox: { top, left, right, bottom, width, height },
},
userSpecifiedKey2: { ... },
...
*/
[userSpecifiedKey: Key]: NodeData,
} = {};
// Similarly, track the dom node and box of our parent element.
parentData: NodeData = {
domNode: null,
boundingBox: null,
};
// If `maintainContainerHeight` prop is set to true, we'll create a
// placeholder element which occupies space so that the parent height
// doesn't change when items are removed from the document flow (which
// happens during leave animations)
heightPlaceholderData: NodeData = {
domNode: null,
};
// Keep track of remaining animations so we know when to fire the
// all-finished callback, and clean up after ourselves.
// NOTE: we can't simply use childrenToAnimate.length to track remaining
// animations, because we need to maintain the list of animating children,
// to pass to the `onFinishAll` handler.
remainingAnimations = 0;
childrenToAnimate: Array<Key> = [];
componentDidMount() {
// Because React 16 no longer requires wrapping elements, Flip Move can opt
// to not wrap the children in an element. In that case, find the parent
// element using `findDOMNode`.
if (this.props.typeName === null) {
this.findDOMContainer();
}
// Run our `appearAnimation` if it was requested, right after the
// component mounts.
const shouldTriggerFLIP =
this.props.appearAnimation && !this.isAnimationDisabled(this.props);
if (shouldTriggerFLIP) {
this.prepForAnimation();
this.runAnimation();
}
}
componentDidUpdate(previousProps: ConvertedProps) {
if (this.props.typeName === null) {
this.findDOMContainer();
}
// If the children have been re-arranged, moved, or added/removed,
// trigger the main FLIP animation.
//
// IMPORTANT: We need to make sure that the children have actually changed.
// At the end of the transition, we clean up nodes that need to be removed.
// We DON'T want this cleanup to trigger another update.
const oldChildrenKeys: Array<?Key> = getElementChildren(
this.props.children,
).map((d: Element<*>) => d.key);
const nextChildrenKeys: Array<?Key> = getElementChildren(
previousProps.children,
).map((d: Element<*>) => d.key);
const shouldTriggerFLIP =
!arraysEqual(oldChildrenKeys, nextChildrenKeys) &&
!this.isAnimationDisabled(this.props);
if (shouldTriggerFLIP) {
this.prepForAnimation();
this.runAnimation();
}
}
findDOMContainer = () => {
// eslint-disable-next-line react/no-find-dom-node
const domNode = ReactDOM.findDOMNode(this);
const parentNode = domNode && domNode.parentNode;
// This ought to be impossible, but handling it for Flow's sake.
if (!parentNode || !(parentNode instanceof HTMLElement)) {
return;
}
// If the parent node has static positioning, leave animations might look
// really funky. Let's automatically apply `position: relative` in this
// case, to prevent any quirkiness.
if (window.getComputedStyle(parentNode).position === 'static') {
parentNode.style.position = 'relative';
parentNodePositionStatic();
}
this.parentData.domNode = parentNode;
};
runAnimation = () => {
const dynamicChildren = this.state.children.filter(
this.doesChildNeedToBeAnimated,
);
// Splitting DOM reads and writes to be peformed in batches
const childrenInitialStyles = dynamicChildren.map(child =>
this.computeInitialStyles(child),
);
dynamicChildren.forEach((child, index) => {
this.remainingAnimations += 1;
this.childrenToAnimate.push(getKey(child));
this.animateChild(child, index, childrenInitialStyles[index]);
});
if (typeof this.props.onStartAll === 'function') {
this.callChildrenHook(this.props.onStartAll);
}
};
doesChildNeedToBeAnimated = (child: ChildData) => {
// If the child doesn't have a key, it's an immovable child (one that we
// do not want to do FLIP stuff to.)
if (!getKey(child)) {
return false;
}
const childData: NodeData = this.getChildData(getKey(child));
const childDomNode = childData.domNode;
const childBoundingBox = childData.boundingBox;
const parentBoundingBox = this.parentData.boundingBox;
if (!childDomNode) {
return false;
}
const {
appearAnimation,
enterAnimation,
leaveAnimation,
getPosition,
} = this.props;
const isAppearingWithAnimation = child.appearing && appearAnimation;
const isEnteringWithAnimation = child.entering && enterAnimation;
const isLeavingWithAnimation = child.leaving && leaveAnimation;
if (
isAppearingWithAnimation ||
isEnteringWithAnimation ||
isLeavingWithAnimation
) {
return true;
}
// If it isn't entering/leaving, we want to animate it if it's
// on-screen position has changed.
const [dX, dY] = getPositionDelta({
childDomNode,
childBoundingBox,
parentBoundingBox,
getPosition,
});
return dX !== 0 || dY !== 0;
};
calculateNextSetOfChildren(
nextChildren: Array<Element<*>>,
): Array<ChildData> {
// We want to:
// - Mark all new children as `entering`
// - Pull in previous children that aren't in nextChildren, and mark them
// as `leaving`
// - Preserve the nextChildren list order, with leaving children in their
// appropriate places.
//
// Start by marking new children as 'entering'
const updatedChildren: Array<ChildData> = nextChildren.map(nextChild => {
const child = this.findChildByKey(nextChild.key);
// If the current child did exist, but it was in the midst of leaving,
// we want to treat it as though it's entering
const isEntering = !child || child.leaving;
return { ...nextChild, element: nextChild, entering: isEntering };
});
// This is tricky. We want to keep the nextChildren's ordering, but with
// any just-removed items maintaining their original position.
// eg.
// this.state.children = [ 1, 2, 3, 4 ]
// nextChildren = [ 3, 1 ]
//
// In this example, we've removed the '2' & '4'
// We want to end up with: [ 2, 3, 1, 4 ]
//
// To accomplish that, we'll iterate through this.state.children. whenever
// we find a match, we'll append our `leaving` flag to it, and insert it
// into the nextChildren in its ORIGINAL position. Note that, as we keep
// inserting old items into the new list, the "original" position will
// keep incrementing.
let numOfChildrenLeaving = 0;
this.state.children.forEach((child: ChildData, index) => {
const isLeaving = !find(({ key }) => key === getKey(child), nextChildren);
// If the child isn't leaving (or, if there is no leave animation),
// we don't need to add it into the state children.
if (!isLeaving || !this.props.leaveAnimation) return;
const nextChild: ChildData = { ...child, leaving: true };
const nextChildIndex = index + numOfChildrenLeaving;
updatedChildren.splice(nextChildIndex, 0, nextChild);
numOfChildrenLeaving += 1;
});
return updatedChildren;
}
prepForAnimation() {
// Our animation prep consists of:
// - remove children that are leaving from the DOM flow, so that the new
// layout can be accurately calculated,
// - update the placeholder container height, if needed, to ensure that
// the parent's height doesn't collapse.
const { leaveAnimation, maintainContainerHeight, getPosition } = this.props;
// we need to make all leaving nodes "invisible" to the layout calculations
// that will take place in the next step (this.runAnimation).
if (leaveAnimation) {
const leavingChildren = this.state.children.filter(
child => child.leaving,
);
leavingChildren.forEach(leavingChild => {
const childData = this.getChildData(getKey(leavingChild));
// Warn if child is disabled
if (
!this.isAnimationDisabled(this.props) &&
childData.domNode &&
childData.domNode.disabled
) {
childIsDisabled();
}
// We need to take the items out of the "flow" of the document, so that
// its siblings can move to take its place.
if (childData.boundingBox) {
removeNodeFromDOMFlow(childData, this.props.verticalAlignment);
}
});
if (maintainContainerHeight && this.heightPlaceholderData.domNode) {
updateHeightPlaceholder({
domNode: this.heightPlaceholderData.domNode,
parentData: this.parentData,
getPosition,
});
}
}
// For all children not in the middle of entering or leaving,
// we need to reset the transition, so that the NEW shuffle starts from
// the right place.
this.state.children.forEach(child => {
const { domNode } = this.getChildData(getKey(child));
// Ignore children that don't render DOM nodes (eg. by returning null)
if (!domNode) {
return;
}
if (!child.entering && !child.leaving) {
applyStylesToDOMNode({
domNode,
styles: {
transition: '',
},
});
}
});
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps: ConvertedProps) {
// When the component is handed new props, we need to figure out the
// "resting" position of all currently-rendered DOM nodes.
// We store that data in this.parent and this.children,
// so it can be used later to work out the animation.
this.updateBoundingBoxCaches();
// Convert opaque children object to array.
const nextChildren: Array<Element<*>> = getElementChildren(
nextProps.children,
);
// Next, we need to update our state, so that it contains our new set of
// children. If animation is disabled or unsupported, this is easy;
// we just copy our props into state.
// Assuming that we can animate, though, we have to do some work.
// Essentially, we want to keep just-deleted nodes in the DOM for a bit
// longer, so that we can animate them away.
this.setState({
children: this.isAnimationDisabled(nextProps)
? nextChildren.map(element => ({ ...element, element }))
: this.calculateNextSetOfChildren(nextChildren),
});
}
animateChild(child: ChildData, index: number, childInitialStyles: Styles) {
const { domNode } = this.getChildData(getKey(child));
if (!domNode) {
return;
}
// Apply the relevant style for this DOM node
// This is the offset from its actual DOM position.
// eg. if an item has been re-rendered 20px lower, we want to apply a
// style of 'transform: translate(-20px)', so that it appears to be where
// it started.
// In FLIP terminology, this is the 'Invert' stage.
applyStylesToDOMNode({
domNode,
styles: childInitialStyles,
});
// Start by invoking the onStart callback for this child.
if (this.props.onStart) this.props.onStart(child, domNode);
// Next, animate the item from it's artificially-offset position to its
// new, natural position.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
// NOTE, RE: the double-requestAnimationFrame:
// Sadly, this is the most browser-compatible way to do this I've found.
// Essentially we need to set the initial styles outside of any request
// callbacks to avoid batching them. Then, a frame needs to pass with
// the styles above rendered. Then, on the second frame, we can apply
// our final styles to perform the animation.
// Our first order of business is to "undo" the styles applied in the
// previous frames, while also adding a `transition` property.
// This way, the item will smoothly transition from its old position
// to its new position.
// eslint-disable-next-line flowtype/require-variable-type
let styles = {
transition: createTransitionString(index, this.props),
transform: '',
opacity: '',
};
if (child.appearing && this.props.appearAnimation) {
styles = {
...styles,
...this.props.appearAnimation.to,
};
} else if (child.entering && this.props.enterAnimation) {
styles = {
...styles,
...this.props.enterAnimation.to,
};
} else if (child.leaving && this.props.leaveAnimation) {
styles = {
...styles,
...this.props.leaveAnimation.to,
};
}
// In FLIP terminology, this is the 'Play' stage.
applyStylesToDOMNode({ domNode, styles });
});
});
this.bindTransitionEndHandler(child);
}
bindTransitionEndHandler(child: ChildData) {
const { domNode } = this.getChildData(getKey(child));
if (!domNode) {
return;
}
// The onFinish callback needs to be bound to the transitionEnd event.
// We also need to unbind it when the transition completes, so this ugly
// inline function is required (we need it here so it closes over
// dependent variables `child` and `domNode`)
const transitionEndHandler = (ev: Event) => {
// It's possible that this handler is fired not on our primary transition,
// but on a nested transition (eg. a hover effect). Ignore these cases.
if (ev.target !== domNode) return;
// Remove the 'transition' inline style we added. This is cleanup.
domNode.style.transition = '';
// Trigger any applicable onFinish/onFinishAll hooks
this.triggerFinishHooks(child, domNode);
domNode.removeEventListener(transitionEnd, transitionEndHandler);
if (child.leaving) {
this.removeChildData(getKey(child));
}
};
domNode.addEventListener(transitionEnd, transitionEndHandler);
}
triggerFinishHooks(child: ChildData, domNode: HTMLElement) {
if (this.props.onFinish) this.props.onFinish(child, domNode);
// Reduce the number of children we need to animate by 1,
// so that we can tell when all children have finished.
this.remainingAnimations -= 1;
if (this.remainingAnimations === 0) {
// Remove any items from the DOM that have left, and reset `entering`.
const nextChildren: Array<ChildData> = this.state.children
.filter(({ leaving }) => !leaving)
.map((item: ChildData) => ({
...item,
// fix for Flow
element: item.element,
appearing: false,
entering: false,
}));
this.setState({ children: nextChildren }, () => {
if (typeof this.props.onFinishAll === 'function') {
this.callChildrenHook(this.props.onFinishAll);
}
// Reset our variables for the next iteration
this.childrenToAnimate = [];
});
// If the placeholder was holding the container open while elements were
// leaving, we we can now set its height to zero.
if (this.heightPlaceholderData.domNode) {
this.heightPlaceholderData.domNode.style.height = '0';
}
}
}
callChildrenHook(hook: ChildrenHook) {
const elements: Array<ElementShape> = [];
const domNodes: Array<?HTMLElement> = [];
this.childrenToAnimate.forEach(childKey => {
// If this was an exit animation, the child may no longer exist.
// If so, skip it.
const child = this.findChildByKey(childKey);
if (!child) {
return;
}
elements.push(child);
if (this.hasChildData(childKey)) {
domNodes.push(this.getChildData(childKey).domNode);
}
});
hook(elements, domNodes);
}
updateBoundingBoxCaches() {
// This is the ONLY place that parentData and childrenData's
// bounding boxes are updated. They will be calculated at other times
// to be compared to this value, but it's important that the cache is
// updated once per update.
const parentDomNode = this.parentData.domNode;
if (!parentDomNode) {
return;
}
this.parentData.boundingBox = this.props.getPosition(parentDomNode);
// Splitting DOM reads and writes to be peformed in batches
const childrenBoundingBoxes = [];
this.state.children.forEach(child => {
const childKey = getKey(child);
// It is possible that a child does not have a `key` property;
// Ignore these children, they don't need to be moved.
if (!childKey) {
childrenBoundingBoxes.push(null);
return;
}
// In very rare circumstances, for reasons unknown, the ref is never
// populated for certain children. In this case, avoid doing this update.
// see: https://github.com/joshwcomeau/react-flip-move/pull/91
if (!this.hasChildData(childKey)) {
childrenBoundingBoxes.push(null);
return;
}
const childData = this.getChildData(childKey);
// If the child element returns null, we need to avoid trying to
// account for it
if (!childData.domNode || !child) {
childrenBoundingBoxes.push(null);
return;
}
childrenBoundingBoxes.push(
getRelativeBoundingBox({
childDomNode: childData.domNode,
parentDomNode,
getPosition: this.props.getPosition,
}),
);
});
this.state.children.forEach((child, index) => {
const childKey = getKey(child);
const childBoundingBox = childrenBoundingBoxes[index];
if (!childKey) {
return;
}
this.setChildData(childKey, {
boundingBox: childBoundingBox,
});
});
}
computeInitialStyles(child: ChildData): Styles {
if (child.appearing) {
return this.props.appearAnimation ? this.props.appearAnimation.from : {};
} else if (child.entering) {
if (!this.props.enterAnimation) {
return {};
}
// If this child was in the middle of leaving, it still has its
// absolute positioning styles applied. We need to undo those.
return {
position: '',
top: '',
left: '',
right: '',
bottom: '',
...this.props.enterAnimation.from,
};
} else if (child.leaving) {
return this.props.leaveAnimation ? this.props.leaveAnimation.from : {};
}
const childData = this.getChildData(getKey(child));
const childDomNode = childData.domNode;
const childBoundingBox = childData.boundingBox;
const parentBoundingBox = this.parentData.boundingBox;
if (!childDomNode) {
return {};
}
const [dX, dY] = getPositionDelta({
childDomNode,
childBoundingBox,
parentBoundingBox,
getPosition: this.props.getPosition,
});
return {
transform: `translate(${dX}px, ${dY}px)`,
};
}
// eslint-disable-next-line class-methods-use-this
isAnimationDisabled(props: ConvertedProps): boolean {
// If the component is explicitly passed a `disableAllAnimations` flag,
// we can skip this whole process. Similarly, if all of the numbers have
// been set to 0, there is no point in trying to animate; doing so would
// only cause a flicker (and the intent is probably to disable animations)
// We can also skip this rigamarole if there's no browser support for it.
return (
noBrowserSupport ||
props.disableAllAnimations ||
(props.duration === 0 &&
props.delay === 0 &&
props.staggerDurationBy === 0 &&
props.staggerDelayBy === 0)
);
}
findChildByKey(key: ?Key): ?ChildData {
return find(child => getKey(child) === key, this.state.children);
}
hasChildData(key: Key): boolean {
// Object has some built-in properties on its prototype, such as toString. hasOwnProperty makes
// sure that key is present on childrenData itself, not on its prototype.
return Object.prototype.hasOwnProperty.call(this.childrenData, key);
}
getChildData(key: Key): NodeData {
return this.hasChildData(key) ? this.childrenData[key] : {};
}
setChildData(key: Key, data: NodeData): void {
this.childrenData[key] = { ...this.getChildData(key), ...data };
}
removeChildData(key: Key): void {
delete this.childrenData[key];
this.setState(prevState => ({
...prevState,
children: prevState.children.filter(child => child.element.key !== key),
}));
}
createHeightPlaceholder(): Element<*> {
const { typeName } = this.props;
// If requested, create an invisible element at the end of the list.
// Its height will be modified to prevent the container from collapsing
// prematurely.
const isContainerAList = typeName === 'ul' || typeName === 'ol';
const placeholderType = isContainerAList ? 'li' : 'div';
return createElement(placeholderType, {
key: 'height-placeholder',
ref: (domNode: ?HTMLElement) => {
this.heightPlaceholderData.domNode = domNode;
},
style: { visibility: 'hidden', height: 0 },
});
}
childrenWithRefs(): Array<Element<*>> {
// We need to clone the provided children, capturing a reference to the
// underlying DOM node. Flip Move needs to use the React escape hatches to
// be able to do its calculations.
return this.state.children.map(child =>
cloneElement(child.element, {
ref: (element: ?ElementRef<*>) => {
// Functional Components without a forwarded ref are not supported by FlipMove,
// because they don't have instances.
if (!element) {
return;
}
const domNode: ?HTMLElement = getNativeNode(element);
this.setChildData(getKey(child), { domNode });
},
}),
);
}
render() {
const {
typeName,
delegated,
leaveAnimation,
maintainContainerHeight,
} = this.props;
const children = this.childrenWithRefs();
if (leaveAnimation && maintainContainerHeight) {
children.push(this.createHeightPlaceholder());
}
if (!typeName) return children;
const props: DelegatedProps = {
...delegated,
children,
ref: (node: ?HTMLElement) => {
this.parentData.domNode = node;
},
};
return createElement(typeName, props);
}
}
const enhancedFlipMove = /* #__PURE__ */ propConverter(FlipMove);
export default enhancedFlipMove;