@overtone-art/canvas-editor-core 0.3.0 → 0.3.2

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
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  CropController: () => CropController,
27
27
  DEFAULT_LAYER_SHADOW: () => DEFAULT_LAYER_SHADOW,
28
28
  DEFAULT_PATTERN_CONFIG: () => DEFAULT_PATTERN_CONFIG,
29
+ DEFAULT_SELECTION_STYLE: () => DEFAULT_SELECTION_STYLE,
29
30
  DEFAULT_TEXT_CURVE: () => DEFAULT_TEXT_CURVE,
30
31
  EventEmitter: () => EventEmitter,
31
32
  FontRegistry: () => FontRegistry,
@@ -47,7 +48,9 @@ __export(index_exports, {
47
48
  UnitConverter: () => UnitConverter,
48
49
  applyAspectLock: () => applyAspectLock,
49
50
  applyLayerShadow: () => applyLayerShadow,
51
+ applyObjectSelectionStyle: () => applyObjectSelectionStyle,
50
52
  applyPatternLocks: () => applyPatternLocks,
53
+ applySelectionStyle: () => applySelectionStyle,
51
54
  buildCurvePathData: () => buildCurvePathData,
52
55
  buildPatternDataURL: () => buildPatternDataURL,
53
56
  captureLocks: () => captureLocks,
@@ -304,6 +307,38 @@ var LayerManager = class {
304
307
  this.emitChanged();
305
308
  this.onPropertyChanged?.();
306
309
  }
310
+ /**
311
+ * Patch a layer's host metadata. A key whose value is `undefined` is removed.
312
+ *
313
+ * `meta` is part of `LayerData` and is serialized, so a host that assigns
314
+ * `layer.meta.x` directly gets neither of the two things every other setter
315
+ * here provides: `layers:changed` (so `useLayers()` keeps returning the old
316
+ * meta) and the history checkpoint (so an undo silently reverts the write —
317
+ * the state IS versioned, it was just never committed at the moment it
318
+ * changed). Going through this method is what makes metadata behave like
319
+ * every other layer property.
320
+ */
321
+ setMeta(id, patch) {
322
+ const layer = this.get(id);
323
+ if (!layer) return;
324
+ const next = { ...layer.meta };
325
+ let changed = false;
326
+ for (const [key, value] of Object.entries(patch)) {
327
+ if (value === void 0) {
328
+ if (key in next) {
329
+ delete next[key];
330
+ changed = true;
331
+ }
332
+ continue;
333
+ }
334
+ if (next[key] !== value) changed = true;
335
+ next[key] = value;
336
+ }
337
+ if (!changed) return;
338
+ layer.meta = next;
339
+ this.emitChanged();
340
+ this.onPropertyChanged?.();
341
+ }
307
342
  clear() {
308
343
  for (const layer of this.layers) {
309
344
  this.canvas.remove(layer.fabricObject);
@@ -1657,6 +1692,43 @@ function applyLayerShadow(object, config) {
1657
1692
  });
1658
1693
  }
1659
1694
 
1695
+ // src/selection-style.ts
1696
+ var DEFAULT_SELECTION_STYLE = {
1697
+ borderColor: "#c9a96e",
1698
+ cornerColor: "#c9a96e",
1699
+ cornerStrokeColor: "#101010",
1700
+ cornerSize: 11,
1701
+ cornerStyle: "circle",
1702
+ borderWidth: 1,
1703
+ borderDashArray: null,
1704
+ padding: 0,
1705
+ marqueeFill: "rgba(201, 169, 110, 0.12)"
1706
+ };
1707
+ function applyObjectSelectionStyle(object, style, zoom) {
1708
+ const scale = zoom > 0 ? 1 / zoom : 1;
1709
+ object.set({
1710
+ cornerSize: style.cornerSize * scale,
1711
+ cornerStyle: style.cornerStyle,
1712
+ cornerColor: style.cornerColor,
1713
+ cornerStrokeColor: style.cornerStrokeColor,
1714
+ // A filled handle is what makes the stroke colour visible at all.
1715
+ transparentCorners: false,
1716
+ borderColor: style.borderColor,
1717
+ borderScaleFactor: style.borderWidth * scale,
1718
+ borderDashArray: style.borderDashArray?.map((segment) => segment * scale) ?? null,
1719
+ padding: style.padding * scale
1720
+ });
1721
+ }
1722
+ function applySelectionStyle(canvas, style, zoom) {
1723
+ for (const object of canvas.getObjects()) applyObjectSelectionStyle(object, style, zoom);
1724
+ const active = canvas.getActiveObject();
1725
+ if (active) applyObjectSelectionStyle(active, style, zoom);
1726
+ canvas.selectionColor = style.marqueeFill;
1727
+ canvas.selectionBorderColor = style.borderColor;
1728
+ canvas.selectionLineWidth = style.borderWidth * (zoom > 0 ? 1 / zoom : 1);
1729
+ canvas.requestRenderAll();
1730
+ }
1731
+
1660
1732
  // src/transform.ts
1661
1733
  var SIDE_CONTROLS = ["ml", "mr", "mt", "mb"];
1662
1734
  function applyAspectLock(object, locked) {
@@ -2664,6 +2736,7 @@ var CanvasEditor = class {
2664
2736
  fileAdapter;
2665
2737
  imageProvider;
2666
2738
  zoomLevel = 1;
2739
+ selectionStyle = { ...DEFAULT_SELECTION_STYLE };
2667
2740
  mockup = null;
2668
2741
  // The design's configured background. The live canvas background is forced
2669
2742
  // transparent while a mockup preview is shown, so this is the source of truth
@@ -2711,6 +2784,7 @@ var CanvasEditor = class {
2711
2784
  this.curves = new TextCurveManager(this.canvas, this.layers, this.history, this.events);
2712
2785
  this.maskPresets = new MaskPresetManager(this.canvas, this.layers, this.history, this.events);
2713
2786
  this.setupCanvasEvents();
2787
+ this.refreshSelectionStyle();
2714
2788
  this.history.saveImmediate();
2715
2789
  this.pages = new ProjectManager(this);
2716
2790
  this.masks = new MaskController(this);
@@ -3381,6 +3455,7 @@ var CanvasEditor = class {
3381
3455
  const next = clamp(round2(level), MIN_ZOOM, MAX_ZOOM);
3382
3456
  if (next === this.zoomLevel) return;
3383
3457
  this.zoomLevel = next;
3458
+ this.refreshSelectionStyle();
3384
3459
  this.events.emit("zoom:changed", { zoom: next });
3385
3460
  }
3386
3461
  /** @deprecated use setZoom — kept for backward compatibility. */
@@ -3529,8 +3604,31 @@ var CanvasEditor = class {
3529
3604
  this.events.removeAllListeners();
3530
3605
  this.canvas.dispose();
3531
3606
  }
3607
+ // ─── Selection style ────────────────────────────────
3608
+ /** Current look of the selection frame and its handles. */
3609
+ getSelectionStyle() {
3610
+ return { ...this.selectionStyle };
3611
+ }
3612
+ /**
3613
+ * Restyles the selection frame and resize handles. Sizes are in screen
3614
+ * pixels and stay constant across zoom levels (see `SelectionStyle`).
3615
+ */
3616
+ setSelectionStyle(style) {
3617
+ this.selectionStyle = { ...this.selectionStyle, ...style };
3618
+ this.refreshSelectionStyle();
3619
+ }
3620
+ refreshSelectionStyle() {
3621
+ applySelectionStyle(this.canvas, this.selectionStyle, this.zoomLevel);
3622
+ }
3532
3623
  // ─── Private ────────────────────────────────────────
3533
3624
  setupCanvasEvents() {
3625
+ this.canvas.on("object:added", (e) => {
3626
+ if (e.target) applyObjectSelectionStyle(e.target, this.selectionStyle, this.zoomLevel);
3627
+ });
3628
+ const styleActive = () => {
3629
+ const active = this.canvas.getActiveObject();
3630
+ if (active) applyObjectSelectionStyle(active, this.selectionStyle, this.zoomLevel);
3631
+ };
3534
3632
  this.canvas.on("object:modified", (e) => {
3535
3633
  if (!e.target) return;
3536
3634
  const layer = this.layers.findByObject(e.target);
@@ -3540,10 +3638,12 @@ var CanvasEditor = class {
3540
3638
  }
3541
3639
  });
3542
3640
  this.canvas.on("selection:created", (e) => {
3641
+ styleActive();
3543
3642
  const selected = (e.selected ?? []).map((obj) => this.layers.findByObject(obj)?.id).filter((id) => id !== void 0);
3544
3643
  this.events.emit("selection:changed", { selected });
3545
3644
  });
3546
3645
  this.canvas.on("selection:updated", (e) => {
3646
+ styleActive();
3547
3647
  const selected = (e.selected ?? []).map((obj) => this.layers.findByObject(obj)?.id).filter((id) => id !== void 0);
3548
3648
  this.events.emit("selection:changed", { selected });
3549
3649
  });
@@ -3625,6 +3725,7 @@ var AnnotationOverlay = class {
3625
3725
  CropController,
3626
3726
  DEFAULT_LAYER_SHADOW,
3627
3727
  DEFAULT_PATTERN_CONFIG,
3728
+ DEFAULT_SELECTION_STYLE,
3628
3729
  DEFAULT_TEXT_CURVE,
3629
3730
  EventEmitter,
3630
3731
  FontRegistry,
@@ -3646,7 +3747,9 @@ var AnnotationOverlay = class {
3646
3747
  UnitConverter,
3647
3748
  applyAspectLock,
3648
3749
  applyLayerShadow,
3750
+ applyObjectSelectionStyle,
3649
3751
  applyPatternLocks,
3752
+ applySelectionStyle,
3650
3753
  buildCurvePathData,
3651
3754
  buildPatternDataURL,
3652
3755
  captureLocks,