@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,772 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fit Scale Element
|
|
3
|
+
description: Responsive wrapper that scales its content uniformly to fill available space while preserving the original aspect ratio.
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Preview & Canvas"
|
|
7
|
+
priority: 21
|
|
8
|
+
api:
|
|
9
|
+
attributes:
|
|
10
|
+
- name: paused
|
|
11
|
+
type: boolean
|
|
12
|
+
default: false
|
|
13
|
+
description: Pause scale calculations (useful during animations)
|
|
14
|
+
properties:
|
|
15
|
+
- name: contentChild
|
|
16
|
+
type: "HTMLElement | null"
|
|
17
|
+
description: First content element (excludes style, script, display:none)
|
|
18
|
+
- name: scaleInfo
|
|
19
|
+
type: "{ scale: number, containerWidth: number, containerHeight: number, contentWidth: number, contentHeight: number }"
|
|
20
|
+
description: Current scale calculation result
|
|
21
|
+
functions:
|
|
22
|
+
- name: computeFitScale(input)
|
|
23
|
+
signature: "computeFitScale(input: ScaleInput): ScaleOutput | null"
|
|
24
|
+
description: Pure function to compute scale and centering for given dimensions
|
|
25
|
+
returns: ScaleOutput | null
|
|
26
|
+
types:
|
|
27
|
+
- name: ScaleInput
|
|
28
|
+
type: interface
|
|
29
|
+
definition: |
|
|
30
|
+
interface ScaleInput {
|
|
31
|
+
containerWidth: number; // Container dimensions
|
|
32
|
+
containerHeight: number;
|
|
33
|
+
contentWidth: number; // Content natural dimensions
|
|
34
|
+
contentHeight: number;
|
|
35
|
+
}
|
|
36
|
+
- name: ScaleOutput
|
|
37
|
+
type: interface
|
|
38
|
+
definition: |
|
|
39
|
+
interface ScaleOutput {
|
|
40
|
+
scale: number; // Scale factor to apply
|
|
41
|
+
translateX: number; // X offset for centering
|
|
42
|
+
translateY: number; // Y offset for centering
|
|
43
|
+
}
|
|
44
|
+
react:
|
|
45
|
+
generate: true
|
|
46
|
+
componentName: FitScale
|
|
47
|
+
importPath: "@editframe/react"
|
|
48
|
+
additionalProps:
|
|
49
|
+
- name: children
|
|
50
|
+
type: ReactNode
|
|
51
|
+
required: true
|
|
52
|
+
description: Content to scale and fit
|
|
53
|
+
nav:
|
|
54
|
+
parent: "Components / Layout"
|
|
55
|
+
priority: 56
|
|
56
|
+
related: ["preview", "timegroup"]
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<!-- html-only -->
|
|
60
|
+
# ef-fit-scale
|
|
61
|
+
<!-- /html-only -->
|
|
62
|
+
<!-- react-only -->
|
|
63
|
+
# FitScale
|
|
64
|
+
<!-- /react-only -->
|
|
65
|
+
|
|
66
|
+
Responsive container that scales content to fit while preserving aspect ratio.
|
|
67
|
+
|
|
68
|
+
<!-- react-only -->
|
|
69
|
+
## Import
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { FitScale } from "@editframe/react";
|
|
73
|
+
import { computeFitScale, needsFitScale } from "@editframe/react";
|
|
74
|
+
```
|
|
75
|
+
<!-- /react-only -->
|
|
76
|
+
|
|
77
|
+
## Basic Usage
|
|
78
|
+
|
|
79
|
+
<!-- html-only -->
|
|
80
|
+
Scale content to fit container:
|
|
81
|
+
|
|
82
|
+
```html live
|
|
83
|
+
<div class="w-full h-[400px] border border-gray-300 rounded overflow-hidden bg-gray-100">
|
|
84
|
+
<ef-fit-scale class="w-full h-full">
|
|
85
|
+
<div class="w-[1920px] h-[1080px] bg-blue-500 text-white flex items-center justify-center text-4xl">
|
|
86
|
+
1920×1080 Content
|
|
87
|
+
</div>
|
|
88
|
+
</ef-fit-scale>
|
|
89
|
+
</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Content scales down to fit the container while maintaining its 16:9 aspect ratio.
|
|
93
|
+
<!-- /html-only -->
|
|
94
|
+
<!-- react-only -->
|
|
95
|
+
```tsx
|
|
96
|
+
import { FitScale, Timegroup, Video } from "@editframe/react";
|
|
97
|
+
|
|
98
|
+
export const App = () => {
|
|
99
|
+
return (
|
|
100
|
+
<div className="w-full h-screen bg-gray-900">
|
|
101
|
+
<FitScale>
|
|
102
|
+
<Timegroup mode="contain" className="w-[1920px] h-[1080px]">
|
|
103
|
+
<Video src="/assets/video.mp4" className="size-full" />
|
|
104
|
+
</Timegroup>
|
|
105
|
+
</FitScale>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
<!-- /react-only -->
|
|
111
|
+
|
|
112
|
+
## How It Works
|
|
113
|
+
|
|
114
|
+
Fit scale automatically:
|
|
115
|
+
|
|
116
|
+
1. Observes container size
|
|
117
|
+
2. Reads content natural dimensions
|
|
118
|
+
3. Calculates scale factor to fit
|
|
119
|
+
4. Centers content with translate
|
|
120
|
+
5. Applies transform to content
|
|
121
|
+
|
|
122
|
+
## Content Detection
|
|
123
|
+
|
|
124
|
+
Fit scale finds the first content element, ignoring:
|
|
125
|
+
|
|
126
|
+
- `<style>` tags
|
|
127
|
+
- `<script>` tags
|
|
128
|
+
- `<meta>` tags
|
|
129
|
+
- Elements with `display: none`
|
|
130
|
+
- Elements with `display: contents`
|
|
131
|
+
|
|
132
|
+
<!-- html-only -->
|
|
133
|
+
```html
|
|
134
|
+
<ef-fit-scale>
|
|
135
|
+
<style>/* Ignored */</style>
|
|
136
|
+
<script>/* Ignored */</script>
|
|
137
|
+
<div><!-- This is the content --></div>
|
|
138
|
+
</ef-fit-scale>
|
|
139
|
+
```
|
|
140
|
+
<!-- /html-only -->
|
|
141
|
+
|
|
142
|
+
<!-- html-only -->
|
|
143
|
+
## Natural Dimensions
|
|
144
|
+
|
|
145
|
+
For media elements (ef-video, ef-image), fit scale uses natural dimensions:
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<ef-fit-scale class="w-full h-[400px]">
|
|
149
|
+
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4"></ef-video>
|
|
150
|
+
</ef-fit-scale>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Video scales based on its native resolution, not CSS dimensions.
|
|
154
|
+
|
|
155
|
+
### getNaturalDimensions Method
|
|
156
|
+
|
|
157
|
+
Media elements can provide natural dimensions:
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
class MyMediaElement extends HTMLElement {
|
|
161
|
+
getNaturalDimensions() {
|
|
162
|
+
return { width: 1920, height: 1080 };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Fit scale calls this method if available.
|
|
168
|
+
<!-- /html-only -->
|
|
169
|
+
|
|
170
|
+
## Responsive Scaling
|
|
171
|
+
|
|
172
|
+
Fit scale updates automatically when:
|
|
173
|
+
|
|
174
|
+
- Container resizes
|
|
175
|
+
- Content resizes
|
|
176
|
+
- Content child changes
|
|
177
|
+
|
|
178
|
+
<!-- html-only -->
|
|
179
|
+
```html live
|
|
180
|
+
<div class="flex flex-col gap-4">
|
|
181
|
+
<div class="w-full h-[300px] border border-gray-300 rounded overflow-hidden bg-gray-100">
|
|
182
|
+
<ef-fit-scale class="w-full h-full">
|
|
183
|
+
<div class="w-[800px] h-[600px] bg-green-500 text-white flex items-center justify-center text-2xl">
|
|
184
|
+
800×600 (4:3)
|
|
185
|
+
</div>
|
|
186
|
+
</ef-fit-scale>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="w-full h-[300px] border border-gray-300 rounded overflow-hidden bg-gray-100">
|
|
190
|
+
<ef-fit-scale class="w-full h-full">
|
|
191
|
+
<div class="w-[1920px] h-[1080px] bg-blue-500 text-white flex items-center justify-center text-2xl">
|
|
192
|
+
1920×1080 (16:9)
|
|
193
|
+
</div>
|
|
194
|
+
</ef-fit-scale>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Different aspect ratios scale correctly.
|
|
200
|
+
<!-- /html-only -->
|
|
201
|
+
<!-- react-only -->
|
|
202
|
+
```tsx
|
|
203
|
+
import { FitScale, Preview, Timegroup, Video } from "@editframe/react";
|
|
204
|
+
|
|
205
|
+
export const ResponsivePreview = () => {
|
|
206
|
+
return (
|
|
207
|
+
<div className="w-full h-screen p-4">
|
|
208
|
+
<FitScale>
|
|
209
|
+
<div className="w-[1920px] h-[1080px] bg-black">
|
|
210
|
+
<Timegroup mode="contain" className="size-full">
|
|
211
|
+
<Video src="/assets/video.mp4" className="size-full object-cover" />
|
|
212
|
+
</Timegroup>
|
|
213
|
+
</div>
|
|
214
|
+
</FitScale>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
```
|
|
219
|
+
<!-- /react-only -->
|
|
220
|
+
|
|
221
|
+
<!-- react-only -->
|
|
222
|
+
## With Fixed Aspect Ratio
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
import { FitScale } from "@editframe/react";
|
|
226
|
+
|
|
227
|
+
export const AspectRatioBox = () => {
|
|
228
|
+
return (
|
|
229
|
+
<div className="w-full h-screen bg-gray-100 p-8">
|
|
230
|
+
<FitScale>
|
|
231
|
+
<div className="w-[1280px] h-[720px] bg-gradient-to-br from-blue-500 to-purple-600">
|
|
232
|
+
<div className="flex items-center justify-center h-full text-white text-4xl">
|
|
233
|
+
16:9 Content
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</FitScale>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Multiple Scaled Containers
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { FitScale, Timegroup, Video } from "@editframe/react";
|
|
246
|
+
|
|
247
|
+
export const MultipleScaled = () => {
|
|
248
|
+
return (
|
|
249
|
+
<div className="grid grid-cols-2 gap-4 p-4 h-screen">
|
|
250
|
+
{/* Left: 16:9 composition */}
|
|
251
|
+
<div className="bg-gray-900">
|
|
252
|
+
<FitScale>
|
|
253
|
+
<Timegroup mode="contain" className="w-[1920px] h-[1080px]">
|
|
254
|
+
<Video src="/assets/video1.mp4" className="size-full" />
|
|
255
|
+
</Timegroup>
|
|
256
|
+
</FitScale>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Right: 1:1 composition */}
|
|
260
|
+
<div className="bg-gray-900">
|
|
261
|
+
<FitScale>
|
|
262
|
+
<Timegroup mode="contain" className="w-[1080px] h-[1080px]">
|
|
263
|
+
<Video src="/assets/video2.mp4" className="size-full object-cover" />
|
|
264
|
+
</Timegroup>
|
|
265
|
+
</FitScale>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
```
|
|
271
|
+
<!-- /react-only -->
|
|
272
|
+
|
|
273
|
+
## Scale Calculation
|
|
274
|
+
|
|
275
|
+
Fit scale uses contain logic (like `object-fit: contain`):
|
|
276
|
+
|
|
277
|
+
<!-- html-only -->
|
|
278
|
+
```javascript
|
|
279
|
+
import { computeFitScale } from '@editframe/elements';
|
|
280
|
+
|
|
281
|
+
const result = computeFitScale({
|
|
282
|
+
containerWidth: 800,
|
|
283
|
+
containerHeight: 600,
|
|
284
|
+
contentWidth: 1920,
|
|
285
|
+
contentHeight: 1080
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// result = {
|
|
289
|
+
// scale: 0.4166, // Scale down to fit
|
|
290
|
+
// translateX: 0, // Centered horizontally
|
|
291
|
+
// translateY: 75 // Centered vertically
|
|
292
|
+
// }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Scale Formula
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
const containerRatio = containerWidth / containerHeight;
|
|
299
|
+
const contentRatio = contentWidth / contentHeight;
|
|
300
|
+
|
|
301
|
+
const scale = containerRatio > contentRatio
|
|
302
|
+
? containerHeight / contentHeight // Limited by height
|
|
303
|
+
: containerWidth / contentWidth; // Limited by width
|
|
304
|
+
```
|
|
305
|
+
<!-- /html-only -->
|
|
306
|
+
<!-- react-only -->
|
|
307
|
+
```tsx
|
|
308
|
+
import { computeFitScale } from "@editframe/react";
|
|
309
|
+
|
|
310
|
+
export const ManualScaleCalculation = () => {
|
|
311
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
312
|
+
const [scale, setScale] = useState<number>(1);
|
|
313
|
+
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
if (!containerRef.current) return;
|
|
316
|
+
|
|
317
|
+
const updateScale = () => {
|
|
318
|
+
const result = computeFitScale({
|
|
319
|
+
containerWidth: containerRef.current!.clientWidth,
|
|
320
|
+
containerHeight: containerRef.current!.clientHeight,
|
|
321
|
+
contentWidth: 1920,
|
|
322
|
+
contentHeight: 1080
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (result) {
|
|
326
|
+
setScale(result.scale);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
updateScale();
|
|
331
|
+
window.addEventListener("resize", updateScale);
|
|
332
|
+
return () => window.removeEventListener("resize", updateScale);
|
|
333
|
+
}, []);
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<div ref={containerRef} className="w-full h-screen bg-gray-100 p-4">
|
|
337
|
+
<div className="absolute top-4 left-4 bg-white p-2 rounded shadow">
|
|
338
|
+
Scale: {scale.toFixed(3)}x
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<div
|
|
342
|
+
className="w-[1920px] h-[1080px] bg-blue-500"
|
|
343
|
+
style={{ transform: `scale(${scale})` }}
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The `computeFitScale` function uses this algorithm:
|
|
351
|
+
|
|
352
|
+
1. Calculate container aspect ratio: `containerWidth / containerHeight`
|
|
353
|
+
2. Calculate content aspect ratio: `contentWidth / contentHeight`
|
|
354
|
+
3. Determine scale based on which dimension is limiting:
|
|
355
|
+
- If container is wider: scale by height
|
|
356
|
+
- If container is taller: scale by width
|
|
357
|
+
4. Calculate centering translations to position content in center
|
|
358
|
+
5. Return `null` if any dimension is zero or negative
|
|
359
|
+
<!-- /react-only -->
|
|
360
|
+
|
|
361
|
+
## Centering
|
|
362
|
+
|
|
363
|
+
Content is centered after scaling:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
const scaledWidth = contentWidth * scale;
|
|
367
|
+
const scaledHeight = contentHeight * scale;
|
|
368
|
+
const translateX = (containerWidth - scaledWidth) / 2;
|
|
369
|
+
const translateY = (containerHeight - scaledHeight) / 2;
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Applied Transform
|
|
373
|
+
|
|
374
|
+
Fit scale applies transform to content child:
|
|
375
|
+
|
|
376
|
+
```css
|
|
377
|
+
.content {
|
|
378
|
+
width: 1920px;
|
|
379
|
+
height: 1080px;
|
|
380
|
+
transform: translate(0px, 75px) scale(0.4166);
|
|
381
|
+
transform-origin: top left;
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Transform origin is top-left for correct positioning.
|
|
386
|
+
|
|
387
|
+
## Pause Calculations
|
|
388
|
+
|
|
389
|
+
Pause scale updates during animations:
|
|
390
|
+
|
|
391
|
+
<!-- html-only -->
|
|
392
|
+
```html
|
|
393
|
+
<ef-fit-scale paused>
|
|
394
|
+
<!-- Scale calculations paused -->
|
|
395
|
+
</ef-fit-scale>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```javascript
|
|
399
|
+
const fitScale = document.querySelector('ef-fit-scale');
|
|
400
|
+
|
|
401
|
+
// Pause
|
|
402
|
+
fitScale.paused = true;
|
|
403
|
+
|
|
404
|
+
// Resume
|
|
405
|
+
fitScale.paused = false; // Recalculates immediately
|
|
406
|
+
```
|
|
407
|
+
<!-- /html-only -->
|
|
408
|
+
<!-- react-only -->
|
|
409
|
+
```tsx
|
|
410
|
+
import { FitScale } from "@editframe/react";
|
|
411
|
+
|
|
412
|
+
export const PausedScale = () => {
|
|
413
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
414
|
+
|
|
415
|
+
return (
|
|
416
|
+
<div className="space-y-4">
|
|
417
|
+
<div className="flex items-center gap-2">
|
|
418
|
+
<input
|
|
419
|
+
type="checkbox"
|
|
420
|
+
checked={isPaused}
|
|
421
|
+
onChange={(e) => setIsPaused(e.target.checked)}
|
|
422
|
+
id="pause-scale"
|
|
423
|
+
/>
|
|
424
|
+
<label htmlFor="pause-scale">Pause scaling</label>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
<div className="w-full h-96 bg-gray-100">
|
|
428
|
+
<FitScale paused={isPaused}>
|
|
429
|
+
<div className="w-[800px] h-[600px] bg-blue-500 text-white flex items-center justify-center text-2xl">
|
|
430
|
+
Resize window to see scaling
|
|
431
|
+
</div>
|
|
432
|
+
</FitScale>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
);
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
<!-- /react-only -->
|
|
439
|
+
|
|
440
|
+
<!-- html-only -->
|
|
441
|
+
## Zero Dimension Warning
|
|
442
|
+
|
|
443
|
+
Fit scale warns when container has zero dimensions:
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
// Console warning if container is 0×0
|
|
447
|
+
// "Container has zero dimensions (0×0). Content will be invisible."
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Ensure all ancestors have resolved height:
|
|
451
|
+
|
|
452
|
+
```css
|
|
453
|
+
html, body {
|
|
454
|
+
height: 100%;
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Scale Info
|
|
459
|
+
|
|
460
|
+
Access current scale calculation:
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
const fitScale = document.querySelector('ef-fit-scale');
|
|
464
|
+
|
|
465
|
+
console.log(fitScale.scaleInfo);
|
|
466
|
+
// {
|
|
467
|
+
// scale: 0.4166,
|
|
468
|
+
// containerWidth: 800,
|
|
469
|
+
// containerHeight: 600,
|
|
470
|
+
// contentWidth: 1920,
|
|
471
|
+
// contentHeight: 1080
|
|
472
|
+
// }
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Content Child
|
|
476
|
+
|
|
477
|
+
Access the content element:
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
const fitScale = document.querySelector('ef-fit-scale');
|
|
481
|
+
const content = fitScale.contentChild;
|
|
482
|
+
|
|
483
|
+
console.log(content.tagName); // 'EF-VIDEO'
|
|
484
|
+
```
|
|
485
|
+
<!-- /html-only -->
|
|
486
|
+
|
|
487
|
+
## Observers
|
|
488
|
+
|
|
489
|
+
Fit scale uses three observers:
|
|
490
|
+
|
|
491
|
+
1. **Container ResizeObserver**: Tracks container size
|
|
492
|
+
2. **Content ResizeObserver**: Tracks content size
|
|
493
|
+
3. **MutationObserver**: Tracks child list changes
|
|
494
|
+
|
|
495
|
+
All observers clean up on disconnect.
|
|
496
|
+
|
|
497
|
+
<!-- html-only -->
|
|
498
|
+
## Video Integration
|
|
499
|
+
|
|
500
|
+
For ef-video elements, fit scale sets explicit canvas dimensions:
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
// Breaks circular dependency
|
|
504
|
+
canvas.style.width = `${naturalWidth}px`;
|
|
505
|
+
canvas.style.height = `${naturalHeight}px`;
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
This prevents video from collapsing to 0×0 when using `width: auto`.
|
|
509
|
+
<!-- /html-only -->
|
|
510
|
+
|
|
511
|
+
## Layout Properties
|
|
512
|
+
|
|
513
|
+
Fit scale applies container styles:
|
|
514
|
+
|
|
515
|
+
```css
|
|
516
|
+
ef-fit-scale {
|
|
517
|
+
display: grid;
|
|
518
|
+
width: 100%;
|
|
519
|
+
height: 100%;
|
|
520
|
+
grid-template-columns: 100%;
|
|
521
|
+
grid-template-rows: 100%;
|
|
522
|
+
overflow: hidden;
|
|
523
|
+
box-sizing: border-box;
|
|
524
|
+
contain: layout paint style;
|
|
525
|
+
position: relative;
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Grid layout simplifies content positioning.
|
|
530
|
+
|
|
531
|
+
## Performance
|
|
532
|
+
|
|
533
|
+
Scale calculations use:
|
|
534
|
+
|
|
535
|
+
- **Fixed precision**: Values rounded to 4 decimal places
|
|
536
|
+
- **Change detection**: Only updates when values change
|
|
537
|
+
- **RAF deferral**: Initial calculation deferred to allow layout
|
|
538
|
+
|
|
539
|
+
<!-- html-only -->
|
|
540
|
+
```javascript
|
|
541
|
+
// Internal precision handling
|
|
542
|
+
transform: `translate(${translateX.toFixed(4)}px, ${translateY.toFixed(4)}px) scale(${scale.toFixed(4)})`;
|
|
543
|
+
```
|
|
544
|
+
<!-- /html-only -->
|
|
545
|
+
|
|
546
|
+
## Cleanup
|
|
547
|
+
|
|
548
|
+
Fit scale removes transform when disconnected:
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
// On disconnect
|
|
552
|
+
content.style.width = '';
|
|
553
|
+
content.style.height = '';
|
|
554
|
+
content.style.transform = '';
|
|
555
|
+
content.style.transformOrigin = '';
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Content returns to normal layout.
|
|
559
|
+
|
|
560
|
+
## Comparison with object-fit
|
|
561
|
+
|
|
562
|
+
**ef-fit-scale**:
|
|
563
|
+
- Works with any element
|
|
564
|
+
- JavaScript-based scaling
|
|
565
|
+
- Provides scale info
|
|
566
|
+
- Can be paused
|
|
567
|
+
|
|
568
|
+
**CSS object-fit**:
|
|
569
|
+
- Only works with replaced elements (img, video)
|
|
570
|
+
- CSS-based scaling
|
|
571
|
+
- No JavaScript API
|
|
572
|
+
- Always active
|
|
573
|
+
|
|
574
|
+
## Nested Fit Scale
|
|
575
|
+
|
|
576
|
+
Multiple fit scales can nest:
|
|
577
|
+
|
|
578
|
+
<!-- html-only -->
|
|
579
|
+
```html
|
|
580
|
+
<ef-fit-scale class="w-full h-full">
|
|
581
|
+
<ef-timegroup class="w-[1920px] h-[1080px]">
|
|
582
|
+
<ef-fit-scale class="w-full h-full">
|
|
583
|
+
<ef-video src="video.mp4"></ef-video>
|
|
584
|
+
</ef-fit-scale>
|
|
585
|
+
</ef-timegroup>
|
|
586
|
+
</ef-fit-scale>
|
|
587
|
+
```
|
|
588
|
+
<!-- /html-only -->
|
|
589
|
+
<!-- react-only -->
|
|
590
|
+
```tsx
|
|
591
|
+
<FitScale>
|
|
592
|
+
<Timegroup className="w-[1920px] h-[1080px]">
|
|
593
|
+
<FitScale>
|
|
594
|
+
<Video src="/assets/video.mp4" />
|
|
595
|
+
</FitScale>
|
|
596
|
+
</Timegroup>
|
|
597
|
+
</FitScale>
|
|
598
|
+
```
|
|
599
|
+
<!-- /react-only -->
|
|
600
|
+
|
|
601
|
+
Each fit scale scales its own content independently.
|
|
602
|
+
|
|
603
|
+
<!-- react-only -->
|
|
604
|
+
## Editor Canvas
|
|
605
|
+
|
|
606
|
+
```tsx
|
|
607
|
+
import { FitScale, Timegroup, Video, Text } from "@editframe/react";
|
|
608
|
+
|
|
609
|
+
export const EditorCanvas = () => {
|
|
610
|
+
return (
|
|
611
|
+
<div className="h-screen flex flex-col bg-gray-900">
|
|
612
|
+
{/* Toolbar */}
|
|
613
|
+
<div className="bg-gray-800 p-4 border-b border-gray-700">
|
|
614
|
+
<h1 className="text-white text-xl">Video Editor</h1>
|
|
615
|
+
</div>
|
|
616
|
+
|
|
617
|
+
{/* Canvas area with fit scaling */}
|
|
618
|
+
<div className="flex-1 p-8">
|
|
619
|
+
<FitScale>
|
|
620
|
+
<Timegroup mode="fixed" duration="10s" className="w-[1920px] h-[1080px]">
|
|
621
|
+
<Video
|
|
622
|
+
src="/assets/background.mp4"
|
|
623
|
+
duration="10s"
|
|
624
|
+
className="size-full object-cover"
|
|
625
|
+
/>
|
|
626
|
+
<Text
|
|
627
|
+
duration="5s"
|
|
628
|
+
className="absolute top-20 left-20 text-white text-4xl"
|
|
629
|
+
>
|
|
630
|
+
Video Title
|
|
631
|
+
</Text>
|
|
632
|
+
</Timegroup>
|
|
633
|
+
</FitScale>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
);
|
|
637
|
+
};
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
## With Viewport Dimensions
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
import { FitScale } from "@editframe/react";
|
|
644
|
+
|
|
645
|
+
export const ViewportAware = () => {
|
|
646
|
+
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
|
647
|
+
|
|
648
|
+
useEffect(() => {
|
|
649
|
+
const updateDimensions = () => {
|
|
650
|
+
setDimensions({
|
|
651
|
+
width: window.innerWidth,
|
|
652
|
+
height: window.innerHeight
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
updateDimensions();
|
|
657
|
+
window.addEventListener("resize", updateDimensions);
|
|
658
|
+
return () => window.removeEventListener("resize", updateDimensions);
|
|
659
|
+
}, []);
|
|
660
|
+
|
|
661
|
+
return (
|
|
662
|
+
<div className="w-full h-screen bg-gray-900 p-4">
|
|
663
|
+
<div className="absolute top-4 right-4 bg-black/50 text-white p-2 text-sm font-mono z-10">
|
|
664
|
+
Viewport: {dimensions.width} x {dimensions.height}
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<FitScale>
|
|
668
|
+
<div className="w-[1920px] h-[1080px] bg-gradient-to-br from-purple-600 to-blue-600" />
|
|
669
|
+
</FitScale>
|
|
670
|
+
</div>
|
|
671
|
+
);
|
|
672
|
+
};
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
## Check if Element Needs Scaling
|
|
676
|
+
|
|
677
|
+
```tsx
|
|
678
|
+
import { needsFitScale } from "@editframe/react";
|
|
679
|
+
|
|
680
|
+
export const ConditionalScale = () => {
|
|
681
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
682
|
+
const [shouldScale, setShouldScale] = useState(false);
|
|
683
|
+
|
|
684
|
+
useEffect(() => {
|
|
685
|
+
if (contentRef.current) {
|
|
686
|
+
setShouldScale(needsFitScale(contentRef.current));
|
|
687
|
+
}
|
|
688
|
+
}, []);
|
|
689
|
+
|
|
690
|
+
return (
|
|
691
|
+
<div className="w-full h-screen bg-gray-100 p-4">
|
|
692
|
+
<div className="mb-4 p-2 bg-white rounded">
|
|
693
|
+
{shouldScale ? "Content needs scaling" : "Content fits naturally"}
|
|
694
|
+
</div>
|
|
695
|
+
|
|
696
|
+
{shouldScale ? (
|
|
697
|
+
<FitScale>
|
|
698
|
+
<div ref={contentRef} className="w-[1920px] h-[1080px] bg-blue-500" />
|
|
699
|
+
</FitScale>
|
|
700
|
+
) : (
|
|
701
|
+
<div ref={contentRef} className="w-full h-full bg-blue-500" />
|
|
702
|
+
)}
|
|
703
|
+
</div>
|
|
704
|
+
);
|
|
705
|
+
};
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## TypeScript Usage
|
|
709
|
+
|
|
710
|
+
```tsx
|
|
711
|
+
import { FitScale, computeFitScale } from "@editframe/react";
|
|
712
|
+
import type { ScaleInput, ScaleOutput } from "@editframe/react";
|
|
713
|
+
|
|
714
|
+
export const TypedScaleCalculation = () => {
|
|
715
|
+
const calculateScale = (input: ScaleInput): ScaleOutput | null => {
|
|
716
|
+
return computeFitScale(input);
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const result = calculateScale({
|
|
720
|
+
containerWidth: 1024,
|
|
721
|
+
containerHeight: 768,
|
|
722
|
+
contentWidth: 1920,
|
|
723
|
+
contentHeight: 1080
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
return (
|
|
727
|
+
<div className="p-4">
|
|
728
|
+
{result && (
|
|
729
|
+
<div className="space-y-2 font-mono text-sm">
|
|
730
|
+
<div>Scale: {result.scale.toFixed(3)}</div>
|
|
731
|
+
<div>Translate X: {result.translateX.toFixed(1)}px</div>
|
|
732
|
+
<div>Translate Y: {result.translateY.toFixed(1)}px</div>
|
|
733
|
+
</div>
|
|
734
|
+
)}
|
|
735
|
+
</div>
|
|
736
|
+
);
|
|
737
|
+
};
|
|
738
|
+
```
|
|
739
|
+
<!-- /react-only -->
|
|
740
|
+
|
|
741
|
+
## Behavior
|
|
742
|
+
|
|
743
|
+
- Automatically observes container and content size changes
|
|
744
|
+
- Recalculates scale on resize using ResizeObserver
|
|
745
|
+
- Centers content within container
|
|
746
|
+
- Maintains content aspect ratio
|
|
747
|
+
- Uses CSS transform for scaling (GPU-accelerated)
|
|
748
|
+
- Handles nested content elements
|
|
749
|
+
- Ignores non-visible elements (display: none, etc.)
|
|
750
|
+
|
|
751
|
+
## Important Notes
|
|
752
|
+
|
|
753
|
+
<!-- html-only -->
|
|
754
|
+
- FitScale is a light DOM component (no shadow DOM)
|
|
755
|
+
- Content should have explicit dimensions (width/height)
|
|
756
|
+
- Works with any content, not just Editframe elements
|
|
757
|
+
- Scale updates automatically on window resize
|
|
758
|
+
- Use `paused` attribute to temporarily stop recalculation
|
|
759
|
+
- Returns `null` from `computeFitScale` for invalid dimensions
|
|
760
|
+
- Uses `contain: layout paint style` for performance
|
|
761
|
+
- Content is centered both horizontally and vertically
|
|
762
|
+
<!-- /html-only -->
|
|
763
|
+
<!-- react-only -->
|
|
764
|
+
- FitScale is a light DOM component (no shadow DOM)
|
|
765
|
+
- Content should have explicit dimensions (width/height)
|
|
766
|
+
- Works with any content, not just Editframe elements
|
|
767
|
+
- Scale updates automatically on window resize
|
|
768
|
+
- Use `paused` prop to temporarily stop recalculation
|
|
769
|
+
- Returns `null` from `computeFitScale` for invalid dimensions
|
|
770
|
+
- Uses `contain: layout paint style` for performance
|
|
771
|
+
- Content is centered both horizontally and vertically
|
|
772
|
+
<!-- /react-only -->
|