import type { ReactElement, ReactElementFiber } from '../element/types'; import getComponentName from '../element/componentName'; import findRootTag from '../element/findRootTag'; import { moduleNameMap, pageInfoAsync, trackState } from '../global/index'; import getXPath, { formatName } from './xpath'; import getTexts from './texts'; import configGlobal from '../config'; import Track from '../track'; import { findTouchableNode } from './touchableComponent'; import findRoute from '../element/findRoute'; const PressableComponentChecker = { check: (node: ReactElementFiber) => node.memoizedProps?.hasOwnProperty('accessible'), getTargetNode: (node: ReactElementFiber) => node?.return, prop: 'onPress', }; function getPressableNode(node: ReactElementFiber): ReactElementFiber | null { const { check, getTargetNode, prop } = PressableComponentChecker; if (!check(node)) { return null; } const targetNode = getTargetNode(node); // console.info(componentName, '--->', getComponentName(targetNode.elementType)) if (!targetNode) { return null; } const props = targetNode.memoizedProps || {}; if (prop && !props.hasOwnProperty(prop)) { return null; } return targetNode; } export function checkTrackClick( element: ReactElement ): ReactElementFiber | null { let node = element._owner; if (!node) { return null; } // 兼容旧实现: // 若这些组件底层不是通过 Pressability 实现,则直接注入 props - onPress // 此时不需要绑定 key const pressableNode = getPressableNode(node); if (pressableNode) { // console.info(getComponentName(pressableNode.elementType)) return pressableNode; } return null; } export function parseTouchEvent(event: any, callback: (touch?: any) => void) { if (!event || !event.nativeEvent) { callback(); return; } //防止异步修改导致event变化 event.persist(); const touch = { locationX: event.nativeEvent.locationX, locationY: event.nativeEvent.locationY, screenX: event.nativeEvent.pageX, screenY: event.nativeEvent.pageY, x: 0, y: 0, width: 0, height: 0, }; if (typeof event.target !== 'object') { const stateNode = event._targetInst?.stateNode; if (stateNode && stateNode.measureInWindow) { stateNode.measureInWindow( (x: number, y: number, width: number, height: number) => { // console.info(1, 'target', x, y, width, height) Object.assign(touch, { x, y, width, height, screenX: x + touch.locationX, screenY: y + touch.locationY, }); callback(touch); } ); } else { callback(touch); } return; } if (event.target === event.currentTarget) { event.target.measureInWindow( (x: number, y: number, width: number, height: number) => { // console.info(1, 'target', x, y, width, height) Object.assign(touch, { x, y, width, height, screenX: x + touch.locationX, screenY: y + touch.locationY, }); callback(touch); } ); return; } if (typeof event.currentTarget === 'object') { const promise1 = new Promise((resolve) => { event.target.measureInWindow((x: number, y: number) => { // console.info(2, 'target', x, y, width, height) Object.assign(touch, { screenX: x + touch.locationX, screenY: y + touch.locationY, }); resolve(touch); }); }); const promise2 = new Promise((resolve) => { event.currentTarget.measureInWindow( (x: number, y: number, width: number, height: number) => { // console.info(3, 'currentTarget', x, y, width, height) Object.assign(touch, { x, y, width, height, }); resolve(touch); } ); }); Promise.all([promise1, promise2]).then(() => { Object.assign(touch, { locationX: touch.screenX - touch.x, locationY: touch.screenY - touch.y, }); callback(touch); }); return; } callback(touch); } export function trackPressability(touchNode: ReactElementFiber, touch: any) { const node = findTouchableNode(touchNode) ?? touchNode; trackClick(node, touch); } function findTarget( propName: string, node: ReactElementFiber ): ReactElementFiber | undefined { if (!node) { return undefined; } if (node.memoizedProps && node.memoizedProps.hasOwnProperty(propName)) { return node; } return findTarget(propName, node.return); } export function trackClick(node: ReactElementFiber, touch: any) { if (!trackState.enable || !trackState.enableClick) { return; } const componentName = formatName(getComponentName(node.elementType)); if (configGlobal.getIgnoreElementSet().has(componentName)) { return; } let targetNode; const elementIdProp = configGlobal.getConfig().elementIdProp; const elementCustomProp = configGlobal.getConfig().elementCustomProp; if (elementIdProp) { targetNode = findTarget(elementIdProp, node); } if (!targetNode && elementCustomProp) { targetNode = findTarget(elementCustomProp, node); } let elementManualKey = ''; const elementConfig = configGlobal.getElementConfig(componentName); if (elementConfig) { elementManualKey = elementConfig.elementID ?? ''; } if (targetNode && elementIdProp) { const elementID = targetNode.memoizedProps[elementIdProp]; if (elementID) { elementManualKey = elementID; } } let elementCustom; if (targetNode && elementCustomProp) { elementCustom = targetNode.memoizedProps[elementCustomProp]; } const rootTag = findRootTag(node); const params = {}; Object.assign(params, { touch_x: Math.round(touch.screenX) || 0, touch_y: Math.round(touch.screenY) || 0, element_width: Math.round(touch.width) || 0, element_height: Math.round(touch.height) || 0, element_type: componentName || '', element_manual_key: elementManualKey || '', element_path: getXPath(node) || '', texts: getTexts(node), custom: elementCustom || {}, $rn_root_tag: rootTag, }); const appKey = moduleNameMap.get(rootTag)!; if ( !trackState.enablePageView || configGlobal.getIgnorePageSet().has(appKey) ) { Track.click(params); return; } const route = findRoute(node); pageInfoAsync.appendCurrentPage(rootTag, route, params).then((_) => { Track.click(params); }); }