UNPKG

@quick-tv/react

Version:

QuickTV react framework

290 lines (271 loc) 9.76 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 { getFiberNodeFromId, getElementFromFiber, eventNamesMap, NATIVE_EVENT } from '../utils/node'; import { trace, isGlobalBubble, isHostComponent } from '../utils'; import HippyEventHub from './hub'; import Event from './event'; type EventParam = string[] | number[]; interface NativeEvent { id: number; name: string; } const eventHubs = new Map(); const componentName = ['%c[event]%c', 'color: green', 'color: auto']; function receiveUIComponentEvent(nativeEvent: any[]) { trace(...componentName, 'receiveUIComponentEvent', nativeEvent); if (!nativeEvent || !Array.isArray(nativeEvent) || nativeEvent.length < 2) { return; } const [targetNodeId, eventName, eventParam] = nativeEvent; if (typeof targetNodeId !== 'number' || typeof eventName !== 'string') { return; } const targetNode = getFiberNodeFromId(targetNodeId); if (!targetNode) { return; } if (isNodePropFunction(eventName, targetNode)) { targetNode.memoizedProps[eventName](eventParam); } } interface ListenerObj { eventName: string; listener: Function; isCapture: boolean; currentTarget: Element; } /** * convertEventName - convert all special event name * @param eventName * @param nodeItem */ function convertEventName(eventName: string, nodeItem: Fiber) { let processedEvenName = eventName; if (nodeItem.memoizedProps && !nodeItem.memoizedProps[eventName]) { const eventNameList = Object.keys(eventNamesMap); for (let i = 0; i < eventNameList.length; i += 1) { const uiEvent = eventNameList[i]; const eventList = eventNamesMap[uiEvent]; if (nodeItem.memoizedProps[uiEvent] && eventName === eventList[NATIVE_EVENT]) { processedEvenName = uiEvent; break; } } } return processedEvenName; } function isNodePropFunction(prop: string, nextNodeItem: Fiber) { return !!(nextNodeItem.memoizedProps && typeof nextNodeItem.memoizedProps[prop] === 'function'); } /** * doCaptureAndBubbleLoop - process capture phase and bubbling phase * @param {string} originalEventName * @param {NativeEvent} nativeEvent * @param {Fiber} nodeItem */ function doCaptureAndBubbleLoop(originalEventName: string, nativeEvent: NativeEvent, nodeItem: Fiber) { const eventQueue: ListenerObj[] = []; let nextNodeItem: Fiber | null = nodeItem; let eventName = originalEventName; // capture and bubbling loop while (nextNodeItem) { eventName = convertEventName(eventName, nextNodeItem); const captureName = `${eventName}Capture`; if (isNodePropFunction(captureName, nextNodeItem)) { // capture phase to add listener at queue head eventQueue.unshift({ eventName: captureName, listener: nextNodeItem.memoizedProps[captureName], isCapture: true, currentTarget: getElementFromFiber(nextNodeItem), }); } if (isNodePropFunction(eventName, nextNodeItem)) { // bubbling phase to add listener at queue tail eventQueue.push({ eventName, listener: nextNodeItem.memoizedProps[eventName], isCapture: false, currentTarget: getElementFromFiber(nextNodeItem), }); } if (eventQueue.length === 0) { nextNodeItem = null; } else { nextNodeItem = nextNodeItem.return; while (nextNodeItem && !isHostComponent(nextNodeItem.tag)) { // only handle HostComponent nextNodeItem = nextNodeItem.return; } } } if (eventQueue.length > 0) { let listenerObj: ListenerObj | undefined; let isStopBubble: any = false; const targetNode = getElementFromFiber(nodeItem); while (!isStopBubble && (listenerObj = eventQueue.shift()) !== undefined) { try { const { eventName, currentTarget: currentTargetNode, listener, isCapture } = listenerObj; const syntheticEvent = new Event(eventName, currentTargetNode, targetNode); Object.assign(syntheticEvent, nativeEvent); // whether it is capture or bubbling event, returning false or calling stopPropagation would both stop phase if (isCapture) { listener(syntheticEvent); // event bubbles flag has higher priority if (!syntheticEvent.bubbles) { isStopBubble = true; } } else { isStopBubble = listener(syntheticEvent); // If callback have no return, use global bubble config to set isStopBubble. if (typeof isStopBubble !== 'boolean') { isStopBubble = !isGlobalBubble(); } // event bubbles flag has higher priority if (!syntheticEvent.bubbles) { isStopBubble = true; } } } catch (err) { console.error(err); } } } } /** * doBubbleLoop - process only bubbling phase * @param {string} originalEventName * @param {NativeEvent} nativeEvent * @param {Fiber} nodeItem */ function doBubbleLoop(originalEventName: string, nativeEvent: NativeEvent, nodeItem: Fiber) { let isStopBubble: any = false; let nextNodeItem: Fiber | null = nodeItem; let eventName = originalEventName; const targetNode = getElementFromFiber(nodeItem); // only bubbling loop do { eventName = convertEventName(eventName, nextNodeItem); if (isNodePropFunction(eventName, nextNodeItem)) { try { const currentTargetNode = getElementFromFiber(nextNodeItem); const syntheticEvent = new Event(eventName, currentTargetNode, targetNode); Object.assign(syntheticEvent, nativeEvent); isStopBubble = nextNodeItem.memoizedProps[eventName](syntheticEvent); // If callback have no return, use global bubble config to set isStopBubble. if (typeof isStopBubble !== 'boolean') { isStopBubble = !isGlobalBubble(); } // event bubbles flag has higher priority if (!syntheticEvent.bubbles) { isStopBubble = true; } } catch (err) { console.error(err); } } if (isStopBubble === false) { nextNodeItem = nextNodeItem.return; while (nextNodeItem && !isHostComponent(nextNodeItem.tag)) { // only handle HostComponent nextNodeItem = nextNodeItem.return; } } } while (!isStopBubble && nextNodeItem); } function receiveNativeGesture(nativeEvent: NativeEvent) { trace(...componentName, 'receiveNativeGesture', nativeEvent); if (!nativeEvent) { return; } const { id: targetNodeId } = nativeEvent; const targetNode = getFiberNodeFromId(targetNodeId); if (!targetNode) { return; } let hasCapturePhase = true; let { name: eventName } = nativeEvent; eventName = convertEventName(eventName, targetNode); const captureName = `${eventName}Capture`; const nextNodeItem: Fiber = targetNode; // if current target has no capture listener, only do bubbling phase loop to improve performance if (targetNode.memoizedProps && typeof targetNode.memoizedProps[captureName] !== 'function') { hasCapturePhase = false; } if (hasCapturePhase) { doCaptureAndBubbleLoop(eventName, nativeEvent, nextNodeItem); } else { doBubbleLoop(eventName, nativeEvent, nextNodeItem); } } function getHippyEventHub(eventName: any) { if (typeof eventName !== 'string') { throw new TypeError(`Invalid eventName for getHippyEventHub: ${eventName}`); } return eventHubs.get(eventName) || null; } function registerNativeEventHub(eventName: any) { trace(...componentName, 'registerNativeEventHub', eventName); if (typeof eventName !== 'string') { throw new TypeError(`Invalid eventName for registerNativeEventHub: ${eventName}`); } let targetEventHub = eventHubs.get(eventName); if (!targetEventHub) { targetEventHub = new HippyEventHub(eventName); eventHubs.set(eventName, targetEventHub); } return targetEventHub; } function unregisterNativeEventHub(eventName: any) { if (typeof eventName !== 'string') { throw new TypeError(`Invalid eventName for unregisterNativeEventHub: ${eventName}`); } if (eventHubs.has(eventName)) { eventHubs.delete(eventName); } } function receiveNativeEvent(nativeEvent: EventParam) { trace(...componentName, 'receiveNativeEvent', nativeEvent); if (!nativeEvent || !Array.isArray(nativeEvent) || nativeEvent.length < 2) { throw new TypeError(`Invalid params for receiveNativeEvent: ${JSON.stringify(nativeEvent)}`); } const [eventName, eventParams] = nativeEvent; if (typeof eventName !== 'string') { throw new TypeError('Invalid arguments for nativeEvent eventName'); } const currEventHub = getHippyEventHub(eventName); if (!currEventHub) { return; } currEventHub.notifyEvent(eventParams); } const EventDispatcher = { registerNativeEventHub, getHippyEventHub, unregisterNativeEventHub, receiveNativeEvent, receiveNativeGesture, receiveUIComponentEvent, }; if (global.__GLOBAL__) { global.__GLOBAL__.jsModuleList.EventDispatcher = EventDispatcher; } export default EventDispatcher;