dialkit 1.0.0 → 1.2.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.
- package/README.md +293 -2
- package/dist/index.cjs +905 -363
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -9
- package/dist/index.d.ts +46 -9
- package/dist/index.js +910 -370
- package/dist/index.js.map +1 -1
- package/dist/solid/index.cjs +1042 -532
- package/dist/solid/index.cjs.map +1 -1
- package/dist/solid/index.d.cts +36 -3
- package/dist/solid/index.d.ts +36 -3
- package/dist/solid/index.js +911 -402
- package/dist/solid/index.js.map +1 -1
- package/dist/store/index.cjs +490 -0
- package/dist/store/index.cjs.map +1 -0
- package/dist/store/index.d.cts +146 -0
- package/dist/store/index.d.ts +146 -0
- package/dist/store/index.js +465 -0
- package/dist/store/index.js.map +1 -0
- package/dist/styles.css +287 -25
- package/dist/svelte/Portal.svelte +30 -0
- package/dist/svelte/Portal.svelte.d.ts +9 -0
- package/dist/svelte/Portal.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ButtonGroup.svelte +16 -0
- package/dist/svelte/components/ButtonGroup.svelte.d.ts +11 -0
- package/dist/svelte/components/ButtonGroup.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ColorControl.svelte +81 -0
- package/dist/svelte/components/ColorControl.svelte.d.ts +9 -0
- package/dist/svelte/components/ColorControl.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ControlRenderer.svelte +96 -0
- package/dist/svelte/components/ControlRenderer.svelte.d.ts +11 -0
- package/dist/svelte/components/ControlRenderer.svelte.d.ts.map +1 -0
- package/dist/svelte/components/DialRoot.svelte +79 -0
- package/dist/svelte/components/DialRoot.svelte.d.ts +14 -0
- package/dist/svelte/components/DialRoot.svelte.d.ts.map +1 -0
- package/dist/svelte/components/EasingVisualization.svelte +50 -0
- package/dist/svelte/components/EasingVisualization.svelte.d.ts +10 -0
- package/dist/svelte/components/EasingVisualization.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Folder.svelte +213 -0
- package/dist/svelte/components/Folder.svelte.d.ts +14 -0
- package/dist/svelte/components/Folder.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Panel.svelte +149 -0
- package/dist/svelte/components/Panel.svelte.d.ts +10 -0
- package/dist/svelte/components/Panel.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PresetManager.svelte +157 -0
- package/dist/svelte/components/PresetManager.svelte.d.ts +10 -0
- package/dist/svelte/components/PresetManager.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SegmentedControl.svelte +62 -0
- package/dist/svelte/components/SegmentedControl.svelte.d.ts +13 -0
- package/dist/svelte/components/SegmentedControl.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SelectControl.svelte +152 -0
- package/dist/svelte/components/SelectControl.svelte.d.ts +14 -0
- package/dist/svelte/components/SelectControl.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ShortcutListener.svelte +265 -0
- package/dist/svelte/components/ShortcutListener.svelte.d.ts +13 -0
- package/dist/svelte/components/ShortcutListener.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ShortcutsMenu.svelte +128 -0
- package/dist/svelte/components/ShortcutsMenu.svelte.d.ts +7 -0
- package/dist/svelte/components/ShortcutsMenu.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Slider.svelte +332 -0
- package/dist/svelte/components/Slider.svelte.d.ts +16 -0
- package/dist/svelte/components/Slider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SpringControl.svelte +126 -0
- package/dist/svelte/components/SpringControl.svelte.d.ts +12 -0
- package/dist/svelte/components/SpringControl.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SpringVisualization.svelte +115 -0
- package/dist/svelte/components/SpringVisualization.svelte.d.ts +9 -0
- package/dist/svelte/components/SpringVisualization.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TextControl.svelte +19 -0
- package/dist/svelte/components/TextControl.svelte.d.ts +10 -0
- package/dist/svelte/components/TextControl.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Toggle.svelte +32 -0
- package/dist/svelte/components/Toggle.svelte.d.ts +12 -0
- package/dist/svelte/components/Toggle.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TransitionControl.svelte +203 -0
- package/dist/svelte/components/TransitionControl.svelte.d.ts +12 -0
- package/dist/svelte/components/TransitionControl.svelte.d.ts.map +1 -0
- package/dist/svelte/components/transitions.d.ts +6 -0
- package/dist/svelte/components/transitions.d.ts.map +1 -0
- package/dist/svelte/components/transitions.js +14 -0
- package/dist/svelte/createDialKit.svelte.d.ts +8 -0
- package/dist/svelte/createDialKit.svelte.d.ts.map +1 -0
- package/dist/svelte/createDialKit.svelte.js +82 -0
- package/dist/svelte/index.d.ts +23 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +23 -0
- package/dist/svelte/shims.d.ts +9 -0
- package/dist/svelte/theme-css.d.ts +2 -0
- package/dist/svelte/theme-css.d.ts.map +1 -0
- package/dist/svelte/theme-css.js +1328 -0
- package/dist/vue/index.cjs +3056 -0
- package/dist/vue/index.cjs.map +1 -0
- package/dist/vue/index.d.cts +675 -0
- package/dist/vue/index.d.ts +675 -0
- package/dist/vue/index.js +3014 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +50 -6
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import { Spring } from 'svelte/motion';
|
|
4
|
+
import type { ShortcutConfig } from 'dialkit/store';
|
|
5
|
+
import { decimalsForStep, roundValue, snapToDecile, formatSliderShortcut } from '../../shortcut-utils';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
label,
|
|
9
|
+
value,
|
|
10
|
+
onChange,
|
|
11
|
+
min = 0,
|
|
12
|
+
max = 1,
|
|
13
|
+
step = 0.01,
|
|
14
|
+
shortcut,
|
|
15
|
+
shortcutActive = false,
|
|
16
|
+
} = $props<{
|
|
17
|
+
label: string;
|
|
18
|
+
value: number;
|
|
19
|
+
onChange: (value: number) => void;
|
|
20
|
+
min?: number;
|
|
21
|
+
max?: number;
|
|
22
|
+
step?: number;
|
|
23
|
+
unit?: string;
|
|
24
|
+
shortcut?: ShortcutConfig;
|
|
25
|
+
shortcutActive?: boolean;
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
const CLICK_THRESHOLD = 3;
|
|
29
|
+
const DEAD_ZONE = 32;
|
|
30
|
+
const MAX_CURSOR_RANGE = 200;
|
|
31
|
+
const MAX_STRETCH = 8;
|
|
32
|
+
const HANDLE_BUFFER = 8;
|
|
33
|
+
const LABEL_CSS_LEFT = 10;
|
|
34
|
+
const VALUE_CSS_RIGHT = 10;
|
|
35
|
+
|
|
36
|
+
let wrapperRef: HTMLDivElement | undefined;
|
|
37
|
+
let labelRef: HTMLSpanElement | undefined;
|
|
38
|
+
let valueSpanRef: HTMLSpanElement | undefined;
|
|
39
|
+
let inputRef: HTMLInputElement | undefined;
|
|
40
|
+
|
|
41
|
+
let isInteracting = $state(false);
|
|
42
|
+
let isDragging = $state(false);
|
|
43
|
+
let isHovered = $state(false);
|
|
44
|
+
let isValueHovered = $state(false);
|
|
45
|
+
let isValueEditable = $state(false);
|
|
46
|
+
let showInput = $state(false);
|
|
47
|
+
let inputValue = $state('');
|
|
48
|
+
|
|
49
|
+
const fillPercent = new Spring(((value - min) / (max - min)) * 100, { stiffness: 0.25, damping: 0.7 });
|
|
50
|
+
const rubberStretchPx = new Spring(0, { stiffness: 0.2, damping: 0.65 });
|
|
51
|
+
const handleOpacityMv = new Spring(0, { stiffness: 0.3, damping: 0.75 });
|
|
52
|
+
const handleScaleXMv = new Spring(0.25, { stiffness: 0.2, damping: 0.6 });
|
|
53
|
+
const handleScaleYMv = new Spring(1, { stiffness: 0.2, damping: 0.6 });
|
|
54
|
+
|
|
55
|
+
let pointerDownPos: { x: number; y: number } | null = null;
|
|
56
|
+
let isClickFlag = true;
|
|
57
|
+
let wrapperRect: DOMRect | null = null;
|
|
58
|
+
let scaleVal = 1;
|
|
59
|
+
let hoverTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
60
|
+
|
|
61
|
+
const percentFromValue = (nextValue: number) => ((nextValue - min) / (max - min)) * 100;
|
|
62
|
+
|
|
63
|
+
const positionToValue = (clientX: number) => {
|
|
64
|
+
if (!wrapperRect || !wrapperRef) return value;
|
|
65
|
+
const screenX = clientX - wrapperRect.left;
|
|
66
|
+
const sceneX = screenX / scaleVal;
|
|
67
|
+
const nativeWidth = wrapperRef.offsetWidth || wrapperRect.width;
|
|
68
|
+
const percent = Math.max(0, Math.min(1, sceneX / nativeWidth));
|
|
69
|
+
const rawValue = min + percent * (max - min);
|
|
70
|
+
return Math.max(min, Math.min(max, rawValue));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const computeRubberStretch = (clientX: number, sign: number) => {
|
|
74
|
+
if (!wrapperRect) return 0;
|
|
75
|
+
const distancePast = sign < 0 ? wrapperRect.left - clientX : clientX - wrapperRect.right;
|
|
76
|
+
const overflow = Math.max(0, distancePast - DEAD_ZONE);
|
|
77
|
+
return sign * MAX_STRETCH * Math.sqrt(Math.min(overflow / MAX_CURSOR_RANGE, 1));
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
$effect(() => {
|
|
81
|
+
value;
|
|
82
|
+
if (!isInteracting) {
|
|
83
|
+
fillPercent.set(((value - min) / (max - min)) * 100, { instant: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
$effect(() => {
|
|
88
|
+
if (hoverTimeout) {
|
|
89
|
+
clearTimeout(hoverTimeout);
|
|
90
|
+
hoverTimeout = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (isValueHovered && !showInput && !isValueEditable) {
|
|
94
|
+
hoverTimeout = setTimeout(() => {
|
|
95
|
+
isValueEditable = true;
|
|
96
|
+
}, 800);
|
|
97
|
+
} else if (!isValueHovered && !showInput) {
|
|
98
|
+
isValueEditable = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
if (hoverTimeout) {
|
|
103
|
+
clearTimeout(hoverTimeout);
|
|
104
|
+
hoverTimeout = null;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
$effect(() => {
|
|
110
|
+
if (showInput) {
|
|
111
|
+
tick().then(() => {
|
|
112
|
+
inputRef?.focus();
|
|
113
|
+
inputRef?.select();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const percentage = $derived(((value - min) / (max - min)) * 100);
|
|
119
|
+
const isActive = $derived(isInteracting || isHovered);
|
|
120
|
+
|
|
121
|
+
const leftThreshold = $derived.by(() => {
|
|
122
|
+
const trackWidth = wrapperRef?.offsetWidth;
|
|
123
|
+
if (trackWidth && labelRef) {
|
|
124
|
+
return ((LABEL_CSS_LEFT + labelRef.offsetWidth + HANDLE_BUFFER) / trackWidth) * 100;
|
|
125
|
+
}
|
|
126
|
+
return 30;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const rightThreshold = $derived.by(() => {
|
|
130
|
+
const trackWidth = wrapperRef?.offsetWidth;
|
|
131
|
+
if (trackWidth && valueSpanRef) {
|
|
132
|
+
return ((trackWidth - VALUE_CSS_RIGHT - valueSpanRef.offsetWidth - HANDLE_BUFFER) / trackWidth) * 100;
|
|
133
|
+
}
|
|
134
|
+
return 78;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const valueDodge = $derived(percentage < leftThreshold || percentage > rightThreshold);
|
|
138
|
+
|
|
139
|
+
const handleOpacity = $derived.by(() => {
|
|
140
|
+
if (!isActive) return 0;
|
|
141
|
+
if (valueDodge) return 0.1;
|
|
142
|
+
if (isDragging) return 0.9;
|
|
143
|
+
return 0.5;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
$effect(() => {
|
|
147
|
+
handleOpacityMv.set(handleOpacity);
|
|
148
|
+
handleScaleXMv.set(isActive ? 1 : 0.25);
|
|
149
|
+
handleScaleYMv.set(isActive && valueDodge ? 0.75 : 1);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const discreteSteps = $derived((max - min) / step);
|
|
153
|
+
|
|
154
|
+
const hashMarks = $derived.by(() => {
|
|
155
|
+
if (discreteSteps <= 10) {
|
|
156
|
+
return Array.from({ length: Math.max(discreteSteps - 1, 0) }, (_, i) => ({
|
|
157
|
+
key: `d-${i + 1}`,
|
|
158
|
+
left: (((i + 1) * step) / (max - min)) * 100,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Array.from({ length: 9 }, (_, i) => ({
|
|
163
|
+
key: `t-${i + 1}`,
|
|
164
|
+
left: (i + 1) * 10,
|
|
165
|
+
}));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const handlePointerDown = (e: PointerEvent) => {
|
|
169
|
+
if (showInput) return;
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
|
|
172
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
173
|
+
pointerDownPos = { x: e.clientX, y: e.clientY };
|
|
174
|
+
isClickFlag = true;
|
|
175
|
+
isInteracting = true;
|
|
176
|
+
|
|
177
|
+
if (wrapperRef) {
|
|
178
|
+
wrapperRect = wrapperRef.getBoundingClientRect();
|
|
179
|
+
scaleVal = wrapperRect.width / wrapperRef.offsetWidth;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const handlePointerMove = (e: PointerEvent) => {
|
|
184
|
+
if (!isInteracting || !pointerDownPos) return;
|
|
185
|
+
|
|
186
|
+
const dx = e.clientX - pointerDownPos.x;
|
|
187
|
+
const dy = e.clientY - pointerDownPos.y;
|
|
188
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
189
|
+
|
|
190
|
+
if (isClickFlag && distance > CLICK_THRESHOLD) {
|
|
191
|
+
isClickFlag = false;
|
|
192
|
+
isDragging = true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!isClickFlag) {
|
|
196
|
+
if (wrapperRect) {
|
|
197
|
+
if (e.clientX < wrapperRect.left) {
|
|
198
|
+
rubberStretchPx.set(computeRubberStretch(e.clientX, -1), { instant: true });
|
|
199
|
+
} else if (e.clientX > wrapperRect.right) {
|
|
200
|
+
rubberStretchPx.set(computeRubberStretch(e.clientX, 1), { instant: true });
|
|
201
|
+
} else {
|
|
202
|
+
rubberStretchPx.set(0, { instant: true });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const newValue = positionToValue(e.clientX);
|
|
207
|
+
fillPercent.set(percentFromValue(newValue), { instant: true });
|
|
208
|
+
onChange(roundValue(newValue, step));
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const handlePointerUp = (e: PointerEvent) => {
|
|
213
|
+
if (!isInteracting) return;
|
|
214
|
+
|
|
215
|
+
if (isClickFlag) {
|
|
216
|
+
const rawValue = positionToValue(e.clientX);
|
|
217
|
+
const steps = (max - min) / step;
|
|
218
|
+
const snappedValue = steps <= 10
|
|
219
|
+
? Math.max(min, Math.min(max, min + Math.round((rawValue - min) / step) * step))
|
|
220
|
+
: snapToDecile(rawValue, min, max);
|
|
221
|
+
|
|
222
|
+
fillPercent.set(percentFromValue(snappedValue));
|
|
223
|
+
onChange(roundValue(snappedValue, step));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (rubberStretchPx.current !== 0) {
|
|
227
|
+
rubberStretchPx.set(0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
isInteracting = false;
|
|
231
|
+
isDragging = false;
|
|
232
|
+
pointerDownPos = null;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handlePointerCancel = () => {
|
|
236
|
+
if (!isInteracting) return;
|
|
237
|
+
isInteracting = false;
|
|
238
|
+
isDragging = false;
|
|
239
|
+
rubberStretchPx.set(0, { instant: true });
|
|
240
|
+
pointerDownPos = null;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const handleInputSubmit = () => {
|
|
244
|
+
const parsed = Number.parseFloat(inputValue);
|
|
245
|
+
if (!Number.isNaN(parsed)) {
|
|
246
|
+
const clamped = Math.max(min, Math.min(max, parsed));
|
|
247
|
+
onChange(roundValue(clamped, step));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
showInput = false;
|
|
251
|
+
isValueHovered = false;
|
|
252
|
+
isValueEditable = false;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const handleValueClick = (e: MouseEvent) => {
|
|
256
|
+
if (!isValueEditable) return;
|
|
257
|
+
e.stopPropagation();
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
showInput = true;
|
|
260
|
+
inputValue = value.toFixed(decimalsForStep(step));
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const displayValue = $derived(value.toFixed(decimalsForStep(step)));
|
|
264
|
+
|
|
265
|
+
const trackStyle = $derived(`width:calc(100% + ${Math.abs(rubberStretchPx.current)}px);transform:translateX(${rubberStretchPx.current < 0 ? rubberStretchPx.current : 0}px);`);
|
|
266
|
+
const fillStyle = $derived(`width:${fillPercent.current}%;`);
|
|
267
|
+
const handleStyle = $derived(`left:max(5px, calc(${fillPercent.current}% - 9px));opacity:${handleOpacityMv.current};transform:translateY(-50%) scaleX(${handleScaleXMv.current}) scaleY(${handleScaleYMv.current});`);
|
|
268
|
+
</script>
|
|
269
|
+
|
|
270
|
+
<div bind:this={wrapperRef} class="dialkit-slider-wrapper">
|
|
271
|
+
<div
|
|
272
|
+
class={`dialkit-slider ${isActive ? 'dialkit-slider-active' : ''}`}
|
|
273
|
+
style={trackStyle}
|
|
274
|
+
onpointerdown={handlePointerDown}
|
|
275
|
+
onpointermove={handlePointerMove}
|
|
276
|
+
onpointerup={handlePointerUp}
|
|
277
|
+
onpointercancel={handlePointerCancel}
|
|
278
|
+
onmouseenter={() => (isHovered = true)}
|
|
279
|
+
onmouseleave={() => (isHovered = false)}
|
|
280
|
+
>
|
|
281
|
+
<div class="dialkit-slider-hashmarks">
|
|
282
|
+
{#each hashMarks as mark (mark.key)}
|
|
283
|
+
<div class="dialkit-slider-hashmark" style:left={`${mark.left}%`} />
|
|
284
|
+
{/each}
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<div class="dialkit-slider-fill" style={fillStyle} />
|
|
288
|
+
|
|
289
|
+
<div class="dialkit-slider-handle" style={handleStyle} />
|
|
290
|
+
|
|
291
|
+
<span bind:this={labelRef} class="dialkit-slider-label">
|
|
292
|
+
{label}
|
|
293
|
+
{#if shortcut}
|
|
294
|
+
<span class={`dialkit-shortcut-pill${shortcutActive ? ' dialkit-shortcut-pill-active' : ''}`}>
|
|
295
|
+
{formatSliderShortcut(shortcut)}
|
|
296
|
+
</span>
|
|
297
|
+
{/if}
|
|
298
|
+
</span>
|
|
299
|
+
|
|
300
|
+
{#if showInput}
|
|
301
|
+
<input
|
|
302
|
+
bind:this={inputRef}
|
|
303
|
+
type="text"
|
|
304
|
+
class="dialkit-slider-input"
|
|
305
|
+
value={inputValue}
|
|
306
|
+
oninput={(e) => (inputValue = (e.currentTarget as HTMLInputElement).value)}
|
|
307
|
+
onkeydown={(e) => {
|
|
308
|
+
if (e.key === 'Enter') handleInputSubmit();
|
|
309
|
+
else if (e.key === 'Escape') {
|
|
310
|
+
showInput = false;
|
|
311
|
+
isValueHovered = false;
|
|
312
|
+
}
|
|
313
|
+
}}
|
|
314
|
+
onblur={handleInputSubmit}
|
|
315
|
+
onclick={(e) => e.stopPropagation()}
|
|
316
|
+
onmousedown={(e) => e.stopPropagation()}
|
|
317
|
+
/>
|
|
318
|
+
{:else}
|
|
319
|
+
<span
|
|
320
|
+
bind:this={valueSpanRef}
|
|
321
|
+
class={`dialkit-slider-value ${isValueEditable ? 'dialkit-slider-value-editable' : ''}`}
|
|
322
|
+
onmouseenter={() => (isValueHovered = true)}
|
|
323
|
+
onmouseleave={() => (isValueHovered = false)}
|
|
324
|
+
onclick={handleValueClick}
|
|
325
|
+
onmousedown={(e) => isValueEditable && e.stopPropagation()}
|
|
326
|
+
style:cursor={isValueEditable ? 'text' : 'default'}
|
|
327
|
+
>
|
|
328
|
+
{displayValue}
|
|
329
|
+
</span>
|
|
330
|
+
{/if}
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ShortcutConfig } from 'dialkit/store';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
onChange: (value: number) => void;
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
step?: number;
|
|
9
|
+
unit?: string;
|
|
10
|
+
shortcut?: ShortcutConfig;
|
|
11
|
+
shortcutActive?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare const Slider: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type Slider = ReturnType<typeof Slider>;
|
|
15
|
+
export default Slider;
|
|
16
|
+
//# sourceMappingURL=Slider.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Slider.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/Slider.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGnD,KAAK,gBAAgB,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AA2SJ,QAAA,MAAM,MAAM,sDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { DialStore } from 'dialkit/store';
|
|
3
|
+
import type { SpringConfig } from 'dialkit/store';
|
|
4
|
+
import Folder from './Folder.svelte';
|
|
5
|
+
import Slider from './Slider.svelte';
|
|
6
|
+
import SegmentedControl from './SegmentedControl.svelte';
|
|
7
|
+
import SpringVisualization from './SpringVisualization.svelte';
|
|
8
|
+
|
|
9
|
+
let { panelId, path, label, spring, onChange } = $props<{
|
|
10
|
+
panelId: string;
|
|
11
|
+
path: string;
|
|
12
|
+
label: string;
|
|
13
|
+
spring: SpringConfig;
|
|
14
|
+
onChange: (spring: SpringConfig) => void;
|
|
15
|
+
}>();
|
|
16
|
+
|
|
17
|
+
let mode = $state<'simple' | 'advanced'>(DialStore.getSpringMode(panelId, path));
|
|
18
|
+
|
|
19
|
+
$effect(() => {
|
|
20
|
+
const unsub = DialStore.subscribe(panelId, () => {
|
|
21
|
+
mode = DialStore.getSpringMode(panelId, path);
|
|
22
|
+
});
|
|
23
|
+
return unsub;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const isSimpleMode = $derived(mode === 'simple');
|
|
27
|
+
|
|
28
|
+
const cache: {
|
|
29
|
+
simple: SpringConfig;
|
|
30
|
+
advanced: SpringConfig;
|
|
31
|
+
} = {
|
|
32
|
+
simple: spring.visualDuration !== undefined ? spring : { type: 'spring', visualDuration: 0.3, bounce: 0.2 },
|
|
33
|
+
advanced: spring.stiffness !== undefined ? spring : { type: 'spring', stiffness: 200, damping: 25, mass: 1 },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleModeChange = (newMode: string) => {
|
|
37
|
+
const typedMode = newMode as 'simple' | 'advanced';
|
|
38
|
+
|
|
39
|
+
if (isSimpleMode) {
|
|
40
|
+
cache.simple = spring;
|
|
41
|
+
} else {
|
|
42
|
+
cache.advanced = spring;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
DialStore.updateSpringMode(panelId, path, typedMode);
|
|
46
|
+
|
|
47
|
+
if (typedMode === 'simple') {
|
|
48
|
+
onChange(cache.simple);
|
|
49
|
+
} else {
|
|
50
|
+
onChange(cache.advanced);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleUpdate = (key: keyof SpringConfig, value: number) => {
|
|
55
|
+
if (isSimpleMode) {
|
|
56
|
+
const { stiffness, damping, mass, ...rest } = spring;
|
|
57
|
+
onChange({ ...rest, [key]: value });
|
|
58
|
+
} else {
|
|
59
|
+
const { visualDuration, bounce, ...rest } = spring;
|
|
60
|
+
onChange({ ...rest, [key]: value });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<Folder title={label} defaultOpen={true}>
|
|
66
|
+
<div style="display: flex; flex-direction: column; gap: 6px;">
|
|
67
|
+
<SpringVisualization {spring} isSimpleMode={isSimpleMode} />
|
|
68
|
+
|
|
69
|
+
<div class="dialkit-labeled-control">
|
|
70
|
+
<span class="dialkit-labeled-control-label">Type</span>
|
|
71
|
+
<SegmentedControl
|
|
72
|
+
options={[
|
|
73
|
+
{ value: 'simple', label: 'Time' },
|
|
74
|
+
{ value: 'advanced', label: 'Physics' },
|
|
75
|
+
]}
|
|
76
|
+
value={mode}
|
|
77
|
+
onChange={handleModeChange}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{#if isSimpleMode}
|
|
82
|
+
<Slider
|
|
83
|
+
label="Duration"
|
|
84
|
+
value={spring.visualDuration ?? 0.3}
|
|
85
|
+
onChange={(v) => handleUpdate('visualDuration', v)}
|
|
86
|
+
min={0.1}
|
|
87
|
+
max={1}
|
|
88
|
+
step={0.05}
|
|
89
|
+
unit="s"
|
|
90
|
+
/>
|
|
91
|
+
<Slider
|
|
92
|
+
label="Bounce"
|
|
93
|
+
value={spring.bounce ?? 0.2}
|
|
94
|
+
onChange={(v) => handleUpdate('bounce', v)}
|
|
95
|
+
min={0}
|
|
96
|
+
max={1}
|
|
97
|
+
step={0.05}
|
|
98
|
+
/>
|
|
99
|
+
{:else}
|
|
100
|
+
<Slider
|
|
101
|
+
label="Stiffness"
|
|
102
|
+
value={spring.stiffness ?? 400}
|
|
103
|
+
onChange={(v) => handleUpdate('stiffness', v)}
|
|
104
|
+
min={1}
|
|
105
|
+
max={1000}
|
|
106
|
+
step={10}
|
|
107
|
+
/>
|
|
108
|
+
<Slider
|
|
109
|
+
label="Damping"
|
|
110
|
+
value={spring.damping ?? 17}
|
|
111
|
+
onChange={(v) => handleUpdate('damping', v)}
|
|
112
|
+
min={1}
|
|
113
|
+
max={100}
|
|
114
|
+
step={1}
|
|
115
|
+
/>
|
|
116
|
+
<Slider
|
|
117
|
+
label="Mass"
|
|
118
|
+
value={spring.mass ?? 1}
|
|
119
|
+
onChange={(v) => handleUpdate('mass', v)}
|
|
120
|
+
min={0.1}
|
|
121
|
+
max={10}
|
|
122
|
+
step={0.1}
|
|
123
|
+
/>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
</Folder>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SpringConfig } from 'dialkit/store';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
panelId: string;
|
|
4
|
+
path: string;
|
|
5
|
+
label: string;
|
|
6
|
+
spring: SpringConfig;
|
|
7
|
+
onChange: (spring: SpringConfig) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const SpringControl: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type SpringControl = ReturnType<typeof SpringControl>;
|
|
11
|
+
export default SpringControl;
|
|
12
|
+
//# sourceMappingURL=SpringControl.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpringControl.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/SpringControl.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAMjD,KAAK,gBAAgB,GAAG;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAC1C,CAAC;AAoFJ,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SpringConfig } from 'dialkit/store';
|
|
3
|
+
|
|
4
|
+
let { spring, isSimpleMode } = $props<{ spring: SpringConfig; isSimpleMode: boolean }>();
|
|
5
|
+
|
|
6
|
+
function generateSpringCurve(
|
|
7
|
+
stiffness: number,
|
|
8
|
+
damping: number,
|
|
9
|
+
mass: number,
|
|
10
|
+
duration: number
|
|
11
|
+
): [number, number][] {
|
|
12
|
+
const points: [number, number][] = [];
|
|
13
|
+
const steps = 100;
|
|
14
|
+
const dt = duration / steps;
|
|
15
|
+
|
|
16
|
+
let position = 0;
|
|
17
|
+
let velocity = 0;
|
|
18
|
+
const target = 1;
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i <= steps; i++) {
|
|
21
|
+
const time = i * dt;
|
|
22
|
+
points.push([time, position]);
|
|
23
|
+
|
|
24
|
+
const springForce = -stiffness * (position - target);
|
|
25
|
+
const dampingForce = -damping * velocity;
|
|
26
|
+
const acceleration = (springForce + dampingForce) / mass;
|
|
27
|
+
|
|
28
|
+
velocity += acceleration * dt;
|
|
29
|
+
position += velocity * dt;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return points;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const width = 256;
|
|
36
|
+
const height = 140;
|
|
37
|
+
|
|
38
|
+
const pathData = $derived.by(() => {
|
|
39
|
+
let stiffness: number;
|
|
40
|
+
let damping: number;
|
|
41
|
+
let mass: number;
|
|
42
|
+
|
|
43
|
+
if (isSimpleMode) {
|
|
44
|
+
const visualDuration = spring.visualDuration ?? 0.3;
|
|
45
|
+
const bounce = spring.bounce ?? 0.2;
|
|
46
|
+
mass = 1;
|
|
47
|
+
stiffness = Math.pow((2 * Math.PI) / visualDuration, 2);
|
|
48
|
+
const dampingRatio = 1 - bounce;
|
|
49
|
+
damping = 2 * dampingRatio * Math.sqrt(stiffness * mass);
|
|
50
|
+
} else {
|
|
51
|
+
stiffness = spring.stiffness ?? 400;
|
|
52
|
+
damping = spring.damping ?? 17;
|
|
53
|
+
mass = spring.mass ?? 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const duration = 2;
|
|
57
|
+
const points = generateSpringCurve(stiffness, damping, mass, duration);
|
|
58
|
+
const values = points.map(([, v]) => v);
|
|
59
|
+
const minValue = Math.min(...values);
|
|
60
|
+
const maxValue = Math.max(...values);
|
|
61
|
+
const valueRange = maxValue - minValue;
|
|
62
|
+
|
|
63
|
+
return points
|
|
64
|
+
.map(([time, value], i) => {
|
|
65
|
+
const x = (time / duration) * width;
|
|
66
|
+
const normalizedValue = (value - minValue) / (valueRange || 1);
|
|
67
|
+
const y = height - (normalizedValue * height * 0.6 + height * 0.2);
|
|
68
|
+
return `${i === 0 ? 'M' : 'L'} ${x} ${y}`;
|
|
69
|
+
})
|
|
70
|
+
.join(' ');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const gridLines = $derived.by(() => {
|
|
74
|
+
const lines: { x1: number; y1: number; x2: number; y2: number }[] = [];
|
|
75
|
+
for (let i = 1; i < 4; i++) {
|
|
76
|
+
const x = (width / 4) * i;
|
|
77
|
+
const y = (height / 4) * i;
|
|
78
|
+
lines.push({ x1: x, y1: 0, x2: x, y2: height });
|
|
79
|
+
lines.push({ x1: 0, y1: y, x2: width, y2: y });
|
|
80
|
+
}
|
|
81
|
+
return lines;
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<svg viewBox={`0 0 ${width} ${height}`} class="dialkit-spring-viz">
|
|
86
|
+
{#each gridLines as line}
|
|
87
|
+
<line
|
|
88
|
+
x1={line.x1}
|
|
89
|
+
y1={line.y1}
|
|
90
|
+
x2={line.x2}
|
|
91
|
+
y2={line.y2}
|
|
92
|
+
stroke="rgba(255, 255, 255, 0.08)"
|
|
93
|
+
stroke-width="1"
|
|
94
|
+
/>
|
|
95
|
+
{/each}
|
|
96
|
+
|
|
97
|
+
<line
|
|
98
|
+
x1={0}
|
|
99
|
+
y1={height / 2}
|
|
100
|
+
x2={width}
|
|
101
|
+
y2={height / 2}
|
|
102
|
+
stroke="rgba(255, 255, 255, 0.15)"
|
|
103
|
+
stroke-width="1"
|
|
104
|
+
stroke-dasharray="4,4"
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<path
|
|
108
|
+
d={pathData}
|
|
109
|
+
fill="none"
|
|
110
|
+
stroke="rgba(255, 255, 255, 0.6)"
|
|
111
|
+
stroke-width="2"
|
|
112
|
+
stroke-linecap="round"
|
|
113
|
+
stroke-linejoin="round"
|
|
114
|
+
/>
|
|
115
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SpringConfig } from 'dialkit/store';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
spring: SpringConfig;
|
|
4
|
+
isSimpleMode: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const SpringVisualization: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type SpringVisualization = ReturnType<typeof SpringVisualization>;
|
|
8
|
+
export default SpringVisualization;
|
|
9
|
+
//# sourceMappingURL=SpringVisualization.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpringVisualization.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/SpringVisualization.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEjD,KAAK,gBAAgB,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC;AAiGzE,QAAA,MAAM,mBAAmB,sDAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { label, value, onChange, placeholder } = $props<{
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
}>();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="dialkit-text-control">
|
|
11
|
+
<label class="dialkit-text-label">{label}</label>
|
|
12
|
+
<input
|
|
13
|
+
type="text"
|
|
14
|
+
class="dialkit-text-input"
|
|
15
|
+
value={value}
|
|
16
|
+
placeholder={placeholder}
|
|
17
|
+
oninput={(e) => onChange((e.currentTarget as HTMLInputElement).value)}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const TextControl: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type TextControl = ReturnType<typeof TextControl>;
|
|
9
|
+
export default TextControl;
|
|
10
|
+
//# sourceMappingURL=TextControl.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextControl.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/TextControl.svelte.ts"],"names":[],"mappings":"AAGC,KAAK,gBAAgB,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAYJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import SegmentedControl from './SegmentedControl.svelte';
|
|
3
|
+
import type { ShortcutConfig } from 'dialkit/store';
|
|
4
|
+
import { formatToggleShortcut } from '../../shortcut-utils';
|
|
5
|
+
|
|
6
|
+
let { label, checked, onChange, shortcut, shortcutActive = false } = $props<{
|
|
7
|
+
label: string;
|
|
8
|
+
checked: boolean;
|
|
9
|
+
onChange: (checked: boolean) => void;
|
|
10
|
+
shortcut?: ShortcutConfig;
|
|
11
|
+
shortcutActive?: boolean;
|
|
12
|
+
}>();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="dialkit-labeled-control">
|
|
16
|
+
<span class="dialkit-labeled-control-label">
|
|
17
|
+
{label}
|
|
18
|
+
{#if shortcut}
|
|
19
|
+
<span class={`dialkit-shortcut-pill${shortcutActive ? ' dialkit-shortcut-pill-active' : ''}`}>
|
|
20
|
+
{formatToggleShortcut(shortcut)}
|
|
21
|
+
</span>
|
|
22
|
+
{/if}
|
|
23
|
+
</span>
|
|
24
|
+
<SegmentedControl
|
|
25
|
+
options={[
|
|
26
|
+
{ value: 'off', label: 'Off' },
|
|
27
|
+
{ value: 'on', label: 'On' },
|
|
28
|
+
]}
|
|
29
|
+
value={checked ? 'on' : 'off'}
|
|
30
|
+
onChange={(val) => onChange(val === 'on')}
|
|
31
|
+
/>
|
|
32
|
+
</div>
|