diff --git a/Libraries/Animated/AnimatedEvent.js b/Libraries/Animated/AnimatedEvent.js index b6de725948a961..b9101f16d42653 100644 --- a/Libraries/Animated/AnimatedEvent.js +++ b/Libraries/Animated/AnimatedEvent.js @@ -11,6 +11,7 @@ 'use strict'; const AnimatedValue = require('./nodes/AnimatedValue'); +const AnimatedValueXY = require('./nodes/AnimatedValueXY'); const NativeAnimatedHelper = require('./NativeAnimatedHelper'); const ReactNative = require('../Renderer/shims/ReactNative'); @@ -18,7 +19,10 @@ const invariant = require('invariant'); const {shouldUseNativeDriver} = require('./NativeAnimatedHelper'); -export type Mapping = {[key: string]: Mapping, ...} | AnimatedValue; +export type Mapping = + | {[key: string]: Mapping, ...} + | AnimatedValue + | AnimatedValueXY; export type EventConfig = { listener?: ?Function, useNativeDriver: boolean, @@ -41,6 +45,9 @@ function attachNativeEvent( 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)); @@ -95,6 +102,13 @@ function validateMapping(argMapping, args) { ); 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, @@ -204,22 +218,27 @@ class AnimatedEvent { validatedMapping = true; } - const traverse = (recMapping, recEvt, key) => { + const traverse = (recMapping, recEvt) => { 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], mappingKey); + traverse(recMapping[mappingKey], recEvt[mappingKey]); } } }; this._argMapping.forEach((mapping, idx) => { - traverse(mapping, args[idx], 'arg' + idx); + traverse(mapping, args[idx]); }); this._callListeners(...args); diff --git a/Libraries/Animated/__tests__/Animated-test.js b/Libraries/Animated/__tests__/Animated-test.js index 2b2ffe04dfdfb8..d09684faadfa69 100644 --- a/Libraries/Animated/__tests__/Animated-test.js +++ b/Libraries/Animated/__tests__/Animated-test.js @@ -638,6 +638,16 @@ describe('Animated tests', () => { handler({bar: 'ignoreBar'}, {state: {baz: 'ignoreBaz', foo: 42}}); expect(value.__getValue()).toBe(42); }); + + it('should validate AnimatedValueXY mappings', () => { + const value = new Animated.ValueXY({x: 0, y: 0}); + const handler = Animated.event([{state: value}], { + useNativeDriver: false, + }); + handler({state: {x: 1, y: 2}}); + expect(value.__getValue()).toMatchObject({x: 1, y: 2}); + }); + it('should call listeners', () => { const value = new Animated.Value(0); const listener = jest.fn(); @@ -650,6 +660,7 @@ describe('Animated tests', () => { expect(listener.mock.calls.length).toBe(1); expect(listener).toBeCalledWith({foo: 42}); }); + it('should call forked event listeners, with Animated.event() listener', () => { const value = new Animated.Value(0); const listener = jest.fn(); @@ -666,6 +677,7 @@ describe('Animated tests', () => { expect(listener2.mock.calls.length).toBe(1); expect(listener2).toBeCalledWith({foo: 42}); }); + it('should call forked event listeners, with js listener', () => { const listener = jest.fn(); const listener2 = jest.fn(); @@ -676,6 +688,7 @@ describe('Animated tests', () => { expect(listener2.mock.calls.length).toBe(1); expect(listener2).toBeCalledWith({foo: 42}); }); + it('should call forked event listeners, with undefined listener', () => { const listener = undefined; const listener2 = jest.fn(); diff --git a/Libraries/Animated/__tests__/AnimatedNative-test.js b/Libraries/Animated/__tests__/AnimatedNative-test.js index e5ecec1fb7d648..a6655d83952cea 100644 --- a/Libraries/Animated/__tests__/AnimatedNative-test.js +++ b/Libraries/Animated/__tests__/AnimatedNative-test.js @@ -248,6 +248,24 @@ describe('Native Animated', () => { ); }); + it('shoud map AnimatedValueXY', () => { + const value = new Animated.ValueXY({x: 0, y: 0}); + value.__makeNative(); + const event = Animated.event([{nativeEvent: {state: value}}], { + useNativeDriver: true, + }); + + TestRenderer.create(); + ['x', 'y'].forEach((key, idx) => + expect( + NativeAnimatedModule.addAnimatedEventToView, + ).toHaveBeenNthCalledWith(idx + 1, expect.any(Number), 'onTouchMove', { + nativeEventPath: ['state', key], + animatedValueTag: value[key].__getNativeTag(), + }), + ); + }); + it('should throw on invalid event path', () => { const value = new Animated.Value(0); value.__makeNative();