@pixerate/editor-svelte 0.128.3 → 0.128.5
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/dist/effects/EffectParameters.svelte +48 -5
- package/dist/effects/EffectParameters.svelte.d.ts +3 -0
- package/dist/image-editor/ImageEffectControls.svelte +9 -2
- package/dist/image-editor/ImageEffectControls.svelte.d.ts +3 -0
- package/dist/image-editor/ImageProviderEffectsPanel.svelte +10 -5
- package/dist/image-editor/ImageProviderEffectsPanel.svelte.d.ts +3 -0
- package/dist/image-editor/controls/ImageParameterSelect.svelte +11 -0
- package/dist/image-editor/controls/ImageParameterSelect.svelte.d.ts +4 -0
- package/dist/image-editor/controls/ImageParameterSlider.svelte +9 -0
- package/dist/image-editor/controls/ImageParameterSlider.svelte.d.ts +4 -0
- package/dist/image-editor/controls/parameterControls.d.ts +4 -0
- package/dist/image-editor/controls/parameterControls.js +9 -0
- package/dist/parameters/ParameterCommitInput.svelte +33 -4
- package/dist/parameters/ParameterFieldControl.svelte +6 -4
- package/dist/parameters/ParameterFieldControl.svelte.d.ts +2 -0
- package/dist/parameters/ParameterNumberInput.svelte +41 -17
- package/dist/parameters/ParameterNumberInput.svelte.d.ts +2 -0
- package/dist/parameters/ParameterSelect.svelte +25 -6
- package/dist/parameters/ParameterSelect.svelte.d.ts +2 -0
- package/dist/parameters/ParameterSlider.svelte +48 -20
- package/dist/parameters/ParameterSlider.svelte.d.ts +2 -0
- package/dist/parameters/ParameterVector.svelte +6 -3
- package/dist/parameters/ParameterVector.svelte.d.ts +2 -0
- package/dist/parameters/ParametersPanel.svelte +54 -18
- package/dist/parameters/ParametersPanel.svelte.d.ts +3 -0
- package/dist/parameters/controls.d.ts +69 -0
- package/dist/parameters/controls.js +18 -0
- package/dist/parameters/index.d.ts +1 -0
- package/dist/parameters/index.js +1 -0
- package/dist/parameters/styles.d.ts +6 -0
- package/dist/parameters/styles.js +7 -0
- package/package.json +2 -2
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { effectFieldValue, effectParameterFields, type EffectDescription, type EffectFieldOptions } from '@pixerate/editor/effects';
|
|
3
3
|
import { parameterLabel, parameterValuesEqual, type ParameterValue } from '@pixerate/editor/parameters';
|
|
4
4
|
import ParameterFieldControl from '../parameters/ParameterFieldControl.svelte';
|
|
5
|
+
import { parameterSelectStyle } from '../parameters/styles.js';
|
|
6
|
+
import { getParameterComponents, resolveParameterComponents, type ParameterComponents } from '../parameters/controls.js';
|
|
5
7
|
|
|
6
8
|
interface Props {
|
|
7
9
|
/** The effect's description (`describe`). */
|
|
@@ -25,12 +27,29 @@
|
|
|
25
27
|
addon?: import('svelte').Snippet<[string]>;
|
|
26
28
|
/** Families offered by font controls (`format: "font"`); default the system font catalog. */
|
|
27
29
|
fonts?: EffectFieldOptions['fonts'];
|
|
30
|
+
/** Custom host components for inputs, sliders, and selects. */
|
|
31
|
+
components?: ParameterComponents;
|
|
28
32
|
disabled?: boolean;
|
|
29
33
|
class?: string;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
let {
|
|
36
|
+
let {
|
|
37
|
+
description,
|
|
38
|
+
values,
|
|
39
|
+
onchange,
|
|
40
|
+
onreset,
|
|
41
|
+
keyframed,
|
|
42
|
+
onpreset,
|
|
43
|
+
onpresetname,
|
|
44
|
+
addon,
|
|
45
|
+
fonts,
|
|
46
|
+
components,
|
|
47
|
+
disabled = false,
|
|
48
|
+
class: className = ''
|
|
49
|
+
}: Props = $props();
|
|
33
50
|
const fields = $derived(effectParameterFields(description, { fonts }));
|
|
51
|
+
const parameterContext = getParameterComponents();
|
|
52
|
+
const CustomPresetSelect = $derived(resolveParameterComponents(components, parameterContext).select);
|
|
34
53
|
const idBase = `fx-${Math.random().toString(36).slice(2, 8)}`;
|
|
35
54
|
let gesture: string | null = null;
|
|
36
55
|
let count = 0;
|
|
@@ -52,10 +71,33 @@
|
|
|
52
71
|
-->
|
|
53
72
|
<div class="space-y-2 {className}" data-effect-parameters={description.id}>
|
|
54
73
|
{#if (onpreset || onpresetname) && description.presets.length > 0}
|
|
55
|
-
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
{#if CustomPresetSelect}
|
|
75
|
+
<CustomPresetSelect
|
|
76
|
+
aria-label="{description.name} presets"
|
|
77
|
+
label="{description.name} presets"
|
|
78
|
+
value=""
|
|
79
|
+
options={[
|
|
80
|
+
{ value: '', label: 'Presets…' },
|
|
81
|
+
...description.presets.map((p) => ({ value: p.name, label: p.name }))
|
|
82
|
+
]}
|
|
83
|
+
class="w-full rounded border border-border px-1 py-0.5 text-xs"
|
|
84
|
+
style={parameterSelectStyle}
|
|
85
|
+
{disabled}
|
|
86
|
+
onchange={(val) => preset(String(val))}
|
|
87
|
+
/>
|
|
88
|
+
{:else}
|
|
89
|
+
<select
|
|
90
|
+
class="w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs"
|
|
91
|
+
style={parameterSelectStyle}
|
|
92
|
+
aria-label="{description.name} presets"
|
|
93
|
+
value=""
|
|
94
|
+
{disabled}
|
|
95
|
+
onchange={(e) => preset(e.currentTarget.value)}
|
|
96
|
+
>
|
|
97
|
+
<option value="" style={parameterSelectStyle}>Presets…</option>
|
|
98
|
+
{#each description.presets as p (p.name)}<option value={p.name} style={parameterSelectStyle}>{p.name}</option>{/each}
|
|
99
|
+
</select>
|
|
100
|
+
{/if}
|
|
59
101
|
{/if}
|
|
60
102
|
{#each fields as field (field.id)}
|
|
61
103
|
{@const label = parameterLabel(field)}
|
|
@@ -75,6 +117,7 @@
|
|
|
75
117
|
{value}
|
|
76
118
|
id="{idBase}-{field.id}"
|
|
77
119
|
disabled={disabled || keyed}
|
|
120
|
+
{components}
|
|
78
121
|
onchange={(v) => onchange(field.id, v, gesture)}
|
|
79
122
|
ongesturestart={() => (gesture = `${idBase}:${field.id}:${++count}`)}
|
|
80
123
|
ongestureend={() => (gesture = null)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type EffectDescription, type EffectFieldOptions } from '@pixerate/editor/effects';
|
|
2
2
|
import { type ParameterValue } from '@pixerate/editor/parameters';
|
|
3
|
+
import { type ParameterComponents } from '../parameters/controls.js';
|
|
3
4
|
interface Props {
|
|
4
5
|
/** The effect's description (`describe`). */
|
|
5
6
|
description: EffectDescription;
|
|
@@ -22,6 +23,8 @@ interface Props {
|
|
|
22
23
|
addon?: import('svelte').Snippet<[string]>;
|
|
23
24
|
/** Families offered by font controls (`format: "font"`); default the system font catalog. */
|
|
24
25
|
fonts?: EffectFieldOptions['fonts'];
|
|
26
|
+
/** Custom host components for inputs, sliders, and selects. */
|
|
27
|
+
components?: ParameterComponents;
|
|
25
28
|
disabled?: boolean;
|
|
26
29
|
class?: string;
|
|
27
30
|
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import type { EffectProvider } from '@pixerate/editor/effects';
|
|
3
3
|
import EffectParameters from '../effects/EffectParameters.svelte';
|
|
4
4
|
import { effectDescriptionOf } from '../effects/effects.svelte.js';
|
|
5
|
+
import { getImageEditorControls } from './controls/controls.js';
|
|
6
|
+
import { imageEditorParameterComponents } from './controls/parameterControls.js';
|
|
7
|
+
import { getParameterComponents, resolveParameterComponents, type ParameterComponents } from '../parameters/controls.js';
|
|
5
8
|
|
|
6
9
|
interface Props {
|
|
7
10
|
provider: EffectProvider | null;
|
|
@@ -10,12 +13,16 @@
|
|
|
10
13
|
values: Readonly<Record<string, unknown>>;
|
|
11
14
|
onchange: (control: string, value: unknown, gesture: string | null) => void;
|
|
12
15
|
onpreset: (values: Readonly<Record<string, unknown>>) => void;
|
|
16
|
+
/** Host parameter controls; else those of `setParameterComponents`, else the image editor's host `Range` / `Select`. */
|
|
17
|
+
components?: ParameterComponents;
|
|
13
18
|
/** Extra classes on the root element. */
|
|
14
19
|
class?: string;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
let { provider, effect, name, values, onchange, onpreset, class: className = '' }: Props = $props();
|
|
22
|
+
let { provider, effect, name, values, onchange, onpreset, components, class: className = '' }: Props = $props();
|
|
18
23
|
const loaded = effectDescriptionOf(() => provider, () => effect);
|
|
24
|
+
const fallback = resolveParameterComponents(getParameterComponents(), imageEditorParameterComponents(getImageEditorControls()));
|
|
25
|
+
const effectiveComponents = $derived(resolveParameterComponents(components, fallback));
|
|
19
26
|
</script>
|
|
20
27
|
|
|
21
28
|
<!-- One effect's controls, loaded from the provider; a note when it does not have the effect. -->
|
|
@@ -28,6 +35,6 @@
|
|
|
28
35
|
<p class="text-[11px] text-muted-foreground">Loading…</p>
|
|
29
36
|
{:else}
|
|
30
37
|
{@const description = loaded.description}
|
|
31
|
-
<EffectParameters {description} {values} {onchange} onreset={(k) => onchange(k, description.defaults[k], null)} {onpreset} />
|
|
38
|
+
<EffectParameters {description} {values} {onchange} onreset={(k) => onchange(k, description.defaults[k], null)} {onpreset} components={effectiveComponents} />
|
|
32
39
|
{/if}
|
|
33
40
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EffectProvider } from '@pixerate/editor/effects';
|
|
2
|
+
import { type ParameterComponents } from '../parameters/controls.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
provider: EffectProvider | null;
|
|
4
5
|
effect: string;
|
|
@@ -6,6 +7,8 @@ interface Props {
|
|
|
6
7
|
values: Readonly<Record<string, unknown>>;
|
|
7
8
|
onchange: (control: string, value: unknown, gesture: string | null) => void;
|
|
8
9
|
onpreset: (values: Readonly<Record<string, unknown>>) => void;
|
|
10
|
+
/** Host parameter controls; else those of `setParameterComponents`, else the image editor's host `Range` / `Select`. */
|
|
11
|
+
components?: ParameterComponents;
|
|
9
12
|
/** Extra classes on the root element. */
|
|
10
13
|
class?: string;
|
|
11
14
|
}
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
import { untrack } from 'svelte';
|
|
5
5
|
import EffectsBrowser from '../effects/EffectsBrowser.svelte';
|
|
6
6
|
import ImageEffectControls from './ImageEffectControls.svelte';
|
|
7
|
+
import ImageCheckbox from './controls/ImageCheckbox.svelte';
|
|
8
|
+
import ImageRange from './controls/ImageRange.svelte';
|
|
9
|
+
import type { ParameterComponents } from '../parameters/index.js';
|
|
7
10
|
|
|
8
11
|
interface Props {
|
|
9
12
|
controller: ImageEditorController;
|
|
13
|
+
/** Host controls for the effects' parameters. */
|
|
14
|
+
components?: ParameterComponents;
|
|
10
15
|
class?: string;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
let { controller, class: className = '' }: Props = $props();
|
|
18
|
+
let { controller, components, class: className = '' }: Props = $props();
|
|
14
19
|
let tick = $state(0);
|
|
15
20
|
$effect(() => {
|
|
16
21
|
const c = controller;
|
|
@@ -78,6 +83,7 @@
|
|
|
78
83
|
effect={generator.effect}
|
|
79
84
|
name={generator.name}
|
|
80
85
|
values={generator.values}
|
|
86
|
+
{components}
|
|
81
87
|
onchange={(k, v, g) => run(() => controller.updateEffectLayer(id, { values: { [k]: v as never } }, commit(g)))}
|
|
82
88
|
onpreset={(values) => run(() => controller.updateEffectLayer(id, { values: values as never }))}
|
|
83
89
|
/>
|
|
@@ -91,8 +97,7 @@
|
|
|
91
97
|
{@const label = f.name ?? f.effect}
|
|
92
98
|
<li class="rounded border border-border p-1" data-smart-effect={f.id}>
|
|
93
99
|
<div class="flex items-center gap-1">
|
|
94
|
-
<
|
|
95
|
-
const enabled = e.currentTarget.checked;
|
|
100
|
+
<ImageCheckbox checked={f.enabled} label="{label} on" onchange={(enabled) => {
|
|
96
101
|
run(() => controller.updateEffectFilter(id, f.id, { enabled }));
|
|
97
102
|
}} />
|
|
98
103
|
<button type="button" class="min-w-0 flex-1 truncate text-left text-foreground" aria-expanded={open === f.id} onclick={() => (open = open === f.id ? null : f.id)}>{label}</button>
|
|
@@ -104,8 +109,7 @@
|
|
|
104
109
|
<div class="mt-1 space-y-2">
|
|
105
110
|
<label class="flex items-center gap-1">
|
|
106
111
|
<span class="w-14 text-[11px] text-foreground">Opacity</span>
|
|
107
|
-
<
|
|
108
|
-
const opacity = Number(e.currentTarget.value);
|
|
112
|
+
<ImageRange min={0} max={1} step={0.01} class="flex-1" value={f.opacity} label="{label} opacity" oninput={(opacity) => {
|
|
109
113
|
run(() => controller.updateEffectFilter(id, f.id, { opacity }));
|
|
110
114
|
}} />
|
|
111
115
|
</label>
|
|
@@ -114,6 +118,7 @@
|
|
|
114
118
|
effect={f.effect}
|
|
115
119
|
name={f.name}
|
|
116
120
|
values={f.values}
|
|
121
|
+
{components}
|
|
117
122
|
onchange={(k, v, g) => run(() => controller.updateEffectFilter(id, f.id, { values: { [k]: v as never } }, commit(g)))}
|
|
118
123
|
onpreset={(values) => run(() => controller.updateEffectFilter(id, f.id, { values: values as never }))}
|
|
119
124
|
/>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type ImageEditorController } from '@pixerate/editor/image-editor';
|
|
2
|
+
import type { ParameterComponents } from '../parameters/index.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
controller: ImageEditorController;
|
|
5
|
+
/** Host controls for the effects' parameters. */
|
|
6
|
+
components?: ParameterComponents;
|
|
4
7
|
class?: string;
|
|
5
8
|
}
|
|
6
9
|
declare const ImageProviderEffectsPanel: import("svelte").Component<Props, {}, "">;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { matchParameterOptionValue } from '@pixerate/editor/parameters';
|
|
3
|
+
import type { ParameterSelectProps } from '../../parameters/controls.js';
|
|
4
|
+
import ImageSelect from './ImageSelect.svelte';
|
|
5
|
+
|
|
6
|
+
let { value, options, label, disabled = false, class: className = '', onchange }: ParameterSelectProps = $props();
|
|
7
|
+
const imageOptions = $derived(options.map((o) => ({ value: String(o.value), label: o.label })));
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<!-- A parameter select drawn with the host's image-editor `Select` (string values mapped back to the options' own). -->
|
|
11
|
+
<ImageSelect value={String(value)} options={imageOptions} {label} {disabled} class={className} onchange={(v) => onchange(matchParameterOptionValue(options, v))} />
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ParameterSelectProps } from '../../parameters/controls.js';
|
|
2
|
+
declare const ImageParameterSelect: import("svelte").Component<ParameterSelectProps, {}, "">;
|
|
3
|
+
type ImageParameterSelect = ReturnType<typeof ImageParameterSelect>;
|
|
4
|
+
export default ImageParameterSelect;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ParameterSliderProps } from '../../parameters/controls.js';
|
|
3
|
+
import ImageRange from './ImageRange.svelte';
|
|
4
|
+
|
|
5
|
+
let { value, min, max, step, label, disabled = false, class: className = '', oninput, ongesturestart, ongestureend }: ParameterSliderProps = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<!-- A parameter slider drawn with the host's image-editor `Range`. -->
|
|
9
|
+
<ImageRange {value} {min} {max} {step} {label} {disabled} class={className} {oninput} onstart={ongesturestart} onend={ongestureend} />
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ParameterSliderProps } from '../../parameters/controls.js';
|
|
2
|
+
declare const ImageParameterSlider: import("svelte").Component<ParameterSliderProps, {}, "">;
|
|
3
|
+
type ImageParameterSlider = ReturnType<typeof ImageParameterSlider>;
|
|
4
|
+
export default ImageParameterSlider;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ParameterComponents } from '../../parameters/controls.js';
|
|
2
|
+
import type { ImageEditorControls } from './controls.js';
|
|
3
|
+
/** The host's image-editor `Range` and `Select` as parameter controls (only the ones it set). */
|
|
4
|
+
export declare function imageEditorParameterComponents(controls: ImageEditorControls): ParameterComponents;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ImageParameterSelect from './ImageParameterSelect.svelte';
|
|
2
|
+
import ImageParameterSlider from './ImageParameterSlider.svelte';
|
|
3
|
+
/** The host's image-editor `Range` and `Select` as parameter controls (only the ones it set). */
|
|
4
|
+
export function imageEditorParameterComponents(controls) {
|
|
5
|
+
return {
|
|
6
|
+
slider: controls.Range ? ImageParameterSlider : undefined,
|
|
7
|
+
select: controls.Select ? ImageParameterSelect : undefined,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
-
import { parameterInputClass } from './styles.js';
|
|
3
|
+
import { parameterInputClass, parameterInputStyle } from './styles.js';
|
|
4
4
|
|
|
5
5
|
interface Props extends Omit<HTMLInputAttributes, 'value' | 'class'> {
|
|
6
6
|
value: string;
|
|
@@ -11,15 +11,27 @@
|
|
|
11
11
|
class?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
let { value, oncommit, multiline = false, class: className = parameterInputClass, onkeydown, ...rest }: Props = $props();
|
|
14
|
+
let { value, oncommit, multiline = false, class: className = parameterInputClass, onkeydown, onchange, style, ...rest }: Props = $props();
|
|
15
|
+
|
|
16
|
+
/** The text last handed to `oncommit` (null once `value` changes), so the blur after Tab or `change` is a no-op. */
|
|
17
|
+
let lastCommitted: string | null = null;
|
|
18
|
+
$effect.pre(() => {
|
|
19
|
+
void value;
|
|
20
|
+
lastCommitted = null;
|
|
21
|
+
});
|
|
15
22
|
|
|
16
23
|
function commit(el: HTMLInputElement | HTMLTextAreaElement) {
|
|
17
|
-
|
|
24
|
+
const text = el.value;
|
|
25
|
+
if (text === value || text === lastCommitted) return;
|
|
26
|
+
oncommit(text, el);
|
|
27
|
+
// A refused edit puts the stored text back: then the same text is tried (and refused) again.
|
|
28
|
+
lastCommitted = el.value === text ? text : null;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
function keydown(e: KeyboardEvent & { currentTarget: HTMLInputElement | HTMLTextAreaElement }) {
|
|
21
32
|
(onkeydown as ((e: KeyboardEvent) => void) | undefined)?.(e);
|
|
22
33
|
if (e.defaultPrevented) return;
|
|
34
|
+
if (e.key === 'Tab') commit(e.currentTarget);
|
|
23
35
|
if (e.key === 'Enter' && (!multiline || e.metaKey || e.ctrlKey)) {
|
|
24
36
|
e.preventDefault();
|
|
25
37
|
commit(e.currentTarget);
|
|
@@ -32,10 +44,27 @@
|
|
|
32
44
|
<textarea
|
|
33
45
|
{...rest as any}
|
|
34
46
|
class="{className} h-auto min-h-[3.5rem] py-1"
|
|
47
|
+
style="{parameterInputStyle} {style ?? ''}"
|
|
35
48
|
{value}
|
|
36
49
|
onblur={(e) => commit(e.currentTarget)}
|
|
50
|
+
onchange={(e) => {
|
|
51
|
+
(onchange as ((e: Event) => void) | undefined)?.(e);
|
|
52
|
+
commit(e.currentTarget);
|
|
53
|
+
}}
|
|
37
54
|
onkeydown={keydown}
|
|
38
55
|
></textarea>
|
|
39
56
|
{:else}
|
|
40
|
-
<input
|
|
57
|
+
<input
|
|
58
|
+
type="text"
|
|
59
|
+
{...rest}
|
|
60
|
+
class={className}
|
|
61
|
+
style="{parameterInputStyle} {style ?? ''}"
|
|
62
|
+
{value}
|
|
63
|
+
onblur={(e) => commit(e.currentTarget)}
|
|
64
|
+
onchange={(e) => {
|
|
65
|
+
(onchange as ((e: Event) => void) | undefined)?.(e);
|
|
66
|
+
commit(e.currentTarget);
|
|
67
|
+
}}
|
|
68
|
+
onkeydown={keydown}
|
|
69
|
+
/>
|
|
41
70
|
{/if}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import ParameterText from './ParameterText.svelte';
|
|
8
8
|
import ParameterToggle from './ParameterToggle.svelte';
|
|
9
9
|
import ParameterVector from './ParameterVector.svelte';
|
|
10
|
+
import type { ParameterComponents } from './controls.js';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
field: ParameterField;
|
|
@@ -17,23 +18,24 @@
|
|
|
17
18
|
resolveFile?: (file: File) => string | Promise<string>;
|
|
18
19
|
disabled?: boolean;
|
|
19
20
|
id?: string;
|
|
21
|
+
components?: ParameterComponents;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
let { field, value, onchange, ongesturestart, ongestureend, resolveFile, disabled = false, id }: Props = $props();
|
|
24
|
+
let { field, value, onchange, ongesturestart, ongestureend, resolveFile, disabled = false, id, components }: Props = $props();
|
|
23
25
|
const set = $derived(onchange as (value: any) => void);
|
|
24
26
|
</script>
|
|
25
27
|
|
|
26
28
|
<!-- The control for any field type. -->
|
|
27
29
|
{#if field.type === 'number' || field.type === 'int'}
|
|
28
|
-
<ParameterSlider {field} value={value as number} onchange={set} {ongesturestart} {ongestureend} {disabled} {id} />
|
|
30
|
+
<ParameterSlider {field} value={value as number} onchange={set} {ongesturestart} {ongestureend} {disabled} {id} {components} />
|
|
29
31
|
{:else if field.type === 'boolean'}
|
|
30
32
|
<ParameterToggle {field} value={value as boolean} onchange={set} {disabled} {id} />
|
|
31
33
|
{:else if field.type === 'choice'}
|
|
32
|
-
<ParameterSelect {field} value={value as string | number} onchange={set} {disabled} {id} />
|
|
34
|
+
<ParameterSelect {field} value={value as string | number} onchange={set} {disabled} {id} {components} />
|
|
33
35
|
{:else if field.type === 'color'}
|
|
34
36
|
<ParameterColor {field} value={value as string} onchange={set} {ongesturestart} {ongestureend} {disabled} {id} />
|
|
35
37
|
{:else if field.type === 'vector'}
|
|
36
|
-
<ParameterVector {field} value={value as number[]} onchange={set} {disabled} {id} />
|
|
38
|
+
<ParameterVector {field} value={value as number[]} onchange={set} {disabled} {id} {components} />
|
|
37
39
|
{:else if field.type === 'image'}
|
|
38
40
|
<ParameterImage {field} value={value as string} onchange={set} {resolveFile} {disabled} {id} />
|
|
39
41
|
{:else if field.type === 'text'}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ParameterField, ParameterValue } from '@pixerate/editor/parameters';
|
|
2
|
+
import type { ParameterComponents } from './controls.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
field: ParameterField;
|
|
4
5
|
value: ParameterValue;
|
|
@@ -8,6 +9,7 @@ interface Props {
|
|
|
8
9
|
resolveFile?: (file: File) => string | Promise<string>;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
id?: string;
|
|
12
|
+
components?: ParameterComponents;
|
|
11
13
|
}
|
|
12
14
|
declare const ParameterFieldControl: import("svelte").Component<Props, {}, "">;
|
|
13
15
|
type ParameterFieldControl = ReturnType<typeof ParameterFieldControl>;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
type NumberParameterField
|
|
9
9
|
} from '@pixerate/editor/parameters';
|
|
10
10
|
import ParameterCommitInput from './ParameterCommitInput.svelte';
|
|
11
|
-
import { parameterInputClass } from './styles.js';
|
|
11
|
+
import { parameterInputClass, parameterInputStyle } from './styles.js';
|
|
12
|
+
import { getParameterComponents, resolveParameterComponents, type ParameterComponents } from './controls.js';
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
14
15
|
field: NumberParameterField;
|
|
@@ -17,11 +18,14 @@
|
|
|
17
18
|
disabled?: boolean;
|
|
18
19
|
id?: string;
|
|
19
20
|
'aria-label'?: string;
|
|
21
|
+
components?: ParameterComponents;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
let { field, value, onchange, disabled = false, id, 'aria-label': ariaLabel }: Props = $props();
|
|
24
|
+
let { field, value, onchange, disabled = false, id, 'aria-label': ariaLabel, components }: Props = $props();
|
|
23
25
|
|
|
24
26
|
const text = $derived(formatParameterNumber({ ...field, unit: undefined }, value));
|
|
27
|
+
const context = getParameterComponents();
|
|
28
|
+
const CustomInput = $derived(resolveParameterComponents(components, context).input);
|
|
25
29
|
|
|
26
30
|
function keydown(e: KeyboardEvent) {
|
|
27
31
|
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
|
@@ -29,25 +33,45 @@
|
|
|
29
33
|
const step = parameterStep(field) * (e.shiftKey ? 10 : 1);
|
|
30
34
|
onchange(clampParameterNumber(field, value + (e.key === 'ArrowUp' ? step : -step)));
|
|
31
35
|
}
|
|
36
|
+
|
|
37
|
+
function commitValue(t: string, input?: HTMLInputElement | HTMLTextAreaElement) {
|
|
38
|
+
const n = parseParameterNumberInput(field, t);
|
|
39
|
+
// Show the stored value again when the text was not a number or clamps to it.
|
|
40
|
+
if (n === null || n === value) {
|
|
41
|
+
if (input) input.value = text;
|
|
42
|
+
} else {
|
|
43
|
+
onchange(n);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
32
46
|
</script>
|
|
33
47
|
|
|
34
48
|
<!-- A number box: type a value (units allowed), or step with the arrow keys (Shift ×10). -->
|
|
35
49
|
<div class="relative min-w-0 flex-1">
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
{#if CustomInput}
|
|
51
|
+
<CustomInput
|
|
52
|
+
{id}
|
|
53
|
+
inputmode="decimal"
|
|
54
|
+
aria-label={ariaLabel ?? parameterLabel(field)}
|
|
55
|
+
label={parameterLabel(field)}
|
|
56
|
+
value={text}
|
|
57
|
+
disabled={disabled || field.readOnly}
|
|
58
|
+
class="{parameterInputClass} {field.unit ? 'pr-6' : ''}"
|
|
59
|
+
style={parameterInputStyle}
|
|
60
|
+
onkeydown={keydown}
|
|
61
|
+
oncommit={commitValue}
|
|
62
|
+
/>
|
|
63
|
+
{:else}
|
|
64
|
+
<ParameterCommitInput
|
|
65
|
+
{id}
|
|
66
|
+
inputmode="decimal"
|
|
67
|
+
aria-label={ariaLabel ?? parameterLabel(field)}
|
|
68
|
+
value={text}
|
|
69
|
+
disabled={disabled || field.readOnly}
|
|
70
|
+
class="{parameterInputClass} {field.unit ? 'pr-6' : ''}"
|
|
71
|
+
onkeydown={keydown}
|
|
72
|
+
oncommit={commitValue}
|
|
73
|
+
/>
|
|
74
|
+
{/if}
|
|
51
75
|
{#if field.unit}
|
|
52
76
|
<span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground">{field.unit}</span>
|
|
53
77
|
{/if}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type NumberParameterField } from '@pixerate/editor/parameters';
|
|
2
|
+
import { type ParameterComponents } from './controls.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
field: NumberParameterField;
|
|
4
5
|
value: number;
|
|
@@ -6,6 +7,7 @@ interface Props {
|
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
id?: string;
|
|
8
9
|
'aria-label'?: string;
|
|
10
|
+
components?: ParameterComponents;
|
|
9
11
|
}
|
|
10
12
|
declare const ParameterNumberInput: import("svelte").Component<Props, {}, "">;
|
|
11
13
|
type ParameterNumberInput = ReturnType<typeof ParameterNumberInput>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { choiceOptionLabel, getChoiceOptions, parameterLabel, type ChoiceParameterField } from '@pixerate/editor/parameters';
|
|
3
|
-
import { parameterInputClass } from './styles.js';
|
|
2
|
+
import { choiceOptionLabel, getChoiceOptions, matchParameterOptionValue, parameterLabel, type ChoiceParameterField } from '@pixerate/editor/parameters';
|
|
3
|
+
import { parameterInputClass, parameterSelectStyle } from './styles.js';
|
|
4
|
+
import { getParameterComponents, resolveParameterComponents, type ParameterComponents } from './controls.js';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
field: ChoiceParameterField;
|
|
@@ -8,17 +9,22 @@
|
|
|
8
9
|
onchange: (value: string | number) => void;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
id?: string;
|
|
12
|
+
components?: ParameterComponents;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
let { field, value, onchange, disabled = false, id }: Props = $props();
|
|
15
|
+
let { field, value, onchange, disabled = false, id, components }: Props = $props();
|
|
14
16
|
|
|
15
17
|
const options = $derived(getChoiceOptions(field));
|
|
16
18
|
const off = $derived(disabled || !!field.readOnly);
|
|
19
|
+
const context = getParameterComponents();
|
|
20
|
+
const CustomSelect = $derived(resolveParameterComponents(components, context).select);
|
|
21
|
+
const label = $derived(parameterLabel(field));
|
|
22
|
+
const customOptions = $derived(options.map((o) => ({ value: o.value, label: choiceOptionLabel(o), description: o.description })));
|
|
17
23
|
</script>
|
|
18
24
|
|
|
19
25
|
<!-- A dropdown (or segmented buttons with `control: 'segmented'`) for `choice` fields. -->
|
|
20
26
|
{#if field.control === 'segmented'}
|
|
21
|
-
<div role="radiogroup" aria-label={
|
|
27
|
+
<div role="radiogroup" aria-label={label} class="inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5">
|
|
22
28
|
{#each options as o (o.value)}
|
|
23
29
|
<button
|
|
24
30
|
type="button"
|
|
@@ -35,20 +41,33 @@
|
|
|
35
41
|
</button>
|
|
36
42
|
{/each}
|
|
37
43
|
</div>
|
|
44
|
+
{:else if CustomSelect}
|
|
45
|
+
<CustomSelect
|
|
46
|
+
{id}
|
|
47
|
+
aria-label={label}
|
|
48
|
+
{label}
|
|
49
|
+
{value}
|
|
50
|
+
options={customOptions}
|
|
51
|
+
disabled={off}
|
|
52
|
+
class={parameterInputClass}
|
|
53
|
+
style={parameterSelectStyle}
|
|
54
|
+
onchange={(v) => onchange(matchParameterOptionValue(options, v))}
|
|
55
|
+
/>
|
|
38
56
|
{:else}
|
|
39
57
|
<select
|
|
40
58
|
{id}
|
|
41
|
-
aria-label={
|
|
59
|
+
aria-label={label}
|
|
42
60
|
value={String(options.findIndex((o) => o.value === value))}
|
|
43
61
|
disabled={off}
|
|
44
62
|
class={parameterInputClass}
|
|
63
|
+
style={parameterSelectStyle}
|
|
45
64
|
onchange={(e) => {
|
|
46
65
|
const option = options[Number(e.currentTarget.value)];
|
|
47
66
|
if (option) onchange(option.value);
|
|
48
67
|
}}
|
|
49
68
|
>
|
|
50
69
|
{#each options as o, i (o.value)}
|
|
51
|
-
<option value={String(i)} title={o.description}>{choiceOptionLabel(o)}</option>
|
|
70
|
+
<option value={String(i)} title={o.description} style={parameterSelectStyle}>{choiceOptionLabel(o)}</option>
|
|
52
71
|
{/each}
|
|
53
72
|
</select>
|
|
54
73
|
{/if}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type ChoiceParameterField } from '@pixerate/editor/parameters';
|
|
2
|
+
import { type ParameterComponents } from './controls.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
field: ChoiceParameterField;
|
|
4
5
|
value: string | number;
|
|
5
6
|
onchange: (value: string | number) => void;
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
id?: string;
|
|
9
|
+
components?: ParameterComponents;
|
|
8
10
|
}
|
|
9
11
|
declare const ParameterSelect: import("svelte").Component<Props, {}, "">;
|
|
10
12
|
type ParameterSelect = ReturnType<typeof ParameterSelect>;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
type NumberParameterField
|
|
9
9
|
} from '@pixerate/editor/parameters';
|
|
10
10
|
import ParameterNumberInput from './ParameterNumberInput.svelte';
|
|
11
|
-
import { parameterRangeClass } from './styles.js';
|
|
11
|
+
import { parameterRangeClass, parameterRangeStyle } from './styles.js';
|
|
12
|
+
import { getParameterComponents, resolveParameterComponents, type ParameterComponents } from './controls.js';
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
14
15
|
field: NumberParameterField;
|
|
@@ -19,9 +20,10 @@
|
|
|
19
20
|
ongestureend?: () => void;
|
|
20
21
|
disabled?: boolean;
|
|
21
22
|
id?: string;
|
|
23
|
+
components?: ParameterComponents;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
let { field, value, onchange, ongesturestart, ongestureend, disabled = false, id }: Props = $props();
|
|
26
|
+
let { field, value, onchange, ongesturestart, ongestureend, disabled = false, id, components }: Props = $props();
|
|
25
27
|
|
|
26
28
|
let dragging = false;
|
|
27
29
|
function start() {
|
|
@@ -34,31 +36,57 @@
|
|
|
34
36
|
dragging = false;
|
|
35
37
|
ongestureend?.();
|
|
36
38
|
}
|
|
39
|
+
|
|
40
|
+
const context = getParameterComponents();
|
|
41
|
+
const CustomSlider = $derived(resolveParameterComponents(components, context).slider);
|
|
42
|
+
const step = $derived(parameterStep(field));
|
|
43
|
+
const label = $derived(parameterLabel(field));
|
|
44
|
+
const valuetext = $derived(formatParameterNumber(field, value));
|
|
37
45
|
</script>
|
|
38
46
|
|
|
39
47
|
<!-- A slider plus number box for `number` / `int` fields (number box only for `control: 'input'` or unbounded fields). -->
|
|
40
48
|
{#if isSliderField(field)}
|
|
41
49
|
<div class="flex items-center gap-2">
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
{#if CustomSlider}
|
|
51
|
+
<CustomSlider
|
|
52
|
+
id={id ? `${id}-slider` : undefined}
|
|
53
|
+
aria-label={label}
|
|
54
|
+
{label}
|
|
55
|
+
aria-valuetext={valuetext}
|
|
56
|
+
min={field.min ?? 0}
|
|
57
|
+
max={field.max ?? 100}
|
|
58
|
+
{step}
|
|
59
|
+
{value}
|
|
60
|
+
disabled={disabled || field.readOnly}
|
|
61
|
+
class={parameterRangeClass}
|
|
62
|
+
style={parameterRangeStyle}
|
|
63
|
+
ongesturestart={start}
|
|
64
|
+
ongestureend={end}
|
|
65
|
+
oninput={(v) => onchange(clampParameterNumber(field, v))}
|
|
66
|
+
/>
|
|
67
|
+
{:else}
|
|
68
|
+
<input
|
|
69
|
+
type="range"
|
|
70
|
+
aria-label={label}
|
|
71
|
+
aria-valuetext={valuetext}
|
|
72
|
+
min={field.min ?? 0}
|
|
73
|
+
max={field.max ?? 100}
|
|
74
|
+
{step}
|
|
75
|
+
{value}
|
|
76
|
+
disabled={disabled || field.readOnly}
|
|
77
|
+
class={parameterRangeClass}
|
|
78
|
+
style={parameterRangeStyle}
|
|
79
|
+
onpointerdown={start}
|
|
80
|
+
onpointerup={end}
|
|
81
|
+
onpointercancel={end}
|
|
82
|
+
onblur={end}
|
|
83
|
+
oninput={(e) => onchange(clampParameterNumber(field, Number(e.currentTarget.value)))}
|
|
84
|
+
/>
|
|
85
|
+
{/if}
|
|
58
86
|
<div class="w-20 shrink-0">
|
|
59
|
-
<ParameterNumberInput {field} {value} {onchange} {disabled} {id} aria-label="{
|
|
87
|
+
<ParameterNumberInput {field} {value} {onchange} {disabled} {id} aria-label="{label} value" {components} />
|
|
60
88
|
</div>
|
|
61
89
|
</div>
|
|
62
90
|
{:else}
|
|
63
|
-
<ParameterNumberInput {field} {value} {onchange} {disabled} {id} />
|
|
91
|
+
<ParameterNumberInput {field} {value} {onchange} {disabled} {id} {components} />
|
|
64
92
|
{/if}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type NumberParameterField } from '@pixerate/editor/parameters';
|
|
2
|
+
import { type ParameterComponents } from './controls.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
field: NumberParameterField;
|
|
4
5
|
value: number;
|
|
@@ -8,6 +9,7 @@ interface Props {
|
|
|
8
9
|
ongestureend?: () => void;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
id?: string;
|
|
12
|
+
components?: ParameterComponents;
|
|
11
13
|
}
|
|
12
14
|
declare const ParameterSlider: import("svelte").Component<Props, {}, "">;
|
|
13
15
|
type ParameterSlider = ReturnType<typeof ParameterSlider>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { parameterLabel, vectorComponentField, vectorSize, type VectorParameterField } from '@pixerate/editor/parameters';
|
|
3
3
|
import ParameterNumberInput from './ParameterNumberInput.svelte';
|
|
4
|
+
import type { ParameterComponents } from './controls.js';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
field: VectorParameterField;
|
|
@@ -8,17 +9,18 @@
|
|
|
8
9
|
onchange: (value: number[]) => void;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
id?: string;
|
|
12
|
+
components?: ParameterComponents;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
let { field, value, onchange, disabled = false, id }: Props = $props();
|
|
15
|
+
let { field, value, onchange, disabled = false, id, components }: Props = $props();
|
|
14
16
|
|
|
15
17
|
const size = $derived(vectorSize(field));
|
|
16
|
-
const
|
|
18
|
+
const vectorFields = $derived(Array.from({ length: size }, (_, i) => vectorComponentField(field, i)));
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
21
|
<!-- One number box per component for `vector` fields. -->
|
|
20
22
|
<div class="grid gap-1.5" style:grid-template-columns="repeat({size}, minmax(0, 1fr))">
|
|
21
|
-
{#each
|
|
23
|
+
{#each vectorFields as component, i (i)}
|
|
22
24
|
<label class="flex min-w-0 items-center gap-1">
|
|
23
25
|
<span class="w-3 shrink-0 text-[10px] uppercase text-muted-foreground">{component.label}</span>
|
|
24
26
|
<ParameterNumberInput
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
id={i === 0 ? id : undefined}
|
|
28
30
|
aria-label="{parameterLabel(field)} {component.label}"
|
|
29
31
|
disabled={disabled || field.readOnly}
|
|
32
|
+
{components}
|
|
30
33
|
onchange={(n) => onchange(Array.from({ length: size }, (_, j) => (j === i ? n : (value[j] ?? 0))))}
|
|
31
34
|
/>
|
|
32
35
|
</label>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type VectorParameterField } from '@pixerate/editor/parameters';
|
|
2
|
+
import type { ParameterComponents } from './controls.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
field: VectorParameterField;
|
|
4
5
|
value: number[];
|
|
5
6
|
onchange: (value: number[]) => void;
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
id?: string;
|
|
9
|
+
components?: ParameterComponents;
|
|
8
10
|
}
|
|
9
11
|
declare const ParameterVector: import("svelte").Component<Props, {}, "">;
|
|
10
12
|
type ParameterVector = ReturnType<typeof ParameterVector>;
|
|
@@ -20,7 +20,14 @@
|
|
|
20
20
|
} from '@pixerate/editor/parameters';
|
|
21
21
|
import KeyframeDiamond from './KeyframeDiamond.svelte';
|
|
22
22
|
import ParameterFieldControl from './ParameterFieldControl.svelte';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
parameterButtonClass,
|
|
25
|
+
parameterButtonStyle,
|
|
26
|
+
parameterInputClass,
|
|
27
|
+
parameterInputStyle,
|
|
28
|
+
parameterSelectStyle
|
|
29
|
+
} from './styles.js';
|
|
30
|
+
import { getParameterComponents, resolveParameterComponents, type ParameterComponents } from './controls.js';
|
|
24
31
|
|
|
25
32
|
interface Props {
|
|
26
33
|
controller: ParametersController;
|
|
@@ -36,6 +43,8 @@
|
|
|
36
43
|
showFieldReset?: boolean;
|
|
37
44
|
/** Turns a picked file into a URL for image fields. */
|
|
38
45
|
resolveFile?: (file: File) => string | Promise<string>;
|
|
46
|
+
/** Custom host components for inputs, sliders, and selects. */
|
|
47
|
+
components?: ParameterComponents;
|
|
39
48
|
/** Shown when the schema has no fields. */
|
|
40
49
|
emptyMessage?: string;
|
|
41
50
|
/** Prefix for the panel's element ids. */
|
|
@@ -68,9 +77,13 @@
|
|
|
68
77
|
children,
|
|
69
78
|
playback,
|
|
70
79
|
playbackId = 'parameters',
|
|
71
|
-
onplaybackreveal
|
|
80
|
+
onplaybackreveal,
|
|
81
|
+
components
|
|
72
82
|
}: Props = $props();
|
|
73
83
|
|
|
84
|
+
const parameterContext = getParameterComponents();
|
|
85
|
+
const CustomPresetSelect = $derived(resolveParameterComponents(components, parameterContext).select);
|
|
86
|
+
|
|
74
87
|
// Agent playback: the cursor visits the controls a call changes.
|
|
75
88
|
let root: HTMLDivElement | undefined = $state();
|
|
76
89
|
const ambientPlayback = getAgentPlayback();
|
|
@@ -140,6 +153,7 @@
|
|
|
140
153
|
value={view.values[field.id]}
|
|
141
154
|
id={inputId}
|
|
142
155
|
{resolveFile}
|
|
156
|
+
{components}
|
|
143
157
|
onchange={(v) => controller.setValue(field.id, v)}
|
|
144
158
|
ongesturestart={() => controller.beginGesture(field.id)}
|
|
145
159
|
ongestureend={() => controller.endGesture()}
|
|
@@ -200,6 +214,7 @@
|
|
|
200
214
|
<button
|
|
201
215
|
type="button"
|
|
202
216
|
class={parameterButtonClass}
|
|
217
|
+
style={parameterButtonStyle}
|
|
203
218
|
disabled={!view.canUndo}
|
|
204
219
|
title={view.canUndo ? `Undo ${view.undoLabel}` : 'Nothing to undo'}
|
|
205
220
|
onclick={() => controller.undo()}>Undo</button
|
|
@@ -207,12 +222,13 @@
|
|
|
207
222
|
<button
|
|
208
223
|
type="button"
|
|
209
224
|
class={parameterButtonClass}
|
|
225
|
+
style={parameterButtonStyle}
|
|
210
226
|
disabled={!view.canRedo}
|
|
211
227
|
title={view.canRedo ? `Redo ${view.redoLabel}` : 'Nothing to redo'}
|
|
212
228
|
onclick={() => controller.redo()}>Redo</button
|
|
213
229
|
>
|
|
214
|
-
<button type="button" class={parameterButtonClass} disabled={!view.modified} onclick={() => controller.reset()}>Reset</button>
|
|
215
|
-
<button type="button" class={parameterButtonClass} onclick={() => controller.randomize()}>Randomize</button>
|
|
230
|
+
<button type="button" class={parameterButtonClass} style={parameterButtonStyle} disabled={!view.modified} onclick={() => controller.reset()}>Reset</button>
|
|
231
|
+
<button type="button" class={parameterButtonClass} style={parameterButtonStyle} onclick={() => controller.randomize()}>Randomize</button>
|
|
216
232
|
</div>
|
|
217
233
|
{/if}
|
|
218
234
|
</div>
|
|
@@ -221,22 +237,42 @@
|
|
|
221
237
|
{#if showPresets}
|
|
222
238
|
<div class="space-y-1.5" data-parameter-presets="">
|
|
223
239
|
<div class="flex items-center gap-1.5">
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
{#if CustomPresetSelect}
|
|
241
|
+
<CustomPresetSelect
|
|
242
|
+
aria-label="Preset"
|
|
243
|
+
label="Preset"
|
|
244
|
+
value={view.activePreset ?? ''}
|
|
245
|
+
options={[
|
|
246
|
+
{ value: '', label: view.presets.length ? 'Presets…' : 'No presets' },
|
|
247
|
+
...view.presets.map((p) => ({ value: p.name, label: p.name, description: p.description }))
|
|
248
|
+
]}
|
|
249
|
+
class={parameterInputClass}
|
|
250
|
+
style={parameterSelectStyle}
|
|
251
|
+
onchange={(val) => {
|
|
252
|
+
const str = String(val);
|
|
253
|
+
if (str) controller.applyPreset(str);
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
{:else}
|
|
257
|
+
<select
|
|
258
|
+
aria-label="Preset"
|
|
259
|
+
class={parameterInputClass}
|
|
260
|
+
style={parameterSelectStyle}
|
|
261
|
+
value={view.activePreset ?? ''}
|
|
262
|
+
onchange={(e) => e.currentTarget.value && controller.applyPreset(e.currentTarget.value)}
|
|
263
|
+
>
|
|
264
|
+
<option value="" style={parameterSelectStyle}>{view.presets.length ? 'Presets…' : 'No presets'}</option>
|
|
265
|
+
{#each view.presets as p (p.name)}
|
|
266
|
+
<option value={p.name} title={p.description} style={parameterSelectStyle}>{p.name}</option>
|
|
267
|
+
{/each}
|
|
268
|
+
</select>
|
|
269
|
+
{/if}
|
|
270
|
+
<button type="button" class={parameterButtonClass} style={parameterButtonStyle} aria-expanded={saving} onclick={() => (saving = !saving)}>Save…</button>
|
|
236
271
|
{#if activePreset && !activePreset.builtIn}
|
|
237
272
|
<button
|
|
238
273
|
type="button"
|
|
239
274
|
class={parameterButtonClass}
|
|
275
|
+
style={parameterButtonStyle}
|
|
240
276
|
aria-label="Delete preset {activePreset.name}"
|
|
241
277
|
onclick={() => controller.deletePreset(activePreset.name)}>Delete</button
|
|
242
278
|
>
|
|
@@ -245,8 +281,8 @@
|
|
|
245
281
|
{#if saving}
|
|
246
282
|
<form class="flex items-center gap-1.5" onsubmit={savePreset}>
|
|
247
283
|
<!-- svelte-ignore a11y_autofocus -->
|
|
248
|
-
<input autofocus aria-label="Preset name" placeholder="Preset name" class={parameterInputClass} bind:value={presetName} />
|
|
249
|
-
<button type="submit" class={parameterButtonClass}>Save</button>
|
|
284
|
+
<input autofocus aria-label="Preset name" placeholder="Preset name" class={parameterInputClass} style={parameterInputStyle} bind:value={presetName} />
|
|
285
|
+
<button type="submit" class={parameterButtonClass} style={parameterButtonStyle}>Save</button>
|
|
250
286
|
</form>
|
|
251
287
|
{/if}
|
|
252
288
|
{#if presetMessage}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Snippet } from 'svelte';
|
|
2
2
|
import type { AgentPlayback } from '@pixerate/editor/tools';
|
|
3
3
|
import { type ParametersController } from '@pixerate/editor/parameters';
|
|
4
|
+
import { type ParameterComponents } from './controls.js';
|
|
4
5
|
interface Props {
|
|
5
6
|
controller: ParametersController;
|
|
6
7
|
/** Heading (default: the schema's title). Pass `null` for none. */
|
|
@@ -15,6 +16,8 @@ interface Props {
|
|
|
15
16
|
showFieldReset?: boolean;
|
|
16
17
|
/** Turns a picked file into a URL for image fields. */
|
|
17
18
|
resolveFile?: (file: File) => string | Promise<string>;
|
|
19
|
+
/** Custom host components for inputs, sliders, and selects. */
|
|
20
|
+
components?: ParameterComponents;
|
|
18
21
|
/** Shown when the schema has no fields. */
|
|
19
22
|
emptyMessage?: string;
|
|
20
23
|
/** Prefix for the panel's element ids. */
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
/** Props for a custom number input in parameter panels. Call `oncommit` when an edit is done (blur, Enter, Tab). */
|
|
3
|
+
export interface ParameterInputProps {
|
|
4
|
+
id?: string;
|
|
5
|
+
/** The stored value as text, without the unit. */
|
|
6
|
+
value: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
'aria-label'?: string;
|
|
9
|
+
/** The field's label. */
|
|
10
|
+
label: string;
|
|
11
|
+
inputmode?: 'decimal' | 'text' | 'numeric';
|
|
12
|
+
class?: string;
|
|
13
|
+
style?: string;
|
|
14
|
+
/** Commits typed text. Pass the element to have a refused edit show the stored value again. */
|
|
15
|
+
oncommit: (value: string, input?: HTMLInputElement | HTMLTextAreaElement) => void;
|
|
16
|
+
/** Arrow keys step the value (Shift ×10). */
|
|
17
|
+
onkeydown?: (e: KeyboardEvent) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Props for a custom slider in parameter panels. */
|
|
20
|
+
export interface ParameterSliderProps {
|
|
21
|
+
id?: string;
|
|
22
|
+
value: number;
|
|
23
|
+
min: number;
|
|
24
|
+
max: number;
|
|
25
|
+
step: number;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
'aria-label'?: string;
|
|
28
|
+
/** The field's label. */
|
|
29
|
+
label: string;
|
|
30
|
+
'aria-valuetext'?: string;
|
|
31
|
+
class?: string;
|
|
32
|
+
style?: string;
|
|
33
|
+
/** Every change while the value moves. */
|
|
34
|
+
oninput: (value: number) => void;
|
|
35
|
+
/** A drag (or key press) starts: changes until `ongestureend` merge into one undo step. */
|
|
36
|
+
ongesturestart?: () => void;
|
|
37
|
+
ongestureend?: () => void;
|
|
38
|
+
}
|
|
39
|
+
/** One option in a dropdown parameter control. */
|
|
40
|
+
export interface ParameterSelectOption {
|
|
41
|
+
value: string | number;
|
|
42
|
+
label: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Props for a custom select in parameter panels. `onchange` takes an option's `value` (or its string form). */
|
|
46
|
+
export interface ParameterSelectProps {
|
|
47
|
+
id?: string;
|
|
48
|
+
value: string | number;
|
|
49
|
+
options: readonly ParameterSelectOption[];
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
'aria-label'?: string;
|
|
52
|
+
/** The control's label. */
|
|
53
|
+
label: string;
|
|
54
|
+
class?: string;
|
|
55
|
+
style?: string;
|
|
56
|
+
onchange: (value: string | number) => void;
|
|
57
|
+
}
|
|
58
|
+
/** Host form controls for parameter panels. Any left out fall back to the default controls. */
|
|
59
|
+
export interface ParameterComponents {
|
|
60
|
+
input?: Component<ParameterInputProps>;
|
|
61
|
+
slider?: Component<ParameterSliderProps>;
|
|
62
|
+
select?: Component<ParameterSelectProps>;
|
|
63
|
+
}
|
|
64
|
+
/** Makes the parameter panels below this component use the host's controls (call it while a component initialises). */
|
|
65
|
+
export declare function setParameterComponents(components: ParameterComponents): ParameterComponents;
|
|
66
|
+
/** The controls set by an ancestor with {@link setParameterComponents}, or none. */
|
|
67
|
+
export declare function getParameterComponents(): ParameterComponents;
|
|
68
|
+
/** The controls to use: each one from `explicit`, else from `fallback` (default: the context's). */
|
|
69
|
+
export declare function resolveParameterComponents(explicit?: ParameterComponents, fallback?: ParameterComponents): ParameterComponents;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const KEY = Symbol('pixerate.parameters.components');
|
|
3
|
+
/** Makes the parameter panels below this component use the host's controls (call it while a component initialises). */
|
|
4
|
+
export function setParameterComponents(components) {
|
|
5
|
+
return setContext(KEY, components);
|
|
6
|
+
}
|
|
7
|
+
/** The controls set by an ancestor with {@link setParameterComponents}, or none. */
|
|
8
|
+
export function getParameterComponents() {
|
|
9
|
+
return getContext(KEY) ?? {};
|
|
10
|
+
}
|
|
11
|
+
/** The controls to use: each one from `explicit`, else from `fallback` (default: the context's). */
|
|
12
|
+
export function resolveParameterComponents(explicit, fallback = getParameterComponents()) {
|
|
13
|
+
return {
|
|
14
|
+
input: explicit?.input ?? fallback.input,
|
|
15
|
+
slider: explicit?.slider ?? fallback.slider,
|
|
16
|
+
select: explicit?.select ?? fallback.select,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -13,5 +13,6 @@ export { default as ParameterText } from "./ParameterText.svelte";
|
|
|
13
13
|
export { default as ParameterCommitInput } from "./ParameterCommitInput.svelte";
|
|
14
14
|
export { default as KeyframeDiamond } from "./KeyframeDiamond.svelte";
|
|
15
15
|
export * from "./createParameters.svelte.js";
|
|
16
|
+
export * from "./controls.js";
|
|
16
17
|
export * from "./styles.js";
|
|
17
18
|
export type ParametersPanelProps = ComponentProps<typeof ParametersPanelComponent>;
|
package/dist/parameters/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export { default as ParameterText } from "./ParameterText.svelte";
|
|
|
11
11
|
export { default as ParameterCommitInput } from "./ParameterCommitInput.svelte";
|
|
12
12
|
export { default as KeyframeDiamond } from "./KeyframeDiamond.svelte";
|
|
13
13
|
export * from "./createParameters.svelte.js";
|
|
14
|
+
export * from "./controls.js";
|
|
14
15
|
export * from "./styles.js";
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export declare const parameterInputClass = "h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
2
2
|
export declare const parameterButtonClass = "inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50";
|
|
3
3
|
export declare const parameterRangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
4
|
+
/** Inline theme styles (`--pixerate-parameter-*` tokens with host fallbacks), shared with React through core. */
|
|
5
|
+
export declare const parameterInputStyle: string;
|
|
6
|
+
export declare const parameterSelectStyle: string;
|
|
7
|
+
/** Text and border only, so the button classes' `hover:` background still applies. */
|
|
8
|
+
export declare const parameterButtonStyle: string;
|
|
9
|
+
export declare const parameterRangeStyle: string;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { parameterThemeStyleText } from "@pixerate/editor/parameters";
|
|
1
2
|
// Class names shared by the parameter controls (the same as the React package's).
|
|
2
3
|
export const parameterInputClass = "h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
3
4
|
export const parameterButtonClass = "inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50";
|
|
4
5
|
export const parameterRangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
6
|
+
/** Inline theme styles (`--pixerate-parameter-*` tokens with host fallbacks), shared with React through core. */
|
|
7
|
+
export const parameterInputStyle = parameterThemeStyleText("input");
|
|
8
|
+
export const parameterSelectStyle = parameterThemeStyleText("select");
|
|
9
|
+
/** Text and border only, so the button classes' `hover:` background still applies. */
|
|
10
|
+
export const parameterButtonStyle = parameterThemeStyleText("button");
|
|
11
|
+
export const parameterRangeStyle = parameterThemeStyleText("range");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.128.
|
|
3
|
+
"version": "0.128.5",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"**/*.css"
|
|
166
166
|
],
|
|
167
167
|
"dependencies": {
|
|
168
|
-
"@pixerate/editor": "0.
|
|
168
|
+
"@pixerate/editor": "0.175.0"
|
|
169
169
|
},
|
|
170
170
|
"peerDependencies": {
|
|
171
171
|
"@dagrejs/dagre": "^1.1.4",
|