@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.
@@ -96,11 +96,11 @@ function useImageEditor(options = {}) {
96
96
  [controller]
97
97
  );
98
98
  const applyInpaintedImage = useCallback(
99
- (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
99
+ (newDataUrl, options2) => controller.applyInpaintedImage(newDataUrl, options2),
100
100
  [controller]
101
101
  );
102
102
  const applyBackgroundRemovedImage = useCallback(
103
- (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
103
+ (newDataUrl, options2) => controller.applyBackgroundRemovedImage(newDataUrl, options2),
104
104
  [controller]
105
105
  );
106
106
  return useMemo(
@@ -629,14 +629,17 @@ var AdjustmentLayersPanel = ({ controller, layers: layersProp, histogram }) => {
629
629
  import { useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
630
630
  import {
631
631
  BLEND_MODES as BLEND_MODES2,
632
+ PSD_FILE_ACCEPT,
633
+ downloadPsd,
632
634
  dropPlacement,
633
635
  dropZone,
636
+ openPsdFile,
634
637
  layerBadge,
635
638
  layerMenu,
636
639
  layerPanelRows,
637
640
  runLayerMenuAction
638
641
  } from "@pixerate/editor/image-editor";
639
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
642
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
640
643
  var iconButton = "px-1.5 py-0.5 rounded text-[10px] bg-slate-800 text-slate-300 hover:text-white disabled:opacity-40";
641
644
  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"}`;
642
645
  var Thumbnail = ({
@@ -671,13 +674,23 @@ var Thumbnail = ({
671
674
  }
672
675
  );
673
676
  };
674
- var LayersPanel = ({ controller, thumbnailSize = 32, className }) => {
677
+ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", className }) => {
675
678
  const [, setTick] = useState5(0);
676
679
  useEffect4(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
677
680
  const [error, setError] = useState5(null);
678
681
  const [renaming, setRenaming] = useState5(null);
679
682
  const [menu, setMenu] = useState5(null);
680
683
  const [drag, setDrag] = useState5(null);
684
+ const [psdNotes, setPsdNotes] = useState5([]);
685
+ const fileInput = useRef3(null);
686
+ const psdTask = async (fn) => {
687
+ try {
688
+ setError(null);
689
+ setPsdNotes(await fn());
690
+ } catch (err) {
691
+ setError(err instanceof Error ? err.message : String(err));
692
+ }
693
+ };
681
694
  const commitNext = useRef3(true);
682
695
  const state = controller.getState();
683
696
  const rows = layerPanelRows(state.layers, state.activeLayerId);
@@ -737,7 +750,31 @@ var LayersPanel = ({ controller, thumbnailSize = 32, className }) => {
737
750
  },
738
751
  children: "\u22EF"
739
752
  }
740
- )
753
+ ),
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(
758
+ "input",
759
+ {
760
+ ref: fileInput,
761
+ type: "file",
762
+ accept: PSD_FILE_ACCEPT,
763
+ className: "hidden",
764
+ "data-testid": "psd-file-input",
765
+ "aria-label": "Open a PSD file",
766
+ onChange: (e) => {
767
+ const file = e.currentTarget.files?.[0];
768
+ e.currentTarget.value = "";
769
+ if (file) psdTask(() => openPsdFile(controller, file));
770
+ }
771
+ }
772
+ )
773
+ ] })
774
+ ] }),
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" }) })
741
778
  ] }),
742
779
  active && /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
743
780
  /* @__PURE__ */ jsxs5("label", { className: "flex flex-col gap-0.5", children: [
@@ -1140,6 +1177,22 @@ var SelectionOptionsBar = ({
1140
1177
  /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
1141
1178
  /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
1142
1179
  "Sample adjusted image"
1180
+ ] }),
1181
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
1182
+ "Sample",
1183
+ /* @__PURE__ */ jsxs7(
1184
+ "select",
1185
+ {
1186
+ "aria-label": "Sample from",
1187
+ value: options.sample ?? "composite",
1188
+ onChange: (e) => set({ sample: e.target.value }),
1189
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5",
1190
+ children: [
1191
+ /* @__PURE__ */ jsx7("option", { value: "composite", children: "All layers" }),
1192
+ /* @__PURE__ */ jsx7("option", { value: "active-layer", children: "Active layer" })
1193
+ ]
1194
+ }
1195
+ )
1143
1196
  ] })
1144
1197
  ] }),
1145
1198
  /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-slate-300", children: [
@@ -1183,7 +1236,7 @@ import {
1183
1236
  adjustmentKindLabel as adjustmentKindLabel2,
1184
1237
  layerMaskFromSelection
1185
1238
  } from "@pixerate/editor/image-editor";
1186
- import { Fragment, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1239
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1187
1240
  var DEFAULT_PARAMS = { radius: 8, smartRadius: false, smooth: 0, feather: 0, contrast: 0, shiftEdge: 0 };
1188
1241
  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";
1189
1242
  var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
@@ -1281,7 +1334,7 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1281
1334
  /* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.selectionFromLayerMask(target.id), children: "Load as Selection" }),
1282
1335
  /* @__PURE__ */ jsx8("button", { type: "button", className: button, disabled: !target.mask, onClick: () => controller.setAdjustmentLayerMask(target.id, null), children: "Remove Mask" })
1283
1336
  ] }),
1284
- target.mask && /* @__PURE__ */ jsxs8(Fragment, { children: [
1337
+ target.mask && /* @__PURE__ */ jsxs8(Fragment2, { children: [
1285
1338
  /* @__PURE__ */ jsxs8("label", { className: "flex flex-col gap-1", children: [
1286
1339
  /* @__PURE__ */ jsxs8("span", { className: "flex justify-between text-slate-400", children: [
1287
1340
  /* @__PURE__ */ jsx8("span", { children: "Density" }),
@@ -1332,7 +1385,7 @@ var RefineEdgePanel = ({ controller, selection, layers, className = "" }) => {
1332
1385
  };
1333
1386
 
1334
1387
  // src/image-editor/ImageEditor.tsx
1335
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1388
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1336
1389
  var ImageEditor = ({
1337
1390
  editor: externalEditor,
1338
1391
  src,
@@ -1375,6 +1428,8 @@ var ImageEditor = ({
1375
1428
  const [isInpainting, setIsInpainting] = useState9(false);
1376
1429
  const [aiError, setAiError] = useState9(null);
1377
1430
  const [aiSuccess, setAiSuccess] = useState9(null);
1431
+ const [aiTarget, setAiTarget] = useState9("auto");
1432
+ const aiOptions = aiTarget === "auto" ? {} : { target: aiTarget };
1378
1433
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
1379
1434
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
1380
1435
  const handleRemoveBackground = async () => {
@@ -1386,10 +1441,10 @@ var ImageEditor = ({
1386
1441
  setAiSuccess(null);
1387
1442
  setIsRemovingBg(true);
1388
1443
  try {
1389
- const imageDataUrl = await editor.toSourceDataURL();
1444
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
1390
1445
  const result = await effectiveOnRemoveBg({ imageDataUrl });
1391
1446
  if (typeof result === "string") {
1392
- await editor.applyBackgroundRemovedImage(result);
1447
+ await editor.applyBackgroundRemovedImage(result, aiOptions);
1393
1448
  setAiSuccess("Background removed successfully!");
1394
1449
  } else {
1395
1450
  setAiSuccess("Background removal complete.");
@@ -1413,7 +1468,7 @@ var ImageEditor = ({
1413
1468
  setAiSuccess(null);
1414
1469
  setIsInpainting(true);
1415
1470
  try {
1416
- const imageDataUrl = await editor.toSourceDataURL();
1471
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
1417
1472
  const maskDataUrl = await editor.toMaskDataURL({
1418
1473
  useAnnotations: inpaintMaskMode === "annotations",
1419
1474
  useCrop: inpaintMaskMode === "crop"
@@ -1425,7 +1480,7 @@ var ImageEditor = ({
1425
1480
  cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
1426
1481
  });
1427
1482
  if (typeof result === "string") {
1428
- await editor.applyInpaintedImage(result);
1483
+ await editor.applyInpaintedImage(result, aiOptions);
1429
1484
  setAiSuccess("Inpainting applied successfully!");
1430
1485
  } else {
1431
1486
  setAiSuccess("Inpainting complete.");
@@ -2440,7 +2495,7 @@ var ImageEditor = ({
2440
2495
  ),
2441
2496
  /* @__PURE__ */ jsx9("span", { children: "Enable Depth / Alpha Mask" })
2442
2497
  ] }),
2443
- state.depthMask.enabled && /* @__PURE__ */ jsxs9(Fragment2, { children: [
2498
+ state.depthMask.enabled && /* @__PURE__ */ jsxs9(Fragment3, { children: [
2444
2499
  /* @__PURE__ */ jsxs9("div", { children: [
2445
2500
  /* @__PURE__ */ jsx9("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
2446
2501
  /* @__PURE__ */ jsx9(
@@ -2558,6 +2613,23 @@ var ImageEditor = ({
2558
2613
  /* @__PURE__ */ jsx9(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
2559
2614
  ] }),
2560
2615
  activeTab === "ai" && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-4 text-xs", children: [
2616
+ /* @__PURE__ */ jsxs9("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
2617
+ /* @__PURE__ */ jsx9("span", { children: "Results go to" }),
2618
+ /* @__PURE__ */ jsxs9(
2619
+ "select",
2620
+ {
2621
+ "aria-label": "AI result target",
2622
+ value: aiTarget,
2623
+ onChange: (e) => setAiTarget(e.target.value),
2624
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
2625
+ children: [
2626
+ /* @__PURE__ */ jsx9("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
2627
+ /* @__PURE__ */ jsx9("option", { value: "new-layer", children: "New layer / mask" }),
2628
+ /* @__PURE__ */ jsx9("option", { value: "replace", children: "Replace image" })
2629
+ ]
2630
+ }
2631
+ )
2632
+ ] }),
2561
2633
  /* @__PURE__ */ jsxs9("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2562
2634
  /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
2563
2635
  /* @__PURE__ */ jsxs9("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
@@ -2574,10 +2646,10 @@ var ImageEditor = ({
2574
2646
  onClick: handleRemoveBackground,
2575
2647
  disabled: isRemovingBg || isInpainting,
2576
2648
  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",
2577
- children: isRemovingBg ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
2649
+ children: isRemovingBg ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
2578
2650
  /* @__PURE__ */ jsx9("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2579
2651
  "Removing Background..."
2580
- ] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
2652
+ ] }) : /* @__PURE__ */ jsxs9(Fragment3, { children: [
2581
2653
  /* @__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" }) }),
2582
2654
  "Remove Background"
2583
2655
  ] })
@@ -2648,10 +2720,10 @@ var ImageEditor = ({
2648
2720
  onClick: handleInpaint,
2649
2721
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
2650
2722
  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",
2651
- children: isInpainting ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
2723
+ children: isInpainting ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
2652
2724
  /* @__PURE__ */ jsx9("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2653
2725
  "Generating Inpaint..."
2654
- ] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
2726
+ ] }) : /* @__PURE__ */ jsxs9(Fragment3, { children: [
2655
2727
  /* @__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" }) }),
2656
2728
  "Apply Inpainting"
2657
2729
  ] })
@@ -2681,4 +2753,4 @@ export {
2681
2753
  RefineEdgePanel,
2682
2754
  ImageEditor
2683
2755
  };
2684
- //# sourceMappingURL=chunk-SYTV33TW.js.map
2756
+ //# sourceMappingURL=chunk-OSOGA2OJ.js.map