norigin-spatial-navigation-react について
norigin-spatial-navigation-reactは、Norigin Spatial Navigationを使い、Reactアプリで矢印キーやリモコンのフォーカスを管理するスキルです。TVアプリなどのために、フォーカス可能な要素、階層コンテナ、境界、プログラムによるフォーカス設定を解説します。
主な機能
- 01React向けの空間フォーカスナビゲーション
- 02フォーカス可能なリーフと階層コンテナ
- 03モーダルやオーバーレイのフォーカス境界
- 04プログラムによるフォーカス設定と安定したキー
- 05方向入力のカスタマイズとイベントコールバック
活用シーン
- 01Smart TV画面の矢印キー操作の構築
- 02メニュー、グリッド、カルーセルのフォーカス管理
- 03モーダルダイアログ内へのフォーカス制限
- 04カスタムリモコンキーへの対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ noriginmedia/ norigin-spatial-navigation --skill norigin-spatial-navigation-reactインストール後、Claude Code で/norigin-spatial-navigation-reactを入力して読み込まれたか確認します。
詳細
Norigin Spatial Navigation
React アプリ(スマートテレビ、セットトップボックス、ブラウザー)で、矢印キー/リモコンによるフォーカス管理を行うライブラリです。空間上の位置に基づいて次にフォーカス可能な要素を計算するため、方向キーのロジックを手動で設定する必要はありません。
ドキュメント全文: https://github.com/NoriginMedia/Norigin-Spatial-Navigation/tree/main/docs
このスキルの範囲を超える詳細が必要な場合は、該当するページを直接参照してください。
- 概念と階層:
docs/guides/concepts.md、docs/guides/focus-hierarchy.md - クイックスタートとインストール:
docs/guides/quick-start.md、docs/guides/installation.md useFocusableAPI:docs/api-reference/useFocusable.mdSpatialNavigationAPI(init、setFocus、pause など):docs/api-reference/SpatialNavigation.md- 境界/モーダル:
docs/guides/focus-boundaries.md - プログラムによるフォーカス設定:
docs/guides/programmatic-focus.md - 距離計算の調整:
docs/guides/distance-calculation.md - キーマッピング(カスタムリモコン):
docs/guides/key-mapping.md - イベントコールバック:
docs/guides/event-callbacks.md - パフォーマンス:
docs/guides/performance.md - デバッグ:
docs/guides/debugging.md - レシピ(リスト、グリッド、仮想化):
docs/guides/recipes.md - アクセシビリティラベル:
docs/guides/accessibility-labels.md - RTL:
docs/guides/rtl-support.md
基本的な考え方
コンポーネントの形状は次の2種類です。
- リーフ — 実際にフォーカスを受け取るインタラクティブな要素です。
useFocusableからfocusedを取得します。 - コンテナー — フォーカス可能な子要素をラップします。
focusKeyを取得し、FocusContext.Providerを通じて提供することで、子要素が階層に参加できるようにします。必要に応じてhasFocusedChildも取得できます(trackChildren: trueが必要です)。
すべてのフォーカス可能なコンポーネントは、返された ref を実際の DOM 要素に必ず設定してください。コンテナーは、子要素を必ず <FocusContext.Provider value={focusKey}> でラップしてください。そうしないと、子要素がルートに孤立し、子要素間のナビゲーションが機能しなくなります。
初期化
アプリの起動時(たとえば App.tsx)に、一度だけ init を呼び出します。
import { init as initNavigation } from '@noriginmedia/norigin-spatial-navigation-core';
initNavigation({
debug: false,
visualDebug: false,
throttle: 0,
distanceCalculationMethod: 'corners' // 'center' | 'edges' | 'corners'
});
カスタムリモコンキーを使用する場合は、キーマッピングも設定してください(docs/guides/key-mapping.md を参照)。
パターン
リーフ(ボタン、メニュー項目)
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation-react';
function Button({ label, onPress }: Props) {
const { ref, focused } = useFocusable({ onEnterPress: onPress });
return (
<div ref={ref} className={focused ? 'focused' : ''}>
{label}
</div>
);
}
コンテナー
import {
useFocusable,
FocusContext
} from '@noriginmedia/norigin-spatial-navigation-react';
function Menu({ items }: Props) {
const { ref, focusKey, hasFocusedChild } = useFocusable({
trackChildren: true,
saveLastFocusedChild: true
});
return (
<FocusContext.Provider value={focusKey}>
<div ref={ref} className={hasFocusedChild ? 'active' : ''}>
{items.map((i) => (
<MenuItem key={i.id} {...i} />
))}
</div>
</FocusContext.Provider>
);
}
モーダル/ポップアップ(フォーカスを閉じ込め、マウント時にフォーカスを取得)
function Popup() {
const { ref, focusKey, focusSelf } = useFocusable({ isFocusBoundary: true });
useEffect(() => {
focusSelf();
}, [focusSelf]);
return (
<FocusContext.Provider value={focusKey}>
<div ref={ref}>{/* buttons */}</div>
</FocusContext.Provider>
);
}
一部のみを閉じ込める場合(たとえば水平方向のみをブロックする場合)は、focusBoundaryDirections を使用してください。docs/guides/focus-boundaries.md を参照してください。
任意の場所からプログラムでフォーカスを設定
import {
setFocus,
doesFocusableExist
} from '@noriginmedia/norigin-spatial-navigation-core';
const KEY = 'primary-cta';
useFocusable({ focusKey: KEY });
if (doesFocusableExist(KEY)) setFocus(KEY);
条件付きでフォーカス可能にする
const { ref } = useFocusable({ focusable: !disabled });
方向入力のブロック/上書き
onArrowPress(direction, props) — デフォルトのナビゲーションを許可するには true を返し、ブロックするには false を返します。ページ分割された行、カスタムカルーセル、または端で現在のコンテナーから移動できないようにする場合に便利です。
useFocusable — 知っておきたいパラメーター
| パラメーター | 目的 |
|---|---|
focusable | 要素をナビゲーションの対象にするかどうかを切り替えます。デフォルトは true です。 |
trackChildren | hasFocusedChild を有効にします。パフォーマンスに影響するため、参照する場合のみ有効にしてください。 |
saveLastFocusedChild | コンテナーに再度入ったとき、最後にフォーカスされていた子要素にフォーカスを戻します。デフォルトは true です。 |
isFocusBoundary / focusBoundaryDirections | コンテナー内にフォーカスを閉じ込めます。 |
focusKey | setFocus / doesFocusableExist 用の安定したキーです。 |
preferredChildFocusKey | コンテナーに入ったとき、どの子要素に最初にフォーカスするかを指定します。 |
onEnterPress、onArrowPress、onFocus、onBlur | イベントフックです。docs/guides/event-callbacks.md を参照してください。 |
extraProps | コールバックに渡される任意のペイロード(項目 ID など)です。 |
戻り値:ref(必須)、focusKey、focused(リーフ)、hasFocusedChild(コンテナー)、focusSelf()。
トップレベル API
import {
init,
setFocus,
getCurrentFocusKey,
doesFocusableExist,
navigateByDirection,
pause,
resume,
setKeyMap,
setThrottle,
destroy
} from '@noriginmedia/norigin-spatial-navigation-core';
一覧とシグネチャーの全容は docs/api-reference/SpatialNavigation.md を参照してください。
ベストプラクティス
推奨
refは必ず DOM 要素にアタッチしてください。- コンテナーの子要素は
<FocusContext.Provider value={focusKey}>で囲んでください。 hasFocusedChildを実際に使用する場合のみ、trackChildrenを有効にしてください。- プログラムから対象にする要素には、安定した
focusKeyを使用してください。 - 特にルートの変更時や非同期データを扱う場合は、
setFocusの呼び出しをdoesFocusableExistでガードしてください。 - モーダル、ドロワー、オーバーレイには
isFocusBoundaryを使用してください。 - 頻繁に切り替わる可能性のある無効または非表示の項目は、アンマウントするのではなく
focusable: falseを設定してください。
非推奨
- コンテナーの
focusedは参照しないでください。代わりにhasFocusedChildを使用してください。 - コンテナーで
FocusContext.Providerを省略しないでください。子要素が兄弟要素から到達できなくなります。 - マウントされていない可能性のあるキーに対して、むやみに
setFocusを呼び出さないでください(フォーカスが通知なく失われます)。 - レンダーのたびに変わる
focusKeyを生成しないでください。フォーカス状態が失われます。 - 本番ビルドでは
debug/visualDebugを有効にしないでください。
デバッグ
init で診断機能を有効にします。
init({ debug: true, visualDebug: true });
よくある問題:
- 要素にフォーカスできない —
refがアタッチされていない、focusable: falseが設定されている、または祖先にFocusContext.Providerがありません。 - フォーカスが誤った隣接要素に移動する — 別の
distanceCalculationMethod(corners/center/edges)を試してください。visualDebugを有効にすると、ヒットボックスを確認できます。 - 再レンダー時にフォーカスが失われる —
focusKeyが不安定か、フォールバックがないままフォーカスされていたノードがアンマウントされています。preferredChildFocusKeyを使用するか、新しいツリーのマウント後にsetFocusを呼び出してください。 - モーダルからフォーカスが漏れる — モーダルコンテナーに
isFocusBoundaryが設定されていません。
詳細:docs/guides/debugging.md、docs/guides/performance.md。
クイックリファレンス
// Leaf
const { ref, focused } = useFocusable();
// Container
const { ref, focusKey } = useFocusable();
<FocusContext.Provider value={focusKey}>
<div ref={ref}>{children}</div>
</FocusContext.Provider>;
// Focus on mount
const { focusSelf } = useFocusable({ isFocusBoundary: true });
useEffect(() => {
focusSelf();
}, [focusSelf]);
// Manual focus
if (doesFocusableExist('my-key')) setFocus('my-key');