Skip to content

Commit 9101e83

Browse files
committed
final refactor
1 parent be50b42 commit 9101e83

File tree

10 files changed

+102
-65
lines changed

10 files changed

+102
-65
lines changed

package.json

Lines changed: 5 additions & 1 deletion
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": "1.0.0-beta.0",
4+
"version": "1.0.0-beta.1",
55
"repository": {
66
"url": "https://github.com/eye-wave/svelte-knobs"
77
},
@@ -26,6 +26,10 @@
2626
".": {
2727
"types": "./dist/index.d.ts",
2828
"svelte": "./dist/index.js"
29+
},
30+
"./params": {
31+
"types": "./dist/params.d.ts",
32+
"import": "./dist/params.js"
2933
}
3034
},
3135
"sideEffects": [

src/lib/Draggable.svelte

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<script lang="ts" module>
2+
import { Spring, type Tween } from 'svelte/motion';
3+
4+
type Motion<T> = Spring<T> | Tween<T>;
5+
26
let shield: HTMLDivElement | null = null;
37
48
export type DraggableProps = {
@@ -7,11 +11,16 @@
711
*/
812
value?: number;
913
14+
/**
15+
* Readonly smoothed value
16+
*/
17+
valueSmoothed?: number;
18+
1019
weight?: number;
1120
1221
/**
1322
* The increment or decrement value for keyboard interactions.
14-
* Defaults to `0.1` if not specified.
23+
* Defaults to `0.05` if not specified.
1524
*/
1625
step?: number;
1726
@@ -22,6 +31,14 @@
2231
2332
snapThreshold?: number;
2433
34+
invertWheel?: boolean;
35+
36+
/**
37+
* "svelte/motion" class instance used to animate the knob.
38+
* Default motion in Spring with stiffness of 0.3
39+
*/
40+
motion?: Motion<number>;
41+
2542
/**
2643
* Initial value for the component.
2744
*/
@@ -45,19 +62,24 @@
4562
4663
let {
4764
value = $bindable(0.5),
65+
valueSmoothed = $bindable(0.5),
4866
children,
4967
disabled: isDisabled = false,
5068
defaultValue,
51-
step = 0.1,
52-
snapPoints,
69+
invertWheel = false,
70+
step = 0.05,
71+
snapPoints: _snapPoints,
72+
motion = new Spring(0.5, { stiffness: 0.3 }),
5373
snapThreshold = 0.08,
5474
weight = 200,
5575
...divProps
5676
}: Props = $props();
5777
5878
type SvelteEvent = { currentTarget: EventTarget & HTMLDivElement };
5979
60-
let isDragging = $state(false);
80+
let snapPoints = $derived(_snapPoints?.toSorted((a, b) => a - b));
81+
82+
let isDragging = false;
6183
let isShieldOn = $state(false);
6284
let startX: number;
6385
let startY: number;
@@ -82,8 +104,9 @@
82104
const touch = event.touches?.[0];
83105
if (!touch) return;
84106
const clientY = touch.clientY;
107+
const clientX = touch.clientX;
85108
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
86-
handler({ clientY } as MouseEvent & SvelteEvent) && event.preventDefault();
109+
handler({ clientY, clientX } as MouseEvent & SvelteEvent) && event.preventDefault();
87110
};
88111
}
89112
@@ -131,7 +154,8 @@
131154
132155
if (isDisabled) return;
133156
e.preventDefault();
134-
const delta = e.deltaY > 0 ? -1.0 : 1.0;
157+
158+
const delta = (e.deltaY > 0 ? 1.0 : -1.0) * (invertWheel ? -1.0 : 1.0);
135159
136160
value = clamp(snap(value + delta * step, snapPoints));
137161
}
@@ -158,7 +182,19 @@
158182
159183
if (isPointingLeft || isPointingRight) e.preventDefault();
160184
161-
value += +isPointingRight * step - +isPointingLeft * step;
185+
if (!e.altKey && snapPoints) {
186+
let next = snapPoints.findIndex((n) => n >= value);
187+
188+
next += +isPointingRight;
189+
next -= +isPointingLeft;
190+
191+
next = clamp(next, 0, snapPoints.length - 1);
192+
193+
value = snapPoints[next];
194+
} else {
195+
value += +isPointingRight * step - +isPointingLeft * step;
196+
}
197+
162198
value = clamp(value);
163199
}
164200
@@ -186,11 +222,20 @@
186222
document.body.style.userSelect = '';
187223
}
188224
});
225+
226+
$effect(() => {
227+
motion.set(value);
228+
});
229+
230+
$effect(() => {
231+
valueSmoothed = motion.current;
232+
});
189233
</script>
190234

191235
<svelte:window onmousemove={handleMouseMove} onmouseup={handleMouseUp} ontouchend={handleMouseUp} />
192236

193237
<div
238+
aria-disabled={isDisabled}
194239
class={divProps.class}
195240
style={divProps.style}
196241
role="slider"
@@ -209,5 +254,10 @@
209254
<style>
210255
div {
211256
width: fit-content;
257+
cursor: grab;
258+
}
259+
260+
div[aria-disabled='true'] {
261+
cursor: auto;
212262
}
213263
</style>

src/lib/ImageKnob.svelte

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<script lang="ts" module>
22
import type { DraggableProps } from './Draggable.svelte';
3-
import { Spring, type Tween } from 'svelte/motion';
4-
5-
type Motion<T> = Spring<T> | Tween<T>;
63
74
export type ImageKnobProps = DraggableProps & {
85
/**
@@ -27,28 +24,22 @@
2724
* Default height is 80;
2825
*/
2926
height?: number;
30-
31-
/**
32-
* "svelte/motion" class instance used to animate the knob.
33-
* Default motion in Spring with stiffness of 0.2
34-
*/
35-
motion?: Motion<number>;
3627
};
3728
</script>
3829

3930
<script lang="ts">
31+
import { onMount } from 'svelte';
4032
import Draggable from './Draggable.svelte';
4133
import type { SvelteHTMLElements } from 'svelte/elements';
42-
import { onMount } from 'svelte';
4334
4435
type Props = SvelteHTMLElements['div'] & ImageKnobProps;
4536
let {
46-
value = $bindable(0),
37+
value = $bindable(0.5),
38+
valueSmoothed = $bindable(0.5),
4739
width = 80,
4840
height = 80,
4941
numberOfFrames,
5042
src,
51-
motion = new Spring(0.0, { stiffness: 0.5 }),
5243
...defaultProps
5344
}: Props = $props();
5445
@@ -65,15 +56,12 @@
6556
};
6657
});
6758
68-
let transform = $derived(Math.floor(motion.current * (numberOfFrames ?? 0)));
69-
70-
$effect(() => {
71-
motion.set(value);
72-
});
59+
let transform = $derived(Math.floor(valueSmoothed * (numberOfFrames ?? 0)));
7360
</script>
7461

7562
<Draggable
7663
bind:value
64+
bind:valueSmoothed
7765
style="width:{width}px;height:{height}px;{defaultProps.style}"
7866
{...defaultProps}
7967
>

src/lib/SvgKnob.svelte

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<script lang="ts" module>
22
import type { DraggableProps } from './Draggable.svelte';
3-
import { Spring, type Tween } from 'svelte/motion';
4-
5-
type Motion<T> = Spring<T> | Tween<T>;
63
74
export type SvgKnobProps = DraggableProps & {
85
/**
@@ -17,12 +14,6 @@
1714
1815
pointerLength?: number;
1916
20-
/**
21-
* "svelte/motion" class instance used to animate the knob.
22-
* Default motion in Spring with stiffness of 0.5
23-
*/
24-
motion?: Motion<number>;
25-
2617
/**
2718
* Background color of the knob.
2819
* Default color is #333
@@ -50,20 +41,22 @@
5041
</script>
5142

5243
<script lang="ts">
44+
import { describeArc, polarToCartesian, valueToAngle } from './helpers/arc.js';
5345
import Draggable from './Draggable.svelte';
5446
import type { SvelteHTMLElements } from 'svelte/elements';
55-
import { describeArc, polarToCartesian, valueToAngle } from './helpers/arc.js';
5647
5748
type Props = SvelteHTMLElements['svg'] & SvgKnobProps;
5849
let {
59-
value = $bindable(0),
50+
value = $bindable(0.5),
51+
valueSmoothed = $bindable(0.5),
6052
size = 80,
61-
motion = new Spring(0.0, { stiffness: 0.5 }),
53+
motion,
6254
bgColor = '#333',
6355
disabledColor = '#777',
6456
minAngle = -135,
6557
maxAngle = 135,
6658
snapPointLength = 0.44,
59+
invertWheel,
6760
circleRadius: cr = 0.32,
6861
arcRadius: ar = 0.4,
6962
pointerLength = 0,
@@ -76,19 +69,23 @@
7669
...svgProps
7770
}: Props = $props();
7871
79-
let draggableProps = $derived({ step, defaultValue, snapPoints, snapThreshold, weight });
72+
let draggableProps = $derived({
73+
defaultValue,
74+
invertWheel,
75+
motion,
76+
snapPoints,
77+
snapThreshold,
78+
step,
79+
weight
80+
});
8081
8182
let c = $derived(size / 2);
8283
let arcRadius = $derived(size * ar);
8384
let circleRadius = $derived(size * cr);
8485
let snapRadius = $derived(size * snapPointLength);
85-
86-
$effect(() => {
87-
motion.set(value);
88-
});
8986
</script>
9087

91-
<Draggable bind:value {...draggableProps} disabled={isDisabled}>
88+
<Draggable bind:value bind:valueSmoothed {...draggableProps} disabled={isDisabled}>
9289
<svg
9390
width="{size}px"
9491
height="{size}px"
@@ -108,7 +105,7 @@
108105
/>
109106
<path
110107
class="knob_line"
111-
d={describeArc(c, c, arcRadius, motion.current, minAngle, maxAngle)}
108+
d={describeArc(c, c, arcRadius, valueSmoothed, minAngle, maxAngle)}
112109
stroke={isDisabled ? disabledColor : 'currentColor'}
113110
fill="none"
114111
/>
@@ -118,7 +115,7 @@
118115
{#each snapPoints ?? [] as p}
119116
{@const [x1, y1] = polarToCartesian(c, c, arcRadius, valueToAngle(p, minAngle, maxAngle))}
120117
{@const [x2, y2] = polarToCartesian(c, c, snapRadius, valueToAngle(p, minAngle, maxAngle))}
121-
{@const stroke = value >= p ? 'currentColor' : bgColor}
118+
{@const stroke = valueSmoothed >= p ? 'currentColor' : bgColor}
122119

123120
<line class="knob_line" {x1} {y1} {x2} {y2} {stroke} />
124121
{/each}
@@ -130,13 +127,13 @@
130127
c,
131128
c,
132129
circleRadius * 0.8,
133-
valueToAngle(motion.current, minAngle, maxAngle)
130+
valueToAngle(valueSmoothed, minAngle, maxAngle)
134131
)}
135132
{@const [x2, y2] = polarToCartesian(
136133
c,
137134
c,
138135
circleRadius * 0.8 - pointerLength * size * 0.52,
139-
valueToAngle(motion.current, minAngle, maxAngle)
136+
valueToAngle(valueSmoothed, minAngle, maxAngle)
140137
)}
141138
<line
142139
class="knob_line"

src/lib/params.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export type { Param } from './params/base.js';
2+
13
export * from './params/lin.js';
24
export * from './params/log.js';
35
export * from './params/bool.js';

src/lib/params/bool.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ export class BoolParam extends Param<boolean> {
77

88
public readonly snapPoints = [0.0, 1.0];
99
public readonly snapThreshold = 0.5;
10-
public readonly step = 1.0;
1110

1211
public get knobProps() {
1312
return {
1413
snapPoints: this.snapPoints,
15-
snapThreshold: this.snapThreshold,
16-
step: this.step
14+
snapThreshold: this.snapThreshold
1715
};
1816
}
1917

src/lib/params/enum.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,10 @@ export class EnumParam<T extends readonly string[]> extends Param<T[number]> {
1515
return 1.0 / (this.#variants.length - 1.0);
1616
}
1717

18-
public get step() {
19-
const error = Math.pow(10, -this.#variants.length);
20-
return this.snapThreshold + error;
21-
}
22-
2318
public get knobProps() {
2419
return {
2520
snapPoints: this.snapPoints,
26-
snapThreshold: this.snapThreshold,
27-
step: this.step
21+
snapThreshold: this.snapThreshold
2822
};
2923
}
3024

src/lib/shield.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
inset: 0;
44
width: 100vw;
55
height: 100vh;
6+
cursor: grab;
67
}

0 commit comments

Comments
 (0)