Listenable
Updated on August 22, 2024Source codeTests
Listenable is a class that enriches an event type (including observation types, media queries, custom gestures, and more), allowing it to:
- Listen for that event type and execute a callback function when it occurs
- Retrieve a list of active listeners that it has added
- Store a status (
ready,listening, orstopped) - Easily clean up all listening activity to avoid memory leaks
Example
Baleada's docs use Listenable to:
- Toggle the dark theme when you press
Shift+D - Toggle the minimalist theme when you press
Shift+M - Set your default theme (dark or light) based on your device's color theme preference
- On mobile, open and close the sidebar nav and table of contents when you swipe right or left
Construct a Listenable instance
The Listenable constructor accepts two parameters:
typeoptionsListenable instance. See the Listenable constructor options section for more guidance.Valid event types
Listenable supports a ton of different event types and can deduce which web APIs to use under the hood based on the type you pass.
Most of the time, you don't need to be concerned with exactly which web API is being used, and you can think of it as an implementation detail.
But in certain cases where you want to customize the way a specific web API behaves, you'll need to know which API is being used in order to know what customization options are available.
You'll find more guidance down below, in the How to customize listen behavior section.
The table below has a breakdown of valid event types, the corresponding web APIs that Listenable uses under the hood, and the main purpose of the API.
click, keydown, mousemove, etc.intersectmessage and messageerrorBroadcastChannelrecognizeableListenable instance 🤯Listens for custom gestures, powered by Recognizeable.
See the How to listen for custom gestures section for more guidance.
Listenable constructor options
recognizeablePasses options for a new instance of the Recognizeable class. See the How to listen for custom gestures section for more guidance.
The recognizeable option only has an effect when your type is recognizeable.
State and methods
typeThe event type you passed to the Listenable constructor.
If you assign a value directly to type, a setter will pass the new value to setType.
statusThe status (String) of the Listenable instance.
status is ready after the instance is constructed, and changes to listening after the listen method is called for the first time, and change to stopped after all web API activity has been stopped and cleaned up.
activeSet of objects that describe all the currently active listeners, observers, etc.recognizeableThe Recognizeable instance constructed using the options you passed to options.recognizeable.
If you didn't pass that option, the recognizeable property will be undefined.
See the How to listen for custom gestures section for more guidance.
setType(type)type, after stopping and cleaning up all existing web API activity.Listenable instancelisten(effect, options)type, and performs side effects via a callback function when the events happen. Can't be called until the DOM is available.A side effect (Function, required) that will be performed when the events are detected, and an optional options object.
To learn more about handling events with your side effect function, see the How to handle events and How to customize listen behavior sections.
Listenable instancestop(options)An optional object with a target property, whose value is a DOM element, window, document, or a BroadcastChannel instance, depending on what type of event you're listening for.
If options.target is passed, only activity related to that target will be stopped.
If options.target is not passed, all activity is stopped.
See the Default values for
Listenable instanceHow to handle events
Depending on your type, your effect—passed as the required first argument of the listen method—will receive different parameters.
The table below has a full breakdown of what the listen method passes to your effect for each specific event type:
typeeffect receivesclick, keydown, mousemove, etc.intersectIntersectionObserverEntry objectsresizeResizeObserverEntry objectsmutateMutationRecord objectsMediaQueryListEvent objectidleIdleDeadline objectmessage and messageerrorrecognizeableThe latest sequenceItem added to your Recognizeable instance's sequence.
Often, your effect won't actually do anything with this argument. Instead, it will reach into listenableInstance.recognizeable.metadata for additional information about the captured sequence of events.
How to customize listen behavior
The listen method accepts an optional second parameter, which is an Object whose properties customize the behavior of the web APIs Listenable uses under the hood.
Depending on your type only certain properties will have an effect.
First, here's a breakdown of what each options property does, and below that, in the Available options for each type section, you'll find a table of which properties can be used for each type:
targetThe target that will listen for events.
See the Default values for target based on type section for more guidance on default target values.
useCaptureuseCapture parameter of addEventListener. Ignored if an addEventListener object was passed.except[]An array of DOM elements that, if they are the target of the event, should not cause your effect to be executed.
When the only option is a non-empty array, except is ignored.
only[]An array of DOM elements that, if they are the target of the event, should cause your effect to be executed.
When only is a non-empty array, except is ignored.
An empty only array is ignored (otherwise, the effect would never execute).
observekeyDirectiondownIndicates which keyboard event should be listened to when detecting keycombos. Valid options are down and up .
The keyDirection option only has an effect when your type is a keycombo, as described the How to format combos.
Default values for target based on type
An object with a `target` property, which should be the `BroadcastChannel` instance that is receiving messages.
The default value for the listen method's target option depends on your type. The table below has a full breakdown.
typetargetdocumentHTMLElementEventMap, e.g. visibilitychange, fullscreenchange, etc.document (can't be overridden)intersectdocument.querySelector('html')resizedocument.querySelector('html')mutatedocument.querySelector('html')idlemessage and messageerrorA new BroadcastChannel.
You'll technically be able to retrieve this BroadcastChannel from listenableInstance.active and send messages from it, but the DX of creating and passing in your own BroadcastChannel is much better.
Be sure to check out Baleada's Broadcastable class, and especially the Using Broadcastable with Listenable docs, if you're interested in handling BroadcastChannel messages with Baleada and Listenable.
recognizeabletype handled by your Recognizeable instance's effects Available options for each type
typetargetaddEventListeneruseCapturewantsUntrustedexceptonly
HTMLElementEventMap, e.g. visibilitychange, fullscreenchange, etc.addEventListeneruseCapturewantsUntrusted
intersectobserverresizeobservemutateobserveidlerequestIdleCallbackmessage and messageerrortargetrecognizeableHow to listen for custom gestures
Listenable allows you to listen for custom gestures defined by Baleada Logic's Recognizeable class.
Before you read any further, it's worth checking out the usage docs for the gestures that Baleada supports out of the box.
Those docs will show you the overall workflow for using Listenable, with Recognizeable under the hood, to recognize complex gestures.
If you get a better understanding of that system and want to define a gesture of your own, definitely visit the Recognizeable docs for more guidance.
Using with TypeScript
Listenable will type check your listen method effect functions and options based on the type (String) you pass to the constructor:
const instance = new Listenable('intersect')
instance.listen(
// Listenable infers from the `intersect` event type that
// `entries` is an array of IntersectionObserverEntry objects.
//
// entries[0].boundingClientRect.width is correctly typed
// as a number, automatically!
entries => console.log(entries[0].boundingClientRect.width),
// Listenable also knows that the `listen` method for an
// 'intersect' type can accept an `observer` option,
// passing an IntersectionObserverInit object.
{ observer: { threshold: 0.5 } }
)
Even complex types like media queries will be detected:
const screenSize = new Listenable('(min-width: 420px)')
screenSize.listen(
// TypeScript knows that this is a MediaQueryListEvent
event => doSomething(event)
)
There's one situation where you'll need to adopt a little bit of type unsafety: when using Listenable to configure a Recognizeable instance for recognizing a custom gesture.
Recall that to use Listenable with Recognizeable, you should pass recognizeable as the Listenable constructor's type argument, and pass your Recognizeable instance's options to listenableOptions.recognizeable
const instance = new Listenable(
'recognizeable',
{ recognizeable: { ... } }
)
There's a way to type-annotate your code so that your options.recognizeable object is fully type checked based on all the possible events that you want your Recognizeable instance to be able to handle, and type unsafety is minimized.
We won't cover all that information here, though. Instead, you should visit the Using with TypeScript section of the Recognizeable docs. Those docs give full information on not only how to set up a standalone Recognizeable instance, but more importantly, how to set up a fully type-safe Listenable instance that uses Recognizeable under the hood to recognize custom gestures.
API design compliance
options object.typesetTypeset<Property> methodsstatus, active, recognizeablelisten, stopstop methodstopable