Skip to content

Commit d33a762

Browse files
committed
feat: v0.1.0
- added a option to disable <Knob /> - made log range work with 0 and negative numbers
1 parent e7016f5 commit d33a762

File tree

6 files changed

+93
-21
lines changed

6 files changed

+93
-21
lines changed

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Set specific snap points and adjust snapping sensitivity using `snapThreshold`.
7070
import { createFloatParam, createRange } from 'svelte-knobs';
7171

7272
const snapParam = createFloatParam(createRange('lin', 0, 2000));
73-
const snapPoints = Array.from({ length: 5 }, (_, i) => i * 500);
73+
const snapPoints = [0, 500, 1000, 1500, 2000];
7474

7575
let snapValue = 0;
7676
```
@@ -101,6 +101,14 @@ let fruitValue: Variant<typeof fruitParam> = '🍉';
101101
<Knob param={fruitParam} bind:value={fruitValue} label="Fruit" />
102102
```
103103

104+
#### Disabled Knob
105+
106+
Disable knob interactivity
107+
108+
```svelte
109+
<Knob param={basicParam} value={58} disabled />
110+
```
111+
104112
## License
105113

106114
MIT License

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "svelte-knobs",
33
"description": "Svelte component library for building customizable knob controls.",
4-
"version": "0.0.3",
4+
"version": "0.1.0",
55
"repository": {
66
"url": "https://github.com/eye-wave/svelte-knobs"
77
},

src/lib/Knob.svelte

+14-6
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
export let decimalDigits = 0;
1515
export let snapValues: number[] = [];
1616
export let snapThreshold = 0.1;
17+
export let disabled = false;
1718
1819
export let arcColor = '#ae98db';
1920
export let bgColor = '#444';
21+
export let disabledColor = '#777';
22+
23+
$: arcColor2 = disabled ? disabledColor : arcColor;
2024
2125
$: if (param.type === 'enum-param') snapValues = [];
2226
@@ -62,6 +66,7 @@
6266
}
6367
6468
function handleMouseMove(event: MouseEvent) {
69+
if (disabled) return;
6570
if (!isDragging) return;
6671
const deltaY = startY - event.clientY;
6772
const deltaValue = deltaY / 200;
@@ -129,12 +134,14 @@
129134
}
130135
131136
function drawSnapMarkers() {
132-
if (snapValues.length === 0 || param.type !== 'float-param') return '';
137+
if (param.type !== 'enum-param' && snapValues.length === 0) return null;
133138
134139
let paths = [];
135140
136-
for (let snapValue of snapValues) {
137-
const normalizedSnapValue = normalize(snapValue, param);
141+
const values = param.type === 'enum-param' ? param.variants : snapValues;
142+
for (let snapValue of values) {
143+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
144+
const normalizedSnapValue = normalize(snapValue as any, param as any);
138145
const angle = normalizedSnapValue * 270 - 135;
139146
const [x1, y1] = polarToCartesian(center, center, arcRadius, angle);
140147
const [x2, y2] = polarToCartesian(center, center, size * 0.46, angle);
@@ -151,8 +158,9 @@
151158

152159
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
153160

154-
<div class="container">
161+
<div class="container" style={$$props.style}>
155162
<svg
163+
class={$$props.class}
156164
role="slider"
157165
tabindex="0"
158166
aria-valuenow={normalizedValue}
@@ -165,7 +173,7 @@
165173
on:mousedown={handleMouseDown}
166174
>
167175
<circle cx={center} cy={center} r={circleRadius} fill={bgColor}></circle>
168-
{#if snapValues.length > 0}
176+
{#if snapValues.length > 0 || param.type === 'enum-param'}
169177
<!-- Snap markers -->
170178
<path d={drawSnapMarkers()} stroke={bgColor} />
171179
{/if}
@@ -178,7 +186,7 @@
178186
<path
179187
d={describeArc(center, center, arcRadius, -135, $rotationDegrees)}
180188
fill="none"
181-
stroke={arcColor}
189+
stroke={arcColor2}
182190
/>
183191
<!-- Knob indicator -->
184192
<line

src/lib/params/float-param.ts

+27-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ export function normalize(value: number, param: FloatParam): number {
1818
return (value - range.min) / (range.max - range.min);
1919
case 'log': {
2020
const base = range.base ?? 10;
21-
const logFunc = base === 10 ? Math.log10 : base === 2 ? Math.log2 : Math.log;
22-
return (logFunc(value) - logFunc(range.min)) / (logFunc(range.max) - logFunc(range.min));
21+
const min = log(range.min, base);
22+
const max = log(range.max, base);
23+
24+
return (log(value, base) - min) / (max - min);
2325
}
2426
default:
2527
throw new Error('Unsupported range type');
@@ -33,12 +35,31 @@ export function unnormalize(value: number, param: FloatParam): number {
3335
return value * (range.max - range.min) + range.min;
3436
case 'log': {
3537
const base = range.base ?? 10;
36-
const logFunc = base === 10 ? Math.log10 : base === 2 ? Math.log2 : Math.log;
37-
const expFunc =
38-
base === 10 ? Math.pow.bind(null, 10) : base === 2 ? Math.pow.bind(null, 2) : Math.exp;
39-
return expFunc(value * (logFunc(range.max) - logFunc(range.min)) + logFunc(range.min));
38+
const min = log(range.min, base);
39+
const max = log(range.max, base);
40+
41+
return exp(value * (max - min) + min, base);
4042
}
4143
default:
4244
throw new Error('Unsupported range type');
4345
}
4446
}
47+
48+
function log(value: number, base = 10): number {
49+
if (value === 0) return 0;
50+
51+
const x = value < 0 ? -value : value;
52+
const sign = Math.sign(value);
53+
54+
if (base === 10) return sign * Math.log10(x);
55+
if (base === 2) return sign * Math.log2(x);
56+
if (base === Math.E) return sign * Math.log(x);
57+
58+
return sign * (Math.log(x) / Math.log(base));
59+
}
60+
61+
function exp(value: number, base = 10): number {
62+
const x = value < 0 ? -value : value;
63+
64+
return Math.pow(base, x) * Math.sign(value);
65+
}

src/lib/params/range.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type BaseRange = {
44
};
55

66
export type LinearRange = { type: 'lin' } & BaseRange;
7-
export type LogRange = { type: 'log'; base?: number } & BaseRange;
7+
export type LogRange = { type: 'log'; base: number } & BaseRange;
88

99
export type Range = LinearRange | LogRange;
1010
export type RangeType = Range['type'];
@@ -16,7 +16,7 @@ export function createRange(type: RangeType, min: number, max: number, a?: numbe
1616
case 'lin':
1717
return { type: 'lin', min, max };
1818
case 'log':
19-
return { type: 'log', min, max, base: a };
19+
return { type: 'log', min, max, base: a ?? 10 };
2020
default:
2121
throw TypeError(`RangeType: "${type}" does not exist.`);
2222
}

src/routes/+page.svelte

+40-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
} from '$lib/index.js';
1111
1212
const basicParam = createFloatParam(createRange('lin', 0, 100));
13-
const logParam = createFloatParam(createRange('log', 20, 20_000));
13+
const freqParam = createFloatParam(createRange('log', 20, 20_000));
14+
const gainParam = createFloatParam(createRange('log', -30, 30, Math.E));
1415
const smoothParam = createFloatParam(createRange('lin', 0, 100));
1516
const snapParam = createFloatParam(createRange('lin', 0, 2000));
1617
const enumParam = createEnumParam(['🍍', '🍉', '🍌', '🍎', '🥭', '🍇', '🥝', '🍋'] as const);
@@ -20,7 +21,8 @@
2021
const snapPoints = Array.from({ length: 5 }, (_, i) => i * 500);
2122
2223
let basicValue = 0;
23-
let logValue = 20;
24+
let freqValue = 20;
25+
let gainValue = 20;
2426
let smoothValue = 0;
2527
let snapValue = 0;
2628
let enumValue: Variant<typeof enumParam> = '🍎';
@@ -40,7 +42,8 @@
4042

4143
<div class="example">
4244
<h2>Logarithmic</h2>
43-
<Knob param={logParam} bind:value={logValue} label="Frequency" unit="hz" />
45+
<Knob param={freqParam} bind:value={freqValue} label="Frequency" unit="hz" />
46+
<Knob param={gainParam} bind:value={gainValue} label="Gain" unit="dB" />
4447

4548
<p>A knob with logarithmic scaling (default base is 10).</p>
4649
</div>
@@ -90,8 +93,9 @@
9093
<Knob param={oscParam} bind:value={oscValue} label="Oscillator Type" />
9194

9295
<p>
93-
An example of how <code>EnumParam</code> works. Due to the way TypeScript functions, we need
94-
to use <code>readonly string[]</code> instead of <code>Enum</code>s.
96+
An example of how <code>EnumParam</code> works: due to the way TypeScript functions, we need
97+
to use <code>readonly string[]</code> instead of <code>Enum</code>s. Since enums are a small
98+
subset of some strings, snap markers are added automatically for clarity.
9599
</p>
96100
<p>
97101
Remember to add <code>as const</code> when creating an <code>EnumParam</code> to improve editor
@@ -100,6 +104,37 @@
100104
<p>For instance:</p>
101105
<code>const fruitParam = createEnumParam(['🍍', '🍉', '🍌', '🥝', '🍋'] as const);</code>
102106
</div>
107+
108+
<div class="example">
109+
<h2>Disabled</h2>
110+
111+
<Knob param={basicParam} value={58} disabled />
112+
113+
<p>
114+
Just like <code>{'<button>'}</code> and other interactive HTML elements,
115+
<code>{'<Knob />'}</code> can also be disabled.
116+
</p>
117+
</div>
118+
119+
<div class="example">
120+
<h2>Colors</h2>
121+
122+
<Knob param={basicParam} value={24} label="Svelte theme" arcColor="#ff3e00" />
123+
<Knob
124+
param={basicParam}
125+
value={48}
126+
label="Light theme"
127+
arcColor="#4292d3"
128+
bgColor="#eef"
129+
style="color:#000"
130+
/>
131+
<Knob param={basicParam} value={64} label="Disabled color" disabledColor="#aaa" disabled />
132+
133+
<p>
134+
Of course, <code>{'<Knob />'}</code> colors can be customized to look however you want.
135+
<b>Disclaimer</b>: Font and thumb colors are based on the current font color.
136+
</p>
137+
</div>
103138
</div>
104139

105140
<div class="code">

0 commit comments

Comments
 (0)