UNPKG

react-native

Version:

A framework for building native apps using React

260 lines (219 loc) • 8.49 kB
/** * 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 */ // flowlint unsafe-getters-setters:off import type { InternalInstanceHandle, Node as ShadowNode, ViewConfig, } from '../../../../../Libraries/Renderer/shims/ReactNativeTypes'; import type { HostInstance, MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, NativeMethods, } from '../../../types/HostInstance'; import type {InstanceHandle} from './internals/NodeInternals'; import type ReactNativeDocument from './ReactNativeDocument'; import TextInputState from '../../../../../Libraries/Components/TextInput/TextInputState'; import {getFabricUIManager} from '../../../../../Libraries/ReactNative/FabricUIManager'; import {create as createAttributePayload} from '../../../../../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload'; import warnForStyleProps from '../../../../../Libraries/ReactNative/ReactFabricPublicInstance/warnForStyleProps'; import { getNativeElementReference, getPublicInstanceFromInstanceHandle, setInstanceHandle, setOwnerDocument, } from './internals/NodeInternals'; import ReadOnlyElement, {getBoundingClientRect} from './ReadOnlyElement'; import NativeDOM from './specs/NativeDOM'; import nullthrows from 'nullthrows'; const noop = () => {}; // Ideally, this class would be exported as-is, but this implementation is // significantly slower than the existing `ReactFabricHostComponent`. // This is a very hot code path (this class is instantiated once per rendered // host component in the tree) and we can't regress performance here. // // This implementation is slower because this is a subclass and we have to call // super(), which is a very slow operation the way that Babel transforms it at // the moment. // // The optimization we're doing is using an old-style function constructor, // where we're not required to use `super()`, and we make that constructor // extend this class so it inherits all the methods and it sets the class // hierarchy correctly. // // An alternative implementation was to implement the constructor as a function // returning a manually constructed instance using `Object.create()` but that // was slower than this method because the engine has to create an object than // we then discard to create a new one. class ReactNativeElement extends ReadOnlyElement implements NativeMethods { // These need to be accessible from `ReactFabricPublicInstanceUtils`. __nativeTag: number; __internalInstanceHandle: InstanceHandle; __viewConfig: ViewConfig; // This constructor isn't really used. See the `ReactNativeElement` function // below. constructor( tag: number, viewConfig: ViewConfig, instanceHandle: InstanceHandle, ownerDocument: ReactNativeDocument, ) { super(instanceHandle, ownerDocument); this.__nativeTag = tag; this.__internalInstanceHandle = instanceHandle; this.__viewConfig = viewConfig; } get offsetHeight(): number { return Math.round( getBoundingClientRect(this, {includeTransform: false}).height, ); } get offsetLeft(): number { const node = getNativeElementReference(this); if (node != null) { const offset = NativeDOM.getOffset(node); return Math.round(offset[2]); } return 0; } get offsetParent(): ReadOnlyElement | null { const node = getNativeElementReference(this); if (node != null) { const offset = NativeDOM.getOffset(node); // For children of the root node we currently return offset data // but a `null` parent because the root node is not accessible // in JavaScript yet. if (offset[0] != null) { const offsetParentInstanceHandle = offset[0]; const offsetParent = getPublicInstanceFromInstanceHandle( offsetParentInstanceHandle, ); // $FlowExpectedError[incompatible-type] The value returned by `getOffset` is always an instance handle for `ReadOnlyElement`. const offsetParentElement: ReadOnlyElement | null = offsetParent; return offsetParentElement; } } return null; } get offsetTop(): number { const node = getNativeElementReference(this); if (node != null) { const offset = NativeDOM.getOffset(node); return Math.round(offset[1]); } return 0; } get offsetWidth(): number { return Math.round( getBoundingClientRect(this, {includeTransform: false}).width, ); } /** * React Native compatibility methods */ blur(): void { TextInputState.blurTextInput(this); } focus() { TextInputState.focusTextInput(this); } measure(callback: MeasureOnSuccessCallback) { const node = getNativeElementReference(this); if (node != null) { // $FlowExpectedError[incompatible-type] This is an element instance so the native node reference is always a shadow node. const shadowNode: ShadowNode = node; nullthrows(getFabricUIManager()).measure(shadowNode, callback); } } measureInWindow(callback: MeasureInWindowOnSuccessCallback) { const node = getNativeElementReference(this); if (node != null) { // $FlowExpectedError[incompatible-type] This is an element instance so the native node reference is always a shadow node. const shadowNode: ShadowNode = node; nullthrows(getFabricUIManager()).measureInWindow(shadowNode, callback); } } measureLayout( relativeToNativeNode: number | HostInstance, onSuccess: MeasureLayoutOnSuccessCallback, onFail?: () => void /* currently unused */, ) { if (!(relativeToNativeNode instanceof ReactNativeElement)) { if (__DEV__) { console.error( 'Warning: ref.measureLayout must be called with a ref to a native component.', ); } return; } const toStateNode = getNativeElementReference(this); const fromStateNode = getNativeElementReference(relativeToNativeNode); if (toStateNode != null && fromStateNode != null) { // $FlowExpectedError[incompatible-type] This is an element instance so the native node reference is always a shadow node. const toStateShadowNode: ShadowNode = toStateNode; // $FlowExpectedError[incompatible-type] This is an element instance so the native node reference is always a shadow node. const fromStateShadowNode: ShadowNode = fromStateNode; nullthrows(getFabricUIManager()).measureLayout( toStateShadowNode, fromStateShadowNode, onFail != null ? onFail : noop, onSuccess != null ? onSuccess : noop, ); } } setNativeProps(nativeProps: {...}): void { if (__DEV__) { warnForStyleProps(nativeProps, this.__viewConfig.validAttributes); } const updatePayload = createAttributePayload( nativeProps, this.__viewConfig.validAttributes, ); const node = getNativeElementReference(this); if (node != null && updatePayload != null) { // $FlowExpectedError[incompatible-type] This is an element instance so the native node reference is always a shadow node. const shadowNode: ShadowNode = node; nullthrows(getFabricUIManager()).setNativeProps( shadowNode, updatePayload, ); } } } type ReactNativeElementT = ReactNativeElement; function replaceConstructorWithoutSuper( ReactNativeElementClass: Class<ReactNativeElementT>, ): Class<ReactNativeElementT> { // Alternative constructor just implemented to provide a better performance than // calling super() in the original class. // eslint-disable-next-line no-shadow function ReactNativeElement( this: ReactNativeElementT, tag: number, viewConfig: ViewConfig, internalInstanceHandle: InternalInstanceHandle, ownerDocument: ReactNativeDocument, ) { // Inlined from `ReadOnlyNode` setOwnerDocument(this, ownerDocument); setInstanceHandle(this, internalInstanceHandle); this.__nativeTag = tag; this.__internalInstanceHandle = internalInstanceHandle; this.__viewConfig = viewConfig; } ReactNativeElement.prototype = ReactNativeElementClass.prototype; // $FlowExpectedError[incompatible-return] return ReactNativeElement; } export default replaceConstructorWithoutSuper( ReactNativeElement, ) as typeof ReactNativeElement;