@pooder/kit 5.0.3 → 5.0.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @pooder/kit
2
2
 
3
+ ## 5.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - bugfix
8
+
3
9
  ## 5.0.3
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -260,7 +260,6 @@ declare class ImageTool implements Extension {
260
260
  private isToolActive;
261
261
  private isImageSelectionActive;
262
262
  private focusedImageId;
263
- private suppressSelectionClearUntil;
264
263
  private renderSeq;
265
264
  private dirtyTrackerDisposable?;
266
265
  activate(context: ExtensionContext): void;
@@ -296,8 +295,8 @@ declare class ImageTool implements Extension {
296
295
  private cloneItems;
297
296
  private emitWorkingChange;
298
297
  private generateId;
299
- private getImageIdFromActiveObject;
300
- private resolveReplaceTargetId;
298
+ private hasImageItem;
299
+ private setImageFocus;
301
300
  private addImageEntry;
302
301
  private upsertImageEntry;
303
302
  private addItemToWorkingSessionIfNeeded;
@@ -330,7 +329,6 @@ declare class ImageTool implements Extension {
330
329
  private updateImageInConfig;
331
330
  private waitImageLoaded;
332
331
  private refitImageToFrame;
333
- private focusImageSelection;
334
332
  private fitImageToArea;
335
333
  private commitWorkingImagesAsCropped;
336
334
  private exportCroppedImageByIds;
package/dist/index.d.ts CHANGED
@@ -260,7 +260,6 @@ declare class ImageTool implements Extension {
260
260
  private isToolActive;
261
261
  private isImageSelectionActive;
262
262
  private focusedImageId;
263
- private suppressSelectionClearUntil;
264
263
  private renderSeq;
265
264
  private dirtyTrackerDisposable?;
266
265
  activate(context: ExtensionContext): void;
@@ -296,8 +295,8 @@ declare class ImageTool implements Extension {
296
295
  private cloneItems;
297
296
  private emitWorkingChange;
298
297
  private generateId;
299
- private getImageIdFromActiveObject;
300
- private resolveReplaceTargetId;
298
+ private hasImageItem;
299
+ private setImageFocus;
301
300
  private addImageEntry;
302
301
  private upsertImageEntry;
303
302
  private addItemToWorkingSessionIfNeeded;
@@ -330,7 +329,6 @@ declare class ImageTool implements Extension {
330
329
  private updateImageInConfig;
331
330
  private waitImageLoaded;
332
331
  private refitImageToFrame;
333
- private focusImageSelection;
334
332
  private fitImageToArea;
335
333
  private commitWorkingImagesAsCropped;
336
334
  private exportCroppedImageByIds;
package/dist/index.js CHANGED
@@ -3790,7 +3790,6 @@ var import_core5 = require("@pooder/core");
3790
3790
  var import_fabric5 = require("fabric");
3791
3791
  var IMAGE_OBJECT_LAYER_ID2 = "image.user";
3792
3792
  var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
3793
- var IMAGE_REPLACE_GUARD_MS = 2500;
3794
3793
  var ImageTool = class {
3795
3794
  constructor() {
3796
3795
  this.id = "pooder.kit.image";
@@ -3806,18 +3805,15 @@ var ImageTool = class {
3806
3805
  this.isToolActive = false;
3807
3806
  this.isImageSelectionActive = false;
3808
3807
  this.focusedImageId = null;
3809
- this.suppressSelectionClearUntil = 0;
3810
3808
  this.renderSeq = 0;
3811
3809
  this.onToolActivated = (event) => {
3812
3810
  const before = this.isToolActive;
3813
3811
  this.syncToolActiveFromWorkbench(event.id);
3814
3812
  if (!this.isToolActive) {
3815
- const now = Date.now();
3816
- const inGuardWindow = now <= this.suppressSelectionClearUntil && !!this.focusedImageId;
3817
- if (!inGuardWindow) {
3818
- this.isImageSelectionActive = false;
3819
- this.focusedImageId = null;
3820
- }
3813
+ this.setImageFocus(null, {
3814
+ syncCanvasSelection: true,
3815
+ skipRender: true
3816
+ });
3821
3817
  }
3822
3818
  this.debug("tool:activated", {
3823
3819
  id: event.id,
@@ -3825,8 +3821,7 @@ var ImageTool = class {
3825
3821
  reason: event.reason,
3826
3822
  before,
3827
3823
  isToolActive: this.isToolActive,
3828
- focusedImageId: this.focusedImageId,
3829
- suppressSelectionClearUntil: this.suppressSelectionClearUntil
3824
+ focusedImageId: this.focusedImageId
3830
3825
  });
3831
3826
  if (!this.isToolActive && this.isDebugEnabled()) {
3832
3827
  console.trace("[ImageTool] tool deactivated trace");
@@ -3865,16 +3860,10 @@ var ImageTool = class {
3865
3860
  this.updateImages();
3866
3861
  };
3867
3862
  this.onSelectionCleared = () => {
3868
- const now = Date.now();
3869
- if (now <= this.suppressSelectionClearUntil && this.focusedImageId) {
3870
- this.debug("selection:cleared ignored", {
3871
- suppressUntil: this.suppressSelectionClearUntil,
3872
- focusedImageId: this.focusedImageId
3873
- });
3874
- return;
3875
- }
3876
- this.isImageSelectionActive = false;
3877
- this.focusedImageId = null;
3863
+ this.setImageFocus(null, {
3864
+ syncCanvasSelection: false,
3865
+ skipRender: true
3866
+ });
3878
3867
  this.debug("selection:cleared applied");
3879
3868
  this.updateImages();
3880
3869
  };
@@ -4030,7 +4019,7 @@ var ImageTool = class {
4030
4019
  id: "image.frame.strokeColor",
4031
4020
  type: "color",
4032
4021
  label: "Image Frame Stroke Color",
4033
- default: "#FF0000"
4022
+ default: "#808080"
4034
4023
  },
4035
4024
  {
4036
4025
  id: "image.frame.strokeWidth",
@@ -4046,7 +4035,7 @@ var ImageTool = class {
4046
4035
  type: "select",
4047
4036
  label: "Image Frame Stroke Style",
4048
4037
  options: ["solid", "dashed", "hidden"],
4049
- default: "solid"
4038
+ default: "dashed"
4050
4039
  },
4051
4040
  {
4052
4041
  id: "image.frame.dashLength",
@@ -4141,6 +4130,13 @@ var ImageTool = class {
4141
4130
  await this.fitImageToDefaultArea(id);
4142
4131
  }
4143
4132
  },
4133
+ {
4134
+ command: "focusImage",
4135
+ title: "Focus Image",
4136
+ handler: (id, options = {}) => {
4137
+ return this.setImageFocus(id, options);
4138
+ }
4139
+ },
4144
4140
  {
4145
4141
  command: "removeImage",
4146
4142
  title: "Remove Image",
@@ -4150,8 +4146,10 @@ var ImageTool = class {
4150
4146
  if (next.length !== this.items.length) {
4151
4147
  this.purgeSourceSizeCacheForItem(removed);
4152
4148
  if (this.focusedImageId === id) {
4153
- this.focusedImageId = null;
4154
- this.isImageSelectionActive = false;
4149
+ this.setImageFocus(null, {
4150
+ syncCanvasSelection: true,
4151
+ skipRender: true
4152
+ });
4155
4153
  }
4156
4154
  this.updateConfig(next);
4157
4155
  }
@@ -4169,8 +4167,10 @@ var ImageTool = class {
4169
4167
  title: "Clear Images",
4170
4168
  handler: () => {
4171
4169
  this.sourceSizeBySrc.clear();
4172
- this.focusedImageId = null;
4173
- this.isImageSelectionActive = false;
4170
+ this.setImageFocus(null, {
4171
+ syncCanvasSelection: true,
4172
+ skipRender: true
4173
+ });
4174
4174
  this.updateConfig([]);
4175
4175
  }
4176
4176
  },
@@ -4235,22 +4235,38 @@ var ImageTool = class {
4235
4235
  generateId() {
4236
4236
  return Math.random().toString(36).substring(2, 9);
4237
4237
  }
4238
- getImageIdFromActiveObject() {
4239
- var _a, _b, _c;
4240
- const active = (_a = this.canvasService) == null ? void 0 : _a.canvas.getActiveObject();
4241
- if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
4242
- return active.data.id;
4243
- }
4244
- return null;
4238
+ hasImageItem(id) {
4239
+ return this.items.some((item) => item.id === id) || this.workingItems.some((item) => item.id === id);
4245
4240
  }
4246
- resolveReplaceTargetId(explicitId) {
4247
- const has = (id) => !!id && this.items.some((item) => item.id === id);
4248
- if (has(explicitId)) return explicitId;
4249
- if (has(this.focusedImageId)) return this.focusedImageId;
4250
- const activeId = this.getImageIdFromActiveObject();
4251
- if (has(activeId)) return activeId;
4252
- if (this.items.length === 1) return this.items[0].id;
4253
- return null;
4241
+ setImageFocus(id, options = {}) {
4242
+ const syncCanvasSelection = options.syncCanvasSelection !== false;
4243
+ if (id && !this.hasImageItem(id)) {
4244
+ return { ok: false, reason: "image-not-found" };
4245
+ }
4246
+ this.focusedImageId = id;
4247
+ this.isImageSelectionActive = !!id;
4248
+ if (syncCanvasSelection && this.canvasService) {
4249
+ const canvas = this.canvasService.canvas;
4250
+ if (!id) {
4251
+ canvas.discardActiveObject();
4252
+ } else {
4253
+ const obj = this.getImageObject(id);
4254
+ if (obj) {
4255
+ obj.set({
4256
+ selectable: true,
4257
+ evented: true,
4258
+ hasControls: true,
4259
+ hasBorders: true
4260
+ });
4261
+ canvas.setActiveObject(obj);
4262
+ }
4263
+ }
4264
+ this.canvasService.requestRenderAll();
4265
+ }
4266
+ if (!options.skipRender) {
4267
+ this.updateImages();
4268
+ }
4269
+ return { ok: true, id };
4254
4270
  }
4255
4271
  async addImageEntry(url, options, fitOnAdd = true) {
4256
4272
  const id = this.generateId();
@@ -4260,9 +4276,6 @@ var ImageTool = class {
4260
4276
  opacity: 1,
4261
4277
  ...options
4262
4278
  });
4263
- this.focusedImageId = id;
4264
- this.isImageSelectionActive = true;
4265
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4266
4279
  const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
4267
4280
  const waitLoaded = this.waitImageLoaded(id, true);
4268
4281
  this.updateConfig([...this.items, newItem]);
@@ -4272,26 +4285,24 @@ var ImageTool = class {
4272
4285
  await this.fitImageToDefaultArea(id);
4273
4286
  }
4274
4287
  if (loaded) {
4275
- this.focusImageSelection(id);
4288
+ this.setImageFocus(id);
4276
4289
  }
4277
4290
  return id;
4278
4291
  }
4279
4292
  async upsertImageEntry(url, options = {}) {
4280
- var _a;
4281
- const mode = options.mode || "auto";
4293
+ const mode = options.mode || (options.id ? "replace" : "add");
4282
4294
  const fitOnAdd = options.fitOnAdd !== false;
4283
- if (mode === "add") {
4284
- const id2 = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4285
- return { id: id2, mode: "add" };
4286
- }
4287
- const targetId = this.resolveReplaceTargetId((_a = options.id) != null ? _a : null);
4288
- if (targetId) {
4295
+ if (mode === "replace") {
4296
+ if (!options.id) {
4297
+ throw new Error("replace-target-id-required");
4298
+ }
4299
+ const targetId = options.id;
4300
+ if (!this.hasImageItem(targetId)) {
4301
+ throw new Error("replace-target-not-found");
4302
+ }
4289
4303
  await this.updateImageInConfig(targetId, { url });
4290
4304
  return { id: targetId, mode: "replace" };
4291
4305
  }
4292
- if (mode === "replace" || options.createIfMissing === false) {
4293
- throw new Error("replace-target-not-found");
4294
- }
4295
4306
  const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4296
4307
  return { id, mode: "add" };
4297
4308
  }
@@ -4466,9 +4477,9 @@ var ImageTool = class {
4466
4477
  var _a, _b;
4467
4478
  const strokeStyleRaw = this.getConfig(
4468
4479
  "image.frame.strokeStyle",
4469
- "solid"
4470
- ) || "solid";
4471
- const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "solid";
4480
+ "dashed"
4481
+ ) || "dashed";
4482
+ const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "dashed";
4472
4483
  const strokeWidth = Number(
4473
4484
  (_a = this.getConfig("image.frame.strokeWidth", 2)) != null ? _a : 2
4474
4485
  );
@@ -4476,7 +4487,7 @@ var ImageTool = class {
4476
4487
  (_b = this.getConfig("image.frame.dashLength", 8)) != null ? _b : 8
4477
4488
  );
4478
4489
  return {
4479
- strokeColor: this.getConfig("image.frame.strokeColor", "#FF0000") || "#FF0000",
4490
+ strokeColor: this.getConfig("image.frame.strokeColor", "#808080") || "#808080",
4480
4491
  strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
4481
4492
  strokeStyle,
4482
4493
  dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
@@ -4741,8 +4752,10 @@ var ImageTool = class {
4741
4752
  const frame = this.getFrameRect();
4742
4753
  const desiredIds = new Set(renderItems.map((item) => item.id));
4743
4754
  if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
4744
- this.focusedImageId = null;
4745
- this.isImageSelectionActive = false;
4755
+ this.setImageFocus(null, {
4756
+ syncCanvasSelection: false,
4757
+ skipRender: true
4758
+ });
4746
4759
  }
4747
4760
  this.getImageObjects().forEach((obj) => {
4748
4761
  var _a, _b;
@@ -4785,8 +4798,10 @@ var ImageTool = class {
4785
4798
  next[index] = this.normalizeItem({ ...next[index], ...updates });
4786
4799
  this.workingItems = next;
4787
4800
  this.hasWorkingChanges = true;
4788
- this.isImageSelectionActive = true;
4789
- this.focusedImageId = id;
4801
+ this.setImageFocus(id, {
4802
+ syncCanvasSelection: false,
4803
+ skipRender: true
4804
+ });
4790
4805
  if (this.isToolActive) {
4791
4806
  this.updateImages();
4792
4807
  }
@@ -4815,16 +4830,13 @@ var ImageTool = class {
4815
4830
  });
4816
4831
  this.updateConfig(next);
4817
4832
  if (replacingSource) {
4818
- this.focusedImageId = id;
4819
- this.isImageSelectionActive = true;
4820
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4821
4833
  this.debug("replace:image:begin", { id, replacingUrl });
4822
4834
  this.purgeSourceSizeCacheForItem(base);
4823
4835
  const loaded = await this.waitImageLoaded(id, true);
4824
4836
  this.debug("replace:image:loaded", { id, loaded });
4825
4837
  if (loaded) {
4826
4838
  await this.refitImageToFrame(id);
4827
- this.focusImageSelection(id);
4839
+ this.setImageFocus(id);
4828
4840
  }
4829
4841
  }
4830
4842
  }
@@ -4868,29 +4880,9 @@ var ImageTool = class {
4868
4880
  this.updateConfig(next);
4869
4881
  this.workingItems = this.cloneItems(next);
4870
4882
  this.hasWorkingChanges = false;
4871
- this.isImageSelectionActive = true;
4872
- this.focusedImageId = id;
4873
4883
  this.updateImages();
4874
4884
  this.emitWorkingChange(id);
4875
4885
  }
4876
- focusImageSelection(id) {
4877
- if (!this.canvasService) return;
4878
- const obj = this.getImageObject(id);
4879
- if (!obj) return;
4880
- this.isImageSelectionActive = true;
4881
- this.focusedImageId = id;
4882
- this.suppressSelectionClearUntil = Date.now() + 700;
4883
- obj.set({
4884
- selectable: true,
4885
- evented: true,
4886
- hasControls: true,
4887
- hasBorders: true
4888
- });
4889
- this.canvasService.canvas.setActiveObject(obj);
4890
- this.debug("focus:image", { id });
4891
- this.canvasService.requestRenderAll();
4892
- this.updateImages();
4893
- }
4894
4886
  async fitImageToArea(id, area) {
4895
4887
  var _a, _b;
4896
4888
  if (!this.canvasService) return;
@@ -4940,7 +4932,6 @@ var ImageTool = class {
4940
4932
  if (!frame.width || !frame.height) {
4941
4933
  return { ok: false, reason: "frame-not-ready" };
4942
4934
  }
4943
- const focusId = this.resolveReplaceTargetId(this.focusedImageId) || (this.workingItems.length === 1 ? this.workingItems[0].id : null);
4944
4935
  const next = [];
4945
4936
  for (const item of this.workingItems) {
4946
4937
  const url = await this.exportCroppedImageByIds([item.id], {
@@ -4964,13 +4955,7 @@ var ImageTool = class {
4964
4955
  this.hasWorkingChanges = false;
4965
4956
  this.workingItems = this.cloneItems(next);
4966
4957
  this.updateConfig(next);
4967
- this.emitWorkingChange(focusId);
4968
- if (focusId) {
4969
- this.focusedImageId = focusId;
4970
- this.isImageSelectionActive = true;
4971
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4972
- this.focusImageSelection(focusId);
4973
- }
4958
+ this.emitWorkingChange(this.focusedImageId);
4974
4959
  return { ok: true };
4975
4960
  }
4976
4961
  async exportCroppedImageByIds(imageIds, options) {
package/dist/index.mjs CHANGED
@@ -3741,7 +3741,6 @@ import {
3741
3741
  import { Canvas as FabricCanvas2, Image as FabricImage, Point as Point2 } from "fabric";
3742
3742
  var IMAGE_OBJECT_LAYER_ID2 = "image.user";
3743
3743
  var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
3744
- var IMAGE_REPLACE_GUARD_MS = 2500;
3745
3744
  var ImageTool = class {
3746
3745
  constructor() {
3747
3746
  this.id = "pooder.kit.image";
@@ -3757,18 +3756,15 @@ var ImageTool = class {
3757
3756
  this.isToolActive = false;
3758
3757
  this.isImageSelectionActive = false;
3759
3758
  this.focusedImageId = null;
3760
- this.suppressSelectionClearUntil = 0;
3761
3759
  this.renderSeq = 0;
3762
3760
  this.onToolActivated = (event) => {
3763
3761
  const before = this.isToolActive;
3764
3762
  this.syncToolActiveFromWorkbench(event.id);
3765
3763
  if (!this.isToolActive) {
3766
- const now = Date.now();
3767
- const inGuardWindow = now <= this.suppressSelectionClearUntil && !!this.focusedImageId;
3768
- if (!inGuardWindow) {
3769
- this.isImageSelectionActive = false;
3770
- this.focusedImageId = null;
3771
- }
3764
+ this.setImageFocus(null, {
3765
+ syncCanvasSelection: true,
3766
+ skipRender: true
3767
+ });
3772
3768
  }
3773
3769
  this.debug("tool:activated", {
3774
3770
  id: event.id,
@@ -3776,8 +3772,7 @@ var ImageTool = class {
3776
3772
  reason: event.reason,
3777
3773
  before,
3778
3774
  isToolActive: this.isToolActive,
3779
- focusedImageId: this.focusedImageId,
3780
- suppressSelectionClearUntil: this.suppressSelectionClearUntil
3775
+ focusedImageId: this.focusedImageId
3781
3776
  });
3782
3777
  if (!this.isToolActive && this.isDebugEnabled()) {
3783
3778
  console.trace("[ImageTool] tool deactivated trace");
@@ -3816,16 +3811,10 @@ var ImageTool = class {
3816
3811
  this.updateImages();
3817
3812
  };
3818
3813
  this.onSelectionCleared = () => {
3819
- const now = Date.now();
3820
- if (now <= this.suppressSelectionClearUntil && this.focusedImageId) {
3821
- this.debug("selection:cleared ignored", {
3822
- suppressUntil: this.suppressSelectionClearUntil,
3823
- focusedImageId: this.focusedImageId
3824
- });
3825
- return;
3826
- }
3827
- this.isImageSelectionActive = false;
3828
- this.focusedImageId = null;
3814
+ this.setImageFocus(null, {
3815
+ syncCanvasSelection: false,
3816
+ skipRender: true
3817
+ });
3829
3818
  this.debug("selection:cleared applied");
3830
3819
  this.updateImages();
3831
3820
  };
@@ -3981,7 +3970,7 @@ var ImageTool = class {
3981
3970
  id: "image.frame.strokeColor",
3982
3971
  type: "color",
3983
3972
  label: "Image Frame Stroke Color",
3984
- default: "#FF0000"
3973
+ default: "#808080"
3985
3974
  },
3986
3975
  {
3987
3976
  id: "image.frame.strokeWidth",
@@ -3997,7 +3986,7 @@ var ImageTool = class {
3997
3986
  type: "select",
3998
3987
  label: "Image Frame Stroke Style",
3999
3988
  options: ["solid", "dashed", "hidden"],
4000
- default: "solid"
3989
+ default: "dashed"
4001
3990
  },
4002
3991
  {
4003
3992
  id: "image.frame.dashLength",
@@ -4092,6 +4081,13 @@ var ImageTool = class {
4092
4081
  await this.fitImageToDefaultArea(id);
4093
4082
  }
4094
4083
  },
4084
+ {
4085
+ command: "focusImage",
4086
+ title: "Focus Image",
4087
+ handler: (id, options = {}) => {
4088
+ return this.setImageFocus(id, options);
4089
+ }
4090
+ },
4095
4091
  {
4096
4092
  command: "removeImage",
4097
4093
  title: "Remove Image",
@@ -4101,8 +4097,10 @@ var ImageTool = class {
4101
4097
  if (next.length !== this.items.length) {
4102
4098
  this.purgeSourceSizeCacheForItem(removed);
4103
4099
  if (this.focusedImageId === id) {
4104
- this.focusedImageId = null;
4105
- this.isImageSelectionActive = false;
4100
+ this.setImageFocus(null, {
4101
+ syncCanvasSelection: true,
4102
+ skipRender: true
4103
+ });
4106
4104
  }
4107
4105
  this.updateConfig(next);
4108
4106
  }
@@ -4120,8 +4118,10 @@ var ImageTool = class {
4120
4118
  title: "Clear Images",
4121
4119
  handler: () => {
4122
4120
  this.sourceSizeBySrc.clear();
4123
- this.focusedImageId = null;
4124
- this.isImageSelectionActive = false;
4121
+ this.setImageFocus(null, {
4122
+ syncCanvasSelection: true,
4123
+ skipRender: true
4124
+ });
4125
4125
  this.updateConfig([]);
4126
4126
  }
4127
4127
  },
@@ -4186,22 +4186,38 @@ var ImageTool = class {
4186
4186
  generateId() {
4187
4187
  return Math.random().toString(36).substring(2, 9);
4188
4188
  }
4189
- getImageIdFromActiveObject() {
4190
- var _a, _b, _c;
4191
- const active = (_a = this.canvasService) == null ? void 0 : _a.canvas.getActiveObject();
4192
- if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
4193
- return active.data.id;
4194
- }
4195
- return null;
4189
+ hasImageItem(id) {
4190
+ return this.items.some((item) => item.id === id) || this.workingItems.some((item) => item.id === id);
4196
4191
  }
4197
- resolveReplaceTargetId(explicitId) {
4198
- const has = (id) => !!id && this.items.some((item) => item.id === id);
4199
- if (has(explicitId)) return explicitId;
4200
- if (has(this.focusedImageId)) return this.focusedImageId;
4201
- const activeId = this.getImageIdFromActiveObject();
4202
- if (has(activeId)) return activeId;
4203
- if (this.items.length === 1) return this.items[0].id;
4204
- return null;
4192
+ setImageFocus(id, options = {}) {
4193
+ const syncCanvasSelection = options.syncCanvasSelection !== false;
4194
+ if (id && !this.hasImageItem(id)) {
4195
+ return { ok: false, reason: "image-not-found" };
4196
+ }
4197
+ this.focusedImageId = id;
4198
+ this.isImageSelectionActive = !!id;
4199
+ if (syncCanvasSelection && this.canvasService) {
4200
+ const canvas = this.canvasService.canvas;
4201
+ if (!id) {
4202
+ canvas.discardActiveObject();
4203
+ } else {
4204
+ const obj = this.getImageObject(id);
4205
+ if (obj) {
4206
+ obj.set({
4207
+ selectable: true,
4208
+ evented: true,
4209
+ hasControls: true,
4210
+ hasBorders: true
4211
+ });
4212
+ canvas.setActiveObject(obj);
4213
+ }
4214
+ }
4215
+ this.canvasService.requestRenderAll();
4216
+ }
4217
+ if (!options.skipRender) {
4218
+ this.updateImages();
4219
+ }
4220
+ return { ok: true, id };
4205
4221
  }
4206
4222
  async addImageEntry(url, options, fitOnAdd = true) {
4207
4223
  const id = this.generateId();
@@ -4211,9 +4227,6 @@ var ImageTool = class {
4211
4227
  opacity: 1,
4212
4228
  ...options
4213
4229
  });
4214
- this.focusedImageId = id;
4215
- this.isImageSelectionActive = true;
4216
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4217
4230
  const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
4218
4231
  const waitLoaded = this.waitImageLoaded(id, true);
4219
4232
  this.updateConfig([...this.items, newItem]);
@@ -4223,26 +4236,24 @@ var ImageTool = class {
4223
4236
  await this.fitImageToDefaultArea(id);
4224
4237
  }
4225
4238
  if (loaded) {
4226
- this.focusImageSelection(id);
4239
+ this.setImageFocus(id);
4227
4240
  }
4228
4241
  return id;
4229
4242
  }
4230
4243
  async upsertImageEntry(url, options = {}) {
4231
- var _a;
4232
- const mode = options.mode || "auto";
4244
+ const mode = options.mode || (options.id ? "replace" : "add");
4233
4245
  const fitOnAdd = options.fitOnAdd !== false;
4234
- if (mode === "add") {
4235
- const id2 = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4236
- return { id: id2, mode: "add" };
4237
- }
4238
- const targetId = this.resolveReplaceTargetId((_a = options.id) != null ? _a : null);
4239
- if (targetId) {
4246
+ if (mode === "replace") {
4247
+ if (!options.id) {
4248
+ throw new Error("replace-target-id-required");
4249
+ }
4250
+ const targetId = options.id;
4251
+ if (!this.hasImageItem(targetId)) {
4252
+ throw new Error("replace-target-not-found");
4253
+ }
4240
4254
  await this.updateImageInConfig(targetId, { url });
4241
4255
  return { id: targetId, mode: "replace" };
4242
4256
  }
4243
- if (mode === "replace" || options.createIfMissing === false) {
4244
- throw new Error("replace-target-not-found");
4245
- }
4246
4257
  const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4247
4258
  return { id, mode: "add" };
4248
4259
  }
@@ -4417,9 +4428,9 @@ var ImageTool = class {
4417
4428
  var _a, _b;
4418
4429
  const strokeStyleRaw = this.getConfig(
4419
4430
  "image.frame.strokeStyle",
4420
- "solid"
4421
- ) || "solid";
4422
- const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "solid";
4431
+ "dashed"
4432
+ ) || "dashed";
4433
+ const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "dashed";
4423
4434
  const strokeWidth = Number(
4424
4435
  (_a = this.getConfig("image.frame.strokeWidth", 2)) != null ? _a : 2
4425
4436
  );
@@ -4427,7 +4438,7 @@ var ImageTool = class {
4427
4438
  (_b = this.getConfig("image.frame.dashLength", 8)) != null ? _b : 8
4428
4439
  );
4429
4440
  return {
4430
- strokeColor: this.getConfig("image.frame.strokeColor", "#FF0000") || "#FF0000",
4441
+ strokeColor: this.getConfig("image.frame.strokeColor", "#808080") || "#808080",
4431
4442
  strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
4432
4443
  strokeStyle,
4433
4444
  dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
@@ -4692,8 +4703,10 @@ var ImageTool = class {
4692
4703
  const frame = this.getFrameRect();
4693
4704
  const desiredIds = new Set(renderItems.map((item) => item.id));
4694
4705
  if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
4695
- this.focusedImageId = null;
4696
- this.isImageSelectionActive = false;
4706
+ this.setImageFocus(null, {
4707
+ syncCanvasSelection: false,
4708
+ skipRender: true
4709
+ });
4697
4710
  }
4698
4711
  this.getImageObjects().forEach((obj) => {
4699
4712
  var _a, _b;
@@ -4736,8 +4749,10 @@ var ImageTool = class {
4736
4749
  next[index] = this.normalizeItem({ ...next[index], ...updates });
4737
4750
  this.workingItems = next;
4738
4751
  this.hasWorkingChanges = true;
4739
- this.isImageSelectionActive = true;
4740
- this.focusedImageId = id;
4752
+ this.setImageFocus(id, {
4753
+ syncCanvasSelection: false,
4754
+ skipRender: true
4755
+ });
4741
4756
  if (this.isToolActive) {
4742
4757
  this.updateImages();
4743
4758
  }
@@ -4766,16 +4781,13 @@ var ImageTool = class {
4766
4781
  });
4767
4782
  this.updateConfig(next);
4768
4783
  if (replacingSource) {
4769
- this.focusedImageId = id;
4770
- this.isImageSelectionActive = true;
4771
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4772
4784
  this.debug("replace:image:begin", { id, replacingUrl });
4773
4785
  this.purgeSourceSizeCacheForItem(base);
4774
4786
  const loaded = await this.waitImageLoaded(id, true);
4775
4787
  this.debug("replace:image:loaded", { id, loaded });
4776
4788
  if (loaded) {
4777
4789
  await this.refitImageToFrame(id);
4778
- this.focusImageSelection(id);
4790
+ this.setImageFocus(id);
4779
4791
  }
4780
4792
  }
4781
4793
  }
@@ -4819,29 +4831,9 @@ var ImageTool = class {
4819
4831
  this.updateConfig(next);
4820
4832
  this.workingItems = this.cloneItems(next);
4821
4833
  this.hasWorkingChanges = false;
4822
- this.isImageSelectionActive = true;
4823
- this.focusedImageId = id;
4824
4834
  this.updateImages();
4825
4835
  this.emitWorkingChange(id);
4826
4836
  }
4827
- focusImageSelection(id) {
4828
- if (!this.canvasService) return;
4829
- const obj = this.getImageObject(id);
4830
- if (!obj) return;
4831
- this.isImageSelectionActive = true;
4832
- this.focusedImageId = id;
4833
- this.suppressSelectionClearUntil = Date.now() + 700;
4834
- obj.set({
4835
- selectable: true,
4836
- evented: true,
4837
- hasControls: true,
4838
- hasBorders: true
4839
- });
4840
- this.canvasService.canvas.setActiveObject(obj);
4841
- this.debug("focus:image", { id });
4842
- this.canvasService.requestRenderAll();
4843
- this.updateImages();
4844
- }
4845
4837
  async fitImageToArea(id, area) {
4846
4838
  var _a, _b;
4847
4839
  if (!this.canvasService) return;
@@ -4891,7 +4883,6 @@ var ImageTool = class {
4891
4883
  if (!frame.width || !frame.height) {
4892
4884
  return { ok: false, reason: "frame-not-ready" };
4893
4885
  }
4894
- const focusId = this.resolveReplaceTargetId(this.focusedImageId) || (this.workingItems.length === 1 ? this.workingItems[0].id : null);
4895
4886
  const next = [];
4896
4887
  for (const item of this.workingItems) {
4897
4888
  const url = await this.exportCroppedImageByIds([item.id], {
@@ -4915,13 +4906,7 @@ var ImageTool = class {
4915
4906
  this.hasWorkingChanges = false;
4916
4907
  this.workingItems = this.cloneItems(next);
4917
4908
  this.updateConfig(next);
4918
- this.emitWorkingChange(focusId);
4919
- if (focusId) {
4920
- this.focusedImageId = focusId;
4921
- this.isImageSelectionActive = true;
4922
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4923
- this.focusImageSelection(focusId);
4924
- }
4909
+ this.emitWorkingChange(this.focusedImageId);
4925
4910
  return { ok: true };
4926
4911
  }
4927
4912
  async exportCroppedImageByIds(imageIds, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/image.ts CHANGED
@@ -57,8 +57,7 @@ interface FrameVisualConfig {
57
57
 
58
58
  interface UpsertImageOptions {
59
59
  id?: string;
60
- mode?: "auto" | "replace" | "add";
61
- createIfMissing?: boolean;
60
+ mode?: "replace" | "add";
62
61
  addOptions?: Partial<ImageItem>;
63
62
  fitOnAdd?: boolean;
64
63
  }
@@ -108,7 +107,6 @@ interface DetectFromFrameOptions {
108
107
 
109
108
  const IMAGE_OBJECT_LAYER_ID = "image.user";
110
109
  const IMAGE_OVERLAY_LAYER_ID = "image-overlay";
111
- const IMAGE_REPLACE_GUARD_MS = 2500;
112
110
  const IMAGE_DETECT_EXPAND_DEFAULT = 30;
113
111
  const IMAGE_DETECT_SIMPLIFY_TOLERANCE_DEFAULT = 2;
114
112
  const IMAGE_DETECT_MULTIPLIER_DEFAULT = 2;
@@ -131,7 +129,6 @@ export class ImageTool implements Extension {
131
129
  private isToolActive = false;
132
130
  private isImageSelectionActive = false;
133
131
  private focusedImageId: string | null = null;
134
- private suppressSelectionClearUntil = 0;
135
132
  private renderSeq = 0;
136
133
  private dirtyTrackerDisposable?: { dispose(): void };
137
134
 
@@ -218,13 +215,10 @@ export class ImageTool implements Extension {
218
215
  const before = this.isToolActive;
219
216
  this.syncToolActiveFromWorkbench(event.id);
220
217
  if (!this.isToolActive) {
221
- const now = Date.now();
222
- const inGuardWindow =
223
- now <= this.suppressSelectionClearUntil && !!this.focusedImageId;
224
- if (!inGuardWindow) {
225
- this.isImageSelectionActive = false;
226
- this.focusedImageId = null;
227
- }
218
+ this.setImageFocus(null, {
219
+ syncCanvasSelection: true,
220
+ skipRender: true,
221
+ });
228
222
  }
229
223
  this.debug("tool:activated", {
230
224
  id: event.id,
@@ -233,7 +227,6 @@ export class ImageTool implements Extension {
233
227
  before,
234
228
  isToolActive: this.isToolActive,
235
229
  focusedImageId: this.focusedImageId,
236
- suppressSelectionClearUntil: this.suppressSelectionClearUntil,
237
230
  });
238
231
  if (!this.isToolActive && this.isDebugEnabled()) {
239
232
  console.trace("[ImageTool] tool deactivated trace");
@@ -271,16 +264,10 @@ export class ImageTool implements Extension {
271
264
  };
272
265
 
273
266
  private onSelectionCleared = () => {
274
- const now = Date.now();
275
- if (now <= this.suppressSelectionClearUntil && this.focusedImageId) {
276
- this.debug("selection:cleared ignored", {
277
- suppressUntil: this.suppressSelectionClearUntil,
278
- focusedImageId: this.focusedImageId,
279
- });
280
- return;
281
- }
282
- this.isImageSelectionActive = false;
283
- this.focusedImageId = null;
267
+ this.setImageFocus(null, {
268
+ syncCanvasSelection: false,
269
+ skipRender: true,
270
+ });
284
271
  this.debug("selection:cleared applied");
285
272
  this.updateImages();
286
273
  };
@@ -353,7 +340,7 @@ export class ImageTool implements Extension {
353
340
  id: "image.frame.strokeColor",
354
341
  type: "color",
355
342
  label: "Image Frame Stroke Color",
356
- default: "#FF0000",
343
+ default: "#808080",
357
344
  },
358
345
  {
359
346
  id: "image.frame.strokeWidth",
@@ -369,7 +356,7 @@ export class ImageTool implements Extension {
369
356
  type: "select",
370
357
  label: "Image Frame Stroke Style",
371
358
  options: ["solid", "dashed", "hidden"],
372
- default: "solid",
359
+ default: "dashed",
373
360
  },
374
361
  {
375
362
  id: "image.frame.dashLength",
@@ -474,6 +461,16 @@ export class ImageTool implements Extension {
474
461
  await this.fitImageToDefaultArea(id);
475
462
  },
476
463
  },
464
+ {
465
+ command: "focusImage",
466
+ title: "Focus Image",
467
+ handler: (
468
+ id: string | null,
469
+ options: { syncCanvasSelection?: boolean } = {},
470
+ ) => {
471
+ return this.setImageFocus(id, options);
472
+ },
473
+ },
477
474
  {
478
475
  command: "removeImage",
479
476
  title: "Remove Image",
@@ -483,8 +480,10 @@ export class ImageTool implements Extension {
483
480
  if (next.length !== this.items.length) {
484
481
  this.purgeSourceSizeCacheForItem(removed);
485
482
  if (this.focusedImageId === id) {
486
- this.focusedImageId = null;
487
- this.isImageSelectionActive = false;
483
+ this.setImageFocus(null, {
484
+ syncCanvasSelection: true,
485
+ skipRender: true,
486
+ });
488
487
  }
489
488
  this.updateConfig(next);
490
489
  }
@@ -506,8 +505,10 @@ export class ImageTool implements Extension {
506
505
  title: "Clear Images",
507
506
  handler: () => {
508
507
  this.sourceSizeBySrc.clear();
509
- this.focusedImageId = null;
510
- this.isImageSelectionActive = false;
508
+ this.setImageFocus(null, {
509
+ syncCanvasSelection: true,
510
+ skipRender: true,
511
+ });
511
512
  this.updateConfig([]);
512
513
  },
513
514
  },
@@ -584,29 +585,50 @@ export class ImageTool implements Extension {
584
585
  return Math.random().toString(36).substring(2, 9);
585
586
  }
586
587
 
587
- private getImageIdFromActiveObject(): string | null {
588
- const active = this.canvasService?.canvas.getActiveObject() as any;
589
- if (
590
- active?.data?.layerId === IMAGE_OBJECT_LAYER_ID &&
591
- typeof active?.data?.id === "string"
592
- ) {
593
- return active.data.id;
594
- }
595
- return null;
588
+ private hasImageItem(id: string): boolean {
589
+ return (
590
+ this.items.some((item) => item.id === id) ||
591
+ this.workingItems.some((item) => item.id === id)
592
+ );
596
593
  }
597
594
 
598
- private resolveReplaceTargetId(explicitId?: string | null): string | null {
599
- const has = (id: string | null | undefined) =>
600
- !!id && this.items.some((item) => item.id === id);
595
+ private setImageFocus(
596
+ id: string | null,
597
+ options: { syncCanvasSelection?: boolean; skipRender?: boolean } = {},
598
+ ) {
599
+ const syncCanvasSelection = options.syncCanvasSelection !== false;
601
600
 
602
- if (has(explicitId)) return explicitId as string;
603
- if (has(this.focusedImageId)) return this.focusedImageId as string;
601
+ if (id && !this.hasImageItem(id)) {
602
+ return { ok: false, reason: "image-not-found" as const };
603
+ }
604
604
 
605
- const activeId = this.getImageIdFromActiveObject();
606
- if (has(activeId)) return activeId;
605
+ this.focusedImageId = id;
606
+ this.isImageSelectionActive = !!id;
607
+
608
+ if (syncCanvasSelection && this.canvasService) {
609
+ const canvas = this.canvasService.canvas;
610
+ if (!id) {
611
+ canvas.discardActiveObject();
612
+ } else {
613
+ const obj = this.getImageObject(id);
614
+ if (obj) {
615
+ obj.set({
616
+ selectable: true,
617
+ evented: true,
618
+ hasControls: true,
619
+ hasBorders: true,
620
+ });
621
+ canvas.setActiveObject(obj);
622
+ }
623
+ }
624
+ this.canvasService.requestRenderAll();
625
+ }
626
+
627
+ if (!options.skipRender) {
628
+ this.updateImages();
629
+ }
607
630
 
608
- if (this.items.length === 1) return this.items[0].id;
609
- return null;
631
+ return { ok: true, id };
610
632
  }
611
633
 
612
634
  private async addImageEntry(
@@ -622,9 +644,6 @@ export class ImageTool implements Extension {
622
644
  ...options,
623
645
  } as ImageItem);
624
646
 
625
- this.focusedImageId = id;
626
- this.isImageSelectionActive = true;
627
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
628
647
  const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
629
648
  const waitLoaded = this.waitImageLoaded(id, true);
630
649
  this.updateConfig([...this.items, newItem]);
@@ -634,7 +653,7 @@ export class ImageTool implements Extension {
634
653
  await this.fitImageToDefaultArea(id);
635
654
  }
636
655
  if (loaded) {
637
- this.focusImageSelection(id);
656
+ this.setImageFocus(id);
638
657
  }
639
658
  return id;
640
659
  }
@@ -643,23 +662,20 @@ export class ImageTool implements Extension {
643
662
  url: string,
644
663
  options: UpsertImageOptions = {},
645
664
  ): Promise<{ id: string; mode: "replace" | "add" }> {
646
- const mode = options.mode || "auto";
665
+ const mode = options.mode || (options.id ? "replace" : "add");
647
666
  const fitOnAdd = options.fitOnAdd !== false;
648
- if (mode === "add") {
649
- const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
650
- return { id, mode: "add" };
651
- }
652
-
653
- const targetId = this.resolveReplaceTargetId(options.id ?? null);
654
- if (targetId) {
667
+ if (mode === "replace") {
668
+ if (!options.id) {
669
+ throw new Error("replace-target-id-required");
670
+ }
671
+ const targetId = options.id;
672
+ if (!this.hasImageItem(targetId)) {
673
+ throw new Error("replace-target-not-found");
674
+ }
655
675
  await this.updateImageInConfig(targetId, { url });
656
676
  return { id: targetId, mode: "replace" };
657
677
  }
658
678
 
659
- if (mode === "replace" || options.createIfMissing === false) {
660
- throw new Error("replace-target-not-found");
661
- }
662
-
663
679
  const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
664
680
  return { id, mode: "add" };
665
681
  }
@@ -870,12 +886,12 @@ export class ImageTool implements Extension {
870
886
  private getFrameVisualConfig(): FrameVisualConfig {
871
887
  const strokeStyleRaw = (this.getConfig<string>(
872
888
  "image.frame.strokeStyle",
873
- "solid",
874
- ) || "solid") as string;
889
+ "dashed",
890
+ ) || "dashed") as string;
875
891
  const strokeStyle: "solid" | "dashed" | "hidden" =
876
892
  strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden"
877
893
  ? strokeStyleRaw
878
- : "solid";
894
+ : "dashed";
879
895
 
880
896
  const strokeWidth = Number(
881
897
  this.getConfig<number>("image.frame.strokeWidth", 2) ?? 2,
@@ -886,8 +902,8 @@ export class ImageTool implements Extension {
886
902
 
887
903
  return {
888
904
  strokeColor:
889
- this.getConfig<string>("image.frame.strokeColor", "#FF0000") ||
890
- "#FF0000",
905
+ this.getConfig<string>("image.frame.strokeColor", "#808080") ||
906
+ "#808080",
891
907
  strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
892
908
  strokeStyle,
893
909
  dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
@@ -1199,8 +1215,10 @@ export class ImageTool implements Extension {
1199
1215
  const frame = this.getFrameRect();
1200
1216
  const desiredIds = new Set(renderItems.map((item) => item.id));
1201
1217
  if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
1202
- this.focusedImageId = null;
1203
- this.isImageSelectionActive = false;
1218
+ this.setImageFocus(null, {
1219
+ syncCanvasSelection: false,
1220
+ skipRender: true,
1221
+ });
1204
1222
  }
1205
1223
 
1206
1224
  this.getImageObjects().forEach((obj: any) => {
@@ -1281,8 +1299,10 @@ export class ImageTool implements Extension {
1281
1299
  next[index] = this.normalizeItem({ ...next[index], ...updates });
1282
1300
  this.workingItems = next;
1283
1301
  this.hasWorkingChanges = true;
1284
- this.isImageSelectionActive = true;
1285
- this.focusedImageId = id;
1302
+ this.setImageFocus(id, {
1303
+ syncCanvasSelection: false,
1304
+ skipRender: true,
1305
+ });
1286
1306
  if (this.isToolActive) {
1287
1307
  this.updateImages();
1288
1308
  }
@@ -1318,16 +1338,13 @@ export class ImageTool implements Extension {
1318
1338
  this.updateConfig(next);
1319
1339
 
1320
1340
  if (replacingSource) {
1321
- this.focusedImageId = id;
1322
- this.isImageSelectionActive = true;
1323
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
1324
1341
  this.debug("replace:image:begin", { id, replacingUrl });
1325
1342
  this.purgeSourceSizeCacheForItem(base);
1326
1343
  const loaded = await this.waitImageLoaded(id, true);
1327
1344
  this.debug("replace:image:loaded", { id, loaded });
1328
1345
  if (loaded) {
1329
1346
  await this.refitImageToFrame(id);
1330
- this.focusImageSelection(id);
1347
+ this.setImageFocus(id);
1331
1348
  }
1332
1349
  }
1333
1350
  }
@@ -1380,32 +1397,10 @@ export class ImageTool implements Extension {
1380
1397
  this.updateConfig(next);
1381
1398
  this.workingItems = this.cloneItems(next);
1382
1399
  this.hasWorkingChanges = false;
1383
- this.isImageSelectionActive = true;
1384
- this.focusedImageId = id;
1385
1400
  this.updateImages();
1386
1401
  this.emitWorkingChange(id);
1387
1402
  }
1388
1403
 
1389
- private focusImageSelection(id: string) {
1390
- if (!this.canvasService) return;
1391
- const obj = this.getImageObject(id);
1392
- if (!obj) return;
1393
-
1394
- this.isImageSelectionActive = true;
1395
- this.focusedImageId = id;
1396
- this.suppressSelectionClearUntil = Date.now() + 700;
1397
- obj.set({
1398
- selectable: true,
1399
- evented: true,
1400
- hasControls: true,
1401
- hasBorders: true,
1402
- });
1403
- this.canvasService.canvas.setActiveObject(obj);
1404
- this.debug("focus:image", { id });
1405
- this.canvasService.requestRenderAll();
1406
- this.updateImages();
1407
- }
1408
-
1409
1404
  private async fitImageToArea(
1410
1405
  id: string,
1411
1406
  area: { width: number; height: number; left?: number; top?: number },
@@ -1473,10 +1468,6 @@ export class ImageTool implements Extension {
1473
1468
  return { ok: false, reason: "frame-not-ready" };
1474
1469
  }
1475
1470
 
1476
- const focusId =
1477
- this.resolveReplaceTargetId(this.focusedImageId) ||
1478
- (this.workingItems.length === 1 ? this.workingItems[0].id : null);
1479
-
1480
1471
  const next: ImageItem[] = [];
1481
1472
  for (const item of this.workingItems) {
1482
1473
  const url = await this.exportCroppedImageByIds([item.id], {
@@ -1502,13 +1493,7 @@ export class ImageTool implements Extension {
1502
1493
  this.hasWorkingChanges = false;
1503
1494
  this.workingItems = this.cloneItems(next);
1504
1495
  this.updateConfig(next);
1505
- this.emitWorkingChange(focusId);
1506
- if (focusId) {
1507
- this.focusedImageId = focusId;
1508
- this.isImageSelectionActive = true;
1509
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
1510
- this.focusImageSelection(focusId);
1511
- }
1496
+ this.emitWorkingChange(this.focusedImageId);
1512
1497
  return { ok: true };
1513
1498
  }
1514
1499