@editframe/create 0.44.0 → 0.45.1
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,756 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dial Element
|
|
3
|
+
description: Rotary dial input for angle values, supporting mouse and touch circular drag gestures with configurable min, max, and step.
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Transform & Manipulation"
|
|
7
|
+
priority: 12
|
|
8
|
+
api:
|
|
9
|
+
attributes:
|
|
10
|
+
- name: value
|
|
11
|
+
type: number
|
|
12
|
+
default: 0
|
|
13
|
+
description: Current angle value in degrees (0-360)
|
|
14
|
+
events:
|
|
15
|
+
- name: change
|
|
16
|
+
detail: "DialChangeDetail"
|
|
17
|
+
description: Fired when value changes during drag
|
|
18
|
+
types:
|
|
19
|
+
- name: DialChangeDetail
|
|
20
|
+
type: interface
|
|
21
|
+
definition: |
|
|
22
|
+
interface DialChangeDetail {
|
|
23
|
+
value: number; // Current angle in degrees (0-360)
|
|
24
|
+
}
|
|
25
|
+
react:
|
|
26
|
+
generate: true
|
|
27
|
+
componentName: Dial
|
|
28
|
+
importPath: "@editframe/react"
|
|
29
|
+
additionalProps:
|
|
30
|
+
- name: className
|
|
31
|
+
type: string
|
|
32
|
+
description: CSS classes for styling
|
|
33
|
+
- name: onChange
|
|
34
|
+
type: "(event) => void"
|
|
35
|
+
description: Called when value changes via interaction
|
|
36
|
+
nav:
|
|
37
|
+
parent: "Components / Controls"
|
|
38
|
+
priority: 54
|
|
39
|
+
related: ["transform-handles"]
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<!-- html-only -->
|
|
43
|
+
# ef-dial
|
|
44
|
+
<!-- /html-only -->
|
|
45
|
+
<!-- react-only -->
|
|
46
|
+
# Dial
|
|
47
|
+
<!-- /react-only -->
|
|
48
|
+
|
|
49
|
+
Rotary control for angle input with circular interaction.
|
|
50
|
+
|
|
51
|
+
<!-- react-only -->
|
|
52
|
+
## Import
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { Dial } from "@editframe/react";
|
|
56
|
+
```
|
|
57
|
+
<!-- /react-only -->
|
|
58
|
+
|
|
59
|
+
## Basic Usage
|
|
60
|
+
|
|
61
|
+
<!-- html-only -->
|
|
62
|
+
Display a rotary dial:
|
|
63
|
+
|
|
64
|
+
```html live
|
|
65
|
+
<ef-dial value="45" class="w-48 h-48"></ef-dial>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Drag the handle to rotate. Value displays in the center.
|
|
69
|
+
<!-- /html-only -->
|
|
70
|
+
<!-- react-only -->
|
|
71
|
+
```tsx
|
|
72
|
+
import { Dial } from "@editframe/react";
|
|
73
|
+
|
|
74
|
+
export const App = () => {
|
|
75
|
+
const [value, setValue] = useState(0);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="flex items-center gap-4">
|
|
79
|
+
<Dial
|
|
80
|
+
value={value}
|
|
81
|
+
onChange={(e) => setValue(e.detail.value)}
|
|
82
|
+
className="w-32 h-32"
|
|
83
|
+
/>
|
|
84
|
+
<div className="font-mono text-xl">{value.toFixed(1)}°</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
<!-- /react-only -->
|
|
90
|
+
|
|
91
|
+
## Value
|
|
92
|
+
|
|
93
|
+
Dial value is in degrees (0-360):
|
|
94
|
+
|
|
95
|
+
<!-- html-only -->
|
|
96
|
+
```javascript
|
|
97
|
+
const dial = document.querySelector('ef-dial');
|
|
98
|
+
|
|
99
|
+
// Set value
|
|
100
|
+
dial.value = 90;
|
|
101
|
+
|
|
102
|
+
// Get value
|
|
103
|
+
console.log(dial.value); // 0-360
|
|
104
|
+
|
|
105
|
+
// Values are normalized to 0-360
|
|
106
|
+
dial.value = 380; // Becomes 20
|
|
107
|
+
dial.value = -30; // Becomes 330
|
|
108
|
+
```
|
|
109
|
+
<!-- /html-only -->
|
|
110
|
+
<!-- react-only -->
|
|
111
|
+
- The dial value is always normalized to 0-360 degrees
|
|
112
|
+
- Values wrap around (361° becomes 1°, -1° becomes 359°)
|
|
113
|
+
- Precision is limited to 6 significant digits
|
|
114
|
+
- Continuous rotation is supported (no stops)
|
|
115
|
+
<!-- /react-only -->
|
|
116
|
+
|
|
117
|
+
## Events
|
|
118
|
+
|
|
119
|
+
<!-- html-only -->
|
|
120
|
+
Listen for value changes:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const dial = document.querySelector('ef-dial');
|
|
124
|
+
|
|
125
|
+
dial.addEventListener('change', (e) => {
|
|
126
|
+
const { value } = e.detail;
|
|
127
|
+
console.log('New angle:', value);
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Events fire during drag operations.
|
|
132
|
+
<!-- /html-only -->
|
|
133
|
+
<!-- react-only -->
|
|
134
|
+
The `onChange` event provides:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
interface DialChangeDetail {
|
|
138
|
+
value: number; // Dial value in degrees (0-360)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
<!-- /react-only -->
|
|
142
|
+
|
|
143
|
+
## Precision
|
|
144
|
+
|
|
145
|
+
Values are limited to 6 significant digits:
|
|
146
|
+
|
|
147
|
+
<!-- html-only -->
|
|
148
|
+
```javascript
|
|
149
|
+
dial.value = 45.123456789;
|
|
150
|
+
console.log(dial.value); // 45.1235 (6 significant digits)
|
|
151
|
+
```
|
|
152
|
+
<!-- /html-only -->
|
|
153
|
+
|
|
154
|
+
## Snap to Increment
|
|
155
|
+
|
|
156
|
+
Hold **Shift** while dragging to snap to 15° increments:
|
|
157
|
+
|
|
158
|
+
<!-- html-only -->
|
|
159
|
+
```html live
|
|
160
|
+
<ef-dial value="0" class="w-48 h-48"></ef-dial>
|
|
161
|
+
<p class="text-sm text-gray-600 mt-2">Hold Shift to snap to 15° increments</p>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Without Shift: smooth rotation. With Shift: 0°, 15°, 30°, 45°, 60°, etc.
|
|
165
|
+
<!-- /html-only -->
|
|
166
|
+
|
|
167
|
+
## Sizing
|
|
168
|
+
|
|
169
|
+
Dial scales to its container:
|
|
170
|
+
|
|
171
|
+
<!-- html-only -->
|
|
172
|
+
```html
|
|
173
|
+
<!-- Small dial -->
|
|
174
|
+
<ef-dial value="45" class="w-24 h-24"></ef-dial>
|
|
175
|
+
|
|
176
|
+
<!-- Medium dial -->
|
|
177
|
+
<ef-dial value="45" class="w-48 h-48"></ef-dial>
|
|
178
|
+
|
|
179
|
+
<!-- Large dial -->
|
|
180
|
+
<ef-dial value="45" class="w-64 h-64"></ef-dial>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Default size is 200×200 pixels.
|
|
184
|
+
<!-- /html-only -->
|
|
185
|
+
<!-- react-only -->
|
|
186
|
+
Size controlled via `className` (width and height). Default size is 200x200px. Always square - maintains 1:1 aspect ratio.
|
|
187
|
+
<!-- /react-only -->
|
|
188
|
+
|
|
189
|
+
## Visual Elements
|
|
190
|
+
|
|
191
|
+
Dial displays:
|
|
192
|
+
|
|
193
|
+
### Handle
|
|
194
|
+
|
|
195
|
+
Blue dot indicating current angle position.
|
|
196
|
+
|
|
197
|
+
### Center Text
|
|
198
|
+
|
|
199
|
+
Current angle value in degrees.
|
|
200
|
+
|
|
201
|
+
### Circle Guide
|
|
202
|
+
|
|
203
|
+
Dashed circle showing rotation path.
|
|
204
|
+
|
|
205
|
+
### Tick Marks
|
|
206
|
+
|
|
207
|
+
Four marks at cardinal directions (0°, 90°, 180°, 270°).
|
|
208
|
+
|
|
209
|
+
## Usage Examples
|
|
210
|
+
|
|
211
|
+
### Rotation Control
|
|
212
|
+
|
|
213
|
+
<!-- html-only -->
|
|
214
|
+
```html live
|
|
215
|
+
<div class="flex gap-8 items-center">
|
|
216
|
+
<ef-dial value="30" class="w-48 h-48" id="rotation-dial"></ef-dial>
|
|
217
|
+
|
|
218
|
+
<div class="relative w-32 h-32">
|
|
219
|
+
<div id="rotation-target" class="w-full h-full bg-blue-500 rounded-lg shadow-lg" style="transform: rotate(30deg)">
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<script>
|
|
225
|
+
const dial = document.getElementById('rotation-dial');
|
|
226
|
+
const target = document.getElementById('rotation-target');
|
|
227
|
+
|
|
228
|
+
dial.addEventListener('change', (e) => {
|
|
229
|
+
target.style.transform = `rotate(${e.detail.value}deg)`;
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
```
|
|
233
|
+
<!-- /html-only -->
|
|
234
|
+
<!-- react-only -->
|
|
235
|
+
```tsx
|
|
236
|
+
import { Dial } from "@editframe/react";
|
|
237
|
+
|
|
238
|
+
export const RotationControl = () => {
|
|
239
|
+
const [rotation, setRotation] = useState(0);
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div className="space-y-4">
|
|
243
|
+
<div className="flex items-center gap-4">
|
|
244
|
+
<label className="font-semibold">Rotation:</label>
|
|
245
|
+
<Dial
|
|
246
|
+
value={rotation}
|
|
247
|
+
onChange={(e) => setRotation(e.detail.value)}
|
|
248
|
+
className="w-24 h-24"
|
|
249
|
+
/>
|
|
250
|
+
<span className="font-mono">{rotation.toFixed(0)}°</span>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div
|
|
254
|
+
className="w-32 h-32 bg-blue-500 mx-auto"
|
|
255
|
+
style={{ transform: `rotate(${rotation}deg)` }}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
```
|
|
261
|
+
<!-- /react-only -->
|
|
262
|
+
|
|
263
|
+
### Volume Knob
|
|
264
|
+
|
|
265
|
+
<!-- html-only -->
|
|
266
|
+
```javascript
|
|
267
|
+
const volumeDial = document.querySelector('#volume-dial');
|
|
268
|
+
const audioElement = document.querySelector('audio');
|
|
269
|
+
|
|
270
|
+
volumeDial.addEventListener('change', (e) => {
|
|
271
|
+
// Map 0-360 degrees to 0-1 volume
|
|
272
|
+
audioElement.volume = e.detail.value / 360;
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
<!-- /html-only -->
|
|
276
|
+
<!-- react-only -->
|
|
277
|
+
```tsx
|
|
278
|
+
import { Dial } from "@editframe/react";
|
|
279
|
+
|
|
280
|
+
export const VolumeControl = () => {
|
|
281
|
+
const [angle, setAngle] = useState(270); // 0-360
|
|
282
|
+
const volume = angle / 360; // Convert to 0-1
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div className="flex flex-col items-center gap-2">
|
|
286
|
+
<Dial
|
|
287
|
+
value={angle}
|
|
288
|
+
onChange={(e) => setAngle(e.detail.value)}
|
|
289
|
+
className="w-40 h-40"
|
|
290
|
+
/>
|
|
291
|
+
<div className="text-center">
|
|
292
|
+
<div className="text-sm text-gray-600">Volume</div>
|
|
293
|
+
<div className="text-2xl font-bold">{Math.round(volume * 100)}%</div>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
```
|
|
299
|
+
<!-- /react-only -->
|
|
300
|
+
|
|
301
|
+
### Hue Selector
|
|
302
|
+
|
|
303
|
+
<!-- html-only -->
|
|
304
|
+
```javascript
|
|
305
|
+
const hueDial = document.querySelector('#hue-dial');
|
|
306
|
+
const colorPreview = document.querySelector('#color-preview');
|
|
307
|
+
|
|
308
|
+
hueDial.addEventListener('change', (e) => {
|
|
309
|
+
const hue = e.detail.value;
|
|
310
|
+
colorPreview.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
<!-- /html-only -->
|
|
314
|
+
<!-- react-only -->
|
|
315
|
+
```tsx
|
|
316
|
+
import { Dial } from "@editframe/react";
|
|
317
|
+
|
|
318
|
+
export const HueSelector = () => {
|
|
319
|
+
const [hue, setHue] = useState(0);
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<div className="space-y-4">
|
|
323
|
+
<Dial
|
|
324
|
+
value={hue}
|
|
325
|
+
onChange={(e) => setHue(e.detail.value)}
|
|
326
|
+
className="w-48 h-48"
|
|
327
|
+
/>
|
|
328
|
+
|
|
329
|
+
<div
|
|
330
|
+
className="w-full h-24 rounded"
|
|
331
|
+
style={{ backgroundColor: `hsl(${hue}, 100%, 50%)` }}
|
|
332
|
+
/>
|
|
333
|
+
|
|
334
|
+
<div className="text-center font-mono">
|
|
335
|
+
hsl({hue.toFixed(0)}°, 100%, 50%)
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
};
|
|
340
|
+
```
|
|
341
|
+
<!-- /react-only -->
|
|
342
|
+
|
|
343
|
+
## Programmatic Animation
|
|
344
|
+
|
|
345
|
+
<!-- html-only -->
|
|
346
|
+
Animate the dial value:
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
const dial = document.querySelector('ef-dial');
|
|
350
|
+
|
|
351
|
+
let angle = 0;
|
|
352
|
+
setInterval(() => {
|
|
353
|
+
angle = (angle + 1) % 360;
|
|
354
|
+
dial.value = angle;
|
|
355
|
+
}, 16);
|
|
356
|
+
```
|
|
357
|
+
<!-- /html-only -->
|
|
358
|
+
<!-- react-only -->
|
|
359
|
+
```tsx
|
|
360
|
+
import { Dial } from "@editframe/react";
|
|
361
|
+
|
|
362
|
+
export const AnimatedDial = () => {
|
|
363
|
+
const [value, setValue] = useState(0);
|
|
364
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
365
|
+
|
|
366
|
+
const animate = () => {
|
|
367
|
+
setIsAnimating(true);
|
|
368
|
+
let current = 0;
|
|
369
|
+
|
|
370
|
+
const interval = setInterval(() => {
|
|
371
|
+
current += 5;
|
|
372
|
+
setValue(current % 360);
|
|
373
|
+
|
|
374
|
+
if (current >= 360) {
|
|
375
|
+
clearInterval(interval);
|
|
376
|
+
setIsAnimating(false);
|
|
377
|
+
}
|
|
378
|
+
}, 16);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<div className="flex flex-col items-center gap-4">
|
|
383
|
+
<Dial
|
|
384
|
+
value={value}
|
|
385
|
+
onChange={(e) => setValue(e.detail.value)}
|
|
386
|
+
className="w-40 h-40"
|
|
387
|
+
/>
|
|
388
|
+
|
|
389
|
+
<button
|
|
390
|
+
onClick={animate}
|
|
391
|
+
disabled={isAnimating}
|
|
392
|
+
className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
|
|
393
|
+
>
|
|
394
|
+
{isAnimating ? "Animating..." : "Spin"}
|
|
395
|
+
</button>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
};
|
|
399
|
+
```
|
|
400
|
+
<!-- /react-only -->
|
|
401
|
+
|
|
402
|
+
## Interaction
|
|
403
|
+
|
|
404
|
+
Dial uses pointer capture for smooth dragging:
|
|
405
|
+
|
|
406
|
+
- **Pointer down**: Capture pointer, start drag
|
|
407
|
+
- **Pointer move**: Update angle based on mouse position
|
|
408
|
+
- **Pointer up**: Release pointer, end drag
|
|
409
|
+
|
|
410
|
+
Drag works anywhere in the dial, not just on the handle.
|
|
411
|
+
|
|
412
|
+
## Angle Calculation
|
|
413
|
+
|
|
414
|
+
Dial calculates angle from pointer position relative to center:
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
// Internal calculation
|
|
418
|
+
const centerX = dialWidth / 2;
|
|
419
|
+
const centerY = dialHeight / 2;
|
|
420
|
+
const x = pointerX - centerX;
|
|
421
|
+
const y = pointerY - centerY;
|
|
422
|
+
const angle = Math.atan2(y, x) * 180 / Math.PI;
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
0° is at 3 o'clock, increases clockwise.
|
|
426
|
+
|
|
427
|
+
## Styling
|
|
428
|
+
|
|
429
|
+
<!-- html-only -->
|
|
430
|
+
Dial uses CSS custom properties:
|
|
431
|
+
|
|
432
|
+
```css
|
|
433
|
+
ef-dial {
|
|
434
|
+
/* Circle border color */
|
|
435
|
+
--dial-stroke: #d0d0d0;
|
|
436
|
+
|
|
437
|
+
/* Tick mark color */
|
|
438
|
+
--dial-tick: #e0e0e0;
|
|
439
|
+
|
|
440
|
+
/* Background color */
|
|
441
|
+
--ef-color-bg-panel: #f5f5f5;
|
|
442
|
+
|
|
443
|
+
/* Border color */
|
|
444
|
+
--ef-color-border: #d0d0d0;
|
|
445
|
+
|
|
446
|
+
/* Handle border color */
|
|
447
|
+
--ef-color-primary: #2196f3;
|
|
448
|
+
|
|
449
|
+
/* Handle background */
|
|
450
|
+
--ef-color-bg-elevated: #fff;
|
|
451
|
+
|
|
452
|
+
/* Text color */
|
|
453
|
+
--ef-color-text: #000;
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
<!-- /html-only -->
|
|
457
|
+
<!-- react-only -->
|
|
458
|
+
Use CSS variables to customize appearance:
|
|
459
|
+
|
|
460
|
+
```css
|
|
461
|
+
.dial {
|
|
462
|
+
--ef-color-bg-panel: #ffffff;
|
|
463
|
+
--ef-color-border: #e5e7eb;
|
|
464
|
+
--ef-color-primary: #3b82f6;
|
|
465
|
+
--ef-color-bg-elevated: #f9fafb;
|
|
466
|
+
--ef-color-text: #111827;
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
<!-- /react-only -->
|
|
470
|
+
|
|
471
|
+
## Accessibility
|
|
472
|
+
|
|
473
|
+
Dial captures pointer events for smooth interaction. Consider adding keyboard support for accessibility:
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
dial.addEventListener('keydown', (e) => {
|
|
477
|
+
if (e.key === 'ArrowRight') {
|
|
478
|
+
dial.value = (dial.value + 5) % 360;
|
|
479
|
+
} else if (e.key === 'ArrowLeft') {
|
|
480
|
+
dial.value = (dial.value - 5 + 360) % 360;
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Visual State
|
|
486
|
+
|
|
487
|
+
Handle shows visual feedback during interaction:
|
|
488
|
+
|
|
489
|
+
- **Normal**: White background with blue border
|
|
490
|
+
- **Dragging**: Blue background (solid)
|
|
491
|
+
- **Cursor**: `grab` when idle, `grabbing` when dragging
|
|
492
|
+
|
|
493
|
+
## Precision Handling
|
|
494
|
+
|
|
495
|
+
Dial normalizes values to prevent floating point issues:
|
|
496
|
+
|
|
497
|
+
<!-- html-only -->
|
|
498
|
+
```javascript
|
|
499
|
+
// Values are automatically normalized
|
|
500
|
+
dial.value = 359.99999999;
|
|
501
|
+
console.log(dial.value); // 0 (wraps around)
|
|
502
|
+
|
|
503
|
+
dial.value = 45.123456789123;
|
|
504
|
+
console.log(dial.value); // 45.1235 (6 sig figs)
|
|
505
|
+
```
|
|
506
|
+
<!-- /html-only -->
|
|
507
|
+
|
|
508
|
+
## Container Sizing
|
|
509
|
+
|
|
510
|
+
<!-- html-only -->
|
|
511
|
+
Dial fills its container and adapts to container dimensions:
|
|
512
|
+
|
|
513
|
+
```css
|
|
514
|
+
ef-dial {
|
|
515
|
+
width: 100%; /* Fill container width */
|
|
516
|
+
height: 100%; /* Fill container height */
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Dial uses `clientWidth` for sizing calculations, so it responds to container size changes.
|
|
521
|
+
<!-- /html-only -->
|
|
522
|
+
|
|
523
|
+
<!-- react-only -->
|
|
524
|
+
## Multiple Dials
|
|
525
|
+
|
|
526
|
+
```tsx
|
|
527
|
+
import { Dial } from "@editframe/react";
|
|
528
|
+
|
|
529
|
+
export const ColorPicker = () => {
|
|
530
|
+
const [hue, setHue] = useState(0);
|
|
531
|
+
const [saturation, setSaturation] = useState(180); // 0-360 maps to 0-100%
|
|
532
|
+
const [lightness, setLightness] = useState(180);
|
|
533
|
+
|
|
534
|
+
const sat = (saturation / 360) * 100;
|
|
535
|
+
const light = (lightness / 360) * 100;
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<div className="grid grid-cols-3 gap-4">
|
|
539
|
+
<div className="flex flex-col items-center gap-2">
|
|
540
|
+
<Dial
|
|
541
|
+
value={hue}
|
|
542
|
+
onChange={(e) => setHue(e.detail.value)}
|
|
543
|
+
className="w-32 h-32"
|
|
544
|
+
/>
|
|
545
|
+
<div className="text-sm">Hue: {hue.toFixed(0)}°</div>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
<div className="flex flex-col items-center gap-2">
|
|
549
|
+
<Dial
|
|
550
|
+
value={saturation}
|
|
551
|
+
onChange={(e) => setSaturation(e.detail.value)}
|
|
552
|
+
className="w-32 h-32"
|
|
553
|
+
/>
|
|
554
|
+
<div className="text-sm">Sat: {sat.toFixed(0)}%</div>
|
|
555
|
+
</div>
|
|
556
|
+
|
|
557
|
+
<div className="flex flex-col items-center gap-2">
|
|
558
|
+
<Dial
|
|
559
|
+
value={lightness}
|
|
560
|
+
onChange={(e) => setLightness(e.detail.value)}
|
|
561
|
+
className="w-32 h-32"
|
|
562
|
+
/>
|
|
563
|
+
<div className="text-sm">Light: {light.toFixed(0)}%</div>
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<div
|
|
567
|
+
className="col-span-3 h-24 rounded"
|
|
568
|
+
style={{ backgroundColor: `hsl(${hue}, ${sat}%, ${light}%)` }}
|
|
569
|
+
/>
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
};
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
## Angle Input
|
|
576
|
+
|
|
577
|
+
```tsx
|
|
578
|
+
import { Dial } from "@editframe/react";
|
|
579
|
+
|
|
580
|
+
export const AngleInput = () => {
|
|
581
|
+
const [angle, setAngle] = useState(45);
|
|
582
|
+
|
|
583
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
584
|
+
const value = parseFloat(e.target.value) || 0;
|
|
585
|
+
setAngle(value % 360);
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<div className="flex items-center gap-4">
|
|
590
|
+
<Dial
|
|
591
|
+
value={angle}
|
|
592
|
+
onChange={(e) => setAngle(e.detail.value)}
|
|
593
|
+
className="w-32 h-32"
|
|
594
|
+
/>
|
|
595
|
+
|
|
596
|
+
<input
|
|
597
|
+
type="number"
|
|
598
|
+
value={angle.toFixed(1)}
|
|
599
|
+
onChange={handleInputChange}
|
|
600
|
+
className="w-24 px-3 py-2 border rounded"
|
|
601
|
+
min="0"
|
|
602
|
+
max="360"
|
|
603
|
+
step="0.1"
|
|
604
|
+
/>
|
|
605
|
+
|
|
606
|
+
<span>degrees</span>
|
|
607
|
+
</div>
|
|
608
|
+
);
|
|
609
|
+
};
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
## Compass
|
|
613
|
+
|
|
614
|
+
```tsx
|
|
615
|
+
import { Dial } from "@editframe/react";
|
|
616
|
+
|
|
617
|
+
export const Compass = () => {
|
|
618
|
+
const [bearing, setBearing] = useState(0);
|
|
619
|
+
|
|
620
|
+
const directions = [
|
|
621
|
+
{ angle: 0, label: "N" },
|
|
622
|
+
{ angle: 45, label: "NE" },
|
|
623
|
+
{ angle: 90, label: "E" },
|
|
624
|
+
{ angle: 135, label: "SE" },
|
|
625
|
+
{ angle: 180, label: "S" },
|
|
626
|
+
{ angle: 225, label: "SW" },
|
|
627
|
+
{ angle: 270, label: "W" },
|
|
628
|
+
{ angle: 315, label: "NW" }
|
|
629
|
+
];
|
|
630
|
+
|
|
631
|
+
const nearest = directions.reduce((prev, curr) =>
|
|
632
|
+
Math.abs(curr.angle - bearing) < Math.abs(prev.angle - bearing)
|
|
633
|
+
? curr : prev
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
return (
|
|
637
|
+
<div className="flex flex-col items-center gap-4">
|
|
638
|
+
<Dial
|
|
639
|
+
value={bearing}
|
|
640
|
+
onChange={(e) => setBearing(e.detail.value)}
|
|
641
|
+
className="w-48 h-48"
|
|
642
|
+
/>
|
|
643
|
+
|
|
644
|
+
<div className="text-center">
|
|
645
|
+
<div className="text-4xl font-bold">{nearest.label}</div>
|
|
646
|
+
<div className="text-sm text-gray-600">{bearing.toFixed(1)}°</div>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
);
|
|
650
|
+
};
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## Preset Values
|
|
654
|
+
|
|
655
|
+
```tsx
|
|
656
|
+
import { Dial } from "@editframe/react";
|
|
657
|
+
|
|
658
|
+
export const PresetDial = () => {
|
|
659
|
+
const [value, setValue] = useState(0);
|
|
660
|
+
|
|
661
|
+
const presets = [
|
|
662
|
+
{ angle: 0, label: "Off" },
|
|
663
|
+
{ angle: 90, label: "Low" },
|
|
664
|
+
{ angle: 180, label: "Medium" },
|
|
665
|
+
{ angle: 270, label: "High" },
|
|
666
|
+
{ angle: 360, label: "Max" }
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
return (
|
|
670
|
+
<div className="space-y-4">
|
|
671
|
+
<Dial
|
|
672
|
+
value={value}
|
|
673
|
+
onChange={(e) => setValue(e.detail.value)}
|
|
674
|
+
className="w-40 h-40 mx-auto"
|
|
675
|
+
/>
|
|
676
|
+
|
|
677
|
+
<div className="flex gap-2 justify-center">
|
|
678
|
+
{presets.map((preset) => (
|
|
679
|
+
<button
|
|
680
|
+
key={preset.angle}
|
|
681
|
+
onClick={() => setValue(preset.angle)}
|
|
682
|
+
className="px-3 py-2 bg-gray-200 rounded hover:bg-gray-300"
|
|
683
|
+
>
|
|
684
|
+
{preset.label}
|
|
685
|
+
</button>
|
|
686
|
+
))}
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
);
|
|
690
|
+
};
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
## Sensitivity Control
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
import { Dial } from "@editframe/react";
|
|
697
|
+
|
|
698
|
+
export const SensitiveControl = () => {
|
|
699
|
+
const [value, setValue] = useState(0);
|
|
700
|
+
const [sensitivity, setSensitivity] = useState(1);
|
|
701
|
+
|
|
702
|
+
const handleChange = (e: CustomEvent<DialChangeDetail>) => {
|
|
703
|
+
const delta = e.detail.value - value;
|
|
704
|
+
const adjustedDelta = delta * sensitivity;
|
|
705
|
+
setValue((value + adjustedDelta) % 360);
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
return (
|
|
709
|
+
<div className="space-y-4">
|
|
710
|
+
<Dial
|
|
711
|
+
value={value}
|
|
712
|
+
onChange={handleChange}
|
|
713
|
+
className="w-40 h-40"
|
|
714
|
+
/>
|
|
715
|
+
|
|
716
|
+
<div className="flex items-center gap-2">
|
|
717
|
+
<label>Sensitivity:</label>
|
|
718
|
+
<input
|
|
719
|
+
type="range"
|
|
720
|
+
min="0.1"
|
|
721
|
+
max="2"
|
|
722
|
+
step="0.1"
|
|
723
|
+
value={sensitivity}
|
|
724
|
+
onChange={(e) => setSensitivity(parseFloat(e.target.value))}
|
|
725
|
+
className="flex-1"
|
|
726
|
+
/>
|
|
727
|
+
<span>{sensitivity.toFixed(1)}x</span>
|
|
728
|
+
</div>
|
|
729
|
+
</div>
|
|
730
|
+
);
|
|
731
|
+
};
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
## CSS Customization
|
|
735
|
+
|
|
736
|
+
Use CSS variables to customize appearance:
|
|
737
|
+
|
|
738
|
+
```css
|
|
739
|
+
.dial {
|
|
740
|
+
--ef-color-bg-panel: #ffffff;
|
|
741
|
+
--ef-color-border: #e5e7eb;
|
|
742
|
+
--ef-color-primary: #3b82f6;
|
|
743
|
+
--ef-color-bg-elevated: #f9fafb;
|
|
744
|
+
--ef-color-text: #111827;
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
## Important Notes
|
|
749
|
+
|
|
750
|
+
- Size controlled via `className` (width and height)
|
|
751
|
+
- Default size is 200x200px
|
|
752
|
+
- Always square - maintains 1:1 aspect ratio
|
|
753
|
+
- Value automatically normalizes to 0-360 range
|
|
754
|
+
- Great for rotation, hue, volume, and angle inputs
|
|
755
|
+
- Handle positioned at edge shows current value visually
|
|
756
|
+
<!-- /react-only -->
|