diff --git a/README.md b/README.md index 81bca58..272b150 100644 --- a/README.md +++ b/README.md @@ -208,19 +208,22 @@ Caption toggle button (`CC`). Renders only for video canvases that carry caption Synchronized transcript panel. Manages bidirectional scroll↔media sync via an XState-based `SyncController`. Provides `TranscriptContext` to compound children. -| Prop | Type | Default | Description | -| -------------------------- | ------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------- | -| `annotations` | `Annotation[]` | `[]` | Transcript segments (falls back to Root context annotations) | -| `syncDebounceMs` | `number` | `150` | Scroll debounce (ms) | -| `syncSettleMs` | `number` | `100` | Settle delay after scroll (ms) | -| `syncPriorityLockDuration` | `number` | `1000` | Priority lock duration (ms) | -| `ariaLabel` | `string` | `"Media transcript"` | Region label | -| `announceActiveSegment` | `boolean` | `true` | Screen reader announcements for active segment | -| `onActiveAnnotationChange` | `(annotation: Annotation \| null) => void` | — | Calls when active segment changes | -| `onSegmentClick` | `(annotation: Annotation, event: { preventDefault: () => void }) => void` | — | Intercept clicks; call `preventDefault()` synchronously to suppress default seek | -| `empty` | `Snippet` | — | Custom empty state | -| `loading` | `Snippet` | — | Custom loading state (replaces the built-in "Loading transcript…") | -| `class` | `string` | `""` | CSS class | +| Prop | Type | Default | Description | +| -------------------------- | ------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `annotations` | `Annotation[]` | `[]` | Transcript segments (falls back to Root context annotations) | +| `syncDebounceMs` | `number` | `150` | Scroll debounce (ms) | +| `syncSettleMs` | `number` | `100` | Settle delay after scroll (ms) | +| `syncPriorityLockDuration` | `number` | `1000` | Priority lock duration (ms) | +| `searchSeekBehavior` | `"change" \| "activate"` | `"change"` | `"change"` preserves legacy behavior — every selected search match seeks immediately. `"activate"` seeks only on explicit activation (Enter or "Go to match"); typing and previous/next only update highlights. **Recommended: `"activate"`.** | +| `scrollToSeek` | `boolean` | `true` | When `false`, transcript scrolling never seeks; a deliberate scroll instead enters Browsing (reading mode) so the next playback update can't pull the panel back. Independent of `readingMode`. Changeable at runtime. **Recommended: `false`.** | +| `readingMode` | `boolean` (bindable) | `false` | `false` is Following (playback may scroll the panel, and may seek if `scrollToSeek` is on); `true` is Browsing (the panel is detached — playback still updates the active-passage highlight, but never scrolls or seeks). Turning it back off scrolls the passage at the current time into view without seeking. Panel-local. | +| `ariaLabel` | `string` | `"Media transcript"` | Region label | +| `announceActiveSegment` | `boolean` | `true` | Screen reader announcements for active segment | +| `onActiveAnnotationChange` | `(annotation: Annotation \| null) => void` | — | Calls when active segment changes | +| `onSegmentClick` | `(annotation: Annotation, event: { preventDefault: () => void }) => void` | — | Intercept clicks; call `preventDefault()` synchronously to suppress default seek | +| `empty` | `Snippet` | — | Custom empty state | +| `loading` | `Snippet` | — | Custom loading state (replaces the built-in "Loading transcript…") | +| `class` | `string` | `""` | CSS class | **Children:** Use `TranscriptSearch` and `TranscriptSegments` as compound children. Without children, renders the empty state. @@ -228,17 +231,24 @@ While Root is fetching a VTT transcript (`annotations="auto"`, `transcriptStatus There is no matching `error` snippet: when the fetch fails (`transcriptStatus === "error"`) the panel falls back to the ordinary empty state. If you want a distinct failure affordance, read `transcriptStatus` from the Root children snippet or handle `onError` and render it yourself. +**Recommended configuration: `searchSeekBehavior="activate"` and `scrollToSeek={false}`.** Playback follows the transcript until the reader searches, scrolls, or explicitly enables reading mode — search and browsing no longer fight playback for control of the panel. With no new props, `Transcript` keeps today's exact behavior (search-driven and scroll-driven seeking). + +Whenever `children` are present, `Transcript` renders one "Follow along" switch (`role="switch"`, `aria-checked`, `data-following`) — turning it off enters Browsing — and, only while Browsing, a "Jump to current" button that returns to Following by scrolling the passage at the media's current time into view (never seeking). Both are bare: no appearance, styled by the host (the default `` element ships a styled pair — see "Use without Svelte" below). This single control replaces the former auto-scroll-pause button; there is no separate "pause auto-scroll but keep scroll-to-seek" state. + #### `IIIFPlayer.TranscriptSearch` Search input for filtering transcript segments. **Dual-mode:** reads from `TranscriptContext` inside `Transcript`, or accepts props directly when used standalone. -| Prop | Type | Default | Description | -| --------------- | ------------------------------------------------ | ---------------------- | --------------------- | -| `annotations` | `Annotation[]` | from context | Annotations to search | -| `placeholder` | `string` | `"Search transcript…"` | Input placeholder | -| `debounceMs` | `number` | `150` | Input debounce (ms) | -| `onmatchchange` | `(matches: Annotation[], index: number) => void` | from context | Match change callback | -| `class` | `string` | `""` | CSS class | +| Prop | Type | Default | Description | +| ----------------- | ------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `annotations` | `Annotation[]` | from context | Annotations to search | +| `placeholder` | `string` | `"Search transcript…"` | Input placeholder | +| `debounceMs` | `number` | `150` | Input debounce (ms) | +| `onmatchchange` | `(matches: Annotation[], index: number) => void` | from context | Debounced match-list/selection change callback | +| `onmatchactivate` | `(annotation: Annotation, index: number) => void` | from context | Explicit activation (Enter, or "Go to match" — shown when `Transcript`'s `searchSeekBehavior` is `"activate"` or while Browsing). Seeks once, regardless of `searchSeekBehavior`. | +| `onmatchnavigate` | `(annotation: Annotation, index: number) => void` | from context | Fired only on previous/next navigation (never typing or activation). The default scrolls the newly-selected match into view through the context; never seeks. | +| `onqueryinput` | `(value: string) => void` | from context | Synchronous input-intent callback, fired before the debounce — the default enters Browsing on a nonempty query in `"activate"` mode. | +| `class` | `string` | `""` | CSS class | #### `IIIFPlayer.TranscriptSegments` @@ -680,14 +690,17 @@ Unlike the IIFE, this entry keeps `svelte` external, so the `svelte` peer depend ### Attributes and properties -| Attribute | Property | Type | Default | Notes | -| -------------- | ------------- | --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `manifest-url` | `manifestUrl` | `string` | — | Required. **Set once, before the element is connected.** To load another manifest, replace the element. A later write does not reload, but it is not inert either: it reaches the player and changes what the next internal retry fetches and evicts from the manifest cache. Connecting without it is a non-fatal host `iiif-player-error`. Clearing it (removing the attribute, or setting it to `""`) unmounts the player and resets `playerRef` to `null`. | -| `canvas-index` | `canvasIndex` | `number` | `0` | Reactive and reflected: every canvas switch — yours or the player's own navigation — writes the live index back to the attribute, and both channels keep working afterwards. Validated: a value that is not a non-negative integer (`"abc"`) falls back to `0` and the attribute is repaired to `0`, and an index past the last canvas is ignored; either way you get a non-fatal host `iiif-player-error`, once per bad value per connection. **Removing** the attribute is not an error — it means "back to the default", canvas `0`, silently. | -| `initial-time` | `initialTime` | `number` | — | Seconds. Applies to the first canvas only, and only on the element's first connection (see the reconnection note under Events). Validated: a value that is not a non-negative finite number is ignored with a non-fatal host `iiif-player-error`, and does not consume the once-per-element application. | -| `autoplay` | `autoplay` | `boolean` | `false` | Browsers may block it; a blocked autoplay is a non-fatal `iiif-player-error` (`source: "playback"`). | -| `label` | `label` | `string` | localized generic name | Host-settable accessible name for the element's top-level region (`role="region"`) — set it when a page has more than one player so assistive technology can tell them apart. | -| `crossorigin` | `crossorigin` | `string` | — | Forwarded to the underlying `