diff --git a/libs/horizon/src/event/ControlledValueUpdater.ts b/libs/horizon/src/event/ControlledValueUpdater.ts new file mode 100644 index 00000000..5e565a1d --- /dev/null +++ b/libs/horizon/src/event/ControlledValueUpdater.ts @@ -0,0 +1,37 @@ +import {getVNodeProps} from '../dom/DOMInternalKeys'; +import {resetValue} from '../dom/valueHandler'; + +let updateList: Array | null = null; + +// 受控组件值重新赋值 +function updateValue(target: Element) { + const props = getVNodeProps(target); + if (props) { + resetValue(target, props); + } +} + +// 存储队列中缓存组件 +export function addValueUpdateList(target: EventTarget): void { + if (updateList) { + updateList.push(target); + } else { + updateList = [target]; + } +} + +// 判断是否需要重新赋值 +export function shouldUpdateValue(): boolean { + return updateList !== null && updateList.length > 0; +} + +// 从缓存队列中对受控组件进行赋值 +export function updateControlledValue() { + if (!updateList) { + return; + } + updateList.forEach(item => { + updateValue(item); + }); + updateList = null; +} diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/horizon/src/event/EventBinding.ts new file mode 100644 index 00000000..54d3fdb4 --- /dev/null +++ b/libs/horizon/src/event/EventBinding.ts @@ -0,0 +1,132 @@ +/** + * 事件绑定实现,分为绑定委托事件和非委托事件 + */ +import {allDelegatedNativeEvents} from './EventCollection'; +import {isDocument} from '../dom/utils/Common'; +import { + getNearestVNode, + getNonDelegatedListenerMap, +} from '../dom/DOMInternalKeys'; +import {runDiscreteUpdates} from '../renderer/TreeBuilder'; +import {isMounted} from '../renderer/vnode/VNodeUtils'; +import {SuspenseComponent} from '../renderer/vnode/VNodeTags'; +import {handleEventMain} from './HorizonEventMain'; +import {decorateNativeEvent} from './customEvents/EventFactory'; + +const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4); + +// 触发委托事件 +function triggerDelegatedEvent( + nativeEvtName: string, + isCapture: boolean, + targetDom: EventTarget, + nativeEvent, // 事件对象event +) { + // 执行之前的调度事件 + runDiscreteUpdates(); + + const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement; + let targetVNode = getNearestVNode(nativeEventTarget); + + if (targetVNode !== null) { + if (isMounted(targetVNode)) { + if (targetVNode.tag === SuspenseComponent) { + targetVNode = null; + } + } else { + // vNode已销毁 + targetVNode = null; + } + } + handleEventMain(nativeEvtName, isCapture, nativeEvent, targetVNode, targetDom); +} + +// 监听委托事件 +function listenToNativeEvent( + nativeEvtName: string, + delegatedElement: Element, + isCapture: boolean, +): void { + let dom: Element | Document = delegatedElement; + // document层次可能触发selectionchange事件,为了捕获这类事件,selectionchange事件绑定在document节点上 + if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) { + dom = delegatedElement.ownerDocument; + } + + const listener = triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, dom); + dom.addEventListener(nativeEvtName, listener, isCapture); +} + +// 监听所有委托事件 +export function listenDelegatedEvents(dom: Element) { + if (dom[listeningMarker]) { + // 不需要重复注册事件 + return; + } + dom[listeningMarker] = true; + + allDelegatedNativeEvents.forEach((nativeEvtName: string) => { + // 委托冒泡事件 + listenToNativeEvent(nativeEvtName, dom, false); + // 委托捕获事件 + listenToNativeEvent(nativeEvtName, dom, true); + }); +} + +// 通过horizon事件名获取到native事件名 +function getNativeEvtName(horizonEventName, capture) { + let nativeName; + if (capture) { + nativeName = horizonEventName.slice(2, -7); + } else { + nativeName = horizonEventName.slice(2); + } + if (!nativeName) { + return ''; + } + return nativeName.toLowerCase(); +} + +// 是否捕获事件 +function isCaptureEvent(horizonEventName) { + if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') { + return false; + } + return horizonEventName.slice(-7) === 'Capture'; +} + +// 封装监听函数 +function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) { + return event => { + const customEvent = decorateNativeEvent(horizonEventName, nativeEvtName, event); + listener(customEvent); + }; +} + +// 非委托事件单独监听到各自dom节点 +export function listenNonDelegatedEvent( + horizonEventName: string, + domElement: Element, + listener, +): void { + const isCapture = isCaptureEvent(horizonEventName); + const nativeEvtName = getNativeEvtName(horizonEventName, isCapture); + + // 先判断是否存在老的监听事件,若存在则移除 + const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement); + const currentListener = nonDelegatedListenerMap.get(horizonEventName); + if (currentListener) { + domElement.removeEventListener(nativeEvtName, currentListener); + nonDelegatedListenerMap.delete(horizonEventName); + } + + if (typeof listener !== 'function') { + return; + } + + // 为了和委托事件对外行为一致,将事件对象封装成CustomBaseEvent + const wrapperListener = getWrapperListener(horizonEventName, nativeEvtName, domElement, listener); + // 添加新的监听 + nonDelegatedListenerMap.set(horizonEventName, wrapperListener); + domElement.addEventListener(nativeEvtName, wrapperListener, isCapture); +} diff --git a/libs/horizon/src/event/EventCollection.ts b/libs/horizon/src/event/EventCollection.ts new file mode 100644 index 00000000..d70dcf10 --- /dev/null +++ b/libs/horizon/src/event/EventCollection.ts @@ -0,0 +1,15 @@ +import {horizonEventToNativeMap} from './const'; + +// 需要委托的horizon事件和原生事件对应关系 +export const allDelegatedHorizonEvents = new Map(); +// 所有委托的原生事件集合 +export const allDelegatedNativeEvents = new Set(); + +horizonEventToNativeMap.forEach((dependencies, horizonEvent) => { + allDelegatedHorizonEvents.set(horizonEvent, dependencies); + allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies); + + dependencies.forEach(d => { + allDelegatedNativeEvents.add(d); + }); +}); diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/horizon/src/event/HorizonEventMain.ts new file mode 100644 index 00000000..08e9f5d0 --- /dev/null +++ b/libs/horizon/src/event/HorizonEventMain.ts @@ -0,0 +1,166 @@ +import type { AnyNativeEvent } from './Types'; +import type { VNode } from '../renderer/Types'; + +import { + CommonEventToHorizonMap, + horizonEventToNativeMap, + EVENT_TYPE_BUBBLE, + EVENT_TYPE_CAPTURE, +} from './const'; +import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler'; +import { getListeners as getSelectionListeners } from './simulatedEvtHandler/SelectionEventHandler'; +import { + setPropertyWritable, +} from './utils'; +import { decorateNativeEvent } from './customEvents/EventFactory'; +import { getListenersFromTree } from './ListenerGetter'; +import { shouldUpdateValue, updateControlledValue } from './ControlledValueUpdater'; +import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer'; +import { getExactNode } from '../renderer/vnode/VNodeUtils'; +import {ListenerUnitList} from './Types'; + +// 获取事件触发的普通事件监听方法队列 +function getCommonListeners( + nativeEvtName: string, + vNode: null | VNode, + nativeEvent: AnyNativeEvent, + target: null | EventTarget, + isCapture: boolean, +): ListenerUnitList { + const name = CommonEventToHorizonMap[nativeEvtName]; + const horizonEvtName = !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`; // 例:dragEnd -> onDragEnd + + if (!horizonEvtName) { + return []; + } + + // 鼠标点击右键 + if (nativeEvent instanceof MouseEvent && nativeEvtName === 'click' && nativeEvent.button === 2) { + return []; + } + + if (nativeEvtName === 'focusin') { + nativeEvtName = 'focus'; + } + + if (nativeEvtName === 'focusout') { + nativeEvtName = 'blur'; + } + + const horizonEvent = decorateNativeEvent(horizonEvtName, nativeEvtName, nativeEvent); + return getListenersFromTree( + vNode, + horizonEvtName, + horizonEvent, + isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE, + ); +} + +// 按顺序执行事件队列 +function processListeners(listenerList: ListenerUnitList): void { + listenerList.forEach(eventUnit => { + const { currentTarget, listener, event } = eventUnit; + if (event.isPropagationStopped()) { + return; + } + + setPropertyWritable(event, 'currentTarget'); + event.currentTarget = currentTarget; + listener(event); + event.currentTarget = null; + }); +} + +function getProcessListeners( + nativeEvtName: string, + vNode: VNode | null, + nativeEvent: AnyNativeEvent, + target, + isCapture: boolean +): ListenerUnitList { + // 触发普通委托事件 + let listenerList: ListenerUnitList = getCommonListeners( + nativeEvtName, + vNode, + nativeEvent, + target, + isCapture, + ); + + // 触发特殊handler委托事件 + if (!isCapture) { + if (horizonEventToNativeMap.get('onChange').includes(nativeEvtName)) { + listenerList = listenerList.concat(getChangeListeners( + nativeEvtName, + nativeEvent, + vNode, + target, + )); + } + + if (horizonEventToNativeMap.get('onSelect').includes(nativeEvtName)) { + listenerList = listenerList.concat(getSelectionListeners( + nativeEvtName, + nativeEvent, + vNode, + target, + )); + } + } + return listenerList; +} + +// 触发可以被执行的horizon事件监听 +function triggerHorizonEvents( + nativeEvtName: string, + isCapture: boolean, + nativeEvent: AnyNativeEvent, + vNode: VNode | null, +): void { + const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement; + + // 获取委托事件队列 + const listenerList = getProcessListeners(nativeEvtName, vNode, nativeEvent, nativeEventTarget, isCapture); + + // 处理触发的事件队列 + processListeners(listenerList); +} + + +// 其他事件正在执行中标记 +let isInEventsExecution = false; + +// 处理委托事件入口 +export function handleEventMain( + nativeEvtName: string, + isCapture: boolean, + nativeEvent: AnyNativeEvent, + vNode: null | VNode, + targetContainer: EventTarget, +): void { + let startVNode = vNode; + if (startVNode !== null) { + startVNode = getExactNode(startVNode, targetContainer); + if (!startVNode) { + return; + } + } + + // 有事件正在执行,同步执行事件 + if (isInEventsExecution) { + triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode); + return; + } + + // 没有事件在执行,经过调度再执行事件 + isInEventsExecution = true; + try { + asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode)); + } finally { + isInEventsExecution = false; + if (shouldUpdateValue()) { + runDiscreteUpdates(); + updateControlledValue(); + } + } +} diff --git a/libs/horizon/src/event/ListenerGetter.ts b/libs/horizon/src/event/ListenerGetter.ts new file mode 100644 index 00000000..d6860f35 --- /dev/null +++ b/libs/horizon/src/event/ListenerGetter.ts @@ -0,0 +1,72 @@ +import { VNode } from '../renderer/Types'; +import { DomComponent } from '../renderer/vnode/VNodeTags'; +import { EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE } from './const'; +import { AnyNativeEvent, ListenerUnitList } from './Types'; + +// 从vnode属性中获取事件listener +function getListenerFromVNode(vNode: VNode, eventName: string): Function | null { + const props = vNode.props; + const mouseEvents = ['onClick', 'onDoubleClick', 'onMouseDown', 'onMouseMove', 'onMouseUp', 'onMouseEnter']; + const formElements = ['button', 'input', 'select', 'textarea']; + + // 是否应该阻止禁用的表单元素触发鼠标事件 + const shouldPreventMouseEvent = + mouseEvents.includes(eventName) && props.disabled && formElements.includes(vNode.type); + + const listener = props[eventName]; + if (shouldPreventMouseEvent) { + return null; + } else { + return listener; + } +} + +// 获取监听事件 +export function getListenersFromTree( + targetVNode: VNode | null, + horizonEvtName: string | null, + nativeEvent: AnyNativeEvent, + eventType: string +): ListenerUnitList { + if (!horizonEvtName) { + return []; + } + + const listeners: ListenerUnitList = []; + + let vNode = targetVNode; + + // 从目标节点到根节点遍历获取listener + while (vNode !== null) { + const { realNode, tag } = vNode; + if (tag === DomComponent && realNode !== null) { + if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) { + const captureName = horizonEvtName + EVENT_TYPE_CAPTURE; + const captureListener = getListenerFromVNode(vNode, captureName); + if (captureListener) { + listeners.unshift({ + vNode, + listener: captureListener, + currentTarget: realNode, + event: nativeEvent, + }); + } + } + + if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) { + const bubbleListener = getListenerFromVNode(vNode, horizonEvtName); + if (bubbleListener) { + listeners.push({ + vNode, + listener: bubbleListener, + currentTarget: realNode, + event: nativeEvent, + }); + } + } + } + vNode = vNode.parent; + } + + return listeners; +} diff --git a/libs/horizon/src/event/Types.ts b/libs/horizon/src/event/Types.ts new file mode 100644 index 00000000..12071e36 --- /dev/null +++ b/libs/horizon/src/event/Types.ts @@ -0,0 +1,13 @@ + +import type {VNode} from '../renderer/Types'; + +export type AnyNativeEvent = KeyboardEvent | MouseEvent | TouchEvent | UIEvent | Event; + +export type ListenerUnit = { + vNode: null | VNode; + listener: Function; + currentTarget: EventTarget; + event: AnyNativeEvent; +}; + +export type ListenerUnitList = Array; diff --git a/libs/horizon/src/event/WrapperListener.ts b/libs/horizon/src/event/WrapperListener.ts new file mode 100644 index 00000000..e69de29b diff --git a/libs/horizon/src/event/const.ts b/libs/horizon/src/event/const.ts new file mode 100644 index 00000000..449663e9 --- /dev/null +++ b/libs/horizon/src/event/const.ts @@ -0,0 +1,79 @@ + +// Horizon事件和原生事件对应关系 +export const horizonEventToNativeMap = new Map([ + ['onKeyPress', ['keypress']], + ['onTextInput', ['textInput']], + ['onClick', ['click']], + ['onDoubleClick', ['dblclick']], + ['onFocus', ['focusin']], + ['onBlur', ['focusout']], + ['onInput', ['input']], + ['onMouseOut', ['mouseout']], + ['onMouseOver', ['mouseover']], + ['onPointerOut', ['pointerout']], + ['onPointerOver', ['pointerover']], + ['onContextMenu', ['contextmenu']], + ['onDragEnd', ['dragend']], + ['onKeyDown', ['keydown']], + ['onKeyUp', ['keyup']], + ['onMouseDown', ['mousedown']], + ['onMouseMove', ['mousemove']], + ['onMouseUp', ['mouseup']], + ['onSelectChange', ['selectionchange']], + ['onTouchEnd', ['touchend']], + ['onTouchMove', ['touchmove']], + ['onTouchStart', ['touchstart']], + + ['onCompositionEnd', ['compositionend']], + ['onCompositionStart', ['compositionstart']], + ['onCompositionUpdate', ['compositionupdate']], + ['onChange', ['change', 'click', 'focusout', 'input']], + ['onSelect', ['focusout', 'contextmenu', 'dragend', 'focusin', + 'keydown', 'keyup', 'mousedown', 'mouseup', 'selectionchange']], + + ['onAnimationEnd', ['animationend']], + ['onAnimationIteration', ['animationiteration']], + ['onAnimationStart', ['animationstart']], + ['onTransitionEnd', ['transitionend']] +]); + +export const CommonEventToHorizonMap = { + click: 'click', + dblclick: 'doubleClick', + contextmenu: 'contextMenu', + dragend: 'dragEnd', + focusin: 'focus', + focusout: 'blur', + input: 'input', + keydown: 'keyDown', + keypress: 'keyPress', + keyup: 'keyUp', + mousedown: 'mouseDown', + mouseup: 'mouseUp', + touchend: 'touchEnd', + touchstart: 'touchStart', + mousemove: 'mouseMove', + mouseout: 'mouseOut', + mouseover: 'mouseOver', + pointermove: 'pointerMove', + pointerout: 'pointerOut', + pointerover: 'pointerOver', + selectionchange: 'selectChange', + textInput: 'textInput', + touchmove: 'touchMove', + animationend: 'animationEnd', + animationiteration: 'animationIteration', + animationstart: 'animationStart', + transitionend: 'transitionEnd', + compositionstart: 'compositionStart', + compositionend: 'compositionEnd', + compositionupdate: 'compositionUpdate', +}; + +export const CHAR_CODE_SPACE = 32; + + +export const EVENT_TYPE_BUBBLE = 'Bubble'; +export const EVENT_TYPE_CAPTURE = 'Capture'; +export const EVENT_TYPE_ALL = 'All'; + diff --git a/libs/horizon/src/event/customEvents/EventFactory.ts b/libs/horizon/src/event/customEvents/EventFactory.ts new file mode 100644 index 00000000..8ca28579 --- /dev/null +++ b/libs/horizon/src/event/customEvents/EventFactory.ts @@ -0,0 +1,36 @@ + +// 兼容IE的event key +const uniqueKeyMap = new Map([ + ['Esc', 'Escape'], + ['Spacebar', ' '], + ['Left', 'ArrowLeft'], + ['Up', 'ArrowUp'], + ['Right', 'ArrowRight'], + ['Down', 'ArrowDown'], + ['Del', 'Delete'], +]); + +const noop = () => {}; +// 创建普通自定义事件对象实例,和原生事件对应 +export function decorateNativeEvent(customEventName, nativeEvtName, nativeEvent) { + + nativeEvent.isDefaultPrevented = () => nativeEvent.defaultPrevented; + nativeEvent.isPropagationStopped = () => nativeEvent.cancelBubble; + // 适配老版本事件api + nativeEvent.persist = noop; + + // custom事件自定义属性 + nativeEvent.customEventName = customEventName; + nativeEvent.nativeEvent = nativeEvent; + // 保存原生的事件类型,因为下面会修改 + nativeEvent.nativeEventType = nativeEvent.type; + + Object.defineProperty(nativeEvent, 'type', { writable: true }); + nativeEvent.type = nativeEvtName; + + const orgKey = nativeEvent.key; + Object.defineProperty(nativeEvent, 'key', { writable: true }); + nativeEvent.key = uniqueKeyMap.get(orgKey) || orgKey; + + return nativeEvent; +} diff --git a/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts new file mode 100644 index 00000000..c0ad947d --- /dev/null +++ b/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts @@ -0,0 +1,60 @@ +import {decorateNativeEvent} from '../customEvents/EventFactory'; +import {getDom} from '../../dom/DOMInternalKeys'; +import {isInputValueChanged} from '../../dom/valueHandler/ValueChangeHandler'; +import {addValueUpdateList} from '../ControlledValueUpdater'; +import {isInputElement} from '../utils'; +import {EVENT_TYPE_ALL} from '../const'; +import {AnyNativeEvent, ListenerUnitList} from '../Types'; +import { + getListenersFromTree, +} from '../ListenerGetter'; +import {VNode} from '../../renderer/Types'; +import {getDomTag} from '../../dom/utils/Common'; + +// 返回是否需要触发change事件标记 +function shouldTriggerChangeEvent(targetDom, evtName) { + const { type } = targetDom; + const domTag = getDomTag(targetDom); + + if (domTag === 'select' || (domTag === 'input' && type === 'file')) { + return evtName === 'change'; + } else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) { + if (evtName === 'click') { + return isInputValueChanged(targetDom); + } + } else if (isInputElement(targetDom)) { + if (evtName === 'input' || evtName === 'change') { + return isInputValueChanged(targetDom); + } + } + return false; +} + +/** + * + * 支持input/textarea/select的onChange事件 + */ +export function getListeners( + nativeEvtName: string, + nativeEvt: AnyNativeEvent, + vNode: null | VNode, + target: null | EventTarget, +): ListenerUnitList { + if (!vNode) { + return []; + } + const targetDom = getDom(vNode); + + // 判断是否需要触发change事件 + if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) { + addValueUpdateList(target); + const event = decorateNativeEvent( + 'onChange', + 'change', + nativeEvt, + ); + return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL); + } + + return []; +} diff --git a/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts new file mode 100644 index 00000000..cd5a09ad --- /dev/null +++ b/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts @@ -0,0 +1,112 @@ +import {decorateNativeEvent} from '../customEvents/EventFactory'; +import {shallowCompare} from '../../renderer/utils/compare'; +import {getFocusedDom} from '../../dom/utils/Common'; +import {getDom} from '../../dom/DOMInternalKeys'; +import {isDocument} from '../../dom/utils/Common'; +import {isInputElement, setPropertyWritable} from '../utils'; +import type {AnyNativeEvent} from '../Types'; +import {getListenersFromTree} from '../ListenerGetter'; +import type {VNode} from '../../renderer/Types'; +import {EVENT_TYPE_ALL} from '../const'; +import {ListenerUnitList} from '../Types'; + +const horizonEventName = 'onSelect'; + +let currentElement = null; +let currentVNode = null; +let lastSelection: Selection | null = null; + +function initTargetCache(dom, vNode) { + if (isInputElement(dom) || dom.contentEditable === 'true') { + currentElement = dom; + currentVNode = vNode; + lastSelection = null; + } +} + +function clearTargetCache() { + currentElement = null; + currentVNode = null; + lastSelection = null; +} + +// 标记是否在鼠标事件过程中 +let isInMouseEvent = false; + +// 获取节点所在的document对象 +function getDocument(eventTarget) { + if (eventTarget.window === eventTarget) { + return eventTarget.document; + } + if (isDocument(eventTarget)) { + return eventTarget; + } + return eventTarget.ownerDocument; +} + +function getSelectEvent(nativeEvent, target) { + const doc = getDocument(target); + if (isInMouseEvent || currentElement == null || currentElement !== getFocusedDom(doc)) { + return []; + } + + const currentSelection = window.getSelection(); + if (!shallowCompare(lastSelection, currentSelection)) { + lastSelection = currentSelection; + + const event = decorateNativeEvent( + horizonEventName, + 'select', + nativeEvent, + ); + setPropertyWritable(nativeEvent, 'target'); + event.target = currentElement; + + return getListenersFromTree( + currentVNode, + horizonEventName, + event, + EVENT_TYPE_ALL + ); + } + return []; +} + + +/** + * 该插件创建一个onSelect事件 + * 支持元素: input、textarea、contentEditable元素 + * 触发场景:用户输入、折叠选择、文本选择 + */ +export function getListeners( + nativeEvtName: string, + nativeEvt: AnyNativeEvent, + vNode: null | VNode, + target: null | EventTarget, +): ListenerUnitList { + const targetNode = vNode ? getDom(vNode) : window; + let eventUnitList: ListenerUnitList = []; + switch (nativeEvtName) { + case 'focusin': + initTargetCache(targetNode, vNode); + break; + case 'focusout': + clearTargetCache(); + break; + case 'mousedown': + isInMouseEvent = true; + break; + case 'contextmenu': + case 'mouseup': + case 'dragend': + isInMouseEvent = false; + eventUnitList = getSelectEvent(nativeEvt, target); + break; + case 'selectionchange': + case 'keydown': + case 'keyup': + eventUnitList = getSelectEvent(nativeEvt, target); + } + + return eventUnitList; +} diff --git a/libs/horizon/src/event/utils.ts b/libs/horizon/src/event/utils.ts new file mode 100644 index 00000000..09d1b240 --- /dev/null +++ b/libs/horizon/src/event/utils.ts @@ -0,0 +1,14 @@ + +export function isInputElement(dom?: HTMLElement): boolean { + if (dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement) { + return true; + } + return false; +} + +export function setPropertyWritable(obj, propName) { + const desc = Object.getOwnPropertyDescriptor(obj, propName); + if (!desc || !desc.writable) { + Object.defineProperty(obj, propName, { writable : true }); + } +}