@es-react/react
Version:
Hippy react framework
259 lines (240 loc) • 7.98 kB
text/typescript
/*
* 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,
};