Files
react-native/Libraries/LayoutAnimation/LayoutAnimation.js
T
Rubén Norte 08757962d2 Use getter instead of constant to provide access to FabricUIManager binding
Summary:
The `FabricUIManager` module was providing access to the global binding defined by Fabric, adding proper typing for it.

The problem about this module is that it only evaluated the global binding once (during module evaluation) and it cached the result. If this module happened to be loaded before the binding is defined, which can happen in apps using the legacy renderer and Fabric together, this modules caches an `undefined` value indefinitely.

This refactors the module as a getter function to make sure this lazy behavior is handled correctly in the callsites.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D44065186

fbshipit-source-id: 7c5cfe674605f03ecb8ca0dabc4c823e0013da6b
2023-03-15 08:36:40 -07:00

202 lines
5.7 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {Spec as FabricUIManagerSpec} from '../ReactNative/FabricUIManager';
import type {
LayoutAnimationConfig as LayoutAnimationConfig_,
LayoutAnimationProperty,
LayoutAnimationType,
} from '../Renderer/shims/ReactNativeTypes';
import {getFabricUIManager} from '../ReactNative/FabricUIManager';
import ReactNativeFeatureFlags from '../ReactNative/ReactNativeFeatureFlags';
import Platform from '../Utilities/Platform';
const UIManager = require('../ReactNative/UIManager');
// Reexport type
export type LayoutAnimationConfig = LayoutAnimationConfig_;
type OnAnimationDidEndCallback = () => void;
type OnAnimationDidFailCallback = () => void;
let isLayoutAnimationEnabled: boolean =
ReactNativeFeatureFlags.isLayoutAnimationEnabled();
function setEnabled(value: boolean) {
isLayoutAnimationEnabled = isLayoutAnimationEnabled;
}
/**
* Configures the next commit to be animated.
*
* onAnimationDidEnd is guaranteed to be called when the animation completes.
* onAnimationDidFail is *never* called in the classic, pre-Fabric renderer,
* and never has been. In the new renderer (Fabric) it is called only if configuration
* parsing fails.
*/
function configureNext(
config: LayoutAnimationConfig,
onAnimationDidEnd?: OnAnimationDidEndCallback,
onAnimationDidFail?: OnAnimationDidFailCallback,
) {
if (Platform.isTesting) {
return;
}
if (!isLayoutAnimationEnabled) {
return;
}
// Since LayoutAnimations may possibly be disabled for now on iOS (Fabric),
// or Android (non-Fabric) we race a setTimeout with animation completion,
// in case onComplete is never called
// from native. Once LayoutAnimations+Fabric unconditionally ship everywhere, we can
// delete this mechanism at least in the Fabric branch.
let animationCompletionHasRun = false;
const onAnimationComplete = () => {
if (animationCompletionHasRun) {
return;
}
animationCompletionHasRun = true;
clearTimeout(raceWithAnimationId);
onAnimationDidEnd?.();
};
const raceWithAnimationId = setTimeout(
onAnimationComplete,
(config.duration ?? 0) + 17 /* one frame + 1ms */,
);
// In Fabric, LayoutAnimations are unconditionally enabled for Android, and
// conditionally enabled on iOS (pending fully shipping; this is a temporary state).
const FabricUIManager = getFabricUIManager();
if (FabricUIManager?.configureNextLayoutAnimation) {
global?.nativeFabricUIManager?.configureNextLayoutAnimation(
config,
onAnimationComplete,
onAnimationDidFail ??
function () {} /* this will only be called if configuration parsing fails */,
);
return;
}
// This will only run if Fabric is *not* installed.
// If you have Fabric + non-Fabric running in the same VM, non-Fabric LayoutAnimations
// will not work.
if (UIManager?.configureNextLayoutAnimation) {
UIManager.configureNextLayoutAnimation(
config,
onAnimationComplete ?? function () {},
onAnimationDidFail ??
function () {} /* this should never be called in Non-Fabric */,
);
}
}
function create(
duration: number,
type: LayoutAnimationType,
property: LayoutAnimationProperty,
): LayoutAnimationConfig {
return {
duration,
create: {type, property},
update: {type},
delete: {type, property},
};
}
const Presets = {
easeInEaseOut: (create(
300,
'easeInEaseOut',
'opacity',
): LayoutAnimationConfig),
linear: (create(500, 'linear', 'opacity'): LayoutAnimationConfig),
spring: {
duration: 700,
create: {
type: 'linear',
property: 'opacity',
},
update: {
type: 'spring',
springDamping: 0.4,
},
delete: {
type: 'linear',
property: 'opacity',
},
},
};
/**
* Automatically animates views to their new positions when the
* next layout happens.
*
* A common way to use this API is to call it before calling `setState`.
*
* Note that in order to get this to work on **Android** you need to set the following flags via `UIManager`:
*
* UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
*/
const LayoutAnimation = {
/**
* Schedules an animation to happen on the next layout.
*
* @param config Specifies animation properties:
*
* - `duration` in milliseconds
* - `create`, `AnimationConfig` for animating in new views
* - `update`, `AnimationConfig` for animating views that have been updated
*
* @param onAnimationDidEnd Called when the animation finished.
* Only supported on iOS.
* @param onError Called on error. Only supported on iOS.
*/
configureNext,
/**
* Helper for creating a config for `configureNext`.
*/
create,
Types: Object.freeze({
spring: 'spring',
linear: 'linear',
easeInEaseOut: 'easeInEaseOut',
easeIn: 'easeIn',
easeOut: 'easeOut',
keyboard: 'keyboard',
}),
Properties: Object.freeze({
opacity: 'opacity',
scaleX: 'scaleX',
scaleY: 'scaleY',
scaleXY: 'scaleXY',
}),
checkConfig(...args: Array<mixed>) {
console.error('LayoutAnimation.checkConfig(...) has been disabled.');
},
Presets,
easeInEaseOut: (configureNext.bind(null, Presets.easeInEaseOut): (
onAnimationDidEnd?: OnAnimationDidEndCallback,
) => void),
linear: (configureNext.bind(null, Presets.linear): (
onAnimationDidEnd?: OnAnimationDidEndCallback,
) => void),
spring: (configureNext.bind(null, Presets.spring): (
onAnimationDidEnd?: OnAnimationDidEndCallback,
) => void),
setEnabled,
};
module.exports = LayoutAnimation;