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