@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.
@@ -31,10 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var image_editor_exports = {};
32
32
  __export(image_editor_exports, {
33
33
  AdjustmentLayersPanel: () => AdjustmentLayersPanel,
34
+ BrushOptionsBar: () => BrushOptionsBar,
34
35
  ControlSlider: () => ControlSlider,
35
36
  CurvesEditor: () => CurvesEditor,
36
37
  Histogram: () => Histogram,
37
38
  ImageEditor: () => ImageEditor,
39
+ LayersPanel: () => LayersPanel,
38
40
  RefineEdgePanel: () => RefineEdgePanel,
39
41
  SelectionOptionsBar: () => SelectionOptionsBar,
40
42
  useImageEditor: () => useImageEditor,
@@ -138,11 +140,11 @@ function useImageEditor(options = {}) {
138
140
  [controller]
139
141
  );
140
142
  const applyInpaintedImage = (0, import_react.useCallback)(
141
- (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
143
+ (newDataUrl, options2) => controller.applyInpaintedImage(newDataUrl, options2),
142
144
  [controller]
143
145
  );
144
146
  const applyBackgroundRemovedImage = (0, import_react.useCallback)(
145
- (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
147
+ (newDataUrl, options2) => controller.applyBackgroundRemovedImage(newDataUrl, options2),
146
148
  [controller]
147
149
  );
148
150
  return (0, import_react.useMemo)(
@@ -201,8 +203,8 @@ function useImageEditor(options = {}) {
201
203
  }
202
204
 
203
205
  // src/image-editor/ImageEditor.tsx
204
- var import_react7 = __toESM(require("react"), 1);
205
- var import_image_editor8 = require("@pixerate/editor/image-editor");
206
+ var import_react9 = __toESM(require("react"), 1);
207
+ var import_image_editor10 = require("@pixerate/editor/image-editor");
206
208
 
207
209
  // src/image-editor/ControlSlider.tsx
208
210
  var import_image_editor2 = require("@pixerate/editor/image-editor");
@@ -633,10 +635,492 @@ var AdjustmentLayersPanel = ({ controller, layers: layersProp, histogram }) => {
633
635
  ] });
634
636
  };
635
637
 
636
- // src/image-editor/SelectionOptionsBar.tsx
638
+ // src/image-editor/LayersPanel.tsx
637
639
  var import_react5 = require("react");
638
640
  var import_image_editor6 = require("@pixerate/editor/image-editor");
639
641
  var import_jsx_runtime5 = require("react/jsx-runtime");
642
+ var iconButton = "px-1.5 py-0.5 rounded text-[10px] bg-slate-800 text-slate-300 hover:text-white disabled:opacity-40";
643
+ 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"}`;
644
+ var Thumbnail = ({
645
+ pixels,
646
+ size,
647
+ label,
648
+ onClick,
649
+ selected,
650
+ testId
651
+ }) => {
652
+ const ref = (0, import_react5.useRef)(null);
653
+ (0, import_react5.useEffect)(() => {
654
+ const canvas = ref.current;
655
+ const ctx = canvas?.getContext("2d");
656
+ if (!canvas || !ctx || !pixels || typeof ctx.createImageData !== "function") return;
657
+ canvas.width = pixels.width;
658
+ canvas.height = pixels.height;
659
+ const img = ctx.createImageData(pixels.width, pixels.height);
660
+ img.data.set(pixels.data);
661
+ ctx.putImageData(img, 0, 0);
662
+ }, [pixels]);
663
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
664
+ "canvas",
665
+ {
666
+ ref,
667
+ role: onClick ? "button" : "img",
668
+ "aria-label": label,
669
+ "data-testid": testId,
670
+ onClick,
671
+ style: { width: size, height: size },
672
+ 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"}`
673
+ }
674
+ );
675
+ };
676
+ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", className }) => {
677
+ const [, setTick] = (0, import_react5.useState)(0);
678
+ (0, import_react5.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
679
+ const [error, setError] = (0, import_react5.useState)(null);
680
+ const [renaming, setRenaming] = (0, import_react5.useState)(null);
681
+ const [menu, setMenu] = (0, import_react5.useState)(null);
682
+ const [drag, setDrag] = (0, import_react5.useState)(null);
683
+ const [psdNotes, setPsdNotes] = (0, import_react5.useState)([]);
684
+ const fileInput = (0, import_react5.useRef)(null);
685
+ const psdTask = async (fn) => {
686
+ try {
687
+ setError(null);
688
+ setPsdNotes(await fn());
689
+ } catch (err) {
690
+ setError(err instanceof Error ? err.message : String(err));
691
+ }
692
+ };
693
+ const commitNext = (0, import_react5.useRef)(true);
694
+ const state = controller.getState();
695
+ const rows = (0, import_image_editor6.layerPanelRows)(state.layers, state.activeLayerId);
696
+ const active = controller.getActiveLayer();
697
+ const attempt = (fn) => {
698
+ try {
699
+ setError(null);
700
+ fn();
701
+ } catch (err) {
702
+ setError(err instanceof Error ? err.message : String(err));
703
+ }
704
+ };
705
+ const run = async (action) => {
706
+ setMenu(null);
707
+ setError(await (0, import_image_editor6.runLayerMenuAction)(controller, action));
708
+ };
709
+ const prop = (key, value) => attempt(() => {
710
+ controller.setLayerProperties(active.id, { [key]: value }, commitNext.current);
711
+ commitNext.current = false;
712
+ });
713
+ const sliderEvents = {
714
+ onPointerDown: () => commitNext.current = true,
715
+ onPointerUp: () => commitNext.current = true,
716
+ onKeyDown: () => commitNext.current = true
717
+ };
718
+ const onDrop = (e, row) => {
719
+ e.preventDefault();
720
+ const id = e.dataTransfer.getData("text/x-pixerate-layer") || drag;
721
+ setDrag(null);
722
+ if (!id) return;
723
+ const rect = e.currentTarget.getBoundingClientRect();
724
+ const fraction = rect.height ? (e.clientY - rect.top) / rect.height : 0.5;
725
+ const placement = (0, import_image_editor6.dropPlacement)(state.layers, id, row.layer.id, (0, import_image_editor6.dropZone)(fraction, row.layer.type === "group"));
726
+ if (placement) attempt(() => controller.moveLayer(id, placement));
727
+ };
728
+ const thumb = (layer) => layer.type === "pixel" ? controller.getLayerThumbnail(layer.id, { maxSide: 64 }) : null;
729
+ const maskThumb = (layer) => layer.mask && !layer.mask.source ? controller.getLayerThumbnail(layer.id, { maxSide: 64, mask: true }) : null;
730
+ const locked = (layer) => Boolean(layer.locks?.all || layer.locks?.pixels || layer.locks?.position || layer.locks?.transparency);
731
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "layers-panel", onClick: () => menu && setMenu(null), children: [
732
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap items-center gap-1", role: "toolbar", "aria-label": "Layer actions", children: [
733
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-layer"), children: "New Layer" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-group"), children: "New Group" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("duplicate"), children: "Duplicate" }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("add-mask"), children: "Add Mask" }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("delete"), children: "Delete" }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
739
+ "button",
740
+ {
741
+ type: "button",
742
+ className: iconButton,
743
+ "aria-label": "Layer menu",
744
+ "aria-haspopup": "menu",
745
+ onClick: (e) => {
746
+ e.stopPropagation();
747
+ const r = e.currentTarget.getBoundingClientRect();
748
+ setMenu(menu ? null : { x: r.left, y: r.bottom });
749
+ },
750
+ children: "\u22EF"
751
+ }
752
+ ),
753
+ psd && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
754
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => fileInput.current?.click(), children: "Open PSD\u2026" }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => psdTask(async () => (await (0, import_image_editor6.downloadPsd)(controller, psdFileName)).warnings), children: "Export PSD" }),
756
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
757
+ "input",
758
+ {
759
+ ref: fileInput,
760
+ type: "file",
761
+ accept: import_image_editor6.PSD_FILE_ACCEPT,
762
+ className: "hidden",
763
+ "data-testid": "psd-file-input",
764
+ "aria-label": "Open a PSD file",
765
+ onChange: (e) => {
766
+ const file = e.currentTarget.files?.[0];
767
+ e.currentTarget.value = "";
768
+ if (file) psdTask(() => (0, import_image_editor6.openPsdFile)(controller, file));
769
+ }
770
+ }
771
+ )
772
+ ] })
773
+ ] }),
774
+ psdNotes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("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: [
775
+ psdNotes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: n }, n)),
776
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "underline", onClick: () => setPsdNotes([]), children: "Dismiss" }) })
777
+ ] }),
778
+ active && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
779
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
781
+ "Opacity ",
782
+ Math.round(active.opacity * 100),
783
+ "%"
784
+ ] }),
785
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
786
+ "input",
787
+ {
788
+ type: "range",
789
+ min: 0,
790
+ max: 100,
791
+ "aria-label": "Layer opacity",
792
+ value: Math.round(active.opacity * 100),
793
+ ...sliderEvents,
794
+ onChange: (e) => prop("opacity", Number(e.target.value) / 100),
795
+ className: "accent-indigo-500"
796
+ }
797
+ )
798
+ ] }),
799
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
801
+ "Fill ",
802
+ Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
803
+ "%"
804
+ ] }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
806
+ "input",
807
+ {
808
+ type: "range",
809
+ min: 0,
810
+ max: 100,
811
+ "aria-label": "Layer fill",
812
+ disabled: active.type !== "pixel",
813
+ value: Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
814
+ ...sliderEvents,
815
+ onChange: (e) => prop("fillOpacity", Number(e.target.value) / 100),
816
+ className: "accent-indigo-500 disabled:opacity-40"
817
+ }
818
+ )
819
+ ] }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
821
+ "select",
822
+ {
823
+ "aria-label": "Layer blend mode",
824
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
825
+ value: active.blend,
826
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { blend: e.target.value })),
827
+ children: [
828
+ active.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "pass-through", children: "Pass Through" }),
829
+ import_image_editor6.BLEND_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: m.id, children: m.label }, m.id))
830
+ ]
831
+ }
832
+ ),
833
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
834
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
835
+ "input",
836
+ {
837
+ type: "checkbox",
838
+ "aria-label": "Clip to layer below",
839
+ checked: Boolean(active.clipped),
840
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { clipped: e.target.checked })),
841
+ className: "accent-indigo-500"
842
+ }
843
+ ),
844
+ "Clip to below"
845
+ ] }),
846
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1", children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
848
+ "input",
849
+ {
850
+ type: "checkbox",
851
+ "aria-label": `Lock ${k}`,
852
+ checked: Boolean(active.locks?.[k]),
853
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { locks: { ...active.locks, [k]: e.target.checked } })),
854
+ className: "accent-indigo-500"
855
+ }
856
+ ),
857
+ k
858
+ ] }, k)) })
859
+ ] }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "flex flex-col gap-0.5", role: "tree", "aria-label": "Layers", children: rows.map((row) => {
861
+ const { layer } = row;
862
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
863
+ "li",
864
+ {
865
+ role: "treeitem",
866
+ "aria-selected": row.active,
867
+ "aria-level": row.depth + 1,
868
+ "data-layer-id": layer.id,
869
+ draggable: !layer.locks?.all,
870
+ onDragStart: (e) => {
871
+ e.dataTransfer.setData("text/x-pixerate-layer", layer.id);
872
+ setDrag(layer.id);
873
+ },
874
+ onDragOver: (e) => e.preventDefault(),
875
+ onDrop: (e) => onDrop(e, row),
876
+ tabIndex: 0,
877
+ onClick: () => attempt(() => controller.setActiveLayer(layer.id, "pixels")),
878
+ onKeyDown: (e) => {
879
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
880
+ e.preventDefault();
881
+ attempt(() => controller.setActiveLayer(layer.id, "pixels"));
882
+ }
883
+ },
884
+ onContextMenu: (e) => {
885
+ e.preventDefault();
886
+ controller.setActiveLayer(layer.id, state.activeLayerId === layer.id ? state.activeTarget : "pixels");
887
+ setMenu({ x: e.clientX, y: e.clientY });
888
+ },
889
+ className: rowClass(row.active),
890
+ style: { paddingLeft: 6 + row.depth * 14 },
891
+ children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
893
+ "button",
894
+ {
895
+ type: "button",
896
+ "aria-label": layer.visible ? `Hide ${row.label}` : `Show ${row.label}`,
897
+ "aria-pressed": layer.visible,
898
+ className: "w-4 text-center text-slate-300",
899
+ onClick: (e) => {
900
+ e.stopPropagation();
901
+ attempt(() => controller.setLayerProperties(layer.id, { visible: !layer.visible }));
902
+ },
903
+ children: layer.visible ? "\u25CF" : "\u25CB"
904
+ }
905
+ ),
906
+ layer.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ "aria-label": layer.collapsed ? `Expand ${row.label}` : `Collapse ${row.label}`,
911
+ className: "w-3 text-slate-400",
912
+ onClick: (e) => {
913
+ e.stopPropagation();
914
+ attempt(() => controller.setLayerProperties(layer.id, { collapsed: !layer.collapsed }));
915
+ },
916
+ children: layer.collapsed ? "\u25B8" : "\u25BE"
917
+ }
918
+ ),
919
+ row.clipped && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-slate-500", "aria-label": "Clipped", children: "\u21B3" }),
920
+ layer.type === "pixel" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
921
+ Thumbnail,
922
+ {
923
+ pixels: thumb(layer),
924
+ size: thumbnailSize,
925
+ label: `${row.label} pixels`,
926
+ selected: row.active && state.activeTarget === "pixels" && Boolean(layer.mask)
927
+ }
928
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
929
+ "span",
930
+ {
931
+ className: "shrink-0 grid place-items-center rounded bg-slate-800 text-[9px] text-slate-400 ring-1 ring-slate-700",
932
+ style: { width: thumbnailSize, height: thumbnailSize },
933
+ children: (0, import_image_editor6.layerBadge)(layer)
934
+ }
935
+ ),
936
+ layer.mask && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
937
+ Thumbnail,
938
+ {
939
+ pixels: maskThumb(layer),
940
+ size: thumbnailSize,
941
+ label: `${row.label} mask${layer.mask.enabled === false ? " (disabled)" : ""}`,
942
+ testId: "layer-mask-thumbnail",
943
+ selected: row.active && state.activeTarget === "mask",
944
+ onClick: (e) => {
945
+ e.stopPropagation();
946
+ if (e.shiftKey) attempt(() => controller.setLayerMaskProps(layer.id, { enabled: layer.mask.enabled === false }));
947
+ else attempt(() => controller.setActiveLayer(layer.id, "mask"));
948
+ }
949
+ }
950
+ ),
951
+ renaming === layer.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
952
+ "input",
953
+ {
954
+ autoFocus: true,
955
+ "aria-label": "Layer name",
956
+ defaultValue: layer.name || row.label,
957
+ className: "flex-1 min-w-0 bg-slate-900 border border-slate-600 rounded px-1",
958
+ onClick: (e) => e.stopPropagation(),
959
+ onBlur: (e) => {
960
+ setRenaming(null);
961
+ if (e.target.value.trim() && e.target.value !== layer.name) attempt(() => controller.setLayerProperties(layer.id, { name: e.target.value }));
962
+ },
963
+ onKeyDown: (e) => {
964
+ if (e.key === "Enter") e.target.blur();
965
+ if (e.key === "Escape") setRenaming(null);
966
+ }
967
+ }
968
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
969
+ "span",
970
+ {
971
+ className: `flex-1 min-w-0 truncate ${layer.visible ? "" : "text-slate-500"}`,
972
+ onDoubleClick: (e) => {
973
+ e.stopPropagation();
974
+ setRenaming(layer.id);
975
+ },
976
+ children: row.label
977
+ }
978
+ ),
979
+ locked(layer) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-label": "Locked", className: "text-slate-400", children: "\u{1F512}" }),
980
+ layer.blend !== "normal" && layer.blend !== "pass-through" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[9px] text-slate-500", children: layer.blend })
981
+ ]
982
+ },
983
+ layer.id
984
+ );
985
+ }) }),
986
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
987
+ menu && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
988
+ "div",
989
+ {
990
+ role: "menu",
991
+ "aria-label": "Layer menu",
992
+ className: "fixed z-50 min-w-44 rounded border border-slate-700 bg-slate-900 py-1 shadow-xl",
993
+ style: { left: menu.x, top: menu.y },
994
+ onClick: (e) => e.stopPropagation(),
995
+ onKeyDown: (e) => e.key === "Escape" && setMenu(null),
996
+ children: (0, import_image_editor6.layerMenu)(controller).map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
997
+ "button",
998
+ {
999
+ type: "button",
1000
+ role: "menuitem",
1001
+ disabled: !item.enabled,
1002
+ className: "block w-full px-3 py-1 text-left hover:bg-slate-800 disabled:opacity-40",
1003
+ onClick: () => run(item.action),
1004
+ children: item.label
1005
+ },
1006
+ item.action
1007
+ ))
1008
+ }
1009
+ )
1010
+ ] });
1011
+ };
1012
+
1013
+ // src/image-editor/BrushOptionsBar.tsx
1014
+ var import_react6 = require("react");
1015
+ var import_image_editor7 = require("@pixerate/editor/image-editor");
1016
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1017
+ 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"}`;
1018
+ var BrushOptionsBar = ({ controller, className }) => {
1019
+ const [, setTick] = (0, import_react6.useState)(0);
1020
+ (0, import_react6.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
1021
+ const [error, setError] = (0, import_react6.useState)(null);
1022
+ const state = controller.getState();
1023
+ const { brush, foreground, background } = state.paint;
1024
+ const attempt = (fn) => {
1025
+ try {
1026
+ setError(null);
1027
+ fn();
1028
+ } catch (err) {
1029
+ setError(err instanceof Error ? err.message : String(err));
1030
+ }
1031
+ };
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "brush-options", children: [
1033
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-1", role: "toolbar", "aria-label": "Paint tools", children: import_image_editor7.PAINT_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1034
+ "button",
1035
+ {
1036
+ type: "button",
1037
+ "aria-pressed": state.activeTool === t.id,
1038
+ title: `${t.label} (${t.shortcut})`,
1039
+ className: chip(state.activeTool === t.id),
1040
+ onClick: () => controller.setTool(t.id),
1041
+ children: t.label
1042
+ },
1043
+ t.id
1044
+ )) }),
1045
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", role: "group", "aria-label": "Colours", children: [
1046
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1047
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1048
+ "input",
1049
+ {
1050
+ type: "color",
1051
+ "aria-label": "Foreground colour",
1052
+ value: foreground,
1053
+ onChange: (e) => controller.setPaintOptions({ foreground: e.target.value }),
1054
+ className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1055
+ }
1056
+ ),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "FG" })
1058
+ ] }),
1059
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1060
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1061
+ "input",
1062
+ {
1063
+ type: "color",
1064
+ "aria-label": "Background colour",
1065
+ value: background,
1066
+ onChange: (e) => controller.setPaintOptions({ background: e.target.value }),
1067
+ className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "BG" })
1071
+ ] }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Swap colours (X)", className: chip(false), onClick: () => controller.swapPaintColors(), children: "Swap" }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Default colours (D)", className: chip(false), onClick: () => controller.resetPaintColors(), children: "Default" })
1074
+ ] }),
1075
+ import_image_editor7.BRUSH_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1076
+ ControlSlider,
1077
+ {
1078
+ spec,
1079
+ value: brush[spec.id],
1080
+ onChange: (v) => controller.setPaintOptions({ brush: { [spec.id]: v } })
1081
+ },
1082
+ spec.id
1083
+ )),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap gap-3 text-slate-300", role: "group", "aria-label": "Pressure", children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1086
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1087
+ "input",
1088
+ {
1089
+ type: "checkbox",
1090
+ "aria-label": "Pressure controls size",
1091
+ checked: brush.pressureSize,
1092
+ onChange: (e) => controller.setPaintOptions({ brush: { pressureSize: e.target.checked } }),
1093
+ className: "accent-indigo-500"
1094
+ }
1095
+ ),
1096
+ "Pressure size"
1097
+ ] }),
1098
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1100
+ "input",
1101
+ {
1102
+ type: "checkbox",
1103
+ "aria-label": "Pressure controls opacity",
1104
+ checked: brush.pressureOpacity,
1105
+ onChange: (e) => controller.setPaintOptions({ brush: { pressureOpacity: e.target.checked } }),
1106
+ className: "accent-indigo-500"
1107
+ }
1108
+ ),
1109
+ "Pressure opacity"
1110
+ ] })
1111
+ ] }),
1112
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-1", children: [
1113
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.fillPixels()), children: "Fill" }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.clearPixels()), children: "Clear" })
1115
+ ] }),
1116
+ error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "text-rose-400", children: error })
1117
+ ] });
1118
+ };
1119
+
1120
+ // src/image-editor/SelectionOptionsBar.tsx
1121
+ var import_react7 = require("react");
1122
+ var import_image_editor8 = require("@pixerate/editor/image-editor");
1123
+ var import_jsx_runtime7 = require("react/jsx-runtime");
640
1124
  var MODIFY_OPS = [
641
1125
  { op: "grow", label: "Grow" },
642
1126
  { op: "shrink", label: "Shrink" },
@@ -653,20 +1137,20 @@ var SelectionOptionsBar = ({
653
1137
  quickMask,
654
1138
  className = ""
655
1139
  }) => {
656
- const [radius, setRadius] = (0, import_react5.useState)(2);
1140
+ const [radius, setRadius] = (0, import_react7.useState)(2);
657
1141
  const set = (patch) => onOptionsChange({ ...options, ...patch });
658
1142
  const sel = selection === void 0 ? controller.getSelection() : selection;
659
1143
  const qm = quickMask ?? Boolean(controller.getState().quickMask);
660
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
661
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { role: "group", "aria-label": "Selection tool", className: "flex flex-wrap gap-1", children: import_image_editor6.SELECTION_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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)) }),
662
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { role: "group", "aria-label": "Selection mode", className: "flex flex-wrap gap-1", children: import_image_editor6.SELECTION_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", "aria-pressed": options.mode === m, className: btn(options.mode === m), onClick: () => set({ mode: m }), children: import_image_editor6.SELECTION_MODE_LABELS[m] }, m)) }),
663
- options.tool === "wand" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-2", children: [
664
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-1", children: [
665
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
666
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Tolerance" }),
667
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-mono", children: options.tolerance })
1144
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { role: "group", "aria-label": "Selection tool", className: "flex flex-wrap gap-1", children: import_image_editor8.SELECTION_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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)) }),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { role: "group", "aria-label": "Selection mode", className: "flex flex-wrap gap-1", children: import_image_editor8.SELECTION_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "aria-pressed": options.mode === m, className: btn(options.mode === m), onClick: () => set({ mode: m }), children: import_image_editor8.SELECTION_MODE_LABELS[m] }, m)) }),
1147
+ options.tool === "wand" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-2", children: [
1148
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex flex-col gap-1", children: [
1149
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Tolerance" }),
1151
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: options.tolerance })
668
1152
  ] }),
669
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1153
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
670
1154
  "input",
671
1155
  {
672
1156
  type: "range",
@@ -680,29 +1164,45 @@ var SelectionOptionsBar = ({
680
1164
  }
681
1165
  )
682
1166
  ] }),
683
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
684
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1168
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
685
1169
  "Contiguous"
686
1170
  ] }),
687
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
688
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
1171
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1172
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
689
1173
  "Sample adjusted image"
1174
+ ] }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1176
+ "Sample",
1177
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1178
+ "select",
1179
+ {
1180
+ "aria-label": "Sample from",
1181
+ value: options.sample ?? "composite",
1182
+ onChange: (e) => set({ sample: e.target.value }),
1183
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5",
1184
+ children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "composite", children: "All layers" }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "active-layer", children: "Active layer" })
1187
+ ]
1188
+ }
1189
+ )
690
1190
  ] })
691
1191
  ] }),
692
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
693
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
1192
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1193
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
694
1194
  "Anti-alias"
695
1195
  ] }),
696
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
697
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
698
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
699
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
700
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
1196
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1197
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
1198
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
1199
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
1200
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
701
1201
  ] }),
702
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
703
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-slate-400", children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1203
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1 text-slate-400", children: [
704
1204
  "Radius",
705
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1205
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
706
1206
  "input",
707
1207
  {
708
1208
  type: "number",
@@ -716,24 +1216,24 @@ var SelectionOptionsBar = ({
716
1216
  }
717
1217
  )
718
1218
  ] }),
719
- MODIFY_OPS.map(({ op, label }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.modifySelection(op, radius), children: label }, op))
1219
+ MODIFY_OPS.map(({ op, label }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.modifySelection(op, radius), children: label }, op))
720
1220
  ] }),
721
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { "data-testid": "selection-status", className: "text-slate-400", children: (0, import_image_editor6.describeSelection)(sel) })
1221
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { "data-testid": "selection-status", className: "text-slate-400", children: (0, import_image_editor8.describeSelection)(sel) })
722
1222
  ] });
723
1223
  };
724
1224
 
725
1225
  // src/image-editor/RefineEdgePanel.tsx
726
- var import_react6 = require("react");
727
- var import_image_editor7 = require("@pixerate/editor/image-editor");
728
- var import_jsx_runtime6 = require("react/jsx-runtime");
1226
+ var import_react8 = require("react");
1227
+ var import_image_editor9 = require("@pixerate/editor/image-editor");
1228
+ var import_jsx_runtime8 = require("react/jsx-runtime");
729
1229
  var DEFAULT_PARAMS = { radius: 8, smartRadius: false, smooth: 0, feather: 0, contrast: 0, shiftEdge: 0 };
730
1230
  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";
731
1231
  var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
732
- const [params, setParams] = (0, import_react6.useState)(DEFAULT_PARAMS);
733
- const [busy, setBusy] = (0, import_react6.useState)(false);
734
- const [error, setError] = (0, import_react6.useState)(null);
735
- const [layerId, setLayerId] = (0, import_react6.useState)("");
736
- const [newKind, setNewKind] = (0, import_react6.useState)("hueSaturation");
1232
+ const [params, setParams] = (0, import_react8.useState)(DEFAULT_PARAMS);
1233
+ const [busy, setBusy] = (0, import_react8.useState)(false);
1234
+ const [error, setError] = (0, import_react8.useState)(null);
1235
+ const [layerId, setLayerId] = (0, import_react8.useState)("");
1236
+ const [newKind, setNewKind] = (0, import_react8.useState)("hueSaturation");
737
1237
  const sel = selection === void 0 ? controller.getSelection() : selection;
738
1238
  const list = layers ?? controller.getAdjustmentLayers();
739
1239
  const target = list.find((l) => l.id === layerId) ?? list[list.length - 1];
@@ -748,17 +1248,17 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
748
1248
  setBusy(false);
749
1249
  }
750
1250
  };
751
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
752
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
753
- import_image_editor7.REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex flex-col gap-1", children: [
754
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
755
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: c.label }),
756
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "font-mono", children: [
1251
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
1253
+ import_image_editor9.REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1255
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: c.label }),
1256
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
757
1257
  params[c.id],
758
1258
  c.unit
759
1259
  ] })
760
1260
  ] }),
761
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1261
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
762
1262
  "input",
763
1263
  {
764
1264
  type: "range",
@@ -772,67 +1272,67 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
772
1272
  }
773
1273
  )
774
1274
  ] }, c.id)),
775
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
776
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
1275
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1276
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
777
1277
  "Smart Radius"
778
1278
  ] }),
779
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-1", children: [
780
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
781
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
1279
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-1", children: [
1280
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
782
1282
  ] }),
783
- error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
784
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
785
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-1 items-center", children: [
786
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1283
+ error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
1284
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
1285
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-1 items-center", children: [
1286
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
787
1287
  "select",
788
1288
  {
789
1289
  "aria-label": "New masked layer kind",
790
1290
  value: newKind,
791
1291
  onChange: (e) => setNewKind(e.target.value),
792
1292
  className: "flex-1 bg-slate-950 border border-slate-800 rounded px-1 py-1 text-slate-200",
793
- children: import_image_editor7.ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: k.kind, children: k.label }, k.kind))
1293
+ children: import_image_editor9.ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: k.kind, children: k.label }, k.kind))
794
1294
  }
795
1295
  ),
796
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1296
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
797
1297
  "button",
798
1298
  {
799
1299
  type: "button",
800
1300
  className: button,
801
1301
  disabled: !sel,
802
- onClick: () => sel && setLayerId(controller.addAdjustmentLayer({ kind: newKind, mask: (0, import_image_editor7.layerMaskFromSelection)(sel) })),
1302
+ onClick: () => sel && setLayerId(controller.addAdjustmentLayer({ kind: newKind, mask: (0, import_image_editor9.layerMaskFromSelection)(sel) })),
803
1303
  children: "New Masked Layer"
804
1304
  }
805
1305
  )
806
1306
  ] }),
807
- list.length > 0 && target && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-2", children: [
808
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1307
+ list.length > 0 && target && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-2", children: [
1308
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
809
1309
  "select",
810
1310
  {
811
1311
  "aria-label": "Mask layer",
812
1312
  value: target.id,
813
1313
  onChange: (e) => setLayerId(e.target.value),
814
1314
  className: "bg-slate-950 border border-slate-800 rounded px-1 py-1 text-slate-200",
815
- children: list.map((l) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("option", { value: l.id, children: [
816
- l.name || (0, import_image_editor7.adjustmentKindLabel)(l.kind),
1315
+ children: list.map((l) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("option", { value: l.id, children: [
1316
+ l.name || (0, import_image_editor9.adjustmentKindLabel)(l.kind),
817
1317
  l.mask ? " (masked)" : ""
818
1318
  ] }, l.id))
819
1319
  }
820
1320
  ),
821
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
822
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
823
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
824
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
1321
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
1323
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
825
1325
  ] }),
826
- target.mask && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
827
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex flex-col gap-1", children: [
828
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
829
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Density" }),
830
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "font-mono", children: [
1326
+ target.mask && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1327
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1328
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1329
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Density" }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
831
1331
  Math.round((target.mask.density ?? 1) * 100),
832
1332
  "%"
833
1333
  ] })
834
1334
  ] }),
835
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1335
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
836
1336
  "input",
837
1337
  {
838
1338
  type: "range",
@@ -846,15 +1346,15 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
846
1346
  }
847
1347
  )
848
1348
  ] }),
849
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex flex-col gap-1", children: [
850
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
851
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Mask Feather" }),
852
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "font-mono", children: [
1349
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1350
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1351
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Mask Feather" }),
1352
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
853
1353
  target.mask.feather ?? 0,
854
1354
  "px"
855
1355
  ] })
856
1356
  ] }),
857
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1357
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
858
1358
  "input",
859
1359
  {
860
1360
  type: "range",
@@ -874,7 +1374,7 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
874
1374
  };
875
1375
 
876
1376
  // src/image-editor/ImageEditor.tsx
877
- var import_jsx_runtime7 = require("react/jsx-runtime");
1377
+ var import_jsx_runtime9 = require("react/jsx-runtime");
878
1378
  var ImageEditor = ({
879
1379
  editor: externalEditor,
880
1380
  src,
@@ -889,26 +1389,36 @@ var ImageEditor = ({
889
1389
  const internalEditor = useImageEditor({ image: src });
890
1390
  const editor = externalEditor || internalEditor;
891
1391
  const { state, controller } = editor;
892
- const legacyProcess = (0, import_image_editor8.normalizeProcessVersion)(state.adjustments.process) < import_image_editor8.LATEST_PROCESS_VERSION;
893
- const canvasRef = (0, import_react7.useRef)(null);
894
- const containerRef = (0, import_react7.useRef)(null);
895
- const [activeTab, setActiveTab] = (0, import_react7.useState)("crop");
896
- const [activeCropRatio, setActiveCropRatio] = (0, import_react7.useState)(null);
897
- const [selectionOptions, setSelectionOptions] = (0, import_react7.useState)(import_image_editor8.DEFAULT_SELECTION_TOOL_OPTIONS);
898
- const selectionOptionsRef = (0, import_react7.useRef)(selectionOptions);
1392
+ const legacyProcess = (0, import_image_editor10.normalizeProcessVersion)(state.adjustments.process) < import_image_editor10.LATEST_PROCESS_VERSION;
1393
+ const canvasRef = (0, import_react9.useRef)(null);
1394
+ const containerRef = (0, import_react9.useRef)(null);
1395
+ const [activeTab, setActiveTab] = (0, import_react9.useState)("crop");
1396
+ const [activeCropRatio, setActiveCropRatio] = (0, import_react9.useState)(null);
1397
+ const [selectionOptions, setSelectionOptions] = (0, import_react9.useState)(import_image_editor10.DEFAULT_SELECTION_TOOL_OPTIONS);
1398
+ const selectionOptionsRef = (0, import_react9.useRef)(selectionOptions);
899
1399
  selectionOptionsRef.current = selectionOptions;
900
- const selectionGestureRef = (0, import_react7.useRef)(null);
1400
+ const selectionGestureRef = (0, import_react9.useRef)(null);
901
1401
  if (!selectionGestureRef.current || selectionGestureRef.current.controller !== controller) {
902
- selectionGestureRef.current = new import_image_editor8.SelectionGesture(controller, () => selectionOptionsRef.current);
1402
+ selectionGestureRef.current = new import_image_editor10.SelectionGesture(controller, () => selectionOptionsRef.current);
903
1403
  }
904
- const selectionDraftRef = (0, import_react7.useRef)(null);
905
- const [selectionError, setSelectionError] = (0, import_react7.useState)(null);
906
- const [inpaintPrompt, setInpaintPrompt] = (0, import_react7.useState)("");
907
- const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react7.useState)("annotations");
908
- const [isRemovingBg, setIsRemovingBg] = (0, import_react7.useState)(false);
909
- const [isInpainting, setIsInpainting] = (0, import_react7.useState)(false);
910
- const [aiError, setAiError] = (0, import_react7.useState)(null);
911
- const [aiSuccess, setAiSuccess] = (0, import_react7.useState)(null);
1404
+ const selectionDraftRef = (0, import_react9.useRef)(null);
1405
+ const [selectionError, setSelectionError] = (0, import_react9.useState)(null);
1406
+ const paintGestureRef = (0, import_react9.useRef)(null);
1407
+ const paintControllerRef = (0, import_react9.useRef)(controller);
1408
+ if (!paintGestureRef.current || paintControllerRef.current !== controller) {
1409
+ paintGestureRef.current = new import_image_editor10.PaintGesture(controller);
1410
+ paintControllerRef.current = controller;
1411
+ }
1412
+ const brushCursorRef = (0, import_react9.useRef)(null);
1413
+ const [paintError, setPaintError] = (0, import_react9.useState)(null);
1414
+ const [inpaintPrompt, setInpaintPrompt] = (0, import_react9.useState)("");
1415
+ const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react9.useState)("annotations");
1416
+ const [isRemovingBg, setIsRemovingBg] = (0, import_react9.useState)(false);
1417
+ const [isInpainting, setIsInpainting] = (0, import_react9.useState)(false);
1418
+ const [aiError, setAiError] = (0, import_react9.useState)(null);
1419
+ const [aiSuccess, setAiSuccess] = (0, import_react9.useState)(null);
1420
+ const [aiTarget, setAiTarget] = (0, import_react9.useState)("auto");
1421
+ const aiOptions = aiTarget === "auto" ? {} : { target: aiTarget };
912
1422
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
913
1423
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
914
1424
  const handleRemoveBackground = async () => {
@@ -920,10 +1430,10 @@ var ImageEditor = ({
920
1430
  setAiSuccess(null);
921
1431
  setIsRemovingBg(true);
922
1432
  try {
923
- const imageDataUrl = await editor.toSourceDataURL();
1433
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
924
1434
  const result = await effectiveOnRemoveBg({ imageDataUrl });
925
1435
  if (typeof result === "string") {
926
- await editor.applyBackgroundRemovedImage(result);
1436
+ await editor.applyBackgroundRemovedImage(result, aiOptions);
927
1437
  setAiSuccess("Background removed successfully!");
928
1438
  } else {
929
1439
  setAiSuccess("Background removal complete.");
@@ -947,7 +1457,7 @@ var ImageEditor = ({
947
1457
  setAiSuccess(null);
948
1458
  setIsInpainting(true);
949
1459
  try {
950
- const imageDataUrl = await editor.toSourceDataURL();
1460
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
951
1461
  const maskDataUrl = await editor.toMaskDataURL({
952
1462
  useAnnotations: inpaintMaskMode === "annotations",
953
1463
  useCrop: inpaintMaskMode === "crop"
@@ -959,7 +1469,7 @@ var ImageEditor = ({
959
1469
  cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
960
1470
  });
961
1471
  if (typeof result === "string") {
962
- await editor.applyInpaintedImage(result);
1472
+ await editor.applyInpaintedImage(result, aiOptions);
963
1473
  setAiSuccess("Inpainting applied successfully!");
964
1474
  } else {
965
1475
  setAiSuccess("Inpainting complete.");
@@ -970,18 +1480,18 @@ var ImageEditor = ({
970
1480
  setIsInpainting(false);
971
1481
  }
972
1482
  };
973
- const [activeColor, setActiveColor] = (0, import_react7.useState)("#ef4444");
974
- const [strokeWidth, setStrokeWidth] = (0, import_react7.useState)(3);
975
- const [fontSize, setFontSize] = (0, import_react7.useState)(24);
976
- const [textInput, setTextInput] = (0, import_react7.useState)("");
977
- const draftAnnotationRef = (0, import_react7.useRef)(null);
978
- const draftCropRef = (0, import_react7.useRef)(null);
979
- const cropDragInfoRef = (0, import_react7.useRef)(null);
980
- const annotationDragInfoRef = (0, import_react7.useRef)(null);
981
- const isDrawingRef = (0, import_react7.useRef)(false);
982
- const currentPathRef = (0, import_react7.useRef)([]);
983
- const dragStartPointRef = (0, import_react7.useRef)(null);
984
- const renderCanvas = (0, import_react7.useCallback)(
1483
+ const [activeColor, setActiveColor] = (0, import_react9.useState)("#ef4444");
1484
+ const [strokeWidth, setStrokeWidth] = (0, import_react9.useState)(3);
1485
+ const [fontSize, setFontSize] = (0, import_react9.useState)(24);
1486
+ const [textInput, setTextInput] = (0, import_react9.useState)("");
1487
+ const draftAnnotationRef = (0, import_react9.useRef)(null);
1488
+ const draftCropRef = (0, import_react9.useRef)(null);
1489
+ const cropDragInfoRef = (0, import_react9.useRef)(null);
1490
+ const annotationDragInfoRef = (0, import_react9.useRef)(null);
1491
+ const isDrawingRef = (0, import_react9.useRef)(false);
1492
+ const currentPathRef = (0, import_react9.useRef)([]);
1493
+ const dragStartPointRef = (0, import_react9.useRef)(null);
1494
+ const renderCanvas = (0, import_react9.useCallback)(
985
1495
  async (draftAnnotation, draftCrop) => {
986
1496
  const canvas = canvasRef.current;
987
1497
  const container = containerRef.current;
@@ -996,12 +1506,13 @@ var ImageEditor = ({
996
1506
  interactive: true,
997
1507
  draftAnnotation: draftAnnotation !== void 0 ? draftAnnotation : draftAnnotationRef.current,
998
1508
  draftCrop: draftCrop !== void 0 ? draftCrop : draftCropRef.current,
999
- draftSelectionPath: selectionDraftRef.current
1509
+ draftSelectionPath: selectionDraftRef.current,
1510
+ brushCursor: activeTab === "paint" && brushCursorRef.current && controller.getState().activeTool !== "move" ? { ...brushCursorRef.current, diameter: controller.getState().paint.brush.size } : null
1000
1511
  });
1001
1512
  },
1002
1513
  [controller, activeTab]
1003
1514
  );
1004
- (0, import_react7.useEffect)(() => {
1515
+ (0, import_react9.useEffect)(() => {
1005
1516
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
1006
1517
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
1007
1518
  const animId = raf(() => {
@@ -1009,12 +1520,12 @@ var ImageEditor = ({
1009
1520
  });
1010
1521
  return () => caf(animId);
1011
1522
  }, [renderCanvas, state]);
1012
- (0, import_react7.useEffect)(() => {
1523
+ (0, import_react9.useEffect)(() => {
1013
1524
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
1014
1525
  editor.setTool("crop");
1015
1526
  }
1016
1527
  }, [activeTab, editor]);
1017
- (0, import_react7.useEffect)(() => {
1528
+ (0, import_react9.useEffect)(() => {
1018
1529
  const container = containerRef.current;
1019
1530
  if (!container) return;
1020
1531
  if (typeof ResizeObserver !== "undefined") {
@@ -1040,13 +1551,60 @@ var ImageEditor = ({
1040
1551
  y: (e.clientY - rect.top) * scaleY
1041
1552
  };
1042
1553
  };
1554
+ const paintSamples = (e) => {
1555
+ const canvas = canvasRef.current;
1556
+ const native = e.nativeEvent;
1557
+ const events = typeof native.getCoalescedEvents === "function" ? native.getCoalescedEvents() : [];
1558
+ const list = events.length ? events : [native];
1559
+ const rect = canvas.getBoundingClientRect();
1560
+ const sx = rect.width > 0 ? canvas.width / rect.width : 1;
1561
+ const sy = rect.height > 0 ? canvas.height / rect.height : 1;
1562
+ const s = controller.getState();
1563
+ return list.map((ev) => ({
1564
+ ...(0, import_image_editor10.canvasToImagePoint)({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s, canvas.width, canvas.height, false),
1565
+ pressure: (0, import_image_editor10.pointerPressure)(ev)
1566
+ }));
1567
+ };
1568
+ const handlePaintPointerDown = (e) => {
1569
+ if (activeTab !== "paint" || e.button !== 0) return;
1570
+ e.preventDefault();
1571
+ try {
1572
+ e.currentTarget.setPointerCapture?.(e.pointerId);
1573
+ } catch {
1574
+ }
1575
+ const [first] = paintSamples(e);
1576
+ setPaintError(paintGestureRef.current.down(first));
1577
+ };
1578
+ const handlePaintPointerMove = (e) => {
1579
+ if (activeTab !== "paint") return;
1580
+ const samples = paintSamples(e);
1581
+ brushCursorRef.current = samples[samples.length - 1] ?? null;
1582
+ if (paintGestureRef.current.active) paintGestureRef.current.moveTo(samples);
1583
+ else void renderCanvas();
1584
+ };
1585
+ const handlePaintPointerUp = () => {
1586
+ if (activeTab !== "paint") return;
1587
+ const g = paintGestureRef.current;
1588
+ if (g.active) {
1589
+ g.up();
1590
+ if (g.error) setPaintError(g.error);
1591
+ }
1592
+ };
1593
+ (0, import_react9.useEffect)(() => {
1594
+ if (activeTab !== "paint" || typeof window === "undefined") return;
1595
+ const onKey = (e) => {
1596
+ if ((0, import_image_editor10.handlePaintShortcut)(controller, e)) e.preventDefault();
1597
+ };
1598
+ window.addEventListener("keydown", onKey);
1599
+ return () => window.removeEventListener("keydown", onKey);
1600
+ }, [activeTab, controller]);
1043
1601
  const handleMouseDown = (e) => {
1044
1602
  const canvas = canvasRef.current;
1045
1603
  if (!canvas) return;
1046
1604
  const canvasPt = getCanvasEventPoint(e);
1047
1605
  if (activeTab === "select") {
1048
1606
  const gesture = selectionGestureRef.current;
1049
- const imgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1607
+ const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1050
1608
  void gesture.begin(imgPt, e).then(() => setSelectionError(gesture.error));
1051
1609
  return;
1052
1610
  }
@@ -1057,8 +1615,8 @@ var ImageEditor = ({
1057
1615
  width: state.imageDimensions.width,
1058
1616
  height: state.imageDimensions.height
1059
1617
  };
1060
- const handle = (0, import_image_editor8.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1061
- const startImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1618
+ const handle = (0, import_image_editor10.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1619
+ const startImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1062
1620
  if (handle) {
1063
1621
  cropDragInfoRef.current = {
1064
1622
  handle,
@@ -1077,12 +1635,12 @@ var ImageEditor = ({
1077
1635
  return;
1078
1636
  }
1079
1637
  if (activeTab === "annotate") {
1080
- const imgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1638
+ const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1081
1639
  dragStartPointRef.current = imgPt;
1082
1640
  if (state.activeTool === "select") {
1083
1641
  for (let i = state.annotations.length - 1; i >= 0; i--) {
1084
1642
  const ann = state.annotations[i];
1085
- if ((0, import_image_editor8.hitTestAnnotation)(imgPt, ann)) {
1643
+ if ((0, import_image_editor10.hitTestAnnotation)(imgPt, ann)) {
1086
1644
  editor.selectAnnotation(ann.id);
1087
1645
  annotationDragInfoRef.current = {
1088
1646
  annId: ann.id,
@@ -1118,7 +1676,7 @@ var ImageEditor = ({
1118
1676
  const canvasPt = getCanvasEventPoint(e);
1119
1677
  if (activeTab === "select") {
1120
1678
  const gesture = selectionGestureRef.current;
1121
- if (gesture.active && gesture.move((0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false))) {
1679
+ if (gesture.active && gesture.move((0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false))) {
1122
1680
  selectionDraftRef.current = gesture.draftPath;
1123
1681
  renderCanvas();
1124
1682
  }
@@ -1127,7 +1685,7 @@ var ImageEditor = ({
1127
1685
  if (activeTab === "crop") {
1128
1686
  const drag = cropDragInfoRef.current;
1129
1687
  if (drag) {
1130
- const currentImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1688
+ const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1131
1689
  let newCrop;
1132
1690
  if (drag.handle === "new") {
1133
1691
  const minX = Math.max(0, Math.min(drag.startImgPt.x, currentImgPt.x));
@@ -1141,7 +1699,7 @@ var ImageEditor = ({
1141
1699
  height: Math.round(Math.max(20, maxY - minY))
1142
1700
  };
1143
1701
  } else {
1144
- newCrop = (0, import_image_editor8.calculateCropDrag)(
1702
+ newCrop = (0, import_image_editor10.calculateCropDrag)(
1145
1703
  drag.startCrop,
1146
1704
  drag.handle,
1147
1705
  drag.startImgPt,
@@ -1161,15 +1719,15 @@ var ImageEditor = ({
1161
1719
  width: state.imageDimensions.width,
1162
1720
  height: state.imageDimensions.height
1163
1721
  };
1164
- const handle = (0, import_image_editor8.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1165
- canvas.style.cursor = (0, import_image_editor8.getCropCursor)(handle, state.transform);
1722
+ const handle = (0, import_image_editor10.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1723
+ canvas.style.cursor = (0, import_image_editor10.getCropCursor)(handle, state.transform);
1166
1724
  }
1167
1725
  return;
1168
1726
  }
1169
1727
  if (activeTab === "annotate") {
1170
1728
  if (annotationDragInfoRef.current) {
1171
1729
  const drag = annotationDragInfoRef.current;
1172
- const currentImgPt2 = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1730
+ const currentImgPt2 = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1173
1731
  const dx = currentImgPt2.x - drag.startImgPt.x;
1174
1732
  const dy = currentImgPt2.y - drag.startImgPt.y;
1175
1733
  editor.updateAnnotation(drag.annId, {
@@ -1181,10 +1739,10 @@ var ImageEditor = ({
1181
1739
  const startPt = dragStartPointRef.current;
1182
1740
  if (!startPt) {
1183
1741
  if (state.activeTool === "select") {
1184
- const imgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1742
+ const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1185
1743
  let hovered = false;
1186
1744
  for (let i = state.annotations.length - 1; i >= 0; i--) {
1187
- if ((0, import_image_editor8.hitTestAnnotation)(imgPt, state.annotations[i])) {
1745
+ if ((0, import_image_editor10.hitTestAnnotation)(imgPt, state.annotations[i])) {
1188
1746
  hovered = true;
1189
1747
  break;
1190
1748
  }
@@ -1195,7 +1753,7 @@ var ImageEditor = ({
1195
1753
  }
1196
1754
  return;
1197
1755
  }
1198
- const currentImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1756
+ const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1199
1757
  if (state.activeTool === "pen" && isDrawingRef.current) {
1200
1758
  currentPathRef.current.push(currentImgPt);
1201
1759
  const draft = {
@@ -1275,7 +1833,7 @@ var ImageEditor = ({
1275
1833
  if (activeTab === "select") {
1276
1834
  const gesture = selectionGestureRef.current;
1277
1835
  if (gesture.active) {
1278
- gesture.end((0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false), e);
1836
+ gesture.end((0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false), e);
1279
1837
  selectionDraftRef.current = null;
1280
1838
  renderCanvas();
1281
1839
  }
@@ -1299,7 +1857,7 @@ var ImageEditor = ({
1299
1857
  }
1300
1858
  const startPt = dragStartPointRef.current;
1301
1859
  if (startPt) {
1302
- const currentImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1860
+ const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1303
1861
  if (state.activeTool === "pen" && isDrawingRef.current) {
1304
1862
  if (currentPathRef.current.length > 1) {
1305
1863
  editor.addAnnotation({
@@ -1395,15 +1953,15 @@ var ImageEditor = ({
1395
1953
  link.click();
1396
1954
  }
1397
1955
  };
1398
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1956
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1399
1957
  "div",
1400
1958
  {
1401
1959
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
1402
1960
  style,
1403
1961
  children: [
1404
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap items-center justify-between px-4 py-3 bg-slate-950/80 border-b border-slate-800 gap-3", children: [
1405
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
1406
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1962
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-wrap items-center justify-between px-4 py-3 bg-slate-950/80 border-b border-slate-800 gap-3", children: [
1963
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
1964
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1407
1965
  "button",
1408
1966
  {
1409
1967
  type: "button",
@@ -1415,7 +1973,7 @@ var ImageEditor = ({
1415
1973
  children: "Transform & Crop"
1416
1974
  }
1417
1975
  ),
1418
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1976
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1419
1977
  "button",
1420
1978
  {
1421
1979
  type: "button",
@@ -1427,7 +1985,31 @@ var ImageEditor = ({
1427
1985
  children: "Adjustments"
1428
1986
  }
1429
1987
  ),
1430
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1988
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1989
+ "button",
1990
+ {
1991
+ type: "button",
1992
+ onClick: () => {
1993
+ setActiveTab("layers");
1994
+ editor.setTool("select");
1995
+ },
1996
+ 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"}`,
1997
+ children: "Layers"
1998
+ }
1999
+ ),
2000
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2001
+ "button",
2002
+ {
2003
+ type: "button",
2004
+ onClick: () => {
2005
+ setActiveTab("paint");
2006
+ editor.setTool("brush");
2007
+ },
2008
+ 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"}`,
2009
+ children: "Paint"
2010
+ }
2011
+ ),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1431
2013
  "button",
1432
2014
  {
1433
2015
  type: "button",
@@ -1439,7 +2021,7 @@ var ImageEditor = ({
1439
2021
  children: "Select & Mask"
1440
2022
  }
1441
2023
  ),
1442
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2024
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1443
2025
  "button",
1444
2026
  {
1445
2027
  type: "button",
@@ -1451,7 +2033,7 @@ var ImageEditor = ({
1451
2033
  children: "Annotations"
1452
2034
  }
1453
2035
  ),
1454
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2036
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1455
2037
  "button",
1456
2038
  {
1457
2039
  type: "button",
@@ -1463,7 +2045,7 @@ var ImageEditor = ({
1463
2045
  children: "Depth Mask"
1464
2046
  }
1465
2047
  ),
1466
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2048
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1467
2049
  "button",
1468
2050
  {
1469
2051
  type: "button",
@@ -1473,14 +2055,14 @@ var ImageEditor = ({
1473
2055
  },
1474
2056
  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"}`,
1475
2057
  children: [
1476
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2058
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1477
2059
  "AI Tools"
1478
2060
  ]
1479
2061
  }
1480
2062
  )
1481
2063
  ] }),
1482
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
1483
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2064
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
2065
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1484
2066
  "button",
1485
2067
  {
1486
2068
  type: "button",
@@ -1491,7 +2073,7 @@ var ImageEditor = ({
1491
2073
  children: "\u21A9"
1492
2074
  }
1493
2075
  ),
1494
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2076
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1495
2077
  "button",
1496
2078
  {
1497
2079
  type: "button",
@@ -1502,7 +2084,7 @@ var ImageEditor = ({
1502
2084
  children: "\u21AA"
1503
2085
  }
1504
2086
  ),
1505
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2087
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1506
2088
  "button",
1507
2089
  {
1508
2090
  type: "button",
@@ -1511,8 +2093,8 @@ var ImageEditor = ({
1511
2093
  children: "Reset"
1512
2094
  }
1513
2095
  ),
1514
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1515
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2096
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2097
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1516
2098
  "button",
1517
2099
  {
1518
2100
  type: "button",
@@ -1522,11 +2104,11 @@ var ImageEditor = ({
1522
2104
  children: "-"
1523
2105
  }
1524
2106
  ),
1525
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
2107
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
1526
2108
  Math.round(state.zoom * 100),
1527
2109
  "%"
1528
2110
  ] }),
1529
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2111
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1530
2112
  "button",
1531
2113
  {
1532
2114
  type: "button",
@@ -1536,8 +2118,8 @@ var ImageEditor = ({
1536
2118
  children: "+"
1537
2119
  }
1538
2120
  ),
1539
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
1540
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2121
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2122
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1541
2123
  "button",
1542
2124
  {
1543
2125
  type: "button",
@@ -1548,7 +2130,7 @@ var ImageEditor = ({
1548
2130
  children: "Hold Compare"
1549
2131
  }
1550
2132
  ),
1551
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2133
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1552
2134
  "button",
1553
2135
  {
1554
2136
  type: "button",
@@ -1559,13 +2141,13 @@ var ImageEditor = ({
1559
2141
  )
1560
2142
  ] })
1561
2143
  ] }),
1562
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
1563
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2144
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
2145
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1564
2146
  "div",
1565
2147
  {
1566
2148
  ref: containerRef,
1567
2149
  className: "relative flex-1 h-[360px] md:h-full min-h-[320px] bg-slate-950 overflow-hidden select-none",
1568
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2150
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1569
2151
  "canvas",
1570
2152
  {
1571
2153
  ref: canvasRef,
@@ -1573,16 +2155,21 @@ var ImageEditor = ({
1573
2155
  onMouseMove: handleMouseMove,
1574
2156
  onMouseUp: handleMouseUp,
1575
2157
  onMouseLeave: handleMouseUp,
1576
- className: "absolute inset-0 w-full h-full block"
2158
+ onPointerDown: handlePaintPointerDown,
2159
+ onPointerMove: handlePaintPointerMove,
2160
+ onPointerUp: handlePaintPointerUp,
2161
+ onPointerCancel: () => paintGestureRef.current?.cancel(),
2162
+ "data-testid": "image-editor-canvas",
2163
+ className: `absolute inset-0 w-full h-full block ${activeTab === "paint" ? "touch-none cursor-crosshair" : ""}`
1577
2164
  }
1578
2165
  )
1579
2166
  }
1580
2167
  ),
1581
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "w-full md:w-80 bg-slate-900/90 border-t md:border-t-0 md:border-l border-slate-800 p-4 overflow-y-auto h-auto md:h-full md:max-h-[560px]", children: [
1582
- activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
1583
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
1584
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
1585
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2168
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "w-full md:w-80 bg-slate-900/90 border-t md:border-t-0 md:border-l border-slate-800 p-4 overflow-y-auto h-auto md:h-full md:max-h-[560px]", children: [
2169
+ activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
2171
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
2172
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1586
2173
  "button",
1587
2174
  {
1588
2175
  type: "button",
@@ -1594,7 +2181,7 @@ var ImageEditor = ({
1594
2181
  children: "Freeform"
1595
2182
  }
1596
2183
  ),
1597
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2184
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1598
2185
  "button",
1599
2186
  {
1600
2187
  type: "button",
@@ -1606,7 +2193,7 @@ var ImageEditor = ({
1606
2193
  children: "1:1 Square"
1607
2194
  }
1608
2195
  ),
1609
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2196
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1610
2197
  "button",
1611
2198
  {
1612
2199
  type: "button",
@@ -1618,7 +2205,7 @@ var ImageEditor = ({
1618
2205
  children: "4:3 Standard"
1619
2206
  }
1620
2207
  ),
1621
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2208
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1622
2209
  "button",
1623
2210
  {
1624
2211
  type: "button",
@@ -1630,7 +2217,7 @@ var ImageEditor = ({
1630
2217
  children: "16:9 Wide"
1631
2218
  }
1632
2219
  ),
1633
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2220
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1634
2221
  "button",
1635
2222
  {
1636
2223
  type: "button",
@@ -1642,7 +2229,7 @@ var ImageEditor = ({
1642
2229
  children: "3:2 Photo"
1643
2230
  }
1644
2231
  ),
1645
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2232
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1646
2233
  "button",
1647
2234
  {
1648
2235
  type: "button",
@@ -1655,10 +2242,10 @@ var ImageEditor = ({
1655
2242
  }
1656
2243
  )
1657
2244
  ] }),
1658
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
1659
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
1660
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
1661
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2245
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2246
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
2247
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
2248
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1662
2249
  "button",
1663
2250
  {
1664
2251
  type: "button",
@@ -1667,7 +2254,7 @@ var ImageEditor = ({
1667
2254
  children: "Rotate -90\xB0"
1668
2255
  }
1669
2256
  ),
1670
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2257
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1671
2258
  "button",
1672
2259
  {
1673
2260
  type: "button",
@@ -1676,7 +2263,7 @@ var ImageEditor = ({
1676
2263
  children: "Rotate +90\xB0"
1677
2264
  }
1678
2265
  ),
1679
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2266
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1680
2267
  "button",
1681
2268
  {
1682
2269
  type: "button",
@@ -1685,7 +2272,7 @@ var ImageEditor = ({
1685
2272
  children: "Flip Horizontal"
1686
2273
  }
1687
2274
  ),
1688
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2275
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1689
2276
  "button",
1690
2277
  {
1691
2278
  type: "button",
@@ -1696,10 +2283,10 @@ var ImageEditor = ({
1696
2283
  )
1697
2284
  ] })
1698
2285
  ] }),
1699
- activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
1700
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between", children: [
1701
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
1702
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2286
+ activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2287
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2288
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1703
2290
  "button",
1704
2291
  {
1705
2292
  type: "button",
@@ -1709,25 +2296,25 @@ var ImageEditor = ({
1709
2296
  }
1710
2297
  )
1711
2298
  ] }),
1712
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Histogram, { histogram: editor.histogram }),
1713
- import_image_editor8.IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react7.default.Fragment, { children: [
1714
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2299
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Histogram, { histogram: editor.histogram }),
2300
+ import_image_editor10.IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react9.default.Fragment, { children: [
2301
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1715
2302
  ControlSlider,
1716
2303
  {
1717
2304
  spec,
1718
- value: (0, import_image_editor8.readAdjustmentControls)(state.adjustments)[spec.id],
2305
+ value: (0, import_image_editor10.readAdjustmentControls)(state.adjustments)[spec.id],
1719
2306
  disabled: spec.requiresProcess !== void 0 && legacyProcess,
1720
2307
  onChange: (v) => editor.setAdjustments({ [spec.id]: v })
1721
2308
  }
1722
2309
  ),
1723
- spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2310
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1724
2311
  "div",
1725
2312
  {
1726
2313
  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",
1727
2314
  "data-testid": "image-editor-process-notice",
1728
2315
  children: [
1729
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
1730
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2316
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
2317
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1731
2318
  "button",
1732
2319
  {
1733
2320
  type: "button",
@@ -1740,7 +2327,7 @@ var ImageEditor = ({
1740
2327
  }
1741
2328
  )
1742
2329
  ] }, spec.id)),
1743
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2330
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1744
2331
  AdjustmentLayersPanel,
1745
2332
  {
1746
2333
  controller,
@@ -1749,9 +2336,19 @@ var ImageEditor = ({
1749
2336
  }
1750
2337
  ) })
1751
2338
  ] }),
1752
- activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
1753
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
1754
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2339
+ activeTab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2340
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Layers" }),
2341
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LayersPanel, { controller })
2342
+ ] }),
2343
+ activeTab === "paint" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2344
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Paint" }),
2345
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BrushOptionsBar, { controller }),
2346
+ paintError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", "data-testid": "paint-error", children: paintError }),
2347
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LayersPanel, { controller }) })
2348
+ ] }),
2349
+ activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2350
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
2351
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1755
2352
  "button",
1756
2353
  {
1757
2354
  type: "button",
@@ -1761,9 +2358,9 @@ var ImageEditor = ({
1761
2358
  },
1762
2359
  t
1763
2360
  )) }),
1764
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
1765
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "text-slate-400", children: "Color:" }),
1766
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2361
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
2362
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "text-slate-400", children: "Color:" }),
2363
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1767
2364
  "input",
1768
2365
  {
1769
2366
  type: "color",
@@ -1772,15 +2369,15 @@ var ImageEditor = ({
1772
2369
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
1773
2370
  }
1774
2371
  ),
1775
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex-1 ml-2", children: [
1776
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
1777
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Width:" }),
1778
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-mono", children: [
2372
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex-1 ml-2", children: [
2373
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
2374
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Width:" }),
2375
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1779
2376
  strokeWidth,
1780
2377
  "px"
1781
2378
  ] })
1782
2379
  ] }),
1783
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2380
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1784
2381
  "input",
1785
2382
  {
1786
2383
  type: "range",
@@ -1793,10 +2390,10 @@ var ImageEditor = ({
1793
2390
  )
1794
2391
  ] })
1795
2392
  ] }),
1796
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
1797
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
1798
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-2", children: [
1799
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2393
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2394
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
2395
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
2396
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1800
2397
  "input",
1801
2398
  {
1802
2399
  type: "text",
@@ -1807,7 +2404,7 @@ var ImageEditor = ({
1807
2404
  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"
1808
2405
  }
1809
2406
  ),
1810
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2407
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1811
2408
  "button",
1812
2409
  {
1813
2410
  type: "button",
@@ -1817,14 +2414,14 @@ var ImageEditor = ({
1817
2414
  }
1818
2415
  )
1819
2416
  ] }),
1820
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
1821
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Font Size:" }),
1822
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-mono", children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
2418
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Font Size:" }),
2419
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1823
2420
  fontSize,
1824
2421
  "px"
1825
2422
  ] })
1826
2423
  ] }),
1827
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2424
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1828
2425
  "input",
1829
2426
  {
1830
2427
  type: "range",
@@ -1835,14 +2432,14 @@ var ImageEditor = ({
1835
2432
  className: "w-full accent-indigo-500"
1836
2433
  }
1837
2434
  ),
1838
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
1839
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between", children: [
1840
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-slate-400", children: [
2435
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2436
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2437
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-slate-400", children: [
1841
2438
  "Layers (",
1842
2439
  state.annotations.length,
1843
2440
  ")"
1844
2441
  ] }),
1845
- state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2442
+ state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1846
2443
  "button",
1847
2444
  {
1848
2445
  type: "button",
@@ -1851,7 +2448,7 @@ var ImageEditor = ({
1851
2448
  children: "Delete Selected"
1852
2449
  }
1853
2450
  ),
1854
- state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2451
+ state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1855
2452
  "button",
1856
2453
  {
1857
2454
  type: "button",
@@ -1862,10 +2459,10 @@ var ImageEditor = ({
1862
2459
  )
1863
2460
  ] })
1864
2461
  ] }),
1865
- activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
1866
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between", children: [
1867
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
1868
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2462
+ activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2463
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2464
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
2465
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1869
2466
  "button",
1870
2467
  {
1871
2468
  type: "button",
@@ -1875,8 +2472,8 @@ var ImageEditor = ({
1875
2472
  }
1876
2473
  )
1877
2474
  ] }),
1878
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
1879
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2475
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
2476
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1880
2477
  "input",
1881
2478
  {
1882
2479
  type: "checkbox",
@@ -1885,12 +2482,12 @@ var ImageEditor = ({
1885
2482
  className: "rounded accent-indigo-500"
1886
2483
  }
1887
2484
  ),
1888
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Enable Depth / Alpha Mask" })
2485
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Enable Depth / Alpha Mask" })
1889
2486
  ] }),
1890
- state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1891
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1892
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
1893
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2487
+ state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2488
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2489
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
2490
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1894
2491
  "input",
1895
2492
  {
1896
2493
  type: "text",
@@ -1901,8 +2498,8 @@ var ImageEditor = ({
1901
2498
  }
1902
2499
  )
1903
2500
  ] }),
1904
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-2", children: [
1905
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2501
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
2502
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1906
2503
  "button",
1907
2504
  {
1908
2505
  type: "button",
@@ -1911,7 +2508,7 @@ var ImageEditor = ({
1911
2508
  children: "Threshold Mode"
1912
2509
  }
1913
2510
  ),
1914
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2511
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1915
2512
  "button",
1916
2513
  {
1917
2514
  type: "button",
@@ -1921,12 +2518,12 @@ var ImageEditor = ({
1921
2518
  }
1922
2519
  )
1923
2520
  ] }),
1924
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1925
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1926
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Depth Start (Lower):" }),
1927
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
2521
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2522
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2523
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Depth Start (Lower):" }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
1928
2525
  ] }),
1929
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2526
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1930
2527
  "input",
1931
2528
  {
1932
2529
  type: "range",
@@ -1940,12 +2537,12 @@ var ImageEditor = ({
1940
2537
  }
1941
2538
  )
1942
2539
  ] }),
1943
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1944
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1945
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Depth Stop (Upper):" }),
1946
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
2540
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2541
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2542
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Depth Stop (Upper):" }),
2543
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
1947
2544
  ] }),
1948
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2545
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1949
2546
  "input",
1950
2547
  {
1951
2548
  type: "range",
@@ -1959,12 +2556,12 @@ var ImageEditor = ({
1959
2556
  }
1960
2557
  )
1961
2558
  ] }),
1962
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1963
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
1964
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Softness:" }),
1965
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
2559
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2560
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2561
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Softness:" }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
1966
2563
  ] }),
1967
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2564
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1968
2565
  "input",
1969
2566
  {
1970
2567
  type: "range",
@@ -1976,8 +2573,8 @@ var ImageEditor = ({
1976
2573
  }
1977
2574
  )
1978
2575
  ] }),
1979
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
1980
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2576
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
2577
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1981
2578
  "input",
1982
2579
  {
1983
2580
  type: "checkbox",
@@ -1986,12 +2583,12 @@ var ImageEditor = ({
1986
2583
  className: "rounded accent-indigo-500"
1987
2584
  }
1988
2585
  ),
1989
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Invert Mask" })
2586
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Invert Mask" })
1990
2587
  ] })
1991
2588
  ] })
1992
2589
  ] }),
1993
- activeTab === "select" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
1994
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2590
+ activeTab === "select" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1995
2592
  SelectionOptionsBar,
1996
2593
  {
1997
2594
  controller,
@@ -2001,51 +2598,68 @@ var ImageEditor = ({
2001
2598
  quickMask: Boolean(state.quickMask)
2002
2599
  }
2003
2600
  ),
2004
- selectionError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", className: "text-rose-400", children: selectionError }),
2005
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2601
+ selectionError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", children: selectionError }),
2602
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2006
2603
  ] }),
2007
- activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2008
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2009
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between", children: [
2010
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2011
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
2604
+ activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2605
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
2606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Results go to" }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2608
+ "select",
2609
+ {
2610
+ "aria-label": "AI result target",
2611
+ value: aiTarget,
2612
+ onChange: (e) => setAiTarget(e.target.value),
2613
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
2614
+ children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
2616
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "new-layer", children: "New layer / mask" }),
2617
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "replace", children: "Replace image" })
2618
+ ]
2619
+ }
2620
+ )
2621
+ ] }),
2622
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2623
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2624
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2625
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
2012
2626
  "Background Removal"
2013
2627
  ] }),
2014
- isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
2628
+ isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
2015
2629
  ] }),
2016
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2017
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2630
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2631
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2018
2632
  "button",
2019
2633
  {
2020
2634
  type: "button",
2021
2635
  onClick: handleRemoveBackground,
2022
2636
  disabled: isRemovingBg || isInpainting,
2023
2637
  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",
2024
- children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2025
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2638
+ children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2026
2640
  "Removing Background..."
2027
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2028
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2641
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2642
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2029
2643
  "Remove Background"
2030
2644
  ] })
2031
2645
  }
2032
2646
  )
2033
2647
  ] }),
2034
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2035
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between", children: [
2036
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2037
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2038
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
2039
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "m14 7 3 3" })
2648
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2649
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2650
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2651
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2652
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
2653
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "m14 7 3 3" })
2040
2654
  ] }),
2041
2655
  "Generative Inpainting"
2042
2656
  ] }),
2043
- isInpainting && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
2657
+ isInpainting && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
2044
2658
  ] }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
2046
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
2047
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2048
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2659
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
2660
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
2661
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2662
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2049
2663
  "textarea",
2050
2664
  {
2051
2665
  id: "ai-inpaint-prompt-react",
@@ -2057,10 +2671,10 @@ var ImageEditor = ({
2057
2671
  }
2058
2672
  )
2059
2673
  ] }),
2060
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
2061
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2062
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
2063
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2674
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
2675
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2676
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
2677
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2064
2678
  "button",
2065
2679
  {
2066
2680
  type: "button",
@@ -2073,7 +2687,7 @@ var ImageEditor = ({
2073
2687
  ]
2074
2688
  }
2075
2689
  ),
2076
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2690
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2077
2691
  "button",
2078
2692
  {
2079
2693
  type: "button",
@@ -2088,25 +2702,25 @@ var ImageEditor = ({
2088
2702
  )
2089
2703
  ] })
2090
2704
  ] }),
2091
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2705
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2092
2706
  "button",
2093
2707
  {
2094
2708
  type: "button",
2095
2709
  onClick: handleInpaint,
2096
2710
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
2097
2711
  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",
2098
- children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2099
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2712
+ children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2713
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2100
2714
  "Generating Inpaint..."
2101
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2102
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2715
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2716
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2103
2717
  "Apply Inpainting"
2104
2718
  ] })
2105
2719
  }
2106
2720
  )
2107
2721
  ] }),
2108
- aiError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
2109
- aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
2722
+ aiError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
2723
+ aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
2110
2724
  ] })
2111
2725
  ] })
2112
2726
  ] })
@@ -2117,10 +2731,12 @@ var ImageEditor = ({
2117
2731
  // Annotate the CommonJS export names for ESM import in node:
2118
2732
  0 && (module.exports = {
2119
2733
  AdjustmentLayersPanel,
2734
+ BrushOptionsBar,
2120
2735
  ControlSlider,
2121
2736
  CurvesEditor,
2122
2737
  Histogram,
2123
2738
  ImageEditor,
2739
+ LayersPanel,
2124
2740
  RefineEdgePanel,
2125
2741
  SelectionOptionsBar,
2126
2742
  useImageEditor,