@pixerate/editor-react 0.76.0 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PaintGradientEditorView-CCPCY5Ib.d.cts +14 -0
- package/dist/PaintGradientEditorView-CCPCY5Ib.d.ts +14 -0
- package/dist/canvas/index.cjs +662 -561
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +36 -5
- package/dist/canvas/index.d.ts +36 -5
- package/dist/canvas/index.js +232 -129
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-LWMUV5TC.js → chunk-2EW42UXC.js} +639 -461
- package/dist/chunk-2EW42UXC.js.map +1 -0
- package/dist/chunk-FX3EREF5.js +206 -0
- package/dist/chunk-FX3EREF5.js.map +1 -0
- package/dist/chunk-MO354USR.js +126 -0
- package/dist/chunk-MO354USR.js.map +1 -0
- package/dist/chunk-ZQZAKUUM.js +416 -0
- package/dist/chunk-ZQZAKUUM.js.map +1 -0
- package/dist/effects/index.cjs +606 -0
- package/dist/effects/index.cjs.map +1 -0
- package/dist/effects/index.d.cts +80 -0
- package/dist/effects/index.d.ts +80 -0
- package/dist/effects/index.js +16 -0
- package/dist/effects/index.js.map +1 -0
- package/dist/image-editor/index.cjs +1388 -534
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +36 -3
- package/dist/image-editor/index.d.ts +36 -3
- package/dist/image-editor/index.js +10 -1
- package/dist/index.cjs +1806 -952
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/paint-editor/index.cjs +438 -289
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +8 -3
- package/dist/paint-editor/index.d.ts +8 -3
- package/dist/paint-editor/index.js +215 -187
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/parameters/index.js +55 -439
- package/dist/parameters/index.js.map +1 -1
- package/dist/video-editor/index.cjs +732 -17
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +31 -3
- package/dist/video-editor/index.d.ts +31 -3
- package/dist/video-editor/index.js +184 -17
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +8 -3
- package/dist/chunk-LWMUV5TC.js.map +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ParameterFieldControl
|
|
3
|
+
} from "./chunk-ZQZAKUUM.js";
|
|
4
|
+
|
|
5
|
+
// src/effects/useEffects.ts
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
function useEffects(host, provider) {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!host || !provider) return;
|
|
10
|
+
return host.useEffects(provider);
|
|
11
|
+
}, [host, provider]);
|
|
12
|
+
}
|
|
13
|
+
function useEffectDescription(provider, effect) {
|
|
14
|
+
const [state, setState] = useState({ key: "", description: null, error: null });
|
|
15
|
+
const key = provider && effect ? `${provider.id}:${effect}` : "";
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!provider || !effect) return;
|
|
18
|
+
let live = true;
|
|
19
|
+
Promise.resolve().then(() => provider.describe(effect)).then(
|
|
20
|
+
(description) => live && setState({ key, description, error: null }),
|
|
21
|
+
(e) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) })
|
|
22
|
+
);
|
|
23
|
+
return () => {
|
|
24
|
+
live = false;
|
|
25
|
+
};
|
|
26
|
+
}, [provider, effect, key]);
|
|
27
|
+
return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/effects/EffectThumbnail.tsx
|
|
31
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
32
|
+
import { renderEffectThumbnail } from "@pixerate/editor/effects";
|
|
33
|
+
import { jsx } from "react/jsx-runtime";
|
|
34
|
+
function EffectThumbnail({ provider, effect, width = 96, height = 54, className = "" }) {
|
|
35
|
+
const ref = useRef(null);
|
|
36
|
+
const [failed, setFailed] = useState2(effect.kind === "motion");
|
|
37
|
+
useEffect2(() => {
|
|
38
|
+
if (effect.kind === "motion") return;
|
|
39
|
+
let live = true;
|
|
40
|
+
renderEffectThumbnail(provider, effect, { width, height }).then(
|
|
41
|
+
(pixels) => {
|
|
42
|
+
const ctx = live ? ref.current?.getContext("2d") : null;
|
|
43
|
+
if (!ctx || typeof ImageData === "undefined") return;
|
|
44
|
+
ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);
|
|
45
|
+
},
|
|
46
|
+
() => live && setFailed(true)
|
|
47
|
+
);
|
|
48
|
+
return () => {
|
|
49
|
+
live = false;
|
|
50
|
+
};
|
|
51
|
+
}, [provider, effect, width, height]);
|
|
52
|
+
return /* @__PURE__ */ jsx(
|
|
53
|
+
"div",
|
|
54
|
+
{
|
|
55
|
+
className: `relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`,
|
|
56
|
+
style: { aspectRatio: `${width} / ${height}` },
|
|
57
|
+
"data-effect-thumbnail": effect.id,
|
|
58
|
+
children: failed ? /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground", children: effect.kind === "motion" ? "Motion" : "No preview" }) : /* @__PURE__ */ jsx("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/effects/EffectsBrowser.tsx
|
|
64
|
+
import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
|
|
65
|
+
import { EFFECT_USE_LABELS } from "@pixerate/editor/effects";
|
|
66
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
67
|
+
function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = "Effects", className = "" }) {
|
|
68
|
+
const [all, setAll] = useState3([]);
|
|
69
|
+
const [query, setQuery] = useState3("");
|
|
70
|
+
const [category, setCategory] = useState3("");
|
|
71
|
+
const [use, setUse] = useState3("");
|
|
72
|
+
const [error, setError] = useState3(null);
|
|
73
|
+
useEffect3(() => {
|
|
74
|
+
if (!provider) return setAll([]);
|
|
75
|
+
let live = true;
|
|
76
|
+
Promise.resolve().then(() => provider.list()).then(
|
|
77
|
+
(list) => live && (setAll(list), setError(null)),
|
|
78
|
+
(e) => live && setError(e instanceof Error ? e.message : String(e))
|
|
79
|
+
);
|
|
80
|
+
return () => {
|
|
81
|
+
live = false;
|
|
82
|
+
};
|
|
83
|
+
}, [provider]);
|
|
84
|
+
const [found, setFound] = useState3([]);
|
|
85
|
+
useEffect3(() => {
|
|
86
|
+
if (!provider) return setFound([]);
|
|
87
|
+
let live = true;
|
|
88
|
+
Promise.resolve().then(() => provider.search(query, { ...category ? { category } : {}, ...use ? { use } : {} })).then((list) => live && setFound(list), () => live && setFound([]));
|
|
89
|
+
return () => {
|
|
90
|
+
live = false;
|
|
91
|
+
};
|
|
92
|
+
}, [provider, query, category, use]);
|
|
93
|
+
const allowed = (e) => (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
|
|
94
|
+
const categories = useMemo(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses]);
|
|
95
|
+
const shown = found.filter(allowed);
|
|
96
|
+
if (!provider)
|
|
97
|
+
return /* @__PURE__ */ jsx2("div", { className: `rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`, "data-effects-browser": "", "data-effects-unavailable": "", children: "No effects provider is registered, so effects cannot be added. Effects already in the document are kept." });
|
|
98
|
+
return /* @__PURE__ */ jsxs("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
|
|
99
|
+
title && /* @__PURE__ */ jsx2("h3", { className: "text-xs font-semibold", children: title }),
|
|
100
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
|
|
101
|
+
/* @__PURE__ */ jsx2(
|
|
102
|
+
"input",
|
|
103
|
+
{
|
|
104
|
+
type: "search",
|
|
105
|
+
className: "min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs",
|
|
106
|
+
placeholder: "Search effects",
|
|
107
|
+
"aria-label": "Search effects",
|
|
108
|
+
value: query,
|
|
109
|
+
onChange: (e) => setQuery(e.currentTarget.value)
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
/* @__PURE__ */ jsxs("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Category", value: category, onChange: (e) => setCategory(e.currentTarget.value), children: [
|
|
113
|
+
/* @__PURE__ */ jsx2("option", { value: "", children: "All categories" }),
|
|
114
|
+
categories.map((c) => /* @__PURE__ */ jsx2("option", { value: c, children: c }, c))
|
|
115
|
+
] }),
|
|
116
|
+
uses && uses.length > 1 && /* @__PURE__ */ jsxs("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Use", value: use, onChange: (e) => setUse(e.currentTarget.value), children: [
|
|
117
|
+
/* @__PURE__ */ jsx2("option", { value: "", children: "Any use" }),
|
|
118
|
+
uses.map((u) => /* @__PURE__ */ jsx2("option", { value: u, children: EFFECT_USE_LABELS[u] }, u))
|
|
119
|
+
] })
|
|
120
|
+
] }),
|
|
121
|
+
error && /* @__PURE__ */ jsx2("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
|
|
122
|
+
/* @__PURE__ */ jsx2("ul", { className: "grid grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5", role: "list", children: shown.map((e) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsxs(
|
|
123
|
+
"button",
|
|
124
|
+
{
|
|
125
|
+
type: "button",
|
|
126
|
+
className: "flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border",
|
|
127
|
+
title: e.description ?? e.name,
|
|
128
|
+
"data-effect-id": e.id,
|
|
129
|
+
onClick: () => onPick(e),
|
|
130
|
+
children: [
|
|
131
|
+
thumbnails && /* @__PURE__ */ jsx2(EffectThumbnail, { provider, effect: e }),
|
|
132
|
+
/* @__PURE__ */ jsx2("span", { className: "truncate text-[11px] font-medium", children: e.name }),
|
|
133
|
+
/* @__PURE__ */ jsx2("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => EFFECT_USE_LABELS[u]).join(" \xB7 ") })
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
) }, e.id)) }),
|
|
137
|
+
!shown.length && !error && /* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
|
|
138
|
+
] });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/effects/EffectParameters.tsx
|
|
142
|
+
import React3, { useId, useMemo as useMemo2 } from "react";
|
|
143
|
+
import { effectFieldValue, effectParameterFields } from "@pixerate/editor/effects";
|
|
144
|
+
import { parameterLabel, parameterValuesEqual } from "@pixerate/editor/parameters";
|
|
145
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
146
|
+
function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = "" }) {
|
|
147
|
+
const fields = useMemo2(() => effectParameterFields(description), [description]);
|
|
148
|
+
const idBase = useId();
|
|
149
|
+
const gesture = React3.useRef(null);
|
|
150
|
+
const count = React3.useRef(0);
|
|
151
|
+
return /* @__PURE__ */ jsxs2("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
|
|
152
|
+
onPreset && description.presets.length > 0 && /* @__PURE__ */ jsxs2(
|
|
153
|
+
"select",
|
|
154
|
+
{
|
|
155
|
+
className: "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs",
|
|
156
|
+
"aria-label": `${description.name} presets`,
|
|
157
|
+
value: "",
|
|
158
|
+
disabled,
|
|
159
|
+
onChange: (e) => {
|
|
160
|
+
const preset = description.presets.find((p) => p.name === e.currentTarget.value);
|
|
161
|
+
if (preset) onPreset({ ...description.defaults, ...preset.values });
|
|
162
|
+
},
|
|
163
|
+
children: [
|
|
164
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Presets\u2026" }),
|
|
165
|
+
description.presets.map((p) => /* @__PURE__ */ jsx3("option", { value: p.name, children: p.name }, p.name))
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
fields.map((field) => {
|
|
170
|
+
const label = parameterLabel(field);
|
|
171
|
+
const inputId = `${idBase}-${field.id}`;
|
|
172
|
+
const keyed = keyframed?.has(field.id) ?? false;
|
|
173
|
+
const value = effectFieldValue(field, values[field.id]);
|
|
174
|
+
const changed = !keyed && !parameterValuesEqual(value, field.default);
|
|
175
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", "data-effect-control": field.id, children: [
|
|
176
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
|
|
177
|
+
/* @__PURE__ */ jsx3("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field.description, children: label }),
|
|
178
|
+
keyed && /* @__PURE__ */ jsx3("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
|
|
179
|
+
onReset && changed && !disabled && /* @__PURE__ */ jsx3("button", { type: "button", className: "rounded px-1 text-[11px] text-muted-foreground hover:text-foreground", "aria-label": `Reset ${label}`, onClick: () => onReset(field.id), children: "\u21BA" })
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ jsx3(
|
|
182
|
+
ParameterFieldControl,
|
|
183
|
+
{
|
|
184
|
+
field,
|
|
185
|
+
value,
|
|
186
|
+
id: inputId,
|
|
187
|
+
disabled: disabled || keyed,
|
|
188
|
+
onChange: (v) => onChange(field.id, v, gesture.current),
|
|
189
|
+
onGestureStart: () => gesture.current = `${idBase}:${field.id}:${++count.current}`,
|
|
190
|
+
onGestureEnd: () => gesture.current = null
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
] }, field.id);
|
|
194
|
+
}),
|
|
195
|
+
!fields.length && /* @__PURE__ */ jsx3("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
|
|
196
|
+
] });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
useEffects,
|
|
201
|
+
useEffectDescription,
|
|
202
|
+
EffectThumbnail,
|
|
203
|
+
EffectsBrowser,
|
|
204
|
+
EffectParameters
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=chunk-FX3EREF5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/effects/useEffects.ts","../src/effects/EffectThumbnail.tsx","../src/effects/EffectsBrowser.tsx","../src/effects/EffectParameters.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport type { EffectDescription, EffectProvider } from '@pixerate/editor/effects';\n\n/** Anything that takes an effects provider: every editor controller with effects support. */\nexport interface EffectsHost {\n useEffects(provider: EffectProvider): () => void;\n}\n\n/**\n * Registers `provider` with an editor while mounted (or while it is the one passed), and removes it\n * again on unmount. Pass `null` or `undefined` for no provider: the editor then behaves as without\n * effects support.\n */\nexport function useEffects(host: EffectsHost | null | undefined, provider: EffectProvider | null | undefined): void {\n useEffect(() => {\n if (!host || !provider) return;\n return host.useEffects(provider);\n }, [host, provider]);\n}\n\n/** An effect's description from the provider, loaded when `effect` changes (`null` while loading or on error). */\nexport function useEffectDescription(provider: EffectProvider | null | undefined, effect: string | null | undefined): { description: EffectDescription | null; error: string | null } {\n const [state, setState] = useState<{ key: string; description: EffectDescription | null; error: string | null }>({ key: '', description: null, error: null });\n const key = provider && effect ? `${provider.id}:${effect}` : '';\n useEffect(() => {\n if (!provider || !effect) return;\n let live = true;\n Promise.resolve()\n .then(() => provider.describe(effect))\n .then(\n (description) => live && setState({ key, description, error: null }),\n (e: unknown) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) }),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, key]);\n return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport { renderEffectThumbnail, type EffectProvider, type EffectSummary } from '@pixerate/editor/effects';\n\nexport interface EffectThumbnailProps {\n provider: EffectProvider;\n effect: EffectSummary;\n /** Pixel size of the preview (default 96 × 54). */\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A preview of an effect drawn by the provider's own renderer (filters on a sample picture,\n * transitions half way, generators mid-way through). Motion effects and failures show a label.\n */\nexport function EffectThumbnail({ provider, effect, width = 96, height = 54, className = '' }: EffectThumbnailProps) {\n const ref = useRef<HTMLCanvasElement>(null);\n const [failed, setFailed] = useState(effect.kind === 'motion');\n useEffect(() => {\n if (effect.kind === 'motion') return;\n let live = true;\n renderEffectThumbnail(provider, effect, { width, height }).then(\n (pixels) => {\n const ctx = live ? ref.current?.getContext('2d') : null;\n if (!ctx || typeof ImageData === 'undefined') return;\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);\n },\n () => live && setFailed(true),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, width, height]);\n return (\n <div\n className={`relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`}\n style={{ aspectRatio: `${width} / ${height}` }}\n data-effect-thumbnail={effect.id}\n >\n {failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground\">{effect.kind === 'motion' ? 'Motion' : 'No preview'}</span>\n ) : (\n <canvas ref={ref} width={width} height={height} className=\"h-full w-full\" aria-hidden=\"true\" />\n )}\n </div>\n );\n}\n","import React, { useEffect, useMemo, useState } from 'react';\nimport { EFFECT_USE_LABELS, type EffectProvider, type EffectSummary, type EffectUse } from '@pixerate/editor/effects';\nimport { EffectThumbnail } from './EffectThumbnail';\n\nexport interface EffectsBrowserProps {\n /** The editor's provider; without one the browser says effects are unavailable. */\n provider: EffectProvider | null | undefined;\n /** Only effects with one of these uses (default: all but motion). */\n uses?: readonly EffectUse[];\n /** Called with the effect the user picks. */\n onPick: (effect: EffectSummary) => void;\n /** Draw previews with the provider's renderer (default true). */\n thumbnails?: boolean;\n /** Heading (default \"Effects\"). */\n title?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Browses a provider's effects: a search box, a category filter (and a use filter when more than\n * one use is allowed), and a grid of effects with previews. Picking one calls `onPick`.\n */\nexport function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = 'Effects', className = '' }: EffectsBrowserProps) {\n const [all, setAll] = useState<readonly EffectSummary[]>([]);\n const [query, setQuery] = useState('');\n const [category, setCategory] = useState('');\n const [use, setUse] = useState<EffectUse | ''>('');\n const [error, setError] = useState<string | null>(null);\n useEffect(() => {\n if (!provider) return setAll([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.list())\n .then(\n (list) => live && (setAll(list), setError(null)),\n (e: unknown) => live && setError(e instanceof Error ? e.message : String(e)),\n );\n return () => {\n live = false;\n };\n }, [provider]);\n const [found, setFound] = useState<readonly EffectSummary[]>([]);\n useEffect(() => {\n if (!provider) return setFound([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.search(query, { ...(category ? { category } : {}), ...(use ? { use } : {}) }))\n .then((list) => live && setFound(list), () => live && setFound([]));\n return () => {\n live = false;\n };\n }, [provider, query, category, use]);\n const allowed = (e: EffectSummary) => (uses ? e.kind !== 'motion' || uses.includes('motion') : e.kind !== 'motion') && (!uses || e.uses.some((u) => uses.includes(u)));\n const categories = useMemo(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses]);\n const shown = found.filter(allowed);\n if (!provider)\n return (\n <div className={`rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`} data-effects-browser=\"\" data-effects-unavailable=\"\">\n No effects provider is registered, so effects cannot be added. Effects already in the document are kept.\n </div>\n );\n return (\n <section className={`flex flex-col gap-2 ${className}`} data-effects-browser=\"\" aria-label=\"Effects browser\">\n {title && <h3 className=\"text-xs font-semibold\">{title}</h3>}\n <div className=\"flex flex-wrap gap-1\">\n <input\n type=\"search\"\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs\"\n placeholder=\"Search effects\"\n aria-label=\"Search effects\"\n value={query}\n onChange={(e) => setQuery(e.currentTarget.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Category\" value={category} onChange={(e) => setCategory(e.currentTarget.value)}>\n <option value=\"\">All categories</option>\n {categories.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n {uses && uses.length > 1 && (\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Use\" value={use} onChange={(e) => setUse(e.currentTarget.value as EffectUse | '')}>\n <option value=\"\">Any use</option>\n {uses.map((u) => (\n <option key={u} value={u}>\n {EFFECT_USE_LABELS[u]}\n </option>\n ))}\n </select>\n )}\n </div>\n {error && <p role=\"alert\" className=\"text-[11px] text-destructive\">{error}</p>}\n <ul className=\"grid grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5\" role=\"list\">\n {shown.map((e) => (\n <li key={e.id}>\n <button\n type=\"button\"\n className=\"flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border\"\n title={e.description ?? e.name}\n data-effect-id={e.id}\n onClick={() => onPick(e)}\n >\n {thumbnails && <EffectThumbnail provider={provider} effect={e} />}\n <span className=\"truncate text-[11px] font-medium\">{e.name}</span>\n <span className=\"truncate text-[10px] text-muted-foreground\">{e.uses.map((u) => EFFECT_USE_LABELS[u]).join(' · ')}</span>\n </button>\n </li>\n ))}\n </ul>\n {!shown.length && !error && <p className=\"text-[11px] text-muted-foreground\">No effects match.</p>}\n </section>\n );\n}\n","import React, { useId, useMemo } from 'react';\nimport { effectFieldValue, effectParameterFields, type EffectDescription } from '@pixerate/editor/effects';\nimport { parameterLabel, parameterValuesEqual, type ParameterValue } from '@pixerate/editor/parameters';\nimport { ParameterFieldControl } from '../parameters/fields';\n\nexport interface EffectParametersProps {\n /** The effect's description (`describe`). */\n description: EffectDescription;\n /** Its saved values; controls left out show their defaults. */\n values: Readonly<Record<string, unknown>>;\n /**\n * A control changed. While a slider or colour drag is under way `gesture` is a key unique to\n * that drag, so the host can fold its changes into one undo step; otherwise `null`.\n */\n onChange: (control: string, value: ParameterValue, gesture: string | null) => void;\n /** Resets one control to its default. */\n onReset?: (control: string) => void;\n /** Controls that are keyframed: shown read-only with a note (edit them as keyframes). */\n keyframed?: ReadonlySet<string>;\n /** Applies a preset's values. */\n onPreset?: (values: Readonly<Record<string, unknown>>) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * The parameter panel of an effect, generated from its JSON Schema controls: sliders for bounded\n * numbers, switches, choices, colour pickers, vectors and text, with each control's description,\n * per-control reset and the effect's presets.\n */\nexport function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = '' }: EffectParametersProps) {\n const fields = useMemo(() => effectParameterFields(description), [description]);\n const idBase = useId();\n const gesture = React.useRef<string | null>(null);\n const count = React.useRef(0);\n return (\n <div className={`space-y-2 ${className}`} data-effect-parameters={description.id}>\n {onPreset && description.presets.length > 0 && (\n <select\n className=\"w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n aria-label={`${description.name} presets`}\n value=\"\"\n disabled={disabled}\n onChange={(e) => {\n const preset = description.presets.find((p) => p.name === e.currentTarget.value);\n if (preset) onPreset({ ...description.defaults, ...preset.values });\n }}\n >\n <option value=\"\">Presets…</option>\n {description.presets.map((p) => (\n <option key={p.name} value={p.name}>\n {p.name}\n </option>\n ))}\n </select>\n )}\n {fields.map((field) => {\n const label = parameterLabel(field);\n const inputId = `${idBase}-${field.id}`;\n const keyed = keyframed?.has(field.id) ?? false;\n const value = effectFieldValue(field, values[field.id]);\n const changed = !keyed && !parameterValuesEqual(value, field.default);\n return (\n <div key={field.id} className=\"space-y-1\" data-effect-control={field.id}>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor={inputId} className=\"min-w-0 flex-1 truncate text-xs font-medium\" title={field.description}>\n {label}\n </label>\n {keyed && <span className=\"text-[10px] text-muted-foreground\">keyframed</span>}\n {onReset && changed && !disabled && (\n <button type=\"button\" className=\"rounded px-1 text-[11px] text-muted-foreground hover:text-foreground\" aria-label={`Reset ${label}`} onClick={() => onReset(field.id)}>\n ↺\n </button>\n )}\n </div>\n <ParameterFieldControl\n field={field}\n value={value}\n id={inputId}\n disabled={disabled || keyed}\n onChange={(v) => onChange(field.id, v, gesture.current)}\n onGestureStart={() => (gesture.current = `${idBase}:${field.id}:${++count.current}`)}\n onGestureEnd={() => (gesture.current = null)}\n />\n </div>\n );\n })}\n {!fields.length && <p className=\"text-[11px] text-muted-foreground\">No controls.</p>}\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","useEffect","useState","jsx","useState","useEffect","React","useMemo","jsx","jsxs","useMemo","React"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/paint-editor/PaintGradientEditorView.tsx
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var button = "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-200 hover:bg-slate-700 disabled:opacity-40";
|
|
5
|
+
var select = "rounded bg-slate-800 px-1 py-0.5 text-[11px]";
|
|
6
|
+
var number = "w-16 rounded bg-slate-800 px-1 py-0.5 text-[11px]";
|
|
7
|
+
var pct = (v) => Math.round(v * 1e3) / 10;
|
|
8
|
+
var PaintGradientEditorView = ({ value, onChange, colors, className, kit }) => {
|
|
9
|
+
const {
|
|
10
|
+
PAINT_GRADIENT_MAPPINGS,
|
|
11
|
+
PAINT_GRADIENT_MAPPING_LABELS,
|
|
12
|
+
PAINT_GRADIENT_PRESETS,
|
|
13
|
+
PAINT_GRADIENT_REPEATS,
|
|
14
|
+
PAINT_GRADIENT_REPEAT_LABELS,
|
|
15
|
+
PAINT_GRADIENT_SPACES,
|
|
16
|
+
PAINT_GRADIENT_SPACE_LABELS,
|
|
17
|
+
addPaintGradientStop,
|
|
18
|
+
paintGradientMappingFor,
|
|
19
|
+
paintSourceCss,
|
|
20
|
+
removePaintGradientStop,
|
|
21
|
+
reversePaintGradient,
|
|
22
|
+
updatePaintGradientStop
|
|
23
|
+
} = kit;
|
|
24
|
+
const [selected, setSelected] = useState(0);
|
|
25
|
+
const sel = Math.min(selected, value.stops.length - 1);
|
|
26
|
+
const stop = value.stops[sel];
|
|
27
|
+
const map = value.map;
|
|
28
|
+
const stopHex = (c) => c === "paint" ? colors.paint : c === "secondary" ? colors.secondary : c;
|
|
29
|
+
const patchStop = (patch) => {
|
|
30
|
+
const r = updatePaintGradientStop(value, sel, patch);
|
|
31
|
+
setSelected(r.index);
|
|
32
|
+
onChange(r.paint);
|
|
33
|
+
};
|
|
34
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-1.5 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "paint-gradient-editor", "aria-label": "Gradient paint", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("select", { "aria-label": "Gradient preset", className: select, value: "", onChange: (e) => e.target.value && (setSelected(0), onChange(PAINT_GRADIENT_PRESETS[Number(e.target.value)].paint)), children: [
|
|
37
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Presets\u2026" }),
|
|
38
|
+
PAINT_GRADIENT_PRESETS.map((p, i) => /* @__PURE__ */ jsx("option", { value: i, children: p.name }, p.name))
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: button, onClick: () => onChange(reversePaintGradient(value)), children: "Reverse" })
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
role: "slider",
|
|
46
|
+
tabIndex: 0,
|
|
47
|
+
"aria-label": "Gradient ramp: click to add a stop",
|
|
48
|
+
"aria-valuemin": 0,
|
|
49
|
+
"aria-valuemax": 100,
|
|
50
|
+
"aria-valuenow": pct(stop.offset),
|
|
51
|
+
"data-testid": "paint-gradient-ramp",
|
|
52
|
+
className: "relative h-6 cursor-copy rounded ring-1 ring-slate-700",
|
|
53
|
+
style: { background: paintSourceCss(value, colors) },
|
|
54
|
+
onClick: (e) => {
|
|
55
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
56
|
+
const r = addPaintGradientStop(value, rect.width ? (e.clientX - rect.left) / rect.width : 0.5, colors);
|
|
57
|
+
if (r.index >= 0) {
|
|
58
|
+
setSelected(r.index);
|
|
59
|
+
onChange(r.paint);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-4", "aria-label": "Stops", children: value.stops.map((s, i) => /* @__PURE__ */ jsx(
|
|
65
|
+
"button",
|
|
66
|
+
{
|
|
67
|
+
type: "button",
|
|
68
|
+
"aria-label": `Stop ${i + 1} at ${pct(s.offset)}%`,
|
|
69
|
+
"aria-pressed": i === sel,
|
|
70
|
+
className: `absolute top-0 h-4 w-3 -translate-x-1/2 rounded-sm ring-1 ${i === sel ? "ring-white" : "ring-slate-600"}`,
|
|
71
|
+
style: { left: `${s.offset * 100}%`, background: stopHex(s.color) },
|
|
72
|
+
onClick: () => setSelected(i)
|
|
73
|
+
},
|
|
74
|
+
i
|
|
75
|
+
)) }),
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1", "data-testid": "paint-gradient-stop", children: [
|
|
77
|
+
/* @__PURE__ */ jsxs("select", { "aria-label": "Stop colour source", className: select, value: stop.color === "paint" || stop.color === "secondary" ? stop.color : "color", onChange: (e) => patchStop({ color: e.target.value === "color" ? stopHex(stop.color) : e.target.value }), children: [
|
|
78
|
+
/* @__PURE__ */ jsx("option", { value: "color", children: "Colour" }),
|
|
79
|
+
/* @__PURE__ */ jsx("option", { value: "paint", children: "Paint colour" }),
|
|
80
|
+
/* @__PURE__ */ jsx("option", { value: "secondary", children: "Second colour" })
|
|
81
|
+
] }),
|
|
82
|
+
stop.color !== "paint" && stop.color !== "secondary" && /* @__PURE__ */ jsx("input", { type: "color", "aria-label": "Stop colour", value: stop.color, onChange: (e) => patchStop({ color: e.target.value }), className: "h-6 w-8 rounded bg-transparent" }),
|
|
83
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
|
|
84
|
+
"At",
|
|
85
|
+
/* @__PURE__ */ jsx("input", { type: "number", "aria-label": "Stop position", min: 0, max: 100, step: 1, value: pct(stop.offset), onChange: (e) => patchStop({ offset: Number(e.target.value) / 100 }), className: number }),
|
|
86
|
+
"%"
|
|
87
|
+
] }),
|
|
88
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
|
|
89
|
+
"Opacity",
|
|
90
|
+
/* @__PURE__ */ jsx("input", { type: "number", "aria-label": "Stop opacity", min: 0, max: 100, step: 5, value: pct(stop.opacity ?? 1), onChange: (e) => patchStop({ opacity: Number(e.target.value) / 100 }), className: number }),
|
|
91
|
+
"%"
|
|
92
|
+
] }),
|
|
93
|
+
sel < value.stops.length - 1 && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
|
|
94
|
+
"Midpoint",
|
|
95
|
+
/* @__PURE__ */ jsx("input", { type: "number", "aria-label": "Stop midpoint", min: 5, max: 95, step: 1, value: pct(stop.midpoint ?? 0.5), onChange: (e) => patchStop({ midpoint: Number(e.target.value) / 100 }), className: number }),
|
|
96
|
+
"%"
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: button, disabled: value.stops.length <= 2, onClick: () => (setSelected(Math.max(0, sel - 1)), onChange(removePaintGradientStop(value, sel))), children: "Remove stop" })
|
|
99
|
+
] }),
|
|
100
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1", "data-testid": "paint-gradient-mapping", children: [
|
|
101
|
+
/* @__PURE__ */ jsx("select", { "aria-label": "Gradient follows", className: select, value: map.by, onChange: (e) => onChange({ ...value, map: paintGradientMappingFor(e.target.value, map) }), children: PAINT_GRADIENT_MAPPINGS.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: PAINT_GRADIENT_MAPPING_LABELS[m] }, m)) }),
|
|
102
|
+
map.by === "distance" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
103
|
+
/* @__PURE__ */ jsx("input", { type: "number", "aria-label": "Gradient length", min: 1, max: 1e5, value: map.length, onChange: (e) => onChange({ ...value, map: { ...map, length: Math.max(1, Number(e.target.value) || 1) } }), className: number }),
|
|
104
|
+
/* @__PURE__ */ jsxs("select", { "aria-label": "Gradient length unit", className: select, value: map.unit ?? "px", onChange: (e) => onChange({ ...value, map: { ...map, unit: e.target.value } }), children: [
|
|
105
|
+
/* @__PURE__ */ jsx("option", { value: "px", children: "px" }),
|
|
106
|
+
/* @__PURE__ */ jsx("option", { value: "dabs", children: "dabs" })
|
|
107
|
+
] })
|
|
108
|
+
] }),
|
|
109
|
+
map.by === "time" && /* @__PURE__ */ jsx("input", { type: "number", "aria-label": "Gradient duration", min: 1, max: 6e5, value: map.duration, onChange: (e) => onChange({ ...value, map: { ...map, duration: Math.max(1, Number(e.target.value) || 1) } }), className: number }),
|
|
110
|
+
map.by === "random" && /* @__PURE__ */ jsx("input", { type: "number", "aria-label": "Dabs per pick", min: 1, max: 500, value: map.every ?? 1, onChange: (e) => onChange({ ...value, map: { ...map, every: Math.max(1, Math.round(Number(e.target.value) || 1)) } }), className: number }),
|
|
111
|
+
(map.by === "distance" || map.by === "time") && /* @__PURE__ */ jsx("select", { "aria-label": "Gradient repeat", className: select, value: map.repeat ?? "clamp", onChange: (e) => onChange({ ...value, map: { ...map, repeat: e.target.value } }), children: PAINT_GRADIENT_REPEATS.map((r) => /* @__PURE__ */ jsx("option", { value: r, children: PAINT_GRADIENT_REPEAT_LABELS[r] }, r)) })
|
|
112
|
+
] }),
|
|
113
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
114
|
+
/* @__PURE__ */ jsx("select", { "aria-label": "Gradient mixing", className: select, value: value.space, onChange: (e) => onChange({ ...value, space: e.target.value }), children: PAINT_GRADIENT_SPACES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: PAINT_GRADIENT_SPACE_LABELS[s] }, s)) }),
|
|
115
|
+
/* @__PURE__ */ jsxs("label", { className: "flex flex-1 items-center gap-1", children: [
|
|
116
|
+
"Softness",
|
|
117
|
+
/* @__PURE__ */ jsx("input", { type: "range", "aria-label": "Gradient softness", min: 0, max: 1, step: 0.05, value: value.softness ?? 1, onChange: (e) => onChange({ ...value, softness: Number(e.target.value) }), className: "flex-1 accent-indigo-500" })
|
|
118
|
+
] })
|
|
119
|
+
] })
|
|
120
|
+
] });
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
PaintGradientEditorView
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-MO354USR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/paint-editor/PaintGradientEditorView.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport type { PaintGradientKit, PaintGradientMappingKind, PaintGradientRepeat, PaintGradientSource, PaintGradientSpace } from '@pixerate/editor/paint-editor';\n\nexport interface PaintGradientEditorProps {\n value: PaintGradientSource;\n onChange: (paint: PaintGradientSource) => void;\n /** What `paint` / `secondary` stops show as (the editor's two colours). */\n colors: { paint: string; secondary: string };\n className?: string;\n}\n\nexport interface PaintGradientEditorViewProps extends PaintGradientEditorProps {\n /** The gradient helpers (`PAINT_GRADIENT_KIT`, from the editor's own entry). */\n kit: PaintGradientKit;\n}\n\nconst button = 'px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-200 hover:bg-slate-700 disabled:opacity-40';\nconst select = 'rounded bg-slate-800 px-1 py-0.5 text-[11px]';\nconst number = 'w-16 rounded bg-slate-800 px-1 py-0.5 text-[11px]';\nconst pct = (v: number) => Math.round(v * 1000) / 10;\n\n/**\n * Gradient paint editor (the vector editor's stop ramp, for paint): click the ramp to add a stop,\n * pick a stop to edit its colour (or the paint / second colour), opacity, position and midpoint;\n * the mixing space, what moves the stroke along it (distance with length and repeat, pressure,\n * tilt, speed, direction, time, across the tip, random), softness, reverse, and starting presets.\n */\nexport const PaintGradientEditorView: React.FC<PaintGradientEditorViewProps> = ({ value, onChange, colors, className, kit }) => {\n const {\n PAINT_GRADIENT_MAPPINGS,\n PAINT_GRADIENT_MAPPING_LABELS,\n PAINT_GRADIENT_PRESETS,\n PAINT_GRADIENT_REPEATS,\n PAINT_GRADIENT_REPEAT_LABELS,\n PAINT_GRADIENT_SPACES,\n PAINT_GRADIENT_SPACE_LABELS,\n addPaintGradientStop,\n paintGradientMappingFor,\n paintSourceCss,\n removePaintGradientStop,\n reversePaintGradient,\n updatePaintGradientStop,\n } = kit;\n const [selected, setSelected] = useState(0);\n const sel = Math.min(selected, value.stops.length - 1);\n const stop = value.stops[sel];\n const map = value.map;\n const stopHex = (c: string) => (c === 'paint' ? colors.paint : c === 'secondary' ? colors.secondary : c);\n const patchStop = (patch: Parameters<PaintGradientKit['updatePaintGradientStop']>[2]) => {\n const r = updatePaintGradientStop(value, sel, patch);\n setSelected(r.index);\n onChange(r.paint);\n };\n return (\n <div className={`flex flex-col gap-1.5 text-xs text-slate-200 ${className ?? ''}`} data-testid=\"paint-gradient-editor\" aria-label=\"Gradient paint\">\n <div className=\"flex items-center gap-1\">\n <select aria-label=\"Gradient preset\" className={select} value=\"\" onChange={(e) => e.target.value && (setSelected(0), onChange(PAINT_GRADIENT_PRESETS[Number(e.target.value)].paint))}>\n <option value=\"\">Presets…</option>\n {PAINT_GRADIENT_PRESETS.map((p, i) => (\n <option key={p.name} value={i}>\n {p.name}\n </option>\n ))}\n </select>\n <button type=\"button\" className={button} onClick={() => onChange(reversePaintGradient(value))}>\n Reverse\n </button>\n </div>\n <div\n role=\"slider\"\n tabIndex={0}\n aria-label=\"Gradient ramp: click to add a stop\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct(stop.offset)}\n data-testid=\"paint-gradient-ramp\"\n className=\"relative h-6 cursor-copy rounded ring-1 ring-slate-700\"\n style={{ background: paintSourceCss(value, colors) }}\n onClick={(e) => {\n const rect = e.currentTarget.getBoundingClientRect();\n const r = addPaintGradientStop(value, rect.width ? (e.clientX - rect.left) / rect.width : 0.5, colors);\n if (r.index >= 0) {\n setSelected(r.index);\n onChange(r.paint);\n }\n }}\n />\n <div className=\"relative h-4\" aria-label=\"Stops\">\n {value.stops.map((s, i) => (\n <button\n key={i}\n type=\"button\"\n aria-label={`Stop ${i + 1} at ${pct(s.offset)}%`}\n aria-pressed={i === sel}\n className={`absolute top-0 h-4 w-3 -translate-x-1/2 rounded-sm ring-1 ${i === sel ? 'ring-white' : 'ring-slate-600'}`}\n style={{ left: `${s.offset * 100}%`, background: stopHex(s.color) }}\n onClick={() => setSelected(i)}\n />\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-1\" data-testid=\"paint-gradient-stop\">\n <select aria-label=\"Stop colour source\" className={select} value={stop.color === 'paint' || stop.color === 'secondary' ? stop.color : 'color'} onChange={(e) => patchStop({ color: e.target.value === 'color' ? stopHex(stop.color) : e.target.value })}>\n <option value=\"color\">Colour</option>\n <option value=\"paint\">Paint colour</option>\n <option value=\"secondary\">Second colour</option>\n </select>\n {stop.color !== 'paint' && stop.color !== 'secondary' && <input type=\"color\" aria-label=\"Stop colour\" value={stop.color} onChange={(e) => patchStop({ color: e.target.value })} className=\"h-6 w-8 rounded bg-transparent\" />}\n <label className=\"flex items-center gap-1\">\n At\n <input type=\"number\" aria-label=\"Stop position\" min={0} max={100} step={1} value={pct(stop.offset)} onChange={(e) => patchStop({ offset: Number(e.target.value) / 100 })} className={number} />%\n </label>\n <label className=\"flex items-center gap-1\">\n Opacity\n <input type=\"number\" aria-label=\"Stop opacity\" min={0} max={100} step={5} value={pct(stop.opacity ?? 1)} onChange={(e) => patchStop({ opacity: Number(e.target.value) / 100 })} className={number} />%\n </label>\n {sel < value.stops.length - 1 && (\n <label className=\"flex items-center gap-1\">\n Midpoint\n <input type=\"number\" aria-label=\"Stop midpoint\" min={5} max={95} step={1} value={pct(stop.midpoint ?? 0.5)} onChange={(e) => patchStop({ midpoint: Number(e.target.value) / 100 })} className={number} />%\n </label>\n )}\n <button type=\"button\" className={button} disabled={value.stops.length <= 2} onClick={() => (setSelected(Math.max(0, sel - 1)), onChange(removePaintGradientStop(value, sel)))}>\n Remove stop\n </button>\n </div>\n <div className=\"flex flex-wrap items-center gap-1\" data-testid=\"paint-gradient-mapping\">\n <select aria-label=\"Gradient follows\" className={select} value={map.by} onChange={(e) => onChange({ ...value, map: paintGradientMappingFor(e.target.value as PaintGradientMappingKind, map) })}>\n {PAINT_GRADIENT_MAPPINGS.map((m) => (\n <option key={m} value={m}>\n {PAINT_GRADIENT_MAPPING_LABELS[m]}\n </option>\n ))}\n </select>\n {map.by === 'distance' && (\n <>\n <input type=\"number\" aria-label=\"Gradient length\" min={1} max={100000} value={map.length} onChange={(e) => onChange({ ...value, map: { ...map, length: Math.max(1, Number(e.target.value) || 1) } })} className={number} />\n <select aria-label=\"Gradient length unit\" className={select} value={map.unit ?? 'px'} onChange={(e) => onChange({ ...value, map: { ...map, unit: e.target.value as 'px' | 'dabs' } })}>\n <option value=\"px\">px</option>\n <option value=\"dabs\">dabs</option>\n </select>\n </>\n )}\n {map.by === 'time' && <input type=\"number\" aria-label=\"Gradient duration\" min={1} max={600000} value={map.duration} onChange={(e) => onChange({ ...value, map: { ...map, duration: Math.max(1, Number(e.target.value) || 1) } })} className={number} />}\n {map.by === 'random' && <input type=\"number\" aria-label=\"Dabs per pick\" min={1} max={500} value={map.every ?? 1} onChange={(e) => onChange({ ...value, map: { ...map, every: Math.max(1, Math.round(Number(e.target.value) || 1)) } })} className={number} />}\n {(map.by === 'distance' || map.by === 'time') && (\n <select aria-label=\"Gradient repeat\" className={select} value={map.repeat ?? 'clamp'} onChange={(e) => onChange({ ...value, map: { ...map, repeat: e.target.value as PaintGradientRepeat } })}>\n {PAINT_GRADIENT_REPEATS.map((r) => (\n <option key={r} value={r}>\n {PAINT_GRADIENT_REPEAT_LABELS[r]}\n </option>\n ))}\n </select>\n )}\n </div>\n <div className=\"flex items-center gap-1\">\n <select aria-label=\"Gradient mixing\" className={select} value={value.space} onChange={(e) => onChange({ ...value, space: e.target.value as PaintGradientSpace })}>\n {PAINT_GRADIENT_SPACES.map((s) => (\n <option key={s} value={s}>\n {PAINT_GRADIENT_SPACE_LABELS[s]}\n </option>\n ))}\n </select>\n <label className=\"flex flex-1 items-center gap-1\">\n Softness\n <input type=\"range\" aria-label=\"Gradient softness\" min={0} max={1} step={0.05} value={value.softness ?? 1} onChange={(e) => onChange({ ...value, softness: Number(e.target.value) })} className=\"flex-1 accent-indigo-500\" />\n </label>\n </div>\n </div>\n );\n};\n"],"mappings":";AAAA,SAAgB,gBAAgB;AAwDxB,SA8EE,UA7EA,KADF;AAxCR,IAAM,SAAS;AACf,IAAM,SAAS;AACf,IAAM,SAAS;AACf,IAAM,MAAM,CAAC,MAAc,KAAK,MAAM,IAAI,GAAI,IAAI;AAQ3C,IAAM,0BAAkE,CAAC,EAAE,OAAO,UAAU,QAAQ,WAAW,IAAI,MAAM;AAC9H,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,CAAC;AAC1C,QAAM,MAAM,KAAK,IAAI,UAAU,MAAM,MAAM,SAAS,CAAC;AACrD,QAAM,OAAO,MAAM,MAAM,GAAG;AAC5B,QAAM,MAAM,MAAM;AAClB,QAAM,UAAU,CAAC,MAAe,MAAM,UAAU,OAAO,QAAQ,MAAM,cAAc,OAAO,YAAY;AACtG,QAAM,YAAY,CAAC,UAAsE;AACvF,UAAM,IAAI,wBAAwB,OAAO,KAAK,KAAK;AACnD,gBAAY,EAAE,KAAK;AACnB,aAAS,EAAE,KAAK;AAAA,EAClB;AACA,SACE,qBAAC,SAAI,WAAW,gDAAgD,aAAa,EAAE,IAAI,eAAY,yBAAwB,cAAW,kBAChI;AAAA,yBAAC,SAAI,WAAU,2BACb;AAAA,2BAAC,YAAO,cAAW,mBAAkB,WAAW,QAAQ,OAAM,IAAG,UAAU,CAAC,MAAM,EAAE,OAAO,UAAU,YAAY,CAAC,GAAG,SAAS,uBAAuB,OAAO,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,IAChL;AAAA,4BAAC,YAAO,OAAM,IAAG,2BAAQ;AAAA,QACxB,uBAAuB,IAAI,CAAC,GAAG,MAC9B,oBAAC,YAAoB,OAAO,GACzB,YAAE,QADQ,EAAE,IAEf,CACD;AAAA,SACH;AAAA,MACA,oBAAC,YAAO,MAAK,UAAS,WAAW,QAAQ,SAAS,MAAM,SAAS,qBAAqB,KAAK,CAAC,GAAG,qBAE/F;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU;AAAA,QACV,cAAW;AAAA,QACX,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,iBAAe,IAAI,KAAK,MAAM;AAAA,QAC9B,eAAY;AAAA,QACZ,WAAU;AAAA,QACV,OAAO,EAAE,YAAY,eAAe,OAAO,MAAM,EAAE;AAAA,QACnD,SAAS,CAAC,MAAM;AACd,gBAAM,OAAO,EAAE,cAAc,sBAAsB;AACnD,gBAAM,IAAI,qBAAqB,OAAO,KAAK,SAAS,EAAE,UAAU,KAAK,QAAQ,KAAK,QAAQ,KAAK,MAAM;AACrG,cAAI,EAAE,SAAS,GAAG;AAChB,wBAAY,EAAE,KAAK;AACnB,qBAAS,EAAE,KAAK;AAAA,UAClB;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,SAAI,WAAU,gBAAe,cAAW,SACtC,gBAAM,MAAM,IAAI,CAAC,GAAG,MACnB;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,cAAY,QAAQ,IAAI,CAAC,OAAO,IAAI,EAAE,MAAM,CAAC;AAAA,QAC7C,gBAAc,MAAM;AAAA,QACpB,WAAW,6DAA6D,MAAM,MAAM,eAAe,gBAAgB;AAAA,QACnH,OAAO,EAAE,MAAM,GAAG,EAAE,SAAS,GAAG,KAAK,YAAY,QAAQ,EAAE,KAAK,EAAE;AAAA,QAClE,SAAS,MAAM,YAAY,CAAC;AAAA;AAAA,MANvB;AAAA,IAOP,CACD,GACH;AAAA,IACA,qBAAC,SAAI,WAAU,qCAAoC,eAAY,uBAC7D;AAAA,2BAAC,YAAO,cAAW,sBAAqB,WAAW,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,UAAU,cAAc,KAAK,QAAQ,SAAS,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,EAAE,OAAO,UAAU,UAAU,QAAQ,KAAK,KAAK,IAAI,EAAE,OAAO,MAAM,CAAC,GACpP;AAAA,4BAAC,YAAO,OAAM,SAAQ,oBAAM;AAAA,QAC5B,oBAAC,YAAO,OAAM,SAAQ,0BAAY;AAAA,QAClC,oBAAC,YAAO,OAAM,aAAY,2BAAa;AAAA,SACzC;AAAA,MACC,KAAK,UAAU,WAAW,KAAK,UAAU,eAAe,oBAAC,WAAM,MAAK,SAAQ,cAAW,eAAc,OAAO,KAAK,OAAO,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC,GAAG,WAAU,kCAAiC;AAAA,MAC3N,qBAAC,WAAM,WAAU,2BAA0B;AAAA;AAAA,QAEzC,oBAAC,WAAM,MAAK,UAAS,cAAW,iBAAgB,KAAK,GAAG,KAAK,KAAK,MAAM,GAAG,OAAO,IAAI,KAAK,MAAM,GAAG,UAAU,CAAC,MAAM,UAAU,EAAE,QAAQ,OAAO,EAAE,OAAO,KAAK,IAAI,IAAI,CAAC,GAAG,WAAW,QAAQ;AAAA,QAAE;AAAA,SACjM;AAAA,MACA,qBAAC,WAAM,WAAU,2BAA0B;AAAA;AAAA,QAEzC,oBAAC,WAAM,MAAK,UAAS,cAAW,gBAAe,KAAK,GAAG,KAAK,KAAK,MAAM,GAAG,OAAO,IAAI,KAAK,WAAW,CAAC,GAAG,UAAU,CAAC,MAAM,UAAU,EAAE,SAAS,OAAO,EAAE,OAAO,KAAK,IAAI,IAAI,CAAC,GAAG,WAAW,QAAQ;AAAA,QAAE;AAAA,SACvM;AAAA,MACC,MAAM,MAAM,MAAM,SAAS,KAC1B,qBAAC,WAAM,WAAU,2BAA0B;AAAA;AAAA,QAEzC,oBAAC,WAAM,MAAK,UAAS,cAAW,iBAAgB,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,OAAO,IAAI,KAAK,YAAY,GAAG,GAAG,UAAU,CAAC,MAAM,UAAU,EAAE,UAAU,OAAO,EAAE,OAAO,KAAK,IAAI,IAAI,CAAC,GAAG,WAAW,QAAQ;AAAA,QAAE;AAAA,SAC3M;AAAA,MAEF,oBAAC,YAAO,MAAK,UAAS,WAAW,QAAQ,UAAU,MAAM,MAAM,UAAU,GAAG,SAAS,OAAO,YAAY,KAAK,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,SAAS,wBAAwB,OAAO,GAAG,CAAC,IAAI,yBAE/K;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,qCAAoC,eAAY,0BAC7D;AAAA,0BAAC,YAAO,cAAW,oBAAmB,WAAW,QAAQ,OAAO,IAAI,IAAI,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,KAAK,wBAAwB,EAAE,OAAO,OAAmC,GAAG,EAAE,CAAC,GAC1L,kCAAwB,IAAI,CAAC,MAC5B,oBAAC,YAAe,OAAO,GACpB,wCAA8B,CAAC,KADrB,CAEb,CACD,GACH;AAAA,MACC,IAAI,OAAO,cACV,iCACE;AAAA,4BAAC,WAAM,MAAK,UAAS,cAAW,mBAAkB,KAAK,GAAG,KAAK,KAAQ,OAAO,IAAI,QAAQ,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,QAAQ,KAAK,IAAI,GAAG,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,WAAW,QAAQ;AAAA,QACzN,qBAAC,YAAO,cAAW,wBAAuB,WAAW,QAAQ,OAAO,IAAI,QAAQ,MAAM,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,MAAM,EAAE,OAAO,MAAuB,EAAE,CAAC,GAClL;AAAA,8BAAC,YAAO,OAAM,MAAK,gBAAE;AAAA,UACrB,oBAAC,YAAO,OAAM,QAAO,kBAAI;AAAA,WAC3B;AAAA,SACF;AAAA,MAED,IAAI,OAAO,UAAU,oBAAC,WAAM,MAAK,UAAS,cAAW,qBAAoB,KAAK,GAAG,KAAK,KAAQ,OAAO,IAAI,UAAU,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,UAAU,KAAK,IAAI,GAAG,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,WAAW,QAAQ;AAAA,MACpP,IAAI,OAAO,YAAY,oBAAC,WAAM,MAAK,UAAS,cAAW,iBAAgB,KAAK,GAAG,KAAK,KAAK,OAAO,IAAI,SAAS,GAAG,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,WAAW,QAAQ;AAAA,OACzP,IAAI,OAAO,cAAc,IAAI,OAAO,WACpC,oBAAC,YAAO,cAAW,mBAAkB,WAAW,QAAQ,OAAO,IAAI,UAAU,SAAS,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,QAAQ,EAAE,OAAO,MAA6B,EAAE,CAAC,GACzL,iCAAuB,IAAI,CAAC,MAC3B,oBAAC,YAAe,OAAO,GACpB,uCAA6B,CAAC,KADpB,CAEb,CACD,GACH;AAAA,OAEJ;AAAA,IACA,qBAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,YAAO,cAAW,mBAAkB,WAAW,QAAQ,OAAO,MAAM,OAAO,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,OAAO,EAAE,OAAO,MAA4B,CAAC,GAC5J,gCAAsB,IAAI,CAAC,MAC1B,oBAAC,YAAe,OAAO,GACpB,sCAA4B,CAAC,KADnB,CAEb,CACD,GACH;AAAA,MACA,qBAAC,WAAM,WAAU,kCAAiC;AAAA;AAAA,QAEhD,oBAAC,WAAM,MAAK,SAAQ,cAAW,qBAAoB,KAAK,GAAG,KAAK,GAAG,MAAM,MAAM,OAAO,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,UAAU,OAAO,EAAE,OAAO,KAAK,EAAE,CAAC,GAAG,WAAU,4BAA2B;AAAA,SAC7N;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|