@bwp-web/canvas 0.8.0 → 0.8.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.js CHANGED
@@ -434,21 +434,26 @@ function setBackgroundContrast(canvas, value) {
434
434
  const contrastIdx = currentFilters.findIndex(
435
435
  (f) => f instanceof filters.Contrast
436
436
  );
437
+ let changed = false;
437
438
  if (contrast === 0) {
438
439
  if (contrastIdx >= 0) {
439
440
  bg.filters = currentFilters.filter(
440
441
  (f) => !(f instanceof filters.Contrast)
441
442
  );
442
443
  bg.applyFilters();
444
+ changed = true;
443
445
  }
444
446
  } else if (contrastIdx >= 0) {
445
447
  currentFilters[contrastIdx].contrast = contrast;
446
448
  bg.applyFilters();
449
+ changed = true;
447
450
  } else {
448
451
  bg.filters = [...currentFilters, new filters.Contrast({ contrast })];
449
452
  bg.applyFilters();
453
+ changed = true;
450
454
  }
451
455
  canvas.requestRenderAll();
456
+ if (changed) canvas.fire("background:modified");
452
457
  }
453
458
  function getBackgroundContrast(canvas) {
454
459
  const bg = getBackgroundImage(canvas);
@@ -463,14 +468,18 @@ function setBackgroundInverted(canvas, inverted) {
463
468
  if (!bg) return;
464
469
  const currentFilters = bg.filters ?? [];
465
470
  const hasInvert = currentFilters.some((f) => f instanceof filters.Invert);
471
+ let changed = false;
466
472
  if (inverted && !hasInvert) {
467
473
  bg.filters = [...currentFilters, new filters.Invert()];
468
474
  bg.applyFilters();
475
+ changed = true;
469
476
  } else if (!inverted && hasInvert) {
470
477
  bg.filters = currentFilters.filter((f) => !(f instanceof filters.Invert));
471
478
  bg.applyFilters();
479
+ changed = true;
472
480
  }
473
481
  canvas.requestRenderAll();
482
+ if (changed) canvas.fire("background:modified");
474
483
  }
475
484
  function getBackgroundInverted(canvas) {
476
485
  const bg = getBackgroundImage(canvas);
@@ -536,6 +545,7 @@ async function setBackgroundImage(canvas, url, options) {
536
545
  setBackgroundContrast(canvas, prevContrast);
537
546
  }
538
547
  canvas.requestRenderAll();
548
+ canvas.fire("background:modified");
539
549
  return img;
540
550
  }
541
551
 
@@ -2540,6 +2550,7 @@ function useEditCanvas(options) {
2540
2550
  canvas.on("object:added", () => setIsDirty(true));
2541
2551
  canvas.on("object:removed", () => setIsDirty(true));
2542
2552
  canvas.on("object:modified", () => setIsDirty(true));
2553
+ canvas.on("background:modified", () => setIsDirty(true));
2543
2554
  }
2544
2555
  if (opts?.history) {
2545
2556
  const syncHistoryState = () => {
@@ -2553,6 +2564,7 @@ function useEditCanvas(options) {
2553
2564
  canvas.on("object:added", syncHistoryState);
2554
2565
  canvas.on("object:removed", syncHistoryState);
2555
2566
  canvas.on("object:modified", syncHistoryState);
2567
+ canvas.on("background:modified", syncHistoryState);
2556
2568
  }
2557
2569
  if (opts?.vertexEdit !== false) {
2558
2570
  const vertexOpts = typeof opts?.vertexEdit === "object" ? opts.vertexEdit : void 0;
@@ -2684,6 +2696,8 @@ function useEditCanvas(options) {
2684
2696
  isDirty,
2685
2697
  /** Reset the dirty flag (e.g., after a successful save). */
2686
2698
  resetDirty: useCallback(() => setIsDirty(false), []),
2699
+ /** Manually mark the canvas as dirty (e.g., after a custom operation not tracked automatically). */
2700
+ markDirty: useCallback(() => setIsDirty(true), []),
2687
2701
  /** Undo the last change. Requires `history: true`. */
2688
2702
  undo: useCallback(async () => {
2689
2703
  const h = historyRef.current;
@@ -2743,6 +2757,7 @@ function useViewCanvas(options) {
2743
2757
  canvas.on("object:added", () => {
2744
2758
  lockCanvas(canvas);
2745
2759
  });
2760
+ canvas.hoverCursor = "pointer";
2746
2761
  canvas.on("mouse:wheel", () => {
2747
2762
  setZoom(canvas.getZoom());
2748
2763
  });
@@ -3196,6 +3211,31 @@ function toPx(v) {
3196
3211
  if (v === void 0) return void 0;
3197
3212
  return typeof v === "number" ? `${v}px` : v;
3198
3213
  }
3214
+ function deriveAngle(top, right, bottom, left) {
3215
+ const hasTop = top !== void 0;
3216
+ const hasRight = right !== void 0;
3217
+ const hasBottom = bottom !== void 0;
3218
+ const hasLeft = left !== void 0;
3219
+ if (hasTop && hasRight) return 45;
3220
+ if (hasTop && hasLeft) return 135;
3221
+ if (hasBottom && hasRight) return 315;
3222
+ if (hasBottom && hasLeft) return 225;
3223
+ if (hasTop) return 90;
3224
+ if (hasRight) return 0;
3225
+ if (hasBottom) return 270;
3226
+ if (hasLeft) return 180;
3227
+ return 45;
3228
+ }
3229
+ function ellipsePosition(angleDeg) {
3230
+ const rad = angleDeg * Math.PI / 180;
3231
+ return {
3232
+ pctX: 50 + 50 * Math.cos(rad),
3233
+ pctY: 50 - 50 * Math.sin(rad)
3234
+ };
3235
+ }
3236
+ function toNum(v) {
3237
+ return typeof v === "number" ? v : 0;
3238
+ }
3199
3239
  function OverlayBadge({
3200
3240
  children,
3201
3241
  maxScale = 2,
@@ -3204,6 +3244,7 @@ function OverlayBadge({
3204
3244
  right,
3205
3245
  bottom,
3206
3246
  left,
3247
+ circular = false,
3207
3248
  sx,
3208
3249
  ...rest
3209
3250
  }) {
@@ -3234,7 +3275,8 @@ function OverlayBadge({
3234
3275
  const overlayScale = parseFloat(
3235
3276
  getComputedStyle(el).getPropertyValue("--overlay-scale")
3236
3277
  ) || 1;
3237
- el.style.transform = `scale(${ownScale / overlayScale})`;
3278
+ const scale = `scale(${ownScale / overlayScale})`;
3279
+ el.style.transform = circular ? `translate(-50%, -50%) ${scale}` : scale;
3238
3280
  });
3239
3281
  }
3240
3282
  const observer = new ResizeObserver(update);
@@ -3244,17 +3286,29 @@ function OverlayBadge({
3244
3286
  observer.disconnect();
3245
3287
  baseSize.current = null;
3246
3288
  };
3247
- }, [maxScale, minScale]);
3289
+ }, [maxScale, minScale, circular]);
3290
+ const positionSx = circular ? (() => {
3291
+ const angle = deriveAngle(top, right, bottom, left);
3292
+ const { pctX, pctY } = ellipsePosition(angle);
3293
+ const leftOffset = toNum(left) - toNum(right);
3294
+ const topOffset = toNum(top) - toNum(bottom);
3295
+ return {
3296
+ left: `calc(${pctX}% + ${leftOffset}px)`,
3297
+ top: `calc(${pctY}% + ${topOffset}px)`
3298
+ };
3299
+ })() : {
3300
+ top: toPx(top),
3301
+ right: toPx(right),
3302
+ bottom: toPx(bottom),
3303
+ left: toPx(left)
3304
+ };
3248
3305
  return /* @__PURE__ */ jsx5(
3249
3306
  Stack4,
3250
3307
  {
3251
3308
  ref,
3252
3309
  sx: {
3253
3310
  position: "absolute",
3254
- top: toPx(top),
3255
- right: toPx(right),
3256
- bottom: toPx(bottom),
3257
- left: toPx(left),
3311
+ ...positionSx,
3258
3312
  transformOrigin: "center center",
3259
3313
  pointerEvents: "auto",
3260
3314
  width: "max-content",