@pixerate/editor-react 0.58.0 → 0.59.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,
@@ -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,458 @@ 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, 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 commitNext = (0, import_react5.useRef)(true);
684
+ const state = controller.getState();
685
+ const rows = (0, import_image_editor6.layerPanelRows)(state.layers, state.activeLayerId);
686
+ const active = controller.getActiveLayer();
687
+ const attempt = (fn) => {
688
+ try {
689
+ setError(null);
690
+ fn();
691
+ } catch (err) {
692
+ setError(err instanceof Error ? err.message : String(err));
693
+ }
694
+ };
695
+ const run = async (action) => {
696
+ setMenu(null);
697
+ setError(await (0, import_image_editor6.runLayerMenuAction)(controller, action));
698
+ };
699
+ const prop = (key, value) => attempt(() => {
700
+ controller.setLayerProperties(active.id, { [key]: value }, commitNext.current);
701
+ commitNext.current = false;
702
+ });
703
+ const sliderEvents = {
704
+ onPointerDown: () => commitNext.current = true,
705
+ onPointerUp: () => commitNext.current = true,
706
+ onKeyDown: () => commitNext.current = true
707
+ };
708
+ const onDrop = (e, row) => {
709
+ e.preventDefault();
710
+ const id = e.dataTransfer.getData("text/x-pixerate-layer") || drag;
711
+ setDrag(null);
712
+ if (!id) return;
713
+ const rect = e.currentTarget.getBoundingClientRect();
714
+ const fraction = rect.height ? (e.clientY - rect.top) / rect.height : 0.5;
715
+ const placement = (0, import_image_editor6.dropPlacement)(state.layers, id, row.layer.id, (0, import_image_editor6.dropZone)(fraction, row.layer.type === "group"));
716
+ if (placement) attempt(() => controller.moveLayer(id, placement));
717
+ };
718
+ const thumb = (layer) => layer.type === "pixel" ? controller.getLayerThumbnail(layer.id, { maxSide: 64 }) : null;
719
+ const maskThumb = (layer) => layer.mask && !layer.mask.source ? controller.getLayerThumbnail(layer.id, { maxSide: 64, mask: true }) : null;
720
+ const locked = (layer) => Boolean(layer.locks?.all || layer.locks?.pixels || layer.locks?.position || layer.locks?.transparency);
721
+ 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: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap items-center gap-1", role: "toolbar", "aria-label": "Layer actions", children: [
723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-layer"), children: "New Layer" }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-group"), children: "New Group" }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("duplicate"), children: "Duplicate" }),
726
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("add-mask"), children: "Add Mask" }),
727
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("delete"), children: "Delete" }),
728
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
729
+ "button",
730
+ {
731
+ type: "button",
732
+ className: iconButton,
733
+ "aria-label": "Layer menu",
734
+ "aria-haspopup": "menu",
735
+ onClick: (e) => {
736
+ e.stopPropagation();
737
+ const r = e.currentTarget.getBoundingClientRect();
738
+ setMenu(menu ? null : { x: r.left, y: r.bottom });
739
+ },
740
+ children: "\u22EF"
741
+ }
742
+ )
743
+ ] }),
744
+ 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: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
746
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
747
+ "Opacity ",
748
+ Math.round(active.opacity * 100),
749
+ "%"
750
+ ] }),
751
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
752
+ "input",
753
+ {
754
+ type: "range",
755
+ min: 0,
756
+ max: 100,
757
+ "aria-label": "Layer opacity",
758
+ value: Math.round(active.opacity * 100),
759
+ ...sliderEvents,
760
+ onChange: (e) => prop("opacity", Number(e.target.value) / 100),
761
+ className: "accent-indigo-500"
762
+ }
763
+ )
764
+ ] }),
765
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
767
+ "Fill ",
768
+ Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
769
+ "%"
770
+ ] }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
772
+ "input",
773
+ {
774
+ type: "range",
775
+ min: 0,
776
+ max: 100,
777
+ "aria-label": "Layer fill",
778
+ disabled: active.type !== "pixel",
779
+ value: Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
780
+ ...sliderEvents,
781
+ onChange: (e) => prop("fillOpacity", Number(e.target.value) / 100),
782
+ className: "accent-indigo-500 disabled:opacity-40"
783
+ }
784
+ )
785
+ ] }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
787
+ "select",
788
+ {
789
+ "aria-label": "Layer blend mode",
790
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
791
+ value: active.blend,
792
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { blend: e.target.value })),
793
+ children: [
794
+ active.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "pass-through", children: "Pass Through" }),
795
+ import_image_editor6.BLEND_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: m.id, children: m.label }, m.id))
796
+ ]
797
+ }
798
+ ),
799
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
801
+ "input",
802
+ {
803
+ type: "checkbox",
804
+ "aria-label": "Clip to layer below",
805
+ checked: Boolean(active.clipped),
806
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { clipped: e.target.checked })),
807
+ className: "accent-indigo-500"
808
+ }
809
+ ),
810
+ "Clip to below"
811
+ ] }),
812
+ /* @__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: [
813
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
814
+ "input",
815
+ {
816
+ type: "checkbox",
817
+ "aria-label": `Lock ${k}`,
818
+ checked: Boolean(active.locks?.[k]),
819
+ onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { locks: { ...active.locks, [k]: e.target.checked } })),
820
+ className: "accent-indigo-500"
821
+ }
822
+ ),
823
+ k
824
+ ] }, k)) })
825
+ ] }),
826
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "flex flex-col gap-0.5", role: "tree", "aria-label": "Layers", children: rows.map((row) => {
827
+ const { layer } = row;
828
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
829
+ "li",
830
+ {
831
+ role: "treeitem",
832
+ "aria-selected": row.active,
833
+ "aria-level": row.depth + 1,
834
+ "data-layer-id": layer.id,
835
+ draggable: !layer.locks?.all,
836
+ onDragStart: (e) => {
837
+ e.dataTransfer.setData("text/x-pixerate-layer", layer.id);
838
+ setDrag(layer.id);
839
+ },
840
+ onDragOver: (e) => e.preventDefault(),
841
+ onDrop: (e) => onDrop(e, row),
842
+ tabIndex: 0,
843
+ onClick: () => attempt(() => controller.setActiveLayer(layer.id, "pixels")),
844
+ onKeyDown: (e) => {
845
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
846
+ e.preventDefault();
847
+ attempt(() => controller.setActiveLayer(layer.id, "pixels"));
848
+ }
849
+ },
850
+ onContextMenu: (e) => {
851
+ e.preventDefault();
852
+ controller.setActiveLayer(layer.id, state.activeLayerId === layer.id ? state.activeTarget : "pixels");
853
+ setMenu({ x: e.clientX, y: e.clientY });
854
+ },
855
+ className: rowClass(row.active),
856
+ style: { paddingLeft: 6 + row.depth * 14 },
857
+ children: [
858
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
859
+ "button",
860
+ {
861
+ type: "button",
862
+ "aria-label": layer.visible ? `Hide ${row.label}` : `Show ${row.label}`,
863
+ "aria-pressed": layer.visible,
864
+ className: "w-4 text-center text-slate-300",
865
+ onClick: (e) => {
866
+ e.stopPropagation();
867
+ attempt(() => controller.setLayerProperties(layer.id, { visible: !layer.visible }));
868
+ },
869
+ children: layer.visible ? "\u25CF" : "\u25CB"
870
+ }
871
+ ),
872
+ layer.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
873
+ "button",
874
+ {
875
+ type: "button",
876
+ "aria-label": layer.collapsed ? `Expand ${row.label}` : `Collapse ${row.label}`,
877
+ className: "w-3 text-slate-400",
878
+ onClick: (e) => {
879
+ e.stopPropagation();
880
+ attempt(() => controller.setLayerProperties(layer.id, { collapsed: !layer.collapsed }));
881
+ },
882
+ children: layer.collapsed ? "\u25B8" : "\u25BE"
883
+ }
884
+ ),
885
+ row.clipped && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-slate-500", "aria-label": "Clipped", children: "\u21B3" }),
886
+ layer.type === "pixel" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
887
+ Thumbnail,
888
+ {
889
+ pixels: thumb(layer),
890
+ size: thumbnailSize,
891
+ label: `${row.label} pixels`,
892
+ selected: row.active && state.activeTarget === "pixels" && Boolean(layer.mask)
893
+ }
894
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
895
+ "span",
896
+ {
897
+ className: "shrink-0 grid place-items-center rounded bg-slate-800 text-[9px] text-slate-400 ring-1 ring-slate-700",
898
+ style: { width: thumbnailSize, height: thumbnailSize },
899
+ children: (0, import_image_editor6.layerBadge)(layer)
900
+ }
901
+ ),
902
+ layer.mask && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
903
+ Thumbnail,
904
+ {
905
+ pixels: maskThumb(layer),
906
+ size: thumbnailSize,
907
+ label: `${row.label} mask${layer.mask.enabled === false ? " (disabled)" : ""}`,
908
+ testId: "layer-mask-thumbnail",
909
+ selected: row.active && state.activeTarget === "mask",
910
+ onClick: (e) => {
911
+ e.stopPropagation();
912
+ if (e.shiftKey) attempt(() => controller.setLayerMaskProps(layer.id, { enabled: layer.mask.enabled === false }));
913
+ else attempt(() => controller.setActiveLayer(layer.id, "mask"));
914
+ }
915
+ }
916
+ ),
917
+ renaming === layer.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
918
+ "input",
919
+ {
920
+ autoFocus: true,
921
+ "aria-label": "Layer name",
922
+ defaultValue: layer.name || row.label,
923
+ className: "flex-1 min-w-0 bg-slate-900 border border-slate-600 rounded px-1",
924
+ onClick: (e) => e.stopPropagation(),
925
+ onBlur: (e) => {
926
+ setRenaming(null);
927
+ if (e.target.value.trim() && e.target.value !== layer.name) attempt(() => controller.setLayerProperties(layer.id, { name: e.target.value }));
928
+ },
929
+ onKeyDown: (e) => {
930
+ if (e.key === "Enter") e.target.blur();
931
+ if (e.key === "Escape") setRenaming(null);
932
+ }
933
+ }
934
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
935
+ "span",
936
+ {
937
+ className: `flex-1 min-w-0 truncate ${layer.visible ? "" : "text-slate-500"}`,
938
+ onDoubleClick: (e) => {
939
+ e.stopPropagation();
940
+ setRenaming(layer.id);
941
+ },
942
+ children: row.label
943
+ }
944
+ ),
945
+ locked(layer) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-label": "Locked", className: "text-slate-400", children: "\u{1F512}" }),
946
+ layer.blend !== "normal" && layer.blend !== "pass-through" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[9px] text-slate-500", children: layer.blend })
947
+ ]
948
+ },
949
+ layer.id
950
+ );
951
+ }) }),
952
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
953
+ menu && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
954
+ "div",
955
+ {
956
+ role: "menu",
957
+ "aria-label": "Layer menu",
958
+ className: "fixed z-50 min-w-44 rounded border border-slate-700 bg-slate-900 py-1 shadow-xl",
959
+ style: { left: menu.x, top: menu.y },
960
+ onClick: (e) => e.stopPropagation(),
961
+ onKeyDown: (e) => e.key === "Escape" && setMenu(null),
962
+ children: (0, import_image_editor6.layerMenu)(controller).map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
963
+ "button",
964
+ {
965
+ type: "button",
966
+ role: "menuitem",
967
+ disabled: !item.enabled,
968
+ className: "block w-full px-3 py-1 text-left hover:bg-slate-800 disabled:opacity-40",
969
+ onClick: () => run(item.action),
970
+ children: item.label
971
+ },
972
+ item.action
973
+ ))
974
+ }
975
+ )
976
+ ] });
977
+ };
978
+
979
+ // src/image-editor/BrushOptionsBar.tsx
980
+ var import_react6 = require("react");
981
+ var import_image_editor7 = require("@pixerate/editor/image-editor");
982
+ var import_jsx_runtime6 = require("react/jsx-runtime");
983
+ 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"}`;
984
+ var BrushOptionsBar = ({ controller, className }) => {
985
+ const [, setTick] = (0, import_react6.useState)(0);
986
+ (0, import_react6.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
987
+ const [error, setError] = (0, import_react6.useState)(null);
988
+ const state = controller.getState();
989
+ const { brush, foreground, background } = state.paint;
990
+ const attempt = (fn) => {
991
+ try {
992
+ setError(null);
993
+ fn();
994
+ } catch (err) {
995
+ setError(err instanceof Error ? err.message : String(err));
996
+ }
997
+ };
998
+ 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: [
999
+ /* @__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)(
1000
+ "button",
1001
+ {
1002
+ type: "button",
1003
+ "aria-pressed": state.activeTool === t.id,
1004
+ title: `${t.label} (${t.shortcut})`,
1005
+ className: chip(state.activeTool === t.id),
1006
+ onClick: () => controller.setTool(t.id),
1007
+ children: t.label
1008
+ },
1009
+ t.id
1010
+ )) }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", role: "group", "aria-label": "Colours", children: [
1012
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1013
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1014
+ "input",
1015
+ {
1016
+ type: "color",
1017
+ "aria-label": "Foreground colour",
1018
+ value: foreground,
1019
+ onChange: (e) => controller.setPaintOptions({ foreground: e.target.value }),
1020
+ className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1021
+ }
1022
+ ),
1023
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "FG" })
1024
+ ] }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1026
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1027
+ "input",
1028
+ {
1029
+ type: "color",
1030
+ "aria-label": "Background colour",
1031
+ value: background,
1032
+ onChange: (e) => controller.setPaintOptions({ background: e.target.value }),
1033
+ className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1034
+ }
1035
+ ),
1036
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "BG" })
1037
+ ] }),
1038
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Swap colours (X)", className: chip(false), onClick: () => controller.swapPaintColors(), children: "Swap" }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Default colours (D)", className: chip(false), onClick: () => controller.resetPaintColors(), children: "Default" })
1040
+ ] }),
1041
+ import_image_editor7.BRUSH_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1042
+ ControlSlider,
1043
+ {
1044
+ spec,
1045
+ value: brush[spec.id],
1046
+ onChange: (v) => controller.setPaintOptions({ brush: { [spec.id]: v } })
1047
+ },
1048
+ spec.id
1049
+ )),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap gap-3 text-slate-300", role: "group", "aria-label": "Pressure", children: [
1051
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1052
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1053
+ "input",
1054
+ {
1055
+ type: "checkbox",
1056
+ "aria-label": "Pressure controls size",
1057
+ checked: brush.pressureSize,
1058
+ onChange: (e) => controller.setPaintOptions({ brush: { pressureSize: e.target.checked } }),
1059
+ className: "accent-indigo-500"
1060
+ }
1061
+ ),
1062
+ "Pressure size"
1063
+ ] }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1066
+ "input",
1067
+ {
1068
+ type: "checkbox",
1069
+ "aria-label": "Pressure controls opacity",
1070
+ checked: brush.pressureOpacity,
1071
+ onChange: (e) => controller.setPaintOptions({ brush: { pressureOpacity: e.target.checked } }),
1072
+ className: "accent-indigo-500"
1073
+ }
1074
+ ),
1075
+ "Pressure opacity"
1076
+ ] })
1077
+ ] }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-1", children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.fillPixels()), children: "Fill" }),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.clearPixels()), children: "Clear" })
1081
+ ] }),
1082
+ error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "text-rose-400", children: error })
1083
+ ] });
1084
+ };
1085
+
1086
+ // src/image-editor/SelectionOptionsBar.tsx
1087
+ var import_react7 = require("react");
1088
+ var import_image_editor8 = require("@pixerate/editor/image-editor");
1089
+ var import_jsx_runtime7 = require("react/jsx-runtime");
640
1090
  var MODIFY_OPS = [
641
1091
  { op: "grow", label: "Grow" },
642
1092
  { op: "shrink", label: "Shrink" },
@@ -653,20 +1103,20 @@ var SelectionOptionsBar = ({
653
1103
  quickMask,
654
1104
  className = ""
655
1105
  }) => {
656
- const [radius, setRadius] = (0, import_react5.useState)(2);
1106
+ const [radius, setRadius] = (0, import_react7.useState)(2);
657
1107
  const set = (patch) => onOptionsChange({ ...options, ...patch });
658
1108
  const sel = selection === void 0 ? controller.getSelection() : selection;
659
1109
  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 })
1110
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1111
+ /* @__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)) }),
1112
+ /* @__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)) }),
1113
+ options.tool === "wand" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-2", children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex flex-col gap-1", children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Tolerance" }),
1117
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: options.tolerance })
668
1118
  ] }),
669
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1119
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
670
1120
  "input",
671
1121
  {
672
1122
  type: "range",
@@ -680,29 +1130,29 @@ var SelectionOptionsBar = ({
680
1130
  }
681
1131
  )
682
1132
  ] }),
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 }) }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1134
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
685
1135
  "Contiguous"
686
1136
  ] }),
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 }) }),
1137
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1138
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
689
1139
  "Sample adjusted image"
690
1140
  ] })
691
1141
  ] }),
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 }) }),
1142
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1143
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
694
1144
  "Anti-alias"
695
1145
  ] }),
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" })
1146
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1147
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
1148
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
1150
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
701
1151
  ] }),
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: [
1152
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1153
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1 text-slate-400", children: [
704
1154
  "Radius",
705
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1155
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
706
1156
  "input",
707
1157
  {
708
1158
  type: "number",
@@ -716,24 +1166,24 @@ var SelectionOptionsBar = ({
716
1166
  }
717
1167
  )
718
1168
  ] }),
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))
1169
+ 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
1170
  ] }),
721
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { "data-testid": "selection-status", className: "text-slate-400", children: (0, import_image_editor6.describeSelection)(sel) })
1171
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { "data-testid": "selection-status", className: "text-slate-400", children: (0, import_image_editor8.describeSelection)(sel) })
722
1172
  ] });
723
1173
  };
724
1174
 
725
1175
  // 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");
1176
+ var import_react8 = require("react");
1177
+ var import_image_editor9 = require("@pixerate/editor/image-editor");
1178
+ var import_jsx_runtime8 = require("react/jsx-runtime");
729
1179
  var DEFAULT_PARAMS = { radius: 8, smartRadius: false, smooth: 0, feather: 0, contrast: 0, shiftEdge: 0 };
730
1180
  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
1181
  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");
1182
+ const [params, setParams] = (0, import_react8.useState)(DEFAULT_PARAMS);
1183
+ const [busy, setBusy] = (0, import_react8.useState)(false);
1184
+ const [error, setError] = (0, import_react8.useState)(null);
1185
+ const [layerId, setLayerId] = (0, import_react8.useState)("");
1186
+ const [newKind, setNewKind] = (0, import_react8.useState)("hueSaturation");
737
1187
  const sel = selection === void 0 ? controller.getSelection() : selection;
738
1188
  const list = layers ?? controller.getAdjustmentLayers();
739
1189
  const target = list.find((l) => l.id === layerId) ?? list[list.length - 1];
@@ -748,17 +1198,17 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
748
1198
  setBusy(false);
749
1199
  }
750
1200
  };
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: [
1201
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
1203
+ import_image_editor9.REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1204
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1205
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: c.label }),
1206
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
757
1207
  params[c.id],
758
1208
  c.unit
759
1209
  ] })
760
1210
  ] }),
761
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1211
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
762
1212
  "input",
763
1213
  {
764
1214
  type: "range",
@@ -772,67 +1222,67 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
772
1222
  }
773
1223
  )
774
1224
  ] }, 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 }) }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1226
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
777
1227
  "Smart Radius"
778
1228
  ] }),
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" })
1229
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-1", children: [
1230
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
782
1232
  ] }),
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)(
1233
+ error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-1 items-center", children: [
1236
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
787
1237
  "select",
788
1238
  {
789
1239
  "aria-label": "New masked layer kind",
790
1240
  value: newKind,
791
1241
  onChange: (e) => setNewKind(e.target.value),
792
1242
  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))
1243
+ children: import_image_editor9.ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: k.kind, children: k.label }, k.kind))
794
1244
  }
795
1245
  ),
796
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1246
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
797
1247
  "button",
798
1248
  {
799
1249
  type: "button",
800
1250
  className: button,
801
1251
  disabled: !sel,
802
- onClick: () => sel && setLayerId(controller.addAdjustmentLayer({ kind: newKind, mask: (0, import_image_editor7.layerMaskFromSelection)(sel) })),
1252
+ onClick: () => sel && setLayerId(controller.addAdjustmentLayer({ kind: newKind, mask: (0, import_image_editor9.layerMaskFromSelection)(sel) })),
803
1253
  children: "New Masked Layer"
804
1254
  }
805
1255
  )
806
1256
  ] }),
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)(
1257
+ list.length > 0 && target && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-2", children: [
1258
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
809
1259
  "select",
810
1260
  {
811
1261
  "aria-label": "Mask layer",
812
1262
  value: target.id,
813
1263
  onChange: (e) => setLayerId(e.target.value),
814
1264
  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),
1265
+ children: list.map((l) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("option", { value: l.id, children: [
1266
+ l.name || (0, import_image_editor9.adjustmentKindLabel)(l.kind),
817
1267
  l.mask ? " (masked)" : ""
818
1268
  ] }, l.id))
819
1269
  }
820
1270
  ),
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" })
1271
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1272
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
1273
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
1274
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
825
1275
  ] }),
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: [
1276
+ target.mask && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1277
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1278
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1279
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Density" }),
1280
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
831
1281
  Math.round((target.mask.density ?? 1) * 100),
832
1282
  "%"
833
1283
  ] })
834
1284
  ] }),
835
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1285
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
836
1286
  "input",
837
1287
  {
838
1288
  type: "range",
@@ -846,15 +1296,15 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
846
1296
  }
847
1297
  )
848
1298
  ] }),
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: [
1299
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1300
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1301
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Mask Feather" }),
1302
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
853
1303
  target.mask.feather ?? 0,
854
1304
  "px"
855
1305
  ] })
856
1306
  ] }),
857
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1307
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
858
1308
  "input",
859
1309
  {
860
1310
  type: "range",
@@ -874,7 +1324,7 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
874
1324
  };
875
1325
 
876
1326
  // src/image-editor/ImageEditor.tsx
877
- var import_jsx_runtime7 = require("react/jsx-runtime");
1327
+ var import_jsx_runtime9 = require("react/jsx-runtime");
878
1328
  var ImageEditor = ({
879
1329
  editor: externalEditor,
880
1330
  src,
@@ -889,26 +1339,34 @@ var ImageEditor = ({
889
1339
  const internalEditor = useImageEditor({ image: src });
890
1340
  const editor = externalEditor || internalEditor;
891
1341
  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);
1342
+ const legacyProcess = (0, import_image_editor10.normalizeProcessVersion)(state.adjustments.process) < import_image_editor10.LATEST_PROCESS_VERSION;
1343
+ const canvasRef = (0, import_react9.useRef)(null);
1344
+ const containerRef = (0, import_react9.useRef)(null);
1345
+ const [activeTab, setActiveTab] = (0, import_react9.useState)("crop");
1346
+ const [activeCropRatio, setActiveCropRatio] = (0, import_react9.useState)(null);
1347
+ const [selectionOptions, setSelectionOptions] = (0, import_react9.useState)(import_image_editor10.DEFAULT_SELECTION_TOOL_OPTIONS);
1348
+ const selectionOptionsRef = (0, import_react9.useRef)(selectionOptions);
899
1349
  selectionOptionsRef.current = selectionOptions;
900
- const selectionGestureRef = (0, import_react7.useRef)(null);
1350
+ const selectionGestureRef = (0, import_react9.useRef)(null);
901
1351
  if (!selectionGestureRef.current || selectionGestureRef.current.controller !== controller) {
902
- selectionGestureRef.current = new import_image_editor8.SelectionGesture(controller, () => selectionOptionsRef.current);
1352
+ selectionGestureRef.current = new import_image_editor10.SelectionGesture(controller, () => selectionOptionsRef.current);
1353
+ }
1354
+ const selectionDraftRef = (0, import_react9.useRef)(null);
1355
+ const [selectionError, setSelectionError] = (0, import_react9.useState)(null);
1356
+ const paintGestureRef = (0, import_react9.useRef)(null);
1357
+ const paintControllerRef = (0, import_react9.useRef)(controller);
1358
+ if (!paintGestureRef.current || paintControllerRef.current !== controller) {
1359
+ paintGestureRef.current = new import_image_editor10.PaintGesture(controller);
1360
+ paintControllerRef.current = controller;
903
1361
  }
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);
1362
+ const brushCursorRef = (0, import_react9.useRef)(null);
1363
+ const [paintError, setPaintError] = (0, import_react9.useState)(null);
1364
+ const [inpaintPrompt, setInpaintPrompt] = (0, import_react9.useState)("");
1365
+ const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react9.useState)("annotations");
1366
+ const [isRemovingBg, setIsRemovingBg] = (0, import_react9.useState)(false);
1367
+ const [isInpainting, setIsInpainting] = (0, import_react9.useState)(false);
1368
+ const [aiError, setAiError] = (0, import_react9.useState)(null);
1369
+ const [aiSuccess, setAiSuccess] = (0, import_react9.useState)(null);
912
1370
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
913
1371
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
914
1372
  const handleRemoveBackground = async () => {
@@ -970,18 +1428,18 @@ var ImageEditor = ({
970
1428
  setIsInpainting(false);
971
1429
  }
972
1430
  };
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)(
1431
+ const [activeColor, setActiveColor] = (0, import_react9.useState)("#ef4444");
1432
+ const [strokeWidth, setStrokeWidth] = (0, import_react9.useState)(3);
1433
+ const [fontSize, setFontSize] = (0, import_react9.useState)(24);
1434
+ const [textInput, setTextInput] = (0, import_react9.useState)("");
1435
+ const draftAnnotationRef = (0, import_react9.useRef)(null);
1436
+ const draftCropRef = (0, import_react9.useRef)(null);
1437
+ const cropDragInfoRef = (0, import_react9.useRef)(null);
1438
+ const annotationDragInfoRef = (0, import_react9.useRef)(null);
1439
+ const isDrawingRef = (0, import_react9.useRef)(false);
1440
+ const currentPathRef = (0, import_react9.useRef)([]);
1441
+ const dragStartPointRef = (0, import_react9.useRef)(null);
1442
+ const renderCanvas = (0, import_react9.useCallback)(
985
1443
  async (draftAnnotation, draftCrop) => {
986
1444
  const canvas = canvasRef.current;
987
1445
  const container = containerRef.current;
@@ -996,12 +1454,13 @@ var ImageEditor = ({
996
1454
  interactive: true,
997
1455
  draftAnnotation: draftAnnotation !== void 0 ? draftAnnotation : draftAnnotationRef.current,
998
1456
  draftCrop: draftCrop !== void 0 ? draftCrop : draftCropRef.current,
999
- draftSelectionPath: selectionDraftRef.current
1457
+ draftSelectionPath: selectionDraftRef.current,
1458
+ brushCursor: activeTab === "paint" && brushCursorRef.current && controller.getState().activeTool !== "move" ? { ...brushCursorRef.current, diameter: controller.getState().paint.brush.size } : null
1000
1459
  });
1001
1460
  },
1002
1461
  [controller, activeTab]
1003
1462
  );
1004
- (0, import_react7.useEffect)(() => {
1463
+ (0, import_react9.useEffect)(() => {
1005
1464
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
1006
1465
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
1007
1466
  const animId = raf(() => {
@@ -1009,12 +1468,12 @@ var ImageEditor = ({
1009
1468
  });
1010
1469
  return () => caf(animId);
1011
1470
  }, [renderCanvas, state]);
1012
- (0, import_react7.useEffect)(() => {
1471
+ (0, import_react9.useEffect)(() => {
1013
1472
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
1014
1473
  editor.setTool("crop");
1015
1474
  }
1016
1475
  }, [activeTab, editor]);
1017
- (0, import_react7.useEffect)(() => {
1476
+ (0, import_react9.useEffect)(() => {
1018
1477
  const container = containerRef.current;
1019
1478
  if (!container) return;
1020
1479
  if (typeof ResizeObserver !== "undefined") {
@@ -1040,13 +1499,60 @@ var ImageEditor = ({
1040
1499
  y: (e.clientY - rect.top) * scaleY
1041
1500
  };
1042
1501
  };
1502
+ const paintSamples = (e) => {
1503
+ const canvas = canvasRef.current;
1504
+ const native = e.nativeEvent;
1505
+ const events = typeof native.getCoalescedEvents === "function" ? native.getCoalescedEvents() : [];
1506
+ const list = events.length ? events : [native];
1507
+ const rect = canvas.getBoundingClientRect();
1508
+ const sx = rect.width > 0 ? canvas.width / rect.width : 1;
1509
+ const sy = rect.height > 0 ? canvas.height / rect.height : 1;
1510
+ const s = controller.getState();
1511
+ return list.map((ev) => ({
1512
+ ...(0, import_image_editor10.canvasToImagePoint)({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s, canvas.width, canvas.height, false),
1513
+ pressure: (0, import_image_editor10.pointerPressure)(ev)
1514
+ }));
1515
+ };
1516
+ const handlePaintPointerDown = (e) => {
1517
+ if (activeTab !== "paint" || e.button !== 0) return;
1518
+ e.preventDefault();
1519
+ try {
1520
+ e.currentTarget.setPointerCapture?.(e.pointerId);
1521
+ } catch {
1522
+ }
1523
+ const [first] = paintSamples(e);
1524
+ setPaintError(paintGestureRef.current.down(first));
1525
+ };
1526
+ const handlePaintPointerMove = (e) => {
1527
+ if (activeTab !== "paint") return;
1528
+ const samples = paintSamples(e);
1529
+ brushCursorRef.current = samples[samples.length - 1] ?? null;
1530
+ if (paintGestureRef.current.active) paintGestureRef.current.moveTo(samples);
1531
+ else void renderCanvas();
1532
+ };
1533
+ const handlePaintPointerUp = () => {
1534
+ if (activeTab !== "paint") return;
1535
+ const g = paintGestureRef.current;
1536
+ if (g.active) {
1537
+ g.up();
1538
+ if (g.error) setPaintError(g.error);
1539
+ }
1540
+ };
1541
+ (0, import_react9.useEffect)(() => {
1542
+ if (activeTab !== "paint" || typeof window === "undefined") return;
1543
+ const onKey = (e) => {
1544
+ if ((0, import_image_editor10.handlePaintShortcut)(controller, e)) e.preventDefault();
1545
+ };
1546
+ window.addEventListener("keydown", onKey);
1547
+ return () => window.removeEventListener("keydown", onKey);
1548
+ }, [activeTab, controller]);
1043
1549
  const handleMouseDown = (e) => {
1044
1550
  const canvas = canvasRef.current;
1045
1551
  if (!canvas) return;
1046
1552
  const canvasPt = getCanvasEventPoint(e);
1047
1553
  if (activeTab === "select") {
1048
1554
  const gesture = selectionGestureRef.current;
1049
- const imgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1555
+ const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1050
1556
  void gesture.begin(imgPt, e).then(() => setSelectionError(gesture.error));
1051
1557
  return;
1052
1558
  }
@@ -1057,8 +1563,8 @@ var ImageEditor = ({
1057
1563
  width: state.imageDimensions.width,
1058
1564
  height: state.imageDimensions.height
1059
1565
  };
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);
1566
+ const handle = (0, import_image_editor10.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1567
+ const startImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1062
1568
  if (handle) {
1063
1569
  cropDragInfoRef.current = {
1064
1570
  handle,
@@ -1077,12 +1583,12 @@ var ImageEditor = ({
1077
1583
  return;
1078
1584
  }
1079
1585
  if (activeTab === "annotate") {
1080
- const imgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1586
+ const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1081
1587
  dragStartPointRef.current = imgPt;
1082
1588
  if (state.activeTool === "select") {
1083
1589
  for (let i = state.annotations.length - 1; i >= 0; i--) {
1084
1590
  const ann = state.annotations[i];
1085
- if ((0, import_image_editor8.hitTestAnnotation)(imgPt, ann)) {
1591
+ if ((0, import_image_editor10.hitTestAnnotation)(imgPt, ann)) {
1086
1592
  editor.selectAnnotation(ann.id);
1087
1593
  annotationDragInfoRef.current = {
1088
1594
  annId: ann.id,
@@ -1118,7 +1624,7 @@ var ImageEditor = ({
1118
1624
  const canvasPt = getCanvasEventPoint(e);
1119
1625
  if (activeTab === "select") {
1120
1626
  const gesture = selectionGestureRef.current;
1121
- if (gesture.active && gesture.move((0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false))) {
1627
+ if (gesture.active && gesture.move((0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false))) {
1122
1628
  selectionDraftRef.current = gesture.draftPath;
1123
1629
  renderCanvas();
1124
1630
  }
@@ -1127,7 +1633,7 @@ var ImageEditor = ({
1127
1633
  if (activeTab === "crop") {
1128
1634
  const drag = cropDragInfoRef.current;
1129
1635
  if (drag) {
1130
- const currentImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1636
+ const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1131
1637
  let newCrop;
1132
1638
  if (drag.handle === "new") {
1133
1639
  const minX = Math.max(0, Math.min(drag.startImgPt.x, currentImgPt.x));
@@ -1141,7 +1647,7 @@ var ImageEditor = ({
1141
1647
  height: Math.round(Math.max(20, maxY - minY))
1142
1648
  };
1143
1649
  } else {
1144
- newCrop = (0, import_image_editor8.calculateCropDrag)(
1650
+ newCrop = (0, import_image_editor10.calculateCropDrag)(
1145
1651
  drag.startCrop,
1146
1652
  drag.handle,
1147
1653
  drag.startImgPt,
@@ -1161,15 +1667,15 @@ var ImageEditor = ({
1161
1667
  width: state.imageDimensions.width,
1162
1668
  height: state.imageDimensions.height
1163
1669
  };
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);
1670
+ const handle = (0, import_image_editor10.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1671
+ canvas.style.cursor = (0, import_image_editor10.getCropCursor)(handle, state.transform);
1166
1672
  }
1167
1673
  return;
1168
1674
  }
1169
1675
  if (activeTab === "annotate") {
1170
1676
  if (annotationDragInfoRef.current) {
1171
1677
  const drag = annotationDragInfoRef.current;
1172
- const currentImgPt2 = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1678
+ const currentImgPt2 = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1173
1679
  const dx = currentImgPt2.x - drag.startImgPt.x;
1174
1680
  const dy = currentImgPt2.y - drag.startImgPt.y;
1175
1681
  editor.updateAnnotation(drag.annId, {
@@ -1181,10 +1687,10 @@ var ImageEditor = ({
1181
1687
  const startPt = dragStartPointRef.current;
1182
1688
  if (!startPt) {
1183
1689
  if (state.activeTool === "select") {
1184
- const imgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1690
+ const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1185
1691
  let hovered = false;
1186
1692
  for (let i = state.annotations.length - 1; i >= 0; i--) {
1187
- if ((0, import_image_editor8.hitTestAnnotation)(imgPt, state.annotations[i])) {
1693
+ if ((0, import_image_editor10.hitTestAnnotation)(imgPt, state.annotations[i])) {
1188
1694
  hovered = true;
1189
1695
  break;
1190
1696
  }
@@ -1195,7 +1701,7 @@ var ImageEditor = ({
1195
1701
  }
1196
1702
  return;
1197
1703
  }
1198
- const currentImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1704
+ const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1199
1705
  if (state.activeTool === "pen" && isDrawingRef.current) {
1200
1706
  currentPathRef.current.push(currentImgPt);
1201
1707
  const draft = {
@@ -1275,7 +1781,7 @@ var ImageEditor = ({
1275
1781
  if (activeTab === "select") {
1276
1782
  const gesture = selectionGestureRef.current;
1277
1783
  if (gesture.active) {
1278
- gesture.end((0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false), e);
1784
+ gesture.end((0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false), e);
1279
1785
  selectionDraftRef.current = null;
1280
1786
  renderCanvas();
1281
1787
  }
@@ -1299,7 +1805,7 @@ var ImageEditor = ({
1299
1805
  }
1300
1806
  const startPt = dragStartPointRef.current;
1301
1807
  if (startPt) {
1302
- const currentImgPt = (0, import_image_editor8.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1808
+ const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1303
1809
  if (state.activeTool === "pen" && isDrawingRef.current) {
1304
1810
  if (currentPathRef.current.length > 1) {
1305
1811
  editor.addAnnotation({
@@ -1395,15 +1901,15 @@ var ImageEditor = ({
1395
1901
  link.click();
1396
1902
  }
1397
1903
  };
1398
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1904
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1399
1905
  "div",
1400
1906
  {
1401
1907
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
1402
1908
  style,
1403
1909
  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)(
1910
+ /* @__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: [
1911
+ /* @__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: [
1912
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1407
1913
  "button",
1408
1914
  {
1409
1915
  type: "button",
@@ -1415,7 +1921,7 @@ var ImageEditor = ({
1415
1921
  children: "Transform & Crop"
1416
1922
  }
1417
1923
  ),
1418
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1924
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1419
1925
  "button",
1420
1926
  {
1421
1927
  type: "button",
@@ -1427,7 +1933,31 @@ var ImageEditor = ({
1427
1933
  children: "Adjustments"
1428
1934
  }
1429
1935
  ),
1430
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1936
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1937
+ "button",
1938
+ {
1939
+ type: "button",
1940
+ onClick: () => {
1941
+ setActiveTab("layers");
1942
+ editor.setTool("select");
1943
+ },
1944
+ 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"}`,
1945
+ children: "Layers"
1946
+ }
1947
+ ),
1948
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1949
+ "button",
1950
+ {
1951
+ type: "button",
1952
+ onClick: () => {
1953
+ setActiveTab("paint");
1954
+ editor.setTool("brush");
1955
+ },
1956
+ 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"}`,
1957
+ children: "Paint"
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1431
1961
  "button",
1432
1962
  {
1433
1963
  type: "button",
@@ -1439,7 +1969,7 @@ var ImageEditor = ({
1439
1969
  children: "Select & Mask"
1440
1970
  }
1441
1971
  ),
1442
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1972
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1443
1973
  "button",
1444
1974
  {
1445
1975
  type: "button",
@@ -1451,7 +1981,7 @@ var ImageEditor = ({
1451
1981
  children: "Annotations"
1452
1982
  }
1453
1983
  ),
1454
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1984
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1455
1985
  "button",
1456
1986
  {
1457
1987
  type: "button",
@@ -1463,7 +1993,7 @@ var ImageEditor = ({
1463
1993
  children: "Depth Mask"
1464
1994
  }
1465
1995
  ),
1466
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1996
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1467
1997
  "button",
1468
1998
  {
1469
1999
  type: "button",
@@ -1473,14 +2003,14 @@ var ImageEditor = ({
1473
2003
  },
1474
2004
  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
2005
  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" }) }),
2006
+ /* @__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
2007
  "AI Tools"
1478
2008
  ]
1479
2009
  }
1480
2010
  )
1481
2011
  ] }),
1482
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
1483
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2012
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
2013
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1484
2014
  "button",
1485
2015
  {
1486
2016
  type: "button",
@@ -1491,7 +2021,7 @@ var ImageEditor = ({
1491
2021
  children: "\u21A9"
1492
2022
  }
1493
2023
  ),
1494
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2024
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1495
2025
  "button",
1496
2026
  {
1497
2027
  type: "button",
@@ -1502,7 +2032,7 @@ var ImageEditor = ({
1502
2032
  children: "\u21AA"
1503
2033
  }
1504
2034
  ),
1505
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2035
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1506
2036
  "button",
1507
2037
  {
1508
2038
  type: "button",
@@ -1511,8 +2041,8 @@ var ImageEditor = ({
1511
2041
  children: "Reset"
1512
2042
  }
1513
2043
  ),
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)(
2044
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2045
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1516
2046
  "button",
1517
2047
  {
1518
2048
  type: "button",
@@ -1522,11 +2052,11 @@ var ImageEditor = ({
1522
2052
  children: "-"
1523
2053
  }
1524
2054
  ),
1525
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
2055
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
1526
2056
  Math.round(state.zoom * 100),
1527
2057
  "%"
1528
2058
  ] }),
1529
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2059
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1530
2060
  "button",
1531
2061
  {
1532
2062
  type: "button",
@@ -1536,8 +2066,8 @@ var ImageEditor = ({
1536
2066
  children: "+"
1537
2067
  }
1538
2068
  ),
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)(
2069
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2070
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1541
2071
  "button",
1542
2072
  {
1543
2073
  type: "button",
@@ -1548,7 +2078,7 @@ var ImageEditor = ({
1548
2078
  children: "Hold Compare"
1549
2079
  }
1550
2080
  ),
1551
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2081
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1552
2082
  "button",
1553
2083
  {
1554
2084
  type: "button",
@@ -1559,13 +2089,13 @@ var ImageEditor = ({
1559
2089
  )
1560
2090
  ] })
1561
2091
  ] }),
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)(
2092
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
2093
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1564
2094
  "div",
1565
2095
  {
1566
2096
  ref: containerRef,
1567
2097
  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)(
2098
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1569
2099
  "canvas",
1570
2100
  {
1571
2101
  ref: canvasRef,
@@ -1573,16 +2103,21 @@ var ImageEditor = ({
1573
2103
  onMouseMove: handleMouseMove,
1574
2104
  onMouseUp: handleMouseUp,
1575
2105
  onMouseLeave: handleMouseUp,
1576
- className: "absolute inset-0 w-full h-full block"
2106
+ onPointerDown: handlePaintPointerDown,
2107
+ onPointerMove: handlePaintPointerMove,
2108
+ onPointerUp: handlePaintPointerUp,
2109
+ onPointerCancel: () => paintGestureRef.current?.cancel(),
2110
+ "data-testid": "image-editor-canvas",
2111
+ className: `absolute inset-0 w-full h-full block ${activeTab === "paint" ? "touch-none cursor-crosshair" : ""}`
1577
2112
  }
1578
2113
  )
1579
2114
  }
1580
2115
  ),
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)(
2116
+ /* @__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: [
2117
+ activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2118
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
2119
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
2120
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1586
2121
  "button",
1587
2122
  {
1588
2123
  type: "button",
@@ -1594,7 +2129,7 @@ var ImageEditor = ({
1594
2129
  children: "Freeform"
1595
2130
  }
1596
2131
  ),
1597
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2132
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1598
2133
  "button",
1599
2134
  {
1600
2135
  type: "button",
@@ -1606,7 +2141,7 @@ var ImageEditor = ({
1606
2141
  children: "1:1 Square"
1607
2142
  }
1608
2143
  ),
1609
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2144
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1610
2145
  "button",
1611
2146
  {
1612
2147
  type: "button",
@@ -1618,7 +2153,7 @@ var ImageEditor = ({
1618
2153
  children: "4:3 Standard"
1619
2154
  }
1620
2155
  ),
1621
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2156
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1622
2157
  "button",
1623
2158
  {
1624
2159
  type: "button",
@@ -1630,7 +2165,7 @@ var ImageEditor = ({
1630
2165
  children: "16:9 Wide"
1631
2166
  }
1632
2167
  ),
1633
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2168
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1634
2169
  "button",
1635
2170
  {
1636
2171
  type: "button",
@@ -1642,7 +2177,7 @@ var ImageEditor = ({
1642
2177
  children: "3:2 Photo"
1643
2178
  }
1644
2179
  ),
1645
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2180
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1646
2181
  "button",
1647
2182
  {
1648
2183
  type: "button",
@@ -1655,10 +2190,10 @@ var ImageEditor = ({
1655
2190
  }
1656
2191
  )
1657
2192
  ] }),
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)(
2193
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2194
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
2195
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
2196
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1662
2197
  "button",
1663
2198
  {
1664
2199
  type: "button",
@@ -1667,7 +2202,7 @@ var ImageEditor = ({
1667
2202
  children: "Rotate -90\xB0"
1668
2203
  }
1669
2204
  ),
1670
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2205
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1671
2206
  "button",
1672
2207
  {
1673
2208
  type: "button",
@@ -1676,7 +2211,7 @@ var ImageEditor = ({
1676
2211
  children: "Rotate +90\xB0"
1677
2212
  }
1678
2213
  ),
1679
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2214
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1680
2215
  "button",
1681
2216
  {
1682
2217
  type: "button",
@@ -1685,7 +2220,7 @@ var ImageEditor = ({
1685
2220
  children: "Flip Horizontal"
1686
2221
  }
1687
2222
  ),
1688
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2223
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1689
2224
  "button",
1690
2225
  {
1691
2226
  type: "button",
@@ -1696,10 +2231,10 @@ var ImageEditor = ({
1696
2231
  )
1697
2232
  ] })
1698
2233
  ] }),
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)(
2234
+ activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2235
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2236
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
2237
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1703
2238
  "button",
1704
2239
  {
1705
2240
  type: "button",
@@ -1709,25 +2244,25 @@ var ImageEditor = ({
1709
2244
  }
1710
2245
  )
1711
2246
  ] }),
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)(
2247
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Histogram, { histogram: editor.histogram }),
2248
+ import_image_editor10.IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react9.default.Fragment, { children: [
2249
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1715
2250
  ControlSlider,
1716
2251
  {
1717
2252
  spec,
1718
- value: (0, import_image_editor8.readAdjustmentControls)(state.adjustments)[spec.id],
2253
+ value: (0, import_image_editor10.readAdjustmentControls)(state.adjustments)[spec.id],
1719
2254
  disabled: spec.requiresProcess !== void 0 && legacyProcess,
1720
2255
  onChange: (v) => editor.setAdjustments({ [spec.id]: v })
1721
2256
  }
1722
2257
  ),
1723
- spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2258
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1724
2259
  "div",
1725
2260
  {
1726
2261
  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
2262
  "data-testid": "image-editor-process-notice",
1728
2263
  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)(
2264
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
2265
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1731
2266
  "button",
1732
2267
  {
1733
2268
  type: "button",
@@ -1740,7 +2275,7 @@ var ImageEditor = ({
1740
2275
  }
1741
2276
  )
1742
2277
  ] }, 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)(
2278
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1744
2279
  AdjustmentLayersPanel,
1745
2280
  {
1746
2281
  controller,
@@ -1749,9 +2284,19 @@ var ImageEditor = ({
1749
2284
  }
1750
2285
  ) })
1751
2286
  ] }),
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)(
2287
+ activeTab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2288
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Layers" }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LayersPanel, { controller })
2290
+ ] }),
2291
+ activeTab === "paint" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2292
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Paint" }),
2293
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BrushOptionsBar, { controller }),
2294
+ paintError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", "data-testid": "paint-error", children: paintError }),
2295
+ /* @__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 }) })
2296
+ ] }),
2297
+ activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2298
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
2299
+ /* @__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
2300
  "button",
1756
2301
  {
1757
2302
  type: "button",
@@ -1761,9 +2306,9 @@ var ImageEditor = ({
1761
2306
  },
1762
2307
  t
1763
2308
  )) }),
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)(
2309
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
2310
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "text-slate-400", children: "Color:" }),
2311
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1767
2312
  "input",
1768
2313
  {
1769
2314
  type: "color",
@@ -1772,15 +2317,15 @@ var ImageEditor = ({
1772
2317
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
1773
2318
  }
1774
2319
  ),
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: [
2320
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex-1 ml-2", children: [
2321
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
2322
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Width:" }),
2323
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1779
2324
  strokeWidth,
1780
2325
  "px"
1781
2326
  ] })
1782
2327
  ] }),
1783
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2328
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1784
2329
  "input",
1785
2330
  {
1786
2331
  type: "range",
@@ -1793,10 +2338,10 @@ var ImageEditor = ({
1793
2338
  )
1794
2339
  ] })
1795
2340
  ] }),
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)(
2341
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
2343
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
2344
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1800
2345
  "input",
1801
2346
  {
1802
2347
  type: "text",
@@ -1807,7 +2352,7 @@ var ImageEditor = ({
1807
2352
  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
2353
  }
1809
2354
  ),
1810
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2355
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1811
2356
  "button",
1812
2357
  {
1813
2358
  type: "button",
@@ -1817,14 +2362,14 @@ var ImageEditor = ({
1817
2362
  }
1818
2363
  )
1819
2364
  ] }),
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: [
2365
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
2366
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Font Size:" }),
2367
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1823
2368
  fontSize,
1824
2369
  "px"
1825
2370
  ] })
1826
2371
  ] }),
1827
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2372
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1828
2373
  "input",
1829
2374
  {
1830
2375
  type: "range",
@@ -1835,14 +2380,14 @@ var ImageEditor = ({
1835
2380
  className: "w-full accent-indigo-500"
1836
2381
  }
1837
2382
  ),
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: [
2383
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2384
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2385
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-slate-400", children: [
1841
2386
  "Layers (",
1842
2387
  state.annotations.length,
1843
2388
  ")"
1844
2389
  ] }),
1845
- state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2390
+ state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1846
2391
  "button",
1847
2392
  {
1848
2393
  type: "button",
@@ -1851,7 +2396,7 @@ var ImageEditor = ({
1851
2396
  children: "Delete Selected"
1852
2397
  }
1853
2398
  ),
1854
- state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2399
+ state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1855
2400
  "button",
1856
2401
  {
1857
2402
  type: "button",
@@ -1862,10 +2407,10 @@ var ImageEditor = ({
1862
2407
  )
1863
2408
  ] })
1864
2409
  ] }),
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)(
2410
+ activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2411
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2412
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
2413
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1869
2414
  "button",
1870
2415
  {
1871
2416
  type: "button",
@@ -1875,8 +2420,8 @@ var ImageEditor = ({
1875
2420
  }
1876
2421
  )
1877
2422
  ] }),
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)(
2423
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
2424
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1880
2425
  "input",
1881
2426
  {
1882
2427
  type: "checkbox",
@@ -1885,12 +2430,12 @@ var ImageEditor = ({
1885
2430
  className: "rounded accent-indigo-500"
1886
2431
  }
1887
2432
  ),
1888
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Enable Depth / Alpha Mask" })
2433
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Enable Depth / Alpha Mask" })
1889
2434
  ] }),
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)(
2435
+ state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2436
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2437
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
2438
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1894
2439
  "input",
1895
2440
  {
1896
2441
  type: "text",
@@ -1901,8 +2446,8 @@ var ImageEditor = ({
1901
2446
  }
1902
2447
  )
1903
2448
  ] }),
1904
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-2", children: [
1905
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2449
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
2450
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1906
2451
  "button",
1907
2452
  {
1908
2453
  type: "button",
@@ -1911,7 +2456,7 @@ var ImageEditor = ({
1911
2456
  children: "Threshold Mode"
1912
2457
  }
1913
2458
  ),
1914
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2459
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1915
2460
  "button",
1916
2461
  {
1917
2462
  type: "button",
@@ -1921,12 +2466,12 @@ var ImageEditor = ({
1921
2466
  }
1922
2467
  )
1923
2468
  ] }),
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] })
2469
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2470
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2471
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Depth Start (Lower):" }),
2472
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
1928
2473
  ] }),
1929
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2474
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1930
2475
  "input",
1931
2476
  {
1932
2477
  type: "range",
@@ -1940,12 +2485,12 @@ var ImageEditor = ({
1940
2485
  }
1941
2486
  )
1942
2487
  ] }),
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] })
2488
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2489
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2490
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Depth Stop (Upper):" }),
2491
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
1947
2492
  ] }),
1948
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2493
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1949
2494
  "input",
1950
2495
  {
1951
2496
  type: "range",
@@ -1959,12 +2504,12 @@ var ImageEditor = ({
1959
2504
  }
1960
2505
  )
1961
2506
  ] }),
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 })
2507
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2508
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2509
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Softness:" }),
2510
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
1966
2511
  ] }),
1967
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2512
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1968
2513
  "input",
1969
2514
  {
1970
2515
  type: "range",
@@ -1976,8 +2521,8 @@ var ImageEditor = ({
1976
2521
  }
1977
2522
  )
1978
2523
  ] }),
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)(
2524
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
2525
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1981
2526
  "input",
1982
2527
  {
1983
2528
  type: "checkbox",
@@ -1986,12 +2531,12 @@ var ImageEditor = ({
1986
2531
  className: "rounded accent-indigo-500"
1987
2532
  }
1988
2533
  ),
1989
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Invert Mask" })
2534
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Invert Mask" })
1990
2535
  ] })
1991
2536
  ] })
1992
2537
  ] }),
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)(
2538
+ activeTab === "select" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2539
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1995
2540
  SelectionOptionsBar,
1996
2541
  {
1997
2542
  controller,
@@ -2001,51 +2546,51 @@ var ImageEditor = ({
2001
2546
  quickMask: Boolean(state.quickMask)
2002
2547
  }
2003
2548
  ),
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 })
2549
+ selectionError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", children: selectionError }),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2006
2551
  ] }),
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" }) }),
2552
+ activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2553
+ /* @__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: [
2554
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2555
+ /* @__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: [
2556
+ /* @__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
2557
  "Background Removal"
2013
2558
  ] }),
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..." })
2559
+ 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
2560
  ] }),
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)(
2561
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2018
2563
  "button",
2019
2564
  {
2020
2565
  type: "button",
2021
2566
  onClick: handleRemoveBackground,
2022
2567
  disabled: isRemovingBg || isInpainting,
2023
2568
  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" }),
2569
+ children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2570
+ /* @__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
2571
  "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" }) }),
2572
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2573
+ /* @__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
2574
  "Remove Background"
2030
2575
  ] })
2031
2576
  }
2032
2577
  )
2033
2578
  ] }),
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" })
2579
+ /* @__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: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2581
+ /* @__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: [
2582
+ /* @__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: [
2583
+ /* @__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" }),
2584
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "m14 7 3 3" })
2040
2585
  ] }),
2041
2586
  "Generative Inpainting"
2042
2587
  ] }),
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..." })
2588
+ 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
2589
  ] }),
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)(
2590
+ /* @__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." }),
2591
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
2592
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2593
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2049
2594
  "textarea",
2050
2595
  {
2051
2596
  id: "ai-inpaint-prompt-react",
@@ -2057,10 +2602,10 @@ var ImageEditor = ({
2057
2602
  }
2058
2603
  )
2059
2604
  ] }),
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)(
2605
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
2606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
2608
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2064
2609
  "button",
2065
2610
  {
2066
2611
  type: "button",
@@ -2073,7 +2618,7 @@ var ImageEditor = ({
2073
2618
  ]
2074
2619
  }
2075
2620
  ),
2076
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2621
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2077
2622
  "button",
2078
2623
  {
2079
2624
  type: "button",
@@ -2088,25 +2633,25 @@ var ImageEditor = ({
2088
2633
  )
2089
2634
  ] })
2090
2635
  ] }),
2091
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2636
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2092
2637
  "button",
2093
2638
  {
2094
2639
  type: "button",
2095
2640
  onClick: handleInpaint,
2096
2641
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
2097
2642
  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" }),
2643
+ children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2644
+ /* @__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
2645
  "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" }) }),
2646
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2647
+ /* @__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
2648
  "Apply Inpainting"
2104
2649
  ] })
2105
2650
  }
2106
2651
  )
2107
2652
  ] }),
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 })
2653
+ 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 }),
2654
+ 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
2655
  ] })
2111
2656
  ] })
2112
2657
  ] })
@@ -2117,10 +2662,12 @@ var ImageEditor = ({
2117
2662
  // Annotate the CommonJS export names for ESM import in node:
2118
2663
  0 && (module.exports = {
2119
2664
  AdjustmentLayersPanel,
2665
+ BrushOptionsBar,
2120
2666
  ControlSlider,
2121
2667
  CurvesEditor,
2122
2668
  Histogram,
2123
2669
  ImageEditor,
2670
+ LayersPanel,
2124
2671
  RefineEdgePanel,
2125
2672
  SelectionOptionsBar,
2126
2673
  useImageEditor,