@pixerate/editor-react 0.59.0 → 0.61.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.
@@ -1,4 +1,4 @@
1
- import { ImageEditorController, ImageEditorState, ImageEditorTool, CropBox, AspectRatio, ImageAdjustments, AdjustmentKind, AdjustmentLayerInit, AdjustmentLayerUpdate, ImageHistogram, DepthMaskOptions, CreateAnnotationPayload, Annotation, ImagePoint, ExportOptions, SerializedImageEditorState, ImageEditorOptions, InpaintHook, RemoveBackgroundHook, ImageEditorAIHooks, ControlSpec, CurvePoint, AdjustmentLayer, SelectionToolOptions, SelectionMask } from '@pixerate/editor/image-editor';
1
+ import { ImageEditorController, ImageEditorState, ImageEditorTool, CropBox, AspectRatio, ImageAdjustments, AdjustmentKind, AdjustmentLayerInit, AdjustmentLayerUpdate, ImageHistogram, DepthMaskOptions, CreateAnnotationPayload, Annotation, ImagePoint, ExportOptions, SerializedImageEditorState, AIResultOptions, ImageEditorOptions, InpaintHook, RemoveBackgroundHook, ImageEditorAIHooks, ControlSpec, CurvePoint, AdjustmentLayer, SelectionToolOptions, SelectionMask } from '@pixerate/editor/image-editor';
2
2
  import React__default from 'react';
3
3
 
4
4
  interface UseImageEditorReturn {
@@ -56,8 +56,10 @@ interface UseImageEditorReturn {
56
56
  useCrop?: boolean;
57
57
  }) => Promise<string>;
58
58
  toSourceDataURL: (options?: Pick<ExportOptions, 'format' | 'quality'>) => Promise<string>;
59
- applyInpaintedImage: (newDataUrl: string) => Promise<void>;
60
- applyBackgroundRemovedImage: (newDataUrl: string) => Promise<void>;
59
+ /** In documents with several layers (or `target: 'new-layer'`) the result goes to a new layer. */
60
+ applyInpaintedImage: (newDataUrl: string, options?: AIResultOptions) => Promise<string | void>;
61
+ /** In documents with several layers (or `target: 'new-layer'`) the result's alpha masks the active layer. */
62
+ applyBackgroundRemovedImage: (newDataUrl: string, options?: AIResultOptions) => Promise<string | void>;
61
63
  }
62
64
  declare function useImageEditor(options?: ImageEditorOptions): UseImageEditorReturn;
63
65
 
@@ -160,6 +162,10 @@ interface LayersPanelProps {
160
162
  controller: ImageEditorController;
161
163
  /** Thumbnail size in px (default 32). */
162
164
  thumbnailSize?: number;
165
+ /** Show Open PSD / Export PSD (default true). */
166
+ psd?: boolean;
167
+ /** Base name of exported PSD files (default `image`). */
168
+ psdFileName?: string;
163
169
  className?: string;
164
170
  }
165
171
  /**
@@ -1,4 +1,4 @@
1
- import { ImageEditorController, ImageEditorState, ImageEditorTool, CropBox, AspectRatio, ImageAdjustments, AdjustmentKind, AdjustmentLayerInit, AdjustmentLayerUpdate, ImageHistogram, DepthMaskOptions, CreateAnnotationPayload, Annotation, ImagePoint, ExportOptions, SerializedImageEditorState, ImageEditorOptions, InpaintHook, RemoveBackgroundHook, ImageEditorAIHooks, ControlSpec, CurvePoint, AdjustmentLayer, SelectionToolOptions, SelectionMask } from '@pixerate/editor/image-editor';
1
+ import { ImageEditorController, ImageEditorState, ImageEditorTool, CropBox, AspectRatio, ImageAdjustments, AdjustmentKind, AdjustmentLayerInit, AdjustmentLayerUpdate, ImageHistogram, DepthMaskOptions, CreateAnnotationPayload, Annotation, ImagePoint, ExportOptions, SerializedImageEditorState, AIResultOptions, ImageEditorOptions, InpaintHook, RemoveBackgroundHook, ImageEditorAIHooks, ControlSpec, CurvePoint, AdjustmentLayer, SelectionToolOptions, SelectionMask } from '@pixerate/editor/image-editor';
2
2
  import React__default from 'react';
3
3
 
4
4
  interface UseImageEditorReturn {
@@ -56,8 +56,10 @@ interface UseImageEditorReturn {
56
56
  useCrop?: boolean;
57
57
  }) => Promise<string>;
58
58
  toSourceDataURL: (options?: Pick<ExportOptions, 'format' | 'quality'>) => Promise<string>;
59
- applyInpaintedImage: (newDataUrl: string) => Promise<void>;
60
- applyBackgroundRemovedImage: (newDataUrl: string) => Promise<void>;
59
+ /** In documents with several layers (or `target: 'new-layer'`) the result goes to a new layer. */
60
+ applyInpaintedImage: (newDataUrl: string, options?: AIResultOptions) => Promise<string | void>;
61
+ /** In documents with several layers (or `target: 'new-layer'`) the result's alpha masks the active layer. */
62
+ applyBackgroundRemovedImage: (newDataUrl: string, options?: AIResultOptions) => Promise<string | void>;
61
63
  }
62
64
  declare function useImageEditor(options?: ImageEditorOptions): UseImageEditorReturn;
63
65
 
@@ -160,6 +162,10 @@ interface LayersPanelProps {
160
162
  controller: ImageEditorController;
161
163
  /** Thumbnail size in px (default 32). */
162
164
  thumbnailSize?: number;
165
+ /** Show Open PSD / Export PSD (default true). */
166
+ psd?: boolean;
167
+ /** Base name of exported PSD files (default `image`). */
168
+ psdFileName?: string;
163
169
  className?: string;
164
170
  }
165
171
  /**
@@ -10,7 +10,7 @@ import {
10
10
  SelectionOptionsBar,
11
11
  useImageEditor,
12
12
  useImageHistogram
13
- } from "../chunk-SYTV33TW.js";
13
+ } from "../chunk-OSOGA2OJ.js";
14
14
  export {
15
15
  AdjustmentLayersPanel,
16
16
  BrushOptionsBar,
package/dist/index.cjs CHANGED
@@ -3720,11 +3720,11 @@ function useImageEditor(options = {}) {
3720
3720
  [controller]
3721
3721
  );
3722
3722
  const applyInpaintedImage = (0, import_react34.useCallback)(
3723
- (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
3723
+ (newDataUrl, options2) => controller.applyInpaintedImage(newDataUrl, options2),
3724
3724
  [controller]
3725
3725
  );
3726
3726
  const applyBackgroundRemovedImage = (0, import_react34.useCallback)(
3727
- (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
3727
+ (newDataUrl, options2) => controller.applyBackgroundRemovedImage(newDataUrl, options2),
3728
3728
  [controller]
3729
3729
  );
3730
3730
  return (0, import_react34.useMemo)(
@@ -4253,13 +4253,23 @@ var Thumbnail = ({
4253
4253
  }
4254
4254
  );
4255
4255
  };
4256
- var LayersPanel = ({ controller, thumbnailSize = 32, className }) => {
4256
+ var LayersPanel = ({ controller, thumbnailSize = 32, psd = true, psdFileName = "image", className }) => {
4257
4257
  const [, setTick] = (0, import_react38.useState)(0);
4258
4258
  (0, import_react38.useEffect)(() => controller.subscribe(() => setTick((n) => n + 1)), [controller]);
4259
4259
  const [error, setError] = (0, import_react38.useState)(null);
4260
4260
  const [renaming, setRenaming] = (0, import_react38.useState)(null);
4261
4261
  const [menu, setMenu] = (0, import_react38.useState)(null);
4262
4262
  const [drag, setDrag] = (0, import_react38.useState)(null);
4263
+ const [psdNotes, setPsdNotes] = (0, import_react38.useState)([]);
4264
+ const fileInput = (0, import_react38.useRef)(null);
4265
+ const psdTask = async (fn) => {
4266
+ try {
4267
+ setError(null);
4268
+ setPsdNotes(await fn());
4269
+ } catch (err) {
4270
+ setError(err instanceof Error ? err.message : String(err));
4271
+ }
4272
+ };
4263
4273
  const commitNext = (0, import_react38.useRef)(true);
4264
4274
  const state = controller.getState();
4265
4275
  const rows = (0, import_image_editor6.layerPanelRows)(state.layers, state.activeLayerId);
@@ -4319,7 +4329,31 @@ var LayersPanel = ({ controller, thumbnailSize = 32, className }) => {
4319
4329
  },
4320
4330
  children: "\u22EF"
4321
4331
  }
4322
- )
4332
+ ),
4333
+ psd && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
4334
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", className: iconButton, onClick: () => fileInput.current?.click(), children: "Open PSD\u2026" }),
4335
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", className: iconButton, onClick: () => psdTask(async () => (await (0, import_image_editor6.downloadPsd)(controller, psdFileName)).warnings), children: "Export PSD" }),
4336
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4337
+ "input",
4338
+ {
4339
+ ref: fileInput,
4340
+ type: "file",
4341
+ accept: import_image_editor6.PSD_FILE_ACCEPT,
4342
+ className: "hidden",
4343
+ "data-testid": "psd-file-input",
4344
+ "aria-label": "Open a PSD file",
4345
+ onChange: (e) => {
4346
+ const file = e.currentTarget.files?.[0];
4347
+ e.currentTarget.value = "";
4348
+ if (file) psdTask(() => (0, import_image_editor6.openPsdFile)(controller, file));
4349
+ }
4350
+ }
4351
+ )
4352
+ ] })
4353
+ ] }),
4354
+ psdNotes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.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: [
4355
+ psdNotes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: n }, n)),
4356
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", className: "underline", onClick: () => setPsdNotes([]), children: "Dismiss" }) })
4323
4357
  ] }),
4324
4358
  active && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1 items-center", "data-testid": "layer-properties", children: [
4325
4359
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
@@ -4717,6 +4751,22 @@ var SelectionOptionsBar = ({
4717
4751
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
4718
4752
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("input", { type: "checkbox", checked: options.sampleMerged, onChange: (e) => set({ sampleMerged: e.target.checked }) }),
4719
4753
  "Sample adjusted image"
4754
+ ] }),
4755
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
4756
+ "Sample",
4757
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
4758
+ "select",
4759
+ {
4760
+ "aria-label": "Sample from",
4761
+ value: options.sample ?? "composite",
4762
+ onChange: (e) => set({ sample: e.target.value }),
4763
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5",
4764
+ children: [
4765
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: "composite", children: "All layers" }),
4766
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: "active-layer", children: "Active layer" })
4767
+ ]
4768
+ }
4769
+ )
4720
4770
  ] })
4721
4771
  ] }),
4722
4772
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
@@ -4947,6 +4997,8 @@ var ImageEditor = ({
4947
4997
  const [isInpainting, setIsInpainting] = (0, import_react42.useState)(false);
4948
4998
  const [aiError, setAiError] = (0, import_react42.useState)(null);
4949
4999
  const [aiSuccess, setAiSuccess] = (0, import_react42.useState)(null);
5000
+ const [aiTarget, setAiTarget] = (0, import_react42.useState)("auto");
5001
+ const aiOptions = aiTarget === "auto" ? {} : { target: aiTarget };
4950
5002
  const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
4951
5003
  const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
4952
5004
  const handleRemoveBackground = async () => {
@@ -4958,10 +5010,10 @@ var ImageEditor = ({
4958
5010
  setAiSuccess(null);
4959
5011
  setIsRemovingBg(true);
4960
5012
  try {
4961
- const imageDataUrl = await editor.toSourceDataURL();
5013
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
4962
5014
  const result = await effectiveOnRemoveBg({ imageDataUrl });
4963
5015
  if (typeof result === "string") {
4964
- await editor.applyBackgroundRemovedImage(result);
5016
+ await editor.applyBackgroundRemovedImage(result, aiOptions);
4965
5017
  setAiSuccess("Background removed successfully!");
4966
5018
  } else {
4967
5019
  setAiSuccess("Background removal complete.");
@@ -4985,7 +5037,7 @@ var ImageEditor = ({
4985
5037
  setAiSuccess(null);
4986
5038
  setIsInpainting(true);
4987
5039
  try {
4988
- const imageDataUrl = await editor.toSourceDataURL();
5040
+ const imageDataUrl = await controller.toAIInputDataURL(aiOptions);
4989
5041
  const maskDataUrl = await editor.toMaskDataURL({
4990
5042
  useAnnotations: inpaintMaskMode === "annotations",
4991
5043
  useCrop: inpaintMaskMode === "crop"
@@ -4997,7 +5049,7 @@ var ImageEditor = ({
4997
5049
  cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
4998
5050
  });
4999
5051
  if (typeof result === "string") {
5000
- await editor.applyInpaintedImage(result);
5052
+ await editor.applyInpaintedImage(result, aiOptions);
5001
5053
  setAiSuccess("Inpainting applied successfully!");
5002
5054
  } else {
5003
5055
  setAiSuccess("Inpainting complete.");
@@ -6130,6 +6182,23 @@ var ImageEditor = ({
6130
6182
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(RefineEdgePanel, { controller, selection: state.selection ?? null, layers: state.adjustments.layers })
6131
6183
  ] }),
6132
6184
  activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
6185
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "flex items-center justify-between gap-2 text-slate-300", children: [
6186
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: "Results go to" }),
6187
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
6188
+ "select",
6189
+ {
6190
+ "aria-label": "AI result target",
6191
+ value: aiTarget,
6192
+ onChange: (e) => setAiTarget(e.target.value),
6193
+ className: "bg-slate-800 border border-slate-700 rounded px-1.5 py-1",
6194
+ children: [
6195
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: "auto", children: controller.isMultiLayer ? "Auto (new layer)" : "Auto (replace image)" }),
6196
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: "new-layer", children: "New layer / mask" }),
6197
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: "replace", children: "Replace image" })
6198
+ ]
6199
+ }
6200
+ )
6201
+ ] }),
6133
6202
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
6134
6203
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-center justify-between", children: [
6135
6204
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [