UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

179 lines (162 loc) • 6.02 kB
'use strict'; import type { AnimatedStyle, StyleProps } from '../../commonTypes'; import { ReanimatedError } from '../../errors'; import { logger } from '../../logger'; import { PropsAllowlists } from '../../propsAllowlists'; import { createReactDOMStyle, createTextShadowValue, createTransformValue, } from './webUtils'; export { createJSReanimatedModule } from './JSReanimated'; // TODO: Install these global functions in a more suitable location. global._makeShareableClone = () => { throw new ReanimatedError( '`_makeShareableClone` should never be called from React runtime.' ); }; global._scheduleHostFunctionOnJS = () => { throw new ReanimatedError( '`_scheduleOnJS` should never be called from React runtime.' ); }; global._scheduleOnRuntime = () => { throw new ReanimatedError( '`_scheduleOnRuntime` should never be called from React runtime.' ); }; 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; } // TODO: Move these functions outside of index file. export const _updatePropsJS = ( // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents updates: StyleProps | AnimatedStyle<any>, viewRef: (JSReanimatedComponent | ReanimatedHTMLElement) & { getAnimatableRef?: () => JSReanimatedComponent | ReanimatedHTMLElement; }, isAnimatedProps?: boolean ): void => { if (viewRef) { const component = viewRef.getAnimatableRef ? viewRef.getAnimatableRef() : viewRef; 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 : ''; logger.warn( `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) { // We need to explicitly set the 'text' property on input component because React Native's // internal _valueTracker (https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/client/inputValueTracking.js) // prevents updates when only modifying attributes. if ((component as HTMLElement).nodeName === 'INPUT' && key === 'text') { (component as HTMLInputElement).value = domStyle[key] as string; } else { (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]; }