Skills/Editor Toolkit/Dial Element

ef-dial

Attributes

value
number

Current angle value in degrees (0-360)

Default:0

Rotary control for angle input with circular interaction.

Basic Usage

Display a rotary dial:

<ef-dial value="45" class="w-48 h-48"></ef-dial>

Drag the handle to rotate. Value displays in the center.

Value

Dial value is in degrees (0-360):

const dial = document.querySelector('ef-dial');
// Set value
dial.value = 90;
// Get value
console.log(dial.value); // 0-360
// Values are normalized to 0-360
dial.value = 380; // Becomes 20
dial.value = -30; // Becomes 330

Events

Listen for value changes:

const dial = document.querySelector('ef-dial');
dial.addEventListener('change', (e) => {
const { value } = e.detail;
console.log('New angle:', value);
});

Events fire during drag operations.

Precision

Values are limited to 6 significant digits:

dial.value = 45.123456789;
console.log(dial.value); // 45.1235 (6 significant digits)

Snap to Increment

Hold Shift while dragging to snap to 15° increments:

<ef-dial value="0" class="w-48 h-48"></ef-dial>
<p class="text-sm text-gray-600 mt-2">Hold Shift to snap to 15° increments</p>

Without Shift: smooth rotation. With Shift: 0°, 15°, 30°, 45°, 60°, etc.

Sizing

Dial scales to its container:

<!-- Small dial -->
<ef-dial value="45" class="w-24 h-24"></ef-dial>
<!-- Medium dial -->
<ef-dial value="45" class="w-48 h-48"></ef-dial>
<!-- Large dial -->
<ef-dial value="45" class="w-64 h-64"></ef-dial>

Default size is 200×200 pixels.

Visual Elements

Dial displays:

Handle

Blue dot indicating current angle position.

Center Text

Current angle value in degrees.

Circle Guide

Dashed circle showing rotation path.

Tick Marks

Four marks at cardinal directions (0°, 90°, 180°, 270°).

Usage Examples

Rotation Control

<div class="flex gap-8 items-center">
<ef-dial value="30" class="w-48 h-48" id="rotation-dial"></ef-dial>
<div class="relative w-32 h-32">
<div id="rotation-target" class="w-full h-full bg-blue-500 rounded-lg shadow-lg" style="transform: rotate(30deg)">
</div>
</div>
</div>
<script>
const dial = document.getElementById('rotation-dial');
const target = document.getElementById('rotation-target');
dial.addEventListener('change', (e) => {
target.style.transform = `rotate(${e.detail.value}deg)`;
});
</script>

Volume Knob

const volumeDial = document.querySelector('#volume-dial');
const audioElement = document.querySelector('audio');
volumeDial.addEventListener('change', (e) => {
// Map 0-360 degrees to 0-1 volume
audioElement.volume = e.detail.value / 360;
});

Hue Selector

const hueDial = document.querySelector('#hue-dial');
const colorPreview = document.querySelector('#color-preview');
hueDial.addEventListener('change', (e) => {
const hue = e.detail.value;
colorPreview.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
});

Programmatic Animation

Animate the dial value:

const dial = document.querySelector('ef-dial');
let angle = 0;
setInterval(() => {
angle = (angle + 1) % 360;
dial.value = angle;
}, 16);

Interaction

Dial uses pointer capture for smooth dragging:

  • Pointer down: Capture pointer, start drag
  • Pointer move: Update angle based on mouse position
  • Pointer up: Release pointer, end drag

Drag works anywhere in the dial, not just on the handle.

Angle Calculation

Dial calculates angle from pointer position relative to center:

// Internal calculation
const centerX = dialWidth / 2;
const centerY = dialHeight / 2;
const x = pointerX - centerX;
const y = pointerY - centerY;
const angle = Math.atan2(y, x) * 180 / Math.PI;

0° is at 3 o'clock, increases clockwise.

Styling

Dial uses CSS custom properties:

ef-dial {
/* Circle border color */
--dial-stroke: #d0d0d0;
/* Tick mark color */
--dial-tick: #e0e0e0;
/* Background color */
--ef-color-bg-panel: #f5f5f5;
/* Border color */
--ef-color-border: #d0d0d0;
/* Handle border color */
--ef-color-primary: #2196f3;
/* Handle background */
--ef-color-bg-elevated: #fff;
/* Text color */
--ef-color-text: #000;
}

Accessibility

Dial captures pointer events for smooth interaction. Consider adding keyboard support for accessibility:

dial.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') {
dial.value = (dial.value + 5) % 360;
} else if (e.key === 'ArrowLeft') {
dial.value = (dial.value - 5 + 360) % 360;
}
});

Visual State

Handle shows visual feedback during interaction:

  • Normal: White background with blue border
  • Dragging: Blue background (solid)
  • Cursor: grab when idle, grabbing when dragging

Precision Handling

Dial normalizes values to prevent floating point issues:

// Values are automatically normalized
dial.value = 359.99999999;
console.log(dial.value); // 0 (wraps around)
dial.value = 45.123456789123;
console.log(dial.value); // 45.1235 (6 sig figs)

Container Sizing

Dial fills its container and adapts to container dimensions:

ef-dial {
width: 100%; /* Fill container width */
height: 100%; /* Fill container height */
}

Dial uses clientWidth for sizing calculations, so it responds to container size changes.