@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.
Files changed (98) hide show
  1. package/dist/index.js +16 -28
  2. package/dist/index.js.map +1 -1
  3. package/dist/skills/editframe-brand-video-generator/README.md +155 -0
  4. package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
  5. package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
  6. package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
  7. package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
  8. package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
  9. package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
  10. package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
  11. package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
  12. package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
  13. package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
  14. package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
  15. package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
  16. package/dist/skills/editframe-composition/SKILL.md +169 -0
  17. package/dist/skills/editframe-composition/references/audio.md +483 -0
  18. package/dist/skills/editframe-composition/references/captions.md +844 -0
  19. package/dist/skills/editframe-composition/references/composition-model.md +73 -0
  20. package/dist/skills/editframe-composition/references/configuration.md +403 -0
  21. package/dist/skills/editframe-composition/references/css-parts.md +105 -0
  22. package/dist/skills/editframe-composition/references/css-variables.md +640 -0
  23. package/dist/skills/editframe-composition/references/entry-points.md +810 -0
  24. package/dist/skills/editframe-composition/references/events.md +499 -0
  25. package/dist/skills/editframe-composition/references/getting-started.md +259 -0
  26. package/dist/skills/editframe-composition/references/hooks.md +234 -0
  27. package/dist/skills/editframe-composition/references/image.md +241 -0
  28. package/dist/skills/editframe-composition/references/r3f.md +580 -0
  29. package/dist/skills/editframe-composition/references/render-api.md +484 -0
  30. package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
  31. package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
  32. package/dist/skills/editframe-composition/references/scripting.md +606 -0
  33. package/dist/skills/editframe-composition/references/sequencing.md +116 -0
  34. package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
  35. package/dist/skills/editframe-composition/references/surface.md +329 -0
  36. package/dist/skills/editframe-composition/references/text.md +627 -0
  37. package/dist/skills/editframe-composition/references/time-model.md +99 -0
  38. package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
  39. package/dist/skills/editframe-composition/references/timegroup.md +457 -0
  40. package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
  41. package/dist/skills/editframe-composition/references/transcription.md +47 -0
  42. package/dist/skills/editframe-composition/references/transitions.md +608 -0
  43. package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
  44. package/dist/skills/editframe-composition/references/video.md +506 -0
  45. package/dist/skills/editframe-composition/references/waveform.md +327 -0
  46. package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
  47. package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
  48. package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
  49. package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
  50. package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
  51. package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
  52. package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
  53. package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
  54. package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
  55. package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
  56. package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
  57. package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
  58. package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
  59. package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
  60. package/dist/skills/editframe-editor-gui/references/play.md +370 -0
  61. package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
  62. package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
  63. package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
  64. package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
  65. package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
  66. package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
  67. package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
  68. package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
  69. package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
  70. package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
  71. package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
  72. package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
  73. package/dist/skills/editframe-motion-design/SKILL.md +101 -0
  74. package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
  75. package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
  76. package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
  77. package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
  78. package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
  79. package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
  80. package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
  81. package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
  82. package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
  83. package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
  84. package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
  85. package/dist/skills/editframe-webhooks/SKILL.md +126 -0
  86. package/dist/skills/editframe-webhooks/references/events.md +382 -0
  87. package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
  88. package/dist/skills/editframe-webhooks/references/security.md +418 -0
  89. package/dist/skills/editframe-webhooks/references/testing.md +409 -0
  90. package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
  91. package/dist/templates/html/AGENTS.md +13 -0
  92. package/dist/templates/react/AGENTS.md +13 -0
  93. package/dist/utils.js +15 -16
  94. package/dist/utils.js.map +1 -1
  95. package/package.json +1 -1
  96. package/tsdown.config.ts +4 -0
  97. package/dist/detectAgent.js +0 -89
  98. package/dist/detectAgent.js.map +0 -1
@@ -0,0 +1,924 @@
1
+ ---
2
+ title: Transform Handles Element
3
+ description: Interactive resize and rotation handles for transforming composition elements with mouse drag in the preview canvas.
4
+ type: reference
5
+ nav:
6
+ parent: "Transform & Manipulation"
7
+ priority: 10
8
+ api:
9
+ attributes:
10
+ - name: bounds
11
+ type: TransformBounds
12
+ required: true
13
+ description: Bounding box to display handles for
14
+ - name: target
15
+ type: "string | HTMLElement"
16
+ description: Target element (for rotation calculation)
17
+ - name: canvas-scale
18
+ type: number
19
+ default: 1
20
+ description: Canvas zoom scale (fallback when context unavailable)
21
+ - name: enable-rotation
22
+ type: boolean
23
+ default: false
24
+ description: Show rotation handle
25
+ - name: enable-resize
26
+ type: boolean
27
+ default: true
28
+ description: Show resize handles
29
+ - name: enable-drag
30
+ type: boolean
31
+ default: true
32
+ description: Enable drag to move
33
+ - name: corners-only
34
+ type: boolean
35
+ default: false
36
+ description: Show only corner handles (hide edge handles)
37
+ - name: lock-aspect-ratio
38
+ type: boolean
39
+ default: false
40
+ description: Maintain aspect ratio during resize
41
+ - name: rotation-step
42
+ type: number
43
+ description: Snap rotation to degrees (e.g. 15 for 15deg increments)
44
+ - name: min-size
45
+ type: number
46
+ default: 10
47
+ description: Minimum width/height during resize
48
+ properties:
49
+ - name: interactionMode
50
+ type: "'idle' | 'dragging' | 'resizing' | 'rotating'"
51
+ description: Current interaction state
52
+ events:
53
+ - name: bounds-change
54
+ detail: "{ bounds: TransformBounds }"
55
+ description: Fired during resize or drag operations
56
+ - name: rotation-change
57
+ detail: "{ rotation: number }"
58
+ description: Fired during rotation operations
59
+ types:
60
+ - name: TransformBounds
61
+ type: interface
62
+ definition: |
63
+ interface TransformBounds {
64
+ x: number; // Top-left x position
65
+ y: number; // Top-left y position
66
+ width: number; // Width
67
+ height: number; // Height
68
+ rotation?: number; // Rotation in degrees
69
+ }
70
+ react:
71
+ generate: true
72
+ componentName: TransformHandles
73
+ importPath: "@editframe/react"
74
+ propMapping:
75
+ canvas-scale: canvasScale
76
+ enable-rotation: enableRotation
77
+ enable-resize: enableResize
78
+ enable-drag: enableDrag
79
+ corners-only: cornersOnly
80
+ lock-aspect-ratio: lockAspectRatio
81
+ rotation-step: rotationStep
82
+ min-size: minSize
83
+ bounds-change: onBoundsChange
84
+ rotation-change: onRotationChange
85
+ additionalProps:
86
+ - name: className
87
+ type: string
88
+ description: CSS classes for styling
89
+ nav:
90
+ parent: "Components / Transform & Manipulation"
91
+ priority: 52
92
+ related: ["overlay-item", "resizable-box"]
93
+ ---
94
+
95
+ <!-- html-only -->
96
+ # ef-transform-handles
97
+ <!-- /html-only -->
98
+ <!-- react-only -->
99
+ # TransformHandles
100
+ <!-- /react-only -->
101
+
102
+ Interactive resize and rotation handles for elements.
103
+
104
+ <!-- react-only -->
105
+ ## Import
106
+
107
+ ```tsx
108
+ import { TransformHandles } from "@editframe/react";
109
+ ```
110
+ <!-- /react-only -->
111
+
112
+ ## Basic Usage
113
+
114
+ <!-- html-only -->
115
+ Display handles for a positioned element:
116
+
117
+ ```html live
118
+ <div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50">
119
+ <ef-transform-handles
120
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
121
+ enable-rotation
122
+ enable-resize
123
+ ></ef-transform-handles>
124
+ </div>
125
+ ```
126
+
127
+ Drag handles to resize, drag rotation handle to rotate.
128
+ <!-- /html-only -->
129
+ <!-- react-only -->
130
+ ```tsx
131
+ import { TransformHandles, OverlayLayer, OverlayItem } from "@editframe/react";
132
+
133
+ export const App = () => {
134
+ const [bounds, setBounds] = useState({
135
+ x: 100,
136
+ y: 100,
137
+ width: 400,
138
+ height: 300,
139
+ rotation: 0
140
+ });
141
+
142
+ return (
143
+ <div className="relative w-full h-screen">
144
+ <OverlayLayer className="absolute inset-0">
145
+ <OverlayItem elementId="video-1">
146
+ <TransformHandles
147
+ bounds={bounds}
148
+ onBoundsChange={(e) => setBounds(e.detail)}
149
+ />
150
+ </OverlayItem>
151
+ </OverlayLayer>
152
+
153
+ <Timegroup mode="contain" className="w-[1920px] h-[1080px]">
154
+ <Video
155
+ id="video-1"
156
+ src="/assets/video.mp4"
157
+ className="absolute"
158
+ style={{
159
+ left: bounds.x,
160
+ top: bounds.y,
161
+ width: bounds.width,
162
+ height: bounds.height,
163
+ transform: `rotate(${bounds.rotation}deg)`
164
+ }}
165
+ />
166
+ </Timegroup>
167
+ </div>
168
+ );
169
+ };
170
+ ```
171
+ <!-- /react-only -->
172
+
173
+ ## Bounds
174
+
175
+ <!-- html-only -->
176
+ Transform handles require bounds in screen coordinates:
177
+
178
+ ```javascript
179
+ const handles = document.querySelector('ef-transform-handles');
180
+
181
+ handles.bounds = {
182
+ x: 100, // Left position
183
+ y: 100, // Top position
184
+ width: 200, // Width
185
+ height: 150, // Height
186
+ rotation: 0 // Optional rotation in degrees
187
+ };
188
+ ```
189
+ <!-- /html-only -->
190
+ <!-- react-only -->
191
+ Bounds define position, size, and optional rotation:
192
+
193
+ ```typescript
194
+ interface TransformBounds {
195
+ x: number; // Top-left x position
196
+ y: number; // Top-left y position
197
+ width: number; // Width
198
+ height: number; // Height
199
+ rotation?: number; // Rotation in degrees
200
+ }
201
+ ```
202
+ <!-- /react-only -->
203
+
204
+ ## Resize Handles
205
+
206
+ <!-- html-only -->
207
+ Enable resize with corner and edge handles:
208
+
209
+ ```html
210
+ <ef-transform-handles
211
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
212
+ enable-resize
213
+ ></ef-transform-handles>
214
+ ```
215
+ <!-- /html-only -->
216
+
217
+ ### Corner Handles
218
+
219
+ Four corner handles resize proportionally by default:
220
+
221
+ - **Northwest**: Top-left corner
222
+ - **Northeast**: Top-right corner
223
+ - **Southwest**: Bottom-left corner
224
+ - **Southeast**: Bottom-right corner
225
+
226
+ ### Edge Handles
227
+
228
+ Four edge handles resize in one dimension:
229
+
230
+ - **North**: Top edge (height only)
231
+ - **East**: Right edge (width only)
232
+ - **South**: Bottom edge (height only)
233
+ - **West**: Left edge (width only)
234
+
235
+ ### Corners Only
236
+
237
+ Hide edge handles, show only corners:
238
+
239
+ <!-- html-only -->
240
+ ```html
241
+ <ef-transform-handles
242
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
243
+ enable-resize
244
+ corners-only
245
+ ></ef-transform-handles>
246
+ ```
247
+ <!-- /html-only -->
248
+ <!-- react-only -->
249
+ ```tsx
250
+ import { TransformHandles } from "@editframe/react";
251
+
252
+ export const CornersOnly = () => {
253
+ const [bounds, setBounds] = useState({
254
+ x: 150,
255
+ y: 150,
256
+ width: 400,
257
+ height: 400,
258
+ rotation: 0
259
+ });
260
+
261
+ return (
262
+ <TransformHandles
263
+ bounds={bounds}
264
+ cornersOnly
265
+ onBoundsChange={(e) => setBounds(e.detail)}
266
+ />
267
+ );
268
+ };
269
+ ```
270
+ <!-- /react-only -->
271
+
272
+ ## Rotation Handle
273
+
274
+ <!-- html-only -->
275
+ Enable rotation with top-center handle:
276
+
277
+ ```html live
278
+ <div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50">
279
+ <ef-transform-handles
280
+ .bounds=${{ x: 150, y: 100, width: 200, height: 150, rotation: 15 }}
281
+ enable-rotation
282
+ enable-resize
283
+ ></ef-transform-handles>
284
+ </div>
285
+ ```
286
+
287
+ Drag the rotation handle to rotate the bounds.
288
+ <!-- /html-only -->
289
+ <!-- react-only -->
290
+ ```tsx
291
+ import { TransformHandles } from "@editframe/react";
292
+
293
+ export const RotatableElement = () => {
294
+ const [bounds, setBounds] = useState({
295
+ x: 200,
296
+ y: 200,
297
+ width: 300,
298
+ height: 200,
299
+ rotation: 0
300
+ });
301
+
302
+ return (
303
+ <TransformHandles
304
+ bounds={bounds}
305
+ enableRotation
306
+ onBoundsChange={(e) => setBounds(e.detail)}
307
+ onRotationChange={(e) => console.log("Rotation:", e.detail)}
308
+ />
309
+ );
310
+ };
311
+ ```
312
+ <!-- /react-only -->
313
+
314
+ ### Rotation Step
315
+
316
+ Snap rotation to specific increments:
317
+
318
+ <!-- html-only -->
319
+ ```html
320
+ <ef-transform-handles
321
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
322
+ enable-rotation
323
+ rotation-step="15"
324
+ ></ef-transform-handles>
325
+ ```
326
+
327
+ Rotation snaps to 15deg increments: 0deg, 15deg, 30deg, 45deg, etc.
328
+ <!-- /html-only -->
329
+ <!-- react-only -->
330
+ ```tsx
331
+ import { TransformHandles } from "@editframe/react";
332
+
333
+ export const SnappedRotation = () => {
334
+ const [bounds, setBounds] = useState({
335
+ x: 200,
336
+ y: 200,
337
+ width: 300,
338
+ height: 300,
339
+ rotation: 0
340
+ });
341
+
342
+ return (
343
+ <TransformHandles
344
+ bounds={bounds}
345
+ enableRotation
346
+ rotationStep={15} // Snap to 15deg increments
347
+ onBoundsChange={(e) => setBounds(e.detail)}
348
+ />
349
+ );
350
+ };
351
+ ```
352
+ <!-- /react-only -->
353
+
354
+ ## Aspect Ratio Lock
355
+
356
+ Maintain aspect ratio during resize:
357
+
358
+ <!-- html-only -->
359
+ ```html
360
+ <ef-transform-handles
361
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
362
+ enable-resize
363
+ lock-aspect-ratio
364
+ ></ef-transform-handles>
365
+ ```
366
+
367
+ All resize operations maintain the original aspect ratio.
368
+ <!-- /html-only -->
369
+ <!-- react-only -->
370
+ ```tsx
371
+ import { TransformHandles } from "@editframe/react";
372
+
373
+ export const LockedAspect = () => {
374
+ const [bounds, setBounds] = useState({
375
+ x: 100,
376
+ y: 100,
377
+ width: 640,
378
+ height: 360, // 16:9 ratio
379
+ rotation: 0
380
+ });
381
+
382
+ return (
383
+ <TransformHandles
384
+ bounds={bounds}
385
+ lockAspectRatio
386
+ onBoundsChange={(e) => setBounds(e.detail)}
387
+ />
388
+ );
389
+ };
390
+ ```
391
+ <!-- /react-only -->
392
+
393
+ ## Drag to Move
394
+
395
+ <!-- html-only -->
396
+ Enable dragging to move the bounds:
397
+
398
+ ```html
399
+ <ef-transform-handles
400
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
401
+ enable-drag
402
+ ></ef-transform-handles>
403
+ ```
404
+
405
+ Click and drag the overlay to move.
406
+
407
+ ### Disable Drag
408
+
409
+ ```html
410
+ <ef-transform-handles
411
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
412
+ enable-drag="false"
413
+ ></ef-transform-handles>
414
+ ```
415
+ <!-- /html-only -->
416
+ <!-- react-only -->
417
+ Click and drag the overlay area to move. Disable with `enableDrag={false}`:
418
+
419
+ ```tsx
420
+ import { TransformHandles } from "@editframe/react";
421
+
422
+ export const ResizeOnly = () => {
423
+ const [bounds, setBounds] = useState({
424
+ x: 100,
425
+ y: 100,
426
+ width: 300,
427
+ height: 200
428
+ });
429
+
430
+ return (
431
+ <TransformHandles
432
+ bounds={bounds}
433
+ enableDrag={false}
434
+ enableRotation={false}
435
+ onBoundsChange={(e) => setBounds(e.detail)}
436
+ />
437
+ );
438
+ };
439
+ ```
440
+ <!-- /react-only -->
441
+
442
+ ## Events
443
+
444
+ <!-- html-only -->
445
+ Listen for transformation events:
446
+
447
+ ```javascript
448
+ const handles = document.querySelector('ef-transform-handles');
449
+
450
+ // Bounds changed (resize or move)
451
+ handles.addEventListener('bounds-change', (e) => {
452
+ const { bounds } = e.detail;
453
+ console.log('New bounds:', bounds);
454
+
455
+ // Update element
456
+ element.style.left = `${bounds.x}px`;
457
+ element.style.top = `${bounds.y}px`;
458
+ element.style.width = `${bounds.width}px`;
459
+ element.style.height = `${bounds.height}px`;
460
+ });
461
+
462
+ // Rotation changed
463
+ handles.addEventListener('rotation-change', (e) => {
464
+ const { rotation } = e.detail;
465
+ console.log('New rotation:', rotation);
466
+
467
+ // Update element
468
+ element.style.transform = `rotate(${rotation}deg)`;
469
+ });
470
+ ```
471
+ <!-- /html-only -->
472
+ <!-- react-only -->
473
+ ### onBoundsChange
474
+
475
+ ```typescript
476
+ interface TransformBounds {
477
+ x: number;
478
+ y: number;
479
+ width: number;
480
+ height: number;
481
+ rotation?: number;
482
+ }
483
+ ```
484
+
485
+ ### onRotationChange
486
+
487
+ ```typescript
488
+ interface RotationChangeDetail {
489
+ rotation: number; // Rotation in degrees
490
+ }
491
+ ```
492
+ <!-- /react-only -->
493
+
494
+ ## Interaction Modes
495
+
496
+ Transform handles track current interaction:
497
+
498
+ <!-- html-only -->
499
+ ```javascript
500
+ const handles = document.querySelector('ef-transform-handles');
501
+
502
+ console.log(handles.interactionMode);
503
+ // 'idle' | 'dragging' | 'resizing' | 'rotating'
504
+
505
+ // Only one mode active at a time
506
+ ```
507
+ <!-- /html-only -->
508
+ <!-- react-only -->
509
+ The `interactionMode` property reflects the current state: `'idle'`, `'dragging'`, `'resizing'`, or `'rotating'`. Only one mode is active at a time.
510
+ <!-- /react-only -->
511
+
512
+ ## Coordinate System
513
+
514
+ Transform handles work in screen pixel coordinates:
515
+
516
+ <!-- html-only -->
517
+ ```javascript
518
+ // Bounds are in screen pixels (not canvas coordinates)
519
+ handles.bounds = {
520
+ x: 100, // Screen x
521
+ y: 100, // Screen y
522
+ width: 200, // Screen width
523
+ height: 150 // Screen height
524
+ };
525
+
526
+ // Events dispatch screen coordinates
527
+ handles.addEventListener('bounds-change', (e) => {
528
+ const { bounds } = e.detail;
529
+ // bounds.x, bounds.y are screen coordinates
530
+ });
531
+ ```
532
+
533
+ When using with canvas, convert between canvas and screen coordinates.
534
+ <!-- /html-only -->
535
+ <!-- react-only -->
536
+ Bounds are in absolute pixel coordinates. When using with canvas, convert between canvas and screen coordinates as needed.
537
+ <!-- /react-only -->
538
+
539
+ ## Canvas Scale
540
+
541
+ <!-- html-only -->
542
+ Provide canvas scale for zoom-aware rendering:
543
+
544
+ ```javascript
545
+ const handles = document.querySelector('ef-transform-handles');
546
+
547
+ // Set scale directly
548
+ handles.canvasScale = 1.5;
549
+
550
+ // Or via context (automatic with pan-zoom)
551
+ // <ef-pan-zoom>
552
+ // <ef-transform-handles></ef-transform-handles>
553
+ // </ef-pan-zoom>
554
+ ```
555
+
556
+ Canvas scale ensures handles render at consistent size regardless of zoom.
557
+ <!-- /html-only -->
558
+ <!-- react-only -->
559
+ Use `canvasScale` when inside PanZoom to maintain handle size at any zoom level:
560
+
561
+ ```tsx
562
+ import { PanZoom, TransformHandles, OverlayLayer, OverlayItem } from "@editframe/react";
563
+
564
+ export const ZoomableTransform = () => {
565
+ const [bounds, setBounds] = useState({
566
+ x: 200,
567
+ y: 200,
568
+ width: 400,
569
+ height: 300,
570
+ rotation: 0
571
+ });
572
+ const [scale, setScale] = useState(1);
573
+
574
+ return (
575
+ <PanZoom
576
+ className="w-full h-full"
577
+ onTransformChanged={(e) => setScale(e.detail.scale)}
578
+ >
579
+ <OverlayLayer className="absolute inset-0">
580
+ <OverlayItem elementId="element-1">
581
+ <TransformHandles
582
+ bounds={bounds}
583
+ canvasScale={scale} // Compensate for zoom
584
+ enableRotation
585
+ onBoundsChange={(e) => setBounds(e.detail)}
586
+ />
587
+ </OverlayItem>
588
+ </OverlayLayer>
589
+
590
+ <Timegroup mode="fixed" duration="5s" className="w-[1920px] h-[1080px]">
591
+ <Video
592
+ id="element-1"
593
+ src="/assets/video.mp4"
594
+ className="absolute"
595
+ style={{
596
+ left: bounds.x,
597
+ top: bounds.y,
598
+ width: bounds.width,
599
+ height: bounds.height,
600
+ transform: `rotate(${bounds.rotation}deg)`
601
+ }}
602
+ />
603
+ </Timegroup>
604
+ </PanZoom>
605
+ );
606
+ };
607
+ ```
608
+ <!-- /react-only -->
609
+
610
+ <!-- html-only -->
611
+ ## Target Element
612
+
613
+ Provide target element for rotation calculations:
614
+
615
+ ```javascript
616
+ const handles = document.querySelector('ef-transform-handles');
617
+ const element = document.getElementById('my-element');
618
+
619
+ // String selector
620
+ handles.target = '#my-element';
621
+
622
+ // Or element reference
623
+ handles.target = element;
624
+ ```
625
+
626
+ Target element's center is used as rotation origin.
627
+ <!-- /html-only -->
628
+
629
+ ## Minimum Size
630
+
631
+ <!-- html-only -->
632
+ Set minimum bounds during resize:
633
+
634
+ ```html
635
+ <ef-transform-handles
636
+ .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
637
+ enable-resize
638
+ min-size="50"
639
+ ></ef-transform-handles>
640
+ ```
641
+
642
+ Width and height cannot be resized below 50 pixels.
643
+ <!-- /html-only -->
644
+ <!-- react-only -->
645
+ ```tsx
646
+ import { TransformHandles } from "@editframe/react";
647
+
648
+ export const MinSizeConstraint = () => {
649
+ const [bounds, setBounds] = useState({
650
+ x: 100,
651
+ y: 100,
652
+ width: 200,
653
+ height: 200
654
+ });
655
+
656
+ return (
657
+ <TransformHandles
658
+ bounds={bounds}
659
+ minSize={100} // Cannot resize smaller than 100x100
660
+ onBoundsChange={(e) => setBounds(e.detail)}
661
+ />
662
+ );
663
+ };
664
+ ```
665
+ <!-- /react-only -->
666
+
667
+ ## Styling
668
+
669
+ Transform handles use CSS custom properties:
670
+
671
+ ```css
672
+ ef-transform-handles {
673
+ /* Border color */
674
+ --ef-transform-handles-border-color: #2196f3;
675
+
676
+ /* Border during drag */
677
+ --ef-transform-handles-dragging-border-color: #1976d2;
678
+
679
+ /* Handle background */
680
+ --ef-transform-handles-handle-color: #fff;
681
+
682
+ /* Handle border */
683
+ --ef-transform-handles-handle-border-color: #2196f3;
684
+
685
+ /* Rotation handle color */
686
+ --ef-transform-handles-rotate-handle-color: #4caf50;
687
+ }
688
+ ```
689
+
690
+ ## One-Way Data Flow
691
+
692
+ Transform handles follow one-way data flow:
693
+
694
+ 1. Parent sets `bounds` prop
695
+ 2. User interacts with handles
696
+ 3. Handles dispatch events
697
+ 4. Parent updates element
698
+ 5. Parent updates `bounds` prop
699
+ 6. Handles re-render
700
+
701
+ Never mutate the `bounds` prop directly from handle events.
702
+
703
+ <!-- html-only -->
704
+ ## Wheel Events
705
+
706
+ Transform handles forward wheel events to parent pan-zoom for seamless zooming:
707
+
708
+ ```html
709
+ <ef-pan-zoom>
710
+ <ef-canvas>
711
+ <!-- Wheel events on handles zoom the canvas -->
712
+ </ef-canvas>
713
+ </ef-pan-zoom>
714
+ ```
715
+
716
+ Mouse wheel over handles zooms the canvas instead of being blocked.
717
+
718
+ ## Rotation Calculation
719
+
720
+ Rotation is calculated relative to target element's center:
721
+
722
+ ```javascript
723
+ // For rotated elements
724
+ handles.target = element;
725
+ handles.bounds = {
726
+ x: element.offsetLeft,
727
+ y: element.offsetTop,
728
+ width: element.offsetWidth,
729
+ height: element.offsetHeight,
730
+ rotation: getCurrentRotation(element)
731
+ };
732
+
733
+ // Rotation handle calculates delta from target center
734
+ ```
735
+
736
+ ## Usage with Canvas
737
+
738
+ Transform handles integrate with ef-canvas:
739
+
740
+ ```javascript
741
+ const canvas = document.querySelector('ef-canvas');
742
+ const handles = document.querySelector('ef-transform-handles');
743
+
744
+ // Get element bounds from canvas
745
+ const data = canvas.getElementData('element-id');
746
+
747
+ // Convert to screen coordinates
748
+ const screenPos = canvas.canvasToScreenCoords(data.x, data.y);
749
+ const scale = panZoom.scale;
750
+
751
+ handles.bounds = {
752
+ x: screenPos.x,
753
+ y: screenPos.y,
754
+ width: data.width * scale,
755
+ height: data.height * scale,
756
+ rotation: data.rotation
757
+ };
758
+
759
+ // Listen for changes
760
+ handles.addEventListener('bounds-change', (e) => {
761
+ // Convert back to canvas coordinates
762
+ const canvasPos = canvas.screenToCanvasCoords(e.detail.bounds.x, e.detail.bounds.y);
763
+ canvas.updateElementPosition('element-id', canvasPos.x, canvasPos.y);
764
+ });
765
+ ```
766
+ <!-- /html-only -->
767
+
768
+ <!-- react-only -->
769
+ ## Multi-Element Editor
770
+
771
+ ```tsx
772
+ import { TransformHandles, OverlayLayer, OverlayItem } from "@editframe/react";
773
+
774
+ interface Element {
775
+ id: string;
776
+ bounds: TransformBounds;
777
+ }
778
+
779
+ export const MultiElementEditor = () => {
780
+ const [elements, setElements] = useState<Element[]>([
781
+ { id: "video-1", bounds: { x: 100, y: 100, width: 400, height: 300, rotation: 0 } },
782
+ { id: "text-1", bounds: { x: 500, y: 200, width: 300, height: 100, rotation: 0 } }
783
+ ]);
784
+ const [selectedId, setSelectedId] = useState<string>("video-1");
785
+
786
+ const updateBounds = (id: string, newBounds: TransformBounds) => {
787
+ setElements(elements.map(el =>
788
+ el.id === id ? { ...el, bounds: newBounds } : el
789
+ ));
790
+ };
791
+
792
+ const selectedElement = elements.find(el => el.id === selectedId);
793
+
794
+ return (
795
+ <div className="relative w-full h-screen">
796
+ <OverlayLayer className="absolute inset-0">
797
+ {selectedElement && (
798
+ <OverlayItem elementId={selectedId}>
799
+ <TransformHandles
800
+ bounds={selectedElement.bounds}
801
+ enableRotation
802
+ onBoundsChange={(e) => updateBounds(selectedId, e.detail)}
803
+ />
804
+ </OverlayItem>
805
+ )}
806
+ </OverlayLayer>
807
+
808
+ <Timegroup mode="contain" className="w-[1920px] h-[1080px]">
809
+ {elements.map((element) => (
810
+ <Video
811
+ key={element.id}
812
+ id={element.id}
813
+ src="/assets/video.mp4"
814
+ className="absolute"
815
+ style={{
816
+ left: element.bounds.x,
817
+ top: element.bounds.y,
818
+ width: element.bounds.width,
819
+ height: element.bounds.height,
820
+ transform: `rotate(${element.bounds.rotation}deg)`
821
+ }}
822
+ onClick={() => setSelectedId(element.id)}
823
+ />
824
+ ))}
825
+ </Timegroup>
826
+ </div>
827
+ );
828
+ };
829
+ ```
830
+
831
+ ## Keyboard Shortcuts
832
+
833
+ ```tsx
834
+ import { TransformHandles } from "@editframe/react";
835
+
836
+ export const KeyboardTransform = () => {
837
+ const [bounds, setBounds] = useState({
838
+ x: 200,
839
+ y: 200,
840
+ width: 300,
841
+ height: 300,
842
+ rotation: 0
843
+ });
844
+
845
+ useEffect(() => {
846
+ const handleKeyDown = (e: KeyboardEvent) => {
847
+ const step = e.shiftKey ? 10 : 1;
848
+
849
+ switch (e.key) {
850
+ case "ArrowLeft":
851
+ setBounds({ ...bounds, x: bounds.x - step });
852
+ break;
853
+ case "ArrowRight":
854
+ setBounds({ ...bounds, x: bounds.x + step });
855
+ break;
856
+ case "ArrowUp":
857
+ setBounds({ ...bounds, y: bounds.y - step });
858
+ break;
859
+ case "ArrowDown":
860
+ setBounds({ ...bounds, y: bounds.y + step });
861
+ break;
862
+ }
863
+ };
864
+
865
+ window.addEventListener("keydown", handleKeyDown);
866
+ return () => window.removeEventListener("keydown", handleKeyDown);
867
+ }, [bounds]);
868
+
869
+ return (
870
+ <TransformHandles
871
+ bounds={bounds}
872
+ enableRotation
873
+ onBoundsChange={(e) => setBounds(e.detail)}
874
+ />
875
+ );
876
+ };
877
+ ```
878
+ <!-- /react-only -->
879
+
880
+ ## Handle Features
881
+
882
+ - **8 resize handles**: nw, n, ne, e, se, s, sw, w
883
+ - **Rotation handle**: Top-center circular handle (when enabled)
884
+ - **Drag area**: Click anywhere inside bounds to drag
885
+ - **Visual feedback**: Handles highlight on hover
886
+ - **Smart cursors**: Automatically set appropriate resize cursors
887
+ - **Shift key**: Hold to maintain aspect ratio during resize
888
+
889
+ ## CSS Customization
890
+
891
+ Use CSS variables to customize handle appearance:
892
+
893
+ ```css
894
+ .transform-handles {
895
+ --ef-transform-handles-border-color: #3b82f6;
896
+ --ef-transform-handles-handle-color: #ffffff;
897
+ --ef-transform-handles-handle-border-color: #3b82f6;
898
+ --ef-transform-handles-rotate-handle-color: #10b981;
899
+ }
900
+ ```
901
+
902
+ ## Pointer Events
903
+
904
+ Transform handles have `pointer-events: auto` for interactivity. Parent overlay layer should have `pointer-events: none`.
905
+
906
+ ## Important Notes
907
+
908
+ <!-- html-only -->
909
+ - Bounds are in absolute pixel coordinates
910
+ - Use `canvas-scale` when inside pan-zoom to maintain handle size
911
+ - Rotation is in degrees (0-360)
912
+ - `lock-aspect-ratio` maintains the initial aspect ratio
913
+ - Handles capture pointer events but allow wheel events to pass through
914
+ - Minimum size prevents resizing below practical limits
915
+ <!-- /html-only -->
916
+ <!-- react-only -->
917
+ - Must be placed inside an OverlayItem within an OverlayLayer
918
+ - Bounds are in absolute pixel coordinates
919
+ - Use `canvasScale` when inside PanZoom to maintain handle size
920
+ - Rotation is in degrees (0-360)
921
+ - `lockAspectRatio` maintains the initial aspect ratio
922
+ - Handles capture pointer events but allow wheel events to pass through
923
+ - Minimum size prevents resizing below practical limits
924
+ <!-- /react-only -->