react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
191 lines (173 loc) • 5.29 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
;
import type { MutableRefObject } from 'react';
import { runOnJS, runOnUI } from 'react-native-worklets';
import {
IS_JEST,
processBoxShadowNative,
processBoxShadowWeb,
processColorsInProps,
processTransformOrigin,
ReanimatedError,
SHOULD_BE_USE_WEB,
} from '../common';
import type {
AnimatedStyle,
ShadowNodeWrapper,
StyleProps,
} from '../commonTypes';
import type {
JSPropsOperation,
PropUpdates,
} from '../createAnimatedComponent/commonTypes';
import jsPropsUpdater from '../createAnimatedComponent/JSPropsUpdater';
import type { Descriptor } from '../hook/commonTypes';
import type { ReanimatedHTMLElement } from '../ReanimatedModule/js-reanimated';
import { _updatePropsJS } from '../ReanimatedModule/js-reanimated';
let updateProps: (
viewDescriptors: ViewDescriptorsWrapper,
updates: PropUpdates,
isAnimatedProps?: boolean
) => void;
if (SHOULD_BE_USE_WEB) {
updateProps = (viewDescriptors, updates, isAnimatedProps) => {
'worklet';
viewDescriptors.value?.forEach((viewDescriptor) => {
const component = viewDescriptor.tag as ReanimatedHTMLElement;
if ('boxShadow' in updates) {
updates.boxShadow = processBoxShadowWeb(updates.boxShadow);
}
_updatePropsJS(updates, component, isAnimatedProps);
});
};
} else {
updateProps = (viewDescriptors, updates) => {
'worklet';
/* TODO: Improve this config structure in the future
* The goal is to create a simplified version of `src/css/platform/native/config.ts`,
* containing only properties that require processing and their associated processors
* */
processColorsInProps(updates);
if ('transformOrigin' in updates) {
updates.transformOrigin = processTransformOrigin(updates.transformOrigin);
}
if ('boxShadow' in updates) {
updates.boxShadow = processBoxShadowNative(updates.boxShadow);
}
global.UpdatePropsManager.update(viewDescriptors, updates);
};
}
export const updatePropsJestWrapper = (
viewDescriptors: ViewDescriptorsWrapper,
updates: AnimatedStyle<any>,
animatedValues: MutableRefObject<AnimatedStyle<any>>,
adapters: ((updates: AnimatedStyle<any>) => void)[]
): void => {
adapters.forEach((adapter) => {
adapter(updates);
});
animatedValues.current.value = {
...animatedValues.current.value,
...updates,
};
updateProps(viewDescriptors, updates);
};
export default updateProps;
function updateJSProps(operations: JSPropsOperation[]) {
jsPropsUpdater.updateProps(operations);
}
type NativePropsOperation = {
shadowNodeWrapper: ShadowNodeWrapper;
updates: StyleProps;
};
function createUpdatePropsManager() {
'worklet';
const nativeOperations: NativePropsOperation[] = [];
const jsOperations: JSPropsOperation[] = [];
let flushPending = false;
const processViewUpdates = (tag: number, updates: PropUpdates) =>
Object.entries(updates).reduce<{
nativePropUpdates?: PropUpdates;
jsPropUpdates?: PropUpdates;
}>((acc, [propName, value]) => {
if (global._tagToJSPropNamesMapping[tag]?.[propName]) {
acc.jsPropUpdates ??= {};
acc.jsPropUpdates[propName] = value;
} else {
acc.nativePropUpdates ??= {};
acc.nativePropUpdates[propName] = value;
}
return acc;
}, {});
return {
update(viewDescriptors: ViewDescriptorsWrapper, updates: PropUpdates) {
viewDescriptors.value.forEach(({ tag, shadowNodeWrapper }) => {
const viewTag = tag as number;
const { nativePropUpdates, jsPropUpdates } = processViewUpdates(
viewTag,
updates
);
if (nativePropUpdates) {
nativeOperations.push({
shadowNodeWrapper,
updates: nativePropUpdates,
});
}
if (jsPropUpdates) {
jsOperations.push({
tag: viewTag,
updates: jsPropUpdates,
});
}
if (!flushPending && (nativePropUpdates || jsPropUpdates)) {
queueMicrotask(this.flush);
flushPending = true;
}
});
},
flush(this: void) {
if (nativeOperations.length) {
global._updateProps!(nativeOperations);
nativeOperations.length = 0;
}
if (jsOperations.length) {
runOnJS(updateJSProps)(jsOperations);
jsOperations.length = 0;
}
flushPending = false;
},
};
}
if (SHOULD_BE_USE_WEB) {
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 (!IS_JEST) {
throw new ReanimatedError(
'`UpdatePropsManager` is not available on non-native platform.'
);
}
};
global.UpdatePropsManager = new Proxy(
{},
{
get: maybeThrowError,
set: () => {
maybeThrowError();
return false;
},
}
);
} else {
runOnUI(() => {
'worklet';
global.UpdatePropsManager = createUpdatePropsManager();
})();
}
/**
* This used to be `SharedValue<Descriptors[]>` but objects holding just a
* single `value` prop are fine too.
*/
interface ViewDescriptorsWrapper {
value: Readonly<Descriptor[]>;
}