amis-rpc-design/node_modules/react-native/Libraries/Animated/AnimatedEvent.js

257 lines
7.1 KiB
JavaScript
Raw Normal View History

2023-10-07 19:42:30 +08:00
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from './AnimatedPlatformConfig';
import {findNodeHandle} from '../ReactNative/RendererProxy';
import NativeAnimatedHelper from './NativeAnimatedHelper';
import AnimatedValue from './nodes/AnimatedValue';
import AnimatedValueXY from './nodes/AnimatedValueXY';
import invariant from 'invariant';
export type Mapping =
| {[key: string]: Mapping, ...}
| AnimatedValue
| AnimatedValueXY;
export type EventConfig = {
listener?: ?Function,
useNativeDriver: boolean,
platformConfig?: PlatformConfig,
};
export function attachNativeEvent(
viewRef: any,
eventName: string,
argMapping: $ReadOnlyArray<?Mapping>,
platformConfig: ?PlatformConfig,
): {detach: () => void} {
// Find animated values in `argMapping` and create an array representing their
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
const eventMappings = [];
const traverse = (value: mixed, path: Array<string>) => {
if (value instanceof AnimatedValue) {
value.__makeNative(platformConfig);
eventMappings.push({
nativeEventPath: path,
animatedValueTag: value.__getNativeTag(),
});
} else if (value instanceof AnimatedValueXY) {
traverse(value.x, path.concat('x'));
traverse(value.y, path.concat('y'));
} else if (typeof value === 'object') {
for (const key in value) {
traverse(value[key], path.concat(key));
}
}
};
invariant(
argMapping[0] && argMapping[0].nativeEvent,
'Native driven events only support animated values contained inside `nativeEvent`.',
);
// Assume that the event containing `nativeEvent` is always the first argument.
traverse(argMapping[0].nativeEvent, []);
const viewTag = findNodeHandle(viewRef);
if (viewTag != null) {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.addAnimatedEventToView(
viewTag,
eventName,
mapping,
);
});
}
return {
detach() {
if (viewTag != null) {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.removeAnimatedEventFromView(
viewTag,
eventName,
// $FlowFixMe[incompatible-call]
mapping.animatedValueTag,
);
});
}
},
};
}
function validateMapping(argMapping: $ReadOnlyArray<?Mapping>, args: any) {
const validate = (recMapping: ?Mapping, recEvt: any, key: string) => {
if (recMapping instanceof AnimatedValue) {
invariant(
typeof recEvt === 'number',
'Bad mapping of event key ' +
key +
', should be number but got ' +
typeof recEvt,
);
return;
}
if (recMapping instanceof AnimatedValueXY) {
invariant(
typeof recEvt.x === 'number' && typeof recEvt.y === 'number',
'Bad mapping of event key ' + key + ', should be XY but got ' + recEvt,
);
return;
}
if (typeof recEvt === 'number') {
invariant(
recMapping instanceof AnimatedValue,
'Bad mapping of type ' +
typeof recMapping +
' for key ' +
key +
', event value must map to AnimatedValue',
);
return;
}
invariant(
typeof recMapping === 'object',
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
);
invariant(
typeof recEvt === 'object',
'Bad event of type ' + typeof recEvt + ' for key ' + key,
);
for (const mappingKey in recMapping) {
validate(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
}
};
invariant(
args.length >= argMapping.length,
'Event has less arguments than mapping',
);
argMapping.forEach((mapping, idx) => {
validate(mapping, args[idx], 'arg' + idx);
});
}
export class AnimatedEvent {
_argMapping: $ReadOnlyArray<?Mapping>;
_listeners: Array<Function> = [];
_attachedEvent: ?{detach: () => void, ...};
__isNative: boolean;
__platformConfig: ?PlatformConfig;
constructor(argMapping: $ReadOnlyArray<?Mapping>, config: EventConfig) {
this._argMapping = argMapping;
if (config == null) {
console.warn('Animated.event now requires a second argument for options');
config = {useNativeDriver: false};
}
if (config.listener) {
this.__addListener(config.listener);
}
this._attachedEvent = null;
this.__isNative = NativeAnimatedHelper.shouldUseNativeDriver(config);
this.__platformConfig = config.platformConfig;
}
__addListener(callback: Function): void {
this._listeners.push(callback);
}
__removeListener(callback: Function): void {
this._listeners = this._listeners.filter(listener => listener !== callback);
}
__attach(viewRef: any, eventName: string): void {
invariant(
this.__isNative,
'Only native driven events need to be attached.',
);
this._attachedEvent = attachNativeEvent(
viewRef,
eventName,
this._argMapping,
this.__platformConfig,
);
}
__detach(viewTag: any, eventName: string): void {
invariant(
this.__isNative,
'Only native driven events need to be detached.',
);
this._attachedEvent && this._attachedEvent.detach();
}
__getHandler(): any | ((...args: any) => void) {
if (this.__isNative) {
if (__DEV__) {
let validatedMapping = false;
return (...args: any) => {
if (!validatedMapping) {
validateMapping(this._argMapping, args);
validatedMapping = true;
}
this._callListeners(...args);
};
} else {
return this._callListeners;
}
}
let validatedMapping = false;
return (...args: any) => {
if (__DEV__ && !validatedMapping) {
validateMapping(this._argMapping, args);
validatedMapping = true;
}
const traverse = (
recMapping: ?(Mapping | AnimatedValue),
recEvt: any,
) => {
if (recMapping instanceof AnimatedValue) {
if (typeof recEvt === 'number') {
recMapping.setValue(recEvt);
}
} else if (recMapping instanceof AnimatedValueXY) {
if (typeof recEvt === 'object') {
traverse(recMapping.x, recEvt.x);
traverse(recMapping.y, recEvt.y);
}
} else if (typeof recMapping === 'object') {
for (const mappingKey in recMapping) {
/* $FlowFixMe[prop-missing] (>=0.120.0) This comment suppresses an
* error found when Flow v0.120 was deployed. To see the error,
* delete this comment and run Flow. */
traverse(recMapping[mappingKey], recEvt[mappingKey]);
}
}
};
this._argMapping.forEach((mapping, idx) => {
traverse(mapping, args[idx]);
});
this._callListeners(...args);
};
}
_callListeners = (...args: any) => {
this._listeners.forEach(listener => listener(...args));
};
}