@pixerate/editor-react 0.15.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 (46) hide show
  1. package/README.md +4 -0
  2. package/dist/canvas/index.cjs +219 -118
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +59 -3
  5. package/dist/canvas/index.d.ts +59 -3
  6. package/dist/canvas/index.js +103 -0
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-7SPRQZ6H.js +216 -0
  9. package/dist/chunk-7SPRQZ6H.js.map +1 -0
  10. package/dist/{chunk-QVQYYHJ5.js → chunk-FYIPXHAE.js} +21 -13
  11. package/dist/chunk-FYIPXHAE.js.map +1 -0
  12. package/dist/{chunk-LPFZMZ3L.js → chunk-XTFR3IMK.js} +676 -322
  13. package/dist/chunk-XTFR3IMK.js.map +1 -0
  14. package/dist/chunk-XWFU2CSG.js +167 -0
  15. package/dist/chunk-XWFU2CSG.js.map +1 -0
  16. package/dist/generation/index.cjs +235 -0
  17. package/dist/generation/index.cjs.map +1 -0
  18. package/dist/generation/index.d.cts +60 -0
  19. package/dist/generation/index.d.ts +60 -0
  20. package/dist/generation/index.js +13 -0
  21. package/dist/generation/index.js.map +1 -0
  22. package/dist/image-editor/index.cjs +684 -336
  23. package/dist/image-editor/index.cjs.map +1 -1
  24. package/dist/image-editor/index.d.cts +60 -2
  25. package/dist/image-editor/index.d.ts +60 -2
  26. package/dist/image-editor/index.js +13 -3
  27. package/dist/index.cjs +1881 -648
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +143 -4
  30. package/dist/index.d.ts +143 -4
  31. package/dist/index.js +626 -73
  32. package/dist/index.js.map +1 -1
  33. package/dist/persistence/index.cjs +191 -0
  34. package/dist/persistence/index.cjs.map +1 -0
  35. package/dist/persistence/index.d.cts +77 -0
  36. package/dist/persistence/index.d.ts +77 -0
  37. package/dist/persistence/index.js +9 -0
  38. package/dist/persistence/index.js.map +1 -0
  39. package/dist/spreadsheet/index.cjs +17 -10
  40. package/dist/spreadsheet/index.cjs.map +1 -1
  41. package/dist/spreadsheet/index.d.cts +2 -0
  42. package/dist/spreadsheet/index.d.ts +2 -0
  43. package/dist/spreadsheet/index.js +1 -1
  44. package/package.json +18 -2
  45. package/dist/chunk-LPFZMZ3L.js.map +0 -1
  46. package/dist/chunk-QVQYYHJ5.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);
@@ -43,6 +45,20 @@ function useImageEditor(options = {}) {
43
45
  );
44
46
  const resetAdjustments = useCallback(() => controller.resetAdjustments(), [controller]);
45
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
+ );
46
62
  const setDepthMask = useCallback((partial) => controller.setDepthMask(partial), [controller]);
47
63
  const resetDepthMask = useCallback(() => controller.resetDepthMask(), [controller]);
48
64
  const addAnnotation = useCallback((ann) => controller.addAnnotation(ann), [controller]);
@@ -104,6 +120,12 @@ function useImageEditor(options = {}) {
104
120
  setAdjustments,
105
121
  resetAdjustments,
106
122
  upgradeProcess,
123
+ addAdjustmentLayer,
124
+ updateAdjustmentLayer,
125
+ removeAdjustmentLayer,
126
+ moveAdjustmentLayer,
127
+ toggleAdjustmentLayer,
128
+ histogram,
107
129
  setDepthMask,
108
130
  resetDepthMask,
109
131
  addAnnotation,
@@ -132,12 +154,12 @@ function useImageEditor(options = {}) {
132
154
  applyBackgroundRemovedImage
133
155
  }),
134
156
  // eslint-disable-next-line react-hooks/exhaustive-deps
135
- [controller, state, historyTicks]
157
+ [controller, state, historyTicks, histogram]
136
158
  );
137
159
  }
138
160
 
139
161
  // src/image-editor/ImageEditor.tsx
140
- 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";
141
163
  import {
142
164
  canvasToImagePoint,
143
165
  hitTestCrop,
@@ -145,9 +167,461 @@ import {
145
167
  calculateCropDrag,
146
168
  hitTestAnnotation,
147
169
  normalizeProcessVersion,
148
- LATEST_PROCESS_VERSION
170
+ LATEST_PROCESS_VERSION,
171
+ IMAGE_ADJUSTMENT_CONTROLS,
172
+ readAdjustmentControls
149
173
  } from "@pixerate/editor/image-editor";
150
- 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";
151
625
  var ImageEditor = ({
152
626
  editor: externalEditor,
153
627
  src,
@@ -163,16 +637,16 @@ var ImageEditor = ({
163
637
  const editor = externalEditor || internalEditor;
164
638
  const { state, controller } = editor;
165
639
  const legacyProcess = normalizeProcessVersion(state.adjustments.process) < LATEST_PROCESS_VERSION;
166
- const canvasRef = useRef2(null);
167
- const containerRef = useRef2(null);
168
- const [activeTab, setActiveTab] = useState2("crop");
169
- const [activeCropRatio, setActiveCropRatio] = useState2(null);
170
- const [inpaintPrompt, setInpaintPrompt] = useState2("");
171
- const [inpaintMaskMode, setInpaintMaskMode] = useState2("annotations");
172
- const [isRemovingBg, setIsRemovingBg] = useState2(false);
173
- const [isInpainting, setIsInpainting] = useState2(false);
174
- const [aiError, setAiError] = useState2(null);
175
- const [aiSuccess, setAiSuccess] = useState2(null);
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);
176
650
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
177
651
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
178
652
  const handleRemoveBackground = async () => {
@@ -234,17 +708,17 @@ var ImageEditor = ({
234
708
  setIsInpainting(false);
235
709
  }
236
710
  };
237
- const [activeColor, setActiveColor] = useState2("#ef4444");
238
- const [strokeWidth, setStrokeWidth] = useState2(3);
239
- const [fontSize, setFontSize] = useState2(24);
240
- const [textInput, setTextInput] = useState2("");
241
- const draftAnnotationRef = useRef2(null);
242
- const draftCropRef = useRef2(null);
243
- const cropDragInfoRef = useRef2(null);
244
- const annotationDragInfoRef = useRef2(null);
245
- const isDrawingRef = useRef2(false);
246
- const currentPathRef = useRef2([]);
247
- 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);
248
722
  const renderCanvas = useCallback2(
249
723
  async (draftAnnotation, draftCrop) => {
250
724
  const canvas = canvasRef.current;
@@ -264,7 +738,7 @@ var ImageEditor = ({
264
738
  },
265
739
  [controller, activeTab]
266
740
  );
267
- useEffect2(() => {
741
+ useEffect4(() => {
268
742
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
269
743
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
270
744
  const animId = raf(() => {
@@ -272,12 +746,12 @@ var ImageEditor = ({
272
746
  });
273
747
  return () => caf(animId);
274
748
  }, [renderCanvas, state]);
275
- useEffect2(() => {
749
+ useEffect4(() => {
276
750
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
277
751
  editor.setTool("crop");
278
752
  }
279
753
  }, [activeTab, editor]);
280
- useEffect2(() => {
754
+ useEffect4(() => {
281
755
  const container = containerRef.current;
282
756
  if (!container) return;
283
757
  if (typeof ResizeObserver !== "undefined") {
@@ -635,15 +1109,15 @@ var ImageEditor = ({
635
1109
  link.click();
636
1110
  }
637
1111
  };
638
- return /* @__PURE__ */ jsxs(
1112
+ return /* @__PURE__ */ jsxs5(
639
1113
  "div",
640
1114
  {
641
1115
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
642
1116
  style,
643
1117
  children: [
644
- /* @__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: [
645
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
646
- /* @__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(
647
1121
  "button",
648
1122
  {
649
1123
  type: "button",
@@ -655,7 +1129,7 @@ var ImageEditor = ({
655
1129
  children: "Transform & Crop"
656
1130
  }
657
1131
  ),
658
- /* @__PURE__ */ jsx(
1132
+ /* @__PURE__ */ jsx5(
659
1133
  "button",
660
1134
  {
661
1135
  type: "button",
@@ -667,7 +1141,7 @@ var ImageEditor = ({
667
1141
  children: "Adjustments"
668
1142
  }
669
1143
  ),
670
- /* @__PURE__ */ jsx(
1144
+ /* @__PURE__ */ jsx5(
671
1145
  "button",
672
1146
  {
673
1147
  type: "button",
@@ -679,7 +1153,7 @@ var ImageEditor = ({
679
1153
  children: "Annotations"
680
1154
  }
681
1155
  ),
682
- /* @__PURE__ */ jsx(
1156
+ /* @__PURE__ */ jsx5(
683
1157
  "button",
684
1158
  {
685
1159
  type: "button",
@@ -691,7 +1165,7 @@ var ImageEditor = ({
691
1165
  children: "Depth Mask"
692
1166
  }
693
1167
  ),
694
- /* @__PURE__ */ jsxs(
1168
+ /* @__PURE__ */ jsxs5(
695
1169
  "button",
696
1170
  {
697
1171
  type: "button",
@@ -701,14 +1175,14 @@ var ImageEditor = ({
701
1175
  },
702
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"}`,
703
1177
  children: [
704
- /* @__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" }) }),
705
1179
  "AI Tools"
706
1180
  ]
707
1181
  }
708
1182
  )
709
1183
  ] }),
710
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
711
- /* @__PURE__ */ jsx(
1184
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-xs", children: [
1185
+ /* @__PURE__ */ jsx5(
712
1186
  "button",
713
1187
  {
714
1188
  type: "button",
@@ -719,7 +1193,7 @@ var ImageEditor = ({
719
1193
  children: "\u21A9"
720
1194
  }
721
1195
  ),
722
- /* @__PURE__ */ jsx(
1196
+ /* @__PURE__ */ jsx5(
723
1197
  "button",
724
1198
  {
725
1199
  type: "button",
@@ -730,7 +1204,7 @@ var ImageEditor = ({
730
1204
  children: "\u21AA"
731
1205
  }
732
1206
  ),
733
- /* @__PURE__ */ jsx(
1207
+ /* @__PURE__ */ jsx5(
734
1208
  "button",
735
1209
  {
736
1210
  type: "button",
@@ -739,8 +1213,8 @@ var ImageEditor = ({
739
1213
  children: "Reset"
740
1214
  }
741
1215
  ),
742
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
743
- /* @__PURE__ */ jsx(
1216
+ /* @__PURE__ */ jsx5("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1217
+ /* @__PURE__ */ jsx5(
744
1218
  "button",
745
1219
  {
746
1220
  type: "button",
@@ -750,11 +1224,11 @@ var ImageEditor = ({
750
1224
  children: "-"
751
1225
  }
752
1226
  ),
753
- /* @__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: [
754
1228
  Math.round(state.zoom * 100),
755
1229
  "%"
756
1230
  ] }),
757
- /* @__PURE__ */ jsx(
1231
+ /* @__PURE__ */ jsx5(
758
1232
  "button",
759
1233
  {
760
1234
  type: "button",
@@ -764,8 +1238,8 @@ var ImageEditor = ({
764
1238
  children: "+"
765
1239
  }
766
1240
  ),
767
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
768
- /* @__PURE__ */ jsx(
1241
+ /* @__PURE__ */ jsx5("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1242
+ /* @__PURE__ */ jsx5(
769
1243
  "button",
770
1244
  {
771
1245
  type: "button",
@@ -776,7 +1250,7 @@ var ImageEditor = ({
776
1250
  children: "Hold Compare"
777
1251
  }
778
1252
  ),
779
- /* @__PURE__ */ jsx(
1253
+ /* @__PURE__ */ jsx5(
780
1254
  "button",
781
1255
  {
782
1256
  type: "button",
@@ -787,13 +1261,13 @@ var ImageEditor = ({
787
1261
  )
788
1262
  ] })
789
1263
  ] }),
790
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
791
- /* @__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(
792
1266
  "div",
793
1267
  {
794
1268
  ref: containerRef,
795
1269
  className: "relative flex-1 h-[360px] md:h-full min-h-[320px] bg-slate-950 overflow-hidden select-none",
796
- children: /* @__PURE__ */ jsx(
1270
+ children: /* @__PURE__ */ jsx5(
797
1271
  "canvas",
798
1272
  {
799
1273
  ref: canvasRef,
@@ -806,11 +1280,11 @@ var ImageEditor = ({
806
1280
  )
807
1281
  }
808
1282
  ),
809
- /* @__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: [
810
- activeTab === "crop" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 text-xs", children: [
811
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
812
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
813
- /* @__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(
814
1288
  "button",
815
1289
  {
816
1290
  type: "button",
@@ -822,7 +1296,7 @@ var ImageEditor = ({
822
1296
  children: "Freeform"
823
1297
  }
824
1298
  ),
825
- /* @__PURE__ */ jsx(
1299
+ /* @__PURE__ */ jsx5(
826
1300
  "button",
827
1301
  {
828
1302
  type: "button",
@@ -834,7 +1308,7 @@ var ImageEditor = ({
834
1308
  children: "1:1 Square"
835
1309
  }
836
1310
  ),
837
- /* @__PURE__ */ jsx(
1311
+ /* @__PURE__ */ jsx5(
838
1312
  "button",
839
1313
  {
840
1314
  type: "button",
@@ -846,7 +1320,7 @@ var ImageEditor = ({
846
1320
  children: "4:3 Standard"
847
1321
  }
848
1322
  ),
849
- /* @__PURE__ */ jsx(
1323
+ /* @__PURE__ */ jsx5(
850
1324
  "button",
851
1325
  {
852
1326
  type: "button",
@@ -858,7 +1332,7 @@ var ImageEditor = ({
858
1332
  children: "16:9 Wide"
859
1333
  }
860
1334
  ),
861
- /* @__PURE__ */ jsx(
1335
+ /* @__PURE__ */ jsx5(
862
1336
  "button",
863
1337
  {
864
1338
  type: "button",
@@ -870,7 +1344,7 @@ var ImageEditor = ({
870
1344
  children: "3:2 Photo"
871
1345
  }
872
1346
  ),
873
- /* @__PURE__ */ jsx(
1347
+ /* @__PURE__ */ jsx5(
874
1348
  "button",
875
1349
  {
876
1350
  type: "button",
@@ -883,10 +1357,10 @@ var ImageEditor = ({
883
1357
  }
884
1358
  )
885
1359
  ] }),
886
- /* @__PURE__ */ jsx("div", { className: "h-px bg-slate-800 my-1" }),
887
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
888
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
889
- /* @__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(
890
1364
  "button",
891
1365
  {
892
1366
  type: "button",
@@ -895,7 +1369,7 @@ var ImageEditor = ({
895
1369
  children: "Rotate -90\xB0"
896
1370
  }
897
1371
  ),
898
- /* @__PURE__ */ jsx(
1372
+ /* @__PURE__ */ jsx5(
899
1373
  "button",
900
1374
  {
901
1375
  type: "button",
@@ -904,7 +1378,7 @@ var ImageEditor = ({
904
1378
  children: "Rotate +90\xB0"
905
1379
  }
906
1380
  ),
907
- /* @__PURE__ */ jsx(
1381
+ /* @__PURE__ */ jsx5(
908
1382
  "button",
909
1383
  {
910
1384
  type: "button",
@@ -913,7 +1387,7 @@ var ImageEditor = ({
913
1387
  children: "Flip Horizontal"
914
1388
  }
915
1389
  ),
916
- /* @__PURE__ */ jsx(
1390
+ /* @__PURE__ */ jsx5(
917
1391
  "button",
918
1392
  {
919
1393
  type: "button",
@@ -924,10 +1398,10 @@ var ImageEditor = ({
924
1398
  )
925
1399
  ] })
926
1400
  ] }),
927
- activeTab === "adjust" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
928
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
929
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
930
- /* @__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(
931
1405
  "button",
932
1406
  {
933
1407
  type: "button",
@@ -937,174 +1411,49 @@ var ImageEditor = ({
937
1411
  }
938
1412
  )
939
1413
  ] }),
940
- /* @__PURE__ */ jsxs("div", { children: [
941
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
942
- /* @__PURE__ */ jsx("span", { children: "Brightness" }),
943
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.brightness })
944
- ] }),
945
- /* @__PURE__ */ jsx(
946
- "input",
947
- {
948
- type: "range",
949
- min: "-100",
950
- max: "100",
951
- value: state.adjustments.brightness,
952
- onChange: (e) => editor.setAdjustments({ brightness: Number(e.target.value) }),
953
- className: "w-full accent-indigo-500"
954
- }
955
- )
956
- ] }),
957
- /* @__PURE__ */ jsxs("div", { children: [
958
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
959
- /* @__PURE__ */ jsx("span", { children: "Contrast" }),
960
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.contrast })
961
- ] }),
962
- /* @__PURE__ */ jsx(
963
- "input",
964
- {
965
- type: "range",
966
- min: "-100",
967
- max: "100",
968
- value: state.adjustments.contrast,
969
- onChange: (e) => editor.setAdjustments({ contrast: Number(e.target.value) }),
970
- className: "w-full accent-indigo-500"
971
- }
972
- )
973
- ] }),
974
- /* @__PURE__ */ jsxs("div", { children: [
975
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
976
- /* @__PURE__ */ jsx("span", { children: "Saturation" }),
977
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.saturation })
978
- ] }),
979
- /* @__PURE__ */ jsx(
980
- "input",
1414
+ /* @__PURE__ */ jsx5(Histogram, { histogram: editor.histogram }),
1415
+ IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ jsxs5(React4.Fragment, { children: [
1416
+ /* @__PURE__ */ jsx5(
1417
+ ControlSlider,
981
1418
  {
982
- type: "range",
983
- min: "-100",
984
- max: "100",
985
- value: state.adjustments.saturation,
986
- onChange: (e) => editor.setAdjustments({ saturation: Number(e.target.value) }),
987
- 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 })
988
1423
  }
989
- )
990
- ] }),
991
- /* @__PURE__ */ jsxs("div", { children: [
992
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
993
- /* @__PURE__ */ jsx("span", { children: "Exposure" }),
994
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.exposure })
995
- ] }),
996
- /* @__PURE__ */ jsx(
997
- "input",
998
- {
999
- type: "range",
1000
- min: "-100",
1001
- max: "100",
1002
- value: state.adjustments.exposure,
1003
- onChange: (e) => editor.setAdjustments({ exposure: Number(e.target.value) }),
1004
- className: "w-full accent-indigo-500"
1005
- }
1006
- )
1007
- ] }),
1008
- /* @__PURE__ */ jsxs("div", { children: [
1009
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1010
- /* @__PURE__ */ jsx("span", { children: "Warmth / Temperature" }),
1011
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.temperature })
1012
- ] }),
1013
- /* @__PURE__ */ jsx(
1014
- "input",
1015
- {
1016
- type: "range",
1017
- min: "-100",
1018
- max: "100",
1019
- value: state.adjustments.temperature,
1020
- onChange: (e) => editor.setAdjustments({ temperature: Number(e.target.value) }),
1021
- className: "w-full accent-indigo-500"
1022
- }
1023
- )
1024
- ] }),
1025
- /* @__PURE__ */ jsxs("div", { children: [
1026
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1027
- /* @__PURE__ */ jsx("span", { children: "Tint" }),
1028
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: state.adjustments.tint ?? 0 })
1029
- ] }),
1030
- /* @__PURE__ */ jsx(
1031
- "input",
1424
+ ),
1425
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ jsxs5(
1426
+ "div",
1032
1427
  {
1033
- type: "range",
1034
- min: "-100",
1035
- max: "100",
1036
- "aria-label": "Tint",
1037
- disabled: legacyProcess,
1038
- value: state.adjustments.tint ?? 0,
1039
- onChange: (e) => editor.setAdjustments({ tint: Number(e.target.value) }),
1040
- className: "w-full accent-indigo-500 disabled:opacity-40"
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
+ ]
1041
1442
  }
1042
1443
  )
1043
- ] }),
1044
- legacyProcess && /* @__PURE__ */ jsxs(
1045
- "div",
1444
+ ] }, spec.id)),
1445
+ /* @__PURE__ */ jsx5("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ jsx5(
1446
+ AdjustmentLayersPanel,
1046
1447
  {
1047
- 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",
1048
- "data-testid": "image-editor-process-notice",
1049
- children: [
1050
- /* @__PURE__ */ jsx("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
1051
- /* @__PURE__ */ jsx(
1052
- "button",
1053
- {
1054
- type: "button",
1055
- onClick: () => editor.upgradeProcess(),
1056
- className: "shrink-0 rounded bg-amber-500/20 px-2 py-0.5 hover:bg-amber-500/30",
1057
- children: "Update"
1058
- }
1059
- )
1060
- ]
1448
+ controller,
1449
+ layers: state.adjustments.layers,
1450
+ histogram: editor.histogram
1061
1451
  }
1062
- ),
1063
- /* @__PURE__ */ jsxs("div", { children: [
1064
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1065
- /* @__PURE__ */ jsx("span", { children: "Blur" }),
1066
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
1067
- state.adjustments.blur,
1068
- "px"
1069
- ] })
1070
- ] }),
1071
- /* @__PURE__ */ jsx(
1072
- "input",
1073
- {
1074
- type: "range",
1075
- min: "0",
1076
- max: "30",
1077
- value: state.adjustments.blur,
1078
- onChange: (e) => editor.setAdjustments({ blur: Number(e.target.value) }),
1079
- className: "w-full accent-indigo-500"
1080
- }
1081
- )
1082
- ] }),
1083
- /* @__PURE__ */ jsxs("div", { children: [
1084
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1085
- /* @__PURE__ */ jsx("span", { children: "Opacity" }),
1086
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
1087
- Math.round(state.adjustments.opacity * 100),
1088
- "%"
1089
- ] })
1090
- ] }),
1091
- /* @__PURE__ */ jsx(
1092
- "input",
1093
- {
1094
- type: "range",
1095
- min: "0",
1096
- max: "1",
1097
- step: "0.05",
1098
- value: state.adjustments.opacity,
1099
- onChange: (e) => editor.setAdjustments({ opacity: Number(e.target.value) }),
1100
- className: "w-full accent-indigo-500"
1101
- }
1102
- )
1103
- ] })
1452
+ ) })
1104
1453
  ] }),
1105
- activeTab === "annotate" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
1106
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
1107
- /* @__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(
1108
1457
  "button",
1109
1458
  {
1110
1459
  type: "button",
@@ -1114,9 +1463,9 @@ var ImageEditor = ({
1114
1463
  },
1115
1464
  t
1116
1465
  )) }),
1117
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
1118
- /* @__PURE__ */ jsx("label", { className: "text-slate-400", children: "Color:" }),
1119
- /* @__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(
1120
1469
  "input",
1121
1470
  {
1122
1471
  type: "color",
@@ -1125,15 +1474,15 @@ var ImageEditor = ({
1125
1474
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
1126
1475
  }
1127
1476
  ),
1128
- /* @__PURE__ */ jsxs("div", { className: "flex-1 ml-2", children: [
1129
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
1130
- /* @__PURE__ */ jsx("span", { children: "Width:" }),
1131
- /* @__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: [
1132
1481
  strokeWidth,
1133
1482
  "px"
1134
1483
  ] })
1135
1484
  ] }),
1136
- /* @__PURE__ */ jsx(
1485
+ /* @__PURE__ */ jsx5(
1137
1486
  "input",
1138
1487
  {
1139
1488
  type: "range",
@@ -1146,10 +1495,10 @@ var ImageEditor = ({
1146
1495
  )
1147
1496
  ] })
1148
1497
  ] }),
1149
- /* @__PURE__ */ jsx("div", { className: "h-px bg-slate-800 my-1" }),
1150
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
1151
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1152
- /* @__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(
1153
1502
  "input",
1154
1503
  {
1155
1504
  type: "text",
@@ -1160,7 +1509,7 @@ var ImageEditor = ({
1160
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"
1161
1510
  }
1162
1511
  ),
1163
- /* @__PURE__ */ jsx(
1512
+ /* @__PURE__ */ jsx5(
1164
1513
  "button",
1165
1514
  {
1166
1515
  type: "button",
@@ -1170,14 +1519,14 @@ var ImageEditor = ({
1170
1519
  }
1171
1520
  )
1172
1521
  ] }),
1173
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-slate-400", children: [
1174
- /* @__PURE__ */ jsx("span", { children: "Font Size:" }),
1175
- /* @__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: [
1176
1525
  fontSize,
1177
1526
  "px"
1178
1527
  ] })
1179
1528
  ] }),
1180
- /* @__PURE__ */ jsx(
1529
+ /* @__PURE__ */ jsx5(
1181
1530
  "input",
1182
1531
  {
1183
1532
  type: "range",
@@ -1188,14 +1537,14 @@ var ImageEditor = ({
1188
1537
  className: "w-full accent-indigo-500"
1189
1538
  }
1190
1539
  ),
1191
- /* @__PURE__ */ jsx("div", { className: "h-px bg-slate-800 my-1" }),
1192
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1193
- /* @__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: [
1194
1543
  "Layers (",
1195
1544
  state.annotations.length,
1196
1545
  ")"
1197
1546
  ] }),
1198
- state.selectedAnnotationId && /* @__PURE__ */ jsx(
1547
+ state.selectedAnnotationId && /* @__PURE__ */ jsx5(
1199
1548
  "button",
1200
1549
  {
1201
1550
  type: "button",
@@ -1204,7 +1553,7 @@ var ImageEditor = ({
1204
1553
  children: "Delete Selected"
1205
1554
  }
1206
1555
  ),
1207
- state.annotations.length > 0 && /* @__PURE__ */ jsx(
1556
+ state.annotations.length > 0 && /* @__PURE__ */ jsx5(
1208
1557
  "button",
1209
1558
  {
1210
1559
  type: "button",
@@ -1215,10 +1564,10 @@ var ImageEditor = ({
1215
1564
  )
1216
1565
  ] })
1217
1566
  ] }),
1218
- activeTab === "depth" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
1219
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1220
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
1221
- /* @__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(
1222
1571
  "button",
1223
1572
  {
1224
1573
  type: "button",
@@ -1228,8 +1577,8 @@ var ImageEditor = ({
1228
1577
  }
1229
1578
  )
1230
1579
  ] }),
1231
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
1232
- /* @__PURE__ */ jsx(
1580
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
1581
+ /* @__PURE__ */ jsx5(
1233
1582
  "input",
1234
1583
  {
1235
1584
  type: "checkbox",
@@ -1238,12 +1587,12 @@ var ImageEditor = ({
1238
1587
  className: "rounded accent-indigo-500"
1239
1588
  }
1240
1589
  ),
1241
- /* @__PURE__ */ jsx("span", { children: "Enable Depth / Alpha Mask" })
1590
+ /* @__PURE__ */ jsx5("span", { children: "Enable Depth / Alpha Mask" })
1242
1591
  ] }),
1243
- state.depthMask.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
1244
- /* @__PURE__ */ jsxs("div", { children: [
1245
- /* @__PURE__ */ jsx("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
1246
- /* @__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(
1247
1596
  "input",
1248
1597
  {
1249
1598
  type: "text",
@@ -1254,8 +1603,8 @@ var ImageEditor = ({
1254
1603
  }
1255
1604
  )
1256
1605
  ] }),
1257
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1258
- /* @__PURE__ */ jsx(
1606
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1607
+ /* @__PURE__ */ jsx5(
1259
1608
  "button",
1260
1609
  {
1261
1610
  type: "button",
@@ -1264,7 +1613,7 @@ var ImageEditor = ({
1264
1613
  children: "Threshold Mode"
1265
1614
  }
1266
1615
  ),
1267
- /* @__PURE__ */ jsx(
1616
+ /* @__PURE__ */ jsx5(
1268
1617
  "button",
1269
1618
  {
1270
1619
  type: "button",
@@ -1274,12 +1623,12 @@ var ImageEditor = ({
1274
1623
  }
1275
1624
  )
1276
1625
  ] }),
1277
- /* @__PURE__ */ jsxs("div", { children: [
1278
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1279
- /* @__PURE__ */ jsx("span", { children: "Depth Start (Lower):" }),
1280
- /* @__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] })
1281
1630
  ] }),
1282
- /* @__PURE__ */ jsx(
1631
+ /* @__PURE__ */ jsx5(
1283
1632
  "input",
1284
1633
  {
1285
1634
  type: "range",
@@ -1293,12 +1642,12 @@ var ImageEditor = ({
1293
1642
  }
1294
1643
  )
1295
1644
  ] }),
1296
- /* @__PURE__ */ jsxs("div", { children: [
1297
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1298
- /* @__PURE__ */ jsx("span", { children: "Depth Stop (Upper):" }),
1299
- /* @__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] })
1300
1649
  ] }),
1301
- /* @__PURE__ */ jsx(
1650
+ /* @__PURE__ */ jsx5(
1302
1651
  "input",
1303
1652
  {
1304
1653
  type: "range",
@@ -1312,12 +1661,12 @@ var ImageEditor = ({
1312
1661
  }
1313
1662
  )
1314
1663
  ] }),
1315
- /* @__PURE__ */ jsxs("div", { children: [
1316
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1317
- /* @__PURE__ */ jsx("span", { children: "Softness:" }),
1318
- /* @__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 })
1319
1668
  ] }),
1320
- /* @__PURE__ */ jsx(
1669
+ /* @__PURE__ */ jsx5(
1321
1670
  "input",
1322
1671
  {
1323
1672
  type: "range",
@@ -1329,8 +1678,8 @@ var ImageEditor = ({
1329
1678
  }
1330
1679
  )
1331
1680
  ] }),
1332
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
1333
- /* @__PURE__ */ jsx(
1681
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
1682
+ /* @__PURE__ */ jsx5(
1334
1683
  "input",
1335
1684
  {
1336
1685
  type: "checkbox",
@@ -1339,52 +1688,52 @@ var ImageEditor = ({
1339
1688
  className: "rounded accent-indigo-500"
1340
1689
  }
1341
1690
  ),
1342
- /* @__PURE__ */ jsx("span", { children: "Invert Mask" })
1691
+ /* @__PURE__ */ jsx5("span", { children: "Invert Mask" })
1343
1692
  ] })
1344
1693
  ] })
1345
1694
  ] }),
1346
- activeTab === "ai" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 text-xs", children: [
1347
- /* @__PURE__ */ jsxs("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
1348
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1349
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
1350
- /* @__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" }) }),
1351
1700
  "Background Removal"
1352
1701
  ] }),
1353
- 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..." })
1354
1703
  ] }),
1355
- /* @__PURE__ */ jsx("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
1356
- /* @__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(
1357
1706
  "button",
1358
1707
  {
1359
1708
  type: "button",
1360
1709
  onClick: handleRemoveBackground,
1361
1710
  disabled: isRemovingBg || isInpainting,
1362
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",
1363
- children: isRemovingBg ? /* @__PURE__ */ jsxs(Fragment, { children: [
1364
- /* @__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" }),
1365
1714
  "Removing Background..."
1366
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1367
- /* @__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" }) }),
1368
1717
  "Remove Background"
1369
1718
  ] })
1370
1719
  }
1371
1720
  )
1372
1721
  ] }),
1373
- /* @__PURE__ */ jsxs("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
1374
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1375
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
1376
- /* @__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: [
1377
- /* @__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" }),
1378
- /* @__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" })
1379
1728
  ] }),
1380
1729
  "Generative Inpainting"
1381
1730
  ] }),
1382
- 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..." })
1383
1732
  ] }),
1384
- /* @__PURE__ */ jsx("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
1385
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1386
- /* @__PURE__ */ jsx("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
1387
- /* @__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(
1388
1737
  "textarea",
1389
1738
  {
1390
1739
  id: "ai-inpaint-prompt-react",
@@ -1396,10 +1745,10 @@ var ImageEditor = ({
1396
1745
  }
1397
1746
  )
1398
1747
  ] }),
1399
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1400
- /* @__PURE__ */ jsx("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
1401
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [
1402
- /* @__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(
1403
1752
  "button",
1404
1753
  {
1405
1754
  type: "button",
@@ -1412,7 +1761,7 @@ var ImageEditor = ({
1412
1761
  ]
1413
1762
  }
1414
1763
  ),
1415
- /* @__PURE__ */ jsxs(
1764
+ /* @__PURE__ */ jsxs5(
1416
1765
  "button",
1417
1766
  {
1418
1767
  type: "button",
@@ -1427,25 +1776,25 @@ var ImageEditor = ({
1427
1776
  )
1428
1777
  ] })
1429
1778
  ] }),
1430
- /* @__PURE__ */ jsx(
1779
+ /* @__PURE__ */ jsx5(
1431
1780
  "button",
1432
1781
  {
1433
1782
  type: "button",
1434
1783
  onClick: handleInpaint,
1435
1784
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
1436
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",
1437
- children: isInpainting ? /* @__PURE__ */ jsxs(Fragment, { children: [
1438
- /* @__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" }),
1439
1788
  "Generating Inpaint..."
1440
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1441
- /* @__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" }) }),
1442
1791
  "Apply Inpainting"
1443
1792
  ] })
1444
1793
  }
1445
1794
  )
1446
1795
  ] }),
1447
- 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 }),
1448
- 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 })
1449
1798
  ] })
1450
1799
  ] })
1451
1800
  ] })
@@ -1456,6 +1805,11 @@ var ImageEditor = ({
1456
1805
 
1457
1806
  export {
1458
1807
  useImageEditor,
1808
+ ControlSlider,
1809
+ Histogram,
1810
+ useImageHistogram,
1811
+ CurvesEditor,
1812
+ AdjustmentLayersPanel,
1459
1813
  ImageEditor
1460
1814
  };
1461
- //# sourceMappingURL=chunk-LPFZMZ3L.js.map
1815
+ //# sourceMappingURL=chunk-XTFR3IMK.js.map