@nasser-sw/fabric 7.0.0-beta1 → 7.0.1-beta10

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 (183) hide show
  1. package/0 +0 -0
  2. package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
  3. package/debug/{konva → konva-master}/README.md +7 -3
  4. package/debug/{konva → konva-master}/package.json +1 -1
  5. package/debug/{konva → konva-master}/release.sh +1 -4
  6. package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
  7. package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
  8. package/dist/index.js +2198 -272
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/index.min.mjs +1 -1
  13. package/dist/index.min.mjs.map +1 -1
  14. package/dist/index.mjs +2198 -272
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.node.cjs +2198 -272
  17. package/dist/index.node.cjs.map +1 -1
  18. package/dist/index.node.mjs +2198 -272
  19. package/dist/index.node.mjs.map +1 -1
  20. package/dist/package.json.min.mjs +1 -1
  21. package/dist/package.json.mjs +1 -1
  22. package/dist/src/shapes/Line.d.ts +33 -86
  23. package/dist/src/shapes/Line.d.ts.map +1 -1
  24. package/dist/src/shapes/Line.min.mjs +1 -1
  25. package/dist/src/shapes/Line.min.mjs.map +1 -1
  26. package/dist/src/shapes/Line.mjs +405 -159
  27. package/dist/src/shapes/Line.mjs.map +1 -1
  28. package/dist/src/shapes/Polyline.d.ts +7 -0
  29. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  30. package/dist/src/shapes/Polyline.min.mjs +1 -1
  31. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  32. package/dist/src/shapes/Polyline.mjs +48 -16
  33. package/dist/src/shapes/Polyline.mjs.map +1 -1
  34. package/dist/src/shapes/Text/Text.d.ts +19 -0
  35. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  36. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  37. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  38. package/dist/src/shapes/Text/Text.mjs +302 -16
  39. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  40. package/dist/src/shapes/Textbox.d.ts +56 -1
  41. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  42. package/dist/src/shapes/Textbox.min.mjs +1 -1
  43. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  44. package/dist/src/shapes/Textbox.mjs +633 -11
  45. package/dist/src/shapes/Textbox.mjs.map +1 -1
  46. package/dist/src/shapes/Triangle.d.ts +27 -2
  47. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  48. package/dist/src/shapes/Triangle.min.mjs +1 -1
  49. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  50. package/dist/src/shapes/Triangle.mjs +72 -12
  51. package/dist/src/shapes/Triangle.mjs.map +1 -1
  52. package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
  53. package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
  54. package/dist/src/text/measure.d.ts +9 -0
  55. package/dist/src/text/measure.d.ts.map +1 -1
  56. package/dist/src/text/measure.min.mjs +1 -1
  57. package/dist/src/text/measure.min.mjs.map +1 -1
  58. package/dist/src/text/measure.mjs +175 -4
  59. package/dist/src/text/measure.mjs.map +1 -1
  60. package/dist/src/text/overlayEditor.d.ts +8 -0
  61. package/dist/src/text/overlayEditor.d.ts.map +1 -1
  62. package/dist/src/text/overlayEditor.min.mjs +1 -1
  63. package/dist/src/text/overlayEditor.min.mjs.map +1 -1
  64. package/dist/src/text/overlayEditor.mjs +395 -56
  65. package/dist/src/text/overlayEditor.mjs.map +1 -1
  66. package/dist/src/text/scriptUtils.d.ts +142 -0
  67. package/dist/src/text/scriptUtils.d.ts.map +1 -0
  68. package/dist/src/text/scriptUtils.min.mjs +2 -0
  69. package/dist/src/text/scriptUtils.min.mjs.map +1 -0
  70. package/dist/src/text/scriptUtils.mjs +212 -0
  71. package/dist/src/text/scriptUtils.mjs.map +1 -0
  72. package/dist/src/util/misc/cornerRadius.d.ts +70 -0
  73. package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
  74. package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
  75. package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
  76. package/dist/src/util/misc/cornerRadius.mjs +181 -0
  77. package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
  78. package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
  79. package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
  80. package/dist-extensions/src/shapes/Line.d.ts +33 -86
  81. package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
  82. package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
  83. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  84. package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
  85. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  86. package/dist-extensions/src/shapes/Textbox.d.ts +56 -1
  87. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  88. package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
  89. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  90. package/dist-extensions/src/text/measure.d.ts +9 -0
  91. package/dist-extensions/src/text/measure.d.ts.map +1 -1
  92. package/dist-extensions/src/text/overlayEditor.d.ts +8 -0
  93. package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
  94. package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
  95. package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
  96. package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
  97. package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
  98. package/fabric-test-editor.html +3552 -0
  99. package/fabric-test2.html +647 -0
  100. package/fabric.ts +182 -182
  101. package/fonts/STV Bold.ttf +0 -0
  102. package/fonts/STV Light.ttf +0 -0
  103. package/fonts/STV Regular.ttf +0 -0
  104. package/package.json +164 -164
  105. package/src/shapes/Line.ts +484 -157
  106. package/src/shapes/Polyline.ts +70 -29
  107. package/src/shapes/Text/Text.ts +317 -19
  108. package/src/shapes/Textbox.ts +663 -12
  109. package/src/shapes/Triangle.spec.ts +76 -0
  110. package/src/shapes/Triangle.ts +85 -15
  111. package/src/text/measure.ts +200 -50
  112. package/src/text/overlayEditor.ts +504 -94
  113. package/src/util/misc/cornerRadius.spec.ts +141 -0
  114. package/src/util/misc/cornerRadius.ts +269 -0
  115. /package/debug/{konva → konva-master}/LICENSE +0 -0
  116. /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
  117. /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
  118. /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
  119. /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
  120. /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
  121. /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
  122. /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
  123. /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
  124. /package/debug/{konva → konva-master}/src/Container.ts +0 -0
  125. /package/debug/{konva → konva-master}/src/Context.ts +0 -0
  126. /package/debug/{konva → konva-master}/src/Core.ts +0 -0
  127. /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
  128. /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
  129. /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
  130. /package/debug/{konva → konva-master}/src/Global.ts +0 -0
  131. /package/debug/{konva → konva-master}/src/Group.ts +0 -0
  132. /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
  133. /package/debug/{konva → konva-master}/src/Node.ts +0 -0
  134. /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
  135. /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
  136. /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
  137. /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
  138. /package/debug/{konva → konva-master}/src/Util.ts +0 -0
  139. /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
  140. /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
  141. /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
  142. /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
  143. /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
  144. /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
  145. /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
  146. /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
  147. /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
  148. /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
  149. /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
  150. /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
  151. /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
  152. /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
  153. /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
  154. /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
  155. /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
  156. /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
  157. /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
  158. /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
  159. /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
  160. /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
  161. /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
  162. /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
  163. /package/debug/{konva → konva-master}/src/index.ts +0 -0
  164. /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
  165. /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
  166. /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
  167. /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
  168. /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
  169. /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
  170. /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
  171. /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
  172. /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
  173. /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
  174. /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
  175. /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
  176. /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
  177. /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
  178. /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
  179. /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
  180. /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
  181. /package/debug/{konva → konva-master}/src/types.ts +0 -0
  182. /package/debug/{konva → konva-master}/tsconfig.json +0 -0
  183. /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
@@ -82,10 +82,10 @@ export class OverlayEditor {
82
82
  if (!container) {
83
83
  throw new Error('Canvas must be mounted in DOM to use overlay editing');
84
84
  }
85
-
85
+
86
86
  // Ensure the container is positioned for absolute overlay positioning
87
87
  container.style.position = 'relative';
88
-
88
+
89
89
  return container;
90
90
  }
91
91
 
@@ -109,10 +109,17 @@ export class OverlayEditor {
109
109
  this.textarea.style.resize = 'none';
110
110
  this.textarea.style.pointerEvents = 'auto';
111
111
  // Set appropriate unicodeBidi based on content and direction
112
- const hasArabicText = /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(this.target.text || '');
112
+ const hasArabicText =
113
+ /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(
114
+ this.target.text || '',
115
+ );
116
+ const hasLatinText = /[a-zA-Z]/.test(this.target.text || '');
113
117
  const isLTRDirection = (this.target as any).direction === 'ltr';
114
-
115
- if (hasArabicText && isLTRDirection) {
118
+
119
+ if (hasArabicText && hasLatinText && isLTRDirection) {
120
+ // For mixed Arabic/Latin text in LTR mode, use embed for consistent line wrapping
121
+ this.textarea.style.unicodeBidi = 'embed';
122
+ } else if (hasArabicText && isLTRDirection) {
116
123
  // For Arabic text in LTR mode, use embed to preserve shaping while respecting direction
117
124
  this.textarea.style.unicodeBidi = 'embed';
118
125
  } else {
@@ -165,7 +172,7 @@ export class OverlayEditor {
165
172
  this.canvas.on('after:render', this.boundHandlers.onAfterRender);
166
173
  this.canvas.on('mouse:wheel', this.boundHandlers.onMouseWheel);
167
174
  this.canvas.on('mouse:down', this.boundHandlers.onMouseDown);
168
-
175
+
169
176
  // Store original methods to detect viewport changes
170
177
  this.setupViewportChangeDetection();
171
178
  }
@@ -190,7 +197,7 @@ export class OverlayEditor {
190
197
  this.canvas.off('after:render', this.boundHandlers.onAfterRender);
191
198
  this.canvas.off('mouse:wheel', this.boundHandlers.onMouseWheel);
192
199
  this.canvas.off('mouse:down', this.boundHandlers.onMouseDown);
193
-
200
+
194
201
  // Restore original methods
195
202
  this.restoreViewportChangeDetection();
196
203
  }
@@ -211,19 +218,33 @@ export class OverlayEditor {
211
218
 
212
219
  const target = this.target;
213
220
  const zoom = this.canvas.getZoom();
214
-
221
+
215
222
  // Get current textbox dimensions from the host div (in canvas coordinates)
216
223
  const currentWidth = parseFloat(this.hostDiv.style.width) / zoom;
217
224
  const currentHeight = parseFloat(this.hostDiv.style.height) / zoom;
218
-
219
- // Only update if there's a meaningful change (avoid float precision issues)
225
+
226
+ // Always update height for responsive controls (especially important for line deletion)
220
227
  const heightDiff = Math.abs(currentHeight - target.height);
221
- const threshold = 1; // 1px threshold to avoid micro-changes
222
-
228
+ const threshold = 0.5; // Lower threshold for better responsiveness to line changes
229
+
223
230
  if (heightDiff > threshold) {
231
+ const oldHeight = target.height;
224
232
  target.height = currentHeight;
225
233
  target.setCoords(); // Update control positions
234
+
235
+ // Force dirty to ensure proper re-rendering
236
+ target.dirty = true;
226
237
  this.canvas.requestRenderAll(); // Re-render to show updated selection
238
+
239
+ // IMPORTANT: Reposition overlay after height change
240
+ requestAnimationFrame(() => {
241
+ if (!this.isDestroyed) {
242
+ this.applyOverlayStyle();
243
+ console.log(
244
+ '📐 Height changed - rechecking alignment after repositioning:',
245
+ );
246
+ }
247
+ });
227
248
  }
228
249
  }
229
250
 
@@ -251,15 +272,7 @@ export class OverlayEditor {
251
272
  // 1. Freshen object's transformations - use aCoords like rtl-test.html
252
273
  target.setCoords();
253
274
  const aCoords = target.aCoords;
254
-
255
- // DEBUG: Log dimensions before edit
256
- console.log('BEFORE EDIT:');
257
- console.log(' target.width =', (target as any).width);
258
- console.log(' target.height =', target.height);
259
- console.log(' target.getScaledWidth() =', target.getScaledWidth());
260
- console.log(' target.getScaledHeight() =', target.getScaledHeight());
261
- console.log(' target.padding =', (target as any).padding);
262
-
275
+
263
276
  // 2. Get canvas position and scroll offsets (like rtl-test.html)
264
277
  const canvasEl = canvas.upperCanvasEl;
265
278
  const canvasRect = canvasEl.getBoundingClientRect();
@@ -275,20 +288,20 @@ export class OverlayEditor {
275
288
 
276
289
  // Transform object's top-left corner coordinates to screen coordinates using viewport transform
277
290
  // aCoords.tl already accounts for object positioning and scaling, just need viewport transform
278
- const screenPoint = transformPoint({ x: aCoords.tl.x, y: aCoords.tl.y }, vpt);
279
-
291
+ const screenPoint = transformPoint(
292
+ { x: aCoords.tl.x, y: aCoords.tl.y },
293
+ vpt,
294
+ );
295
+
280
296
  const left = canvasRect.left + scrollX + screenPoint.x;
281
297
  const top = canvasRect.top + scrollY + screenPoint.y;
282
298
 
283
- // 4. Get dimensions with zoom scaling - use target.width for text wrapping, scaled height for container
284
- const width = (target as any).width * (target.scaleX || 1) * zoom; // Account for object scale and viewport zoom
285
- const height = target.height * (target.scaleY || 1) * zoom;
286
-
287
- console.log('WIDTH CALCULATION:');
288
- console.log(' target.width =', (target as any).width);
289
- console.log(' scaledWidth =', target.getScaledWidth());
290
- console.log(' zoom =', zoom);
291
- console.log(' final width =', width);
299
+ // 4. Calculate the precise width and height for the container
300
+ // **THE FIX:** Use getBoundingRect() for BOTH width and height.
301
+ // This is the most reliable measure of the object's final rendered dimensions.
302
+ const objectBounds = target.getBoundingRect();
303
+ const width = Math.round(objectBounds.width * zoom);
304
+ const height = Math.round(objectBounds.height * zoom);
292
305
 
293
306
  // 5. Apply styles to host DIV - absolute positioning like rtl-test.html
294
307
  this.hostDiv.style.position = 'absolute';
@@ -307,99 +320,440 @@ export class OverlayEditor {
307
320
  }
308
321
 
309
322
  // 6. Style the textarea - match Fabric's exact rendering with padding
310
- const baseFontSize = (target.fontSize ?? 16);
323
+ const baseFontSize = target.fontSize ?? 16;
311
324
  // Use scaleX for font scaling to match Fabric text scaling exactly
312
325
  const scaleX = target.scaleX || 1;
313
326
  const finalFontSize = baseFontSize * scaleX * zoom;
314
327
  const fabricLineHeight = target.lineHeight || 1.16;
315
- // Apply padding and dimensions to textarea
316
- const textareaWidth = paddingX > 0 ? `calc(100% - ${2 * paddingX}px)` : '100%';
317
- const textareaHeight = paddingY > 0 ? `calc(100% - ${2 * paddingY}px)` : '100%';
318
-
319
- this.textarea.style.width = textareaWidth;
320
- this.textarea.style.height = textareaHeight;
328
+ // **THE FIX:** Use 'border-box' so the width property includes padding.
329
+ // This makes alignment much easier and more reliable.
330
+ this.textarea.style.boxSizing = 'border-box';
331
+
332
+ // **THE FIX:** Set the textarea width to be IDENTICAL to the host div's width.
333
+ // The padding will now be correctly contained *inside* this width.
334
+ this.textarea.style.width = `${width}px`;
335
+ this.textarea.style.height = '100%'; // Let hostDiv control height
321
336
  this.textarea.style.padding = `${paddingY}px ${paddingX}px`;
337
+
338
+ // Apply all other font and text styles to match Fabric
339
+ const letterSpacingPx = ((target.charSpacing || 0) / 1000) * finalFontSize;
322
340
 
341
+ // Special handling for text objects loaded from JSON - ensure they're properly initialized
342
+ if (target.dirty !== false && target.initDimensions) {
343
+ console.log('🔧 Ensuring text object is properly initialized before overlay editing');
344
+ // Force re-initialization if the text object seems to be in a dirty state
345
+ target.initDimensions();
346
+ }
347
+
323
348
  this.textarea.style.fontSize = `${finalFontSize}px`;
324
- this.textarea.style.lineHeight = String(fabricLineHeight); // Use unit-less multiplier
349
+ this.textarea.style.lineHeight = String(fabricLineHeight);
325
350
  this.textarea.style.fontFamily = target.fontFamily || 'Arial';
326
351
  this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
327
352
  this.textarea.style.fontStyle = target.fontStyle || 'normal';
328
- this.textarea.style.textAlign = (target as any).textAlign || 'left';
353
+ // Handle text alignment and justification
354
+ const textAlign = (target as any).textAlign || 'left';
355
+ let cssTextAlign = textAlign;
356
+
357
+ // Detect text direction from content for proper justify handling
358
+ const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
359
+
360
+ // DEBUG: Log alignment details
361
+ console.log('🔍 ALIGNMENT DEBUG:');
362
+ console.log(' Fabric textAlign:', textAlign);
363
+ console.log(' Fabric direction:', (target as any).direction);
364
+ console.log(' Text content:', JSON.stringify(target.text));
365
+ console.log(' Detected direction:', autoDetectedDirection);
366
+
367
+ // Map fabric.js justify to CSS
368
+ if (textAlign.includes('justify')) {
369
+ // Try to match fabric.js justify behavior more precisely
370
+ try {
371
+ // For justify, we need to replicate fabric.js space expansion
372
+ // Use CSS justify but with specific settings to match fabric.js better
373
+ cssTextAlign = 'justify';
374
+
375
+ // Set text-align-last based on justify type and detected direction
376
+ // Smart justify: respect detected direction even when fabric alignment doesn't match
377
+ if (textAlign === 'justify') {
378
+ this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
379
+ } else if (textAlign === 'justify-left') {
380
+ // If text is RTL but fabric says justify-left, override to justify-right for better UX
381
+ if (autoDetectedDirection === 'rtl') {
382
+ this.textarea.style.textAlignLast = 'right';
383
+ console.log(' → Overrode justify-left to justify-right for RTL text');
384
+ } else {
385
+ this.textarea.style.textAlignLast = 'left';
386
+ }
387
+ } else if (textAlign === 'justify-right') {
388
+ // If text is LTR but fabric says justify-right, override to justify-left for better UX
389
+ if (autoDetectedDirection === 'ltr') {
390
+ this.textarea.style.textAlignLast = 'left';
391
+ console.log(' → Overrode justify-right to justify-left for LTR text');
392
+ } else {
393
+ this.textarea.style.textAlignLast = 'right';
394
+ }
395
+ } else if (textAlign === 'justify-center') {
396
+ this.textarea.style.textAlignLast = 'center';
397
+ }
398
+
399
+ // Enhanced justify settings for better fabric.js matching
400
+ (this.textarea.style as any).textJustify = 'inter-word';
401
+ (this.textarea.style as any).wordSpacing = 'normal';
402
+
403
+ // Additional CSS properties for better justify matching
404
+ this.textarea.style.textAlign = 'justify';
405
+ this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
406
+
407
+ // Try to force better justify behavior
408
+ (this.textarea.style as any).textJustifyTrim = 'none';
409
+ (this.textarea.style as any).textAutospace = 'none';
410
+
411
+ console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
412
+ } catch (error) {
413
+ console.warn(' → Justify setup failed, falling back to standard alignment:', error);
414
+ cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
415
+ }
416
+ } else {
417
+ this.textarea.style.textAlignLast = 'auto';
418
+ (this.textarea.style as any).textJustify = 'auto';
419
+ (this.textarea.style as any).wordSpacing = 'normal';
420
+ console.log(' → Applied standard alignment:', cssTextAlign);
421
+ }
422
+
423
+ this.textarea.style.textAlign = cssTextAlign;
329
424
  this.textarea.style.color = target.fill?.toString() || '#000';
330
- this.textarea.style.letterSpacing = `${((target.charSpacing || 0) / 1000)}em`;
331
- this.textarea.style.direction = (target as any).direction || this.firstStrongDir(this.textarea.value || '');
425
+ this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
426
+
427
+ // Use the already detected direction from above
428
+ const fabricDirection = (target as any).direction;
332
429
 
333
- // Ensure consistent font rendering between Fabric and CSS
430
+ // Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
431
+ this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
334
432
  this.textarea.style.fontVariant = 'normal';
335
433
  this.textarea.style.fontStretch = 'normal';
336
- this.textarea.style.textRendering = 'auto';
337
- this.textarea.style.fontKerning = 'auto';
338
- this.textarea.style.boxSizing = 'content-box'; // Padding is added outside width/height
434
+ this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
435
+ this.textarea.style.fontKerning = 'normal';
436
+ this.textarea.style.fontFeatureSettings = 'normal';
437
+ this.textarea.style.fontVariationSettings = 'normal';
339
438
  this.textarea.style.margin = '0';
340
439
  this.textarea.style.border = 'none';
341
440
  this.textarea.style.outline = 'none';
342
441
  this.textarea.style.background = 'transparent';
343
- this.textarea.style.wordWrap = 'break-word';
442
+ this.textarea.style.overflowWrap = 'break-word';
344
443
  this.textarea.style.whiteSpace = 'pre-wrap';
444
+ this.textarea.style.hyphens = 'none';
445
+
446
+ // DEBUG: Log final CSS properties
447
+ console.log('🎨 FINAL TEXTAREA CSS:');
448
+ console.log(' textAlign:', this.textarea.style.textAlign);
449
+ console.log(' textAlignLast:', this.textarea.style.textAlignLast);
450
+ console.log(' direction:', this.textarea.style.direction);
451
+ console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
452
+ console.log(' width:', this.textarea.style.width);
453
+ console.log(' textJustify:', (this.textarea.style as any).textJustify);
454
+ console.log(' wordSpacing:', (this.textarea.style as any).wordSpacing);
455
+ console.log(' whiteSpace:', this.textarea.style.whiteSpace);
456
+
457
+ // If justify, log Fabric object dimensions for comparison
458
+ if (textAlign.includes('justify')) {
459
+ console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
460
+ console.log(' Fabric width:', (target as any).width);
461
+ console.log(' Fabric calcTextWidth:', (target as any).calcTextWidth?.());
462
+ console.log(' Fabric textAlign:', (target as any).textAlign);
463
+ console.log(' Text lines:', (target as any).textLines);
464
+ }
465
+
466
+ // Debug font properties matching
467
+ console.log('🔤 FONT PROPERTIES COMPARISON:');
468
+ console.log(' Fabric fontFamily:', target.fontFamily);
469
+ console.log(' Fabric fontWeight:', target.fontWeight);
470
+ console.log(' Fabric fontStyle:', target.fontStyle);
471
+ console.log(' Fabric fontSize:', target.fontSize);
472
+ console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
473
+ console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
474
+ console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
475
+ console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
476
+
477
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
478
+
479
+ // Enhanced font rendering to better match fabric.js canvas rendering
480
+ // Default to auto for more natural rendering
481
+ (this.textarea.style as any).webkitFontSmoothing = 'auto';
482
+ (this.textarea.style as any).mozOsxFontSmoothing = 'auto';
483
+ (this.textarea.style as any).fontSmooth = 'auto';
484
+ (this.textarea.style as any).textSizeAdjust = 'none';
485
+
486
+ // For bold fonts, use subpixel rendering to match canvas thickness better
487
+ const fontWeight = String(target.fontWeight || 'normal');
488
+ const isBold = fontWeight === 'bold' || fontWeight === '700' ||
489
+ (parseInt(fontWeight) >= 600);
490
+
491
+ if (isBold) {
492
+ (this.textarea.style as any).webkitFontSmoothing = 'subpixel-antialiased';
493
+ (this.textarea.style as any).mozOsxFontSmoothing = 'unset';
494
+ console.log('🔤 Applied enhanced bold rendering for better thickness matching');
495
+ }
345
496
 
346
- // DEBUG: Log final textarea dimensions
347
- console.log('TEXTAREA AFTER SETUP:');
348
- console.log(' textarea width =', this.textarea.style.width);
349
- console.log(' textarea height =', this.textarea.style.height);
350
- console.log(' textarea padding =', this.textarea.style.padding);
351
- console.log(' paddingX =', paddingX, 'paddingY =', paddingY);
352
- console.log(' baseFontSize =', baseFontSize);
353
- console.log(' scaleX =', scaleX);
354
- console.log(' zoom =', zoom);
355
- console.log(' finalFontSize =', finalFontSize);
356
- console.log(' fabricLineHeight =', fabricLineHeight);
497
+ console.log('🎨 FONT SMOOTHING APPLIED:');
498
+ console.log(' webkitFontSmoothing:', (this.textarea.style as any).webkitFontSmoothing);
499
+ console.log(' mozOsxFontSmoothing:', (this.textarea.style as any).mozOsxFontSmoothing);
500
+
357
501
 
358
502
  // Initial bounds are set correctly by Fabric.js - don't force update here
503
+ }
359
504
 
360
-
505
+ /**
506
+ * Debug method to compare textarea and canvas object bounding boxes
507
+ */
508
+ private debugBoundingBoxComparison(): void {
509
+ const target = this.target;
510
+ const canvas = this.canvas;
511
+ const zoom = canvas.getZoom();
512
+
513
+ // Get textarea bounding box (in screen coordinates)
514
+ const textareaRect = this.textarea.getBoundingClientRect();
515
+ const hostRect = this.hostDiv.getBoundingClientRect();
516
+
517
+ // Get canvas object bounding box (in screen coordinates)
518
+ const canvasBounds = target.getBoundingRect();
519
+ const canvasRect = canvas.upperCanvasEl.getBoundingClientRect();
520
+
521
+ // Convert canvas object bounds to screen coordinates
522
+ const vpt = canvas.viewportTransform;
523
+ const screenObjectBounds = {
524
+ left: canvasRect.left + canvasBounds.left * zoom + vpt[4],
525
+ top: canvasRect.top + canvasBounds.top * zoom + vpt[5],
526
+ width: canvasBounds.width * zoom,
527
+ height: canvasBounds.height * zoom,
528
+ };
529
+
530
+ console.log('🔍 BOUNDING BOX COMPARISON:');
531
+ console.log('📦 Textarea Rect:', {
532
+ left: Math.round(textareaRect.left * 100) / 100,
533
+ top: Math.round(textareaRect.top * 100) / 100,
534
+ width: Math.round(textareaRect.width * 100) / 100,
535
+ height: Math.round(textareaRect.height * 100) / 100,
536
+ });
537
+ console.log('📦 Host Div Rect:', {
538
+ left: Math.round(hostRect.left * 100) / 100,
539
+ top: Math.round(hostRect.top * 100) / 100,
540
+ width: Math.round(hostRect.width * 100) / 100,
541
+ height: Math.round(hostRect.height * 100) / 100,
542
+ });
543
+ console.log('📦 Canvas Object Bounds (screen):', {
544
+ left: Math.round(screenObjectBounds.left * 100) / 100,
545
+ top: Math.round(screenObjectBounds.top * 100) / 100,
546
+ width: Math.round(screenObjectBounds.width * 100) / 100,
547
+ height: Math.round(screenObjectBounds.height * 100) / 100,
548
+ });
549
+ console.log('📦 Canvas Object Bounds (canvas):', canvasBounds);
550
+
551
+ // Calculate differences
552
+ const hostVsObject = {
553
+ leftDiff:
554
+ Math.round((hostRect.left - screenObjectBounds.left) * 100) / 100,
555
+ topDiff: Math.round((hostRect.top - screenObjectBounds.top) * 100) / 100,
556
+ widthDiff:
557
+ Math.round((hostRect.width - screenObjectBounds.width) * 100) / 100,
558
+ heightDiff:
559
+ Math.round((hostRect.height - screenObjectBounds.height) * 100) / 100,
560
+ };
561
+
562
+ const textareaVsObject = {
563
+ leftDiff:
564
+ Math.round((textareaRect.left - screenObjectBounds.left) * 100) / 100,
565
+ topDiff:
566
+ Math.round((textareaRect.top - screenObjectBounds.top) * 100) / 100,
567
+ widthDiff:
568
+ Math.round((textareaRect.width - screenObjectBounds.width) * 100) / 100,
569
+ heightDiff:
570
+ Math.round((textareaRect.height - screenObjectBounds.height) * 100) /
571
+ 100,
572
+ };
573
+
574
+ console.log('📏 Host Div vs Canvas Object Diff:', hostVsObject);
575
+ console.log('📏 Textarea vs Canvas Object Diff:', textareaVsObject);
576
+
577
+ // Check if they're aligned (within 2px tolerance)
578
+ const tolerance = 2;
579
+ const hostAligned =
580
+ Math.abs(hostVsObject.leftDiff) < tolerance &&
581
+ Math.abs(hostVsObject.topDiff) < tolerance &&
582
+ Math.abs(hostVsObject.widthDiff) < tolerance &&
583
+ Math.abs(hostVsObject.heightDiff) < tolerance;
584
+
585
+ const textareaAligned =
586
+ Math.abs(textareaVsObject.leftDiff) < tolerance &&
587
+ Math.abs(textareaVsObject.topDiff) < tolerance &&
588
+ Math.abs(textareaVsObject.widthDiff) < tolerance &&
589
+ Math.abs(textareaVsObject.heightDiff) < tolerance;
590
+
591
+ console.log(
592
+ hostAligned
593
+ ? '✅ Host Div ALIGNED with canvas object'
594
+ : '❌ Host Div MISALIGNED with canvas object',
595
+ );
596
+ console.log(
597
+ textareaAligned
598
+ ? '✅ Textarea ALIGNED with canvas object'
599
+ : '❌ Textarea MISALIGNED with canvas object',
600
+ );
601
+ console.log('🔍 Zoom:', zoom, 'Viewport Transform:', vpt);
602
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
361
603
  }
362
604
 
605
+ /**
606
+ * Debug method to compare text wrapping between textarea and Fabric text object
607
+ */
608
+ private debugTextWrapping(): void {
609
+ const target = this.target;
610
+ const text = this.textarea.value;
611
+
612
+ console.log('📝 TEXT WRAPPING COMPARISON:');
613
+ console.log('📄 Text Content:', `"${text}"`);
614
+ console.log('📄 Text Length:', text.length);
615
+
616
+ // Analyze line breaks
617
+ const explicitLines = text.split('\n');
618
+ console.log('📄 Explicit Lines (\\n):', explicitLines.length);
619
+ explicitLines.forEach((line, i) => {
620
+ console.log(` Line ${i + 1}: "${line}" (${line.length} chars)`);
621
+ });
622
+
623
+ // Get textarea computed styles for wrapping analysis
624
+ const textareaStyles = window.getComputedStyle(this.textarea);
625
+ console.log('📐 Textarea Wrapping Styles:');
626
+ console.log(' width:', textareaStyles.width);
627
+ console.log(' fontSize:', textareaStyles.fontSize);
628
+ console.log(' fontFamily:', textareaStyles.fontFamily);
629
+ console.log(' fontWeight:', textareaStyles.fontWeight);
630
+ console.log(' letterSpacing:', textareaStyles.letterSpacing);
631
+ console.log(' lineHeight:', textareaStyles.lineHeight);
632
+ console.log(' whiteSpace:', textareaStyles.whiteSpace);
633
+ console.log(' wordWrap:', textareaStyles.wordWrap);
634
+ console.log(' overflowWrap:', textareaStyles.overflowWrap);
635
+ console.log(' direction:', textareaStyles.direction);
636
+ console.log(' textAlign:', textareaStyles.textAlign);
637
+
638
+ // Get Fabric text object properties for comparison
639
+ console.log('📐 Fabric Text Object Properties:');
640
+ console.log(' width:', (target as any).width);
641
+ console.log(' fontSize:', target.fontSize);
642
+ console.log(' fontFamily:', target.fontFamily);
643
+ console.log(' fontWeight:', target.fontWeight);
644
+ console.log(' charSpacing:', target.charSpacing);
645
+ console.log(' lineHeight:', target.lineHeight);
646
+ console.log(' direction:', (target as any).direction);
647
+ console.log(' textAlign:', (target as any).textAlign);
648
+ console.log(' scaleX:', target.scaleX);
649
+ console.log(' scaleY:', target.scaleY);
650
+
651
+ // Calculate effective dimensions for comparison - use actual rendered width
652
+ // **THE FIX:** Use getBoundingRect to get the *actual rendered width* of the Fabric object.
653
+ const fabricEffectiveWidth = this.target.getBoundingRect().width;
654
+ // Use the exact width set on textarea for comparison
655
+ const textareaComputedWidth = parseFloat(
656
+ window.getComputedStyle(this.textarea).width,
657
+ );
658
+ const textareaEffectiveWidth =
659
+ textareaComputedWidth / this.canvas.getZoom();
660
+ const widthDiff = Math.abs(textareaEffectiveWidth - fabricEffectiveWidth);
661
+
662
+ console.log('📏 Effective Width Comparison:');
663
+ console.log(' Textarea Effective Width:', textareaEffectiveWidth);
664
+ console.log(' Fabric Effective Width:', fabricEffectiveWidth);
665
+ console.log(' Width Difference:', widthDiff.toFixed(2) + 'px');
666
+ console.log(
667
+ widthDiff < 1
668
+ ? '✅ Widths MATCH for wrapping'
669
+ : '❌ Width MISMATCH may cause different wrapping',
670
+ );
671
+
672
+ // Check text direction and bidi handling
673
+ const hasRTLText =
674
+ /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(
675
+ text,
676
+ );
677
+ const hasBidiText = /[\u0590-\u06FF]/.test(text) && /[a-zA-Z]/.test(text);
678
+
679
+ console.log('🌍 Text Direction Analysis:');
680
+ console.log(' Has RTL characters:', hasRTLText);
681
+ console.log(' Has mixed Bidi text:', hasBidiText);
682
+ console.log(' Textarea direction:', textareaStyles.direction);
683
+ console.log(' Fabric direction:', (target as any).direction || 'auto');
684
+ console.log(' Textarea unicodeBidi:', textareaStyles.unicodeBidi);
685
+
686
+ // Measure actual rendered line count
687
+ const textareaScrollHeight = this.textarea.scrollHeight;
688
+ const textareaLineHeight =
689
+ parseFloat(textareaStyles.lineHeight) ||
690
+ parseFloat(textareaStyles.fontSize) * 1.2;
691
+ const estimatedTextareaLines = Math.round(
692
+ textareaScrollHeight / textareaLineHeight,
693
+ );
694
+
695
+ console.log('📊 Line Count Analysis:');
696
+ console.log(' Textarea scrollHeight:', textareaScrollHeight);
697
+ console.log(' Textarea lineHeight:', textareaLineHeight);
698
+ console.log(' Estimated rendered lines:', estimatedTextareaLines);
699
+ console.log(' Explicit line breaks:', explicitLines.length);
700
+
701
+ if (estimatedTextareaLines > explicitLines.length) {
702
+ console.log('🔄 Text wrapping detected in textarea');
703
+ console.log(
704
+ ' Wrapped lines:',
705
+ estimatedTextareaLines - explicitLines.length,
706
+ );
707
+ } else {
708
+ console.log('📏 No text wrapping in textarea');
709
+ }
710
+
711
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
712
+ }
713
+
714
+
363
715
  /**
364
716
  * Focus the textarea and position cursor at end
365
717
  */
366
718
  private focusTextarea(): void {
367
-
368
-
369
719
  // For overlay editing, we want to keep the object in "selection mode" not "editing mode"
370
720
  // This means keeping selected=true and isEditing=false to show boundaries
371
-
721
+
372
722
  // Hide the text content only (not the entire object)
373
723
  this.target.opacity = 0.01; // Nearly transparent but not fully hidden
374
-
724
+
375
725
  // Ensure object stays selected to show boundaries
376
726
  (this.target as any).selected = true;
377
727
  (this.target as any).isEditing = false; // Override any editing state
378
-
728
+
379
729
  // Make sure controls are enabled and movement is allowed during overlay editing
380
730
  this.target.set({
381
731
  hasControls: true,
382
732
  hasBorders: true,
383
733
  selectable: true,
384
734
  lockMovementX: false,
385
- lockMovementY: false
735
+ lockMovementY: false,
386
736
  });
387
-
737
+
388
738
  // Keep as active object
389
739
  this.canvas.setActiveObject(this.target);
390
-
740
+
391
741
  this.canvas.requestRenderAll();
392
742
  this.target.setCoords();
393
743
  this.applyOverlayStyle();
394
744
 
395
-
745
+ // Fix character mapping issues after JSON loading for browser-wrapped fonts
746
+ if ((this.target as any)._fixCharacterMappingAfterJsonLoad) {
747
+ (this.target as any)._fixCharacterMappingAfterJsonLoad();
748
+ }
396
749
 
397
750
  this.textarea.focus();
751
+
398
752
  this.textarea.setSelectionRange(
399
753
  this.textarea.value.length,
400
754
  this.textarea.value.length,
401
755
  );
402
-
756
+
403
757
  // Ensure the object stays selected even after textarea focus
404
758
  this.canvas.setActiveObject(this.target);
405
759
  this.canvas.requestRenderAll();
@@ -442,6 +796,26 @@ export class OverlayEditor {
442
796
  // Handle commit/cancel after restoring visibility
443
797
  if (commit && !this.isComposing) {
444
798
  const finalText = this.textarea.value;
799
+
800
+ // Auto-detect text direction and update fabric object if needed
801
+ const detectedDirection = this.firstStrongDir(finalText);
802
+ const currentDirection = (this.target as any).direction || 'ltr';
803
+
804
+ if (detectedDirection && detectedDirection !== currentDirection) {
805
+ console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
806
+ console.log(` Text content: "${finalText.substring(0, 50)}..."`);
807
+
808
+ // Update the fabric object's direction
809
+ (this.target as any).set('direction', detectedDirection);
810
+
811
+ // Force a re-render to apply the direction change
812
+ this.canvas.requestRenderAll();
813
+
814
+ console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
815
+ } else {
816
+ console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
817
+ }
818
+
445
819
  if (this.onCommit) {
446
820
  this.onCommit(finalText);
447
821
  }
@@ -470,6 +844,7 @@ export class OverlayEditor {
470
844
  // Live update target text
471
845
  this.target.text = this.textarea.value;
472
846
 
847
+
473
848
  // Auto-resize textarea to match new content
474
849
  this.autoResizeTextarea();
475
850
 
@@ -482,25 +857,40 @@ export class OverlayEditor {
482
857
  }
483
858
 
484
859
  private autoResizeTextarea(): void {
485
- // Allow both vertical growth and shrinking; host width stays fixed
486
- const oldHeight = parseFloat(window.getComputedStyle(this.textarea).height);
487
-
488
- // Reset height to measure actual needed height
489
- this.textarea.style.height = 'auto';
860
+ // Store the scroll position and the container's old height for comparison.
861
+ const scrollTop = this.textarea.scrollTop;
862
+ const oldHeight = parseFloat(this.hostDiv.style.height || '0');
863
+
864
+ // 1. **Force a reliable reflow.**
865
+ // First, reset the textarea's height to a minimal value. This is the crucial step
866
+ // that forces the browser to recalculate the content's height from scratch,
867
+ // ignoring the hostDiv's larger, stale height.
868
+ this.textarea.style.height = '1px';
869
+
870
+ // 2. Read the now-accurate scrollHeight. This value reflects the minimum
871
+ // height required for the content, whether it's single or multi-line.
490
872
  const scrollHeight = this.textarea.scrollHeight;
491
-
492
- // Add extra padding to prevent text clipping (especially for line height)
493
- const lineHeightBuffer = 8; // Extra space to prevent clipping
494
- const newHeight = Math.max(scrollHeight + lineHeightBuffer, 25); // Minimum height with buffer
495
- const heightChanged = Math.abs(newHeight - oldHeight) > 2; // Only if meaningful change
496
-
497
- this.textarea.style.height = `${newHeight}px`;
498
- this.hostDiv.style.height = `${newHeight}px`; // Match exactly
499
-
500
- // Only update object bounds if height actually changed
873
+
874
+ // A small buffer for rendering consistency across browsers.
875
+ const buffer = 2;
876
+ const newHeight = scrollHeight + buffer;
877
+
878
+ // Check if the height has changed significantly.
879
+ const heightChanged = Math.abs(newHeight - oldHeight) > 1;
880
+
881
+ // 4. Only update heights and object bounds if there was a change.
501
882
  if (heightChanged) {
883
+ this.textarea.style.height = `${newHeight}px`;
884
+ this.hostDiv.style.height = `${newHeight}px`;
502
885
  this.updateObjectBounds();
886
+ } else {
887
+ // If no significant change, ensure the textarea's height matches the container
888
+ // to prevent any minor visual misalignment.
889
+ this.textarea.style.height = this.hostDiv.style.height;
503
890
  }
891
+
892
+ // 5. Restore the original scroll position.
893
+ this.textarea.scrollTop = scrollTop;
504
894
  }
505
895
 
506
896
  private handleKeyDown(e: KeyboardEvent): void {
@@ -510,6 +900,23 @@ export class OverlayEditor {
510
900
  } else if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
511
901
  e.preventDefault();
512
902
  this.destroy(true); // Commit
903
+ } else if (
904
+ e.key === 'Enter' ||
905
+ e.key === 'Backspace' ||
906
+ e.key === 'Delete'
907
+ ) {
908
+ // For keys that might change the height, schedule a resize check
909
+ // Use both immediate and delayed checks to catch all scenarios
910
+ requestAnimationFrame(() => {
911
+ if (!this.isDestroyed) {
912
+ this.autoResizeTextarea();
913
+ }
914
+ });
915
+ setTimeout(() => {
916
+ if (!this.isDestroyed) {
917
+ this.autoResizeTextarea();
918
+ }
919
+ }, 10); // Small delay to ensure DOM is updated
513
920
  }
514
921
  }
515
922
 
@@ -553,9 +960,10 @@ export class OverlayEditor {
553
960
  private setupViewportChangeDetection(): void {
554
961
  // Store original methods
555
962
  (this.canvas as any).__originalSetZoom = this.canvas.setZoom;
556
- (this.canvas as any).__originalSetViewportTransform = this.canvas.setViewportTransform;
963
+ (this.canvas as any).__originalSetViewportTransform =
964
+ this.canvas.setViewportTransform;
557
965
  (this.canvas as any).__overlayEditor = this;
558
-
966
+
559
967
  // Override setZoom to detect zoom changes
560
968
  const originalSetZoom = this.canvas.setZoom.bind(this.canvas);
561
969
  this.canvas.setZoom = (value: number) => {
@@ -565,9 +973,11 @@ export class OverlayEditor {
565
973
  }
566
974
  return result;
567
975
  };
568
-
976
+
569
977
  // Override setViewportTransform to detect pan changes
570
- const originalSetViewportTransform = this.canvas.setViewportTransform.bind(this.canvas);
978
+ const originalSetViewportTransform = this.canvas.setViewportTransform.bind(
979
+ this.canvas,
980
+ );
571
981
  this.canvas.setViewportTransform = (vpt: TMat2D) => {
572
982
  const result = originalSetViewportTransform(vpt);
573
983
  if ((this.canvas as any).__overlayEditor && !this.isDestroyed) {
@@ -576,7 +986,7 @@ export class OverlayEditor {
576
986
  return result;
577
987
  };
578
988
  }
579
-
989
+
580
990
  /**
581
991
  * Restore original viewport methods
582
992
  */
@@ -586,13 +996,13 @@ export class OverlayEditor {
586
996
  delete (this.canvas as any).__originalSetZoom;
587
997
  }
588
998
  if ((this.canvas as any).__originalSetViewportTransform) {
589
- this.canvas.setViewportTransform = (this.canvas as any).__originalSetViewportTransform;
999
+ this.canvas.setViewportTransform = (
1000
+ this.canvas as any
1001
+ ).__originalSetViewportTransform;
590
1002
  delete (this.canvas as any).__originalSetViewportTransform;
591
1003
  }
592
1004
  delete (this.canvas as any).__overlayEditor;
593
1005
  }
594
-
595
-
596
1006
  }
597
1007
 
598
1008
  /**
@@ -622,7 +1032,7 @@ export function enterTextOverlayEdit(
622
1032
  });
623
1033
 
624
1034
  // We no longer change fill, so no need to store it
625
-
1035
+
626
1036
  // Store reference on target for cleanup
627
1037
  (target as any).__overlayEditor = editor;
628
1038