@nasser-sw/fabric 7.0.1-beta30 → 7.0.1-beta32

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.
Files changed (40) hide show
  1. package/.history/package_20251227172435.json +164 -0
  2. package/.history/package_20251227191616.json +164 -0
  3. package/dist/index.js +58 -67
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.js +1 -1
  6. package/dist/index.min.js.map +1 -1
  7. package/dist/index.min.mjs +1 -1
  8. package/dist/index.min.mjs.map +1 -1
  9. package/dist/index.mjs +58 -67
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/index.node.cjs +58 -67
  12. package/dist/index.node.cjs.map +1 -1
  13. package/dist/index.node.mjs +58 -67
  14. package/dist/index.node.mjs.map +1 -1
  15. package/dist/package.json.min.mjs +1 -1
  16. package/dist/package.json.mjs +1 -1
  17. package/dist/src/controls/expandControl.d.ts +4 -0
  18. package/dist/src/controls/expandControl.d.ts.map +1 -1
  19. package/dist/src/controls/expandControl.min.mjs +1 -1
  20. package/dist/src/controls/expandControl.min.mjs.map +1 -1
  21. package/dist/src/controls/expandControl.mjs +37 -47
  22. package/dist/src/controls/expandControl.mjs.map +1 -1
  23. package/dist/src/shapes/Frame.d.ts.map +1 -1
  24. package/dist/src/shapes/Frame.mjs +2 -5
  25. package/dist/src/shapes/Frame.mjs.map +1 -1
  26. package/dist/src/shapes/Object/InteractiveObject.d.ts +6 -2
  27. package/dist/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
  28. package/dist/src/shapes/Object/InteractiveObject.min.mjs +1 -1
  29. package/dist/src/shapes/Object/InteractiveObject.min.mjs.map +1 -1
  30. package/dist/src/shapes/Object/InteractiveObject.mjs +18 -14
  31. package/dist/src/shapes/Object/InteractiveObject.mjs.map +1 -1
  32. package/dist-extensions/src/controls/expandControl.d.ts +4 -0
  33. package/dist-extensions/src/controls/expandControl.d.ts.map +1 -1
  34. package/dist-extensions/src/shapes/Frame.d.ts.map +1 -1
  35. package/dist-extensions/src/shapes/Object/InteractiveObject.d.ts +6 -2
  36. package/dist-extensions/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/controls/expandControl.ts +37 -47
  39. package/src/shapes/Frame.ts +2 -5
  40. package/src/shapes/Object/InteractiveObject.ts +18 -14
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "@nasser-sw/fabric",
3
+ "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
4
+ "homepage": "http://fabricjs.com/",
5
+ "version": "7.0.1-beta31",
6
+ "author": "Juriy Zaytsev <kangax@gmail.com>",
7
+ "contributors": [
8
+ {
9
+ "name": "Andrea Bogazzi",
10
+ "email": "andreabogazzi79@gmail.com",
11
+ "url": "https://github.com/asturur"
12
+ },
13
+ {
14
+ "name": "Shachar Nencel",
15
+ "email": "shacharnen@gmail.com",
16
+ "url": "https://github.com/ShaMan123"
17
+ },
18
+ {
19
+ "name": "Steve Eberhardt",
20
+ "email": "melchiar2@gmail.com",
21
+ "url": "https://github.com/melchiar"
22
+ }
23
+ ],
24
+ "keywords": [
25
+ "canvas",
26
+ "graphic",
27
+ "graphics",
28
+ "SVG",
29
+ "node-canvas",
30
+ "parser",
31
+ "HTML5",
32
+ "object model"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/fabricjs/fabric.js"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/fabricjs/fabric.js/issues"
43
+ },
44
+ "license": "MIT",
45
+ "scripts": {
46
+ "docs": "typedoc",
47
+ "cli": "node ./scripts/index.mjs",
48
+ "sandboxscript": "node ./scripts/sandbox.mjs",
49
+ "build": "npm run cli -- build",
50
+ "build:fast": "npm run build -- -f",
51
+ "dev": "npm run cli -- dev",
52
+ "start": "npm run sandboxscript -- start",
53
+ "export": "npm run cli -- website export",
54
+ "test:vitest": "vitest --run --project unit-node",
55
+ "test:vitest:chromium": "vitest --run --project unit-chromium",
56
+ "test:vitest:firefox": "vitest --run --project unit-firefox",
57
+ "test:vitest:all": "vitest --run",
58
+ "test:vitest:coverage": "vitest --run --coverage --project unit-node",
59
+ "test:vitest:coverage:watch": "npm run test:vitest --coverage=true",
60
+ "coverage:merge": "nyc merge coveragefiles .nyc_output/merged-coverage.json",
61
+ "coverage:report": "nyc report --skip-full=true --reporter=lcov --reporter=text --reporter=text-summary",
62
+ "coverage:report:ci": "nyc report --reporter=text-summary",
63
+ "test:e2e": "npm run playwright:typecheck && playwright test",
64
+ "playwright:typecheck": "tsc -p ./e2e/tsconfig.json --noEmit",
65
+ "sandbox": "npm run sandboxscript -- sandbox",
66
+ "local-server": "serve ./ -l tcp://localhost:8080",
67
+ "lint": "eslint src extensions",
68
+ "prettier:check": "prettier --check .",
69
+ "prettier:write": "prettier --write ."
70
+ },
71
+ "devDependencies": {
72
+ "@babel/cli": "^7.28.3",
73
+ "@babel/core": "^7.28.3",
74
+ "@babel/preset-env": "^7.28.3",
75
+ "@babel/preset-typescript": "^7.27.1",
76
+ "@eslint/js": "^9.31.0",
77
+ "@playwright/test": "^1.55.0",
78
+ "@rollup/plugin-babel": "^6.0.4",
79
+ "@rollup/plugin-json": "^6.1.0",
80
+ "@rollup/plugin-terser": "^0.4.4",
81
+ "@rollup/plugin-typescript": "^12.1.4",
82
+ "@types/fs-extra": "^11.0.4",
83
+ "@types/jsdom": "^21.1.7",
84
+ "@types/micromatch": "^4.0.9",
85
+ "@types/node": "^24.3.0",
86
+ "@vitest/browser": "^3.2.4",
87
+ "@vitest/coverage-v8": "^3.2.4",
88
+ "@vitest/ui": "^3.2.4",
89
+ "babel-plugin-transform-imports": "git+https://git@github.com/fabricjs/babel-plugin-transform-imports.git",
90
+ "chalk": "^5.6.0",
91
+ "commander": "^14.0.0",
92
+ "es-toolkit": "1.39.7",
93
+ "eslint-config-prettier": "^10.1.8",
94
+ "fs-extra": "^11.3.1",
95
+ "inquirer": "^12.9.4",
96
+ "micromatch": "^4.0.8",
97
+ "moment": "^2.30.1",
98
+ "nyc": "^17.1.0",
99
+ "prettier": "^3.6.2",
100
+ "ps-list": "^8.1.1",
101
+ "rollup": "^4.48.0",
102
+ "semver": "^7.7.2",
103
+ "serve": "^14.2.4",
104
+ "tslib": "^2.8.1",
105
+ "typescript": "^5.9.2",
106
+ "typescript-eslint": "^8.40.0",
107
+ "v8-to-istanbul": "^9.3.0",
108
+ "vite": "^6.3.5",
109
+ "vitest": "^3.2.4"
110
+ },
111
+ "engines": {
112
+ "node": ">=18.0.0"
113
+ },
114
+ "overrides": {
115
+ "canvas": {
116
+ "canvas": "3.2.0"
117
+ }
118
+ },
119
+ "module": "./dist/index.mjs",
120
+ "types": "./dist/index.d.ts",
121
+ "typesVersions": {
122
+ ">=4.2": {
123
+ "*": [
124
+ "dist/index.d.ts"
125
+ ],
126
+ "node": [
127
+ "dist/index.node.d.ts"
128
+ ]
129
+ }
130
+ },
131
+ "sideEffects": false,
132
+ "exports": {
133
+ ".": {
134
+ "types": "./dist/index.d.ts",
135
+ "import": "./dist/index.min.mjs",
136
+ "require": "./dist/index.min.js",
137
+ "default": "./dist/index.min.js"
138
+ },
139
+ "./es": {
140
+ "types": "./dist/index.d.ts",
141
+ "import": "./dist/fabric.min.mjs",
142
+ "require": null,
143
+ "default": null
144
+ },
145
+ "./node": {
146
+ "node": "./dist/index.node.cjs",
147
+ "types": "./dist/index.node.d.ts",
148
+ "import": "./dist/index.node.mjs",
149
+ "require": "./dist/index.node.cjs",
150
+ "default": "./dist/index.node.cjs"
151
+ },
152
+ "./extensions": {
153
+ "node": "./dist-extensions/index.mjs",
154
+ "types": "./dist-extensions/extensions/index.d.ts",
155
+ "import": "./dist-extensions/index.mjs",
156
+ "require": null,
157
+ "default": "./dist-extensions/fabric-extensions.min.js"
158
+ }
159
+ },
160
+ "optionalDependencies": {
161
+ "canvas": "^3.2.0",
162
+ "jsdom": "^26.1.0"
163
+ }
164
+ }
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "@nasser-sw/fabric",
3
+ "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
4
+ "homepage": "http://fabricjs.com/",
5
+ "version": "7.0.1-beta32",
6
+ "author": "Juriy Zaytsev <kangax@gmail.com>",
7
+ "contributors": [
8
+ {
9
+ "name": "Andrea Bogazzi",
10
+ "email": "andreabogazzi79@gmail.com",
11
+ "url": "https://github.com/asturur"
12
+ },
13
+ {
14
+ "name": "Shachar Nencel",
15
+ "email": "shacharnen@gmail.com",
16
+ "url": "https://github.com/ShaMan123"
17
+ },
18
+ {
19
+ "name": "Steve Eberhardt",
20
+ "email": "melchiar2@gmail.com",
21
+ "url": "https://github.com/melchiar"
22
+ }
23
+ ],
24
+ "keywords": [
25
+ "canvas",
26
+ "graphic",
27
+ "graphics",
28
+ "SVG",
29
+ "node-canvas",
30
+ "parser",
31
+ "HTML5",
32
+ "object model"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/fabricjs/fabric.js"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/fabricjs/fabric.js/issues"
43
+ },
44
+ "license": "MIT",
45
+ "scripts": {
46
+ "docs": "typedoc",
47
+ "cli": "node ./scripts/index.mjs",
48
+ "sandboxscript": "node ./scripts/sandbox.mjs",
49
+ "build": "npm run cli -- build",
50
+ "build:fast": "npm run build -- -f",
51
+ "dev": "npm run cli -- dev",
52
+ "start": "npm run sandboxscript -- start",
53
+ "export": "npm run cli -- website export",
54
+ "test:vitest": "vitest --run --project unit-node",
55
+ "test:vitest:chromium": "vitest --run --project unit-chromium",
56
+ "test:vitest:firefox": "vitest --run --project unit-firefox",
57
+ "test:vitest:all": "vitest --run",
58
+ "test:vitest:coverage": "vitest --run --coverage --project unit-node",
59
+ "test:vitest:coverage:watch": "npm run test:vitest --coverage=true",
60
+ "coverage:merge": "nyc merge coveragefiles .nyc_output/merged-coverage.json",
61
+ "coverage:report": "nyc report --skip-full=true --reporter=lcov --reporter=text --reporter=text-summary",
62
+ "coverage:report:ci": "nyc report --reporter=text-summary",
63
+ "test:e2e": "npm run playwright:typecheck && playwright test",
64
+ "playwright:typecheck": "tsc -p ./e2e/tsconfig.json --noEmit",
65
+ "sandbox": "npm run sandboxscript -- sandbox",
66
+ "local-server": "serve ./ -l tcp://localhost:8080",
67
+ "lint": "eslint src extensions",
68
+ "prettier:check": "prettier --check .",
69
+ "prettier:write": "prettier --write ."
70
+ },
71
+ "devDependencies": {
72
+ "@babel/cli": "^7.28.3",
73
+ "@babel/core": "^7.28.3",
74
+ "@babel/preset-env": "^7.28.3",
75
+ "@babel/preset-typescript": "^7.27.1",
76
+ "@eslint/js": "^9.31.0",
77
+ "@playwright/test": "^1.55.0",
78
+ "@rollup/plugin-babel": "^6.0.4",
79
+ "@rollup/plugin-json": "^6.1.0",
80
+ "@rollup/plugin-terser": "^0.4.4",
81
+ "@rollup/plugin-typescript": "^12.1.4",
82
+ "@types/fs-extra": "^11.0.4",
83
+ "@types/jsdom": "^21.1.7",
84
+ "@types/micromatch": "^4.0.9",
85
+ "@types/node": "^24.3.0",
86
+ "@vitest/browser": "^3.2.4",
87
+ "@vitest/coverage-v8": "^3.2.4",
88
+ "@vitest/ui": "^3.2.4",
89
+ "babel-plugin-transform-imports": "git+https://git@github.com/fabricjs/babel-plugin-transform-imports.git",
90
+ "chalk": "^5.6.0",
91
+ "commander": "^14.0.0",
92
+ "es-toolkit": "1.39.7",
93
+ "eslint-config-prettier": "^10.1.8",
94
+ "fs-extra": "^11.3.1",
95
+ "inquirer": "^12.9.4",
96
+ "micromatch": "^4.0.8",
97
+ "moment": "^2.30.1",
98
+ "nyc": "^17.1.0",
99
+ "prettier": "^3.6.2",
100
+ "ps-list": "^8.1.1",
101
+ "rollup": "^4.48.0",
102
+ "semver": "^7.7.2",
103
+ "serve": "^14.2.4",
104
+ "tslib": "^2.8.1",
105
+ "typescript": "^5.9.2",
106
+ "typescript-eslint": "^8.40.0",
107
+ "v8-to-istanbul": "^9.3.0",
108
+ "vite": "^6.3.5",
109
+ "vitest": "^3.2.4"
110
+ },
111
+ "engines": {
112
+ "node": ">=18.0.0"
113
+ },
114
+ "overrides": {
115
+ "canvas": {
116
+ "canvas": "3.2.0"
117
+ }
118
+ },
119
+ "module": "./dist/index.mjs",
120
+ "types": "./dist/index.d.ts",
121
+ "typesVersions": {
122
+ ">=4.2": {
123
+ "*": [
124
+ "dist/index.d.ts"
125
+ ],
126
+ "node": [
127
+ "dist/index.node.d.ts"
128
+ ]
129
+ }
130
+ },
131
+ "sideEffects": false,
132
+ "exports": {
133
+ ".": {
134
+ "types": "./dist/index.d.ts",
135
+ "import": "./dist/index.min.mjs",
136
+ "require": "./dist/index.min.js",
137
+ "default": "./dist/index.min.js"
138
+ },
139
+ "./es": {
140
+ "types": "./dist/index.d.ts",
141
+ "import": "./dist/fabric.min.mjs",
142
+ "require": null,
143
+ "default": null
144
+ },
145
+ "./node": {
146
+ "node": "./dist/index.node.cjs",
147
+ "types": "./dist/index.node.d.ts",
148
+ "import": "./dist/index.node.mjs",
149
+ "require": "./dist/index.node.cjs",
150
+ "default": "./dist/index.node.cjs"
151
+ },
152
+ "./extensions": {
153
+ "node": "./dist-extensions/index.mjs",
154
+ "types": "./dist-extensions/extensions/index.d.ts",
155
+ "import": "./dist-extensions/index.mjs",
156
+ "require": null,
157
+ "default": "./dist-extensions/fabric-extensions.min.js"
158
+ }
159
+ },
160
+ "optionalDependencies": {
161
+ "canvas": "^3.2.0",
162
+ "jsdom": "^26.1.0"
163
+ }
164
+ }
package/dist/index.js CHANGED
@@ -360,7 +360,7 @@
360
360
  }
361
361
  const cache = new Cache();
362
362
 
363
- var version = "7.0.1-beta29";
363
+ var version = "7.0.1-beta31";
364
364
 
365
365
  // use this syntax so babel plugin see this import here
366
366
  const VERSION = version;
@@ -9338,6 +9338,10 @@
9338
9338
  * Position handler for expand controls
9339
9339
  * Positions controls at the expansion boundary, not the object boundary
9340
9340
  * Note: expansion values are stored in UNSCALED local object coordinates
9341
+ *
9342
+ * Key insight: dim already includes zoom, and finalMatrix undoes zoom.
9343
+ * So we need to express expansion as a PROPORTION of dim, not as a separate offset.
9344
+ * This ensures the expansion scales correctly with zoom.
9341
9345
  */
9342
9346
  positionHandler(dim, finalMatrix, fabricObject, currentControl) {
9343
9347
  const expansion = getExpansion(fabricObject);
@@ -9348,54 +9352,39 @@
9348
9352
  expandBottom
9349
9353
  } = expansion;
9350
9354
 
9351
- // dim already includes object's own scale (dim = width * scaleX, height * scaleY)
9352
- const halfWidth = dim.x / 2;
9353
- const halfHeight = dim.y / 2;
9354
-
9355
- // Expansion values are in UNSCALED coordinates.
9356
- // dim is in SCALED coordinates.
9357
- // Scale expansion by object's scale to match dim.
9358
- const scaleFactorX = fabricObject.scaleX || 1;
9359
- const scaleFactorY = fabricObject.scaleY || 1;
9360
-
9361
- // Scale expansion values to match dim's coordinate space
9362
- const scaledExpandLeft = expandLeft * scaleFactorX;
9363
- const scaledExpandRight = expandRight * scaleFactorX;
9364
- const scaledExpandTop = expandTop * scaleFactorY;
9365
- const scaledExpandBottom = expandBottom * scaleFactorY;
9366
-
9367
- // Calculate the expanded half dimensions
9368
- const expandedHalfWidth = halfWidth + (scaledExpandLeft + scaledExpandRight) / 2;
9369
- const expandedHalfHeight = halfHeight + (scaledExpandTop + scaledExpandBottom) / 2;
9370
-
9371
- // Calculate offset from center based on asymmetric expansion
9372
- const centerOffsetX = (scaledExpandRight - scaledExpandLeft) / 2;
9373
- const centerOffsetY = (scaledExpandBottom - scaledExpandTop) / 2;
9374
- let posX;
9375
- let posY;
9376
-
9377
- // Position based on which side this control is on
9355
+ // Get object's base dimensions (unscaled)
9356
+ const objWidth = fabricObject.width || 1;
9357
+ const objHeight = fabricObject.height || 1;
9358
+
9359
+ // Express expansion as a proportion of object size
9360
+ // dim includes zoom, finalMatrix applies inverse zoom
9361
+ // The result matches drawExpandPreview which also uses ratio * size
9362
+ const expandLeftRatio = expandLeft / objWidth;
9363
+ const expandRightRatio = expandRight / objWidth;
9364
+ const expandTopRatio = expandTop / objHeight;
9365
+ const expandBottomRatio = expandBottom / objHeight;
9366
+
9367
+ // Calculate position as a multiple of dim (same pattern as default control)
9368
+ // this.x is -0.5 for left, 0 for center, 0.5 for right
9369
+ let xMultiplier = this.x;
9370
+ let yMultiplier = this.y;
9378
9371
  if (this.x < 0) {
9379
- // Left side controls
9380
- posX = -expandedHalfWidth + centerOffsetX;
9372
+ // Left side: extend further left
9373
+ xMultiplier = this.x - expandLeftRatio;
9381
9374
  } else if (this.x > 0) {
9382
- // Right side controls
9383
- posX = expandedHalfWidth + centerOffsetX;
9384
- } else {
9385
- // Center (top/bottom only)
9386
- posX = centerOffsetX;
9375
+ // Right side: extend further right
9376
+ xMultiplier = this.x + expandRightRatio;
9387
9377
  }
9388
9378
  if (this.y < 0) {
9389
- // Top side controls
9390
- posY = -expandedHalfHeight + centerOffsetY;
9379
+ // Top side: extend further up
9380
+ yMultiplier = this.y - expandTopRatio;
9391
9381
  } else if (this.y > 0) {
9392
- // Bottom side controls
9393
- posY = expandedHalfHeight + centerOffsetY;
9394
- } else {
9395
- // Center (left/right only)
9396
- posY = centerOffsetY;
9382
+ // Bottom side: extend further down
9383
+ yMultiplier = this.y + expandBottomRatio;
9397
9384
  }
9398
- return new Point(posX, posY).transform(finalMatrix);
9385
+
9386
+ // Same formula as default positionHandler, just with adjusted multipliers
9387
+ return new Point(xMultiplier * dim.x + this.offsetX, yMultiplier * dim.y + this.offsetY).transform(finalMatrix);
9399
9388
  }
9400
9389
 
9401
9390
  /**
@@ -9450,16 +9439,17 @@
9450
9439
  } = transform;
9451
9440
  const expansion = getExpansion(target);
9452
9441
 
9453
- // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9442
+ // Use Fabric's getLocalPoint with center origin
9454
9443
  const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9455
9444
  const scaleX = target.scaleX || 1;
9456
- const scaledHalfWidth = target.width * scaleX / 2;
9445
+ // Use _getTransformedDimensions for consistency with scale handlers
9446
+ const dim = target._getTransformedDimensions();
9447
+ const scaledHalfWidth = dim.x / 2;
9457
9448
 
9458
- // Calculate expansion in scaled coordinates first
9449
+ // Calculate expansion in scaled coordinates
9459
9450
  const scaledExpansion = Math.max(0, -scaledHalfWidth - localPoint.x);
9460
9451
 
9461
- // Store in UNSCALED coordinates (divide by scale)
9462
- // This ensures positionHandler can scale it back correctly
9452
+ // Store in UNSCALED object coordinates (divide by scale)
9463
9453
  const newExpandLeft = scaledExpansion / scaleX;
9464
9454
  if (Math.abs(newExpandLeft - expansion.expandLeft) > 0.5) {
9465
9455
  var _target$canvas;
@@ -10393,9 +10383,13 @@
10393
10383
  * Draw the expansion preview area
10394
10384
  * Called during border rendering when in expand mode
10395
10385
  * @param ctx Canvas rendering context
10396
- * @param size Object dimensions (already includes scale and parent transforms)
10397
- * @param options Decomposed transform options (contains scaleX, scaleY from full transform)
10386
+ * @param size Object dimensions (already in correct coordinate space from drawBorders)
10387
+ * @param options Decomposed transform options (contains scaleX, scaleY)
10398
10388
  * Note: expansion values are stored in local object coordinates
10389
+ *
10390
+ * Key insight: size is proportional to object dimensions.
10391
+ * Express expansion as a proportion of object size, then scale by size.
10392
+ * This ensures correct behavior with zoom and grouped objects.
10399
10393
  */
10400
10394
  drawExpandPreview(ctx, size, options) {
10401
10395
  if (!this.expandMode) return;
@@ -10406,20 +10400,20 @@
10406
10400
  expandBottom
10407
10401
  } = this.expansion;
10408
10402
 
10409
- // Expansion values are stored in UNSCALED object coordinates.
10410
- // size is in screen coordinates (includes full transform).
10411
- // We need to scale expansion by the FULL transform to match size.
10412
- // options.scaleX/Y contains the full scale (object * parent * viewport).
10413
- const scaleFactorX = (options === null || options === void 0 ? void 0 : options.scaleX) || this.scaleX || 1;
10414
- const scaleFactorY = (options === null || options === void 0 ? void 0 : options.scaleY) || this.scaleY || 1;
10403
+ // Express expansion as proportions of object dimensions
10404
+ // size already includes scale and zoom, so ratio * size gives correct scaling
10405
+ const expandLeftRatio = expandLeft / (this.width || 1);
10406
+ const expandRightRatio = expandRight / (this.width || 1);
10407
+ const expandTopRatio = expandTop / (this.height || 1);
10408
+ const expandBottomRatio = expandBottom / (this.height || 1);
10415
10409
 
10416
- // Scale expansion values to screen coordinates
10417
- const scaledExpandLeft = expandLeft * scaleFactorX;
10418
- const scaledExpandRight = expandRight * scaleFactorX;
10419
- const scaledExpandTop = expandTop * scaleFactorY;
10420
- const scaledExpandBottom = expandBottom * scaleFactorY;
10410
+ // Scale expansion proportionally to size
10411
+ const scaledExpandLeft = expandLeftRatio * size.x;
10412
+ const scaledExpandRight = expandRightRatio * size.x;
10413
+ const scaledExpandTop = expandTopRatio * size.y;
10414
+ const scaledExpandBottom = expandBottomRatio * size.y;
10421
10415
 
10422
- // size already accounts for full transform, add scaled expansion
10416
+ // Calculate expanded dimensions
10423
10417
  const expandedWidth = size.x + scaledExpandLeft + scaledExpandRight;
10424
10418
  const expandedHeight = size.y + scaledExpandTop + scaledExpandBottom;
10425
10419
 
@@ -31803,11 +31797,8 @@
31803
31797
  // Draw edit mode overlay if in edit mode
31804
31798
  if (this.isEditMode && this._editModeClipPath) {
31805
31799
  this._renderEditModeOverlay(ctx, false);
31806
-
31807
- // In expand mode, re-render the content image's controls ON TOP of the overlay
31808
- if (this._contentExpandMode && this._contentImage) {
31809
- this._contentImage._renderControls(ctx);
31810
- }
31800
+ // Note: Controls are rendered by the canvas's normal rendering pipeline
31801
+ // with proper viewport transforms - no manual _renderControls call needed
31811
31802
  }
31812
31803
  }
31813
31804