UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

212 lines (202 loc) • 7.27 kB
'use strict'; import { runOnUIImmediately } from "../../threads.js"; import { registerEventHandler, unregisterEventHandler } from "../../core.js"; import { Platform } from 'react-native'; import { isJest, shouldBeUseWeb } from "../../PlatformChecker.js"; import { ReanimatedError } from "../../errors.js"; const IS_ANDROID = Platform.OS === 'android'; export class ProgressTransitionManager { _sharedElementCount = 0; _eventHandler = { isRegistered: false, onTransitionProgress: -1, onAppear: -1, onDisappear: -1, onSwipeDismiss: -1 }; addProgressAnimation(viewTag, progressAnimation) { runOnUIImmediately(() => { 'worklet'; global.ProgressTransitionRegister.addProgressAnimation(viewTag, progressAnimation); })(); this.registerEventHandlers(); } removeProgressAnimation(viewTag, isUnmounting = true) { this.unregisterEventHandlers(); runOnUIImmediately(() => { 'worklet'; global.ProgressTransitionRegister.removeProgressAnimation(viewTag, isUnmounting); })(); } registerEventHandlers() { this._sharedElementCount++; const eventHandler = this._eventHandler; if (!eventHandler.isRegistered) { eventHandler.isRegistered = true; const eventPrefix = IS_ANDROID ? 'on' : 'top'; let lastProgressValue = -1; eventHandler.onTransitionProgress = registerEventHandler(event => { 'worklet'; const progress = event.progress; if (progress === lastProgressValue) { // During screen transition, handler receives two events with the same progress // value for both screens, but for modals, there is only one event. To optimize // performance and avoid unnecessary worklet calls, let's skip the second event. return; } lastProgressValue = progress; global.ProgressTransitionRegister.frame(progress); }, eventPrefix + 'TransitionProgress'); eventHandler.onAppear = registerEventHandler(() => { 'worklet'; global.ProgressTransitionRegister.onTransitionEnd(); }, eventPrefix + 'Appear'); if (IS_ANDROID) { // onFinishTransitioning event is available only on Android and // is used to handle closing modals eventHandler.onDisappear = registerEventHandler(() => { 'worklet'; global.ProgressTransitionRegister.onAndroidFinishTransitioning(); }, 'onFinishTransitioning'); } else if (Platform.OS === 'ios') { // topDisappear event is required to handle closing modals on iOS eventHandler.onDisappear = registerEventHandler(() => { 'worklet'; global.ProgressTransitionRegister.onTransitionEnd(true); }, 'topDisappear'); eventHandler.onSwipeDismiss = registerEventHandler(() => { 'worklet'; global.ProgressTransitionRegister.onTransitionEnd(); }, 'topGestureCancel'); } } } unregisterEventHandlers() { this._sharedElementCount--; if (this._sharedElementCount === 0) { const eventHandler = this._eventHandler; eventHandler.isRegistered = false; if (eventHandler.onTransitionProgress !== -1) { unregisterEventHandler(eventHandler.onTransitionProgress); eventHandler.onTransitionProgress = -1; } if (eventHandler.onAppear !== -1) { unregisterEventHandler(eventHandler.onAppear); eventHandler.onAppear = -1; } if (eventHandler.onDisappear !== -1) { unregisterEventHandler(eventHandler.onDisappear); eventHandler.onDisappear = -1; } if (eventHandler.onSwipeDismiss !== -1) { unregisterEventHandler(eventHandler.onSwipeDismiss); eventHandler.onSwipeDismiss = -1; } } } } function createProgressTransitionRegister() { 'worklet'; const progressAnimations = new Map(); const snapshots = new Map(); const currentTransitions = new Set(); const toRemove = new Set(); let skipCleaning = false; let isTransitionRestart = false; const progressTransitionManager = { addProgressAnimation: (viewTag, progressAnimation) => { if (currentTransitions.size > 0 && !progressAnimations.has(viewTag)) { // there is no need to prevent cleaning on android isTransitionRestart = !IS_ANDROID; } progressAnimations.set(viewTag, progressAnimation); }, removeProgressAnimation: (viewTag, isUnmounting) => { if (currentTransitions.size > 0) { // there is no need to prevent cleaning on android isTransitionRestart = !IS_ANDROID; } if (isUnmounting) { // Remove the animation config after the transition is finished toRemove.add(viewTag); } else { // if the animation is removed, without ever being started, it can be removed immediately progressAnimations.delete(viewTag); } }, onTransitionStart: (viewTag, snapshot) => { skipCleaning = isTransitionRestart; snapshots.set(viewTag, snapshot); currentTransitions.add(viewTag); // set initial style for re-parented components progressTransitionManager.frame(0); }, frame: progress => { for (const viewTag of currentTransitions) { const progressAnimation = progressAnimations.get(viewTag); if (!progressAnimation) { continue; } const snapshot = snapshots.get(viewTag); progressAnimation(viewTag, snapshot, progress); } }, onAndroidFinishTransitioning: () => { if (toRemove.size > 0) { // it should be ran only on modal closing progressTransitionManager.onTransitionEnd(); } }, onTransitionEnd: (removeViews = false) => { if (currentTransitions.size === 0) { toRemove.clear(); return; } if (skipCleaning) { skipCleaning = false; isTransitionRestart = false; return; } for (const viewTag of currentTransitions) { global._notifyAboutEnd(viewTag, removeViews); } currentTransitions.clear(); if (isTransitionRestart) { // on transition restart, progressAnimations should be saved // because they potentially can be used in the next transition return; } snapshots.clear(); if (toRemove.size > 0) { for (const viewTag of toRemove) { progressAnimations.delete(viewTag); global._notifyAboutEnd(viewTag, removeViews); } toRemove.clear(); } } }; return progressTransitionManager; } if (shouldBeUseWeb()) { const maybeThrowError = () => { // Jest attempts to access a property of this object to check if it is a Jest mock // so we can't throw an error in the getter. if (!isJest()) { throw new ReanimatedError('`ProgressTransitionRegister` is not available on non-native platform.'); } }; global.ProgressTransitionRegister = new Proxy({}, { get: maybeThrowError, set: () => { maybeThrowError(); return false; } }); } else { runOnUIImmediately(() => { 'worklet'; global.ProgressTransitionRegister = createProgressTransitionRegister(); })(); } //# sourceMappingURL=ProgressTransitionManager.js.map