@editframe/create 0.43.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/README.md +11 -0
- 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 +2 -2
- package/tsdown.config.ts +4 -0
- package/dist/detectAgent.js +0 -89
- package/dist/detectAgent.js.map +0 -1
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Resizable Box Element
|
|
3
|
+
description: Resizable box component with draggable edge and corner handles, supporting configurable minimum and maximum size constraints.
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Transform & Manipulation"
|
|
7
|
+
priority: 11
|
|
8
|
+
api:
|
|
9
|
+
attributes:
|
|
10
|
+
- name: bounds
|
|
11
|
+
type: BoxBounds
|
|
12
|
+
required: true
|
|
13
|
+
description: Bounding box dimensions and position
|
|
14
|
+
- name: min-size
|
|
15
|
+
type: number
|
|
16
|
+
default: 10
|
|
17
|
+
description: Minimum width and height during resize
|
|
18
|
+
events:
|
|
19
|
+
- name: bounds-change
|
|
20
|
+
detail: "{ bounds: BoxBounds }"
|
|
21
|
+
description: Fired during resize or move operations
|
|
22
|
+
types:
|
|
23
|
+
- name: BoxBounds
|
|
24
|
+
type: interface
|
|
25
|
+
definition: |
|
|
26
|
+
interface BoxBounds {
|
|
27
|
+
x: number; // Left position
|
|
28
|
+
y: number; // Top position
|
|
29
|
+
width: number; // Width
|
|
30
|
+
height: number; // Height
|
|
31
|
+
}
|
|
32
|
+
react:
|
|
33
|
+
generate: true
|
|
34
|
+
componentName: ResizableBox
|
|
35
|
+
importPath: "@editframe/react"
|
|
36
|
+
propMapping:
|
|
37
|
+
min-size: minSize
|
|
38
|
+
bounds-change: onBoundsChange
|
|
39
|
+
additionalProps:
|
|
40
|
+
- name: className
|
|
41
|
+
type: string
|
|
42
|
+
description: CSS classes for styling
|
|
43
|
+
nav:
|
|
44
|
+
parent: "Components / Transform & Manipulation"
|
|
45
|
+
priority: 53
|
|
46
|
+
related: ["transform-handles", "dial"]
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
<!-- html-only -->
|
|
50
|
+
# ef-resizable-box
|
|
51
|
+
<!-- /html-only -->
|
|
52
|
+
<!-- react-only -->
|
|
53
|
+
# ResizableBox
|
|
54
|
+
<!-- /react-only -->
|
|
55
|
+
|
|
56
|
+
Resizable container with drag handles for interactive resizing and positioning.
|
|
57
|
+
|
|
58
|
+
<!-- react-only -->
|
|
59
|
+
## Import
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { ResizableBox } from "@editframe/react";
|
|
63
|
+
```
|
|
64
|
+
<!-- /react-only -->
|
|
65
|
+
|
|
66
|
+
## Basic Usage
|
|
67
|
+
|
|
68
|
+
<!-- html-only -->
|
|
69
|
+
Display a resizable box:
|
|
70
|
+
|
|
71
|
+
```html live
|
|
72
|
+
<div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50">
|
|
73
|
+
<ef-resizable-box
|
|
74
|
+
.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
|
|
75
|
+
>
|
|
76
|
+
<div class="flex items-center justify-center text-gray-600">
|
|
77
|
+
Drag handles to resize
|
|
78
|
+
</div>
|
|
79
|
+
</ef-resizable-box>
|
|
80
|
+
</div>
|
|
81
|
+
```
|
|
82
|
+
<!-- /html-only -->
|
|
83
|
+
<!-- react-only -->
|
|
84
|
+
```tsx
|
|
85
|
+
import { ResizableBox } from "@editframe/react";
|
|
86
|
+
|
|
87
|
+
export const App = () => {
|
|
88
|
+
const [bounds, setBounds] = useState({
|
|
89
|
+
x: 100,
|
|
90
|
+
y: 100,
|
|
91
|
+
width: 300,
|
|
92
|
+
height: 200
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="relative w-full h-screen bg-gray-100">
|
|
97
|
+
<ResizableBox
|
|
98
|
+
bounds={bounds}
|
|
99
|
+
onBoundsChange={(e) => setBounds(e.detail)}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
```
|
|
105
|
+
<!-- /react-only -->
|
|
106
|
+
|
|
107
|
+
## Bounds
|
|
108
|
+
|
|
109
|
+
<!-- html-only -->
|
|
110
|
+
Resizable box requires bounds relative to its container:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const box = document.querySelector('ef-resizable-box');
|
|
114
|
+
|
|
115
|
+
box.bounds = {
|
|
116
|
+
x: 100, // Left position
|
|
117
|
+
y: 100, // Top position
|
|
118
|
+
width: 200, // Width
|
|
119
|
+
height: 150 // Height
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
<!-- /html-only -->
|
|
123
|
+
<!-- react-only -->
|
|
124
|
+
Bounds define the box position and size relative to its parent container:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface BoxBounds {
|
|
128
|
+
x: number; // Left position
|
|
129
|
+
y: number; // Top position
|
|
130
|
+
width: number; // Box width
|
|
131
|
+
height: number; // Box height
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
<!-- /react-only -->
|
|
135
|
+
|
|
136
|
+
## Resize Handles
|
|
137
|
+
|
|
138
|
+
Eight handles for resizing:
|
|
139
|
+
|
|
140
|
+
### Corner Handles
|
|
141
|
+
|
|
142
|
+
- **Northwest** (top-left)
|
|
143
|
+
- **Northeast** (top-right)
|
|
144
|
+
- **Southwest** (bottom-left)
|
|
145
|
+
- **Southeast** (bottom-right)
|
|
146
|
+
|
|
147
|
+
Corner handles resize both width and height.
|
|
148
|
+
|
|
149
|
+
### Edge Handles
|
|
150
|
+
|
|
151
|
+
- **North** (top edge) - height only
|
|
152
|
+
- **East** (right edge) - width only
|
|
153
|
+
- **South** (bottom edge) - height only
|
|
154
|
+
- **West** (left edge) - width only
|
|
155
|
+
|
|
156
|
+
## Drag to Move
|
|
157
|
+
|
|
158
|
+
<!-- html-only -->
|
|
159
|
+
Click and drag the box content (not handles) to move:
|
|
160
|
+
|
|
161
|
+
```html live
|
|
162
|
+
<div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50">
|
|
163
|
+
<ef-resizable-box
|
|
164
|
+
.bounds=${{ x: 100, y: 80, width: 180, height: 120 }}
|
|
165
|
+
>
|
|
166
|
+
<div class="flex items-center justify-center text-gray-600 cursor-grab active:cursor-grabbing">
|
|
167
|
+
Drag to move
|
|
168
|
+
</div>
|
|
169
|
+
</ef-resizable-box>
|
|
170
|
+
</div>
|
|
171
|
+
```
|
|
172
|
+
<!-- /html-only -->
|
|
173
|
+
<!-- react-only -->
|
|
174
|
+
Click and drag the box interior to reposition. Visual feedback shows appropriate cursors on hover.
|
|
175
|
+
<!-- /react-only -->
|
|
176
|
+
|
|
177
|
+
## Events
|
|
178
|
+
|
|
179
|
+
<!-- html-only -->
|
|
180
|
+
Listen for bounds changes:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
const box = document.querySelector('ef-resizable-box');
|
|
184
|
+
|
|
185
|
+
box.addEventListener('bounds-change', (e) => {
|
|
186
|
+
const { bounds } = e.detail;
|
|
187
|
+
console.log('New bounds:', bounds);
|
|
188
|
+
|
|
189
|
+
// bounds = { x, y, width, height }
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Events fire during both resize and move operations.
|
|
194
|
+
<!-- /html-only -->
|
|
195
|
+
<!-- react-only -->
|
|
196
|
+
The `onBoundsChange` callback fires during both resize and move operations:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
<ResizableBox
|
|
200
|
+
bounds={bounds}
|
|
201
|
+
onBoundsChange={(e) => {
|
|
202
|
+
const newBounds = e.detail;
|
|
203
|
+
console.log('New bounds:', newBounds);
|
|
204
|
+
setBounds(newBounds);
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
```
|
|
208
|
+
<!-- /react-only -->
|
|
209
|
+
|
|
210
|
+
## Minimum Size
|
|
211
|
+
|
|
212
|
+
<!-- html-only -->
|
|
213
|
+
Set minimum dimensions during resize:
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<ef-resizable-box
|
|
217
|
+
.bounds=${{ x: 50, y: 50, width: 200, height: 150 }}
|
|
218
|
+
min-size="50"
|
|
219
|
+
>
|
|
220
|
+
Cannot resize below 50x50
|
|
221
|
+
</ef-resizable-box>
|
|
222
|
+
```
|
|
223
|
+
<!-- /html-only -->
|
|
224
|
+
<!-- react-only -->
|
|
225
|
+
```tsx
|
|
226
|
+
import { ResizableBox } from "@editframe/react";
|
|
227
|
+
|
|
228
|
+
export const ConstrainedBox = () => {
|
|
229
|
+
const [bounds, setBounds] = useState({
|
|
230
|
+
x: 150,
|
|
231
|
+
y: 150,
|
|
232
|
+
width: 200,
|
|
233
|
+
height: 200
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<ResizableBox
|
|
238
|
+
bounds={bounds}
|
|
239
|
+
minSize={50} // Cannot resize smaller than 50x50
|
|
240
|
+
onBoundsChange={(e) => setBounds(e.detail)}
|
|
241
|
+
/>
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
<!-- /react-only -->
|
|
246
|
+
|
|
247
|
+
## Container Constraints
|
|
248
|
+
|
|
249
|
+
Resizable box constrains to its parent container:
|
|
250
|
+
|
|
251
|
+
<!-- html-only -->
|
|
252
|
+
```html
|
|
253
|
+
<div class="relative w-[400px] h-[300px] border">
|
|
254
|
+
<ef-resizable-box
|
|
255
|
+
.bounds=${{ x: 20, y: 20, width: 100, height: 80 }}
|
|
256
|
+
>
|
|
257
|
+
Cannot move or resize outside parent
|
|
258
|
+
</ef-resizable-box>
|
|
259
|
+
</div>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Box cannot be moved or resized beyond container boundaries.
|
|
263
|
+
<!-- /html-only -->
|
|
264
|
+
<!-- react-only -->
|
|
265
|
+
Box automatically constrains to parent boundaries. It cannot be moved or resized beyond its positioned parent container.
|
|
266
|
+
<!-- /react-only -->
|
|
267
|
+
|
|
268
|
+
<!-- html-only -->
|
|
269
|
+
## Visual Feedback
|
|
270
|
+
|
|
271
|
+
Box shows visual feedback during interaction:
|
|
272
|
+
|
|
273
|
+
```html live
|
|
274
|
+
<div class="relative w-[600px] h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-50">
|
|
275
|
+
<ef-resizable-box
|
|
276
|
+
.bounds=${{ x: 50, y: 50, width: 180, height: 120 }}
|
|
277
|
+
>
|
|
278
|
+
<div class="flex items-center justify-center">
|
|
279
|
+
Interactive Box
|
|
280
|
+
</div>
|
|
281
|
+
</ef-resizable-box>
|
|
282
|
+
</div>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Border and background change during drag operations.
|
|
286
|
+
|
|
287
|
+
## Programmatic Control
|
|
288
|
+
|
|
289
|
+
Update bounds programmatically:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const box = document.querySelector('ef-resizable-box');
|
|
293
|
+
|
|
294
|
+
// Animate size
|
|
295
|
+
let size = 100;
|
|
296
|
+
const interval = setInterval(() => {
|
|
297
|
+
size += 10;
|
|
298
|
+
if (size > 300) size = 100;
|
|
299
|
+
|
|
300
|
+
box.bounds = {
|
|
301
|
+
x: 50,
|
|
302
|
+
y: 50,
|
|
303
|
+
width: size,
|
|
304
|
+
height: size * 0.75
|
|
305
|
+
};
|
|
306
|
+
}, 100);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Slotted Content
|
|
310
|
+
|
|
311
|
+
Add any content inside the box:
|
|
312
|
+
|
|
313
|
+
```html
|
|
314
|
+
<ef-resizable-box .bounds=${{ x: 50, y: 50, width: 200, height: 150 }}>
|
|
315
|
+
<img src="image.jpg" class="w-full h-full object-cover">
|
|
316
|
+
</ef-resizable-box>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Content fills the box dimensions automatically.
|
|
320
|
+
<!-- /html-only -->
|
|
321
|
+
|
|
322
|
+
<!-- react-only -->
|
|
323
|
+
## Crop Region Selector
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { ResizableBox } from "@editframe/react";
|
|
327
|
+
|
|
328
|
+
export const CropSelector = () => {
|
|
329
|
+
const [cropBounds, setCropBounds] = useState({
|
|
330
|
+
x: 50,
|
|
331
|
+
y: 50,
|
|
332
|
+
width: 400,
|
|
333
|
+
height: 300
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const handleCrop = () => {
|
|
337
|
+
console.log("Crop region:", cropBounds);
|
|
338
|
+
// Apply crop to video/image
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<div className="relative">
|
|
343
|
+
<img
|
|
344
|
+
src="/assets/image.jpg"
|
|
345
|
+
alt="Source"
|
|
346
|
+
className="w-full h-auto"
|
|
347
|
+
/>
|
|
348
|
+
|
|
349
|
+
<div className="absolute inset-0">
|
|
350
|
+
<ResizableBox
|
|
351
|
+
bounds={cropBounds}
|
|
352
|
+
onBoundsChange={(e) => setCropBounds(e.detail)}
|
|
353
|
+
/>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<button
|
|
357
|
+
onClick={handleCrop}
|
|
358
|
+
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
|
|
359
|
+
>
|
|
360
|
+
Apply Crop
|
|
361
|
+
</button>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Region Markers
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import { ResizableBox } from "@editframe/react";
|
|
371
|
+
|
|
372
|
+
interface Region {
|
|
373
|
+
id: string;
|
|
374
|
+
bounds: BoxBounds;
|
|
375
|
+
color: string;
|
|
376
|
+
label: string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export const RegionMarkers = () => {
|
|
380
|
+
const [regions, setRegions] = useState<Region[]>([
|
|
381
|
+
{ id: "1", bounds: { x: 100, y: 100, width: 200, height: 150 }, color: "blue", label: "Face" },
|
|
382
|
+
{ id: "2", bounds: { x: 400, y: 200, width: 150, height: 150 }, color: "red", label: "Object" }
|
|
383
|
+
]);
|
|
384
|
+
const [selectedId, setSelectedId] = useState<string>("1");
|
|
385
|
+
|
|
386
|
+
const updateRegion = (id: string, newBounds: BoxBounds) => {
|
|
387
|
+
setRegions(regions.map(r =>
|
|
388
|
+
r.id === id ? { ...r, bounds: newBounds } : r
|
|
389
|
+
));
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const selectedRegion = regions.find(r => r.id === selectedId);
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<div className="relative w-full h-screen">
|
|
396
|
+
<img
|
|
397
|
+
src="/assets/photo.jpg"
|
|
398
|
+
alt="Annotated"
|
|
399
|
+
className="w-full h-full object-contain"
|
|
400
|
+
/>
|
|
401
|
+
|
|
402
|
+
{/* Show all regions */}
|
|
403
|
+
{regions.map((region) => (
|
|
404
|
+
<div
|
|
405
|
+
key={region.id}
|
|
406
|
+
className={`absolute border-2 border-${region.color}-500`}
|
|
407
|
+
style={{
|
|
408
|
+
left: region.bounds.x,
|
|
409
|
+
top: region.bounds.y,
|
|
410
|
+
width: region.bounds.width,
|
|
411
|
+
height: region.bounds.height,
|
|
412
|
+
opacity: selectedId === region.id ? 1 : 0.5
|
|
413
|
+
}}
|
|
414
|
+
onClick={() => setSelectedId(region.id)}
|
|
415
|
+
>
|
|
416
|
+
<span className={`bg-${region.color}-500 text-white px-2 py-1 text-xs`}>
|
|
417
|
+
{region.label}
|
|
418
|
+
</span>
|
|
419
|
+
</div>
|
|
420
|
+
))}
|
|
421
|
+
|
|
422
|
+
{/* Editable selected region */}
|
|
423
|
+
{selectedRegion && (
|
|
424
|
+
<div
|
|
425
|
+
className="absolute"
|
|
426
|
+
style={{
|
|
427
|
+
left: selectedRegion.bounds.x,
|
|
428
|
+
top: selectedRegion.bounds.y
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
431
|
+
<ResizableBox
|
|
432
|
+
bounds={selectedRegion.bounds}
|
|
433
|
+
onBoundsChange={(e) => updateRegion(selectedId, e.detail)}
|
|
434
|
+
/>
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
</div>
|
|
438
|
+
);
|
|
439
|
+
};
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Bounding Box Editor
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
import { ResizableBox } from "@editframe/react";
|
|
446
|
+
|
|
447
|
+
export const BoundingBoxEditor = () => {
|
|
448
|
+
const [boxes, setBoxes] = useState([
|
|
449
|
+
{ x: 50, y: 50, width: 150, height: 150 },
|
|
450
|
+
{ x: 250, y: 100, width: 200, height: 100 }
|
|
451
|
+
]);
|
|
452
|
+
|
|
453
|
+
const updateBox = (index: number, newBounds: BoxBounds) => {
|
|
454
|
+
setBoxes(boxes.map((box, i) => i === index ? newBounds : box));
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const addBox = () => {
|
|
458
|
+
setBoxes([
|
|
459
|
+
...boxes,
|
|
460
|
+
{ x: 100 + boxes.length * 50, y: 100, width: 150, height: 150 }
|
|
461
|
+
]);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<div className="relative w-full h-screen bg-gray-100">
|
|
466
|
+
{boxes.map((box, index) => (
|
|
467
|
+
<ResizableBox
|
|
468
|
+
key={index}
|
|
469
|
+
bounds={box}
|
|
470
|
+
onBoundsChange={(e) => updateBox(index, e.detail)}
|
|
471
|
+
/>
|
|
472
|
+
))}
|
|
473
|
+
|
|
474
|
+
<button
|
|
475
|
+
onClick={addBox}
|
|
476
|
+
className="absolute top-4 right-4 px-4 py-2 bg-blue-500 text-white rounded"
|
|
477
|
+
>
|
|
478
|
+
Add Box
|
|
479
|
+
</button>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Selection Rectangle
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
import { ResizableBox } from "@editframe/react";
|
|
489
|
+
|
|
490
|
+
export const SelectionTool = () => {
|
|
491
|
+
const [selection, setSelection] = useState<BoxBounds | null>(null);
|
|
492
|
+
const [isSelecting, setIsSelecting] = useState(false);
|
|
493
|
+
|
|
494
|
+
const startSelection = (e: React.MouseEvent) => {
|
|
495
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
496
|
+
const x = e.clientX - rect.left;
|
|
497
|
+
const y = e.clientY - rect.top;
|
|
498
|
+
|
|
499
|
+
setSelection({ x, y, width: 0, height: 0 });
|
|
500
|
+
setIsSelecting(true);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<div
|
|
505
|
+
className="relative w-full h-screen bg-gray-100"
|
|
506
|
+
onMouseDown={startSelection}
|
|
507
|
+
>
|
|
508
|
+
{selection && (
|
|
509
|
+
<ResizableBox
|
|
510
|
+
bounds={selection}
|
|
511
|
+
onBoundsChange={(e) => setSelection(e.detail)}
|
|
512
|
+
/>
|
|
513
|
+
)}
|
|
514
|
+
</div>
|
|
515
|
+
);
|
|
516
|
+
};
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## With Bounds Display
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
import { ResizableBox } from "@editframe/react";
|
|
523
|
+
|
|
524
|
+
export const BoundsDisplay = () => {
|
|
525
|
+
const [bounds, setBounds] = useState({
|
|
526
|
+
x: 100,
|
|
527
|
+
y: 100,
|
|
528
|
+
width: 300,
|
|
529
|
+
height: 200
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
return (
|
|
533
|
+
<div className="relative w-full h-screen bg-gray-100">
|
|
534
|
+
<ResizableBox
|
|
535
|
+
bounds={bounds}
|
|
536
|
+
onBoundsChange={(e) => setBounds(e.detail)}
|
|
537
|
+
/>
|
|
538
|
+
|
|
539
|
+
<div className="absolute top-4 left-4 bg-white p-4 rounded shadow font-mono text-sm">
|
|
540
|
+
<div>x: {bounds.x.toFixed(0)}</div>
|
|
541
|
+
<div>y: {bounds.y.toFixed(0)}</div>
|
|
542
|
+
<div>width: {bounds.width.toFixed(0)}</div>
|
|
543
|
+
<div>height: {bounds.height.toFixed(0)}</div>
|
|
544
|
+
<div>area: {(bounds.width * bounds.height).toFixed(0)}px2</div>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
);
|
|
548
|
+
};
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Aspect Ratio Calculator
|
|
552
|
+
|
|
553
|
+
```tsx
|
|
554
|
+
import { ResizableBox } from "@editframe/react";
|
|
555
|
+
|
|
556
|
+
export const AspectRatioTool = () => {
|
|
557
|
+
const [bounds, setBounds] = useState({
|
|
558
|
+
x: 100,
|
|
559
|
+
y: 100,
|
|
560
|
+
width: 640,
|
|
561
|
+
height: 360
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const aspectRatio = bounds.width / bounds.height;
|
|
565
|
+
const commonRatios = [
|
|
566
|
+
{ name: "16:9", value: 16/9 },
|
|
567
|
+
{ name: "4:3", value: 4/3 },
|
|
568
|
+
{ name: "1:1", value: 1 }
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
const closestRatio = commonRatios.reduce((prev, curr) =>
|
|
572
|
+
Math.abs(curr.value - aspectRatio) < Math.abs(prev.value - aspectRatio)
|
|
573
|
+
? curr : prev
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
return (
|
|
577
|
+
<div className="relative w-full h-screen bg-gray-100">
|
|
578
|
+
<ResizableBox
|
|
579
|
+
bounds={bounds}
|
|
580
|
+
onBoundsChange={(e) => setBounds(e.detail)}
|
|
581
|
+
/>
|
|
582
|
+
|
|
583
|
+
<div className="absolute top-4 left-4 bg-white p-4 rounded shadow">
|
|
584
|
+
<div className="font-semibold mb-2">Aspect Ratio</div>
|
|
585
|
+
<div className="text-2xl font-mono">{aspectRatio.toFixed(3)}</div>
|
|
586
|
+
<div className="text-sm text-gray-600 mt-1">
|
|
587
|
+
Closest: {closestRatio.name}
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
};
|
|
593
|
+
```
|
|
594
|
+
<!-- /react-only -->
|
|
595
|
+
|
|
596
|
+
## Cursor Styles
|
|
597
|
+
|
|
598
|
+
Handles show appropriate resize cursors:
|
|
599
|
+
|
|
600
|
+
- **nw, se**: `nwse-resize` (diagonal)
|
|
601
|
+
- **ne, sw**: `nesw-resize` (diagonal)
|
|
602
|
+
- **n, s**: `ns-resize` (vertical)
|
|
603
|
+
- **e, w**: `ew-resize` (horizontal)
|
|
604
|
+
- **content**: `grab` / `grabbing` (move)
|
|
605
|
+
|
|
606
|
+
## Styling
|
|
607
|
+
|
|
608
|
+
Resizable box uses CSS custom properties:
|
|
609
|
+
|
|
610
|
+
```css
|
|
611
|
+
ef-resizable-box {
|
|
612
|
+
/* Border color */
|
|
613
|
+
--ef-resizable-box-border-color: #2196f3;
|
|
614
|
+
|
|
615
|
+
/* Background color */
|
|
616
|
+
--ef-resizable-box-bg-color: rgba(33, 150, 243, 0.2);
|
|
617
|
+
|
|
618
|
+
/* Border during drag */
|
|
619
|
+
--ef-resizable-box-dragging-border-color: #1976d2;
|
|
620
|
+
|
|
621
|
+
/* Background during drag */
|
|
622
|
+
--ef-resizable-box-dragging-bg-color: rgba(33, 150, 243, 0.3);
|
|
623
|
+
|
|
624
|
+
/* Handle color */
|
|
625
|
+
--ef-resizable-box-handle-color: #2196f3;
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
<!-- html-only -->
|
|
630
|
+
## ResizeObserver
|
|
631
|
+
|
|
632
|
+
Resizable box observes its parent container for size changes:
|
|
633
|
+
|
|
634
|
+
```javascript
|
|
635
|
+
// Box automatically updates constraints when container resizes
|
|
636
|
+
const container = box.offsetParent;
|
|
637
|
+
|
|
638
|
+
// Container dimensions tracked via ResizeObserver
|
|
639
|
+
```
|
|
640
|
+
<!-- /html-only -->
|
|
641
|
+
|
|
642
|
+
## Comparison with Transform Handles
|
|
643
|
+
|
|
644
|
+
<!-- html-only -->
|
|
645
|
+
Use **ef-resizable-box** when:
|
|
646
|
+
<!-- /html-only -->
|
|
647
|
+
<!-- react-only -->
|
|
648
|
+
Use **ResizableBox** when:
|
|
649
|
+
<!-- /react-only -->
|
|
650
|
+
|
|
651
|
+
- You need a self-contained resizable container
|
|
652
|
+
- Box is the primary content (not an overlay)
|
|
653
|
+
- Container constraints are important
|
|
654
|
+
- Rotation is not needed
|
|
655
|
+
|
|
656
|
+
<!-- html-only -->
|
|
657
|
+
Use **ef-transform-handles** when:
|
|
658
|
+
<!-- /html-only -->
|
|
659
|
+
<!-- react-only -->
|
|
660
|
+
Use **TransformHandles** when:
|
|
661
|
+
<!-- /react-only -->
|
|
662
|
+
|
|
663
|
+
- You need to transform existing elements
|
|
664
|
+
- Overlay-style interaction is needed
|
|
665
|
+
- Rotation is required
|
|
666
|
+
- Multi-element selection is needed
|
|
667
|
+
<!-- html-only -->
|
|
668
|
+
- Integration with ef-canvas is needed
|
|
669
|
+
<!-- /html-only -->
|
|
670
|
+
<!-- react-only -->
|
|
671
|
+
- Integration with Canvas is needed
|
|
672
|
+
<!-- /react-only -->
|
|
673
|
+
|
|
674
|
+
## Pointer Events
|
|
675
|
+
|
|
676
|
+
Resizable box and its handles have `pointer-events: auto` and capture pointer events during interaction.
|
|
677
|
+
|
|
678
|
+
## Touch Support
|
|
679
|
+
|
|
680
|
+
Handles use `touch-action: none` for proper touch device support.
|
|
681
|
+
|
|
682
|
+
## Aspect Ratio
|
|
683
|
+
|
|
684
|
+
<!-- html-only -->
|
|
685
|
+
Resizable box does not lock aspect ratio by default. To maintain aspect ratio, handle the `bounds-change` event:
|
|
686
|
+
|
|
687
|
+
```javascript
|
|
688
|
+
const box = document.querySelector('ef-resizable-box');
|
|
689
|
+
const aspectRatio = box.bounds.width / box.bounds.height;
|
|
690
|
+
|
|
691
|
+
box.addEventListener('bounds-change', (e) => {
|
|
692
|
+
const { bounds } = e.detail;
|
|
693
|
+
|
|
694
|
+
// Maintain aspect ratio
|
|
695
|
+
const newHeight = bounds.width / aspectRatio;
|
|
696
|
+
box.bounds = {
|
|
697
|
+
...bounds,
|
|
698
|
+
height: newHeight
|
|
699
|
+
};
|
|
700
|
+
});
|
|
701
|
+
```
|
|
702
|
+
<!-- /html-only -->
|
|
703
|
+
<!-- react-only -->
|
|
704
|
+
ResizableBox does not lock aspect ratio by default. To maintain aspect ratio, handle the `onBoundsChange` callback and adjust the bounds accordingly.
|
|
705
|
+
<!-- /react-only -->
|
|
706
|
+
|
|
707
|
+
## Handle Features
|
|
708
|
+
|
|
709
|
+
- **8 resize handles**: nw, n, ne, e, se, s, sw, w (corners and edges)
|
|
710
|
+
- **Drag to move**: Click and drag the box interior to reposition
|
|
711
|
+
- **Visual feedback**: Handles show appropriate cursors on hover
|
|
712
|
+
- **Constrained sizing**: Respects `minSize` constraint
|
|
713
|
+
- **Boundary clipping**: Automatically constrained to parent container
|
|
714
|
+
|
|
715
|
+
<!-- react-only -->
|
|
716
|
+
## Event Details
|
|
717
|
+
|
|
718
|
+
The `onBoundsChange` event provides:
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
interface BoxBounds {
|
|
722
|
+
x: number; // Left position
|
|
723
|
+
y: number; // Top position
|
|
724
|
+
width: number; // Box width
|
|
725
|
+
height: number; // Box height
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
## Differences from TransformHandles
|
|
730
|
+
|
|
731
|
+
- **No rotation**: ResizableBox does not support rotation
|
|
732
|
+
- **Simpler API**: Only x, y, width, height (no rotation property)
|
|
733
|
+
- **Self-contained**: Doesn't require OverlayLayer/OverlayItem wrapper
|
|
734
|
+
- **Positioned in parent**: Uses parent's coordinate space directly
|
|
735
|
+
<!-- /react-only -->
|
|
736
|
+
|
|
737
|
+
## Important Notes
|
|
738
|
+
|
|
739
|
+
- Position is relative to parent container
|
|
740
|
+
- Must have a positioned parent (relative, absolute, or fixed)
|
|
741
|
+
- Automatically constrained to parent boundaries
|
|
742
|
+
- All handles are always visible (no corners-only mode)
|
|
743
|
+
- Use for simple resize/drag operations without rotation
|
|
744
|
+
<!-- html-only -->
|
|
745
|
+
- For more advanced transforms, use ef-transform-handles
|
|
746
|
+
<!-- /html-only -->
|
|
747
|
+
<!-- react-only -->
|
|
748
|
+
- For more advanced transforms, use TransformHandles component
|
|
749
|
+
<!-- /react-only -->
|