chore: update map
This commit is contained in:
Generated
Vendored
-460
@@ -1,460 +0,0 @@
|
||||
'use strict';
|
||||
import '../layoutReanimation/animationsManager';
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
import { maybeBuild } from '../animationBuilder';
|
||||
import { IS_JEST, IS_WEB, logger } from '../common';
|
||||
import type { StyleProps } from '../commonTypes';
|
||||
import { LayoutAnimationType } from '../commonTypes';
|
||||
import { SkipEnteringContext } from '../component/LayoutAnimationConfig';
|
||||
import ReanimatedAnimatedComponent from '../css/component/AnimatedComponent';
|
||||
import type { AnimatedStyleHandle } from '../hook/commonTypes';
|
||||
import {
|
||||
configureWebLayoutAnimations,
|
||||
getReducedMotionFromConfig,
|
||||
saveSnapshot,
|
||||
startWebLayoutAnimation,
|
||||
tryActivateLayoutTransition,
|
||||
} from '../layoutReanimation/web';
|
||||
import type { CustomConfig } from '../layoutReanimation/web/config';
|
||||
import { addHTMLMutationObserver } from '../layoutReanimation/web/domUtils';
|
||||
import type { ReanimatedHTMLElement } from '../ReanimatedModule/js-reanimated';
|
||||
import { updateLayoutAnimations } from '../UpdateLayoutAnimations';
|
||||
import type {
|
||||
AnimatedComponentProps,
|
||||
AnimatedComponentRef,
|
||||
AnimatedProps,
|
||||
AnyComponent,
|
||||
IAnimatedComponentInternal,
|
||||
INativeEventsManager,
|
||||
InitialComponentProps,
|
||||
LayoutAnimationOrBuilder,
|
||||
NestedArray,
|
||||
} from './commonTypes';
|
||||
import { InlinePropManager } from './InlinePropManager';
|
||||
import jsPropsUpdater from './JSPropsUpdater';
|
||||
import { NativeEventsManager } from './NativeEventsManager';
|
||||
import { PropsFilter } from './PropsFilter';
|
||||
import { filterStyles, flattenArray } from './utils';
|
||||
|
||||
let id = 0;
|
||||
|
||||
if (IS_WEB) {
|
||||
configureWebLayoutAnimations();
|
||||
}
|
||||
|
||||
export type Options<P> = {
|
||||
setNativeProps?: (ref: AnimatedComponentRef, props: P) => void;
|
||||
jsProps?: string[];
|
||||
};
|
||||
|
||||
export default class AnimatedComponent
|
||||
extends ReanimatedAnimatedComponent<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
>
|
||||
implements IAnimatedComponentInternal
|
||||
{
|
||||
_options?: Options<InitialComponentProps>;
|
||||
_displayName: string;
|
||||
_animatedStyles: StyleProps[] = [];
|
||||
_prevAnimatedStyles: StyleProps[] = [];
|
||||
_animatedProps: Partial<AnimatedComponentProps<AnimatedProps>>[] = [];
|
||||
_prevAnimatedProps: Partial<AnimatedComponentProps<AnimatedProps>>[] = [];
|
||||
_isFirstRender = true;
|
||||
jestInlineStyle: NestedArray<StyleProps> | undefined;
|
||||
jestAnimatedStyle: { value: StyleProps } = { value: {} };
|
||||
jestAnimatedProps: { value: AnimatedProps } = { value: {} };
|
||||
_InlinePropManager = new InlinePropManager();
|
||||
_PropsFilter = new PropsFilter();
|
||||
_NativeEventsManager?: INativeEventsManager;
|
||||
static contextType = SkipEnteringContext;
|
||||
context!: React.ContextType<typeof SkipEnteringContext>;
|
||||
reanimatedID = id++;
|
||||
|
||||
constructor(
|
||||
ChildComponent: AnyComponent,
|
||||
props: AnimatedComponentProps<InitialComponentProps>,
|
||||
displayName: string,
|
||||
options?: Options<InitialComponentProps>
|
||||
) {
|
||||
super(ChildComponent, props);
|
||||
this._options = options;
|
||||
this._displayName = displayName;
|
||||
|
||||
if (IS_JEST) {
|
||||
this.jestAnimatedStyle = { value: {} };
|
||||
this.jestAnimatedProps = { value: {} };
|
||||
}
|
||||
|
||||
const skipEntering = this.context?.current;
|
||||
if (!skipEntering) {
|
||||
this._configureLayoutAnimation(
|
||||
LayoutAnimationType.ENTERING,
|
||||
this.props.entering
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
if (!IS_WEB) {
|
||||
// It exists only on native platforms. We initialize it here because the ref to the animated component is available only post-mount
|
||||
this._NativeEventsManager = new NativeEventsManager(this, this._options);
|
||||
}
|
||||
this._NativeEventsManager?.attachEvents();
|
||||
this._updateAnimatedStylesAndProps();
|
||||
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());
|
||||
|
||||
if (this._options?.jsProps?.length) {
|
||||
jsPropsUpdater.registerComponent(this, this._options.jsProps);
|
||||
}
|
||||
|
||||
this._configureLayoutAnimation(
|
||||
LayoutAnimationType.LAYOUT,
|
||||
this.props.layout
|
||||
);
|
||||
this._configureLayoutAnimation(
|
||||
LayoutAnimationType.EXITING,
|
||||
this.props.exiting
|
||||
);
|
||||
|
||||
if (IS_WEB && this._componentDOMRef) {
|
||||
const element = this._componentDOMRef as ReanimatedHTMLElement;
|
||||
const dummyClone = element.dummyClone;
|
||||
// If the element was cloned (because of the exiting animation), we need bring it
|
||||
// back to the DOM
|
||||
while (dummyClone?.firstChild) {
|
||||
element.appendChild(dummyClone.firstChild);
|
||||
}
|
||||
delete element.dummyClone;
|
||||
|
||||
if (this.props.exiting) {
|
||||
saveSnapshot(element);
|
||||
}
|
||||
|
||||
if (
|
||||
!this.props.entering ||
|
||||
getReducedMotionFromConfig(this.props.entering as CustomConfig)
|
||||
) {
|
||||
this._isFirstRender = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const skipEntering = this.context?.current;
|
||||
if (!skipEntering) {
|
||||
startWebLayoutAnimation(
|
||||
this.props,
|
||||
element,
|
||||
LayoutAnimationType.ENTERING
|
||||
);
|
||||
} else if (element.style) {
|
||||
element.style.visibility = 'initial';
|
||||
}
|
||||
}
|
||||
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this._NativeEventsManager?.detachEvents();
|
||||
this._detachStyles();
|
||||
this._InlinePropManager.detachInlineProps();
|
||||
|
||||
if (this._options?.jsProps?.length) {
|
||||
jsPropsUpdater.unregisterComponent(this);
|
||||
}
|
||||
|
||||
const exiting = this.props.exiting;
|
||||
|
||||
if (
|
||||
IS_WEB &&
|
||||
this._componentDOMRef &&
|
||||
exiting &&
|
||||
!getReducedMotionFromConfig(exiting as CustomConfig)
|
||||
) {
|
||||
addHTMLMutationObserver();
|
||||
|
||||
startWebLayoutAnimation(
|
||||
this.props,
|
||||
this._componentDOMRef as ReanimatedHTMLElement,
|
||||
LayoutAnimationType.EXITING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_detachStyles() {
|
||||
const viewTag = this.getComponentViewTag();
|
||||
if (viewTag !== -1) {
|
||||
for (const style of this._animatedStyles) {
|
||||
style.viewDescriptors.remove(viewTag);
|
||||
}
|
||||
if (this.props.animatedProps?.viewDescriptors) {
|
||||
this.props.animatedProps.viewDescriptors.remove(viewTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNativeProps(props: StyleProps) {
|
||||
if (this._options?.setNativeProps) {
|
||||
this._options.setNativeProps(
|
||||
this._componentRef as AnimatedComponentRef,
|
||||
props
|
||||
);
|
||||
} else {
|
||||
(this._componentRef as AnimatedComponentRef)?.setNativeProps?.(props);
|
||||
}
|
||||
}
|
||||
|
||||
_handleAnimatedStylesUpdate(
|
||||
prevStyles: StyleProps[],
|
||||
currentStyles: StyleProps[],
|
||||
jestAnimatedStyleOrProps: { value: StyleProps }
|
||||
) {
|
||||
const { viewTag, shadowNodeWrapper } = this._getViewInfo();
|
||||
const newStyles = new Set<StyleProps>(currentStyles);
|
||||
|
||||
const isStyleAttached = (style: StyleProps) =>
|
||||
style.viewDescriptors.has(viewTag);
|
||||
|
||||
// remove old styles
|
||||
if (prevStyles) {
|
||||
// in most of the cases, views have only a single animated style and it remains unchanged
|
||||
const hasOneSameStyle =
|
||||
currentStyles.length === 1 &&
|
||||
prevStyles.length === 1 &&
|
||||
currentStyles[0] === prevStyles[0];
|
||||
|
||||
if (hasOneSameStyle && isStyleAttached(prevStyles[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, remove each style that is not present in new styles
|
||||
for (const prevStyle of prevStyles) {
|
||||
const isPresent = currentStyles.some((style) => {
|
||||
if (style === prevStyle && isStyleAttached(style)) {
|
||||
newStyles.delete(style);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (!isPresent) {
|
||||
prevStyle.viewDescriptors.remove(viewTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newStyles.forEach((style) => {
|
||||
style.viewDescriptors.add(
|
||||
{
|
||||
tag: viewTag,
|
||||
shadowNodeWrapper,
|
||||
},
|
||||
style.styleUpdaterContainer
|
||||
);
|
||||
if (IS_JEST) {
|
||||
/**
|
||||
* We need to connect Jest's TestObject instance whose contains just
|
||||
* props object with the updateProps() function where we update the
|
||||
* properties of the component. We can't update props object directly
|
||||
* because TestObject contains a copy of props - look at render
|
||||
* function: const props = this._filterNonAnimatedProps(this.props);
|
||||
*/
|
||||
Object.assign(jestAnimatedStyleOrProps.value, style.initial.value);
|
||||
style.jestAnimatedValues.current = jestAnimatedStyleOrProps;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_updateAnimatedStylesAndProps() {
|
||||
this._handleAnimatedStylesUpdate(
|
||||
this._prevAnimatedStyles,
|
||||
this._animatedStyles,
|
||||
this.jestAnimatedStyle
|
||||
);
|
||||
this._handleAnimatedStylesUpdate(
|
||||
this._prevAnimatedProps,
|
||||
this._animatedProps,
|
||||
this.jestAnimatedProps
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: AnimatedComponentProps<InitialComponentProps>,
|
||||
_prevState: Readonly<unknown>,
|
||||
snapshot: DOMRect | null
|
||||
) {
|
||||
this._configureLayoutAnimation(
|
||||
LayoutAnimationType.LAYOUT,
|
||||
this.props.layout,
|
||||
prevProps.layout
|
||||
);
|
||||
this._configureLayoutAnimation(
|
||||
LayoutAnimationType.EXITING,
|
||||
this.props.exiting,
|
||||
prevProps.exiting
|
||||
);
|
||||
|
||||
this._NativeEventsManager?.updateEvents(prevProps);
|
||||
this._updateAnimatedStylesAndProps();
|
||||
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());
|
||||
|
||||
if (IS_WEB && this.props.exiting && this._componentDOMRef) {
|
||||
saveSnapshot(this._componentDOMRef);
|
||||
}
|
||||
|
||||
if (
|
||||
IS_WEB &&
|
||||
snapshot &&
|
||||
this.props.layout &&
|
||||
!getReducedMotionFromConfig(this.props.layout as CustomConfig)
|
||||
) {
|
||||
tryActivateLayoutTransition(
|
||||
this.props,
|
||||
this._componentDOMRef as ReanimatedHTMLElement,
|
||||
snapshot
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_updateStyles(props: AnimatedComponentProps<InitialComponentProps>): void {
|
||||
const filteredStyles = filterStyles(flattenArray(props.style ?? []));
|
||||
this._prevAnimatedStyles = this._animatedStyles;
|
||||
this._animatedStyles = filteredStyles.animatedStyles;
|
||||
|
||||
const filteredAnimatedProps = filterStyles(
|
||||
flattenArray(props.animatedProps ?? [])
|
||||
);
|
||||
this._prevAnimatedProps = this._animatedProps;
|
||||
this._animatedProps = filteredAnimatedProps.animatedStyles;
|
||||
|
||||
if (filteredAnimatedProps.cssStyle) {
|
||||
if (__DEV__ && filteredStyles.cssStyle) {
|
||||
logger.warn(
|
||||
'AnimatedComponent: CSS properties cannot be used in style and animatedProps at the same time. Using properties from the style object.'
|
||||
);
|
||||
this._cssStyle = filteredStyles.cssStyle;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add all remaining props to cssStyle object
|
||||
// (e.g. SVG components are styled via top level props, not via style object)
|
||||
const mergedProps = {
|
||||
...props,
|
||||
...filteredAnimatedProps.cssStyle,
|
||||
};
|
||||
delete mergedProps.style;
|
||||
delete mergedProps.animatedProps;
|
||||
this._cssStyle = mergedProps;
|
||||
} else {
|
||||
this._cssStyle = filteredStyles.cssStyle ?? {};
|
||||
}
|
||||
}
|
||||
_configureLayoutAnimation(
|
||||
type: LayoutAnimationType,
|
||||
currentConfig: LayoutAnimationOrBuilder | undefined,
|
||||
previousConfig?: LayoutAnimationOrBuilder
|
||||
) {
|
||||
if (IS_WEB || currentConfig === previousConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateLayoutAnimations(
|
||||
type === LayoutAnimationType.ENTERING
|
||||
? this.reanimatedID
|
||||
: this.getComponentViewTag(),
|
||||
type,
|
||||
currentConfig &&
|
||||
maybeBuild(
|
||||
currentConfig,
|
||||
type === LayoutAnimationType.LAYOUT
|
||||
? undefined /* We don't have to warn user if style has common properties with animation for LAYOUT */
|
||||
: this.props?.style,
|
||||
this._displayName
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// This is a component lifecycle method from React, therefore we are not calling it directly.
|
||||
// It is called before the component gets rerendered. This way we can access components' position before it changed
|
||||
// and later on, in componentDidUpdate, calculate translation for layout transition.
|
||||
getSnapshotBeforeUpdate() {
|
||||
if (
|
||||
IS_WEB &&
|
||||
this.props.layout &&
|
||||
this._componentDOMRef?.getBoundingClientRect
|
||||
) {
|
||||
return this._componentDOMRef.getBoundingClientRect();
|
||||
}
|
||||
|
||||
// `getSnapshotBeforeUpdate` has to return value which is not `undefined`.
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const filteredProps = this._PropsFilter.filterNonAnimatedProps(this);
|
||||
|
||||
if (IS_JEST) {
|
||||
filteredProps.jestAnimatedStyle = this.jestAnimatedStyle;
|
||||
filteredProps.jestAnimatedProps = this.jestAnimatedProps;
|
||||
}
|
||||
|
||||
// Layout animations on web are set inside `componentDidMount` method, which is called after first render.
|
||||
// Because of that we can encounter a situation in which component is visible for a short amount of time, and later on animation triggers.
|
||||
// I've tested that on various browsers and devices and it did not happen to me. To be sure that it won't happen to someone else,
|
||||
// I've decided to hide component at first render. Its visibility is reset in `componentDidMount`.
|
||||
if (
|
||||
this._isFirstRender &&
|
||||
IS_WEB &&
|
||||
filteredProps.entering &&
|
||||
!getReducedMotionFromConfig(filteredProps.entering as CustomConfig)
|
||||
) {
|
||||
filteredProps.style = Array.isArray(filteredProps.style)
|
||||
? filteredProps.style.concat([{ visibility: 'hidden' }])
|
||||
: {
|
||||
...(filteredProps.style ?? {}),
|
||||
visibility: 'hidden', // Hide component until `componentDidMount` triggers
|
||||
};
|
||||
}
|
||||
|
||||
const skipEntering = this.context?.current;
|
||||
const nativeID = skipEntering ? undefined : `${this.reanimatedID}`;
|
||||
|
||||
const jestProps = IS_JEST
|
||||
? {
|
||||
jestInlineStyle:
|
||||
this.props.style && filterOutAnimatedStyles(this.props.style),
|
||||
jestAnimatedStyle: this.jestAnimatedStyle,
|
||||
jestAnimatedProps: this.jestAnimatedProps,
|
||||
}
|
||||
: {};
|
||||
|
||||
return super.render({
|
||||
nativeID,
|
||||
...filteredProps,
|
||||
...jestProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function filterOutAnimatedStyles(
|
||||
style: NestedArray<StyleProps | AnimatedStyleHandle | null | undefined>
|
||||
): NestedArray<StyleProps | null | undefined> {
|
||||
if (!style) {
|
||||
return style;
|
||||
}
|
||||
if (!Array.isArray(style)) {
|
||||
return style?.viewDescriptors ? {} : style;
|
||||
}
|
||||
return style
|
||||
.filter(
|
||||
(styleElement) => !(styleElement && 'viewDescriptors' in styleElement)
|
||||
)
|
||||
.map((styleElement) => {
|
||||
if (Array.isArray(styleElement)) {
|
||||
return filterOutAnimatedStyles(styleElement);
|
||||
}
|
||||
return styleElement;
|
||||
});
|
||||
}
|
||||
frontend-admin/node_modules/react-native-reanimated/src/createAnimatedComponent/InlinePropManager.ts
Generated
Vendored
+49
-31
@@ -1,17 +1,24 @@
|
||||
'use strict';
|
||||
import type { StyleProps } from '../commonTypes';
|
||||
import { isSharedValue } from '../isSharedValue';
|
||||
import { startMapper, stopMapper } from '../mappers';
|
||||
import { updateProps } from '../updateProps';
|
||||
import type { ViewDescriptorsSet } from '../ViewDescriptorsSet';
|
||||
import { makeViewDescriptorsSet } from '../ViewDescriptorsSet';
|
||||
import type { StyleProps } from '../reanimated2';
|
||||
import type {
|
||||
IAnimatedComponentInternal,
|
||||
AnimatedComponentProps,
|
||||
AnimatedComponentType,
|
||||
IInlinePropManager,
|
||||
ViewInfo,
|
||||
} from './commonTypes';
|
||||
import { flattenArray } from './utils';
|
||||
import { makeViewDescriptorsSet } from '../reanimated2/ViewDescriptorsSet';
|
||||
import type {
|
||||
ViewDescriptorsSet,
|
||||
ViewRefSet,
|
||||
} from '../reanimated2/ViewDescriptorsSet';
|
||||
import { adaptViewConfig } from '../ConfigHelper';
|
||||
import updateProps from '../reanimated2/UpdateProps';
|
||||
import { stopMapper, startMapper } from '../reanimated2/mappers';
|
||||
import { isSharedValue } from '../reanimated2/isSharedValue';
|
||||
import { shouldBeUseWeb } from '../reanimated2/PlatformChecker';
|
||||
|
||||
const SHOULD_BE_USE_WEB = shouldBeUseWeb();
|
||||
|
||||
function isInlineStyleTransform(transform: unknown): boolean {
|
||||
if (!Array.isArray(transform)) {
|
||||
@@ -30,30 +37,29 @@ function inlinePropsHasChanged(
|
||||
}
|
||||
|
||||
for (const key of Object.keys(styles1)) {
|
||||
if (styles1[key] !== styles2[key]) {
|
||||
return true;
|
||||
}
|
||||
if (styles1[key] !== styles2[key]) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getInlinePropsUpdate(styleValue: StyleProps): unknown {
|
||||
function getInlinePropsUpdate(inlineProps: Record<string, unknown>) {
|
||||
'worklet';
|
||||
if (isSharedValue(styleValue)) {
|
||||
return styleValue.value;
|
||||
}
|
||||
if (Array.isArray(styleValue)) {
|
||||
return styleValue.map(getInlinePropsUpdate);
|
||||
}
|
||||
if (styleValue && typeof styleValue === 'object') {
|
||||
const update: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(styleValue)) {
|
||||
update[key] = getInlinePropsUpdate(value);
|
||||
const update: Record<string, unknown> = {};
|
||||
for (const [key, styleValue] of Object.entries(inlineProps)) {
|
||||
if (isSharedValue(styleValue)) {
|
||||
update[key] = styleValue.value;
|
||||
} else if (Array.isArray(styleValue)) {
|
||||
update[key] = styleValue.map((item) => {
|
||||
return getInlinePropsUpdate(item);
|
||||
});
|
||||
} else if (typeof styleValue === 'object') {
|
||||
update[key] = getInlinePropsUpdate(styleValue as Record<string, unknown>);
|
||||
} else {
|
||||
update[key] = styleValue;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
return styleValue;
|
||||
return update;
|
||||
}
|
||||
|
||||
function extractSharedValuesMapFromProps(
|
||||
@@ -71,14 +77,14 @@ function extractSharedValuesMapFromProps(
|
||||
if (!style) {
|
||||
return;
|
||||
}
|
||||
for (const [styleKey, styleValue] of Object.entries(style)) {
|
||||
for (const [key, styleValue] of Object.entries(style)) {
|
||||
if (isSharedValue(styleValue)) {
|
||||
inlineProps[styleKey] = styleValue;
|
||||
inlineProps[key] = styleValue;
|
||||
} else if (
|
||||
styleKey === 'transform' &&
|
||||
key === 'transform' &&
|
||||
isInlineStyleTransform(styleValue)
|
||||
) {
|
||||
inlineProps[styleKey] = styleValue;
|
||||
inlineProps[key] = styleValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -108,7 +114,7 @@ export function getInlineStyle(
|
||||
isFirstRender: boolean
|
||||
) {
|
||||
if (isFirstRender) {
|
||||
return getInlinePropsUpdate(style) as Record<string, unknown>;
|
||||
return getInlinePropsUpdate(style);
|
||||
}
|
||||
const newStyle: StyleProps = {};
|
||||
for (const [key, styleValue] of Object.entries(style)) {
|
||||
@@ -128,7 +134,8 @@ export class InlinePropManager implements IInlinePropManager {
|
||||
_inlineProps: StyleProps = {};
|
||||
|
||||
public attachInlineProps(
|
||||
animatedComponent: AnimatedComponentType,
|
||||
animatedComponent: React.Component<unknown, unknown> &
|
||||
IAnimatedComponentInternal,
|
||||
viewInfo: ViewInfo
|
||||
) {
|
||||
const newInlineProps: Record<string, unknown> =
|
||||
@@ -139,20 +146,31 @@ export class InlinePropManager implements IInlinePropManager {
|
||||
if (!this._inlinePropsViewDescriptors) {
|
||||
this._inlinePropsViewDescriptors = makeViewDescriptorsSet();
|
||||
|
||||
const { viewTag, shadowNodeWrapper } = viewInfo;
|
||||
const { viewTag, viewName, shadowNodeWrapper, viewConfig } = viewInfo;
|
||||
|
||||
if (Object.keys(newInlineProps).length && viewConfig) {
|
||||
adaptViewConfig(viewConfig);
|
||||
}
|
||||
|
||||
this._inlinePropsViewDescriptors.add({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
tag: viewTag as number,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
name: viewName!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
shadowNodeWrapper: shadowNodeWrapper!,
|
||||
});
|
||||
}
|
||||
const shareableViewDescriptors =
|
||||
this._inlinePropsViewDescriptors.shareableViewDescriptors;
|
||||
|
||||
const maybeViewRef = SHOULD_BE_USE_WEB
|
||||
? ({ items: new Set([animatedComponent]) } as ViewRefSet<unknown>) // see makeViewsRefSet
|
||||
: undefined;
|
||||
const updaterFunction = () => {
|
||||
'worklet';
|
||||
const update = getInlinePropsUpdate(newInlineProps);
|
||||
updateProps(shareableViewDescriptors, update);
|
||||
updateProps(shareableViewDescriptors, update, maybeViewRef);
|
||||
};
|
||||
this._inlineProps = newInlineProps;
|
||||
if (this._inlinePropsMapperId) {
|
||||
|
||||
Generated
Vendored
+108
-42
@@ -1,55 +1,124 @@
|
||||
'use strict';
|
||||
import { runOnUI } from 'react-native-worklets';
|
||||
|
||||
import { SHOULD_BE_USE_WEB } from '../common';
|
||||
import {
|
||||
NativeEventEmitter,
|
||||
NativeModules,
|
||||
findNodeHandle,
|
||||
} from 'react-native';
|
||||
import { shouldBeUseWeb } from '../reanimated2/PlatformChecker';
|
||||
import type { StyleProps } from '../reanimated2';
|
||||
import { runOnJS, runOnUIImmediately } from '../reanimated2/threads';
|
||||
import type {
|
||||
AnimatedComponentProps,
|
||||
AnimatedComponentType,
|
||||
IAnimatedComponentInternal,
|
||||
IJSPropsUpdater,
|
||||
InitialComponentProps,
|
||||
JSPropsOperation,
|
||||
} from './commonTypes';
|
||||
|
||||
class JSPropsUpdaterNative implements IJSPropsUpdater {
|
||||
private static _tagToComponentMapping = new Map<
|
||||
number,
|
||||
AnimatedComponentType
|
||||
>();
|
||||
interface ListenerData {
|
||||
viewTag: number;
|
||||
props: StyleProps;
|
||||
}
|
||||
|
||||
public registerComponent(
|
||||
animatedComponent: AnimatedComponentType,
|
||||
jsProps: string[]
|
||||
const SHOULD_BE_USE_WEB = shouldBeUseWeb();
|
||||
|
||||
class JSPropsUpdaterPaper implements IJSPropsUpdater {
|
||||
private static _tagToComponentMapping = new Map();
|
||||
private _reanimatedEventEmitter: NativeEventEmitter;
|
||||
|
||||
constructor() {
|
||||
this._reanimatedEventEmitter = new NativeEventEmitter(
|
||||
NativeModules.ReanimatedModule
|
||||
);
|
||||
}
|
||||
|
||||
public addOnJSPropsChangeListener(
|
||||
animatedComponent: React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
IAnimatedComponentInternal
|
||||
) {
|
||||
const viewTag = animatedComponent.getComponentViewTag();
|
||||
JSPropsUpdaterNative._tagToComponentMapping.set(viewTag, animatedComponent);
|
||||
|
||||
runOnUI(() => {
|
||||
global._tagToJSPropNamesMapping[viewTag] = Object.fromEntries(
|
||||
jsProps.map((propName) => [propName, true])
|
||||
const viewTag = findNodeHandle(animatedComponent);
|
||||
JSPropsUpdaterPaper._tagToComponentMapping.set(viewTag, animatedComponent);
|
||||
if (JSPropsUpdaterPaper._tagToComponentMapping.size === 1) {
|
||||
const listener = (data: ListenerData) => {
|
||||
const component = JSPropsUpdaterPaper._tagToComponentMapping.get(
|
||||
data.viewTag
|
||||
);
|
||||
component?._updateFromNative(data.props);
|
||||
};
|
||||
this._reanimatedEventEmitter.addListener(
|
||||
'onReanimatedPropsChange',
|
||||
listener
|
||||
);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
public unregisterComponent(animatedComponent: AnimatedComponentType) {
|
||||
const viewTag = animatedComponent.getComponentViewTag();
|
||||
JSPropsUpdaterNative._tagToComponentMapping.delete(viewTag);
|
||||
public removeOnJSPropsChangeListener(
|
||||
animatedComponent: React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
IAnimatedComponentInternal
|
||||
) {
|
||||
const viewTag = findNodeHandle(animatedComponent);
|
||||
JSPropsUpdaterPaper._tagToComponentMapping.delete(viewTag);
|
||||
if (JSPropsUpdaterPaper._tagToComponentMapping.size === 0) {
|
||||
this._reanimatedEventEmitter.removeAllListeners(
|
||||
'onReanimatedPropsChange'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runOnUI(() => {
|
||||
delete global._tagToJSPropNamesMapping[viewTag];
|
||||
})();
|
||||
class JSPropsUpdaterFabric implements IJSPropsUpdater {
|
||||
private static _tagToComponentMapping = new Map();
|
||||
private static isInitialized = false;
|
||||
|
||||
constructor() {
|
||||
if (!JSPropsUpdaterFabric.isInitialized) {
|
||||
const updater = (viewTag: number, props: unknown) => {
|
||||
const component =
|
||||
JSPropsUpdaterFabric._tagToComponentMapping.get(viewTag);
|
||||
component?._updateFromNative(props);
|
||||
};
|
||||
runOnUIImmediately(() => {
|
||||
'worklet';
|
||||
global.updateJSProps = (viewTag: number, props: unknown) => {
|
||||
runOnJS(updater)(viewTag, props);
|
||||
};
|
||||
})();
|
||||
JSPropsUpdaterFabric.isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public updateProps(operations: JSPropsOperation[]) {
|
||||
operations.forEach(({ tag, updates }) => {
|
||||
const component = JSPropsUpdaterNative._tagToComponentMapping.get(tag);
|
||||
component?.setNativeProps(updates);
|
||||
});
|
||||
public addOnJSPropsChangeListener(
|
||||
animatedComponent: React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
IAnimatedComponentInternal
|
||||
) {
|
||||
if (!JSPropsUpdaterFabric.isInitialized) {
|
||||
return;
|
||||
}
|
||||
const viewTag = findNodeHandle(animatedComponent);
|
||||
JSPropsUpdaterFabric._tagToComponentMapping.set(viewTag, animatedComponent);
|
||||
}
|
||||
|
||||
public removeOnJSPropsChangeListener(
|
||||
animatedComponent: React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
IAnimatedComponentInternal
|
||||
) {
|
||||
if (!JSPropsUpdaterFabric.isInitialized) {
|
||||
return;
|
||||
}
|
||||
const viewTag = findNodeHandle(animatedComponent);
|
||||
JSPropsUpdaterFabric._tagToComponentMapping.delete(viewTag);
|
||||
}
|
||||
}
|
||||
|
||||
class JSPropsUpdaterWeb implements IJSPropsUpdater {
|
||||
public registerComponent(
|
||||
public addOnJSPropsChangeListener(
|
||||
_animatedComponent: React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
@@ -58,7 +127,7 @@ class JSPropsUpdaterWeb implements IJSPropsUpdater {
|
||||
// noop
|
||||
}
|
||||
|
||||
public unregisterComponent(
|
||||
public removeOnJSPropsChangeListener(
|
||||
_animatedComponent: React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
@@ -66,23 +135,20 @@ class JSPropsUpdaterWeb implements IJSPropsUpdater {
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public updateProps(_operations: JSPropsOperation[]) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
type JSPropsUpdaterOptions =
|
||||
| typeof JSPropsUpdaterWeb
|
||||
| typeof JSPropsUpdaterNative;
|
||||
| typeof JSPropsUpdaterFabric
|
||||
| typeof JSPropsUpdaterPaper;
|
||||
|
||||
let JSPropsUpdater: JSPropsUpdaterOptions;
|
||||
if (SHOULD_BE_USE_WEB) {
|
||||
JSPropsUpdater = JSPropsUpdaterWeb;
|
||||
} else if (global._IS_FABRIC) {
|
||||
JSPropsUpdater = JSPropsUpdaterFabric;
|
||||
} else {
|
||||
JSPropsUpdater = JSPropsUpdaterNative;
|
||||
JSPropsUpdater = JSPropsUpdaterPaper;
|
||||
}
|
||||
|
||||
const jsPropsUpdater = new JSPropsUpdater();
|
||||
|
||||
export default jsPropsUpdater;
|
||||
export default JSPropsUpdater;
|
||||
|
||||
Generated
Vendored
-150
@@ -1,150 +0,0 @@
|
||||
'use strict';
|
||||
import { findNodeHandle } from '../platformFunctions/findNodeHandle';
|
||||
import { WorkletEventHandler } from '../WorkletEventHandler';
|
||||
import type {
|
||||
AnimatedComponentProps,
|
||||
AnimatedComponentRef,
|
||||
INativeEventsManager,
|
||||
InitialComponentProps,
|
||||
ManagedAnimatedComponent,
|
||||
} from './commonTypes';
|
||||
import { has } from './utils';
|
||||
|
||||
export class NativeEventsManager implements INativeEventsManager {
|
||||
readonly #managedComponent: ManagedAnimatedComponent;
|
||||
readonly #componentOptions?: ComponentOptions;
|
||||
#eventViewTag = -1;
|
||||
|
||||
constructor(component: ManagedAnimatedComponent, options?: ComponentOptions) {
|
||||
this.#managedComponent = component;
|
||||
this.#componentOptions = options;
|
||||
this.#eventViewTag = this.getEventViewTag();
|
||||
}
|
||||
|
||||
public attachEvents() {
|
||||
executeForEachEventHandler(this.#managedComponent.props, (key, handler) => {
|
||||
handler.registerForEvents(this.#eventViewTag, key);
|
||||
});
|
||||
}
|
||||
|
||||
public detachEvents() {
|
||||
executeForEachEventHandler(
|
||||
this.#managedComponent.props,
|
||||
(_key, handler) => {
|
||||
handler.unregisterFromEvents(this.#eventViewTag);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public updateEvents(
|
||||
prevProps: AnimatedComponentProps<InitialComponentProps>
|
||||
) {
|
||||
const computedEventTag = this.getEventViewTag(true);
|
||||
// If the event view tag changes, we need to completely re-mount all events
|
||||
if (this.#eventViewTag !== computedEventTag) {
|
||||
// Remove all bindings from previous props that ran on the old viewTag
|
||||
executeForEachEventHandler(prevProps, (_key, handler) => {
|
||||
handler.unregisterFromEvents(this.#eventViewTag);
|
||||
});
|
||||
// We don't need to unregister from current (new) props, because their events weren't registered yet
|
||||
// Replace the view tag
|
||||
this.#eventViewTag = computedEventTag;
|
||||
// Attach the events with a new viewTag
|
||||
this.attachEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
executeForEachEventHandler(prevProps, (key, prevHandler) => {
|
||||
const newProp = this.#managedComponent.props[key];
|
||||
if (!newProp) {
|
||||
// Prop got deleted
|
||||
prevHandler.unregisterFromEvents(this.#eventViewTag);
|
||||
} else if (
|
||||
isWorkletEventHandler(newProp) &&
|
||||
newProp.workletEventHandler !== prevHandler
|
||||
) {
|
||||
// Prop got changed
|
||||
prevHandler.unregisterFromEvents(this.#eventViewTag);
|
||||
newProp.workletEventHandler.registerForEvents(this.#eventViewTag);
|
||||
}
|
||||
});
|
||||
|
||||
executeForEachEventHandler(this.#managedComponent.props, (key, handler) => {
|
||||
if (!prevProps[key]) {
|
||||
// Prop got added
|
||||
handler.registerForEvents(this.#eventViewTag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getEventViewTag(componentUpdate: boolean = false) {
|
||||
// Get the tag for registering events - since the event emitting view can be nested inside the main component
|
||||
const componentAnimatedRef = this.#managedComponent
|
||||
._componentRef as AnimatedComponentRef & { __nativeTag?: number };
|
||||
if (componentAnimatedRef?.getScrollableNode) {
|
||||
/*
|
||||
In most cases, getScrollableNode() returns a view tag, and findNodeHandle is not required.
|
||||
However, to cover more exotic list cases, we will continue to use findNodeHandle
|
||||
for consistency. For numerical values, findNodeHandle should return the value immediately,
|
||||
as documented here: https://github.com/facebook/react/blob/91061073d57783c061889ac6720ef1ab7f0c2149/packages/react-native-renderer/src/ReactNativePublicCompat.js#L113
|
||||
*/
|
||||
const scrollableNode = componentAnimatedRef.getScrollableNode();
|
||||
if (typeof scrollableNode === 'number') {
|
||||
return scrollableNode;
|
||||
}
|
||||
return findNodeHandle(scrollableNode) ?? -1;
|
||||
}
|
||||
if (this.#componentOptions?.setNativeProps) {
|
||||
// This case ensures backward compatibility with components that
|
||||
// have their own setNativeProps method passed as an option.
|
||||
return findNodeHandle(this.#managedComponent) ?? -1;
|
||||
}
|
||||
if (!componentUpdate) {
|
||||
// On the first render of a component, we may already receive a resolved view tag.
|
||||
return this.#managedComponent.getComponentViewTag();
|
||||
}
|
||||
if (componentAnimatedRef?.__nativeTag) {
|
||||
return componentAnimatedRef.__nativeTag ?? -1;
|
||||
}
|
||||
/*
|
||||
When a component is updated, a child could potentially change and have a different
|
||||
view tag. This can occur with a GestureDetector component.
|
||||
*/
|
||||
return findNodeHandle(componentAnimatedRef) ?? -1;
|
||||
}
|
||||
}
|
||||
|
||||
function isWorkletEventHandler(
|
||||
prop: unknown
|
||||
): prop is WorkletEventHandlerHolder {
|
||||
return (
|
||||
has('workletEventHandler', prop) &&
|
||||
prop.workletEventHandler instanceof WorkletEventHandler
|
||||
);
|
||||
}
|
||||
|
||||
function executeForEachEventHandler(
|
||||
props: AnimatedComponentProps<InitialComponentProps>,
|
||||
callback: (
|
||||
key: string,
|
||||
handler: InstanceType<typeof WorkletEventHandler>
|
||||
) => void
|
||||
) {
|
||||
for (const key in props) {
|
||||
const prop = props[key];
|
||||
if (isWorkletEventHandler(prop)) {
|
||||
callback(key, prop.workletEventHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ComponentOptions = {
|
||||
setNativeProps?: (
|
||||
ref: AnimatedComponentRef,
|
||||
props: InitialComponentProps
|
||||
) => void;
|
||||
};
|
||||
|
||||
type WorkletEventHandlerHolder = {
|
||||
workletEventHandler: InstanceType<typeof WorkletEventHandler>;
|
||||
};
|
||||
Generated
Vendored
+38
-50
@@ -1,19 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
import { initialUpdaterRun } from '../animation';
|
||||
import type { StyleProps } from '../commonTypes';
|
||||
import type { AnimatedStyleHandle } from '../hook/commonTypes';
|
||||
import { isSharedValue } from '../isSharedValue';
|
||||
import { WorkletEventHandler } from '../WorkletEventHandler';
|
||||
import type { StyleProps, SharedValue } from '../reanimated2';
|
||||
import { isSharedValue } from '../reanimated2';
|
||||
import { isChromeDebugger } from '../reanimated2/PlatformChecker';
|
||||
import WorkletEventHandler from '../reanimated2/WorkletEventHandler';
|
||||
import { initialUpdaterRun } from '../reanimated2/animation';
|
||||
import { hasInlineStyles, getInlineStyle } from './InlinePropManager';
|
||||
import type {
|
||||
AnimatedComponentProps,
|
||||
AnimatedComponentType,
|
||||
AnimatedProps,
|
||||
InitialComponentProps,
|
||||
IAnimatedComponentInternal,
|
||||
IPropsFilter,
|
||||
} from './commonTypes';
|
||||
import { getInlineStyle, hasInlineStyles } from './InlinePropManager';
|
||||
import { flattenArray, has } from './utils';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
function dummyListener() {
|
||||
// empty listener we use to assign to listener properties for which animated
|
||||
@@ -21,68 +21,56 @@ function dummyListener() {
|
||||
}
|
||||
|
||||
export class PropsFilter implements IPropsFilter {
|
||||
private _initialPropsMap = new Map<AnimatedStyleHandle, StyleProps>();
|
||||
private _initialStyle = {};
|
||||
|
||||
public filterNonAnimatedProps(
|
||||
component: AnimatedComponentType
|
||||
component: React.Component<unknown, unknown> & IAnimatedComponentInternal
|
||||
): Record<string, unknown> {
|
||||
const inputProps =
|
||||
component.props as AnimatedComponentProps<InitialComponentProps>;
|
||||
const props: Record<string, unknown> = {};
|
||||
|
||||
for (const key in inputProps) {
|
||||
const value = inputProps[key];
|
||||
if (key === 'style') {
|
||||
const styleProp = inputProps.style;
|
||||
const styles = flattenArray<StyleProps>(styleProp ?? []);
|
||||
|
||||
const processedStyle: StyleProps[] = styles.map((style) => {
|
||||
if (style?.viewDescriptors) {
|
||||
const handle = style as AnimatedStyleHandle;
|
||||
|
||||
const processedStyle: StyleProps = styles.map((style) => {
|
||||
if (style && style.viewDescriptors) {
|
||||
// this is how we recognize styles returned by useAnimatedStyle
|
||||
style.viewsRef.add(component);
|
||||
if (component._isFirstRender) {
|
||||
this._initialPropsMap.set(handle, {
|
||||
...handle.initial.value,
|
||||
...initialUpdaterRun(handle.initial.updater),
|
||||
} as StyleProps);
|
||||
this._initialStyle = {
|
||||
...style.initial.value,
|
||||
...this._initialStyle,
|
||||
...initialUpdaterRun<StyleProps>(style.initial.updater),
|
||||
};
|
||||
}
|
||||
|
||||
return this._initialPropsMap.get(handle) ?? {};
|
||||
return this._initialStyle;
|
||||
} else if (hasInlineStyles(style)) {
|
||||
return getInlineStyle(style, component._isFirstRender);
|
||||
} else {
|
||||
return style;
|
||||
}
|
||||
});
|
||||
// keep styles as they were passed by the user
|
||||
// it will help other libs to interpret styles correctly
|
||||
props[key] = processedStyle;
|
||||
props[key] = StyleSheet.flatten(processedStyle);
|
||||
} else if (key === 'animatedProps') {
|
||||
const animatedPropsProp = inputProps.animatedProps;
|
||||
const animatedPropsArray = flattenArray<
|
||||
Partial<AnimatedComponentProps<AnimatedProps>>
|
||||
>(animatedPropsProp ?? []);
|
||||
|
||||
animatedPropsArray.forEach((animatedProps) => {
|
||||
if (animatedProps?.viewDescriptors && animatedProps.initial) {
|
||||
Object.keys(animatedProps.initial.value).forEach(
|
||||
(initialValueKey) => {
|
||||
props[initialValueKey] =
|
||||
animatedProps.initial?.value[initialValueKey];
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
const animatedProp = inputProps.animatedProps as Partial<
|
||||
AnimatedComponentProps<AnimatedProps>
|
||||
>;
|
||||
if (animatedProp.initial !== undefined) {
|
||||
Object.keys(animatedProp.initial.value).forEach((key) => {
|
||||
props[key] = animatedProp.initial?.value[key];
|
||||
animatedProp.viewsRef?.add(component);
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
has('workletEventHandler', value) &&
|
||||
value.workletEventHandler instanceof WorkletEventHandler
|
||||
has('current', value) &&
|
||||
value.current instanceof WorkletEventHandler
|
||||
) {
|
||||
if (value.workletEventHandler.eventNames.length > 0) {
|
||||
value.workletEventHandler.eventNames.forEach((eventName) => {
|
||||
props[eventName] = has('listeners', value.workletEventHandler)
|
||||
? (
|
||||
value.workletEventHandler.listeners as Record<string, unknown>
|
||||
)[eventName]
|
||||
if (value.current.eventNames.length > 0) {
|
||||
value.current.eventNames.forEach((eventName) => {
|
||||
props[eventName] = has('listeners', value.current)
|
||||
? (value.current.listeners as Record<string, unknown>)[eventName]
|
||||
: dummyListener;
|
||||
});
|
||||
} else {
|
||||
@@ -90,9 +78,9 @@ export class PropsFilter implements IPropsFilter {
|
||||
}
|
||||
} else if (isSharedValue(value)) {
|
||||
if (component._isFirstRender) {
|
||||
props[key] = value.value;
|
||||
props[key] = (value as SharedValue<unknown>).value;
|
||||
}
|
||||
} else {
|
||||
} else if (key !== 'onGestureHandlerStateChange' || !isChromeDebugger()) {
|
||||
props[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Generated
Vendored
+39
-93
@@ -1,35 +1,32 @@
|
||||
'use strict';
|
||||
import type { Component, Ref, RefObject } from 'react';
|
||||
|
||||
import type { Ref, Component } from 'react';
|
||||
import type {
|
||||
AnimatedStyle,
|
||||
EntryExitAnimationFunction,
|
||||
ILayoutAnimationBuilder,
|
||||
ShadowNodeWrapper,
|
||||
SharedValue,
|
||||
StyleProps,
|
||||
StyleUpdaterContainer,
|
||||
} from '../commonTypes';
|
||||
import type { SkipEnteringContext } from '../component/LayoutAnimationConfig';
|
||||
import type { BaseAnimationBuilder } from '../layoutReanimation';
|
||||
import type { ViewDescriptorsSet } from '../ViewDescriptorsSet';
|
||||
BaseAnimationBuilder,
|
||||
ILayoutAnimationBuilder,
|
||||
EntryExitAnimationFunction,
|
||||
SharedTransition,
|
||||
SharedValue,
|
||||
} from '../reanimated2';
|
||||
import type {
|
||||
ViewDescriptorsSet,
|
||||
ViewRefSet,
|
||||
} from '../reanimated2/ViewDescriptorsSet';
|
||||
import type { SkipEnteringContext } from '../reanimated2/component/LayoutAnimationConfig';
|
||||
import type { ShadowNodeWrapper } from '../reanimated2/commonTypes';
|
||||
import type { ViewConfig } from '../ConfigHelper';
|
||||
|
||||
export interface AnimatedProps extends Record<string, unknown> {
|
||||
viewDescriptors?: ViewDescriptorsSet;
|
||||
viewsRef?: ViewRefSet<unknown>;
|
||||
initial?: SharedValue<StyleProps>;
|
||||
styleUpdaterContainer?: StyleUpdaterContainer;
|
||||
}
|
||||
|
||||
export interface ViewInfo {
|
||||
viewTag: number | AnimatedComponentRef | HTMLElement | null;
|
||||
viewTag: number | HTMLElement | null;
|
||||
viewName: string | null;
|
||||
shadowNodeWrapper: ShadowNodeWrapper | null;
|
||||
// This is a React host instance view name which might differ from the
|
||||
// Fabric component name. For clarity, we use the viewName property
|
||||
// here and componentName in C++ after converting react viewName to
|
||||
// Fabric component name.
|
||||
// (see react/renderer/componentregistry/componentNameByReactViewName.cpp)
|
||||
viewName?: string;
|
||||
DOMElement?: HTMLElement | null;
|
||||
viewConfig: ViewConfig;
|
||||
}
|
||||
|
||||
export interface IInlinePropManager {
|
||||
@@ -40,49 +37,31 @@ export interface IInlinePropManager {
|
||||
detachInlineProps(): void;
|
||||
}
|
||||
|
||||
export type AnimatedComponentType = React.Component<unknown, unknown> &
|
||||
IAnimatedComponentInternal;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
export type PropUpdates = StyleProps | AnimatedStyle<any>;
|
||||
|
||||
export interface IPropsFilter {
|
||||
filterNonAnimatedProps: (
|
||||
component: AnimatedComponentType
|
||||
component: React.Component<unknown, unknown> & IAnimatedComponentInternal
|
||||
) => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type JSPropsOperation = {
|
||||
tag: number;
|
||||
updates: StyleProps;
|
||||
};
|
||||
|
||||
export interface IJSPropsUpdater {
|
||||
registerComponent(
|
||||
animatedComponent: AnimatedComponentType,
|
||||
jsProps: string[]
|
||||
addOnJSPropsChangeListener(
|
||||
animatedComponent: React.Component<unknown, unknown> &
|
||||
IAnimatedComponentInternal
|
||||
): void;
|
||||
removeOnJSPropsChangeListener(
|
||||
animatedComponent: React.Component<unknown, unknown> &
|
||||
IAnimatedComponentInternal
|
||||
): void;
|
||||
unregisterComponent(animatedComponent: AnimatedComponentType): void;
|
||||
updateProps(operations: JSPropsOperation[]): void;
|
||||
}
|
||||
|
||||
export interface INativeEventsManager {
|
||||
attachEvents(): void;
|
||||
detachEvents(): void;
|
||||
updateEvents(prevProps: AnimatedComponentProps<InitialComponentProps>): void;
|
||||
}
|
||||
|
||||
export type LayoutAnimationStaticContext = {
|
||||
presetName: string;
|
||||
};
|
||||
|
||||
export type AnimatedComponentProps<
|
||||
P extends Record<string, unknown> = Record<string, unknown>,
|
||||
> = P & {
|
||||
ref?: Ref<Component>;
|
||||
export type AnimatedComponentProps<P extends Record<string, unknown>> = P & {
|
||||
forwardedRef?: Ref<Component>;
|
||||
style?: NestedArray<StyleProps>;
|
||||
animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
|
||||
jestAnimatedValues?: RefObject<AnimatedProps>;
|
||||
animatedStyle?: StyleProps;
|
||||
layout?: (
|
||||
| BaseAnimationBuilder
|
||||
@@ -104,67 +83,34 @@ export type AnimatedComponentProps<
|
||||
| Keyframe
|
||||
) &
|
||||
LayoutAnimationStaticContext;
|
||||
sharedTransitionTag?: string;
|
||||
sharedTransitionStyle?: SharedTransition;
|
||||
};
|
||||
|
||||
export type LayoutAnimationOrBuilder = (
|
||||
| BaseAnimationBuilder
|
||||
| typeof BaseAnimationBuilder
|
||||
| EntryExitAnimationFunction
|
||||
| Keyframe
|
||||
| ILayoutAnimationBuilder
|
||||
) &
|
||||
LayoutAnimationStaticContext;
|
||||
|
||||
export interface AnimatedComponentRef extends Component {
|
||||
setNativeProps?: (props: Record<string, unknown>) => void;
|
||||
getScrollableNode?: () => AnimatedComponentRef;
|
||||
getAnimatableRef?: () => AnimatedComponentRef;
|
||||
// Case for SVG components on Web
|
||||
elementRef?: React.RefObject<HTMLElement>;
|
||||
}
|
||||
|
||||
export interface IAnimatedComponentInternalBase {
|
||||
ChildComponent: AnyComponent;
|
||||
_componentRef: AnimatedComponentRef | HTMLElement | null;
|
||||
_hasAnimatedRef: boolean;
|
||||
_viewInfo?: ViewInfo;
|
||||
|
||||
/**
|
||||
* Used for Layout Animations and Animated Styles. It is not related to event
|
||||
* handling.
|
||||
*/
|
||||
getComponentViewTag: () => number;
|
||||
}
|
||||
|
||||
export interface IAnimatedComponentInternal
|
||||
extends IAnimatedComponentInternalBase {
|
||||
_animatedStyles: StyleProps[];
|
||||
_prevAnimatedStyles: StyleProps[];
|
||||
_animatedProps: Partial<AnimatedComponentProps<AnimatedProps>>[];
|
||||
_prevAnimatedProps: Partial<AnimatedComponentProps<AnimatedProps>>[];
|
||||
export interface IAnimatedComponentInternal {
|
||||
_styles: StyleProps[] | null;
|
||||
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
|
||||
_viewTag: number;
|
||||
_isFirstRender: boolean;
|
||||
jestInlineStyle: NestedArray<StyleProps> | undefined;
|
||||
jestAnimatedStyle: { value: StyleProps };
|
||||
jestAnimatedProps: { value: AnimatedProps };
|
||||
animatedStyle: { value: StyleProps };
|
||||
_component: AnimatedComponentRef | HTMLElement | null;
|
||||
_sharedElementTransition: SharedTransition | null;
|
||||
_jsPropsUpdater: IJSPropsUpdater;
|
||||
_InlinePropManager: IInlinePropManager;
|
||||
_PropsFilter: IPropsFilter;
|
||||
/** Doesn't exist on web. */
|
||||
_NativeEventsManager?: INativeEventsManager;
|
||||
_viewInfo?: ViewInfo;
|
||||
context: React.ContextType<typeof SkipEnteringContext>;
|
||||
setNativeProps: (props: StyleProps) => void;
|
||||
}
|
||||
|
||||
export type NestedArray<T> = T | NestedArray<T>[];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AnyComponent = React.ComponentType<any>;
|
||||
|
||||
export interface InitialComponentProps extends Record<string, unknown> {
|
||||
ref?: Ref<Component>;
|
||||
collapsable?: boolean;
|
||||
}
|
||||
|
||||
export type ManagedAnimatedComponent = React.Component<
|
||||
AnimatedComponentProps<InitialComponentProps>
|
||||
> &
|
||||
IAnimatedComponentInternal;
|
||||
|
||||
Generated
Vendored
+561
-57
@@ -1,24 +1,81 @@
|
||||
'use strict';
|
||||
import type {
|
||||
Component,
|
||||
ComponentClass,
|
||||
ComponentType,
|
||||
FunctionComponent,
|
||||
Ref,
|
||||
MutableRefObject,
|
||||
} from 'react';
|
||||
import React from 'react';
|
||||
import type { FlatList, FlatListProps } from 'react-native';
|
||||
|
||||
import type { AnimatedProps } from '../helperTypes';
|
||||
import type { Options } from './AnimatedComponent';
|
||||
import AnimatedComponentImpl from './AnimatedComponent';
|
||||
import { findNodeHandle, Platform } from 'react-native';
|
||||
import WorkletEventHandler from '../reanimated2/WorkletEventHandler';
|
||||
import '../reanimated2/layoutReanimation/animationsManager';
|
||||
import invariant from 'invariant';
|
||||
import { adaptViewConfig } from '../ConfigHelper';
|
||||
import { RNRenderer } from '../reanimated2/platform-specific/RNRenderer';
|
||||
import {
|
||||
configureLayoutAnimations,
|
||||
enableLayoutAnimations,
|
||||
} from '../reanimated2/core';
|
||||
import {
|
||||
SharedTransition,
|
||||
LayoutAnimationType,
|
||||
} from '../reanimated2/layoutReanimation';
|
||||
import type { StyleProps, ShadowNodeWrapper } from '../reanimated2/commonTypes';
|
||||
import { getShadowNodeWrapperFromRef } from '../reanimated2/fabricUtils';
|
||||
import { removeFromPropsRegistry } from '../reanimated2/PropsRegistry';
|
||||
import { getReduceMotionFromConfig } from '../reanimated2/animation/util';
|
||||
import { maybeBuild } from '../animationBuilder';
|
||||
import { SkipEnteringContext } from '../reanimated2/component/LayoutAnimationConfig';
|
||||
import type { AnimateProps } from '../reanimated2';
|
||||
import JSPropsUpdater from './JSPropsUpdater';
|
||||
import type {
|
||||
AnimatedComponentProps,
|
||||
AnimatedProps,
|
||||
InitialComponentProps,
|
||||
AnimatedComponentRef,
|
||||
IAnimatedComponentInternal,
|
||||
ViewInfo,
|
||||
} from './commonTypes';
|
||||
import { has, flattenArray } from './utils';
|
||||
import setAndForwardRef from './setAndForwardRef';
|
||||
import {
|
||||
isFabric,
|
||||
isJest,
|
||||
isWeb,
|
||||
shouldBeUseWeb,
|
||||
} from '../reanimated2/PlatformChecker';
|
||||
import { InlinePropManager } from './InlinePropManager';
|
||||
import { PropsFilter } from './PropsFilter';
|
||||
import {
|
||||
startWebLayoutAnimation,
|
||||
tryActivateLayoutTransition,
|
||||
configureWebLayoutAnimations,
|
||||
getReducedMotionFromConfig,
|
||||
} from '../reanimated2/layoutReanimation/web';
|
||||
import type { CustomConfig } from '../reanimated2/layoutReanimation/web/config';
|
||||
import type { FlatList, FlatListProps } from 'react-native';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnimatableComponent<C extends ComponentType<any>> = C & {
|
||||
jsProps?: string[];
|
||||
const IS_WEB = isWeb();
|
||||
const IS_FABRIC = isFabric();
|
||||
|
||||
function onlyAnimatedStyles(styles: StyleProps[]): StyleProps[] {
|
||||
return styles.filter((style) => style?.viewDescriptors);
|
||||
}
|
||||
|
||||
function isSameAnimatedStyle(
|
||||
style1?: StyleProps,
|
||||
style2?: StyleProps
|
||||
): boolean {
|
||||
// We cannot use equality check to compare useAnimatedStyle outputs directly.
|
||||
// Instead, we can compare its viewsRefs.
|
||||
return style1?.viewsRef === style2?.viewsRef;
|
||||
}
|
||||
|
||||
const isSameAnimatedProps = isSameAnimatedStyle;
|
||||
|
||||
type Options<P> = {
|
||||
setNativeProps: (ref: AnimatedComponentRef, props: P) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,70 +86,517 @@ type AnimatableComponent<C extends ComponentType<any>> = C & {
|
||||
* @see https://docs.swmansion.com/react-native-reanimated/docs/core/createAnimatedComponent
|
||||
*/
|
||||
|
||||
// Don't change the order of overloads, since such a change breaks current behavior
|
||||
export function createAnimatedComponent<P extends object>(
|
||||
component: AnimatableComponent<FunctionComponent<P>>,
|
||||
options?: Options<P>
|
||||
): FunctionComponent<AnimatedProps<P>>;
|
||||
|
||||
export function createAnimatedComponent<P extends object>(
|
||||
component: AnimatableComponent<ComponentClass<P>>,
|
||||
options?: Options<P>
|
||||
): ComponentClass<AnimatedProps<P>>;
|
||||
|
||||
export function createAnimatedComponent<P extends object>(
|
||||
// Actually ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P> but we need this overload too
|
||||
// since some external components (like FastImage) are typed just as ComponentType
|
||||
component: AnimatableComponent<ComponentType<P>>,
|
||||
options?: Options<P>
|
||||
): FunctionComponent<AnimatedProps<P>> | ComponentClass<AnimatedProps<P>>;
|
||||
|
||||
/**
|
||||
* @deprecated Please use `Animated.FlatList` component instead of calling
|
||||
* `Animated.createAnimatedComponent(FlatList)` manually.
|
||||
* @deprecated Please use `Animated.FlatList` component instead of calling `Animated.createAnimatedComponent(FlatList)` manually.
|
||||
*/
|
||||
// @ts-ignore This is required to create this overload, since type of createAnimatedComponent is incorrect and doesn't include typeof FlatList
|
||||
export function createAnimatedComponent(
|
||||
component: AnimatableComponent<typeof FlatList<unknown>>,
|
||||
options?: Options<typeof FlatList<unknown>>
|
||||
): ComponentClass<AnimatedProps<FlatListProps<unknown>>>;
|
||||
component: typeof FlatList<unknown>,
|
||||
options?: Options<any>
|
||||
): ComponentClass<AnimateProps<FlatListProps<unknown>>>;
|
||||
|
||||
export function createAnimatedComponent<P extends object>(
|
||||
component: FunctionComponent<P>,
|
||||
options?: Options<P>
|
||||
): FunctionComponent<AnimateProps<P>>;
|
||||
|
||||
export function createAnimatedComponent<P extends object>(
|
||||
component: ComponentClass<P>,
|
||||
options?: Options<P>
|
||||
): ComponentClass<AnimateProps<P>>;
|
||||
|
||||
export function createAnimatedComponent(
|
||||
Component: AnimatableComponent<ComponentType<InitialComponentProps>>,
|
||||
Component: ComponentType<InitialComponentProps>,
|
||||
options?: Options<InitialComponentProps>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): any {
|
||||
class AnimatedComponent extends AnimatedComponentImpl {
|
||||
static displayName = `AnimatedComponent(${
|
||||
Component.displayName || Component.name || 'Component'
|
||||
})`;
|
||||
invariant(
|
||||
typeof Component !== 'function' ||
|
||||
(Component.prototype && Component.prototype.isReactComponent),
|
||||
`Looks like you're passing a function component \`${Component.name}\` to \`createAnimatedComponent\` function which supports only class components. Please wrap your function component with \`React.forwardRef()\` or use a class component instead.`
|
||||
);
|
||||
|
||||
class AnimatedComponent
|
||||
extends React.Component<AnimatedComponentProps<InitialComponentProps>>
|
||||
implements IAnimatedComponentInternal
|
||||
{
|
||||
_styles: StyleProps[] | null = null;
|
||||
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
|
||||
_viewTag = -1;
|
||||
_isFirstRender = true;
|
||||
animatedStyle: { value: StyleProps } = { value: {} };
|
||||
_component: AnimatedComponentRef | HTMLElement | null = null;
|
||||
_sharedElementTransition: SharedTransition | null = null;
|
||||
_jsPropsUpdater = new JSPropsUpdater();
|
||||
_InlinePropManager = new InlinePropManager();
|
||||
_PropsFilter = new PropsFilter();
|
||||
_viewInfo?: ViewInfo;
|
||||
static displayName: string;
|
||||
static contextType = SkipEnteringContext;
|
||||
context!: React.ContextType<typeof SkipEnteringContext>;
|
||||
|
||||
constructor(props: AnimatedComponentProps<InitialComponentProps>) {
|
||||
// User can override component-defined jsProps via options
|
||||
const jsProps = options?.jsProps ?? Component.jsProps;
|
||||
const modifiedOptions = jsProps?.length
|
||||
? { ...options, jsProps }
|
||||
: options;
|
||||
super(Component, props, AnimatedComponent.displayName, modifiedOptions);
|
||||
super(props);
|
||||
if (isJest()) {
|
||||
this.animatedStyle = { value: {} };
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._attachNativeEvents();
|
||||
this._jsPropsUpdater.addOnJSPropsChangeListener(this);
|
||||
this._attachAnimatedStyles();
|
||||
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());
|
||||
|
||||
if (IS_WEB) {
|
||||
configureWebLayoutAnimations();
|
||||
|
||||
if (!this.props.entering) {
|
||||
this._isFirstRender = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (getReducedMotionFromConfig(this.props.entering as CustomConfig)) {
|
||||
this._isFirstRender = false;
|
||||
return;
|
||||
}
|
||||
|
||||
startWebLayoutAnimation(
|
||||
this.props,
|
||||
this._component as HTMLElement,
|
||||
LayoutAnimationType.ENTERING
|
||||
);
|
||||
}
|
||||
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._detachNativeEvents();
|
||||
this._jsPropsUpdater.removeOnJSPropsChangeListener(this);
|
||||
this._detachStyles();
|
||||
this._InlinePropManager.detachInlineProps();
|
||||
this._sharedElementTransition?.unregisterTransition(this._viewTag);
|
||||
|
||||
if (
|
||||
IS_WEB &&
|
||||
this.props.exiting &&
|
||||
!getReducedMotionFromConfig(this.props.exiting as CustomConfig)
|
||||
) {
|
||||
startWebLayoutAnimation(
|
||||
this.props,
|
||||
this._component as HTMLElement,
|
||||
LayoutAnimationType.EXITING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_getEventViewRef() {
|
||||
// Make sure to get the scrollable node for components that implement
|
||||
// `ScrollResponder.Mixin`.
|
||||
return (this._component as AnimatedComponentRef)?.getScrollableNode
|
||||
? (this._component as AnimatedComponentRef).getScrollableNode?.()
|
||||
: this._component;
|
||||
}
|
||||
|
||||
_attachNativeEvents() {
|
||||
const node = this._getEventViewRef() as AnimatedComponentRef;
|
||||
let viewTag = null; // We set it only if needed
|
||||
|
||||
for (const key in this.props) {
|
||||
const prop = this.props[key];
|
||||
if (
|
||||
has('current', prop) &&
|
||||
prop.current instanceof WorkletEventHandler
|
||||
) {
|
||||
if (viewTag === null) {
|
||||
viewTag = findNodeHandle(options?.setNativeProps ? this : node);
|
||||
}
|
||||
prop.current.registerForEvents(viewTag as number, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_detachNativeEvents() {
|
||||
for (const key in this.props) {
|
||||
const prop = this.props[key];
|
||||
if (
|
||||
has('current', prop) &&
|
||||
prop.current instanceof WorkletEventHandler
|
||||
) {
|
||||
prop.current.unregisterFromEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_detachStyles() {
|
||||
if (IS_WEB && this._styles !== null) {
|
||||
for (const style of this._styles) {
|
||||
if (style?.viewsRef) {
|
||||
style.viewsRef.remove(this);
|
||||
}
|
||||
}
|
||||
} else if (this._viewTag !== -1 && this._styles !== null) {
|
||||
for (const style of this._styles) {
|
||||
style.viewDescriptors.remove(this._viewTag);
|
||||
}
|
||||
if (this.props.animatedProps?.viewDescriptors) {
|
||||
this.props.animatedProps.viewDescriptors.remove(this._viewTag);
|
||||
}
|
||||
if (IS_FABRIC) {
|
||||
removeFromPropsRegistry(this._viewTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_reattachNativeEvents(
|
||||
prevProps: AnimatedComponentProps<InitialComponentProps>
|
||||
) {
|
||||
for (const key in prevProps) {
|
||||
const prop = this.props[key];
|
||||
if (
|
||||
has('current', prop) &&
|
||||
prop.current instanceof WorkletEventHandler &&
|
||||
prop.current.reattachNeeded
|
||||
) {
|
||||
prop.current.unregisterFromEvents();
|
||||
}
|
||||
}
|
||||
|
||||
let viewTag = null;
|
||||
|
||||
for (const key in this.props) {
|
||||
const prop = this.props[key];
|
||||
if (
|
||||
has('current', prop) &&
|
||||
prop.current instanceof WorkletEventHandler &&
|
||||
prop.current.reattachNeeded
|
||||
) {
|
||||
if (viewTag === null) {
|
||||
const node = this._getEventViewRef() as AnimatedComponentRef;
|
||||
viewTag = findNodeHandle(options?.setNativeProps ? this : node);
|
||||
}
|
||||
prop.current.registerForEvents(viewTag as number, key);
|
||||
prop.current.reattachNeeded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateFromNative(props: StyleProps) {
|
||||
if (options?.setNativeProps) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
options.setNativeProps(this._component as AnimatedComponentRef, props);
|
||||
} else {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(this._component as AnimatedComponentRef)?.setNativeProps?.(props);
|
||||
}
|
||||
}
|
||||
|
||||
_getViewInfo(): ViewInfo {
|
||||
if (this._viewInfo !== undefined) {
|
||||
return this._viewInfo;
|
||||
}
|
||||
|
||||
let viewTag: number | HTMLElement | null;
|
||||
let viewName: string | null;
|
||||
let shadowNodeWrapper: ShadowNodeWrapper | null = null;
|
||||
let viewConfig;
|
||||
// Component can specify ref which should be animated when animated version of the component is created.
|
||||
// Otherwise, we animate the component itself.
|
||||
const component = (this._component as AnimatedComponentRef)
|
||||
?.getAnimatableRef
|
||||
? (this._component as AnimatedComponentRef).getAnimatableRef?.()
|
||||
: this;
|
||||
|
||||
if (IS_WEB) {
|
||||
// At this point I assume that `_setComponentRef` was already called and `_component` is set.
|
||||
// `this._component` on web represents HTMLElement of our component, that's why we use casting
|
||||
viewTag = this._component as HTMLElement;
|
||||
viewName = null;
|
||||
shadowNodeWrapper = null;
|
||||
viewConfig = null;
|
||||
} else {
|
||||
// hostInstance can be null for a component that doesn't render anything (render function returns null). Example: svg Stop: https://github.com/react-native-svg/react-native-svg/blob/develop/src/elements/Stop.tsx
|
||||
const hostInstance = RNRenderer.findHostInstance_DEPRECATED(component);
|
||||
if (!hostInstance) {
|
||||
throw new Error(
|
||||
'[Reanimated] Cannot find host instance for this component. Maybe it renders nothing?'
|
||||
);
|
||||
}
|
||||
// we can access view tag in the same way it's accessed here https://github.com/facebook/react/blob/e3f4eb7272d4ca0ee49f27577156b57eeb07cf73/packages/react-native-renderer/src/ReactFabric.js#L146
|
||||
viewTag = hostInstance?._nativeTag;
|
||||
/**
|
||||
* RN uses viewConfig for components for storing different properties of the component(example: https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js#L24).
|
||||
* The name we're looking for is in the field named uiViewClassName.
|
||||
*/
|
||||
viewName = hostInstance?.viewConfig?.uiViewClassName;
|
||||
|
||||
viewConfig = hostInstance?.viewConfig;
|
||||
|
||||
if (IS_FABRIC) {
|
||||
shadowNodeWrapper = getShadowNodeWrapperFromRef(this);
|
||||
}
|
||||
}
|
||||
this._viewInfo = { viewTag, viewName, shadowNodeWrapper, viewConfig };
|
||||
return this._viewInfo;
|
||||
}
|
||||
|
||||
_attachAnimatedStyles() {
|
||||
const styles = this.props.style
|
||||
? onlyAnimatedStyles(flattenArray<StyleProps>(this.props.style))
|
||||
: [];
|
||||
const prevStyles = this._styles;
|
||||
this._styles = styles;
|
||||
|
||||
const prevAnimatedProps = this._animatedProps;
|
||||
this._animatedProps = this.props.animatedProps;
|
||||
|
||||
const { viewTag, viewName, shadowNodeWrapper, viewConfig } =
|
||||
this._getViewInfo();
|
||||
|
||||
// update UI props whitelist for this view
|
||||
const hasReanimated2Props =
|
||||
this.props.animatedProps?.viewDescriptors || styles.length;
|
||||
if (hasReanimated2Props && viewConfig) {
|
||||
adaptViewConfig(viewConfig);
|
||||
}
|
||||
|
||||
this._viewTag = viewTag as number;
|
||||
|
||||
// remove old styles
|
||||
if (prevStyles) {
|
||||
// in most of the cases, views have only a single animated style and it remains unchanged
|
||||
const hasOneSameStyle =
|
||||
styles.length === 1 &&
|
||||
prevStyles.length === 1 &&
|
||||
isSameAnimatedStyle(styles[0], prevStyles[0]);
|
||||
|
||||
if (!hasOneSameStyle) {
|
||||
// otherwise, remove each style that is not present in new styles
|
||||
for (const prevStyle of prevStyles) {
|
||||
const isPresent = styles.some((style) =>
|
||||
isSameAnimatedStyle(style, prevStyle)
|
||||
);
|
||||
if (!isPresent) {
|
||||
prevStyle.viewDescriptors.remove(viewTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
styles.forEach((style) => {
|
||||
style.viewDescriptors.add({
|
||||
tag: viewTag,
|
||||
name: viewName,
|
||||
shadowNodeWrapper,
|
||||
});
|
||||
if (isJest()) {
|
||||
/**
|
||||
* We need to connect Jest's TestObject instance whose contains just props object
|
||||
* with the updateProps() function where we update the properties of the component.
|
||||
* We can't update props object directly because TestObject contains a copy of props - look at render function:
|
||||
* const props = this._filterNonAnimatedProps(this.props);
|
||||
*/
|
||||
this.animatedStyle.value = {
|
||||
...this.animatedStyle.value,
|
||||
...style.initial.value,
|
||||
};
|
||||
style.animatedStyle.current = this.animatedStyle;
|
||||
}
|
||||
});
|
||||
|
||||
// detach old animatedProps
|
||||
if (
|
||||
prevAnimatedProps &&
|
||||
!isSameAnimatedProps(prevAnimatedProps, this.props.animatedProps)
|
||||
) {
|
||||
prevAnimatedProps.viewDescriptors!.remove(viewTag as number);
|
||||
}
|
||||
|
||||
// attach animatedProps property
|
||||
if (this.props.animatedProps?.viewDescriptors) {
|
||||
this.props.animatedProps.viewDescriptors.add({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
tag: viewTag as number,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
name: viewName!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
shadowNodeWrapper: shadowNodeWrapper!,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: AnimatedComponentProps<InitialComponentProps>,
|
||||
_prevState: Readonly<unknown>,
|
||||
// This type comes straight from React
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
snapshot: DOMRect | null
|
||||
) {
|
||||
this._reattachNativeEvents(prevProps);
|
||||
this._attachAnimatedStyles();
|
||||
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());
|
||||
|
||||
// Snapshot won't be undefined because it comes from getSnapshotBeforeUpdate method
|
||||
if (
|
||||
IS_WEB &&
|
||||
snapshot !== null &&
|
||||
this.props.layout &&
|
||||
!getReducedMotionFromConfig(this.props.layout as CustomConfig)
|
||||
) {
|
||||
tryActivateLayoutTransition(
|
||||
this.props,
|
||||
this._component as HTMLElement,
|
||||
snapshot
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_setComponentRef = setAndForwardRef<Component | HTMLElement>({
|
||||
getForwardedRef: () =>
|
||||
this.props.forwardedRef as MutableRefObject<
|
||||
Component<Record<string, unknown>, Record<string, unknown>, unknown>
|
||||
>,
|
||||
setLocalRef: (ref) => {
|
||||
// TODO update config
|
||||
|
||||
const tag = IS_WEB
|
||||
? (ref as HTMLElement)
|
||||
: findNodeHandle(ref as Component);
|
||||
|
||||
const { layout, entering, exiting, sharedTransitionTag } = this.props;
|
||||
if (
|
||||
(layout || entering || exiting || sharedTransitionTag) &&
|
||||
tag != null
|
||||
) {
|
||||
if (!shouldBeUseWeb()) {
|
||||
enableLayoutAnimations(true, false);
|
||||
}
|
||||
if (layout) {
|
||||
configureLayoutAnimations(
|
||||
tag,
|
||||
LayoutAnimationType.LAYOUT,
|
||||
maybeBuild(
|
||||
layout,
|
||||
undefined /* We don't have to warn user if style has common properties with animation for LAYOUT */,
|
||||
AnimatedComponent.displayName
|
||||
)
|
||||
);
|
||||
}
|
||||
const skipEntering = this.context?.current;
|
||||
if (entering && !skipEntering) {
|
||||
configureLayoutAnimations(
|
||||
tag,
|
||||
LayoutAnimationType.ENTERING,
|
||||
maybeBuild(
|
||||
entering,
|
||||
this.props?.style,
|
||||
AnimatedComponent.displayName
|
||||
)
|
||||
);
|
||||
}
|
||||
if (exiting) {
|
||||
const reduceMotionInExiting =
|
||||
'getReduceMotion' in exiting &&
|
||||
typeof exiting.getReduceMotion === 'function'
|
||||
? getReduceMotionFromConfig(exiting.getReduceMotion())
|
||||
: getReduceMotionFromConfig();
|
||||
if (!reduceMotionInExiting) {
|
||||
configureLayoutAnimations(
|
||||
tag,
|
||||
LayoutAnimationType.EXITING,
|
||||
maybeBuild(
|
||||
exiting,
|
||||
this.props?.style,
|
||||
AnimatedComponent.displayName
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sharedTransitionTag && !IS_WEB) {
|
||||
const sharedElementTransition =
|
||||
this.props.sharedTransitionStyle ?? new SharedTransition();
|
||||
const reduceMotionInTransition = getReduceMotionFromConfig(
|
||||
sharedElementTransition.getReduceMotion()
|
||||
);
|
||||
if (!reduceMotionInTransition) {
|
||||
sharedElementTransition.registerTransition(
|
||||
tag as number,
|
||||
sharedTransitionTag
|
||||
);
|
||||
this._sharedElementTransition = sharedElementTransition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ref !== this._component) {
|
||||
this._component = ref;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// This is a component lifecycle method from React, therefore we are not calling it directly.
|
||||
// It is called before the component gets rerendered. This way we can access components' position before it changed
|
||||
// and later on, in componentDidUpdate, calculate translation for layout transition.
|
||||
getSnapshotBeforeUpdate() {
|
||||
if (
|
||||
(this._component as HTMLElement).getBoundingClientRect !== undefined
|
||||
) {
|
||||
return (this._component as HTMLElement).getBoundingClientRect();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this._PropsFilter.filterNonAnimatedProps(this);
|
||||
|
||||
if (isJest()) {
|
||||
props.animatedStyle = this.animatedStyle;
|
||||
}
|
||||
|
||||
// Layout animations on web are set inside `componentDidMount` method, which is called after first render.
|
||||
// Because of that we can encounter a situation in which component is visible for a short amount of time, and later on animation triggers.
|
||||
// I've tested that on various browsers and devices and it did not happen to me. To be sure that it won't happen to someone else,
|
||||
// I've decided to hide component at first render. Its visibility is reset in `componentDidMount`.
|
||||
if (
|
||||
this._isFirstRender &&
|
||||
IS_WEB &&
|
||||
props.entering &&
|
||||
!getReducedMotionFromConfig(props.entering as CustomConfig)
|
||||
) {
|
||||
props.style = {
|
||||
...(props.style ?? {}),
|
||||
visibility: 'hidden', // Hide component until `componentDidMount` triggers
|
||||
};
|
||||
}
|
||||
|
||||
const platformProps = Platform.select({
|
||||
web: {},
|
||||
default: { collapsable: false },
|
||||
});
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
// Casting is used here, because ref can be null - in that case it cannot be assigned to HTMLElement.
|
||||
// After spending some time trying to figure out what to do with this problem, we decided to leave it this way
|
||||
ref={this._setComponentRef as (ref: Component) => void}
|
||||
{...platformProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const animatedComponent = (
|
||||
props: AnimatedComponentProps & { ref: Ref<AnimatedComponent> }
|
||||
) => {
|
||||
AnimatedComponent.displayName = `AnimatedComponent(${
|
||||
Component.displayName || Component.name || 'Component'
|
||||
})`;
|
||||
|
||||
return React.forwardRef<Component>((props, ref) => {
|
||||
return (
|
||||
<AnimatedComponent
|
||||
{...props}
|
||||
// Needed to prevent react from signing AnimatedComponent to the ref
|
||||
// (we want to handle the ref assignment in the AnimatedComponent)
|
||||
ref={null}
|
||||
{...(props.ref === null ? null : { forwardedRef: props.ref })}
|
||||
{...(ref === null ? null : { forwardedRef: ref })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
animatedComponent.displayName =
|
||||
Component.displayName || Component.name || 'Component';
|
||||
|
||||
return animatedComponent;
|
||||
});
|
||||
}
|
||||
|
||||
Generated
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import type { HostInstance } from '../platform-specific/findHostInstance';
|
||||
|
||||
export function getViewInfo(element: HostInstance): {
|
||||
viewName?: string;
|
||||
viewTag?: number;
|
||||
} {
|
||||
return {
|
||||
viewName: (element?._viewConfig?.uiViewClassName ??
|
||||
element?.__internalInstanceHandle?.type ??
|
||||
element?.__internalInstanceHandle?.elementType) as string,
|
||||
viewTag: element?.__nativeTag,
|
||||
};
|
||||
}
|
||||
Generated
Vendored
+1
-26
@@ -1,6 +1,4 @@
|
||||
'use strict';
|
||||
import type { StyleProps } from '../commonTypes';
|
||||
import type { CSSStyle } from '../css';
|
||||
import type { NestedArray } from './commonTypes';
|
||||
|
||||
export function flattenArray<T>(array: NestedArray<T>): T[] {
|
||||
@@ -25,7 +23,7 @@ export function flattenArray<T>(array: NestedArray<T>): T[] {
|
||||
export const has = <K extends string>(
|
||||
key: K,
|
||||
x: unknown
|
||||
): x is { [key in K]: unknown } => {
|
||||
): x is typeof x & { [key in K]: unknown } => {
|
||||
if (typeof x === 'function' || typeof x === 'object') {
|
||||
if (x === null || x === undefined) {
|
||||
return false;
|
||||
@@ -35,26 +33,3 @@ export const has = <K extends string>(
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
type FilteredStyles = {
|
||||
cssStyle: CSSStyle | null;
|
||||
animatedStyles: StyleProps[];
|
||||
};
|
||||
|
||||
export function filterStyles(styles: StyleProps[] | undefined): FilteredStyles {
|
||||
if (!styles) {
|
||||
return { animatedStyles: [], cssStyle: null };
|
||||
}
|
||||
|
||||
return styles.reduce<FilteredStyles>(
|
||||
({ animatedStyles, cssStyle }, style) => {
|
||||
if (style?.viewDescriptors) {
|
||||
animatedStyles.push(style);
|
||||
} else {
|
||||
cssStyle = { ...cssStyle, ...style } as CSSStyle;
|
||||
}
|
||||
return { animatedStyles, cssStyle };
|
||||
},
|
||||
{ animatedStyles: [], cssStyle: null }
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user