Skip to main content

Focus Management

Focus management is essential for building accessible and user-friendly TV, set-top box, and remote-driven applications. Hosanna UI's focus system ensures that users can navigate your app smoothly using a remote or keyboard, with clear visual feedback and predictable behavior.

  • What is Focus? Focus determines which UI element is currently active and ready to receive input. Only one element can be focused at a time, and navigation keys (arrows, OK, etc.) move focus between elements.

  • Why is Focus Management Important? Good focus management improves usability, accessibility, and user satisfaction, especially on devices without touch or mouse input. It also enables features like focus rings, keyboard shortcuts, and modal dialogs.

  • How Hosanna UI Helps: Hosanna UI provides:

    • Automatic focus navigation between controls
    • Customizable focus logic for advanced layouts
    • Visual focus indicators (focus rectangles)
    • Support for focus locking and modal dialogs
    • Debug tools for visualizing and troubleshooting focus

Focus Resolution Flow

How focus moves on key presses and state changes.

Focus Resolution

Tip: Start Declarative

Begin with focusMap and isInitialFocus. Only override hooks when you need special rules.

Auto focus resolution and hooks

Focus in Hosanna is resolved automatically wherever possible, and can be guided declaratively or imperatively.

  • Auto initial focus: Set isInitialFocus: true on a subview to receive focus when the parent is focused.
  • Declarative focus map: Provide focusMap on any view to steer directional navigation.
  • Programmatic helpers: Override view hooks or call helper methods where needed.

Key hooks and helpers:

  /**
* Default animation provider mapping for simple properties.
* By default, maps 'opacity' to the base renderer's 'opacity' field.
* Views can override to provide richer mappings.
*/
getAnimationPropsForStateProperty(prop: string): AnimationFieldBinding[] | undefined {
if (this.renderer) {
return [{ renderer: this.renderer, field: prop }];
}
return [];
}

/** Convenience: animate logical properties across this view's subtree. */
animateViews(anims: AnimationSpec, duration: number): HosannaViewAnimator {
return HosannaViewAnimator.animateViews(this as unknown as BaseView<ViewState>, anims, duration);
}

Using onFocus and onBlur

Override hooks to react to focus enter/exit. The onFocus(event) callback provides the ability to steer focus (e.g., set event.nextFocusId).

@view('SampleFocusable')
export class SampleFocusableView extends BaseExampleScreenView<ViewState> {
protected override onFocus(event: FocusEvent<ViewState>) {
console.info('Focused:', this.id);
// Example: force the next focus to a particular subview when pressing Right
if (event.direction === Direction.Right) {
event.nextFocusId = 'ok';
}
}

protected override onBlur() {
console.info('Blurred:', this.id);
}
}

Initial focus resolution helpers:

  protected getInitialFocusId() {
if (this.initialFocusId) {
return this.initialFocusId;
} else if (this.children[0]?.id !== undefined) {
return this.children[0]?.id;
} else if (this.children[0]?.pendingState.id !== undefined) {
return this.children[0]?.pendingState.id;
}
return undefined;
}

protected setFocusedSubview(id: string) {
if (id) {
const child = this.getViewById(id);
if (child) {
if (this.isInFocusChain()) {
this.focusManager.setFocus(child);
} else {
this.focusedChildId = id;
}
}
}
}

Restore focus helpers that can be overridden:

  protected restoreFocus(nextFocusId: string) {
if (nextFocusId) {
let child = this.getViewById(nextFocusId) as IHosannaView<ViewState>;
if (child !== undefined && child.canReceiveFocus === true && child.visible === true) {
this.focusManager.setFocus(child)
} else {
child = this.findNextFocusable({ direction: Direction.None, originView: this, previousView: this }) as IHosannaView<ViewState>;
if (child) {
this.focusManager.setFocus(child);
return child;
} else {
for (const subView of this.subViews) {
if (subView.canReceiveFocus && subView.visible) {
this.focusManager.setFocus(subView);
return subView;
}
}
}
}
}
}

Utilities to restore focus to the last focused child:

  protected restoreFocusToFocusedChildId() {
if (this.focusedChildId) {
const child = this.getViewById(this.focusedChildId) as IHosannaView<ViewState>;
if (child !== undefined && child.canReceiveFocus && child.visible) {
this.focusManager.setFocus(child);
return true;
}
}
return false;
}

Directional resolution with NextViewFocus and focusMap:

export interface ViewFocusMap {
[key: string]: string | NextViewFocus | undefined;
}

export enum NextViewFocus {
Maintain = 'maintain',
Exit = 'exit',
None = 'none',
}

Declarative Focus Maps

Use focusMap to steer directional navigation with IDs or NextViewFocus tokens.

VGroup([
Button({ id: 'ok', text: 'OK' }),
Button({ id: 'cancel', text: 'Cancel' })
])
.onFocus((e) => { e.nextFocusId = 'ok'; })
.focusMap({ left: 'cancel', right: 'ok', default: NextViewFocus.Maintain });

Another example mapping a grid of items:

GridGroup({ id: 'grid', numCols: 3 }, items)
.focusMap({
// Keep focus within the grid by default
default: NextViewFocus.Maintain,
// Jump to toolbar when pressing Up from the first row
up: 'toolbar',
});

Override hooks:

  • onFocus(event) and onBlur() can be overridden to react to focus entry/exit.
  • findNextFocusable(event) can be implemented to programmatically choose the next view.
  • getViewToRestoreFocusTo() can be overridden to customize fallback restoration.

FocusManager Class

The core of focus handling is the FocusManager class, which:

  • Manages which view is currently focused
  • Handles key events (arrows, OK, back, etc.) to move focus
  • Supports focus locking and custom navigation
  • Provides debug utilities for visualizing focus

Key Features

  • Automatic Focus Navigation: Moves focus between focusable elements using remote or keyboard input.
  • Custom Focus Logic: Override or extend navigation by providing custom handlers.
  • Focus Locking: Temporarily lock focus to a specific view (e.g., modal dialogs).
  • Debug View: Visualize focus transitions and current focus state for development.

Focus Classes Overview

How the main classes relate.

Focus Classes

Key Types
  • FocusManager: global navigation and focus state
  • BaseView: implements hooks and helpers
  • ViewFocusMap: declarative directional rules per view
  • Automatic Focus Navigation: Moves focus between focusable elements using remote or keyboard input.
  • Custom Focus Logic: Override or extend navigation by providing custom handlers.
  • Focus Locking: Temporarily lock focus to a specific view (e.g., modal dialogs).
  • Debug View: Visualize focus transitions and current focus state for development.

Example: Basic Focus Navigation

import { FocusManager } from 'hosanna-ui/hosanna-bridge-lib/FocusManager';

const focusManager = new FocusManager();

// Set initial focus ocusManager.setFocus(myButton);

// Handle key events (e.g., from remote)
document.addEventListener('keydown', (e) => {
focusManager.onKeyEvent(e.key, true);
});

Example: Locking Focus

// Lock focus to a dialog
focusManager._setFocusLocked(myDialog);

// Unlock focus when done
focusManager._setFocusUnlocked();

Example: Adding a Debug View

focusManager.addDebugView(); // Shows a visual overlay for focus

CollectionViewFocusManager

For list/grid navigation, CollectionViewFocusManager manages the focus rectangle and smooth transitions between items.

import { CollectionViewFocusManager } from 'hosanna-ui/hosanna-list/CollectionViewFocusManager';

const collectionFocusManager = new CollectionViewFocusManager(myCollectionView);
collectionFocusManager.setNextFocusInfo({
rowIndex: 0,
itemIndex: 1,
offset: [0,0],
size: [200,80],
imageUri: 'focusrect.9.png',
color: '#fff',
feedbackOffsets: [0,0,0,0],
displayMode: 1,
});
collectionFocusManager.renderCurrentFocusInfo();

For advanced scenarios, see the API docs for FocusManager, CollectionViewFocusManager, and related interfaces.