| import { |
| ContinuousEventPriority, |
| DefaultEventPriority, |
| DiscreteEventPriority, |
| NoEventPriority, |
| } from 'react-reconciler/constants.js' |
| import { logError } from '../../utils/log.js' |
| import { HANDLER_FOR_EVENT } from './event-handlers.js' |
| import type { EventTarget, TerminalEvent } from './terminal-event.js' |
|
|
| |
|
|
| type DispatchListener = { |
| node: EventTarget |
| handler: (event: TerminalEvent) => void |
| phase: 'capturing' | 'at_target' | 'bubbling' |
| } |
|
|
| function getHandler( |
| node: EventTarget, |
| eventType: string, |
| capture: boolean, |
| ): ((event: TerminalEvent) => void) | undefined { |
| const handlers = node._eventHandlers |
| if (!handlers) return undefined |
|
|
| const mapping = HANDLER_FOR_EVENT[eventType] |
| if (!mapping) return undefined |
|
|
| const propName = capture ? mapping.capture : mapping.bubble |
| if (!propName) return undefined |
|
|
| return handlers[propName] as ((event: TerminalEvent) => void) | undefined |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function collectListeners( |
| target: EventTarget, |
| event: TerminalEvent, |
| ): DispatchListener[] { |
| const listeners: DispatchListener[] = [] |
|
|
| let node: EventTarget | undefined = target |
| while (node) { |
| const isTarget = node === target |
|
|
| const captureHandler = getHandler(node, event.type, true) |
| const bubbleHandler = getHandler(node, event.type, false) |
|
|
| if (captureHandler) { |
| listeners.unshift({ |
| node, |
| handler: captureHandler, |
| phase: isTarget ? 'at_target' : 'capturing', |
| }) |
| } |
|
|
| if (bubbleHandler && (event.bubbles || isTarget)) { |
| listeners.push({ |
| node, |
| handler: bubbleHandler, |
| phase: isTarget ? 'at_target' : 'bubbling', |
| }) |
| } |
|
|
| node = node.parentNode |
| } |
|
|
| return listeners |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function processDispatchQueue( |
| listeners: DispatchListener[], |
| event: TerminalEvent, |
| ): void { |
| let previousNode: EventTarget | undefined |
|
|
| for (const { node, handler, phase } of listeners) { |
| if (event._isImmediatePropagationStopped()) { |
| break |
| } |
|
|
| if (event._isPropagationStopped() && node !== previousNode) { |
| break |
| } |
|
|
| event._setEventPhase(phase) |
| event._setCurrentTarget(node) |
| event._prepareForTarget(node) |
|
|
| try { |
| handler(event) |
| } catch (error) { |
| logError(error) |
| } |
|
|
| previousNode = node |
| } |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| function getEventPriority(eventType: string): number { |
| switch (eventType) { |
| case 'keydown': |
| case 'keyup': |
| case 'click': |
| case 'focus': |
| case 'blur': |
| case 'paste': |
| return DiscreteEventPriority as number |
| case 'resize': |
| case 'scroll': |
| case 'mousemove': |
| return ContinuousEventPriority as number |
| default: |
| return DefaultEventPriority as number |
| } |
| } |
|
|
| |
|
|
| type DiscreteUpdates = <A, B>( |
| fn: (a: A, b: B) => boolean, |
| a: A, |
| b: B, |
| c: undefined, |
| d: undefined, |
| ) => boolean |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export class Dispatcher { |
| currentEvent: TerminalEvent | null = null |
| currentUpdatePriority: number = DefaultEventPriority as number |
| discreteUpdates: DiscreteUpdates | null = null |
|
|
| |
| |
| |
| |
| |
| resolveEventPriority(): number { |
| if (this.currentUpdatePriority !== (NoEventPriority as number)) { |
| return this.currentUpdatePriority |
| } |
| if (this.currentEvent) { |
| return getEventPriority(this.currentEvent.type) |
| } |
| return DefaultEventPriority as number |
| } |
|
|
| |
| |
| |
| |
| dispatch(target: EventTarget, event: TerminalEvent): boolean { |
| const previousEvent = this.currentEvent |
| this.currentEvent = event |
| try { |
| event._setTarget(target) |
|
|
| const listeners = collectListeners(target, event) |
| processDispatchQueue(listeners, event) |
|
|
| event._setEventPhase('none') |
| event._setCurrentTarget(null) |
|
|
| return !event.defaultPrevented |
| } finally { |
| this.currentEvent = previousEvent |
| } |
| } |
|
|
| |
| |
| |
| |
| dispatchDiscrete(target: EventTarget, event: TerminalEvent): boolean { |
| if (!this.discreteUpdates) { |
| return this.dispatch(target, event) |
| } |
| return this.discreteUpdates( |
| (t, e) => this.dispatch(t, e), |
| target, |
| event, |
| undefined, |
| undefined, |
| ) |
| } |
|
|
| |
| |
| |
| |
| dispatchContinuous(target: EventTarget, event: TerminalEvent): boolean { |
| const previousPriority = this.currentUpdatePriority |
| try { |
| this.currentUpdatePriority = ContinuousEventPriority as number |
| return this.dispatch(target, event) |
| } finally { |
| this.currentUpdatePriority = previousPriority |
| } |
| } |
| } |
|
|