UNPKG

@es-react/react

Version:

Hippy react framework

259 lines (240 loc) 7.98 kB
/* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Fiber } from '@hippy/react-reconciler'; import { Device, UIManager } from '../global'; import { getRootViewId, findNodeById, findNodeByCondition } from '../utils/node'; import { isFunction, warn, trace } from '../utils'; import Element from '../dom/element-node'; import {HippyTypes} from '../types' const { createNode, updateNode, deleteNode, flushBatch, endBatch, sendRenderError, } = UIManager; const LOG_TYPE = ['%c[native]%c', 'color: red', 'color: auto']; const getNodeById = findNodeById; /** * Get the nodeId from FiberNode ref. * * @param {Fiber} ref - ref instance. */ function getElementFromFiberRef(ref: Fiber | Element) { if (ref instanceof Element) { return ref; } if (!ref) return null; const internalFiber = (ref as any)._reactInternalFiber || (ref as any)._reactInternals; if (internalFiber?.child) { let targetNode = internalFiber.child; while (targetNode && !(targetNode.stateNode instanceof Element)) { targetNode = targetNode.child; } if (!targetNode || !targetNode.stateNode) { return null; } return targetNode.stateNode; } return null; } /** * Get the nodeId number by ref * Most use in the module access components. * * @param {string | Fiber | Fiber} ref - ref instance, reference to the class is recommend */ function getNodeIdByRef(ref: string | Fiber | Element): number { // typeof ref === 'string' let tempRef = ref; if (typeof ref === 'string') { warn(`getNodeIdByRef('${ref}') use string ref will affect to performance, recommend use reference to the ref instead`); const targetElement = findNodeByCondition((node: Fiber) => { /* eslint-disable-next-line no-underscore-dangle */ if (!node.return || !node.return.ref || !(node.return.ref as any)._stringRef) { return false; } /* eslint-disable-next-line no-underscore-dangle */ return (node.return.ref as any)._stringRef === ref; }); if (!targetElement || !targetElement.stateNode) { return 0; } tempRef = targetElement.stateNode; } // typeof fiberRef === 'Fiber' if (!(tempRef as Element).nodeId) { const targetElement = getElementFromFiberRef(tempRef as Element); if (!targetElement) { return 0; } return targetElement.nodeId; } // typeof ref === 'Element' return (tempRef as Element).nodeId; } /** * Component access UI functions * * @param {ViewNode} ref - Element ref that have nodeId. * @param {string} funcName - function name. * @param {Array} options - function options. */ function callUIFunction(ref: Element | Fiber, funcName: string, ...options: any[]): void { let { nativeName: componentName, nodeId } = ref as Element; if (!nodeId || !componentName) { const targetElement = getElementFromFiberRef(ref); if (targetElement) { ({ nodeId, nativeName: componentName } = targetElement); } } if (!componentName) { throw new Error('callUIFunction is calling a unnamed component'); } if (!nodeId) { throw new Error('callUIFunction is calling a component have no nodeId'); } let [paramList = [], callback] = options; if (isFunction(paramList)) { callback = paramList; paramList = []; } const rootViewId = getRootViewId(); if (rootViewId === null) { return; } trace(...LOG_TYPE, 'callUIFunction', { nodeId, funcName, paramList }); UIManager.callUIFunction(nodeId, funcName, paramList, callback); } /** * Get the ref position and size in the visible window. * > For the position and size in the layout, use onLayout event. * * @param {string} method * @param {Fiber | Element} ref - ref that need to measure. * @param {function} callback */ function measureInWindowByMethod( method: string, ref: Fiber, callback?: (layout: HippyTypes.LayoutEvent | string) => void, ) { const nodeId = getNodeIdByRef(ref); return new Promise((resolve, reject) => { if (!nodeId) { if (callback && isFunction(callback)) { // Forward compatibility for old callback callback('this view is null'); } return reject(new Error(`${method} cannot get nodeId`)); } trace(...LOG_TYPE, 'callUIFunction', { nodeId, funcName: method, paramList: [] }); return UIManager.callUIFunction(nodeId, method, [], (layout: HippyTypes.LayoutEvent | string) => { if (callback && isFunction(callback)) { callback(layout); } if (layout === 'this view is null') { return reject(new Error('Android cannot get the node')); } return resolve(layout); }); }); } /** * Get the ref position and size in the visible window. * > For the position and size in the layout, use onLayout event. * P.S. iOS can only obtain the layout of rootView container, * so measureInAppWindow method is recommended * * @deprecated * @param {Fiber | Element} ref - ref that need to measure. * @param {Function} callback */ function measureInWindow(ref: Fiber, callback?: (layout: HippyTypes.LayoutEvent | string) => void) { return measureInWindowByMethod('measureInWindow', ref, callback); } /** * Get the ref position and size in the App visible window. * > For the position and size in the layout, use onLayout event. * * @param {Fiber | Element} ref - ref that need to measure. * @param {Function} callback */ function measureInAppWindow(ref: Fiber, callback?: (layout: HippyTypes.LayoutEvent | string) => void) { if (Device.platform.OS === 'android') { return measureInWindowByMethod('measureInWindow', ref, callback); } return measureInWindowByMethod('measureInAppWindow', ref, callback); } /** * Returns a Promise with DOMRect object providing * information about the size of an element and its position relative to the RootView or Container * @param ref * @param options */ function getBoundingClientRect(ref: Fiber, options: { relToContainer?: boolean }): Promise<HippyTypes.DOMRect> { const nodeId = getNodeIdByRef(ref); return new Promise((resolve, reject) => { if (!nodeId) { return reject(new Error(`getBoundingClientRect cannot get nodeId of ${ref}`)); } trace(...LOG_TYPE, 'callUIFunction', { nodeId, funcName: 'getBoundingClientRect', params: options }); return UIManager.callUIFunction(nodeId, 'getBoundingClientRect', [options], (res: HippyTypes.LayoutEvent) => { if (!res || res.errMsg) { return reject(new Error((res?.errMsg) || 'getBoundingClientRect error with no response')); } const { x, y, width, height } = res; let bottom: undefined | number = undefined; let right: undefined | number = undefined; if (typeof y === 'number' && typeof height === 'number') { bottom = y + height; } if (typeof x === 'number' && typeof width === 'number') { right = x + width; } return resolve({ x, y, width, height, bottom, right, left: x, top: y, }); }); }); } export { createNode, updateNode, deleteNode, flushBatch, endBatch, sendRenderError, getNodeById, getNodeIdByRef, getElementFromFiberRef, callUIFunction, getBoundingClientRect, measureInWindow, measureInAppWindow, };