@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,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 -->
|