@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.d.ts CHANGED
@@ -256,7 +256,7 @@ export declare function normalizeFontFamily(fontStack: string): string;
256
256
  * Package version banner. Bump alongside package.json so we can confirm
257
257
  * (via browser:log) that the deployed bundle matches the expected build.
258
258
  */
259
- export declare const PACKAGE_VERSION = "0.5.101";
259
+ export declare const PACKAGE_VERSION = "0.5.103";
260
260
 
261
261
  export declare interface PageSettings {
262
262
  backgroundColor?: string;
package/dist/index.js CHANGED
@@ -3663,28 +3663,33 @@ function updateCoverLayout(g) {
3663
3663
  const hasCustomSvgMask = Boolean(
3664
3664
  currentClipPath && (isSvgMaskClipPath(currentClipPath) || isLuminanceMaskClipPath(currentClipPath) || currentClipPath.__svgMask)
3665
3665
  );
3666
- if (hasCustomSvgMask) {
3666
+ const hasEdgeFadeMask = Boolean(currentClipPath && currentClipPath.__edgeFadeMask);
3667
+ if (hasCustomSvgMask || hasEdgeFadeMask) {
3667
3668
  if (isSvgMaskClipPath(currentClipPath)) {
3668
3669
  syncSvgMaskClipPath(g);
3669
3670
  } else if (currentClipPath && typeof currentClipPath.set === "function") {
3670
- const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3671
- const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3672
- currentClipPath.set({
3673
- left: 0,
3674
- top: 0,
3675
- originX: "center",
3676
- originY: "center",
3677
- scaleX: frameW / baseW,
3678
- scaleY: frameH / baseH,
3679
- selectable: false,
3680
- evented: false,
3681
- hasControls: false,
3682
- hasBorders: false
3683
- });
3684
- currentClipPath.absolutePositioned = false;
3685
- currentClipPath.excludeFromExport = true;
3686
- currentClipPath.dirty = true;
3687
- currentClipPath.setCoords();
3671
+ if (hasEdgeFadeMask) {
3672
+ currentClipPath.dirty = true;
3673
+ } else {
3674
+ const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3675
+ const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3676
+ currentClipPath.set({
3677
+ left: 0,
3678
+ top: 0,
3679
+ originX: "center",
3680
+ originY: "center",
3681
+ scaleX: frameW / baseW,
3682
+ scaleY: frameH / baseH,
3683
+ selectable: false,
3684
+ evented: false,
3685
+ hasControls: false,
3686
+ hasBorders: false
3687
+ });
3688
+ currentClipPath.absolutePositioned = false;
3689
+ currentClipPath.excludeFromExport = true;
3690
+ currentClipPath.dirty = true;
3691
+ currentClipPath.setCoords();
3692
+ }
3688
3693
  }
3689
3694
  } else {
3690
3695
  const needsNewClipPath = !g.clipPath || shape === "circle" && !(g.clipPath instanceof fabric.Ellipse) || shape !== "circle" && !(g.clipPath instanceof fabric.Rect);
@@ -4641,10 +4646,12 @@ function exitCropMode(g, commit = true) {
4641
4646
  g.hasControls = true;
4642
4647
  g.borderDashArray = void 0;
4643
4648
  installCanvaMaskControls(g);
4644
- g.borderColor = void 0;
4645
- g.cornerColor = void 0;
4646
- g.cornerStrokeColor = void 0;
4649
+ g.borderColor = "#4f46e5";
4650
+ g.borderDashArray = void 0;
4651
+ g.cornerColor = "#ffffff";
4652
+ g.cornerStrokeColor = "#4f46e5";
4647
4653
  g.cornerStyle = "rect";
4654
+ g.transparentCorners = false;
4648
4655
  g[CROP_MODE_FLAG] = false;
4649
4656
  g.__cropZoomLastPointer = void 0;
4650
4657
  g.__lastPointerForCrop = void 0;
@@ -6174,6 +6181,129 @@ function renderSmartElementToDataUri(type, props, width, height) {
6174
6181
  if (!svg) return null;
6175
6182
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
6176
6183
  }
6184
+ function hasEdgeFade(p) {
6185
+ if (!p) return false;
6186
+ const sides = [
6187
+ [p.fadeTopSize, p.fadeTopAmount],
6188
+ [p.fadeRightSize, p.fadeRightAmount],
6189
+ [p.fadeBottomSize, p.fadeBottomAmount],
6190
+ [p.fadeLeftSize, p.fadeLeftAmount]
6191
+ ];
6192
+ for (const [size, amount] of sides) {
6193
+ const s = Number(size) || 0;
6194
+ const a = amount == null ? 1 : Number(amount);
6195
+ if (s > 0 && a < 1) return true;
6196
+ }
6197
+ return false;
6198
+ }
6199
+ function edgeFadeKey(p) {
6200
+ if (!hasEdgeFade(p)) return "";
6201
+ return [
6202
+ (p == null ? void 0 : p.fadeTopSize) ?? 0,
6203
+ (p == null ? void 0 : p.fadeTopAmount) ?? 1,
6204
+ (p == null ? void 0 : p.fadeRightSize) ?? 0,
6205
+ (p == null ? void 0 : p.fadeRightAmount) ?? 1,
6206
+ (p == null ? void 0 : p.fadeBottomSize) ?? 0,
6207
+ (p == null ? void 0 : p.fadeBottomAmount) ?? 1,
6208
+ (p == null ? void 0 : p.fadeLeftSize) ?? 0,
6209
+ (p == null ? void 0 : p.fadeLeftAmount) ?? 1,
6210
+ (p == null ? void 0 : p.fadeTopHardness) ?? 1,
6211
+ (p == null ? void 0 : p.fadeRightHardness) ?? 1,
6212
+ (p == null ? void 0 : p.fadeBottomHardness) ?? 1,
6213
+ (p == null ? void 0 : p.fadeLeftHardness) ?? 1
6214
+ ].join("|");
6215
+ }
6216
+ function clamp01(n) {
6217
+ if (!Number.isFinite(n)) return 0;
6218
+ return Math.max(0, Math.min(1, n));
6219
+ }
6220
+ function bakeEdgeFade(source, fade) {
6221
+ const w = source.naturalWidth || source.width || 0;
6222
+ const h = source.naturalHeight || source.height || 0;
6223
+ const canvas = document.createElement("canvas");
6224
+ canvas.width = Math.max(1, w);
6225
+ canvas.height = Math.max(1, h);
6226
+ const ctx = canvas.getContext("2d");
6227
+ if (!ctx) return canvas;
6228
+ ctx.drawImage(source, 0, 0, canvas.width, canvas.height);
6229
+ const clampHardness = (n) => {
6230
+ const v = Number(n);
6231
+ if (!Number.isFinite(v) || v <= 0) return 1;
6232
+ return Math.max(0.1, Math.min(5, v));
6233
+ };
6234
+ const sides = [
6235
+ { side: "top", size: clamp01(fade.fadeTopSize ?? 0), amount: clamp01(fade.fadeTopAmount ?? 1), hardness: clampHardness(fade.fadeTopHardness) },
6236
+ { side: "right", size: clamp01(fade.fadeRightSize ?? 0), amount: clamp01(fade.fadeRightAmount ?? 1), hardness: clampHardness(fade.fadeRightHardness) },
6237
+ { side: "bottom", size: clamp01(fade.fadeBottomSize ?? 0), amount: clamp01(fade.fadeBottomAmount ?? 1), hardness: clampHardness(fade.fadeBottomHardness) },
6238
+ { side: "left", size: clamp01(fade.fadeLeftSize ?? 0), amount: clamp01(fade.fadeLeftAmount ?? 1), hardness: clampHardness(fade.fadeLeftHardness) }
6239
+ ];
6240
+ for (const { side, size, amount, hardness } of sides) {
6241
+ if (size <= 0 || amount >= 1) continue;
6242
+ const mask = document.createElement("canvas");
6243
+ mask.width = canvas.width;
6244
+ mask.height = canvas.height;
6245
+ const mctx = mask.getContext("2d");
6246
+ if (!mctx) continue;
6247
+ mctx.fillStyle = "#000";
6248
+ mctx.fillRect(0, 0, mask.width, mask.height);
6249
+ let g;
6250
+ let x = 0, y = 0, rectW = mask.width, rectH = mask.height;
6251
+ const STOPS = 64;
6252
+ const gamma = 1 / hardness;
6253
+ const seamGuardPx = 2;
6254
+ const innerAlpha = (t) => {
6255
+ const keepProgress = Math.pow(t, gamma);
6256
+ const erase = (1 - amount) * (1 - keepProgress);
6257
+ return 1 - erase;
6258
+ };
6259
+ const fillStops = (grad, reverse) => {
6260
+ for (let i = 0; i <= STOPS; i++) {
6261
+ const t = i / STOPS;
6262
+ const a = innerAlpha(t);
6263
+ grad.addColorStop(t, `rgba(0,0,0,${a})`);
6264
+ }
6265
+ };
6266
+ if (side === "top") {
6267
+ const band = Math.max(1, Math.round(mask.height * size));
6268
+ g = mctx.createLinearGradient(0, 0, 0, band);
6269
+ fillStops(g);
6270
+ rectH = band;
6271
+ } else if (side === "bottom") {
6272
+ const band = Math.max(1, Math.round(mask.height * size));
6273
+ y = mask.height - band;
6274
+ g = mctx.createLinearGradient(0, mask.height, 0, y);
6275
+ fillStops(g);
6276
+ rectH = band;
6277
+ } else if (side === "left") {
6278
+ const band = Math.max(1, Math.round(mask.width * size));
6279
+ g = mctx.createLinearGradient(0, 0, band, 0);
6280
+ fillStops(g);
6281
+ rectW = band;
6282
+ } else {
6283
+ const band = Math.max(1, Math.round(mask.width * size));
6284
+ x = mask.width - band;
6285
+ g = mctx.createLinearGradient(mask.width, 0, x, 0);
6286
+ fillStops(g);
6287
+ rectW = band;
6288
+ }
6289
+ mctx.fillStyle = g;
6290
+ mctx.fillRect(x, y, rectW, rectH);
6291
+ {
6292
+ const edgePx = Math.min(seamGuardPx, side === "top" || side === "bottom" ? rectH : rectW);
6293
+ mctx.fillStyle = "rgba(0,0,0,0)";
6294
+ mctx.globalCompositeOperation = "copy";
6295
+ if (side === "top") mctx.fillRect(0, 0, mask.width, edgePx);
6296
+ if (side === "bottom") mctx.fillRect(0, mask.height - edgePx, mask.width, edgePx);
6297
+ if (side === "left") mctx.fillRect(0, 0, edgePx, mask.height);
6298
+ if (side === "right") mctx.fillRect(mask.width - edgePx, 0, edgePx, mask.height);
6299
+ mctx.globalCompositeOperation = "source-over";
6300
+ }
6301
+ ctx.globalCompositeOperation = "destination-in";
6302
+ ctx.drawImage(mask, 0, 0);
6303
+ ctx.globalCompositeOperation = "source-over";
6304
+ }
6305
+ return canvas;
6306
+ }
6177
6307
  function angleToCoords(angleDeg) {
6178
6308
  const rad = angleDeg * Math.PI / 180;
6179
6309
  const x1 = 0.5 - Math.sin(rad) * 0.5;
@@ -7683,6 +7813,10 @@ const PageCanvas = forwardRef(
7683
7813
  fabricCanvas.on("mouse:dblclick", (e) => {
7684
7814
  if (!isActiveRef.current || !allowEditing) return;
7685
7815
  let target = e.target;
7816
+ if (!target) {
7817
+ const active = fabricCanvas.getActiveObject();
7818
+ if (active) target = active;
7819
+ }
7686
7820
  if (target && target instanceof fabric.Group && target.__cropGroup) {
7687
7821
  const ct = target.__cropData;
7688
7822
  if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
@@ -7838,7 +7972,7 @@ const PageCanvas = forwardRef(
7838
7972
  visibilityUpdateInProgressRef.current = false;
7839
7973
  }
7840
7974
  doSyncRef.current = () => {
7841
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
7975
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
7842
7976
  const shouldSkipUpdates2 = syncLockedRef.current || editLockRef.current;
7843
7977
  const state = useEditorStore.getState();
7844
7978
  const elementsToSync = elements;
@@ -7999,15 +8133,22 @@ const PageCanvas = forwardRef(
7999
8133
  const storedImageUrl = existingObj.__imageSrc;
8000
8134
  const currentUrlNormalized = currentImageUrl.trim();
8001
8135
  const storedUrlNormalized = storedImageUrl ? String(storedImageUrl).trim() : "";
8002
- const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8003
- const storedColorMapStr = existingObj.__svgColorMap || "";
8004
- const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8005
- const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8006
- const needsReload = sourceUrlChanged || colorMapChanged;
8007
8136
  const hasUrl = currentUrlNormalized !== "";
8008
8137
  const isCropGroup2 = existingObj instanceof fabric.Group && existingObj.__cropGroup;
8009
8138
  const isPlaceholderGroup = isEmptyImagePlaceholderGroup(existingObj);
8010
8139
  const isPlaceholder = isPlaceholderGroup || !(existingObj instanceof fabric.FabricImage) || existingObj instanceof fabric.Group && !isCropGroup2;
8140
+ const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8141
+ const storedColorMapStr = existingObj.__svgColorMap || "";
8142
+ const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8143
+ const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8144
+ const newFadeKey = edgeFadeKey(element);
8145
+ const oldFadeKey = isCropGroup2 ? existingObj.__edgeFadeInputKey || "" : existingObj.__edgeFadeKey || "";
8146
+ const innerImg = (_e = existingObj == null ? void 0 : existingObj.__cropData) == null ? void 0 : _e._img;
8147
+ const innerOldKey = isCropGroup2 ? oldFadeKey : innerImg ? innerImg.__edgeFadeKey || "" : oldFadeKey;
8148
+ const cropFadeRendererMissing = isCropGroup2 && Boolean(newFadeKey) && !existingObj.__edgeFadeRenderConfig;
8149
+ const fadeKeyChanged = newFadeKey !== oldFadeKey || newFadeKey !== innerOldKey || cropFadeRendererMissing;
8150
+ const needsReload = sourceUrlChanged || colorMapChanged || fadeKeyChanged && !isCropGroup2;
8151
+ const needsCropGroupFadeUpdate = isCropGroup2 && fadeKeyChanged;
8011
8152
  const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
8012
8153
  if (!hasUrl && hadUrlBefore) {
8013
8154
  const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
@@ -8023,16 +8164,16 @@ const PageCanvas = forwardRef(
8023
8164
  fc.requestRenderAll();
8024
8165
  continue;
8025
8166
  }
8026
- const imageFitForReplace = element.imageFit || ((_e = element.style) == null ? void 0 : _e.imageFit) || "cover";
8027
- const clipShapeForReplace = element.clipShape ?? ((_f = element.style) == null ? void 0 : _f.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8167
+ const imageFitForReplace = element.imageFit || ((_f = element.style) == null ? void 0 : _f.imageFit) || "cover";
8168
+ const clipShapeForReplace = element.clipShape ?? ((_g = element.style) == null ? void 0 : _g.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8028
8169
  const needCropGroupForElement = imageFitForReplace !== "fill" || clipShapeForReplace && clipShapeForReplace !== "none";
8029
8170
  const plainImageNeedsCropGroup = hasUrl && !isCropGroup2 && existingObj instanceof fabric.FabricImage && needCropGroupForElement;
8030
- if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup)) {
8171
+ if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup || needsCropGroupFadeUpdate)) {
8031
8172
  if (needsReload && !isBeingTransformed && (!wasJustModified || sourceUrlChanged)) {
8032
8173
  loadImageAsync(element, existingObj, fc);
8033
8174
  } else if (plainImageNeedsCropGroup) {
8034
8175
  loadImageAsync(element, existingObj, fc);
8035
- } else if (!needsReload && isCropGroup2) {
8176
+ } else if ((!needsReload || needsCropGroupFadeUpdate) && isCropGroup2) {
8036
8177
  const ct = existingObj.__cropData;
8037
8178
  if (ct) {
8038
8179
  const resolvedCrop = pageTree.length > 0 ? getNodeBounds(element, pageTree) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
@@ -8129,6 +8270,7 @@ const PageCanvas = forwardRef(
8129
8270
  }
8130
8271
  }
8131
8272
  updateCoverLayout(existingObj);
8273
+ applyEdgeFadeFrameClipPath(existingObj, element, ct.frameW, ct.frameH, ct.shape || "rect", ct.rx || 0);
8132
8274
  if (allowEditing) {
8133
8275
  installCanvaMaskControls(existingObj);
8134
8276
  } else {
@@ -8438,7 +8580,7 @@ const PageCanvas = forwardRef(
8438
8580
  fc.add(placeholder);
8439
8581
  fc.bringObjectToFront(placeholder);
8440
8582
  const activeObj = fc.getActiveObject();
8441
- if (activeObj && (((_g = activeObj._ct) == null ? void 0 : _g.isCropGroup) || activeObj.__cropGroup)) {
8583
+ if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8442
8584
  fc.setActiveObject(activeObj);
8443
8585
  }
8444
8586
  placeholder.dirty = true;
@@ -8478,7 +8620,7 @@ const PageCanvas = forwardRef(
8478
8620
  fc.add(obj);
8479
8621
  fc.bringObjectToFront(obj);
8480
8622
  const activeObj = fc.getActiveObject();
8481
- if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8623
+ if (activeObj && (((_i = activeObj._ct) == null ? void 0 : _i.isCropGroup) || activeObj.__cropGroup)) {
8482
8624
  fc.setActiveObject(activeObj);
8483
8625
  }
8484
8626
  obj.dirty = true;
@@ -8512,7 +8654,7 @@ const PageCanvas = forwardRef(
8512
8654
  isRebuildingRef.current = false;
8513
8655
  fc.requestRenderAll();
8514
8656
  if (activeBeforeSync && fc.getObjects().includes(activeBeforeSync)) {
8515
- const isCropGroup2 = ((_i = activeBeforeSync._ct) == null ? void 0 : _i.isCropGroup) || activeBeforeSync.__cropGroup;
8657
+ const isCropGroup2 = ((_j = activeBeforeSync._ct) == null ? void 0 : _j.isCropGroup) || activeBeforeSync.__cropGroup;
8516
8658
  if (isCropGroup2) {
8517
8659
  fc.setActiveObject(activeBeforeSync);
8518
8660
  fc.requestRenderAll();
@@ -8934,6 +9076,7 @@ const PageCanvas = forwardRef(
8934
9076
  }
8935
9077
  }
8936
9078
  updateCoverLayout(obj);
9079
+ applyEdgeFadeFrameClipPath(obj, element, elementWidth, elementHeight, ct.shape || "rect", ct.rx || 0);
8937
9080
  obj.setCoords();
8938
9081
  obj.dirty = true;
8939
9082
  if (obj.clipPath) {
@@ -9517,8 +9660,137 @@ const PageCanvas = forwardRef(
9517
9660
  obj.__lastStrokeGradientJson = "null";
9518
9661
  }
9519
9662
  };
9663
+ const clamp2 = (v, min, max) => {
9664
+ return Math.max(min, Math.min(max, v));
9665
+ };
9666
+ const applyEdgeFadeFrameClipPath = (group, element, frameW, frameH, shape, rxRatio) => {
9667
+ var _a, _b, _c;
9668
+ const fadeElement = element;
9669
+ const fadeGroup = group;
9670
+ const inputKey = edgeFadeKey(fadeElement);
9671
+ const hasFade = hasEdgeFade(fadeElement);
9672
+ if (!fadeGroup.__edgeFadeOriginalDrawObject) {
9673
+ fadeGroup.__edgeFadeOriginalDrawObject = group.drawObject;
9674
+ }
9675
+ if (hasFade) {
9676
+ if ((_a = group.clipPath) == null ? void 0 : _a.__edgeFadeMask) {
9677
+ group.clipPath = void 0;
9678
+ updateCoverLayout(group);
9679
+ }
9680
+ fadeGroup.__edgeFadeRenderConfig = {
9681
+ key: `${inputKey}|${Math.round(frameW)}|${Math.round(frameH)}|${shape}|${rxRatio}`,
9682
+ frameW: Math.max(1, Number(frameW) || 1),
9683
+ frameH: Math.max(1, Number(frameH) || 1),
9684
+ topSize: clamp2(Number(fadeElement.fadeTopSize) || 0, 0, 1),
9685
+ topAmount: clamp2(fadeElement.fadeTopAmount == null ? 1 : Number(fadeElement.fadeTopAmount), 0, 1),
9686
+ rightSize: clamp2(Number(fadeElement.fadeRightSize) || 0, 0, 1),
9687
+ rightAmount: clamp2(fadeElement.fadeRightAmount == null ? 1 : Number(fadeElement.fadeRightAmount), 0, 1),
9688
+ bottomSize: clamp2(Number(fadeElement.fadeBottomSize) || 0, 0, 1),
9689
+ bottomAmount: clamp2(fadeElement.fadeBottomAmount == null ? 1 : Number(fadeElement.fadeBottomAmount), 0, 1),
9690
+ leftSize: clamp2(Number(fadeElement.fadeLeftSize) || 0, 0, 1),
9691
+ leftAmount: clamp2(fadeElement.fadeLeftAmount == null ? 1 : Number(fadeElement.fadeLeftAmount), 0, 1),
9692
+ topHardness: clamp2(Number(fadeElement.fadeTopHardness) || 1, 0.1, 5),
9693
+ rightHardness: clamp2(Number(fadeElement.fadeRightHardness) || 1, 0.1, 5),
9694
+ bottomHardness: clamp2(Number(fadeElement.fadeBottomHardness) || 1, 0.1, 5),
9695
+ leftHardness: clamp2(Number(fadeElement.fadeLeftHardness) || 1, 0.1, 5)
9696
+ };
9697
+ const originalDrawObject = fadeGroup.__edgeFadeOriginalDrawObject ?? group.drawObject;
9698
+ group.drawObject = function edgeFadeDrawObject(ctx, forClipping, context) {
9699
+ originalDrawObject.call(this, ctx, forClipping, context);
9700
+ if (forClipping) return;
9701
+ const cfg = this.__edgeFadeRenderConfig;
9702
+ if (!cfg) return;
9703
+ const liveW = Number(this.width) || 0;
9704
+ const liveH = Number(this.height) || 0;
9705
+ const w = liveW > 0 ? liveW : cfg.frameW;
9706
+ const h = liveH > 0 ? liveH : cfg.frameH;
9707
+ const x = -w / 2;
9708
+ const y = -h / 2;
9709
+ const paintBand = (side, size, amount, hardness) => {
9710
+ if (size <= 0 || amount >= 1) return;
9711
+ ctx.save();
9712
+ ctx.globalCompositeOperation = "destination-out";
9713
+ let gradient;
9714
+ const eraseAtEdge = 1 - amount;
9715
+ const STOPS = 64;
9716
+ const gamma = 1 / hardness;
9717
+ const seamGuardPx = 2;
9718
+ const addStops = (g) => {
9719
+ for (let i = 0; i <= STOPS; i++) {
9720
+ const t = i / STOPS;
9721
+ const keepProgress = Math.pow(t, gamma);
9722
+ const a = eraseAtEdge * (1 - keepProgress);
9723
+ g.addColorStop(t, `rgba(0,0,0,${a})`);
9724
+ }
9725
+ };
9726
+ if (side === "top") {
9727
+ const band = Math.max(1, h * size);
9728
+ gradient = ctx.createLinearGradient(0, y, 0, y + band);
9729
+ addStops(gradient);
9730
+ ctx.fillStyle = gradient;
9731
+ ctx.fillRect(x, y, w, band);
9732
+ ctx.fillStyle = "rgba(0,0,0,1)";
9733
+ ctx.fillRect(x, y, w, Math.min(seamGuardPx, band));
9734
+ } else if (side === "bottom") {
9735
+ const band = Math.max(1, h * size);
9736
+ gradient = ctx.createLinearGradient(0, y + h, 0, y + h - band);
9737
+ addStops(gradient);
9738
+ ctx.fillStyle = gradient;
9739
+ ctx.fillRect(x, y + h - band, w, band);
9740
+ ctx.fillStyle = "rgba(0,0,0,1)";
9741
+ ctx.fillRect(x, y + h - Math.min(seamGuardPx, band), w, Math.min(seamGuardPx, band));
9742
+ } else if (side === "left") {
9743
+ const band = Math.max(1, w * size);
9744
+ gradient = ctx.createLinearGradient(x, 0, x + band, 0);
9745
+ addStops(gradient);
9746
+ ctx.fillStyle = gradient;
9747
+ ctx.fillRect(x, y, band, h);
9748
+ ctx.fillStyle = "rgba(0,0,0,1)";
9749
+ ctx.fillRect(x, y, Math.min(seamGuardPx, band), h);
9750
+ } else {
9751
+ const band = Math.max(1, w * size);
9752
+ gradient = ctx.createLinearGradient(x + w, 0, x + w - band, 0);
9753
+ addStops(gradient);
9754
+ ctx.fillStyle = gradient;
9755
+ ctx.fillRect(x + w - band, y, band, h);
9756
+ ctx.fillStyle = "rgba(0,0,0,1)";
9757
+ ctx.fillRect(x + w - Math.min(seamGuardPx, band), y, Math.min(seamGuardPx, band), h);
9758
+ }
9759
+ ctx.restore();
9760
+ };
9761
+ paintBand("top", cfg.topSize, cfg.topAmount, cfg.topHardness);
9762
+ paintBand("right", cfg.rightSize, cfg.rightAmount, cfg.rightHardness);
9763
+ paintBand("bottom", cfg.bottomSize, cfg.bottomAmount, cfg.bottomHardness);
9764
+ paintBand("left", cfg.leftSize, cfg.leftAmount, cfg.leftHardness);
9765
+ };
9766
+ group.set({ objectCaching: true, noScaleCache: false });
9767
+ fadeGroup.__edgeFadeKey = fadeGroup.__edgeFadeRenderConfig.key;
9768
+ fadeGroup.__edgeFadeInputKey = inputKey;
9769
+ } else {
9770
+ if (fadeGroup.__edgeFadeOriginalDrawObject) {
9771
+ group.drawObject = fadeGroup.__edgeFadeOriginalDrawObject;
9772
+ }
9773
+ delete fadeGroup.__edgeFadeRenderConfig;
9774
+ delete fadeGroup.__edgeFadeKey;
9775
+ delete fadeGroup.__edgeFadeInputKey;
9776
+ if ((_b = group.clipPath) == null ? void 0 : _b.__edgeFadeMask) {
9777
+ group.clipPath = void 0;
9778
+ updateCoverLayout(group);
9779
+ }
9780
+ }
9781
+ if (group.clipPath) {
9782
+ group.clipPath.dirty = true;
9783
+ group.clipPath.setCoords();
9784
+ }
9785
+ fadeGroup.dirty = true;
9786
+ const children = group.getObjects();
9787
+ children.forEach((child) => {
9788
+ child.dirty = true;
9789
+ });
9790
+ (_c = group.canvas) == null ? void 0 : _c.requestRenderAll();
9791
+ };
9520
9792
  const loadImageAsync = async (element, placeholder, fc) => {
9521
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
9793
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
9522
9794
  const imageUrl = element.src || element.imageUrl;
9523
9795
  if (!imageUrl) return;
9524
9796
  const elementId = element.id;
@@ -9531,7 +9803,9 @@ const PageCanvas = forwardRef(
9531
9803
  const existingImg = ct == null ? void 0 : ct._img;
9532
9804
  const existingSrc = placeholder.__imageSrc;
9533
9805
  const existingSvgColorMap = placeholder.__svgColorMap || "";
9534
- if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap) {
9806
+ const existingFadeKey = existingImg && existingImg.__edgeFadeKey || placeholder.__edgeFadeKey || "";
9807
+ const nextFadeKey = edgeFadeKey(element);
9808
+ if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap && existingFadeKey === nextFadeKey) {
9535
9809
  return placeholder;
9536
9810
  }
9537
9811
  }
@@ -9548,6 +9822,22 @@ const PageCanvas = forwardRef(
9548
9822
  if (!fabricRef.current || !isLatestRequest()) return;
9549
9823
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
9550
9824
  if (!isLatestRequest()) return;
9825
+ const imageFitForFade = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9826
+ const clipShapeForFade = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9827
+ const willUseCropGroupForFade = imageFitForFade !== "fill" || clipShapeForFade && clipShapeForFade !== "none";
9828
+ try {
9829
+ if (hasEdgeFade(element) && !willUseCropGroupForFade) {
9830
+ const srcEl = (_c = img.getElement) == null ? void 0 : _c.call(img);
9831
+ if (srcEl) {
9832
+ const baked = bakeEdgeFade(srcEl, element);
9833
+ img.setElement(baked);
9834
+ img.__edgeFadeKey = edgeFadeKey(element);
9835
+ img.dirty = true;
9836
+ }
9837
+ }
9838
+ } catch (e) {
9839
+ console.warn("[edgeFade] bake failed:", e);
9840
+ }
9551
9841
  const isHidden = !element.visible;
9552
9842
  img.set({
9553
9843
  originX: "left",
@@ -9580,8 +9870,8 @@ const PageCanvas = forwardRef(
9580
9870
  });
9581
9871
  img.setCoords();
9582
9872
  } else {
9583
- const imageFit = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9584
- const clipShape = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9873
+ const imageFit = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9874
+ const clipShape = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9585
9875
  const needCropGroup2 = imageFit !== "fill" || clipShape && clipShape !== "none";
9586
9876
  const imgNaturalWidth = img.width || 1;
9587
9877
  const imgNaturalHeight = img.height || 1;
@@ -9608,7 +9898,7 @@ const PageCanvas = forwardRef(
9608
9898
  if (imageFit === "fill" && !needCropGroup2) {
9609
9899
  const finalScaleX = baseScaleX * (element.scaleX ?? 1);
9610
9900
  const finalScaleY = baseScaleY * (element.scaleY ?? 1);
9611
- const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_c = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _c.children) ?? [];
9901
+ const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9612
9902
  const createPos = pageTreeForCreate.length > 0 ? (() => {
9613
9903
  const node = findNodeById(pageTreeForCreate, element.id);
9614
9904
  return node ? getAbsoluteBounds(node, pageTreeForCreate) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9653,12 +9943,12 @@ const PageCanvas = forwardRef(
9653
9943
  }
9654
9944
  img.__imageSrc = imageUrl;
9655
9945
  img.__svgColorMap = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
9656
- const imageFitFinal = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9657
- const clipShapeFinal = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9946
+ const imageFitFinal = element.imageFit || ((_g = element.style) == null ? void 0 : _g.imageFit) || "cover";
9947
+ const clipShapeFinal = element.clipShape ?? ((_h = element.style) == null ? void 0 : _h.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9658
9948
  const needCropGroup = imageFitFinal !== "fill" || clipShapeFinal && clipShapeFinal !== "none";
9659
9949
  let finalObject = img;
9660
9950
  if (needCropGroup) {
9661
- const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9951
+ const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_i = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _i.children) ?? [];
9662
9952
  const nodeForSize = pageTreeForCropResolve.length ? findNodeById(pageTreeForCropResolve, element.id) : null;
9663
9953
  const w = nodeForSize && isElement(nodeForSize) ? nodeForSize.width : element.width;
9664
9954
  const h = nodeForSize && isElement(nodeForSize) ? nodeForSize.height : element.height;
@@ -9690,16 +9980,16 @@ const PageCanvas = forwardRef(
9690
9980
  let panY = element.cropPanY ?? 0.5;
9691
9981
  let zoom2 = element.cropZoom ?? 1;
9692
9982
  if (existingCropGroup) {
9693
- const existingImg = (_g = existingCropGroup.__cropData) == null ? void 0 : _g._img;
9983
+ const existingImg = (_j = existingCropGroup.__cropData) == null ? void 0 : _j._img;
9694
9984
  if (existingImg) {
9695
- panX = ((_h = existingImg._ct) == null ? void 0 : _h.panX) ?? existingImg.__panX ?? panX;
9696
- panY = ((_i = existingImg._ct) == null ? void 0 : _i.panY) ?? existingImg.__panY ?? panY;
9697
- zoom2 = ((_j = existingImg._ct) == null ? void 0 : _j.zoom) ?? zoom2;
9985
+ panX = ((_k = existingImg._ct) == null ? void 0 : _k.panX) ?? existingImg.__panX ?? panX;
9986
+ panY = ((_l = existingImg._ct) == null ? void 0 : _l.panY) ?? existingImg.__panY ?? panY;
9987
+ zoom2 = ((_m = existingImg._ct) == null ? void 0 : _m.zoom) ?? zoom2;
9698
9988
  }
9699
9989
  }
9700
9990
  const isDynamicField = dynamicFieldIds.includes(element.id);
9701
9991
  const canBeEvented = isEditorMode || isPreviewMode && isDynamicField;
9702
- const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_k = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _k.children) ?? [];
9992
+ const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_n = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _n.children) ?? [];
9703
9993
  const createPosForCrop = pageTreeForCrop.length > 0 ? (() => {
9704
9994
  const node = findNodeById(pageTreeForCrop, element.id);
9705
9995
  return node ? getAbsoluteBounds(node, pageTreeForCrop) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9750,17 +10040,18 @@ const PageCanvas = forwardRef(
9750
10040
  } else {
9751
10041
  installCanvaMaskControls(cropGroup);
9752
10042
  }
9753
- const cropImg = (_l = cropGroup.__cropData) == null ? void 0 : _l._img;
10043
+ const cropImg = (_o = cropGroup.__cropData) == null ? void 0 : _o._img;
9754
10044
  if (cropImg) {
9755
10045
  cropImg._ct = { panX, panY, zoom: zoom2 };
9756
10046
  updateCoverLayout(cropGroup);
9757
10047
  }
10048
+ applyEdgeFadeFrameClipPath(cropGroup, element, frameW, frameH, shape, rxRatio);
9758
10049
  setObjectData(cropGroup, element.id);
9759
10050
  cropGroup.__imageElement = cropImg;
9760
10051
  cropGroup.__imageSrc = imageUrl;
9761
10052
  cropGroup.__svgColorMap = nextSvgColorMap;
9762
10053
  if (cropImg && element.imageNaturalWidth == null) {
9763
- const el = ((_m = cropImg.getElement) == null ? void 0 : _m.call(cropImg)) ?? cropImg._element;
10054
+ const el = ((_p = cropImg.getElement) == null ? void 0 : _p.call(cropImg)) ?? cropImg._element;
9764
10055
  const orig = typeof cropImg.getOriginalSize === "function" ? cropImg.getOriginalSize() : null;
9765
10056
  const nw = (orig == null ? void 0 : orig.width) ?? (el == null ? void 0 : el.naturalWidth) ?? cropImg.width;
9766
10057
  const nh = (orig == null ? void 0 : orig.height) ?? (el == null ? void 0 : el.naturalHeight) ?? cropImg.height;
@@ -13133,7 +13424,7 @@ function PixldocsPreview(props) {
13133
13424
  !canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
13134
13425
  ] });
13135
13426
  }
13136
- const PACKAGE_VERSION = "0.5.101";
13427
+ const PACKAGE_VERSION = "0.5.103";
13137
13428
  const roundParityValue = (value) => {
13138
13429
  if (typeof value !== "number") return value;
13139
13430
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -13921,12 +14212,69 @@ class PixldocsRenderer {
13921
14212
  { width: canvasWidth, height: canvasHeight },
13922
14213
  { cssOnly: false, backstoreOnly: false }
13923
14214
  );
14215
+ const fadeBakeRecords = [];
14216
+ try {
14217
+ const objs = fabricInstance.getObjects().slice();
14218
+ for (const obj of objs) {
14219
+ const isFadedCropGroup = obj instanceof fabric.Group && (Boolean(obj.__edgeFadeRenderConfig) || Boolean(obj.__edgeFadeKey) || Boolean(obj.__edgeFadeInputKey));
14220
+ if (!isFadedCropGroup) continue;
14221
+ try {
14222
+ const baked = obj.toCanvasElement({
14223
+ multiplier: 2,
14224
+ enableRetinaScaling: false
14225
+ });
14226
+ const rect = obj.getBoundingRect();
14227
+ const replacement = new fabric.FabricImage(baked, {
14228
+ left: rect.left,
14229
+ top: rect.top,
14230
+ originX: "left",
14231
+ originY: "top",
14232
+ scaleX: rect.width / baked.width,
14233
+ scaleY: rect.height / baked.height,
14234
+ selectable: false,
14235
+ evented: false,
14236
+ objectCaching: false
14237
+ });
14238
+ const insertIndex = fabricInstance._objects.indexOf(obj);
14239
+ const prevExclude = obj.excludeFromExport;
14240
+ obj.excludeFromExport = true;
14241
+ if (insertIndex >= 0) {
14242
+ fabricInstance.insertAt(insertIndex + 1, replacement);
14243
+ } else {
14244
+ fabricInstance.add(replacement);
14245
+ }
14246
+ fadeBakeRecords.push({
14247
+ original: obj,
14248
+ replacement,
14249
+ prevExclude,
14250
+ insertIndex
14251
+ });
14252
+ } catch (bakeErr) {
14253
+ console.warn("[canvas-renderer][edgeFade] bake-for-svg failed:", bakeErr);
14254
+ }
14255
+ }
14256
+ if (fadeBakeRecords.length) {
14257
+ fabricInstance.renderAll();
14258
+ console.log(
14259
+ `[canvas-renderer][edgeFade] baked ${fadeBakeRecords.length} faded object(s) for SVG capture`
14260
+ );
14261
+ }
14262
+ } catch (e) {
14263
+ console.warn("[canvas-renderer][edgeFade] bake pass failed:", e);
14264
+ }
13924
14265
  const rawSvgString = fabricInstance.toSVG();
13925
14266
  const svgString = this.normalizeSvgDimensions(
13926
14267
  rawSvgString,
13927
14268
  canvasWidth,
13928
14269
  canvasHeight
13929
14270
  );
14271
+ for (const rec of fadeBakeRecords) {
14272
+ try {
14273
+ fabricInstance.remove(rec.replacement);
14274
+ rec.original.excludeFromExport = rec.prevExclude;
14275
+ } catch {
14276
+ }
14277
+ }
13930
14278
  fabricInstance.enableRetinaScaling = prevRetina;
13931
14279
  fabricInstance.setDimensions(
13932
14280
  { width: prevWidth, height: prevHeight },