@pixerate/editor-react 0.8.0 → 0.9.1

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.
package/dist/index.cjs CHANGED
@@ -1315,6 +1315,22 @@ function useImageEditor(options = {}) {
1315
1315
  const toBlob = (0, import_react14.useCallback)((opts) => controller.toBlob(opts), [controller]);
1316
1316
  const toJSON = (0, import_react14.useCallback)(() => controller.toJSON(), [controller]);
1317
1317
  const loadJSON = (0, import_react14.useCallback)((data) => controller.loadJSON(data), [controller]);
1318
+ const renderMask = (0, import_react14.useCallback)(
1319
+ (opts) => controller.renderMask(opts),
1320
+ [controller]
1321
+ );
1322
+ const toMaskDataURL = (0, import_react14.useCallback)(
1323
+ (opts) => controller.toMaskDataURL(opts),
1324
+ [controller]
1325
+ );
1326
+ const applyInpaintedImage = (0, import_react14.useCallback)(
1327
+ (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
1328
+ [controller]
1329
+ );
1330
+ const applyBackgroundRemovedImage = (0, import_react14.useCallback)(
1331
+ (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
1332
+ [controller]
1333
+ );
1318
1334
  return (0, import_react14.useMemo)(
1319
1335
  () => ({
1320
1336
  controller,
@@ -1351,7 +1367,11 @@ function useImageEditor(options = {}) {
1351
1367
  toDataURL,
1352
1368
  toBlob,
1353
1369
  toJSON,
1354
- loadJSON
1370
+ loadJSON,
1371
+ renderMask,
1372
+ toMaskDataURL,
1373
+ applyInpaintedImage,
1374
+ applyBackgroundRemovedImage
1355
1375
  }),
1356
1376
  // eslint-disable-next-line react-hooks/exhaustive-deps
1357
1377
  [controller, state, historyTicks]
@@ -1368,7 +1388,10 @@ var ImageEditor = ({
1368
1388
  className = "",
1369
1389
  style,
1370
1390
  onSave,
1371
- exportOptions
1391
+ exportOptions,
1392
+ onInpaint,
1393
+ onRemoveBackground,
1394
+ aiHooks
1372
1395
  }) => {
1373
1396
  const internalEditor = useImageEditor({ image: src });
1374
1397
  const editor = externalEditor || internalEditor;
@@ -1377,6 +1400,73 @@ var ImageEditor = ({
1377
1400
  const containerRef = (0, import_react15.useRef)(null);
1378
1401
  const [activeTab, setActiveTab] = (0, import_react15.useState)("crop");
1379
1402
  const [activeCropRatio, setActiveCropRatio] = (0, import_react15.useState)(null);
1403
+ const [inpaintPrompt, setInpaintPrompt] = (0, import_react15.useState)("");
1404
+ const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react15.useState)("annotations");
1405
+ const [isRemovingBg, setIsRemovingBg] = (0, import_react15.useState)(false);
1406
+ const [isInpainting, setIsInpainting] = (0, import_react15.useState)(false);
1407
+ const [aiError, setAiError] = (0, import_react15.useState)(null);
1408
+ const [aiSuccess, setAiSuccess] = (0, import_react15.useState)(null);
1409
+ const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
1410
+ const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
1411
+ const handleRemoveBackground = async () => {
1412
+ if (!effectiveOnRemoveBg) {
1413
+ setAiError("No background removal hook is configured.");
1414
+ return;
1415
+ }
1416
+ setAiError(null);
1417
+ setAiSuccess(null);
1418
+ setIsRemovingBg(true);
1419
+ try {
1420
+ const imageDataUrl = await editor.toDataURL(exportOptions);
1421
+ const result = await effectiveOnRemoveBg({ imageDataUrl });
1422
+ if (typeof result === "string") {
1423
+ await editor.applyBackgroundRemovedImage(result);
1424
+ setAiSuccess("Background removed successfully!");
1425
+ } else {
1426
+ setAiSuccess("Background removal complete.");
1427
+ }
1428
+ } catch (err) {
1429
+ setAiError(err?.message || "Failed to remove background.");
1430
+ } finally {
1431
+ setIsRemovingBg(false);
1432
+ }
1433
+ };
1434
+ const handleInpaint = async () => {
1435
+ if (!effectiveOnInpaint) {
1436
+ setAiError("No inpainting hook is configured.");
1437
+ return;
1438
+ }
1439
+ if (!inpaintPrompt.trim()) {
1440
+ setAiError("Please enter an inpainting prompt.");
1441
+ return;
1442
+ }
1443
+ setAiError(null);
1444
+ setAiSuccess(null);
1445
+ setIsInpainting(true);
1446
+ try {
1447
+ const imageDataUrl = await editor.toDataURL(exportOptions);
1448
+ const maskDataUrl = await editor.toMaskDataURL({
1449
+ useAnnotations: inpaintMaskMode === "annotations",
1450
+ useCrop: inpaintMaskMode === "crop"
1451
+ });
1452
+ const result = await effectiveOnInpaint({
1453
+ prompt: inpaintPrompt.trim(),
1454
+ imageDataUrl,
1455
+ maskDataUrl,
1456
+ cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
1457
+ });
1458
+ if (typeof result === "string") {
1459
+ await editor.applyInpaintedImage(result);
1460
+ setAiSuccess("Inpainting applied successfully!");
1461
+ } else {
1462
+ setAiSuccess("Inpainting complete.");
1463
+ }
1464
+ } catch (err) {
1465
+ setAiError(err?.message || "Failed to apply inpainting.");
1466
+ } finally {
1467
+ setIsInpainting(false);
1468
+ }
1469
+ };
1380
1470
  const [activeColor, setActiveColor] = (0, import_react15.useState)("#ef4444");
1381
1471
  const [strokeWidth, setStrokeWidth] = (0, import_react15.useState)(3);
1382
1472
  const [fontSize, setFontSize] = (0, import_react15.useState)(24);
@@ -1832,6 +1922,21 @@ var ImageEditor = ({
1832
1922
  className: `px-3 py-1.5 rounded-md font-medium transition-colors ${activeTab === "depth" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
1833
1923
  children: "Depth Mask"
1834
1924
  }
1925
+ ),
1926
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1927
+ "button",
1928
+ {
1929
+ type: "button",
1930
+ onClick: () => {
1931
+ setActiveTab("ai");
1932
+ editor.setTool("select");
1933
+ },
1934
+ 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"}`,
1935
+ children: [
1936
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
1937
+ "AI Tools"
1938
+ ]
1939
+ }
1835
1940
  )
1836
1941
  ] }),
1837
1942
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
@@ -2431,6 +2536,110 @@ var ImageEditor = ({
2431
2536
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Invert Mask" })
2432
2537
  ] })
2433
2538
  ] })
2539
+ ] }),
2540
+ activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2541
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2542
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
2543
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2544
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
2545
+ "Background Removal"
2546
+ ] }),
2547
+ isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
2548
+ ] }),
2549
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2551
+ "button",
2552
+ {
2553
+ type: "button",
2554
+ onClick: handleRemoveBackground,
2555
+ disabled: isRemovingBg || isInpainting,
2556
+ 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",
2557
+ children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2558
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2559
+ "Removing Background..."
2560
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2561
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2562
+ "Remove Background"
2563
+ ] })
2564
+ }
2565
+ )
2566
+ ] }),
2567
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
2568
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
2569
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
2570
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2571
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
2572
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m14 7 3 3" })
2573
+ ] }),
2574
+ "Generative Inpainting"
2575
+ ] }),
2576
+ isInpainting && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
2577
+ ] }),
2578
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
2579
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
2581
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2582
+ "textarea",
2583
+ {
2584
+ id: "ai-inpaint-prompt-react",
2585
+ value: inpaintPrompt,
2586
+ onChange: (e) => setInpaintPrompt(e.target.value),
2587
+ placeholder: "e.g. replace sunglasses with steampunk goggles, remove person in background...",
2588
+ rows: 3,
2589
+ className: "w-full bg-slate-900 border border-slate-700 rounded-lg p-2 text-xs text-slate-100 placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none"
2590
+ }
2591
+ )
2592
+ ] }),
2593
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
2594
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
2595
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
2596
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2597
+ "button",
2598
+ {
2599
+ type: "button",
2600
+ onClick: () => setInpaintMaskMode("annotations"),
2601
+ className: `px-2 py-1.5 rounded border text-[11px] font-medium text-left transition-colors ${inpaintMaskMode === "annotations" ? "bg-purple-600/30 border-purple-500 text-purple-200" : "bg-slate-900 border-slate-700 text-slate-400 hover:text-slate-200"}`,
2602
+ children: [
2603
+ "Drawn Shapes (",
2604
+ state.annotations.length,
2605
+ ")"
2606
+ ]
2607
+ }
2608
+ ),
2609
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2610
+ "button",
2611
+ {
2612
+ type: "button",
2613
+ onClick: () => setInpaintMaskMode("crop"),
2614
+ className: `px-2 py-1.5 rounded border text-[11px] font-medium text-left transition-colors ${inpaintMaskMode === "crop" ? "bg-purple-600/30 border-purple-500 text-purple-200" : "bg-slate-900 border-slate-700 text-slate-400 hover:text-slate-200"}`,
2615
+ children: [
2616
+ "Crop Box (",
2617
+ state.crop ? "Selected" : "Full",
2618
+ ")"
2619
+ ]
2620
+ }
2621
+ )
2622
+ ] })
2623
+ ] }),
2624
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2625
+ "button",
2626
+ {
2627
+ type: "button",
2628
+ onClick: handleInpaint,
2629
+ disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
2630
+ 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",
2631
+ children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2632
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
2633
+ "Generating Inpaint..."
2634
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2635
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2636
+ "Apply Inpainting"
2637
+ ] })
2638
+ }
2639
+ )
2640
+ ] }),
2641
+ aiError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
2642
+ aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
2434
2643
  ] })
2435
2644
  ] })
2436
2645
  ] })