@pixldocs/canvas-renderer 0.5.101 → 0.5.103

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
@@ -3682,28 +3682,33 @@ function updateCoverLayout(g) {
3682
3682
  const hasCustomSvgMask = Boolean(
3683
3683
  currentClipPath && (isSvgMaskClipPath(currentClipPath) || isLuminanceMaskClipPath(currentClipPath) || currentClipPath.__svgMask)
3684
3684
  );
3685
- if (hasCustomSvgMask) {
3685
+ const hasEdgeFadeMask = Boolean(currentClipPath && currentClipPath.__edgeFadeMask);
3686
+ if (hasCustomSvgMask || hasEdgeFadeMask) {
3686
3687
  if (isSvgMaskClipPath(currentClipPath)) {
3687
3688
  syncSvgMaskClipPath(g);
3688
3689
  } else if (currentClipPath && typeof currentClipPath.set === "function") {
3689
- const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3690
- const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3691
- currentClipPath.set({
3692
- left: 0,
3693
- top: 0,
3694
- originX: "center",
3695
- originY: "center",
3696
- scaleX: frameW / baseW,
3697
- scaleY: frameH / baseH,
3698
- selectable: false,
3699
- evented: false,
3700
- hasControls: false,
3701
- hasBorders: false
3702
- });
3703
- currentClipPath.absolutePositioned = false;
3704
- currentClipPath.excludeFromExport = true;
3705
- currentClipPath.dirty = true;
3706
- currentClipPath.setCoords();
3690
+ if (hasEdgeFadeMask) {
3691
+ currentClipPath.dirty = true;
3692
+ } else {
3693
+ const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3694
+ const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3695
+ currentClipPath.set({
3696
+ left: 0,
3697
+ top: 0,
3698
+ originX: "center",
3699
+ originY: "center",
3700
+ scaleX: frameW / baseW,
3701
+ scaleY: frameH / baseH,
3702
+ selectable: false,
3703
+ evented: false,
3704
+ hasControls: false,
3705
+ hasBorders: false
3706
+ });
3707
+ currentClipPath.absolutePositioned = false;
3708
+ currentClipPath.excludeFromExport = true;
3709
+ currentClipPath.dirty = true;
3710
+ currentClipPath.setCoords();
3711
+ }
3707
3712
  }
3708
3713
  } else {
3709
3714
  const needsNewClipPath = !g.clipPath || shape === "circle" && !(g.clipPath instanceof fabric__namespace.Ellipse) || shape !== "circle" && !(g.clipPath instanceof fabric__namespace.Rect);
@@ -4660,10 +4665,12 @@ function exitCropMode(g, commit = true) {
4660
4665
  g.hasControls = true;
4661
4666
  g.borderDashArray = void 0;
4662
4667
  installCanvaMaskControls(g);
4663
- g.borderColor = void 0;
4664
- g.cornerColor = void 0;
4665
- g.cornerStrokeColor = void 0;
4668
+ g.borderColor = "#4f46e5";
4669
+ g.borderDashArray = void 0;
4670
+ g.cornerColor = "#ffffff";
4671
+ g.cornerStrokeColor = "#4f46e5";
4666
4672
  g.cornerStyle = "rect";
4673
+ g.transparentCorners = false;
4667
4674
  g[CROP_MODE_FLAG] = false;
4668
4675
  g.__cropZoomLastPointer = void 0;
4669
4676
  g.__lastPointerForCrop = void 0;
@@ -6193,6 +6200,129 @@ function renderSmartElementToDataUri(type, props, width, height) {
6193
6200
  if (!svg) return null;
6194
6201
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
6195
6202
  }
6203
+ function hasEdgeFade(p) {
6204
+ if (!p) return false;
6205
+ const sides = [
6206
+ [p.fadeTopSize, p.fadeTopAmount],
6207
+ [p.fadeRightSize, p.fadeRightAmount],
6208
+ [p.fadeBottomSize, p.fadeBottomAmount],
6209
+ [p.fadeLeftSize, p.fadeLeftAmount]
6210
+ ];
6211
+ for (const [size, amount] of sides) {
6212
+ const s = Number(size) || 0;
6213
+ const a = amount == null ? 1 : Number(amount);
6214
+ if (s > 0 && a < 1) return true;
6215
+ }
6216
+ return false;
6217
+ }
6218
+ function edgeFadeKey(p) {
6219
+ if (!hasEdgeFade(p)) return "";
6220
+ return [
6221
+ (p == null ? void 0 : p.fadeTopSize) ?? 0,
6222
+ (p == null ? void 0 : p.fadeTopAmount) ?? 1,
6223
+ (p == null ? void 0 : p.fadeRightSize) ?? 0,
6224
+ (p == null ? void 0 : p.fadeRightAmount) ?? 1,
6225
+ (p == null ? void 0 : p.fadeBottomSize) ?? 0,
6226
+ (p == null ? void 0 : p.fadeBottomAmount) ?? 1,
6227
+ (p == null ? void 0 : p.fadeLeftSize) ?? 0,
6228
+ (p == null ? void 0 : p.fadeLeftAmount) ?? 1,
6229
+ (p == null ? void 0 : p.fadeTopHardness) ?? 1,
6230
+ (p == null ? void 0 : p.fadeRightHardness) ?? 1,
6231
+ (p == null ? void 0 : p.fadeBottomHardness) ?? 1,
6232
+ (p == null ? void 0 : p.fadeLeftHardness) ?? 1
6233
+ ].join("|");
6234
+ }
6235
+ function clamp01(n) {
6236
+ if (!Number.isFinite(n)) return 0;
6237
+ return Math.max(0, Math.min(1, n));
6238
+ }
6239
+ function bakeEdgeFade(source, fade) {
6240
+ const w = source.naturalWidth || source.width || 0;
6241
+ const h = source.naturalHeight || source.height || 0;
6242
+ const canvas = document.createElement("canvas");
6243
+ canvas.width = Math.max(1, w);
6244
+ canvas.height = Math.max(1, h);
6245
+ const ctx = canvas.getContext("2d");
6246
+ if (!ctx) return canvas;
6247
+ ctx.drawImage(source, 0, 0, canvas.width, canvas.height);
6248
+ const clampHardness = (n) => {
6249
+ const v = Number(n);
6250
+ if (!Number.isFinite(v) || v <= 0) return 1;
6251
+ return Math.max(0.1, Math.min(5, v));
6252
+ };
6253
+ const sides = [
6254
+ { side: "top", size: clamp01(fade.fadeTopSize ?? 0), amount: clamp01(fade.fadeTopAmount ?? 1), hardness: clampHardness(fade.fadeTopHardness) },
6255
+ { side: "right", size: clamp01(fade.fadeRightSize ?? 0), amount: clamp01(fade.fadeRightAmount ?? 1), hardness: clampHardness(fade.fadeRightHardness) },
6256
+ { side: "bottom", size: clamp01(fade.fadeBottomSize ?? 0), amount: clamp01(fade.fadeBottomAmount ?? 1), hardness: clampHardness(fade.fadeBottomHardness) },
6257
+ { side: "left", size: clamp01(fade.fadeLeftSize ?? 0), amount: clamp01(fade.fadeLeftAmount ?? 1), hardness: clampHardness(fade.fadeLeftHardness) }
6258
+ ];
6259
+ for (const { side, size, amount, hardness } of sides) {
6260
+ if (size <= 0 || amount >= 1) continue;
6261
+ const mask = document.createElement("canvas");
6262
+ mask.width = canvas.width;
6263
+ mask.height = canvas.height;
6264
+ const mctx = mask.getContext("2d");
6265
+ if (!mctx) continue;
6266
+ mctx.fillStyle = "#000";
6267
+ mctx.fillRect(0, 0, mask.width, mask.height);
6268
+ let g;
6269
+ let x = 0, y = 0, rectW = mask.width, rectH = mask.height;
6270
+ const STOPS = 64;
6271
+ const gamma = 1 / hardness;
6272
+ const seamGuardPx = 2;
6273
+ const innerAlpha = (t) => {
6274
+ const keepProgress = Math.pow(t, gamma);
6275
+ const erase = (1 - amount) * (1 - keepProgress);
6276
+ return 1 - erase;
6277
+ };
6278
+ const fillStops = (grad, reverse) => {
6279
+ for (let i = 0; i <= STOPS; i++) {
6280
+ const t = i / STOPS;
6281
+ const a = innerAlpha(t);
6282
+ grad.addColorStop(t, `rgba(0,0,0,${a})`);
6283
+ }
6284
+ };
6285
+ if (side === "top") {
6286
+ const band = Math.max(1, Math.round(mask.height * size));
6287
+ g = mctx.createLinearGradient(0, 0, 0, band);
6288
+ fillStops(g);
6289
+ rectH = band;
6290
+ } else if (side === "bottom") {
6291
+ const band = Math.max(1, Math.round(mask.height * size));
6292
+ y = mask.height - band;
6293
+ g = mctx.createLinearGradient(0, mask.height, 0, y);
6294
+ fillStops(g);
6295
+ rectH = band;
6296
+ } else if (side === "left") {
6297
+ const band = Math.max(1, Math.round(mask.width * size));
6298
+ g = mctx.createLinearGradient(0, 0, band, 0);
6299
+ fillStops(g);
6300
+ rectW = band;
6301
+ } else {
6302
+ const band = Math.max(1, Math.round(mask.width * size));
6303
+ x = mask.width - band;
6304
+ g = mctx.createLinearGradient(mask.width, 0, x, 0);
6305
+ fillStops(g);
6306
+ rectW = band;
6307
+ }
6308
+ mctx.fillStyle = g;
6309
+ mctx.fillRect(x, y, rectW, rectH);
6310
+ {
6311
+ const edgePx = Math.min(seamGuardPx, side === "top" || side === "bottom" ? rectH : rectW);
6312
+ mctx.fillStyle = "rgba(0,0,0,0)";
6313
+ mctx.globalCompositeOperation = "copy";
6314
+ if (side === "top") mctx.fillRect(0, 0, mask.width, edgePx);
6315
+ if (side === "bottom") mctx.fillRect(0, mask.height - edgePx, mask.width, edgePx);
6316
+ if (side === "left") mctx.fillRect(0, 0, edgePx, mask.height);
6317
+ if (side === "right") mctx.fillRect(mask.width - edgePx, 0, edgePx, mask.height);
6318
+ mctx.globalCompositeOperation = "source-over";
6319
+ }
6320
+ ctx.globalCompositeOperation = "destination-in";
6321
+ ctx.drawImage(mask, 0, 0);
6322
+ ctx.globalCompositeOperation = "source-over";
6323
+ }
6324
+ return canvas;
6325
+ }
6196
6326
  function angleToCoords(angleDeg) {
6197
6327
  const rad = angleDeg * Math.PI / 180;
6198
6328
  const x1 = 0.5 - Math.sin(rad) * 0.5;
@@ -7702,6 +7832,10 @@ const PageCanvas = react.forwardRef(
7702
7832
  fabricCanvas.on("mouse:dblclick", (e) => {
7703
7833
  if (!isActiveRef.current || !allowEditing) return;
7704
7834
  let target = e.target;
7835
+ if (!target) {
7836
+ const active = fabricCanvas.getActiveObject();
7837
+ if (active) target = active;
7838
+ }
7705
7839
  if (target && target instanceof fabric__namespace.Group && target.__cropGroup) {
7706
7840
  const ct = target.__cropData;
7707
7841
  if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
@@ -7857,7 +7991,7 @@ const PageCanvas = react.forwardRef(
7857
7991
  visibilityUpdateInProgressRef.current = false;
7858
7992
  }
7859
7993
  doSyncRef.current = () => {
7860
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
7994
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
7861
7995
  const shouldSkipUpdates2 = syncLockedRef.current || editLockRef.current;
7862
7996
  const state = useEditorStore.getState();
7863
7997
  const elementsToSync = elements;
@@ -8018,15 +8152,22 @@ const PageCanvas = react.forwardRef(
8018
8152
  const storedImageUrl = existingObj.__imageSrc;
8019
8153
  const currentUrlNormalized = currentImageUrl.trim();
8020
8154
  const storedUrlNormalized = storedImageUrl ? String(storedImageUrl).trim() : "";
8021
- const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8022
- const storedColorMapStr = existingObj.__svgColorMap || "";
8023
- const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8024
- const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8025
- const needsReload = sourceUrlChanged || colorMapChanged;
8026
8155
  const hasUrl = currentUrlNormalized !== "";
8027
8156
  const isCropGroup2 = existingObj instanceof fabric__namespace.Group && existingObj.__cropGroup;
8028
8157
  const isPlaceholderGroup = isEmptyImagePlaceholderGroup(existingObj);
8029
8158
  const isPlaceholder = isPlaceholderGroup || !(existingObj instanceof fabric__namespace.FabricImage) || existingObj instanceof fabric__namespace.Group && !isCropGroup2;
8159
+ const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8160
+ const storedColorMapStr = existingObj.__svgColorMap || "";
8161
+ const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8162
+ const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8163
+ const newFadeKey = edgeFadeKey(element);
8164
+ const oldFadeKey = isCropGroup2 ? existingObj.__edgeFadeInputKey || "" : existingObj.__edgeFadeKey || "";
8165
+ const innerImg = (_e = existingObj == null ? void 0 : existingObj.__cropData) == null ? void 0 : _e._img;
8166
+ const innerOldKey = isCropGroup2 ? oldFadeKey : innerImg ? innerImg.__edgeFadeKey || "" : oldFadeKey;
8167
+ const cropFadeRendererMissing = isCropGroup2 && Boolean(newFadeKey) && !existingObj.__edgeFadeRenderConfig;
8168
+ const fadeKeyChanged = newFadeKey !== oldFadeKey || newFadeKey !== innerOldKey || cropFadeRendererMissing;
8169
+ const needsReload = sourceUrlChanged || colorMapChanged || fadeKeyChanged && !isCropGroup2;
8170
+ const needsCropGroupFadeUpdate = isCropGroup2 && fadeKeyChanged;
8030
8171
  const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
8031
8172
  if (!hasUrl && hadUrlBefore) {
8032
8173
  const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
@@ -8042,16 +8183,16 @@ const PageCanvas = react.forwardRef(
8042
8183
  fc.requestRenderAll();
8043
8184
  continue;
8044
8185
  }
8045
- const imageFitForReplace = element.imageFit || ((_e = element.style) == null ? void 0 : _e.imageFit) || "cover";
8046
- const clipShapeForReplace = element.clipShape ?? ((_f = element.style) == null ? void 0 : _f.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8186
+ const imageFitForReplace = element.imageFit || ((_f = element.style) == null ? void 0 : _f.imageFit) || "cover";
8187
+ const clipShapeForReplace = element.clipShape ?? ((_g = element.style) == null ? void 0 : _g.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8047
8188
  const needCropGroupForElement = imageFitForReplace !== "fill" || clipShapeForReplace && clipShapeForReplace !== "none";
8048
8189
  const plainImageNeedsCropGroup = hasUrl && !isCropGroup2 && existingObj instanceof fabric__namespace.FabricImage && needCropGroupForElement;
8049
- if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup)) {
8190
+ if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup || needsCropGroupFadeUpdate)) {
8050
8191
  if (needsReload && !isBeingTransformed && (!wasJustModified || sourceUrlChanged)) {
8051
8192
  loadImageAsync(element, existingObj, fc);
8052
8193
  } else if (plainImageNeedsCropGroup) {
8053
8194
  loadImageAsync(element, existingObj, fc);
8054
- } else if (!needsReload && isCropGroup2) {
8195
+ } else if ((!needsReload || needsCropGroupFadeUpdate) && isCropGroup2) {
8055
8196
  const ct = existingObj.__cropData;
8056
8197
  if (ct) {
8057
8198
  const resolvedCrop = pageTree.length > 0 ? getNodeBounds(element, pageTree) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
@@ -8148,6 +8289,7 @@ const PageCanvas = react.forwardRef(
8148
8289
  }
8149
8290
  }
8150
8291
  updateCoverLayout(existingObj);
8292
+ applyEdgeFadeFrameClipPath(existingObj, element, ct.frameW, ct.frameH, ct.shape || "rect", ct.rx || 0);
8151
8293
  if (allowEditing) {
8152
8294
  installCanvaMaskControls(existingObj);
8153
8295
  } else {
@@ -8457,7 +8599,7 @@ const PageCanvas = react.forwardRef(
8457
8599
  fc.add(placeholder);
8458
8600
  fc.bringObjectToFront(placeholder);
8459
8601
  const activeObj = fc.getActiveObject();
8460
- if (activeObj && (((_g = activeObj._ct) == null ? void 0 : _g.isCropGroup) || activeObj.__cropGroup)) {
8602
+ if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8461
8603
  fc.setActiveObject(activeObj);
8462
8604
  }
8463
8605
  placeholder.dirty = true;
@@ -8497,7 +8639,7 @@ const PageCanvas = react.forwardRef(
8497
8639
  fc.add(obj);
8498
8640
  fc.bringObjectToFront(obj);
8499
8641
  const activeObj = fc.getActiveObject();
8500
- if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8642
+ if (activeObj && (((_i = activeObj._ct) == null ? void 0 : _i.isCropGroup) || activeObj.__cropGroup)) {
8501
8643
  fc.setActiveObject(activeObj);
8502
8644
  }
8503
8645
  obj.dirty = true;
@@ -8531,7 +8673,7 @@ const PageCanvas = react.forwardRef(
8531
8673
  isRebuildingRef.current = false;
8532
8674
  fc.requestRenderAll();
8533
8675
  if (activeBeforeSync && fc.getObjects().includes(activeBeforeSync)) {
8534
- const isCropGroup2 = ((_i = activeBeforeSync._ct) == null ? void 0 : _i.isCropGroup) || activeBeforeSync.__cropGroup;
8676
+ const isCropGroup2 = ((_j = activeBeforeSync._ct) == null ? void 0 : _j.isCropGroup) || activeBeforeSync.__cropGroup;
8535
8677
  if (isCropGroup2) {
8536
8678
  fc.setActiveObject(activeBeforeSync);
8537
8679
  fc.requestRenderAll();
@@ -8953,6 +9095,7 @@ const PageCanvas = react.forwardRef(
8953
9095
  }
8954
9096
  }
8955
9097
  updateCoverLayout(obj);
9098
+ applyEdgeFadeFrameClipPath(obj, element, elementWidth, elementHeight, ct.shape || "rect", ct.rx || 0);
8956
9099
  obj.setCoords();
8957
9100
  obj.dirty = true;
8958
9101
  if (obj.clipPath) {
@@ -9536,8 +9679,137 @@ const PageCanvas = react.forwardRef(
9536
9679
  obj.__lastStrokeGradientJson = "null";
9537
9680
  }
9538
9681
  };
9682
+ const clamp2 = (v, min, max) => {
9683
+ return Math.max(min, Math.min(max, v));
9684
+ };
9685
+ const applyEdgeFadeFrameClipPath = (group, element, frameW, frameH, shape, rxRatio) => {
9686
+ var _a, _b, _c;
9687
+ const fadeElement = element;
9688
+ const fadeGroup = group;
9689
+ const inputKey = edgeFadeKey(fadeElement);
9690
+ const hasFade = hasEdgeFade(fadeElement);
9691
+ if (!fadeGroup.__edgeFadeOriginalDrawObject) {
9692
+ fadeGroup.__edgeFadeOriginalDrawObject = group.drawObject;
9693
+ }
9694
+ if (hasFade) {
9695
+ if ((_a = group.clipPath) == null ? void 0 : _a.__edgeFadeMask) {
9696
+ group.clipPath = void 0;
9697
+ updateCoverLayout(group);
9698
+ }
9699
+ fadeGroup.__edgeFadeRenderConfig = {
9700
+ key: `${inputKey}|${Math.round(frameW)}|${Math.round(frameH)}|${shape}|${rxRatio}`,
9701
+ frameW: Math.max(1, Number(frameW) || 1),
9702
+ frameH: Math.max(1, Number(frameH) || 1),
9703
+ topSize: clamp2(Number(fadeElement.fadeTopSize) || 0, 0, 1),
9704
+ topAmount: clamp2(fadeElement.fadeTopAmount == null ? 1 : Number(fadeElement.fadeTopAmount), 0, 1),
9705
+ rightSize: clamp2(Number(fadeElement.fadeRightSize) || 0, 0, 1),
9706
+ rightAmount: clamp2(fadeElement.fadeRightAmount == null ? 1 : Number(fadeElement.fadeRightAmount), 0, 1),
9707
+ bottomSize: clamp2(Number(fadeElement.fadeBottomSize) || 0, 0, 1),
9708
+ bottomAmount: clamp2(fadeElement.fadeBottomAmount == null ? 1 : Number(fadeElement.fadeBottomAmount), 0, 1),
9709
+ leftSize: clamp2(Number(fadeElement.fadeLeftSize) || 0, 0, 1),
9710
+ leftAmount: clamp2(fadeElement.fadeLeftAmount == null ? 1 : Number(fadeElement.fadeLeftAmount), 0, 1),
9711
+ topHardness: clamp2(Number(fadeElement.fadeTopHardness) || 1, 0.1, 5),
9712
+ rightHardness: clamp2(Number(fadeElement.fadeRightHardness) || 1, 0.1, 5),
9713
+ bottomHardness: clamp2(Number(fadeElement.fadeBottomHardness) || 1, 0.1, 5),
9714
+ leftHardness: clamp2(Number(fadeElement.fadeLeftHardness) || 1, 0.1, 5)
9715
+ };
9716
+ const originalDrawObject = fadeGroup.__edgeFadeOriginalDrawObject ?? group.drawObject;
9717
+ group.drawObject = function edgeFadeDrawObject(ctx, forClipping, context) {
9718
+ originalDrawObject.call(this, ctx, forClipping, context);
9719
+ if (forClipping) return;
9720
+ const cfg = this.__edgeFadeRenderConfig;
9721
+ if (!cfg) return;
9722
+ const liveW = Number(this.width) || 0;
9723
+ const liveH = Number(this.height) || 0;
9724
+ const w = liveW > 0 ? liveW : cfg.frameW;
9725
+ const h = liveH > 0 ? liveH : cfg.frameH;
9726
+ const x = -w / 2;
9727
+ const y = -h / 2;
9728
+ const paintBand = (side, size, amount, hardness) => {
9729
+ if (size <= 0 || amount >= 1) return;
9730
+ ctx.save();
9731
+ ctx.globalCompositeOperation = "destination-out";
9732
+ let gradient;
9733
+ const eraseAtEdge = 1 - amount;
9734
+ const STOPS = 64;
9735
+ const gamma = 1 / hardness;
9736
+ const seamGuardPx = 2;
9737
+ const addStops = (g) => {
9738
+ for (let i = 0; i <= STOPS; i++) {
9739
+ const t = i / STOPS;
9740
+ const keepProgress = Math.pow(t, gamma);
9741
+ const a = eraseAtEdge * (1 - keepProgress);
9742
+ g.addColorStop(t, `rgba(0,0,0,${a})`);
9743
+ }
9744
+ };
9745
+ if (side === "top") {
9746
+ const band = Math.max(1, h * size);
9747
+ gradient = ctx.createLinearGradient(0, y, 0, y + band);
9748
+ addStops(gradient);
9749
+ ctx.fillStyle = gradient;
9750
+ ctx.fillRect(x, y, w, band);
9751
+ ctx.fillStyle = "rgba(0,0,0,1)";
9752
+ ctx.fillRect(x, y, w, Math.min(seamGuardPx, band));
9753
+ } else if (side === "bottom") {
9754
+ const band = Math.max(1, h * size);
9755
+ gradient = ctx.createLinearGradient(0, y + h, 0, y + h - band);
9756
+ addStops(gradient);
9757
+ ctx.fillStyle = gradient;
9758
+ ctx.fillRect(x, y + h - band, w, band);
9759
+ ctx.fillStyle = "rgba(0,0,0,1)";
9760
+ ctx.fillRect(x, y + h - Math.min(seamGuardPx, band), w, Math.min(seamGuardPx, band));
9761
+ } else if (side === "left") {
9762
+ const band = Math.max(1, w * size);
9763
+ gradient = ctx.createLinearGradient(x, 0, x + band, 0);
9764
+ addStops(gradient);
9765
+ ctx.fillStyle = gradient;
9766
+ ctx.fillRect(x, y, band, h);
9767
+ ctx.fillStyle = "rgba(0,0,0,1)";
9768
+ ctx.fillRect(x, y, Math.min(seamGuardPx, band), h);
9769
+ } else {
9770
+ const band = Math.max(1, w * size);
9771
+ gradient = ctx.createLinearGradient(x + w, 0, x + w - band, 0);
9772
+ addStops(gradient);
9773
+ ctx.fillStyle = gradient;
9774
+ ctx.fillRect(x + w - band, y, band, h);
9775
+ ctx.fillStyle = "rgba(0,0,0,1)";
9776
+ ctx.fillRect(x + w - Math.min(seamGuardPx, band), y, Math.min(seamGuardPx, band), h);
9777
+ }
9778
+ ctx.restore();
9779
+ };
9780
+ paintBand("top", cfg.topSize, cfg.topAmount, cfg.topHardness);
9781
+ paintBand("right", cfg.rightSize, cfg.rightAmount, cfg.rightHardness);
9782
+ paintBand("bottom", cfg.bottomSize, cfg.bottomAmount, cfg.bottomHardness);
9783
+ paintBand("left", cfg.leftSize, cfg.leftAmount, cfg.leftHardness);
9784
+ };
9785
+ group.set({ objectCaching: true, noScaleCache: false });
9786
+ fadeGroup.__edgeFadeKey = fadeGroup.__edgeFadeRenderConfig.key;
9787
+ fadeGroup.__edgeFadeInputKey = inputKey;
9788
+ } else {
9789
+ if (fadeGroup.__edgeFadeOriginalDrawObject) {
9790
+ group.drawObject = fadeGroup.__edgeFadeOriginalDrawObject;
9791
+ }
9792
+ delete fadeGroup.__edgeFadeRenderConfig;
9793
+ delete fadeGroup.__edgeFadeKey;
9794
+ delete fadeGroup.__edgeFadeInputKey;
9795
+ if ((_b = group.clipPath) == null ? void 0 : _b.__edgeFadeMask) {
9796
+ group.clipPath = void 0;
9797
+ updateCoverLayout(group);
9798
+ }
9799
+ }
9800
+ if (group.clipPath) {
9801
+ group.clipPath.dirty = true;
9802
+ group.clipPath.setCoords();
9803
+ }
9804
+ fadeGroup.dirty = true;
9805
+ const children = group.getObjects();
9806
+ children.forEach((child) => {
9807
+ child.dirty = true;
9808
+ });
9809
+ (_c = group.canvas) == null ? void 0 : _c.requestRenderAll();
9810
+ };
9539
9811
  const loadImageAsync = async (element, placeholder, fc) => {
9540
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
9812
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
9541
9813
  const imageUrl = element.src || element.imageUrl;
9542
9814
  if (!imageUrl) return;
9543
9815
  const elementId = element.id;
@@ -9550,7 +9822,9 @@ const PageCanvas = react.forwardRef(
9550
9822
  const existingImg = ct == null ? void 0 : ct._img;
9551
9823
  const existingSrc = placeholder.__imageSrc;
9552
9824
  const existingSvgColorMap = placeholder.__svgColorMap || "";
9553
- if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap) {
9825
+ const existingFadeKey = existingImg && existingImg.__edgeFadeKey || placeholder.__edgeFadeKey || "";
9826
+ const nextFadeKey = edgeFadeKey(element);
9827
+ if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap && existingFadeKey === nextFadeKey) {
9554
9828
  return placeholder;
9555
9829
  }
9556
9830
  }
@@ -9567,6 +9841,22 @@ const PageCanvas = react.forwardRef(
9567
9841
  if (!fabricRef.current || !isLatestRequest()) return;
9568
9842
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
9569
9843
  if (!isLatestRequest()) return;
9844
+ const imageFitForFade = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9845
+ const clipShapeForFade = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9846
+ const willUseCropGroupForFade = imageFitForFade !== "fill" || clipShapeForFade && clipShapeForFade !== "none";
9847
+ try {
9848
+ if (hasEdgeFade(element) && !willUseCropGroupForFade) {
9849
+ const srcEl = (_c = img.getElement) == null ? void 0 : _c.call(img);
9850
+ if (srcEl) {
9851
+ const baked = bakeEdgeFade(srcEl, element);
9852
+ img.setElement(baked);
9853
+ img.__edgeFadeKey = edgeFadeKey(element);
9854
+ img.dirty = true;
9855
+ }
9856
+ }
9857
+ } catch (e) {
9858
+ console.warn("[edgeFade] bake failed:", e);
9859
+ }
9570
9860
  const isHidden = !element.visible;
9571
9861
  img.set({
9572
9862
  originX: "left",
@@ -9599,8 +9889,8 @@ const PageCanvas = react.forwardRef(
9599
9889
  });
9600
9890
  img.setCoords();
9601
9891
  } else {
9602
- const imageFit = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9603
- const clipShape = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9892
+ const imageFit = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9893
+ const clipShape = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9604
9894
  const needCropGroup2 = imageFit !== "fill" || clipShape && clipShape !== "none";
9605
9895
  const imgNaturalWidth = img.width || 1;
9606
9896
  const imgNaturalHeight = img.height || 1;
@@ -9627,7 +9917,7 @@ const PageCanvas = react.forwardRef(
9627
9917
  if (imageFit === "fill" && !needCropGroup2) {
9628
9918
  const finalScaleX = baseScaleX * (element.scaleX ?? 1);
9629
9919
  const finalScaleY = baseScaleY * (element.scaleY ?? 1);
9630
- const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_c = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _c.children) ?? [];
9920
+ const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9631
9921
  const createPos = pageTreeForCreate.length > 0 ? (() => {
9632
9922
  const node = findNodeById(pageTreeForCreate, element.id);
9633
9923
  return node ? getAbsoluteBounds(node, pageTreeForCreate) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9672,12 +9962,12 @@ const PageCanvas = react.forwardRef(
9672
9962
  }
9673
9963
  img.__imageSrc = imageUrl;
9674
9964
  img.__svgColorMap = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
9675
- const imageFitFinal = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9676
- const clipShapeFinal = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9965
+ const imageFitFinal = element.imageFit || ((_g = element.style) == null ? void 0 : _g.imageFit) || "cover";
9966
+ const clipShapeFinal = element.clipShape ?? ((_h = element.style) == null ? void 0 : _h.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9677
9967
  const needCropGroup = imageFitFinal !== "fill" || clipShapeFinal && clipShapeFinal !== "none";
9678
9968
  let finalObject = img;
9679
9969
  if (needCropGroup) {
9680
- const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9970
+ const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_i = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _i.children) ?? [];
9681
9971
  const nodeForSize = pageTreeForCropResolve.length ? findNodeById(pageTreeForCropResolve, element.id) : null;
9682
9972
  const w = nodeForSize && isElement(nodeForSize) ? nodeForSize.width : element.width;
9683
9973
  const h = nodeForSize && isElement(nodeForSize) ? nodeForSize.height : element.height;
@@ -9709,16 +9999,16 @@ const PageCanvas = react.forwardRef(
9709
9999
  let panY = element.cropPanY ?? 0.5;
9710
10000
  let zoom2 = element.cropZoom ?? 1;
9711
10001
  if (existingCropGroup) {
9712
- const existingImg = (_g = existingCropGroup.__cropData) == null ? void 0 : _g._img;
10002
+ const existingImg = (_j = existingCropGroup.__cropData) == null ? void 0 : _j._img;
9713
10003
  if (existingImg) {
9714
- panX = ((_h = existingImg._ct) == null ? void 0 : _h.panX) ?? existingImg.__panX ?? panX;
9715
- panY = ((_i = existingImg._ct) == null ? void 0 : _i.panY) ?? existingImg.__panY ?? panY;
9716
- zoom2 = ((_j = existingImg._ct) == null ? void 0 : _j.zoom) ?? zoom2;
10004
+ panX = ((_k = existingImg._ct) == null ? void 0 : _k.panX) ?? existingImg.__panX ?? panX;
10005
+ panY = ((_l = existingImg._ct) == null ? void 0 : _l.panY) ?? existingImg.__panY ?? panY;
10006
+ zoom2 = ((_m = existingImg._ct) == null ? void 0 : _m.zoom) ?? zoom2;
9717
10007
  }
9718
10008
  }
9719
10009
  const isDynamicField = dynamicFieldIds.includes(element.id);
9720
10010
  const canBeEvented = isEditorMode || isPreviewMode && isDynamicField;
9721
- const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_k = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _k.children) ?? [];
10011
+ const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_n = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _n.children) ?? [];
9722
10012
  const createPosForCrop = pageTreeForCrop.length > 0 ? (() => {
9723
10013
  const node = findNodeById(pageTreeForCrop, element.id);
9724
10014
  return node ? getAbsoluteBounds(node, pageTreeForCrop) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9769,17 +10059,18 @@ const PageCanvas = react.forwardRef(
9769
10059
  } else {
9770
10060
  installCanvaMaskControls(cropGroup);
9771
10061
  }
9772
- const cropImg = (_l = cropGroup.__cropData) == null ? void 0 : _l._img;
10062
+ const cropImg = (_o = cropGroup.__cropData) == null ? void 0 : _o._img;
9773
10063
  if (cropImg) {
9774
10064
  cropImg._ct = { panX, panY, zoom: zoom2 };
9775
10065
  updateCoverLayout(cropGroup);
9776
10066
  }
10067
+ applyEdgeFadeFrameClipPath(cropGroup, element, frameW, frameH, shape, rxRatio);
9777
10068
  setObjectData(cropGroup, element.id);
9778
10069
  cropGroup.__imageElement = cropImg;
9779
10070
  cropGroup.__imageSrc = imageUrl;
9780
10071
  cropGroup.__svgColorMap = nextSvgColorMap;
9781
10072
  if (cropImg && element.imageNaturalWidth == null) {
9782
- const el = ((_m = cropImg.getElement) == null ? void 0 : _m.call(cropImg)) ?? cropImg._element;
10073
+ const el = ((_p = cropImg.getElement) == null ? void 0 : _p.call(cropImg)) ?? cropImg._element;
9783
10074
  const orig = typeof cropImg.getOriginalSize === "function" ? cropImg.getOriginalSize() : null;
9784
10075
  const nw = (orig == null ? void 0 : orig.width) ?? (el == null ? void 0 : el.naturalWidth) ?? cropImg.width;
9785
10076
  const nh = (orig == null ? void 0 : orig.height) ?? (el == null ? void 0 : el.naturalHeight) ?? cropImg.height;
@@ -13152,7 +13443,7 @@ function PixldocsPreview(props) {
13152
13443
  !canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
13153
13444
  ] });
13154
13445
  }
13155
- const PACKAGE_VERSION = "0.5.101";
13446
+ const PACKAGE_VERSION = "0.5.103";
13156
13447
  const roundParityValue = (value) => {
13157
13448
  if (typeof value !== "number") return value;
13158
13449
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -13940,12 +14231,69 @@ class PixldocsRenderer {
13940
14231
  { width: canvasWidth, height: canvasHeight },
13941
14232
  { cssOnly: false, backstoreOnly: false }
13942
14233
  );
14234
+ const fadeBakeRecords = [];
14235
+ try {
14236
+ const objs = fabricInstance.getObjects().slice();
14237
+ for (const obj of objs) {
14238
+ const isFadedCropGroup = obj instanceof fabric__namespace.Group && (Boolean(obj.__edgeFadeRenderConfig) || Boolean(obj.__edgeFadeKey) || Boolean(obj.__edgeFadeInputKey));
14239
+ if (!isFadedCropGroup) continue;
14240
+ try {
14241
+ const baked = obj.toCanvasElement({
14242
+ multiplier: 2,
14243
+ enableRetinaScaling: false
14244
+ });
14245
+ const rect = obj.getBoundingRect();
14246
+ const replacement = new fabric__namespace.FabricImage(baked, {
14247
+ left: rect.left,
14248
+ top: rect.top,
14249
+ originX: "left",
14250
+ originY: "top",
14251
+ scaleX: rect.width / baked.width,
14252
+ scaleY: rect.height / baked.height,
14253
+ selectable: false,
14254
+ evented: false,
14255
+ objectCaching: false
14256
+ });
14257
+ const insertIndex = fabricInstance._objects.indexOf(obj);
14258
+ const prevExclude = obj.excludeFromExport;
14259
+ obj.excludeFromExport = true;
14260
+ if (insertIndex >= 0) {
14261
+ fabricInstance.insertAt(insertIndex + 1, replacement);
14262
+ } else {
14263
+ fabricInstance.add(replacement);
14264
+ }
14265
+ fadeBakeRecords.push({
14266
+ original: obj,
14267
+ replacement,
14268
+ prevExclude,
14269
+ insertIndex
14270
+ });
14271
+ } catch (bakeErr) {
14272
+ console.warn("[canvas-renderer][edgeFade] bake-for-svg failed:", bakeErr);
14273
+ }
14274
+ }
14275
+ if (fadeBakeRecords.length) {
14276
+ fabricInstance.renderAll();
14277
+ console.log(
14278
+ `[canvas-renderer][edgeFade] baked ${fadeBakeRecords.length} faded object(s) for SVG capture`
14279
+ );
14280
+ }
14281
+ } catch (e) {
14282
+ console.warn("[canvas-renderer][edgeFade] bake pass failed:", e);
14283
+ }
13943
14284
  const rawSvgString = fabricInstance.toSVG();
13944
14285
  const svgString = this.normalizeSvgDimensions(
13945
14286
  rawSvgString,
13946
14287
  canvasWidth,
13947
14288
  canvasHeight
13948
14289
  );
14290
+ for (const rec of fadeBakeRecords) {
14291
+ try {
14292
+ fabricInstance.remove(rec.replacement);
14293
+ rec.original.excludeFromExport = rec.prevExclude;
14294
+ } catch {
14295
+ }
14296
+ }
13949
14297
  fabricInstance.enableRetinaScaling = prevRetina;
13950
14298
  fabricInstance.setDimensions(
13951
14299
  { width: prevWidth, height: prevHeight },