@editframe/create 0.44.0 → 0.45.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.
- package/dist/index.js +16 -28
- package/dist/index.js.map +1 -1
- package/dist/skills/editframe-brand-video-generator/README.md +155 -0
- package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
- package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
- package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
- package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
- package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
- package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
- package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
- package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
- package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
- package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
- package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
- package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
- package/dist/skills/editframe-composition/SKILL.md +169 -0
- package/dist/skills/editframe-composition/references/audio.md +483 -0
- package/dist/skills/editframe-composition/references/captions.md +844 -0
- package/dist/skills/editframe-composition/references/composition-model.md +73 -0
- package/dist/skills/editframe-composition/references/configuration.md +403 -0
- package/dist/skills/editframe-composition/references/css-parts.md +105 -0
- package/dist/skills/editframe-composition/references/css-variables.md +640 -0
- package/dist/skills/editframe-composition/references/entry-points.md +810 -0
- package/dist/skills/editframe-composition/references/events.md +499 -0
- package/dist/skills/editframe-composition/references/getting-started.md +259 -0
- package/dist/skills/editframe-composition/references/hooks.md +234 -0
- package/dist/skills/editframe-composition/references/image.md +241 -0
- package/dist/skills/editframe-composition/references/r3f.md +580 -0
- package/dist/skills/editframe-composition/references/render-api.md +484 -0
- package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
- package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
- package/dist/skills/editframe-composition/references/scripting.md +606 -0
- package/dist/skills/editframe-composition/references/sequencing.md +116 -0
- package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
- package/dist/skills/editframe-composition/references/surface.md +329 -0
- package/dist/skills/editframe-composition/references/text.md +627 -0
- package/dist/skills/editframe-composition/references/time-model.md +99 -0
- package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
- package/dist/skills/editframe-composition/references/timegroup.md +457 -0
- package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
- package/dist/skills/editframe-composition/references/transcription.md +47 -0
- package/dist/skills/editframe-composition/references/transitions.md +608 -0
- package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
- package/dist/skills/editframe-composition/references/video.md +506 -0
- package/dist/skills/editframe-composition/references/waveform.md +327 -0
- package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
- package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
- package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
- package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
- package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
- package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
- package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
- package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
- package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
- package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
- package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
- package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
- package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
- package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
- package/dist/skills/editframe-editor-gui/references/play.md +370 -0
- package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
- package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
- package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
- package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
- package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
- package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
- package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
- package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
- package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
- package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
- package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
- package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
- package/dist/skills/editframe-motion-design/SKILL.md +101 -0
- package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
- package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
- package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
- package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
- package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
- package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
- package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
- package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
- package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
- package/dist/skills/editframe-webhooks/SKILL.md +126 -0
- package/dist/skills/editframe-webhooks/references/events.md +382 -0
- package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
- package/dist/skills/editframe-webhooks/references/security.md +418 -0
- package/dist/skills/editframe-webhooks/references/testing.md +409 -0
- package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
- package/dist/templates/html/AGENTS.md +13 -0
- package/dist/templates/react/AGENTS.md +13 -0
- package/dist/utils.js +15 -16
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/tsdown.config.ts +4 -0
- package/dist/detectAgent.js +0 -89
- package/dist/detectAgent.js.map +0 -1
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Trim Handles Element
|
|
3
|
+
description: Draggable in-point and out-point handles for trimming media element duration directly on the composition timeline.
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Timeline"
|
|
7
|
+
priority: 20
|
|
8
|
+
api:
|
|
9
|
+
attributes:
|
|
10
|
+
- name: mode
|
|
11
|
+
type: string
|
|
12
|
+
default: standalone
|
|
13
|
+
description: Layout mode ("standalone" or "track")
|
|
14
|
+
- name: element-id
|
|
15
|
+
type: string
|
|
16
|
+
description: ID of element being trimmed (included in events)
|
|
17
|
+
- name: pixels-per-ms
|
|
18
|
+
type: number
|
|
19
|
+
description: Zoom level for pixel-to-time conversion
|
|
20
|
+
- name: value
|
|
21
|
+
type: TrimValue
|
|
22
|
+
description: Current trim value ({startMs, endMs})
|
|
23
|
+
- name: intrinsic-duration-ms
|
|
24
|
+
type: number
|
|
25
|
+
description: Total duration of source media
|
|
26
|
+
- name: show-overlays
|
|
27
|
+
type: boolean
|
|
28
|
+
default: true
|
|
29
|
+
description: Show darkened overlay on trimmed regions
|
|
30
|
+
- name: seek-target
|
|
31
|
+
type: string
|
|
32
|
+
description: ID of element to seek when trimming
|
|
33
|
+
types:
|
|
34
|
+
- name: TrimValue
|
|
35
|
+
definition: "{ startMs: number; endMs: number }"
|
|
36
|
+
description: Trim start and end times in milliseconds
|
|
37
|
+
- name: TrimChangeDetail
|
|
38
|
+
definition: "{ elementId: string; type: 'start' | 'end' | 'region'; value: TrimValue }"
|
|
39
|
+
description: Event detail for trim-change events
|
|
40
|
+
events:
|
|
41
|
+
- name: trim-change
|
|
42
|
+
detail: TrimChangeDetail
|
|
43
|
+
description: Emitted during trim handle drag (continuous)
|
|
44
|
+
- name: trim-change-end
|
|
45
|
+
detail: "{ elementId: string; type: 'start' | 'end' | 'region' }"
|
|
46
|
+
description: Emitted when trim drag completes
|
|
47
|
+
react:
|
|
48
|
+
generate: true
|
|
49
|
+
componentName: TrimHandles
|
|
50
|
+
importPath: "@editframe/react"
|
|
51
|
+
propMapping:
|
|
52
|
+
element-id: elementId
|
|
53
|
+
pixels-per-ms: pixelsPerMs
|
|
54
|
+
intrinsic-duration-ms: intrinsicDurationMs
|
|
55
|
+
show-overlays: showOverlays
|
|
56
|
+
seek-target: seekTarget
|
|
57
|
+
additionalProps:
|
|
58
|
+
- name: onTrimChange
|
|
59
|
+
type: "(event: CustomEvent<TrimChangeDetail>) => void"
|
|
60
|
+
description: Callback fired when trim values change
|
|
61
|
+
- name: onTrimChangeEnd
|
|
62
|
+
type: "(event: CustomEvent<TrimChangeDetail>) => void"
|
|
63
|
+
description: Callback fired when trim drag ends
|
|
64
|
+
- name: className
|
|
65
|
+
type: string
|
|
66
|
+
description: CSS classes for styling
|
|
67
|
+
nav:
|
|
68
|
+
parent: "Tools"
|
|
69
|
+
priority: 47
|
|
70
|
+
related: ["timeline", "video", "audio"]
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
<!-- html-only -->
|
|
74
|
+
# ef-trim-handles
|
|
75
|
+
<!-- /html-only -->
|
|
76
|
+
<!-- react-only -->
|
|
77
|
+
# TrimHandles
|
|
78
|
+
<!-- /react-only -->
|
|
79
|
+
|
|
80
|
+
Interactive trim handles for adjusting the start and end points of media elements. Provides visual feedback and emits events for timeline integration.
|
|
81
|
+
|
|
82
|
+
<!-- react-only -->
|
|
83
|
+
## Import
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { TrimHandles } from "@editframe/react";
|
|
87
|
+
```
|
|
88
|
+
<!-- /react-only -->
|
|
89
|
+
|
|
90
|
+
## Basic Usage
|
|
91
|
+
|
|
92
|
+
<!-- html-only -->
|
|
93
|
+
Trim handles on a video element.
|
|
94
|
+
|
|
95
|
+
```html live
|
|
96
|
+
<div class="space-y-4">
|
|
97
|
+
<ef-timegroup mode="contain" class="w-[720px] h-[480px] bg-black" id="trim-demo">
|
|
98
|
+
<ef-video
|
|
99
|
+
src="https://assets.editframe.com/bars-n-tone.mp4"
|
|
100
|
+
class="size-full object-contain"
|
|
101
|
+
id="trim-video"
|
|
102
|
+
></ef-video>
|
|
103
|
+
</ef-timegroup>
|
|
104
|
+
|
|
105
|
+
<div class="p-4 bg-gray-900 rounded-lg">
|
|
106
|
+
<div class="text-white text-sm mb-2">Drag handles to trim video:</div>
|
|
107
|
+
<div class="h-[60px] relative bg-gray-800 rounded-lg">
|
|
108
|
+
<ef-trim-handles
|
|
109
|
+
element-id="trim-video"
|
|
110
|
+
intrinsic-duration-ms="10000"
|
|
111
|
+
seek-target="trim-demo"
|
|
112
|
+
></ef-trim-handles>
|
|
113
|
+
</div>
|
|
114
|
+
<ef-controls target="trim-demo" class="flex gap-2 mt-3">
|
|
115
|
+
<ef-toggle-play class="px-3 py-1 bg-blue-600 text-white rounded"></ef-toggle-play>
|
|
116
|
+
<ef-time-display class="text-white font-mono"></ef-time-display>
|
|
117
|
+
</ef-controls>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<script>
|
|
122
|
+
const handles = document.querySelector('ef-trim-handles');
|
|
123
|
+
const video = document.getElementById('trim-video');
|
|
124
|
+
|
|
125
|
+
handles.addEventListener('trim-change', (e) => {
|
|
126
|
+
const { value } = e.detail;
|
|
127
|
+
video.setAttribute('trimstart', `${value.startMs}ms`);
|
|
128
|
+
video.setAttribute('trimend', `${value.endMs}ms`);
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
```
|
|
132
|
+
<!-- /html-only -->
|
|
133
|
+
<!-- react-only -->
|
|
134
|
+
```tsx
|
|
135
|
+
import { useState } from "react";
|
|
136
|
+
import { TrimHandles, Video } from "@editframe/react";
|
|
137
|
+
|
|
138
|
+
export const TrimExample = () => {
|
|
139
|
+
const [trim, setTrim] = useState({ startMs: 0, endMs: 0 });
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className="relative h-24 bg-gray-800">
|
|
143
|
+
<TrimHandles
|
|
144
|
+
elementId="video-1"
|
|
145
|
+
value={trim}
|
|
146
|
+
intrinsicDurationMs={10000}
|
|
147
|
+
pixelsPerMs={0.04}
|
|
148
|
+
onTrimChange={(e) => setTrim(e.detail.value)}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<Video
|
|
152
|
+
id="video-1"
|
|
153
|
+
src="/video.mp4"
|
|
154
|
+
trimStart={`${trim.startMs}ms`}
|
|
155
|
+
trimEnd={`${trim.endMs}ms`}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
<!-- /react-only -->
|
|
162
|
+
|
|
163
|
+
## With Timeline
|
|
164
|
+
|
|
165
|
+
<!-- html-only -->
|
|
166
|
+
Trim handles are automatically enabled in `ef-timeline` when `enable-trim` is set.
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<ef-timeline target="composition" enable-trim></ef-timeline>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The timeline handles trim events internally and updates element attributes.
|
|
173
|
+
<!-- /html-only -->
|
|
174
|
+
<!-- react-only -->
|
|
175
|
+
TrimHandles are automatically included when trim mode is enabled:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { Timeline, Timegroup, Video } from "@editframe/react";
|
|
179
|
+
|
|
180
|
+
<Timeline
|
|
181
|
+
enableTrim
|
|
182
|
+
className="w-full h-96"
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
<Timegroup mode="sequence" className="w-[1920px] h-[1080px]">
|
|
186
|
+
<Video src="/video.mp4" />
|
|
187
|
+
</Timegroup>
|
|
188
|
+
```
|
|
189
|
+
<!-- /react-only -->
|
|
190
|
+
|
|
191
|
+
## Track Mode
|
|
192
|
+
|
|
193
|
+
<!-- html-only -->
|
|
194
|
+
In track mode, handles are pinned to container edges for timeline integration.
|
|
195
|
+
|
|
196
|
+
```html live
|
|
197
|
+
<div class="space-y-4">
|
|
198
|
+
<ef-timegroup mode="contain" class="w-[720px] h-[480px] bg-black" id="track-demo">
|
|
199
|
+
<ef-video
|
|
200
|
+
src="https://assets.editframe.com/bars-n-tone.mp4"
|
|
201
|
+
class="size-full object-contain"
|
|
202
|
+
id="track-video"
|
|
203
|
+
trimstart="2s"
|
|
204
|
+
trimend="3s"
|
|
205
|
+
></ef-video>
|
|
206
|
+
</ef-timegroup>
|
|
207
|
+
|
|
208
|
+
<div class="p-4 bg-gray-900 rounded-lg">
|
|
209
|
+
<div class="text-white text-sm mb-2">Track mode (handles at edges):</div>
|
|
210
|
+
<div class="h-[48px] relative bg-gray-800 rounded-lg overflow-hidden">
|
|
211
|
+
<div style="position: absolute; left: 80px; right: 120px; top: 0; bottom: 0; background: rgba(59,130,246,0.2);">
|
|
212
|
+
<ef-trim-handles
|
|
213
|
+
mode="track"
|
|
214
|
+
element-id="track-video"
|
|
215
|
+
intrinsic-duration-ms="10000"
|
|
216
|
+
pixels-per-ms="0.04"
|
|
217
|
+
></ef-trim-handles>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<ef-controls target="track-demo" class="flex gap-2 mt-3">
|
|
221
|
+
<ef-toggle-play class="px-3 py-1 bg-blue-600 text-white rounded"></ef-toggle-play>
|
|
222
|
+
<ef-time-display class="text-white font-mono"></ef-time-display>
|
|
223
|
+
</ef-controls>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<script>
|
|
228
|
+
const trackHandles = document.querySelectorAll('ef-trim-handles[mode="track"]')[0];
|
|
229
|
+
const trackVideo = document.getElementById('track-video');
|
|
230
|
+
|
|
231
|
+
if (trackHandles && trackVideo) {
|
|
232
|
+
trackHandles.value = { startMs: 2000, endMs: 3000 };
|
|
233
|
+
|
|
234
|
+
trackHandles.addEventListener('trim-change', (e) => {
|
|
235
|
+
const { value } = e.detail;
|
|
236
|
+
trackVideo.setAttribute('trimstart', `${value.startMs}ms`);
|
|
237
|
+
trackVideo.setAttribute('trimend', `${value.endMs}ms`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
</script>
|
|
241
|
+
```
|
|
242
|
+
<!-- /html-only -->
|
|
243
|
+
<!-- react-only -->
|
|
244
|
+
Handles pinned to track edges:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import { TrimHandles } from "@editframe/react";
|
|
248
|
+
|
|
249
|
+
<div className="relative h-16 bg-gray-800">
|
|
250
|
+
<TrimHandles
|
|
251
|
+
mode="track"
|
|
252
|
+
elementId="video-1"
|
|
253
|
+
value={{ startMs: 1000, endMs: 2000 }}
|
|
254
|
+
intrinsicDurationMs={10000}
|
|
255
|
+
pixelsPerMs={0.04}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
```
|
|
259
|
+
<!-- /react-only -->
|
|
260
|
+
|
|
261
|
+
<!-- react-only -->
|
|
262
|
+
## Standalone Mode
|
|
263
|
+
|
|
264
|
+
Handles positioned inline with trim values:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<TrimHandles
|
|
268
|
+
mode="standalone"
|
|
269
|
+
elementId="video-1"
|
|
270
|
+
value={{ startMs: 2000, endMs: 3000 }}
|
|
271
|
+
intrinsicDurationMs={10000}
|
|
272
|
+
pixelsPerMs={0.08}
|
|
273
|
+
/>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Hide Overlays
|
|
277
|
+
|
|
278
|
+
Show handles without dimmed regions:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
<TrimHandles
|
|
282
|
+
elementId="video-1"
|
|
283
|
+
value={{ startMs: 1000, endMs: 1000 }}
|
|
284
|
+
intrinsicDurationMs={10000}
|
|
285
|
+
showOverlays={false}
|
|
286
|
+
/>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## With Seek Target
|
|
290
|
+
|
|
291
|
+
Automatically seek video when dragging:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { useState } from "react";
|
|
295
|
+
import { TrimHandles, Video } from "@editframe/react";
|
|
296
|
+
|
|
297
|
+
export const SeekingTrimExample = () => {
|
|
298
|
+
const [trim, setTrim] = useState({ startMs: 0, endMs: 0 });
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<div>
|
|
302
|
+
<Video
|
|
303
|
+
id="preview-video"
|
|
304
|
+
src="/video.mp4"
|
|
305
|
+
className="w-full aspect-video mb-4"
|
|
306
|
+
/>
|
|
307
|
+
|
|
308
|
+
<div className="relative h-16 bg-gray-800">
|
|
309
|
+
<TrimHandles
|
|
310
|
+
elementId="video-1"
|
|
311
|
+
seekTarget="preview-video"
|
|
312
|
+
value={trim}
|
|
313
|
+
intrinsicDurationMs={10000}
|
|
314
|
+
onTrimChange={(e) => setTrim(e.detail.value)}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Trim Both Ends
|
|
323
|
+
|
|
324
|
+
Adjust start and end simultaneously:
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { useState } from "react";
|
|
328
|
+
import { TrimHandles } from "@editframe/react";
|
|
329
|
+
|
|
330
|
+
export const DualTrimExample = () => {
|
|
331
|
+
const [trim, setTrim] = useState({ startMs: 2000, endMs: 3000 });
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<div>
|
|
335
|
+
<div className="mb-2 text-sm font-mono">
|
|
336
|
+
Trimmed: {trim.startMs}ms to {10000 - trim.endMs}ms
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div className="relative h-16 bg-gray-800 rounded">
|
|
340
|
+
<TrimHandles
|
|
341
|
+
elementId="video-1"
|
|
342
|
+
value={trim}
|
|
343
|
+
intrinsicDurationMs={10000}
|
|
344
|
+
pixelsPerMs={0.08}
|
|
345
|
+
onTrimChange={(e) => setTrim(e.detail.value)}
|
|
346
|
+
/>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
```
|
|
352
|
+
<!-- /react-only -->
|
|
353
|
+
|
|
354
|
+
## Region Dragging
|
|
355
|
+
|
|
356
|
+
<!-- html-only -->
|
|
357
|
+
In standalone mode, drag the region between handles to slide the trim window.
|
|
358
|
+
|
|
359
|
+
```html live
|
|
360
|
+
<div class="space-y-4">
|
|
361
|
+
<ef-timegroup mode="contain" class="w-[720px] h-[480px] bg-black" id="region-demo">
|
|
362
|
+
<ef-video
|
|
363
|
+
src="https://assets.editframe.com/bars-n-tone.mp4"
|
|
364
|
+
sourcein="2s"
|
|
365
|
+
sourceout="6s"
|
|
366
|
+
class="size-full object-contain"
|
|
367
|
+
id="region-video"
|
|
368
|
+
></ef-video>
|
|
369
|
+
</ef-timegroup>
|
|
370
|
+
|
|
371
|
+
<div class="p-4 bg-gray-900 rounded-lg">
|
|
372
|
+
<div class="text-white text-sm mb-2">Drag region to slide trim window:</div>
|
|
373
|
+
<div class="h-[60px] relative bg-gray-800 rounded-lg">
|
|
374
|
+
<ef-trim-handles
|
|
375
|
+
element-id="region-video"
|
|
376
|
+
intrinsic-duration-ms="10000"
|
|
377
|
+
seek-target="region-demo"
|
|
378
|
+
></ef-trim-handles>
|
|
379
|
+
</div>
|
|
380
|
+
<ef-controls target="region-demo" class="flex gap-2 mt-3">
|
|
381
|
+
<ef-toggle-play class="px-3 py-1 bg-blue-600 text-white rounded"></ef-toggle-play>
|
|
382
|
+
<ef-time-display class="text-white font-mono"></ef-time-display>
|
|
383
|
+
</ef-controls>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<script>
|
|
388
|
+
const regionHandles = document.querySelectorAll('ef-trim-handles')[2];
|
|
389
|
+
const regionVideo = document.getElementById('region-video');
|
|
390
|
+
|
|
391
|
+
if (regionHandles && regionVideo) {
|
|
392
|
+
regionHandles.value = { startMs: 2000, endMs: 4000 };
|
|
393
|
+
|
|
394
|
+
regionHandles.addEventListener('trim-change', (e) => {
|
|
395
|
+
const { value, type } = e.detail;
|
|
396
|
+
|
|
397
|
+
if (type === 'region') {
|
|
398
|
+
// Region drag - update sourcein/sourceout instead of trim
|
|
399
|
+
const duration = 10000 - value.startMs - value.endMs;
|
|
400
|
+
regionVideo.setAttribute('sourcein', `${value.startMs}ms`);
|
|
401
|
+
regionVideo.setAttribute('sourceout', `${value.startMs + duration}ms`);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
</script>
|
|
406
|
+
```
|
|
407
|
+
<!-- /html-only -->
|
|
408
|
+
<!-- react-only -->
|
|
409
|
+
Drag the region between handles to move both:
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
import { useState } from "react";
|
|
413
|
+
import { TrimHandles } from "@editframe/react";
|
|
414
|
+
|
|
415
|
+
export const RegionDragExample = () => {
|
|
416
|
+
const [trim, setTrim] = useState({ startMs: 2000, endMs: 2000 });
|
|
417
|
+
|
|
418
|
+
const handleTrimChange = (e: CustomEvent<TrimChangeDetail>) => {
|
|
419
|
+
console.log("Trim type:", e.detail.type); // "start", "end", or "region"
|
|
420
|
+
setTrim(e.detail.value);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<div className="relative h-16 bg-gray-800">
|
|
425
|
+
<TrimHandles
|
|
426
|
+
mode="standalone"
|
|
427
|
+
elementId="video-1"
|
|
428
|
+
value={trim}
|
|
429
|
+
intrinsicDurationMs={10000}
|
|
430
|
+
pixelsPerMs={0.08}
|
|
431
|
+
onTrimChange={handleTrimChange}
|
|
432
|
+
/>
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
};
|
|
436
|
+
```
|
|
437
|
+
<!-- /react-only -->
|
|
438
|
+
|
|
439
|
+
## Custom Styling
|
|
440
|
+
|
|
441
|
+
<!-- html-only -->
|
|
442
|
+
Style handle appearance with CSS custom properties.
|
|
443
|
+
|
|
444
|
+
```html live
|
|
445
|
+
<div class="space-y-4">
|
|
446
|
+
<ef-timegroup mode="contain" class="w-[720px] h-[480px] bg-black" id="styled-demo">
|
|
447
|
+
<ef-video
|
|
448
|
+
src="https://assets.editframe.com/bars-n-tone.mp4"
|
|
449
|
+
class="size-full object-contain"
|
|
450
|
+
id="styled-video"
|
|
451
|
+
></ef-video>
|
|
452
|
+
</ef-timegroup>
|
|
453
|
+
|
|
454
|
+
<div class="p-4 bg-gray-900 rounded-lg">
|
|
455
|
+
<div class="text-white text-sm mb-2">Custom styled handles:</div>
|
|
456
|
+
<div class="h-[60px] relative bg-gray-800 rounded-lg">
|
|
457
|
+
<ef-trim-handles
|
|
458
|
+
id="styled-handles"
|
|
459
|
+
element-id="styled-video"
|
|
460
|
+
intrinsic-duration-ms="10000"
|
|
461
|
+
></ef-trim-handles>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<style>
|
|
467
|
+
#styled-handles {
|
|
468
|
+
--trim-handle-width: 12px;
|
|
469
|
+
--trim-handle-color: rgba(139, 92, 246, 0.8);
|
|
470
|
+
--trim-handle-active-color: #8b5cf6;
|
|
471
|
+
--trim-overlay-color: rgba(0, 0, 0, 0.6);
|
|
472
|
+
}
|
|
473
|
+
</style>
|
|
474
|
+
|
|
475
|
+
<script>
|
|
476
|
+
const styledHandles = document.getElementById('styled-handles');
|
|
477
|
+
const styledVideo = document.getElementById('styled-video');
|
|
478
|
+
|
|
479
|
+
styledHandles.addEventListener('trim-change', (e) => {
|
|
480
|
+
const { value } = e.detail;
|
|
481
|
+
styledVideo.setAttribute('trimstart', `${value.startMs}ms`);
|
|
482
|
+
styledVideo.setAttribute('trimend', `${value.endMs}ms`);
|
|
483
|
+
});
|
|
484
|
+
</script>
|
|
485
|
+
```
|
|
486
|
+
<!-- /html-only -->
|
|
487
|
+
<!-- react-only -->
|
|
488
|
+
Customize handle appearance:
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
<TrimHandles
|
|
492
|
+
elementId="video-1"
|
|
493
|
+
value={{ startMs: 1000, endMs: 1000 }}
|
|
494
|
+
intrinsicDurationMs={10000}
|
|
495
|
+
className="custom-trim"
|
|
496
|
+
style={{
|
|
497
|
+
'--trim-handle-width': '12px',
|
|
498
|
+
'--trim-handle-color': 'rgba(59, 130, 246, 0.8)',
|
|
499
|
+
'--trim-handle-active-color': '#3b82f6',
|
|
500
|
+
'--trim-overlay-color': 'rgba(0, 0, 0, 0.6)',
|
|
501
|
+
} as React.CSSProperties}
|
|
502
|
+
/>
|
|
503
|
+
```
|
|
504
|
+
<!-- /react-only -->
|
|
505
|
+
|
|
506
|
+
## Trim Modes
|
|
507
|
+
|
|
508
|
+
**Standalone mode** (`mode="standalone"`, default):
|
|
509
|
+
- Handles positioned absolutely based on trim values
|
|
510
|
+
- Shows draggable region between handles
|
|
511
|
+
- Best for full-width trim UI
|
|
512
|
+
|
|
513
|
+
**Track mode** (`mode="track"`):
|
|
514
|
+
- Handles pinned to container edges
|
|
515
|
+
- No draggable region
|
|
516
|
+
- Best for timeline track integration
|
|
517
|
+
|
|
518
|
+
## Event Handling
|
|
519
|
+
|
|
520
|
+
<!-- html-only -->
|
|
521
|
+
### trim-change Event
|
|
522
|
+
|
|
523
|
+
Fired continuously during drag operations.
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
interface TrimChangeDetail {
|
|
527
|
+
elementId: string; // Element being trimmed
|
|
528
|
+
type: 'start' | 'end' | 'region'; // Which handle/region moved
|
|
529
|
+
value: { // New trim values
|
|
530
|
+
startMs: number;
|
|
531
|
+
endMs: number;
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### trim-change-end Event
|
|
537
|
+
|
|
538
|
+
Fired when drag completes (on pointer up).
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
interface TrimChangeEndDetail {
|
|
542
|
+
elementId: string;
|
|
543
|
+
type: 'start' | 'end' | 'region';
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
<!-- /html-only -->
|
|
547
|
+
<!-- react-only -->
|
|
548
|
+
Handle trim events:
|
|
549
|
+
|
|
550
|
+
```tsx
|
|
551
|
+
import { useState } from "react";
|
|
552
|
+
import { TrimHandles } from "@editframe/react";
|
|
553
|
+
|
|
554
|
+
export const TrimEventsExample = () => {
|
|
555
|
+
const [trim, setTrim] = useState({ startMs: 0, endMs: 0 });
|
|
556
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<div>
|
|
560
|
+
<div className="mb-2 text-sm">
|
|
561
|
+
{isDragging ? "Dragging..." : "Idle"}
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div className="relative h-16 bg-gray-800">
|
|
565
|
+
<TrimHandles
|
|
566
|
+
elementId="video-1"
|
|
567
|
+
value={trim}
|
|
568
|
+
intrinsicDurationMs={10000}
|
|
569
|
+
onTrimChange={(e) => {
|
|
570
|
+
setTrim(e.detail.value);
|
|
571
|
+
setIsDragging(true);
|
|
572
|
+
}}
|
|
573
|
+
onTrimChangeEnd={(e) => {
|
|
574
|
+
console.log("Final trim:", e.detail.value);
|
|
575
|
+
setIsDragging(false);
|
|
576
|
+
}}
|
|
577
|
+
/>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
);
|
|
581
|
+
};
|
|
582
|
+
```
|
|
583
|
+
<!-- /react-only -->
|
|
584
|
+
|
|
585
|
+
## Trim Value Semantics
|
|
586
|
+
|
|
587
|
+
Trim values represent time trimmed from source media:
|
|
588
|
+
- `startMs` — Time trimmed from start
|
|
589
|
+
- `endMs` — Time trimmed from end
|
|
590
|
+
- Effective duration = `intrinsicDurationMs - startMs - endMs`
|
|
591
|
+
|
|
592
|
+
Example with 10 second source:
|
|
593
|
+
- `{startMs: 2000, endMs: 3000}` = play seconds 2-7 (5 second duration)
|
|
594
|
+
- `{startMs: 0, endMs: 0}` = play entire source (10 second duration)
|
|
595
|
+
|
|
596
|
+
## Seeking During Trim
|
|
597
|
+
|
|
598
|
+
<!-- html-only -->
|
|
599
|
+
When `seek-target` is set, the element seeks during trim operations:
|
|
600
|
+
<!-- /html-only -->
|
|
601
|
+
<!-- react-only -->
|
|
602
|
+
When `seekTarget` is set, the element seeks during trim operations:
|
|
603
|
+
<!-- /react-only -->
|
|
604
|
+
- Dragging start handle — seeks to trimmed start (0:00 of visible region)
|
|
605
|
+
- Dragging end handle — seeks to trimmed end (last frame of visible region)
|
|
606
|
+
- Dragging region — maintains relative position
|
|
607
|
+
|
|
608
|
+
<!-- react-only -->
|
|
609
|
+
## Controlled Trim
|
|
610
|
+
|
|
611
|
+
Full control over trim state:
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
import { useState } from "react";
|
|
615
|
+
import { TrimHandles, Video } from "@editframe/react";
|
|
616
|
+
|
|
617
|
+
export const ControlledTrimExample = () => {
|
|
618
|
+
const [trim, setTrim] = useState({ startMs: 0, endMs: 0 });
|
|
619
|
+
const intrinsicDuration = 10000;
|
|
620
|
+
|
|
621
|
+
const effectiveDuration = intrinsicDuration - trim.startMs - trim.endMs;
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<div>
|
|
625
|
+
<div className="flex gap-4 mb-4">
|
|
626
|
+
<div>
|
|
627
|
+
<label className="text-sm">Trim Start (ms)</label>
|
|
628
|
+
<input
|
|
629
|
+
type="number"
|
|
630
|
+
value={trim.startMs}
|
|
631
|
+
onChange={(e) => setTrim({ ...trim, startMs: Number(e.target.value) })}
|
|
632
|
+
className="w-full p-2 bg-gray-800 rounded"
|
|
633
|
+
/>
|
|
634
|
+
</div>
|
|
635
|
+
|
|
636
|
+
<div>
|
|
637
|
+
<label className="text-sm">Trim End (ms)</label>
|
|
638
|
+
<input
|
|
639
|
+
type="number"
|
|
640
|
+
value={trim.endMs}
|
|
641
|
+
onChange={(e) => setTrim({ ...trim, endMs: Number(e.target.value) })}
|
|
642
|
+
className="w-full p-2 bg-gray-800 rounded"
|
|
643
|
+
/>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<div className="text-sm">
|
|
647
|
+
<div>Effective Duration: {effectiveDuration}ms</div>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
|
|
651
|
+
<div className="relative h-16 bg-gray-800 rounded mb-4">
|
|
652
|
+
<TrimHandles
|
|
653
|
+
elementId="video-1"
|
|
654
|
+
value={trim}
|
|
655
|
+
intrinsicDurationMs={intrinsicDuration}
|
|
656
|
+
pixelsPerMs={0.08}
|
|
657
|
+
onTrimChange={(e) => setTrim(e.detail.value)}
|
|
658
|
+
/>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<Video
|
|
662
|
+
id="video-1"
|
|
663
|
+
src="/video.mp4"
|
|
664
|
+
trimStart={`${trim.startMs}ms`}
|
|
665
|
+
trimEnd={`${trim.endMs}ms`}
|
|
666
|
+
className="w-full aspect-video"
|
|
667
|
+
/>
|
|
668
|
+
</div>
|
|
669
|
+
);
|
|
670
|
+
};
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## Responsive Handles
|
|
674
|
+
|
|
675
|
+
Handles adapt to container width:
|
|
676
|
+
|
|
677
|
+
```tsx
|
|
678
|
+
<div className="w-full max-w-4xl mx-auto">
|
|
679
|
+
<TrimHandles
|
|
680
|
+
elementId="video-1"
|
|
681
|
+
value={{ startMs: 1000, endMs: 1000 }}
|
|
682
|
+
intrinsicDurationMs={10000}
|
|
683
|
+
className="h-16"
|
|
684
|
+
/>
|
|
685
|
+
</div>
|
|
686
|
+
```
|
|
687
|
+
<!-- /react-only -->
|
|
688
|
+
|
|
689
|
+
## CSS Custom Properties
|
|
690
|
+
|
|
691
|
+
Customize handle appearance:
|
|
692
|
+
|
|
693
|
+
```css
|
|
694
|
+
ef-trim-handles {
|
|
695
|
+
--trim-handle-width: 8px;
|
|
696
|
+
--trim-handle-color: rgba(255, 255, 255, 0.7);
|
|
697
|
+
--trim-handle-active-color: #3b82f6;
|
|
698
|
+
--trim-overlay-color: rgba(0, 0, 0, 0.4);
|
|
699
|
+
--trim-handle-border-radius-start: 2px 0 0 2px;
|
|
700
|
+
--trim-handle-border-radius-end: 0 2px 2px 0;
|
|
701
|
+
--trim-selected-border-width: 0px;
|
|
702
|
+
--trim-selected-border-color: transparent;
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
## Notes
|
|
707
|
+
|
|
708
|
+
- TrimHandles work in two modes: standalone (inline positioning) or track (pinned to edges)
|
|
709
|
+
- Handles constrain dragging to valid ranges
|
|
710
|
+
- Region between handles can be dragged to move both simultaneously
|
|
711
|
+
- Overlays show trimmed (non-visible) regions
|
|
712
|
+
<!-- html-only -->
|
|
713
|
+
- Use `seek-target` to update video preview while dragging
|
|
714
|
+
<!-- /html-only -->
|
|
715
|
+
<!-- react-only -->
|
|
716
|
+
- Use `seekTarget` to update video preview while dragging
|
|
717
|
+
<!-- /react-only -->
|
|
718
|
+
- Event types: "start", "end", or "region" indicate which handle was dragged
|
|
719
|
+
<!-- html-only -->
|
|
720
|
+
- Automatically accounts for zoom level via `pixels-per-ms`
|
|
721
|
+
<!-- /html-only -->
|
|
722
|
+
<!-- react-only -->
|
|
723
|
+
- Automatically accounts for zoom level via `pixelsPerMs`
|
|
724
|
+
<!-- /react-only -->
|
|
725
|
+
- CSS custom properties allow extensive styling customization
|