ChunDe Claude commited on
Commit
3c80b47
·
1 Parent(s): 1ea738f

refactor: Remove offsetX/offsetY from text objects for more reliable positioning

Browse files

Breaking change: Simplifies text positioning by removing offsetX/offsetY properties
and relying on Konva's built-in alignment instead.

Changes:
- Remove offsetX and offsetY from TextObject type definition
- Update all layouts to use proper x/y positioning without offsets
- Remove offset handling from Canvas.tsx (group snapping, single snapping, transforms, textarea positioning)
- Remove offset rendering from TextEditable.tsx
- Update layoutExporter.ts to export fontWeight instead of offsets

Benefits:
- Eliminates complex offset scaling during transformations
- Reduces multiple sources of truth for positioning
- Makes text transformations more predictable and reliable
- Simplifies codebase maintenance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

src/components/Canvas/Canvas.tsx CHANGED
@@ -322,11 +322,9 @@ export default function Canvas({
322
  const nodeHeight = selectedNode.height();
323
  const nodeX = selectedNode.x();
324
  const nodeY = selectedNode.y();
325
- const offsetX = (selectedNode as any).attrs.offsetX || 0;
326
- const offsetY = (selectedNode as any).attrs.offsetY || 0;
327
 
328
- const left = nodeX - offsetX;
329
- const top = nodeY - offsetY;
330
  const right = left + nodeWidth;
331
  const bottom = top + nodeHeight;
332
 
@@ -391,11 +389,9 @@ export default function Canvas({
391
  const nodeX = node.x();
392
  const nodeY = node.y();
393
 
394
- // Calculate object center position (accounting for offsetX/offsetY if present)
395
- const offsetX = (node as any).attrs.offsetX || 0;
396
- const offsetY = (node as any).attrs.offsetY || 0;
397
- const objectCenterX = nodeX + (nodeWidth / 2) - offsetX;
398
- const objectCenterY = nodeY + (nodeHeight / 2) - offsetY;
399
 
400
  let snappedX = nodeX;
401
  let snappedY = nodeY;
@@ -413,14 +409,14 @@ export default function Canvas({
413
  // Check horizontal center snapping
414
  // If Shift is pressed and moving vertically, don't snap horizontally
415
  if (Math.abs(objectCenterX - centerX) < snapThreshold && (!shiftPressed || isHorizontalMovement)) {
416
- snappedX = centerX - (nodeWidth / 2) + offsetX;
417
  showVerticalGuide = true;
418
  }
419
 
420
  // Check vertical center snapping
421
  // If Shift is pressed and moving horizontally, don't snap vertically
422
  if (Math.abs(objectCenterY - centerY) < snapThreshold && (!shiftPressed || !isHorizontalMovement)) {
423
- snappedY = centerY - (nodeHeight / 2) + offsetY;
424
  showHorizontalGuide = true;
425
  }
426
 
@@ -516,21 +512,15 @@ export default function Canvas({
516
  rotation: objNode.rotation()
517
  };
518
 
519
- // Handle text objects: scale fontSize and preserve offsets
520
  if (obj.type === 'text') {
521
  const scaleFactor = Math.min(nodeScaleX, nodeScaleY);
522
  const newFontSize = Math.max(10, obj.fontSize * scaleFactor);
523
 
524
- // Scale offsetX and offsetY if they exist
525
- const scaledOffsetX = obj.offsetX ? obj.offsetX * nodeScaleX : undefined;
526
- const scaledOffsetY = obj.offsetY ? obj.offsetY * nodeScaleY : undefined;
527
-
528
  return {
529
  ...updated,
530
  fontSize: newFontSize,
531
- isFixedSize: true,
532
- offsetX: scaledOffsetX,
533
- offsetY: scaledOffsetY
534
  };
535
  }
536
  return updated;
@@ -564,16 +554,10 @@ export default function Canvas({
564
  const scaleFactor = Math.min(scaleX, scaleY);
565
  const newFontSize = Math.max(10, obj.fontSize * scaleFactor);
566
 
567
- // Scale offsetX and offsetY if they exist
568
- const scaledOffsetX = obj.offsetX ? obj.offsetX * scaleX : undefined;
569
- const scaledOffsetY = obj.offsetY ? obj.offsetY * scaleY : undefined;
570
-
571
  return {
572
  ...updated,
573
  fontSize: newFontSize,
574
- isFixedSize: true,
575
- offsetX: scaledOffsetX,
576
- offsetY: scaledOffsetY
577
  };
578
  }
579
  return updated;
@@ -695,13 +679,9 @@ export default function Canvas({
695
  const container = stage.container();
696
  const containerRect = container.getBoundingClientRect();
697
 
698
- // Account for offsetX and offsetY when positioning textarea
699
- const offsetX = editingText.offsetX || 0;
700
- const offsetY = editingText.offsetY || 0;
701
-
702
  return {
703
- top: containerRect.top + editingText.y - offsetY,
704
- left: containerRect.left + editingText.x - offsetX
705
  };
706
  };
707
 
 
322
  const nodeHeight = selectedNode.height();
323
  const nodeX = selectedNode.x();
324
  const nodeY = selectedNode.y();
 
 
325
 
326
+ const left = nodeX;
327
+ const top = nodeY;
328
  const right = left + nodeWidth;
329
  const bottom = top + nodeHeight;
330
 
 
389
  const nodeX = node.x();
390
  const nodeY = node.y();
391
 
392
+ // Calculate object center position
393
+ const objectCenterX = nodeX + (nodeWidth / 2);
394
+ const objectCenterY = nodeY + (nodeHeight / 2);
 
 
395
 
396
  let snappedX = nodeX;
397
  let snappedY = nodeY;
 
409
  // Check horizontal center snapping
410
  // If Shift is pressed and moving vertically, don't snap horizontally
411
  if (Math.abs(objectCenterX - centerX) < snapThreshold && (!shiftPressed || isHorizontalMovement)) {
412
+ snappedX = centerX - (nodeWidth / 2);
413
  showVerticalGuide = true;
414
  }
415
 
416
  // Check vertical center snapping
417
  // If Shift is pressed and moving horizontally, don't snap vertically
418
  if (Math.abs(objectCenterY - centerY) < snapThreshold && (!shiftPressed || !isHorizontalMovement)) {
419
+ snappedY = centerY - (nodeHeight / 2);
420
  showHorizontalGuide = true;
421
  }
422
 
 
512
  rotation: objNode.rotation()
513
  };
514
 
515
+ // Handle text objects: scale fontSize
516
  if (obj.type === 'text') {
517
  const scaleFactor = Math.min(nodeScaleX, nodeScaleY);
518
  const newFontSize = Math.max(10, obj.fontSize * scaleFactor);
519
 
 
 
 
 
520
  return {
521
  ...updated,
522
  fontSize: newFontSize,
523
+ isFixedSize: true
 
 
524
  };
525
  }
526
  return updated;
 
554
  const scaleFactor = Math.min(scaleX, scaleY);
555
  const newFontSize = Math.max(10, obj.fontSize * scaleFactor);
556
 
 
 
 
 
557
  return {
558
  ...updated,
559
  fontSize: newFontSize,
560
+ isFixedSize: true
 
 
561
  };
562
  }
563
  return updated;
 
679
  const container = stage.container();
680
  const containerRect = container.getBoundingClientRect();
681
 
 
 
 
 
682
  return {
683
+ top: containerRect.top + editingText.y,
684
+ left: containerRect.left + editingText.x
685
  };
686
  };
687
 
src/components/Canvas/TextEditable.tsx CHANGED
@@ -125,8 +125,6 @@ export default function TextEditable({
125
  y={object.y}
126
  width={object.width}
127
  height={object.height}
128
- offsetX={object.offsetX || 0}
129
- offsetY={object.offsetY || 0}
130
  text={object.text}
131
  fontSize={displayFontSize}
132
  fontFamily={object.fontFamily}
 
125
  y={object.y}
126
  width={object.width}
127
  height={object.height}
 
 
128
  text={object.text}
129
  fontSize={displayFontSize}
130
  fontFamily={object.fontFamily}
src/data/layouts.ts CHANGED
@@ -52,7 +52,7 @@ export const LAYOUTS: Record<string, Layout> = {
52
  {
53
  id: 'title-text',
54
  type: 'text',
55
- x: 550,
56
  y: 81.58,
57
  width: 945.2583618164062,
58
  height: 140,
@@ -65,7 +65,6 @@ export const LAYOUTS: Record<string, Layout> = {
65
  bold: true,
66
  italic: false,
67
  align: 'center',
68
- offsetX: 426,
69
  },
70
  {
71
  id: 'logo-placeholder',
@@ -131,7 +130,7 @@ export const LAYOUTS: Record<string, Layout> = {
131
  {
132
  id: 'description-text',
133
  type: 'text',
134
- x: 600,
135
  y: 503.48,
136
  width: 1015,
137
  height: 107,
@@ -145,7 +144,6 @@ export const LAYOUTS: Record<string, Layout> = {
145
  italic: false,
146
  align: 'center',
147
  isFixedSize: true,
148
- offsetX: 507.5,
149
  },
150
  {
151
  id: 'singing-huggy',
@@ -181,7 +179,7 @@ export const LAYOUTS: Record<string, Layout> = {
181
  {
182
  id: 'title-text',
183
  type: 'text',
184
- x: 559.4214782714844,
185
  y: 329.67,
186
  width: 724.1570434570312,
187
  height: 115.93,
@@ -195,13 +193,12 @@ export const LAYOUTS: Record<string, Layout> = {
195
  italic: false,
196
  align: 'center',
197
  isFixedSize: true,
198
- offsetX: 321.5,
199
  fontWeight: 'black',
200
  },
201
  {
202
  id: 'subtitle-text',
203
  type: 'text',
204
- x: 590.1729888916016,
205
  y: 441.535,
206
  width: 440.6540222167969,
207
  height: 63.93,
@@ -215,7 +212,6 @@ export const LAYOUTS: Record<string, Layout> = {
215
  italic: false,
216
  align: 'center',
217
  isFixedSize: true,
218
- offsetX: 210.5,
219
  },
220
  ],
221
  },
 
52
  {
53
  id: 'title-text',
54
  type: 'text',
55
+ x: 124,
56
  y: 81.58,
57
  width: 945.2583618164062,
58
  height: 140,
 
65
  bold: true,
66
  italic: false,
67
  align: 'center',
 
68
  },
69
  {
70
  id: 'logo-placeholder',
 
130
  {
131
  id: 'description-text',
132
  type: 'text',
133
+ x: 92.5,
134
  y: 503.48,
135
  width: 1015,
136
  height: 107,
 
144
  italic: false,
145
  align: 'center',
146
  isFixedSize: true,
 
147
  },
148
  {
149
  id: 'singing-huggy',
 
179
  {
180
  id: 'title-text',
181
  type: 'text',
182
+ x: 237.9214782714844,
183
  y: 329.67,
184
  width: 724.1570434570312,
185
  height: 115.93,
 
193
  italic: false,
194
  align: 'center',
195
  isFixedSize: true,
 
196
  fontWeight: 'black',
197
  },
198
  {
199
  id: 'subtitle-text',
200
  type: 'text',
201
+ x: 379.6729888916016,
202
  y: 441.535,
203
  width: 440.6540222167969,
204
  height: 63.93,
 
212
  italic: false,
213
  align: 'center',
214
  isFixedSize: true,
 
215
  },
216
  ],
217
  },
src/types/canvas.types.ts CHANGED
@@ -45,8 +45,6 @@ export interface TextObject extends CanvasObjectBase {
45
  bold: boolean;
46
  italic: boolean;
47
  align?: 'left' | 'center' | 'right';
48
- offsetX?: number; // Horizontal offset for centering or custom positioning
49
- offsetY?: number; // Vertical offset for centering or custom positioning
50
  isFixedSize?: boolean; // Track if text box size is fixed (Stage 2) or growing (Stage 1)
51
  isEditing?: boolean; // Track if text is currently being edited
52
  fontWeight?: 'normal' | 'bold' | 'black'; // Track font weight variation (normal = 400, bold = 700, black = 900)
 
45
  bold: boolean;
46
  italic: boolean;
47
  align?: 'left' | 'center' | 'right';
 
 
48
  isFixedSize?: boolean; // Track if text box size is fixed (Stage 2) or growing (Stage 1)
49
  isEditing?: boolean; // Track if text is currently being edited
50
  fontWeight?: 'normal' | 'bold' | 'black'; // Track font weight variation (normal = 400, bold = 700, black = 900)
src/utils/layoutExporter.ts CHANGED
@@ -39,8 +39,7 @@ export function exportCanvasAsLayout(
39
  lines.push(`${indent} italic: ${obj.italic},`);
40
  if (obj.align) lines.push(`${indent} align: '${obj.align}',`);
41
  if (obj.isFixedSize) lines.push(`${indent} isFixedSize: ${obj.isFixedSize},`);
42
- if (obj.offsetX !== undefined) lines.push(`${indent} offsetX: ${obj.offsetX},`);
43
- if (obj.offsetY !== undefined) lines.push(`${indent} offsetY: ${obj.offsetY},`);
44
  } else if (obj.type === 'image' || obj.type === 'huggy') {
45
  lines.push(`${indent} src: '${obj.src}',`);
46
  lines.push(`${indent} name: '${obj.name || 'Untitled'}',`);
@@ -88,10 +87,7 @@ export function logCanvasObjects(objects: CanvasObject[]): void {
88
  console.log('Text:', obj.text);
89
  console.log('Font:', `${obj.fontSize}px ${obj.fontFamily}`);
90
  console.log('Color:', obj.fill);
91
- console.log('Styles:', { bold: obj.bold, italic: obj.italic });
92
- if (obj.offsetX !== undefined || obj.offsetY !== undefined) {
93
- console.log('Offset:', { offsetX: obj.offsetX, offsetY: obj.offsetY });
94
- }
95
  } else if (obj.type === 'image' || obj.type === 'huggy') {
96
  console.log('Source:', obj.src);
97
  console.log('Name:', obj.name);
 
39
  lines.push(`${indent} italic: ${obj.italic},`);
40
  if (obj.align) lines.push(`${indent} align: '${obj.align}',`);
41
  if (obj.isFixedSize) lines.push(`${indent} isFixedSize: ${obj.isFixedSize},`);
42
+ if (obj.fontWeight && obj.fontWeight !== 'normal') lines.push(`${indent} fontWeight: '${obj.fontWeight}',`);
 
43
  } else if (obj.type === 'image' || obj.type === 'huggy') {
44
  lines.push(`${indent} src: '${obj.src}',`);
45
  lines.push(`${indent} name: '${obj.name || 'Untitled'}',`);
 
87
  console.log('Text:', obj.text);
88
  console.log('Font:', `${obj.fontSize}px ${obj.fontFamily}`);
89
  console.log('Color:', obj.fill);
90
+ console.log('Styles:', { bold: obj.bold, italic: obj.italic, fontWeight: obj.fontWeight });
 
 
 
91
  } else if (obj.type === 'image' || obj.type === 'huggy') {
92
  console.log('Source:', obj.src);
93
  console.log('Name:', obj.name);