diff --git a/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js b/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js index 1f9e9b77b0a..99dbff9956b 100644 --- a/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js +++ b/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js @@ -5,22 +5,55 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow strict-local */ // flowlint unsafe-getters-setters:off import type { HostComponent, + INativeMethods, + InternalInstanceHandle, MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, + ViewConfig, } from '../../Renderer/shims/ReactNativeTypes'; import type {ElementRef} from 'react'; +import TextInputState from '../../Components/TextInput/TextInputState'; +import {getFabricUIManager} from '../../ReactNative/FabricUIManager'; +import {create as createAttributePayload} from '../../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload'; +import warnForStyleProps from '../../ReactNative/ReactFabricPublicInstance/warnForStyleProps'; import ReadOnlyElement from './ReadOnlyElement'; +import ReadOnlyNode from './ReadOnlyNode'; +import {getShadowNode} from './ReadOnlyNode'; +import nullthrows from 'nullthrows'; + +const noop = () => {}; + +export default class ReactNativeElement + extends ReadOnlyElement + implements INativeMethods +{ + // These need to be accessible from `ReactFabricPublicInstanceUtils`. + __nativeTag: number; + __internalInstanceHandle: InternalInstanceHandle; + + _viewConfig: ViewConfig; + + constructor( + tag: number, + viewConfig: ViewConfig, + internalInstanceHandle: InternalInstanceHandle, + ) { + super(internalInstanceHandle); + + this.__nativeTag = tag; + this.__internalInstanceHandle = internalInstanceHandle; + this._viewConfig = viewConfig; + } -export default class ReactNativeElement extends ReadOnlyElement { get offsetHeight(): number { throw new TypeError('Unimplemented'); } @@ -46,30 +79,71 @@ export default class ReactNativeElement extends ReadOnlyElement { */ blur(): void { - throw new TypeError('Unimplemented'); + // $FlowFixMe[incompatible-exact] Migrate all usages of `NativeMethods` to an interface to fix this. + TextInputState.blurTextInput(this); } - focus(): void { - throw new TypeError('Unimplemented'); + focus() { + // $FlowFixMe[incompatible-exact] Migrate all usages of `NativeMethods` to an interface to fix this. + TextInputState.focusTextInput(this); } - measure(callback: MeasureOnSuccessCallback): void { - throw new TypeError('Unimplemented'); + measure(callback: MeasureOnSuccessCallback) { + const node = getShadowNode(this); + if (node != null) { + nullthrows(getFabricUIManager()).measure(node, callback); + } } - measureInWindow(callback: MeasureInWindowOnSuccessCallback): void { - throw new TypeError('Unimplemented'); + measureInWindow(callback: MeasureInWindowOnSuccessCallback) { + const node = getShadowNode(this); + if (node != null) { + nullthrows(getFabricUIManager()).measureInWindow(node, callback); + } } measureLayout( relativeToNativeNode: number | ElementRef>, onSuccess: MeasureLayoutOnSuccessCallback, onFail?: () => void /* currently unused */, - ): void { - throw new TypeError('Unimplemented'); + ) { + if (!(relativeToNativeNode instanceof ReadOnlyNode)) { + if (__DEV__) { + console.error( + 'Warning: ref.measureLayout must be called with a ref to a native component.', + ); + } + + return; + } + + const toStateNode = getShadowNode(this); + const fromStateNode = getShadowNode(relativeToNativeNode); + + if (toStateNode != null && fromStateNode != null) { + nullthrows(getFabricUIManager()).measureLayout( + toStateNode, + fromStateNode, + onFail != null ? onFail : noop, + onSuccess != null ? onSuccess : noop, + ); + } } setNativeProps(nativeProps: {...}): void { - throw new TypeError('Unimplemented'); + if (__DEV__) { + warnForStyleProps(nativeProps, this._viewConfig.validAttributes); + } + + const updatePayload = createAttributePayload( + nativeProps, + this._viewConfig.validAttributes, + ); + + const node = getShadowNode(this); + + if (node != null && updatePayload != null) { + nullthrows(getFabricUIManager()).setNativeProps(node, updatePayload); + } } } diff --git a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js index 1307ba57c8b..8d2de166fe3 100644 --- a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js +++ b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow strict-local */ // flowlint unsafe-getters-setters:off diff --git a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js index d6cc6b878f4..fc8dc2df8cb 100644 --- a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js +++ b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js @@ -5,15 +5,25 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow strict-local */ // flowlint unsafe-getters-setters:off +import type { + InternalInstanceHandle, + Node as ShadowNode, +} from '../../Renderer/shims/ReactNativeTypes'; import type NodeList from '../OldStyleCollections/NodeList'; import type ReadOnlyElement from './ReadOnlyElement'; +import ReactFabric from '../../Renderer/shims/ReactFabric'; + export default class ReadOnlyNode { + constructor(internalInstanceHandle: InternalInstanceHandle) { + setInstanceHandle(this, internalInstanceHandle); + } + get childNodes(): NodeList { throw new TypeError('Unimplemented'); } @@ -165,3 +175,22 @@ export default class ReadOnlyNode { */ static DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number = 32; } + +const INSTANCE_HANDLE_KEY = Symbol('internalInstanceHandle'); + +function getInstanceHandle(node: ReadOnlyNode): InternalInstanceHandle { + // $FlowExpectedError[prop-missing] + return node[INSTANCE_HANDLE_KEY]; +} + +function setInstanceHandle( + node: ReadOnlyNode, + instanceHandle: InternalInstanceHandle, +): void { + // $FlowExpectedError[prop-missing] + node[INSTANCE_HANDLE_KEY] = instanceHandle; +} + +export function getShadowNode(node: ReadOnlyNode): ?ShadowNode { + return ReactFabric.getNodeFromInternalInstanceHandle(getInstanceHandle(node)); +} diff --git a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js index b423fad97c7..f71d6fa9bf7 100644 --- a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js +++ b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js @@ -9,7 +9,6 @@ */ import type { - AttributeConfiguration, HostComponent, INativeMethods, InternalInstanceHandle, @@ -24,6 +23,7 @@ import TextInputState from '../../Components/TextInput/TextInputState'; import {getNodeFromInternalInstanceHandle} from '../../Renderer/shims/ReactFabric'; import {getFabricUIManager} from '../FabricUIManager'; import {create} from './ReactNativeAttributePayload'; +import warnForStyleProps from './warnForStyleProps'; import nullthrows from 'nullthrows'; const { @@ -149,24 +149,3 @@ export default class ReactFabricHostComponent implements INativeMethods { } } } - -function warnForStyleProps( - props: {...}, - validAttributes: AttributeConfiguration, -): void { - if (__DEV__) { - for (const key in validAttributes.style) { - if (!(validAttributes[key] || props[key] === undefined)) { - console.error( - 'You are setting the style `{ %s' + - ': ... }` as a prop. You ' + - 'should nest it in a style object. ' + - 'E.g. `{ style: { %s' + - ': ... } }`', - key, - key, - ); - } - } - } -} diff --git a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance.js b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance.js index a2c6581912d..f8722df1d60 100644 --- a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance.js +++ b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance.js @@ -8,15 +8,22 @@ * @flow strict-local */ +import type ReactNativeElement from '../../DOM/Nodes/ReactNativeElement'; import typeof ReactFabricType from '../../Renderer/shims/ReactFabric'; import type { InternalInstanceHandle, + Node, ViewConfig, } from '../../Renderer/shims/ReactNativeTypes'; -import type ReactFabricHostComponentType from './ReactFabricHostComponent'; +import type ReactFabricHostComponent from './ReactFabricHostComponent'; + +import ReactNativeFeatureFlags from '../ReactNativeFeatureFlags'; // Lazy loaded to avoid evaluating the module when using the legacy renderer. -let ReactFabricHostComponent: Class; +let PublicInstanceClass: + | Class + | Class; + // Lazy loaded to avoid evaluating the module when using the legacy renderer. let ReactFabric: ReactFabricType; @@ -24,11 +31,19 @@ export function createPublicInstance( tag: number, viewConfig: ViewConfig, internalInstanceHandle: InternalInstanceHandle, -): ReactFabricHostComponentType { - if (ReactFabricHostComponent == null) { - ReactFabricHostComponent = require('./ReactFabricHostComponent').default; +): ReactFabricHostComponent | ReactNativeElement { + if (PublicInstanceClass == null) { + // We don't use inline requires in react-native, so this forces lazy loading + // the right module to avoid eagerly loading both. + if (ReactNativeFeatureFlags.enableAccessToHostTreeInFabric()) { + PublicInstanceClass = + require('../../DOM/Nodes/ReactNativeElement').default; + } else { + PublicInstanceClass = require('./ReactFabricHostComponent').default; + } } - return new ReactFabricHostComponent(tag, viewConfig, internalInstanceHandle); + + return new PublicInstanceClass(tag, viewConfig, internalInstanceHandle); } export function createPublicTextInstance(internalInstanceHandle: mixed): {} { @@ -39,14 +54,14 @@ export function createPublicTextInstance(internalInstanceHandle: mixed): {} { } export function getNativeTagFromPublicInstance( - publicInstance: ReactFabricHostComponentType, + publicInstance: ReactFabricHostComponent | ReactNativeElement, ): number { return publicInstance.__nativeTag; } export function getNodeFromPublicInstance( - publicInstance: ReactFabricHostComponentType, -): mixed { + publicInstance: ReactFabricHostComponent | ReactNativeElement, +): ?Node { // Avoid loading ReactFabric if using an instance from the legacy renderer. if (publicInstance.__internalInstanceHandle == null) { return null; @@ -55,7 +70,6 @@ export function getNodeFromPublicInstance( if (ReactFabric == null) { ReactFabric = require('../../Renderer/shims/ReactFabric'); } - return ReactFabric.getNodeFromInternalInstanceHandle( publicInstance.__internalInstanceHandle, ); diff --git a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/ReactFabricPublicInstance-test.js b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/ReactFabricPublicInstance-test.js index e2987243b82..73e54a8fdd6 100644 --- a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/ReactFabricPublicInstance-test.js +++ b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/ReactFabricPublicInstance-test.js @@ -15,13 +15,13 @@ import * as React from 'react'; import {act} from 'react-test-renderer'; const TextInputState = require('../../../Components/TextInput/TextInputState'); -const FabricUIManager = require('../../../ReactNative/FabricUIManager'); const ReactFabric = require('../../../Renderer/shims/ReactFabric'); const ReactNativeViewConfigRegistry = require('../../../Renderer/shims/ReactNativeViewConfigRegistry'); +const FabricUIManager = require('../../FabricUIManager'); const nullthrows = require('nullthrows'); -jest.mock('../../../ReactNative/FabricUIManager', () => - require('../../../ReactNative/__mocks__/FabricUIManager'), +jest.mock('../../FabricUIManager', () => + require('../../__mocks__/FabricUIManager'), ); /** @@ -107,161 +107,171 @@ async function mockRenderKeys( return result; } -describe('ReactFabricPublicInstance', () => { - beforeEach(() => { - jest.resetModules(); - // Installs the global `nativeFabricUIManager` pointing to the mock. - require('../../../ReactNative/__mocks__/FabricUIManager'); - jest.spyOn(TextInputState, 'blurTextInput'); - jest.spyOn(TextInputState, 'focusTextInput'); - }); +[ + {enableAccessToHostTreeInFabric: false}, + {enableAccessToHostTreeInFabric: true}, +].forEach(flags => { + describe(`ReactFabricPublicInstance (ReactNativeFeatureFlags.enableAccessToHostTreeInFabric = ${String( + flags.enableAccessToHostTreeInFabric, + )})'`, () => { + beforeEach(() => { + jest.resetModules(); + // Installs the global `nativeFabricUIManager` pointing to the mock. + require('../../../ReactNative/__mocks__/FabricUIManager'); + jest.spyOn(TextInputState, 'blurTextInput'); + jest.spyOn(TextInputState, 'focusTextInput'); - describe('blur', () => { - test('blur() invokes TextInputState', async () => { - const result = await mockRenderKeys([['foo']]); - const fooRef = nullthrows(result?.[0]?.[0]); - - fooRef.blur(); - - expect(mockOf(TextInputState.blurTextInput).mock.calls).toEqual([ - [fooRef], - ]); - }); - }); - - describe('focus', () => { - test('focus() invokes TextInputState', async () => { - const result = await mockRenderKeys([['foo']]); - const fooRef = nullthrows(result?.[0]?.[0]); - - fooRef.focus(); - - expect(mockOf(TextInputState.focusTextInput).mock.calls).toEqual([ - [fooRef], - ]); - }); - }); - - describe('measure', () => { - test('component.measure(...) invokes callback', async () => { - const result = await mockRenderKeys([['foo']]); - const fooRef = nullthrows(result?.[0]?.[0]); - - const callback = jest.fn(); - fooRef.measure(callback); - - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measure, - ).toHaveBeenCalledTimes(1); - expect(callback.mock.calls).toEqual([[10, 10, 100, 100, 0, 0]]); + require('../../../ReactNative/ReactNativeFeatureFlags').enableAccessToHostTreeInFabric = + () => flags.enableAccessToHostTreeInFabric; }); - test('unmounted.measure(...) does nothing', async () => { - const result = await mockRenderKeys([['foo'], null]); - const fooRef = nullthrows(result?.[0]?.[0]); - const callback = jest.fn(); - fooRef.measure(callback); + describe('blur', () => { + test('blur() invokes TextInputState', async () => { + const result = await mockRenderKeys([['foo']]); + const fooRef = nullthrows(result?.[0]?.[0]); - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measure, - ).not.toHaveBeenCalled(); - expect(callback).not.toHaveBeenCalled(); - }); - }); + fooRef.blur(); - describe('measureInWindow', () => { - test('component.measureInWindow(...) invokes callback', async () => { - const result = await mockRenderKeys([['foo']]); - const fooRef = nullthrows(result?.[0]?.[0]); - - const callback = jest.fn(); - fooRef.measureInWindow(callback); - - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measureInWindow, - ).toHaveBeenCalledTimes(1); - expect(callback.mock.calls).toEqual([[10, 10, 100, 100]]); + expect(mockOf(TextInputState.blurTextInput).mock.calls).toEqual([ + [fooRef], + ]); + }); }); - test('unmounted.measureInWindow(...) does nothing', async () => { - const result = await mockRenderKeys([['foo'], null]); - const fooRef = nullthrows(result?.[0]?.[0]); + describe('focus', () => { + test('focus() invokes TextInputState', async () => { + const result = await mockRenderKeys([['foo']]); + const fooRef = nullthrows(result?.[0]?.[0]); - const callback = jest.fn(); - fooRef.measureInWindow(callback); + fooRef.focus(); - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measureInWindow, - ).not.toHaveBeenCalled(); - expect(callback).not.toHaveBeenCalled(); - }); - }); - - describe('measureLayout', () => { - test('component.measureLayout(component, ...) invokes callback', async () => { - const result = await mockRenderKeys([['foo', 'bar']]); - const fooRef = nullthrows(result?.[0]?.[0]); - const barRef = nullthrows(result?.[0]?.[1]); - - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - fooRef.measureLayout(barRef, successCallback, failureCallback); - - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, - ).toHaveBeenCalledTimes(1); - expect(successCallback.mock.calls).toEqual([[1, 1, 100, 100]]); + expect(mockOf(TextInputState.focusTextInput).mock.calls).toEqual([ + [fooRef], + ]); + }); }); - test('unmounted.measureLayout(component, ...) does nothing', async () => { - const result = await mockRenderKeys([ - ['foo', 'bar'], - ['foo', null], - ]); - const fooRef = nullthrows(result?.[0]?.[0]); - const barRef = nullthrows(result?.[0]?.[1]); + describe('measure', () => { + test('component.measure(...) invokes callback', async () => { + const result = await mockRenderKeys([['foo']]); + const fooRef = nullthrows(result?.[0]?.[0]); - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - fooRef.measureLayout(barRef, successCallback, failureCallback); + const callback = jest.fn(); + fooRef.measure(callback); - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, - ).not.toHaveBeenCalled(); - expect(successCallback).not.toHaveBeenCalled(); + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measure, + ).toHaveBeenCalledTimes(1); + expect(callback.mock.calls).toEqual([[10, 10, 100, 100, 0, 0]]); + }); + + test('unmounted.measure(...) does nothing', async () => { + const result = await mockRenderKeys([['foo'], null]); + const fooRef = nullthrows(result?.[0]?.[0]); + const callback = jest.fn(); + fooRef.measure(callback); + + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measure, + ).not.toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + }); }); - test('component.measureLayout(unmounted, ...) does nothing', async () => { - const result = await mockRenderKeys([ - ['foo', 'bar'], - [null, 'bar'], - ]); - const fooRef = nullthrows(result?.[0]?.[0]); - const barRef = nullthrows(result?.[0]?.[1]); + describe('measureInWindow', () => { + test('component.measureInWindow(...) invokes callback', async () => { + const result = await mockRenderKeys([['foo']]); + const fooRef = nullthrows(result?.[0]?.[0]); - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - fooRef.measureLayout(barRef, successCallback, failureCallback); + const callback = jest.fn(); + fooRef.measureInWindow(callback); - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, - ).not.toHaveBeenCalled(); - expect(successCallback).not.toHaveBeenCalled(); + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measureInWindow, + ).toHaveBeenCalledTimes(1); + expect(callback.mock.calls).toEqual([[10, 10, 100, 100]]); + }); + + test('unmounted.measureInWindow(...) does nothing', async () => { + const result = await mockRenderKeys([['foo'], null]); + const fooRef = nullthrows(result?.[0]?.[0]); + + const callback = jest.fn(); + fooRef.measureInWindow(callback); + + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measureInWindow, + ).not.toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + }); }); - test('unmounted.measureLayout(unmounted, ...) does nothing', async () => { - const result = await mockRenderKeys([['foo', 'bar'], null]); - const fooRef = nullthrows(result?.[0]?.[0]); - const barRef = nullthrows(result?.[0]?.[1]); + describe('measureLayout', () => { + test('component.measureLayout(component, ...) invokes callback', async () => { + const result = await mockRenderKeys([['foo', 'bar']]); + const fooRef = nullthrows(result?.[0]?.[0]); + const barRef = nullthrows(result?.[0]?.[1]); - const successCallback = jest.fn(); - const failureCallback = jest.fn(); - fooRef.measureLayout(barRef, successCallback, failureCallback); + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); - expect( - nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, - ).not.toHaveBeenCalled(); - expect(successCallback).not.toHaveBeenCalled(); + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, + ).toHaveBeenCalledTimes(1); + expect(successCallback.mock.calls).toEqual([[1, 1, 100, 100]]); + }); + + test('unmounted.measureLayout(component, ...) does nothing', async () => { + const result = await mockRenderKeys([ + ['foo', 'bar'], + ['foo', null], + ]); + const fooRef = nullthrows(result?.[0]?.[0]); + const barRef = nullthrows(result?.[0]?.[1]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, + ).not.toHaveBeenCalled(); + expect(successCallback).not.toHaveBeenCalled(); + }); + + test('component.measureLayout(unmounted, ...) does nothing', async () => { + const result = await mockRenderKeys([ + ['foo', 'bar'], + [null, 'bar'], + ]); + const fooRef = nullthrows(result?.[0]?.[0]); + const barRef = nullthrows(result?.[0]?.[1]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, + ).not.toHaveBeenCalled(); + expect(successCallback).not.toHaveBeenCalled(); + }); + + test('unmounted.measureLayout(unmounted, ...) does nothing', async () => { + const result = await mockRenderKeys([['foo', 'bar'], null]); + const fooRef = nullthrows(result?.[0]?.[0]); + const barRef = nullthrows(result?.[0]?.[1]); + + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + fooRef.measureLayout(barRef, successCallback, failureCallback); + + expect( + nullthrows(FabricUIManager.getFabricUIManager()).measureLayout, + ).not.toHaveBeenCalled(); + expect(successCallback).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/warnForStyleProps.js b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/warnForStyleProps.js new file mode 100644 index 00000000000..25713ab4454 --- /dev/null +++ b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/warnForStyleProps.js @@ -0,0 +1,32 @@ +/** + * 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. + * + * @format + * @flow strict-local + */ + +import type {AttributeConfiguration} from '../../Renderer/shims/ReactNativeTypes'; + +export default function warnForStyleProps( + props: {...}, + validAttributes: AttributeConfiguration, +): void { + if (__DEV__) { + for (const key in validAttributes.style) { + if (!(validAttributes[key] || props[key] === undefined)) { + console.error( + 'You are setting the style `{ %s' + + ': ... }` as a prop. You ' + + 'should nest it in a style object. ' + + 'E.g. `{ style: { %s' + + ': ... } }`', + key, + key, + ); + } + } + } +}