fragment-tools 0.2.2 → 0.2.4
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/package.json +1 -1
- package/src/client/app/components/Build.svelte +64 -0
- package/src/client/app/{ui → components}/Preview.svelte +1 -2
- package/src/client/app/inputs/MIDI.js +0 -1
- package/src/client/app/modules/MidiPanel.svelte +23 -13
- package/src/client/app/modules/Params.svelte +31 -16
- package/src/client/app/state/Sketch.svelte.js +77 -73
- package/src/client/app/state/layout.svelte.js +8 -2
- package/src/client/app/state/rendering.svelte.js +23 -5
- package/src/client/app/state/utils.svelte.js +28 -4
- package/src/client/app/ui/Field.svelte +13 -11
- package/src/client/app/ui/FieldGroup.svelte +5 -2
- package/src/client/app/ui/FieldSection.svelte +4 -4
- package/src/client/app/ui/Layout.svelte +1 -1
- package/src/client/app/ui/LayoutBuild.svelte +54 -0
- package/src/client/app/ui/LayoutColumn.svelte +2 -2
- package/src/client/app/ui/LayoutComponent.svelte +14 -4
- package/src/client/app/ui/LayoutResizer.svelte +11 -2
- package/src/client/app/ui/LayoutRow.svelte +2 -2
- package/src/client/app/ui/fields/ButtonInput.svelte +3 -3
- package/src/client/app/ui/fields/ColorInput.svelte +23 -13
- package/src/client/app/ui/fields/ImportInput.svelte +52 -0
- package/src/client/app/ui/fields/Input.svelte +4 -2
- package/src/client/app/ui/fields/IntervalInput.svelte +8 -6
- package/src/client/app/ui/fields/ProgressInput.svelte +47 -17
- package/src/client/app/ui/fields/Select.svelte +35 -41
- package/src/client/app/ui/fields/VectorInput.svelte +63 -18
- package/src/client/app/utils/fields.utils.js +70 -48
- package/src/client/app/utils/math.utils.js +6 -0
- package/src/client/public/css/global.css +14 -0
- package/src/client/app/ui/Build.svelte +0 -91
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import ButtonInput from './fields/ButtonInput.svelte';
|
|
10
10
|
import ImageInput from './fields/ImageInput.svelte';
|
|
11
11
|
import IntervalInput from './fields/IntervalInput.svelte';
|
|
12
|
-
import { fieldTypes
|
|
12
|
+
import { fieldTypes } from '../utils/fields.utils.js';
|
|
13
13
|
|
|
14
14
|
const fields = {
|
|
15
15
|
[`${fieldTypes.SELECT}`]: Select,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
[`${fieldTypes.COLOR}`]: ColorInput,
|
|
22
22
|
[`${fieldTypes.BUTTON}`]: ButtonInput,
|
|
23
23
|
[`${fieldTypes.DOWNLOAD}`]: ButtonInput,
|
|
24
|
+
[`${fieldTypes.IMPORT}`]: ImportInput,
|
|
24
25
|
[`${fieldTypes.IMAGE}`]: ImageInput,
|
|
25
26
|
[`${fieldTypes.INTERVAL}`]: IntervalInput,
|
|
26
27
|
};
|
|
@@ -35,10 +36,12 @@
|
|
|
35
36
|
import { inferFieldType } from '../utils/fields.utils.js';
|
|
36
37
|
import IconTriggers from '../components/IconTriggers.svelte';
|
|
37
38
|
import IconLocked from '../components/IconLocked.svelte';
|
|
39
|
+
import ImportInput from './fields/ImportInput.svelte';
|
|
40
|
+
import { deepEqual } from '../state/utils.svelte';
|
|
38
41
|
|
|
39
42
|
let {
|
|
40
43
|
key,
|
|
41
|
-
value
|
|
44
|
+
value,
|
|
42
45
|
initialValue = value,
|
|
43
46
|
context = null,
|
|
44
47
|
params = $bindable({}),
|
|
@@ -89,7 +92,7 @@
|
|
|
89
92
|
let fieldType = $derived(inferFieldType({ type, value, params, key }));
|
|
90
93
|
let fieldProps = $derived(composeFieldProps(params, disabled));
|
|
91
94
|
let onTrigger = $derived(frameDebounce(onTriggers[fieldType]));
|
|
92
|
-
let
|
|
95
|
+
let Component = $derived(fields[fieldType]);
|
|
93
96
|
let triggerable = $derived(
|
|
94
97
|
params.triggerable !== false &&
|
|
95
98
|
((fieldType === fieldTypes.NUMBER &&
|
|
@@ -115,12 +118,17 @@
|
|
|
115
118
|
context,
|
|
116
119
|
};
|
|
117
120
|
}
|
|
121
|
+
|
|
122
|
+
function hasChanged(current, next) {
|
|
123
|
+
const changed = !deepEqual(current, next);
|
|
124
|
+
return changed;
|
|
125
|
+
}
|
|
118
126
|
</script>
|
|
119
127
|
|
|
120
128
|
<div
|
|
121
129
|
class="field"
|
|
122
130
|
class:disabled
|
|
123
|
-
class:changed={!disabled && hasChanged(
|
|
131
|
+
class:changed={!disabled && hasChanged(value, initialValue)}
|
|
124
132
|
style="--index: {index};"
|
|
125
133
|
>
|
|
126
134
|
<FieldSection
|
|
@@ -151,13 +159,7 @@
|
|
|
151
159
|
{/if}
|
|
152
160
|
</div>
|
|
153
161
|
{/snippet}
|
|
154
|
-
<
|
|
155
|
-
this={input}
|
|
156
|
-
{value}
|
|
157
|
-
{...fieldProps}
|
|
158
|
-
{onchange}
|
|
159
|
-
onclick={onTrigger}
|
|
160
|
-
/>
|
|
162
|
+
<Component {value} {...fieldProps} {onchange} onclick={onTrigger} />
|
|
161
163
|
{@render children?.()}
|
|
162
164
|
</FieldSection>
|
|
163
165
|
{#if triggerable}
|
|
@@ -79,7 +79,8 @@
|
|
|
79
79
|
transition: opacity 0.1s ease;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
.header__action:hover .header__icon
|
|
82
|
+
:global(body:not(.fragment-dragging)) .header__action:hover .header__icon,
|
|
83
|
+
.header__action:focus-visible .header__icon {
|
|
83
84
|
opacity: 1;
|
|
84
85
|
}
|
|
85
86
|
|
|
@@ -98,7 +99,9 @@
|
|
|
98
99
|
transition: opacity 0.1s ease;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
:global(body:not(.fragment-dragging))
|
|
103
|
+
.header__action:hover
|
|
104
|
+
.field-group__name,
|
|
102
105
|
.header__action:focus-visible .field-group__name {
|
|
103
106
|
opacity: 1;
|
|
104
107
|
}
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
grid-template-columns: 1fr;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
.field__section:hover .field__label,
|
|
51
|
+
:global(body:not(.fragment-dragging)) .field__section:hover .field__label,
|
|
52
52
|
.field__section:focus-within .field__label {
|
|
53
53
|
opacity: 1;
|
|
54
54
|
}
|
|
@@ -102,9 +102,9 @@
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
.field__label:focus-visible {
|
|
105
|
-
outline:
|
|
106
|
-
|
|
107
|
-
border-radius:
|
|
105
|
+
outline: 2px var(--color-active) solid;
|
|
106
|
+
outline-offset: 2px;
|
|
107
|
+
border-radius: 1px;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
.field__section.secondary {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import Root from './LayoutRoot.svelte';
|
|
3
3
|
import Column from './LayoutColumn.svelte';
|
|
4
|
-
import Build from '
|
|
4
|
+
import Build from '../components/Build.svelte';
|
|
5
5
|
import Row from './LayoutRow.svelte';
|
|
6
6
|
import ModuleRenderer from './ModuleRenderer.svelte';
|
|
7
7
|
import { layout } from '../state/layout.svelte.js';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Monitor from '../modules/Monitor.svelte';
|
|
3
|
+
import Params from '../modules/Params.svelte';
|
|
4
|
+
import FloatingParams from './FloatingParams.svelte';
|
|
5
|
+
import Column from './LayoutColumn.svelte';
|
|
6
|
+
import Row from './LayoutRow.svelte';
|
|
7
|
+
|
|
8
|
+
let { buildConfig = {}, sketch, sketchKey } = $props();
|
|
9
|
+
|
|
10
|
+
let gui = $derived(buildConfig.gui ?? {});
|
|
11
|
+
let layout = $derived(buildConfig.layout ?? {});
|
|
12
|
+
let headless = $derived(layout.headless ?? false);
|
|
13
|
+
let resizable = $derived(layout.resizable ?? false);
|
|
14
|
+
|
|
15
|
+
let guiOutput = $derived(gui.output ?? true);
|
|
16
|
+
let guiAlign = $derived(gui.align ?? 'right');
|
|
17
|
+
let guiHidden = $derived(gui?.hidden);
|
|
18
|
+
let guiSize = $derived(gui?.size ?? 0.25);
|
|
19
|
+
let guiMinimize = $derived(gui?.minimize);
|
|
20
|
+
let guiPosition = $derived(gui?.position);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if guiPosition === 'fixed'}
|
|
24
|
+
<Row>
|
|
25
|
+
{#if guiAlign === 'left'}
|
|
26
|
+
<Column size={guiSize} {resizable}>
|
|
27
|
+
<Params {headless} />
|
|
28
|
+
</Column>
|
|
29
|
+
<Column size={1 - guiSize} {resizable}>
|
|
30
|
+
<Monitor {headless} params={{ selected: sketchKey }} />
|
|
31
|
+
</Column>
|
|
32
|
+
{:else}
|
|
33
|
+
<Column size={1 - guiSize} {resizable}>
|
|
34
|
+
<Monitor {headless} params={{ selected: sketchKey }} />
|
|
35
|
+
</Column>
|
|
36
|
+
<Column size={guiSize} {resizable}>
|
|
37
|
+
<Params {headless} />
|
|
38
|
+
</Column>
|
|
39
|
+
{/if}
|
|
40
|
+
</Row>
|
|
41
|
+
{:else}
|
|
42
|
+
<Row>
|
|
43
|
+
<Monitor {headless} {sketchKey} params={{ selected: sketchKey }} />
|
|
44
|
+
{#if gui}
|
|
45
|
+
<FloatingParams
|
|
46
|
+
output={guiOutput}
|
|
47
|
+
align={guiAlign}
|
|
48
|
+
size={guiSize}
|
|
49
|
+
hidden={guiHidden}
|
|
50
|
+
minimize={guiMinimize}
|
|
51
|
+
/>
|
|
52
|
+
{/if}
|
|
53
|
+
</Row>
|
|
54
|
+
{/if}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import LayoutComponent from './LayoutComponent.svelte';
|
|
3
3
|
|
|
4
|
-
let { size = 1, children } = $props();
|
|
4
|
+
let { size = 1, children, resizable = true } = $props();
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
|
-
<LayoutComponent
|
|
7
|
+
<LayoutComponent type="column" {size} {resizable}>
|
|
8
8
|
{@render children?.()}
|
|
9
9
|
</LayoutComponent>
|
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
import Toolbar from './LayoutToolbar.svelte';
|
|
5
5
|
import Resizer from './LayoutResizer.svelte';
|
|
6
6
|
import ModuleRenderer from './ModuleRenderer.svelte';
|
|
7
|
-
import Preview from '
|
|
7
|
+
import Preview from '../components/Preview.svelte';
|
|
8
8
|
import LayoutComponent from './LayoutComponent.svelte';
|
|
9
9
|
|
|
10
|
-
let {
|
|
10
|
+
let {
|
|
11
|
+
id = layout.getID(),
|
|
12
|
+
size = 1,
|
|
13
|
+
type = 'column',
|
|
14
|
+
children,
|
|
15
|
+
resizable = true,
|
|
16
|
+
} = $props();
|
|
11
17
|
|
|
12
18
|
let parent = getContext('parent');
|
|
13
19
|
let isColumn = $derived(type === 'column');
|
|
@@ -120,7 +126,7 @@
|
|
|
120
126
|
{/if}
|
|
121
127
|
{/each}
|
|
122
128
|
{:else}
|
|
123
|
-
{@render children()}
|
|
129
|
+
{@render children?.()}
|
|
124
130
|
{/if}
|
|
125
131
|
{#if layout.editing && (isRoot || (childComponents.length === 1 && childComponents[0].type === 'module') || childComponents.length === 0)}
|
|
126
132
|
<Toolbar
|
|
@@ -134,7 +140,11 @@
|
|
|
134
140
|
{/if}
|
|
135
141
|
</div>
|
|
136
142
|
{#if !isRoot}
|
|
137
|
-
<Resizer
|
|
143
|
+
<Resizer
|
|
144
|
+
direction={isColumn ? 'vertical' : 'horizontal'}
|
|
145
|
+
{current}
|
|
146
|
+
disabled={!resizable}
|
|
147
|
+
/>
|
|
138
148
|
{/if}
|
|
139
149
|
|
|
140
150
|
<style>
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
import { layout } from '../state/layout.svelte.js';
|
|
12
12
|
import { clamp, map } from '../utils/math.utils.js';
|
|
13
13
|
|
|
14
|
-
let {
|
|
14
|
+
let {
|
|
15
|
+
direction = DIRECTIONS.HORIZONTAL,
|
|
16
|
+
current,
|
|
17
|
+
disabled = false,
|
|
18
|
+
} = $props();
|
|
15
19
|
|
|
16
20
|
let visible = $state(false);
|
|
17
21
|
let isDragging = $state(false);
|
|
@@ -133,6 +137,7 @@
|
|
|
133
137
|
class="resizer resizer--{direction}"
|
|
134
138
|
class:dragging={isDragging}
|
|
135
139
|
class:editing={layout.editing}
|
|
140
|
+
class:disabled
|
|
136
141
|
>
|
|
137
142
|
<div
|
|
138
143
|
class="resizer-hover"
|
|
@@ -150,6 +155,10 @@
|
|
|
150
155
|
position: relative;
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
.resizer.disabled {
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
153
162
|
[class~='resizer']:last-of-type {
|
|
154
163
|
display: none;
|
|
155
164
|
}
|
|
@@ -177,7 +186,7 @@
|
|
|
177
186
|
opacity: 0.1;
|
|
178
187
|
}
|
|
179
188
|
|
|
180
|
-
.resizer .resizer-hover:hover:before {
|
|
189
|
+
:global(body:not(.fragment-dragging)) .resizer .resizer-hover:hover:before {
|
|
181
190
|
opacity: 0.25;
|
|
182
191
|
}
|
|
183
192
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import LayoutComponent from './LayoutComponent.svelte';
|
|
3
3
|
|
|
4
|
-
let { size = 1, children } = $props();
|
|
4
|
+
let { size = 1, children, resizable = true } = $props();
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
|
-
<LayoutComponent
|
|
7
|
+
<LayoutComponent type="row" {size} {resizable}>
|
|
8
8
|
{@render children?.()}
|
|
9
9
|
</LayoutComponent>
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
color: var(--color-text-input-disabled);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
.button:hover {
|
|
60
|
+
:global(body:not(.fragment-dragging)) .button:hover {
|
|
61
61
|
color: var(--color-text);
|
|
62
62
|
|
|
63
63
|
box-shadow: inset 0 0 0 1px
|
|
64
64
|
var(--box-shadow-color-active, var(--color-active));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
.button:active,
|
|
68
|
-
.button:focus-visible {
|
|
67
|
+
:global(body:not(.fragment-dragging)) .button:active,
|
|
68
|
+
:global(body:not(.fragment-dragging)) .button:focus-visible {
|
|
69
69
|
box-shadow: 0 0 0 2px
|
|
70
70
|
var(--box-shadow-color-active, var(--color-active));
|
|
71
71
|
}
|
|
@@ -3,19 +3,27 @@
|
|
|
3
3
|
import TextInput from './TextInput.svelte';
|
|
4
4
|
import Field from '../Field.svelte';
|
|
5
5
|
|
|
6
|
-
let {
|
|
6
|
+
let {
|
|
7
|
+
value,
|
|
8
|
+
context = null,
|
|
9
|
+
key = '',
|
|
10
|
+
disabled = false,
|
|
11
|
+
onchange,
|
|
12
|
+
} = $props();
|
|
7
13
|
|
|
8
14
|
let format = $derived(color.getColorFormat(value));
|
|
9
15
|
let hexValue = $derived(color.toHex(value, format));
|
|
10
16
|
let textValue = $state();
|
|
11
17
|
let alpha = $state(1);
|
|
12
|
-
let hasAlpha = $derived(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
let hasAlpha = $derived(
|
|
19
|
+
[
|
|
20
|
+
color.FORMATS.RGBA_STRING,
|
|
21
|
+
color.FORMATS.VEC4_STRING,
|
|
22
|
+
color.FORMATS.VEC4_ARRAY,
|
|
23
|
+
color.FORMATS.RGBA_OBJECT,
|
|
24
|
+
color.FORMATS.HSLA_STRING,
|
|
25
|
+
].includes(format),
|
|
26
|
+
);
|
|
19
27
|
|
|
20
28
|
$effect(() => {
|
|
21
29
|
if (hasAlpha) {
|
|
@@ -24,7 +32,7 @@
|
|
|
24
32
|
} else {
|
|
25
33
|
alpha = 1;
|
|
26
34
|
}
|
|
27
|
-
})
|
|
35
|
+
});
|
|
28
36
|
|
|
29
37
|
$effect(() => {
|
|
30
38
|
textValue = color.toString(value, format)?.toLowerCase();
|
|
@@ -36,10 +44,10 @@
|
|
|
36
44
|
if (format === newFormat) {
|
|
37
45
|
onchange(newColor);
|
|
38
46
|
} else {
|
|
39
|
-
const components = color.toComponents(newColor);
|
|
47
|
+
const components = color.toComponents(newColor);
|
|
40
48
|
const [r, g, b] = components;
|
|
41
49
|
|
|
42
|
-
switch(format) {
|
|
50
|
+
switch (format) {
|
|
43
51
|
case color.FORMATS.RGB_OBJECT:
|
|
44
52
|
onchange({ r, g, b });
|
|
45
53
|
break;
|
|
@@ -47,7 +55,9 @@
|
|
|
47
55
|
onchange({ r, g, b, a: alpha });
|
|
48
56
|
break;
|
|
49
57
|
default:
|
|
50
|
-
onchange(
|
|
58
|
+
onchange(
|
|
59
|
+
color.componentsToFormat([r, g, b, alpha], format),
|
|
60
|
+
);
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
}
|
|
@@ -207,7 +217,7 @@
|
|
|
207
217
|
pointer-events: none;
|
|
208
218
|
}
|
|
209
219
|
|
|
210
|
-
.mirror:hover {
|
|
220
|
+
:global(body:not(.fragment-dragging)) .mirror:hover {
|
|
211
221
|
box-shadow: inset 0 0 0 1px var(--box-shadow-color, var(--color-active));
|
|
212
222
|
}
|
|
213
223
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import ButtonInput from './ButtonInput.svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
value,
|
|
6
|
+
label = 'import',
|
|
7
|
+
disabled = false,
|
|
8
|
+
title = '',
|
|
9
|
+
accept,
|
|
10
|
+
readAs = 'readAsText',
|
|
11
|
+
children,
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
/** @type {HTMLInputElement}*/
|
|
15
|
+
let input;
|
|
16
|
+
|
|
17
|
+
let fileReader = new FileReader();
|
|
18
|
+
fileReader.onload = (event) => {
|
|
19
|
+
value(event);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function handleClick(event) {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
input.click();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleChange(event) {
|
|
28
|
+
if (event.target.files.length > 0) {
|
|
29
|
+
const readAsFn = fileReader[readAs];
|
|
30
|
+
|
|
31
|
+
if (!readAsFn) {
|
|
32
|
+
console.error(
|
|
33
|
+
`readAs: '${readAs}' is not a function of FileReader.`,
|
|
34
|
+
);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
readAsFn.call(fileReader, event.target.files[0]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<ButtonInput onclick={handleClick} {disabled} {label} {title}>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</ButtonInput>
|
|
46
|
+
<input
|
|
47
|
+
class="visually-hidden"
|
|
48
|
+
onchange={handleChange}
|
|
49
|
+
type="file"
|
|
50
|
+
bind:this={input}
|
|
51
|
+
{accept}
|
|
52
|
+
/>
|
|
@@ -53,11 +53,13 @@
|
|
|
53
53
|
box-shadow: inset 0 0 0 1px var(--color-border-input);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
:global(body:not(.fragment-dragging))
|
|
57
|
+
.input-container:not(.disabled):hover {
|
|
57
58
|
box-shadow: inset 0 0 0 1px var(--color-active);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
:global(body:not(.fragment-dragging))
|
|
62
|
+
.input-container:not(.disabled):focus-within {
|
|
61
63
|
box-shadow: 0 0 0 2px var(--color-active);
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
/** @type {DOMRect}*/
|
|
22
22
|
let rect;
|
|
23
23
|
/** @type {boolean}*/
|
|
24
|
-
let isDragging = false;
|
|
24
|
+
let isDragging = $state(false);
|
|
25
25
|
|
|
26
26
|
let proximityIndex = -1;
|
|
27
27
|
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
* @param {MouseEvent} event
|
|
31
31
|
*/
|
|
32
32
|
function handleMouseDown(event) {
|
|
33
|
-
document.body.
|
|
33
|
+
document.body.classList.add('fragment-dragging');
|
|
34
|
+
|
|
34
35
|
document.addEventListener('mousemove', handleMouseMove);
|
|
35
36
|
document.addEventListener('mouseup', handleMouseUp);
|
|
36
37
|
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function handleMouseUp() {
|
|
88
|
-
document.body.
|
|
89
|
+
document.body.classList.remove('fragment-dragging');
|
|
89
90
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
90
91
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
91
92
|
|
|
@@ -119,9 +120,10 @@
|
|
|
119
120
|
<div class="interval-input" class:disabled>
|
|
120
121
|
<FieldInputRow --grid-template-columns="1fr 0.5fr">
|
|
121
122
|
<div
|
|
122
|
-
class="range
|
|
123
|
+
class="range"
|
|
124
|
+
class:dragging={isDragging}
|
|
123
125
|
bind:this={node}
|
|
124
|
-
|
|
126
|
+
onmousedown={handleMouseDown}
|
|
125
127
|
>
|
|
126
128
|
<div class="handler" style="--position: {p1};" />
|
|
127
129
|
<div class="filler" style="--p1: {p1}; --p2: {p2};"></div>
|
|
@@ -189,7 +191,7 @@
|
|
|
189
191
|
container-type: size;
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
.range:hover {
|
|
194
|
+
:global(body:not(.fragment-dragging)) .range:hover {
|
|
193
195
|
box-shadow: inset 0 0 0 1px var(--color-active);
|
|
194
196
|
}
|
|
195
197
|
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import
|
|
2
|
+
import Keyboard from '../../inputs/Keyboard.js';
|
|
3
3
|
import { map, clamp, roundToStep } from '../../utils/math.utils.js';
|
|
4
4
|
|
|
5
|
-
let {
|
|
6
|
-
value,
|
|
7
|
-
min,
|
|
8
|
-
max,
|
|
9
|
-
step,
|
|
10
|
-
context = null,
|
|
11
|
-
key = '',
|
|
12
|
-
disabled = false,
|
|
13
|
-
onchange,
|
|
14
|
-
} = $props();
|
|
5
|
+
let { value, min, max, step, disabled = false, onchange } = $props();
|
|
15
6
|
|
|
16
7
|
let node;
|
|
17
8
|
let rect;
|
|
18
9
|
|
|
19
|
-
let isDragging = false;
|
|
10
|
+
let isDragging = $state(false);
|
|
11
|
+
let steppedValue = $derived(roundToStep(value, step));
|
|
20
12
|
|
|
21
13
|
// handlers
|
|
22
14
|
function handleMouseDown(event) {
|
|
15
|
+
document.body.classList.add('fragment-dragging');
|
|
23
16
|
document.addEventListener('mousemove', handleMouseMove);
|
|
24
17
|
document.addEventListener('mouseup', handleMouseUp);
|
|
25
18
|
|
|
@@ -47,24 +40,59 @@
|
|
|
47
40
|
}
|
|
48
41
|
}
|
|
49
42
|
|
|
43
|
+
function handleKeyDown(event) {
|
|
44
|
+
const direction = ['ArrowUp', 'ArrowRight'].includes(event.key)
|
|
45
|
+
? 1
|
|
46
|
+
: ['ArrowDown', 'ArrowLeft'].includes(event.key)
|
|
47
|
+
? -1
|
|
48
|
+
: 0;
|
|
49
|
+
|
|
50
|
+
const diff = Keyboard.getStepFromEvent(event) * step;
|
|
51
|
+
|
|
52
|
+
if (direction !== 0) {
|
|
53
|
+
const newValue = clamp(
|
|
54
|
+
roundToStep(value + direction * diff, step),
|
|
55
|
+
min,
|
|
56
|
+
max,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (newValue !== value) {
|
|
60
|
+
onchange(newValue);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
function handleMouseUp() {
|
|
66
|
+
document.body.classList.remove('fragment-dragging');
|
|
51
67
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
52
68
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
53
69
|
|
|
54
70
|
isDragging = false;
|
|
55
71
|
}
|
|
56
72
|
|
|
57
|
-
let progress = $derived(
|
|
73
|
+
let progress = $derived(
|
|
74
|
+
clamp(map(steppedValue, min, max, 0, 1), 0.0001, 1),
|
|
75
|
+
);
|
|
58
76
|
let opacity = $derived(progress > 0 ? 1 : 0);
|
|
59
77
|
</script>
|
|
60
78
|
|
|
61
79
|
<div
|
|
62
|
-
class="progress
|
|
80
|
+
class="progress"
|
|
63
81
|
bind:this={node}
|
|
64
82
|
onmousedown={handleMouseDown}
|
|
83
|
+
onkeydown={handleKeyDown}
|
|
65
84
|
class:disabled
|
|
85
|
+
class:dragging={isDragging}
|
|
86
|
+
role="slider"
|
|
87
|
+
aria-valuemin={min}
|
|
88
|
+
aria-valuemax={max}
|
|
89
|
+
aria-valuenow={value}
|
|
90
|
+
tabindex="0"
|
|
66
91
|
>
|
|
67
|
-
<div
|
|
92
|
+
<div
|
|
93
|
+
class="fill"
|
|
94
|
+
style="--progress: {progress}; --opacity: {opacity};"
|
|
95
|
+
></div>
|
|
68
96
|
</div>
|
|
69
97
|
|
|
70
98
|
<style>
|
|
@@ -78,13 +106,15 @@
|
|
|
78
106
|
background: var(--color-background-input);
|
|
79
107
|
cursor: ew-resize;
|
|
80
108
|
container-type: size;
|
|
109
|
+
outline: 0;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
.progress:hover {
|
|
112
|
+
:global(body:not(.fragment-dragging)) .progress:hover {
|
|
84
113
|
box-shadow: inset 0 0 0 1px var(--color-active);
|
|
85
114
|
}
|
|
86
115
|
|
|
87
|
-
.progress.dragging
|
|
116
|
+
.progress.dragging,
|
|
117
|
+
:global(body:not(.fragment-dragging)) .progress:focus-visible {
|
|
88
118
|
box-shadow: 0 0 0 2px var(--color-active);
|
|
89
119
|
}
|
|
90
120
|
|