# SeekIndicator

A temporary visual indicator for keyboard and gesture seeking

## Import

```tsx
import { SeekIndicator } from '@videojs/react';
```

## Anatomy

```tsx
<SeekIndicator.Root>
  <SeekIndicator.Value />
</SeekIndicator.Root>
```

## Behavior

`SeekIndicator` displays feedback for seek actions emitted by a [`Hotkey`](https://videojs.org/docs/framework/react/reference/components/hotkey) or [`Gesture`](https://videojs.org/docs/framework/react/reference/components/gesture) in the same player `Container`. It does not react to current-time changes or seek commands invoked directly through the player store.

| Action | Direction | Value |
| --- | --- | --- |
| `seekStep` | The sign of `value` determines `forward` or `backward` | The absolute number of seconds, such as `10s` |
| `seekToPercent` | The target percentage is compared with the current time | The formatted current time when the action occurred |

Rapid `seekStep` actions in the same direction accumulate while the indicator is open. For example, two 10-second forward actions display `20s`. Changing direction or using `seekToPercent` starts a new display. During a rapid sequence, another full step is not added when it would pass the start or duration boundary.

For `seekToPercent`, pass a percentage from 0 to 100 as `value`. A `Hotkey` with `keys="0-9"` can omit `value`; the pressed digit maps to 0%, 10%, and so on through 90%.

The indicator closes after `closeDelay`, which defaults to 800 milliseconds.

`SeekIndicator.Root` stops rendering after its close transition.

## Styling

| Attribute | Values | Description |
| --- | --- | --- |
| `data-open` | Present / absent | Present while the indicator is open |
| `data-direction` | `"forward"` \| `"backward"` | Direction of the handled seek |
| `data-starting-style` | Present / absent | Present during the open transition |
| `data-ending-style` | Present / absent | Present during the close transition |

Position the root from `data-direction` and use the transition attributes for entry and exit styles.

React renders standard DOM elements. Add a `className` to the Root:

```css
.seek-indicator[data-direction="backward"] {
  left: 1rem;
}

.seek-indicator[data-direction="forward"] {
  right: 1rem;
}

.seek-indicator[data-starting-style],
.seek-indicator[data-ending-style] {
  opacity: 0;
}
```

## Accessibility

`SeekIndicator` is visual feedback and does not create a live region. Keep the same seek operation available through keyboard-operable controls, and pair it with [`StatusAnnouncer`](https://videojs.org/docs/framework/react/reference/components/status-announcer) when changes should be announced to screen readers. Do not make `SeekIndicator.Value` a live region because rapid seek input would produce repeated announcements.

## Examples

### Basic Usage

Focus the player, then press the left or right arrow key to seek by ten seconds. Press a digit from 0 through 9 to seek to a percentage of the duration.

**App.tsx**

```tsx
import { Container, createPlayer, Hotkey, SeekIndicator } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';

import './BasicUsage.css';

const { Player } = createPlayer({ features: videoFeatures });

export default function BasicUsage() {
  return (
    <Player>
      <Container className="react-seek-indicator-basic" tabIndex={0}>
        <Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
        <p className="react-seek-indicator-basic__instructions">
          Focus the player · ←/→: seek 10s · 0–9: seek to percent
        </p>
        <SeekIndicator.Root className="react-seek-indicator-basic__indicator" aria-hidden="true">
          <SeekIndicator.Value className="react-seek-indicator-basic__value" />
        </SeekIndicator.Root>
        <Hotkey keys="ArrowLeft" action="seekStep" value={-10} />
        <Hotkey keys="ArrowRight" action="seekStep" value={10} />
        <Hotkey keys="0-9" action="seekToPercent" />
      </Container>
    </Player>
  );
}
```

**App.css**

```css
.react-seek-indicator-basic {
  position: relative;
}

.react-seek-indicator-basic:focus-visible {
  outline: 3px solid #60a5fa;
  outline-offset: 2px;
}

.react-seek-indicator-basic video {
  display: block;
  width: 100%;
}

.react-seek-indicator-basic__instructions {
  position: absolute;
  top: 10px;
  left: 50%;
  padding: 6px 10px;
  margin: 0;
  color: white;
  text-align: center;
  background: rgb(0 0 0 / 70%);
  border-radius: 4px;
  translate: -50%;
}

.react-seek-indicator-basic__indicator {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  min-width: 72px;
  padding: 16px;
  color: white;
  pointer-events: none;
  background: rgb(0 0 0 / 72%);
  border-radius: 9999px;
  place-items: center;
  transform: translate(-50%, -50%);
  transition:
    opacity 160ms ease-in-out,
    scale 160ms ease-in-out;
}

.react-seek-indicator-basic__indicator::before {
  font-size: 24px;
  line-height: 1;
  content: "↔";
}

.react-seek-indicator-basic__indicator[data-direction="backward"] {
  right: auto;
  left: 16px;
  transform: translateY(-50%);
}

.react-seek-indicator-basic__indicator[data-direction="backward"]::before {
  content: "↶";
}

.react-seek-indicator-basic__indicator[data-direction="forward"] {
  right: 16px;
  left: auto;
  transform: translateY(-50%);
}

.react-seek-indicator-basic__indicator[data-direction="forward"]::before {
  content: "↷";
}

.react-seek-indicator-basic__indicator[data-starting-style],
.react-seek-indicator-basic__indicator[data-ending-style] {
  opacity: 0;
  scale: 0.85;
}

.react-seek-indicator-basic__value {
  font-variant-numeric: tabular-nums;
}
```

## API Reference

### Root

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `closeDelay` | `number` | — | Delay in milliseconds before the indicator closes. |

#### State

State is accessible via the `render`, `className`, and `style` props.

| Property | Type | Description |
| --- | --- | --- |
| `transitionStarting` | `boolean` | Whether the open transition is in progress. |
| `transitionEnding` | `boolean` | Whether the close transition is in progress. |
| `open` | `boolean` | Whether the indicator is open. |
| `generation` | `number` | Increments each time a seek input action updates the indicator. |
| `direction` | `'forward' \| 'backward' \| null` | Direction of the seek, or `null` when the target does not change the current time. |
| `count` | `number` | Number of same-direction seek steps accumulated in the current display. |
| `seekTotal` | `number` | Absolute number of seconds accumulated from seek-step actions. |
| `value` | `string \| null` | Accumulated seek-step label, or `null` for percentage seeks. |
| `currentTime` | `string` | Formatted current time captured when the input action occurred. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-open` | — | Present while the indicator is open. |
| `data-direction` | `'forward' \| 'backward' \| null` | Direction of the seek as `"forward"` or `"backward"`. |
| `data-starting-style` | — | Present during the open transition. |
| `data-ending-style` | — | Present during the close transition. |

### Value

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: SeekIndicatorState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: SeekIndicatorState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: SeekIndicatorState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

---

React documentation: https://videojs.org/docs/framework/react/llms.txt
All documentation: https://videojs.org/llms.txt
