Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pocket-stack/pocketjs/llms.txt

Use this file to discover all available pages before exploring further.

@pocketjs/framework/input exposes the low-level input surface: the PSP button bitmask constants, programmatic focus control, and the imperative scope and grid stacks that back the FocusScope and FocusGrid components. In application code, prefer the FocusScope and FocusGrid components from @pocketjs/framework/components — they manage push/pop automatically via the reactive owner. Reach for these imperative functions when building custom focus abstractions, animation-driven transitions, or test utilities. The focus model operates over a JS mirror of the native tree. Default traversal follows document order (DFS). pushFocusScope restricts traversal to a subtree; pushFocusGrid gives a subtree explicit row/column d-pad semantics. The stacks are independent — a grid inside a scope works correctly. Every focus change calls ops.setFocus so the Rust core applies the focus: style variant with zero further JS.
import {
  BTN,
  focusNode,
  getFocused,
  pushFocusScope,
  pushFocusGrid,
} from '@pocketjs/framework/input';
import type { FocusScopeOptions, FocusGridOptions } from '@pocketjs/framework/input';

BTN

The PSP button bitmask. Values are identical on every host — web and Bun hosts remap keyboard keys to these same bits, so code written against BTN works unchanged on all targets.
ConstantValueButton
BTN.SELECT0x0001Select
BTN.START0x0008Start
BTN.UP0x0010D-pad Up
BTN.RIGHT0x0020D-pad Right
BTN.DOWN0x0040D-pad Down
BTN.LEFT0x0080D-pad Left
BTN.LTRIGGER0x0100L Trigger (left shoulder)
BTN.RTRIGGER0x0200R Trigger (right shoulder)
BTN.TRIANGLE0x1000Triangle
BTN.CIRCLE0x2000Circle (Confirm / onPress action)
BTN.CROSS0x4000Cross (Back)
BTN.SQUARE0x8000Square
CIRCLE is the primary confirm/action button: pressing it while a node is focused fires the nearest ancestor onPress handler. The runtime handles CIRCLE automatically during focus navigation — you do not need to call focusNode or getFocused to intercept it. Use onButtonPress(BTN.CIRCLE, …) only for unconditional CIRCLE bindings outside the focus system.

focusNode

Programmatically focuses a node, or clears focus when called with null. Immediately calls ops.setFocus on the native core so the focus: style variant is applied synchronously.
function focusNode(node: NodeMirror | null): void
node
NodeMirror | null
required
The node to focus. Must be within the active focus scope (if any). Pass null to clear focus entirely.
Focus lost to focusNode(null) does not trigger the scope’s restoreFocus path — it is an explicit imperative clear. Use it sparingly; prefer letting the d-pad navigation and autoFocus handle focus automatically.

getFocused

Returns the currently focused NodeMirror, or null if no node is focused.
function getFocused(): NodeMirror | null
This is a plain read — it does not create a reactive dependency. Call it inside an onFrame or button handler when you need to inspect focus without subscribing to changes.

pushFocusScope

Temporarily restricts d-pad traversal and CIRCLE press to the subtree rooted at node. Pushes an entry onto an internal scope stack; the returned disposer pops it and optionally restores the previously focused node.
function pushFocusScope(node: NodeMirror, opts?: FocusScopeOptions): () => void
node
NodeMirror
required
The subtree root. All focusable nodes inside this subtree become reachable; nodes outside become unreachable while the scope is active.
opts
FocusScopeOptions
Auto-focus and restore-focus behavior. See FocusScopeOptions below.
Returns: () => void — a disposer. Call it when the scope should be released. Calling more than once is safe. Scopes are stacked — pushing a second scope while one is active restricts to the newer scope’s subtree. Popping the outer scope re-enters the inner one. This matches the Modal nesting contract.

FocusScopeOptions

FieldTypeDefaultDescription
autoFocusbooleantrueFocus the first focusable descendant when the scope is pushed. Set to false to preserve current focus.
restoreFocusbooleantrueWhen the scope is disposed, restore focus to the node that was focused before the scope was pushed, if that node is still within the new active scope.

pushFocusGrid

Gives the subtree rooted at node explicit row/column d-pad traversal semantics. Without a grid, UP/DOWN navigates forward/backward in document order; with a grid, UP/DOWN moves between rows and LEFT/RIGHT moves between columns.
function pushFocusGrid(node: NodeMirror, opts: FocusGridOptions): () => void
node
NodeMirror
required
The subtree root. Focusable nodes inside are addressed as a columns-wide grid in document order.
opts
FocusGridOptions
required
Grid column count and wrap behavior. columns is required.
Returns: () => void — a disposer that removes the grid registration from the stack. Calling more than once is safe. Grid registrations are also stacked. The innermost grid that contains the currently focused node wins. A grid inside a scope works correctly — the grid narrows traversal within the scope’s already-narrowed subtree.

FocusGridOptions

FieldTypeDefaultDescription
columnsnumberNumber of columns. Required. Clamped to a minimum of 1. Drives traversal only — visible column count is determined by tile widths and container width.
wrapbooleanfalseWhen true, pressing RIGHT at the last column of a row jumps to the first column of that row, and pressing LEFT at the first column jumps to the last. Same wrap applies vertically: pressing DOWN on the last row jumps to the same column of the first row.

Focus model details

Understanding the focus model helps when building custom navigation patterns:
  • Default traversal order is document order over the mirror tree (DFS), derived lazily on each navigation press. It is always correct after <For> reorders because it re-walks the live tree.
  • Active scope is the top of the focusScopeStack, or the input root when the stack is empty. Navigation is confined to focusable nodes within the active scope’s subtree.
  • Active grid is the innermost focusGridStack entry whose node contains the focused node and is within the active scope. Grid traversal overrides linear traversal when an active grid exists.
  • Focus loss on removal: when the focused node’s subtree is detached, focus moves to the next sibling subtree → previous sibling subtree → nearest focusable ancestor → null. This repair happens before the mirror unlink so the transition is seamless.
  • CIRCLE: fires onPress on the focused node, bubbling to the nearest ancestor that has a handler. The runtime handles this automatically — no onButtonPress(BTN.CIRCLE, …) is needed for standard Focusable/onPress patterns.

Usage examples

Programmatic focus on mount:
import { focusNode } from '@pocketjs/framework/input';
import { onMount } from 'solid-js';
import type { NodeMirror } from '@pocketjs/framework';

function AutoFocusedButton() {
  let node: NodeMirror | undefined;

  onMount(() => {
    if (node) focusNode(node);
  });

  return (
    <Focusable ref={(n) => { node = n; }} onPress={() => console.log('pressed')}>
      <Text>Confirm</Text>
    </Focusable>
  );
}
Imperative scope for a custom dialog:
import { pushFocusScope } from '@pocketjs/framework/input';
import { createEffect, onCleanup } from 'solid-js';
import type { NodeMirror } from '@pocketjs/framework';

function CustomDialog(props: { open: () => boolean; children: JSX.Element }) {
  let root: NodeMirror | undefined;

  createEffect(() => {
    if (!root || !props.open()) return;
    const dispose = pushFocusScope(root, { autoFocus: true, restoreFocus: true });
    onCleanup(dispose);
  });

  return (
    <View ref={(n) => { root = n; }}>
      {props.children}
    </View>
  );
}
A 3-column grid of focusable tiles:
import { pushFocusGrid } from '@pocketjs/framework/input';
import type { NodeMirror } from '@pocketjs/framework';

function TileGrid(props: { items: string[] }) {
  let root: NodeMirror | undefined;

  onMount(() => {
    if (root) {
      const dispose = pushFocusGrid(root, { columns: 3, wrap: false });
      onCleanup(dispose);
    }
  });

  return (
    <View ref={(n) => { root = n; }} class="flex-row flex-wrap gap-2">
      <For each={props.items}>
        {(item) => (
          <Focusable class="w-[148] h-[80] bg-white rounded-lg" onPress={() => selectItem(item)}>
            <Text>{item}</Text>
          </Focusable>
        )}
      </For>
    </View>
  );
}
In application code, prefer <FocusGrid columns={3}> from @pocketjs/framework/components — it wraps this pattern with reactive cleanup automatically.

Build docs developers (and LLMs) love