fix(clip): clip mode keeps its range, its focus and its keys through the player's own moves

- A mark on the very end seeks a moment short of it; while a clip is marked the end of the item
  neither closes the player nor steps the queue, and Plex's auto-skip waits.
- Plex: Escape and Back peel what is over the picture (info, up-next, the countdown) before the
  clip; Back under a dialog is the dialog's; no range opens while the length is unknown.
- YouTube: the focus goes back to the scissors the user can see, else the player; the panel keeps
  its name, Loop and focus when the view is maximised or restored under it.
- A typed time stays in its field until taken, then shows where it landed; unreadable stays marked
  invalid; a decimal comma is read (ui/TimecodeField, the editor's fields too).
- RangeTrack: a drag is its pointer's, a cancelled drag reports final, an adornment sits beside the
  slider, a caller-chosen focus ring (amber on the clip strip). CropFrame: a drag is its pointer's.
- The editor's remove-cut is a real button at the cut's foot; a segment's keep toggle sits over the
  cut's hit area.
This commit is contained in:
2026-10-10 15:35:21 +02:00
parent b4bf71ea64
commit c489b60c8b
18 changed files with 607 additions and 121 deletions
+5 -1
View File
@@ -435,6 +435,8 @@ export function useAutoSkip(opts: {
videoRef: RefObject<HTMLVideoElement | null>;
/** Says a refused watched mark out loud; the translated string belongs to the component layer. */
reportWatchedError: (e: unknown) => void;
/** No countdown while true (a clip being marked); the Skip button still skips. */
suspended?: boolean;
}): {
activeMarker: PlexMarker | undefined;
skipProgress: number | null;
@@ -453,6 +455,7 @@ export function useAutoSkip(opts: {
go,
seekTo,
videoRef,
suspended = false,
} = opts;
const activeMarker: PlexMarker | undefined = markers?.find(
(m) => abs >= m.start_s && abs < m.end_s - 1,
@@ -513,7 +516,7 @@ export function useAutoSkip(opts: {
if (!m) return setSkipProgress(null);
const key = `${m.type}-${m.start_s}`;
const enabled = (m.type === "intro" ? autoSkipIntro : autoSkipCredits) && activeAuto;
if (!enabled || cancelledMarkerRef.current === key) return setSkipProgress(null);
if (!enabled || suspended || cancelledMarkerRef.current === key) return setSkipProgress(null);
if (autoSkipDelay <= 0) return doSkip(m); // immediate
let elapsed = 0;
setSkipProgress(0);
@@ -541,6 +544,7 @@ export function useAutoSkip(opts: {
autoSkipIntro,
autoSkipCredits,
autoSkipDelay,
suspended,
doSkip,
videoRef,
activeMarkerRef,