@pixerate/editor-react 0.49.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -192,6 +192,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
192
192
  delta: (e.clientX - d.x) * 1e3 / px,
193
193
  track: d.mode === "move" ? trackAt(d.clip, e.clientY) : void 0,
194
194
  ripple: e.altKey,
195
+ placement: e.altKey ? "insert" : e.shiftKey ? "overwrite" : "refuse",
195
196
  snap: (t) => controller.snap(t, { exclude: [d.clip] }).time
196
197
  });
197
198
  const update = (next) => {
@@ -233,7 +234,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
233
234
  return;
234
235
  }
235
236
  const plan = live.current.planFor(d, ev);
236
- if (plan?.edit) controller.apply(plan.edit, d.mode === "move" ? "Move clip" : ev.altKey ? "Ripple trim" : "Trim clip");
237
+ if (plan?.edit) controller.apply(plan.edit, (0, import_video_editor4.videoDragLabel)(plan.edit));
237
238
  };
238
239
  const cancel = () => {
239
240
  stop();
@@ -458,7 +459,7 @@ function Row({ label, name, children }) {
458
459
  }
459
460
  function ClipInspector({ controller, className = "" }) {
460
461
  const state = useVideoEditorState(controller);
461
- const { project, selection, readOnly } = state;
462
+ const { project, selection, readOnly, time } = state;
462
463
  if (selection.length !== 1)
463
464
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: `text-xs text-muted-foreground ${className}`, "data-clip-inspector": "", children: selection.length ? `${selection.length} clips selected. Select one clip to edit it.` : "Select a clip on the timeline to edit it." });
464
465
  const at = (0, import_video_editor5.findVideoClip)(project, selection[0]);
@@ -493,11 +494,48 @@ function ClipInspector({ controller, className = "" }) {
493
494
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Name", name: "name", children: text("name", "Name") }),
494
495
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Start", name: "start", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tabular-nums text-xs", children: (0, import_video_editor5.formatVideoSeconds)(clip.start) }) }),
495
496
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Duration", name: "duration", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tabular-nums text-xs", children: (0, import_video_editor5.formatVideoSeconds)(clip.duration) }) }),
496
- clip.type !== "media" || project.assets.find((a) => a.id === clip.asset)?.kind !== "audio" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Opacity", name: "opacity", children: number("opacity", "Opacity", { min: 0, max: 1, step: 0.05 }) }) : null,
497
+ (0, import_video_editor5.videoKeyframeRows)(project, clip.id, time).map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: row.label, name: row.property, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "flex min-w-0 items-center gap-1", "data-keyframe-row": row.property, children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
499
+ Commit,
500
+ {
501
+ type: "number",
502
+ value: row.keyed || (row.property === "opacity" ? clip.opacity : row.property === "volume" ? clip.volume : clip.transform?.[row.property]) !== void 0 ? String(row.value) : "",
503
+ placeholder: String(row.value),
504
+ "aria-label": row.label,
505
+ disabled,
506
+ min: row.min,
507
+ max: row.max,
508
+ step: row.step,
509
+ onCommit: (raw) => {
510
+ const n = Number(raw);
511
+ if (raw.trim() && !Number.isFinite(n)) return;
512
+ const edit = (0, import_video_editor5.videoKeyframeValueEdit)(project, clip.id, row.property, time, raw.trim() ? n : void 0);
513
+ if (edit) controller.apply(edit, row.keyed ? `Keyframe ${row.label.toLowerCase()}` : `Change ${row.label.toLowerCase()}`);
514
+ }
515
+ }
516
+ ),
517
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
518
+ "button",
519
+ {
520
+ type: "button",
521
+ className: `shrink-0 rounded px-1 text-xs disabled:opacity-50 ${row.onKey ? "text-amber-500" : row.keyed ? "text-foreground" : "text-muted-foreground"}`,
522
+ "aria-label": `${row.onKey ? "Remove" : "Add"} ${row.label.toLowerCase()} keyframe at playhead`,
523
+ "aria-pressed": row.onKey,
524
+ title: row.keyed ? `${row.keys} key${row.keys === 1 ? "" : "s"}; ${row.onKey ? "remove the one" : "add one"} at the playhead` : "Animate: add a key at the playhead",
525
+ disabled,
526
+ "data-keyframe-toggle": row.property,
527
+ onClick: () => {
528
+ const edit = (0, import_video_editor5.videoKeyframeToggleEdit)(project, clip.id, row.property, time);
529
+ if (edit) controller.apply(edit, `${row.onKey ? "Remove" : "Add"} ${row.label.toLowerCase()} keyframe`);
530
+ },
531
+ children: row.onKey ? "\u25C6" : "\u25C7"
532
+ }
533
+ ),
534
+ row.keyed && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-[10px] tabular-nums text-muted-foreground", "data-keyframe-count": row.property, children: row.keys })
535
+ ] }) }, row.property)),
497
536
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Fade in (ms)", name: "fadeIn", children: number("fadeIn", "Fade in", { min: 0, step: 100 }) }),
498
537
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Fade out (ms)", name: "fadeOut", children: number("fadeOut", "Fade out", { min: 0, step: 100 }) }),
499
538
  clip.type === "media" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
500
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Volume", name: "volume", children: number("volume", "Volume", { min: 0, step: 0.1 }) }),
501
539
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Speed", name: "speed", children: number("speed", "Speed", { min: 0.1, step: 0.25 }) }),
502
540
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Row, { label: "Muted", name: "muted", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", className: "justify-self-start", checked: !!clip.muted, disabled, "aria-label": "Muted", onChange: (e) => set("muted", e.target.checked || void 0, e.target.checked ? "Mute clip" : "Unmute clip") }) })
503
541
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/video-editor/index.ts","../../src/video-editor/useVideoEditor.ts","../../src/video-editor/VideoPreview.tsx","../../src/video-editor/VideoTransport.tsx","../../src/video-editor/VideoTimeline.tsx","../../src/video-editor/ClipInspector.tsx","../../src/video-editor/MediaBin.tsx","../../src/video-editor/VideoEditor.tsx"],"sourcesContent":["export * from './useVideoEditor';\nexport * from './VideoPreview';\nexport * from './VideoTransport';\nexport * from './VideoTimeline';\nexport * from './ClipInspector';\nexport * from './MediaBin';\nexport * from './VideoEditor';\n","import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n VideoEditorController,\n createVideoEditorToolProvider,\n type VideoEditResult,\n type VideoEditorOptions,\n type VideoEditorState,\n type VideoEditorToolProviderOptions,\n} from '@pixerate/editor/video-editor';\nimport type { ToolRegistry } from '@pixerate/editor/tools';\n\n/** A `VideoEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useVideoEditorController(options: VideoEditorOptions = {}): VideoEditorController {\n const ref = useRef<VideoEditorController | null>(null);\n if (!ref.current) ref.current = new VideoEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change, including each played frame). */\nexport function useVideoEditorState(controller: VideoEditorController): VideoEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseVideoEditorResult {\n controller: VideoEditorController;\n state: VideoEditorState;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n /** Moves the playhead (ms). */\n seek: (time: number) => void;\n /** Moves the playhead by whole frames (negative steps back), pausing. */\n step: (frames: number) => void;\n undo: () => void;\n redo: () => void;\n /** Splits the selected clips (or every clip) under the playhead, as one undo step. */\n splitAtPlayhead: () => VideoEditResult;\n /** Deletes the selected clips; `ripple` closes the gaps they leave. */\n deleteSelection: (options?: { ripple?: boolean }) => VideoEditResult;\n}\n\n/**\n * Subscribes to a video editor controller and exposes its state with bound actions.\n * Pass options instead of a controller to create one for the component's lifetime.\n */\nexport function useVideoEditor(controllerOrOptions: VideoEditorController | VideoEditorOptions = {}): UseVideoEditorResult {\n const own = useRef<VideoEditorController | null>(null);\n let controller: VideoEditorController;\n if (controllerOrOptions instanceof VideoEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new VideoEditorController(controllerOrOptions);\n controller = own.current;\n }\n useEffect(() => () => own.current?.destroy(), []);\n const state = useVideoEditorState(controller);\n const actions = useMemo(\n () => ({\n play: () => controller.play(),\n pause: () => controller.pause(),\n toggle: () => controller.toggle(),\n seek: (time: number) => controller.seek(time),\n step: (frames: number) => controller.step(frames),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n splitAtPlayhead: () => controller.splitAtPlayhead(),\n deleteSelection: (options?: { ripple?: boolean }) => controller.deleteSelection(options),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/**\n * Registers the video editor's agent tools (`get_video_project`, `add_video_clip`,\n * `trim_video_clip`, …) in `registry` while mounted.\n */\nexport function useVideoEditorTools(\n registry: ToolRegistry | null | undefined,\n controller: VideoEditorController,\n options: VideoEditorToolProviderOptions = {},\n): void {\n const { id, namespace, readOnly, rasterize } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createVideoEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));\n }, [registry, controller, id, namespace, readOnly, rasterize]);\n}\n","import React, { useEffect, useRef } from 'react';\nimport { VideoPreviewEngine, type VideoEditorController, type VideoPreviewOptions } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoPreviewProps {\n controller: VideoEditorController;\n /** Canvas pixels per frame pixel (default: fits the displayed width at the device pixel ratio, at most 1). */\n resolution?: number;\n /** Called after each frame is drawn. */\n onFrame?: VideoPreviewOptions['onFrame'];\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Plays the controller's project on a canvas the shape of its frame: a `VideoPreviewEngine` keeps\n * one `<video>` / `<audio>` element per playing clip in step with the playhead and draws each frame.\n * The engine is created when the canvas mounts and destroyed on unmount or when the controller changes.\n */\nexport function VideoPreview({ controller, resolution, onFrame, className = '', style }: VideoPreviewProps) {\n const { project } = useVideoEditorState(controller);\n const canvas = useRef<HTMLCanvasElement>(null);\n const frameRef = useRef(onFrame);\n frameRef.current = onFrame;\n useEffect(() => {\n const el = canvas.current;\n if (!el) return;\n const engine = new VideoPreviewEngine(controller, el, { resolution, onFrame: (frame) => frameRef.current?.(frame) });\n return () => engine.destroy();\n }, [controller, resolution]);\n return (\n <canvas\n ref={canvas}\n className={className}\n style={{ display: 'block', width: '100%', aspectRatio: `${project.width} / ${project.height}`, background: project.background ?? '#000', ...style }}\n role=\"img\"\n aria-label={`Preview of ${project.name ?? 'the video'}`}\n data-video-preview=\"\"\n />\n );\n}\n","import React from 'react';\nimport { formatVideoTimecode, type VideoEditorController } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTransportProps {\n controller: VideoEditorController;\n /** Show the editing buttons (split, delete, ripple delete, undo, redo; default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * Play / pause, a frame back and forward, the playhead and total length as timecode, and the\n * edits that work on the playhead and the selection: split, delete, ripple delete, undo and redo.\n */\nexport function VideoTransport({ controller, editable = true, className = '' }: VideoTransportProps) {\n const state = useVideoEditorState(controller);\n const { time, duration, playing, project, selection, readOnly } = state;\n const canEdit = editable && !readOnly;\n return (\n <div className={`flex flex-wrap items-center gap-1.5 text-xs ${className}`} data-video-transport=\"\">\n <button type=\"button\" className={btn} onClick={() => controller.step(-1)} aria-label=\"Previous frame\" title=\"Previous frame (←)\" data-step-back=\"\">\n ◀|\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.toggle()} aria-label={playing ? 'Pause' : 'Play'} title=\"Play / pause (Space)\" disabled={duration <= 0} data-play=\"\">\n {playing ? 'Pause' : 'Play'}\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.step(1)} aria-label=\"Next frame\" title=\"Next frame (→)\" data-step-forward=\"\">\n |▶\n </button>\n <span className=\"px-1 tabular-nums text-muted-foreground\" data-timecode=\"\">\n {formatVideoTimecode(time, project.fps)} / {formatVideoTimecode(duration, project.fps)}\n </span>\n {editable && (\n <>\n <span className=\"mx-1 h-4 w-px bg-border\" />\n <button type=\"button\" className={btn} onClick={() => controller.splitAtPlayhead()} disabled={!canEdit} title=\"Split at the playhead (S)\" data-split=\"\">\n Split\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.deleteSelection()} disabled={!canEdit || !selection.length} title=\"Delete the selected clips (Delete)\" data-delete=\"\">\n Delete\n </button>\n <button\n type=\"button\"\n className={btn}\n onClick={() => controller.deleteSelection({ ripple: true })}\n disabled={!canEdit || !selection.length}\n title=\"Delete the selected clips and close the gaps (Shift+Delete)\"\n data-ripple-delete=\"\"\n >\n Ripple delete\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.undo()} disabled={!canEdit || !state.canUndo} title={state.canUndo ? `Undo ${state.undoLabel}` : 'Nothing to undo'} data-undo=\"\">\n Undo\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.redo()} disabled={!canEdit || !state.canRedo} title={state.canRedo ? `Redo ${state.redoLabel}` : 'Nothing to redo'} data-redo=\"\">\n Redo\n </button>\n </>\n )}\n </div>\n );\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport {\n planVideoClipDrag,\n videoAssetMap,\n videoClipLabel,\n videoClipTint,\n videoDropTracks,\n videoRulerTicks,\n formatVideoSeconds,\n type VideoClip,\n type VideoClipDragMode,\n type VideoClipDragPlan,\n type VideoEditorController,\n type VideoTrack,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTimelineProps {\n controller: VideoEditorController;\n /** Zoom: pixels per second of timeline. With `onPxPerSecondChange` the host owns it; otherwise it is the starting zoom (default 100). */\n pxPerSecond?: number;\n /** Called when the zoom control changes. Without it the timeline keeps its own zoom and shows a zoom slider. */\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Allow selecting, dragging, trimming and the track toggles (default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst HEADER = 150;\nconst RULER = 24;\nconst ROW = 44;\nconst HANDLE = 6;\nconst DRAG_THRESHOLD = 3;\nconst MIN_ZOOM = 10;\nconst MAX_ZOOM = 400;\n\ninterface Drag {\n clip: string;\n mode: VideoClipDragMode;\n x: number;\n pointer: number;\n moved: boolean;\n plan: VideoClipDragPlan | null;\n}\n\nconst isField = (el: EventTarget | null) => el instanceof HTMLElement && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);\n\n/**\n * The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it\n * to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.\n * Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another\n * track of the same kind too), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,\n * markers, the playhead and frames. Every edit is one undo step; refused edits show why.\n *\n * Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes\n * the selection (Shift to ripple), Cmd/Ctrl+Z undoes, Shift+Cmd/Ctrl+Z redoes.\n */\nexport function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable = true, className = '' }: VideoTimelineProps) {\n const state = useVideoEditorState(controller);\n const { project, duration, time, selection, readOnly, error } = state;\n const [ownZoom, setOwnZoom] = useState(pxPerSecond ?? 100);\n const px = onPxPerSecondChange ? (pxPerSecond ?? 100) : ownZoom;\n const canEdit = editable && !readOnly;\n const [drag, setDrag] = useState<Drag | null>(null);\n const dragRef = useRef<Drag | null>(null);\n const rows = useRef(new Map<string, HTMLDivElement>());\n const ruler = useRef<HTMLDivElement>(null);\n const assets = videoAssetMap(project);\n const msToPx = (ms: number) => (ms * px) / 1000;\n const span = Math.max(duration + 5000, 10000);\n const width = msToPx(span);\n const ticks = videoRulerTicks(span, px, project.fps);\n\n const seekAt = (clientX: number) => {\n const box = ruler.current!.getBoundingClientRect();\n controller.seek(Math.max(0, ((clientX - box.left) * 1000) / px));\n };\n\n const trackAt = (clipId: string, clientY: number): string | undefined => {\n const allowed = videoDropTracks(project, clipId);\n for (const [id, el] of rows.current) {\n const box = el.getBoundingClientRect();\n if (clientY >= box.top && clientY < box.bottom && allowed.includes(id)) return id;\n }\n return undefined;\n };\n\n const planFor = (d: Drag, e: { clientX: number; clientY: number; altKey: boolean }) =>\n planVideoClipDrag(project, {\n clip: d.clip,\n mode: d.mode,\n delta: ((e.clientX - d.x) * 1000) / px,\n track: d.mode === 'move' ? trackAt(d.clip, e.clientY) : undefined,\n ripple: e.altKey,\n snap: (t) => controller.snap(t, { exclude: [d.clip] }).time,\n });\n\n const update = (next: Drag | null) => {\n dragRef.current = next;\n setDrag(next);\n };\n\n // The latest render's values, for the window listeners of a drag in progress.\n const live = useRef({ planFor, selection });\n live.current = { planFor, selection };\n const stopDrag = useRef<(() => void) | null>(null);\n useEffect(() => () => stopDrag.current?.(), []);\n\n const onClipPointerDown = (e: React.PointerEvent<HTMLDivElement>, clip: VideoClip, track: VideoTrack) => {\n if (e.button !== undefined && e.button !== 0) return;\n e.stopPropagation();\n if (!editable) return;\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n const handle = (e.target as HTMLElement).closest('[data-handle]')?.getAttribute('data-handle');\n const mode: VideoClipDragMode = handle === 'start' ? 'trim-start' : handle === 'end' ? 'trim-end' : 'move';\n const fixed = !canEdit || !!track.locked;\n stopDrag.current?.();\n update({ clip: clip.id, mode, x: e.clientX, pointer: e.pointerId, moved: false, plan: null });\n // Follow the pointer on the window: the block may move to another track's row (a new element) mid-drag.\n const move = (ev: PointerEvent) => {\n const d = dragRef.current;\n if (!d || fixed) return;\n if (!d.moved && Math.abs(ev.clientX - d.x) < DRAG_THRESHOLD) return;\n if (!d.moved && !live.current.selection.includes(d.clip)) controller.select([d.clip]);\n update({ ...d, moved: true, plan: live.current.planFor(d, ev) });\n };\n const up = (ev: PointerEvent) => {\n stop();\n const d = dragRef.current;\n if (!d) return;\n update(null);\n if (!d.moved) {\n if (ev.shiftKey || ev.metaKey || ev.ctrlKey) controller.select([d.clip], { add: true });\n else controller.select([d.clip]);\n return;\n }\n const plan = live.current.planFor(d, ev);\n if (plan?.edit) controller.apply(plan.edit, d.mode === 'move' ? 'Move clip' : ev.altKey ? 'Ripple trim' : 'Trim clip');\n };\n const cancel = () => {\n stop();\n update(null);\n };\n const stop = () => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n window.removeEventListener('pointercancel', cancel);\n stopDrag.current = null;\n };\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n window.addEventListener('pointercancel', cancel);\n stopDrag.current = stop;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (isField(e.target)) return;\n const mod = e.metaKey || e.ctrlKey;\n const key = e.key.toLowerCase();\n if (e.key === ' ') {\n if ((e.target as HTMLElement).tagName === 'BUTTON') return;\n controller.toggle();\n } else if (e.key === 'ArrowLeft' && !mod) controller.step(-1);\n else if (e.key === 'ArrowRight' && !mod) controller.step(1);\n else if (e.key === 'Escape') controller.select(null);\n else if (!canEdit) return;\n else if (key === 's' && !mod) controller.splitAtPlayhead();\n else if ((e.key === 'Delete' || e.key === 'Backspace') && !mod) controller.deleteSelection({ ripple: e.shiftKey });\n else if (key === 'z' && mod) {\n if (e.shiftKey) controller.redo();\n else controller.undo();\n } else return;\n e.preventDefault();\n };\n\n // Where each clip is drawn: the dragged one follows the plan (possibly on another track).\n const placed = project.tracks.map((track) => ({\n track,\n clips: project.tracks.flatMap((t) =>\n t.clips\n .map((clip) => {\n const plan = drag?.moved && drag.clip === clip.id ? drag.plan : null;\n const on = plan ? plan.track : t.id;\n return on === track.id ? { clip, home: t, start: plan ? plan.start : clip.start, duration: plan ? plan.duration : clip.duration, dragging: !!plan } : null;\n })\n .filter((x): x is NonNullable<typeof x> => x !== null),\n ),\n }));\n\n const toggle = (track: VideoTrack, field: 'muted' | 'hidden' | 'locked', label: string) => (\n <button\n type=\"button\"\n className={`rounded border px-1 text-[10px] leading-4 ${track[field] ? 'border-rose-500 bg-rose-500/15 text-rose-600' : 'border-border text-muted-foreground'} disabled:opacity-40`}\n aria-pressed={!!track[field]}\n aria-label={`${label} ${track.name ?? track.id}`}\n title={label}\n disabled={!canEdit}\n data-track-toggle={field}\n onClick={() => controller.apply({ op: 'update_track', track: track.id, set: { [field]: !track[field] } }, `${track[field] ? 'Un' : ''}${field === 'muted' ? 'mute' : field === 'hidden' ? 'hide' : 'lock'} track`)}\n >\n {field === 'muted' ? 'M' : field === 'hidden' ? 'H' : 'L'}\n </button>\n );\n\n return (\n <div className={`flex flex-col gap-1 ${className}`} data-video-timeline-wrapper=\"\">\n {!onPxPerSecondChange && (\n <label className=\"flex items-center gap-2 self-end text-xs text-muted-foreground\">\n Zoom\n <input type=\"range\" min={MIN_ZOOM} max={MAX_ZOOM} step={5} value={ownZoom} aria-label=\"Timeline zoom\" onChange={(e) => setOwnZoom(Number(e.target.value))} data-zoom=\"\" />\n </label>\n )}\n <div\n className=\"relative overflow-x-auto rounded border border-border outline-none focus-visible:ring-2 focus-visible:ring-rose-500/50\"\n tabIndex={0}\n role=\"group\"\n aria-label=\"Video timeline\"\n onKeyDown={onKeyDown}\n data-video-timeline=\"\"\n >\n <div className=\"relative\" style={{ width: HEADER + width }}>\n <div className=\"flex\" style={{ height: RULER }}>\n <div className=\"sticky left-0 z-20 shrink-0 border-b border-r border-border bg-background\" style={{ width: HEADER }} />\n <div\n ref={ruler}\n className=\"relative shrink-0 cursor-ew-resize select-none touch-none border-b border-border text-[10px] text-muted-foreground\"\n style={{ width }}\n role=\"slider\"\n aria-label=\"Playhead\"\n aria-valuemin={0}\n aria-valuemax={duration}\n aria-valuenow={Math.round(time)}\n data-ruler=\"\"\n onPointerDown={(e) => {\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n seekAt(e.clientX);\n }}\n onPointerMove={(e) => {\n if (e.buttons & 1) seekAt(e.clientX);\n }}\n >\n {ticks.map((t) => (\n <div key={t.time} className={`absolute bottom-0 w-px ${t.major ? 'h-2.5 bg-muted-foreground/70' : 'h-1 bg-muted-foreground/40'}`} style={{ left: t.x }} data-tick={t.time}>\n {t.label && <span className=\"absolute -top-3.5 left-1 whitespace-nowrap tabular-nums\">{t.label}</span>}\n </div>\n ))}\n {(project.markers ?? []).map((m) => (\n <div\n key={m.id}\n className=\"absolute top-0 h-2.5 w-2.5 -translate-x-1/2 rotate-45\"\n style={{ left: msToPx(m.time), background: m.color ?? '#f59e0b' }}\n title={`${m.label ?? m.id} at ${formatVideoSeconds(m.time)}`}\n data-marker={m.id}\n />\n ))}\n </div>\n </div>\n {placed.map(({ track, clips }) => (\n <div key={track.id} className=\"flex border-b border-border last:border-b-0\" style={{ height: ROW }} data-track={track.id}>\n <div className=\"sticky left-0 z-20 flex shrink-0 flex-col justify-center gap-0.5 border-r border-border bg-background px-2 text-xs\" style={{ width: HEADER }} data-track-header=\"\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate font-medium\">{track.name ?? track.id}</span>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-track-kind=\"\">\n {track.kind}\n </span>\n </div>\n {editable && (\n <div className=\"flex gap-1\">\n {toggle(track, 'muted', 'Mute')}\n {track.kind === 'video' && toggle(track, 'hidden', 'Hide')}\n {toggle(track, 'locked', 'Lock')}\n </div>\n )}\n </div>\n <div\n ref={(el) => {\n if (el) rows.current.set(track.id, el);\n else rows.current.delete(track.id);\n }}\n className={`relative shrink-0 ${track.hidden ? 'opacity-50' : ''} ${track.locked ? 'bg-muted/40' : ''}`}\n style={{ width }}\n data-track-lane={track.id}\n onPointerDown={(e) => {\n if (e.target === e.currentTarget && editable) controller.select(null);\n }}\n >\n {clips.map(({ clip, home, start, duration: length, dragging }) => {\n const asset = clip.type === 'media' ? assets.get(clip.asset) : undefined;\n const selected = selection.includes(clip.id);\n const label = videoClipLabel(clip, asset);\n return (\n <div\n key={clip.id}\n className={`absolute top-1 bottom-1 select-none touch-none overflow-hidden rounded text-[11px] text-white ${canEdit && !home.locked ? 'cursor-grab' : 'cursor-default'} ${dragging ? 'opacity-80 shadow-lg' : ''}`}\n style={{\n left: msToPx(start),\n width: Math.max(2, msToPx(length)),\n background: videoClipTint(clip, asset),\n outline: selected ? '2px solid #f43f5e' : undefined,\n outlineOffset: selected ? 1 : undefined,\n zIndex: dragging ? 10 : selected ? 2 : 1,\n }}\n title={`${label} · ${formatVideoSeconds(start)}–${formatVideoSeconds(start + length)}`}\n data-clip={clip.id}\n data-selected={selected ? '' : undefined}\n data-dragging={dragging ? '' : undefined}\n aria-selected={selected}\n onPointerDown={(e) => onClipPointerDown(e, clip, home)}\n >\n <span className=\"pointer-events-none block truncate px-1.5 py-0.5 leading-4\">{label}</span>\n {clip.transition && (\n <span className=\"pointer-events-none absolute left-1 bottom-0.5 rounded bg-black/40 px-1 text-[9px] leading-3\" title={`${clip.transition.type} ${clip.transition.duration} ms`} data-transition={clip.transition.type}>\n ⧓ {clip.transition.type}\n </span>\n )}\n {canEdit && !home.locked && (\n <>\n <div className=\"absolute inset-y-0 left-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"start\" aria-hidden=\"true\" />\n <div className=\"absolute inset-y-0 right-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"end\" aria-hidden=\"true\" />\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n ))}\n <div className=\"pointer-events-none absolute top-0 bottom-0 w-0.5 -translate-x-1/2 bg-rose-500\" style={{ left: HEADER + msToPx(time), zIndex: 15 }} data-playhead=\"\" />\n </div>\n </div>\n {error && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-edit-error=\"\">\n {error}\n </p>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport {\n findVideoClip,\n formatVideoSeconds,\n VIDEO_TRANSITION_TYPES,\n type VideoClip,\n type VideoEditorController,\n type VideoTransitionType,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface ClipInspectorProps {\n controller: VideoEditorController;\n className?: string;\n}\n\nconst field = 'w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs disabled:opacity-50';\nconst DEFAULT_TRANSITION = 500;\n\n/** An input (or textarea) that commits on blur or Enter, and resets when its value changes from outside. */\nfunction Commit({\n value,\n onCommit,\n multiline,\n ...rest\n}: { value: string; onCommit: (raw: string) => void; multiline?: boolean } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue'>) {\n const commit = (raw: string) => {\n if (raw !== value) onCommit(raw);\n };\n if (multiline)\n return (\n <textarea\n key={value}\n className={field}\n rows={2}\n defaultValue={value}\n disabled={rest.disabled}\n aria-label={rest['aria-label']}\n onBlur={(e) => commit(e.currentTarget.value)}\n />\n );\n return <input key={value} className={field} {...rest} defaultValue={value} onKeyDown={(e) => e.key === 'Enter' && e.currentTarget.blur()} onBlur={(e) => commit(e.currentTarget.value)} />;\n}\n\nfunction Row({ label, name, children }: { label: string; name: string; children: React.ReactNode }) {\n return (\n <label className=\"grid grid-cols-[5.5rem_minmax(0,1fr)] items-center gap-2 text-xs\" data-field={name}>\n <span className=\"text-muted-foreground\">{label}</span>\n {children}\n </label>\n );\n}\n\n/**\n * Edits the one selected clip: its name, opacity and fades; volume, speed and mute for media;\n * text, colour, size and background for titles; colour for colour clips; and the transition into\n * it. Each change is one undo step (`update_clip` / `set_transition`), committed on blur or change.\n */\nexport function ClipInspector({ controller, className = '' }: ClipInspectorProps) {\n const state = useVideoEditorState(controller);\n const { project, selection, readOnly } = state;\n if (selection.length !== 1)\n return (\n <p className={`text-xs text-muted-foreground ${className}`} data-clip-inspector=\"\">\n {selection.length ? `${selection.length} clips selected. Select one clip to edit it.` : 'Select a clip on the timeline to edit it.'}\n </p>\n );\n const at = findVideoClip(project, selection[0]);\n if (!at) return null;\n const clip: VideoClip = at.clip;\n const disabled = readOnly || !!at.track.locked;\n\n const set = (key: string, value: unknown, label = `Change ${key}`) => {\n if (value === undefined || value === '') controller.apply({ op: 'update_clip', clip: clip.id, remove: [key] }, label);\n else controller.apply({ op: 'update_clip', clip: clip.id, set: { [key]: value } }, label);\n };\n const num = (key: string, raw: string, label?: string) => {\n const trimmed = raw.trim();\n if (!trimmed) return set(key, undefined, label);\n const n = Number(trimmed);\n if (Number.isFinite(n)) set(key, n, label);\n };\n const text = (key: string, label?: string) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return (\n <Commit value={value === undefined ? '' : String(value)} aria-label={label ?? key} disabled={disabled} onCommit={(raw) => set(key, raw.trim() || undefined, `Change ${label ?? key}`)} />\n );\n };\n const number = (key: string, label: string, attrs: { min?: number; max?: number; step?: number } = {}) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return <Commit type=\"number\" value={value === undefined ? '' : String(value)} aria-label={label} disabled={disabled} onCommit={(raw) => num(key, raw, `Change ${label.toLowerCase()}`)} {...attrs} />;\n };\n\n const transition = clip.transition;\n return (\n <div className={`flex flex-col gap-1.5 ${className}`} data-clip-inspector=\"\" data-clip-id={clip.id}>\n <div className=\"flex items-center gap-2 text-xs\">\n <span className=\"font-medium\">{clip.type === 'media' ? 'Media clip' : clip.type === 'text' ? 'Title' : 'Colour clip'}</span>\n <code className=\"text-muted-foreground\">{clip.id}</code>\n {at.track.locked && <span className=\"text-amber-500\">locked track</span>}\n </div>\n <Row label=\"Name\" name=\"name\">\n {text('name', 'Name')}\n </Row>\n <Row label=\"Start\" name=\"start\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.start)}</span>\n </Row>\n <Row label=\"Duration\" name=\"duration\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.duration)}</span>\n </Row>\n {clip.type !== 'media' || project.assets.find((a) => a.id === clip.asset)?.kind !== 'audio' ? (\n <Row label=\"Opacity\" name=\"opacity\">\n {number('opacity', 'Opacity', { min: 0, max: 1, step: 0.05 })}\n </Row>\n ) : null}\n <Row label=\"Fade in (ms)\" name=\"fadeIn\">\n {number('fadeIn', 'Fade in', { min: 0, step: 100 })}\n </Row>\n <Row label=\"Fade out (ms)\" name=\"fadeOut\">\n {number('fadeOut', 'Fade out', { min: 0, step: 100 })}\n </Row>\n {clip.type === 'media' && (\n <>\n <Row label=\"Volume\" name=\"volume\">\n {number('volume', 'Volume', { min: 0, step: 0.1 })}\n </Row>\n <Row label=\"Speed\" name=\"speed\">\n {number('speed', 'Speed', { min: 0.1, step: 0.25 })}\n </Row>\n <Row label=\"Muted\" name=\"muted\">\n <input type=\"checkbox\" className=\"justify-self-start\" checked={!!clip.muted} disabled={disabled} aria-label=\"Muted\" onChange={(e) => set('muted', e.target.checked || undefined, e.target.checked ? 'Mute clip' : 'Unmute clip')} />\n </Row>\n </>\n )}\n {clip.type === 'text' && (\n <>\n <Row label=\"Text\" name=\"text\">\n <Commit multiline value={clip.text} aria-label=\"Text\" disabled={disabled} onCommit={(raw) => set('text', raw, 'Change text')} />\n </Row>\n <Row label=\"Colour\" name=\"color\">\n {text('color', 'Colour')}\n </Row>\n <Row label=\"Font size\" name=\"fontSize\">\n {number('fontSize', 'Font size', { min: 1, step: 2 })}\n </Row>\n <Row label=\"Background\" name=\"background\">\n {text('background', 'Background')}\n </Row>\n </>\n )}\n {clip.type === 'color' && (\n <Row label=\"Colour\" name=\"color\">\n <Commit value={clip.color} aria-label=\"Colour\" disabled={disabled} onCommit={(raw) => raw.trim() && set('color', raw.trim(), 'Change colour')} />\n </Row>\n )}\n <Row label=\"Transition in\" name=\"transition\">\n <select\n className={field}\n value={transition?.type ?? ''}\n disabled={disabled}\n aria-label=\"Transition\"\n onChange={(e) => {\n const type = e.target.value as VideoTransitionType | '';\n controller.apply(\n { op: 'set_transition', clip: clip.id, transition: type ? { ...transition, type, duration: transition?.duration ?? DEFAULT_TRANSITION } : null },\n type ? 'Set transition' : 'Remove transition',\n );\n }}\n >\n <option value=\"\">None</option>\n {VIDEO_TRANSITION_TYPES.map((t) => (\n <option key={t} value={t}>\n {t === 'crossfade' ? 'Crossfade' : t === 'dip' ? 'Dip to colour' : 'Wipe'}\n </option>\n ))}\n </select>\n </Row>\n {transition && (\n <Row label=\"Length (ms)\" name=\"transitionDuration\">\n <Commit\n type=\"number\"\n min={1}\n step={100}\n value={String(transition.duration)}\n aria-label=\"Transition length\"\n disabled={disabled}\n onCommit={(raw) => {\n const n = Number(raw);\n if (raw.trim() && Number.isFinite(n) && n > 0) controller.apply({ op: 'set_transition', clip: clip.id, transition: { ...transition, duration: n } }, 'Change transition length');\n }}\n />\n </Row>\n )}\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport {\n formatVideoSeconds,\n probeVideoMedia,\n videoAssetDropEdit,\n videoAssetKindOf,\n type VideoAsset,\n type VideoAssetKind,\n type VideoEditorController,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface MediaBinProps {\n controller: VideoEditorController;\n /** Show the import controls and \"Add to timeline\" buttons (default true). */\n editable?: boolean;\n /** Turns a file into a URL the preview and exporter can open (default `URL.createObjectURL`). */\n fileToUrl?: (file: File) => string;\n /** Reads a source's length and size (default `probeVideoMedia`). */\n probe?: typeof probeVideoMedia;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * The project's assets (name, kind, length), each with an \"Add to timeline\" button that puts it at\n * the end of the first unlocked track of its kind. Import media by choosing or dropping files (read\n * as object URLs and probed for their length and size) or by URL. Each import is one undo step.\n */\nexport function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMedia, className = '' }: MediaBinProps) {\n const { project, readOnly } = useVideoEditorState(controller);\n const canEdit = editable && !readOnly;\n const [url, setUrl] = useState('');\n const [urlKind, setUrlKind] = useState<VideoAssetKind | ''>('');\n const [busy, setBusy] = useState(0);\n const [problem, setProblem] = useState<string | null>(null);\n const [over, setOver] = useState(false);\n\n const addSource = async (src: string, kind: VideoAssetKind, name: string) => {\n setBusy((n) => n + 1);\n try {\n const found = await probe(src, kind).catch(() => null);\n const result = controller.apply({ op: 'add_asset', asset: { kind, src, name, ...(found ?? {}) } }, `Import ${name}`);\n if (!result.ok) setProblem(result.error);\n } finally {\n setBusy((n) => n - 1);\n }\n };\n\n const addFiles = async (files: FileList | File[] | null) => {\n setProblem(null);\n for (const file of Array.from(files ?? [])) {\n const kind = videoAssetKindOf(file.type || file.name);\n if (!kind) {\n setProblem(`\"${file.name}\" is not a video, audio or image file.`);\n continue;\n }\n const src = fileToUrl ? fileToUrl(file) : URL.createObjectURL(file);\n await addSource(src, kind, file.name);\n }\n };\n\n const addUrl = async () => {\n const src = url.trim();\n if (!src) return;\n const kind = urlKind || videoAssetKindOf(src.replace(/[?#].*$/, ''));\n if (!kind) {\n setProblem(`Can't tell whether \"${src}\" is video, audio or an image; choose its kind.`);\n return;\n }\n setProblem(null);\n await addSource(src, kind, src.replace(/[?#].*$/, '').replace(/^.*[\\\\/]/, '') || src);\n setUrl('');\n };\n\n const addToTimeline = (asset: VideoAsset) => {\n const edit = videoAssetDropEdit(project, asset.id);\n if (!edit) {\n setProblem(`There is no unlocked ${asset.kind === 'audio' ? 'audio' : 'video'} track to put \"${asset.name ?? asset.id}\" on.`);\n return;\n }\n setProblem(null);\n const result = controller.apply(edit, `Add ${asset.name ?? asset.id}`);\n if (result.ok) {\n const added = result.outcomes[0]?.clip;\n if (added) controller.select([added]);\n }\n };\n\n return (\n <div\n className={`flex flex-col gap-2 rounded ${over ? 'ring-2 ring-rose-500/60' : ''} ${className}`}\n data-media-bin=\"\"\n onDragOver={(e) => {\n if (!canEdit || !e.dataTransfer?.types?.includes('Files')) return;\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n setOver(false);\n if (!canEdit || !e.dataTransfer?.files?.length) return;\n e.preventDefault();\n void addFiles(e.dataTransfer.files);\n }}\n >\n <div className=\"text-xs font-medium\">Media</div>\n {project.assets.length ? (\n <ul className=\"flex flex-col gap-1\" aria-label=\"Media assets\">\n {project.assets.map((asset) => (\n <li key={asset.id} className=\"flex items-center gap-2 text-xs\" data-asset={asset.id}>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-asset-kind=\"\">\n {asset.kind}\n </span>\n <span className=\"min-w-0 flex-1 truncate\" title={asset.src}>\n {asset.name ?? asset.id}\n </span>\n {asset.duration !== undefined && <span className=\"tabular-nums text-muted-foreground\">{formatVideoSeconds(asset.duration)}</span>}\n {editable && (\n <button type=\"button\" className={btn} disabled={!canEdit} onClick={() => addToTimeline(asset)} data-add-to-timeline=\"\">\n Add to timeline\n </button>\n )}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-xs text-muted-foreground\">No media yet.{editable ? ' Choose or drop files, or add a URL.' : ''}</p>\n )}\n {editable && (\n <>\n <label className=\"flex flex-col gap-1 rounded border border-dashed border-border p-2 text-xs text-muted-foreground\">\n Choose or drop video, audio or image files\n <input\n type=\"file\"\n multiple\n accept=\"video/*,audio/*,image/*\"\n disabled={!canEdit}\n aria-label=\"Import media files\"\n data-import-files=\"\"\n onChange={(e) => {\n const files = e.currentTarget.files ? Array.from(e.currentTarget.files) : [];\n e.currentTarget.value = '';\n void addFiles(files);\n }}\n />\n </label>\n <form\n className=\"flex items-center gap-1\"\n onSubmit={(e) => {\n e.preventDefault();\n void addUrl();\n }}\n >\n <input\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n placeholder=\"https://…/clip.mp4\"\n value={url}\n disabled={!canEdit}\n aria-label=\"Media URL\"\n onChange={(e) => setUrl(e.target.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" value={urlKind} disabled={!canEdit} aria-label=\"Media kind\" onChange={(e) => setUrlKind(e.target.value as VideoAssetKind | '')}>\n <option value=\"\">Auto</option>\n <option value=\"video\">Video</option>\n <option value=\"audio\">Audio</option>\n <option value=\"image\">Image</option>\n </select>\n <button type=\"submit\" className={btn} disabled={!canEdit || !url.trim()} data-add-url=\"\">\n Add\n </button>\n </form>\n </>\n )}\n {busy > 0 && <p className=\"text-xs text-muted-foreground\">Reading media…</p>}\n {problem && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-media-error=\"\">\n {problem}\n </p>\n )}\n </div>\n );\n}\n","import React, { useEffect } from 'react';\nimport type { VideoEditorController, VideoEditorOptions } from '@pixerate/editor/video-editor';\nimport { ClipInspector } from './ClipInspector';\nimport { MediaBin } from './MediaBin';\nimport { useVideoEditor } from './useVideoEditor';\nimport { VideoPreview } from './VideoPreview';\nimport { VideoTimeline } from './VideoTimeline';\nimport { VideoTransport } from './VideoTransport';\n\nexport interface VideoEditorProps {\n /** The controller to edit. Without it one is created from `options` for the component's lifetime. */\n controller?: VideoEditorController;\n /** Options for the controller created when `controller` is not given. */\n options?: VideoEditorOptions;\n /**\n * Show the editing UI: the media bin's imports, the transport's edit buttons, timeline dragging\n * and track toggles, and the clip inspector (default true). False also makes the project read-only\n * while mounted.\n */\n editable?: boolean;\n /** Timeline zoom in pixels per second (with `onPxPerSecondChange` the host owns it). */\n pxPerSecond?: number;\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Canvas pixels per frame pixel for the preview (see `<VideoPreview resolution>`). */\n resolution?: number;\n /** List the project's issues under the timeline (default true). */\n showIssues?: boolean;\n className?: string;\n}\n\n/**\n * A whole non-linear video editor for a `VideoEditorController`: the media bin, the preview, the\n * transport, the timeline and the clip inspector, with the project's issues listed under the\n * timeline. Every edit is one undo step; the controller's agent tools edit the same project.\n */\nexport function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, className = '' }: VideoEditorProps) {\n const { controller, state } = useVideoEditor(given ?? options ?? {});\n useEffect(() => {\n if (editable) return;\n const was = controller.getState().readOnly;\n controller.setReadOnly(true);\n return () => controller.setReadOnly(was);\n }, [controller, editable]);\n const { issues } = state;\n const issueList = showIssues && issues.length > 0 && (\n <ul className=\"space-y-0.5 text-xs\" aria-label=\"Video issues\" data-video-issues=\"\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity === 'error' ? 'text-red-500' : issue.severity === 'warning' ? 'text-amber-500' : 'text-muted-foreground'} data-issue={issue.code}>\n {issue.message}\n </li>\n ))}\n </ul>\n );\n return (\n <div className={`flex flex-col gap-3 ${className}`} data-video-editor=\"\">\n <div className={`grid gap-3 ${editable ? 'lg:grid-cols-[16rem_minmax(0,1fr)_18rem]' : ''}`}>\n {editable && <MediaBin controller={controller} className=\"min-w-0\" />}\n <div className=\"flex min-w-0 flex-col gap-2\">\n <VideoPreview controller={controller} resolution={resolution} className=\"rounded-md border border-border\" />\n <VideoTransport controller={controller} editable={editable} />\n </div>\n {editable && <ClipInspector controller={controller} className=\"min-w-0\" />}\n </div>\n <VideoTimeline controller={controller} editable={editable} pxPerSecond={pxPerSecond} onPxPerSecondChange={onPxPerSecondChange} />\n {issueList}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_video_editor","import_video_editor","import_jsx_runtime","import_react","import_video_editor","import_jsx_runtime","field","import_video_editor","import_jsx_runtime","import_react","import_video_editor","import_jsx_runtime","btn","import_react","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/video-editor/index.ts","../../src/video-editor/useVideoEditor.ts","../../src/video-editor/VideoPreview.tsx","../../src/video-editor/VideoTransport.tsx","../../src/video-editor/VideoTimeline.tsx","../../src/video-editor/ClipInspector.tsx","../../src/video-editor/MediaBin.tsx","../../src/video-editor/VideoEditor.tsx"],"sourcesContent":["export * from './useVideoEditor';\nexport * from './VideoPreview';\nexport * from './VideoTransport';\nexport * from './VideoTimeline';\nexport * from './ClipInspector';\nexport * from './MediaBin';\nexport * from './VideoEditor';\n","import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n VideoEditorController,\n createVideoEditorToolProvider,\n type VideoEditResult,\n type VideoEditorOptions,\n type VideoEditorState,\n type VideoEditorToolProviderOptions,\n} from '@pixerate/editor/video-editor';\nimport type { ToolRegistry } from '@pixerate/editor/tools';\n\n/** A `VideoEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useVideoEditorController(options: VideoEditorOptions = {}): VideoEditorController {\n const ref = useRef<VideoEditorController | null>(null);\n if (!ref.current) ref.current = new VideoEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change, including each played frame). */\nexport function useVideoEditorState(controller: VideoEditorController): VideoEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseVideoEditorResult {\n controller: VideoEditorController;\n state: VideoEditorState;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n /** Moves the playhead (ms). */\n seek: (time: number) => void;\n /** Moves the playhead by whole frames (negative steps back), pausing. */\n step: (frames: number) => void;\n undo: () => void;\n redo: () => void;\n /** Splits the selected clips (or every clip) under the playhead, as one undo step. */\n splitAtPlayhead: () => VideoEditResult;\n /** Deletes the selected clips; `ripple` closes the gaps they leave. */\n deleteSelection: (options?: { ripple?: boolean }) => VideoEditResult;\n}\n\n/**\n * Subscribes to a video editor controller and exposes its state with bound actions.\n * Pass options instead of a controller to create one for the component's lifetime.\n */\nexport function useVideoEditor(controllerOrOptions: VideoEditorController | VideoEditorOptions = {}): UseVideoEditorResult {\n const own = useRef<VideoEditorController | null>(null);\n let controller: VideoEditorController;\n if (controllerOrOptions instanceof VideoEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new VideoEditorController(controllerOrOptions);\n controller = own.current;\n }\n useEffect(() => () => own.current?.destroy(), []);\n const state = useVideoEditorState(controller);\n const actions = useMemo(\n () => ({\n play: () => controller.play(),\n pause: () => controller.pause(),\n toggle: () => controller.toggle(),\n seek: (time: number) => controller.seek(time),\n step: (frames: number) => controller.step(frames),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n splitAtPlayhead: () => controller.splitAtPlayhead(),\n deleteSelection: (options?: { ripple?: boolean }) => controller.deleteSelection(options),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/**\n * Registers the video editor's agent tools (`get_video_project`, `add_video_clip`,\n * `trim_video_clip`, …) in `registry` while mounted.\n */\nexport function useVideoEditorTools(\n registry: ToolRegistry | null | undefined,\n controller: VideoEditorController,\n options: VideoEditorToolProviderOptions = {},\n): void {\n const { id, namespace, readOnly, rasterize } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createVideoEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));\n }, [registry, controller, id, namespace, readOnly, rasterize]);\n}\n","import React, { useEffect, useRef } from 'react';\nimport { VideoPreviewEngine, type VideoEditorController, type VideoPreviewOptions } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoPreviewProps {\n controller: VideoEditorController;\n /** Canvas pixels per frame pixel (default: fits the displayed width at the device pixel ratio, at most 1). */\n resolution?: number;\n /** Called after each frame is drawn. */\n onFrame?: VideoPreviewOptions['onFrame'];\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Plays the controller's project on a canvas the shape of its frame: a `VideoPreviewEngine` keeps\n * one `<video>` / `<audio>` element per playing clip in step with the playhead and draws each frame.\n * The engine is created when the canvas mounts and destroyed on unmount or when the controller changes.\n */\nexport function VideoPreview({ controller, resolution, onFrame, className = '', style }: VideoPreviewProps) {\n const { project } = useVideoEditorState(controller);\n const canvas = useRef<HTMLCanvasElement>(null);\n const frameRef = useRef(onFrame);\n frameRef.current = onFrame;\n useEffect(() => {\n const el = canvas.current;\n if (!el) return;\n const engine = new VideoPreviewEngine(controller, el, { resolution, onFrame: (frame) => frameRef.current?.(frame) });\n return () => engine.destroy();\n }, [controller, resolution]);\n return (\n <canvas\n ref={canvas}\n className={className}\n style={{ display: 'block', width: '100%', aspectRatio: `${project.width} / ${project.height}`, background: project.background ?? '#000', ...style }}\n role=\"img\"\n aria-label={`Preview of ${project.name ?? 'the video'}`}\n data-video-preview=\"\"\n />\n );\n}\n","import React from 'react';\nimport { formatVideoTimecode, type VideoEditorController } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTransportProps {\n controller: VideoEditorController;\n /** Show the editing buttons (split, delete, ripple delete, undo, redo; default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * Play / pause, a frame back and forward, the playhead and total length as timecode, and the\n * edits that work on the playhead and the selection: split, delete, ripple delete, undo and redo.\n */\nexport function VideoTransport({ controller, editable = true, className = '' }: VideoTransportProps) {\n const state = useVideoEditorState(controller);\n const { time, duration, playing, project, selection, readOnly } = state;\n const canEdit = editable && !readOnly;\n return (\n <div className={`flex flex-wrap items-center gap-1.5 text-xs ${className}`} data-video-transport=\"\">\n <button type=\"button\" className={btn} onClick={() => controller.step(-1)} aria-label=\"Previous frame\" title=\"Previous frame (←)\" data-step-back=\"\">\n ◀|\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.toggle()} aria-label={playing ? 'Pause' : 'Play'} title=\"Play / pause (Space)\" disabled={duration <= 0} data-play=\"\">\n {playing ? 'Pause' : 'Play'}\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.step(1)} aria-label=\"Next frame\" title=\"Next frame (→)\" data-step-forward=\"\">\n |▶\n </button>\n <span className=\"px-1 tabular-nums text-muted-foreground\" data-timecode=\"\">\n {formatVideoTimecode(time, project.fps)} / {formatVideoTimecode(duration, project.fps)}\n </span>\n {editable && (\n <>\n <span className=\"mx-1 h-4 w-px bg-border\" />\n <button type=\"button\" className={btn} onClick={() => controller.splitAtPlayhead()} disabled={!canEdit} title=\"Split at the playhead (S)\" data-split=\"\">\n Split\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.deleteSelection()} disabled={!canEdit || !selection.length} title=\"Delete the selected clips (Delete)\" data-delete=\"\">\n Delete\n </button>\n <button\n type=\"button\"\n className={btn}\n onClick={() => controller.deleteSelection({ ripple: true })}\n disabled={!canEdit || !selection.length}\n title=\"Delete the selected clips and close the gaps (Shift+Delete)\"\n data-ripple-delete=\"\"\n >\n Ripple delete\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.undo()} disabled={!canEdit || !state.canUndo} title={state.canUndo ? `Undo ${state.undoLabel}` : 'Nothing to undo'} data-undo=\"\">\n Undo\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.redo()} disabled={!canEdit || !state.canRedo} title={state.canRedo ? `Redo ${state.redoLabel}` : 'Nothing to redo'} data-redo=\"\">\n Redo\n </button>\n </>\n )}\n </div>\n );\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport {\n planVideoClipDrag,\n videoDragLabel,\n videoAssetMap,\n videoClipLabel,\n videoClipTint,\n videoDropTracks,\n videoRulerTicks,\n formatVideoSeconds,\n type VideoClip,\n type VideoClipDragMode,\n type VideoClipDragPlan,\n type VideoEditorController,\n type VideoTrack,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTimelineProps {\n controller: VideoEditorController;\n /** Zoom: pixels per second of timeline. With `onPxPerSecondChange` the host owns it; otherwise it is the starting zoom (default 100). */\n pxPerSecond?: number;\n /** Called when the zoom control changes. Without it the timeline keeps its own zoom and shows a zoom slider. */\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Allow selecting, dragging, trimming and the track toggles (default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst HEADER = 150;\nconst RULER = 24;\nconst ROW = 44;\nconst HANDLE = 6;\nconst DRAG_THRESHOLD = 3;\nconst MIN_ZOOM = 10;\nconst MAX_ZOOM = 400;\n\ninterface Drag {\n clip: string;\n mode: VideoClipDragMode;\n x: number;\n pointer: number;\n moved: boolean;\n plan: VideoClipDragPlan | null;\n}\n\nconst isField = (el: EventTarget | null) => el instanceof HTMLElement && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);\n\n/**\n * The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it\n * to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.\n * Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another\n * track of the same kind too; hold Alt to insert, pushing later material along on every sync-locked\n * track, or Shift to overwrite what is underneath), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,\n * markers, the playhead and frames. Every edit is one undo step; refused edits show why.\n *\n * Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes\n * the selection (Shift to ripple), Cmd/Ctrl+Z undoes, Shift+Cmd/Ctrl+Z redoes.\n */\nexport function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable = true, className = '' }: VideoTimelineProps) {\n const state = useVideoEditorState(controller);\n const { project, duration, time, selection, readOnly, error } = state;\n const [ownZoom, setOwnZoom] = useState(pxPerSecond ?? 100);\n const px = onPxPerSecondChange ? (pxPerSecond ?? 100) : ownZoom;\n const canEdit = editable && !readOnly;\n const [drag, setDrag] = useState<Drag | null>(null);\n const dragRef = useRef<Drag | null>(null);\n const rows = useRef(new Map<string, HTMLDivElement>());\n const ruler = useRef<HTMLDivElement>(null);\n const assets = videoAssetMap(project);\n const msToPx = (ms: number) => (ms * px) / 1000;\n const span = Math.max(duration + 5000, 10000);\n const width = msToPx(span);\n const ticks = videoRulerTicks(span, px, project.fps);\n\n const seekAt = (clientX: number) => {\n const box = ruler.current!.getBoundingClientRect();\n controller.seek(Math.max(0, ((clientX - box.left) * 1000) / px));\n };\n\n const trackAt = (clipId: string, clientY: number): string | undefined => {\n const allowed = videoDropTracks(project, clipId);\n for (const [id, el] of rows.current) {\n const box = el.getBoundingClientRect();\n if (clientY >= box.top && clientY < box.bottom && allowed.includes(id)) return id;\n }\n return undefined;\n };\n\n const planFor = (d: Drag, e: { clientX: number; clientY: number; altKey: boolean; shiftKey: boolean }) =>\n planVideoClipDrag(project, {\n clip: d.clip,\n mode: d.mode,\n delta: ((e.clientX - d.x) * 1000) / px,\n track: d.mode === 'move' ? trackAt(d.clip, e.clientY) : undefined,\n ripple: e.altKey,\n placement: e.altKey ? 'insert' : e.shiftKey ? 'overwrite' : 'refuse',\n snap: (t) => controller.snap(t, { exclude: [d.clip] }).time,\n });\n\n const update = (next: Drag | null) => {\n dragRef.current = next;\n setDrag(next);\n };\n\n // The latest render's values, for the window listeners of a drag in progress.\n const live = useRef({ planFor, selection });\n live.current = { planFor, selection };\n const stopDrag = useRef<(() => void) | null>(null);\n useEffect(() => () => stopDrag.current?.(), []);\n\n const onClipPointerDown = (e: React.PointerEvent<HTMLDivElement>, clip: VideoClip, track: VideoTrack) => {\n if (e.button !== undefined && e.button !== 0) return;\n e.stopPropagation();\n if (!editable) return;\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n const handle = (e.target as HTMLElement).closest('[data-handle]')?.getAttribute('data-handle');\n const mode: VideoClipDragMode = handle === 'start' ? 'trim-start' : handle === 'end' ? 'trim-end' : 'move';\n const fixed = !canEdit || !!track.locked;\n stopDrag.current?.();\n update({ clip: clip.id, mode, x: e.clientX, pointer: e.pointerId, moved: false, plan: null });\n // Follow the pointer on the window: the block may move to another track's row (a new element) mid-drag.\n const move = (ev: PointerEvent) => {\n const d = dragRef.current;\n if (!d || fixed) return;\n if (!d.moved && Math.abs(ev.clientX - d.x) < DRAG_THRESHOLD) return;\n if (!d.moved && !live.current.selection.includes(d.clip)) controller.select([d.clip]);\n update({ ...d, moved: true, plan: live.current.planFor(d, ev) });\n };\n const up = (ev: PointerEvent) => {\n stop();\n const d = dragRef.current;\n if (!d) return;\n update(null);\n if (!d.moved) {\n if (ev.shiftKey || ev.metaKey || ev.ctrlKey) controller.select([d.clip], { add: true });\n else controller.select([d.clip]);\n return;\n }\n const plan = live.current.planFor(d, ev);\n if (plan?.edit) controller.apply(plan.edit, videoDragLabel(plan.edit));\n };\n const cancel = () => {\n stop();\n update(null);\n };\n const stop = () => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n window.removeEventListener('pointercancel', cancel);\n stopDrag.current = null;\n };\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n window.addEventListener('pointercancel', cancel);\n stopDrag.current = stop;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (isField(e.target)) return;\n const mod = e.metaKey || e.ctrlKey;\n const key = e.key.toLowerCase();\n if (e.key === ' ') {\n if ((e.target as HTMLElement).tagName === 'BUTTON') return;\n controller.toggle();\n } else if (e.key === 'ArrowLeft' && !mod) controller.step(-1);\n else if (e.key === 'ArrowRight' && !mod) controller.step(1);\n else if (e.key === 'Escape') controller.select(null);\n else if (!canEdit) return;\n else if (key === 's' && !mod) controller.splitAtPlayhead();\n else if ((e.key === 'Delete' || e.key === 'Backspace') && !mod) controller.deleteSelection({ ripple: e.shiftKey });\n else if (key === 'z' && mod) {\n if (e.shiftKey) controller.redo();\n else controller.undo();\n } else return;\n e.preventDefault();\n };\n\n // Where each clip is drawn: the dragged one follows the plan (possibly on another track).\n const placed = project.tracks.map((track) => ({\n track,\n clips: project.tracks.flatMap((t) =>\n t.clips\n .map((clip) => {\n const plan = drag?.moved && drag.clip === clip.id ? drag.plan : null;\n const on = plan ? plan.track : t.id;\n return on === track.id ? { clip, home: t, start: plan ? plan.start : clip.start, duration: plan ? plan.duration : clip.duration, dragging: !!plan } : null;\n })\n .filter((x): x is NonNullable<typeof x> => x !== null),\n ),\n }));\n\n const toggle = (track: VideoTrack, field: 'muted' | 'hidden' | 'locked', label: string) => (\n <button\n type=\"button\"\n className={`rounded border px-1 text-[10px] leading-4 ${track[field] ? 'border-rose-500 bg-rose-500/15 text-rose-600' : 'border-border text-muted-foreground'} disabled:opacity-40`}\n aria-pressed={!!track[field]}\n aria-label={`${label} ${track.name ?? track.id}`}\n title={label}\n disabled={!canEdit}\n data-track-toggle={field}\n onClick={() => controller.apply({ op: 'update_track', track: track.id, set: { [field]: !track[field] } }, `${track[field] ? 'Un' : ''}${field === 'muted' ? 'mute' : field === 'hidden' ? 'hide' : 'lock'} track`)}\n >\n {field === 'muted' ? 'M' : field === 'hidden' ? 'H' : 'L'}\n </button>\n );\n\n return (\n <div className={`flex flex-col gap-1 ${className}`} data-video-timeline-wrapper=\"\">\n {!onPxPerSecondChange && (\n <label className=\"flex items-center gap-2 self-end text-xs text-muted-foreground\">\n Zoom\n <input type=\"range\" min={MIN_ZOOM} max={MAX_ZOOM} step={5} value={ownZoom} aria-label=\"Timeline zoom\" onChange={(e) => setOwnZoom(Number(e.target.value))} data-zoom=\"\" />\n </label>\n )}\n <div\n className=\"relative overflow-x-auto rounded border border-border outline-none focus-visible:ring-2 focus-visible:ring-rose-500/50\"\n tabIndex={0}\n role=\"group\"\n aria-label=\"Video timeline\"\n onKeyDown={onKeyDown}\n data-video-timeline=\"\"\n >\n <div className=\"relative\" style={{ width: HEADER + width }}>\n <div className=\"flex\" style={{ height: RULER }}>\n <div className=\"sticky left-0 z-20 shrink-0 border-b border-r border-border bg-background\" style={{ width: HEADER }} />\n <div\n ref={ruler}\n className=\"relative shrink-0 cursor-ew-resize select-none touch-none border-b border-border text-[10px] text-muted-foreground\"\n style={{ width }}\n role=\"slider\"\n aria-label=\"Playhead\"\n aria-valuemin={0}\n aria-valuemax={duration}\n aria-valuenow={Math.round(time)}\n data-ruler=\"\"\n onPointerDown={(e) => {\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n seekAt(e.clientX);\n }}\n onPointerMove={(e) => {\n if (e.buttons & 1) seekAt(e.clientX);\n }}\n >\n {ticks.map((t) => (\n <div key={t.time} className={`absolute bottom-0 w-px ${t.major ? 'h-2.5 bg-muted-foreground/70' : 'h-1 bg-muted-foreground/40'}`} style={{ left: t.x }} data-tick={t.time}>\n {t.label && <span className=\"absolute -top-3.5 left-1 whitespace-nowrap tabular-nums\">{t.label}</span>}\n </div>\n ))}\n {(project.markers ?? []).map((m) => (\n <div\n key={m.id}\n className=\"absolute top-0 h-2.5 w-2.5 -translate-x-1/2 rotate-45\"\n style={{ left: msToPx(m.time), background: m.color ?? '#f59e0b' }}\n title={`${m.label ?? m.id} at ${formatVideoSeconds(m.time)}`}\n data-marker={m.id}\n />\n ))}\n </div>\n </div>\n {placed.map(({ track, clips }) => (\n <div key={track.id} className=\"flex border-b border-border last:border-b-0\" style={{ height: ROW }} data-track={track.id}>\n <div className=\"sticky left-0 z-20 flex shrink-0 flex-col justify-center gap-0.5 border-r border-border bg-background px-2 text-xs\" style={{ width: HEADER }} data-track-header=\"\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate font-medium\">{track.name ?? track.id}</span>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-track-kind=\"\">\n {track.kind}\n </span>\n </div>\n {editable && (\n <div className=\"flex gap-1\">\n {toggle(track, 'muted', 'Mute')}\n {track.kind === 'video' && toggle(track, 'hidden', 'Hide')}\n {toggle(track, 'locked', 'Lock')}\n </div>\n )}\n </div>\n <div\n ref={(el) => {\n if (el) rows.current.set(track.id, el);\n else rows.current.delete(track.id);\n }}\n className={`relative shrink-0 ${track.hidden ? 'opacity-50' : ''} ${track.locked ? 'bg-muted/40' : ''}`}\n style={{ width }}\n data-track-lane={track.id}\n onPointerDown={(e) => {\n if (e.target === e.currentTarget && editable) controller.select(null);\n }}\n >\n {clips.map(({ clip, home, start, duration: length, dragging }) => {\n const asset = clip.type === 'media' ? assets.get(clip.asset) : undefined;\n const selected = selection.includes(clip.id);\n const label = videoClipLabel(clip, asset);\n return (\n <div\n key={clip.id}\n className={`absolute top-1 bottom-1 select-none touch-none overflow-hidden rounded text-[11px] text-white ${canEdit && !home.locked ? 'cursor-grab' : 'cursor-default'} ${dragging ? 'opacity-80 shadow-lg' : ''}`}\n style={{\n left: msToPx(start),\n width: Math.max(2, msToPx(length)),\n background: videoClipTint(clip, asset),\n outline: selected ? '2px solid #f43f5e' : undefined,\n outlineOffset: selected ? 1 : undefined,\n zIndex: dragging ? 10 : selected ? 2 : 1,\n }}\n title={`${label} · ${formatVideoSeconds(start)}–${formatVideoSeconds(start + length)}`}\n data-clip={clip.id}\n data-selected={selected ? '' : undefined}\n data-dragging={dragging ? '' : undefined}\n aria-selected={selected}\n onPointerDown={(e) => onClipPointerDown(e, clip, home)}\n >\n <span className=\"pointer-events-none block truncate px-1.5 py-0.5 leading-4\">{label}</span>\n {clip.transition && (\n <span className=\"pointer-events-none absolute left-1 bottom-0.5 rounded bg-black/40 px-1 text-[9px] leading-3\" title={`${clip.transition.type} ${clip.transition.duration} ms`} data-transition={clip.transition.type}>\n ⧓ {clip.transition.type}\n </span>\n )}\n {canEdit && !home.locked && (\n <>\n <div className=\"absolute inset-y-0 left-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"start\" aria-hidden=\"true\" />\n <div className=\"absolute inset-y-0 right-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"end\" aria-hidden=\"true\" />\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n ))}\n <div className=\"pointer-events-none absolute top-0 bottom-0 w-0.5 -translate-x-1/2 bg-rose-500\" style={{ left: HEADER + msToPx(time), zIndex: 15 }} data-playhead=\"\" />\n </div>\n </div>\n {error && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-edit-error=\"\">\n {error}\n </p>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport {\n findVideoClip,\n formatVideoSeconds,\n VIDEO_TRANSITION_TYPES,\n videoKeyframeRows,\n videoKeyframeToggleEdit,\n videoKeyframeValueEdit,\n type VideoClip,\n type VideoEditorController,\n type VideoTransitionType,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface ClipInspectorProps {\n controller: VideoEditorController;\n className?: string;\n}\n\nconst field = 'w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs disabled:opacity-50';\nconst DEFAULT_TRANSITION = 500;\n\n/** An input (or textarea) that commits on blur or Enter, and resets when its value changes from outside. */\nfunction Commit({\n value,\n onCommit,\n multiline,\n ...rest\n}: { value: string; onCommit: (raw: string) => void; multiline?: boolean } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue'>) {\n const commit = (raw: string) => {\n if (raw !== value) onCommit(raw);\n };\n if (multiline)\n return (\n <textarea\n key={value}\n className={field}\n rows={2}\n defaultValue={value}\n disabled={rest.disabled}\n aria-label={rest['aria-label']}\n onBlur={(e) => commit(e.currentTarget.value)}\n />\n );\n return <input key={value} className={field} {...rest} defaultValue={value} onKeyDown={(e) => e.key === 'Enter' && e.currentTarget.blur()} onBlur={(e) => commit(e.currentTarget.value)} />;\n}\n\nfunction Row({ label, name, children }: { label: string; name: string; children: React.ReactNode }) {\n return (\n <label className=\"grid grid-cols-[5.5rem_minmax(0,1fr)] items-center gap-2 text-xs\" data-field={name}>\n <span className=\"text-muted-foreground\">{label}</span>\n {children}\n </label>\n );\n}\n\n/**\n * Edits the one selected clip: its name, fades, opacity, volume and transform (each with a keyframe\n * toggle at the playhead: ◆ adds a key holding the value there or removes the one there; typing a\n * value on a keyed property sets the key at the playhead); volume, speed and mute for media;\n * text, colour, size and background for titles; colour for colour clips; and the transition into\n * it. Each change is one undo step (`update_clip` / `set_transition`), committed on blur or change.\n */\nexport function ClipInspector({ controller, className = '' }: ClipInspectorProps) {\n const state = useVideoEditorState(controller);\n const { project, selection, readOnly, time } = state;\n if (selection.length !== 1)\n return (\n <p className={`text-xs text-muted-foreground ${className}`} data-clip-inspector=\"\">\n {selection.length ? `${selection.length} clips selected. Select one clip to edit it.` : 'Select a clip on the timeline to edit it.'}\n </p>\n );\n const at = findVideoClip(project, selection[0]);\n if (!at) return null;\n const clip: VideoClip = at.clip;\n const disabled = readOnly || !!at.track.locked;\n\n const set = (key: string, value: unknown, label = `Change ${key}`) => {\n if (value === undefined || value === '') controller.apply({ op: 'update_clip', clip: clip.id, remove: [key] }, label);\n else controller.apply({ op: 'update_clip', clip: clip.id, set: { [key]: value } }, label);\n };\n const num = (key: string, raw: string, label?: string) => {\n const trimmed = raw.trim();\n if (!trimmed) return set(key, undefined, label);\n const n = Number(trimmed);\n if (Number.isFinite(n)) set(key, n, label);\n };\n const text = (key: string, label?: string) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return (\n <Commit value={value === undefined ? '' : String(value)} aria-label={label ?? key} disabled={disabled} onCommit={(raw) => set(key, raw.trim() || undefined, `Change ${label ?? key}`)} />\n );\n };\n const number = (key: string, label: string, attrs: { min?: number; max?: number; step?: number } = {}) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return <Commit type=\"number\" value={value === undefined ? '' : String(value)} aria-label={label} disabled={disabled} onCommit={(raw) => num(key, raw, `Change ${label.toLowerCase()}`)} {...attrs} />;\n };\n\n const transition = clip.transition;\n return (\n <div className={`flex flex-col gap-1.5 ${className}`} data-clip-inspector=\"\" data-clip-id={clip.id}>\n <div className=\"flex items-center gap-2 text-xs\">\n <span className=\"font-medium\">{clip.type === 'media' ? 'Media clip' : clip.type === 'text' ? 'Title' : 'Colour clip'}</span>\n <code className=\"text-muted-foreground\">{clip.id}</code>\n {at.track.locked && <span className=\"text-amber-500\">locked track</span>}\n </div>\n <Row label=\"Name\" name=\"name\">\n {text('name', 'Name')}\n </Row>\n <Row label=\"Start\" name=\"start\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.start)}</span>\n </Row>\n <Row label=\"Duration\" name=\"duration\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.duration)}</span>\n </Row>\n {videoKeyframeRows(project, clip.id, time).map((row) => (\n <Row key={row.property} label={row.label} name={row.property}>\n <span className=\"flex min-w-0 items-center gap-1\" data-keyframe-row={row.property}>\n <Commit\n type=\"number\"\n value={row.keyed || (row.property === 'opacity' ? clip.opacity : row.property === 'volume' ? (clip as { volume?: unknown }).volume : clip.transform?.[row.property]) !== undefined ? String(row.value) : ''}\n placeholder={String(row.value)}\n aria-label={row.label}\n disabled={disabled}\n min={row.min}\n max={row.max}\n step={row.step}\n onCommit={(raw) => {\n const n = Number(raw);\n if (raw.trim() && !Number.isFinite(n)) return;\n const edit = videoKeyframeValueEdit(project, clip.id, row.property, time, raw.trim() ? n : undefined);\n if (edit) controller.apply(edit, row.keyed ? `Keyframe ${row.label.toLowerCase()}` : `Change ${row.label.toLowerCase()}`);\n }}\n />\n <button\n type=\"button\"\n className={`shrink-0 rounded px-1 text-xs disabled:opacity-50 ${row.onKey ? 'text-amber-500' : row.keyed ? 'text-foreground' : 'text-muted-foreground'}`}\n aria-label={`${row.onKey ? 'Remove' : 'Add'} ${row.label.toLowerCase()} keyframe at playhead`}\n aria-pressed={row.onKey}\n title={row.keyed ? `${row.keys} key${row.keys === 1 ? '' : 's'}; ${row.onKey ? 'remove the one' : 'add one'} at the playhead` : 'Animate: add a key at the playhead'}\n disabled={disabled}\n data-keyframe-toggle={row.property}\n onClick={() => {\n const edit = videoKeyframeToggleEdit(project, clip.id, row.property, time);\n if (edit) controller.apply(edit, `${row.onKey ? 'Remove' : 'Add'} ${row.label.toLowerCase()} keyframe`);\n }}\n >\n {row.onKey ? '◆' : '◇'}\n </button>\n {row.keyed && (\n <span className=\"shrink-0 text-[10px] tabular-nums text-muted-foreground\" data-keyframe-count={row.property}>\n {row.keys}\n </span>\n )}\n </span>\n </Row>\n ))}\n <Row label=\"Fade in (ms)\" name=\"fadeIn\">\n {number('fadeIn', 'Fade in', { min: 0, step: 100 })}\n </Row>\n <Row label=\"Fade out (ms)\" name=\"fadeOut\">\n {number('fadeOut', 'Fade out', { min: 0, step: 100 })}\n </Row>\n {clip.type === 'media' && (\n <>\n <Row label=\"Speed\" name=\"speed\">\n {number('speed', 'Speed', { min: 0.1, step: 0.25 })}\n </Row>\n <Row label=\"Muted\" name=\"muted\">\n <input type=\"checkbox\" className=\"justify-self-start\" checked={!!clip.muted} disabled={disabled} aria-label=\"Muted\" onChange={(e) => set('muted', e.target.checked || undefined, e.target.checked ? 'Mute clip' : 'Unmute clip')} />\n </Row>\n </>\n )}\n {clip.type === 'text' && (\n <>\n <Row label=\"Text\" name=\"text\">\n <Commit multiline value={clip.text} aria-label=\"Text\" disabled={disabled} onCommit={(raw) => set('text', raw, 'Change text')} />\n </Row>\n <Row label=\"Colour\" name=\"color\">\n {text('color', 'Colour')}\n </Row>\n <Row label=\"Font size\" name=\"fontSize\">\n {number('fontSize', 'Font size', { min: 1, step: 2 })}\n </Row>\n <Row label=\"Background\" name=\"background\">\n {text('background', 'Background')}\n </Row>\n </>\n )}\n {clip.type === 'color' && (\n <Row label=\"Colour\" name=\"color\">\n <Commit value={clip.color} aria-label=\"Colour\" disabled={disabled} onCommit={(raw) => raw.trim() && set('color', raw.trim(), 'Change colour')} />\n </Row>\n )}\n <Row label=\"Transition in\" name=\"transition\">\n <select\n className={field}\n value={transition?.type ?? ''}\n disabled={disabled}\n aria-label=\"Transition\"\n onChange={(e) => {\n const type = e.target.value as VideoTransitionType | '';\n controller.apply(\n { op: 'set_transition', clip: clip.id, transition: type ? { ...transition, type, duration: transition?.duration ?? DEFAULT_TRANSITION } : null },\n type ? 'Set transition' : 'Remove transition',\n );\n }}\n >\n <option value=\"\">None</option>\n {VIDEO_TRANSITION_TYPES.map((t) => (\n <option key={t} value={t}>\n {t === 'crossfade' ? 'Crossfade' : t === 'dip' ? 'Dip to colour' : 'Wipe'}\n </option>\n ))}\n </select>\n </Row>\n {transition && (\n <Row label=\"Length (ms)\" name=\"transitionDuration\">\n <Commit\n type=\"number\"\n min={1}\n step={100}\n value={String(transition.duration)}\n aria-label=\"Transition length\"\n disabled={disabled}\n onCommit={(raw) => {\n const n = Number(raw);\n if (raw.trim() && Number.isFinite(n) && n > 0) controller.apply({ op: 'set_transition', clip: clip.id, transition: { ...transition, duration: n } }, 'Change transition length');\n }}\n />\n </Row>\n )}\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport {\n formatVideoSeconds,\n probeVideoMedia,\n videoAssetDropEdit,\n videoAssetKindOf,\n type VideoAsset,\n type VideoAssetKind,\n type VideoEditorController,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface MediaBinProps {\n controller: VideoEditorController;\n /** Show the import controls and \"Add to timeline\" buttons (default true). */\n editable?: boolean;\n /** Turns a file into a URL the preview and exporter can open (default `URL.createObjectURL`). */\n fileToUrl?: (file: File) => string;\n /** Reads a source's length and size (default `probeVideoMedia`). */\n probe?: typeof probeVideoMedia;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * The project's assets (name, kind, length), each with an \"Add to timeline\" button that puts it at\n * the end of the first unlocked track of its kind. Import media by choosing or dropping files (read\n * as object URLs and probed for their length and size) or by URL. Each import is one undo step.\n */\nexport function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMedia, className = '' }: MediaBinProps) {\n const { project, readOnly } = useVideoEditorState(controller);\n const canEdit = editable && !readOnly;\n const [url, setUrl] = useState('');\n const [urlKind, setUrlKind] = useState<VideoAssetKind | ''>('');\n const [busy, setBusy] = useState(0);\n const [problem, setProblem] = useState<string | null>(null);\n const [over, setOver] = useState(false);\n\n const addSource = async (src: string, kind: VideoAssetKind, name: string) => {\n setBusy((n) => n + 1);\n try {\n const found = await probe(src, kind).catch(() => null);\n const result = controller.apply({ op: 'add_asset', asset: { kind, src, name, ...(found ?? {}) } }, `Import ${name}`);\n if (!result.ok) setProblem(result.error);\n } finally {\n setBusy((n) => n - 1);\n }\n };\n\n const addFiles = async (files: FileList | File[] | null) => {\n setProblem(null);\n for (const file of Array.from(files ?? [])) {\n const kind = videoAssetKindOf(file.type || file.name);\n if (!kind) {\n setProblem(`\"${file.name}\" is not a video, audio or image file.`);\n continue;\n }\n const src = fileToUrl ? fileToUrl(file) : URL.createObjectURL(file);\n await addSource(src, kind, file.name);\n }\n };\n\n const addUrl = async () => {\n const src = url.trim();\n if (!src) return;\n const kind = urlKind || videoAssetKindOf(src.replace(/[?#].*$/, ''));\n if (!kind) {\n setProblem(`Can't tell whether \"${src}\" is video, audio or an image; choose its kind.`);\n return;\n }\n setProblem(null);\n await addSource(src, kind, src.replace(/[?#].*$/, '').replace(/^.*[\\\\/]/, '') || src);\n setUrl('');\n };\n\n const addToTimeline = (asset: VideoAsset) => {\n const edit = videoAssetDropEdit(project, asset.id);\n if (!edit) {\n setProblem(`There is no unlocked ${asset.kind === 'audio' ? 'audio' : 'video'} track to put \"${asset.name ?? asset.id}\" on.`);\n return;\n }\n setProblem(null);\n const result = controller.apply(edit, `Add ${asset.name ?? asset.id}`);\n if (result.ok) {\n const added = result.outcomes[0]?.clip;\n if (added) controller.select([added]);\n }\n };\n\n return (\n <div\n className={`flex flex-col gap-2 rounded ${over ? 'ring-2 ring-rose-500/60' : ''} ${className}`}\n data-media-bin=\"\"\n onDragOver={(e) => {\n if (!canEdit || !e.dataTransfer?.types?.includes('Files')) return;\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n setOver(false);\n if (!canEdit || !e.dataTransfer?.files?.length) return;\n e.preventDefault();\n void addFiles(e.dataTransfer.files);\n }}\n >\n <div className=\"text-xs font-medium\">Media</div>\n {project.assets.length ? (\n <ul className=\"flex flex-col gap-1\" aria-label=\"Media assets\">\n {project.assets.map((asset) => (\n <li key={asset.id} className=\"flex items-center gap-2 text-xs\" data-asset={asset.id}>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-asset-kind=\"\">\n {asset.kind}\n </span>\n <span className=\"min-w-0 flex-1 truncate\" title={asset.src}>\n {asset.name ?? asset.id}\n </span>\n {asset.duration !== undefined && <span className=\"tabular-nums text-muted-foreground\">{formatVideoSeconds(asset.duration)}</span>}\n {editable && (\n <button type=\"button\" className={btn} disabled={!canEdit} onClick={() => addToTimeline(asset)} data-add-to-timeline=\"\">\n Add to timeline\n </button>\n )}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-xs text-muted-foreground\">No media yet.{editable ? ' Choose or drop files, or add a URL.' : ''}</p>\n )}\n {editable && (\n <>\n <label className=\"flex flex-col gap-1 rounded border border-dashed border-border p-2 text-xs text-muted-foreground\">\n Choose or drop video, audio or image files\n <input\n type=\"file\"\n multiple\n accept=\"video/*,audio/*,image/*\"\n disabled={!canEdit}\n aria-label=\"Import media files\"\n data-import-files=\"\"\n onChange={(e) => {\n const files = e.currentTarget.files ? Array.from(e.currentTarget.files) : [];\n e.currentTarget.value = '';\n void addFiles(files);\n }}\n />\n </label>\n <form\n className=\"flex items-center gap-1\"\n onSubmit={(e) => {\n e.preventDefault();\n void addUrl();\n }}\n >\n <input\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n placeholder=\"https://…/clip.mp4\"\n value={url}\n disabled={!canEdit}\n aria-label=\"Media URL\"\n onChange={(e) => setUrl(e.target.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" value={urlKind} disabled={!canEdit} aria-label=\"Media kind\" onChange={(e) => setUrlKind(e.target.value as VideoAssetKind | '')}>\n <option value=\"\">Auto</option>\n <option value=\"video\">Video</option>\n <option value=\"audio\">Audio</option>\n <option value=\"image\">Image</option>\n </select>\n <button type=\"submit\" className={btn} disabled={!canEdit || !url.trim()} data-add-url=\"\">\n Add\n </button>\n </form>\n </>\n )}\n {busy > 0 && <p className=\"text-xs text-muted-foreground\">Reading media…</p>}\n {problem && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-media-error=\"\">\n {problem}\n </p>\n )}\n </div>\n );\n}\n","import React, { useEffect } from 'react';\nimport type { VideoEditorController, VideoEditorOptions } from '@pixerate/editor/video-editor';\nimport { ClipInspector } from './ClipInspector';\nimport { MediaBin } from './MediaBin';\nimport { useVideoEditor } from './useVideoEditor';\nimport { VideoPreview } from './VideoPreview';\nimport { VideoTimeline } from './VideoTimeline';\nimport { VideoTransport } from './VideoTransport';\n\nexport interface VideoEditorProps {\n /** The controller to edit. Without it one is created from `options` for the component's lifetime. */\n controller?: VideoEditorController;\n /** Options for the controller created when `controller` is not given. */\n options?: VideoEditorOptions;\n /**\n * Show the editing UI: the media bin's imports, the transport's edit buttons, timeline dragging\n * and track toggles, and the clip inspector (default true). False also makes the project read-only\n * while mounted.\n */\n editable?: boolean;\n /** Timeline zoom in pixels per second (with `onPxPerSecondChange` the host owns it). */\n pxPerSecond?: number;\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Canvas pixels per frame pixel for the preview (see `<VideoPreview resolution>`). */\n resolution?: number;\n /** List the project's issues under the timeline (default true). */\n showIssues?: boolean;\n className?: string;\n}\n\n/**\n * A whole non-linear video editor for a `VideoEditorController`: the media bin, the preview, the\n * transport, the timeline and the clip inspector, with the project's issues listed under the\n * timeline. Every edit is one undo step; the controller's agent tools edit the same project.\n */\nexport function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, className = '' }: VideoEditorProps) {\n const { controller, state } = useVideoEditor(given ?? options ?? {});\n useEffect(() => {\n if (editable) return;\n const was = controller.getState().readOnly;\n controller.setReadOnly(true);\n return () => controller.setReadOnly(was);\n }, [controller, editable]);\n const { issues } = state;\n const issueList = showIssues && issues.length > 0 && (\n <ul className=\"space-y-0.5 text-xs\" aria-label=\"Video issues\" data-video-issues=\"\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity === 'error' ? 'text-red-500' : issue.severity === 'warning' ? 'text-amber-500' : 'text-muted-foreground'} data-issue={issue.code}>\n {issue.message}\n </li>\n ))}\n </ul>\n );\n return (\n <div className={`flex flex-col gap-3 ${className}`} data-video-editor=\"\">\n <div className={`grid gap-3 ${editable ? 'lg:grid-cols-[16rem_minmax(0,1fr)_18rem]' : ''}`}>\n {editable && <MediaBin controller={controller} className=\"min-w-0\" />}\n <div className=\"flex min-w-0 flex-col gap-2\">\n <VideoPreview controller={controller} resolution={resolution} className=\"rounded-md border border-border\" />\n <VideoTransport controller={controller} editable={editable} />\n </div>\n {editable && <ClipInspector controller={controller} className=\"min-w-0\" />}\n </div>\n <VideoTimeline controller={controller} editable={editable} pxPerSecond={pxPerSecond} onPxPerSecondChange={onPxPerSecondChange} />\n {issueList}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_video_editor","import_video_editor","import_jsx_runtime","import_react","import_video_editor","import_jsx_runtime","field","import_video_editor","import_jsx_runtime","import_react","import_video_editor","import_jsx_runtime","btn","import_react","import_jsx_runtime"]}
@@ -78,7 +78,8 @@ interface VideoTimelineProps {
78
78
  * The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it
79
79
  * to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.
80
80
  * Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another
81
- * track of the same kind too), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,
81
+ * track of the same kind too; hold Alt to insert, pushing later material along on every sync-locked
82
+ * track, or Shift to overwrite what is underneath), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,
82
83
  * markers, the playhead and frames. Every edit is one undo step; refused edits show why.
83
84
  *
84
85
  * Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes
@@ -91,7 +92,9 @@ interface ClipInspectorProps {
91
92
  className?: string;
92
93
  }
93
94
  /**
94
- * Edits the one selected clip: its name, opacity and fades; volume, speed and mute for media;
95
+ * Edits the one selected clip: its name, fades, opacity, volume and transform (each with a keyframe
96
+ * toggle at the playhead: ◆ adds a key holding the value there or removes the one there; typing a
97
+ * value on a keyed property sets the key at the playhead); volume, speed and mute for media;
95
98
  * text, colour, size and background for titles; colour for colour clips; and the transition into
96
99
  * it. Each change is one undo step (`update_clip` / `set_transition`), committed on blur or change.
97
100
  */
@@ -78,7 +78,8 @@ interface VideoTimelineProps {
78
78
  * The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it
79
79
  * to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.
80
80
  * Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another
81
- * track of the same kind too), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,
81
+ * track of the same kind too; hold Alt to insert, pushing later material along on every sync-locked
82
+ * track, or Shift to overwrite what is underneath), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,
82
83
  * markers, the playhead and frames. Every edit is one undo step; refused edits show why.
83
84
  *
84
85
  * Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes
@@ -91,7 +92,9 @@ interface ClipInspectorProps {
91
92
  className?: string;
92
93
  }
93
94
  /**
94
- * Edits the one selected clip: its name, opacity and fades; volume, speed and mute for media;
95
+ * Edits the one selected clip: its name, fades, opacity, volume and transform (each with a keyframe
96
+ * toggle at the playhead: ◆ adds a key holding the value there or removes the one there; typing a
97
+ * value on a keyed property sets the key at the playhead); volume, speed and mute for media;
95
98
  * text, colour, size and background for titles; colour for colour clips; and the transition into
96
99
  * it. Each change is one undo step (`update_clip` / `set_transition`), committed on blur or change.
97
100
  */
@@ -119,6 +119,7 @@ function VideoTransport({ controller, editable = true, className = "" }) {
119
119
  import { useEffect as useEffect3, useRef as useRef3, useState } from "react";
120
120
  import {
121
121
  planVideoClipDrag,
122
+ videoDragLabel,
122
123
  videoAssetMap,
123
124
  videoClipLabel,
124
125
  videoClipTint,
@@ -168,6 +169,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
168
169
  delta: (e.clientX - d.x) * 1e3 / px,
169
170
  track: d.mode === "move" ? trackAt(d.clip, e.clientY) : void 0,
170
171
  ripple: e.altKey,
172
+ placement: e.altKey ? "insert" : e.shiftKey ? "overwrite" : "refuse",
171
173
  snap: (t) => controller.snap(t, { exclude: [d.clip] }).time
172
174
  });
173
175
  const update = (next) => {
@@ -209,7 +211,7 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
209
211
  return;
210
212
  }
211
213
  const plan = live.current.planFor(d, ev);
212
- if (plan?.edit) controller.apply(plan.edit, d.mode === "move" ? "Move clip" : ev.altKey ? "Ripple trim" : "Trim clip");
214
+ if (plan?.edit) controller.apply(plan.edit, videoDragLabel(plan.edit));
213
215
  };
214
216
  const cancel = () => {
215
217
  stop();
@@ -401,7 +403,10 @@ function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable
401
403
  import {
402
404
  findVideoClip,
403
405
  formatVideoSeconds as formatVideoSeconds2,
404
- VIDEO_TRANSITION_TYPES
406
+ VIDEO_TRANSITION_TYPES,
407
+ videoKeyframeRows,
408
+ videoKeyframeToggleEdit,
409
+ videoKeyframeValueEdit
405
410
  } from "@pixerate/editor/video-editor";
406
411
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
407
412
  var field = "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs disabled:opacity-50";
@@ -438,7 +443,7 @@ function Row({ label, name, children }) {
438
443
  }
439
444
  function ClipInspector({ controller, className = "" }) {
440
445
  const state = useVideoEditorState(controller);
441
- const { project, selection, readOnly } = state;
446
+ const { project, selection, readOnly, time } = state;
442
447
  if (selection.length !== 1)
443
448
  return /* @__PURE__ */ jsx4("p", { className: `text-xs text-muted-foreground ${className}`, "data-clip-inspector": "", children: selection.length ? `${selection.length} clips selected. Select one clip to edit it.` : "Select a clip on the timeline to edit it." });
444
449
  const at = findVideoClip(project, selection[0]);
@@ -473,11 +478,48 @@ function ClipInspector({ controller, className = "" }) {
473
478
  /* @__PURE__ */ jsx4(Row, { label: "Name", name: "name", children: text("name", "Name") }),
474
479
  /* @__PURE__ */ jsx4(Row, { label: "Start", name: "start", children: /* @__PURE__ */ jsx4("span", { className: "tabular-nums text-xs", children: formatVideoSeconds2(clip.start) }) }),
475
480
  /* @__PURE__ */ jsx4(Row, { label: "Duration", name: "duration", children: /* @__PURE__ */ jsx4("span", { className: "tabular-nums text-xs", children: formatVideoSeconds2(clip.duration) }) }),
476
- clip.type !== "media" || project.assets.find((a) => a.id === clip.asset)?.kind !== "audio" ? /* @__PURE__ */ jsx4(Row, { label: "Opacity", name: "opacity", children: number("opacity", "Opacity", { min: 0, max: 1, step: 0.05 }) }) : null,
481
+ videoKeyframeRows(project, clip.id, time).map((row) => /* @__PURE__ */ jsx4(Row, { label: row.label, name: row.property, children: /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-1", "data-keyframe-row": row.property, children: [
482
+ /* @__PURE__ */ jsx4(
483
+ Commit,
484
+ {
485
+ type: "number",
486
+ value: row.keyed || (row.property === "opacity" ? clip.opacity : row.property === "volume" ? clip.volume : clip.transform?.[row.property]) !== void 0 ? String(row.value) : "",
487
+ placeholder: String(row.value),
488
+ "aria-label": row.label,
489
+ disabled,
490
+ min: row.min,
491
+ max: row.max,
492
+ step: row.step,
493
+ onCommit: (raw) => {
494
+ const n = Number(raw);
495
+ if (raw.trim() && !Number.isFinite(n)) return;
496
+ const edit = videoKeyframeValueEdit(project, clip.id, row.property, time, raw.trim() ? n : void 0);
497
+ if (edit) controller.apply(edit, row.keyed ? `Keyframe ${row.label.toLowerCase()}` : `Change ${row.label.toLowerCase()}`);
498
+ }
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsx4(
502
+ "button",
503
+ {
504
+ type: "button",
505
+ className: `shrink-0 rounded px-1 text-xs disabled:opacity-50 ${row.onKey ? "text-amber-500" : row.keyed ? "text-foreground" : "text-muted-foreground"}`,
506
+ "aria-label": `${row.onKey ? "Remove" : "Add"} ${row.label.toLowerCase()} keyframe at playhead`,
507
+ "aria-pressed": row.onKey,
508
+ title: row.keyed ? `${row.keys} key${row.keys === 1 ? "" : "s"}; ${row.onKey ? "remove the one" : "add one"} at the playhead` : "Animate: add a key at the playhead",
509
+ disabled,
510
+ "data-keyframe-toggle": row.property,
511
+ onClick: () => {
512
+ const edit = videoKeyframeToggleEdit(project, clip.id, row.property, time);
513
+ if (edit) controller.apply(edit, `${row.onKey ? "Remove" : "Add"} ${row.label.toLowerCase()} keyframe`);
514
+ },
515
+ children: row.onKey ? "\u25C6" : "\u25C7"
516
+ }
517
+ ),
518
+ row.keyed && /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[10px] tabular-nums text-muted-foreground", "data-keyframe-count": row.property, children: row.keys })
519
+ ] }) }, row.property)),
477
520
  /* @__PURE__ */ jsx4(Row, { label: "Fade in (ms)", name: "fadeIn", children: number("fadeIn", "Fade in", { min: 0, step: 100 }) }),
478
521
  /* @__PURE__ */ jsx4(Row, { label: "Fade out (ms)", name: "fadeOut", children: number("fadeOut", "Fade out", { min: 0, step: 100 }) }),
479
522
  clip.type === "media" && /* @__PURE__ */ jsxs3(Fragment3, { children: [
480
- /* @__PURE__ */ jsx4(Row, { label: "Volume", name: "volume", children: number("volume", "Volume", { min: 0, step: 0.1 }) }),
481
523
  /* @__PURE__ */ jsx4(Row, { label: "Speed", name: "speed", children: number("speed", "Speed", { min: 0.1, step: 0.25 }) }),
482
524
  /* @__PURE__ */ jsx4(Row, { label: "Muted", name: "muted", children: /* @__PURE__ */ jsx4("input", { type: "checkbox", className: "justify-self-start", checked: !!clip.muted, disabled, "aria-label": "Muted", onChange: (e) => set("muted", e.target.checked || void 0, e.target.checked ? "Mute clip" : "Unmute clip") }) })
483
525
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/video-editor/useVideoEditor.ts","../../src/video-editor/VideoPreview.tsx","../../src/video-editor/VideoTransport.tsx","../../src/video-editor/VideoTimeline.tsx","../../src/video-editor/ClipInspector.tsx","../../src/video-editor/MediaBin.tsx","../../src/video-editor/VideoEditor.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n VideoEditorController,\n createVideoEditorToolProvider,\n type VideoEditResult,\n type VideoEditorOptions,\n type VideoEditorState,\n type VideoEditorToolProviderOptions,\n} from '@pixerate/editor/video-editor';\nimport type { ToolRegistry } from '@pixerate/editor/tools';\n\n/** A `VideoEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useVideoEditorController(options: VideoEditorOptions = {}): VideoEditorController {\n const ref = useRef<VideoEditorController | null>(null);\n if (!ref.current) ref.current = new VideoEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change, including each played frame). */\nexport function useVideoEditorState(controller: VideoEditorController): VideoEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseVideoEditorResult {\n controller: VideoEditorController;\n state: VideoEditorState;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n /** Moves the playhead (ms). */\n seek: (time: number) => void;\n /** Moves the playhead by whole frames (negative steps back), pausing. */\n step: (frames: number) => void;\n undo: () => void;\n redo: () => void;\n /** Splits the selected clips (or every clip) under the playhead, as one undo step. */\n splitAtPlayhead: () => VideoEditResult;\n /** Deletes the selected clips; `ripple` closes the gaps they leave. */\n deleteSelection: (options?: { ripple?: boolean }) => VideoEditResult;\n}\n\n/**\n * Subscribes to a video editor controller and exposes its state with bound actions.\n * Pass options instead of a controller to create one for the component's lifetime.\n */\nexport function useVideoEditor(controllerOrOptions: VideoEditorController | VideoEditorOptions = {}): UseVideoEditorResult {\n const own = useRef<VideoEditorController | null>(null);\n let controller: VideoEditorController;\n if (controllerOrOptions instanceof VideoEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new VideoEditorController(controllerOrOptions);\n controller = own.current;\n }\n useEffect(() => () => own.current?.destroy(), []);\n const state = useVideoEditorState(controller);\n const actions = useMemo(\n () => ({\n play: () => controller.play(),\n pause: () => controller.pause(),\n toggle: () => controller.toggle(),\n seek: (time: number) => controller.seek(time),\n step: (frames: number) => controller.step(frames),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n splitAtPlayhead: () => controller.splitAtPlayhead(),\n deleteSelection: (options?: { ripple?: boolean }) => controller.deleteSelection(options),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/**\n * Registers the video editor's agent tools (`get_video_project`, `add_video_clip`,\n * `trim_video_clip`, …) in `registry` while mounted.\n */\nexport function useVideoEditorTools(\n registry: ToolRegistry | null | undefined,\n controller: VideoEditorController,\n options: VideoEditorToolProviderOptions = {},\n): void {\n const { id, namespace, readOnly, rasterize } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createVideoEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));\n }, [registry, controller, id, namespace, readOnly, rasterize]);\n}\n","import React, { useEffect, useRef } from 'react';\nimport { VideoPreviewEngine, type VideoEditorController, type VideoPreviewOptions } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoPreviewProps {\n controller: VideoEditorController;\n /** Canvas pixels per frame pixel (default: fits the displayed width at the device pixel ratio, at most 1). */\n resolution?: number;\n /** Called after each frame is drawn. */\n onFrame?: VideoPreviewOptions['onFrame'];\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Plays the controller's project on a canvas the shape of its frame: a `VideoPreviewEngine` keeps\n * one `<video>` / `<audio>` element per playing clip in step with the playhead and draws each frame.\n * The engine is created when the canvas mounts and destroyed on unmount or when the controller changes.\n */\nexport function VideoPreview({ controller, resolution, onFrame, className = '', style }: VideoPreviewProps) {\n const { project } = useVideoEditorState(controller);\n const canvas = useRef<HTMLCanvasElement>(null);\n const frameRef = useRef(onFrame);\n frameRef.current = onFrame;\n useEffect(() => {\n const el = canvas.current;\n if (!el) return;\n const engine = new VideoPreviewEngine(controller, el, { resolution, onFrame: (frame) => frameRef.current?.(frame) });\n return () => engine.destroy();\n }, [controller, resolution]);\n return (\n <canvas\n ref={canvas}\n className={className}\n style={{ display: 'block', width: '100%', aspectRatio: `${project.width} / ${project.height}`, background: project.background ?? '#000', ...style }}\n role=\"img\"\n aria-label={`Preview of ${project.name ?? 'the video'}`}\n data-video-preview=\"\"\n />\n );\n}\n","import React from 'react';\nimport { formatVideoTimecode, type VideoEditorController } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTransportProps {\n controller: VideoEditorController;\n /** Show the editing buttons (split, delete, ripple delete, undo, redo; default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * Play / pause, a frame back and forward, the playhead and total length as timecode, and the\n * edits that work on the playhead and the selection: split, delete, ripple delete, undo and redo.\n */\nexport function VideoTransport({ controller, editable = true, className = '' }: VideoTransportProps) {\n const state = useVideoEditorState(controller);\n const { time, duration, playing, project, selection, readOnly } = state;\n const canEdit = editable && !readOnly;\n return (\n <div className={`flex flex-wrap items-center gap-1.5 text-xs ${className}`} data-video-transport=\"\">\n <button type=\"button\" className={btn} onClick={() => controller.step(-1)} aria-label=\"Previous frame\" title=\"Previous frame (←)\" data-step-back=\"\">\n ◀|\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.toggle()} aria-label={playing ? 'Pause' : 'Play'} title=\"Play / pause (Space)\" disabled={duration <= 0} data-play=\"\">\n {playing ? 'Pause' : 'Play'}\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.step(1)} aria-label=\"Next frame\" title=\"Next frame (→)\" data-step-forward=\"\">\n |▶\n </button>\n <span className=\"px-1 tabular-nums text-muted-foreground\" data-timecode=\"\">\n {formatVideoTimecode(time, project.fps)} / {formatVideoTimecode(duration, project.fps)}\n </span>\n {editable && (\n <>\n <span className=\"mx-1 h-4 w-px bg-border\" />\n <button type=\"button\" className={btn} onClick={() => controller.splitAtPlayhead()} disabled={!canEdit} title=\"Split at the playhead (S)\" data-split=\"\">\n Split\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.deleteSelection()} disabled={!canEdit || !selection.length} title=\"Delete the selected clips (Delete)\" data-delete=\"\">\n Delete\n </button>\n <button\n type=\"button\"\n className={btn}\n onClick={() => controller.deleteSelection({ ripple: true })}\n disabled={!canEdit || !selection.length}\n title=\"Delete the selected clips and close the gaps (Shift+Delete)\"\n data-ripple-delete=\"\"\n >\n Ripple delete\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.undo()} disabled={!canEdit || !state.canUndo} title={state.canUndo ? `Undo ${state.undoLabel}` : 'Nothing to undo'} data-undo=\"\">\n Undo\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.redo()} disabled={!canEdit || !state.canRedo} title={state.canRedo ? `Redo ${state.redoLabel}` : 'Nothing to redo'} data-redo=\"\">\n Redo\n </button>\n </>\n )}\n </div>\n );\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport {\n planVideoClipDrag,\n videoAssetMap,\n videoClipLabel,\n videoClipTint,\n videoDropTracks,\n videoRulerTicks,\n formatVideoSeconds,\n type VideoClip,\n type VideoClipDragMode,\n type VideoClipDragPlan,\n type VideoEditorController,\n type VideoTrack,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTimelineProps {\n controller: VideoEditorController;\n /** Zoom: pixels per second of timeline. With `onPxPerSecondChange` the host owns it; otherwise it is the starting zoom (default 100). */\n pxPerSecond?: number;\n /** Called when the zoom control changes. Without it the timeline keeps its own zoom and shows a zoom slider. */\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Allow selecting, dragging, trimming and the track toggles (default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst HEADER = 150;\nconst RULER = 24;\nconst ROW = 44;\nconst HANDLE = 6;\nconst DRAG_THRESHOLD = 3;\nconst MIN_ZOOM = 10;\nconst MAX_ZOOM = 400;\n\ninterface Drag {\n clip: string;\n mode: VideoClipDragMode;\n x: number;\n pointer: number;\n moved: boolean;\n plan: VideoClipDragPlan | null;\n}\n\nconst isField = (el: EventTarget | null) => el instanceof HTMLElement && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);\n\n/**\n * The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it\n * to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.\n * Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another\n * track of the same kind too), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,\n * markers, the playhead and frames. Every edit is one undo step; refused edits show why.\n *\n * Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes\n * the selection (Shift to ripple), Cmd/Ctrl+Z undoes, Shift+Cmd/Ctrl+Z redoes.\n */\nexport function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable = true, className = '' }: VideoTimelineProps) {\n const state = useVideoEditorState(controller);\n const { project, duration, time, selection, readOnly, error } = state;\n const [ownZoom, setOwnZoom] = useState(pxPerSecond ?? 100);\n const px = onPxPerSecondChange ? (pxPerSecond ?? 100) : ownZoom;\n const canEdit = editable && !readOnly;\n const [drag, setDrag] = useState<Drag | null>(null);\n const dragRef = useRef<Drag | null>(null);\n const rows = useRef(new Map<string, HTMLDivElement>());\n const ruler = useRef<HTMLDivElement>(null);\n const assets = videoAssetMap(project);\n const msToPx = (ms: number) => (ms * px) / 1000;\n const span = Math.max(duration + 5000, 10000);\n const width = msToPx(span);\n const ticks = videoRulerTicks(span, px, project.fps);\n\n const seekAt = (clientX: number) => {\n const box = ruler.current!.getBoundingClientRect();\n controller.seek(Math.max(0, ((clientX - box.left) * 1000) / px));\n };\n\n const trackAt = (clipId: string, clientY: number): string | undefined => {\n const allowed = videoDropTracks(project, clipId);\n for (const [id, el] of rows.current) {\n const box = el.getBoundingClientRect();\n if (clientY >= box.top && clientY < box.bottom && allowed.includes(id)) return id;\n }\n return undefined;\n };\n\n const planFor = (d: Drag, e: { clientX: number; clientY: number; altKey: boolean }) =>\n planVideoClipDrag(project, {\n clip: d.clip,\n mode: d.mode,\n delta: ((e.clientX - d.x) * 1000) / px,\n track: d.mode === 'move' ? trackAt(d.clip, e.clientY) : undefined,\n ripple: e.altKey,\n snap: (t) => controller.snap(t, { exclude: [d.clip] }).time,\n });\n\n const update = (next: Drag | null) => {\n dragRef.current = next;\n setDrag(next);\n };\n\n // The latest render's values, for the window listeners of a drag in progress.\n const live = useRef({ planFor, selection });\n live.current = { planFor, selection };\n const stopDrag = useRef<(() => void) | null>(null);\n useEffect(() => () => stopDrag.current?.(), []);\n\n const onClipPointerDown = (e: React.PointerEvent<HTMLDivElement>, clip: VideoClip, track: VideoTrack) => {\n if (e.button !== undefined && e.button !== 0) return;\n e.stopPropagation();\n if (!editable) return;\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n const handle = (e.target as HTMLElement).closest('[data-handle]')?.getAttribute('data-handle');\n const mode: VideoClipDragMode = handle === 'start' ? 'trim-start' : handle === 'end' ? 'trim-end' : 'move';\n const fixed = !canEdit || !!track.locked;\n stopDrag.current?.();\n update({ clip: clip.id, mode, x: e.clientX, pointer: e.pointerId, moved: false, plan: null });\n // Follow the pointer on the window: the block may move to another track's row (a new element) mid-drag.\n const move = (ev: PointerEvent) => {\n const d = dragRef.current;\n if (!d || fixed) return;\n if (!d.moved && Math.abs(ev.clientX - d.x) < DRAG_THRESHOLD) return;\n if (!d.moved && !live.current.selection.includes(d.clip)) controller.select([d.clip]);\n update({ ...d, moved: true, plan: live.current.planFor(d, ev) });\n };\n const up = (ev: PointerEvent) => {\n stop();\n const d = dragRef.current;\n if (!d) return;\n update(null);\n if (!d.moved) {\n if (ev.shiftKey || ev.metaKey || ev.ctrlKey) controller.select([d.clip], { add: true });\n else controller.select([d.clip]);\n return;\n }\n const plan = live.current.planFor(d, ev);\n if (plan?.edit) controller.apply(plan.edit, d.mode === 'move' ? 'Move clip' : ev.altKey ? 'Ripple trim' : 'Trim clip');\n };\n const cancel = () => {\n stop();\n update(null);\n };\n const stop = () => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n window.removeEventListener('pointercancel', cancel);\n stopDrag.current = null;\n };\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n window.addEventListener('pointercancel', cancel);\n stopDrag.current = stop;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (isField(e.target)) return;\n const mod = e.metaKey || e.ctrlKey;\n const key = e.key.toLowerCase();\n if (e.key === ' ') {\n if ((e.target as HTMLElement).tagName === 'BUTTON') return;\n controller.toggle();\n } else if (e.key === 'ArrowLeft' && !mod) controller.step(-1);\n else if (e.key === 'ArrowRight' && !mod) controller.step(1);\n else if (e.key === 'Escape') controller.select(null);\n else if (!canEdit) return;\n else if (key === 's' && !mod) controller.splitAtPlayhead();\n else if ((e.key === 'Delete' || e.key === 'Backspace') && !mod) controller.deleteSelection({ ripple: e.shiftKey });\n else if (key === 'z' && mod) {\n if (e.shiftKey) controller.redo();\n else controller.undo();\n } else return;\n e.preventDefault();\n };\n\n // Where each clip is drawn: the dragged one follows the plan (possibly on another track).\n const placed = project.tracks.map((track) => ({\n track,\n clips: project.tracks.flatMap((t) =>\n t.clips\n .map((clip) => {\n const plan = drag?.moved && drag.clip === clip.id ? drag.plan : null;\n const on = plan ? plan.track : t.id;\n return on === track.id ? { clip, home: t, start: plan ? plan.start : clip.start, duration: plan ? plan.duration : clip.duration, dragging: !!plan } : null;\n })\n .filter((x): x is NonNullable<typeof x> => x !== null),\n ),\n }));\n\n const toggle = (track: VideoTrack, field: 'muted' | 'hidden' | 'locked', label: string) => (\n <button\n type=\"button\"\n className={`rounded border px-1 text-[10px] leading-4 ${track[field] ? 'border-rose-500 bg-rose-500/15 text-rose-600' : 'border-border text-muted-foreground'} disabled:opacity-40`}\n aria-pressed={!!track[field]}\n aria-label={`${label} ${track.name ?? track.id}`}\n title={label}\n disabled={!canEdit}\n data-track-toggle={field}\n onClick={() => controller.apply({ op: 'update_track', track: track.id, set: { [field]: !track[field] } }, `${track[field] ? 'Un' : ''}${field === 'muted' ? 'mute' : field === 'hidden' ? 'hide' : 'lock'} track`)}\n >\n {field === 'muted' ? 'M' : field === 'hidden' ? 'H' : 'L'}\n </button>\n );\n\n return (\n <div className={`flex flex-col gap-1 ${className}`} data-video-timeline-wrapper=\"\">\n {!onPxPerSecondChange && (\n <label className=\"flex items-center gap-2 self-end text-xs text-muted-foreground\">\n Zoom\n <input type=\"range\" min={MIN_ZOOM} max={MAX_ZOOM} step={5} value={ownZoom} aria-label=\"Timeline zoom\" onChange={(e) => setOwnZoom(Number(e.target.value))} data-zoom=\"\" />\n </label>\n )}\n <div\n className=\"relative overflow-x-auto rounded border border-border outline-none focus-visible:ring-2 focus-visible:ring-rose-500/50\"\n tabIndex={0}\n role=\"group\"\n aria-label=\"Video timeline\"\n onKeyDown={onKeyDown}\n data-video-timeline=\"\"\n >\n <div className=\"relative\" style={{ width: HEADER + width }}>\n <div className=\"flex\" style={{ height: RULER }}>\n <div className=\"sticky left-0 z-20 shrink-0 border-b border-r border-border bg-background\" style={{ width: HEADER }} />\n <div\n ref={ruler}\n className=\"relative shrink-0 cursor-ew-resize select-none touch-none border-b border-border text-[10px] text-muted-foreground\"\n style={{ width }}\n role=\"slider\"\n aria-label=\"Playhead\"\n aria-valuemin={0}\n aria-valuemax={duration}\n aria-valuenow={Math.round(time)}\n data-ruler=\"\"\n onPointerDown={(e) => {\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n seekAt(e.clientX);\n }}\n onPointerMove={(e) => {\n if (e.buttons & 1) seekAt(e.clientX);\n }}\n >\n {ticks.map((t) => (\n <div key={t.time} className={`absolute bottom-0 w-px ${t.major ? 'h-2.5 bg-muted-foreground/70' : 'h-1 bg-muted-foreground/40'}`} style={{ left: t.x }} data-tick={t.time}>\n {t.label && <span className=\"absolute -top-3.5 left-1 whitespace-nowrap tabular-nums\">{t.label}</span>}\n </div>\n ))}\n {(project.markers ?? []).map((m) => (\n <div\n key={m.id}\n className=\"absolute top-0 h-2.5 w-2.5 -translate-x-1/2 rotate-45\"\n style={{ left: msToPx(m.time), background: m.color ?? '#f59e0b' }}\n title={`${m.label ?? m.id} at ${formatVideoSeconds(m.time)}`}\n data-marker={m.id}\n />\n ))}\n </div>\n </div>\n {placed.map(({ track, clips }) => (\n <div key={track.id} className=\"flex border-b border-border last:border-b-0\" style={{ height: ROW }} data-track={track.id}>\n <div className=\"sticky left-0 z-20 flex shrink-0 flex-col justify-center gap-0.5 border-r border-border bg-background px-2 text-xs\" style={{ width: HEADER }} data-track-header=\"\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate font-medium\">{track.name ?? track.id}</span>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-track-kind=\"\">\n {track.kind}\n </span>\n </div>\n {editable && (\n <div className=\"flex gap-1\">\n {toggle(track, 'muted', 'Mute')}\n {track.kind === 'video' && toggle(track, 'hidden', 'Hide')}\n {toggle(track, 'locked', 'Lock')}\n </div>\n )}\n </div>\n <div\n ref={(el) => {\n if (el) rows.current.set(track.id, el);\n else rows.current.delete(track.id);\n }}\n className={`relative shrink-0 ${track.hidden ? 'opacity-50' : ''} ${track.locked ? 'bg-muted/40' : ''}`}\n style={{ width }}\n data-track-lane={track.id}\n onPointerDown={(e) => {\n if (e.target === e.currentTarget && editable) controller.select(null);\n }}\n >\n {clips.map(({ clip, home, start, duration: length, dragging }) => {\n const asset = clip.type === 'media' ? assets.get(clip.asset) : undefined;\n const selected = selection.includes(clip.id);\n const label = videoClipLabel(clip, asset);\n return (\n <div\n key={clip.id}\n className={`absolute top-1 bottom-1 select-none touch-none overflow-hidden rounded text-[11px] text-white ${canEdit && !home.locked ? 'cursor-grab' : 'cursor-default'} ${dragging ? 'opacity-80 shadow-lg' : ''}`}\n style={{\n left: msToPx(start),\n width: Math.max(2, msToPx(length)),\n background: videoClipTint(clip, asset),\n outline: selected ? '2px solid #f43f5e' : undefined,\n outlineOffset: selected ? 1 : undefined,\n zIndex: dragging ? 10 : selected ? 2 : 1,\n }}\n title={`${label} · ${formatVideoSeconds(start)}–${formatVideoSeconds(start + length)}`}\n data-clip={clip.id}\n data-selected={selected ? '' : undefined}\n data-dragging={dragging ? '' : undefined}\n aria-selected={selected}\n onPointerDown={(e) => onClipPointerDown(e, clip, home)}\n >\n <span className=\"pointer-events-none block truncate px-1.5 py-0.5 leading-4\">{label}</span>\n {clip.transition && (\n <span className=\"pointer-events-none absolute left-1 bottom-0.5 rounded bg-black/40 px-1 text-[9px] leading-3\" title={`${clip.transition.type} ${clip.transition.duration} ms`} data-transition={clip.transition.type}>\n ⧓ {clip.transition.type}\n </span>\n )}\n {canEdit && !home.locked && (\n <>\n <div className=\"absolute inset-y-0 left-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"start\" aria-hidden=\"true\" />\n <div className=\"absolute inset-y-0 right-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"end\" aria-hidden=\"true\" />\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n ))}\n <div className=\"pointer-events-none absolute top-0 bottom-0 w-0.5 -translate-x-1/2 bg-rose-500\" style={{ left: HEADER + msToPx(time), zIndex: 15 }} data-playhead=\"\" />\n </div>\n </div>\n {error && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-edit-error=\"\">\n {error}\n </p>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport {\n findVideoClip,\n formatVideoSeconds,\n VIDEO_TRANSITION_TYPES,\n type VideoClip,\n type VideoEditorController,\n type VideoTransitionType,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface ClipInspectorProps {\n controller: VideoEditorController;\n className?: string;\n}\n\nconst field = 'w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs disabled:opacity-50';\nconst DEFAULT_TRANSITION = 500;\n\n/** An input (or textarea) that commits on blur or Enter, and resets when its value changes from outside. */\nfunction Commit({\n value,\n onCommit,\n multiline,\n ...rest\n}: { value: string; onCommit: (raw: string) => void; multiline?: boolean } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue'>) {\n const commit = (raw: string) => {\n if (raw !== value) onCommit(raw);\n };\n if (multiline)\n return (\n <textarea\n key={value}\n className={field}\n rows={2}\n defaultValue={value}\n disabled={rest.disabled}\n aria-label={rest['aria-label']}\n onBlur={(e) => commit(e.currentTarget.value)}\n />\n );\n return <input key={value} className={field} {...rest} defaultValue={value} onKeyDown={(e) => e.key === 'Enter' && e.currentTarget.blur()} onBlur={(e) => commit(e.currentTarget.value)} />;\n}\n\nfunction Row({ label, name, children }: { label: string; name: string; children: React.ReactNode }) {\n return (\n <label className=\"grid grid-cols-[5.5rem_minmax(0,1fr)] items-center gap-2 text-xs\" data-field={name}>\n <span className=\"text-muted-foreground\">{label}</span>\n {children}\n </label>\n );\n}\n\n/**\n * Edits the one selected clip: its name, opacity and fades; volume, speed and mute for media;\n * text, colour, size and background for titles; colour for colour clips; and the transition into\n * it. Each change is one undo step (`update_clip` / `set_transition`), committed on blur or change.\n */\nexport function ClipInspector({ controller, className = '' }: ClipInspectorProps) {\n const state = useVideoEditorState(controller);\n const { project, selection, readOnly } = state;\n if (selection.length !== 1)\n return (\n <p className={`text-xs text-muted-foreground ${className}`} data-clip-inspector=\"\">\n {selection.length ? `${selection.length} clips selected. Select one clip to edit it.` : 'Select a clip on the timeline to edit it.'}\n </p>\n );\n const at = findVideoClip(project, selection[0]);\n if (!at) return null;\n const clip: VideoClip = at.clip;\n const disabled = readOnly || !!at.track.locked;\n\n const set = (key: string, value: unknown, label = `Change ${key}`) => {\n if (value === undefined || value === '') controller.apply({ op: 'update_clip', clip: clip.id, remove: [key] }, label);\n else controller.apply({ op: 'update_clip', clip: clip.id, set: { [key]: value } }, label);\n };\n const num = (key: string, raw: string, label?: string) => {\n const trimmed = raw.trim();\n if (!trimmed) return set(key, undefined, label);\n const n = Number(trimmed);\n if (Number.isFinite(n)) set(key, n, label);\n };\n const text = (key: string, label?: string) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return (\n <Commit value={value === undefined ? '' : String(value)} aria-label={label ?? key} disabled={disabled} onCommit={(raw) => set(key, raw.trim() || undefined, `Change ${label ?? key}`)} />\n );\n };\n const number = (key: string, label: string, attrs: { min?: number; max?: number; step?: number } = {}) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return <Commit type=\"number\" value={value === undefined ? '' : String(value)} aria-label={label} disabled={disabled} onCommit={(raw) => num(key, raw, `Change ${label.toLowerCase()}`)} {...attrs} />;\n };\n\n const transition = clip.transition;\n return (\n <div className={`flex flex-col gap-1.5 ${className}`} data-clip-inspector=\"\" data-clip-id={clip.id}>\n <div className=\"flex items-center gap-2 text-xs\">\n <span className=\"font-medium\">{clip.type === 'media' ? 'Media clip' : clip.type === 'text' ? 'Title' : 'Colour clip'}</span>\n <code className=\"text-muted-foreground\">{clip.id}</code>\n {at.track.locked && <span className=\"text-amber-500\">locked track</span>}\n </div>\n <Row label=\"Name\" name=\"name\">\n {text('name', 'Name')}\n </Row>\n <Row label=\"Start\" name=\"start\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.start)}</span>\n </Row>\n <Row label=\"Duration\" name=\"duration\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.duration)}</span>\n </Row>\n {clip.type !== 'media' || project.assets.find((a) => a.id === clip.asset)?.kind !== 'audio' ? (\n <Row label=\"Opacity\" name=\"opacity\">\n {number('opacity', 'Opacity', { min: 0, max: 1, step: 0.05 })}\n </Row>\n ) : null}\n <Row label=\"Fade in (ms)\" name=\"fadeIn\">\n {number('fadeIn', 'Fade in', { min: 0, step: 100 })}\n </Row>\n <Row label=\"Fade out (ms)\" name=\"fadeOut\">\n {number('fadeOut', 'Fade out', { min: 0, step: 100 })}\n </Row>\n {clip.type === 'media' && (\n <>\n <Row label=\"Volume\" name=\"volume\">\n {number('volume', 'Volume', { min: 0, step: 0.1 })}\n </Row>\n <Row label=\"Speed\" name=\"speed\">\n {number('speed', 'Speed', { min: 0.1, step: 0.25 })}\n </Row>\n <Row label=\"Muted\" name=\"muted\">\n <input type=\"checkbox\" className=\"justify-self-start\" checked={!!clip.muted} disabled={disabled} aria-label=\"Muted\" onChange={(e) => set('muted', e.target.checked || undefined, e.target.checked ? 'Mute clip' : 'Unmute clip')} />\n </Row>\n </>\n )}\n {clip.type === 'text' && (\n <>\n <Row label=\"Text\" name=\"text\">\n <Commit multiline value={clip.text} aria-label=\"Text\" disabled={disabled} onCommit={(raw) => set('text', raw, 'Change text')} />\n </Row>\n <Row label=\"Colour\" name=\"color\">\n {text('color', 'Colour')}\n </Row>\n <Row label=\"Font size\" name=\"fontSize\">\n {number('fontSize', 'Font size', { min: 1, step: 2 })}\n </Row>\n <Row label=\"Background\" name=\"background\">\n {text('background', 'Background')}\n </Row>\n </>\n )}\n {clip.type === 'color' && (\n <Row label=\"Colour\" name=\"color\">\n <Commit value={clip.color} aria-label=\"Colour\" disabled={disabled} onCommit={(raw) => raw.trim() && set('color', raw.trim(), 'Change colour')} />\n </Row>\n )}\n <Row label=\"Transition in\" name=\"transition\">\n <select\n className={field}\n value={transition?.type ?? ''}\n disabled={disabled}\n aria-label=\"Transition\"\n onChange={(e) => {\n const type = e.target.value as VideoTransitionType | '';\n controller.apply(\n { op: 'set_transition', clip: clip.id, transition: type ? { ...transition, type, duration: transition?.duration ?? DEFAULT_TRANSITION } : null },\n type ? 'Set transition' : 'Remove transition',\n );\n }}\n >\n <option value=\"\">None</option>\n {VIDEO_TRANSITION_TYPES.map((t) => (\n <option key={t} value={t}>\n {t === 'crossfade' ? 'Crossfade' : t === 'dip' ? 'Dip to colour' : 'Wipe'}\n </option>\n ))}\n </select>\n </Row>\n {transition && (\n <Row label=\"Length (ms)\" name=\"transitionDuration\">\n <Commit\n type=\"number\"\n min={1}\n step={100}\n value={String(transition.duration)}\n aria-label=\"Transition length\"\n disabled={disabled}\n onCommit={(raw) => {\n const n = Number(raw);\n if (raw.trim() && Number.isFinite(n) && n > 0) controller.apply({ op: 'set_transition', clip: clip.id, transition: { ...transition, duration: n } }, 'Change transition length');\n }}\n />\n </Row>\n )}\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport {\n formatVideoSeconds,\n probeVideoMedia,\n videoAssetDropEdit,\n videoAssetKindOf,\n type VideoAsset,\n type VideoAssetKind,\n type VideoEditorController,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface MediaBinProps {\n controller: VideoEditorController;\n /** Show the import controls and \"Add to timeline\" buttons (default true). */\n editable?: boolean;\n /** Turns a file into a URL the preview and exporter can open (default `URL.createObjectURL`). */\n fileToUrl?: (file: File) => string;\n /** Reads a source's length and size (default `probeVideoMedia`). */\n probe?: typeof probeVideoMedia;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * The project's assets (name, kind, length), each with an \"Add to timeline\" button that puts it at\n * the end of the first unlocked track of its kind. Import media by choosing or dropping files (read\n * as object URLs and probed for their length and size) or by URL. Each import is one undo step.\n */\nexport function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMedia, className = '' }: MediaBinProps) {\n const { project, readOnly } = useVideoEditorState(controller);\n const canEdit = editable && !readOnly;\n const [url, setUrl] = useState('');\n const [urlKind, setUrlKind] = useState<VideoAssetKind | ''>('');\n const [busy, setBusy] = useState(0);\n const [problem, setProblem] = useState<string | null>(null);\n const [over, setOver] = useState(false);\n\n const addSource = async (src: string, kind: VideoAssetKind, name: string) => {\n setBusy((n) => n + 1);\n try {\n const found = await probe(src, kind).catch(() => null);\n const result = controller.apply({ op: 'add_asset', asset: { kind, src, name, ...(found ?? {}) } }, `Import ${name}`);\n if (!result.ok) setProblem(result.error);\n } finally {\n setBusy((n) => n - 1);\n }\n };\n\n const addFiles = async (files: FileList | File[] | null) => {\n setProblem(null);\n for (const file of Array.from(files ?? [])) {\n const kind = videoAssetKindOf(file.type || file.name);\n if (!kind) {\n setProblem(`\"${file.name}\" is not a video, audio or image file.`);\n continue;\n }\n const src = fileToUrl ? fileToUrl(file) : URL.createObjectURL(file);\n await addSource(src, kind, file.name);\n }\n };\n\n const addUrl = async () => {\n const src = url.trim();\n if (!src) return;\n const kind = urlKind || videoAssetKindOf(src.replace(/[?#].*$/, ''));\n if (!kind) {\n setProblem(`Can't tell whether \"${src}\" is video, audio or an image; choose its kind.`);\n return;\n }\n setProblem(null);\n await addSource(src, kind, src.replace(/[?#].*$/, '').replace(/^.*[\\\\/]/, '') || src);\n setUrl('');\n };\n\n const addToTimeline = (asset: VideoAsset) => {\n const edit = videoAssetDropEdit(project, asset.id);\n if (!edit) {\n setProblem(`There is no unlocked ${asset.kind === 'audio' ? 'audio' : 'video'} track to put \"${asset.name ?? asset.id}\" on.`);\n return;\n }\n setProblem(null);\n const result = controller.apply(edit, `Add ${asset.name ?? asset.id}`);\n if (result.ok) {\n const added = result.outcomes[0]?.clip;\n if (added) controller.select([added]);\n }\n };\n\n return (\n <div\n className={`flex flex-col gap-2 rounded ${over ? 'ring-2 ring-rose-500/60' : ''} ${className}`}\n data-media-bin=\"\"\n onDragOver={(e) => {\n if (!canEdit || !e.dataTransfer?.types?.includes('Files')) return;\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n setOver(false);\n if (!canEdit || !e.dataTransfer?.files?.length) return;\n e.preventDefault();\n void addFiles(e.dataTransfer.files);\n }}\n >\n <div className=\"text-xs font-medium\">Media</div>\n {project.assets.length ? (\n <ul className=\"flex flex-col gap-1\" aria-label=\"Media assets\">\n {project.assets.map((asset) => (\n <li key={asset.id} className=\"flex items-center gap-2 text-xs\" data-asset={asset.id}>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-asset-kind=\"\">\n {asset.kind}\n </span>\n <span className=\"min-w-0 flex-1 truncate\" title={asset.src}>\n {asset.name ?? asset.id}\n </span>\n {asset.duration !== undefined && <span className=\"tabular-nums text-muted-foreground\">{formatVideoSeconds(asset.duration)}</span>}\n {editable && (\n <button type=\"button\" className={btn} disabled={!canEdit} onClick={() => addToTimeline(asset)} data-add-to-timeline=\"\">\n Add to timeline\n </button>\n )}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-xs text-muted-foreground\">No media yet.{editable ? ' Choose or drop files, or add a URL.' : ''}</p>\n )}\n {editable && (\n <>\n <label className=\"flex flex-col gap-1 rounded border border-dashed border-border p-2 text-xs text-muted-foreground\">\n Choose or drop video, audio or image files\n <input\n type=\"file\"\n multiple\n accept=\"video/*,audio/*,image/*\"\n disabled={!canEdit}\n aria-label=\"Import media files\"\n data-import-files=\"\"\n onChange={(e) => {\n const files = e.currentTarget.files ? Array.from(e.currentTarget.files) : [];\n e.currentTarget.value = '';\n void addFiles(files);\n }}\n />\n </label>\n <form\n className=\"flex items-center gap-1\"\n onSubmit={(e) => {\n e.preventDefault();\n void addUrl();\n }}\n >\n <input\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n placeholder=\"https://…/clip.mp4\"\n value={url}\n disabled={!canEdit}\n aria-label=\"Media URL\"\n onChange={(e) => setUrl(e.target.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" value={urlKind} disabled={!canEdit} aria-label=\"Media kind\" onChange={(e) => setUrlKind(e.target.value as VideoAssetKind | '')}>\n <option value=\"\">Auto</option>\n <option value=\"video\">Video</option>\n <option value=\"audio\">Audio</option>\n <option value=\"image\">Image</option>\n </select>\n <button type=\"submit\" className={btn} disabled={!canEdit || !url.trim()} data-add-url=\"\">\n Add\n </button>\n </form>\n </>\n )}\n {busy > 0 && <p className=\"text-xs text-muted-foreground\">Reading media…</p>}\n {problem && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-media-error=\"\">\n {problem}\n </p>\n )}\n </div>\n );\n}\n","import React, { useEffect } from 'react';\nimport type { VideoEditorController, VideoEditorOptions } from '@pixerate/editor/video-editor';\nimport { ClipInspector } from './ClipInspector';\nimport { MediaBin } from './MediaBin';\nimport { useVideoEditor } from './useVideoEditor';\nimport { VideoPreview } from './VideoPreview';\nimport { VideoTimeline } from './VideoTimeline';\nimport { VideoTransport } from './VideoTransport';\n\nexport interface VideoEditorProps {\n /** The controller to edit. Without it one is created from `options` for the component's lifetime. */\n controller?: VideoEditorController;\n /** Options for the controller created when `controller` is not given. */\n options?: VideoEditorOptions;\n /**\n * Show the editing UI: the media bin's imports, the transport's edit buttons, timeline dragging\n * and track toggles, and the clip inspector (default true). False also makes the project read-only\n * while mounted.\n */\n editable?: boolean;\n /** Timeline zoom in pixels per second (with `onPxPerSecondChange` the host owns it). */\n pxPerSecond?: number;\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Canvas pixels per frame pixel for the preview (see `<VideoPreview resolution>`). */\n resolution?: number;\n /** List the project's issues under the timeline (default true). */\n showIssues?: boolean;\n className?: string;\n}\n\n/**\n * A whole non-linear video editor for a `VideoEditorController`: the media bin, the preview, the\n * transport, the timeline and the clip inspector, with the project's issues listed under the\n * timeline. Every edit is one undo step; the controller's agent tools edit the same project.\n */\nexport function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, className = '' }: VideoEditorProps) {\n const { controller, state } = useVideoEditor(given ?? options ?? {});\n useEffect(() => {\n if (editable) return;\n const was = controller.getState().readOnly;\n controller.setReadOnly(true);\n return () => controller.setReadOnly(was);\n }, [controller, editable]);\n const { issues } = state;\n const issueList = showIssues && issues.length > 0 && (\n <ul className=\"space-y-0.5 text-xs\" aria-label=\"Video issues\" data-video-issues=\"\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity === 'error' ? 'text-red-500' : issue.severity === 'warning' ? 'text-amber-500' : 'text-muted-foreground'} data-issue={issue.code}>\n {issue.message}\n </li>\n ))}\n </ul>\n );\n return (\n <div className={`flex flex-col gap-3 ${className}`} data-video-editor=\"\">\n <div className={`grid gap-3 ${editable ? 'lg:grid-cols-[16rem_minmax(0,1fr)_18rem]' : ''}`}>\n {editable && <MediaBin controller={controller} className=\"min-w-0\" />}\n <div className=\"flex min-w-0 flex-col gap-2\">\n <VideoPreview controller={controller} resolution={resolution} className=\"rounded-md border border-border\" />\n <VideoTransport controller={controller} editable={editable} />\n </div>\n {editable && <ClipInspector controller={controller} className=\"min-w-0\" />}\n </div>\n <VideoTimeline controller={controller} editable={editable} pxPerSecond={pxPerSecond} onPxPerSecondChange={onPxPerSecondChange} />\n {issueList}\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useRef","useRef","useEffect","jsx","useEffect","useRef","Fragment","jsx","jsxs","useRef","useEffect","field","formatVideoSeconds","Fragment","jsx","jsxs","formatVideoSeconds","useState","formatVideoSeconds","Fragment","jsx","jsxs","btn","useState","formatVideoSeconds","useEffect","jsx","jsxs","useEffect"]}
1
+ {"version":3,"sources":["../../src/video-editor/useVideoEditor.ts","../../src/video-editor/VideoPreview.tsx","../../src/video-editor/VideoTransport.tsx","../../src/video-editor/VideoTimeline.tsx","../../src/video-editor/ClipInspector.tsx","../../src/video-editor/MediaBin.tsx","../../src/video-editor/VideoEditor.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n VideoEditorController,\n createVideoEditorToolProvider,\n type VideoEditResult,\n type VideoEditorOptions,\n type VideoEditorState,\n type VideoEditorToolProviderOptions,\n} from '@pixerate/editor/video-editor';\nimport type { ToolRegistry } from '@pixerate/editor/tools';\n\n/** A `VideoEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useVideoEditorController(options: VideoEditorOptions = {}): VideoEditorController {\n const ref = useRef<VideoEditorController | null>(null);\n if (!ref.current) ref.current = new VideoEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change, including each played frame). */\nexport function useVideoEditorState(controller: VideoEditorController): VideoEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseVideoEditorResult {\n controller: VideoEditorController;\n state: VideoEditorState;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n /** Moves the playhead (ms). */\n seek: (time: number) => void;\n /** Moves the playhead by whole frames (negative steps back), pausing. */\n step: (frames: number) => void;\n undo: () => void;\n redo: () => void;\n /** Splits the selected clips (or every clip) under the playhead, as one undo step. */\n splitAtPlayhead: () => VideoEditResult;\n /** Deletes the selected clips; `ripple` closes the gaps they leave. */\n deleteSelection: (options?: { ripple?: boolean }) => VideoEditResult;\n}\n\n/**\n * Subscribes to a video editor controller and exposes its state with bound actions.\n * Pass options instead of a controller to create one for the component's lifetime.\n */\nexport function useVideoEditor(controllerOrOptions: VideoEditorController | VideoEditorOptions = {}): UseVideoEditorResult {\n const own = useRef<VideoEditorController | null>(null);\n let controller: VideoEditorController;\n if (controllerOrOptions instanceof VideoEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new VideoEditorController(controllerOrOptions);\n controller = own.current;\n }\n useEffect(() => () => own.current?.destroy(), []);\n const state = useVideoEditorState(controller);\n const actions = useMemo(\n () => ({\n play: () => controller.play(),\n pause: () => controller.pause(),\n toggle: () => controller.toggle(),\n seek: (time: number) => controller.seek(time),\n step: (frames: number) => controller.step(frames),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n splitAtPlayhead: () => controller.splitAtPlayhead(),\n deleteSelection: (options?: { ripple?: boolean }) => controller.deleteSelection(options),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/**\n * Registers the video editor's agent tools (`get_video_project`, `add_video_clip`,\n * `trim_video_clip`, …) in `registry` while mounted.\n */\nexport function useVideoEditorTools(\n registry: ToolRegistry | null | undefined,\n controller: VideoEditorController,\n options: VideoEditorToolProviderOptions = {},\n): void {\n const { id, namespace, readOnly, rasterize } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createVideoEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));\n }, [registry, controller, id, namespace, readOnly, rasterize]);\n}\n","import React, { useEffect, useRef } from 'react';\nimport { VideoPreviewEngine, type VideoEditorController, type VideoPreviewOptions } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoPreviewProps {\n controller: VideoEditorController;\n /** Canvas pixels per frame pixel (default: fits the displayed width at the device pixel ratio, at most 1). */\n resolution?: number;\n /** Called after each frame is drawn. */\n onFrame?: VideoPreviewOptions['onFrame'];\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Plays the controller's project on a canvas the shape of its frame: a `VideoPreviewEngine` keeps\n * one `<video>` / `<audio>` element per playing clip in step with the playhead and draws each frame.\n * The engine is created when the canvas mounts and destroyed on unmount or when the controller changes.\n */\nexport function VideoPreview({ controller, resolution, onFrame, className = '', style }: VideoPreviewProps) {\n const { project } = useVideoEditorState(controller);\n const canvas = useRef<HTMLCanvasElement>(null);\n const frameRef = useRef(onFrame);\n frameRef.current = onFrame;\n useEffect(() => {\n const el = canvas.current;\n if (!el) return;\n const engine = new VideoPreviewEngine(controller, el, { resolution, onFrame: (frame) => frameRef.current?.(frame) });\n return () => engine.destroy();\n }, [controller, resolution]);\n return (\n <canvas\n ref={canvas}\n className={className}\n style={{ display: 'block', width: '100%', aspectRatio: `${project.width} / ${project.height}`, background: project.background ?? '#000', ...style }}\n role=\"img\"\n aria-label={`Preview of ${project.name ?? 'the video'}`}\n data-video-preview=\"\"\n />\n );\n}\n","import React from 'react';\nimport { formatVideoTimecode, type VideoEditorController } from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTransportProps {\n controller: VideoEditorController;\n /** Show the editing buttons (split, delete, ripple delete, undo, redo; default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * Play / pause, a frame back and forward, the playhead and total length as timecode, and the\n * edits that work on the playhead and the selection: split, delete, ripple delete, undo and redo.\n */\nexport function VideoTransport({ controller, editable = true, className = '' }: VideoTransportProps) {\n const state = useVideoEditorState(controller);\n const { time, duration, playing, project, selection, readOnly } = state;\n const canEdit = editable && !readOnly;\n return (\n <div className={`flex flex-wrap items-center gap-1.5 text-xs ${className}`} data-video-transport=\"\">\n <button type=\"button\" className={btn} onClick={() => controller.step(-1)} aria-label=\"Previous frame\" title=\"Previous frame (←)\" data-step-back=\"\">\n ◀|\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.toggle()} aria-label={playing ? 'Pause' : 'Play'} title=\"Play / pause (Space)\" disabled={duration <= 0} data-play=\"\">\n {playing ? 'Pause' : 'Play'}\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.step(1)} aria-label=\"Next frame\" title=\"Next frame (→)\" data-step-forward=\"\">\n |▶\n </button>\n <span className=\"px-1 tabular-nums text-muted-foreground\" data-timecode=\"\">\n {formatVideoTimecode(time, project.fps)} / {formatVideoTimecode(duration, project.fps)}\n </span>\n {editable && (\n <>\n <span className=\"mx-1 h-4 w-px bg-border\" />\n <button type=\"button\" className={btn} onClick={() => controller.splitAtPlayhead()} disabled={!canEdit} title=\"Split at the playhead (S)\" data-split=\"\">\n Split\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.deleteSelection()} disabled={!canEdit || !selection.length} title=\"Delete the selected clips (Delete)\" data-delete=\"\">\n Delete\n </button>\n <button\n type=\"button\"\n className={btn}\n onClick={() => controller.deleteSelection({ ripple: true })}\n disabled={!canEdit || !selection.length}\n title=\"Delete the selected clips and close the gaps (Shift+Delete)\"\n data-ripple-delete=\"\"\n >\n Ripple delete\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.undo()} disabled={!canEdit || !state.canUndo} title={state.canUndo ? `Undo ${state.undoLabel}` : 'Nothing to undo'} data-undo=\"\">\n Undo\n </button>\n <button type=\"button\" className={btn} onClick={() => controller.redo()} disabled={!canEdit || !state.canRedo} title={state.canRedo ? `Redo ${state.redoLabel}` : 'Nothing to redo'} data-redo=\"\">\n Redo\n </button>\n </>\n )}\n </div>\n );\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport {\n planVideoClipDrag,\n videoDragLabel,\n videoAssetMap,\n videoClipLabel,\n videoClipTint,\n videoDropTracks,\n videoRulerTicks,\n formatVideoSeconds,\n type VideoClip,\n type VideoClipDragMode,\n type VideoClipDragPlan,\n type VideoEditorController,\n type VideoTrack,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface VideoTimelineProps {\n controller: VideoEditorController;\n /** Zoom: pixels per second of timeline. With `onPxPerSecondChange` the host owns it; otherwise it is the starting zoom (default 100). */\n pxPerSecond?: number;\n /** Called when the zoom control changes. Without it the timeline keeps its own zoom and shows a zoom slider. */\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Allow selecting, dragging, trimming and the track toggles (default true). */\n editable?: boolean;\n className?: string;\n}\n\nconst HEADER = 150;\nconst RULER = 24;\nconst ROW = 44;\nconst HANDLE = 6;\nconst DRAG_THRESHOLD = 3;\nconst MIN_ZOOM = 10;\nconst MAX_ZOOM = 400;\n\ninterface Drag {\n clip: string;\n mode: VideoClipDragMode;\n x: number;\n pointer: number;\n moved: boolean;\n plan: VideoClipDragPlan | null;\n}\n\nconst isField = (el: EventTarget | null) => el instanceof HTMLElement && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);\n\n/**\n * The tracks of a `VideoEditorController` laid out in time: a ruler with markers (click or drag it\n * to scrub), a playhead, and one row per track with its toggles (mute, hide, lock) and its clips.\n * Click a clip to select it (Shift / Cmd to add or remove it), drag it to move it (onto another\n * track of the same kind too; hold Alt to insert, pushing later material along on every sync-locked\n * track, or Shift to overwrite what is underneath), drag an edge to trim it (Alt to ripple). Moves snap to clip edges,\n * markers, the playhead and frames. Every edit is one undo step; refused edits show why.\n *\n * Keyboard (when focused): Space plays, ←/→ step a frame, S splits at the playhead, Delete removes\n * the selection (Shift to ripple), Cmd/Ctrl+Z undoes, Shift+Cmd/Ctrl+Z redoes.\n */\nexport function VideoTimeline({ controller, pxPerSecond, onPxPerSecondChange, editable = true, className = '' }: VideoTimelineProps) {\n const state = useVideoEditorState(controller);\n const { project, duration, time, selection, readOnly, error } = state;\n const [ownZoom, setOwnZoom] = useState(pxPerSecond ?? 100);\n const px = onPxPerSecondChange ? (pxPerSecond ?? 100) : ownZoom;\n const canEdit = editable && !readOnly;\n const [drag, setDrag] = useState<Drag | null>(null);\n const dragRef = useRef<Drag | null>(null);\n const rows = useRef(new Map<string, HTMLDivElement>());\n const ruler = useRef<HTMLDivElement>(null);\n const assets = videoAssetMap(project);\n const msToPx = (ms: number) => (ms * px) / 1000;\n const span = Math.max(duration + 5000, 10000);\n const width = msToPx(span);\n const ticks = videoRulerTicks(span, px, project.fps);\n\n const seekAt = (clientX: number) => {\n const box = ruler.current!.getBoundingClientRect();\n controller.seek(Math.max(0, ((clientX - box.left) * 1000) / px));\n };\n\n const trackAt = (clipId: string, clientY: number): string | undefined => {\n const allowed = videoDropTracks(project, clipId);\n for (const [id, el] of rows.current) {\n const box = el.getBoundingClientRect();\n if (clientY >= box.top && clientY < box.bottom && allowed.includes(id)) return id;\n }\n return undefined;\n };\n\n const planFor = (d: Drag, e: { clientX: number; clientY: number; altKey: boolean; shiftKey: boolean }) =>\n planVideoClipDrag(project, {\n clip: d.clip,\n mode: d.mode,\n delta: ((e.clientX - d.x) * 1000) / px,\n track: d.mode === 'move' ? trackAt(d.clip, e.clientY) : undefined,\n ripple: e.altKey,\n placement: e.altKey ? 'insert' : e.shiftKey ? 'overwrite' : 'refuse',\n snap: (t) => controller.snap(t, { exclude: [d.clip] }).time,\n });\n\n const update = (next: Drag | null) => {\n dragRef.current = next;\n setDrag(next);\n };\n\n // The latest render's values, for the window listeners of a drag in progress.\n const live = useRef({ planFor, selection });\n live.current = { planFor, selection };\n const stopDrag = useRef<(() => void) | null>(null);\n useEffect(() => () => stopDrag.current?.(), []);\n\n const onClipPointerDown = (e: React.PointerEvent<HTMLDivElement>, clip: VideoClip, track: VideoTrack) => {\n if (e.button !== undefined && e.button !== 0) return;\n e.stopPropagation();\n if (!editable) return;\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n const handle = (e.target as HTMLElement).closest('[data-handle]')?.getAttribute('data-handle');\n const mode: VideoClipDragMode = handle === 'start' ? 'trim-start' : handle === 'end' ? 'trim-end' : 'move';\n const fixed = !canEdit || !!track.locked;\n stopDrag.current?.();\n update({ clip: clip.id, mode, x: e.clientX, pointer: e.pointerId, moved: false, plan: null });\n // Follow the pointer on the window: the block may move to another track's row (a new element) mid-drag.\n const move = (ev: PointerEvent) => {\n const d = dragRef.current;\n if (!d || fixed) return;\n if (!d.moved && Math.abs(ev.clientX - d.x) < DRAG_THRESHOLD) return;\n if (!d.moved && !live.current.selection.includes(d.clip)) controller.select([d.clip]);\n update({ ...d, moved: true, plan: live.current.planFor(d, ev) });\n };\n const up = (ev: PointerEvent) => {\n stop();\n const d = dragRef.current;\n if (!d) return;\n update(null);\n if (!d.moved) {\n if (ev.shiftKey || ev.metaKey || ev.ctrlKey) controller.select([d.clip], { add: true });\n else controller.select([d.clip]);\n return;\n }\n const plan = live.current.planFor(d, ev);\n if (plan?.edit) controller.apply(plan.edit, videoDragLabel(plan.edit));\n };\n const cancel = () => {\n stop();\n update(null);\n };\n const stop = () => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n window.removeEventListener('pointercancel', cancel);\n stopDrag.current = null;\n };\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n window.addEventListener('pointercancel', cancel);\n stopDrag.current = stop;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (isField(e.target)) return;\n const mod = e.metaKey || e.ctrlKey;\n const key = e.key.toLowerCase();\n if (e.key === ' ') {\n if ((e.target as HTMLElement).tagName === 'BUTTON') return;\n controller.toggle();\n } else if (e.key === 'ArrowLeft' && !mod) controller.step(-1);\n else if (e.key === 'ArrowRight' && !mod) controller.step(1);\n else if (e.key === 'Escape') controller.select(null);\n else if (!canEdit) return;\n else if (key === 's' && !mod) controller.splitAtPlayhead();\n else if ((e.key === 'Delete' || e.key === 'Backspace') && !mod) controller.deleteSelection({ ripple: e.shiftKey });\n else if (key === 'z' && mod) {\n if (e.shiftKey) controller.redo();\n else controller.undo();\n } else return;\n e.preventDefault();\n };\n\n // Where each clip is drawn: the dragged one follows the plan (possibly on another track).\n const placed = project.tracks.map((track) => ({\n track,\n clips: project.tracks.flatMap((t) =>\n t.clips\n .map((clip) => {\n const plan = drag?.moved && drag.clip === clip.id ? drag.plan : null;\n const on = plan ? plan.track : t.id;\n return on === track.id ? { clip, home: t, start: plan ? plan.start : clip.start, duration: plan ? plan.duration : clip.duration, dragging: !!plan } : null;\n })\n .filter((x): x is NonNullable<typeof x> => x !== null),\n ),\n }));\n\n const toggle = (track: VideoTrack, field: 'muted' | 'hidden' | 'locked', label: string) => (\n <button\n type=\"button\"\n className={`rounded border px-1 text-[10px] leading-4 ${track[field] ? 'border-rose-500 bg-rose-500/15 text-rose-600' : 'border-border text-muted-foreground'} disabled:opacity-40`}\n aria-pressed={!!track[field]}\n aria-label={`${label} ${track.name ?? track.id}`}\n title={label}\n disabled={!canEdit}\n data-track-toggle={field}\n onClick={() => controller.apply({ op: 'update_track', track: track.id, set: { [field]: !track[field] } }, `${track[field] ? 'Un' : ''}${field === 'muted' ? 'mute' : field === 'hidden' ? 'hide' : 'lock'} track`)}\n >\n {field === 'muted' ? 'M' : field === 'hidden' ? 'H' : 'L'}\n </button>\n );\n\n return (\n <div className={`flex flex-col gap-1 ${className}`} data-video-timeline-wrapper=\"\">\n {!onPxPerSecondChange && (\n <label className=\"flex items-center gap-2 self-end text-xs text-muted-foreground\">\n Zoom\n <input type=\"range\" min={MIN_ZOOM} max={MAX_ZOOM} step={5} value={ownZoom} aria-label=\"Timeline zoom\" onChange={(e) => setOwnZoom(Number(e.target.value))} data-zoom=\"\" />\n </label>\n )}\n <div\n className=\"relative overflow-x-auto rounded border border-border outline-none focus-visible:ring-2 focus-visible:ring-rose-500/50\"\n tabIndex={0}\n role=\"group\"\n aria-label=\"Video timeline\"\n onKeyDown={onKeyDown}\n data-video-timeline=\"\"\n >\n <div className=\"relative\" style={{ width: HEADER + width }}>\n <div className=\"flex\" style={{ height: RULER }}>\n <div className=\"sticky left-0 z-20 shrink-0 border-b border-r border-border bg-background\" style={{ width: HEADER }} />\n <div\n ref={ruler}\n className=\"relative shrink-0 cursor-ew-resize select-none touch-none border-b border-border text-[10px] text-muted-foreground\"\n style={{ width }}\n role=\"slider\"\n aria-label=\"Playhead\"\n aria-valuemin={0}\n aria-valuemax={duration}\n aria-valuenow={Math.round(time)}\n data-ruler=\"\"\n onPointerDown={(e) => {\n try {\n e.currentTarget.setPointerCapture?.(e.pointerId);\n } catch {\n // No pointer to capture (synthetic events).\n }\n seekAt(e.clientX);\n }}\n onPointerMove={(e) => {\n if (e.buttons & 1) seekAt(e.clientX);\n }}\n >\n {ticks.map((t) => (\n <div key={t.time} className={`absolute bottom-0 w-px ${t.major ? 'h-2.5 bg-muted-foreground/70' : 'h-1 bg-muted-foreground/40'}`} style={{ left: t.x }} data-tick={t.time}>\n {t.label && <span className=\"absolute -top-3.5 left-1 whitespace-nowrap tabular-nums\">{t.label}</span>}\n </div>\n ))}\n {(project.markers ?? []).map((m) => (\n <div\n key={m.id}\n className=\"absolute top-0 h-2.5 w-2.5 -translate-x-1/2 rotate-45\"\n style={{ left: msToPx(m.time), background: m.color ?? '#f59e0b' }}\n title={`${m.label ?? m.id} at ${formatVideoSeconds(m.time)}`}\n data-marker={m.id}\n />\n ))}\n </div>\n </div>\n {placed.map(({ track, clips }) => (\n <div key={track.id} className=\"flex border-b border-border last:border-b-0\" style={{ height: ROW }} data-track={track.id}>\n <div className=\"sticky left-0 z-20 flex shrink-0 flex-col justify-center gap-0.5 border-r border-border bg-background px-2 text-xs\" style={{ width: HEADER }} data-track-header=\"\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate font-medium\">{track.name ?? track.id}</span>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-track-kind=\"\">\n {track.kind}\n </span>\n </div>\n {editable && (\n <div className=\"flex gap-1\">\n {toggle(track, 'muted', 'Mute')}\n {track.kind === 'video' && toggle(track, 'hidden', 'Hide')}\n {toggle(track, 'locked', 'Lock')}\n </div>\n )}\n </div>\n <div\n ref={(el) => {\n if (el) rows.current.set(track.id, el);\n else rows.current.delete(track.id);\n }}\n className={`relative shrink-0 ${track.hidden ? 'opacity-50' : ''} ${track.locked ? 'bg-muted/40' : ''}`}\n style={{ width }}\n data-track-lane={track.id}\n onPointerDown={(e) => {\n if (e.target === e.currentTarget && editable) controller.select(null);\n }}\n >\n {clips.map(({ clip, home, start, duration: length, dragging }) => {\n const asset = clip.type === 'media' ? assets.get(clip.asset) : undefined;\n const selected = selection.includes(clip.id);\n const label = videoClipLabel(clip, asset);\n return (\n <div\n key={clip.id}\n className={`absolute top-1 bottom-1 select-none touch-none overflow-hidden rounded text-[11px] text-white ${canEdit && !home.locked ? 'cursor-grab' : 'cursor-default'} ${dragging ? 'opacity-80 shadow-lg' : ''}`}\n style={{\n left: msToPx(start),\n width: Math.max(2, msToPx(length)),\n background: videoClipTint(clip, asset),\n outline: selected ? '2px solid #f43f5e' : undefined,\n outlineOffset: selected ? 1 : undefined,\n zIndex: dragging ? 10 : selected ? 2 : 1,\n }}\n title={`${label} · ${formatVideoSeconds(start)}–${formatVideoSeconds(start + length)}`}\n data-clip={clip.id}\n data-selected={selected ? '' : undefined}\n data-dragging={dragging ? '' : undefined}\n aria-selected={selected}\n onPointerDown={(e) => onClipPointerDown(e, clip, home)}\n >\n <span className=\"pointer-events-none block truncate px-1.5 py-0.5 leading-4\">{label}</span>\n {clip.transition && (\n <span className=\"pointer-events-none absolute left-1 bottom-0.5 rounded bg-black/40 px-1 text-[9px] leading-3\" title={`${clip.transition.type} ${clip.transition.duration} ms`} data-transition={clip.transition.type}>\n ⧓ {clip.transition.type}\n </span>\n )}\n {canEdit && !home.locked && (\n <>\n <div className=\"absolute inset-y-0 left-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"start\" aria-hidden=\"true\" />\n <div className=\"absolute inset-y-0 right-0 cursor-ew-resize bg-white/0 hover:bg-white/40\" style={{ width: HANDLE }} data-handle=\"end\" aria-hidden=\"true\" />\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n ))}\n <div className=\"pointer-events-none absolute top-0 bottom-0 w-0.5 -translate-x-1/2 bg-rose-500\" style={{ left: HEADER + msToPx(time), zIndex: 15 }} data-playhead=\"\" />\n </div>\n </div>\n {error && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-edit-error=\"\">\n {error}\n </p>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport {\n findVideoClip,\n formatVideoSeconds,\n VIDEO_TRANSITION_TYPES,\n videoKeyframeRows,\n videoKeyframeToggleEdit,\n videoKeyframeValueEdit,\n type VideoClip,\n type VideoEditorController,\n type VideoTransitionType,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface ClipInspectorProps {\n controller: VideoEditorController;\n className?: string;\n}\n\nconst field = 'w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs disabled:opacity-50';\nconst DEFAULT_TRANSITION = 500;\n\n/** An input (or textarea) that commits on blur or Enter, and resets when its value changes from outside. */\nfunction Commit({\n value,\n onCommit,\n multiline,\n ...rest\n}: { value: string; onCommit: (raw: string) => void; multiline?: boolean } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue'>) {\n const commit = (raw: string) => {\n if (raw !== value) onCommit(raw);\n };\n if (multiline)\n return (\n <textarea\n key={value}\n className={field}\n rows={2}\n defaultValue={value}\n disabled={rest.disabled}\n aria-label={rest['aria-label']}\n onBlur={(e) => commit(e.currentTarget.value)}\n />\n );\n return <input key={value} className={field} {...rest} defaultValue={value} onKeyDown={(e) => e.key === 'Enter' && e.currentTarget.blur()} onBlur={(e) => commit(e.currentTarget.value)} />;\n}\n\nfunction Row({ label, name, children }: { label: string; name: string; children: React.ReactNode }) {\n return (\n <label className=\"grid grid-cols-[5.5rem_minmax(0,1fr)] items-center gap-2 text-xs\" data-field={name}>\n <span className=\"text-muted-foreground\">{label}</span>\n {children}\n </label>\n );\n}\n\n/**\n * Edits the one selected clip: its name, fades, opacity, volume and transform (each with a keyframe\n * toggle at the playhead: ◆ adds a key holding the value there or removes the one there; typing a\n * value on a keyed property sets the key at the playhead); volume, speed and mute for media;\n * text, colour, size and background for titles; colour for colour clips; and the transition into\n * it. Each change is one undo step (`update_clip` / `set_transition`), committed on blur or change.\n */\nexport function ClipInspector({ controller, className = '' }: ClipInspectorProps) {\n const state = useVideoEditorState(controller);\n const { project, selection, readOnly, time } = state;\n if (selection.length !== 1)\n return (\n <p className={`text-xs text-muted-foreground ${className}`} data-clip-inspector=\"\">\n {selection.length ? `${selection.length} clips selected. Select one clip to edit it.` : 'Select a clip on the timeline to edit it.'}\n </p>\n );\n const at = findVideoClip(project, selection[0]);\n if (!at) return null;\n const clip: VideoClip = at.clip;\n const disabled = readOnly || !!at.track.locked;\n\n const set = (key: string, value: unknown, label = `Change ${key}`) => {\n if (value === undefined || value === '') controller.apply({ op: 'update_clip', clip: clip.id, remove: [key] }, label);\n else controller.apply({ op: 'update_clip', clip: clip.id, set: { [key]: value } }, label);\n };\n const num = (key: string, raw: string, label?: string) => {\n const trimmed = raw.trim();\n if (!trimmed) return set(key, undefined, label);\n const n = Number(trimmed);\n if (Number.isFinite(n)) set(key, n, label);\n };\n const text = (key: string, label?: string) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return (\n <Commit value={value === undefined ? '' : String(value)} aria-label={label ?? key} disabled={disabled} onCommit={(raw) => set(key, raw.trim() || undefined, `Change ${label ?? key}`)} />\n );\n };\n const number = (key: string, label: string, attrs: { min?: number; max?: number; step?: number } = {}) => {\n const value = (clip as unknown as Record<string, unknown>)[key];\n return <Commit type=\"number\" value={value === undefined ? '' : String(value)} aria-label={label} disabled={disabled} onCommit={(raw) => num(key, raw, `Change ${label.toLowerCase()}`)} {...attrs} />;\n };\n\n const transition = clip.transition;\n return (\n <div className={`flex flex-col gap-1.5 ${className}`} data-clip-inspector=\"\" data-clip-id={clip.id}>\n <div className=\"flex items-center gap-2 text-xs\">\n <span className=\"font-medium\">{clip.type === 'media' ? 'Media clip' : clip.type === 'text' ? 'Title' : 'Colour clip'}</span>\n <code className=\"text-muted-foreground\">{clip.id}</code>\n {at.track.locked && <span className=\"text-amber-500\">locked track</span>}\n </div>\n <Row label=\"Name\" name=\"name\">\n {text('name', 'Name')}\n </Row>\n <Row label=\"Start\" name=\"start\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.start)}</span>\n </Row>\n <Row label=\"Duration\" name=\"duration\">\n <span className=\"tabular-nums text-xs\">{formatVideoSeconds(clip.duration)}</span>\n </Row>\n {videoKeyframeRows(project, clip.id, time).map((row) => (\n <Row key={row.property} label={row.label} name={row.property}>\n <span className=\"flex min-w-0 items-center gap-1\" data-keyframe-row={row.property}>\n <Commit\n type=\"number\"\n value={row.keyed || (row.property === 'opacity' ? clip.opacity : row.property === 'volume' ? (clip as { volume?: unknown }).volume : clip.transform?.[row.property]) !== undefined ? String(row.value) : ''}\n placeholder={String(row.value)}\n aria-label={row.label}\n disabled={disabled}\n min={row.min}\n max={row.max}\n step={row.step}\n onCommit={(raw) => {\n const n = Number(raw);\n if (raw.trim() && !Number.isFinite(n)) return;\n const edit = videoKeyframeValueEdit(project, clip.id, row.property, time, raw.trim() ? n : undefined);\n if (edit) controller.apply(edit, row.keyed ? `Keyframe ${row.label.toLowerCase()}` : `Change ${row.label.toLowerCase()}`);\n }}\n />\n <button\n type=\"button\"\n className={`shrink-0 rounded px-1 text-xs disabled:opacity-50 ${row.onKey ? 'text-amber-500' : row.keyed ? 'text-foreground' : 'text-muted-foreground'}`}\n aria-label={`${row.onKey ? 'Remove' : 'Add'} ${row.label.toLowerCase()} keyframe at playhead`}\n aria-pressed={row.onKey}\n title={row.keyed ? `${row.keys} key${row.keys === 1 ? '' : 's'}; ${row.onKey ? 'remove the one' : 'add one'} at the playhead` : 'Animate: add a key at the playhead'}\n disabled={disabled}\n data-keyframe-toggle={row.property}\n onClick={() => {\n const edit = videoKeyframeToggleEdit(project, clip.id, row.property, time);\n if (edit) controller.apply(edit, `${row.onKey ? 'Remove' : 'Add'} ${row.label.toLowerCase()} keyframe`);\n }}\n >\n {row.onKey ? '◆' : '◇'}\n </button>\n {row.keyed && (\n <span className=\"shrink-0 text-[10px] tabular-nums text-muted-foreground\" data-keyframe-count={row.property}>\n {row.keys}\n </span>\n )}\n </span>\n </Row>\n ))}\n <Row label=\"Fade in (ms)\" name=\"fadeIn\">\n {number('fadeIn', 'Fade in', { min: 0, step: 100 })}\n </Row>\n <Row label=\"Fade out (ms)\" name=\"fadeOut\">\n {number('fadeOut', 'Fade out', { min: 0, step: 100 })}\n </Row>\n {clip.type === 'media' && (\n <>\n <Row label=\"Speed\" name=\"speed\">\n {number('speed', 'Speed', { min: 0.1, step: 0.25 })}\n </Row>\n <Row label=\"Muted\" name=\"muted\">\n <input type=\"checkbox\" className=\"justify-self-start\" checked={!!clip.muted} disabled={disabled} aria-label=\"Muted\" onChange={(e) => set('muted', e.target.checked || undefined, e.target.checked ? 'Mute clip' : 'Unmute clip')} />\n </Row>\n </>\n )}\n {clip.type === 'text' && (\n <>\n <Row label=\"Text\" name=\"text\">\n <Commit multiline value={clip.text} aria-label=\"Text\" disabled={disabled} onCommit={(raw) => set('text', raw, 'Change text')} />\n </Row>\n <Row label=\"Colour\" name=\"color\">\n {text('color', 'Colour')}\n </Row>\n <Row label=\"Font size\" name=\"fontSize\">\n {number('fontSize', 'Font size', { min: 1, step: 2 })}\n </Row>\n <Row label=\"Background\" name=\"background\">\n {text('background', 'Background')}\n </Row>\n </>\n )}\n {clip.type === 'color' && (\n <Row label=\"Colour\" name=\"color\">\n <Commit value={clip.color} aria-label=\"Colour\" disabled={disabled} onCommit={(raw) => raw.trim() && set('color', raw.trim(), 'Change colour')} />\n </Row>\n )}\n <Row label=\"Transition in\" name=\"transition\">\n <select\n className={field}\n value={transition?.type ?? ''}\n disabled={disabled}\n aria-label=\"Transition\"\n onChange={(e) => {\n const type = e.target.value as VideoTransitionType | '';\n controller.apply(\n { op: 'set_transition', clip: clip.id, transition: type ? { ...transition, type, duration: transition?.duration ?? DEFAULT_TRANSITION } : null },\n type ? 'Set transition' : 'Remove transition',\n );\n }}\n >\n <option value=\"\">None</option>\n {VIDEO_TRANSITION_TYPES.map((t) => (\n <option key={t} value={t}>\n {t === 'crossfade' ? 'Crossfade' : t === 'dip' ? 'Dip to colour' : 'Wipe'}\n </option>\n ))}\n </select>\n </Row>\n {transition && (\n <Row label=\"Length (ms)\" name=\"transitionDuration\">\n <Commit\n type=\"number\"\n min={1}\n step={100}\n value={String(transition.duration)}\n aria-label=\"Transition length\"\n disabled={disabled}\n onCommit={(raw) => {\n const n = Number(raw);\n if (raw.trim() && Number.isFinite(n) && n > 0) controller.apply({ op: 'set_transition', clip: clip.id, transition: { ...transition, duration: n } }, 'Change transition length');\n }}\n />\n </Row>\n )}\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport {\n formatVideoSeconds,\n probeVideoMedia,\n videoAssetDropEdit,\n videoAssetKindOf,\n type VideoAsset,\n type VideoAssetKind,\n type VideoEditorController,\n} from '@pixerate/editor/video-editor';\nimport { useVideoEditorState } from './useVideoEditor';\n\nexport interface MediaBinProps {\n controller: VideoEditorController;\n /** Show the import controls and \"Add to timeline\" buttons (default true). */\n editable?: boolean;\n /** Turns a file into a URL the preview and exporter can open (default `URL.createObjectURL`). */\n fileToUrl?: (file: File) => string;\n /** Reads a source's length and size (default `probeVideoMedia`). */\n probe?: typeof probeVideoMedia;\n className?: string;\n}\n\nconst btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';\n\n/**\n * The project's assets (name, kind, length), each with an \"Add to timeline\" button that puts it at\n * the end of the first unlocked track of its kind. Import media by choosing or dropping files (read\n * as object URLs and probed for their length and size) or by URL. Each import is one undo step.\n */\nexport function MediaBin({ controller, editable = true, fileToUrl, probe = probeVideoMedia, className = '' }: MediaBinProps) {\n const { project, readOnly } = useVideoEditorState(controller);\n const canEdit = editable && !readOnly;\n const [url, setUrl] = useState('');\n const [urlKind, setUrlKind] = useState<VideoAssetKind | ''>('');\n const [busy, setBusy] = useState(0);\n const [problem, setProblem] = useState<string | null>(null);\n const [over, setOver] = useState(false);\n\n const addSource = async (src: string, kind: VideoAssetKind, name: string) => {\n setBusy((n) => n + 1);\n try {\n const found = await probe(src, kind).catch(() => null);\n const result = controller.apply({ op: 'add_asset', asset: { kind, src, name, ...(found ?? {}) } }, `Import ${name}`);\n if (!result.ok) setProblem(result.error);\n } finally {\n setBusy((n) => n - 1);\n }\n };\n\n const addFiles = async (files: FileList | File[] | null) => {\n setProblem(null);\n for (const file of Array.from(files ?? [])) {\n const kind = videoAssetKindOf(file.type || file.name);\n if (!kind) {\n setProblem(`\"${file.name}\" is not a video, audio or image file.`);\n continue;\n }\n const src = fileToUrl ? fileToUrl(file) : URL.createObjectURL(file);\n await addSource(src, kind, file.name);\n }\n };\n\n const addUrl = async () => {\n const src = url.trim();\n if (!src) return;\n const kind = urlKind || videoAssetKindOf(src.replace(/[?#].*$/, ''));\n if (!kind) {\n setProblem(`Can't tell whether \"${src}\" is video, audio or an image; choose its kind.`);\n return;\n }\n setProblem(null);\n await addSource(src, kind, src.replace(/[?#].*$/, '').replace(/^.*[\\\\/]/, '') || src);\n setUrl('');\n };\n\n const addToTimeline = (asset: VideoAsset) => {\n const edit = videoAssetDropEdit(project, asset.id);\n if (!edit) {\n setProblem(`There is no unlocked ${asset.kind === 'audio' ? 'audio' : 'video'} track to put \"${asset.name ?? asset.id}\" on.`);\n return;\n }\n setProblem(null);\n const result = controller.apply(edit, `Add ${asset.name ?? asset.id}`);\n if (result.ok) {\n const added = result.outcomes[0]?.clip;\n if (added) controller.select([added]);\n }\n };\n\n return (\n <div\n className={`flex flex-col gap-2 rounded ${over ? 'ring-2 ring-rose-500/60' : ''} ${className}`}\n data-media-bin=\"\"\n onDragOver={(e) => {\n if (!canEdit || !e.dataTransfer?.types?.includes('Files')) return;\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n setOver(false);\n if (!canEdit || !e.dataTransfer?.files?.length) return;\n e.preventDefault();\n void addFiles(e.dataTransfer.files);\n }}\n >\n <div className=\"text-xs font-medium\">Media</div>\n {project.assets.length ? (\n <ul className=\"flex flex-col gap-1\" aria-label=\"Media assets\">\n {project.assets.map((asset) => (\n <li key={asset.id} className=\"flex items-center gap-2 text-xs\" data-asset={asset.id}>\n <span className=\"rounded bg-muted px-1 text-[10px] uppercase text-muted-foreground\" data-asset-kind=\"\">\n {asset.kind}\n </span>\n <span className=\"min-w-0 flex-1 truncate\" title={asset.src}>\n {asset.name ?? asset.id}\n </span>\n {asset.duration !== undefined && <span className=\"tabular-nums text-muted-foreground\">{formatVideoSeconds(asset.duration)}</span>}\n {editable && (\n <button type=\"button\" className={btn} disabled={!canEdit} onClick={() => addToTimeline(asset)} data-add-to-timeline=\"\">\n Add to timeline\n </button>\n )}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-xs text-muted-foreground\">No media yet.{editable ? ' Choose or drop files, or add a URL.' : ''}</p>\n )}\n {editable && (\n <>\n <label className=\"flex flex-col gap-1 rounded border border-dashed border-border p-2 text-xs text-muted-foreground\">\n Choose or drop video, audio or image files\n <input\n type=\"file\"\n multiple\n accept=\"video/*,audio/*,image/*\"\n disabled={!canEdit}\n aria-label=\"Import media files\"\n data-import-files=\"\"\n onChange={(e) => {\n const files = e.currentTarget.files ? Array.from(e.currentTarget.files) : [];\n e.currentTarget.value = '';\n void addFiles(files);\n }}\n />\n </label>\n <form\n className=\"flex items-center gap-1\"\n onSubmit={(e) => {\n e.preventDefault();\n void addUrl();\n }}\n >\n <input\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n placeholder=\"https://…/clip.mp4\"\n value={url}\n disabled={!canEdit}\n aria-label=\"Media URL\"\n onChange={(e) => setUrl(e.target.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" value={urlKind} disabled={!canEdit} aria-label=\"Media kind\" onChange={(e) => setUrlKind(e.target.value as VideoAssetKind | '')}>\n <option value=\"\">Auto</option>\n <option value=\"video\">Video</option>\n <option value=\"audio\">Audio</option>\n <option value=\"image\">Image</option>\n </select>\n <button type=\"submit\" className={btn} disabled={!canEdit || !url.trim()} data-add-url=\"\">\n Add\n </button>\n </form>\n </>\n )}\n {busy > 0 && <p className=\"text-xs text-muted-foreground\">Reading media…</p>}\n {problem && (\n <p className=\"text-xs text-red-500\" role=\"alert\" data-media-error=\"\">\n {problem}\n </p>\n )}\n </div>\n );\n}\n","import React, { useEffect } from 'react';\nimport type { VideoEditorController, VideoEditorOptions } from '@pixerate/editor/video-editor';\nimport { ClipInspector } from './ClipInspector';\nimport { MediaBin } from './MediaBin';\nimport { useVideoEditor } from './useVideoEditor';\nimport { VideoPreview } from './VideoPreview';\nimport { VideoTimeline } from './VideoTimeline';\nimport { VideoTransport } from './VideoTransport';\n\nexport interface VideoEditorProps {\n /** The controller to edit. Without it one is created from `options` for the component's lifetime. */\n controller?: VideoEditorController;\n /** Options for the controller created when `controller` is not given. */\n options?: VideoEditorOptions;\n /**\n * Show the editing UI: the media bin's imports, the transport's edit buttons, timeline dragging\n * and track toggles, and the clip inspector (default true). False also makes the project read-only\n * while mounted.\n */\n editable?: boolean;\n /** Timeline zoom in pixels per second (with `onPxPerSecondChange` the host owns it). */\n pxPerSecond?: number;\n onPxPerSecondChange?: (pxPerSecond: number) => void;\n /** Canvas pixels per frame pixel for the preview (see `<VideoPreview resolution>`). */\n resolution?: number;\n /** List the project's issues under the timeline (default true). */\n showIssues?: boolean;\n className?: string;\n}\n\n/**\n * A whole non-linear video editor for a `VideoEditorController`: the media bin, the preview, the\n * transport, the timeline and the clip inspector, with the project's issues listed under the\n * timeline. Every edit is one undo step; the controller's agent tools edit the same project.\n */\nexport function VideoEditor({ controller: given, options, editable = true, pxPerSecond, onPxPerSecondChange, resolution, showIssues = true, className = '' }: VideoEditorProps) {\n const { controller, state } = useVideoEditor(given ?? options ?? {});\n useEffect(() => {\n if (editable) return;\n const was = controller.getState().readOnly;\n controller.setReadOnly(true);\n return () => controller.setReadOnly(was);\n }, [controller, editable]);\n const { issues } = state;\n const issueList = showIssues && issues.length > 0 && (\n <ul className=\"space-y-0.5 text-xs\" aria-label=\"Video issues\" data-video-issues=\"\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity === 'error' ? 'text-red-500' : issue.severity === 'warning' ? 'text-amber-500' : 'text-muted-foreground'} data-issue={issue.code}>\n {issue.message}\n </li>\n ))}\n </ul>\n );\n return (\n <div className={`flex flex-col gap-3 ${className}`} data-video-editor=\"\">\n <div className={`grid gap-3 ${editable ? 'lg:grid-cols-[16rem_minmax(0,1fr)_18rem]' : ''}`}>\n {editable && <MediaBin controller={controller} className=\"min-w-0\" />}\n <div className=\"flex min-w-0 flex-col gap-2\">\n <VideoPreview controller={controller} resolution={resolution} className=\"rounded-md border border-border\" />\n <VideoTransport controller={controller} editable={editable} />\n </div>\n {editable && <ClipInspector controller={controller} className=\"min-w-0\" />}\n </div>\n <VideoTimeline controller={controller} editable={editable} pxPerSecond={pxPerSecond} onPxPerSecondChange={onPxPerSecondChange} />\n {issueList}\n </div>\n );\n}\n"],"mappings":";AAAA,SAAS,WAAW,SAAS,QAAQ,4BAA4B;AACjE;AAAA,EACE;AAAA,EACA;AAAA,OAKK;AAIA,SAAS,yBAAyB,UAA8B,CAAC,GAA0B;AAChG,QAAM,MAAM,OAAqC,IAAI;AACrD,MAAI,CAAC,IAAI,QAAS,KAAI,UAAU,IAAI,sBAAsB,OAAO;AACjE,QAAM,aAAa,IAAI;AACvB,YAAU,MAAM,MAAM,WAAW,QAAQ,GAAG,CAAC,UAAU,CAAC;AACxD,SAAO;AACT;AAGO,SAAS,oBAAoB,YAAqD;AACvF,SAAO,qBAAqB,WAAW,WAAW,WAAW,UAAU,WAAW,QAAQ;AAC5F;AAwBO,SAAS,eAAe,sBAAkE,CAAC,GAAyB;AACzH,QAAM,MAAM,OAAqC,IAAI;AACrD,MAAI;AACJ,MAAI,+BAA+B,sBAAuB,cAAa;AAAA,OAClE;AACH,QAAI,CAAC,IAAI,QAAS,KAAI,UAAU,IAAI,sBAAsB,mBAAmB;AAC7E,iBAAa,IAAI;AAAA,EACnB;AACA,YAAU,MAAM,MAAM,IAAI,SAAS,QAAQ,GAAG,CAAC,CAAC;AAChD,QAAM,QAAQ,oBAAoB,UAAU;AAC5C,QAAM,UAAU;AAAA,IACd,OAAO;AAAA,MACL,MAAM,MAAM,WAAW,KAAK;AAAA,MAC5B,OAAO,MAAM,WAAW,MAAM;AAAA,MAC9B,QAAQ,MAAM,WAAW,OAAO;AAAA,MAChC,MAAM,CAAC,SAAiB,WAAW,KAAK,IAAI;AAAA,MAC5C,MAAM,CAAC,WAAmB,WAAW,KAAK,MAAM;AAAA,MAChD,MAAM,MAAM,KAAK,WAAW,KAAK;AAAA,MACjC,MAAM,MAAM,KAAK,WAAW,KAAK;AAAA,MACjC,iBAAiB,MAAM,WAAW,gBAAgB;AAAA,MAClD,iBAAiB,CAAC,YAAmC,WAAW,gBAAgB,OAAO;AAAA,IACzF;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AACA,SAAO,EAAE,YAAY,OAAO,GAAG,QAAQ;AACzC;AAMO,SAAS,oBACd,UACA,YACA,UAA0C,CAAC,GACrC;AACN,QAAM,EAAE,IAAI,WAAW,UAAU,UAAU,IAAI;AAC/C,YAAU,MAAM;AACd,QAAI,CAAC,SAAU;AACf,WAAO,SAAS,SAAS,8BAA8B,YAAY,EAAE,IAAI,WAAW,UAAU,UAAU,CAAC,CAAC;AAAA,EAC5G,GAAG,CAAC,UAAU,YAAY,IAAI,WAAW,UAAU,SAAS,CAAC;AAC/D;;;ACxFA,SAAgB,aAAAA,YAAW,UAAAC,eAAc;AACzC,SAAS,0BAAgF;AA8BrF;AAZG,SAAS,aAAa,EAAE,YAAY,YAAY,SAAS,YAAY,IAAI,MAAM,GAAsB;AAC1G,QAAM,EAAE,QAAQ,IAAI,oBAAoB,UAAU;AAClD,QAAM,SAASC,QAA0B,IAAI;AAC7C,QAAM,WAAWA,QAAO,OAAO;AAC/B,WAAS,UAAU;AACnB,EAAAC,WAAU,MAAM;AACd,UAAM,KAAK,OAAO;AAClB,QAAI,CAAC,GAAI;AACT,UAAM,SAAS,IAAI,mBAAmB,YAAY,IAAI,EAAE,YAAY,SAAS,CAAC,UAAU,SAAS,UAAU,KAAK,EAAE,CAAC;AACnH,WAAO,MAAM,OAAO,QAAQ;AAAA,EAC9B,GAAG,CAAC,YAAY,UAAU,CAAC;AAC3B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,OAAO,EAAE,SAAS,SAAS,OAAO,QAAQ,aAAa,GAAG,QAAQ,KAAK,MAAM,QAAQ,MAAM,IAAI,YAAY,QAAQ,cAAc,QAAQ,GAAG,MAAM;AAAA,MAClJ,MAAK;AAAA,MACL,cAAY,cAAc,QAAQ,QAAQ,WAAW;AAAA,MACrD,sBAAmB;AAAA;AAAA,EACrB;AAEJ;;;ACvCA,SAAS,2BAAuD;AAsB1D,SAaE,UAbF,OAAAC,MASA,YATA;AAZN,IAAM,MAAM;AAML,SAAS,eAAe,EAAE,YAAY,WAAW,MAAM,YAAY,GAAG,GAAwB;AACnG,QAAM,QAAQ,oBAAoB,UAAU;AAC5C,QAAM,EAAE,MAAM,UAAU,SAAS,SAAS,WAAW,SAAS,IAAI;AAClE,QAAM,UAAU,YAAY,CAAC;AAC7B,SACE,qBAAC,SAAI,WAAW,+CAA+C,SAAS,IAAI,wBAAqB,IAC/F;AAAA,oBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,KAAK,EAAE,GAAG,cAAW,kBAAiB,OAAM,2BAAqB,kBAAe,IAAG,qBAEnJ;AAAA,IACA,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,OAAO,GAAG,cAAY,UAAU,UAAU,QAAQ,OAAM,wBAAuB,UAAU,YAAY,GAAG,aAAU,IAC/K,oBAAU,UAAU,QACvB;AAAA,IACA,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,KAAK,CAAC,GAAG,cAAW,cAAa,OAAM,uBAAiB,qBAAkB,IAAG,qBAE7I;AAAA,IACA,qBAAC,UAAK,WAAU,2CAA0C,iBAAc,IACrE;AAAA,0BAAoB,MAAM,QAAQ,GAAG;AAAA,MAAE;AAAA,MAAI,oBAAoB,UAAU,QAAQ,GAAG;AAAA,OACvF;AAAA,IACC,YACC,iCACE;AAAA,sBAAAA,KAAC,UAAK,WAAU,2BAA0B;AAAA,MAC1C,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,gBAAgB,GAAG,UAAU,CAAC,SAAS,OAAM,6BAA4B,cAAW,IAAG,mBAEvJ;AAAA,MACA,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,gBAAgB,GAAG,UAAU,CAAC,WAAW,CAAC,UAAU,QAAQ,OAAM,sCAAqC,eAAY,IAAG,oBAEtL;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW;AAAA,UACX,SAAS,MAAM,WAAW,gBAAgB,EAAE,QAAQ,KAAK,CAAC;AAAA,UAC1D,UAAU,CAAC,WAAW,CAAC,UAAU;AAAA,UACjC,OAAM;AAAA,UACN,sBAAmB;AAAA,UACpB;AAAA;AAAA,MAED;AAAA,MACA,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,SAAS,OAAO,MAAM,UAAU,QAAQ,MAAM,SAAS,KAAK,mBAAmB,aAAU,IAAG,kBAEjM;AAAA,MACA,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,MAAM,WAAW,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,SAAS,OAAO,MAAM,UAAU,QAAQ,MAAM,SAAS,KAAK,mBAAmB,aAAU,IAAG,kBAEjM;AAAA,OACF;AAAA,KAEJ;AAEJ;;;AChEA,SAAgB,aAAAC,YAAW,UAAAC,SAAQ,gBAAgB;AACnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AAsLH,SAkIoB,YAAAC,WAlIpB,OAAAC,MAiBI,QAAAC,aAjBJ;AAxKJ,IAAM,SAAS;AACf,IAAM,QAAQ;AACd,IAAM,MAAM;AACZ,IAAM,SAAS;AACf,IAAM,iBAAiB;AACvB,IAAM,WAAW;AACjB,IAAM,WAAW;AAWjB,IAAM,UAAU,CAAC,OAA2B,cAAc,eAAe,4BAA4B,KAAK,GAAG,OAAO;AAa7G,SAAS,cAAc,EAAE,YAAY,aAAa,qBAAqB,WAAW,MAAM,YAAY,GAAG,GAAuB;AACnI,QAAM,QAAQ,oBAAoB,UAAU;AAC5C,QAAM,EAAE,SAAS,UAAU,MAAM,WAAW,UAAU,MAAM,IAAI;AAChE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,eAAe,GAAG;AACzD,QAAM,KAAK,sBAAuB,eAAe,MAAO;AACxD,QAAM,UAAU,YAAY,CAAC;AAC7B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,UAAUC,QAAoB,IAAI;AACxC,QAAM,OAAOA,QAAO,oBAAI,IAA4B,CAAC;AACrD,QAAM,QAAQA,QAAuB,IAAI;AACzC,QAAM,SAAS,cAAc,OAAO;AACpC,QAAM,SAAS,CAAC,OAAgB,KAAK,KAAM;AAC3C,QAAM,OAAO,KAAK,IAAI,WAAW,KAAM,GAAK;AAC5C,QAAM,QAAQ,OAAO,IAAI;AACzB,QAAM,QAAQ,gBAAgB,MAAM,IAAI,QAAQ,GAAG;AAEnD,QAAM,SAAS,CAAC,YAAoB;AAClC,UAAM,MAAM,MAAM,QAAS,sBAAsB;AACjD,eAAW,KAAK,KAAK,IAAI,IAAK,UAAU,IAAI,QAAQ,MAAQ,EAAE,CAAC;AAAA,EACjE;AAEA,QAAM,UAAU,CAAC,QAAgB,YAAwC;AACvE,UAAM,UAAU,gBAAgB,SAAS,MAAM;AAC/C,eAAW,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS;AACnC,YAAM,MAAM,GAAG,sBAAsB;AACrC,UAAI,WAAW,IAAI,OAAO,UAAU,IAAI,UAAU,QAAQ,SAAS,EAAE,EAAG,QAAO;AAAA,IACjF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,CAAC,GAAS,MACxB,kBAAkB,SAAS;AAAA,IACzB,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,QAAS,EAAE,UAAU,EAAE,KAAK,MAAQ;AAAA,IACpC,OAAO,EAAE,SAAS,SAAS,QAAQ,EAAE,MAAM,EAAE,OAAO,IAAI;AAAA,IACxD,QAAQ,EAAE;AAAA,IACV,WAAW,EAAE,SAAS,WAAW,EAAE,WAAW,cAAc;AAAA,IAC5D,MAAM,CAAC,MAAM,WAAW,KAAK,GAAG,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;AAAA,EACzD,CAAC;AAEH,QAAM,SAAS,CAAC,SAAsB;AACpC,YAAQ,UAAU;AAClB,YAAQ,IAAI;AAAA,EACd;AAGA,QAAM,OAAOA,QAAO,EAAE,SAAS,UAAU,CAAC;AAC1C,OAAK,UAAU,EAAE,SAAS,UAAU;AACpC,QAAM,WAAWA,QAA4B,IAAI;AACjD,EAAAC,WAAU,MAAM,MAAM,SAAS,UAAU,GAAG,CAAC,CAAC;AAE9C,QAAM,oBAAoB,CAAC,GAAuC,MAAiB,UAAsB;AACvG,QAAI,EAAE,WAAW,UAAa,EAAE,WAAW,EAAG;AAC9C,MAAE,gBAAgB;AAClB,QAAI,CAAC,SAAU;AACf,QAAI;AACF,QAAE,cAAc,oBAAoB,EAAE,SAAS;AAAA,IACjD,QAAQ;AAAA,IAER;AACA,UAAM,SAAU,EAAE,OAAuB,QAAQ,eAAe,GAAG,aAAa,aAAa;AAC7F,UAAM,OAA0B,WAAW,UAAU,eAAe,WAAW,QAAQ,aAAa;AACpG,UAAM,QAAQ,CAAC,WAAW,CAAC,CAAC,MAAM;AAClC,aAAS,UAAU;AACnB,WAAO,EAAE,MAAM,KAAK,IAAI,MAAM,GAAG,EAAE,SAAS,SAAS,EAAE,WAAW,OAAO,OAAO,MAAM,KAAK,CAAC;AAE5F,UAAM,OAAO,CAAC,OAAqB;AACjC,YAAM,IAAI,QAAQ;AAClB,UAAI,CAAC,KAAK,MAAO;AACjB,UAAI,CAAC,EAAE,SAAS,KAAK,IAAI,GAAG,UAAU,EAAE,CAAC,IAAI,eAAgB;AAC7D,UAAI,CAAC,EAAE,SAAS,CAAC,KAAK,QAAQ,UAAU,SAAS,EAAE,IAAI,EAAG,YAAW,OAAO,CAAC,EAAE,IAAI,CAAC;AACpF,aAAO,EAAE,GAAG,GAAG,OAAO,MAAM,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,EAAE,CAAC;AAAA,IACjE;AACA,UAAM,KAAK,CAAC,OAAqB;AAC/B,WAAK;AACL,YAAM,IAAI,QAAQ;AAClB,UAAI,CAAC,EAAG;AACR,aAAO,IAAI;AACX,UAAI,CAAC,EAAE,OAAO;AACZ,YAAI,GAAG,YAAY,GAAG,WAAW,GAAG,QAAS,YAAW,OAAO,CAAC,EAAE,IAAI,GAAG,EAAE,KAAK,KAAK,CAAC;AAAA,YACjF,YAAW,OAAO,CAAC,EAAE,IAAI,CAAC;AAC/B;AAAA,MACF;AACA,YAAM,OAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE;AACvC,UAAI,MAAM,KAAM,YAAW,MAAM,KAAK,MAAM,eAAe,KAAK,IAAI,CAAC;AAAA,IACvE;AACA,UAAM,SAAS,MAAM;AACnB,WAAK;AACL,aAAO,IAAI;AAAA,IACb;AACA,UAAM,OAAO,MAAM;AACjB,aAAO,oBAAoB,eAAe,IAAI;AAC9C,aAAO,oBAAoB,aAAa,EAAE;AAC1C,aAAO,oBAAoB,iBAAiB,MAAM;AAClD,eAAS,UAAU;AAAA,IACrB;AACA,WAAO,iBAAiB,eAAe,IAAI;AAC3C,WAAO,iBAAiB,aAAa,EAAE;AACvC,WAAO,iBAAiB,iBAAiB,MAAM;AAC/C,aAAS,UAAU;AAAA,EACrB;AAEA,QAAM,YAAY,CAAC,MAA2C;AAC5D,QAAI,QAAQ,EAAE,MAAM,EAAG;AACvB,UAAM,MAAM,EAAE,WAAW,EAAE;AAC3B,UAAM,MAAM,EAAE,IAAI,YAAY;AAC9B,QAAI,EAAE,QAAQ,KAAK;AACjB,UAAK,EAAE,OAAuB,YAAY,SAAU;AACpD,iBAAW,OAAO;AAAA,IACpB,WAAW,EAAE,QAAQ,eAAe,CAAC,IAAK,YAAW,KAAK,EAAE;AAAA,aACnD,EAAE,QAAQ,gBAAgB,CAAC,IAAK,YAAW,KAAK,CAAC;AAAA,aACjD,EAAE,QAAQ,SAAU,YAAW,OAAO,IAAI;AAAA,aAC1C,CAAC,QAAS;AAAA,aACV,QAAQ,OAAO,CAAC,IAAK,YAAW,gBAAgB;AAAA,cAC/C,EAAE,QAAQ,YAAY,EAAE,QAAQ,gBAAgB,CAAC,IAAK,YAAW,gBAAgB,EAAE,QAAQ,EAAE,SAAS,CAAC;AAAA,aACxG,QAAQ,OAAO,KAAK;AAC3B,UAAI,EAAE,SAAU,YAAW,KAAK;AAAA,UAC3B,YAAW,KAAK;AAAA,IACvB,MAAO;AACP,MAAE,eAAe;AAAA,EACnB;AAGA,QAAM,SAAS,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5C;AAAA,IACA,OAAO,QAAQ,OAAO;AAAA,MAAQ,CAAC,MAC7B,EAAE,MACC,IAAI,CAAC,SAAS;AACb,cAAM,OAAO,MAAM,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK,OAAO;AAChE,cAAM,KAAK,OAAO,KAAK,QAAQ,EAAE;AACjC,eAAO,OAAO,MAAM,KAAK,EAAE,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,QAAQ,KAAK,OAAO,UAAU,OAAO,KAAK,WAAW,KAAK,UAAU,UAAU,CAAC,CAAC,KAAK,IAAI;AAAA,MACxJ,CAAC,EACA,OAAO,CAAC,MAAkC,MAAM,IAAI;AAAA,IACzD;AAAA,EACF,EAAE;AAEF,QAAM,SAAS,CAAC,OAAmBC,QAAsC,UACvE,gBAAAJ;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,6CAA6C,MAAMI,MAAK,IAAI,iDAAiD,qCAAqC;AAAA,MAC7J,gBAAc,CAAC,CAAC,MAAMA,MAAK;AAAA,MAC3B,cAAY,GAAG,KAAK,IAAI,MAAM,QAAQ,MAAM,EAAE;AAAA,MAC9C,OAAO;AAAA,MACP,UAAU,CAAC;AAAA,MACX,qBAAmBA;AAAA,MACnB,SAAS,MAAM,WAAW,MAAM,EAAE,IAAI,gBAAgB,OAAO,MAAM,IAAI,KAAK,EAAE,CAACA,MAAK,GAAG,CAAC,MAAMA,MAAK,EAAE,EAAE,GAAG,GAAG,MAAMA,MAAK,IAAI,OAAO,EAAE,GAAGA,WAAU,UAAU,SAASA,WAAU,WAAW,SAAS,MAAM,QAAQ;AAAA,MAEhN,UAAAA,WAAU,UAAU,MAAMA,WAAU,WAAW,MAAM;AAAA;AAAA,EACxD;AAGF,SACE,gBAAAH,MAAC,SAAI,WAAW,uBAAuB,SAAS,IAAI,+BAA4B,IAC7E;AAAA,KAAC,uBACA,gBAAAA,MAAC,WAAM,WAAU,kEAAiE;AAAA;AAAA,MAEhF,gBAAAD,KAAC,WAAM,MAAK,SAAQ,KAAK,UAAU,KAAK,UAAU,MAAM,GAAG,OAAO,SAAS,cAAW,iBAAgB,UAAU,CAAC,MAAM,WAAW,OAAO,EAAE,OAAO,KAAK,CAAC,GAAG,aAAU,IAAG;AAAA,OAC1K;AAAA,IAEF,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,UAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAW;AAAA,QACX;AAAA,QACA,uBAAoB;AAAA,QAEpB,0BAAAC,MAAC,SAAI,WAAU,YAAW,OAAO,EAAE,OAAO,SAAS,MAAM,GACvD;AAAA,0BAAAA,MAAC,SAAI,WAAU,QAAO,OAAO,EAAE,QAAQ,MAAM,GAC3C;AAAA,4BAAAD,KAAC,SAAI,WAAU,6EAA4E,OAAO,EAAE,OAAO,OAAO,GAAG;AAAA,YACrH,gBAAAC;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK;AAAA,gBACL,WAAU;AAAA,gBACV,OAAO,EAAE,MAAM;AAAA,gBACf,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,iBAAe;AAAA,gBACf,iBAAe;AAAA,gBACf,iBAAe,KAAK,MAAM,IAAI;AAAA,gBAC9B,cAAW;AAAA,gBACX,eAAe,CAAC,MAAM;AACpB,sBAAI;AACF,sBAAE,cAAc,oBAAoB,EAAE,SAAS;AAAA,kBACjD,QAAQ;AAAA,kBAER;AACA,yBAAO,EAAE,OAAO;AAAA,gBAClB;AAAA,gBACA,eAAe,CAAC,MAAM;AACpB,sBAAI,EAAE,UAAU,EAAG,QAAO,EAAE,OAAO;AAAA,gBACrC;AAAA,gBAEC;AAAA,wBAAM,IAAI,CAAC,MACV,gBAAAD,KAAC,SAAiB,WAAW,0BAA0B,EAAE,QAAQ,iCAAiC,4BAA4B,IAAI,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,aAAW,EAAE,MAClK,YAAE,SAAS,gBAAAA,KAAC,UAAK,WAAU,2DAA2D,YAAE,OAAM,KADvF,EAAE,IAEZ,CACD;AAAA,mBACC,QAAQ,WAAW,CAAC,GAAG,IAAI,CAAC,MAC5B,gBAAAA;AAAA,oBAAC;AAAA;AAAA,sBAEC,WAAU;AAAA,sBACV,OAAO,EAAE,MAAM,OAAO,EAAE,IAAI,GAAG,YAAY,EAAE,SAAS,UAAU;AAAA,sBAChE,OAAO,GAAG,EAAE,SAAS,EAAE,EAAE,OAAO,mBAAmB,EAAE,IAAI,CAAC;AAAA,sBAC1D,eAAa,EAAE;AAAA;AAAA,oBAJV,EAAE;AAAA,kBAKT,CACD;AAAA;AAAA;AAAA,YACH;AAAA,aACF;AAAA,UACC,OAAO,IAAI,CAAC,EAAE,OAAO,MAAM,MAC1B,gBAAAC,MAAC,SAAmB,WAAU,+CAA8C,OAAO,EAAE,QAAQ,IAAI,GAAG,cAAY,MAAM,IACpH;AAAA,4BAAAA,MAAC,SAAI,WAAU,sHAAqH,OAAO,EAAE,OAAO,OAAO,GAAG,qBAAkB,IAC9K;AAAA,8BAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,gCAAAD,KAAC,UAAK,WAAU,wBAAwB,gBAAM,QAAQ,MAAM,IAAG;AAAA,gBAC/D,gBAAAA,KAAC,UAAK,WAAU,qEAAoE,mBAAgB,IACjG,gBAAM,MACT;AAAA,iBACF;AAAA,cACC,YACC,gBAAAC,MAAC,SAAI,WAAU,cACZ;AAAA,uBAAO,OAAO,SAAS,MAAM;AAAA,gBAC7B,MAAM,SAAS,WAAW,OAAO,OAAO,UAAU,MAAM;AAAA,gBACxD,OAAO,OAAO,UAAU,MAAM;AAAA,iBACjC;AAAA,eAEJ;AAAA,YACA,gBAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK,CAAC,OAAO;AACX,sBAAI,GAAI,MAAK,QAAQ,IAAI,MAAM,IAAI,EAAE;AAAA,sBAChC,MAAK,QAAQ,OAAO,MAAM,EAAE;AAAA,gBACnC;AAAA,gBACA,WAAW,qBAAqB,MAAM,SAAS,eAAe,EAAE,IAAI,MAAM,SAAS,gBAAgB,EAAE;AAAA,gBACrG,OAAO,EAAE,MAAM;AAAA,gBACf,mBAAiB,MAAM;AAAA,gBACvB,eAAe,CAAC,MAAM;AACpB,sBAAI,EAAE,WAAW,EAAE,iBAAiB,SAAU,YAAW,OAAO,IAAI;AAAA,gBACtE;AAAA,gBAEC,gBAAM,IAAI,CAAC,EAAE,MAAM,MAAM,OAAO,UAAU,QAAQ,SAAS,MAAM;AAChE,wBAAM,QAAQ,KAAK,SAAS,UAAU,OAAO,IAAI,KAAK,KAAK,IAAI;AAC/D,wBAAM,WAAW,UAAU,SAAS,KAAK,EAAE;AAC3C,wBAAM,QAAQ,eAAe,MAAM,KAAK;AACxC,yBACE,gBAAAC;AAAA,oBAAC;AAAA;AAAA,sBAEC,WAAW,iGAAiG,WAAW,CAAC,KAAK,SAAS,gBAAgB,gBAAgB,IAAI,WAAW,yBAAyB,EAAE;AAAA,sBAChN,OAAO;AAAA,wBACL,MAAM,OAAO,KAAK;AAAA,wBAClB,OAAO,KAAK,IAAI,GAAG,OAAO,MAAM,CAAC;AAAA,wBACjC,YAAY,cAAc,MAAM,KAAK;AAAA,wBACrC,SAAS,WAAW,sBAAsB;AAAA,wBAC1C,eAAe,WAAW,IAAI;AAAA,wBAC9B,QAAQ,WAAW,KAAK,WAAW,IAAI;AAAA,sBACzC;AAAA,sBACA,OAAO,GAAG,KAAK,SAAM,mBAAmB,KAAK,CAAC,SAAI,mBAAmB,QAAQ,MAAM,CAAC;AAAA,sBACpF,aAAW,KAAK;AAAA,sBAChB,iBAAe,WAAW,KAAK;AAAA,sBAC/B,iBAAe,WAAW,KAAK;AAAA,sBAC/B,iBAAe;AAAA,sBACf,eAAe,CAAC,MAAM,kBAAkB,GAAG,MAAM,IAAI;AAAA,sBAErD;AAAA,wCAAAD,KAAC,UAAK,WAAU,8DAA8D,iBAAM;AAAA,wBACnF,KAAK,cACJ,gBAAAC,MAAC,UAAK,WAAU,gGAA+F,OAAO,GAAG,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,QAAQ,OAAO,mBAAiB,KAAK,WAAW,MAAM;AAAA;AAAA,0BAClN,KAAK,WAAW;AAAA,2BACrB;AAAA,wBAED,WAAW,CAAC,KAAK,UAChB,gBAAAA,MAAAF,WAAA,EACE;AAAA,0CAAAC,KAAC,SAAI,WAAU,2EAA0E,OAAO,EAAE,OAAO,OAAO,GAAG,eAAY,SAAQ,eAAY,QAAO;AAAA,0BAC1J,gBAAAA,KAAC,SAAI,WAAU,4EAA2E,OAAO,EAAE,OAAO,OAAO,GAAG,eAAY,OAAM,eAAY,QAAO;AAAA,2BAC3J;AAAA;AAAA;AAAA,oBA3BG,KAAK;AAAA,kBA6BZ;AAAA,gBAEJ,CAAC;AAAA;AAAA,YACH;AAAA,eAlEQ,MAAM,EAmEhB,CACD;AAAA,UACD,gBAAAA,KAAC,SAAI,WAAU,kFAAiF,OAAO,EAAE,MAAM,SAAS,OAAO,IAAI,GAAG,QAAQ,GAAG,GAAG,iBAAc,IAAG;AAAA,WACvK;AAAA;AAAA,IACF;AAAA,IACC,SACC,gBAAAA,KAAC,OAAE,WAAU,wBAAuB,MAAK,SAAQ,mBAAgB,IAC9D,iBACH;AAAA,KAEJ;AAEJ;;;AC3VA;AAAA,EACE;AAAA,EACA,sBAAAK;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAuBD,SAkIE,YAAAC,WAlIF,OAAAC,MAeF,QAAAC,aAfE;AAfN,IAAM,QAAQ;AACd,IAAM,qBAAqB;AAG3B,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA0J;AACxJ,QAAM,SAAS,CAAC,QAAgB;AAC9B,QAAI,QAAQ,MAAO,UAAS,GAAG;AAAA,EACjC;AACA,MAAI;AACF,WACE,gBAAAD;AAAA,MAAC;AAAA;AAAA,QAEC,WAAW;AAAA,QACX,MAAM;AAAA,QACN,cAAc;AAAA,QACd,UAAU,KAAK;AAAA,QACf,cAAY,KAAK,YAAY;AAAA,QAC7B,QAAQ,CAAC,MAAM,OAAO,EAAE,cAAc,KAAK;AAAA;AAAA,MANtC;AAAA,IAOP;AAEJ,SAAO,gBAAAA,KAAC,WAAkB,WAAW,OAAQ,GAAG,MAAM,cAAc,OAAO,WAAW,CAAC,MAAM,EAAE,QAAQ,WAAW,EAAE,cAAc,KAAK,GAAG,QAAQ,CAAC,MAAM,OAAO,EAAE,cAAc,KAAK,KAAlK,KAAqK;AAC1L;AAEA,SAAS,IAAI,EAAE,OAAO,MAAM,SAAS,GAA+D;AAClG,SACE,gBAAAC,MAAC,WAAM,WAAU,oEAAmE,cAAY,MAC9F;AAAA,oBAAAD,KAAC,UAAK,WAAU,yBAAyB,iBAAM;AAAA,IAC9C;AAAA,KACH;AAEJ;AASO,SAAS,cAAc,EAAE,YAAY,YAAY,GAAG,GAAuB;AAChF,QAAM,QAAQ,oBAAoB,UAAU;AAC5C,QAAM,EAAE,SAAS,WAAW,UAAU,KAAK,IAAI;AAC/C,MAAI,UAAU,WAAW;AACvB,WACE,gBAAAA,KAAC,OAAE,WAAW,iCAAiC,SAAS,IAAI,uBAAoB,IAC7E,oBAAU,SAAS,GAAG,UAAU,MAAM,iDAAiD,6CAC1F;AAEJ,QAAM,KAAK,cAAc,SAAS,UAAU,CAAC,CAAC;AAC9C,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,OAAkB,GAAG;AAC3B,QAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM;AAExC,QAAM,MAAM,CAAC,KAAa,OAAgB,QAAQ,UAAU,GAAG,OAAO;AACpE,QAAI,UAAU,UAAa,UAAU,GAAI,YAAW,MAAM,EAAE,IAAI,eAAe,MAAM,KAAK,IAAI,QAAQ,CAAC,GAAG,EAAE,GAAG,KAAK;AAAA,QAC/G,YAAW,MAAM,EAAE,IAAI,eAAe,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC,GAAG,GAAG,MAAM,EAAE,GAAG,KAAK;AAAA,EAC1F;AACA,QAAM,MAAM,CAAC,KAAa,KAAa,UAAmB;AACxD,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,CAAC,QAAS,QAAO,IAAI,KAAK,QAAW,KAAK;AAC9C,UAAM,IAAI,OAAO,OAAO;AACxB,QAAI,OAAO,SAAS,CAAC,EAAG,KAAI,KAAK,GAAG,KAAK;AAAA,EAC3C;AACA,QAAM,OAAO,CAAC,KAAa,UAAmB;AAC5C,UAAM,QAAS,KAA4C,GAAG;AAC9D,WACE,gBAAAA,KAAC,UAAO,OAAO,UAAU,SAAY,KAAK,OAAO,KAAK,GAAG,cAAY,SAAS,KAAK,UAAoB,UAAU,CAAC,QAAQ,IAAI,KAAK,IAAI,KAAK,KAAK,QAAW,UAAU,SAAS,GAAG,EAAE,GAAG;AAAA,EAE3L;AACA,QAAM,SAAS,CAAC,KAAa,OAAe,QAAuD,CAAC,MAAM;AACxG,UAAM,QAAS,KAA4C,GAAG;AAC9D,WAAO,gBAAAA,KAAC,UAAO,MAAK,UAAS,OAAO,UAAU,SAAY,KAAK,OAAO,KAAK,GAAG,cAAY,OAAO,UAAoB,UAAU,CAAC,QAAQ,IAAI,KAAK,KAAK,UAAU,MAAM,YAAY,CAAC,EAAE,GAAI,GAAG,OAAO;AAAA,EACrM;AAEA,QAAM,aAAa,KAAK;AACxB,SACE,gBAAAC,MAAC,SAAI,WAAW,yBAAyB,SAAS,IAAI,uBAAoB,IAAG,gBAAc,KAAK,IAC9F;AAAA,oBAAAA,MAAC,SAAI,WAAU,mCACb;AAAA,sBAAAD,KAAC,UAAK,WAAU,eAAe,eAAK,SAAS,UAAU,eAAe,KAAK,SAAS,SAAS,UAAU,eAAc;AAAA,MACrH,gBAAAA,KAAC,UAAK,WAAU,yBAAyB,eAAK,IAAG;AAAA,MAChD,GAAG,MAAM,UAAU,gBAAAA,KAAC,UAAK,WAAU,kBAAiB,0BAAY;AAAA,OACnE;AAAA,IACA,gBAAAA,KAAC,OAAI,OAAM,QAAO,MAAK,QACpB,eAAK,QAAQ,MAAM,GACtB;AAAA,IACA,gBAAAA,KAAC,OAAI,OAAM,SAAQ,MAAK,SACtB,0BAAAA,KAAC,UAAK,WAAU,wBAAwB,UAAAE,oBAAmB,KAAK,KAAK,GAAE,GACzE;AAAA,IACA,gBAAAF,KAAC,OAAI,OAAM,YAAW,MAAK,YACzB,0BAAAA,KAAC,UAAK,WAAU,wBAAwB,UAAAE,oBAAmB,KAAK,QAAQ,GAAE,GAC5E;AAAA,IACC,kBAAkB,SAAS,KAAK,IAAI,IAAI,EAAE,IAAI,CAAC,QAC9C,gBAAAF,KAAC,OAAuB,OAAO,IAAI,OAAO,MAAM,IAAI,UAClD,0BAAAC,MAAC,UAAK,WAAU,mCAAkC,qBAAmB,IAAI,UACvE;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO,IAAI,UAAU,IAAI,aAAa,YAAY,KAAK,UAAU,IAAI,aAAa,WAAY,KAA8B,SAAS,KAAK,YAAY,IAAI,QAAQ,OAAO,SAAY,OAAO,IAAI,KAAK,IAAI;AAAA,UACzM,aAAa,OAAO,IAAI,KAAK;AAAA,UAC7B,cAAY,IAAI;AAAA,UAChB;AAAA,UACA,KAAK,IAAI;AAAA,UACT,KAAK,IAAI;AAAA,UACT,MAAM,IAAI;AAAA,UACV,UAAU,CAAC,QAAQ;AACjB,kBAAM,IAAI,OAAO,GAAG;AACpB,gBAAI,IAAI,KAAK,KAAK,CAAC,OAAO,SAAS,CAAC,EAAG;AACvC,kBAAM,OAAO,uBAAuB,SAAS,KAAK,IAAI,IAAI,UAAU,MAAM,IAAI,KAAK,IAAI,IAAI,MAAS;AACpG,gBAAI,KAAM,YAAW,MAAM,MAAM,IAAI,QAAQ,YAAY,IAAI,MAAM,YAAY,CAAC,KAAK,UAAU,IAAI,MAAM,YAAY,CAAC,EAAE;AAAA,UAC1H;AAAA;AAAA,MACF;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,qDAAqD,IAAI,QAAQ,mBAAmB,IAAI,QAAQ,oBAAoB,uBAAuB;AAAA,UACtJ,cAAY,GAAG,IAAI,QAAQ,WAAW,KAAK,IAAI,IAAI,MAAM,YAAY,CAAC;AAAA,UACtE,gBAAc,IAAI;AAAA,UAClB,OAAO,IAAI,QAAQ,GAAG,IAAI,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,GAAG,KAAK,IAAI,QAAQ,mBAAmB,SAAS,qBAAqB;AAAA,UAChI;AAAA,UACA,wBAAsB,IAAI;AAAA,UAC1B,SAAS,MAAM;AACb,kBAAM,OAAO,wBAAwB,SAAS,KAAK,IAAI,IAAI,UAAU,IAAI;AACzE,gBAAI,KAAM,YAAW,MAAM,MAAM,GAAG,IAAI,QAAQ,WAAW,KAAK,IAAI,IAAI,MAAM,YAAY,CAAC,WAAW;AAAA,UACxG;AAAA,UAEC,cAAI,QAAQ,WAAM;AAAA;AAAA,MACrB;AAAA,MACC,IAAI,SACH,gBAAAA,KAAC,UAAK,WAAU,2DAA0D,uBAAqB,IAAI,UAChG,cAAI,MACP;AAAA,OAEJ,KAtCQ,IAAI,QAuCd,CACD;AAAA,IACD,gBAAAA,KAAC,OAAI,OAAM,gBAAe,MAAK,UAC5B,iBAAO,UAAU,WAAW,EAAE,KAAK,GAAG,MAAM,IAAI,CAAC,GACpD;AAAA,IACA,gBAAAA,KAAC,OAAI,OAAM,iBAAgB,MAAK,WAC7B,iBAAO,WAAW,YAAY,EAAE,KAAK,GAAG,MAAM,IAAI,CAAC,GACtD;AAAA,IACC,KAAK,SAAS,WACb,gBAAAC,MAAAF,WAAA,EACE;AAAA,sBAAAC,KAAC,OAAI,OAAM,SAAQ,MAAK,SACrB,iBAAO,SAAS,SAAS,EAAE,KAAK,KAAK,MAAM,KAAK,CAAC,GACpD;AAAA,MACA,gBAAAA,KAAC,OAAI,OAAM,SAAQ,MAAK,SACtB,0BAAAA,KAAC,WAAM,MAAK,YAAW,WAAU,sBAAqB,SAAS,CAAC,CAAC,KAAK,OAAO,UAAoB,cAAW,SAAQ,UAAU,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,WAAW,QAAW,EAAE,OAAO,UAAU,cAAc,aAAa,GAAG,GACpO;AAAA,OACF;AAAA,IAED,KAAK,SAAS,UACb,gBAAAC,MAAAF,WAAA,EACE;AAAA,sBAAAC,KAAC,OAAI,OAAM,QAAO,MAAK,QACrB,0BAAAA,KAAC,UAAO,WAAS,MAAC,OAAO,KAAK,MAAM,cAAW,QAAO,UAAoB,UAAU,CAAC,QAAQ,IAAI,QAAQ,KAAK,aAAa,GAAG,GAChI;AAAA,MACA,gBAAAA,KAAC,OAAI,OAAM,UAAS,MAAK,SACtB,eAAK,SAAS,QAAQ,GACzB;AAAA,MACA,gBAAAA,KAAC,OAAI,OAAM,aAAY,MAAK,YACzB,iBAAO,YAAY,aAAa,EAAE,KAAK,GAAG,MAAM,EAAE,CAAC,GACtD;AAAA,MACA,gBAAAA,KAAC,OAAI,OAAM,cAAa,MAAK,cAC1B,eAAK,cAAc,YAAY,GAClC;AAAA,OACF;AAAA,IAED,KAAK,SAAS,WACb,gBAAAA,KAAC,OAAI,OAAM,UAAS,MAAK,SACvB,0BAAAA,KAAC,UAAO,OAAO,KAAK,OAAO,cAAW,UAAS,UAAoB,UAAU,CAAC,QAAQ,IAAI,KAAK,KAAK,IAAI,SAAS,IAAI,KAAK,GAAG,eAAe,GAAG,GACjJ;AAAA,IAEF,gBAAAA,KAAC,OAAI,OAAM,iBAAgB,MAAK,cAC9B,0BAAAC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,QACX,OAAO,YAAY,QAAQ;AAAA,QAC3B;AAAA,QACA,cAAW;AAAA,QACX,UAAU,CAAC,MAAM;AACf,gBAAM,OAAO,EAAE,OAAO;AACtB,qBAAW;AAAA,YACT,EAAE,IAAI,kBAAkB,MAAM,KAAK,IAAI,YAAY,OAAO,EAAE,GAAG,YAAY,MAAM,UAAU,YAAY,YAAY,mBAAmB,IAAI,KAAK;AAAA,YAC/I,OAAO,mBAAmB;AAAA,UAC5B;AAAA,QACF;AAAA,QAEA;AAAA,0BAAAD,KAAC,YAAO,OAAM,IAAG,kBAAI;AAAA,UACpB,uBAAuB,IAAI,CAAC,MAC3B,gBAAAA,KAAC,YAAe,OAAO,GACpB,gBAAM,cAAc,cAAc,MAAM,QAAQ,kBAAkB,UADxD,CAEb,CACD;AAAA;AAAA;AAAA,IACH,GACF;AAAA,IACC,cACC,gBAAAA,KAAC,OAAI,OAAM,eAAc,MAAK,sBAC5B,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,OAAO,WAAW,QAAQ;AAAA,QACjC,cAAW;AAAA,QACX;AAAA,QACA,UAAU,CAAC,QAAQ;AACjB,gBAAM,IAAI,OAAO,GAAG;AACpB,cAAI,IAAI,KAAK,KAAK,OAAO,SAAS,CAAC,KAAK,IAAI,EAAG,YAAW,MAAM,EAAE,IAAI,kBAAkB,MAAM,KAAK,IAAI,YAAY,EAAE,GAAG,YAAY,UAAU,EAAE,EAAE,GAAG,0BAA0B;AAAA,QACjL;AAAA;AAAA,IACF,GACF;AAAA,KAEJ;AAEJ;;;AC1OA,SAAgB,YAAAG,iBAAgB;AAChC;AAAA,EACE,sBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAkGD,SAwBE,YAAAC,WAxBF,OAAAC,MAIM,QAAAC,aAJN;AApFN,IAAMC,OAAM;AAOL,SAAS,SAAS,EAAE,YAAY,WAAW,MAAM,WAAW,QAAQ,iBAAiB,YAAY,GAAG,GAAkB;AAC3H,QAAM,EAAE,SAAS,SAAS,IAAI,oBAAoB,UAAU;AAC5D,QAAM,UAAU,YAAY,CAAC;AAC7B,QAAM,CAAC,KAAK,MAAM,IAAIC,UAAS,EAAE;AACjC,QAAM,CAAC,SAAS,UAAU,IAAIA,UAA8B,EAAE;AAC9D,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAS,CAAC;AAClC,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAwB,IAAI;AAC1D,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAS,KAAK;AAEtC,QAAM,YAAY,OAAO,KAAa,MAAsB,SAAiB;AAC3E,YAAQ,CAAC,MAAM,IAAI,CAAC;AACpB,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM,KAAK,IAAI,EAAE,MAAM,MAAM,IAAI;AACrD,YAAM,SAAS,WAAW,MAAM,EAAE,IAAI,aAAa,OAAO,EAAE,MAAM,KAAK,MAAM,GAAI,SAAS,CAAC,EAAG,EAAE,GAAG,UAAU,IAAI,EAAE;AACnH,UAAI,CAAC,OAAO,GAAI,YAAW,OAAO,KAAK;AAAA,IACzC,UAAE;AACA,cAAQ,CAAC,MAAM,IAAI,CAAC;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,WAAW,OAAO,UAAoC;AAC1D,eAAW,IAAI;AACf,eAAW,QAAQ,MAAM,KAAK,SAAS,CAAC,CAAC,GAAG;AAC1C,YAAM,OAAO,iBAAiB,KAAK,QAAQ,KAAK,IAAI;AACpD,UAAI,CAAC,MAAM;AACT,mBAAW,IAAI,KAAK,IAAI,wCAAwC;AAChE;AAAA,MACF;AACA,YAAM,MAAM,YAAY,UAAU,IAAI,IAAI,IAAI,gBAAgB,IAAI;AAClE,YAAM,UAAU,KAAK,MAAM,KAAK,IAAI;AAAA,IACtC;AAAA,EACF;AAEA,QAAM,SAAS,YAAY;AACzB,UAAM,MAAM,IAAI,KAAK;AACrB,QAAI,CAAC,IAAK;AACV,UAAM,OAAO,WAAW,iBAAiB,IAAI,QAAQ,WAAW,EAAE,CAAC;AACnE,QAAI,CAAC,MAAM;AACT,iBAAW,uBAAuB,GAAG,iDAAiD;AACtF;AAAA,IACF;AACA,eAAW,IAAI;AACf,UAAM,UAAU,KAAK,MAAM,IAAI,QAAQ,WAAW,EAAE,EAAE,QAAQ,YAAY,EAAE,KAAK,GAAG;AACpF,WAAO,EAAE;AAAA,EACX;AAEA,QAAM,gBAAgB,CAAC,UAAsB;AAC3C,UAAM,OAAO,mBAAmB,SAAS,MAAM,EAAE;AACjD,QAAI,CAAC,MAAM;AACT,iBAAW,wBAAwB,MAAM,SAAS,UAAU,UAAU,OAAO,kBAAkB,MAAM,QAAQ,MAAM,EAAE,OAAO;AAC5H;AAAA,IACF;AACA,eAAW,IAAI;AACf,UAAM,SAAS,WAAW,MAAM,MAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,EAAE;AACrE,QAAI,OAAO,IAAI;AACb,YAAM,QAAQ,OAAO,SAAS,CAAC,GAAG;AAClC,UAAI,MAAO,YAAW,OAAO,CAAC,KAAK,CAAC;AAAA,IACtC;AAAA,EACF;AAEA,SACE,gBAAAF;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,+BAA+B,OAAO,4BAA4B,EAAE,IAAI,SAAS;AAAA,MAC5F,kBAAe;AAAA,MACf,YAAY,CAAC,MAAM;AACjB,YAAI,CAAC,WAAW,CAAC,EAAE,cAAc,OAAO,SAAS,OAAO,EAAG;AAC3D,UAAE,eAAe;AACjB,gBAAQ,IAAI;AAAA,MACd;AAAA,MACA,aAAa,MAAM,QAAQ,KAAK;AAAA,MAChC,QAAQ,CAAC,MAAM;AACb,gBAAQ,KAAK;AACb,YAAI,CAAC,WAAW,CAAC,EAAE,cAAc,OAAO,OAAQ;AAChD,UAAE,eAAe;AACjB,aAAK,SAAS,EAAE,aAAa,KAAK;AAAA,MACpC;AAAA,MAEA;AAAA,wBAAAD,KAAC,SAAI,WAAU,uBAAsB,mBAAK;AAAA,QACzC,QAAQ,OAAO,SACd,gBAAAA,KAAC,QAAG,WAAU,uBAAsB,cAAW,gBAC5C,kBAAQ,OAAO,IAAI,CAAC,UACnB,gBAAAC,MAAC,QAAkB,WAAU,mCAAkC,cAAY,MAAM,IAC/E;AAAA,0BAAAD,KAAC,UAAK,WAAU,qEAAoE,mBAAgB,IACjG,gBAAM,MACT;AAAA,UACA,gBAAAA,KAAC,UAAK,WAAU,2BAA0B,OAAO,MAAM,KACpD,gBAAM,QAAQ,MAAM,IACvB;AAAA,UACC,MAAM,aAAa,UAAa,gBAAAA,KAAC,UAAK,WAAU,sCAAsC,UAAAI,oBAAmB,MAAM,QAAQ,GAAE;AAAA,UACzH,YACC,gBAAAJ,KAAC,YAAO,MAAK,UAAS,WAAWE,MAAK,UAAU,CAAC,SAAS,SAAS,MAAM,cAAc,KAAK,GAAG,wBAAqB,IAAG,6BAEvH;AAAA,aAXK,MAAM,EAaf,CACD,GACH,IAEA,gBAAAD,MAAC,OAAE,WAAU,iCAAgC;AAAA;AAAA,UAAc,WAAW,yCAAyC;AAAA,WAAG;AAAA,QAEnH,YACC,gBAAAA,MAAAF,WAAA,EACE;AAAA,0BAAAE,MAAC,WAAM,WAAU,oGAAmG;AAAA;AAAA,YAElH,gBAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,UAAQ;AAAA,gBACR,QAAO;AAAA,gBACP,UAAU,CAAC;AAAA,gBACX,cAAW;AAAA,gBACX,qBAAkB;AAAA,gBAClB,UAAU,CAAC,MAAM;AACf,wBAAM,QAAQ,EAAE,cAAc,QAAQ,MAAM,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAC3E,oBAAE,cAAc,QAAQ;AACxB,uBAAK,SAAS,KAAK;AAAA,gBACrB;AAAA;AAAA,YACF;AAAA,aACF;AAAA,UACA,gBAAAC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,UAAU,CAAC,MAAM;AACf,kBAAE,eAAe;AACjB,qBAAK,OAAO;AAAA,cACd;AAAA,cAEA;AAAA,gCAAAD;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAU;AAAA,oBACV,aAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,UAAU,CAAC;AAAA,oBACX,cAAW;AAAA,oBACX,UAAU,CAAC,MAAM,OAAO,EAAE,OAAO,KAAK;AAAA;AAAA,gBACxC;AAAA,gBACA,gBAAAC,MAAC,YAAO,WAAU,mEAAkE,OAAO,SAAS,UAAU,CAAC,SAAS,cAAW,cAAa,UAAU,CAAC,MAAM,WAAW,EAAE,OAAO,KAA4B,GAC/M;AAAA,kCAAAD,KAAC,YAAO,OAAM,IAAG,kBAAI;AAAA,kBACrB,gBAAAA,KAAC,YAAO,OAAM,SAAQ,mBAAK;AAAA,kBAC3B,gBAAAA,KAAC,YAAO,OAAM,SAAQ,mBAAK;AAAA,kBAC3B,gBAAAA,KAAC,YAAO,OAAM,SAAQ,mBAAK;AAAA,mBAC7B;AAAA,gBACA,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAWE,MAAK,UAAU,CAAC,WAAW,CAAC,IAAI,KAAK,GAAG,gBAAa,IAAG,iBAEzF;AAAA;AAAA;AAAA,UACF;AAAA,WACF;AAAA,QAED,OAAO,KAAK,gBAAAF,KAAC,OAAE,WAAU,iCAAgC,iCAAc;AAAA,QACvE,WACC,gBAAAA,KAAC,OAAE,WAAU,wBAAuB,MAAK,SAAQ,oBAAiB,IAC/D,mBACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;;;ACvLA,SAAgB,aAAAK,kBAAiB;AA+CzB,gBAAAC,MAUA,QAAAC,aAVA;AAZD,SAAS,YAAY,EAAE,YAAY,OAAO,SAAS,WAAW,MAAM,aAAa,qBAAqB,YAAY,aAAa,MAAM,YAAY,GAAG,GAAqB;AAC9K,QAAM,EAAE,YAAY,MAAM,IAAI,eAAe,SAAS,WAAW,CAAC,CAAC;AACnE,EAAAC,WAAU,MAAM;AACd,QAAI,SAAU;AACd,UAAM,MAAM,WAAW,SAAS,EAAE;AAClC,eAAW,YAAY,IAAI;AAC3B,WAAO,MAAM,WAAW,YAAY,GAAG;AAAA,EACzC,GAAG,CAAC,YAAY,QAAQ,CAAC;AACzB,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,YAAY,cAAc,OAAO,SAAS,KAC9C,gBAAAF,KAAC,QAAG,WAAU,uBAAsB,cAAW,gBAAe,qBAAkB,IAC7E,iBAAO,IAAI,CAAC,OAAO,MAClB,gBAAAA,KAAC,QAAW,WAAW,MAAM,aAAa,UAAU,iBAAiB,MAAM,aAAa,YAAY,mBAAmB,yBAAyB,cAAY,MAAM,MAC/J,gBAAM,WADA,CAET,CACD,GACH;AAEF,SACE,gBAAAC,MAAC,SAAI,WAAW,uBAAuB,SAAS,IAAI,qBAAkB,IACpE;AAAA,oBAAAA,MAAC,SAAI,WAAW,cAAc,WAAW,6CAA6C,EAAE,IACrF;AAAA,kBAAY,gBAAAD,KAAC,YAAS,YAAwB,WAAU,WAAU;AAAA,MACnE,gBAAAC,MAAC,SAAI,WAAU,+BACb;AAAA,wBAAAD,KAAC,gBAAa,YAAwB,YAAwB,WAAU,mCAAkC;AAAA,QAC1G,gBAAAA,KAAC,kBAAe,YAAwB,UAAoB;AAAA,SAC9D;AAAA,MACC,YAAY,gBAAAA,KAAC,iBAAc,YAAwB,WAAU,WAAU;AAAA,OAC1E;AAAA,IACA,gBAAAA,KAAC,iBAAc,YAAwB,UAAoB,aAA0B,qBAA0C;AAAA,IAC9H;AAAA,KACH;AAEJ;","names":["useEffect","useRef","useRef","useEffect","jsx","useEffect","useRef","Fragment","jsx","jsxs","useRef","useEffect","field","formatVideoSeconds","Fragment","jsx","jsxs","formatVideoSeconds","useState","formatVideoSeconds","Fragment","jsx","jsxs","btn","useState","formatVideoSeconds","useEffect","jsx","jsxs","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-react",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "description": "React bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -131,7 +131,7 @@
131
131
  },
132
132
  "peerDependencies": {
133
133
  "@dagrejs/dagre": "^1.1.8",
134
- "@pixerate/editor": ">=0.76.0 <1.0.0-0",
134
+ "@pixerate/editor": ">=0.80.0 <1.0.0-0",
135
135
  "@tiptap/core": "^2.11.5",
136
136
  "@tiptap/pm": "^2.11.5",
137
137
  "@tiptap/react": "^2.11.5",
@@ -158,7 +158,7 @@
158
158
  "tsup": "^8.4.0",
159
159
  "typescript": "^5.7.3",
160
160
  "vitest": "^4.1.11",
161
- "@pixerate/editor": "0.76.0"
161
+ "@pixerate/editor": "0.80.0"
162
162
  },
163
163
  "scripts": {
164
164
  "build": "tsup",