@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.
Files changed (99) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +16 -28
  3. package/dist/index.js.map +1 -1
  4. package/dist/skills/editframe-brand-video-generator/README.md +155 -0
  5. package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
  6. package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
  7. package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
  8. package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
  9. package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
  10. package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
  11. package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
  12. package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
  13. package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
  14. package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
  15. package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
  16. package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
  17. package/dist/skills/editframe-composition/SKILL.md +169 -0
  18. package/dist/skills/editframe-composition/references/audio.md +483 -0
  19. package/dist/skills/editframe-composition/references/captions.md +844 -0
  20. package/dist/skills/editframe-composition/references/composition-model.md +73 -0
  21. package/dist/skills/editframe-composition/references/configuration.md +403 -0
  22. package/dist/skills/editframe-composition/references/css-parts.md +105 -0
  23. package/dist/skills/editframe-composition/references/css-variables.md +640 -0
  24. package/dist/skills/editframe-composition/references/entry-points.md +810 -0
  25. package/dist/skills/editframe-composition/references/events.md +499 -0
  26. package/dist/skills/editframe-composition/references/getting-started.md +259 -0
  27. package/dist/skills/editframe-composition/references/hooks.md +234 -0
  28. package/dist/skills/editframe-composition/references/image.md +241 -0
  29. package/dist/skills/editframe-composition/references/r3f.md +580 -0
  30. package/dist/skills/editframe-composition/references/render-api.md +484 -0
  31. package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
  32. package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
  33. package/dist/skills/editframe-composition/references/scripting.md +606 -0
  34. package/dist/skills/editframe-composition/references/sequencing.md +116 -0
  35. package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
  36. package/dist/skills/editframe-composition/references/surface.md +329 -0
  37. package/dist/skills/editframe-composition/references/text.md +627 -0
  38. package/dist/skills/editframe-composition/references/time-model.md +99 -0
  39. package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
  40. package/dist/skills/editframe-composition/references/timegroup.md +457 -0
  41. package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
  42. package/dist/skills/editframe-composition/references/transcription.md +47 -0
  43. package/dist/skills/editframe-composition/references/transitions.md +608 -0
  44. package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
  45. package/dist/skills/editframe-composition/references/video.md +506 -0
  46. package/dist/skills/editframe-composition/references/waveform.md +327 -0
  47. package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
  48. package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
  49. package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
  50. package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
  51. package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
  52. package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
  53. package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
  54. package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
  55. package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
  56. package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
  57. package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
  58. package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
  59. package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
  60. package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
  61. package/dist/skills/editframe-editor-gui/references/play.md +370 -0
  62. package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
  63. package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
  64. package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
  65. package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
  66. package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
  67. package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
  68. package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
  69. package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
  70. package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
  71. package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
  72. package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
  73. package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
  74. package/dist/skills/editframe-motion-design/SKILL.md +101 -0
  75. package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
  76. package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
  77. package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
  78. package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
  79. package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
  80. package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
  81. package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
  82. package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
  83. package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
  84. package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
  85. package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
  86. package/dist/skills/editframe-webhooks/SKILL.md +126 -0
  87. package/dist/skills/editframe-webhooks/references/events.md +382 -0
  88. package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
  89. package/dist/skills/editframe-webhooks/references/security.md +418 -0
  90. package/dist/skills/editframe-webhooks/references/testing.md +409 -0
  91. package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
  92. package/dist/templates/html/AGENTS.md +13 -0
  93. package/dist/templates/react/AGENTS.md +13 -0
  94. package/dist/utils.js +15 -16
  95. package/dist/utils.js.map +1 -1
  96. package/package.json +2 -2
  97. package/tsdown.config.ts +4 -0
  98. package/dist/detectAgent.js +0 -89
  99. 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 -->