@pixerate/editor-react 0.59.0 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -140,11 +140,11 @@ function useImageEditor(options = {}) {
140
140
  [controller]
141
141
  );
142
142
  const applyInpaintedImage = (0, import_react.useCallback)(
143
- (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
143
+ (newDataUrl, options2) => controller.applyInpaintedImage(newDataUrl, options2),
144
144
  [controller]
145
145
  );
146
146
  const applyBackgroundRemovedImage = (0, import_react.useCallback)(
147
- (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
147
+ (newDataUrl, options2) => controller.applyBackgroundRemovedImage(newDataUrl, options2),
148
148
  [controller]
149
149
  );
150
150
  return (0, import_react.useMemo)(
@@ -673,13 +673,23 @@ var Thumbnail = ({
673
673
  }
674
674
  );
675
675
  };
676
- var LayersPanel = ({ controller, thumbnailSize = 32, className }) => {
676
+ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", className }) => {
677
677
  const [, setTick] = (0, import_react5.useState)(0);
678
678
  (0, import_react5.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
679
679
  const [error, setError] = (0, import_react5.useState)(null);
680
680
  const [renaming, setRenaming] = (0, import_react5.useState)(null);
681
681
  const [menu, setMenu] = (0, import_react5.useState)(null);
682
682
  const [drag, setDrag] = (0, import_react5.useState)(null);
683
+ const [psdNotes, setPsdNotes] = (0, import_react5.useState)([]);
684
+ const fileInput = (0, import_react5.useRef)(null);
685
+ const psdTask = async (fn) => {
686
+ try {
687
+ setError(null);
688
+ setPsdNotes(await fn());
689
+ } catch (err) {
690
+ setError(err instanceof Error ? err.message : String(err));
691
+ }
692
+ };
683
693
  const commitNext = (0, import_react5.useRef)(true);
684
694
  const state = controller.getState();
685
695
  const rows = (0, import_image_editor6.layerPanelRows)(state.layers, state.activeLayerId);
@@ -739,7 +749,31 @@ var LayersPanel = ({ controller, thumbnailSize = 32, className }) => {
739
749
  },
740
750
  children: "\u22EF"
741
751
  }
742
- )
752
+ ),
753
+ psd && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
754
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => fileInput.current?.click(), children: "Open PSD\u2026" }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: iconButton, onClick: () => psdTask(async () => (await (0, import_image_editor6.downloadPsd)(controller, psdFileName)).warnings), children: "Export PSD" }),
756
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
757
+ "input",
758
+ {
759
+ ref: fileInput,
760
+ type: "file",
761
+ accept: import_image_editor6.PSD_FILE_ACCEPT,
762
+ className: "hidden",
763
+ "data-testid": "psd-file-input",
764
+ "aria-label": "Open a PSD file",
765
+ onChange: (e) => {
766
+ const file = e.currentTarget.files?.[0];
767
+ e.currentTarget.value = "";
768
+ if (file) psdTask(() => (0, import_image_editor6.openPsdFile)(controller, file));
769
+ }
770
+ }
771
+ )
772
+ ] })
773
+ ] }),
774
+ psdNotes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("ul", { className: "rounded bg-amber-900/30 text-amber-200 px-2 py-1 space-y-0.5", "data-testid": "psd-warnings", "aria-label": "PSD warnings", children: [
775
+ psdNotes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: n }, n)),
776
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "underline", onClick: () => setPsdNotes([]), children: "Dismiss" }) })
743
777
  ] }),
744
778
  active && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
745
779
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
@@ -1137,6 +1171,22 @@ var SelectionOptionsBar = ({
1137
1171
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1138
1172
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
1139
1173
  "Sample adjusted image"
1174
+ ] }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
1176
+ "Sample",
1177
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1178
+ "select",
1179
+ {
1180
+ "aria-label": "Sample from",
1181
+ value: options.sample ?? "composite",
1182
+ onChange: (e) => set({ sample: e.target.value }),
1183
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5",
1184
+ children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "composite", children: "All layers" }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "active-layer", children: "Active layer" })
1187
+ ]
1188
+ }
1189
+ )
1140
1190
  ] })
1141
1191
  ] }),
1142
1192
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
@@ -1367,6 +1417,8 @@ var ImageEditor = ({
1367
1417
  const [isInpainting, setIsInpainting] = (0, import_react9.useState)(false);
1368
1418
  const [aiError, setAiError] = (0, import_react9.useState)(null);
1369
1419
  const [aiSuccess, setAiSuccess] = (0, import_react9.useState)(null);
1420
+ const [aiTarget, setAiTarget] = (0, import_react9.useState)("auto");
1421
+ const aiOptions = aiTarget === "auto" ? {} : { target: aiTarget };
1370
1422
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
1371
1423
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
1372
1424
  const handleRemoveBackground = async () => {
@@ -1378,10 +1430,10 @@ var ImageEditor = ({
1378
1430
  setAiSuccess(null);
1379
1431
  setIsRemovingBg(true);
1380
1432
  try {
1381
- const imageDataUrl = await editor.toSourceDataURL();
1433
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
1382
1434
  const result = await effectiveOnRemoveBg({ imageDataUrl });
1383
1435
  if (typeof result === "string") {
1384
- await editor.applyBackgroundRemovedImage(result);
1436
+ await editor.applyBackgroundRemovedImage(result, aiOptions);
1385
1437
  setAiSuccess("Background removed successfully!");
1386
1438
  } else {
1387
1439
  setAiSuccess("Background removal complete.");
@@ -1405,7 +1457,7 @@ var ImageEditor = ({
1405
1457
  setAiSuccess(null);
1406
1458
  setIsInpainting(true);
1407
1459
  try {
1408
- const imageDataUrl = await editor.toSourceDataURL();
1460
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
1409
1461
  const maskDataUrl = await editor.toMaskDataURL({
1410
1462
  useAnnotations: inpaintMaskMode === "annotations",
1411
1463
  useCrop: inpaintMaskMode === "crop"
@@ -1417,7 +1469,7 @@ var ImageEditor = ({
1417
1469
  cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
1418
1470
  });
1419
1471
  if (typeof result === "string") {
1420
- await editor.applyInpaintedImage(result);
1472
+ await editor.applyInpaintedImage(result, aiOptions);
1421
1473
  setAiSuccess("Inpainting applied successfully!");
1422
1474
  } else {
1423
1475
  setAiSuccess("Inpainting complete.");
@@ -2550,6 +2602,23 @@ var ImageEditor = ({
2550
2602
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2551
2603
  ] }),
2552
2604
  activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2605
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
2606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Results go to" }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2608
+ "select",
2609
+ {
2610
+ "aria-label": "AI result target",
2611
+ value: aiTarget,
2612
+ onChange: (e) => setAiTarget(e.target.value),
2613
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
2614
+ children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
2616
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "new-layer", children: "New layer / mask" }),
2617
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "replace", children: "Replace image" })
2618
+ ]
2619
+ }
2620
+ )
2621
+ ] }),
2553
2622
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2554
2623
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between", children: [
2555
2624
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [