react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
192 lines (170 loc) • 6.29 kB
text/typescript
import JSReanimated from './JSReanimated';
import type { StyleProps } from '../commonTypes';
import type { AnimatedStyle } from '../helperTypes';
import { isWeb } from '../PlatformChecker';
import { PropsAllowlists } from '../../propsAllowlists';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createReactDOMStyle: (style: any) => any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createTransformValue: (transform: any) => any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createTextShadowValue: (style: any) => void | string;
if (isWeb()) {
try {
createReactDOMStyle =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('react-native-web/dist/exports/StyleSheet/compiler/createReactDOMStyle').default;
} catch (e) {}
try {
// React Native Web 0.19+
createTransformValue =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('react-native-web/dist/exports/StyleSheet/preprocess').createTransformValue;
} catch (e) {}
try {
createTextShadowValue =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('react-native-web/dist/exports/StyleSheet/preprocess').createTextShadowValue;
} catch (e) {}
}
const reanimatedJS = new JSReanimated();
global._makeShareableClone = () => {
throw new Error(
'[Reanimated] _makeShareableClone should never be called in JSReanimated.'
);
};
global._scheduleOnJS = () => {
throw new Error(
'[Reanimated] _scheduleOnJS should never be called in JSReanimated.'
);
};
global._scheduleOnRuntime = () => {
throw new Error(
'[Reanimated] _scheduleOnRuntime should never be called in JSReanimated.'
);
};
interface JSReanimatedComponent {
previousStyle: StyleProps;
setNativeProps?: (style: StyleProps) => void;
style?: StyleProps;
props: Record<string, string | number>;
_touchableNode: {
setAttribute: (key: string, props: unknown) => void;
};
}
export interface ReanimatedHTMLElement extends HTMLElement {
previousStyle: StyleProps;
setNativeProps?: (style: StyleProps) => void;
props: Record<string, string | number>;
_touchableNode: {
setAttribute: (key: string, props: unknown) => void;
};
reanimatedDummy?: boolean;
removedAfterAnimation?: boolean;
}
export const _updatePropsJS = (
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
updates: StyleProps | AnimatedStyle<any>,
viewRef: { _component?: JSReanimatedComponent | ReanimatedHTMLElement },
isAnimatedProps?: boolean
): void => {
if (viewRef._component) {
const component = viewRef._component;
const [rawStyles] = Object.keys(updates).reduce(
(acc: [StyleProps, AnimatedStyle<any>], key) => {
const value = updates[key];
const index = typeof value === 'function' ? 1 : 0;
acc[index][key] = value;
return acc;
},
[{}, {}]
);
if (typeof component.setNativeProps === 'function') {
// This is the legacy way to update props on React Native Web <= 0.18.
// Also, some components (e.g. from react-native-svg) don't have styles
// and always provide setNativeProps function instead (even on React Native Web 0.19+).
setNativeProps(component, rawStyles, isAnimatedProps);
} else if (
createReactDOMStyle !== undefined &&
component.style !== undefined
) {
// React Native Web 0.19+ no longer provides setNativeProps function,
// so we need to update DOM nodes directly.
updatePropsDOM(component, rawStyles, isAnimatedProps);
} else if (Object.keys(component.props).length > 0) {
Object.keys(component.props).forEach((key) => {
if (!rawStyles[key]) {
return;
}
const dashedKey = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
component._touchableNode.setAttribute(dashedKey, rawStyles[key]);
});
} else {
const componentName =
'className' in component ? component?.className : '';
console.warn(
`[Reanimated] It's not possible to manipulate the component ${componentName}`
);
}
}
};
const setNativeProps = (
component: JSReanimatedComponent | ReanimatedHTMLElement,
newProps: StyleProps,
isAnimatedProps?: boolean
): void => {
if (isAnimatedProps) {
const uiProps: Record<string, unknown> = {};
for (const key in newProps) {
if (isNativeProp(key)) {
uiProps[key] = newProps[key];
}
}
// Only update UI props directly on the component,
// other props can be updated as standard style props.
component.setNativeProps?.(uiProps);
}
const previousStyle = component.previousStyle ? component.previousStyle : {};
const currentStyle = { ...previousStyle, ...newProps };
component.previousStyle = currentStyle;
component.setNativeProps?.({ style: currentStyle });
};
const updatePropsDOM = (
component: JSReanimatedComponent | HTMLElement,
style: StyleProps,
isAnimatedProps?: boolean
): void => {
const previousStyle = (component as JSReanimatedComponent).previousStyle
? (component as JSReanimatedComponent).previousStyle
: {};
const currentStyle = { ...previousStyle, ...style };
(component as JSReanimatedComponent).previousStyle = currentStyle;
const domStyle = createReactDOMStyle(currentStyle);
if (Array.isArray(domStyle.transform) && createTransformValue !== undefined) {
domStyle.transform = createTransformValue(domStyle.transform);
}
if (
createTextShadowValue !== undefined &&
(domStyle.textShadowColor ||
domStyle.textShadowRadius ||
domStyle.textShadowOffset)
) {
domStyle.textShadow = createTextShadowValue({
textShadowColor: domStyle.textShadowColor,
textShadowOffset: domStyle.textShadowOffset,
textShadowRadius: domStyle.textShadowRadius,
});
}
for (const key in domStyle) {
if (isAnimatedProps) {
(component as HTMLElement).setAttribute(key, domStyle[key]);
} else {
(component.style as StyleProps)[key] = domStyle[key];
}
}
};
function isNativeProp(propName: string): boolean {
return !!PropsAllowlists.NATIVE_THREAD_PROPS_WHITELIST[propName];
}
export default reanimatedJS;
;