@pixerate/editor-react 0.19.0 → 0.20.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.
@@ -0,0 +1,692 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/parameters/index.ts
21
+ var parameters_exports = {};
22
+ __export(parameters_exports, {
23
+ KeyframeDiamond: () => KeyframeDiamond,
24
+ ParameterColor: () => ParameterColor,
25
+ ParameterCommitInput: () => ParameterCommitInput,
26
+ ParameterFieldControl: () => ParameterFieldControl,
27
+ ParameterImage: () => ParameterImage,
28
+ ParameterNumberInput: () => ParameterNumberInput,
29
+ ParameterSelect: () => ParameterSelect,
30
+ ParameterSlider: () => ParameterSlider,
31
+ ParameterText: () => ParameterText,
32
+ ParameterToggle: () => ParameterToggle,
33
+ ParameterVector: () => ParameterVector,
34
+ ParametersPanel: () => ParametersPanel,
35
+ parameterButtonClass: () => parameterButtonClass,
36
+ parameterInputClass: () => parameterInputClass,
37
+ useParameterCommands: () => useParameterCommands,
38
+ useParameters: () => useParameters,
39
+ useParametersController: () => useParametersController,
40
+ useParametersState: () => useParametersState
41
+ });
42
+ module.exports = __toCommonJS(parameters_exports);
43
+
44
+ // src/parameters/useParameters.ts
45
+ var import_react = require("react");
46
+ var import_parameters = require("@pixerate/editor/parameters");
47
+ function useParametersController(options) {
48
+ const ref = (0, import_react.useRef)(null);
49
+ if (!ref.current) ref.current = new import_parameters.ParametersController(options);
50
+ const controller = ref.current;
51
+ const schema = options.schema;
52
+ const first = (0, import_react.useRef)(true);
53
+ (0, import_react.useEffect)(() => {
54
+ if (first.current) {
55
+ first.current = false;
56
+ return;
57
+ }
58
+ controller.setSchema(schema);
59
+ }, [controller, schema]);
60
+ return controller;
61
+ }
62
+ function useParametersState(controller) {
63
+ return (0, import_react.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
64
+ }
65
+ function useParameters(controllerOrOptions) {
66
+ const own = (0, import_react.useRef)(null);
67
+ let controller;
68
+ if (controllerOrOptions instanceof import_parameters.ParametersController) controller = controllerOrOptions;
69
+ else {
70
+ if (!own.current) own.current = new import_parameters.ParametersController(controllerOrOptions);
71
+ controller = own.current;
72
+ }
73
+ const state = useParametersState(controller);
74
+ const actions = (0, import_react.useMemo)(
75
+ () => ({
76
+ setValue: (id, value) => controller.setValue(id, value),
77
+ undo: () => void controller.undo(),
78
+ redo: () => void controller.redo(),
79
+ reset: (ids) => void controller.reset(ids),
80
+ randomize: (ids) => void controller.randomize({ ids })
81
+ }),
82
+ [controller]
83
+ );
84
+ return { controller, state, values: state.values, ...actions };
85
+ }
86
+ function useParameterCommands(registry, controller, options = {}) {
87
+ const { prefix, group, scope } = options;
88
+ (0, import_react.useEffect)(() => {
89
+ if (!registry) return;
90
+ return registry.register((0, import_parameters.createParameterCommands)(controller, { prefix, group, scope }));
91
+ }, [registry, controller, prefix, group, scope]);
92
+ }
93
+
94
+ // src/parameters/fields.tsx
95
+ var import_react2 = require("react");
96
+ var import_parameters2 = require("@pixerate/editor/parameters");
97
+ var import_jsx_runtime = require("react/jsx-runtime");
98
+ var 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";
99
+ var 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";
100
+ var rangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
101
+ function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown, ...rest }) {
102
+ const commit = (el) => {
103
+ if (el.value !== value) onCommit(el.value, el);
104
+ };
105
+ const keyDown = (e) => {
106
+ onKeyDown?.(e);
107
+ if (e.defaultPrevented) return;
108
+ if (e.key === "Enter" && (!multiline || e.metaKey || e.ctrlKey)) {
109
+ e.preventDefault();
110
+ commit(e.currentTarget);
111
+ }
112
+ if (e.key === "Escape") e.currentTarget.value = value;
113
+ };
114
+ const props = {
115
+ defaultValue: value,
116
+ className: className ?? parameterInputClass,
117
+ onBlur: (e) => commit(e.currentTarget),
118
+ onKeyDown: keyDown,
119
+ ...rest
120
+ };
121
+ return multiline ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "text", ...props }, value);
122
+ }
123
+ var plainNumber = (field, value) => (0, import_parameters2.formatParameterNumber)({ ...field, unit: void 0 }, value);
124
+ function ParameterNumberInput({
125
+ field,
126
+ value,
127
+ onChange,
128
+ disabled,
129
+ id,
130
+ "aria-label": ariaLabel
131
+ }) {
132
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
134
+ ParameterCommitInput,
135
+ {
136
+ id,
137
+ inputMode: "decimal",
138
+ "aria-label": ariaLabel ?? (0, import_parameters2.parameterLabel)(field),
139
+ value: plainNumber(field, value),
140
+ disabled: disabled || field.readOnly,
141
+ className: `${parameterInputClass} ${field.unit ? "pr-6" : ""}`,
142
+ onCommit: (text, input) => {
143
+ const n = (0, import_parameters2.parseParameterNumberInput)(field, text);
144
+ if (n === null || n === value) input.value = plainNumber(field, value);
145
+ else onChange(n);
146
+ },
147
+ onKeyDown: (e) => {
148
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
149
+ e.preventDefault();
150
+ const step = (0, import_parameters2.parameterStep)(field) * (e.shiftKey ? 10 : 1);
151
+ onChange((0, import_parameters2.clampParameterNumber)(field, value + (e.key === "ArrowUp" ? step : -step)));
152
+ }
153
+ }
154
+ ),
155
+ field.unit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground", children: field.unit })
156
+ ] });
157
+ }
158
+ function ParameterSlider(props) {
159
+ const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;
160
+ const dragging = (0, import_react2.useRef)(false);
161
+ const end = () => {
162
+ if (!dragging.current) return;
163
+ dragging.current = false;
164
+ onGestureEnd?.();
165
+ };
166
+ if (!(0, import_parameters2.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterNumberInput, { ...props });
167
+ const step = (0, import_parameters2.parameterStep)(field);
168
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
170
+ "input",
171
+ {
172
+ type: "range",
173
+ "aria-label": (0, import_parameters2.parameterLabel)(field),
174
+ "aria-valuetext": (0, import_parameters2.formatParameterNumber)(field, value),
175
+ min: field.min,
176
+ max: field.max,
177
+ step,
178
+ value,
179
+ disabled: disabled || field.readOnly,
180
+ className: rangeClass,
181
+ onPointerDown: () => {
182
+ if (dragging.current) return;
183
+ dragging.current = true;
184
+ onGestureStart?.();
185
+ },
186
+ onPointerUp: end,
187
+ onPointerCancel: end,
188
+ onBlur: end,
189
+ onChange: (e) => onChange((0, import_parameters2.clampParameterNumber)(field, Number(e.target.value)))
190
+ }
191
+ ),
192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters2.parameterLabel)(field)} value` }) })
193
+ ] });
194
+ }
195
+ function ParameterToggle({ field, value, onChange, disabled, id }) {
196
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ role: "switch",
201
+ id,
202
+ "aria-checked": value,
203
+ "aria-label": (0, import_parameters2.parameterLabel)(field),
204
+ disabled: disabled || field.readOnly,
205
+ onClick: () => onChange(!value),
206
+ className: `relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50 ${value ? "bg-primary" : "bg-muted"}`,
207
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
208
+ "span",
209
+ {
210
+ className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? "translate-x-4" : "translate-x-0.5"}`
211
+ }
212
+ )
213
+ }
214
+ );
215
+ }
216
+ function ParameterSelect({ field, value, onChange, disabled, id }) {
217
+ const options = (0, import_parameters2.getChoiceOptions)(field);
218
+ const off = disabled || field.readOnly;
219
+ if (field.control === "segmented") {
220
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "radiogroup", "aria-label": (0, import_parameters2.parameterLabel)(field), className: "inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
221
+ "button",
222
+ {
223
+ type: "button",
224
+ role: "radio",
225
+ "aria-checked": o.value === value,
226
+ title: o.description,
227
+ disabled: off,
228
+ onClick: () => onChange(o.value),
229
+ className: `rounded px-2 py-0.5 text-xs disabled:opacity-50 ${o.value === value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
230
+ children: (0, import_parameters2.choiceOptionLabel)(o)
231
+ },
232
+ String(o.value)
233
+ )) });
234
+ }
235
+ const index = options.findIndex((o) => o.value === value);
236
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
237
+ "select",
238
+ {
239
+ id,
240
+ "aria-label": (0, import_parameters2.parameterLabel)(field),
241
+ value: String(index),
242
+ disabled: off,
243
+ onChange: (e) => {
244
+ const option = options[Number(e.target.value)];
245
+ if (option) onChange(option.value);
246
+ },
247
+ className: parameterInputClass,
248
+ children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters2.choiceOptionLabel)(o) }, String(o.value)))
249
+ }
250
+ );
251
+ }
252
+ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }) {
253
+ const picking = (0, import_react2.useRef)(false);
254
+ const alpha = !!field.alpha;
255
+ const hex = (0, import_parameters2.normalizeHexColor)(value, true) ?? "#000000ff";
256
+ const rgb = hex.slice(0, 7);
257
+ const a = hex.slice(7);
258
+ const off = disabled || field.readOnly;
259
+ const start = () => {
260
+ if (picking.current) return;
261
+ picking.current = true;
262
+ onGestureStart?.();
263
+ };
264
+ const end = () => {
265
+ if (!picking.current) return;
266
+ picking.current = false;
267
+ onGestureEnd?.();
268
+ };
269
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1.5", children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "relative h-7 w-9 shrink-0 overflow-hidden rounded-md border border-input bg-[repeating-conic-gradient(#d4d4d8_0_25%,#fff_0_50%)] bg-[length:8px_8px]", children: [
272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
274
+ "input",
275
+ {
276
+ type: "color",
277
+ "aria-label": (0, import_parameters2.parameterLabel)(field),
278
+ value: rgb,
279
+ disabled: off,
280
+ className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed",
281
+ onChange: (e) => {
282
+ start();
283
+ onChange(alpha ? `${e.target.value}${a}` : e.target.value);
284
+ },
285
+ onBlur: end
286
+ }
287
+ )
288
+ ] }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
290
+ ParameterCommitInput,
291
+ {
292
+ id,
293
+ "aria-label": `${(0, import_parameters2.parameterLabel)(field)} hex`,
294
+ value: alpha ? hex : rgb,
295
+ disabled: off,
296
+ spellCheck: false,
297
+ className: `${parameterInputClass} font-mono`,
298
+ onCommit: (text) => {
299
+ const next = (0, import_parameters2.normalizeHexColor)(text, alpha);
300
+ if (next) onChange(next);
301
+ }
302
+ }
303
+ )
304
+ ] }),
305
+ alpha && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
306
+ "input",
307
+ {
308
+ type: "range",
309
+ "aria-label": `${(0, import_parameters2.parameterLabel)(field)} opacity`,
310
+ min: 0,
311
+ max: 255,
312
+ step: 1,
313
+ value: parseInt(a, 16),
314
+ disabled: off,
315
+ className: rangeClass,
316
+ onPointerDown: start,
317
+ onPointerUp: end,
318
+ onPointerCancel: end,
319
+ onBlur: end,
320
+ onChange: (e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, "0")}`)
321
+ }
322
+ ),
323
+ field.swatches && field.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-1", children: field.swatches.map((s) => {
324
+ const swatch = (0, import_parameters2.normalizeHexColor)(s, alpha);
325
+ if (!swatch) return null;
326
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
327
+ "button",
328
+ {
329
+ type: "button",
330
+ title: swatch,
331
+ "aria-label": `${(0, import_parameters2.parameterLabel)(field)} ${swatch}`,
332
+ "aria-pressed": swatch === (alpha ? hex : rgb),
333
+ disabled: off,
334
+ onClick: () => onChange(swatch),
335
+ className: "h-5 w-5 rounded border border-input aria-pressed:ring-1 aria-pressed:ring-primary",
336
+ style: { background: swatch }
337
+ },
338
+ s
339
+ );
340
+ }) })
341
+ ] });
342
+ }
343
+ function ParameterVector({ field, value, onChange, disabled, id }) {
344
+ const size = (0, import_parameters2.vectorSize)(field);
345
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
346
+ const component = (0, import_parameters2.vectorComponentField)(field, i);
347
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
350
+ ParameterNumberInput,
351
+ {
352
+ field: component,
353
+ value: value[i] ?? 0,
354
+ id: i === 0 ? id : void 0,
355
+ "aria-label": `${(0, import_parameters2.parameterLabel)(field)} ${component.label}`,
356
+ disabled: disabled || field.readOnly,
357
+ onChange: (n) => onChange(Array.from({ length: size }, (_2, j) => j === i ? n : value[j] ?? 0))
358
+ }
359
+ )
360
+ ] }, i);
361
+ }) });
362
+ }
363
+ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
364
+ const off = disabled || field.readOnly;
365
+ const name = (0, import_parameters2.parameterLabel)(field);
366
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1.5", children: [
367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input bg-muted", children: value ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
371
+ ParameterCommitInput,
372
+ {
373
+ id,
374
+ "aria-label": `${name} URL`,
375
+ placeholder: "Image URL",
376
+ value: value.startsWith("data:") ? "" : value,
377
+ disabled: off,
378
+ onCommit: (text) => onChange(text.trim())
379
+ }
380
+ ),
381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-1", children: [
382
+ resolveFile && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
383
+ "Choose file\u2026",
384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
385
+ "input",
386
+ {
387
+ type: "file",
388
+ className: "sr-only",
389
+ accept: field.accept ?? "image/*",
390
+ disabled: off,
391
+ "aria-label": `${name} file`,
392
+ onChange: async (e) => {
393
+ const file = e.target.files?.[0];
394
+ e.target.value = "";
395
+ if (file) onChange(await resolveFile(file));
396
+ }
397
+ }
398
+ )
399
+ ] }),
400
+ value && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
401
+ ] })
402
+ ] })
403
+ ] }),
404
+ field.options && field.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-1", children: field.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
405
+ "button",
406
+ {
407
+ type: "button",
408
+ title: o.label ?? o.url,
409
+ "aria-label": o.label ?? o.url,
410
+ "aria-pressed": o.url === value,
411
+ disabled: off,
412
+ onClick: () => onChange(o.url),
413
+ className: "h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary",
414
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
415
+ },
416
+ o.url
417
+ )) })
418
+ ] });
419
+ }
420
+ function ParameterText({ field, value, onChange, disabled, id }) {
421
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
422
+ ParameterCommitInput,
423
+ {
424
+ id,
425
+ "aria-label": (0, import_parameters2.parameterLabel)(field),
426
+ multiline: field.multiline,
427
+ placeholder: field.placeholder,
428
+ maxLength: field.maxLength,
429
+ value,
430
+ disabled: disabled || field.readOnly,
431
+ onCommit: onChange
432
+ }
433
+ );
434
+ }
435
+ function KeyframeDiamond({ state, onToggle, label, disabled, className = "" }) {
436
+ const keyed = state === "keyed";
437
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
438
+ "button",
439
+ {
440
+ type: "button",
441
+ "data-key-state": state,
442
+ "aria-pressed": keyed,
443
+ "aria-label": `${keyed ? "Remove" : "Add"} keyframe for ${label}`,
444
+ title: keyed ? "Remove keyframe here" : "Add keyframe at the playhead",
445
+ disabled,
446
+ onClick: onToggle,
447
+ className: `inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-40 ${state === "none" ? "text-muted-foreground hover:text-foreground" : "text-amber-500"} ${className}`,
448
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 10 10", className: "h-2.5 w-2.5", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
449
+ "path",
450
+ {
451
+ d: "M5 0.8 9.2 5 5 9.2 0.8 5Z",
452
+ fill: keyed ? "currentColor" : "none",
453
+ stroke: "currentColor",
454
+ strokeWidth: "1.3",
455
+ opacity: state === "animated" ? 0.7 : 1
456
+ }
457
+ ) })
458
+ }
459
+ );
460
+ }
461
+ function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest }) {
462
+ const set = onChange;
463
+ switch (field.type) {
464
+ case "number":
465
+ case "int":
466
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
467
+ case "boolean":
468
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
469
+ case "choice":
470
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
471
+ case "color":
472
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
473
+ case "vector":
474
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
475
+ case "image":
476
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
477
+ case "text":
478
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParameterText, { field, value, onChange: set, ...rest });
479
+ }
480
+ }
481
+
482
+ // src/parameters/ParametersPanel.tsx
483
+ var import_react3 = require("react");
484
+ var import_parameters3 = require("@pixerate/editor/parameters");
485
+ var import_jsx_runtime2 = require("react/jsx-runtime");
486
+ function FieldRow({
487
+ controller,
488
+ state,
489
+ field,
490
+ idBase,
491
+ showKeyframes,
492
+ showFieldReset,
493
+ resolveFile
494
+ }) {
495
+ const label = (0, import_parameters3.parameterLabel)(field);
496
+ const inputId = `${idBase}-${field.id}`;
497
+ const value = state.values[field.id];
498
+ const error = state.errors[field.id];
499
+ const changed = !(0, import_parameters3.parameterValuesEqual)(value, state.defaults[field.id]);
500
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-parameter-id": field.id, "data-parameter-type": field.type, className: "space-y-1", children: [
501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex min-h-[1.25rem] items-center gap-1.5", children: [
502
+ showKeyframes && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(KeyframeDiamond, { state: state.keys[field.id] ?? "none", label, onToggle: () => controller.toggleKeyframe(field.id) }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium text-foreground", title: field.description, children: label }),
504
+ showFieldReset && changed && !field.readOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
505
+ "button",
506
+ {
507
+ type: "button",
508
+ className: "rounded px-1 text-[11px] text-muted-foreground hover:text-foreground",
509
+ title: `Reset to ${String(state.defaults[field.id])}`,
510
+ "aria-label": `Reset ${label}`,
511
+ onClick: () => controller.reset([field.id]),
512
+ children: "\u21BA"
513
+ }
514
+ )
515
+ ] }),
516
+ field.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[11px] leading-snug text-muted-foreground", children: field.description }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
518
+ ParameterFieldControl,
519
+ {
520
+ field,
521
+ value,
522
+ id: inputId,
523
+ resolveFile,
524
+ onChange: (v) => controller.setValue(field.id, v),
525
+ onGestureStart: () => controller.beginGesture(field.id),
526
+ onGestureEnd: () => controller.endGesture()
527
+ }
528
+ ),
529
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error })
530
+ ] });
531
+ }
532
+ function PresetBar({ controller, state }) {
533
+ const [saving, setSaving] = (0, import_react3.useState)(false);
534
+ const [name, setName] = (0, import_react3.useState)("");
535
+ const [message, setMessage] = (0, import_react3.useState)(null);
536
+ const active = state.presets.find((p) => p.name === state.activePreset);
537
+ const save = (e) => {
538
+ e.preventDefault();
539
+ const result = controller.savePreset(name);
540
+ if (result.ok) {
541
+ setSaving(false);
542
+ setName("");
543
+ setMessage(null);
544
+ } else setMessage(result.message);
545
+ };
546
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-1.5", "data-parameter-presets": "", children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5", children: [
548
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
549
+ "select",
550
+ {
551
+ "aria-label": "Preset",
552
+ className: parameterInputClass,
553
+ value: state.activePreset ?? "",
554
+ onChange: (e) => e.target.value && controller.applyPreset(e.target.value),
555
+ children: [
556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: state.presets.length ? "Presets\u2026" : "No presets" }),
557
+ state.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: p.name, title: p.description, children: p.name }, p.name))
558
+ ]
559
+ }
560
+ ),
561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: parameterButtonClass, onClick: () => setSaving((s) => !s), "aria-expanded": saving, children: "Save\u2026" }),
562
+ active && !active.builtIn && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: parameterButtonClass, "aria-label": `Delete preset ${active.name}`, onClick: () => controller.deletePreset(active.name), children: "Delete" })
563
+ ] }),
564
+ saving && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { className: "flex items-center gap-1.5", onSubmit: save, children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
566
+ "input",
567
+ {
568
+ autoFocus: true,
569
+ "aria-label": "Preset name",
570
+ placeholder: "Preset name",
571
+ className: parameterInputClass,
572
+ value: name,
573
+ onChange: (e) => setName(e.target.value)
574
+ }
575
+ ),
576
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", className: parameterButtonClass, children: "Save" })
577
+ ] }),
578
+ message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
579
+ ] });
580
+ }
581
+ function ParametersPanel({
582
+ controller,
583
+ title,
584
+ showToolbar = true,
585
+ showPresets = true,
586
+ showKeyframes,
587
+ showFieldReset = true,
588
+ resolveFile,
589
+ emptyMessage = "No parameters.",
590
+ className = ""
591
+ }) {
592
+ const state = useParametersState(controller);
593
+ const idBase = (0, import_react3.useId)();
594
+ const keys = showKeyframes ?? state.keyframesEnabled;
595
+ const heading = title === void 0 ? state.schema.title : title;
596
+ const renderItems = (items) => items.map((item) => {
597
+ if (state.visible[item.id] === false) return null;
598
+ if ((0, import_parameters3.isParameterGroup)(item)) return renderGroup(item);
599
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
600
+ FieldRow,
601
+ {
602
+ controller,
603
+ state,
604
+ field: item,
605
+ idBase,
606
+ showKeyframes: keys,
607
+ showFieldReset,
608
+ resolveFile
609
+ },
610
+ item.id
611
+ );
612
+ });
613
+ const renderGroup = (group) => {
614
+ const collapsed = !!state.collapsed[group.id];
615
+ const bodyId = `${idBase}-group-${group.id}`;
616
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { "data-parameter-group": group.id, className: "rounded-md border border-border", children: [
617
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
618
+ "button",
619
+ {
620
+ type: "button",
621
+ "aria-expanded": !collapsed,
622
+ "aria-controls": bodyId,
623
+ onClick: () => controller.toggleCollapsed(group.id),
624
+ className: "flex w-full items-center gap-1.5 px-2 py-1.5 text-left text-xs font-semibold text-foreground hover:bg-muted",
625
+ children: [
626
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", className: `inline-block text-[10px] text-muted-foreground transition-transform ${collapsed ? "" : "rotate-90"}`, children: "\u25B6" }),
627
+ (0, import_parameters3.parameterLabel)(group)
628
+ ]
629
+ }
630
+ ),
631
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { id: bodyId, className: "space-y-3 border-t border-border px-2 py-2", children: [
632
+ group.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[11px] text-muted-foreground", children: group.description }),
633
+ renderItems(group.fields)
634
+ ] })
635
+ ] }, group.id);
636
+ };
637
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `space-y-3 text-foreground ${className}`, "data-parameters-panel": "", children: [
638
+ (heading || showToolbar) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
639
+ heading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "mr-auto text-sm font-semibold", children: heading }),
640
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap gap-1", role: "toolbar", "aria-label": "Parameter actions", children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
642
+ "button",
643
+ {
644
+ type: "button",
645
+ className: parameterButtonClass,
646
+ disabled: !state.canUndo,
647
+ title: state.canUndo ? `Undo ${state.undoLabel}` : "Nothing to undo",
648
+ onClick: () => controller.undo(),
649
+ children: "Undo"
650
+ }
651
+ ),
652
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
653
+ "button",
654
+ {
655
+ type: "button",
656
+ className: parameterButtonClass,
657
+ disabled: !state.canRedo,
658
+ title: state.canRedo ? `Redo ${state.redoLabel}` : "Nothing to redo",
659
+ onClick: () => controller.redo(),
660
+ children: "Redo"
661
+ }
662
+ ),
663
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: parameterButtonClass, disabled: !state.modified, onClick: () => controller.reset(), children: "Reset" }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: parameterButtonClass, onClick: () => controller.randomize(), children: "Randomize" })
665
+ ] })
666
+ ] }),
667
+ showPresets && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PresetBar, { controller, state }),
668
+ state.fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : renderItems(state.schema.fields)
669
+ ] });
670
+ }
671
+ // Annotate the CommonJS export names for ESM import in node:
672
+ 0 && (module.exports = {
673
+ KeyframeDiamond,
674
+ ParameterColor,
675
+ ParameterCommitInput,
676
+ ParameterFieldControl,
677
+ ParameterImage,
678
+ ParameterNumberInput,
679
+ ParameterSelect,
680
+ ParameterSlider,
681
+ ParameterText,
682
+ ParameterToggle,
683
+ ParameterVector,
684
+ ParametersPanel,
685
+ parameterButtonClass,
686
+ parameterInputClass,
687
+ useParameterCommands,
688
+ useParameters,
689
+ useParametersController,
690
+ useParametersState
691
+ });
692
+ //# sourceMappingURL=index.cjs.map