@pixerate/editor-react 0.14.0 → 0.16.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.
Files changed (54) hide show
  1. package/README.md +6 -1
  2. package/dist/canvas/index.cjs +352 -118
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +112 -8
  5. package/dist/canvas/index.d.ts +112 -8
  6. package/dist/canvas/index.js +258 -19
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-4ZC2WCUO.js +196 -0
  9. package/dist/chunk-4ZC2WCUO.js.map +1 -0
  10. package/dist/chunk-7SPRQZ6H.js +216 -0
  11. package/dist/chunk-7SPRQZ6H.js.map +1 -0
  12. package/dist/{chunk-25S5EHH4.js → chunk-FYIPXHAE.js} +22 -13
  13. package/dist/chunk-FYIPXHAE.js.map +1 -0
  14. package/dist/{chunk-TL27COKM.js → chunk-XTFR3IMK.js} +683 -286
  15. package/dist/chunk-XTFR3IMK.js.map +1 -0
  16. package/dist/chunk-XWFU2CSG.js +167 -0
  17. package/dist/chunk-XWFU2CSG.js.map +1 -0
  18. package/dist/commands/index.cjs +232 -0
  19. package/dist/commands/index.cjs.map +1 -0
  20. package/dist/commands/index.d.cts +69 -0
  21. package/dist/commands/index.d.ts +69 -0
  22. package/dist/commands/index.js +13 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/generation/index.cjs +235 -0
  25. package/dist/generation/index.cjs.map +1 -0
  26. package/dist/generation/index.d.cts +60 -0
  27. package/dist/generation/index.d.ts +60 -0
  28. package/dist/generation/index.js +13 -0
  29. package/dist/generation/index.js.map +1 -0
  30. package/dist/image-editor/index.cjs +688 -299
  31. package/dist/image-editor/index.cjs.map +1 -1
  32. package/dist/image-editor/index.d.cts +65 -5
  33. package/dist/image-editor/index.d.ts +65 -5
  34. package/dist/image-editor/index.js +13 -3
  35. package/dist/index.cjs +2200 -683
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +201 -23
  38. package/dist/index.d.ts +201 -23
  39. package/dist/index.js +743 -129
  40. package/dist/index.js.map +1 -1
  41. package/dist/persistence/index.cjs +191 -0
  42. package/dist/persistence/index.cjs.map +1 -0
  43. package/dist/persistence/index.d.cts +77 -0
  44. package/dist/persistence/index.d.ts +77 -0
  45. package/dist/persistence/index.js +9 -0
  46. package/dist/persistence/index.js.map +1 -0
  47. package/dist/spreadsheet/index.cjs +17 -10
  48. package/dist/spreadsheet/index.cjs.map +1 -1
  49. package/dist/spreadsheet/index.d.cts +9 -7
  50. package/dist/spreadsheet/index.d.ts +9 -7
  51. package/dist/spreadsheet/index.js +1 -1
  52. package/package.json +26 -2
  53. package/dist/chunk-25S5EHH4.js.map +0 -1
  54. package/dist/chunk-TL27COKM.js.map +0 -1
@@ -11,6 +11,8 @@ function useImageEditor(options = {}) {
11
11
  const controller = controllerRef.current;
12
12
  const [state, setState] = useState(() => controller.getState());
13
13
  const [historyTicks, setHistoryTicks] = useState(0);
14
+ const [histogram, setHistogram] = useState(() => controller.getHistogram());
15
+ useEffect(() => controller.subscribeHistogram(setHistogram), [controller]);
14
16
  useEffect(() => {
15
17
  const unsub = controller.subscribe((next) => {
16
18
  setState(next);
@@ -42,6 +44,21 @@ function useImageEditor(options = {}) {
42
44
  [controller]
43
45
  );
44
46
  const resetAdjustments = useCallback(() => controller.resetAdjustments(), [controller]);
47
+ const upgradeProcess = useCallback(() => controller.upgradeProcess(), [controller]);
48
+ const addAdjustmentLayer = useCallback(
49
+ (init, index) => controller.addAdjustmentLayer(init, index),
50
+ [controller]
51
+ );
52
+ const updateAdjustmentLayer = useCallback(
53
+ (id, update, commitToHistory = true) => controller.updateAdjustmentLayer(id, update, commitToHistory),
54
+ [controller]
55
+ );
56
+ const removeAdjustmentLayer = useCallback((id) => controller.removeAdjustmentLayer(id), [controller]);
57
+ const moveAdjustmentLayer = useCallback((id, to) => controller.moveAdjustmentLayer(id, to), [controller]);
58
+ const toggleAdjustmentLayer = useCallback(
59
+ (id, enabled) => controller.toggleAdjustmentLayer(id, enabled),
60
+ [controller]
61
+ );
45
62
  const setDepthMask = useCallback((partial) => controller.setDepthMask(partial), [controller]);
46
63
  const resetDepthMask = useCallback(() => controller.resetDepthMask(), [controller]);
47
64
  const addAnnotation = useCallback((ann) => controller.addAnnotation(ann), [controller]);
@@ -102,6 +119,13 @@ function useImageEditor(options = {}) {
102
119
  flipVertical,
103
120
  setAdjustments,
104
121
  resetAdjustments,
122
+ upgradeProcess,
123
+ addAdjustmentLayer,
124
+ updateAdjustmentLayer,
125
+ removeAdjustmentLayer,
126
+ moveAdjustmentLayer,
127
+ toggleAdjustmentLayer,
128
+ histogram,
105
129
  setDepthMask,
106
130
  resetDepthMask,
107
131
  addAnnotation,
@@ -130,20 +154,474 @@ function useImageEditor(options = {}) {
130
154
  applyBackgroundRemovedImage
131
155
  }),
132
156
  // eslint-disable-next-line react-hooks/exhaustive-deps
133
- [controller, state, historyTicks]
157
+ [controller, state, historyTicks, histogram]
134
158
  );
135
159
  }
136
160
 
137
161
  // src/image-editor/ImageEditor.tsx
138
- import { useRef as useRef2, useEffect as useEffect2, useState as useState2, useCallback as useCallback2 } from "react";
162
+ import React4, { useRef as useRef3, useEffect as useEffect4, useState as useState5, useCallback as useCallback2 } from "react";
139
163
  import {
140
164
  canvasToImagePoint,
141
165
  hitTestCrop,
142
166
  getCropCursor,
143
167
  calculateCropDrag,
144
- hitTestAnnotation
168
+ hitTestAnnotation,
169
+ normalizeProcessVersion,
170
+ LATEST_PROCESS_VERSION,
171
+ IMAGE_ADJUSTMENT_CONTROLS,
172
+ readAdjustmentControls
145
173
  } from "@pixerate/editor/image-editor";
146
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
174
+
175
+ // src/image-editor/ControlSlider.tsx
176
+ import { controlTrackBackground, formatControlValue } from "@pixerate/editor/image-editor";
177
+ import { jsx, jsxs } from "react/jsx-runtime";
178
+ var ControlSlider = ({ spec, value, onChange, disabled, label }) => {
179
+ const name = label ?? spec.label;
180
+ const track = controlTrackBackground(spec);
181
+ return /* @__PURE__ */ jsxs("div", { "data-control-id": spec.id, children: [
182
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
183
+ /* @__PURE__ */ jsx("span", { children: name }),
184
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: formatControlValue(spec, value) })
185
+ ] }),
186
+ /* @__PURE__ */ jsx(
187
+ "input",
188
+ {
189
+ type: "range",
190
+ min: spec.min,
191
+ max: spec.max,
192
+ step: spec.step,
193
+ "aria-label": name,
194
+ disabled,
195
+ value,
196
+ onChange: (e) => onChange(Number(e.target.value)),
197
+ className: "w-full accent-indigo-500 disabled:opacity-40"
198
+ }
199
+ ),
200
+ track && /* @__PURE__ */ jsx("div", { className: "h-0.5 rounded-full opacity-70", style: { background: track } })
201
+ ] });
202
+ };
203
+
204
+ // src/image-editor/Histogram.tsx
205
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
206
+ import { histogramPath } from "@pixerate/editor/image-editor";
207
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
208
+ var FILL = {
209
+ luminance: "rgba(148, 163, 184, 0.45)",
210
+ red: "rgba(239, 68, 68, 0.45)",
211
+ green: "rgba(34, 197, 94, 0.45)",
212
+ blue: "rgba(59, 130, 246, 0.45)"
213
+ };
214
+ var W = 256;
215
+ var Histogram = ({ histogram, channel = "rgb", height = 64, className = "", style }) => {
216
+ const channels = channel === "rgb" ? ["luminance", "red", "green", "blue"] : [channel];
217
+ const paths = useMemo2(
218
+ () => histogram && histogram.samples > 0 ? channels.map((c) => ({ c, d: histogramPath(histogram, c, W, height) })) : [],
219
+ // eslint-disable-next-line react-hooks/exhaustive-deps
220
+ [histogram, channel, height]
221
+ );
222
+ const clipLow = histogram && histogram.samples ? Math.max(...histogram.shadows) / histogram.samples : 0;
223
+ const clipHigh = histogram && histogram.samples ? Math.max(...histogram.highlights) / histogram.samples : 0;
224
+ return /* @__PURE__ */ jsxs2(
225
+ "svg",
226
+ {
227
+ viewBox: `0 0 ${W} ${height}`,
228
+ preserveAspectRatio: "none",
229
+ role: "img",
230
+ "aria-label": histogram ? `Histogram of ${histogram.samples} pixels` : "Histogram (no data)",
231
+ "data-testid": "image-editor-histogram",
232
+ className: `w-full rounded bg-slate-950/60 ${className}`,
233
+ style: { height, ...style },
234
+ children: [
235
+ paths.map(({ c, d }) => /* @__PURE__ */ jsx2("path", { d, fill: FILL[c], stroke: "none" }, c)),
236
+ clipLow > 5e-3 && /* @__PURE__ */ jsx2("rect", { x: 0, y: 0, width: 3, height, fill: "#3b82f6", "data-testid": "histogram-clip-shadows" }),
237
+ clipHigh > 5e-3 && /* @__PURE__ */ jsx2("rect", { x: W - 3, y: 0, width: 3, height, fill: "#ef4444", "data-testid": "histogram-clip-highlights" })
238
+ ]
239
+ }
240
+ );
241
+ };
242
+ function useImageHistogram(controller) {
243
+ const [histogram, setHistogram] = useState2(() => controller.getHistogram());
244
+ useEffect2(() => controller.subscribeHistogram(setHistogram), [controller]);
245
+ return histogram;
246
+ }
247
+
248
+ // src/image-editor/AdjustmentLayersPanel.tsx
249
+ import { useEffect as useEffect3, useState as useState4 } from "react";
250
+ import {
251
+ ADJUSTMENT_KINDS,
252
+ ADJUSTMENT_LAYER_CONTROLS,
253
+ ADJUSTMENT_LAYER_OPACITY_CONTROL,
254
+ BLEND_MODES,
255
+ HUE_RANGE_LABELS,
256
+ adjustmentKindLabel,
257
+ getControlPath,
258
+ hueRangeWeight,
259
+ setControlPath
260
+ } from "@pixerate/editor/image-editor";
261
+
262
+ // src/image-editor/CurvesEditor.tsx
263
+ import { useRef as useRef2, useState as useState3 } from "react";
264
+ import {
265
+ boxToCurvePoint,
266
+ curvePointToBox,
267
+ curveSvgPath,
268
+ hitTestCurvePoint,
269
+ histogramPath as histogramPath2,
270
+ insertCurvePoint,
271
+ moveCurvePoint,
272
+ removeCurvePoint
273
+ } from "@pixerate/editor/image-editor";
274
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
275
+ var BOX = 256;
276
+ var STROKE = { master: "#e2e8f0", red: "#ef4444", green: "#22c55e", blue: "#3b82f6" };
277
+ var CurvesEditor = ({ points, onChange, histogram, channel = "master", size = 200 }) => {
278
+ const svgRef = useRef2(null);
279
+ const [drag, setDrag] = useState3(null);
280
+ const toBox = (e) => {
281
+ const rect = svgRef.current?.getBoundingClientRect();
282
+ if (!rect || !rect.width || !rect.height) return { x: 0, y: 0 };
283
+ return { x: (e.clientX - rect.left) / rect.width * BOX, y: (e.clientY - rect.top) / rect.height * BOX };
284
+ };
285
+ const onPointerDown = (e) => {
286
+ const { x, y } = toBox(e);
287
+ let index = hitTestCurvePoint(points, x, y, BOX, BOX, 10);
288
+ if (index === -1) {
289
+ const inserted = insertCurvePoint(points, boxToCurvePoint(x, y, BOX, BOX));
290
+ onChange(inserted.points, true);
291
+ index = inserted.index;
292
+ } else {
293
+ onChange(points, true);
294
+ }
295
+ setDrag(index);
296
+ e.target.setPointerCapture?.(e.pointerId);
297
+ };
298
+ const onPointerMove = (e) => {
299
+ if (drag === null) return;
300
+ onChange(moveCurvePoint(points, drag, boxToCurvePoint(toBox(e).x, toBox(e).y, BOX, BOX)), false);
301
+ };
302
+ const onDoubleClick = (e) => {
303
+ const { x, y } = toBox(e);
304
+ const index = hitTestCurvePoint(points, x, y, BOX, BOX, 10);
305
+ if (index !== -1 && points.length > 2) onChange(removeCurvePoint(points, index), true);
306
+ };
307
+ const histChannel = channel === "master" ? "luminance" : channel;
308
+ return /* @__PURE__ */ jsxs3(
309
+ "svg",
310
+ {
311
+ ref: svgRef,
312
+ viewBox: `0 0 ${BOX} ${BOX}`,
313
+ width: size,
314
+ height: size,
315
+ role: "application",
316
+ "aria-label": `${channel} curve`,
317
+ "data-testid": "curves-editor",
318
+ className: "rounded bg-slate-950/70 touch-none select-none cursor-crosshair",
319
+ onPointerDown,
320
+ onPointerMove,
321
+ onPointerUp: () => setDrag(null),
322
+ onPointerCancel: () => setDrag(null),
323
+ onDoubleClick,
324
+ children: [
325
+ histogram && histogram.samples > 0 && /* @__PURE__ */ jsx3("path", { d: histogramPath2(histogram, histChannel, BOX, BOX), fill: "rgba(148, 163, 184, 0.25)" }),
326
+ [64, 128, 192].map((v) => /* @__PURE__ */ jsxs3("g", { stroke: "rgba(148, 163, 184, 0.2)", strokeWidth: 1, children: [
327
+ /* @__PURE__ */ jsx3("line", { x1: v, y1: 0, x2: v, y2: BOX }),
328
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: v, x2: BOX, y2: v })
329
+ ] }, v)),
330
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: BOX, x2: BOX, y2: 0, stroke: "rgba(148, 163, 184, 0.35)", strokeDasharray: "4 4" }),
331
+ /* @__PURE__ */ jsx3("path", { d: curveSvgPath(points, BOX, BOX), fill: "none", stroke: STROKE[channel], strokeWidth: 2 }),
332
+ points.map((p, i) => {
333
+ const b = curvePointToBox(p, BOX, BOX);
334
+ return /* @__PURE__ */ jsx3(
335
+ "circle",
336
+ {
337
+ cx: b.x,
338
+ cy: b.y,
339
+ r: 5,
340
+ fill: drag === i ? STROKE[channel] : "#0f172a",
341
+ stroke: STROKE[channel],
342
+ strokeWidth: 2,
343
+ "aria-label": `Point ${p.input} to ${p.output}`
344
+ },
345
+ i
346
+ );
347
+ })
348
+ ]
349
+ }
350
+ );
351
+ };
352
+
353
+ // src/image-editor/AdjustmentLayersPanel.tsx
354
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
355
+ var CHANNELS = [
356
+ { id: "master", label: "RGB" },
357
+ { id: "red", label: "Red" },
358
+ { id: "green", label: "Green" },
359
+ { id: "blue", label: "Blue" }
360
+ ];
361
+ var TONES = [
362
+ { id: "shadows", label: "Shadows" },
363
+ { id: "midtones", label: "Midtones" },
364
+ { id: "highlights", label: "Highlights" }
365
+ ];
366
+ var selectClass = "bg-slate-800 border border-slate-700 rounded px-1.5 py-1 text-slate-200";
367
+ var chipClass = (on) => `px-2 py-0.5 rounded text-[10px] ${on ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-400 hover:text-slate-200"}`;
368
+ var HueRangeStrip = ({ bounds }) => {
369
+ const pts = Array.from({ length: 73 }, (_, i) => `${i / 72 * 360},${20 - hueRangeWeight({ bounds }, i * 5) * 18}`).join(" ");
370
+ return /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 360 20", preserveAspectRatio: "none", className: "w-full h-4 rounded", "data-testid": "hue-range-strip", children: [
371
+ /* @__PURE__ */ jsx4("defs", { children: /* @__PURE__ */ jsx4("linearGradient", { id: "pixerate-hue-strip", children: [0, 60, 120, 180, 240, 300, 360].map((h) => /* @__PURE__ */ jsx4("stop", { offset: `${h / 360 * 100}%`, stopColor: `hsl(${h}, 90%, 50%)` }, h)) }) }),
372
+ /* @__PURE__ */ jsx4("rect", { width: 360, height: 20, fill: "url(#pixerate-hue-strip)", opacity: 0.6 }),
373
+ /* @__PURE__ */ jsx4("polyline", { points: pts, fill: "none", stroke: "#fff", strokeWidth: 1.5 })
374
+ ] });
375
+ };
376
+ var AdjustmentLayersPanel = ({ controller, layers: layersProp, histogram }) => {
377
+ const follow = layersProp === void 0;
378
+ const [, setTick] = useState4(0);
379
+ useEffect3(() => follow ? controller.subscribe(() => setTick((n) => n + 1)) : void 0, [controller, follow]);
380
+ const layers = layersProp ?? controller.getAdjustmentLayers();
381
+ const [newKind, setNewKind] = useState4("curves");
382
+ const [selectedId, setSelectedId] = useState4(null);
383
+ const [channel, setChannel] = useState4("master");
384
+ const [range, setRange] = useState4(-1);
385
+ const [tone, setTone] = useState4("midtones");
386
+ const selected = layers.find((l) => l.id === selectedId) ?? null;
387
+ const topFirst = [...layers].reverse();
388
+ const setParam = (layer, path, value, commit = true) => controller.updateAdjustmentLayer(layer.id, { params: setControlPath(layer.params, path, value) }, commit);
389
+ const slider = (layer, spec, label) => /* @__PURE__ */ jsx4(
390
+ ControlSlider,
391
+ {
392
+ spec,
393
+ label,
394
+ value: Number(getControlPath(layer.params, spec.id) ?? spec.default),
395
+ onChange: (v) => setParam(layer, spec.id, v)
396
+ },
397
+ spec.id
398
+ );
399
+ const controlsOf = (layer, filter = () => true) => ADJUSTMENT_LAYER_CONTROLS[layer.kind].filter(filter);
400
+ const checkbox = (layer, path, label) => /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-slate-300", children: [
401
+ /* @__PURE__ */ jsx4(
402
+ "input",
403
+ {
404
+ type: "checkbox",
405
+ checked: Boolean(getControlPath(layer.params, path)),
406
+ onChange: (e) => setParam(layer, path, e.target.checked),
407
+ className: "accent-indigo-500"
408
+ }
409
+ ),
410
+ label
411
+ ] });
412
+ const editor = (layer) => {
413
+ switch (layer.kind) {
414
+ case "curves":
415
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
416
+ /* @__PURE__ */ jsx4("div", { className: "flex gap-1", role: "group", "aria-label": "Curve channel", children: CHANNELS.map((c) => /* @__PURE__ */ jsx4("button", { type: "button", className: chipClass(channel === c.id), onClick: () => setChannel(c.id), children: c.label }, c.id)) }),
417
+ /* @__PURE__ */ jsx4(
418
+ CurvesEditor,
419
+ {
420
+ points: layer.params[channel],
421
+ channel,
422
+ histogram,
423
+ onChange: (points, commit) => setParam(layer, channel, points, commit)
424
+ }
425
+ )
426
+ ] });
427
+ case "levels":
428
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
429
+ /* @__PURE__ */ jsx4("div", { className: "flex gap-1", role: "group", "aria-label": "Levels channel", children: CHANNELS.map((c) => /* @__PURE__ */ jsx4("button", { type: "button", className: chipClass(channel === c.id), onClick: () => setChannel(c.id), children: c.label }, c.id)) }),
430
+ controlsOf(layer, (s) => s.id.startsWith(`${channel}.`)).map((s) => slider(layer, s))
431
+ ] });
432
+ case "hueSaturation":
433
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
434
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": "Hue range", children: [
435
+ /* @__PURE__ */ jsx4("button", { type: "button", className: chipClass(range === -1), onClick: () => setRange(-1), children: "Master" }),
436
+ HUE_RANGE_LABELS.map((name, i) => /* @__PURE__ */ jsx4("button", { type: "button", className: chipClass(range === i), onClick: () => setRange(i), disabled: layer.params.colorize, children: name }, name))
437
+ ] }),
438
+ range >= 0 && !layer.params.colorize && /* @__PURE__ */ jsx4(HueRangeStrip, { bounds: layer.params.ranges[range].bounds }),
439
+ controlsOf(layer, (s) => range === -1 || layer.params.colorize ? !s.id.startsWith("ranges.") : s.id.startsWith(`ranges.${range}.`)).map(
440
+ (s) => slider(layer, s)
441
+ ),
442
+ range === -1 && checkbox(layer, "colorize", "Colorize")
443
+ ] });
444
+ case "colorBalance":
445
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
446
+ /* @__PURE__ */ jsx4("div", { className: "flex gap-1", role: "group", "aria-label": "Tone", children: TONES.map((t) => /* @__PURE__ */ jsx4("button", { type: "button", className: chipClass(tone === t.id), onClick: () => setTone(t.id), children: t.label }, t.id)) }),
447
+ controlsOf(layer, (s) => s.id.startsWith(`${tone}.`)).map((s) => slider(layer, s)),
448
+ checkbox(layer, "preserveLuminosity", "Preserve luminosity")
449
+ ] });
450
+ case "blackWhite":
451
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
452
+ controlsOf(layer).map((s) => slider(layer, s)),
453
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-slate-300", children: [
454
+ /* @__PURE__ */ jsx4(
455
+ "input",
456
+ {
457
+ type: "checkbox",
458
+ checked: layer.params.tint !== null,
459
+ onChange: (e) => setParam(layer, "tint", e.target.checked ? "#e1d3b3" : null),
460
+ className: "accent-indigo-500"
461
+ }
462
+ ),
463
+ "Tint",
464
+ layer.params.tint && /* @__PURE__ */ jsx4(
465
+ "input",
466
+ {
467
+ type: "color",
468
+ "aria-label": "Tint colour",
469
+ value: layer.params.tint,
470
+ onChange: (e) => setParam(layer, "tint", e.target.value),
471
+ className: "h-5 w-8 bg-transparent"
472
+ }
473
+ )
474
+ ] })
475
+ ] });
476
+ case "photoFilter":
477
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
478
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-slate-300", children: [
479
+ "Filter colour",
480
+ /* @__PURE__ */ jsx4(
481
+ "input",
482
+ {
483
+ type: "color",
484
+ "aria-label": "Filter colour",
485
+ value: layer.params.color,
486
+ onChange: (e) => setParam(layer, "color", e.target.value),
487
+ className: "h-5 w-8 bg-transparent"
488
+ }
489
+ )
490
+ ] }),
491
+ controlsOf(layer).map((s) => slider(layer, s)),
492
+ checkbox(layer, "preserveLuminosity", "Preserve luminosity")
493
+ ] });
494
+ case "invert":
495
+ return /* @__PURE__ */ jsx4("p", { className: "text-slate-500 text-[11px]", children: "Invert has no settings." });
496
+ default:
497
+ return /* @__PURE__ */ jsx4("div", { className: "flex flex-col gap-2", children: controlsOf(layer).map((s) => slider(layer, s)) });
498
+ }
499
+ };
500
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2 text-xs", "data-testid": "adjustment-layers-panel", children: [
501
+ /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx4("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Adjustment Layers" }) }),
502
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-1.5", children: [
503
+ /* @__PURE__ */ jsx4(
504
+ "select",
505
+ {
506
+ "aria-label": "New adjustment layer kind",
507
+ className: `${selectClass} flex-1`,
508
+ value: newKind,
509
+ onChange: (e) => setNewKind(e.target.value),
510
+ children: ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ jsx4("option", { value: k.kind, children: k.label }, k.kind))
511
+ }
512
+ ),
513
+ /* @__PURE__ */ jsx4(
514
+ "button",
515
+ {
516
+ type: "button",
517
+ className: "px-2 py-1 rounded bg-indigo-600 text-white hover:bg-indigo-500",
518
+ onClick: () => {
519
+ setSelectedId(controller.addAdjustmentLayer(newKind));
520
+ setChannel("master");
521
+ setRange(-1);
522
+ },
523
+ children: "Add Layer"
524
+ }
525
+ )
526
+ ] }),
527
+ topFirst.length > 0 && /* @__PURE__ */ jsx4("ul", { className: "flex flex-col gap-1", "aria-label": "Adjustment layers", children: topFirst.map((layer) => {
528
+ const index = layers.indexOf(layer);
529
+ const label = layer.name || adjustmentKindLabel(layer.kind);
530
+ return /* @__PURE__ */ jsxs4(
531
+ "li",
532
+ {
533
+ className: `flex items-center gap-1.5 rounded px-1.5 py-1 ${layer.id === selectedId ? "bg-indigo-600/20 ring-1 ring-indigo-500/50" : "bg-slate-800/60"}`,
534
+ children: [
535
+ /* @__PURE__ */ jsx4(
536
+ "input",
537
+ {
538
+ type: "checkbox",
539
+ "aria-label": `Show ${label}`,
540
+ checked: layer.enabled,
541
+ onChange: () => controller.toggleAdjustmentLayer(layer.id),
542
+ className: "accent-indigo-500"
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsx4(
546
+ "button",
547
+ {
548
+ type: "button",
549
+ className: `flex-1 text-left truncate ${layer.enabled ? "text-slate-200" : "text-slate-500 line-through"}`,
550
+ onClick: () => setSelectedId(layer.id === selectedId ? null : layer.id),
551
+ "aria-pressed": layer.id === selectedId,
552
+ children: label
553
+ }
554
+ ),
555
+ /* @__PURE__ */ jsx4(
556
+ "button",
557
+ {
558
+ type: "button",
559
+ "aria-label": `Move ${label} up`,
560
+ disabled: index === layers.length - 1,
561
+ onClick: () => controller.moveAdjustmentLayer(layer.id, index + 1),
562
+ className: "px-1 text-slate-400 hover:text-slate-200 disabled:opacity-30",
563
+ children: "\u2191"
564
+ }
565
+ ),
566
+ /* @__PURE__ */ jsx4(
567
+ "button",
568
+ {
569
+ type: "button",
570
+ "aria-label": `Move ${label} down`,
571
+ disabled: index === 0,
572
+ onClick: () => controller.moveAdjustmentLayer(layer.id, index - 1),
573
+ className: "px-1 text-slate-400 hover:text-slate-200 disabled:opacity-30",
574
+ children: "\u2193"
575
+ }
576
+ ),
577
+ /* @__PURE__ */ jsx4(
578
+ "button",
579
+ {
580
+ type: "button",
581
+ "aria-label": `Remove ${label}`,
582
+ onClick: () => {
583
+ controller.removeAdjustmentLayer(layer.id);
584
+ if (selectedId === layer.id) setSelectedId(null);
585
+ },
586
+ className: "px-1 text-rose-400 hover:text-rose-300",
587
+ children: "\u2715"
588
+ }
589
+ )
590
+ ]
591
+ },
592
+ layer.id
593
+ );
594
+ }) }),
595
+ selected && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2 rounded border border-slate-700/60 p-2", "data-testid": "adjustment-layer-editor", children: [
596
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
597
+ /* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "Blend" }),
598
+ /* @__PURE__ */ jsx4(
599
+ "select",
600
+ {
601
+ "aria-label": "Blend mode",
602
+ className: `${selectClass} flex-1`,
603
+ value: selected.blend,
604
+ onChange: (e) => controller.updateAdjustmentLayer(selected.id, { blend: e.target.value }),
605
+ children: BLEND_MODES.map((m) => /* @__PURE__ */ jsx4("option", { value: m.id, children: m.label }, m.id))
606
+ }
607
+ )
608
+ ] }),
609
+ /* @__PURE__ */ jsx4(
610
+ ControlSlider,
611
+ {
612
+ spec: ADJUSTMENT_LAYER_OPACITY_CONTROL,
613
+ label: "Layer opacity",
614
+ value: selected.opacity,
615
+ onChange: (v) => controller.updateAdjustmentLayer(selected.id, { opacity: v })
616
+ }
617
+ ),
618
+ editor(selected)
619
+ ] })
620
+ ] });
621
+ };
622
+
623
+ // src/image-editor/ImageEditor.tsx
624
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
147
625
  var ImageEditor = ({
148
626
  editor: externalEditor,
149
627
  src,
@@ -158,16 +636,17 @@ var ImageEditor = ({
158
636
  const internalEditor = useImageEditor({ image: src });
159
637
  const editor = externalEditor || internalEditor;
160
638
  const { state, controller } = editor;
161
- const canvasRef = useRef2(null);
162
- const containerRef = useRef2(null);
163
- const [activeTab, setActiveTab] = useState2("crop");
164
- const [activeCropRatio, setActiveCropRatio] = useState2(null);
165
- const [inpaintPrompt, setInpaintPrompt] = useState2("");
166
- const [inpaintMaskMode, setInpaintMaskMode] = useState2("annotations");
167
- const [isRemovingBg, setIsRemovingBg] = useState2(false);
168
- const [isInpainting, setIsInpainting] = useState2(false);
169
- const [aiError, setAiError] = useState2(null);
170
- const [aiSuccess, setAiSuccess] = useState2(null);
639
+ const legacyProcess = normalizeProcessVersion(state.adjustments.process) < LATEST_PROCESS_VERSION;
640
+ const canvasRef = useRef3(null);
641
+ const containerRef = useRef3(null);
642
+ const [activeTab, setActiveTab] = useState5("crop");
643
+ const [activeCropRatio, setActiveCropRatio] = useState5(null);
644
+ const [inpaintPrompt, setInpaintPrompt] = useState5("");
645
+ const [inpaintMaskMode, setInpaintMaskMode] = useState5("annotations");
646
+ const [isRemovingBg, setIsRemovingBg] = useState5(false);
647
+ const [isInpainting, setIsInpainting] = useState5(false);
648
+ const [aiError, setAiError] = useState5(null);
649
+ const [aiSuccess, setAiSuccess] = useState5(null);
171
650
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
172
651
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
173
652
  const handleRemoveBackground = async () => {
@@ -229,17 +708,17 @@ var ImageEditor = ({
229
708
  setIsInpainting(false);
230
709
  }
231
710
  };
232
- const [activeColor, setActiveColor] = useState2("#ef4444");
233
- const [strokeWidth, setStrokeWidth] = useState2(3);
234
- const [fontSize, setFontSize] = useState2(24);
235
- const [textInput, setTextInput] = useState2("");
236
- const draftAnnotationRef = useRef2(null);
237
- const draftCropRef = useRef2(null);
238
- const cropDragInfoRef = useRef2(null);
239
- const annotationDragInfoRef = useRef2(null);
240
- const isDrawingRef = useRef2(false);
241
- const currentPathRef = useRef2([]);
242
- const dragStartPointRef = useRef2(null);
711
+ const [activeColor, setActiveColor] = useState5("#ef4444");
712
+ const [strokeWidth, setStrokeWidth] = useState5(3);
713
+ const [fontSize, setFontSize] = useState5(24);
714
+ const [textInput, setTextInput] = useState5("");
715
+ const draftAnnotationRef = useRef3(null);
716
+ const draftCropRef = useRef3(null);
717
+ const cropDragInfoRef = useRef3(null);
718
+ const annotationDragInfoRef = useRef3(null);
719
+ const isDrawingRef = useRef3(false);
720
+ const currentPathRef = useRef3([]);
721
+ const dragStartPointRef = useRef3(null);
243
722
  const renderCanvas = useCallback2(
244
723
  async (draftAnnotation, draftCrop) => {
245
724
  const canvas = canvasRef.current;
@@ -259,7 +738,7 @@ var ImageEditor = ({
259
738
  },
260
739
  [controller, activeTab]
261
740
  );
262
- useEffect2(() => {
741
+ useEffect4(() => {
263
742
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
264
743
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
265
744
  const animId = raf(() => {
@@ -267,12 +746,12 @@ var ImageEditor = ({
267
746
  });
268
747
  return () => caf(animId);
269
748
  }, [renderCanvas, state]);
270
- useEffect2(() => {
749
+ useEffect4(() => {
271
750
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
272
751
  editor.setTool("crop");
273
752
  }
274
753
  }, [activeTab, editor]);
275
- useEffect2(() => {
754
+ useEffect4(() => {
276
755
  const container = containerRef.current;
277
756
  if (!container) return;
278
757
  if (typeof ResizeObserver !== "undefined") {
@@ -630,15 +1109,15 @@ var ImageEditor = ({
630
1109
  link.click();
631
1110
  }
632
1111
  };
633
- return /* @__PURE__ */ jsxs(
1112
+ return /* @__PURE__ */ jsxs5(
634
1113
  "div",
635
1114
  {
636
1115
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
637
1116
  style,
638
1117
  children: [
639
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between px-4 py-3 bg-slate-950/80 border-b border-slate-800 gap-3", children: [
640
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
641
- /* @__PURE__ */ jsx(
1118
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center justify-between px-4 py-3 bg-slate-950/80 border-b border-slate-800 gap-3", children: [
1119
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
1120
+ /* @__PURE__ */ jsx5(
642
1121
  "button",
643
1122
  {
644
1123
  type: "button",
@@ -650,7 +1129,7 @@ var ImageEditor = ({
650
1129
  children: "Transform & Crop"
651
1130
  }
652
1131
  ),
653
- /* @__PURE__ */ jsx(
1132
+ /* @__PURE__ */ jsx5(
654
1133
  "button",
655
1134
  {
656
1135
  type: "button",
@@ -662,7 +1141,7 @@ var ImageEditor = ({
662
1141
  children: "Adjustments"
663
1142
  }
664
1143
  ),
665
- /* @__PURE__ */ jsx(
1144
+ /* @__PURE__ */ jsx5(
666
1145
  "button",
667
1146
  {
668
1147
  type: "button",
@@ -674,7 +1153,7 @@ var ImageEditor = ({
674
1153
  children: "Annotations"
675
1154
  }
676
1155
  ),
677
- /* @__PURE__ */ jsx(
1156
+ /* @__PURE__ */ jsx5(
678
1157
  "button",
679
1158
  {
680
1159
  type: "button",
@@ -686,7 +1165,7 @@ var ImageEditor = ({
686
1165
  children: "Depth Mask"
687
1166
  }
688
1167
  ),
689
- /* @__PURE__ */ jsxs(
1168
+ /* @__PURE__ */ jsxs5(
690
1169
  "button",
691
1170
  {
692
1171
  type: "button",
@@ -696,14 +1175,14 @@ var ImageEditor = ({
696
1175
  },
697
1176
  className: `px-3 py-1.5 rounded-md font-medium transition-colors flex items-center gap-1.5 ${activeTab === "ai" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
698
1177
  children: [
699
- /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1178
+ /* @__PURE__ */ jsx5("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx5("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
700
1179
  "AI Tools"
701
1180
  ]
702
1181
  }
703
1182
  )
704
1183
  ] }),
705
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
706
- /* @__PURE__ */ jsx(
1184
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-xs", children: [
1185
+ /* @__PURE__ */ jsx5(
707
1186
  "button",
708
1187
  {
709
1188
  type: "button",
@@ -714,7 +1193,7 @@ var ImageEditor = ({
714
1193
  children: "\u21A9"
715
1194
  }
716
1195
  ),
717
- /* @__PURE__ */ jsx(
1196
+ /* @__PURE__ */ jsx5(
718
1197
  "button",
719
1198
  {
720
1199
  type: "button",
@@ -725,7 +1204,7 @@ var ImageEditor = ({
725
1204
  children: "\u21AA"
726
1205
  }
727
1206
  ),
728
- /* @__PURE__ */ jsx(
1207
+ /* @__PURE__ */ jsx5(
729
1208
  "button",
730
1209
  {
731
1210
  type: "button",
@@ -734,8 +1213,8 @@ var ImageEditor = ({
734
1213
  children: "Reset"
735
1214
  }
736
1215
  ),
737
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
738
- /* @__PURE__ */ jsx(
1216
+ /* @__PURE__ */ jsx5("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1217
+ /* @__PURE__ */ jsx5(
739
1218
  "button",
740
1219
  {
741
1220
  type: "button",
@@ -745,11 +1224,11 @@ var ImageEditor = ({
745
1224
  children: "-"
746
1225
  }
747
1226
  ),
748
- /* @__PURE__ */ jsxs("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
1227
+ /* @__PURE__ */ jsxs5("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
749
1228
  Math.round(state.zoom * 100),
750
1229
  "%"
751
1230
  ] }),
752
- /* @__PURE__ */ jsx(
1231
+ /* @__PURE__ */ jsx5(
753
1232
  "button",
754
1233
  {
755
1234
  type: "button",
@@ -759,8 +1238,8 @@ var ImageEditor = ({
759
1238
  children: "+"
760
1239
  }
761
1240
  ),
762
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
763
- /* @__PURE__ */ jsx(
1241
+ /* @__PURE__ */ jsx5("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1242
+ /* @__PURE__ */ jsx5(
764
1243
  "button",
765
1244
  {
766
1245
  type: "button",
@@ -771,7 +1250,7 @@ var ImageEditor = ({
771
1250
  children: "Hold Compare"
772
1251
  }
773
1252
  ),
774
- /* @__PURE__ */ jsx(
1253
+ /* @__PURE__ */ jsx5(
775
1254
  "button",
776
1255
  {
777
1256
  type: "button",
@@ -782,13 +1261,13 @@ var ImageEditor = ({
782
1261
  )
783
1262
  ] })
784
1263
  ] }),
785
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
786
- /* @__PURE__ */ jsx(
1264
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
1265
+ /* @__PURE__ */ jsx5(
787
1266
  "div",
788
1267
  {
789
1268
  ref: containerRef,
790
1269
  className: "relative flex-1 h-[360px] md:h-full min-h-[320px] bg-slate-950 overflow-hidden select-none",
791
- children: /* @__PURE__ */ jsx(
1270
+ children: /* @__PURE__ */ jsx5(
792
1271
  "canvas",
793
1272
  {
794
1273
  ref: canvasRef,
@@ -801,11 +1280,11 @@ var ImageEditor = ({
801
1280
  )
802
1281
  }
803
1282
  ),
804
- /* @__PURE__ */ jsxs("div", { className: "w-full md:w-80 bg-slate-900/90 border-t md:border-t-0 md:border-l border-slate-800 p-4 overflow-y-auto h-auto md:h-full md:max-h-[560px]", children: [
805
- activeTab === "crop" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 text-xs", children: [
806
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
807
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
808
- /* @__PURE__ */ jsx(
1283
+ /* @__PURE__ */ jsxs5("div", { className: "w-full md:w-80 bg-slate-900/90 border-t md:border-t-0 md:border-l border-slate-800 p-4 overflow-y-auto h-auto md:h-full md:max-h-[560px]", children: [
1284
+ activeTab === "crop" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4 text-xs", children: [
1285
+ /* @__PURE__ */ jsx5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
1286
+ /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-3 gap-2", children: [
1287
+ /* @__PURE__ */ jsx5(
809
1288
  "button",
810
1289
  {
811
1290
  type: "button",
@@ -817,7 +1296,7 @@ var ImageEditor = ({
817
1296
  children: "Freeform"
818
1297
  }
819
1298
  ),
820
- /* @__PURE__ */ jsx(
1299
+ /* @__PURE__ */ jsx5(
821
1300
  "button",
822
1301
  {
823
1302
  type: "button",
@@ -829,7 +1308,7 @@ var ImageEditor = ({
829
1308
  children: "1:1 Square"
830
1309
  }
831
1310
  ),
832
- /* @__PURE__ */ jsx(
1311
+ /* @__PURE__ */ jsx5(
833
1312
  "button",
834
1313
  {
835
1314
  type: "button",
@@ -841,7 +1320,7 @@ var ImageEditor = ({
841
1320
  children: "4:3 Standard"
842
1321
  }
843
1322
  ),
844
- /* @__PURE__ */ jsx(
1323
+ /* @__PURE__ */ jsx5(
845
1324
  "button",
846
1325
  {
847
1326
  type: "button",
@@ -853,7 +1332,7 @@ var ImageEditor = ({
853
1332
  children: "16:9 Wide"
854
1333
  }
855
1334
  ),
856
- /* @__PURE__ */ jsx(
1335
+ /* @__PURE__ */ jsx5(
857
1336
  "button",
858
1337
  {
859
1338
  type: "button",
@@ -865,7 +1344,7 @@ var ImageEditor = ({
865
1344
  children: "3:2 Photo"
866
1345
  }
867
1346
  ),
868
- /* @__PURE__ */ jsx(
1347
+ /* @__PURE__ */ jsx5(
869
1348
  "button",
870
1349
  {
871
1350
  type: "button",
@@ -878,10 +1357,10 @@ var ImageEditor = ({
878
1357
  }
879
1358
  )
880
1359
  ] }),
881
- /* @__PURE__ */ jsx("div", { className: "h-px bg-slate-800 my-1" }),
882
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
883
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
884
- /* @__PURE__ */ jsx(
1360
+ /* @__PURE__ */ jsx5("div", { className: "h-px bg-slate-800 my-1" }),
1361
+ /* @__PURE__ */ jsx5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
1362
+ /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-2", children: [
1363
+ /* @__PURE__ */ jsx5(
885
1364
  "button",
886
1365
  {
887
1366
  type: "button",
@@ -890,7 +1369,7 @@ var ImageEditor = ({
890
1369
  children: "Rotate -90\xB0"
891
1370
  }
892
1371
  ),
893
- /* @__PURE__ */ jsx(
1372
+ /* @__PURE__ */ jsx5(
894
1373
  "button",
895
1374
  {
896
1375
  type: "button",
@@ -899,7 +1378,7 @@ var ImageEditor = ({
899
1378
  children: "Rotate +90\xB0"
900
1379
  }
901
1380
  ),
902
- /* @__PURE__ */ jsx(
1381
+ /* @__PURE__ */ jsx5(
903
1382
  "button",
904
1383
  {
905
1384
  type: "button",
@@ -908,7 +1387,7 @@ var ImageEditor = ({
908
1387
  children: "Flip Horizontal"
909
1388
  }
910
1389
  ),
911
- /* @__PURE__ */ jsx(
1390
+ /* @__PURE__ */ jsx5(
912
1391
  "button",
913
1392
  {
914
1393
  type: "button",
@@ -919,10 +1398,10 @@ var ImageEditor = ({
919
1398
  )
920
1399
  ] })
921
1400
  ] }),
922
- activeTab === "adjust" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
923
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
924
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
925
- /* @__PURE__ */ jsx(
1401
+ activeTab === "adjust" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-3 text-xs", children: [
1402
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1403
+ /* @__PURE__ */ jsx5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
1404
+ /* @__PURE__ */ jsx5(
926
1405
  "button",
927
1406
  {
928
1407
  type: "button",
@@ -932,136 +1411,49 @@ var ImageEditor = ({
932
1411
  }
933
1412
  )
934
1413
  ] }),
935
- /* @__PURE__ */ jsxs("div", { children: [
936
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
937
- /* @__PURE__ */ jsx("span", { children: "Brightness" }),
938
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.brightness })
939
- ] }),
940
- /* @__PURE__ */ jsx(
941
- "input",
942
- {
943
- type: "range",
944
- min: "-100",
945
- max: "100",
946
- value: state.adjustments.brightness,
947
- onChange: (e) => editor.setAdjustments({ brightness: Number(e.target.value) }),
948
- className: "w-full accent-indigo-500"
949
- }
950
- )
951
- ] }),
952
- /* @__PURE__ */ jsxs("div", { children: [
953
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
954
- /* @__PURE__ */ jsx("span", { children: "Contrast" }),
955
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.contrast })
956
- ] }),
957
- /* @__PURE__ */ jsx(
958
- "input",
959
- {
960
- type: "range",
961
- min: "-100",
962
- max: "100",
963
- value: state.adjustments.contrast,
964
- onChange: (e) => editor.setAdjustments({ contrast: Number(e.target.value) }),
965
- className: "w-full accent-indigo-500"
966
- }
967
- )
968
- ] }),
969
- /* @__PURE__ */ jsxs("div", { children: [
970
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
971
- /* @__PURE__ */ jsx("span", { children: "Saturation" }),
972
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.saturation })
973
- ] }),
974
- /* @__PURE__ */ jsx(
975
- "input",
976
- {
977
- type: "range",
978
- min: "-100",
979
- max: "100",
980
- value: state.adjustments.saturation,
981
- onChange: (e) => editor.setAdjustments({ saturation: Number(e.target.value) }),
982
- className: "w-full accent-indigo-500"
983
- }
984
- )
985
- ] }),
986
- /* @__PURE__ */ jsxs("div", { children: [
987
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
988
- /* @__PURE__ */ jsx("span", { children: "Exposure" }),
989
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.exposure })
990
- ] }),
991
- /* @__PURE__ */ jsx(
992
- "input",
993
- {
994
- type: "range",
995
- min: "-100",
996
- max: "100",
997
- value: state.adjustments.exposure,
998
- onChange: (e) => editor.setAdjustments({ exposure: Number(e.target.value) }),
999
- className: "w-full accent-indigo-500"
1000
- }
1001
- )
1002
- ] }),
1003
- /* @__PURE__ */ jsxs("div", { children: [
1004
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1005
- /* @__PURE__ */ jsx("span", { children: "Warmth / Temperature" }),
1006
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.temperature })
1007
- ] }),
1008
- /* @__PURE__ */ jsx(
1009
- "input",
1010
- {
1011
- type: "range",
1012
- min: "-100",
1013
- max: "100",
1014
- value: state.adjustments.temperature,
1015
- onChange: (e) => editor.setAdjustments({ temperature: Number(e.target.value) }),
1016
- className: "w-full accent-indigo-500"
1017
- }
1018
- )
1019
- ] }),
1020
- /* @__PURE__ */ jsxs("div", { children: [
1021
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1022
- /* @__PURE__ */ jsx("span", { children: "Blur" }),
1023
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
1024
- state.adjustments.blur,
1025
- "px"
1026
- ] })
1027
- ] }),
1028
- /* @__PURE__ */ jsx(
1029
- "input",
1414
+ /* @__PURE__ */ jsx5(Histogram, { histogram: editor.histogram }),
1415
+ IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ jsxs5(React4.Fragment, { children: [
1416
+ /* @__PURE__ */ jsx5(
1417
+ ControlSlider,
1030
1418
  {
1031
- type: "range",
1032
- min: "0",
1033
- max: "30",
1034
- value: state.adjustments.blur,
1035
- onChange: (e) => editor.setAdjustments({ blur: Number(e.target.value) }),
1036
- className: "w-full accent-indigo-500"
1419
+ spec,
1420
+ value: readAdjustmentControls(state.adjustments)[spec.id],
1421
+ disabled: spec.requiresProcess !== void 0 && legacyProcess,
1422
+ onChange: (v) => editor.setAdjustments({ [spec.id]: v })
1037
1423
  }
1038
- )
1039
- ] }),
1040
- /* @__PURE__ */ jsxs("div", { children: [
1041
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1042
- /* @__PURE__ */ jsx("span", { children: "Opacity" }),
1043
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
1044
- Math.round(state.adjustments.opacity * 100),
1045
- "%"
1046
- ] })
1047
- ] }),
1048
- /* @__PURE__ */ jsx(
1049
- "input",
1424
+ ),
1425
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ jsxs5(
1426
+ "div",
1050
1427
  {
1051
- type: "range",
1052
- min: "0",
1053
- max: "1",
1054
- step: "0.05",
1055
- value: state.adjustments.opacity,
1056
- onChange: (e) => editor.setAdjustments({ opacity: Number(e.target.value) }),
1057
- className: "w-full accent-indigo-500"
1428
+ className: "flex items-center justify-between gap-2 rounded border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-200",
1429
+ "data-testid": "image-editor-process-notice",
1430
+ children: [
1431
+ /* @__PURE__ */ jsx5("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
1432
+ /* @__PURE__ */ jsx5(
1433
+ "button",
1434
+ {
1435
+ type: "button",
1436
+ onClick: () => editor.upgradeProcess(),
1437
+ className: "shrink-0 rounded bg-amber-500/20 px-2 py-0.5 hover:bg-amber-500/30",
1438
+ children: "Update"
1439
+ }
1440
+ )
1441
+ ]
1058
1442
  }
1059
1443
  )
1060
- ] })
1444
+ ] }, spec.id)),
1445
+ /* @__PURE__ */ jsx5("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ jsx5(
1446
+ AdjustmentLayersPanel,
1447
+ {
1448
+ controller,
1449
+ layers: state.adjustments.layers,
1450
+ histogram: editor.histogram
1451
+ }
1452
+ ) })
1061
1453
  ] }),
1062
- activeTab === "annotate" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
1063
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
1064
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ jsx(
1454
+ activeTab === "annotate" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-3 text-xs", children: [
1455
+ /* @__PURE__ */ jsx5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
1456
+ /* @__PURE__ */ jsx5("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ jsx5(
1065
1457
  "button",
1066
1458
  {
1067
1459
  type: "button",
@@ -1071,9 +1463,9 @@ var ImageEditor = ({
1071
1463
  },
1072
1464
  t
1073
1465
  )) }),
1074
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
1075
- /* @__PURE__ */ jsx("label", { className: "text-slate-400", children: "Color:" }),
1076
- /* @__PURE__ */ jsx(
1466
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 mt-2", children: [
1467
+ /* @__PURE__ */ jsx5("label", { className: "text-slate-400", children: "Color:" }),
1468
+ /* @__PURE__ */ jsx5(
1077
1469
  "input",
1078
1470
  {
1079
1471
  type: "color",
@@ -1082,15 +1474,15 @@ var ImageEditor = ({
1082
1474
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
1083
1475
  }
1084
1476
  ),
1085
- /* @__PURE__ */ jsxs("div", { className: "flex-1 ml-2", children: [
1086
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
1087
- /* @__PURE__ */ jsx("span", { children: "Width:" }),
1088
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
1477
+ /* @__PURE__ */ jsxs5("div", { className: "flex-1 ml-2", children: [
1478
+ /* @__PURE__ */ jsxs5("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
1479
+ /* @__PURE__ */ jsx5("span", { children: "Width:" }),
1480
+ /* @__PURE__ */ jsxs5("span", { className: "font-mono", children: [
1089
1481
  strokeWidth,
1090
1482
  "px"
1091
1483
  ] })
1092
1484
  ] }),
1093
- /* @__PURE__ */ jsx(
1485
+ /* @__PURE__ */ jsx5(
1094
1486
  "input",
1095
1487
  {
1096
1488
  type: "range",
@@ -1103,10 +1495,10 @@ var ImageEditor = ({
1103
1495
  )
1104
1496
  ] })
1105
1497
  ] }),
1106
- /* @__PURE__ */ jsx("div", { className: "h-px bg-slate-800 my-1" }),
1107
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
1108
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1109
- /* @__PURE__ */ jsx(
1498
+ /* @__PURE__ */ jsx5("div", { className: "h-px bg-slate-800 my-1" }),
1499
+ /* @__PURE__ */ jsx5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
1500
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1501
+ /* @__PURE__ */ jsx5(
1110
1502
  "input",
1111
1503
  {
1112
1504
  type: "text",
@@ -1117,7 +1509,7 @@ var ImageEditor = ({
1117
1509
  className: "flex-1 bg-slate-950 border border-slate-700 rounded-lg px-2.5 py-1.5 text-xs text-slate-100 placeholder-slate-500 focus:outline-none focus:border-indigo-500"
1118
1510
  }
1119
1511
  ),
1120
- /* @__PURE__ */ jsx(
1512
+ /* @__PURE__ */ jsx5(
1121
1513
  "button",
1122
1514
  {
1123
1515
  type: "button",
@@ -1127,14 +1519,14 @@ var ImageEditor = ({
1127
1519
  }
1128
1520
  )
1129
1521
  ] }),
1130
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-slate-400", children: [
1131
- /* @__PURE__ */ jsx("span", { children: "Font Size:" }),
1132
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
1522
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-slate-400", children: [
1523
+ /* @__PURE__ */ jsx5("span", { children: "Font Size:" }),
1524
+ /* @__PURE__ */ jsxs5("span", { className: "font-mono", children: [
1133
1525
  fontSize,
1134
1526
  "px"
1135
1527
  ] })
1136
1528
  ] }),
1137
- /* @__PURE__ */ jsx(
1529
+ /* @__PURE__ */ jsx5(
1138
1530
  "input",
1139
1531
  {
1140
1532
  type: "range",
@@ -1145,14 +1537,14 @@ var ImageEditor = ({
1145
1537
  className: "w-full accent-indigo-500"
1146
1538
  }
1147
1539
  ),
1148
- /* @__PURE__ */ jsx("div", { className: "h-px bg-slate-800 my-1" }),
1149
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1150
- /* @__PURE__ */ jsxs("span", { className: "text-slate-400", children: [
1540
+ /* @__PURE__ */ jsx5("div", { className: "h-px bg-slate-800 my-1" }),
1541
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1542
+ /* @__PURE__ */ jsxs5("span", { className: "text-slate-400", children: [
1151
1543
  "Layers (",
1152
1544
  state.annotations.length,
1153
1545
  ")"
1154
1546
  ] }),
1155
- state.selectedAnnotationId && /* @__PURE__ */ jsx(
1547
+ state.selectedAnnotationId && /* @__PURE__ */ jsx5(
1156
1548
  "button",
1157
1549
  {
1158
1550
  type: "button",
@@ -1161,7 +1553,7 @@ var ImageEditor = ({
1161
1553
  children: "Delete Selected"
1162
1554
  }
1163
1555
  ),
1164
- state.annotations.length > 0 && /* @__PURE__ */ jsx(
1556
+ state.annotations.length > 0 && /* @__PURE__ */ jsx5(
1165
1557
  "button",
1166
1558
  {
1167
1559
  type: "button",
@@ -1172,10 +1564,10 @@ var ImageEditor = ({
1172
1564
  )
1173
1565
  ] })
1174
1566
  ] }),
1175
- activeTab === "depth" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
1176
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1177
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
1178
- /* @__PURE__ */ jsx(
1567
+ activeTab === "depth" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-3 text-xs", children: [
1568
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1569
+ /* @__PURE__ */ jsx5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
1570
+ /* @__PURE__ */ jsx5(
1179
1571
  "button",
1180
1572
  {
1181
1573
  type: "button",
@@ -1185,8 +1577,8 @@ var ImageEditor = ({
1185
1577
  }
1186
1578
  )
1187
1579
  ] }),
1188
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
1189
- /* @__PURE__ */ jsx(
1580
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
1581
+ /* @__PURE__ */ jsx5(
1190
1582
  "input",
1191
1583
  {
1192
1584
  type: "checkbox",
@@ -1195,12 +1587,12 @@ var ImageEditor = ({
1195
1587
  className: "rounded accent-indigo-500"
1196
1588
  }
1197
1589
  ),
1198
- /* @__PURE__ */ jsx("span", { children: "Enable Depth / Alpha Mask" })
1590
+ /* @__PURE__ */ jsx5("span", { children: "Enable Depth / Alpha Mask" })
1199
1591
  ] }),
1200
- state.depthMask.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
1201
- /* @__PURE__ */ jsxs("div", { children: [
1202
- /* @__PURE__ */ jsx("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
1203
- /* @__PURE__ */ jsx(
1592
+ state.depthMask.enabled && /* @__PURE__ */ jsxs5(Fragment, { children: [
1593
+ /* @__PURE__ */ jsxs5("div", { children: [
1594
+ /* @__PURE__ */ jsx5("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
1595
+ /* @__PURE__ */ jsx5(
1204
1596
  "input",
1205
1597
  {
1206
1598
  type: "text",
@@ -1211,8 +1603,8 @@ var ImageEditor = ({
1211
1603
  }
1212
1604
  )
1213
1605
  ] }),
1214
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1215
- /* @__PURE__ */ jsx(
1606
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1607
+ /* @__PURE__ */ jsx5(
1216
1608
  "button",
1217
1609
  {
1218
1610
  type: "button",
@@ -1221,7 +1613,7 @@ var ImageEditor = ({
1221
1613
  children: "Threshold Mode"
1222
1614
  }
1223
1615
  ),
1224
- /* @__PURE__ */ jsx(
1616
+ /* @__PURE__ */ jsx5(
1225
1617
  "button",
1226
1618
  {
1227
1619
  type: "button",
@@ -1231,12 +1623,12 @@ var ImageEditor = ({
1231
1623
  }
1232
1624
  )
1233
1625
  ] }),
1234
- /* @__PURE__ */ jsxs("div", { children: [
1235
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1236
- /* @__PURE__ */ jsx("span", { children: "Depth Start (Lower):" }),
1237
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
1626
+ /* @__PURE__ */ jsxs5("div", { children: [
1627
+ /* @__PURE__ */ jsxs5("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1628
+ /* @__PURE__ */ jsx5("span", { children: "Depth Start (Lower):" }),
1629
+ /* @__PURE__ */ jsx5("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
1238
1630
  ] }),
1239
- /* @__PURE__ */ jsx(
1631
+ /* @__PURE__ */ jsx5(
1240
1632
  "input",
1241
1633
  {
1242
1634
  type: "range",
@@ -1250,12 +1642,12 @@ var ImageEditor = ({
1250
1642
  }
1251
1643
  )
1252
1644
  ] }),
1253
- /* @__PURE__ */ jsxs("div", { children: [
1254
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1255
- /* @__PURE__ */ jsx("span", { children: "Depth Stop (Upper):" }),
1256
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
1645
+ /* @__PURE__ */ jsxs5("div", { children: [
1646
+ /* @__PURE__ */ jsxs5("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1647
+ /* @__PURE__ */ jsx5("span", { children: "Depth Stop (Upper):" }),
1648
+ /* @__PURE__ */ jsx5("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
1257
1649
  ] }),
1258
- /* @__PURE__ */ jsx(
1650
+ /* @__PURE__ */ jsx5(
1259
1651
  "input",
1260
1652
  {
1261
1653
  type: "range",
@@ -1269,12 +1661,12 @@ var ImageEditor = ({
1269
1661
  }
1270
1662
  )
1271
1663
  ] }),
1272
- /* @__PURE__ */ jsxs("div", { children: [
1273
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1274
- /* @__PURE__ */ jsx("span", { children: "Softness:" }),
1275
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.depthMask.softness })
1664
+ /* @__PURE__ */ jsxs5("div", { children: [
1665
+ /* @__PURE__ */ jsxs5("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1666
+ /* @__PURE__ */ jsx5("span", { children: "Softness:" }),
1667
+ /* @__PURE__ */ jsx5("span", { className: "font-mono", children: state.depthMask.softness })
1276
1668
  ] }),
1277
- /* @__PURE__ */ jsx(
1669
+ /* @__PURE__ */ jsx5(
1278
1670
  "input",
1279
1671
  {
1280
1672
  type: "range",
@@ -1286,8 +1678,8 @@ var ImageEditor = ({
1286
1678
  }
1287
1679
  )
1288
1680
  ] }),
1289
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
1290
- /* @__PURE__ */ jsx(
1681
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
1682
+ /* @__PURE__ */ jsx5(
1291
1683
  "input",
1292
1684
  {
1293
1685
  type: "checkbox",
@@ -1296,52 +1688,52 @@ var ImageEditor = ({
1296
1688
  className: "rounded accent-indigo-500"
1297
1689
  }
1298
1690
  ),
1299
- /* @__PURE__ */ jsx("span", { children: "Invert Mask" })
1691
+ /* @__PURE__ */ jsx5("span", { children: "Invert Mask" })
1300
1692
  ] })
1301
1693
  ] })
1302
1694
  ] }),
1303
- activeTab === "ai" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 text-xs", children: [
1304
- /* @__PURE__ */ jsxs("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
1305
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1306
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
1307
- /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
1695
+ activeTab === "ai" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4 text-xs", children: [
1696
+ /* @__PURE__ */ jsxs5("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
1697
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1698
+ /* @__PURE__ */ jsxs5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
1699
+ /* @__PURE__ */ jsx5("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx5("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
1308
1700
  "Background Removal"
1309
1701
  ] }),
1310
- isRemovingBg && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
1702
+ isRemovingBg && /* @__PURE__ */ jsx5("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
1311
1703
  ] }),
1312
- /* @__PURE__ */ jsx("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
1313
- /* @__PURE__ */ jsx(
1704
+ /* @__PURE__ */ jsx5("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
1705
+ /* @__PURE__ */ jsx5(
1314
1706
  "button",
1315
1707
  {
1316
1708
  type: "button",
1317
1709
  onClick: handleRemoveBackground,
1318
1710
  disabled: isRemovingBg || isInpainting,
1319
1711
  className: "mt-1 px-3 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white font-medium shadow transition-colors flex items-center justify-center gap-2",
1320
- children: isRemovingBg ? /* @__PURE__ */ jsxs(Fragment, { children: [
1321
- /* @__PURE__ */ jsx("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
1712
+ children: isRemovingBg ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1713
+ /* @__PURE__ */ jsx5("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
1322
1714
  "Removing Background..."
1323
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1324
- /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1715
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1716
+ /* @__PURE__ */ jsx5("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx5("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1325
1717
  "Remove Background"
1326
1718
  ] })
1327
1719
  }
1328
1720
  )
1329
1721
  ] }),
1330
- /* @__PURE__ */ jsxs("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
1331
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1332
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
1333
- /* @__PURE__ */ jsxs("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1334
- /* @__PURE__ */ jsx("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
1335
- /* @__PURE__ */ jsx("path", { d: "m14 7 3 3" })
1722
+ /* @__PURE__ */ jsxs5("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
1723
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1724
+ /* @__PURE__ */ jsxs5("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
1725
+ /* @__PURE__ */ jsxs5("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1726
+ /* @__PURE__ */ jsx5("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
1727
+ /* @__PURE__ */ jsx5("path", { d: "m14 7 3 3" })
1336
1728
  ] }),
1337
1729
  "Generative Inpainting"
1338
1730
  ] }),
1339
- isInpainting && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
1731
+ isInpainting && /* @__PURE__ */ jsx5("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
1340
1732
  ] }),
1341
- /* @__PURE__ */ jsx("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
1342
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1343
- /* @__PURE__ */ jsx("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
1344
- /* @__PURE__ */ jsx(
1733
+ /* @__PURE__ */ jsx5("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
1734
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
1735
+ /* @__PURE__ */ jsx5("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
1736
+ /* @__PURE__ */ jsx5(
1345
1737
  "textarea",
1346
1738
  {
1347
1739
  id: "ai-inpaint-prompt-react",
@@ -1353,10 +1745,10 @@ var ImageEditor = ({
1353
1745
  }
1354
1746
  )
1355
1747
  ] }),
1356
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1357
- /* @__PURE__ */ jsx("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
1358
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [
1359
- /* @__PURE__ */ jsxs(
1748
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
1749
+ /* @__PURE__ */ jsx5("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
1750
+ /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-1.5", children: [
1751
+ /* @__PURE__ */ jsxs5(
1360
1752
  "button",
1361
1753
  {
1362
1754
  type: "button",
@@ -1369,7 +1761,7 @@ var ImageEditor = ({
1369
1761
  ]
1370
1762
  }
1371
1763
  ),
1372
- /* @__PURE__ */ jsxs(
1764
+ /* @__PURE__ */ jsxs5(
1373
1765
  "button",
1374
1766
  {
1375
1767
  type: "button",
@@ -1384,25 +1776,25 @@ var ImageEditor = ({
1384
1776
  )
1385
1777
  ] })
1386
1778
  ] }),
1387
- /* @__PURE__ */ jsx(
1779
+ /* @__PURE__ */ jsx5(
1388
1780
  "button",
1389
1781
  {
1390
1782
  type: "button",
1391
1783
  onClick: handleInpaint,
1392
1784
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
1393
1785
  className: "mt-1 px-3 py-2 rounded-lg bg-purple-600 hover:bg-purple-500 disabled:opacity-50 text-white font-medium shadow transition-colors flex items-center justify-center gap-2",
1394
- children: isInpainting ? /* @__PURE__ */ jsxs(Fragment, { children: [
1395
- /* @__PURE__ */ jsx("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
1786
+ children: isInpainting ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1787
+ /* @__PURE__ */ jsx5("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
1396
1788
  "Generating Inpaint..."
1397
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1398
- /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1789
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1790
+ /* @__PURE__ */ jsx5("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx5("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1399
1791
  "Apply Inpainting"
1400
1792
  ] })
1401
1793
  }
1402
1794
  )
1403
1795
  ] }),
1404
- aiError && /* @__PURE__ */ jsx("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
1405
- aiSuccess && /* @__PURE__ */ jsx("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
1796
+ aiError && /* @__PURE__ */ jsx5("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
1797
+ aiSuccess && /* @__PURE__ */ jsx5("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
1406
1798
  ] })
1407
1799
  ] })
1408
1800
  ] })
@@ -1413,6 +1805,11 @@ var ImageEditor = ({
1413
1805
 
1414
1806
  export {
1415
1807
  useImageEditor,
1808
+ ControlSlider,
1809
+ Histogram,
1810
+ useImageHistogram,
1811
+ CurvesEditor,
1812
+ AdjustmentLayersPanel,
1416
1813
  ImageEditor
1417
1814
  };
1418
- //# sourceMappingURL=chunk-TL27COKM.js.map
1815
+ //# sourceMappingURL=chunk-XTFR3IMK.js.map