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