@pooder/kit 6.0.0 → 6.0.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.mjs CHANGED
@@ -923,9 +923,11 @@ import {
923
923
  } from "@pooder/core";
924
924
  import {
925
925
  Canvas as FabricCanvas,
926
+ Control,
926
927
  Image as FabricImage2,
927
928
  Pattern,
928
- Point
929
+ Point,
930
+ controlsUtils
929
931
  } from "fabric";
930
932
 
931
933
  // src/extensions/geometry.ts
@@ -1550,6 +1552,34 @@ function getPathBounds(pathData) {
1550
1552
  // src/extensions/image.ts
1551
1553
  var IMAGE_OBJECT_LAYER_ID = "image.user";
1552
1554
  var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
1555
+ var IMAGE_DEFAULT_CONTROL_CAPABILITIES = [
1556
+ "rotate",
1557
+ "scale"
1558
+ ];
1559
+ var IMAGE_CONTROL_DESCRIPTORS = [
1560
+ {
1561
+ key: "tl",
1562
+ capability: "rotate",
1563
+ create: () => new Control({
1564
+ x: -0.5,
1565
+ y: -0.5,
1566
+ actionName: "rotate",
1567
+ actionHandler: controlsUtils.rotationWithSnapping,
1568
+ cursorStyleHandler: controlsUtils.rotationStyleHandler
1569
+ })
1570
+ },
1571
+ {
1572
+ key: "br",
1573
+ capability: "scale",
1574
+ create: () => new Control({
1575
+ x: 0.5,
1576
+ y: 0.5,
1577
+ actionName: "scale",
1578
+ actionHandler: controlsUtils.scalingEqually,
1579
+ cursorStyleHandler: controlsUtils.scaleCursorStyleHandler
1580
+ })
1581
+ }
1582
+ ];
1553
1583
  var ImageTool = class {
1554
1584
  constructor() {
1555
1585
  this.id = "pooder.kit.image";
@@ -1568,6 +1598,7 @@ var ImageTool = class {
1568
1598
  this.renderSeq = 0;
1569
1599
  this.imageSpecs = [];
1570
1600
  this.overlaySpecs = [];
1601
+ this.imageControlsByCapabilityKey = /* @__PURE__ */ new Map();
1571
1602
  this.onToolActivated = (event) => {
1572
1603
  const before = this.isToolActive;
1573
1604
  this.syncToolActiveFromWorkbench(event.id);
@@ -1732,7 +1763,10 @@ var ImageTool = class {
1732
1763
  this.updateImages();
1733
1764
  return;
1734
1765
  }
1735
- if (e.key.startsWith("size.") || e.key.startsWith("image.frame.")) {
1766
+ if (e.key.startsWith("size.") || e.key.startsWith("image.frame.") || e.key.startsWith("image.control.")) {
1767
+ if (e.key.startsWith("image.control.")) {
1768
+ this.imageControlsByCapabilityKey.clear();
1769
+ }
1736
1770
  this.updateImages();
1737
1771
  }
1738
1772
  });
@@ -1760,6 +1794,7 @@ var ImageTool = class {
1760
1794
  this.cropShapeHatchPatternKey = void 0;
1761
1795
  this.imageSpecs = [];
1762
1796
  this.overlaySpecs = [];
1797
+ this.imageControlsByCapabilityKey.clear();
1763
1798
  this.clearRenderedImages();
1764
1799
  (_b = this.renderProducerDisposable) == null ? void 0 : _b.dispose();
1765
1800
  this.renderProducerDisposable = void 0;
@@ -1782,6 +1817,90 @@ var ImageTool = class {
1782
1817
  isImageEditingVisible() {
1783
1818
  return this.isToolActive || this.isImageSelectionActive || !!this.focusedImageId;
1784
1819
  }
1820
+ getEnabledImageControlCapabilities() {
1821
+ return IMAGE_DEFAULT_CONTROL_CAPABILITIES;
1822
+ }
1823
+ getImageControls(capabilities) {
1824
+ const normalized = [...new Set(capabilities)].sort();
1825
+ const cacheKey = normalized.join("|");
1826
+ const cached = this.imageControlsByCapabilityKey.get(cacheKey);
1827
+ if (cached) {
1828
+ return cached;
1829
+ }
1830
+ const enabled = new Set(normalized);
1831
+ const controls = {};
1832
+ IMAGE_CONTROL_DESCRIPTORS.forEach((descriptor) => {
1833
+ if (!enabled.has(descriptor.capability)) return;
1834
+ controls[descriptor.key] = descriptor.create();
1835
+ });
1836
+ this.imageControlsByCapabilityKey.set(cacheKey, controls);
1837
+ return controls;
1838
+ }
1839
+ getImageControlVisualConfig() {
1840
+ var _a, _b, _c, _d;
1841
+ const cornerSizeRaw = Number(
1842
+ (_a = this.getConfig("image.control.cornerSize", 14)) != null ? _a : 14
1843
+ );
1844
+ const touchCornerSizeRaw = Number(
1845
+ (_b = this.getConfig("image.control.touchCornerSize", 24)) != null ? _b : 24
1846
+ );
1847
+ const borderScaleFactorRaw = Number(
1848
+ (_c = this.getConfig("image.control.borderScaleFactor", 1.5)) != null ? _c : 1.5
1849
+ );
1850
+ const paddingRaw = Number(
1851
+ (_d = this.getConfig("image.control.padding", 0)) != null ? _d : 0
1852
+ );
1853
+ const cornerStyleRaw = this.getConfig(
1854
+ "image.control.cornerStyle",
1855
+ "circle"
1856
+ ) || "circle";
1857
+ const cornerStyle = cornerStyleRaw === "rect" ? "rect" : "circle";
1858
+ return {
1859
+ cornerSize: Number.isFinite(cornerSizeRaw) ? Math.max(4, Math.min(64, cornerSizeRaw)) : 14,
1860
+ touchCornerSize: Number.isFinite(touchCornerSizeRaw) ? Math.max(8, Math.min(96, touchCornerSizeRaw)) : 24,
1861
+ cornerStyle,
1862
+ cornerColor: this.getConfig("image.control.cornerColor", "#ffffff") || "#ffffff",
1863
+ cornerStrokeColor: this.getConfig("image.control.cornerStrokeColor", "#1677ff") || "#1677ff",
1864
+ transparentCorners: !!this.getConfig(
1865
+ "image.control.transparentCorners",
1866
+ false
1867
+ ),
1868
+ borderColor: this.getConfig("image.control.borderColor", "#1677ff") || "#1677ff",
1869
+ borderScaleFactor: Number.isFinite(borderScaleFactorRaw) ? Math.max(0.5, Math.min(8, borderScaleFactorRaw)) : 1.5,
1870
+ padding: Number.isFinite(paddingRaw) ? Math.max(0, Math.min(64, paddingRaw)) : 0
1871
+ };
1872
+ }
1873
+ applyImageObjectInteractionState(obj) {
1874
+ var _a;
1875
+ if (!obj) return;
1876
+ const visible = this.isImageEditingVisible();
1877
+ const visual = this.getImageControlVisualConfig();
1878
+ obj.set({
1879
+ selectable: visible,
1880
+ evented: visible,
1881
+ hasControls: visible,
1882
+ hasBorders: visible,
1883
+ lockScalingFlip: true,
1884
+ cornerSize: visual.cornerSize,
1885
+ touchCornerSize: visual.touchCornerSize,
1886
+ cornerStyle: visual.cornerStyle,
1887
+ cornerColor: visual.cornerColor,
1888
+ cornerStrokeColor: visual.cornerStrokeColor,
1889
+ transparentCorners: visual.transparentCorners,
1890
+ borderColor: visual.borderColor,
1891
+ borderScaleFactor: visual.borderScaleFactor,
1892
+ padding: visual.padding
1893
+ });
1894
+ obj.controls = this.getImageControls(
1895
+ this.getEnabledImageControlCapabilities()
1896
+ );
1897
+ (_a = obj.setCoords) == null ? void 0 : _a.call(obj);
1898
+ }
1899
+ refreshImageObjectInteractionState() {
1900
+ this.getImageObjects().forEach(
1901
+ (obj) => this.applyImageObjectInteractionState(obj)
1902
+ );
1903
+ }
1785
1904
  isDebugEnabled() {
1786
1905
  return !!this.getConfig("image.debug", false);
1787
1906
  }
@@ -1824,6 +1943,73 @@ var ImageTool = class {
1824
1943
  label: "Image Debug Log",
1825
1944
  default: false
1826
1945
  },
1946
+ {
1947
+ id: "image.control.cornerSize",
1948
+ type: "number",
1949
+ label: "Image Control Corner Size",
1950
+ min: 4,
1951
+ max: 64,
1952
+ step: 1,
1953
+ default: 14
1954
+ },
1955
+ {
1956
+ id: "image.control.touchCornerSize",
1957
+ type: "number",
1958
+ label: "Image Control Touch Corner Size",
1959
+ min: 8,
1960
+ max: 96,
1961
+ step: 1,
1962
+ default: 24
1963
+ },
1964
+ {
1965
+ id: "image.control.cornerStyle",
1966
+ type: "select",
1967
+ label: "Image Control Corner Style",
1968
+ options: ["circle", "rect"],
1969
+ default: "circle"
1970
+ },
1971
+ {
1972
+ id: "image.control.cornerColor",
1973
+ type: "color",
1974
+ label: "Image Control Corner Color",
1975
+ default: "#ffffff"
1976
+ },
1977
+ {
1978
+ id: "image.control.cornerStrokeColor",
1979
+ type: "color",
1980
+ label: "Image Control Corner Stroke Color",
1981
+ default: "#1677ff"
1982
+ },
1983
+ {
1984
+ id: "image.control.transparentCorners",
1985
+ type: "boolean",
1986
+ label: "Image Control Transparent Corners",
1987
+ default: false
1988
+ },
1989
+ {
1990
+ id: "image.control.borderColor",
1991
+ type: "color",
1992
+ label: "Image Control Border Color",
1993
+ default: "#1677ff"
1994
+ },
1995
+ {
1996
+ id: "image.control.borderScaleFactor",
1997
+ type: "number",
1998
+ label: "Image Control Border Width",
1999
+ min: 0.5,
2000
+ max: 8,
2001
+ step: 0.1,
2002
+ default: 1.5
2003
+ },
2004
+ {
2005
+ id: "image.control.padding",
2006
+ type: "number",
2007
+ label: "Image Control Padding",
2008
+ min: 0,
2009
+ max: 64,
2010
+ step: 1,
2011
+ default: 0
2012
+ },
1827
2013
  {
1828
2014
  id: "image.frame.strokeColor",
1829
2015
  type: "color",
@@ -2061,12 +2247,7 @@ var ImageTool = class {
2061
2247
  } else {
2062
2248
  const obj = this.getImageObject(id);
2063
2249
  if (obj) {
2064
- obj.set({
2065
- selectable: true,
2066
- evented: true,
2067
- hasControls: true,
2068
- hasBorders: true
2069
- });
2250
+ this.applyImageObjectInteractionState(obj);
2070
2251
  canvas.setActiveObject(obj);
2071
2252
  }
2072
2253
  }
@@ -2863,6 +3044,7 @@ var ImageTool = class {
2863
3044
  this.overlaySpecs = this.buildOverlaySpecs(frame, sceneGeometry);
2864
3045
  await this.canvasService.flushRenderFromProducers();
2865
3046
  if (seq !== this.renderSeq) return;
3047
+ this.refreshImageObjectInteractionState();
2866
3048
  renderItems.forEach((item) => {
2867
3049
  if (!this.getImageObject(item.id)) return;
2868
3050
  const resolver = this.loadResolvers.get(item.id);
@@ -5218,6 +5400,10 @@ var DielineTool = class {
5218
5400
  {
5219
5401
  type: "clipPath",
5220
5402
  id: "dieline.clip.image",
5403
+ visibility: {
5404
+ op: "not",
5405
+ expr: { op: "anySessionActive" }
5406
+ },
5221
5407
  targetPassIds: [IMAGE_OBJECT_LAYER_ID2],
5222
5408
  source: {
5223
5409
  id: "dieline.effect.clip-path",
@@ -8871,6 +9057,9 @@ function evaluateVisibilityExpr(expr, context) {
8871
9057
  if (!toolId) return false;
8872
9058
  return context.isSessionActive ? context.isSessionActive(toolId) : false;
8873
9059
  }
9060
+ if (expr.op === "anySessionActive") {
9061
+ return context.hasAnyActiveSession ? context.hasAnyActiveSession() : false;
9062
+ }
8874
9063
  if (expr.op === "layerExists") {
8875
9064
  return layerState(context, expr.layerId).exists === true;
8876
9065
  }
@@ -8904,6 +9093,7 @@ var CanvasService = class {
8904
9093
  this.visibilityRefreshScheduled = false;
8905
9094
  this.managedProducerPassIds = /* @__PURE__ */ new Set();
8906
9095
  this.managedPassMetas = /* @__PURE__ */ new Map();
9096
+ this.managedPassEffects = [];
8907
9097
  this.canvasForwardersBound = false;
8908
9098
  this.forwardSelectionCreated = (e) => {
8909
9099
  var _a;
@@ -8931,9 +9121,11 @@ var CanvasService = class {
8931
9121
  };
8932
9122
  this.onToolActivated = () => {
8933
9123
  this.applyManagedPassVisibility();
9124
+ void this.applyManagedPassEffects(void 0, { render: true });
8934
9125
  };
8935
9126
  this.onToolSessionChanged = () => {
8936
9127
  this.applyManagedPassVisibility();
9128
+ void this.applyManagedPassEffects(void 0, { render: true });
8937
9129
  };
8938
9130
  this.onCanvasObjectChanged = () => {
8939
9131
  if (this.producerApplyInProgress) return;
@@ -8998,6 +9190,7 @@ var CanvasService = class {
8998
9190
  this.renderProducers.clear();
8999
9191
  this.managedProducerPassIds.clear();
9000
9192
  this.managedPassMetas.clear();
9193
+ this.managedPassEffects = [];
9001
9194
  this.context = void 0;
9002
9195
  this.workbenchService = void 0;
9003
9196
  this.toolSessionService = void 0;
@@ -9104,6 +9297,7 @@ var CanvasService = class {
9104
9297
  return {
9105
9298
  type: "clipPath",
9106
9299
  key,
9300
+ visibility: effect.visibility,
9107
9301
  source: {
9108
9302
  ...source,
9109
9303
  id: sourceId
@@ -9211,22 +9405,30 @@ var CanvasService = class {
9211
9405
  });
9212
9406
  return state;
9213
9407
  }
9214
- applyManagedPassVisibility(options = {}) {
9408
+ isSessionActive(toolId) {
9409
+ if (!this.toolSessionService) return false;
9410
+ return this.toolSessionService.getState(toolId).status === "active";
9411
+ }
9412
+ hasAnyActiveSession() {
9215
9413
  var _a, _b;
9414
+ return (_b = (_a = this.toolSessionService) == null ? void 0 : _a.hasAnyActiveSession()) != null ? _b : false;
9415
+ }
9416
+ buildVisibilityEvalContext(layers) {
9417
+ var _a, _b;
9418
+ return {
9419
+ activeToolId: (_b = (_a = this.workbenchService) == null ? void 0 : _a.activeToolId) != null ? _b : null,
9420
+ isSessionActive: (toolId) => this.isSessionActive(toolId),
9421
+ hasAnyActiveSession: () => this.hasAnyActiveSession(),
9422
+ layers
9423
+ };
9424
+ }
9425
+ applyManagedPassVisibility(options = {}) {
9216
9426
  if (!this.managedPassMetas.size) return false;
9217
9427
  const layers = this.getPassRuntimeState();
9218
- const activeToolId = (_b = (_a = this.workbenchService) == null ? void 0 : _a.activeToolId) != null ? _b : null;
9219
- const isSessionActive = (toolId) => {
9220
- if (!this.toolSessionService) return false;
9221
- return this.toolSessionService.getState(toolId).status === "active";
9222
- };
9428
+ const context = this.buildVisibilityEvalContext(layers);
9223
9429
  let changed = false;
9224
9430
  this.managedPassMetas.forEach((meta) => {
9225
- const visible = evaluateVisibilityExpr(meta.visibility, {
9226
- activeToolId,
9227
- isSessionActive,
9228
- layers
9229
- });
9431
+ const visible = evaluateVisibilityExpr(meta.visibility, context);
9230
9432
  changed = this.setPassVisibility(meta.id, visible) || changed;
9231
9433
  });
9232
9434
  if (changed && options.render !== false) {
@@ -9294,18 +9496,24 @@ var CanvasService = class {
9294
9496
  }
9295
9497
  this.managedProducerPassIds = nextPassIds;
9296
9498
  this.managedPassMetas = nextManagedPassMetas;
9499
+ this.managedPassEffects = nextEffects;
9297
9500
  this.syncManagedPassStacking(Array.from(nextManagedPassMetas.values()));
9298
- await this.applyManagedPassEffects(nextEffects);
9501
+ await this.applyManagedPassEffects(nextEffects, { render: false });
9299
9502
  this.applyManagedPassVisibility({ render: false });
9300
9503
  } finally {
9301
9504
  this.producerApplyInProgress = false;
9302
9505
  }
9303
9506
  this.requestRenderAll();
9304
9507
  }
9305
- async applyManagedPassEffects(effects) {
9508
+ async applyManagedPassEffects(effects = this.managedPassEffects, options = {}) {
9306
9509
  const effectTargetMap = /* @__PURE__ */ new Map();
9510
+ const layers = this.getPassRuntimeState();
9511
+ const visibilityContext = this.buildVisibilityEvalContext(layers);
9307
9512
  for (const effect of effects) {
9308
9513
  if (effect.type !== "clipPath") continue;
9514
+ if (!evaluateVisibilityExpr(effect.visibility, visibilityContext)) {
9515
+ continue;
9516
+ }
9309
9517
  effect.targetPassIds.forEach((targetPassId) => {
9310
9518
  this.getPassCanvasObjects(targetPassId).forEach((obj) => {
9311
9519
  effectTargetMap.set(obj, effect);
@@ -9337,6 +9545,9 @@ var CanvasService = class {
9337
9545
  targetEffect.key
9338
9546
  );
9339
9547
  }
9548
+ if (options.render !== false) {
9549
+ this.requestRenderAll();
9550
+ }
9340
9551
  }
9341
9552
  getObject(id, passId) {
9342
9553
  const normalizedId = String(id || "").trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -900,6 +900,10 @@ export class DielineTool implements Extension {
900
900
  {
901
901
  type: "clipPath",
902
902
  id: "dieline.clip.image",
903
+ visibility: {
904
+ op: "not",
905
+ expr: { op: "anySessionActive" },
906
+ },
903
907
  targetPassIds: [IMAGE_OBJECT_LAYER_ID],
904
908
  source: {
905
909
  id: "dieline.effect.clip-path",