UNPKG

react-native-macos

Version:

A framework for building native macOS apps using React

283 lines (254 loc) • 10 kB
/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule NativeMethodsMixin * @flow */ 'use strict'; var ReactNative = require('ReactNative'); var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var TextInputState = require('TextInputState'); var UIManager = require('UIManager'); var invariant = require('fbjs/lib/invariant'); var findNodeHandle = require('findNodeHandle'); var { mountSafeCallback, throwOnStylesProp, warnForStyleProps, } = require('NativeMethodsMixinUtils'); import type { MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, } from 'NativeMethodsMixinUtils'; import type { ReactNativeBaseComponentViewConfig, } from 'ReactNativeViewConfigRegistry'; /** * `NativeMethodsMixin` provides methods to access the underlying native * component directly. This can be useful in cases when you want to focus * a view or measure its on-screen dimensions, for example. * * The methods described here are available on most of the default components * provided by React Native. Note, however, that they are *not* available on * composite components that aren't directly backed by a native view. This will * generally include most components that you define in your own app. For more * information, see [Direct * Manipulation](docs/direct-manipulation.html). */ // TODO (bvaughn) Figure out how to use the NativeMethodsInterface type to- // ensure that these mixins and ReactNativeFiberHostComponent stay in sync. // Unfortunately, using it causes Flow to complain WRT createClass mixins: // "call of method `createClass`. Expected an exact object instead of ..." var NativeMethodsMixin = { /** * Determines the location on screen, width, and height of the given view and * returns the values via an async callback. If successful, the callback will * be called with the following arguments: * * - x * - y * - width * - height * - pageX * - pageY * * Note that these measurements are not available until after the rendering * has been completed in native. If you need the measurements as soon as * possible, consider using the [`onLayout` * prop](docs/view.html#onlayout) instead. */ measure: function(callback: MeasureOnSuccessCallback) { UIManager.measure( ReactNative.findNodeHandle(this), mountSafeCallback(this, callback), ); }, /** * Determines the location of the given view in the window and returns the * values via an async callback. If the React root view is embedded in * another native view, this will give you the absolute coordinates. If * successful, the callback will be called with the following * arguments: * * - x * - y * - width * - height * * Note that these measurements are not available until after the rendering * has been completed in native. */ measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) { UIManager.measureInWindow( ReactNative.findNodeHandle(this), mountSafeCallback(this, callback), ); }, /** * Like [`measure()`](#measure), but measures the view relative an ancestor, * specified as `relativeToNativeNode`. This means that the returned x, y * are relative to the origin x, y of the ancestor view. * * As always, to obtain a native node handle for a component, you can use * `ReactNative.findNodeHandle(component)`. */ measureLayout: function( relativeToNativeNode: number, onSuccess: MeasureLayoutOnSuccessCallback, onFail: () => void /* currently unused */, ) { UIManager.measureLayout( ReactNative.findNodeHandle(this), relativeToNativeNode, mountSafeCallback(this, onFail), mountSafeCallback(this, onSuccess), ); }, /** * This function sends props straight to native. They will not participate in * future diff process - this means that if you do not include them in the * next render, they will remain active (see [Direct * Manipulation](docs/direct-manipulation.html)). */ setNativeProps: function(nativeProps: Object) { // Ensure ReactNative factory function has configured findNodeHandle. // Requiring it won't execute the factory function until first referenced. // It's possible for tests that use ReactTestRenderer to reach this point, // Without having executed ReactNative. // Defer the factory function until now to avoid a cycle with UIManager. // TODO (bvaughn) Remove this once ReactNativeStack is dropped. require('ReactNative'); injectedSetNativeProps(this, nativeProps); }, /** * Requests focus for the given input or view. The exact behavior triggered * will depend on the platform and type of view. */ focus: function() { TextInputState.focusTextInput(ReactNative.findNodeHandle(this)); }, /** * Removes focus from an input or view. This is the opposite of `focus()`. */ blur: function() { TextInputState.blurTextInput(ReactNative.findNodeHandle(this)); }, }; // TODO (bvaughn) Inline this once ReactNativeStack is dropped. function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) { // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. // Use findNodeHandle() rather than ReactNative.findNodeHandle() because // We want the instance/wrapper (not the native tag). let maybeInstance; // Fiber errors if findNodeHandle is called for an umounted component. // Tests using ReactTestRenderer will trigger this case indirectly. // Mimicking stack behavior, we should silently ignore this case. // TODO Fix ReactTestRenderer so we can remove this try/catch. try { maybeInstance = findNodeHandle(componentOrHandle); } catch (error) {} // If there is no host component beneath this we should fail silently. // This is not an error; it could mean a class component rendered null. if (maybeInstance == null) { return; } const viewConfig: ReactNativeBaseComponentViewConfig = maybeInstance.viewConfig; if (__DEV__) { warnForStyleProps(nativeProps, viewConfig.validAttributes); } var updatePayload = ReactNativeAttributePayload.create( nativeProps, viewConfig.validAttributes, ); UIManager.updateView( maybeInstance._nativeTag, viewConfig.uiViewClassName, updatePayload, ); } // TODO (bvaughn) Remove this once ReactNativeStack is dropped. function setNativePropsStack(componentOrHandle: any, nativeProps: Object) { // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. // Use findNodeHandle() rather than ReactNative.findNodeHandle() because // We want the instance/wrapper (not the native tag). let maybeInstance = findNodeHandle(componentOrHandle); // If there is no host component beneath this we should fail silently. // This is not an error; it could mean a class component rendered null. if (maybeInstance == null) { return; } let viewConfig: ReactNativeBaseComponentViewConfig; if (maybeInstance.viewConfig !== undefined) { // ReactNativeBaseComponent viewConfig = maybeInstance.viewConfig; } else if ( maybeInstance._instance !== undefined && maybeInstance._instance.viewConfig !== undefined ) { // ReactCompositeComponentWrapper // Some instances (eg Text) define their own viewConfig viewConfig = maybeInstance._instance.viewConfig; } else { // ReactCompositeComponentWrapper // Other instances (eg TextInput) defer to their children's viewConfig while (maybeInstance._renderedComponent !== undefined) { maybeInstance = maybeInstance._renderedComponent; } viewConfig = maybeInstance.viewConfig; } const tag: number = typeof maybeInstance.getHostNode === 'function' ? maybeInstance.getHostNode() : maybeInstance._rootNodeID; if (__DEV__) { warnForStyleProps(nativeProps, viewConfig.validAttributes); } var updatePayload = ReactNativeAttributePayload.create( nativeProps, viewConfig.validAttributes, ); UIManager.updateView(tag, viewConfig.uiViewClassName, updatePayload); } // Switching based on fiber vs stack to avoid a lot of inline checks at runtime. // HACK Normally this injection would be done by the renderer, but in this case // that would result in a cycle between ReactNative and NativeMethodsMixin. // We avoid requiring additional code for this injection so it's probably ok? // TODO (bvaughn) Remove this once ReactNativeStack is gone. let injectedSetNativeProps: ( componentOrHandle: any, nativeProps: Object, ) => void; if (ReactNativeFeatureFlags.useFiber) { injectedSetNativeProps = setNativePropsFiber; } else { injectedSetNativeProps = setNativePropsStack; } if (__DEV__) { // hide this from Flow since we can't define these properties outside of // __DEV__ without actually implementing them (setting them to undefined // isn't allowed by ReactClass) var NativeMethodsMixin_DEV = (NativeMethodsMixin: any); invariant( !NativeMethodsMixin_DEV.componentWillMount && !NativeMethodsMixin_DEV.componentWillReceiveProps, 'Do not override existing functions.', ); NativeMethodsMixin_DEV.componentWillMount = function() { throwOnStylesProp(this, this.props); }; NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) { throwOnStylesProp(this, newProps); }; } module.exports = NativeMethodsMixin;