editor-gui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEditor GUI Components
编辑器GUI组件
Build bespoke video editing interfaces by composing discrete GUI components. Each component is a self-contained building block that connects to your composition via targets.
通过组合独立的GUI组件构建定制化视频编辑界面。每个组件都是一个独立的构建块,可通过target属性与你的合成内容关联。
Core Principle
核心原则
GUI components are lego bricks. They don't contain content—they provide controls and views for content defined by Elements (EFTimegroup, EFVideo, EFText, etc.). Connect them using attributes.
targetGUI组件就像乐高积木。它们不包含内容——而是为Elements(EFTimegroup、EFVideo、EFText等)定义的内容提供控件和视图。使用属性进行关联。
targetQuick Start: Minimal Player
快速开始:极简播放器
html
<ef-timegroup id="my-video" mode="fixed" duration="5s">
<ef-video src="/video.mp4" duration="5s"></ef-video>
</ef-timegroup>
<ef-controls target="my-video">
<ef-toggle-play></ef-toggle-play>
<ef-scrubber></ef-scrubber>
<ef-time-display></ef-time-display>
</ef-controls>html
<ef-timegroup id="my-video" mode="fixed" duration="5s">
<ef-video src="/video.mp4" duration="5s"></ef-video>
</ef-timegroup>
<ef-controls target="my-video">
<ef-toggle-play></ef-toggle-play>
<ef-scrubber></ef-scrubber>
<ef-time-display></ef-time-display>
</ef-controls>Component Reference
组件参考
Preview & Canvas
预览与画布
html
<!-- Basic preview (renders composition at current time) -->
<ef-preview target="my-canvas"></ef-preview>
<!-- Canvas with pan/zoom container -->
<ef-pan-zoom>
<ef-canvas id="my-canvas">
<ef-timegroup>...</ef-timegroup>
</ef-canvas>
</ef-pan-zoom>
<!-- Visual transform handles for selected elements -->
<ef-transform-handles></ef-transform-handles>html
<!-- 基础预览(渲染当前时间点的合成内容) -->
<ef-preview target="my-canvas"></ef-preview>
<!-- 带平移/缩放容器的画布 -->
<ef-pan-zoom>
<ef-canvas id="my-canvas">
<ef-timegroup>...</ef-timegroup>
</ef-canvas>
</ef-pan-zoom>
<!-- 选中元素的可视化变换手柄 -->
<ef-transform-handles></ef-transform-handles>Playback Controls
播放控件
html
<!-- Controls container (provides context to children) -->
<ef-controls target="my-timegroup">
<!-- Individual controls -->
<ef-play></ef-play>
<ef-pause></ef-pause>
<ef-toggle-play></ef-toggle-play>
<ef-toggle-loop></ef-toggle-loop>
<ef-scrubber></ef-scrubber>
<ef-time-display></ef-time-display>
</ef-controls>
<!-- Or use controls standalone with explicit targets -->
<ef-toggle-play target="my-timegroup"></ef-toggle-play>
<ef-scrubber target="my-timegroup"></ef-scrubber>html
<!-- 控件容器(为子元素提供上下文) -->
<ef-controls target="my-timegroup">
<!-- 单个控件 -->
<ef-play></ef-play>
<ef-pause></ef-pause>
<ef-toggle-play></ef-toggle-play>
<ef-toggle-loop></ef-toggle-loop>
<ef-scrubber></ef-scrubber>
<ef-time-display></ef-time-display>
</ef-controls>
<!-- 或单独使用控件并指定目标 -->
<ef-toggle-play target="my-timegroup"></ef-toggle-play>
<ef-scrubber target="my-timegroup"></ef-scrubber>Toggle Play with Custom Slots
自定义插槽的播放/暂停切换按钮
html
<ef-toggle-play>
<button slot="play">▶ Play</button>
<button slot="pause">⏸ Pause</button>
</ef-toggle-play>html
<ef-toggle-play>
<button slot="play">▶ 播放</button>
<button slot="pause">⏸ 暂停</button>
</ef-toggle-play>Timeline
时间轴
html
<!-- Full timeline with tracks, ruler, zoom -->
<ef-timeline
target="my-canvas"
show-ruler
show-playback-controls
pixels-per-ms="0.1"
></ef-timeline>
<!-- Just the ruler -->
<ef-timeline-ruler target="my-canvas"></ef-timeline-ruler>
<!-- Trim handles for timeline editing -->
<ef-trim-handles></ef-trim-handles>html
<!-- 包含轨道、标尺、缩放功能的完整时间轴 -->
<ef-timeline
target="my-canvas"
show-ruler
show-playback-controls
pixels-per-ms="0.1"
></ef-timeline>
<!-- 仅显示标尺 -->
<ef-timeline-ruler target="my-canvas"></ef-timeline-ruler>
<!-- 用于时间轴编辑的修剪手柄 -->
<ef-trim-handles></ef-trim-handles>Filmstrip & Thumbnail Strip
胶片条与缩略图条
html
<!-- Thumbnail strip navigation -->
<ef-filmstrip target="my-timegroup"></ef-filmstrip>
<!-- Thumbnail strip visualization (for tracks) -->
<ef-thumbnail-strip></ef-thumbnail-strip>ThumbnailStrip standalone props (for use outside timeline):
| Prop | Type | Description |
|---|---|---|
| | Direct ref to an EFVideo or EFTimegroup |
| | ID-based target (alternative to targetElement) |
| | Use media's intrinsic duration instead of timeline duration |
| | Thumbnail height in px (default 24) |
html
<!-- 缩略图条导航 -->
<ef-filmstrip target="my-timegroup"></ef-filmstrip>
<!-- (轨道用)缩略图条可视化 -->
<ef-thumbnail-strip></ef-thumbnail-strip>ThumbnailStrip独立使用属性(用于时间轴外场景):
| 属性 | 类型 | 描述 |
|---|---|---|
| | EFVideo或EFTimegroup的直接引用 |
| | 基于ID的目标(targetElement的替代方案) |
| | 使用媒体的固有时长而非时间轴时长 |
| | 缩略图高度(单位:px,默认24) |
Trim
修剪功能
TrimHandles in standalone mode overlays on any container (e.g. a ThumbnailStrip):
html
<ef-trim-handles
value='{"startMs": 0, "endMs": 0}'
intrinsic-duration-ms="10000"
seek-target="my-video"
></ef-trim-handles>| Prop | Type | Description |
|---|---|---|
| | |
| | Total media duration in ms |
| | ID of element to seek during drag |
| | Show dimmed overlay on trimmed regions (default true) |
Slots: , — custom handle content (e.g. SVG icons).
handle-starthandle-endCSS custom properties:
css
--trim-handle-width: 16px;
--trim-handle-color: gold;
--trim-handle-active-color: gold;
--trim-handle-border-radius-start: 4px 0 0 4px;
--trim-handle-border-radius-end: 0 4px 4px 0;
--trim-overlay-color: rgba(0, 0, 0, 0.7);
--trim-selected-border-color: gold;
--trim-selected-border-width: 3px;Events: — fires where .
onTrimChangeCustomEvent<TrimChangeDetail>TrimChangeDetail = { elementId, type, value: TrimValue }独立模式的TrimHandles可覆盖在任意容器上(例如ThumbnailStrip):
html
<ef-trim-handles
value='{"startMs": 0, "endMs": 0}'
intrinsic-duration-ms="10000"
seek-target="my-video"
></ef-trim-handles>| 属性 | 类型 | 描述 |
|---|---|---|
| | |
| | 媒体总时长(单位:ms) |
| | 拖拽期间跳转的元素ID |
| | 显示修剪区域的暗化遮罩(默认true) |
插槽:、 —— 自定义手柄内容(例如SVG图标)。
handle-starthandle-endCSS自定义属性:
css
--trim-handle-width: 16px;
--trim-handle-color: gold;
--trim-handle-active-color: gold;
--trim-handle-border-radius-start: 4px 0 0 4px;
--trim-handle-border-radius-end: 0 4px 4px 0;
--trim-overlay-color: rgba(0, 0, 0, 0.7);
--trim-selected-border-color: gold;
--trim-selected-border-width: 3px;事件: —— 触发,其中。
onTrimChangeCustomEvent<TrimChangeDetail>TrimChangeDetail = { elementId, type, value: TrimValue }Hierarchy Panel
层级面板
html
<!-- Element tree panel -->
<ef-hierarchy
target="my-canvas"
show-header
header="LAYERS"
></ef-hierarchy>html
<!-- 元素树面板 -->
<ef-hierarchy
target="my-canvas"
show-header
header="LAYERS"
></ef-hierarchy>Workbench (Full Editor Shell)
工作台(完整编辑器框架)
html
<!-- Complete editing environment with slots -->
<ef-workbench>
<ef-hierarchy slot="hierarchy" target="my-canvas"></ef-hierarchy>
<ef-pan-zoom slot="canvas">
<ef-canvas id="my-canvas">...</ef-canvas>
</ef-pan-zoom>
<ef-timeline slot="timeline" target="my-canvas"></ef-timeline>
</ef-workbench>
<!-- Or use shorthand on timegroup -->
<ef-timegroup workbench mode="fixed" duration="10s">
...
</ef-timegroup>html
<!-- 带插槽的完整编辑环境 -->
<ef-workbench>
<ef-hierarchy slot="hierarchy" target="my-canvas"></ef-hierarchy>
<ef-pan-zoom slot="canvas">
<ef-canvas id="my-canvas">...</ef-canvas>
</ef-pan-zoom>
<ef-timeline slot="timeline" target="my-canvas"></ef-timeline>
</ef-workbench>
<!-- 或在timegroup上使用简写形式 -->
<ef-timegroup workbench mode="fixed" duration="10s">
...
</ef-timegroup>React Wrappers
React封装
tsx
import {
Preview,
Timeline,
Controls,
Scrubber,
TogglePlay,
TimeDisplay,
Filmstrip,
ThumbnailStrip,
TrimHandles,
Hierarchy,
Workbench,
Video,
useMediaInfo,
type TrimValue,
} from '@editframe/react';
import type { EFVideo } from '@editframe/elements';
function SimplePlayer({ timegroupId }) {
return (
<Controls target={timegroupId}>
<TogglePlay />
<Scrubber />
<TimeDisplay />
</Controls>
);
}tsx
import {
Preview,
Timeline,
Controls,
Scrubber,
TogglePlay,
TimeDisplay,
Filmstrip,
ThumbnailStrip,
TrimHandles,
Hierarchy,
Workbench,
Video,
useMediaInfo,
type TrimValue,
} from '@editframe/react';
import type { EFVideo } from '@editframe/elements';
function SimplePlayer({ timegroupId }) {
return (
<Controls target={timegroupId}>
<TogglePlay />
<Scrubber />
<TimeDisplay />
</Controls>
);
}Hooks
Hooks
useMediaInfo
useMediaInfo
Returns intrinsic duration and loading state for a media element ref.
tsx
const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs, loading } = useMediaInfo(videoRef);返回媒体元素引用的固有时长和加载状态。
tsx
const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs, loading } = useMediaInfo(videoRef);Video Trim Props
视频修剪属性
Video (and all temporal elements) support trim props that clip playback:
tsx
<Video
src="/video.mp4"
trimStartMs={2000} // trim 2s from start
trimEndMs={1500} // trim 1.5s from end
/>trimStartMstrimEndMsVideo(及所有时间相关元素)支持修剪属性以裁剪播放内容:
tsx
<Video
src="/video.mp4"
trimStartMs={2000} // 从开头修剪2秒
trimEndMs={1500} // 从结尾修剪1.5秒
/>trimStartMstrimEndMsComposition Patterns
合成模式
Pattern 1: Minimal Preview + Controls
模式1:极简预览+控件
tsx
import { Timegroup, Video, Controls, TogglePlay, Scrubber, TimeDisplay } from '@editframe/react';
export function VideoPlayer({ src }) {
return (
<div className="flex flex-col gap-2">
<Timegroup id="player" mode="fixed" duration="10s">
<Video src={src} duration="10s" style={{ width: '100%' }} />
</Timegroup>
<Controls target="player" className="flex items-center gap-2">
<TogglePlay />
<Scrubber className="flex-1" />
<TimeDisplay />
</Controls>
</div>
);
}tsx
import { Timegroup, Video, Controls, TogglePlay, Scrubber, TimeDisplay } from '@editframe/react';
export function VideoPlayer({ src }) {
return (
<div className="flex flex-col gap-2">
<Timegroup id="player" mode="fixed" duration="10s">
<Video src={src} duration="10s" style={{ width: '100%' }} />
</Timegroup>
<Controls target="player" className="flex items-center gap-2">
<TogglePlay />
<Scrubber className="flex-1" />
<TimeDisplay />
</Controls>
</div>
);
}Pattern 2: Timeline-Only Editor
模式2:仅时间轴编辑器
tsx
import { Canvas, Timegroup, Video, Timeline } from '@editframe/react';
export function TimelineEditor({ clips }) {
return (
<div className="flex flex-col h-screen">
<Canvas id="editor" className="flex-1 bg-black">
<Timegroup id="root" mode="sequence">
{clips.map(clip => (
<Video key={clip.id} src={clip.src} duration={clip.duration} />
))}
</Timegroup>
</Canvas>
<Timeline
target="editor"
showRuler
showPlaybackControls
className="h-48 border-t"
/>
</div>
);
}tsx
import { Canvas, Timegroup, Video, Timeline } from '@editframe/react';
export function TimelineEditor({ clips }) {
return (
<div className="flex flex-col h-screen">
<Canvas id="editor" className="flex-1 bg-black">
<Timegroup id="root" mode="sequence">
{clips.map(clip => (
<Video key={clip.id} src={clip.src} duration={clip.duration} />
))}
</Timegroup>
</Canvas>
<Timeline
target="editor"
showRuler
showPlaybackControls
className="h-48 border-t"
/>
</div>
);
}Pattern 3: Filmstrip Navigator
模式3:胶片条导航器
tsx
import { Timegroup, Video, Filmstrip, Controls, TogglePlay } from '@editframe/react';
export function FilmstripViewer({ src }) {
return (
<div className="flex flex-col gap-4">
<Timegroup id="viewer" mode="fixed" duration="30s">
<Video src={src} duration="30s" style={{ width: '100%' }} />
</Timegroup>
<Filmstrip target="viewer" className="h-16" />
<Controls target="viewer" className="flex justify-center">
<TogglePlay />
</Controls>
</div>
);
}tsx
import { Timegroup, Video, Filmstrip, Controls, TogglePlay } from '@editframe/react';
export function FilmstripViewer({ src }) {
return (
<div className="flex flex-col gap-4">
<Timegroup id="viewer" mode="fixed" duration="30s">
<Video src={src} duration="30s" style={{ width: '100%' }} />
</Timegroup>
<Filmstrip target="viewer" className="h-16" />
<Controls target="viewer" className="flex justify-center">
<TogglePlay />
</Controls>
</div>
);
}Pattern 4: Trim Tool
模式4:修剪工具
tsx
import { useId, useState, useRef } from "react";
import {
Video,
ThumbnailStrip,
TrimHandles,
TogglePlay,
useMediaInfo,
type TrimValue,
} from "@editframe/react";
import type { EFVideo } from "@editframe/elements";
export function TrimTool({ src }: { src: string }) {
const videoId = useId();
const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs } = useMediaInfo(videoRef);
const [trim, setTrim] = useState<TrimValue>({ startMs: 0, endMs: 0 });
const totalDuration = intrinsicDurationMs ?? 0;
return (
<div>
<Video
id={videoId}
ref={videoRef}
src={src}
loop
trimStartMs={trim.startMs}
trimEndMs={trim.endMs}
className="aspect-video w-full object-contain"
/>
<div className="relative" style={{ height: 64 }}>
<ThumbnailStrip
targetElement={videoRef.current}
useIntrinsicDuration
thumbnailHeight={64}
className="pointer-events-none absolute inset-0"
/>
<TrimHandles
value={trim}
intrinsicDurationMs={totalDuration}
seekTarget={videoId}
onTrimChange={(e) => setTrim(e.detail.value)}
className="absolute inset-0"
>
<span slot="handle-start">⟨</span>
<span slot="handle-end">⟩</span>
</TrimHandles>
</div>
<TogglePlay target={videoId}>
<button slot="play">▶</button>
<button slot="pause">⏸</button>
</TogglePlay>
</div>
);
}tsx
import { useId, useState, useRef } from "react";
import {
Video,
ThumbnailStrip,
TrimHandles,
TogglePlay,
useMediaInfo,
type TrimValue,
} from "@editframe/react";
import type { EFVideo } from "@editframe/elements";
export function TrimTool({ src }: { src: string }) {
const videoId = useId();
const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs } = useMediaInfo(videoRef);
const [trim, setTrim] = useState<TrimValue>({ startMs: 0, endMs: 0 });
const totalDuration = intrinsicDurationMs ?? 0;
return (
<div>
<Video
id={videoId}
ref={videoRef}
src={src}
loop
trimStartMs={trim.startMs}
trimEndMs={trim.endMs}
className="aspect-video w-full object-contain"
/>
<div className="relative" style={{ height: 64 }}>
<ThumbnailStrip
targetElement={videoRef.current}
useIntrinsicDuration
thumbnailHeight={64}
className="pointer-events-none absolute inset-0"
/>
<TrimHandles
value={trim}
intrinsicDurationMs={totalDuration}
seekTarget={videoId}
onTrimChange={(e) => setTrim(e.detail.value)}
className="absolute inset-0"
>
<span slot="handle-start">⟨</span>
<span slot="handle-end">⟩</span>
</TrimHandles>
</div>
<TogglePlay target={videoId}>
<button slot="play">▶</button>
<button slot="pause">⏸</button>
</TogglePlay>
</div>
);
}Pattern 5: Multi-Layer Editor with Hierarchy
模式5:带层级面板的多图层编辑器
tsx
import { Workbench, Hierarchy, PanZoom, Canvas, Timegroup, Timeline } from '@editframe/react';
export function LayeredEditor({ children }) {
return (
<Workbench className="h-screen">
<Hierarchy slot="hierarchy" target="main-canvas" showHeader header="LAYERS" />
<PanZoom slot="canvas">
<Canvas id="main-canvas">
<Timegroup id="root" mode="contain" duration="10s">
{children}
</Timegroup>
</Canvas>
</PanZoom>
<Timeline slot="timeline" target="main-canvas" showRuler showPlaybackControls />
</Workbench>
);
}tsx
import { Workbench, Hierarchy, PanZoom, Canvas, Timegroup, Timeline } from '@editframe/react';
export function LayeredEditor({ children }) {
return (
<Workbench className="h-screen">
<Hierarchy slot="hierarchy" target="main-canvas" showHeader header="LAYERS" />
<PanZoom slot="canvas">
<Canvas id="main-canvas">
<Timegroup id="root" mode="contain" duration="10s">
{children}
</Timegroup>
</Canvas>
</PanZoom>
<Timeline slot="timeline" target="main-canvas" showRuler showPlaybackControls />
</Workbench>
);
}Target Linking
目标关联
GUI components connect to content using IDs:
html
<!-- Content with ID -->
<ef-canvas id="my-canvas">
<ef-timegroup id="my-root">...</ef-timegroup>
</ef-canvas>
<!-- GUI components target by ID -->
<ef-timeline target="my-canvas"></ef-timeline>
<ef-hierarchy target="my-canvas"></ef-hierarchy>
<ef-controls target="my-root"></ef-controls>
<ef-filmstrip target="my-root"></ef-filmstrip>| Component | Target Type | Purpose |
|---|---|---|
| Timeline | Canvas ID | Shows all tracks from canvas content |
| Hierarchy | Canvas ID | Shows element tree from canvas |
| Controls | Timegroup ID | Play/pause/seek the timegroup |
| Filmstrip | Timegroup ID | Thumbnail navigation for timegroup |
| Preview | Canvas ID | Renders canvas at current time |
GUI组件通过ID与内容关联:
html
<!-- 带ID的内容 -->
<ef-canvas id="my-canvas">
<ef-timegroup id="my-root">...</ef-timegroup>
</ef-canvas>
<!-- 按ID指定目标的GUI组件 -->
<ef-timeline target="my-canvas"></ef-timeline>
<ef-hierarchy target="my-canvas"></ef-hierarchy>
<ef-controls target="my-root"></ef-controls>
<ef-filmstrip target="my-root"></ef-filmstrip>| 组件 | 目标类型 | 用途 |
|---|---|---|
| Timeline | Canvas ID | 显示画布内容中的所有轨道 |
| Hierarchy | Canvas ID | 显示画布的元素树 |
| Controls | Timegroup ID | 播放/暂停/跳转时间轴 |
| Filmstrip | Timegroup ID | 时间轴的缩略图导航 |
| Preview | Canvas ID | 渲染当前时间点的画布内容 |
Styling
样式定制
All components accept for styling:
classNametsx
<Scrubber className="h-2 rounded-full" />
<TogglePlay className="w-10 h-10 bg-white rounded-full" />
<Timeline className="h-48 bg-gray-900" />Components use CSS custom properties for theming:
css
ef-timeline {
--ef-timeline-bg: #1a1a1a;
--ef-timeline-track-bg: #2a2a2a;
--ef-timeline-playhead: #ff0000;
}
ef-scrubber {
--ef-scrubber-track: #333;
--ef-scrubber-fill: #fff;
--ef-scrubber-thumb: #fff;
}所有组件都支持用于样式定制:
classNametsx
<Scrubber className="h-2 rounded-full" />
<TogglePlay className="w-10 h-10 bg-white rounded-full" />
<Timeline className="h-48 bg-gray-900" />组件支持通过CSS自定义属性进行主题定制:
css
ef-timeline {
--ef-timeline-bg: #1a1a1a;
--ef-timeline-track-bg: #2a2a2a;
--ef-timeline-playhead: #ff0000;
}
ef-scrubber {
--ef-scrubber-track: #333;
--ef-scrubber-fill: #fff;
--ef-scrubber-thumb: #fff;
}File Locations
文件位置
elements/packages/elements/src/gui/
├── EFConfiguration.ts # Root config wrapper
├── EFWorkbench.ts # Full editor shell
├── EFControls.ts # Control container
├── EFPlay.ts # Play button
├── EFPause.ts # Pause button
├── EFTogglePlay.ts # Play/pause toggle
├── EFToggleLoop.ts # Loop toggle
├── EFScrubber.ts # Time scrubber
├── EFTimeDisplay.ts # Time display
├── EFFilmstrip.ts # Thumbnail strip
├── EFPreview.ts # Preview renderer
├── hierarchy/
│ └── EFHierarchy.ts # Layer panel
└── timeline/
├── EFTimeline.ts # Main timeline
├── EFTimelineRuler.ts # Time ruler
└── EFTrimHandles.ts # Trim handles
elements/packages/react/src/gui/
├── Workbench.ts # React wrapper
├── Timeline.ts # React wrapper
├── Controls.ts # React wrapper
├── Scrubber.ts # React wrapper
└── ... # All components have React wrapperselements/packages/elements/src/gui/
├── EFConfiguration.ts # 根配置包装器
├── EFWorkbench.ts # 完整编辑器框架
├── EFControls.ts # 控件容器
├── EFPlay.ts # 播放按钮
├── EFPause.ts # 暂停按钮
├── EFTogglePlay.ts # 播放/暂停切换按钮
├── EFToggleLoop.ts # 循环切换按钮
├── EFScrubber.ts # 时间进度条
├── EFTimeDisplay.ts # 时间显示组件
├── EFFilmstrip.ts # 胶片条组件
├── EFPreview.ts # 预览渲染组件
├── hierarchy/
│ └── EFHierarchy.ts # 图层面板
└── timeline/
├── EFTimeline.ts # 主时间轴
├── EFTimelineRuler.ts # 时间标尺
└── EFTrimHandles.ts # 修剪手柄
elements/packages/react/src/gui/
├── Workbench.ts # React封装
├── Timeline.ts # React封装
├── Controls.ts # React封装
├── Scrubber.ts # React封装
└── ... # 所有组件均有React封装Agent Panel & ef-edit Event System
Agent面板与ef-edit事件系统
EFWorkbench includes a built-in right-column Agent Sync panel () that accumulates GUI edits and generates a copyable prompt for coding agents.
ef-agent-panelEFWorkbench包含内置的右侧栏Agent Sync面板(),可累积GUI编辑操作并生成可供复制的提示语,用于AI编码助手。
ef-agent-panelHow it works
工作原理
- When a user drags, resizes, or rotates an element in EFCanvas, the canvas dispatches an CustomEvent with
ef-edit.bubbles: true, composed: true - EFWorkbench listens for on itself (event bubbles up), then calls
ef-edit.agentPanel.addEdit(event.detail) - The panel accumulates edits, renders a list, and exposes a "Copy Prompt" button.
- 当用户在EFCanvas中拖拽、调整大小或旋转元素时,画布会触发自定义事件,设置
ef-edit。bubbles: true, composed: true - EFWorkbench监听自身上的事件(事件会冒泡),然后调用
ef-edit。agentPanel.addEdit(event.detail) - 面板会累积编辑操作,渲染列表,并提供“复制提示语”按钮。
ef-edit event payload (EditEvent)
ef-edit事件负载(EditEvent)
ts
interface EditEvent {
operation: ElementMovedOperation | ElementResizedOperation | ElementRotatedOperation;
description: string; // human-readable sentence
selector: string; // CSS path stopping at infrastructure tags
elementHtml: string; // cleaned outerHTML (runtime attrs stripped)
timestamp: number;
}ts
interface EditEvent {
operation: ElementMovedOperation | ElementResizedOperation | ElementRotatedOperation;
description: string; # 人类可读的描述语句
selector: string; # 到基础设施标签为止的CSS路径
elementHtml: string; # 清理后的outerHTML(移除运行时属性)
timestamp: number;
}Dispatching ef-edit
触发ef-edit事件
ts
import { createEditCustomEvent } from './editEvents.js';
element.dispatchEvent(createEditCustomEvent(editEvent));ts
import { createEditCustomEvent } from './editEvents.js';
element.dispatchEvent(createEditCustomEvent(editEvent));Listening from outside
外部监听
ts
// Bubbles + composed — listen on workbench or any ancestor
workbench.addEventListener('ef-edit', (e: CustomEvent<EditEvent>) => {
console.log(e.detail.description, e.detail.selector);
});ts
// 事件会冒泡且可跨DOM树——在工作台或任何祖先元素上监听
workbench.addEventListener('ef-edit', (e: CustomEvent<EditEvent>) => {
console.log(e.detail.description, e.detail.selector);
});Selector path rules
选择器路径规则
- walks up the DOM stopping at INFRASTRUCTURE_TAGS (
buildSelectorPath(),ef-canvas,ef-workbench, etc.)ef-pan-zoom - Auto-generated IDs (containing 8+ digits) are omitted; nth-of-type is used instead
- strips
getElementHtml(),style,data-element-id, and auto-generated ids from outerHTMLdata-selected
- 遍历DOM树,遇到INFRASTRUCTURE_TAGS(
buildSelectorPath()、ef-canvas、ef-workbench等)时停止ef-pan-zoom - 自动生成的ID(包含8位以上数字)会被省略;改用nth-of-type
- 会从outerHTML中移除
getElementHtml()、style、data-element-id和自动生成的IDdata-selected
Agent panel API
Agent面板API
ts
agentPanel.addEdit(editEvent: EditEvent): void // called by EFWorkbench
agentPanel.clearEdits(): voidts
agentPanel.addEdit(editEvent: EditEvent): void // 由EFWorkbench调用
agentPanel.clearEdits(): voidFile locations
文件位置
elements/packages/elements/src/gui/
├── EFAgentPanel.ts # Lit component for the panel
├── editEvents.ts # EditEvent types, buildSelectorPath, buildAgentPromptelements/packages/elements/src/gui/
├── EFAgentPanel.ts # 面板的Lit组件
├── editEvents.ts # EditEvent类型、buildSelectorPath、buildAgentPromptSandboxes
沙箱环境
Test individual components:
bash
elements/scripts/npm run sandboxBrowse to component sandboxes:
src/gui/EFScrubber.sandbox.tssrc/gui/EFTimeline.sandbox.tssrc/gui/EFFilmstrip.sandbox.tssrc/gui/EFControls.sandbox.ts
测试单个组件:
bash
elements/scripts/npm run sandbox浏览组件沙箱:
src/gui/EFScrubber.sandbox.tssrc/gui/EFTimeline.sandbox.tssrc/gui/EFFilmstrip.sandbox.tssrc/gui/EFControls.sandbox.ts