fragment-tools 0.2.11 → 0.2.13
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 +12 -11
- package/src/cli/build.js +1 -0
- package/src/cli/create.js +22 -4
- package/src/cli/createConfig.js +2 -2
- package/src/cli/getEntries.js +10 -1
- package/src/cli/plugins/hot-shader-replacement.js +54 -16
- package/src/cli/plugins/save.js +97 -38
- package/src/cli/prompts.js +89 -36
- package/src/cli/run.js +1 -1
- package/src/cli/templates/blank/index.ts +1 -1
- package/src/cli/templates/default/index.js +10 -2
- package/src/cli/templates/default/index.ts +5 -2
- package/src/cli/templates/fragment-gl/index.ts +1 -1
- package/src/cli/templates/p5/index.ts +1 -1
- package/src/cli/templates/p5-webgl/index.ts +1 -1
- package/src/cli/templates/three-fragment/index.js +5 -3
- package/src/cli/templates/three-fragment/index.ts +5 -4
- package/src/cli/templates/three-orthographic/index.js +6 -1
- package/src/cli/templates/three-orthographic/index.ts +6 -2
- package/src/cli/templates/three-perspective/index.js +6 -1
- package/src/cli/templates/three-perspective/index.ts +6 -2
- package/src/client/app/actions/resize.js +8 -1
- package/src/client/app/attachments/draggable.js +93 -0
- package/src/client/app/client.js +90 -18
- package/src/client/app/components/IconFlip.svelte +46 -0
- package/src/client/app/hooks.js +25 -1
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
- package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
- package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
- package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
- package/src/client/app/lib/canvas-recorder/utils.js +18 -9
- package/src/client/app/modules/Params.svelte +1 -0
- package/src/client/app/renderers/2DRenderer.js +20 -16
- package/src/client/app/renderers/P5GLRenderer.js +13 -5
- package/src/client/app/renderers/P5Renderer.js +9 -1
- package/src/client/app/renderers/THREERenderer.js +63 -48
- package/src/client/app/state/Sketch.svelte.js +150 -10
- package/src/client/app/state/errors.svelte.js +19 -0
- package/src/client/app/state/exports.svelte.js +14 -1
- package/src/client/app/state/rendering.svelte.js +90 -13
- package/src/client/app/state/sketches.svelte.js +43 -7
- package/src/client/app/state/utils.svelte.js +49 -0
- package/src/client/app/ui/Field.svelte +63 -16
- package/src/client/app/ui/FieldSection.svelte +4 -4
- package/src/client/app/ui/ParamsOutput.svelte +7 -5
- package/src/client/app/ui/SketchRenderer.svelte +21 -0
- package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
- package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
- package/src/client/app/ui/fields/ColorInput.svelte +16 -11
- package/src/client/app/ui/fields/GradientInput.svelte +607 -0
- package/src/client/app/ui/fields/Input.svelte +10 -6
- package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
- package/src/client/app/ui/fields/NumberInput.svelte +51 -13
- package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
- package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
- package/src/client/app/ui/fields/TextareaInput.svelte +93 -0
- package/src/client/app/utils/canvas.utils.js +105 -28
- package/src/client/app/utils/color.utils.js +74 -17
- package/src/client/app/utils/fields.utils.js +70 -17
- package/src/client/app/utils/file.utils.js +86 -31
- package/src/client/app/utils/glsl.utils.js +11 -2
- package/src/client/app/utils/glslErrors.js +31 -21
- package/src/client/app/utils/index.js +28 -12
- package/src/client/main.js +7 -1
- package/src/types/global.d.ts +143 -0
- package/src/types/props.d.ts +41 -15
- package/tsconfig.json +1 -1
|
@@ -8,14 +8,18 @@
|
|
|
8
8
|
onkeydown,
|
|
9
9
|
onfocus,
|
|
10
10
|
onblur,
|
|
11
|
+
node = $bindable(),
|
|
11
12
|
} = $props();
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @param {KeyboardEvent} event
|
|
16
|
+
*/
|
|
16
17
|
function onKeyPress(event) {
|
|
17
|
-
if (
|
|
18
|
-
|
|
18
|
+
if (
|
|
19
|
+
event.currentTarget instanceof HTMLInputElement &&
|
|
20
|
+
event.key === 'Enter'
|
|
21
|
+
) {
|
|
22
|
+
event.currentTarget.blur();
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
</script>
|
|
@@ -34,7 +38,7 @@
|
|
|
34
38
|
{onfocus}
|
|
35
39
|
{onblur}
|
|
36
40
|
onkeypress={onKeyPress}
|
|
37
|
-
|
|
41
|
+
{disabled}
|
|
38
42
|
autocomplete="off"
|
|
39
43
|
spellcheck="false"
|
|
40
44
|
/>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import FieldInputRow from './FieldInputRow.svelte';
|
|
3
3
|
import NumberInput from './NumberInput.svelte';
|
|
4
4
|
import { map, clamp, roundToStep } from '../../utils/math.utils';
|
|
5
|
+
import { draggable } from '../../attachments/draggable.js';
|
|
5
6
|
|
|
6
7
|
let {
|
|
7
8
|
value = null,
|
|
@@ -21,51 +22,49 @@
|
|
|
21
22
|
/** @type {DOMRect}*/
|
|
22
23
|
let rect;
|
|
23
24
|
/** @type {boolean}*/
|
|
24
|
-
let
|
|
25
|
+
let dragging = $state(false);
|
|
25
26
|
|
|
26
27
|
let proximityIndex = -1;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
*
|
|
30
31
|
* @param {MouseEvent} event
|
|
32
|
+
* @param {DOMRect}
|
|
31
33
|
*/
|
|
32
|
-
function
|
|
33
|
-
document.body.classList.add('fragment-dragging');
|
|
34
|
-
|
|
35
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
36
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
37
|
-
|
|
38
|
-
rect = node.getBoundingClientRect();
|
|
39
|
-
|
|
40
|
-
isDragging = true;
|
|
41
|
-
|
|
42
|
-
let dragValue = computeDrag(event);
|
|
43
|
-
|
|
44
|
-
let abs0 = Math.abs(dragValue - value[0]);
|
|
45
|
-
let abs1 = Math.abs(dragValue - value[1]);
|
|
46
|
-
|
|
47
|
-
proximityIndex = abs0 < abs1 ? 0 : 1;
|
|
48
|
-
|
|
49
|
-
onDrag(event);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function computeDrag(event) {
|
|
34
|
+
function computeDrag(event, rect) {
|
|
53
35
|
let dragValue = clamp(
|
|
54
36
|
map(event.clientX, rect.left, rect.right, min, max),
|
|
55
37
|
min,
|
|
56
38
|
max,
|
|
57
39
|
);
|
|
58
40
|
dragValue = roundToStep(dragValue, step);
|
|
41
|
+
|
|
59
42
|
return dragValue;
|
|
60
43
|
}
|
|
61
44
|
|
|
62
|
-
|
|
63
|
-
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {MouseEvent} event
|
|
48
|
+
* @param {object} params
|
|
49
|
+
* @param {DOMRect | undefined} params.rect
|
|
50
|
+
*/
|
|
51
|
+
function onDragStart(event, params) {
|
|
52
|
+
if (params.rect) {
|
|
53
|
+
let dragValue = computeDrag(event, params.rect);
|
|
54
|
+
|
|
55
|
+
let abs0 = Math.abs(dragValue - value[0]);
|
|
56
|
+
let abs1 = Math.abs(dragValue - value[1]);
|
|
57
|
+
|
|
58
|
+
proximityIndex = abs0 < abs1 ? 0 : 1;
|
|
59
|
+
|
|
60
|
+
onDrag(event, params);
|
|
61
|
+
}
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
function onDrag(event) {
|
|
67
|
-
|
|
64
|
+
function onDrag(event, params) {
|
|
65
|
+
dragging = params.isDragging;
|
|
68
66
|
|
|
67
|
+
let dragValue = computeDrag(event, params.rect);
|
|
69
68
|
let prevValue = value[proximityIndex];
|
|
70
69
|
|
|
71
70
|
if (dragValue !== prevValue) {
|
|
@@ -82,13 +81,6 @@
|
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
function handleMouseUp() {
|
|
86
|
-
document.body.classList.remove('fragment-dragging');
|
|
87
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
88
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
89
|
-
|
|
90
|
-
isDragging = false;
|
|
91
|
-
}
|
|
92
84
|
|
|
93
85
|
function handleValueChange(index, newValue) {
|
|
94
86
|
let newValues = [...value];
|
|
@@ -119,9 +111,9 @@
|
|
|
119
111
|
<FieldInputRow --grid-template-columns="1fr 0.5fr">
|
|
120
112
|
<div
|
|
121
113
|
class="range"
|
|
122
|
-
class:dragging={
|
|
114
|
+
class:dragging={dragging}
|
|
123
115
|
bind:this={node}
|
|
124
|
-
|
|
116
|
+
{@attach draggable({ onDragStart, onDrag })}
|
|
125
117
|
>
|
|
126
118
|
<div class="handler" style="--position: {p1};" />
|
|
127
119
|
<div class="filler" style="--p1: {p1}; --p2: {p2};"></div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import { tick } from 'svelte';
|
|
2
3
|
import FieldInputRow from './FieldInputRow.svelte';
|
|
3
4
|
import Input from './Input.svelte';
|
|
4
5
|
import ProgressInput from './ProgressInput.svelte';
|
|
@@ -17,16 +18,30 @@
|
|
|
17
18
|
key = '',
|
|
18
19
|
progress = true,
|
|
19
20
|
onchange,
|
|
21
|
+
onfocus,
|
|
22
|
+
onblur,
|
|
23
|
+
node = $bindable(),
|
|
20
24
|
} = $props();
|
|
21
25
|
|
|
22
26
|
let hasProgress = $derived(progress && isFinite(min) && isFinite(max));
|
|
23
27
|
let isFocused = $state(false);
|
|
24
28
|
let precision = $derived(step.toString().split('.')[1]?.length || 0);
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} v
|
|
32
|
+
* @param {string} suffix
|
|
33
|
+
*/
|
|
26
34
|
function sanitize(v, suffix) {
|
|
27
35
|
return suffix && suffix !== '' ? Number(v.split(suffix)[0]) : Number(v);
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {number} v
|
|
41
|
+
* @param {boolean} isFocused
|
|
42
|
+
* @param {string} suffix
|
|
43
|
+
* @param {number} precision
|
|
44
|
+
*/
|
|
30
45
|
function composeValue(v, isFocused, suffix = '', precision) {
|
|
31
46
|
const clampedValue = clamp(
|
|
32
47
|
v,
|
|
@@ -45,32 +60,53 @@
|
|
|
45
60
|
composeValue(value, isFocused, suffix, precision),
|
|
46
61
|
);
|
|
47
62
|
|
|
48
|
-
|
|
63
|
+
/**
|
|
64
|
+
* @param {FocusEvent} event
|
|
65
|
+
*/
|
|
66
|
+
function onFocus(event) {
|
|
49
67
|
isFocused = true;
|
|
68
|
+
onfocus?.(event);
|
|
50
69
|
}
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
/**
|
|
72
|
+
* @param {KeyboardEvent} event
|
|
73
|
+
*/
|
|
74
|
+
async function onBlur(event) {
|
|
75
|
+
let currentTarget = event.currentTarget;
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
let isNotValid = isNaN(Number(event.currentTarget.value));
|
|
77
|
+
await tick();
|
|
57
78
|
|
|
58
|
-
if (
|
|
59
|
-
newValue =
|
|
60
|
-
|
|
79
|
+
if (currentTarget instanceof HTMLInputElement) {
|
|
80
|
+
let newValue = currentTarget.value;
|
|
81
|
+
isFocused = false;
|
|
82
|
+
let sanitizedValue = sanitize(newValue, suffix);
|
|
83
|
+
|
|
84
|
+
if (isNaN(sanitizedValue)) {
|
|
85
|
+
onchange(value, true);
|
|
86
|
+
} else {
|
|
87
|
+
onchange(sanitizedValue, true);
|
|
88
|
+
}
|
|
61
89
|
|
|
62
|
-
|
|
90
|
+
onblur?.(event);
|
|
91
|
+
}
|
|
63
92
|
}
|
|
64
93
|
|
|
94
|
+
/**
|
|
95
|
+
* @param {KeyboardEvent} event
|
|
96
|
+
*/
|
|
65
97
|
function onKeyDown(event) {
|
|
66
|
-
if (
|
|
98
|
+
if (
|
|
99
|
+
event.currentTarget instanceof HTMLInputElement &&
|
|
100
|
+
['ArrowDown', 'ArrowUp'].includes(event.key)
|
|
101
|
+
) {
|
|
67
102
|
event.preventDefault();
|
|
68
103
|
|
|
69
104
|
const diff = Keyboard.getStepFromEvent(event) * step;
|
|
70
|
-
const direction = event.
|
|
71
|
-
const
|
|
105
|
+
const direction = event.key === 'ArrowUp' ? 1 : -1;
|
|
106
|
+
const sanitizedValue =
|
|
107
|
+
sanitize(event.currentTarget.value, suffix) + direction * diff;
|
|
72
108
|
|
|
73
|
-
onchange(
|
|
109
|
+
onchange(sanitizedValue, false);
|
|
74
110
|
}
|
|
75
111
|
}
|
|
76
112
|
</script>
|
|
@@ -93,6 +129,7 @@
|
|
|
93
129
|
{disabled}
|
|
94
130
|
{context}
|
|
95
131
|
{key}
|
|
132
|
+
bind:node
|
|
96
133
|
onkeydown={onKeyDown}
|
|
97
134
|
onfocus={onFocus}
|
|
98
135
|
onblur={onBlur}
|
|
@@ -108,6 +145,7 @@
|
|
|
108
145
|
onkeydown={onKeyDown}
|
|
109
146
|
onfocus={onFocus}
|
|
110
147
|
onblur={onBlur}
|
|
148
|
+
bind:node
|
|
111
149
|
value={composedValue}
|
|
112
150
|
/>
|
|
113
151
|
{/if}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import * as color from '../../utils/color.utils.js';
|
|
3
|
+
import ButtonInput from './ButtonInput.svelte';
|
|
4
|
+
import ColorInput from './ColorInput.svelte';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
value,
|
|
8
|
+
context = null,
|
|
9
|
+
key = '',
|
|
10
|
+
disabled = false,
|
|
11
|
+
onchange,
|
|
12
|
+
editable = true,
|
|
13
|
+
extensible = true,
|
|
14
|
+
} = $props();
|
|
15
|
+
|
|
16
|
+
let selected = $state(-1);
|
|
17
|
+
|
|
18
|
+
let hexValues = $derived.by(() => {
|
|
19
|
+
return value.map((v) => {
|
|
20
|
+
const format = color.getColorFormat(v);
|
|
21
|
+
return color.toHex(v, format);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
$effect(() => {
|
|
26
|
+
// handle value length changes when a color is selected
|
|
27
|
+
if (selected >= hexValues.length) {
|
|
28
|
+
selected = hexValues.length - 1;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {PointerEvent} event
|
|
35
|
+
* @param {number} index
|
|
36
|
+
*/
|
|
37
|
+
function handleClick(event, index) {
|
|
38
|
+
if (selected !== index) {
|
|
39
|
+
selected = index;
|
|
40
|
+
} else {
|
|
41
|
+
selected = -1;
|
|
42
|
+
event.currentTarget.blur();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param {string} color
|
|
49
|
+
*/
|
|
50
|
+
function handleColorChange(color) {
|
|
51
|
+
let palette = value.map((v) => v);
|
|
52
|
+
palette[selected] = color;
|
|
53
|
+
|
|
54
|
+
onchange(palette);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handleClickAdd() {
|
|
58
|
+
let palette = value.map((v) => v);
|
|
59
|
+
palette.push('#ffffff');
|
|
60
|
+
|
|
61
|
+
onchange(palette);
|
|
62
|
+
|
|
63
|
+
selected = palette.length - 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleClickDelete() {
|
|
67
|
+
let index = selected;
|
|
68
|
+
let palette = value.map((v) => v);
|
|
69
|
+
palette.splice(index, 1);
|
|
70
|
+
|
|
71
|
+
onchange(palette);
|
|
72
|
+
selected = -1;
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<div class="palette-input" class:disabled style="--count: {hexValues.length}">
|
|
77
|
+
<div class="palette-list">
|
|
78
|
+
{#if editable && extensible}
|
|
79
|
+
<div class="palette-action">
|
|
80
|
+
<ButtonInput label="+" onclick={handleClickAdd} />
|
|
81
|
+
</div>
|
|
82
|
+
{/if}
|
|
83
|
+
{#each hexValues as hexValue, index}
|
|
84
|
+
{#if editable}
|
|
85
|
+
<button
|
|
86
|
+
class="palette-action"
|
|
87
|
+
style="--current-color: {hexValue}"
|
|
88
|
+
class:selected={selected === index}
|
|
89
|
+
onclick={(event) => handleClick(event, index)}
|
|
90
|
+
>
|
|
91
|
+
<span class="visually-hidden">Change color {index}</span>
|
|
92
|
+
</button>
|
|
93
|
+
{:else}
|
|
94
|
+
<div
|
|
95
|
+
class="palette-action"
|
|
96
|
+
style="--current-color: {hexValue}"
|
|
97
|
+
></div>
|
|
98
|
+
{/if}
|
|
99
|
+
{/each}
|
|
100
|
+
</div>
|
|
101
|
+
{#if value[selected] && editable}
|
|
102
|
+
<div class="palette-editor">
|
|
103
|
+
<ColorInput value={value[selected]} onchange={handleColorChange} />
|
|
104
|
+
</div>
|
|
105
|
+
{#if extensible}
|
|
106
|
+
<div class="palette-delete">
|
|
107
|
+
<ButtonInput label="delete" onclick={handleClickDelete} />
|
|
108
|
+
</div>
|
|
109
|
+
{/if}
|
|
110
|
+
{/if}
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<style>
|
|
114
|
+
.palette-input {
|
|
115
|
+
position: relative;
|
|
116
|
+
width: 100%;
|
|
117
|
+
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
row-gap: var(--column-gap);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.palette-list {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-wrap: wrap;
|
|
126
|
+
|
|
127
|
+
column-gap: var(--column-gap);
|
|
128
|
+
row-gap: var(--column-gap);
|
|
129
|
+
align-items: center;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.palette-action {
|
|
133
|
+
position: relative;
|
|
134
|
+
aspect-ratio: 1;
|
|
135
|
+
height: var(--fragment-input-height);
|
|
136
|
+
|
|
137
|
+
border-radius: var(--fragment-input-border-radius);
|
|
138
|
+
background-color: var(
|
|
139
|
+
--background-color,
|
|
140
|
+
var(--fragment-input-background-color)
|
|
141
|
+
);
|
|
142
|
+
box-shadow: inset 0 0 0 1px
|
|
143
|
+
var(--box-shadow-color, var(--fragment-input-border-color));
|
|
144
|
+
outline: 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
button.palette-action {
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.palette-input:focus-within .palette-action.selected {
|
|
152
|
+
box-shadow: 0 0 0 2px var(--fragment-accent-color);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.palette-action:before {
|
|
156
|
+
--gap: 1px;
|
|
157
|
+
content: '';
|
|
158
|
+
|
|
159
|
+
position: absolute;
|
|
160
|
+
z-index: 1;
|
|
161
|
+
top: var(--gap);
|
|
162
|
+
left: var(--gap);
|
|
163
|
+
right: var(--gap);
|
|
164
|
+
bottom: var(--gap);
|
|
165
|
+
|
|
166
|
+
background-color: var(--current-color);
|
|
167
|
+
border-radius: calc(var(--fragment-input-border-radius) - var(--gap));
|
|
168
|
+
opacity: var(--opacity, 1);
|
|
169
|
+
pointer-events: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
:global(body:not(.fragment-dragging))
|
|
173
|
+
button.palette-action:not(.disabled):hover {
|
|
174
|
+
box-shadow: inset 0 0 0 1px var(--fragment-accent-color);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
:global(body:not(.fragment-dragging))
|
|
178
|
+
button.palette-action:not(.disabled):focus-within {
|
|
179
|
+
box-shadow: 0 0 0 2px var(--fragment-accent-color);
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
@@ -2,46 +2,74 @@
|
|
|
2
2
|
import Keyboard from '../../inputs/Keyboard.js';
|
|
3
3
|
import { map, clamp, roundToStep } from '../../utils/math.utils.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} Props
|
|
7
|
+
* @property {number} value
|
|
8
|
+
* @property {number} min
|
|
9
|
+
* @property {number} max
|
|
10
|
+
* @property {number} step
|
|
11
|
+
* @property {boolean} disabled
|
|
12
|
+
* @property {(value: number) => void|undefined} onchange
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** @type {Props} */
|
|
5
16
|
let { value, min, max, step, disabled = false, onchange } = $props();
|
|
6
17
|
|
|
18
|
+
/** @type {HTMLElement|undefined} */
|
|
7
19
|
let node;
|
|
20
|
+
/** @type {DOMRect|undefined} */
|
|
8
21
|
let rect;
|
|
9
22
|
|
|
10
23
|
let isDragging = $state(false);
|
|
11
24
|
let steppedValue = $derived(roundToStep(value, step));
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @param {MouseEvent} event
|
|
28
|
+
*/
|
|
14
29
|
function handleMouseDown(event) {
|
|
15
30
|
if (disabled) return;
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
if (node) {
|
|
33
|
+
document.body.classList.add('fragment-dragging');
|
|
34
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
35
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
rect = node.getBoundingClientRect();
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
isDragging = true;
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
onDrag(event);
|
|
42
|
+
}
|
|
26
43
|
}
|
|
27
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @param {MouseEvent} event
|
|
47
|
+
*/
|
|
28
48
|
function handleMouseMove(event) {
|
|
29
49
|
onDrag(event);
|
|
30
50
|
}
|
|
31
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @param {MouseEvent} event
|
|
54
|
+
*/
|
|
32
55
|
function onDrag(event) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
if (rect) {
|
|
57
|
+
let dragValue = clamp(
|
|
58
|
+
map(event.clientX, rect.left, rect.right, min, max),
|
|
59
|
+
min,
|
|
60
|
+
max,
|
|
61
|
+
);
|
|
62
|
+
dragValue = roundToStep(dragValue, step);
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
64
|
+
if (dragValue !== value) {
|
|
65
|
+
onchange?.(dragValue);
|
|
66
|
+
}
|
|
42
67
|
}
|
|
43
68
|
}
|
|
44
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param {KeyboardEvent} event
|
|
72
|
+
*/
|
|
45
73
|
function handleKeyDown(event) {
|
|
46
74
|
const direction = ['ArrowUp', 'ArrowRight'].includes(event.key)
|
|
47
75
|
? 1
|
|
@@ -59,7 +87,7 @@
|
|
|
59
87
|
);
|
|
60
88
|
|
|
61
89
|
if (newValue !== value) {
|
|
62
|
-
onchange(newValue);
|
|
90
|
+
onchange?.(newValue);
|
|
63
91
|
}
|
|
64
92
|
}
|
|
65
93
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let {
|
|
3
|
+
value = $bindable(),
|
|
4
|
+
height,
|
|
5
|
+
disabled = false,
|
|
6
|
+
oninput,
|
|
7
|
+
onchange,
|
|
8
|
+
onkeydown,
|
|
9
|
+
onfocus,
|
|
10
|
+
onblur,
|
|
11
|
+
} = $props();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {KeyboardEvent} event
|
|
15
|
+
*/
|
|
16
|
+
function onKeyPress(event) {
|
|
17
|
+
if (
|
|
18
|
+
event.currentTarget instanceof HTMLTextAreaElement &&
|
|
19
|
+
event.key === 'Enter' &&
|
|
20
|
+
!event.shiftKey
|
|
21
|
+
) {
|
|
22
|
+
event.currentTarget.blur();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
class="input-container"
|
|
29
|
+
class:disabled
|
|
30
|
+
style={height ? `--height: ${height}` : null}
|
|
31
|
+
>
|
|
32
|
+
<textarea
|
|
33
|
+
class="input"
|
|
34
|
+
bind:value
|
|
35
|
+
{oninput}
|
|
36
|
+
{onchange}
|
|
37
|
+
{onkeydown}
|
|
38
|
+
{onfocus}
|
|
39
|
+
{onblur}
|
|
40
|
+
onkeypress={onKeyPress}
|
|
41
|
+
{disabled}
|
|
42
|
+
autocomplete="off"
|
|
43
|
+
spellcheck="false"
|
|
44
|
+
></textarea>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.input-container {
|
|
49
|
+
position: relative;
|
|
50
|
+
|
|
51
|
+
display: flex;
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: var(--height, var(--fragment-input-height));
|
|
54
|
+
margin: 2px 0;
|
|
55
|
+
|
|
56
|
+
border-radius: var(--fragment-input-border-radius);
|
|
57
|
+
background-color: var(--fragment-input-background-color);
|
|
58
|
+
box-shadow: inset 0 0 0 1px var(--fragment-input-border-color);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:global(body:not(.fragment-dragging))
|
|
62
|
+
.input-container:not(.disabled):hover {
|
|
63
|
+
box-shadow: inset 0 0 0 1px var(--fragment-accent-color);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
:global(body:not(.fragment-dragging))
|
|
67
|
+
.input-container:not(.disabled):focus-within {
|
|
68
|
+
box-shadow: 0 0 0 2px var(--fragment-accent-color);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.input {
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
padding: var(--padding) var(--padding);
|
|
75
|
+
|
|
76
|
+
color: var(--fragment-input-text-color);
|
|
77
|
+
font-size: var(--fragment-input-font-size);
|
|
78
|
+
text-align: left;
|
|
79
|
+
|
|
80
|
+
background: transparent;
|
|
81
|
+
outline: 0;
|
|
82
|
+
resize: none;
|
|
83
|
+
border: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.input:disabled {
|
|
87
|
+
color: var(--fragment-input-disabled-text-color);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.input:focus {
|
|
91
|
+
color: var(--fragment-text-color);
|
|
92
|
+
}
|
|
93
|
+
</style>
|