@pixerate/editor-react 0.63.0 → 0.65.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.
@@ -34,8 +34,10 @@ __export(image_editor_exports, {
34
34
  BrushOptionsBar: () => BrushOptionsBar,
35
35
  ControlSlider: () => ControlSlider,
36
36
  CurvesEditor: () => CurvesEditor,
37
+ FreeTransformOptionsBar: () => FreeTransformOptionsBar,
37
38
  Histogram: () => Histogram,
38
39
  ImageEditor: () => ImageEditor,
40
+ LayerEffectsPanel: () => LayerEffectsPanel,
39
41
  LayersPanel: () => LayersPanel,
40
42
  RefineEdgePanel: () => RefineEdgePanel,
41
43
  SelectionOptionsBar: () => SelectionOptionsBar,
@@ -203,8 +205,8 @@ function useImageEditor(options = {}) {
203
205
  }
204
206
 
205
207
  // src/image-editor/ImageEditor.tsx
206
- var import_react9 = __toESM(require("react"), 1);
207
- var import_image_editor10 = require("@pixerate/editor/image-editor");
208
+ var import_react11 = __toESM(require("react"), 1);
209
+ var import_image_editor12 = require("@pixerate/editor/image-editor");
208
210
 
209
211
  // src/image-editor/ControlSlider.tsx
210
212
  var import_image_editor2 = require("@pixerate/editor/image-editor");
@@ -636,9 +638,198 @@ var AdjustmentLayersPanel = ({ controller, layers: layersProp, histogram }) => {
636
638
  };
637
639
 
638
640
  // src/image-editor/LayersPanel.tsx
641
+ var import_react6 = require("react");
642
+
643
+ // src/image-editor/LayerEffectsPanel.tsx
639
644
  var import_react5 = require("react");
640
645
  var import_image_editor6 = require("@pixerate/editor/image-editor");
641
646
  var import_jsx_runtime5 = require("react/jsx-runtime");
647
+ var button = "px-1.5 py-0.5 rounded text-[10px] bg-slate-800 text-slate-300 hover:text-white disabled:opacity-40";
648
+ var LayerEffectsPanel = ({ controller, layerId, className }) => {
649
+ const [, setTick] = (0, import_react5.useState)(0);
650
+ (0, import_react5.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
651
+ const [open, setOpen] = (0, import_react5.useState)(null);
652
+ const [error, setError] = (0, import_react5.useState)(null);
653
+ const commitNext = (0, import_react5.useRef)(true);
654
+ const layer = layerId ? controller.getLayer(layerId) : controller.getActiveLayer();
655
+ const model = (0, import_image_editor6.layerEffectsPanel)(layer);
656
+ const id = layer?.id;
657
+ const attempt = (fn) => {
658
+ try {
659
+ setError(null);
660
+ fn();
661
+ } catch (err) {
662
+ setError(err instanceof Error ? err.message : String(err));
663
+ }
664
+ };
665
+ const sliderEvents = {
666
+ onPointerDown: () => commitNext.current = true,
667
+ onPointerUp: () => commitNext.current = true,
668
+ onKeyDown: () => commitNext.current = true
669
+ };
670
+ if (!model.available || !id) {
671
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `text-xs text-slate-500 ${className ?? ""}`, "data-testid": "layer-effects", children: model.reason });
672
+ }
673
+ const effects = controller.getLayerEffects(id);
674
+ const patterns = controller.getDocumentPatterns();
675
+ const change = (effectId, control, value, drag = false) => attempt(() => {
676
+ const effect = controller.getLayerEffects(id).items.find((e) => e.id === effectId);
677
+ controller.updateLayerEffect(id, effectId, (0, import_image_editor6.effectControlChange)(effect, control.key, value, patterns), drag ? commitNext.current : true);
678
+ if (drag) commitNext.current = false;
679
+ });
680
+ const renderControl = (effectId, c) => {
681
+ const label = `${c.label}`;
682
+ switch (c.type) {
683
+ case "range": {
684
+ const k = c.percent ? 100 : 1;
685
+ const shown = Math.round(c.value * k * 100) / 100;
686
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
688
+ label,
689
+ " ",
690
+ shown,
691
+ c.unit
692
+ ] }),
693
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
694
+ "input",
695
+ {
696
+ type: "range",
697
+ "aria-label": label,
698
+ min: c.min * k,
699
+ max: c.max * k,
700
+ step: c.step * k,
701
+ value: shown,
702
+ disabled: model.readOnly,
703
+ ...sliderEvents,
704
+ onChange: (e) => change(effectId, c, Number(e.target.value) / k, true),
705
+ className: "accent-indigo-500"
706
+ }
707
+ )
708
+ ] }, c.key);
709
+ }
710
+ case "color":
711
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
712
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "color", "aria-label": label, value: c.value, disabled: model.readOnly, onChange: (e) => change(effectId, c, e.target.value) }),
713
+ label
714
+ ] }, c.key);
715
+ case "checkbox":
716
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
717
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "checkbox", "aria-label": label, checked: c.value, disabled: model.readOnly, onChange: (e) => change(effectId, c, e.target.checked), className: "accent-indigo-500" }),
718
+ label
719
+ ] }, c.key);
720
+ case "select":
721
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-slate-400", children: label }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
724
+ "select",
725
+ {
726
+ "aria-label": label,
727
+ value: c.value,
728
+ disabled: model.readOnly,
729
+ onChange: (e) => change(effectId, c, e.target.value),
730
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
731
+ children: c.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: o.value, children: o.label }, o.value))
732
+ }
733
+ )
734
+ ] }, c.key);
735
+ }
736
+ };
737
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `flex flex-col gap-1.5 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "layer-effects", "aria-label": "Layer effects", role: "group", children: [
738
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
739
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-semibold text-slate-300", children: "fx Effects" }),
740
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-slate-300", children: [
741
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
742
+ "input",
743
+ {
744
+ type: "checkbox",
745
+ "aria-label": "Effects on",
746
+ checked: model.enabled,
747
+ disabled: model.readOnly,
748
+ onChange: (e) => attempt(() => controller.setLayerEffectsEnabled(id, e.target.checked)),
749
+ className: "accent-indigo-500"
750
+ }
751
+ ),
752
+ "On"
753
+ ] }),
754
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-slate-400", children: [
755
+ "Scale",
756
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
757
+ "input",
758
+ {
759
+ type: "number",
760
+ "aria-label": "Scale effects",
761
+ min: 1,
762
+ max: 1e3,
763
+ step: 1,
764
+ value: Math.round(model.scale * 100),
765
+ disabled: model.readOnly,
766
+ onChange: (e) => {
767
+ const v = Number(e.target.value);
768
+ if (v >= 1 && v <= 1e3) attempt(() => controller.setLayerEffectsEnabled(id, model.enabled, v / 100));
769
+ },
770
+ className: "w-14 bg-slate-800 border border-slate-700 rounded px-1"
771
+ }
772
+ ),
773
+ "%"
774
+ ] }),
775
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
776
+ "select",
777
+ {
778
+ "aria-label": "Add effect",
779
+ value: "",
780
+ disabled: model.readOnly,
781
+ onChange: (e) => {
782
+ const kind = e.target.value;
783
+ if (!kind) return;
784
+ attempt(() => setOpen(controller.addLayerEffect(id, kind).effect.id));
785
+ },
786
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
787
+ children: [
788
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: "Add effect\u2026" }),
789
+ import_image_editor6.LAYER_EFFECT_MENU.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: m.kind, children: m.label }, m.kind))
790
+ ]
791
+ }
792
+ )
793
+ ] }),
794
+ model.readOnly && model.reason && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-slate-500", children: model.reason }),
795
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "flex flex-col gap-1", "aria-label": "Effects", children: model.rows.map((row) => {
796
+ const effect = effects.items.find((e) => e.id === row.id);
797
+ const expanded = open === row.id;
798
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { "data-effect-id": row.id, className: "rounded bg-slate-900/60 ring-1 ring-slate-800", children: [
799
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-1.5 px-1.5 py-1", children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
801
+ "input",
802
+ {
803
+ type: "checkbox",
804
+ "aria-label": `${row.label} on`,
805
+ checked: row.enabled,
806
+ disabled: model.readOnly,
807
+ onChange: (e) => attempt(() => controller.updateLayerEffect(id, row.id, { enabled: e.target.checked })),
808
+ className: "accent-indigo-500"
809
+ }
810
+ ),
811
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
812
+ "button",
813
+ {
814
+ type: "button",
815
+ "aria-expanded": expanded,
816
+ className: `flex-1 text-left ${row.enabled && model.enabled ? "" : "text-slate-500"}`,
817
+ onClick: () => setOpen(expanded ? null : row.id),
818
+ children: row.label
819
+ }
820
+ ),
821
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: button, "aria-label": `Remove ${row.label}`, disabled: model.readOnly, onClick: () => attempt(() => controller.removeLayerEffect(id, row.id)), children: "\u2715" })
822
+ ] }),
823
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 px-1.5 pb-1.5", "data-testid": "effect-settings", children: (0, import_image_editor6.layerEffectControls)(effect, patterns).map((c) => renderControl(row.id, c)) })
824
+ ] }, row.id);
825
+ }) }),
826
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "alert", className: "text-rose-400", children: error })
827
+ ] });
828
+ };
829
+
830
+ // src/image-editor/LayersPanel.tsx
831
+ var import_image_editor7 = require("@pixerate/editor/image-editor");
832
+ var import_jsx_runtime6 = require("react/jsx-runtime");
642
833
  var iconButton = "px-1.5 py-0.5 rounded text-[10px] bg-slate-800 text-slate-300 hover:text-white disabled:opacity-40";
643
834
  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
835
  var Thumbnail = ({
@@ -649,8 +840,8 @@ var Thumbnail = ({
649
840
  selected,
650
841
  testId
651
842
  }) => {
652
- const ref = (0, import_react5.useRef)(null);
653
- (0, import_react5.useEffect)(() => {
843
+ const ref = (0, import_react6.useRef)(null);
844
+ (0, import_react6.useEffect)(() => {
654
845
  const canvas = ref.current;
655
846
  const ctx = canvas?.getContext("2d");
656
847
  if (!canvas || !ctx || !pixels || typeof ctx.createImageData !== "function") return;
@@ -660,7 +851,7 @@ var Thumbnail = ({
660
851
  img.data.set(pixels.data);
661
852
  ctx.putImageData(img, 0, 0);
662
853
  }, [pixels]);
663
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
854
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
664
855
  "canvas",
665
856
  {
666
857
  ref,
@@ -673,15 +864,15 @@ var Thumbnail = ({
673
864
  }
674
865
  );
675
866
  };
676
- var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", className }) => {
677
- const [, setTick] = (0, import_react5.useState)(0);
678
- (0, import_react5.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
679
- const [error, setError] = (0, import_react5.useState)(null);
680
- const [renaming, setRenaming] = (0, import_react5.useState)(null);
681
- const [menu, setMenu] = (0, import_react5.useState)(null);
682
- const [drag, setDrag] = (0, import_react5.useState)(null);
683
- const [psdNotes, setPsdNotes] = (0, import_react5.useState)([]);
684
- const fileInput = (0, import_react5.useRef)(null);
867
+ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", effects = true, className }) => {
868
+ const [, setTick] = (0, import_react6.useState)(0);
869
+ (0, import_react6.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
870
+ const [error, setError] = (0, import_react6.useState)(null);
871
+ const [renaming, setRenaming] = (0, import_react6.useState)(null);
872
+ const [menu, setMenu] = (0, import_react6.useState)(null);
873
+ const [drag, setDrag] = (0, import_react6.useState)(null);
874
+ const [psdNotes, setPsdNotes] = (0, import_react6.useState)([]);
875
+ const fileInput = (0, import_react6.useRef)(null);
685
876
  const psdTask = async (fn) => {
686
877
  try {
687
878
  setError(null);
@@ -690,9 +881,9 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
690
881
  setError(err instanceof Error ? err.message : String(err));
691
882
  }
692
883
  };
693
- const commitNext = (0, import_react5.useRef)(true);
884
+ const commitNext = (0, import_react6.useRef)(true);
694
885
  const state = controller.getState();
695
- const rows = (0, import_image_editor6.layerPanelRows)(state.layers, state.activeLayerId);
886
+ const rows = (0, import_image_editor7.layerPanelRows)(state.layers, state.activeLayerId);
696
887
  const active = controller.getActiveLayer();
697
888
  const attempt = (fn) => {
698
889
  try {
@@ -704,7 +895,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
704
895
  };
705
896
  const run = async (action) => {
706
897
  setMenu(null);
707
- setError(await (0, import_image_editor6.runLayerMenuAction)(controller, action));
898
+ setError(await (0, import_image_editor7.runLayerMenuAction)(controller, action));
708
899
  };
709
900
  const prop = (key, value) => attempt(() => {
710
901
  controller.setLayerProperties(active.id, { [key]: value }, commitNext.current);
@@ -722,20 +913,20 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
722
913
  if (!id) return;
723
914
  const rect = e.currentTarget.getBoundingClientRect();
724
915
  const fraction = rect.height ? (e.clientY - rect.top) / rect.height : 0.5;
725
- const placement = (0, import_image_editor6.dropPlacement)(state.layers, id, row.layer.id, (0, import_image_editor6.dropZone)(fraction, row.layer.type === "group"));
916
+ const placement = (0, import_image_editor7.dropPlacement)(state.layers, id, row.layer.id, (0, import_image_editor7.dropZone)(fraction, row.layer.type === "group"));
726
917
  if (placement) attempt(() => controller.moveLayer(id, placement));
727
918
  };
728
919
  const thumb = (layer) => layer.type === "pixel" ? controller.getLayerThumbnail(layer.id, { maxSide: 64 }) : null;
729
920
  const maskThumb = (layer) => layer.mask && !layer.mask.source ? controller.getLayerThumbnail(layer.id, { maxSide: 64, mask: true }) : null;
730
921
  const locked = (layer) => Boolean(layer.locks?.all || layer.locks?.pixels || layer.locks?.position || layer.locks?.transparency);
731
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "layers-panel", onClick: () => menu && setMenu(null), children: [
732
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap items-center gap-1", role: "toolbar", "aria-label": "Layer actions", children: [
733
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-layer"), children: "New Layer" }),
734
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-group"), children: "New Group" }),
735
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("duplicate"), children: "Duplicate" }),
736
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("add-mask"), children: "Add Mask" }),
737
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("delete"), children: "Delete" }),
738
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
922
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "layers-panel", onClick: () => menu && setMenu(null), children: [
923
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-1", role: "toolbar", "aria-label": "Layer actions", children: [
924
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-layer"), children: "New Layer" }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, onClick: () => run("new-group"), children: "New Group" }),
926
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("duplicate"), children: "Duplicate" }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("add-mask"), children: "Add Mask" }),
928
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, disabled: !active, onClick: () => run("delete"), children: "Delete" }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
739
930
  "button",
740
931
  {
741
932
  type: "button",
@@ -750,39 +941,39 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
750
941
  children: "\u22EF"
751
942
  }
752
943
  ),
753
- psd && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
754
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => fileInput.current?.click(), children: "Open PSD\u2026" }),
755
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => psdTask(async () => (await (0, import_image_editor6.downloadPsd)(controller, psdFileName)).warnings), children: "Export PSD" }),
756
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
944
+ psd && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, onClick: () => fileInput.current?.click(), children: "Open PSD\u2026" }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: iconButton, onClick: () => psdTask(async () => (await (0, import_image_editor7.downloadPsd)(controller, psdFileName)).warnings), children: "Export PSD" }),
947
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
757
948
  "input",
758
949
  {
759
950
  ref: fileInput,
760
951
  type: "file",
761
- accept: import_image_editor6.PSD_FILE_ACCEPT,
952
+ accept: import_image_editor7.PSD_FILE_ACCEPT,
762
953
  className: "hidden",
763
954
  "data-testid": "psd-file-input",
764
955
  "aria-label": "Open a PSD file",
765
956
  onChange: (e) => {
766
957
  const file = e.currentTarget.files?.[0];
767
958
  e.currentTarget.value = "";
768
- if (file) psdTask(() => (0, import_image_editor6.openPsdFile)(controller, file));
959
+ if (file) psdTask(() => (0, import_image_editor7.openPsdFile)(controller, file));
769
960
  }
770
961
  }
771
962
  )
772
963
  ] })
773
964
  ] }),
774
- psdNotes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("ul", { className: "rounded bg-amber-900/30 text-amber-200 px-2 py-1 space-y-0.5", "data-testid": "psd-warnings", "aria-label": "PSD warnings", children: [
775
- psdNotes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: n }, n)),
776
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "underline", onClick: () => setPsdNotes([]), children: "Dismiss" }) })
965
+ psdNotes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("ul", { className: "rounded bg-amber-900/30 text-amber-200 px-2 py-1 space-y-0.5", "data-testid": "psd-warnings", "aria-label": "PSD warnings", children: [
966
+ psdNotes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: n }, n)),
967
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "underline", onClick: () => setPsdNotes([]), children: "Dismiss" }) })
777
968
  ] }),
778
- active && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
779
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
780
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
969
+ active && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
970
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "text-slate-400", children: [
781
972
  "Opacity ",
782
973
  Math.round(active.opacity * 100),
783
974
  "%"
784
975
  ] }),
785
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
976
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
786
977
  "input",
787
978
  {
788
979
  type: "range",
@@ -796,13 +987,13 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
796
987
  }
797
988
  )
798
989
  ] }),
799
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
800
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-slate-400", children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "text-slate-400", children: [
801
992
  "Fill ",
802
993
  Math.round(((active.type === "pixel" ? active.fillOpacity : void 0) ?? 1) * 100),
803
994
  "%"
804
995
  ] }),
805
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
996
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
806
997
  "input",
807
998
  {
808
999
  type: "range",
@@ -817,7 +1008,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
817
1008
  }
818
1009
  )
819
1010
  ] }),
820
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1011
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
821
1012
  "select",
822
1013
  {
823
1014
  "aria-label": "Layer blend mode",
@@ -825,13 +1016,13 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
825
1016
  value: active.blend,
826
1017
  onChange: (e) => attempt(() => controller.setLayerProperties(active.id, { blend: e.target.value })),
827
1018
  children: [
828
- active.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "pass-through", children: "Pass Through" }),
829
- import_image_editor6.BLEND_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: m.id, children: m.label }, m.id))
1019
+ active.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "pass-through", children: "Pass Through" }),
1020
+ import_image_editor7.BLEND_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: m.id, children: m.label }, m.id))
830
1021
  ]
831
1022
  }
832
1023
  ),
833
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
834
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1024
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5 text-slate-300", children: [
1025
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
835
1026
  "input",
836
1027
  {
837
1028
  type: "checkbox",
@@ -843,8 +1034,8 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
843
1034
  ),
844
1035
  "Clip to below"
845
1036
  ] }),
846
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "col-span-2 flex flex-wrap gap-2 text-slate-400", role: "group", "aria-label": "Layer locks", children: ["pixels", "transparency", "position", "all"].map((k) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1", children: [
847
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1037
+ /* @__PURE__ */ (0, import_jsx_runtime6.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_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1038
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
848
1039
  "input",
849
1040
  {
850
1041
  type: "checkbox",
@@ -857,9 +1048,10 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
857
1048
  k
858
1049
  ] }, k)) })
859
1050
  ] }),
860
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "flex flex-col gap-0.5", role: "tree", "aria-label": "Layers", children: rows.map((row) => {
1051
+ effects && active && active.type !== "adjustment" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayerEffectsPanel, { controller, className: "border-t border-slate-800 pt-1.5" }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "flex flex-col gap-0.5", role: "tree", "aria-label": "Layers", children: rows.map((row) => {
861
1053
  const { layer } = row;
862
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1054
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
863
1055
  "li",
864
1056
  {
865
1057
  role: "treeitem",
@@ -889,7 +1081,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
889
1081
  className: rowClass(row.active),
890
1082
  style: { paddingLeft: 6 + row.depth * 14 },
891
1083
  children: [
892
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1084
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
893
1085
  "button",
894
1086
  {
895
1087
  type: "button",
@@ -903,7 +1095,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
903
1095
  children: layer.visible ? "\u25CF" : "\u25CB"
904
1096
  }
905
1097
  ),
906
- layer.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1098
+ layer.type === "group" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
907
1099
  "button",
908
1100
  {
909
1101
  type: "button",
@@ -916,8 +1108,8 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
916
1108
  children: layer.collapsed ? "\u25B8" : "\u25BE"
917
1109
  }
918
1110
  ),
919
- row.clipped && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-slate-500", "aria-label": "Clipped", children: "\u21B3" }),
920
- layer.type === "pixel" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1111
+ row.clipped && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-500", "aria-label": "Clipped", children: "\u21B3" }),
1112
+ layer.type === "pixel" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
921
1113
  Thumbnail,
922
1114
  {
923
1115
  pixels: thumb(layer),
@@ -925,15 +1117,31 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
925
1117
  label: `${row.label} pixels`,
926
1118
  selected: row.active && state.activeTarget === "pixels" && Boolean(layer.mask)
927
1119
  }
928
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1120
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
929
1121
  "span",
930
1122
  {
931
1123
  className: "shrink-0 grid place-items-center rounded bg-slate-800 text-[9px] text-slate-400 ring-1 ring-slate-700",
932
1124
  style: { width: thumbnailSize, height: thumbnailSize },
933
- children: (0, import_image_editor6.layerBadge)(layer)
1125
+ children: (0, import_image_editor7.layerBadge)(layer)
934
1126
  }
935
1127
  ),
936
- layer.mask && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1128
+ layer.mask && !layer.mask.source && layer.type === "pixel" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1129
+ "button",
1130
+ {
1131
+ type: "button",
1132
+ "aria-label": `${layer.mask.linked === false ? "Link" : "Unlink"} ${row.label} mask`,
1133
+ "aria-pressed": layer.mask.linked !== false,
1134
+ title: layer.mask.linked === false ? "Mask unlinked: it stays put when the layer moves or transforms. Click to link." : "Mask linked: it moves with the layer. Click to unlink.",
1135
+ "data-testid": "layer-mask-link",
1136
+ className: `shrink-0 text-[10px] leading-none ${layer.mask.linked === false ? "text-slate-600" : "text-slate-300"}`,
1137
+ onClick: (e) => {
1138
+ e.stopPropagation();
1139
+ attempt(() => controller.setLayerMaskProps(layer.id, { linked: layer.mask.linked === false }));
1140
+ },
1141
+ children: layer.mask.linked === false ? "\u26D3\uFE0E\u0338" : "\u26D3\uFE0E"
1142
+ }
1143
+ ),
1144
+ layer.mask && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
937
1145
  Thumbnail,
938
1146
  {
939
1147
  pixels: maskThumb(layer),
@@ -948,7 +1156,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
948
1156
  }
949
1157
  }
950
1158
  ),
951
- renaming === layer.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1159
+ renaming === layer.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
952
1160
  "input",
953
1161
  {
954
1162
  autoFocus: true,
@@ -965,7 +1173,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
965
1173
  if (e.key === "Escape") setRenaming(null);
966
1174
  }
967
1175
  }
968
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1176
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
969
1177
  "span",
970
1178
  {
971
1179
  className: `flex-1 min-w-0 truncate ${layer.visible ? "" : "text-slate-500"}`,
@@ -976,15 +1184,30 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
976
1184
  children: row.label
977
1185
  }
978
1186
  ),
979
- locked(layer) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-label": "Locked", className: "text-slate-400", children: "\u{1F512}" }),
980
- layer.blend !== "normal" && layer.blend !== "pass-through" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[9px] text-slate-500", children: layer.blend })
1187
+ (0, import_image_editor7.layerEffectsBadge)(layer) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1188
+ "button",
1189
+ {
1190
+ type: "button",
1191
+ "data-testid": "layer-fx-badge",
1192
+ "aria-label": (0, import_image_editor7.layerEffectsBadge)(layer) === "on" ? `Turn effects off for ${row.label}` : `Turn effects on for ${row.label}`,
1193
+ "aria-pressed": (0, import_image_editor7.layerEffectsBadge)(layer) === "on",
1194
+ className: `text-[9px] italic font-semibold ${(0, import_image_editor7.layerEffectsBadge)(layer) === "on" ? "text-indigo-300" : "text-slate-600 line-through"}`,
1195
+ onClick: (e) => {
1196
+ e.stopPropagation();
1197
+ attempt(() => controller.setLayerEffectsEnabled(layer.id, (0, import_image_editor7.layerEffectsBadge)(layer) !== "on"));
1198
+ },
1199
+ children: "fx"
1200
+ }
1201
+ ),
1202
+ locked(layer) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-label": "Locked", className: "text-slate-400", children: "\u{1F512}" }),
1203
+ layer.blend !== "normal" && layer.blend !== "pass-through" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-[9px] text-slate-500", children: layer.blend })
981
1204
  ]
982
1205
  },
983
1206
  layer.id
984
1207
  );
985
1208
  }) }),
986
- error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
987
- menu && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1209
+ error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
1210
+ menu && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
988
1211
  "div",
989
1212
  {
990
1213
  role: "menu",
@@ -993,7 +1216,7 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
993
1216
  style: { left: menu.x, top: menu.y },
994
1217
  onClick: (e) => e.stopPropagation(),
995
1218
  onKeyDown: (e) => e.key === "Escape" && setMenu(null),
996
- children: (0, import_image_editor6.layerMenu)(controller).map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1219
+ children: (0, import_image_editor7.layerMenu)(controller).map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
997
1220
  "button",
998
1221
  {
999
1222
  type: "button",
@@ -1011,14 +1234,14 @@ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "
1011
1234
  };
1012
1235
 
1013
1236
  // src/image-editor/BrushOptionsBar.tsx
1014
- var import_react6 = require("react");
1015
- var import_image_editor7 = require("@pixerate/editor/image-editor");
1016
- var import_jsx_runtime6 = require("react/jsx-runtime");
1237
+ var import_react7 = require("react");
1238
+ var import_image_editor8 = require("@pixerate/editor/image-editor");
1239
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1017
1240
  var chip = (on) => `px-2 py-1 rounded text-[11px] ${on ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-300 hover:text-white"}`;
1018
1241
  var BrushOptionsBar = ({ controller, className }) => {
1019
- const [, setTick] = (0, import_react6.useState)(0);
1020
- (0, import_react6.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
1021
- const [error, setError] = (0, import_react6.useState)(null);
1242
+ const [, setTick] = (0, import_react7.useState)(0);
1243
+ (0, import_react7.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
1244
+ const [error, setError] = (0, import_react7.useState)(null);
1022
1245
  const state = controller.getState();
1023
1246
  const { brush, foreground, background, mixer, mixerLoad } = state.paint;
1024
1247
  const attempt = (fn) => {
@@ -1029,8 +1252,8 @@ var BrushOptionsBar = ({ controller, className }) => {
1029
1252
  setError(err instanceof Error ? err.message : String(err));
1030
1253
  }
1031
1254
  };
1032
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "brush-options", children: [
1033
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-1", role: "toolbar", "aria-label": "Paint tools", children: import_image_editor7.PAINT_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1255
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `flex flex-col gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "brush-options", children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex gap-1", role: "toolbar", "aria-label": "Paint tools", children: import_image_editor8.PAINT_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1034
1257
  "button",
1035
1258
  {
1036
1259
  type: "button",
@@ -1042,9 +1265,9 @@ var BrushOptionsBar = ({ controller, className }) => {
1042
1265
  },
1043
1266
  t.id
1044
1267
  )) }),
1045
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", role: "group", "aria-label": "Colours", children: [
1046
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1047
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1268
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", role: "group", "aria-label": "Colours", children: [
1269
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1", children: [
1270
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1048
1271
  "input",
1049
1272
  {
1050
1273
  type: "color",
@@ -1054,10 +1277,10 @@ var BrushOptionsBar = ({ controller, className }) => {
1054
1277
  className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1055
1278
  }
1056
1279
  ),
1057
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "FG" })
1280
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-slate-400", children: "FG" })
1058
1281
  ] }),
1059
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
1060
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1282
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1", children: [
1283
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1061
1284
  "input",
1062
1285
  {
1063
1286
  type: "color",
@@ -1067,12 +1290,12 @@ var BrushOptionsBar = ({ controller, className }) => {
1067
1290
  className: "h-6 w-8 rounded border border-slate-700 bg-transparent"
1068
1291
  }
1069
1292
  ),
1070
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "BG" })
1293
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-slate-400", children: "BG" })
1071
1294
  ] }),
1072
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Swap colours (X)", className: chip(false), onClick: () => controller.swapPaintColors(), children: "Swap" }),
1073
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Default colours (D)", className: chip(false), onClick: () => controller.resetPaintColors(), children: "Default" })
1295
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", title: "Swap colours (X)", className: chip(false), onClick: () => controller.swapPaintColors(), children: "Swap" }),
1296
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", title: "Default colours (D)", className: chip(false), onClick: () => controller.resetPaintColors(), children: "Default" })
1074
1297
  ] }),
1075
- import_image_editor7.BRUSH_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1298
+ import_image_editor8.BRUSH_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1076
1299
  ControlSlider,
1077
1300
  {
1078
1301
  spec,
@@ -1081,21 +1304,21 @@ var BrushOptionsBar = ({ controller, className }) => {
1081
1304
  },
1082
1305
  spec.id
1083
1306
  )),
1084
- state.activeTool === "mixer" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-2 border-t border-slate-800 pt-2", role: "group", "aria-label": "Mixer Brush", "data-testid": "mixer-options", children: [
1085
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
1086
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1307
+ state.activeTool === "mixer" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-2 border-t border-slate-800 pt-2", role: "group", "aria-label": "Mixer Brush", "data-testid": "mixer-options", children: [
1308
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1087
1310
  "span",
1088
1311
  {
1089
1312
  "aria-label": "Brush load",
1090
- title: mixerLoad.reservoir ? `Loaded ${(0, import_image_editor7.rgbUnitToHex)(mixerLoad.reservoir)}, ${Math.round(mixerLoad.amount * 100)}% left` : "Unloaded",
1313
+ title: mixerLoad.reservoir ? `Loaded ${(0, import_image_editor8.rgbUnitToHex)(mixerLoad.reservoir)}, ${Math.round(mixerLoad.amount * 100)}% left` : "Unloaded",
1091
1314
  className: "h-6 w-6 rounded border border-slate-700",
1092
- style: { background: mixerLoad.reservoir ? (0, import_image_editor7.rgbUnitToHex)(mixerLoad.reservoir) : "transparent" }
1315
+ style: { background: mixerLoad.reservoir ? (0, import_image_editor8.rgbUnitToHex)(mixerLoad.reservoir) : "transparent" }
1093
1316
  }
1094
1317
  ),
1095
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Fill the reservoir with the foreground colour", className: chip(false), onClick: () => controller.loadMixerBrush(), children: "Load brush" }),
1096
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", title: "Empty the picked-up colour", className: chip(false), onClick: () => controller.cleanMixerBrush(), children: "Clean brush" })
1318
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", title: "Fill the reservoir with the foreground colour", className: chip(false), onClick: () => controller.loadMixerBrush(), children: "Load brush" }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", title: "Empty the picked-up colour", className: chip(false), onClick: () => controller.cleanMixerBrush(), children: "Clean brush" })
1097
1320
  ] }),
1098
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-1", role: "group", "aria-label": "Mixing", children: import_image_editor7.MIXER_MIXING_OPTIONS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1321
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex gap-1", role: "group", "aria-label": "Mixing", children: import_image_editor8.MIXER_MIXING_OPTIONS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1099
1322
  "button",
1100
1323
  {
1101
1324
  type: "button",
@@ -1107,10 +1330,10 @@ var BrushOptionsBar = ({ controller, className }) => {
1107
1330
  },
1108
1331
  m.id
1109
1332
  )) }),
1110
- import_image_editor7.MIXER_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ControlSlider, { spec, value: mixer[spec.id], onChange: (v) => controller.setPaintOptions({ mixer: { [spec.id]: v } }) }, spec.id)),
1111
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap gap-3 text-slate-300", children: [
1112
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1113
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1333
+ import_image_editor8.MIXER_OPTION_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ControlSlider, { spec, value: mixer[spec.id], onChange: (v) => controller.setPaintOptions({ mixer: { [spec.id]: v } }) }, spec.id)),
1334
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap gap-3 text-slate-300", children: [
1335
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1336
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1114
1337
  "input",
1115
1338
  {
1116
1339
  type: "checkbox",
@@ -1122,8 +1345,8 @@ var BrushOptionsBar = ({ controller, className }) => {
1122
1345
  ),
1123
1346
  "Load each stroke"
1124
1347
  ] }),
1125
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1126
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1348
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1349
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1127
1350
  "input",
1128
1351
  {
1129
1352
  type: "checkbox",
@@ -1137,9 +1360,9 @@ var BrushOptionsBar = ({ controller, className }) => {
1137
1360
  ] })
1138
1361
  ] })
1139
1362
  ] }),
1140
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap gap-3 text-slate-300", role: "group", "aria-label": "Pressure", children: [
1141
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1142
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1363
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap gap-3 text-slate-300", role: "group", "aria-label": "Pressure", children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1143
1366
  "input",
1144
1367
  {
1145
1368
  type: "checkbox",
@@ -1151,8 +1374,8 @@ var BrushOptionsBar = ({ controller, className }) => {
1151
1374
  ),
1152
1375
  "Pressure size"
1153
1376
  ] }),
1154
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1155
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1377
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1.5", children: [
1378
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1156
1379
  "input",
1157
1380
  {
1158
1381
  type: "checkbox",
@@ -1165,18 +1388,18 @@ var BrushOptionsBar = ({ controller, className }) => {
1165
1388
  "Pressure opacity"
1166
1389
  ] })
1167
1390
  ] }),
1168
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-1", children: [
1169
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.fillPixels()), children: "Fill" }),
1170
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.clearPixels()), children: "Clear" })
1391
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-1", children: [
1392
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.fillPixels()), children: "Fill" }),
1393
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: chip(false), onClick: () => attempt(() => controller.clearPixels()), children: "Clear" })
1171
1394
  ] }),
1172
- error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "text-rose-400", children: error })
1395
+ error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", className: "text-rose-400", children: error })
1173
1396
  ] });
1174
1397
  };
1175
1398
 
1176
1399
  // src/image-editor/SelectionOptionsBar.tsx
1177
- var import_react7 = require("react");
1178
- var import_image_editor8 = require("@pixerate/editor/image-editor");
1179
- var import_jsx_runtime7 = require("react/jsx-runtime");
1400
+ var import_react8 = require("react");
1401
+ var import_image_editor9 = require("@pixerate/editor/image-editor");
1402
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1180
1403
  var MODIFY_OPS = [
1181
1404
  { op: "grow", label: "Grow" },
1182
1405
  { op: "shrink", label: "Shrink" },
@@ -1193,20 +1416,20 @@ var SelectionOptionsBar = ({
1193
1416
  quickMask,
1194
1417
  className = ""
1195
1418
  }) => {
1196
- const [radius, setRadius] = (0, import_react7.useState)(2);
1419
+ const [radius, setRadius] = (0, import_react8.useState)(2);
1197
1420
  const set = (patch) => onOptionsChange({ ...options, ...patch });
1198
1421
  const sel = selection === void 0 ? controller.getSelection() : selection;
1199
1422
  const qm = quickMask ?? Boolean(controller.getState().quickMask);
1200
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1201
- /* @__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)) }),
1202
- /* @__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)) }),
1203
- options.tool === "wand" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-2", children: [
1204
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex flex-col gap-1", children: [
1205
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1206
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Tolerance" }),
1207
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: options.tolerance })
1423
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-testid": "selection-options-bar", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "group", "aria-label": "Selection tool", className: "flex flex-wrap gap-1", children: import_image_editor9.SELECTION_TOOLS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime8.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)) }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "group", "aria-label": "Selection mode", className: "flex flex-wrap gap-1", children: import_image_editor9.SELECTION_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-pressed": options.mode === m, className: btn(options.mode === m), onClick: () => set({ mode: m }), children: import_image_editor9.SELECTION_MODE_LABELS[m] }, m)) }),
1426
+ options.tool === "wand" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-2", children: [
1427
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1428
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1429
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Tolerance" }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-mono", children: options.tolerance })
1208
1431
  ] }),
1209
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1432
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1210
1433
  "input",
1211
1434
  {
1212
1435
  type: "range",
@@ -1220,17 +1443,17 @@ var SelectionOptionsBar = ({
1220
1443
  }
1221
1444
  )
1222
1445
  ] }),
1223
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1224
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
1446
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1447
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: options.contiguous, onChange: (e) => set({ contiguous: e.target.checked }) }),
1225
1448
  "Contiguous"
1226
1449
  ] }),
1227
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1228
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1451
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
1229
1452
  "Sample adjusted image"
1230
1453
  ] }),
1231
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1454
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1232
1455
  "Sample",
1233
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1456
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1234
1457
  "select",
1235
1458
  {
1236
1459
  "aria-label": "Sample from",
@@ -1238,27 +1461,27 @@ var SelectionOptionsBar = ({
1238
1461
  onChange: (e) => set({ sample: e.target.value }),
1239
1462
  className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5",
1240
1463
  children: [
1241
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "composite", children: "All layers" }),
1242
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "active-layer", children: "Active layer" })
1464
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "composite", children: "All layers" }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "active-layer", children: "Active layer" })
1243
1466
  ]
1244
1467
  }
1245
1468
  )
1246
1469
  ] })
1247
1470
  ] }),
1248
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1249
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
1471
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1472
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: options.antiAlias, onChange: (e) => set({ antiAlias: e.target.checked }) }),
1250
1473
  "Anti-alias"
1251
1474
  ] }),
1252
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1253
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
1255
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
1256
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
1475
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1476
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.selectAll(), children: "Select All" }),
1477
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.deselect(), children: "Deselect" }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: btn(false), onClick: () => controller.invertSelection(), children: "Inverse" }),
1479
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-pressed": qm, className: btn(qm), onClick: () => controller.setQuickMask(!qm), children: "Quick Mask" })
1257
1480
  ] }),
1258
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1259
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-1 text-slate-400", children: [
1481
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1482
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-1 text-slate-400", children: [
1260
1483
  "Radius",
1261
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1484
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1262
1485
  "input",
1263
1486
  {
1264
1487
  type: "number",
@@ -1272,24 +1495,24 @@ var SelectionOptionsBar = ({
1272
1495
  }
1273
1496
  )
1274
1497
  ] }),
1275
- 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))
1498
+ MODIFY_OPS.map(({ op, label }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: btn(false), disabled: !sel, onClick: () => controller.modifySelection(op, radius), children: label }, op))
1276
1499
  ] }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { "data-testid": "selection-status", className: "text-slate-400", children: (0, import_image_editor8.describeSelection)(sel) })
1500
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { "data-testid": "selection-status", className: "text-slate-400", children: (0, import_image_editor9.describeSelection)(sel) })
1278
1501
  ] });
1279
1502
  };
1280
1503
 
1281
1504
  // src/image-editor/RefineEdgePanel.tsx
1282
- var import_react8 = require("react");
1283
- var import_image_editor9 = require("@pixerate/editor/image-editor");
1284
- var import_jsx_runtime8 = require("react/jsx-runtime");
1505
+ var import_react9 = require("react");
1506
+ var import_image_editor10 = require("@pixerate/editor/image-editor");
1507
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1285
1508
  var DEFAULT_PARAMS = { radius: 8, smartRadius: false, smooth: 0, feather: 0, contrast: 0, shiftEdge: 0 };
1286
- 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";
1509
+ var button2 = "px-2 py-1 rounded-md border border-slate-800 bg-slate-950 text-[11px] text-slate-200 hover:text-white disabled:opacity-40";
1287
1510
  var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1288
- const [params, setParams] = (0, import_react8.useState)(DEFAULT_PARAMS);
1289
- const [busy, setBusy] = (0, import_react8.useState)(false);
1290
- const [error, setError] = (0, import_react8.useState)(null);
1291
- const [layerId, setLayerId] = (0, import_react8.useState)("");
1292
- const [newKind, setNewKind] = (0, import_react8.useState)("hueSaturation");
1511
+ const [params, setParams] = (0, import_react9.useState)(DEFAULT_PARAMS);
1512
+ const [busy, setBusy] = (0, import_react9.useState)(false);
1513
+ const [error, setError] = (0, import_react9.useState)(null);
1514
+ const [layerId, setLayerId] = (0, import_react9.useState)("");
1515
+ const [newKind, setNewKind] = (0, import_react9.useState)("hueSaturation");
1293
1516
  const sel = selection === void 0 ? controller.getSelection() : selection;
1294
1517
  const list = layers ?? controller.getAdjustmentLayers();
1295
1518
  const target = list.find((l) => l.id === layerId) ?? list[list.length - 1];
@@ -1304,17 +1527,17 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1304
1527
  setBusy(false);
1305
1528
  }
1306
1529
  };
1307
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1308
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
1309
- import_image_editor9.REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1310
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1311
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: c.label }),
1312
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
1530
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-testid": "refine-edge-panel", className: `flex flex-col gap-3 text-xs ${className}`, children: [
1531
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Refine Edge" }),
1532
+ import_image_editor10.REFINE_EDGE_CONTROLS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex flex-col gap-1", children: [
1533
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: c.label }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1313
1536
  params[c.id],
1314
1537
  c.unit
1315
1538
  ] })
1316
1539
  ] }),
1317
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1540
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1318
1541
  "input",
1319
1542
  {
1320
1543
  type: "range",
@@ -1328,67 +1551,67 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1328
1551
  }
1329
1552
  )
1330
1553
  ] }, c.id)),
1331
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1332
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
1554
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1555
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { type: "checkbox", checked: params.smartRadius, onChange: (e) => setParams({ ...params, smartRadius: e.target.checked }) }),
1333
1556
  "Smart Radius"
1334
1557
  ] }),
1335
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-1", children: [
1336
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
1337
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
1558
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-1", children: [
1559
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button2, disabled: !sel || busy, onClick: refine, children: "Refine Edge" }),
1560
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button2, onClick: () => setParams(DEFAULT_PARAMS), children: "Reset" })
1338
1561
  ] }),
1339
- error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
1340
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
1341
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-1 items-center", children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1562
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", children: error }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] mt-2", children: "Layer Mask" }),
1564
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-1 items-center", children: [
1565
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1343
1566
  "select",
1344
1567
  {
1345
1568
  "aria-label": "New masked layer kind",
1346
1569
  value: newKind,
1347
1570
  onChange: (e) => setNewKind(e.target.value),
1348
1571
  className: "flex-1 bg-slate-950 border border-slate-800 rounded px-1 py-1 text-slate-200",
1349
- children: import_image_editor9.ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: k.kind, children: k.label }, k.kind))
1572
+ children: import_image_editor10.ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: k.kind, children: k.label }, k.kind))
1350
1573
  }
1351
1574
  ),
1352
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1575
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1353
1576
  "button",
1354
1577
  {
1355
1578
  type: "button",
1356
- className: button,
1579
+ className: button2,
1357
1580
  disabled: !sel,
1358
- onClick: () => sel && setLayerId(controller.addAdjustmentLayer({ kind: newKind, mask: (0, import_image_editor9.layerMaskFromSelection)(sel) })),
1581
+ onClick: () => sel && setLayerId(controller.addAdjustmentLayer({ kind: newKind, mask: (0, import_image_editor10.layerMaskFromSelection)(sel) })),
1359
1582
  children: "New Masked Layer"
1360
1583
  }
1361
1584
  )
1362
1585
  ] }),
1363
- list.length > 0 && target && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-2", children: [
1364
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1586
+ list.length > 0 && target && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-2", children: [
1587
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1365
1588
  "select",
1366
1589
  {
1367
1590
  "aria-label": "Mask layer",
1368
1591
  value: target.id,
1369
1592
  onChange: (e) => setLayerId(e.target.value),
1370
1593
  className: "bg-slate-950 border border-slate-800 rounded px-1 py-1 text-slate-200",
1371
- children: list.map((l) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("option", { value: l.id, children: [
1372
- l.name || (0, import_image_editor9.adjustmentKindLabel)(l.kind),
1594
+ children: list.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("option", { value: l.id, children: [
1595
+ l.name || (0, import_image_editor10.adjustmentKindLabel)(l.kind),
1373
1596
  l.mask ? " (masked)" : ""
1374
1597
  ] }, l.id))
1375
1598
  }
1376
1599
  ),
1377
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1378
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
1379
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
1380
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
1600
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1601
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button2, disabled: !sel, onClick: () => controller.applySelectionAsLayerMask(target.id), children: "Mask with Selection" }),
1602
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button2, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
1603
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: button2, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
1381
1604
  ] }),
1382
- target.mask && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1383
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1384
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Density" }),
1386
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
1605
+ target.mask && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex flex-col gap-1", children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1608
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Density" }),
1609
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1387
1610
  Math.round((target.mask.density ?? 1) * 100),
1388
1611
  "%"
1389
1612
  ] })
1390
1613
  ] }),
1391
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1614
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1392
1615
  "input",
1393
1616
  {
1394
1617
  type: "range",
@@ -1402,15 +1625,15 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1402
1625
  }
1403
1626
  )
1404
1627
  ] }),
1405
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex flex-col gap-1", children: [
1406
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1407
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Mask Feather" }),
1408
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "font-mono", children: [
1628
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex flex-col gap-1", children: [
1629
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex justify-between text-slate-400", children: [
1630
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Mask Feather" }),
1631
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
1409
1632
  target.mask.feather ?? 0,
1410
1633
  "px"
1411
1634
  ] })
1412
1635
  ] }),
1413
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1636
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1414
1637
  "input",
1415
1638
  {
1416
1639
  type: "range",
@@ -1429,8 +1652,83 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1429
1652
  ] });
1430
1653
  };
1431
1654
 
1655
+ // src/image-editor/FreeTransformOptionsBar.tsx
1656
+ var import_react10 = require("react");
1657
+ var import_image_editor11 = require("@pixerate/editor/image-editor");
1658
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1659
+ var chip2 = (on) => `px-2 py-1 rounded text-[11px] ${on ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-300 hover:text-white"}`;
1660
+ var round = (v, unit) => unit === "px" ? Math.round(v * 10) / 10 : Math.round(v * 100) / 100;
1661
+ var FreeTransformOptionsBar = ({ tool, className }) => {
1662
+ const [, setTick] = (0, import_react10.useState)(0);
1663
+ (0, import_react10.useEffect)(() => tool.subscribe(() => setTick((n) => n + 1)), [tool]);
1664
+ const bar = tool.bar();
1665
+ if (!tool.active || !bar) return null;
1666
+ const apply = (field, raw) => {
1667
+ const v = Number(raw);
1668
+ if (raw.trim() !== "" && Number.isFinite(v) && Math.abs(v - bar[field]) > 1e-9) tool.setField(field, v);
1669
+ };
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-wrap items-center gap-2 text-xs text-slate-200 ${className ?? ""}`, "data-testid": "free-transform-options", role: "toolbar", "aria-label": "Free Transform options", children: [
1671
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex gap-1", role: "group", "aria-label": "Transform mode", children: import_image_editor11.FREE_TRANSFORM_MODE_OPTIONS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", title: m.title, "aria-pressed": tool.mode === m.id, className: chip2(tool.mode === m.id), onClick: () => tool.setMode(m.id), children: m.label }, m.id)) }),
1672
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid grid-cols-3 gap-px", role: "group", "aria-label": "Reference point", children: import_image_editor11.TRANSFORM_ANCHORS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-label": `Reference point ${a}`, title: `Reference point: ${a}`, className: "h-2.5 w-2.5 rounded-sm bg-slate-700 hover:bg-indigo-500", onClick: () => tool.setReference(a) }, a)) }),
1673
+ import_image_editor11.FREE_TRANSFORM_FIELDS.map((f) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex items-center gap-1", title: f.title, children: [
1674
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-slate-400", children: f.label }),
1675
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1676
+ "input",
1677
+ {
1678
+ type: "number",
1679
+ "aria-label": f.title,
1680
+ defaultValue: round(bar[f.id], f.unit),
1681
+ disabled: !bar.affine,
1682
+ step: f.unit === "px" ? 1 : 0.1,
1683
+ className: "w-16 rounded border border-slate-700 bg-slate-900 px-1 py-0.5 text-right disabled:opacity-40",
1684
+ onBlur: (e) => apply(f.id, e.currentTarget.value),
1685
+ onKeyDown: (e) => {
1686
+ if (e.key === "Enter") {
1687
+ e.preventDefault();
1688
+ e.stopPropagation();
1689
+ apply(f.id, e.currentTarget.value);
1690
+ }
1691
+ }
1692
+ },
1693
+ `${f.id}:${round(bar[f.id], f.unit)}`
1694
+ ),
1695
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-slate-500", children: f.unit }),
1696
+ f.id === "scaleX" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1697
+ "button",
1698
+ {
1699
+ type: "button",
1700
+ "aria-label": "Keep width and height proportional",
1701
+ "aria-pressed": tool.proportional,
1702
+ className: chip2(tool.proportional),
1703
+ onClick: () => {
1704
+ tool.proportional = !tool.proportional;
1705
+ setTick((n) => n + 1);
1706
+ },
1707
+ children: "\u26D3\uFE0E"
1708
+ }
1709
+ )
1710
+ ] }, f.id)),
1711
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex items-center gap-1", children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-slate-400", children: "Interpolation" }),
1713
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1714
+ "select",
1715
+ {
1716
+ "aria-label": "Interpolation",
1717
+ value: tool.interpolation,
1718
+ onChange: (e) => tool.setInterpolation(e.target.value),
1719
+ className: "rounded border border-slate-700 bg-slate-900 px-1 py-0.5",
1720
+ children: import_image_editor11.INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: i, children: i[0].toUpperCase() + i.slice(1) }, i))
1721
+ }
1722
+ )
1723
+ ] }),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", title: "Commit (Enter)", className: "px-2 py-1 rounded bg-emerald-600 text-white hover:bg-emerald-500", onClick: () => tool.commit(), children: "Commit" }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", title: "Cancel (Esc)", className: chip2(false), onClick: () => tool.cancel(), children: "Cancel" }),
1726
+ tool.error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { role: "alert", className: "text-rose-400", children: tool.error })
1727
+ ] });
1728
+ };
1729
+
1432
1730
  // src/image-editor/ImageEditor.tsx
1433
- var import_jsx_runtime9 = require("react/jsx-runtime");
1731
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1434
1732
  var ImageEditor = ({
1435
1733
  editor: externalEditor,
1436
1734
  src,
@@ -1445,35 +1743,47 @@ var ImageEditor = ({
1445
1743
  const internalEditor = useImageEditor({ image: src });
1446
1744
  const editor = externalEditor || internalEditor;
1447
1745
  const { state, controller } = editor;
1448
- const legacyProcess = (0, import_image_editor10.normalizeProcessVersion)(state.adjustments.process) < import_image_editor10.LATEST_PROCESS_VERSION;
1449
- const canvasRef = (0, import_react9.useRef)(null);
1450
- const containerRef = (0, import_react9.useRef)(null);
1451
- const [activeTab, setActiveTab] = (0, import_react9.useState)("crop");
1452
- const [activeCropRatio, setActiveCropRatio] = (0, import_react9.useState)(null);
1453
- const [selectionOptions, setSelectionOptions] = (0, import_react9.useState)(import_image_editor10.DEFAULT_SELECTION_TOOL_OPTIONS);
1454
- const selectionOptionsRef = (0, import_react9.useRef)(selectionOptions);
1746
+ const legacyProcess = (0, import_image_editor12.normalizeProcessVersion)(state.adjustments.process) < import_image_editor12.LATEST_PROCESS_VERSION;
1747
+ const canvasRef = (0, import_react11.useRef)(null);
1748
+ const containerRef = (0, import_react11.useRef)(null);
1749
+ const [activeTab, setActiveTab] = (0, import_react11.useState)("crop");
1750
+ const [activeCropRatio, setActiveCropRatio] = (0, import_react11.useState)(null);
1751
+ const [selectionOptions, setSelectionOptions] = (0, import_react11.useState)(import_image_editor12.DEFAULT_SELECTION_TOOL_OPTIONS);
1752
+ const selectionOptionsRef = (0, import_react11.useRef)(selectionOptions);
1455
1753
  selectionOptionsRef.current = selectionOptions;
1456
- const selectionGestureRef = (0, import_react9.useRef)(null);
1754
+ const selectionGestureRef = (0, import_react11.useRef)(null);
1457
1755
  if (!selectionGestureRef.current || selectionGestureRef.current.controller !== controller) {
1458
- selectionGestureRef.current = new import_image_editor10.SelectionGesture(controller, () => selectionOptionsRef.current);
1756
+ selectionGestureRef.current = new import_image_editor12.SelectionGesture(controller, () => selectionOptionsRef.current);
1459
1757
  }
1460
- const selectionDraftRef = (0, import_react9.useRef)(null);
1461
- const [selectionError, setSelectionError] = (0, import_react9.useState)(null);
1462
- const paintGestureRef = (0, import_react9.useRef)(null);
1463
- const paintControllerRef = (0, import_react9.useRef)(controller);
1758
+ const selectionDraftRef = (0, import_react11.useRef)(null);
1759
+ const [selectionError, setSelectionError] = (0, import_react11.useState)(null);
1760
+ const paintGestureRef = (0, import_react11.useRef)(null);
1761
+ const paintControllerRef = (0, import_react11.useRef)(controller);
1464
1762
  if (!paintGestureRef.current || paintControllerRef.current !== controller) {
1465
- paintGestureRef.current = new import_image_editor10.PaintGesture(controller);
1763
+ paintGestureRef.current = new import_image_editor12.PaintGesture(controller);
1466
1764
  paintControllerRef.current = controller;
1467
1765
  }
1468
- const brushCursorRef = (0, import_react9.useRef)(null);
1469
- const [paintError, setPaintError] = (0, import_react9.useState)(null);
1470
- const [inpaintPrompt, setInpaintPrompt] = (0, import_react9.useState)("");
1471
- const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react9.useState)("annotations");
1472
- const [isRemovingBg, setIsRemovingBg] = (0, import_react9.useState)(false);
1473
- const [isInpainting, setIsInpainting] = (0, import_react9.useState)(false);
1474
- const [aiError, setAiError] = (0, import_react9.useState)(null);
1475
- const [aiSuccess, setAiSuccess] = (0, import_react9.useState)(null);
1476
- const [aiTarget, setAiTarget] = (0, import_react9.useState)("auto");
1766
+ const brushCursorRef = (0, import_react11.useRef)(null);
1767
+ const [paintError, setPaintError] = (0, import_react11.useState)(null);
1768
+ const transformToolRef = (0, import_react11.useRef)(null);
1769
+ const transformControllerRef = (0, import_react11.useRef)(controller);
1770
+ if (!transformToolRef.current || transformControllerRef.current !== controller) {
1771
+ transformToolRef.current?.cancel();
1772
+ transformToolRef.current = new import_image_editor12.FreeTransformTool(controller, {
1773
+ schedule: (run) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(run) : run()
1774
+ });
1775
+ transformControllerRef.current = controller;
1776
+ }
1777
+ const transformTool = transformToolRef.current;
1778
+ const [, setTransformTick] = (0, import_react11.useState)(0);
1779
+ const [transformCursor, setTransformCursor] = (0, import_react11.useState)("default");
1780
+ const [inpaintPrompt, setInpaintPrompt] = (0, import_react11.useState)("");
1781
+ const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react11.useState)("annotations");
1782
+ const [isRemovingBg, setIsRemovingBg] = (0, import_react11.useState)(false);
1783
+ const [isInpainting, setIsInpainting] = (0, import_react11.useState)(false);
1784
+ const [aiError, setAiError] = (0, import_react11.useState)(null);
1785
+ const [aiSuccess, setAiSuccess] = (0, import_react11.useState)(null);
1786
+ const [aiTarget, setAiTarget] = (0, import_react11.useState)("auto");
1477
1787
  const aiOptions = aiTarget === "auto" ? {} : { target: aiTarget };
1478
1788
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
1479
1789
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
@@ -1536,18 +1846,18 @@ var ImageEditor = ({
1536
1846
  setIsInpainting(false);
1537
1847
  }
1538
1848
  };
1539
- const [activeColor, setActiveColor] = (0, import_react9.useState)("#ef4444");
1540
- const [strokeWidth, setStrokeWidth] = (0, import_react9.useState)(3);
1541
- const [fontSize, setFontSize] = (0, import_react9.useState)(24);
1542
- const [textInput, setTextInput] = (0, import_react9.useState)("");
1543
- const draftAnnotationRef = (0, import_react9.useRef)(null);
1544
- const draftCropRef = (0, import_react9.useRef)(null);
1545
- const cropDragInfoRef = (0, import_react9.useRef)(null);
1546
- const annotationDragInfoRef = (0, import_react9.useRef)(null);
1547
- const isDrawingRef = (0, import_react9.useRef)(false);
1548
- const currentPathRef = (0, import_react9.useRef)([]);
1549
- const dragStartPointRef = (0, import_react9.useRef)(null);
1550
- const renderCanvas = (0, import_react9.useCallback)(
1849
+ const [activeColor, setActiveColor] = (0, import_react11.useState)("#ef4444");
1850
+ const [strokeWidth, setStrokeWidth] = (0, import_react11.useState)(3);
1851
+ const [fontSize, setFontSize] = (0, import_react11.useState)(24);
1852
+ const [textInput, setTextInput] = (0, import_react11.useState)("");
1853
+ const draftAnnotationRef = (0, import_react11.useRef)(null);
1854
+ const draftCropRef = (0, import_react11.useRef)(null);
1855
+ const cropDragInfoRef = (0, import_react11.useRef)(null);
1856
+ const annotationDragInfoRef = (0, import_react11.useRef)(null);
1857
+ const isDrawingRef = (0, import_react11.useRef)(false);
1858
+ const currentPathRef = (0, import_react11.useRef)([]);
1859
+ const dragStartPointRef = (0, import_react11.useRef)(null);
1860
+ const renderCanvas = (0, import_react11.useCallback)(
1551
1861
  async (draftAnnotation, draftCrop) => {
1552
1862
  const canvas = canvasRef.current;
1553
1863
  const container = containerRef.current;
@@ -1563,12 +1873,64 @@ var ImageEditor = ({
1563
1873
  draftAnnotation: draftAnnotation !== void 0 ? draftAnnotation : draftAnnotationRef.current,
1564
1874
  draftCrop: draftCrop !== void 0 ? draftCrop : draftCropRef.current,
1565
1875
  draftSelectionPath: selectionDraftRef.current,
1566
- brushCursor: activeTab === "paint" && brushCursorRef.current && controller.getState().activeTool !== "move" ? { ...brushCursorRef.current, diameter: controller.getState().paint.brush.size } : null
1876
+ brushCursor: activeTab === "paint" && brushCursorRef.current && controller.getState().activeTool !== "move" && !transformTool.active ? { ...brushCursorRef.current, diameter: controller.getState().paint.brush.size } : null,
1877
+ freeTransform: transformTool.overlay()
1567
1878
  });
1568
1879
  },
1569
- [controller, activeTab]
1880
+ [controller, activeTab, transformTool]
1570
1881
  );
1571
- (0, import_react9.useEffect)(() => {
1882
+ (0, import_react11.useEffect)(() => transformTool.subscribe(() => {
1883
+ setTransformTick((n) => n + 1);
1884
+ void renderCanvas();
1885
+ }), [transformTool, renderCanvas]);
1886
+ const transformPoint = (e) => {
1887
+ const canvas = canvasRef.current;
1888
+ const rect = canvas.getBoundingClientRect();
1889
+ const sx = rect.width > 0 ? canvas.width / rect.width : 1;
1890
+ const sy = rect.height > 0 ? canvas.height / rect.height : 1;
1891
+ const s = controller.getState();
1892
+ const cropping = activeTab === "crop";
1893
+ const projection = (0, import_image_editor12.getViewportProjection)(s, canvas.width, canvas.height, cropping);
1894
+ const pt = (0, import_image_editor12.canvasToImagePoint)({ x: (e.clientX - rect.left) * sx, y: (e.clientY - rect.top) * sy }, s, canvas.width, canvas.height, cropping);
1895
+ transformTool.setView(projection.scale);
1896
+ return { pt, tolerance: 6 / (projection.scale || 1) };
1897
+ };
1898
+ const handleTransformPointerDown = (e) => {
1899
+ if (!transformTool.active) return false;
1900
+ if (e.button !== 0) return true;
1901
+ e.preventDefault();
1902
+ try {
1903
+ e.currentTarget.setPointerCapture?.(e.pointerId);
1904
+ } catch {
1905
+ }
1906
+ const { pt, tolerance } = transformPoint(e);
1907
+ transformTool.down(pt, tolerance);
1908
+ return true;
1909
+ };
1910
+ const handleTransformPointerMove = (e) => {
1911
+ if (!transformTool.active) return false;
1912
+ const { pt, tolerance } = transformPoint(e);
1913
+ if (e.buttons & 1) transformTool.move(pt, e);
1914
+ else {
1915
+ const over = transformTool.hit(pt, tolerance);
1916
+ setTransformCursor(transformTool.cursor(over));
1917
+ }
1918
+ return true;
1919
+ };
1920
+ const handleTransformPointerUp = () => {
1921
+ if (!transformTool.active) return false;
1922
+ transformTool.up();
1923
+ return true;
1924
+ };
1925
+ (0, import_react11.useEffect)(() => {
1926
+ if (typeof window === "undefined") return;
1927
+ const onKey = (e) => {
1928
+ if (transformTool.key(e)) e.preventDefault();
1929
+ };
1930
+ window.addEventListener("keydown", onKey);
1931
+ return () => window.removeEventListener("keydown", onKey);
1932
+ }, [transformTool]);
1933
+ (0, import_react11.useEffect)(() => {
1572
1934
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
1573
1935
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
1574
1936
  const animId = raf(() => {
@@ -1576,12 +1938,12 @@ var ImageEditor = ({
1576
1938
  });
1577
1939
  return () => caf(animId);
1578
1940
  }, [renderCanvas, state]);
1579
- (0, import_react9.useEffect)(() => {
1941
+ (0, import_react11.useEffect)(() => {
1580
1942
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
1581
1943
  editor.setTool("crop");
1582
1944
  }
1583
1945
  }, [activeTab, editor]);
1584
- (0, import_react9.useEffect)(() => {
1946
+ (0, import_react11.useEffect)(() => {
1585
1947
  const container = containerRef.current;
1586
1948
  if (!container) return;
1587
1949
  if (typeof ResizeObserver !== "undefined") {
@@ -1617,11 +1979,12 @@ var ImageEditor = ({
1617
1979
  const sy = rect.height > 0 ? canvas.height / rect.height : 1;
1618
1980
  const s = controller.getState();
1619
1981
  return list.map((ev) => ({
1620
- ...(0, import_image_editor10.canvasToImagePoint)({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s, canvas.width, canvas.height, false),
1621
- pressure: (0, import_image_editor10.pointerPressure)(ev)
1982
+ ...(0, import_image_editor12.canvasToImagePoint)({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s, canvas.width, canvas.height, false),
1983
+ pressure: (0, import_image_editor12.pointerPressure)(ev)
1622
1984
  }));
1623
1985
  };
1624
1986
  const handlePaintPointerDown = (e) => {
1987
+ if (handleTransformPointerDown(e)) return;
1625
1988
  if (activeTab !== "paint" || e.button !== 0) return;
1626
1989
  e.preventDefault();
1627
1990
  try {
@@ -1632,6 +1995,7 @@ var ImageEditor = ({
1632
1995
  setPaintError(paintGestureRef.current.down(first));
1633
1996
  };
1634
1997
  const handlePaintPointerMove = (e) => {
1998
+ if (handleTransformPointerMove(e)) return;
1635
1999
  if (activeTab !== "paint") return;
1636
2000
  const samples = paintSamples(e);
1637
2001
  brushCursorRef.current = samples[samples.length - 1] ?? null;
@@ -1639,6 +2003,7 @@ var ImageEditor = ({
1639
2003
  else void renderCanvas();
1640
2004
  };
1641
2005
  const handlePaintPointerUp = () => {
2006
+ if (handleTransformPointerUp()) return;
1642
2007
  if (activeTab !== "paint") return;
1643
2008
  const g = paintGestureRef.current;
1644
2009
  if (g.active) {
@@ -1646,21 +2011,21 @@ var ImageEditor = ({
1646
2011
  if (g.error) setPaintError(g.error);
1647
2012
  }
1648
2013
  };
1649
- (0, import_react9.useEffect)(() => {
2014
+ (0, import_react11.useEffect)(() => {
1650
2015
  if (activeTab !== "paint" || typeof window === "undefined") return;
1651
2016
  const onKey = (e) => {
1652
- if ((0, import_image_editor10.handlePaintShortcut)(controller, e)) e.preventDefault();
2017
+ if ((0, import_image_editor12.handlePaintShortcut)(controller, e)) e.preventDefault();
1653
2018
  };
1654
2019
  window.addEventListener("keydown", onKey);
1655
2020
  return () => window.removeEventListener("keydown", onKey);
1656
2021
  }, [activeTab, controller]);
1657
2022
  const handleMouseDown = (e) => {
1658
2023
  const canvas = canvasRef.current;
1659
- if (!canvas) return;
2024
+ if (!canvas || transformTool.active) return;
1660
2025
  const canvasPt = getCanvasEventPoint(e);
1661
2026
  if (activeTab === "select") {
1662
2027
  const gesture = selectionGestureRef.current;
1663
- const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2028
+ const imgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1664
2029
  void gesture.begin(imgPt, e).then(() => setSelectionError(gesture.error));
1665
2030
  return;
1666
2031
  }
@@ -1671,8 +2036,8 @@ var ImageEditor = ({
1671
2036
  width: state.imageDimensions.width,
1672
2037
  height: state.imageDimensions.height
1673
2038
  };
1674
- const handle = (0, import_image_editor10.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1675
- const startImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
2039
+ const handle = (0, import_image_editor12.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
2040
+ const startImgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1676
2041
  if (handle) {
1677
2042
  cropDragInfoRef.current = {
1678
2043
  handle,
@@ -1691,12 +2056,12 @@ var ImageEditor = ({
1691
2056
  return;
1692
2057
  }
1693
2058
  if (activeTab === "annotate") {
1694
- const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2059
+ const imgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1695
2060
  dragStartPointRef.current = imgPt;
1696
2061
  if (state.activeTool === "select") {
1697
2062
  for (let i = state.annotations.length - 1; i >= 0; i--) {
1698
2063
  const ann = state.annotations[i];
1699
- if ((0, import_image_editor10.hitTestAnnotation)(imgPt, ann)) {
2064
+ if ((0, import_image_editor12.hitTestAnnotation)(imgPt, ann)) {
1700
2065
  editor.selectAnnotation(ann.id);
1701
2066
  annotationDragInfoRef.current = {
1702
2067
  annId: ann.id,
@@ -1728,11 +2093,11 @@ var ImageEditor = ({
1728
2093
  };
1729
2094
  const handleMouseMove = (e) => {
1730
2095
  const canvas = canvasRef.current;
1731
- if (!canvas) return;
2096
+ if (!canvas || transformTool.active) return;
1732
2097
  const canvasPt = getCanvasEventPoint(e);
1733
2098
  if (activeTab === "select") {
1734
2099
  const gesture = selectionGestureRef.current;
1735
- if (gesture.active && gesture.move((0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false))) {
2100
+ if (gesture.active && gesture.move((0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false))) {
1736
2101
  selectionDraftRef.current = gesture.draftPath;
1737
2102
  renderCanvas();
1738
2103
  }
@@ -1741,7 +2106,7 @@ var ImageEditor = ({
1741
2106
  if (activeTab === "crop") {
1742
2107
  const drag = cropDragInfoRef.current;
1743
2108
  if (drag) {
1744
- const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
2109
+ const currentImgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
1745
2110
  let newCrop;
1746
2111
  if (drag.handle === "new") {
1747
2112
  const minX = Math.max(0, Math.min(drag.startImgPt.x, currentImgPt.x));
@@ -1755,7 +2120,7 @@ var ImageEditor = ({
1755
2120
  height: Math.round(Math.max(20, maxY - minY))
1756
2121
  };
1757
2122
  } else {
1758
- newCrop = (0, import_image_editor10.calculateCropDrag)(
2123
+ newCrop = (0, import_image_editor12.calculateCropDrag)(
1759
2124
  drag.startCrop,
1760
2125
  drag.handle,
1761
2126
  drag.startImgPt,
@@ -1775,15 +2140,15 @@ var ImageEditor = ({
1775
2140
  width: state.imageDimensions.width,
1776
2141
  height: state.imageDimensions.height
1777
2142
  };
1778
- const handle = (0, import_image_editor10.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
1779
- canvas.style.cursor = (0, import_image_editor10.getCropCursor)(handle, state.transform);
2143
+ const handle = (0, import_image_editor12.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
2144
+ canvas.style.cursor = (0, import_image_editor12.getCropCursor)(handle, state.transform);
1780
2145
  }
1781
2146
  return;
1782
2147
  }
1783
2148
  if (activeTab === "annotate") {
1784
2149
  if (annotationDragInfoRef.current) {
1785
2150
  const drag = annotationDragInfoRef.current;
1786
- const currentImgPt2 = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2151
+ const currentImgPt2 = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1787
2152
  const dx = currentImgPt2.x - drag.startImgPt.x;
1788
2153
  const dy = currentImgPt2.y - drag.startImgPt.y;
1789
2154
  editor.updateAnnotation(drag.annId, {
@@ -1795,10 +2160,10 @@ var ImageEditor = ({
1795
2160
  const startPt = dragStartPointRef.current;
1796
2161
  if (!startPt) {
1797
2162
  if (state.activeTool === "select") {
1798
- const imgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2163
+ const imgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1799
2164
  let hovered = false;
1800
2165
  for (let i = state.annotations.length - 1; i >= 0; i--) {
1801
- if ((0, import_image_editor10.hitTestAnnotation)(imgPt, state.annotations[i])) {
2166
+ if ((0, import_image_editor12.hitTestAnnotation)(imgPt, state.annotations[i])) {
1802
2167
  hovered = true;
1803
2168
  break;
1804
2169
  }
@@ -1809,7 +2174,7 @@ var ImageEditor = ({
1809
2174
  }
1810
2175
  return;
1811
2176
  }
1812
- const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2177
+ const currentImgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1813
2178
  if (state.activeTool === "pen" && isDrawingRef.current) {
1814
2179
  currentPathRef.current.push(currentImgPt);
1815
2180
  const draft = {
@@ -1884,12 +2249,12 @@ var ImageEditor = ({
1884
2249
  };
1885
2250
  const handleMouseUp = (e) => {
1886
2251
  const canvas = canvasRef.current;
1887
- if (!canvas) return;
2252
+ if (!canvas || transformTool.active) return;
1888
2253
  const canvasPt = getCanvasEventPoint(e);
1889
2254
  if (activeTab === "select") {
1890
2255
  const gesture = selectionGestureRef.current;
1891
2256
  if (gesture.active) {
1892
- gesture.end((0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false), e);
2257
+ gesture.end((0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false), e);
1893
2258
  selectionDraftRef.current = null;
1894
2259
  renderCanvas();
1895
2260
  }
@@ -1913,7 +2278,7 @@ var ImageEditor = ({
1913
2278
  }
1914
2279
  const startPt = dragStartPointRef.current;
1915
2280
  if (startPt) {
1916
- const currentImgPt = (0, import_image_editor10.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2281
+ const currentImgPt = (0, import_image_editor12.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
1917
2282
  if (state.activeTool === "pen" && isDrawingRef.current) {
1918
2283
  if (currentPathRef.current.length > 1) {
1919
2284
  editor.addAnnotation({
@@ -2009,15 +2374,15 @@ var ImageEditor = ({
2009
2374
  link.click();
2010
2375
  }
2011
2376
  };
2012
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2377
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2013
2378
  "div",
2014
2379
  {
2015
2380
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
2016
2381
  style,
2017
2382
  children: [
2018
- /* @__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: [
2019
- /* @__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: [
2020
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2383
+ /* @__PURE__ */ (0, import_jsx_runtime11.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: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
2385
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2021
2386
  "button",
2022
2387
  {
2023
2388
  type: "button",
@@ -2029,7 +2394,7 @@ var ImageEditor = ({
2029
2394
  children: "Transform & Crop"
2030
2395
  }
2031
2396
  ),
2032
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2397
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2033
2398
  "button",
2034
2399
  {
2035
2400
  type: "button",
@@ -2041,7 +2406,7 @@ var ImageEditor = ({
2041
2406
  children: "Adjustments"
2042
2407
  }
2043
2408
  ),
2044
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2409
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2045
2410
  "button",
2046
2411
  {
2047
2412
  type: "button",
@@ -2053,7 +2418,7 @@ var ImageEditor = ({
2053
2418
  children: "Layers"
2054
2419
  }
2055
2420
  ),
2056
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2421
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2057
2422
  "button",
2058
2423
  {
2059
2424
  type: "button",
@@ -2065,7 +2430,7 @@ var ImageEditor = ({
2065
2430
  children: "Paint"
2066
2431
  }
2067
2432
  ),
2068
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2433
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2069
2434
  "button",
2070
2435
  {
2071
2436
  type: "button",
@@ -2077,7 +2442,7 @@ var ImageEditor = ({
2077
2442
  children: "Select & Mask"
2078
2443
  }
2079
2444
  ),
2080
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2445
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2081
2446
  "button",
2082
2447
  {
2083
2448
  type: "button",
@@ -2089,7 +2454,7 @@ var ImageEditor = ({
2089
2454
  children: "Annotations"
2090
2455
  }
2091
2456
  ),
2092
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2457
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2093
2458
  "button",
2094
2459
  {
2095
2460
  type: "button",
@@ -2101,7 +2466,7 @@ var ImageEditor = ({
2101
2466
  children: "Depth Mask"
2102
2467
  }
2103
2468
  ),
2104
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2469
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2105
2470
  "button",
2106
2471
  {
2107
2472
  type: "button",
@@ -2111,14 +2476,14 @@ var ImageEditor = ({
2111
2476
  },
2112
2477
  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"}`,
2113
2478
  children: [
2114
- /* @__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" }) }),
2479
+ /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.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" }) }),
2115
2480
  "AI Tools"
2116
2481
  ]
2117
2482
  }
2118
2483
  )
2119
2484
  ] }),
2120
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
2121
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2485
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2122
2487
  "button",
2123
2488
  {
2124
2489
  type: "button",
@@ -2129,7 +2494,7 @@ var ImageEditor = ({
2129
2494
  children: "\u21A9"
2130
2495
  }
2131
2496
  ),
2132
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2497
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2133
2498
  "button",
2134
2499
  {
2135
2500
  type: "button",
@@ -2140,7 +2505,7 @@ var ImageEditor = ({
2140
2505
  children: "\u21AA"
2141
2506
  }
2142
2507
  ),
2143
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2508
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2144
2509
  "button",
2145
2510
  {
2146
2511
  type: "button",
@@ -2149,8 +2514,8 @@ var ImageEditor = ({
2149
2514
  children: "Reset"
2150
2515
  }
2151
2516
  ),
2152
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2153
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2517
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2518
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2154
2519
  "button",
2155
2520
  {
2156
2521
  type: "button",
@@ -2160,11 +2525,11 @@ var ImageEditor = ({
2160
2525
  children: "-"
2161
2526
  }
2162
2527
  ),
2163
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
2528
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
2164
2529
  Math.round(state.zoom * 100),
2165
2530
  "%"
2166
2531
  ] }),
2167
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2532
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2168
2533
  "button",
2169
2534
  {
2170
2535
  type: "button",
@@ -2174,8 +2539,8 @@ var ImageEditor = ({
2174
2539
  children: "+"
2175
2540
  }
2176
2541
  ),
2177
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2178
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2542
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2543
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2179
2544
  "button",
2180
2545
  {
2181
2546
  type: "button",
@@ -2186,7 +2551,7 @@ var ImageEditor = ({
2186
2551
  children: "Hold Compare"
2187
2552
  }
2188
2553
  ),
2189
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2554
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2190
2555
  "button",
2191
2556
  {
2192
2557
  type: "button",
@@ -2197,13 +2562,15 @@ var ImageEditor = ({
2197
2562
  )
2198
2563
  ] })
2199
2564
  ] }),
2200
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
2201
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2565
+ transformTool.active && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(FreeTransformOptionsBar, { tool: transformTool, className: "border-b border-slate-800 bg-slate-900/95 px-4 py-2" }),
2566
+ !transformTool.active && transformTool.error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { role: "alert", className: "border-b border-rose-900 bg-rose-950/80 px-4 py-1.5 text-xs text-rose-200", children: transformTool.error }),
2567
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
2568
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2202
2569
  "div",
2203
2570
  {
2204
2571
  ref: containerRef,
2205
2572
  className: "relative flex-1 h-[360px] md:h-full min-h-[320px] bg-slate-950 overflow-hidden select-none",
2206
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2573
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2207
2574
  "canvas",
2208
2575
  {
2209
2576
  ref: canvasRef,
@@ -2216,16 +2583,17 @@ var ImageEditor = ({
2216
2583
  onPointerUp: handlePaintPointerUp,
2217
2584
  onPointerCancel: () => paintGestureRef.current?.cancel(),
2218
2585
  "data-testid": "image-editor-canvas",
2219
- className: `absolute inset-0 w-full h-full block ${activeTab === "paint" ? "touch-none cursor-crosshair" : ""}`
2586
+ style: transformTool.active ? { cursor: transformCursor } : void 0,
2587
+ className: `absolute inset-0 w-full h-full block ${transformTool.active ? "touch-none" : activeTab === "paint" ? "touch-none cursor-crosshair" : ""}`
2220
2588
  }
2221
2589
  )
2222
2590
  }
2223
2591
  ),
2224
- /* @__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: [
2225
- activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2226
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
2227
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
2228
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2592
+ /* @__PURE__ */ (0, import_jsx_runtime11.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: [
2593
+ activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2594
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
2595
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
2596
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2229
2597
  "button",
2230
2598
  {
2231
2599
  type: "button",
@@ -2237,7 +2605,7 @@ var ImageEditor = ({
2237
2605
  children: "Freeform"
2238
2606
  }
2239
2607
  ),
2240
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2608
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2241
2609
  "button",
2242
2610
  {
2243
2611
  type: "button",
@@ -2249,7 +2617,7 @@ var ImageEditor = ({
2249
2617
  children: "1:1 Square"
2250
2618
  }
2251
2619
  ),
2252
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2620
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2253
2621
  "button",
2254
2622
  {
2255
2623
  type: "button",
@@ -2261,7 +2629,7 @@ var ImageEditor = ({
2261
2629
  children: "4:3 Standard"
2262
2630
  }
2263
2631
  ),
2264
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2632
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2265
2633
  "button",
2266
2634
  {
2267
2635
  type: "button",
@@ -2273,7 +2641,7 @@ var ImageEditor = ({
2273
2641
  children: "16:9 Wide"
2274
2642
  }
2275
2643
  ),
2276
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2644
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2277
2645
  "button",
2278
2646
  {
2279
2647
  type: "button",
@@ -2285,7 +2653,7 @@ var ImageEditor = ({
2285
2653
  children: "3:2 Photo"
2286
2654
  }
2287
2655
  ),
2288
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2656
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2289
2657
  "button",
2290
2658
  {
2291
2659
  type: "button",
@@ -2298,10 +2666,10 @@ var ImageEditor = ({
2298
2666
  }
2299
2667
  )
2300
2668
  ] }),
2301
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2302
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
2303
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
2304
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2669
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2670
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
2671
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
2672
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2305
2673
  "button",
2306
2674
  {
2307
2675
  type: "button",
@@ -2310,7 +2678,7 @@ var ImageEditor = ({
2310
2678
  children: "Rotate -90\xB0"
2311
2679
  }
2312
2680
  ),
2313
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2681
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2314
2682
  "button",
2315
2683
  {
2316
2684
  type: "button",
@@ -2319,7 +2687,7 @@ var ImageEditor = ({
2319
2687
  children: "Rotate +90\xB0"
2320
2688
  }
2321
2689
  ),
2322
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2690
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2323
2691
  "button",
2324
2692
  {
2325
2693
  type: "button",
@@ -2328,7 +2696,7 @@ var ImageEditor = ({
2328
2696
  children: "Flip Horizontal"
2329
2697
  }
2330
2698
  ),
2331
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2699
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2332
2700
  "button",
2333
2701
  {
2334
2702
  type: "button",
@@ -2339,10 +2707,10 @@ var ImageEditor = ({
2339
2707
  )
2340
2708
  ] })
2341
2709
  ] }),
2342
- activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2343
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2344
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
2345
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2710
+ activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2711
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
2712
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
2713
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2346
2714
  "button",
2347
2715
  {
2348
2716
  type: "button",
@@ -2352,25 +2720,25 @@ var ImageEditor = ({
2352
2720
  }
2353
2721
  )
2354
2722
  ] }),
2355
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Histogram, { histogram: editor.histogram }),
2356
- import_image_editor10.IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react9.default.Fragment, { children: [
2357
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2723
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Histogram, { histogram: editor.histogram }),
2724
+ import_image_editor12.IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
2725
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2358
2726
  ControlSlider,
2359
2727
  {
2360
2728
  spec,
2361
- value: (0, import_image_editor10.readAdjustmentControls)(state.adjustments)[spec.id],
2729
+ value: (0, import_image_editor12.readAdjustmentControls)(state.adjustments)[spec.id],
2362
2730
  disabled: spec.requiresProcess !== void 0 && legacyProcess,
2363
2731
  onChange: (v) => editor.setAdjustments({ [spec.id]: v })
2364
2732
  }
2365
2733
  ),
2366
- spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2734
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2367
2735
  "div",
2368
2736
  {
2369
2737
  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",
2370
2738
  "data-testid": "image-editor-process-notice",
2371
2739
  children: [
2372
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
2373
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2740
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
2741
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2374
2742
  "button",
2375
2743
  {
2376
2744
  type: "button",
@@ -2383,7 +2751,7 @@ var ImageEditor = ({
2383
2751
  }
2384
2752
  )
2385
2753
  ] }, spec.id)),
2386
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2754
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2387
2755
  AdjustmentLayersPanel,
2388
2756
  {
2389
2757
  controller,
@@ -2392,19 +2760,41 @@ var ImageEditor = ({
2392
2760
  }
2393
2761
  ) })
2394
2762
  ] }),
2395
- activeTab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2396
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Layers" }),
2397
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LayersPanel, { controller })
2763
+ activeTab === "layers" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2764
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Layers" }),
2765
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2766
+ "button",
2767
+ {
2768
+ type: "button",
2769
+ title: "Edit \u203A Free Transform (Ctrl/Cmd+T): the selected pixels, or the whole active layer",
2770
+ className: "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-300 hover:text-white self-start disabled:opacity-40",
2771
+ disabled: transformTool.active,
2772
+ onClick: () => transformTool.begin(),
2773
+ children: "Free Transform"
2774
+ }
2775
+ ),
2776
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(LayersPanel, { controller })
2398
2777
  ] }),
2399
- activeTab === "paint" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2400
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Paint" }),
2401
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BrushOptionsBar, { controller }),
2402
- paintError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", "data-testid": "paint-error", children: paintError }),
2403
- /* @__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 }) })
2778
+ activeTab === "paint" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2779
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Paint" }),
2780
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BrushOptionsBar, { controller }),
2781
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2782
+ "button",
2783
+ {
2784
+ type: "button",
2785
+ title: "Edit \u203A Free Transform (Ctrl/Cmd+T): the selected pixels, or the whole active layer",
2786
+ className: "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-300 hover:text-white self-start",
2787
+ disabled: transformTool.active,
2788
+ onClick: () => transformTool.begin(),
2789
+ children: "Free Transform"
2790
+ }
2791
+ ),
2792
+ paintError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "alert", className: "text-rose-400", "data-testid": "paint-error", children: paintError }),
2793
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(LayersPanel, { controller }) })
2404
2794
  ] }),
2405
- activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2406
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
2407
- /* @__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)(
2795
+ activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2796
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
2797
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2408
2798
  "button",
2409
2799
  {
2410
2800
  type: "button",
@@ -2414,9 +2804,9 @@ var ImageEditor = ({
2414
2804
  },
2415
2805
  t
2416
2806
  )) }),
2417
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
2418
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "text-slate-400", children: "Color:" }),
2419
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2807
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
2808
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "text-slate-400", children: "Color:" }),
2809
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2420
2810
  "input",
2421
2811
  {
2422
2812
  type: "color",
@@ -2425,15 +2815,15 @@ var ImageEditor = ({
2425
2815
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
2426
2816
  }
2427
2817
  ),
2428
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex-1 ml-2", children: [
2429
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
2430
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Width:" }),
2431
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
2818
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex-1 ml-2", children: [
2819
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
2820
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Width:" }),
2821
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-mono", children: [
2432
2822
  strokeWidth,
2433
2823
  "px"
2434
2824
  ] })
2435
2825
  ] }),
2436
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2826
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2437
2827
  "input",
2438
2828
  {
2439
2829
  type: "range",
@@ -2446,10 +2836,10 @@ var ImageEditor = ({
2446
2836
  )
2447
2837
  ] })
2448
2838
  ] }),
2449
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2450
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
2451
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
2452
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2839
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2840
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
2841
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2", children: [
2842
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2453
2843
  "input",
2454
2844
  {
2455
2845
  type: "text",
@@ -2460,7 +2850,7 @@ var ImageEditor = ({
2460
2850
  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"
2461
2851
  }
2462
2852
  ),
2463
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2853
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2464
2854
  "button",
2465
2855
  {
2466
2856
  type: "button",
@@ -2470,14 +2860,14 @@ var ImageEditor = ({
2470
2860
  }
2471
2861
  )
2472
2862
  ] }),
2473
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
2474
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Font Size:" }),
2475
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-mono", children: [
2863
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
2864
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Font Size:" }),
2865
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-mono", children: [
2476
2866
  fontSize,
2477
2867
  "px"
2478
2868
  ] })
2479
2869
  ] }),
2480
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2870
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2481
2871
  "input",
2482
2872
  {
2483
2873
  type: "range",
@@ -2488,14 +2878,14 @@ var ImageEditor = ({
2488
2878
  className: "w-full accent-indigo-500"
2489
2879
  }
2490
2880
  ),
2491
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2492
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2493
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-slate-400", children: [
2881
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2882
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
2883
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "text-slate-400", children: [
2494
2884
  "Layers (",
2495
2885
  state.annotations.length,
2496
2886
  ")"
2497
2887
  ] }),
2498
- state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2888
+ state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2499
2889
  "button",
2500
2890
  {
2501
2891
  type: "button",
@@ -2504,7 +2894,7 @@ var ImageEditor = ({
2504
2894
  children: "Delete Selected"
2505
2895
  }
2506
2896
  ),
2507
- state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2897
+ state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2508
2898
  "button",
2509
2899
  {
2510
2900
  type: "button",
@@ -2515,10 +2905,10 @@ var ImageEditor = ({
2515
2905
  )
2516
2906
  ] })
2517
2907
  ] }),
2518
- activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2519
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2520
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
2521
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2908
+ activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2909
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
2910
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
2911
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2522
2912
  "button",
2523
2913
  {
2524
2914
  type: "button",
@@ -2528,8 +2918,8 @@ var ImageEditor = ({
2528
2918
  }
2529
2919
  )
2530
2920
  ] }),
2531
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
2532
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2921
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
2922
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2533
2923
  "input",
2534
2924
  {
2535
2925
  type: "checkbox",
@@ -2538,12 +2928,12 @@ var ImageEditor = ({
2538
2928
  className: "rounded accent-indigo-500"
2539
2929
  }
2540
2930
  ),
2541
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Enable Depth / Alpha Mask" })
2931
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Enable Depth / Alpha Mask" })
2542
2932
  ] }),
2543
- state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2544
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2545
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
2546
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2933
+ state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2934
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
2935
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
2936
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2547
2937
  "input",
2548
2938
  {
2549
2939
  type: "text",
@@ -2554,8 +2944,8 @@ var ImageEditor = ({
2554
2944
  }
2555
2945
  )
2556
2946
  ] }),
2557
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
2558
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2947
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2", children: [
2948
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2559
2949
  "button",
2560
2950
  {
2561
2951
  type: "button",
@@ -2564,7 +2954,7 @@ var ImageEditor = ({
2564
2954
  children: "Threshold Mode"
2565
2955
  }
2566
2956
  ),
2567
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2957
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2568
2958
  "button",
2569
2959
  {
2570
2960
  type: "button",
@@ -2574,12 +2964,12 @@ var ImageEditor = ({
2574
2964
  }
2575
2965
  )
2576
2966
  ] }),
2577
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2578
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2579
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Depth Start (Lower):" }),
2580
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
2967
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
2968
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2969
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Depth Start (Lower):" }),
2970
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
2581
2971
  ] }),
2582
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2972
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2583
2973
  "input",
2584
2974
  {
2585
2975
  type: "range",
@@ -2593,12 +2983,12 @@ var ImageEditor = ({
2593
2983
  }
2594
2984
  )
2595
2985
  ] }),
2596
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2598
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Depth Stop (Upper):" }),
2599
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
2986
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
2987
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2988
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Depth Stop (Upper):" }),
2989
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
2600
2990
  ] }),
2601
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2991
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2602
2992
  "input",
2603
2993
  {
2604
2994
  type: "range",
@@ -2612,12 +3002,12 @@ var ImageEditor = ({
2612
3002
  }
2613
3003
  )
2614
3004
  ] }),
2615
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2616
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2617
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Softness:" }),
2618
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
3005
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
3006
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3007
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Softness:" }),
3008
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
2619
3009
  ] }),
2620
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3010
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2621
3011
  "input",
2622
3012
  {
2623
3013
  type: "range",
@@ -2629,8 +3019,8 @@ var ImageEditor = ({
2629
3019
  }
2630
3020
  )
2631
3021
  ] }),
2632
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
2633
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3022
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
3023
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2634
3024
  "input",
2635
3025
  {
2636
3026
  type: "checkbox",
@@ -2639,12 +3029,12 @@ var ImageEditor = ({
2639
3029
  className: "rounded accent-indigo-500"
2640
3030
  }
2641
3031
  ),
2642
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Invert Mask" })
3032
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Invert Mask" })
2643
3033
  ] })
2644
3034
  ] })
2645
3035
  ] }),
2646
- activeTab === "select" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2647
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3036
+ activeTab === "select" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
3037
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2648
3038
  SelectionOptionsBar,
2649
3039
  {
2650
3040
  controller,
@@ -2654,13 +3044,37 @@ var ImageEditor = ({
2654
3044
  quickMask: Boolean(state.quickMask)
2655
3045
  }
2656
3046
  ),
2657
- selectionError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-rose-400", children: selectionError }),
2658
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
3047
+ selectionError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "alert", className: "text-rose-400", children: selectionError }),
3048
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-1", children: [
3049
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3050
+ "button",
3051
+ {
3052
+ type: "button",
3053
+ title: "Select \u203A Transform Selection: the outline only",
3054
+ className: "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-300 hover:text-white self-start disabled:opacity-40",
3055
+ disabled: transformTool.active,
3056
+ onClick: () => transformTool.begin({ content: "selection-outline" }),
3057
+ children: "Transform Selection"
3058
+ }
3059
+ ),
3060
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3061
+ "button",
3062
+ {
3063
+ type: "button",
3064
+ title: "Edit \u203A Free Transform: the selected pixels of the active layer",
3065
+ className: "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-300 hover:text-white self-start disabled:opacity-40",
3066
+ disabled: transformTool.active,
3067
+ onClick: () => transformTool.begin({ content: "selection" }),
3068
+ children: "Free Transform Pixels"
3069
+ }
3070
+ )
3071
+ ] }),
3072
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2659
3073
  ] }),
2660
- activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2661
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
2662
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Results go to" }),
2663
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3074
+ activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
3075
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
3076
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Results go to" }),
3077
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2664
3078
  "select",
2665
3079
  {
2666
3080
  "aria-label": "AI result target",
@@ -2668,54 +3082,54 @@ var ImageEditor = ({
2668
3082
  onChange: (e) => setAiTarget(e.target.value),
2669
3083
  className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
2670
3084
  children: [
2671
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
2672
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "new-layer", children: "New layer / mask" }),
2673
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "replace", children: "Replace image" })
3085
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
3086
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "new-layer", children: "New layer / mask" }),
3087
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "replace", children: "Replace image" })
2674
3088
  ]
2675
3089
  }
2676
3090
  )
2677
3091
  ] }),
2678
- /* @__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: [
2679
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2680
- /* @__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: [
2681
- /* @__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" }) }),
3092
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
3093
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
3094
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
3095
+ /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.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" }) }),
2682
3096
  "Background Removal"
2683
3097
  ] }),
2684
- 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..." })
3098
+ isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime11.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..." })
2685
3099
  ] }),
2686
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2687
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3100
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
3101
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2688
3102
  "button",
2689
3103
  {
2690
3104
  type: "button",
2691
3105
  onClick: handleRemoveBackground,
2692
3106
  disabled: isRemovingBg || isInpainting,
2693
3107
  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",
2694
- children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2695
- /* @__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" }),
3108
+ children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3109
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2696
3110
  "Removing Background..."
2697
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2698
- /* @__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" }) }),
3111
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3112
+ /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.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" }) }),
2699
3113
  "Remove Background"
2700
3114
  ] })
2701
3115
  }
2702
3116
  )
2703
3117
  ] }),
2704
- /* @__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: [
2705
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2706
- /* @__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: [
2707
- /* @__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: [
2708
- /* @__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" }),
2709
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "m14 7 3 3" })
3118
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
3119
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
3120
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
3121
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
3122
+ /* @__PURE__ */ (0, import_jsx_runtime11.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" }),
3123
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m14 7 3 3" })
2710
3124
  ] }),
2711
3125
  "Generative Inpainting"
2712
3126
  ] }),
2713
- 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..." })
3127
+ isInpainting && /* @__PURE__ */ (0, import_jsx_runtime11.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..." })
2714
3128
  ] }),
2715
- /* @__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." }),
2716
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
2717
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2718
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3129
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
3130
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-1", children: [
3131
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
3132
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2719
3133
  "textarea",
2720
3134
  {
2721
3135
  id: "ai-inpaint-prompt-react",
@@ -2727,10 +3141,10 @@ var ImageEditor = ({
2727
3141
  }
2728
3142
  )
2729
3143
  ] }),
2730
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
2731
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2732
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
2733
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3144
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-1", children: [
3145
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
3146
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
3147
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2734
3148
  "button",
2735
3149
  {
2736
3150
  type: "button",
@@ -2743,7 +3157,7 @@ var ImageEditor = ({
2743
3157
  ]
2744
3158
  }
2745
3159
  ),
2746
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3160
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2747
3161
  "button",
2748
3162
  {
2749
3163
  type: "button",
@@ -2758,25 +3172,25 @@ var ImageEditor = ({
2758
3172
  )
2759
3173
  ] })
2760
3174
  ] }),
2761
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3175
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2762
3176
  "button",
2763
3177
  {
2764
3178
  type: "button",
2765
3179
  onClick: handleInpaint,
2766
3180
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
2767
3181
  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",
2768
- children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2769
- /* @__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" }),
3182
+ children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3183
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2770
3184
  "Generating Inpaint..."
2771
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2772
- /* @__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" }) }),
3185
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3186
+ /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.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" }) }),
2773
3187
  "Apply Inpainting"
2774
3188
  ] })
2775
3189
  }
2776
3190
  )
2777
3191
  ] }),
2778
- 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 }),
2779
- 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 })
3192
+ aiError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
3193
+ aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
2780
3194
  ] })
2781
3195
  ] })
2782
3196
  ] })
@@ -2790,8 +3204,10 @@ var ImageEditor = ({
2790
3204
  BrushOptionsBar,
2791
3205
  ControlSlider,
2792
3206
  CurvesEditor,
3207
+ FreeTransformOptionsBar,
2793
3208
  Histogram,
2794
3209
  ImageEditor,
3210
+ LayerEffectsPanel,
2795
3211
  LayersPanel,
2796
3212
  RefineEdgePanel,
2797
3213
  SelectionOptionsBar,