@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.mjs CHANGED
@@ -231,6 +231,38 @@ var LayerManager = class {
231
231
  this.emitChanged();
232
232
  this.onPropertyChanged?.();
233
233
  }
234
+ /**
235
+ * Patch a layer's host metadata. A key whose value is `undefined` is removed.
236
+ *
237
+ * `meta` is part of `LayerData` and is serialized, so a host that assigns
238
+ * `layer.meta.x` directly gets neither of the two things every other setter
239
+ * here provides: `layers:changed` (so `useLayers()` keeps returning the old
240
+ * meta) and the history checkpoint (so an undo silently reverts the write —
241
+ * the state IS versioned, it was just never committed at the moment it
242
+ * changed). Going through this method is what makes metadata behave like
243
+ * every other layer property.
244
+ */
245
+ setMeta(id, patch) {
246
+ const layer = this.get(id);
247
+ if (!layer) return;
248
+ const next = { ...layer.meta };
249
+ let changed = false;
250
+ for (const [key, value] of Object.entries(patch)) {
251
+ if (value === void 0) {
252
+ if (key in next) {
253
+ delete next[key];
254
+ changed = true;
255
+ }
256
+ continue;
257
+ }
258
+ if (next[key] !== value) changed = true;
259
+ next[key] = value;
260
+ }
261
+ if (!changed) return;
262
+ layer.meta = next;
263
+ this.emitChanged();
264
+ this.onPropertyChanged?.();
265
+ }
234
266
  clear() {
235
267
  for (const layer of this.layers) {
236
268
  this.canvas.remove(layer.fabricObject);
@@ -1584,6 +1616,43 @@ function applyLayerShadow(object, config) {
1584
1616
  });
1585
1617
  }
1586
1618
 
1619
+ // src/selection-style.ts
1620
+ var DEFAULT_SELECTION_STYLE = {
1621
+ borderColor: "#c9a96e",
1622
+ cornerColor: "#c9a96e",
1623
+ cornerStrokeColor: "#101010",
1624
+ cornerSize: 11,
1625
+ cornerStyle: "circle",
1626
+ borderWidth: 1,
1627
+ borderDashArray: null,
1628
+ padding: 0,
1629
+ marqueeFill: "rgba(201, 169, 110, 0.12)"
1630
+ };
1631
+ function applyObjectSelectionStyle(object, style, zoom) {
1632
+ const scale = zoom > 0 ? 1 / zoom : 1;
1633
+ object.set({
1634
+ cornerSize: style.cornerSize * scale,
1635
+ cornerStyle: style.cornerStyle,
1636
+ cornerColor: style.cornerColor,
1637
+ cornerStrokeColor: style.cornerStrokeColor,
1638
+ // A filled handle is what makes the stroke colour visible at all.
1639
+ transparentCorners: false,
1640
+ borderColor: style.borderColor,
1641
+ borderScaleFactor: style.borderWidth * scale,
1642
+ borderDashArray: style.borderDashArray?.map((segment) => segment * scale) ?? null,
1643
+ padding: style.padding * scale
1644
+ });
1645
+ }
1646
+ function applySelectionStyle(canvas, style, zoom) {
1647
+ for (const object of canvas.getObjects()) applyObjectSelectionStyle(object, style, zoom);
1648
+ const active = canvas.getActiveObject();
1649
+ if (active) applyObjectSelectionStyle(active, style, zoom);
1650
+ canvas.selectionColor = style.marqueeFill;
1651
+ canvas.selectionBorderColor = style.borderColor;
1652
+ canvas.selectionLineWidth = style.borderWidth * (zoom > 0 ? 1 / zoom : 1);
1653
+ canvas.requestRenderAll();
1654
+ }
1655
+
1587
1656
  // src/transform.ts
1588
1657
  var SIDE_CONTROLS = ["ml", "mr", "mt", "mb"];
1589
1658
  function applyAspectLock(object, locked) {
@@ -2340,6 +2409,7 @@ var CanvasEditor = class {
2340
2409
  fileAdapter;
2341
2410
  imageProvider;
2342
2411
  zoomLevel = 1;
2412
+ selectionStyle = { ...DEFAULT_SELECTION_STYLE };
2343
2413
  mockup = null;
2344
2414
  // The design's configured background. The live canvas background is forced
2345
2415
  // transparent while a mockup preview is shown, so this is the source of truth
@@ -2387,6 +2457,7 @@ var CanvasEditor = class {
2387
2457
  this.curves = new TextCurveManager(this.canvas, this.layers, this.history, this.events);
2388
2458
  this.maskPresets = new MaskPresetManager(this.canvas, this.layers, this.history, this.events);
2389
2459
  this.setupCanvasEvents();
2460
+ this.refreshSelectionStyle();
2390
2461
  this.history.saveImmediate();
2391
2462
  this.pages = new ProjectManager(this);
2392
2463
  this.masks = new MaskController(this);
@@ -3057,6 +3128,7 @@ var CanvasEditor = class {
3057
3128
  const next = clamp(round2(level), MIN_ZOOM, MAX_ZOOM);
3058
3129
  if (next === this.zoomLevel) return;
3059
3130
  this.zoomLevel = next;
3131
+ this.refreshSelectionStyle();
3060
3132
  this.events.emit("zoom:changed", { zoom: next });
3061
3133
  }
3062
3134
  /** @deprecated use setZoom — kept for backward compatibility. */
@@ -3205,8 +3277,31 @@ var CanvasEditor = class {
3205
3277
  this.events.removeAllListeners();
3206
3278
  this.canvas.dispose();
3207
3279
  }
3280
+ // ─── Selection style ────────────────────────────────
3281
+ /** Current look of the selection frame and its handles. */
3282
+ getSelectionStyle() {
3283
+ return { ...this.selectionStyle };
3284
+ }
3285
+ /**
3286
+ * Restyles the selection frame and resize handles. Sizes are in screen
3287
+ * pixels and stay constant across zoom levels (see `SelectionStyle`).
3288
+ */
3289
+ setSelectionStyle(style) {
3290
+ this.selectionStyle = { ...this.selectionStyle, ...style };
3291
+ this.refreshSelectionStyle();
3292
+ }
3293
+ refreshSelectionStyle() {
3294
+ applySelectionStyle(this.canvas, this.selectionStyle, this.zoomLevel);
3295
+ }
3208
3296
  // ─── Private ────────────────────────────────────────
3209
3297
  setupCanvasEvents() {
3298
+ this.canvas.on("object:added", (e) => {
3299
+ if (e.target) applyObjectSelectionStyle(e.target, this.selectionStyle, this.zoomLevel);
3300
+ });
3301
+ const styleActive = () => {
3302
+ const active = this.canvas.getActiveObject();
3303
+ if (active) applyObjectSelectionStyle(active, this.selectionStyle, this.zoomLevel);
3304
+ };
3210
3305
  this.canvas.on("object:modified", (e) => {
3211
3306
  if (!e.target) return;
3212
3307
  const layer = this.layers.findByObject(e.target);
@@ -3216,10 +3311,12 @@ var CanvasEditor = class {
3216
3311
  }
3217
3312
  });
3218
3313
  this.canvas.on("selection:created", (e) => {
3314
+ styleActive();
3219
3315
  const selected = (e.selected ?? []).map((obj) => this.layers.findByObject(obj)?.id).filter((id) => id !== void 0);
3220
3316
  this.events.emit("selection:changed", { selected });
3221
3317
  });
3222
3318
  this.canvas.on("selection:updated", (e) => {
3319
+ styleActive();
3223
3320
  const selected = (e.selected ?? []).map((obj) => this.layers.findByObject(obj)?.id).filter((id) => id !== void 0);
3224
3321
  this.events.emit("selection:changed", { selected });
3225
3322
  });
@@ -3300,6 +3397,7 @@ export {
3300
3397
  CropController,
3301
3398
  DEFAULT_LAYER_SHADOW,
3302
3399
  DEFAULT_PATTERN_CONFIG,
3400
+ DEFAULT_SELECTION_STYLE,
3303
3401
  DEFAULT_TEXT_CURVE,
3304
3402
  EventEmitter,
3305
3403
  FontRegistry,
@@ -3321,7 +3419,9 @@ export {
3321
3419
  UnitConverter,
3322
3420
  applyAspectLock,
3323
3421
  applyLayerShadow,
3422
+ applyObjectSelectionStyle,
3324
3423
  applyPatternLocks,
3424
+ applySelectionStyle,
3325
3425
  buildCurvePathData,
3326
3426
  buildPatternDataURL,
3327
3427
  captureLocks,