Skip to content

Event Propagation

The EventPropagation package is responsible for functionality related to propagating events registered and triggered by consumers. It is a foundational component of the FocusNavigation library.

Info

The functionality defined in the EventPropagation package is currently an implementation detail of the FocusNavigationService. It should only be used for the purpose of further library development.

Types

EventPhase

type EventPhase = "Bubble" | "Capture" | "Target"
The EventPhase represents the phase of the event propagation cycle a given EventHandler should be registered to be called in.

Capture - The initial phase of event propagation. EventHandlers registered in this phase are called in order from the furthest ancestor of the target to the target itself.

Target - The second phase of event propagation. EventHandlers registered in this phase are called after the capture phase, and are only called when registered to the instance that the event is propagated from.

Bubble - This is the default phase that EventHandlers are registered to. EventHandlers registered in this phase are called in order from the target instance to the targets furthest ancestor.

Event

type Event<T> = {
    cancelled: boolean,
    phase: EventPhase,
    currentInstance: Instance,
    targetInstance: Instance,
    eventName: string,
    eventData: T,
    cancel: (self: Event<T>) -> ()
}
Events are passed to EventHandlers with the appropriate information when the EventHandler is called during event propagation. Note that each EventHandler is called with it's own Event, mutations to the Event will not be picked up by subsequent handlers.

Events may have extra data attached to them when they're propagated, which can be read via the eventData field. To prevent an event from being propagated further, call event:cancel() in an EventHandler.

EventHandler

type EventHandler<T> = (e: Event<T>) -> ()
EventHandlers are simple functions that take an event as an argument, and return nothing.

EventHandlerMap

type EventHandlerMap<T> = {
    [string]: {
        handler: EventHandler<T>,
        phase: EventPhase?,
    },
}
An EventHandlerMap gets used to register an EventHandler to an event in a given phase (or "Bubble" if omitted). The keys of the map are the names of the events that will be used when EventPropagationService:propagateEvent is called.

API

new

function EventPropagationService.new(): EventPropagationService
Create a new EventPropagationService. Intended only to be called once.

registerEventHandler

function EventPropagationService:registerEventHandler(
    instance: Instance,
    eventName: string,
    eventHandler: EventHandler,
    phase: EventPhase?
)
Register an individual EventHandler. This requires a target Instance that the handler will be associated with, the event handler itself, and the name of the event. Event names are equivalent to those provided in the propagateEvent method and will be meaningful within the context of the application. An EventPhase optionally can be passed in to indicate which event propagation phase the handler should be triggered in, this defaults to "Bubble".

registerEventHandlers

function EventPropagationService:registerEventHandlers(
    instance: Instance,
    map: EventHandlerMap
)
Register multiple EventHandlers from an instance using an EventHandlerMap.

deRegisterEventHandler

function EventPropagationService:deRegisterEventHandler(
    instance: Instance,
    eventName: string,
    eventHandler: EventHandler,
    phase: EventPhase?
)
De-register a single EventHandler from an Instance based on a phase. If phase is not passed in it defaults to "Bubble".

deRegisterEventHandlers

function EventPropagationService:deRegisterEventHandlers(
    instance: Instance,
    map: EventHandlerMap
)
De-register multiple EventHandlers from an instance using an EventHandlerMap.

propagateEvent

function EventPropagationService:propagateEvent(
    instance: Instance,
    eventName: string,
    silent: boolean
)
Propagate an event on a given Instance by name.

Behind the scenes, it creates a list of ancestors with relevant registered eventHandlers. The list is then processed as follows:

  1. Looping from furthest ancestor to the target, call all eventHandlers that are registered for the Capture phase
  2. Call the event handler for the Target phase on the target Instance provided
  3. Looping from the target to the furthest ancestor, call all of the eventHandlers registered for the Bubble phase.

In essence, the phase order is Capture → Target → Bubble. It should be noted that the Target phase is special: the only handler that runs during the Target phase is the handler on the currently focused element. These phases and their meaning are based on those from the Web API for event propagation.

Note: Events can optionally be propagated in silent mode, which will only call EventHandlers on the target instance. This mode is useful for migrating from a non-event-propagation system to an event propagation system.

Usage

local eventPropagationService = EventPropagationService.new()
local exampleInstance = Instance.new("frame")
function eventHandler(e: Event)
    print(e.phase)
end
eventPropagationService:registerEventHandler(exampleInstance, "exampleEvent", eventHandler)
eventPropagationService:propagateEvent(exampleInstance, "exampleEvent", nil, false)
-- prints "Bubble"