Handlers
Every node in a navigation tree has a handler that determines its behavior. The handler responds to actions and indicates where focus should move.
At its core, a handler is a function that runs every time a node receives an action. The handler function is defined like this:
export type NavigationHandler = (
node: NavtreeNode,
action: NavigationAction,
next: (id?: NodeId, action?: NavigationAction) => NodeId | null,
) => NodeId | null;Parameters
node- the current node on which an action was executedaction- the action to handle (e.g., move up)next- a function for passing the action to the next handler; it can optionally be given a differentNodeIdand action
Return value
A handler can return a NodeId that indicates where the focus should move next. Not all handlers need to do this. They can return null, which means that focus should remain in the same place.
Example
Here is an example of a handler that handles focus and move actions and passes the rest to the next handler:
function exampleHandler(
node: NavtreeNode,
action: NavigationAction,
next: HandlerNext
) => NodeId | null) {
if (action.kind === "focus") {
// handle focus
}
if (action.kind === "move") {
// handle movement
}
// pass action to next handler in line
return next();
}Composing handlers
Handling all the different action types in a single function would become messy very quickly. That's why it's possible to compose multiple primitive handlers together and create more complex ones that handle multiple actions. The key to this is the next function. It can pass an action to the next handler in line, similar to middleware in a web framework.
Extending built-in handlers
Built-in handlers can be extended to add additional functionality or modify the current behavior. The simplest way to do this is by prepending a handler to an existing one:
import { defaultHandler } from "@fiveway/core";
// defaultHandler is a basic composed handler
// that can be extended with custom functionality
const customHandler = defaultHandler.prepend((node, action, next) => {
if (action.kind === "move" && action.direction === "up") {
console.log("moving up");
}
return next(); // pass action to next handler in line
});This creates a new handler called customHandler that prints 'moving up' in response to the move up action and passes it to the next handler in line, in this case the defaultHandler.
Creating composed handlers from primitive ones
Sometimes you might want to create a completely new handler by combining multiple primitive handlers together. This is a more advanced approach used by the library itself.
export const horizontalHandler = chainedHandler([
focusHandler({ skipEmpty: true, direction: horizontalFocusDirection }),
horizontalMovementHandler,
parentHandler,
]);Most of the built-in handlers are composed from these three primitive handlers:
focusHandler- handlesfocusactionsmovementHandler- in this casehorizontalMovementHandlerthat handlesmoveactionsparentHandler- passes unhandled actions to the parent node