Skip to main content

Focus Management

Hosanna UI has one active focus owner at a time. Platform input adapters normalize remote, keyboard, debug, mouse, and touch input into framework input events, then FocusManager routes those events through the focused view and its parent chain.

Use declarative focus hints first. Override focus hooks only when a layout needs rules that cannot be expressed with isInitialFocus, nextFocusMap, or focused-child restoration.

Focus Resolution Flow

Focus Resolution

Start Declarative

Use isInitialFocus and nextFocusMap for most layouts. Use onInputEvent or onFindNextFocusable only when the view owns custom input or navigation behavior.

Core Concepts

  • FocusManager.setFocus(view) moves framework focus to a view.
  • FocusManager.handleInputEvent(event) handles normalized HsInputEvent objects from input adapters.
  • BaseView.onInputEvent(event) receives non-direction keys and direction keys before focus resolution.
  • BaseView.findNextFocusable(event) resolves a directional move.
  • nextFocusMap maps directions such as left, right, up, down, and default to a target id or NextViewFocus token.
  • requestFocusedChild(target, childId?) stores or applies a preferred child for composite views.

Input Events

View handlers receive HsInputEvent, which includes the normalized key, key state, direction, source adapter, long-press metadata, and consumption flags.

import { Key } from 'hosanna-ui/hosanna-bridge-lib/api';
import { KeyState, type HsInputEvent } from 'hosanna-ui/hosanna-bridge-lib/IFocusManager';

Button({ id: 'play', text: 'Play' })
.onInputEvent((event: HsInputEvent) => {
if (event.key === Key.Ok && event.keyState === KeyState.Press) {
this.play();
event.preventDefault = true;
event.consumed = true;
}
});

Set event.preventDefault = true when the view handled the event and the default focus behavior should stop.

Declarative Focus Maps

Use nextFocusMap to steer directional navigation. Map a direction to a sibling view id, or use a NextViewFocus token.

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

NextViewFocus.Maintain keeps focus inside the current view. NextViewFocus.Exit jumps out of a view-owner boundary. NextViewFocus.None continues bubbling focus resolution to the parent.

Focused Children

Composite views can remember or request which direct child should receive focus.

import { ChildFocusTarget } from 'hosanna-ui/views/lib/view-api';

this.requestFocusedChild(ChildFocusTarget.First);
this.requestFocusedChild(ChildFocusTarget.Last);
this.requestFocusedChild(ChildFocusTarget.Default);
this.requestFocusedChild(ChildFocusTarget.Specific, 'detailsButton');

If the composite is already in the focus chain, Hosanna moves focus immediately. Otherwise it stores focusedChildId and uses it the next time focus cascades into the view.

setFocusedSubview(id) is still available inside BaseView subclasses as a concise helper for ChildFocusTarget.Specific.

Custom Resolution

Use onFindNextFocusable for layouts that need runtime decisions.

GridGroup({ id: 'grid', numCols: 3 }, cells)
.onFindNextFocusable(event => {
if (event.direction === Direction.Up && this.isOnFirstRow()) {
return this.getSubView('toolbar');
}

return undefined;
})
.nextFocusMap({
default: NextViewFocus.Maintain,
});

Return a focusable view, a NextViewFocus token, or undefined to let parent views continue resolving.

View Hooks

  • onFocus(event): called when a view enters focus; set event.nextFocusId to guide child focus.
  • onBlur(): called when a view leaves focus.
  • onInputEvent(event): handles normalized input events.
  • onFocusedChildChange(event): observes focus movement inside a composite view.
  • onFindNextFocusable(event): chooses the next focus target for directional movement.
  • onRestoreFocus(event): customizes restoration when a composite receives focus again.

Ui And Interaction Focus Management

@view('UiAndInteractionFocusManagement')
export class UiAndInteractionFocusManagementView extends BaseExampleScreenView<UiAndInteractionFocusManagementState> {

  protected getViews(): ViewStruct<ViewState>[] {
    // --- edit below ---

    return [
      VGroup([
        Label({ text: 'Focus Manager - onFocus/onBlur + nextFocusMap' }),

        HGroup([
          Button({ id: 'ok', text: 'OK', width: 220, height: 60 })
            .isInitialFocus(true)
            .onFocus((e: FocusEvent) => { e.nextFocusId = 'ok'; }),
          Button({ id: 'cancel', text: 'Cancel', width: 220, height: 60 })
            .onBlur(() => { /* blur side-effects go here */ }),
        ])
          .itemSpacing(16)
          .nextFocusMap({ left: 'cancel', right: 'ok', default: NextViewFocus.Maintain }),

      ])
        .itemSpacing(12)
        .translation([50, 50])
    ];

    // --- edit above ---
  }
}

Focus Classes

Focus Classes

Related APIs

Use the API reference for exact type signatures: FocusManager, IFocusManager, HsInputEvent, ViewFocusMap, NextViewFocus, and ChildFocusTarget.

CollectionView Focus

CollectionView owns row and item focus internally. Application code usually listens to row/item events or updates the data source instead of directly moving cell focus.

Use CollectionView APIs such as jumpToRow, scrollToRow, getFocusedDataSourceRow, and getFocusedDataSourceItem when restoring position, opening deep links, or responding to selection.