@pixerate/editor-react 0.58.0 → 0.60.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.
@@ -96,11 +96,11 @@ function useImageEditor(options = {}) {
96
96
  [controller]
97
97
  );
98
98
  const applyInpaintedImage = useCallback(
99
- (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
99
+ (newDataUrl, options2) => controller.applyInpaintedImage(newDataUrl, options2),
100
100
  [controller]
101
101
  );
102
102
  const applyBackgroundRemovedImage = useCallback(
103
- (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
103
+ (newDataUrl, options2) => controller.applyBackgroundRemovedImage(newDataUrl, options2),
104
104
  [controller]
105
105
  );
106
106
  return useMemo(
@@ -159,7 +159,7 @@ function useImageEditor(options = {}) {
159
159
  }
160
160
 
161
161
  // src/image-editor/ImageEditor.tsx
162
- import React6, { useRef as useRef3, useEffect as useEffect4, useState as useState7, useCallback as useCallback2 } from "react";
162
+ import React8, { useRef as useRef4, useEffect as useEffect6, useState as useState9, useCallback as useCallback2 } from "react";
163
163
  import {
164
164
  canvasToImagePoint,
165
165
  hitTestCrop,
@@ -171,7 +171,10 @@ import {
171
171
  IMAGE_ADJUSTMENT_CONTROLS,
172
172
  readAdjustmentControls,
173
173
  DEFAULT_SELECTION_TOOL_OPTIONS,
174
- SelectionGesture
174
+ SelectionGesture,
175
+ PaintGesture,
176
+ handlePaintShortcut,
177
+ pointerPressure
175
178
  } from "@pixerate/editor/image-editor";
176
179
 
177
180
  // src/image-editor/ControlSlider.tsx
@@ -622,15 +625,508 @@ var AdjustmentLayersPanel = ({ controller, layers: layersProp, histogram }) => {
622
625
  ] });
623
626
  };
624
627
 
628
+ // src/image-editor/LayersPanel.tsx
629
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
630
+ import {
631
+ BLEND_MODES as BLEND_MODES2,
632
+ PSD_FILE_ACCEPT,
633
+ downloadPsd,
634
+ dropPlacement,
635
+ dropZone,
636
+ openPsdFile,
637
+ layerBadge,
638
+ layerMenu,
639
+ layerPanelRows,
640
+ runLayerMenuAction
641
+ } from "@pixerate/editor/image-editor";
642
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
643
+ var iconButton = "px-1.5 py-0.5 rounded text-[10px] bg-slate-800 text-slate-300 hover:text-white disabled:opacity-40";
644
+ var rowClass = (active) => `flex items-center gap-1.5 px-1.5 py-1 rounded cursor-pointer select-none ${active ? "bg-indigo-600/30 ring-1 ring-indigo-500" : "hover:bg-slate-800"}`;
645
+ var Thumbnail = ({
646
+ pixels,
647
+ size,
648
+ label,
649
+ onClick,
650
+ selected,
651
+ testId
652
+ }) => {
653
+ const ref = useRef3(null);
654
+ useEffect4(() => {
655
+ const canvas = ref.current;
656
+ const ctx = canvas?.getContext("2d");
657
+ if (!canvas || !ctx || !pixels || typeof ctx.createImageData !== "function") return;
658
+ canvas.width = pixels.width;
659
+ canvas.height = pixels.height;
660
+ const img = ctx.createImageData(pixels.width, pixels.height);
661
+ img.data.set(pixels.data);
662
+ ctx.putImageData(img, 0, 0);
663
+ }, [pixels]);
664
+ return /* @__PURE__ */ jsx5(
665
+ "canvas",
666
+ {
667
+ ref,
668
+ role: onClick ? "button" : "img",
669
+ "aria-label": label,
670
+ "data-testid": testId,
671
+ onClick,
672
+ style: { width: size, height: size },
673
+ className: `shrink-0 rounded bg-[repeating-conic-gradient(#334155_0%_25%,#1e293b_0%_50%)] [background-size:8px_8px] object-contain ${selected ? "ring-2 ring-white" : "ring-1 ring-slate-700"}`
674
+ }
675
+ );
676
+ };
677
+ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", className }) => {
678
+ const [, setTick] = useState5(0);
679
+ useEffect4(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
680
+ const [error, setError] = useState5(null);
681
+ const [renaming, setRenaming] = useState5(null);
682
+ const [menu, setMenu] = useState5(null);
683
+ const [drag, setDrag] = useState5(null);
684
+ const [psdNotes, setPsdNotes] = useState5([]);
685
+ const fileInput = useRef3(null);
686
+ const psdTask = async (fn) => {
687
+ try {
688
+ setError(null);
689
+ setPsdNotes(await fn());
690
+ } catch (err) {
691
+ setError(err instanceof Error ? err.message : String(err));
692
+ }
693
+ };
694
+ const commitNext = useRef3(true);
695
+ const state = controller.getState();
696
+ const rows = layerPanelRows(state.layers, state.activeLayerId);
697
+ const active = controller.getActiveLayer();
698
+ const attempt = (fn) => {
699
+ try {
700
+ setError(null);
701
+ fn();
702
+ } catch (err) {
703
+ setError(err instanceof Error ? err.message : String(err));
704
+ }
705
+ };
706
+ const run = async (action) => {
707
+ setMenu(null);
708
+ setError(await runLayerMenuAction(controller, action));
709
+ };
710
+ const prop = (key, value) => attempt(() => {
711
+ controller.setLayerProperties(active.id, { [key]: value }, commitNext.current);
712
+ commitNext.current = false;
713
+ });
714
+ const sliderEvents = {
715
+ onPointerDown: () => commitNext.current = true,
716
+ onPointerUp: () => commitNext.current = true,
717
+ onKeyDown: () => commitNext.current = true
718
+ };
719
+ const onDrop = (e, row) => {
720
+ e.preventDefault();
721
+ const id = e.dataTransfer.getData("text/x-pixerate-layer") || drag;
722
+ setDrag(null);
723
+ if (!id) return;
724
+ const rect = e.currentTarget.getBoundingClientRect();
725
+ const fraction = rect.height ? (e.clientY - rect.top) / rect.height : 0.5;
726
+ const placement = dropPlacement(state.layers, id, row.layer.id, dropZone(fraction, row.layer.type === "group"));
727
+ if (placement) attempt(() => controller.moveLayer(id, placement));
728
+ };
729
+ const thumb = (layer) => layer.type === "pixel" ? controller.getLayerThumbnail(layer.id, { maxSide: 64 }) : null;
730
+ const maskThumb = (layer) => layer.mask && !layer.mask.source ? controller.getLayerThumbnail(layer.id, { maxSide: 64, mask: true }) : null;
731
+ const locked = (layer) => Boolean(layer.locks?.all || layer.locks?.pixels || layer.locks?.position || layer.locks?.transparency);
732
+ return /* @__PURE__ */ jsxs5("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "layers-panel", onClick: () => menu && setMenu(null), children: [
733
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-1", role: "toolbar", "aria-label": "Layer actions", children: [
734
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, onClick: () => run("new-layer"), children: "New Layer" }),
735
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, onClick: () => run("new-group"), children: "New Group" }),
736
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("duplicate"), children: "Duplicate" }),
737
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("add-mask"), children: "Add Mask" }),
738
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("delete"), children: "Delete" }),
739
+ /* @__PURE__ */ jsx5(
740
+ "button",
741
+ {
742
+ type: "button",
743
+ className: iconButton,
744
+ "aria-label": "Layer menu",
745
+ "aria-haspopup": "menu",
746
+ onClick: (e) => {
747
+ e.stopPropagation();
748
+ const r = e.currentTarget.getBoundingClientRect();
749
+ setMenu(menu ? null : { x: r.left, y: r.bottom });
750
+ },
751
+ children: "\u22EF"
752
+ }
753
+ ),
754
+ psd && /* @__PURE__ */ jsxs5(Fragment, { children: [
755
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, onClick: () => fileInput.current?.click(), children: "Open PSD\u2026" }),
756
+ /* @__PURE__ */ jsx5("button", { type: "button", className: iconButton, onClick: () => psdTask(async () => (await downloadPsd(controller, psdFileName)).warnings), children: "Export PSD" }),
757
+ /* @__PURE__ */ jsx5(
758
+ "input",
759
+ {
760
+ ref: fileInput,
761
+ type: "file",
762
+ accept: PSD_FILE_ACCEPT,
763
+ className: "hidden",
764
+ "data-testid": "psd-file-input",
765
+ "aria-label": "Open a PSD file",
766
+ onChange: (e) => {
767
+ const file = e.currentTarget.files?.[0];
768
+ e.currentTarget.value = "";
769
+ if (file) psdTask(() => openPsdFile(controller, file));
770
+ }
771
+ }
772
+ )
773
+ ] })
774
+ ] }),
775
+ psdNotes.length > 0 && /* @__PURE__ */ jsxs5("ul", { className: "rounded bg-amber-900/30 text-amber-200 px-2 py-1 space-y-0.5", "data-testid": "psd-warnings", "aria-label": "PSD warnings", children: [
776
+ psdNotes.map((n) => /* @__PURE__ */ jsx5("li", { children: n }, n)),
777
+ /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5("button", { type: "button", className: "underline", onClick: () => setPsdNotes([]), children: "Dismiss" }) })
778
+ ] }),
779
+ active && /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
780
+ /* @__PURE__ */ jsxs5("label", { className: "flex flex-col gap-0.5", children: [
781
+ /* @__PURE__ */ jsxs5("span", { className: "text-slate-400", children: [
782
+ "Opacity ",
783
+ Math.round(active.opacity * 100),
784
+ "%"
785
+ ] }),
786
+ /* @__PURE__ */ jsx5(
787
+ "input",
788
+ {
789
+ type: "range",
790
+ min: 0,
791
+ max: 100,
792
+ "aria-label": "Layer opacity",
793
+ value: Math.round(active.opacity * 100),
794
+ ...sliderEvents,
795
+ onChange: (e) => prop("opacity", Number(e.target.value) / 100),
796
+ className: "accent-indigo-500"
797
+ }
798
+ )
799
+ ] }),
800
+ /* @__PURE__ */ jsxs5("label", { className: "flex flex-col gap-0.5", children: [
801
+ /* @__PURE__ */ jsxs5("span", { className: "text-slate-400", children: [
802
+ "Fill ",
803
+ Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
804
+ "%"
805
+ ] }),
806
+ /* @__PURE__ */ jsx5(
807
+ "input",
808
+ {
809
+ type: "range",
810
+ min: 0,
811
+ max: 100,
812
+ "aria-label": "Layer fill",
813
+ disabled: active.type !== "pixel",
814
+ value: Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
815
+ ...sliderEvents,
816
+ onChange: (e) => prop("fillOpacity", Number(e.target.value) / 100),
817
+ className: "accent-indigo-500 disabled:opacity-40"
818
+ }
819
+ )
820
+ ] }),
821
+ /* @__PURE__ */ jsxs5(
822
+ "select",
823
+ {
824
+ "aria-label": "Layer blend mode",
825
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
826
+ value: active.blend,
827
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { blend: e.target.value })),
828
+ children: [
829
+ active.type === "group" && /* @__PURE__ */ jsx5("option", { value: "pass-through", children: "Pass Through" }),
830
+ BLEND_MODES2.map((m) => /* @__PURE__ */ jsx5("option", { value: m.id, children: m.label }, m.id))
831
+ ]
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
835
+ /* @__PURE__ */ jsx5(
836
+ "input",
837
+ {
838
+ type: "checkbox",
839
+ "aria-label": "Clip to layer below",
840
+ checked: Boolean(active.clipped),
841
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { clipped: e.target.checked })),
842
+ className: "accent-indigo-500"
843
+ }
844
+ ),
845
+ "Clip to below"
846
+ ] }),
847
+ /* @__PURE__ */ jsx5("div", { className: "col-span-2 flex flex-wrap gap-2 text-slate-400", role: "group", "aria-label": "Layer locks", children: ["pixels", "transparency", "position", "all"].map((k) => /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1", children: [
848
+ /* @__PURE__ */ jsx5(
849
+ "input",
850
+ {
851
+ type: "checkbox",
852
+ "aria-label": `Lock ${k}`,
853
+ checked: Boolean(active.locks?.[k]),
854
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { locks: { ...active.locks, [k]: e.target.checked } })),
855
+ className: "accent-indigo-500"
856
+ }
857
+ ),
858
+ k
859
+ ] }, k)) })
860
+ ] }),
861
+ /* @__PURE__ */ jsx5("ul", { className: "flex flex-col gap-0.5", role: "tree", "aria-label": "Layers", children: rows.map((row) => {
862
+ const { layer } = row;
863
+ return /* @__PURE__ */ jsxs5(
864
+ "li",
865
+ {
866
+ role: "treeitem",
867
+ "aria-selected": row.active,
868
+ "aria-level": row.depth + 1,
869
+ "data-layer-id": layer.id,
870
+ draggable: !layer.locks?.all,
871
+ onDragStart: (e) => {
872
+ e.dataTransfer.setData("text/x-pixerate-layer", layer.id);
873
+ setDrag(layer.id);
874
+ },
875
+ onDragOver: (e) => e.preventDefault(),
876
+ onDrop: (e) => onDrop(e, row),
877
+ tabIndex: 0,
878
+ onClick: () => attempt(() => controller.setActiveLayer(layer.id, "pixels")),
879
+ onKeyDown: (e) => {
880
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
881
+ e.preventDefault();
882
+ attempt(() => controller.setActiveLayer(layer.id, "pixels"));
883
+ }
884
+ },
885
+ onContextMenu: (e) => {
886
+ e.preventDefault();
887
+ controller.setActiveLayer(layer.id, state.activeLayerId === layer.id ? state.activeTarget : "pixels");
888
+ setMenu({ x: e.clientX, y: e.clientY });
889
+ },
890
+ className: rowClass(row.active),
891
+ style: { paddingLeft: 6 + row.depth * 14 },
892
+ children: [
893
+ /* @__PURE__ */ jsx5(
894
+ "button",
895
+ {
896
+ type: "button",
897
+ "aria-label": layer.visible ? `Hide ${row.label}` : `Show ${row.label}`,
898
+ "aria-pressed": layer.visible,
899
+ className: "w-4 text-center text-slate-300",
900
+ onClick: (e) => {
901
+ e.stopPropagation();
902
+ attempt(() => controller.setLayerProperties(layer.id, { visible: !layer.visible }));
903
+ },
904
+ children: layer.visible ? "\u25CF" : "\u25CB"
905
+ }
906
+ ),
907
+ layer.type === "group" && /* @__PURE__ */ jsx5(
908
+ "button",
909
+ {
910
+ type: "button",
911
+ "aria-label": layer.collapsed ? `Expand ${row.label}` : `Collapse ${row.label}`,
912
+ className: "w-3 text-slate-400",
913
+ onClick: (e) => {
914
+ e.stopPropagation();
915
+ attempt(() => controller.setLayerProperties(layer.id, { collapsed: !layer.collapsed }));
916
+ },
917
+ children: layer.collapsed ? "\u25B8" : "\u25BE"
918
+ }
919
+ ),
920
+ row.clipped && /* @__PURE__ */ jsx5("span", { className: "text-slate-500", "aria-label": "Clipped", children: "\u21B3" }),
921
+ layer.type === "pixel" ? /* @__PURE__ */ jsx5(
922
+ Thumbnail,
923
+ {
924
+ pixels: thumb(layer),
925
+ size: thumbnailSize,
926
+ label: `${row.label} pixels`,
927
+ selected: row.active && state.activeTarget === "pixels" && Boolean(layer.mask)
928
+ }
929
+ ) : /* @__PURE__ */ jsx5(
930
+ "span",
931
+ {
932
+ className: "shrink-0 grid place-items-center rounded bg-slate-800 text-[9px] text-slate-400 ring-1 ring-slate-700",
933
+ style: { width: thumbnailSize, height: thumbnailSize },
934
+ children: layerBadge(layer)
935
+ }
936
+ ),
937
+ layer.mask && /* @__PURE__ */ jsx5(
938
+ Thumbnail,
939
+ {
940
+ pixels: maskThumb(layer),
941
+ size: thumbnailSize,
942
+ label: `${row.label} mask${layer.mask.enabled === false ? " (disabled)" : ""}`,
943
+ testId: "layer-mask-thumbnail",
944
+ selected: row.active && state.activeTarget === "mask",
945
+ onClick: (e) => {
946
+ e.stopPropagation();
947
+ if (e.shiftKey) attempt(() => controller.setLayerMaskProps(layer.id, { enabled: layer.mask.enabled === false }));
948
+ else attempt(() => controller.setActiveLayer(layer.id, "mask"));
949
+ }
950
+ }
951
+ ),
952
+ renaming === layer.id ? /* @__PURE__ */ jsx5(
953
+ "input",
954
+ {
955
+ autoFocus: true,
956
+ "aria-label": "Layer name",
957
+ defaultValue: layer.name || row.label,
958
+ className: "flex-1 min-w-0 bg-slate-900 border border-slate-600 rounded px-1",
959
+ onClick: (e) => e.stopPropagation(),
960
+ onBlur: (e) => {
961
+ setRenaming(null);
962
+ if (e.target.value.trim() && e.target.value !== layer.name) attempt(() => controller.setLayerProperties(layer.id, { name: e.target.value }));
963
+ },
964
+ onKeyDown: (e) => {
965
+ if (e.key === "Enter") e.target.blur();
966
+ if (e.key === "Escape") setRenaming(null);
967
+ }
968
+ }
969
+ ) : /* @__PURE__ */ jsx5(
970
+ "span",
971
+ {
972
+ className: `flex-1 min-w-0 truncate ${layer.visible ? "" : "text-slate-500"}`,
973
+ onDoubleClick: (e) => {
974
+ e.stopPropagation();
975
+ setRenaming(layer.id);
976
+ },
977
+ children: row.label
978
+ }
979
+ ),
980
+ locked(layer) && /* @__PURE__ */ jsx5("span", { "aria-label": "Locked", className: "text-slate-400", children: "\u{1F512}" }),
981
+ layer.blend !== "normal" && layer.blend !== "pass-through" && /* @__PURE__ */ jsx5("span", { className: "text-[9px] text-slate-500", children: layer.blend })
982
+ ]
983
+ },
984
+ layer.id
985
+ );
986
+ }) }),
987
+ error && /* @__PURE__ */ jsx5("p", { role: "alert", className: "text-rose-400", children: error }),
988
+ menu && /* @__PURE__ */ jsx5(
989
+ "div",
990
+ {
991
+ role: "menu",
992
+ "aria-label": "Layer menu",
993
+ className: "fixed z-50 min-w-44 rounded border border-slate-700 bg-slate-900 py-1 shadow-xl",
994
+ style: { left: menu.x, top: menu.y },
995
+ onClick: (e) => e.stopPropagation(),
996
+ onKeyDown: (e) => e.key === "Escape" && setMenu(null),
997
+ children: layerMenu(controller).map((item) => /* @__PURE__ */ jsx5(
998
+ "button",
999
+ {
1000
+ type: "button",
1001
+ role: "menuitem",
1002
+ disabled: !item.enabled,
1003
+ className: "block w-full px-3 py-1 text-left hover:bg-slate-800 disabled:opacity-40",
1004
+ onClick: () => run(item.action),
1005
+ children: item.label
1006
+ },
1007
+ item.action
1008
+ ))
1009
+ }
1010
+ )
1011
+ ] });
1012
+ };
1013
+
1014
+ // src/image-editor/BrushOptionsBar.tsx
1015
+ import { useEffect as useEffect5, useState as useState6 } from "react";
1016
+ import { BRUSH_OPTION_CONTROLS, PAINT_TOOLS } from "@pixerate/editor/image-editor";
1017
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1018
+ var chip = (on) => `px-2 py-1 rounded text-[11px] ${on ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-300 hover:text-white"}`;
1019
+ var BrushOptionsBar = ({ controller, className }) => {
1020
+ const [, setTick] = useState6(0);
1021
+ useEffect5(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
1022
+ const [error, setError] = useState6(null);
1023
+ const state = controller.getState();
1024
+ const { brush, foreground, background } = state.paint;
1025
+ const attempt = (fn) => {
1026
+ try {
1027
+ setError(null);
1028
+ fn();
1029
+ } catch (err) {
1030
+ setError(err instanceof Error ? err.message : String(err));
1031
+ }
1032
+ };
1033
+ return /* @__PURE__ */ jsxs6("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "brush-options", children: [
1034
+ /* @__PURE__ */ jsx6("div", { className: "flex gap-1", role: "toolbar", "aria-label": "Paint tools", children: PAINT_TOOLS.map((t) => /* @__PURE__ */ jsx6(
1035
+ "button",
1036
+ {
1037
+ type: "button",
1038
+ "aria-pressed": state.activeTool === t.id,
1039
+ title: `${t.label} (${t.shortcut})`,
1040
+ className: chip(state.activeTool === t.id),
1041
+ onClick: () => controller.setTool(t.id),
1042
+ children: t.label
1043
+ },
1044
+ t.id
1045
+ )) }),
1046
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", role: "group", "aria-label": "Colours", children: [
1047
+ /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1", children: [
1048
+ /* @__PURE__ */ jsx6(
1049
+ "input",
1050
+ {
1051
+ type: "color",
1052
+ "aria-label": "Foreground colour",
1053
+ value: foreground,
1054
+ onChange: (e) => controller.setPaintOptions({ foreground: e.target.value }),
1055
+ className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1056
+ }
1057
+ ),
1058
+ /* @__PURE__ */ jsx6("span", { className: "text-slate-400", children: "FG" })
1059
+ ] }),
1060
+ /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1", children: [
1061
+ /* @__PURE__ */ jsx6(
1062
+ "input",
1063
+ {
1064
+ type: "color",
1065
+ "aria-label": "Background colour",
1066
+ value: background,
1067
+ onChange: (e) => controller.setPaintOptions({ background: e.target.value }),
1068
+ className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1069
+ }
1070
+ ),
1071
+ /* @__PURE__ */ jsx6("span", { className: "text-slate-400", children: "BG" })
1072
+ ] }),
1073
+ /* @__PURE__ */ jsx6("button", { type: "button", title: "Swap colours (X)", className: chip(false), onClick: () => controller.swapPaintColors(), children: "Swap" }),
1074
+ /* @__PURE__ */ jsx6("button", { type: "button", title: "Default colours (D)", className: chip(false), onClick: () => controller.resetPaintColors(), children: "Default" })
1075
+ ] }),
1076
+ BRUSH_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ jsx6(
1077
+ ControlSlider,
1078
+ {
1079
+ spec,
1080
+ value: brush[spec.id],
1081
+ onChange: (v) => controller.setPaintOptions({ brush: { [spec.id]: v } })
1082
+ },
1083
+ spec.id
1084
+ )),
1085
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap gap-3 text-slate-300", role: "group", "aria-label": "Pressure", children: [
1086
+ /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5", children: [
1087
+ /* @__PURE__ */ jsx6(
1088
+ "input",
1089
+ {
1090
+ type: "checkbox",
1091
+ "aria-label": "Pressure controls size",
1092
+ checked: brush.pressureSize,
1093
+ onChange: (e) => controller.setPaintOptions({ brush: { pressureSize: e.target.checked } }),
1094
+ className: "accent-indigo-500"
1095
+ }
1096
+ ),
1097
+ "Pressure size"
1098
+ ] }),
1099
+ /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5", children: [
1100
+ /* @__PURE__ */ jsx6(
1101
+ "input",
1102
+ {
1103
+ type: "checkbox",
1104
+ "aria-label": "Pressure controls opacity",
1105
+ checked: brush.pressureOpacity,
1106
+ onChange: (e) => controller.setPaintOptions({ brush: { pressureOpacity: e.target.checked } }),
1107
+ className: "accent-indigo-500"
1108
+ }
1109
+ ),
1110
+ "Pressure opacity"
1111
+ ] })
1112
+ ] }),
1113
+ /* @__PURE__ */ jsxs6("div", { className: "flex gap-1", children: [
1114
+ /* @__PURE__ */ jsx6("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.fillPixels()), children: "Fill" }),
1115
+ /* @__PURE__ */ jsx6("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.clearPixels()), children: "Clear" })
1116
+ ] }),
1117
+ error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "text-rose-400", children: error })
1118
+ ] });
1119
+ };
1120
+
625
1121
  // src/image-editor/SelectionOptionsBar.tsx
626
- import { useState as useState5 } from "react";
1122
+ import { useState as useState7 } from "react";
627
1123
  import {
628
1124
  SELECTION_MODES,
629
1125
  SELECTION_MODE_LABELS,
630
1126
  SELECTION_TOOLS,
631
1127
  describeSelection
632
1128
  } from "@pixerate/editor/image-editor";
633
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1129
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
634
1130
  var MODIFY_OPS = [
635
1131
  { op: "grow", label: "Grow" },
636
1132
  { op: "shrink", label: "Shrink" },
@@ -647,20 +1143,20 @@ var SelectionOptionsBar = ({
647
1143
  quickMask,
648
1144
  className = ""
649
1145
  }) => {
650
- const [radius, setRadius] = useState5(2);
1146
+ const [radius, setRadius] = useState7(2);
651
1147
  const set = (patch) => onOptionsChange({ ...options, ...patch });
652
1148
  const sel = selection === void 0 ? controller.getSelection() : selection;
653
1149
  const qm = quickMask ?? Boolean(controller.getState().quickMask);
654
- return /* @__PURE__ */ jsxs5("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
655
- /* @__PURE__ */ jsx5("div", { role: "group", "aria-label": "Selection tool", className: "flex flex-wrap gap-1", children: SELECTION_TOOLS.map((t) => /* @__PURE__ */ jsx5("button", { type: "button", "aria-pressed": options.tool === t.id, className: btn(options.tool === t.id), onClick: () => set({ tool: t.id }), children: t.label }, t.id)) }),
656
- /* @__PURE__ */ jsx5("div", { role: "group", "aria-label": "Selection mode", className: "flex flex-wrap gap-1", children: SELECTION_MODES.map((m) => /* @__PURE__ */ jsx5("button", { type: "button", "aria-pressed": options.mode === m, className: btn(options.mode === m), onClick: () => set({ mode: m }), children: SELECTION_MODE_LABELS[m] }, m)) }),
657
- options.tool === "wand" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
658
- /* @__PURE__ */ jsxs5("label", { className: "flex flex-col gap-1", children: [
659
- /* @__PURE__ */ jsxs5("span", { className: "flex justify-between text-slate-400", children: [
660
- /* @__PURE__ */ jsx5("span", { children: "Tolerance" }),
661
- /* @__PURE__ */ jsx5("span", { className: "font-mono", children: options.tolerance })
1150
+ return /* @__PURE__ */ jsxs7("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1151
+ /* @__PURE__ */ jsx7("div", { role: "group", "aria-label": "Selection tool", className: "flex flex-wrap gap-1", children: SELECTION_TOOLS.map((t) => /* @__PURE__ */ jsx7("button", { type: "button", "aria-pressed": options.tool === t.id, className: btn(options.tool === t.id), onClick: () => set({ tool: t.id }), children: t.label }, t.id)) }),
1152
+ /* @__PURE__ */ jsx7("div", { role: "group", "aria-label": "Selection mode", className: "flex flex-wrap gap-1", children: SELECTION_MODES.map((m) => /* @__PURE__ */ jsx7("button", { type: "button", "aria-pressed": options.mode === m, className: btn(options.mode === m), onClick: () => set({ mode: m }), children: SELECTION_MODE_LABELS[m] }, m)) }),
1153
+ options.tool === "wand" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-2", children: [
1154
+ /* @__PURE__ */ jsxs7("label", { className: "flex flex-col gap-1", children: [
1155
+ /* @__PURE__ */ jsxs7("span", { className: "flex justify-between text-slate-400", children: [
1156
+ /* @__PURE__ */ jsx7("span", { children: "Tolerance" }),
1157
+ /* @__PURE__ */ jsx7("span", { className: "font-mono", children: options.tolerance })
662
1158
  ] }),
663
- /* @__PURE__ */ jsx5(
1159
+ /* @__PURE__ */ jsx7(
664
1160
  "input",
665
1161
  {
666
1162
  type: "range",
@@ -674,29 +1170,45 @@ var SelectionOptionsBar = ({
674
1170
  }
675
1171
  )
676
1172
  ] }),
677
- /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-slate-300", children: [
678
- /* @__PURE__ */ jsx5("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
1173
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
1174
+ /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
679
1175
  "Contiguous"
680
1176
  ] }),
681
- /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-slate-300", children: [
682
- /* @__PURE__ */ jsx5("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
1177
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
1178
+ /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
683
1179
  "Sample adjusted image"
1180
+ ] }),
1181
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
1182
+ "Sample",
1183
+ /* @__PURE__ */ jsxs7(
1184
+ "select",
1185
+ {
1186
+ "aria-label": "Sample from",
1187
+ value: options.sample ?? "composite",
1188
+ onChange: (e) => set({ sample: e.target.value }),
1189
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5",
1190
+ children: [
1191
+ /* @__PURE__ */ jsx7("option", { value: "composite", children: "All layers" }),
1192
+ /* @__PURE__ */ jsx7("option", { value: "active-layer", children: "Active layer" })
1193
+ ]
1194
+ }
1195
+ )
684
1196
  ] })
685
1197
  ] }),
686
- /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-slate-300", children: [
687
- /* @__PURE__ */ jsx5("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
1198
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
1199
+ /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
688
1200
  "Anti-alias"
689
1201
  ] }),
690
- /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap gap-1", children: [
691
- /* @__PURE__ */ jsx5("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
692
- /* @__PURE__ */ jsx5("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
693
- /* @__PURE__ */ jsx5("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
694
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
1202
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-1", children: [
1203
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
1204
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
1205
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
1206
+ /* @__PURE__ */ jsx7("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
695
1207
  ] }),
696
- /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-1", children: [
697
- /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-slate-400", children: [
1208
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", children: [
1209
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-slate-400", children: [
698
1210
  "Radius",
699
- /* @__PURE__ */ jsx5(
1211
+ /* @__PURE__ */ jsx7(
700
1212
  "input",
701
1213
  {
702
1214
  type: "number",
@@ -710,29 +1222,29 @@ var SelectionOptionsBar = ({
710
1222
  }
711
1223
  )
712
1224
  ] }),
713
- MODIFY_OPS.map(({ op, label }) => /* @__PURE__ */ jsx5("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.modifySelection(op, radius), children: label }, op))
1225
+ MODIFY_OPS.map(({ op, label }) => /* @__PURE__ */ jsx7("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.modifySelection(op, radius), children: label }, op))
714
1226
  ] }),
715
- /* @__PURE__ */ jsx5("p", { "data-testid": "selection-status", className: "text-slate-400", children: describeSelection(sel) })
1227
+ /* @__PURE__ */ jsx7("p", { "data-testid": "selection-status", className: "text-slate-400", children: describeSelection(sel) })
716
1228
  ] });
717
1229
  };
718
1230
 
719
1231
  // src/image-editor/RefineEdgePanel.tsx
720
- import { useState as useState6 } from "react";
1232
+ import { useState as useState8 } from "react";
721
1233
  import {
722
1234
  ADJUSTMENT_KINDS as ADJUSTMENT_KINDS2,
723
1235
  REFINE_EDGE_CONTROLS,
724
1236
  adjustmentKindLabel as adjustmentKindLabel2,
725
1237
  layerMaskFromSelection
726
1238
  } from "@pixerate/editor/image-editor";
727
- import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1239
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
728
1240
  var DEFAULT_PARAMS = { radius: 8, smartRadius: false, smooth: 0, feather: 0, contrast: 0, shiftEdge: 0 };
729
1241
  var button = "px-2 py-1 rounded-md border border-slate-800 bg-slate-950 text-[11px] text-slate-200 hover:text-white disabled:opacity-40";
730
1242
  var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
731
- const [params, setParams] = useState6(DEFAULT_PARAMS);
732
- const [busy, setBusy] = useState6(false);
733
- const [error, setError] = useState6(null);
734
- const [layerId, setLayerId] = useState6("");
735
- const [newKind, setNewKind] = useState6("hueSaturation");
1243
+ const [params, setParams] = useState8(DEFAULT_PARAMS);
1244
+ const [busy, setBusy] = useState8(false);
1245
+ const [error, setError] = useState8(null);
1246
+ const [layerId, setLayerId] = useState8("");
1247
+ const [newKind, setNewKind] = useState8("hueSaturation");
736
1248
  const sel = selection === void 0 ? controller.getSelection() : selection;
737
1249
  const list = layers ?? controller.getAdjustmentLayers();
738
1250
  const target = list.find((l) => l.id === layerId) ?? list[list.length - 1];
@@ -747,17 +1259,17 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
747
1259
  setBusy(false);
748
1260
  }
749
1261
  };
750
- return /* @__PURE__ */ jsxs6("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
751
- /* @__PURE__ */ jsx6("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
752
- REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ jsxs6("label", { className: "flex flex-col gap-1", children: [
753
- /* @__PURE__ */ jsxs6("span", { className: "flex justify-between text-slate-400", children: [
754
- /* @__PURE__ */ jsx6("span", { children: c.label }),
755
- /* @__PURE__ */ jsxs6("span", { className: "font-mono", children: [
1262
+ return /* @__PURE__ */ jsxs8("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1263
+ /* @__PURE__ */ jsx8("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
1264
+ REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ jsxs8("label", { className: "flex flex-col gap-1", children: [
1265
+ /* @__PURE__ */ jsxs8("span", { className: "flex justify-between text-slate-400", children: [
1266
+ /* @__PURE__ */ jsx8("span", { children: c.label }),
1267
+ /* @__PURE__ */ jsxs8("span", { className: "font-mono", children: [
756
1268
  params[c.id],
757
1269
  c.unit
758
1270
  ] })
759
1271
  ] }),
760
- /* @__PURE__ */ jsx6(
1272
+ /* @__PURE__ */ jsx8(
761
1273
  "input",
762
1274
  {
763
1275
  type: "range",
@@ -771,28 +1283,28 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
771
1283
  }
772
1284
  )
773
1285
  ] }, c.id)),
774
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 text-slate-300", children: [
775
- /* @__PURE__ */ jsx6("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
1286
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-slate-300", children: [
1287
+ /* @__PURE__ */ jsx8("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
776
1288
  "Smart Radius"
777
1289
  ] }),
778
- /* @__PURE__ */ jsxs6("div", { className: "flex gap-1", children: [
779
- /* @__PURE__ */ jsx6("button", { type: "button", className: button, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
780
- /* @__PURE__ */ jsx6("button", { type: "button", className: button, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
1290
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-1", children: [
1291
+ /* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
1292
+ /* @__PURE__ */ jsx8("button", { type: "button", className: button, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
781
1293
  ] }),
782
- error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "text-rose-400", children: error }),
783
- /* @__PURE__ */ jsx6("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
784
- /* @__PURE__ */ jsxs6("div", { className: "flex gap-1 items-center", children: [
785
- /* @__PURE__ */ jsx6(
1294
+ error && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-rose-400", children: error }),
1295
+ /* @__PURE__ */ jsx8("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
1296
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-1 items-center", children: [
1297
+ /* @__PURE__ */ jsx8(
786
1298
  "select",
787
1299
  {
788
1300
  "aria-label": "New masked layer kind",
789
1301
  value: newKind,
790
1302
  onChange: (e) => setNewKind(e.target.value),
791
1303
  className: "flex-1 bg-slate-950 border border-slate-800 rounded px-1 py-1 text-slate-200",
792
- children: ADJUSTMENT_KINDS2.map((k) => /* @__PURE__ */ jsx6("option", { value: k.kind, children: k.label }, k.kind))
1304
+ children: ADJUSTMENT_KINDS2.map((k) => /* @__PURE__ */ jsx8("option", { value: k.kind, children: k.label }, k.kind))
793
1305
  }
794
1306
  ),
795
- /* @__PURE__ */ jsx6(
1307
+ /* @__PURE__ */ jsx8(
796
1308
  "button",
797
1309
  {
798
1310
  type: "button",
@@ -803,35 +1315,35 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
803
1315
  }
804
1316
  )
805
1317
  ] }),
806
- list.length > 0 && target && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2", children: [
807
- /* @__PURE__ */ jsx6(
1318
+ list.length > 0 && target && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
1319
+ /* @__PURE__ */ jsx8(
808
1320
  "select",
809
1321
  {
810
1322
  "aria-label": "Mask layer",
811
1323
  value: target.id,
812
1324
  onChange: (e) => setLayerId(e.target.value),
813
1325
  className: "bg-slate-950 border border-slate-800 rounded px-1 py-1 text-slate-200",
814
- children: list.map((l) => /* @__PURE__ */ jsxs6("option", { value: l.id, children: [
1326
+ children: list.map((l) => /* @__PURE__ */ jsxs8("option", { value: l.id, children: [
815
1327
  l.name || adjustmentKindLabel2(l.kind),
816
1328
  l.mask ? " (masked)" : ""
817
1329
  ] }, l.id))
818
1330
  }
819
1331
  ),
820
- /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap gap-1", children: [
821
- /* @__PURE__ */ jsx6("button", { type: "button", className: button, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
822
- /* @__PURE__ */ jsx6("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
823
- /* @__PURE__ */ jsx6("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
1332
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap gap-1", children: [
1333
+ /* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
1334
+ /* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
1335
+ /* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
824
1336
  ] }),
825
- target.mask && /* @__PURE__ */ jsxs6(Fragment, { children: [
826
- /* @__PURE__ */ jsxs6("label", { className: "flex flex-col gap-1", children: [
827
- /* @__PURE__ */ jsxs6("span", { className: "flex justify-between text-slate-400", children: [
828
- /* @__PURE__ */ jsx6("span", { children: "Density" }),
829
- /* @__PURE__ */ jsxs6("span", { className: "font-mono", children: [
1337
+ target.mask && /* @__PURE__ */ jsxs8(Fragment2, { children: [
1338
+ /* @__PURE__ */ jsxs8("label", { className: "flex flex-col gap-1", children: [
1339
+ /* @__PURE__ */ jsxs8("span", { className: "flex justify-between text-slate-400", children: [
1340
+ /* @__PURE__ */ jsx8("span", { children: "Density" }),
1341
+ /* @__PURE__ */ jsxs8("span", { className: "font-mono", children: [
830
1342
  Math.round((target.mask.density ?? 1) * 100),
831
1343
  "%"
832
1344
  ] })
833
1345
  ] }),
834
- /* @__PURE__ */ jsx6(
1346
+ /* @__PURE__ */ jsx8(
835
1347
  "input",
836
1348
  {
837
1349
  type: "range",
@@ -845,15 +1357,15 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
845
1357
  }
846
1358
  )
847
1359
  ] }),
848
- /* @__PURE__ */ jsxs6("label", { className: "flex flex-col gap-1", children: [
849
- /* @__PURE__ */ jsxs6("span", { className: "flex justify-between text-slate-400", children: [
850
- /* @__PURE__ */ jsx6("span", { children: "Mask Feather" }),
851
- /* @__PURE__ */ jsxs6("span", { className: "font-mono", children: [
1360
+ /* @__PURE__ */ jsxs8("label", { className: "flex flex-col gap-1", children: [
1361
+ /* @__PURE__ */ jsxs8("span", { className: "flex justify-between text-slate-400", children: [
1362
+ /* @__PURE__ */ jsx8("span", { children: "Mask Feather" }),
1363
+ /* @__PURE__ */ jsxs8("span", { className: "font-mono", children: [
852
1364
  target.mask.feather ?? 0,
853
1365
  "px"
854
1366
  ] })
855
1367
  ] }),
856
- /* @__PURE__ */ jsx6(
1368
+ /* @__PURE__ */ jsx8(
857
1369
  "input",
858
1370
  {
859
1371
  type: "range",
@@ -873,7 +1385,7 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
873
1385
  };
874
1386
 
875
1387
  // src/image-editor/ImageEditor.tsx
876
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1388
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
877
1389
  var ImageEditor = ({
878
1390
  editor: externalEditor,
879
1391
  src,
@@ -889,25 +1401,35 @@ var ImageEditor = ({
889
1401
  const editor = externalEditor || internalEditor;
890
1402
  const { state, controller } = editor;
891
1403
  const legacyProcess = normalizeProcessVersion(state.adjustments.process) < LATEST_PROCESS_VERSION;
892
- const canvasRef = useRef3(null);
893
- const containerRef = useRef3(null);
894
- const [activeTab, setActiveTab] = useState7("crop");
895
- const [activeCropRatio, setActiveCropRatio] = useState7(null);
896
- const [selectionOptions, setSelectionOptions] = useState7(DEFAULT_SELECTION_TOOL_OPTIONS);
897
- const selectionOptionsRef = useRef3(selectionOptions);
1404
+ const canvasRef = useRef4(null);
1405
+ const containerRef = useRef4(null);
1406
+ const [activeTab, setActiveTab] = useState9("crop");
1407
+ const [activeCropRatio, setActiveCropRatio] = useState9(null);
1408
+ const [selectionOptions, setSelectionOptions] = useState9(DEFAULT_SELECTION_TOOL_OPTIONS);
1409
+ const selectionOptionsRef = useRef4(selectionOptions);
898
1410
  selectionOptionsRef.current = selectionOptions;
899
- const selectionGestureRef = useRef3(null);
1411
+ const selectionGestureRef = useRef4(null);
900
1412
  if (!selectionGestureRef.current || selectionGestureRef.current.controller !== controller) {
901
1413
  selectionGestureRef.current = new SelectionGesture(controller, () => selectionOptionsRef.current);
902
1414
  }
903
- const selectionDraftRef = useRef3(null);
904
- const [selectionError, setSelectionError] = useState7(null);
905
- const [inpaintPrompt, setInpaintPrompt] = useState7("");
906
- const [inpaintMaskMode, setInpaintMaskMode] = useState7("annotations");
907
- const [isRemovingBg, setIsRemovingBg] = useState7(false);
908
- const [isInpainting, setIsInpainting] = useState7(false);
909
- const [aiError, setAiError] = useState7(null);
910
- const [aiSuccess, setAiSuccess] = useState7(null);
1415
+ const selectionDraftRef = useRef4(null);
1416
+ const [selectionError, setSelectionError] = useState9(null);
1417
+ const paintGestureRef = useRef4(null);
1418
+ const paintControllerRef = useRef4(controller);
1419
+ if (!paintGestureRef.current || paintControllerRef.current !== controller) {
1420
+ paintGestureRef.current = new PaintGesture(controller);
1421
+ paintControllerRef.current = controller;
1422
+ }
1423
+ const brushCursorRef = useRef4(null);
1424
+ const [paintError, setPaintError] = useState9(null);
1425
+ const [inpaintPrompt, setInpaintPrompt] = useState9("");
1426
+ const [inpaintMaskMode, setInpaintMaskMode] = useState9("annotations");
1427
+ const [isRemovingBg, setIsRemovingBg] = useState9(false);
1428
+ const [isInpainting, setIsInpainting] = useState9(false);
1429
+ const [aiError, setAiError] = useState9(null);
1430
+ const [aiSuccess, setAiSuccess] = useState9(null);
1431
+ const [aiTarget, setAiTarget] = useState9("auto");
1432
+ const aiOptions = aiTarget === "auto" ? {} : { target: aiTarget };
911
1433
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
912
1434
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
913
1435
  const handleRemoveBackground = async () => {
@@ -919,10 +1441,10 @@ var ImageEditor = ({
919
1441
  setAiSuccess(null);
920
1442
  setIsRemovingBg(true);
921
1443
  try {
922
- const imageDataUrl = await editor.toSourceDataURL();
1444
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
923
1445
  const result = await effectiveOnRemoveBg({ imageDataUrl });
924
1446
  if (typeof result === "string") {
925
- await editor.applyBackgroundRemovedImage(result);
1447
+ await editor.applyBackgroundRemovedImage(result, aiOptions);
926
1448
  setAiSuccess("Background removed successfully!");
927
1449
  } else {
928
1450
  setAiSuccess("Background removal complete.");
@@ -946,7 +1468,7 @@ var ImageEditor = ({
946
1468
  setAiSuccess(null);
947
1469
  setIsInpainting(true);
948
1470
  try {
949
- const imageDataUrl = await editor.toSourceDataURL();
1471
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
950
1472
  const maskDataUrl = await editor.toMaskDataURL({
951
1473
  useAnnotations: inpaintMaskMode === "annotations",
952
1474
  useCrop: inpaintMaskMode === "crop"
@@ -958,7 +1480,7 @@ var ImageEditor = ({
958
1480
  cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
959
1481
  });
960
1482
  if (typeof result === "string") {
961
- await editor.applyInpaintedImage(result);
1483
+ await editor.applyInpaintedImage(result, aiOptions);
962
1484
  setAiSuccess("Inpainting applied successfully!");
963
1485
  } else {
964
1486
  setAiSuccess("Inpainting complete.");
@@ -969,17 +1491,17 @@ var ImageEditor = ({
969
1491
  setIsInpainting(false);
970
1492
  }
971
1493
  };
972
- const [activeColor, setActiveColor] = useState7("#ef4444");
973
- const [strokeWidth, setStrokeWidth] = useState7(3);
974
- const [fontSize, setFontSize] = useState7(24);
975
- const [textInput, setTextInput] = useState7("");
976
- const draftAnnotationRef = useRef3(null);
977
- const draftCropRef = useRef3(null);
978
- const cropDragInfoRef = useRef3(null);
979
- const annotationDragInfoRef = useRef3(null);
980
- const isDrawingRef = useRef3(false);
981
- const currentPathRef = useRef3([]);
982
- const dragStartPointRef = useRef3(null);
1494
+ const [activeColor, setActiveColor] = useState9("#ef4444");
1495
+ const [strokeWidth, setStrokeWidth] = useState9(3);
1496
+ const [fontSize, setFontSize] = useState9(24);
1497
+ const [textInput, setTextInput] = useState9("");
1498
+ const draftAnnotationRef = useRef4(null);
1499
+ const draftCropRef = useRef4(null);
1500
+ const cropDragInfoRef = useRef4(null);
1501
+ const annotationDragInfoRef = useRef4(null);
1502
+ const isDrawingRef = useRef4(false);
1503
+ const currentPathRef = useRef4([]);
1504
+ const dragStartPointRef = useRef4(null);
983
1505
  const renderCanvas = useCallback2(
984
1506
  async (draftAnnotation, draftCrop) => {
985
1507
  const canvas = canvasRef.current;
@@ -995,12 +1517,13 @@ var ImageEditor = ({
995
1517
  interactive: true,
996
1518
  draftAnnotation: draftAnnotation !== void 0 ? draftAnnotation : draftAnnotationRef.current,
997
1519
  draftCrop: draftCrop !== void 0 ? draftCrop : draftCropRef.current,
998
- draftSelectionPath: selectionDraftRef.current
1520
+ draftSelectionPath: selectionDraftRef.current,
1521
+ brushCursor: activeTab === "paint" && brushCursorRef.current && controller.getState().activeTool !== "move" ? { ...brushCursorRef.current, diameter: controller.getState().paint.brush.size } : null
999
1522
  });
1000
1523
  },
1001
1524
  [controller, activeTab]
1002
1525
  );
1003
- useEffect4(() => {
1526
+ useEffect6(() => {
1004
1527
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
1005
1528
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
1006
1529
  const animId = raf(() => {
@@ -1008,12 +1531,12 @@ var ImageEditor = ({
1008
1531
  });
1009
1532
  return () => caf(animId);
1010
1533
  }, [renderCanvas, state]);
1011
- useEffect4(() => {
1534
+ useEffect6(() => {
1012
1535
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
1013
1536
  editor.setTool("crop");
1014
1537
  }
1015
1538
  }, [activeTab, editor]);
1016
- useEffect4(() => {
1539
+ useEffect6(() => {
1017
1540
  const container = containerRef.current;
1018
1541
  if (!container) return;
1019
1542
  if (typeof ResizeObserver !== "undefined") {
@@ -1039,6 +1562,53 @@ var ImageEditor = ({
1039
1562
  y: (e.clientY - rect.top) * scaleY
1040
1563
  };
1041
1564
  };
1565
+ const paintSamples = (e) => {
1566
+ const canvas = canvasRef.current;
1567
+ const native = e.nativeEvent;
1568
+ const events = typeof native.getCoalescedEvents === "function" ? native.getCoalescedEvents() : [];
1569
+ const list = events.length ? events : [native];
1570
+ const rect = canvas.getBoundingClientRect();
1571
+ const sx = rect.width > 0 ? canvas.width / rect.width : 1;
1572
+ const sy = rect.height > 0 ? canvas.height / rect.height : 1;
1573
+ const s = controller.getState();
1574
+ return list.map((ev) => ({
1575
+ ...canvasToImagePoint({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s, canvas.width, canvas.height, false),
1576
+ pressure: pointerPressure(ev)
1577
+ }));
1578
+ };
1579
+ const handlePaintPointerDown = (e) => {
1580
+ if (activeTab !== "paint" || e.button !== 0) return;
1581
+ e.preventDefault();
1582
+ try {
1583
+ e.currentTarget.setPointerCapture?.(e.pointerId);
1584
+ } catch {
1585
+ }
1586
+ const [first] = paintSamples(e);
1587
+ setPaintError(paintGestureRef.current.down(first));
1588
+ };
1589
+ const handlePaintPointerMove = (e) => {
1590
+ if (activeTab !== "paint") return;
1591
+ const samples = paintSamples(e);
1592
+ brushCursorRef.current = samples[samples.length - 1] ?? null;
1593
+ if (paintGestureRef.current.active) paintGestureRef.current.moveTo(samples);
1594
+ else void renderCanvas();
1595
+ };
1596
+ const handlePaintPointerUp = () => {
1597
+ if (activeTab !== "paint") return;
1598
+ const g = paintGestureRef.current;
1599
+ if (g.active) {
1600
+ g.up();
1601
+ if (g.error) setPaintError(g.error);
1602
+ }
1603
+ };
1604
+ useEffect6(() => {
1605
+ if (activeTab !== "paint" || typeof window === "undefined") return;
1606
+ const onKey = (e) => {
1607
+ if (handlePaintShortcut(controller, e)) e.preventDefault();
1608
+ };
1609
+ window.addEventListener("keydown", onKey);
1610
+ return () => window.removeEventListener("keydown", onKey);
1611
+ }, [activeTab, controller]);
1042
1612
  const handleMouseDown = (e) => {
1043
1613
  const canvas = canvasRef.current;
1044
1614
  if (!canvas) return;
@@ -1394,15 +1964,15 @@ var ImageEditor = ({
1394
1964
  link.click();
1395
1965
  }
1396
1966
  };
1397
- return /* @__PURE__ */ jsxs7(
1967
+ return /* @__PURE__ */ jsxs9(
1398
1968
  "div",
1399
1969
  {
1400
1970
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
1401
1971
  style,
1402
1972
  children: [
1403
- /* @__PURE__ */ jsxs7("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: [
1404
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
1405
- /* @__PURE__ */ jsx7(
1973
+ /* @__PURE__ */ jsxs9("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: [
1974
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
1975
+ /* @__PURE__ */ jsx9(
1406
1976
  "button",
1407
1977
  {
1408
1978
  type: "button",
@@ -1414,7 +1984,7 @@ var ImageEditor = ({
1414
1984
  children: "Transform & Crop"
1415
1985
  }
1416
1986
  ),
1417
- /* @__PURE__ */ jsx7(
1987
+ /* @__PURE__ */ jsx9(
1418
1988
  "button",
1419
1989
  {
1420
1990
  type: "button",
@@ -1426,7 +1996,31 @@ var ImageEditor = ({
1426
1996
  children: "Adjustments"
1427
1997
  }
1428
1998
  ),
1429
- /* @__PURE__ */ jsx7(
1999
+ /* @__PURE__ */ jsx9(
2000
+ "button",
2001
+ {
2002
+ type: "button",
2003
+ onClick: () => {
2004
+ setActiveTab("layers");
2005
+ editor.setTool("select");
2006
+ },
2007
+ className: `px-3 py-1.5 rounded-md font-medium transition-colors ${activeTab === "layers" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
2008
+ children: "Layers"
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ jsx9(
2012
+ "button",
2013
+ {
2014
+ type: "button",
2015
+ onClick: () => {
2016
+ setActiveTab("paint");
2017
+ editor.setTool("brush");
2018
+ },
2019
+ className: `px-3 py-1.5 rounded-md font-medium transition-colors ${activeTab === "paint" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
2020
+ children: "Paint"
2021
+ }
2022
+ ),
2023
+ /* @__PURE__ */ jsx9(
1430
2024
  "button",
1431
2025
  {
1432
2026
  type: "button",
@@ -1438,7 +2032,7 @@ var ImageEditor = ({
1438
2032
  children: "Select & Mask"
1439
2033
  }
1440
2034
  ),
1441
- /* @__PURE__ */ jsx7(
2035
+ /* @__PURE__ */ jsx9(
1442
2036
  "button",
1443
2037
  {
1444
2038
  type: "button",
@@ -1450,7 +2044,7 @@ var ImageEditor = ({
1450
2044
  children: "Annotations"
1451
2045
  }
1452
2046
  ),
1453
- /* @__PURE__ */ jsx7(
2047
+ /* @__PURE__ */ jsx9(
1454
2048
  "button",
1455
2049
  {
1456
2050
  type: "button",
@@ -1462,7 +2056,7 @@ var ImageEditor = ({
1462
2056
  children: "Depth Mask"
1463
2057
  }
1464
2058
  ),
1465
- /* @__PURE__ */ jsxs7(
2059
+ /* @__PURE__ */ jsxs9(
1466
2060
  "button",
1467
2061
  {
1468
2062
  type: "button",
@@ -1472,14 +2066,14 @@ var ImageEditor = ({
1472
2066
  },
1473
2067
  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"}`,
1474
2068
  children: [
1475
- /* @__PURE__ */ jsx7("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx7("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" }) }),
2069
+ /* @__PURE__ */ jsx9("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx9("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" }) }),
1476
2070
  "AI Tools"
1477
2071
  ]
1478
2072
  }
1479
2073
  )
1480
2074
  ] }),
1481
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 text-xs", children: [
1482
- /* @__PURE__ */ jsx7(
2075
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 text-xs", children: [
2076
+ /* @__PURE__ */ jsx9(
1483
2077
  "button",
1484
2078
  {
1485
2079
  type: "button",
@@ -1490,7 +2084,7 @@ var ImageEditor = ({
1490
2084
  children: "\u21A9"
1491
2085
  }
1492
2086
  ),
1493
- /* @__PURE__ */ jsx7(
2087
+ /* @__PURE__ */ jsx9(
1494
2088
  "button",
1495
2089
  {
1496
2090
  type: "button",
@@ -1501,7 +2095,7 @@ var ImageEditor = ({
1501
2095
  children: "\u21AA"
1502
2096
  }
1503
2097
  ),
1504
- /* @__PURE__ */ jsx7(
2098
+ /* @__PURE__ */ jsx9(
1505
2099
  "button",
1506
2100
  {
1507
2101
  type: "button",
@@ -1510,8 +2104,8 @@ var ImageEditor = ({
1510
2104
  children: "Reset"
1511
2105
  }
1512
2106
  ),
1513
- /* @__PURE__ */ jsx7("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1514
- /* @__PURE__ */ jsx7(
2107
+ /* @__PURE__ */ jsx9("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2108
+ /* @__PURE__ */ jsx9(
1515
2109
  "button",
1516
2110
  {
1517
2111
  type: "button",
@@ -1521,11 +2115,11 @@ var ImageEditor = ({
1521
2115
  children: "-"
1522
2116
  }
1523
2117
  ),
1524
- /* @__PURE__ */ jsxs7("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
2118
+ /* @__PURE__ */ jsxs9("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
1525
2119
  Math.round(state.zoom * 100),
1526
2120
  "%"
1527
2121
  ] }),
1528
- /* @__PURE__ */ jsx7(
2122
+ /* @__PURE__ */ jsx9(
1529
2123
  "button",
1530
2124
  {
1531
2125
  type: "button",
@@ -1535,8 +2129,8 @@ var ImageEditor = ({
1535
2129
  children: "+"
1536
2130
  }
1537
2131
  ),
1538
- /* @__PURE__ */ jsx7("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1539
- /* @__PURE__ */ jsx7(
2132
+ /* @__PURE__ */ jsx9("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2133
+ /* @__PURE__ */ jsx9(
1540
2134
  "button",
1541
2135
  {
1542
2136
  type: "button",
@@ -1547,7 +2141,7 @@ var ImageEditor = ({
1547
2141
  children: "Hold Compare"
1548
2142
  }
1549
2143
  ),
1550
- /* @__PURE__ */ jsx7(
2144
+ /* @__PURE__ */ jsx9(
1551
2145
  "button",
1552
2146
  {
1553
2147
  type: "button",
@@ -1558,13 +2152,13 @@ var ImageEditor = ({
1558
2152
  )
1559
2153
  ] })
1560
2154
  ] }),
1561
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
1562
- /* @__PURE__ */ jsx7(
2155
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
2156
+ /* @__PURE__ */ jsx9(
1563
2157
  "div",
1564
2158
  {
1565
2159
  ref: containerRef,
1566
2160
  className: "relative flex-1 h-[360px] md:h-full min-h-[320px] bg-slate-950 overflow-hidden select-none",
1567
- children: /* @__PURE__ */ jsx7(
2161
+ children: /* @__PURE__ */ jsx9(
1568
2162
  "canvas",
1569
2163
  {
1570
2164
  ref: canvasRef,
@@ -1572,16 +2166,21 @@ var ImageEditor = ({
1572
2166
  onMouseMove: handleMouseMove,
1573
2167
  onMouseUp: handleMouseUp,
1574
2168
  onMouseLeave: handleMouseUp,
1575
- className: "absolute inset-0 w-full h-full block"
2169
+ onPointerDown: handlePaintPointerDown,
2170
+ onPointerMove: handlePaintPointerMove,
2171
+ onPointerUp: handlePaintPointerUp,
2172
+ onPointerCancel: () => paintGestureRef.current?.cancel(),
2173
+ "data-testid": "image-editor-canvas",
2174
+ className: `absolute inset-0 w-full h-full block ${activeTab === "paint" ? "touch-none cursor-crosshair" : ""}`
1576
2175
  }
1577
2176
  )
1578
2177
  }
1579
2178
  ),
1580
- /* @__PURE__ */ jsxs7("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: [
1581
- activeTab === "crop" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-4 text-xs", children: [
1582
- /* @__PURE__ */ jsx7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
1583
- /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-3 gap-2", children: [
1584
- /* @__PURE__ */ jsx7(
2179
+ /* @__PURE__ */ jsxs9("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: [
2180
+ activeTab === "crop" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-4 text-xs", children: [
2181
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
2182
+ /* @__PURE__ */ jsxs9("div", { className: "grid grid-cols-3 gap-2", children: [
2183
+ /* @__PURE__ */ jsx9(
1585
2184
  "button",
1586
2185
  {
1587
2186
  type: "button",
@@ -1593,7 +2192,7 @@ var ImageEditor = ({
1593
2192
  children: "Freeform"
1594
2193
  }
1595
2194
  ),
1596
- /* @__PURE__ */ jsx7(
2195
+ /* @__PURE__ */ jsx9(
1597
2196
  "button",
1598
2197
  {
1599
2198
  type: "button",
@@ -1605,7 +2204,7 @@ var ImageEditor = ({
1605
2204
  children: "1:1 Square"
1606
2205
  }
1607
2206
  ),
1608
- /* @__PURE__ */ jsx7(
2207
+ /* @__PURE__ */ jsx9(
1609
2208
  "button",
1610
2209
  {
1611
2210
  type: "button",
@@ -1617,7 +2216,7 @@ var ImageEditor = ({
1617
2216
  children: "4:3 Standard"
1618
2217
  }
1619
2218
  ),
1620
- /* @__PURE__ */ jsx7(
2219
+ /* @__PURE__ */ jsx9(
1621
2220
  "button",
1622
2221
  {
1623
2222
  type: "button",
@@ -1629,7 +2228,7 @@ var ImageEditor = ({
1629
2228
  children: "16:9 Wide"
1630
2229
  }
1631
2230
  ),
1632
- /* @__PURE__ */ jsx7(
2231
+ /* @__PURE__ */ jsx9(
1633
2232
  "button",
1634
2233
  {
1635
2234
  type: "button",
@@ -1641,7 +2240,7 @@ var ImageEditor = ({
1641
2240
  children: "3:2 Photo"
1642
2241
  }
1643
2242
  ),
1644
- /* @__PURE__ */ jsx7(
2243
+ /* @__PURE__ */ jsx9(
1645
2244
  "button",
1646
2245
  {
1647
2246
  type: "button",
@@ -1654,10 +2253,10 @@ var ImageEditor = ({
1654
2253
  }
1655
2254
  )
1656
2255
  ] }),
1657
- /* @__PURE__ */ jsx7("div", { className: "h-px bg-slate-800 my-1" }),
1658
- /* @__PURE__ */ jsx7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
1659
- /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-2 gap-2", children: [
1660
- /* @__PURE__ */ jsx7(
2256
+ /* @__PURE__ */ jsx9("div", { className: "h-px bg-slate-800 my-1" }),
2257
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
2258
+ /* @__PURE__ */ jsxs9("div", { className: "grid grid-cols-2 gap-2", children: [
2259
+ /* @__PURE__ */ jsx9(
1661
2260
  "button",
1662
2261
  {
1663
2262
  type: "button",
@@ -1666,7 +2265,7 @@ var ImageEditor = ({
1666
2265
  children: "Rotate -90\xB0"
1667
2266
  }
1668
2267
  ),
1669
- /* @__PURE__ */ jsx7(
2268
+ /* @__PURE__ */ jsx9(
1670
2269
  "button",
1671
2270
  {
1672
2271
  type: "button",
@@ -1675,7 +2274,7 @@ var ImageEditor = ({
1675
2274
  children: "Rotate +90\xB0"
1676
2275
  }
1677
2276
  ),
1678
- /* @__PURE__ */ jsx7(
2277
+ /* @__PURE__ */ jsx9(
1679
2278
  "button",
1680
2279
  {
1681
2280
  type: "button",
@@ -1684,7 +2283,7 @@ var ImageEditor = ({
1684
2283
  children: "Flip Horizontal"
1685
2284
  }
1686
2285
  ),
1687
- /* @__PURE__ */ jsx7(
2286
+ /* @__PURE__ */ jsx9(
1688
2287
  "button",
1689
2288
  {
1690
2289
  type: "button",
@@ -1695,10 +2294,10 @@ var ImageEditor = ({
1695
2294
  )
1696
2295
  ] })
1697
2296
  ] }),
1698
- activeTab === "adjust" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-3 text-xs", children: [
1699
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
1700
- /* @__PURE__ */ jsx7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
1701
- /* @__PURE__ */ jsx7(
2297
+ activeTab === "adjust" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-3 text-xs", children: [
2298
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
2299
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
2300
+ /* @__PURE__ */ jsx9(
1702
2301
  "button",
1703
2302
  {
1704
2303
  type: "button",
@@ -1708,9 +2307,9 @@ var ImageEditor = ({
1708
2307
  }
1709
2308
  )
1710
2309
  ] }),
1711
- /* @__PURE__ */ jsx7(Histogram, { histogram: editor.histogram }),
1712
- IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ jsxs7(React6.Fragment, { children: [
1713
- /* @__PURE__ */ jsx7(
2310
+ /* @__PURE__ */ jsx9(Histogram, { histogram: editor.histogram }),
2311
+ IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ jsxs9(React8.Fragment, { children: [
2312
+ /* @__PURE__ */ jsx9(
1714
2313
  ControlSlider,
1715
2314
  {
1716
2315
  spec,
@@ -1719,14 +2318,14 @@ var ImageEditor = ({
1719
2318
  onChange: (v) => editor.setAdjustments({ [spec.id]: v })
1720
2319
  }
1721
2320
  ),
1722
- spec.id === "tint" && legacyProcess && /* @__PURE__ */ jsxs7(
2321
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ jsxs9(
1723
2322
  "div",
1724
2323
  {
1725
2324
  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",
1726
2325
  "data-testid": "image-editor-process-notice",
1727
2326
  children: [
1728
- /* @__PURE__ */ jsx7("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
1729
- /* @__PURE__ */ jsx7(
2327
+ /* @__PURE__ */ jsx9("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
2328
+ /* @__PURE__ */ jsx9(
1730
2329
  "button",
1731
2330
  {
1732
2331
  type: "button",
@@ -1739,7 +2338,7 @@ var ImageEditor = ({
1739
2338
  }
1740
2339
  )
1741
2340
  ] }, spec.id)),
1742
- /* @__PURE__ */ jsx7("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ jsx7(
2341
+ /* @__PURE__ */ jsx9("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ jsx9(
1743
2342
  AdjustmentLayersPanel,
1744
2343
  {
1745
2344
  controller,
@@ -1748,9 +2347,19 @@ var ImageEditor = ({
1748
2347
  }
1749
2348
  ) })
1750
2349
  ] }),
1751
- activeTab === "annotate" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-3 text-xs", children: [
1752
- /* @__PURE__ */ jsx7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
1753
- /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ jsx7(
2350
+ activeTab === "layers" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-3 text-xs", children: [
2351
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Layers" }),
2352
+ /* @__PURE__ */ jsx9(LayersPanel, { controller })
2353
+ ] }),
2354
+ activeTab === "paint" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-3 text-xs", children: [
2355
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Paint" }),
2356
+ /* @__PURE__ */ jsx9(BrushOptionsBar, { controller }),
2357
+ paintError && /* @__PURE__ */ jsx9("p", { role: "alert", className: "text-rose-400", "data-testid": "paint-error", children: paintError }),
2358
+ /* @__PURE__ */ jsx9("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ jsx9(LayersPanel, { controller }) })
2359
+ ] }),
2360
+ activeTab === "annotate" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-3 text-xs", children: [
2361
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
2362
+ /* @__PURE__ */ jsx9("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ jsx9(
1754
2363
  "button",
1755
2364
  {
1756
2365
  type: "button",
@@ -1760,9 +2369,9 @@ var ImageEditor = ({
1760
2369
  },
1761
2370
  t
1762
2371
  )) }),
1763
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 mt-2", children: [
1764
- /* @__PURE__ */ jsx7("label", { className: "text-slate-400", children: "Color:" }),
1765
- /* @__PURE__ */ jsx7(
2372
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 mt-2", children: [
2373
+ /* @__PURE__ */ jsx9("label", { className: "text-slate-400", children: "Color:" }),
2374
+ /* @__PURE__ */ jsx9(
1766
2375
  "input",
1767
2376
  {
1768
2377
  type: "color",
@@ -1771,15 +2380,15 @@ var ImageEditor = ({
1771
2380
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
1772
2381
  }
1773
2382
  ),
1774
- /* @__PURE__ */ jsxs7("div", { className: "flex-1 ml-2", children: [
1775
- /* @__PURE__ */ jsxs7("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
1776
- /* @__PURE__ */ jsx7("span", { children: "Width:" }),
1777
- /* @__PURE__ */ jsxs7("span", { className: "font-mono", children: [
2383
+ /* @__PURE__ */ jsxs9("div", { className: "flex-1 ml-2", children: [
2384
+ /* @__PURE__ */ jsxs9("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
2385
+ /* @__PURE__ */ jsx9("span", { children: "Width:" }),
2386
+ /* @__PURE__ */ jsxs9("span", { className: "font-mono", children: [
1778
2387
  strokeWidth,
1779
2388
  "px"
1780
2389
  ] })
1781
2390
  ] }),
1782
- /* @__PURE__ */ jsx7(
2391
+ /* @__PURE__ */ jsx9(
1783
2392
  "input",
1784
2393
  {
1785
2394
  type: "range",
@@ -1792,10 +2401,10 @@ var ImageEditor = ({
1792
2401
  )
1793
2402
  ] })
1794
2403
  ] }),
1795
- /* @__PURE__ */ jsx7("div", { className: "h-px bg-slate-800 my-1" }),
1796
- /* @__PURE__ */ jsx7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
1797
- /* @__PURE__ */ jsxs7("div", { className: "flex gap-2", children: [
1798
- /* @__PURE__ */ jsx7(
2404
+ /* @__PURE__ */ jsx9("div", { className: "h-px bg-slate-800 my-1" }),
2405
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
2406
+ /* @__PURE__ */ jsxs9("div", { className: "flex gap-2", children: [
2407
+ /* @__PURE__ */ jsx9(
1799
2408
  "input",
1800
2409
  {
1801
2410
  type: "text",
@@ -1806,7 +2415,7 @@ var ImageEditor = ({
1806
2415
  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"
1807
2416
  }
1808
2417
  ),
1809
- /* @__PURE__ */ jsx7(
2418
+ /* @__PURE__ */ jsx9(
1810
2419
  "button",
1811
2420
  {
1812
2421
  type: "button",
@@ -1816,14 +2425,14 @@ var ImageEditor = ({
1816
2425
  }
1817
2426
  )
1818
2427
  ] }),
1819
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between text-slate-400", children: [
1820
- /* @__PURE__ */ jsx7("span", { children: "Font Size:" }),
1821
- /* @__PURE__ */ jsxs7("span", { className: "font-mono", children: [
2428
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between text-slate-400", children: [
2429
+ /* @__PURE__ */ jsx9("span", { children: "Font Size:" }),
2430
+ /* @__PURE__ */ jsxs9("span", { className: "font-mono", children: [
1822
2431
  fontSize,
1823
2432
  "px"
1824
2433
  ] })
1825
2434
  ] }),
1826
- /* @__PURE__ */ jsx7(
2435
+ /* @__PURE__ */ jsx9(
1827
2436
  "input",
1828
2437
  {
1829
2438
  type: "range",
@@ -1834,14 +2443,14 @@ var ImageEditor = ({
1834
2443
  className: "w-full accent-indigo-500"
1835
2444
  }
1836
2445
  ),
1837
- /* @__PURE__ */ jsx7("div", { className: "h-px bg-slate-800 my-1" }),
1838
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
1839
- /* @__PURE__ */ jsxs7("span", { className: "text-slate-400", children: [
2446
+ /* @__PURE__ */ jsx9("div", { className: "h-px bg-slate-800 my-1" }),
2447
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
2448
+ /* @__PURE__ */ jsxs9("span", { className: "text-slate-400", children: [
1840
2449
  "Layers (",
1841
2450
  state.annotations.length,
1842
2451
  ")"
1843
2452
  ] }),
1844
- state.selectedAnnotationId && /* @__PURE__ */ jsx7(
2453
+ state.selectedAnnotationId && /* @__PURE__ */ jsx9(
1845
2454
  "button",
1846
2455
  {
1847
2456
  type: "button",
@@ -1850,7 +2459,7 @@ var ImageEditor = ({
1850
2459
  children: "Delete Selected"
1851
2460
  }
1852
2461
  ),
1853
- state.annotations.length > 0 && /* @__PURE__ */ jsx7(
2462
+ state.annotations.length > 0 && /* @__PURE__ */ jsx9(
1854
2463
  "button",
1855
2464
  {
1856
2465
  type: "button",
@@ -1861,10 +2470,10 @@ var ImageEditor = ({
1861
2470
  )
1862
2471
  ] })
1863
2472
  ] }),
1864
- activeTab === "depth" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-3 text-xs", children: [
1865
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
1866
- /* @__PURE__ */ jsx7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
1867
- /* @__PURE__ */ jsx7(
2473
+ activeTab === "depth" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-3 text-xs", children: [
2474
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
2475
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
2476
+ /* @__PURE__ */ jsx9(
1868
2477
  "button",
1869
2478
  {
1870
2479
  type: "button",
@@ -1874,8 +2483,8 @@ var ImageEditor = ({
1874
2483
  }
1875
2484
  )
1876
2485
  ] }),
1877
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
1878
- /* @__PURE__ */ jsx7(
2486
+ /* @__PURE__ */ jsxs9("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
2487
+ /* @__PURE__ */ jsx9(
1879
2488
  "input",
1880
2489
  {
1881
2490
  type: "checkbox",
@@ -1884,12 +2493,12 @@ var ImageEditor = ({
1884
2493
  className: "rounded accent-indigo-500"
1885
2494
  }
1886
2495
  ),
1887
- /* @__PURE__ */ jsx7("span", { children: "Enable Depth / Alpha Mask" })
2496
+ /* @__PURE__ */ jsx9("span", { children: "Enable Depth / Alpha Mask" })
1888
2497
  ] }),
1889
- state.depthMask.enabled && /* @__PURE__ */ jsxs7(Fragment2, { children: [
1890
- /* @__PURE__ */ jsxs7("div", { children: [
1891
- /* @__PURE__ */ jsx7("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
1892
- /* @__PURE__ */ jsx7(
2498
+ state.depthMask.enabled && /* @__PURE__ */ jsxs9(Fragment3, { children: [
2499
+ /* @__PURE__ */ jsxs9("div", { children: [
2500
+ /* @__PURE__ */ jsx9("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
2501
+ /* @__PURE__ */ jsx9(
1893
2502
  "input",
1894
2503
  {
1895
2504
  type: "text",
@@ -1900,8 +2509,8 @@ var ImageEditor = ({
1900
2509
  }
1901
2510
  )
1902
2511
  ] }),
1903
- /* @__PURE__ */ jsxs7("div", { className: "flex gap-2", children: [
1904
- /* @__PURE__ */ jsx7(
2512
+ /* @__PURE__ */ jsxs9("div", { className: "flex gap-2", children: [
2513
+ /* @__PURE__ */ jsx9(
1905
2514
  "button",
1906
2515
  {
1907
2516
  type: "button",
@@ -1910,7 +2519,7 @@ var ImageEditor = ({
1910
2519
  children: "Threshold Mode"
1911
2520
  }
1912
2521
  ),
1913
- /* @__PURE__ */ jsx7(
2522
+ /* @__PURE__ */ jsx9(
1914
2523
  "button",
1915
2524
  {
1916
2525
  type: "button",
@@ -1920,12 +2529,12 @@ var ImageEditor = ({
1920
2529
  }
1921
2530
  )
1922
2531
  ] }),
1923
- /* @__PURE__ */ jsxs7("div", { children: [
1924
- /* @__PURE__ */ jsxs7("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1925
- /* @__PURE__ */ jsx7("span", { children: "Depth Start (Lower):" }),
1926
- /* @__PURE__ */ jsx7("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
2532
+ /* @__PURE__ */ jsxs9("div", { children: [
2533
+ /* @__PURE__ */ jsxs9("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2534
+ /* @__PURE__ */ jsx9("span", { children: "Depth Start (Lower):" }),
2535
+ /* @__PURE__ */ jsx9("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
1927
2536
  ] }),
1928
- /* @__PURE__ */ jsx7(
2537
+ /* @__PURE__ */ jsx9(
1929
2538
  "input",
1930
2539
  {
1931
2540
  type: "range",
@@ -1939,12 +2548,12 @@ var ImageEditor = ({
1939
2548
  }
1940
2549
  )
1941
2550
  ] }),
1942
- /* @__PURE__ */ jsxs7("div", { children: [
1943
- /* @__PURE__ */ jsxs7("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1944
- /* @__PURE__ */ jsx7("span", { children: "Depth Stop (Upper):" }),
1945
- /* @__PURE__ */ jsx7("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
2551
+ /* @__PURE__ */ jsxs9("div", { children: [
2552
+ /* @__PURE__ */ jsxs9("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2553
+ /* @__PURE__ */ jsx9("span", { children: "Depth Stop (Upper):" }),
2554
+ /* @__PURE__ */ jsx9("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
1946
2555
  ] }),
1947
- /* @__PURE__ */ jsx7(
2556
+ /* @__PURE__ */ jsx9(
1948
2557
  "input",
1949
2558
  {
1950
2559
  type: "range",
@@ -1958,12 +2567,12 @@ var ImageEditor = ({
1958
2567
  }
1959
2568
  )
1960
2569
  ] }),
1961
- /* @__PURE__ */ jsxs7("div", { children: [
1962
- /* @__PURE__ */ jsxs7("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1963
- /* @__PURE__ */ jsx7("span", { children: "Softness:" }),
1964
- /* @__PURE__ */ jsx7("span", { className: "font-mono", children: state.depthMask.softness })
2570
+ /* @__PURE__ */ jsxs9("div", { children: [
2571
+ /* @__PURE__ */ jsxs9("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2572
+ /* @__PURE__ */ jsx9("span", { children: "Softness:" }),
2573
+ /* @__PURE__ */ jsx9("span", { className: "font-mono", children: state.depthMask.softness })
1965
2574
  ] }),
1966
- /* @__PURE__ */ jsx7(
2575
+ /* @__PURE__ */ jsx9(
1967
2576
  "input",
1968
2577
  {
1969
2578
  type: "range",
@@ -1975,8 +2584,8 @@ var ImageEditor = ({
1975
2584
  }
1976
2585
  )
1977
2586
  ] }),
1978
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
1979
- /* @__PURE__ */ jsx7(
2587
+ /* @__PURE__ */ jsxs9("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
2588
+ /* @__PURE__ */ jsx9(
1980
2589
  "input",
1981
2590
  {
1982
2591
  type: "checkbox",
@@ -1985,12 +2594,12 @@ var ImageEditor = ({
1985
2594
  className: "rounded accent-indigo-500"
1986
2595
  }
1987
2596
  ),
1988
- /* @__PURE__ */ jsx7("span", { children: "Invert Mask" })
2597
+ /* @__PURE__ */ jsx9("span", { children: "Invert Mask" })
1989
2598
  ] })
1990
2599
  ] })
1991
2600
  ] }),
1992
- activeTab === "select" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-4 text-xs", children: [
1993
- /* @__PURE__ */ jsx7(
2601
+ activeTab === "select" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-4 text-xs", children: [
2602
+ /* @__PURE__ */ jsx9(
1994
2603
  SelectionOptionsBar,
1995
2604
  {
1996
2605
  controller,
@@ -2000,51 +2609,68 @@ var ImageEditor = ({
2000
2609
  quickMask: Boolean(state.quickMask)
2001
2610
  }
2002
2611
  ),
2003
- selectionError && /* @__PURE__ */ jsx7("p", { role: "alert", className: "text-rose-400", children: selectionError }),
2004
- /* @__PURE__ */ jsx7(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2612
+ selectionError && /* @__PURE__ */ jsx9("p", { role: "alert", className: "text-rose-400", children: selectionError }),
2613
+ /* @__PURE__ */ jsx9(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2005
2614
  ] }),
2006
- activeTab === "ai" && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-4 text-xs", children: [
2007
- /* @__PURE__ */ jsxs7("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2008
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
2009
- /* @__PURE__ */ jsxs7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2010
- /* @__PURE__ */ jsx7("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx7("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" }) }),
2615
+ activeTab === "ai" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-4 text-xs", children: [
2616
+ /* @__PURE__ */ jsxs9("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
2617
+ /* @__PURE__ */ jsx9("span", { children: "Results go to" }),
2618
+ /* @__PURE__ */ jsxs9(
2619
+ "select",
2620
+ {
2621
+ "aria-label": "AI result target",
2622
+ value: aiTarget,
2623
+ onChange: (e) => setAiTarget(e.target.value),
2624
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
2625
+ children: [
2626
+ /* @__PURE__ */ jsx9("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
2627
+ /* @__PURE__ */ jsx9("option", { value: "new-layer", children: "New layer / mask" }),
2628
+ /* @__PURE__ */ jsx9("option", { value: "replace", children: "Replace image" })
2629
+ ]
2630
+ }
2631
+ )
2632
+ ] }),
2633
+ /* @__PURE__ */ jsxs9("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2634
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
2635
+ /* @__PURE__ */ jsxs9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2636
+ /* @__PURE__ */ jsx9("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx9("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" }) }),
2011
2637
  "Background Removal"
2012
2638
  ] }),
2013
- isRemovingBg && /* @__PURE__ */ jsx7("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..." })
2639
+ isRemovingBg && /* @__PURE__ */ jsx9("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..." })
2014
2640
  ] }),
2015
- /* @__PURE__ */ jsx7("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2016
- /* @__PURE__ */ jsx7(
2641
+ /* @__PURE__ */ jsx9("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2642
+ /* @__PURE__ */ jsx9(
2017
2643
  "button",
2018
2644
  {
2019
2645
  type: "button",
2020
2646
  onClick: handleRemoveBackground,
2021
2647
  disabled: isRemovingBg || isInpainting,
2022
2648
  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",
2023
- children: isRemovingBg ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2024
- /* @__PURE__ */ jsx7("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2649
+ children: isRemovingBg ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
2650
+ /* @__PURE__ */ jsx9("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2025
2651
  "Removing Background..."
2026
- ] }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
2027
- /* @__PURE__ */ jsx7("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx7("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" }) }),
2652
+ ] }) : /* @__PURE__ */ jsxs9(Fragment3, { children: [
2653
+ /* @__PURE__ */ jsx9("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx9("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" }) }),
2028
2654
  "Remove Background"
2029
2655
  ] })
2030
2656
  }
2031
2657
  )
2032
2658
  ] }),
2033
- /* @__PURE__ */ jsxs7("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2034
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
2035
- /* @__PURE__ */ jsxs7("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2036
- /* @__PURE__ */ jsxs7("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2037
- /* @__PURE__ */ jsx7("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" }),
2038
- /* @__PURE__ */ jsx7("path", { d: "m14 7 3 3" })
2659
+ /* @__PURE__ */ jsxs9("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2660
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
2661
+ /* @__PURE__ */ jsxs9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2662
+ /* @__PURE__ */ jsxs9("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2663
+ /* @__PURE__ */ jsx9("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" }),
2664
+ /* @__PURE__ */ jsx9("path", { d: "m14 7 3 3" })
2039
2665
  ] }),
2040
2666
  "Generative Inpainting"
2041
2667
  ] }),
2042
- isInpainting && /* @__PURE__ */ jsx7("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..." })
2668
+ isInpainting && /* @__PURE__ */ jsx9("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..." })
2043
2669
  ] }),
2044
- /* @__PURE__ */ jsx7("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
2045
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
2046
- /* @__PURE__ */ jsx7("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2047
- /* @__PURE__ */ jsx7(
2670
+ /* @__PURE__ */ jsx9("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
2671
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1", children: [
2672
+ /* @__PURE__ */ jsx9("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2673
+ /* @__PURE__ */ jsx9(
2048
2674
  "textarea",
2049
2675
  {
2050
2676
  id: "ai-inpaint-prompt-react",
@@ -2056,10 +2682,10 @@ var ImageEditor = ({
2056
2682
  }
2057
2683
  )
2058
2684
  ] }),
2059
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
2060
- /* @__PURE__ */ jsx7("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2061
- /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-2 gap-1.5", children: [
2062
- /* @__PURE__ */ jsxs7(
2685
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1", children: [
2686
+ /* @__PURE__ */ jsx9("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2687
+ /* @__PURE__ */ jsxs9("div", { className: "grid grid-cols-2 gap-1.5", children: [
2688
+ /* @__PURE__ */ jsxs9(
2063
2689
  "button",
2064
2690
  {
2065
2691
  type: "button",
@@ -2072,7 +2698,7 @@ var ImageEditor = ({
2072
2698
  ]
2073
2699
  }
2074
2700
  ),
2075
- /* @__PURE__ */ jsxs7(
2701
+ /* @__PURE__ */ jsxs9(
2076
2702
  "button",
2077
2703
  {
2078
2704
  type: "button",
@@ -2087,25 +2713,25 @@ var ImageEditor = ({
2087
2713
  )
2088
2714
  ] })
2089
2715
  ] }),
2090
- /* @__PURE__ */ jsx7(
2716
+ /* @__PURE__ */ jsx9(
2091
2717
  "button",
2092
2718
  {
2093
2719
  type: "button",
2094
2720
  onClick: handleInpaint,
2095
2721
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
2096
2722
  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",
2097
- children: isInpainting ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2098
- /* @__PURE__ */ jsx7("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2723
+ children: isInpainting ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
2724
+ /* @__PURE__ */ jsx9("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2099
2725
  "Generating Inpaint..."
2100
- ] }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
2101
- /* @__PURE__ */ jsx7("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx7("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" }) }),
2726
+ ] }) : /* @__PURE__ */ jsxs9(Fragment3, { children: [
2727
+ /* @__PURE__ */ jsx9("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx9("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" }) }),
2102
2728
  "Apply Inpainting"
2103
2729
  ] })
2104
2730
  }
2105
2731
  )
2106
2732
  ] }),
2107
- aiError && /* @__PURE__ */ jsx7("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
2108
- aiSuccess && /* @__PURE__ */ jsx7("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
2733
+ aiError && /* @__PURE__ */ jsx9("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
2734
+ aiSuccess && /* @__PURE__ */ jsx9("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
2109
2735
  ] })
2110
2736
  ] })
2111
2737
  ] })
@@ -2121,8 +2747,10 @@ export {
2121
2747
  useImageHistogram,
2122
2748
  CurvesEditor,
2123
2749
  AdjustmentLayersPanel,
2750
+ LayersPanel,
2751
+ BrushOptionsBar,
2124
2752
  SelectionOptionsBar,
2125
2753
  RefineEdgePanel,
2126
2754
  ImageEditor
2127
2755
  };
2128
- //# sourceMappingURL=chunk-ZU54BMDC.js.map
2756
+ //# sourceMappingURL=chunk-OSOGA2OJ.js.map