Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0725520

Browse files
committedDec 1, 2024··
fix: svgknob focus state, refactor, docs
1 parent c58c184 commit 0725520

File tree

9 files changed

+85
-83
lines changed

9 files changed

+85
-83
lines changed
 

‎src/lib/ImageKnob.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
step,
2727
acceleration,
2828
maxSpeed,
29-
initialDelay,
3029
defaultValue,
3130
param,
3231
stiffness = 0.5,
@@ -40,7 +39,7 @@
4039
colors = {}
4140
}: Props = $props();
4241
43-
// TODO rewrite base knob logic
42+
// TODO Refactor
4443
const rotationDegrees = spring(normalize(value, param) * 270 - 135, { stiffness });
4544
4645
function draw() {
@@ -49,6 +48,7 @@
4948
if (!image) return;
5049
if (!('width' in image && 'height' in image)) return;
5150
51+
// TODO Refactor
5252
const normalized = ($rotationDegrees + 135) / 270;
5353
const i = Math.floor(normalized * numberOfFrames);
5454
const y = image.width * i;
@@ -125,7 +125,6 @@
125125
{step}
126126
{style}
127127
{unit}
128-
{initialDelay}
129128
bind:value
130129
{rotationDegrees}
131130
>
@@ -137,6 +136,7 @@
137136
normalizedValue
138137
})}
139138
<canvas
139+
{style}
140140
role="slider"
141141
tabindex="0"
142142
aria-valuenow={normalizedValue}

‎src/lib/KnobBase.svelte

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
/** multiplier for acceleration */
2525
acceleration?: number;
2626
maxSpeed?: number;
27-
/** initial delay before acceleration starts (ms) */
28-
initialDelay?: number;
2927
3028
defaultValue?: number | string;
3129
label?: string;
@@ -43,9 +41,11 @@
4341
import { clamp } from './helpers/clamp.js';
4442
import { normalize, format, unnormalizeToString, unnormalizeToNumber } from './params.js';
4543
import type { EnumParam, FloatParam } from './params.js';
44+
import './shield.css';
4645
4746
type KnobBaseProps = {
4847
ui: Snippet<[SnippetProps]>;
48+
// FIX unwanted knob jiggle
4949
rotationDegrees: Spring<number>;
5050
} & SharedKnobProps;
5151
@@ -57,9 +57,8 @@
5757
onChange,
5858
value = $bindable(),
5959
step = 0.01,
60-
acceleration = 1.4,
60+
acceleration = 1.2,
6161
maxSpeed = 0.2,
62-
initialDelay = 100,
6362
defaultValue,
6463
param,
6564
rotationDegrees,
@@ -77,8 +76,6 @@
7776
let startY: number;
7877
let startValue: number;
7978
80-
// This is needed in case some snap value is very close to the min or max range
81-
// preventing the user from selecting that value
8279
function completeFixedSnapValues(snapValues: number[]) {
8380
if (param.type === 'enum-param') return [];
8481
if (snapValues.length < 1) return [];
@@ -100,32 +97,26 @@
10097
function toMobile(handler: ({ clientY }: MouseEvent) => void | boolean) {
10198
return (event: TouchEvent) => {
10299
const touch = event.touches?.[0];
103-
if (touch === undefined) return;
104-
100+
if (!touch) return;
105101
const clientY = touch.clientY;
106-
107102
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
108103
handler({ clientY } as MouseEvent) && event.preventDefault();
109104
};
110105
}
111106
112107
function handleMouseDown({ clientY }: MouseEvent) {
113-
if (!draggable) return;
108+
if (!draggable || isDisabled) return;
114109
isDragging = true;
115110
startY = clientY;
116111
startValue = normalizedValue;
117-
118112
return true;
119113
}
120114
121115
function handleMouseMove({ clientY }: MouseEvent) {
122-
if (!draggable) return;
123-
if (isDisabled) return;
124-
if (!isDragging) return;
116+
if (!draggable || isDisabled || !isDragging) return;
125117
const deltaY = startY - clientY;
126118
const deltaValue = deltaY / 200;
127119
setValue(startValue + deltaValue);
128-
129120
return true;
130121
}
131122
@@ -136,8 +127,9 @@
136127
function handleDblClick() {
137128
const val =
138129
defaultValue ??
139-
(param as FloatParam)?.range.min ??
130+
(param as FloatParam)?.range?.min ??
140131
(param as EnumParam<string[]>).variants?.[0];
132+
141133
if (val === undefined) return;
142134
143135
setValue(normalize(val, param));
@@ -148,7 +140,6 @@
148140
149141
type Direction = 'left' | 'right';
150142
151-
let intervalId = -1;
152143
let currentSpeed = step;
153144
154145
const directions: Record<string, Direction> = {
@@ -158,52 +149,49 @@
158149
ArrowUp: 'right'
159150
};
160151
161-
function adjustValue(direction: Direction) {
162-
const delta = direction === 'right' ? currentSpeed : -currentSpeed;
163-
console.log(direction);
164-
setValue(normalizedValue + delta);
152+
function handleKeyDown(e: KeyboardEvent) {
153+
if (isDisabled || !(e.key in directions)) return;
154+
isDragging = true;
155+
const direction = directions[e.key];
165156
166-
currentSpeed = Math.min(maxSpeed, currentSpeed * acceleration);
167-
}
157+
if (param.type === 'enum-param') {
158+
const i = param.variants.findIndex((v) => v === value) ?? 0;
159+
const step = direction === 'right' ? 1 : -1;
168160
169-
function handleKeyDown(e: KeyboardEvent) {
170-
if (isDisabled) return;
171-
if (!(e.key in directions)) return;
172-
if (intervalId > -1) return;
161+
value = param.variants[clamp(i + step, 0, param.variants.length - 1)];
162+
onChange?.(value);
173163
174-
intervalId = window.setInterval(() => adjustValue(directions[e.key]), initialDelay);
164+
return;
165+
}
166+
167+
const delta = direction === 'right' ? currentSpeed : -currentSpeed;
168+
setValue(normalizedValue + delta);
169+
currentSpeed = Math.min(maxSpeed, currentSpeed * acceleration);
175170
}
176171
177172
function handleKeyUp() {
178-
if (intervalId === -1) return;
179-
180-
window.clearInterval(intervalId);
181-
intervalId = -1;
173+
isDragging = false;
182174
currentSpeed = step;
183175
}
184176
185177
$effect(() => {
186178
rotationDegrees.set(normalizedValue * 270 - 135);
187-
188-
// this was easier in svelte 4 :/
189179
window.addEventListener('touchmove', handleTouchMove, { passive: false });
180+
190181
return () => window.removeEventListener('touchmove', handleTouchMove);
191182
});
192183
193184
function setValue(newNormalizedValue: number) {
194185
if (param.type === 'enum-param') {
195-
const newValue = unnormalizeToString(newNormalizedValue, param);
196-
186+
const newValue = unnormalizeToString(clamp(newNormalizedValue, 0, 1), param);
197187
if (value !== newValue) {
198188
value = newValue;
199189
onChange?.(value);
200190
}
201-
202191
return;
203192
}
204193
205194
let newValue = unnormalizeToNumber(clamp(newNormalizedValue, 0, 1), param);
206-
207195
if (fixedSnapValues.length > 0) {
208196
const nearestSnapValue = fixedSnapValues.reduce((prev, curr) => {
209197
const currNormalized = normalize(curr, param);
@@ -213,18 +201,34 @@
213201
? curr
214202
: prev;
215203
});
216-
217204
const nearestSnapNormalized = normalize(nearestSnapValue, param);
218205
if (Math.abs(nearestSnapNormalized - newNormalizedValue) <= snapThreshold) {
219206
newValue = nearestSnapValue;
220207
}
221208
}
222209
223210
if (value !== newValue) {
211+
if (isNaN(newValue)) {
212+
newValue = 0;
213+
console.warn('newValue is NaN');
214+
}
224215
value = newValue;
225216
onChange?.(value);
226217
}
227218
}
219+
220+
let shield = document.createElement('div');
221+
222+
$effect(() => {
223+
if (isDragging) {
224+
shield.className = 'shield tf68Uh';
225+
document.body.append(shield);
226+
document.body.style.userSelect = 'none';
227+
} else {
228+
shield.remove();
229+
document.body.style.userSelect = '';
230+
}
231+
});
228232
</script>
229233

230234
<svelte:window
@@ -259,7 +263,6 @@
259263
span {
260264
user-select: none;
261265
}
262-
263266
.container {
264267
position: relative;
265268
display: flex;

‎src/lib/SvgKnob.svelte

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { spring } from 'svelte/motion';
44
import KnobBase from './KnobBase.svelte';
55
import type { SharedKnobProps } from './KnobBase.svelte';
6+
import './svgknob.css';
67
78
type Props = {
89
size?: number;
@@ -28,7 +29,6 @@
2829
step,
2930
acceleration,
3031
maxSpeed,
31-
initialDelay,
3232
defaultValue,
3333
param,
3434
stiffness = 0.5,
@@ -109,9 +109,8 @@
109109
{snapThreshold}
110110
{snapValues}
111111
{step}
112-
{style}
113112
{unit}
114-
{initialDelay}
113+
{style}
115114
bind:value
116115
>
117116
{#snippet ui({
@@ -122,16 +121,16 @@
122121
handleKeyDown
123122
})}
124123
<svg
125-
style="--stroke-width: {strokeWidth ?? lineWidth}px"
126-
class={className}
127-
role="slider"
128-
tabindex="0"
129-
aria-valuenow={normalizedValue}
124+
style="--stroke-width:{strokeWidth ?? lineWidth}px;{style}"
125+
class="dK3qx2 {className}"
130126
width={size}
131127
height={size}
132128
viewBox="0 0 {size} {size}"
133129
stroke-linecap="round"
134130
stroke-linejoin="round"
131+
role="slider"
132+
tabindex="0"
133+
aria-valuenow={normalizedValue}
135134
onmousedown={handleMouseDown}
136135
ontouchstart={handleTouchStart}
137136
ondblclick={handleDblClick}
@@ -144,20 +143,20 @@
144143
{/if}
145144
<!-- Arcs -->
146145
<path
147-
class="line"
146+
class="knob_line"
148147
d={describeArc(center, center, arcRadius, $rotationDegrees, 135)}
149148
stroke={bgColor}
150149
fill="none"
151150
/>
152151
<path
153-
class="line"
152+
class="knob_line"
154153
d={describeArc(center, center, arcRadius, -135, $rotationDegrees)}
155154
stroke={arcColor2}
156155
fill="none"
157156
/>
158157
<!-- Knob indicator -->
159158
<line
160-
class="line"
159+
class="knob_line"
161160
x1={center}
162161
y1={center * 0.7}
163162
x2={center}
@@ -168,28 +167,3 @@
168167
</svg>
169168
{/snippet}
170169
</KnobBase>
171-
172-
<style>
173-
svg {
174-
outline: none;
175-
}
176-
177-
.line {
178-
transition: stroke-width 200ms;
179-
stroke-width: var(--stroke-width);
180-
}
181-
182-
.focus {
183-
display: none;
184-
}
185-
186-
svg:active .focus,
187-
svg:focus .focus {
188-
display: block;
189-
}
190-
191-
svg:active .line,
192-
svg:focus .line {
193-
stroke-width: calc(var(--stroke-width) * 1.8);
194-
}
195-
</style>

‎src/lib/VideoKnob.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
step,
3737
acceleration,
3838
maxSpeed,
39-
initialDelay,
4039
defaultValue,
4140
param,
4241
stiffness = 0.5,
@@ -51,12 +50,14 @@
5150
colors = {}
5251
}: Props = $props();
5352
53+
// TODO Refactor
5454
const rotationDegrees = spring(normalize(value, param) * 270 - 135, { stiffness });
5555
const frames: Array<HTMLImageElement> = [];
5656
5757
function draw() {
5858
if (!ctx) return;
5959
60+
// TODO Refactor
6061
const normalized = ($rotationDegrees + 135) / 270;
6162
const i = Math.floor(normalized * numberOfFrames);
6263
if (i < 0) return;
@@ -209,7 +210,6 @@
209210
{defaultValue}
210211
{disabled}
211212
{draggable}
212-
{initialDelay}
213213
{label}
214214
{maxSpeed}
215215
{onChange}
@@ -230,6 +230,7 @@
230230
normalizedValue
231231
})}
232232
<canvas
233+
{style}
233234
role="slider"
234235
tabindex="0"
235236
aria-valuenow={normalizedValue}

‎src/lib/shield.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.shield.tf68Uh {
2+
position: fixed;
3+
inset: 0;
4+
width: 100vw;
5+
height: 100vh;
6+
}

‎src/lib/svgknob.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
svg.dK3qx2 {
2+
outline: none;
3+
}
4+
5+
.knob_line {
6+
transition: stroke-width 200ms;
7+
stroke-width: var(--stroke-width);
8+
}
9+
10+
svg.dK3qx2:hover .knob_line,
11+
svg.dK3qx2:focus .knob_line,
12+
svg.dK3qx2:focus-within .knob_line,
13+
svg.dK3qx2:active .knob_line {
14+
stroke-width: calc(var(--stroke-width) * 1.8);
15+
}

‎src/routes/CopyPaste.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
box-sizing: border-box;
6464
border-radius: 8px;
6565
overflow-x: scroll;
66+
scrollbar-width: none;
6667
padding: 1rem;
6768
}
6869

‎src/routes/examples/ColoredKnobs.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
disabled
2626
/>
2727

28-
<Knob param={basicParam} value={24} label="Chubby" strokeWidth={6} colors={{ arc: '#ff3e00' }} />
29-
<Knob param={basicParam} value={24} label="Elegant" strokeWidth={2} colors={{ arc: '#ff3e00' }} />
28+
<Knob param={basicParam} value={24} label="Chubby" strokeWidth={6} colors={{ arc: '#3eff00' }} />
29+
<Knob param={basicParam} value={24} label="Slim" strokeWidth={1} colors={{ arc: '#4e80ce' }} />
3030
</div>
3131

3232
<style>

‎src/routes/examples/ImageStrip.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
unit="%"
1515
source={videoSource}
1616
numberOfFrames={79}
17+
stiffness={0.2}
1718
cacheFrames
1819
/>
1920

@@ -23,5 +24,6 @@
2324
label="Volume"
2425
unit="%"
2526
source={imageSource}
27+
stiffness={0.2}
2628
numberOfFrames={79}
2729
/>

0 commit comments

Comments
 (0)
Please sign in to comment.