Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/accessibility-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@noriginmedia/norigin-spatial-navigation-core': minor
'@noriginmedia/norigin-spatial-navigation-react': minor
'@noriginmedia/norigin-spatial-navigation': minor
---

Add accessibility labels on focusable components.

Introduces a new optional `onUtterText` callback on `init()` and an `accessibilityLabel` prop on `useFocusable()` (and the underlying `addFocusable` / `updateFocusable` payloads). When focus lands on a focusable component, the library concatenates the labels of all newly-entered parent regions with the leaf node's own label and passes the resulting string to `onUtterText`. Parent region labels are only included when focus enters a region for the first time (similar to how `aria-label` on `role="region"` behaves), so subsequent focus moves within the same parent only utter the leaf label.

This library does not implement Text-To-Speech itself — it only provides a unified way to declare accessibility labels and wire the callback to the platform's TTS engine, which is particularly useful for cross-platform TV apps where native `aria-*` support is fragmented.
8 changes: 7 additions & 1 deletion apps/react-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import logo from './logo.png';
init({
debug: false,
visualDebug: false,
distanceCalculationMethod: 'center'
distanceCalculationMethod: 'center',
onUtterText: (text: string) => {
// eslint-disable-next-line no-console
console.log('onUtterText', text);
}
});

const rows = shuffle([
Expand Down Expand Up @@ -233,6 +237,7 @@ function Asset({
index
}: AssetProps) {
const { ref, focused } = useFocusable<object, HTMLDivElement>({
accessibilityLabel: title,
onEnterPress,
onFocus,
extraProps: {
Expand Down Expand Up @@ -298,6 +303,7 @@ function ContentRow({
isShuffleSize
}: ContentRowProps) {
const { ref, focusKey } = useFocusable<object, HTMLDivElement>({
accessibilityLabel: rowTitle,
onFocus
});

Expand Down
36 changes: 21 additions & 15 deletions docs/api-reference/SpatialNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,27 @@ init(config?: {
isVerticalDirection: boolean,
distanceCalculationMethod: string
) => number;
onUtterText?: (text: string) => void;
}): void
```

### Config options

| Option | Type | Default | Description |
| ----------------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `debug` | `boolean` | `false` | Log navigation decisions to the browser console. |
| `visualDebug` | `boolean` | `false` | Draw a canvas overlay showing component bounding boxes and navigation paths. |
| `nativeMode` | `boolean` | `false` | **Deprecated.** Disable DOM key event listeners (for React Native). You must drive navigation manually. |
| `throttle` | `number` | `0` | Milliseconds to wait between processing repeated key presses. `0` means no throttle. |
| `throttleKeypresses` | `boolean` | `false` | When `true` and `throttle > 0`, throttle key repeat events while a key is held down. |
| `useGetBoundingClientRect` | `boolean` | `false` | Use `getBoundingClientRect()` instead of `offsetLeft/Top` for layout measurement. Use this when elements are CSS-transformed or scaled. |
| `shouldFocusDOMNode` | `boolean` | `false` | Call `HTMLElement.focus()` on the focused component's DOM node, enabling native browser focus behavior and accessibility. |
| `domNodeFocusOptions` | `FocusOptions` | `undefined` | Options passed to `HTMLElement.focus()` when `shouldFocusDOMNode` is `true`. |
| `shouldUseNativeEvents` | `boolean` | `false` | Do not call `preventDefault()` on key events, allowing the browser to handle them natively as well. |
| `rtl` | `boolean` | `false` | Enable right-to-left layout mode. Left and right navigation directions are swapped. |
| `distanceCalculationMethod` | `'center' \| 'edges' \| 'corners'` | `'corners'` | Algorithm used to calculate distance between components. See [Distance Calculation](../guides/distance-calculation.md). |
| `customDistanceCalculationFunction` | `function` | `undefined` | Override the secondary-axis distance calculation. See [Distance Calculation](../guides/distance-calculation.md). |
| Option | Type | Default | Description |
| ----------------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `debug` | `boolean` | `false` | Log navigation decisions to the browser console. |
| `visualDebug` | `boolean` | `false` | Draw a canvas overlay showing component bounding boxes and navigation paths. |
| `nativeMode` | `boolean` | `false` | **Deprecated.** Disable DOM key event listeners (for React Native). You must drive navigation manually. |
| `throttle` | `number` | `0` | Milliseconds to wait between processing repeated key presses. `0` means no throttle. |
| `throttleKeypresses` | `boolean` | `false` | When `true` and `throttle > 0`, throttle key repeat events while a key is held down. |
| `useGetBoundingClientRect` | `boolean` | `false` | Use `getBoundingClientRect()` instead of `offsetLeft/Top` for layout measurement. Use this when elements are CSS-transformed or scaled. |
| `shouldFocusDOMNode` | `boolean` | `false` | Call `HTMLElement.focus()` on the focused component's DOM node, enabling native browser focus behavior and accessibility. |
| `domNodeFocusOptions` | `FocusOptions` | `undefined` | Options passed to `HTMLElement.focus()` when `shouldFocusDOMNode` is `true`. |
| `shouldUseNativeEvents` | `boolean` | `false` | Do not call `preventDefault()` on key events, allowing the browser to handle them natively as well. |
| `rtl` | `boolean` | `false` | Enable right-to-left layout mode. Left and right navigation directions are swapped. |
| `distanceCalculationMethod` | `'center' \| 'edges' \| 'corners'` | `'corners'` | Algorithm used to calculate distance between components. See [Distance Calculation](../guides/distance-calculation.md). |
| `customDistanceCalculationFunction` | `function` | `undefined` | Override the secondary-axis distance calculation. See [Distance Calculation](../guides/distance-calculation.md). |
| `onUtterText` | `(text: string) => void` | `undefined` | Global callback invoked with a concatenated accessibility label string whenever focus changes. Wire this to your platform's Text-To-Speech engine. See the [Accessibility Labels](../guides/accessibility-labels.md) guide. |

### Example

Expand All @@ -83,7 +85,11 @@ init({
visualDebug: false,
distanceCalculationMethod: 'center',
throttle: 150,
throttleKeypresses: true
throttleKeypresses: true,
onUtterText: (text) => {
// Hand the string off to the platform's Text-To-Speech engine
platformTTS.speak(text);
}
});
```

Expand Down
Loading
Loading