@growgroup/visual-editor 0.3.1 → 0.4.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.
Files changed (60) hide show
  1. package/README.md +6 -1
  2. package/dist/editor.css +1 -1
  3. package/package.json +1 -1
  4. package/src/editor/EditorContext.tsx +3 -2
  5. package/src/editor/EditorToolbar.tsx +8 -2
  6. package/src/editor/FrontendVisualEditor.tsx +123 -44
  7. package/src/editor/components/EditorCanvas.tsx +73 -15
  8. package/src/editor/components/EditorFooter.tsx +2 -0
  9. package/src/editor/components/EditorHeader.tsx +3 -2
  10. package/src/editor/components/FigmaColorPicker.tsx +2 -1
  11. package/src/editor/components/JsEditorDialog.tsx +1 -0
  12. package/src/editor/components/PageSettingsDialog.tsx +1 -0
  13. package/src/editor/components/comments/CommentBoard.tsx +9 -6
  14. package/src/editor/components/multi-page/CanvasFrameDecor.tsx +172 -0
  15. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +179 -77
  16. package/src/editor/components/multi-page/PageFramePreview.tsx +99 -21
  17. package/src/editor/components/multi-page/index.ts +1 -0
  18. package/src/editor/components/ppt/PptChrome.tsx +6 -2
  19. package/src/editor/components/ppt/PptComments.tsx +411 -71
  20. package/src/editor/constants.ts +16 -1
  21. package/src/editor/contexts/EditorArtboardContext.tsx +8 -0
  22. package/src/editor/contexts/EditorComponentsContext.tsx +31 -27
  23. package/src/editor/contexts/EditorUIStateContext.tsx +4 -3
  24. package/src/editor/contexts/EditorVariablesContext.tsx +5 -4
  25. package/src/editor/contexts/MultiPageCanvasContext.tsx +194 -26
  26. package/src/editor/hooks/useAiReplace.ts +4 -3
  27. package/src/editor/hooks/useBrowserZoomPrevention.ts +2 -1
  28. package/src/editor/hooks/useComponentEditMode.ts +27 -26
  29. package/src/editor/hooks/useEditorMessages.ts +3 -2
  30. package/src/editor/hooks/useElementActions.ts +27 -26
  31. package/src/editor/hooks/useElementSelection.ts +19 -16
  32. package/src/editor/hooks/useFocusManagement.ts +7 -6
  33. package/src/editor/hooks/useGoogleFonts.ts +3 -2
  34. package/src/editor/hooks/useIframeInitializer.ts +6 -3
  35. package/src/editor/hooks/useIframeSetup.ts +4 -3
  36. package/src/editor/hooks/useImageUpload.ts +4 -3
  37. package/src/editor/hooks/useMarqueeSelection.ts +2 -1
  38. package/src/editor/hooks/usePageSettingsManager.ts +24 -23
  39. package/src/editor/hooks/useRichPaste.ts +11 -10
  40. package/src/editor/parts.ts +7 -25
  41. package/src/editor/utils/component-renderer.ts +8 -7
  42. package/src/editor/utils/component-sync.ts +16 -15
  43. package/src/editor/utils/debug.ts +52 -0
  44. package/src/editor/utils/dom-utils.ts +43 -11
  45. package/src/editor/utils/drop-target.ts +135 -0
  46. package/src/editor/utils/eyedropper.ts +3 -1
  47. package/src/editor/utils/figma-export.ts +6 -5
  48. package/src/editor/utils/figma-kiwi-decoder.ts +28 -27
  49. package/src/editor/utils/figma-kiwi-encoder.ts +9 -8
  50. package/src/editor/utils/figma-paste.ts +57 -56
  51. package/src/editor/utils/flex-utils.ts +12 -10
  52. package/src/editor/utils/override-apply.ts +3 -2
  53. package/src/editor/utils/override-detection.ts +11 -10
  54. package/src/editor/utils/tailwind-utils.ts +4 -3
  55. package/src/io.ts +22 -1
  56. package/src/lib/firebase/css-variables.ts +4 -3
  57. package/src/lib/firebase/storage.ts +3 -2
  58. package/src/styles/skin.css +9 -4
  59. package/src/types/editor.ts +6 -2
  60. package/src/version.ts +1 -1
@@ -9,6 +9,7 @@ import { readHTMLMessage } from 'fig-kiwi';
9
9
  import { generateElementId } from './dom-utils';
10
10
  import { parseFigmaClipboardToJson, extractNodes, type FigmaNode } from './figma-kiwi-decoder';
11
11
  import { cacheFigKiwiSchema } from './figma-export';
12
+ import { debugLog } from './debug';
12
13
 
13
14
  // Type definitions for Figma data structures
14
15
  // Based on fig-kiwi schema-defs
@@ -254,7 +255,7 @@ export function parseFigmaClipboard(html: string): FigmaPasteResult {
254
255
  // Cache the schema for later use in export
255
256
  if (parsed?.schema) {
256
257
  cacheFigKiwiSchema(parsed.schema);
257
- console.log('[figma-paste] Cached fig-kiwi schema for export');
258
+ debugLog('[figma-paste] Cached fig-kiwi schema for export');
258
259
  }
259
260
  } catch (decodeError) {
260
261
  console.warn('[figma-paste] fig-kiwi decode failed, trying custom decoder:', decodeError);
@@ -267,12 +268,12 @@ export function parseFigmaClipboard(html: string): FigmaPasteResult {
267
268
  // Use custom decoder with extended type support
268
269
  try {
269
270
  const customResult = parseFigmaClipboardToJson(html);
270
- console.log('[figma-paste] Custom decoder result:', customResult.message);
271
- console.log('[figma-paste] Schema has', customResult.schema.definitions.length, 'definitions');
271
+ debugLog('[figma-paste] Custom decoder result:', customResult.message);
272
+ debugLog('[figma-paste] Schema has', customResult.schema.definitions.length, 'definitions');
272
273
 
273
274
  // Extract nodes from custom decoder result
274
275
  const customNodes = extractNodes(customResult.message);
275
- console.log('[figma-paste] Extracted', customNodes.length, 'nodes from custom decoder');
276
+ debugLog('[figma-paste] Extracted', customNodes.length, 'nodes from custom decoder');
276
277
 
277
278
  // Convert FigmaNode to NodeChange format
278
279
  nodeChanges = convertFigmaNodes(customNodes);
@@ -294,7 +295,7 @@ export function parseFigmaClipboard(html: string): FigmaPasteResult {
294
295
  nodeChanges = message.nodeChanges || [];
295
296
  }
296
297
 
297
- console.log('[figma-paste] Parsed nodes:', nodeChanges.length);
298
+ debugLog('[figma-paste] Parsed nodes:', nodeChanges.length);
298
299
 
299
300
  // Convert to SVG
300
301
  const svg = convertNodesToSvg(nodeChanges);
@@ -586,7 +587,7 @@ function nodeToSvgElement(node: NodeChange, defs: string[]): string {
586
587
 
587
588
  default:
588
589
  // Fallback to rectangle for unknown types
589
- console.log(`[figma-paste] Unknown node type: ${type}`);
590
+ debugLog(`[figma-paste] Unknown node type: ${type}`);
590
591
  return wrapWithTransform(
591
592
  `<rect x="0" y="0" width="${width}" height="${height}" ${styleStr} data-name="${escapeXml(name)}" data-type="${type}"/>`
592
593
  );
@@ -850,7 +851,7 @@ function buildNodeHierarchy(nodes: NodeChange[]): HierarchyNode[] {
850
851
 
851
852
  // Check if nodes have parentIndex with valid guid
852
853
  const hasParentIndex = nodes.some(n => n.parentIndex?.guid !== undefined);
853
- console.log('[figma-paste] Has parentIndex.guid field:', hasParentIndex);
854
+ debugLog('[figma-paste] Has parentIndex.guid field:', hasParentIndex);
854
855
 
855
856
  if (hasParentIndex) {
856
857
  return buildHierarchyFromParentIndex(nodes);
@@ -863,7 +864,7 @@ function buildNodeHierarchy(nodes: NodeChange[]): HierarchyNode[] {
863
864
  * Build hierarchy using Figma's parentIndex field (accurate method)
864
865
  */
865
866
  function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
866
- console.log('[figma-paste] Building hierarchy from parentIndex for', nodes.length, 'nodes');
867
+ debugLog('[figma-paste] Building hierarchy from parentIndex for', nodes.length, 'nodes');
867
868
 
868
869
  // Create hierarchy nodes and map by GUID
869
870
  const nodeMap = new Map<string, HierarchyNodeWithBounds>();
@@ -882,7 +883,7 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
882
883
  const guidKey = guid ? `${guid.sessionID}:${guid.localID}` : 'no-guid';
883
884
  const parentKey = parentGuid ? `${parentGuid.sessionID}:${parentGuid.localID}` : 'no-parent';
884
885
 
885
- console.log(`[figma-paste] Node: ${node.type} "${node.name}" guid=${guidKey} parent=${parentKey} pos=(${x.toFixed(0)},${y.toFixed(0)}) size=${width.toFixed(0)}x${height.toFixed(0)}`);
886
+ debugLog(`[figma-paste] Node: ${node.type} "${node.name}" guid=${guidKey} parent=${parentKey} pos=(${x.toFixed(0)},${y.toFixed(0)}) size=${width.toFixed(0)}x${height.toFixed(0)}`);
886
887
 
887
888
  const hNode: HierarchyNodeWithBounds = {
888
889
  node,
@@ -902,7 +903,7 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
902
903
  }
903
904
  }
904
905
 
905
- console.log('[figma-paste] Node map has', nodeMap.size, 'entries');
906
+ debugLog('[figma-paste] Node map has', nodeMap.size, 'entries');
906
907
 
907
908
  // Second pass: build parent-child relationships
908
909
  const roots: HierarchyNodeWithBounds[] = [];
@@ -916,26 +917,26 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
916
917
  const parent = nodeMap.get(parentKey);
917
918
 
918
919
  if (parent) {
919
- console.log(`[figma-paste] ${nodeName} → child of ${parent.node.type} "${parent.node.name}"`);
920
+ debugLog(`[figma-paste] ${nodeName} → child of ${parent.node.type} "${parent.node.name}"`);
920
921
  parent.children.push(hNode);
921
922
  childCount++;
922
923
  } else {
923
924
  // Parent not found in copied nodes - this is a root
924
- console.log(`[figma-paste] ${nodeName} → ROOT (parent ${parentKey} not in selection)`);
925
+ debugLog(`[figma-paste] ${nodeName} → ROOT (parent ${parentKey} not in selection)`);
925
926
  hNode.relativeX = 0;
926
927
  hNode.relativeY = 0;
927
928
  roots.push(hNode);
928
929
  }
929
930
  } else {
930
931
  // No parent - this is a root
931
- console.log(`[figma-paste] ${nodeName} → ROOT (no parentIndex)`);
932
+ debugLog(`[figma-paste] ${nodeName} → ROOT (no parentIndex)`);
932
933
  hNode.relativeX = 0;
933
934
  hNode.relativeY = 0;
934
935
  roots.push(hNode);
935
936
  }
936
937
  }
937
938
 
938
- console.log(`[figma-paste] Result: ${roots.length} roots, ${childCount} children linked`);
939
+ debugLog(`[figma-paste] Result: ${roots.length} roots, ${childCount} children linked`);
939
940
 
940
941
  // Debug: Print tree structure
941
942
  printHierarchyTree(roots);
@@ -947,7 +948,7 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
947
948
  * Build hierarchy using containment-based detection (fallback method)
948
949
  */
949
950
  function buildHierarchyFromContainment(nodes: NodeChange[]): HierarchyNode[] {
950
- console.log('[figma-paste] Building hierarchy from containment (fallback)');
951
+ debugLog('[figma-paste] Building hierarchy from containment (fallback)');
951
952
 
952
953
  // Sort by area descending - larger containers processed first
953
954
  const sorted = [...nodes].sort((a, b) => {
@@ -1014,9 +1015,9 @@ function buildHierarchyFromContainment(nodes: NodeChange[]): HierarchyNode[] {
1014
1015
  * Debug helper: Print hierarchy tree
1015
1016
  */
1016
1017
  function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
1017
- console.log('[figma-paste] ========================================');
1018
- console.log('[figma-paste] HIERARCHY STRUCTURE:', roots.length, 'root(s)');
1019
- console.log('[figma-paste] ========================================');
1018
+ debugLog('[figma-paste] ========================================');
1019
+ debugLog('[figma-paste] HIERARCHY STRUCTURE:', roots.length, 'root(s)');
1020
+ debugLog('[figma-paste] ========================================');
1020
1021
 
1021
1022
  function countNodes(node: HierarchyNodeWithBounds): number {
1022
1023
  return 1 + node.children.reduce((sum, c) => sum + countNodes(c as HierarchyNodeWithBounds), 0);
@@ -1028,7 +1029,7 @@ function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
1028
1029
  const sizeStr = node.width > 0 && node.height > 0
1029
1030
  ? `${node.width.toFixed(0)}x${node.height.toFixed(0)}`
1030
1031
  : 'no-size';
1031
- console.log(`[figma-paste] ${indent}├─ ${node.node.type} "${node.node.name}"${imageMarker} [${sizeStr}] (${node.relativeX.toFixed(0)},${node.relativeY.toFixed(0)})`);
1032
+ debugLog(`[figma-paste] ${indent}├─ ${node.node.type} "${node.node.name}"${imageMarker} [${sizeStr}] (${node.relativeX.toFixed(0)},${node.relativeY.toFixed(0)})`);
1032
1033
  for (const child of node.children) {
1033
1034
  printTree(child as HierarchyNodeWithBounds, indent + '│ ');
1034
1035
  }
@@ -1040,7 +1041,7 @@ function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
1040
1041
  for (const root of roots) {
1041
1042
  totalNodes += countNodes(root);
1042
1043
  printTree(root);
1043
- console.log('[figma-paste] ----------------------------------------');
1044
+ debugLog('[figma-paste] ----------------------------------------');
1044
1045
  }
1045
1046
 
1046
1047
  // Count nodes with image fills
@@ -1053,11 +1054,11 @@ function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
1053
1054
  nodesWithImages += countImagesInTree(root);
1054
1055
  }
1055
1056
 
1056
- console.log(`[figma-paste] Summary: ${totalNodes} nodes, ${nodesWithImages} with image fills, ${roots.length} root(s)`);
1057
+ debugLog(`[figma-paste] Summary: ${totalNodes} nodes, ${nodesWithImages} with image fills, ${roots.length} root(s)`);
1057
1058
  if (roots.length > 1) {
1058
1059
  console.warn('[figma-paste] ⚠️ Multiple roots detected - elements will stack vertically');
1059
1060
  }
1060
- console.log('[figma-paste] ========================================');
1061
+ debugLog('[figma-paste] ========================================');
1061
1062
  }
1062
1063
 
1063
1064
  /**
@@ -1075,7 +1076,7 @@ function hierarchyNodeToElement(
1075
1076
 
1076
1077
  // Skip invisible nodes - but still process children in case they're visible
1077
1078
  if (node.visible === false) {
1078
- console.log(`[figma-paste] Skip invisible node: ${node.type} "${node.name}"`);
1079
+ debugLog(`[figma-paste] Skip invisible node: ${node.type} "${node.name}"`);
1079
1080
  // Still process children - they might be visible even if parent is marked invisible
1080
1081
  const visibleChildren: HTMLElement[] = [];
1081
1082
  for (const child of hNode.children) {
@@ -1092,7 +1093,7 @@ function hierarchyNodeToElement(
1092
1093
  // Skip nodes without valid size (but not containers which may have zero size themselves)
1093
1094
  const isContainer = ['FRAME', 'GROUP', 'COMPONENT', 'INSTANCE', 'SECTION'].includes(node.type || '');
1094
1095
  if (!isContainer && (!node.size || node.size.x <= 0 || node.size.y <= 0)) {
1095
- console.log(`[figma-paste] Skip zero-size node: ${node.type} "${node.name}"`);
1096
+ debugLog(`[figma-paste] Skip zero-size node: ${node.type} "${node.name}"`);
1096
1097
  return null;
1097
1098
  }
1098
1099
 
@@ -1340,7 +1341,7 @@ function hierarchyNodeToElement(
1340
1341
  }
1341
1342
  }
1342
1343
  } else {
1343
- console.log(`[figma-paste] Skipping children for ${node.type} "${node.name}" (will render as instance)`);
1344
+ debugLog(`[figma-paste] Skipping children for ${node.type} "${node.name}" (will render as instance)`);
1344
1345
  }
1345
1346
 
1346
1347
  return el;
@@ -1420,7 +1421,7 @@ function getBackgroundCss(paints: Paint[] | undefined): BackgroundCssResult {
1420
1421
  backgroundPosition = 'center';
1421
1422
  }
1422
1423
 
1423
- console.log(`[figma-paste] IMAGE fill detected: hash=${detectedHash?.substring(0, 16) || 'none'} scaleMode=${scaleMode}`);
1424
+ debugLog(`[figma-paste] IMAGE fill detected: hash=${detectedHash?.substring(0, 16) || 'none'} scaleMode=${scaleMode}`);
1424
1425
  }
1425
1426
  }
1426
1427
 
@@ -1827,7 +1828,7 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
1827
1828
  // If decode failed (schema version mismatch), signal to fallback to image
1828
1829
  if (!result.success) {
1829
1830
  if (result.error === 'DECODE_FAILED') {
1830
- console.log('[figma-paste] Decode failed, should fallback to image paste');
1831
+ debugLog('[figma-paste] Decode failed, should fallback to image paste');
1831
1832
  return { elements: [], shouldFallbackToImage: true };
1832
1833
  }
1833
1834
  console.warn('[figma-paste] Failed to process Figma paste:', result.error);
@@ -1842,22 +1843,22 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
1842
1843
  // Collect image hashes and node IDs from all nodes
1843
1844
  const imageInfo = collectImageInfo(result.nodes);
1844
1845
  if (imageInfo.hashes.length > 0) {
1845
- console.log(`[figma-paste] Found ${imageInfo.hashes.length} image hashes:`, imageInfo.hashes);
1846
+ debugLog(`[figma-paste] Found ${imageInfo.hashes.length} image hashes:`, imageInfo.hashes);
1846
1847
  }
1847
1848
  if (imageInfo.nodeIds.length > 0) {
1848
- console.log(`[figma-paste] Found ${imageInfo.nodeIds.length} image node IDs:`, imageInfo.nodeIds);
1849
+ debugLog(`[figma-paste] Found ${imageInfo.nodeIds.length} image node IDs:`, imageInfo.nodeIds);
1849
1850
  }
1850
1851
 
1851
1852
  // Filter out only document structure nodes - keep everything else for hierarchy building
1852
1853
  // IMPORTANT: We must NOT filter nodes before building hierarchy, or children become orphaned
1853
1854
  const SKIP_NODE_TYPES = ['DOCUMENT', 'CANVAS', 'PAGE'];
1854
- console.log('[figma-paste] Total nodes from clipboard:', result.nodes.length);
1855
+ debugLog('[figma-paste] Total nodes from clipboard:', result.nodes.length);
1855
1856
 
1856
1857
  // Only remove document structure nodes - keep all content nodes including containers
1857
1858
  // The hierarchy builder needs parent nodes to properly nest children
1858
1859
  const allContentNodes = result.nodes.filter(n => {
1859
1860
  if (n.type && SKIP_NODE_TYPES.includes(n.type)) {
1860
- console.log(`[figma-paste] SKIP: ${n.type} "${n.name}" (structure node)`);
1861
+ debugLog(`[figma-paste] SKIP: ${n.type} "${n.name}" (structure node)`);
1861
1862
  return false;
1862
1863
  }
1863
1864
  return true;
@@ -1868,7 +1869,7 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
1868
1869
  return { elements: [], shouldFallbackToImage: true };
1869
1870
  }
1870
1871
 
1871
- console.log('[figma-paste] Content nodes for hierarchy:', allContentNodes.length);
1872
+ debugLog('[figma-paste] Content nodes for hierarchy:', allContentNodes.length);
1872
1873
 
1873
1874
  // Convert ALL nodes to DOM elements - hierarchy builder handles parent-child nesting
1874
1875
  // Filtering of invisible/zero-size nodes happens during element conversion, not before
@@ -1881,7 +1882,7 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
1881
1882
 
1882
1883
  // Log generated elements
1883
1884
  for (const el of elements) {
1884
- console.log('[figma-paste] Generated element:',
1885
+ debugLog('[figma-paste] Generated element:',
1885
1886
  el.style.width, 'x', el.style.height,
1886
1887
  'with', el.children.length, 'nested children');
1887
1888
  }
@@ -1924,7 +1925,7 @@ function deepExtractHash(value: unknown, maxDepth: number): string | null {
1924
1925
  if (obj[key] !== undefined) {
1925
1926
  const result = deepExtractHash(obj[key], maxDepth - 1);
1926
1927
  if (result) {
1927
- console.log(`[figma-paste] Found hash via key: ${key}`);
1928
+ debugLog(`[figma-paste] Found hash via key: ${key}`);
1928
1929
  return result;
1929
1930
  }
1930
1931
  }
@@ -1934,7 +1935,7 @@ function deepExtractHash(value: unknown, maxDepth: number): string | null {
1934
1935
  if (['hash', 'value', 'ref', 'data', 'bytes', 'imageHash', 'imageRef'].includes(k)) continue;
1935
1936
  const result = deepExtractHash(v, maxDepth - 1);
1936
1937
  if (result) {
1937
- console.log(`[figma-paste] Found hash via deep search at key: ${k}`);
1938
+ debugLog(`[figma-paste] Found hash via deep search at key: ${k}`);
1938
1939
  return result;
1939
1940
  }
1940
1941
  }
@@ -1956,7 +1957,7 @@ function extractImageHashFromPaint(paint: Record<string, unknown>): string | nul
1956
1957
 
1957
1958
  const hash = deepExtractHash(field, 4); // search up to 4 levels deep
1958
1959
  if (hash) {
1959
- console.log(`[figma-paste] Extracted hash from paint.${key}: ${hash.substring(0, 20)}...`);
1960
+ debugLog(`[figma-paste] Extracted hash from paint.${key}: ${hash.substring(0, 20)}...`);
1960
1961
  return hash;
1961
1962
  }
1962
1963
  }
@@ -1993,7 +1994,7 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
1993
1994
  const nodeId = `${node.guid.sessionID}:${node.guid.localID}`;
1994
1995
  instanceNodeIds.add(nodeId);
1995
1996
  nodeIds.add(nodeId);
1996
- console.log(`[figma-paste] INSTANCE/COMPONENT "${node.name}" will be rendered as single image: ${nodeId}`);
1997
+ debugLog(`[figma-paste] INSTANCE/COMPONENT "${node.name}" will be rendered as single image: ${nodeId}`);
1997
1998
  }
1998
1999
  }
1999
2000
  }
@@ -2018,7 +2019,7 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
2018
2019
 
2019
2020
  // Skip children of INSTANCE/COMPONENT nodes (they're rendered as part of the parent)
2020
2021
  if (childrenOfInstances.has(nodeId)) {
2021
- console.log(`[figma-paste] Skipping child of instance: "${node.name}"`);
2022
+ debugLog(`[figma-paste] Skipping child of instance: "${node.name}"`);
2022
2023
  continue;
2023
2024
  }
2024
2025
 
@@ -2034,15 +2035,15 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
2034
2035
  if (paint.type === 'IMAGE') {
2035
2036
  // Deep-inspect the `image` field
2036
2037
  const imageField = anyPaint.image;
2037
- console.log(`[figma-paste] IMAGE paint on "${node.name}": image field type=${typeof imageField}, isUint8Array=${imageField instanceof Uint8Array}`);
2038
+ debugLog(`[figma-paste] IMAGE paint on "${node.name}": image field type=${typeof imageField}, isUint8Array=${imageField instanceof Uint8Array}`);
2038
2039
 
2039
2040
  if (imageField && typeof imageField === 'object') {
2040
2041
  if (imageField instanceof Uint8Array) {
2041
- console.log(`[figma-paste] image = <Uint8Array(${imageField.length})> hex=${bytesToHex(imageField).substring(0, 40)}...`);
2042
+ debugLog(`[figma-paste] image = <Uint8Array(${imageField.length})> hex=${bytesToHex(imageField).substring(0, 40)}...`);
2042
2043
  } else {
2043
2044
  // Log nested object structure for debugging
2044
2045
  const imgObj = imageField as Record<string, unknown>;
2045
- console.log(`[figma-paste] image object keys: [${Object.keys(imgObj).join(', ')}]`);
2046
+ debugLog(`[figma-paste] image object keys: [${Object.keys(imgObj).join(', ')}]`);
2046
2047
  logObjectDeep(imgObj, 'image', 2);
2047
2048
  }
2048
2049
  }
@@ -2050,23 +2051,23 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
2050
2051
  // Extract hash using recursive deep search
2051
2052
  const hash = extractImageHashFromPaint(anyPaint);
2052
2053
  if (hash) {
2053
- console.log(`[figma-paste] => extracted hash: "${hash.substring(0, 40)}${hash.length > 40 ? '...' : ''}"`);
2054
+ debugLog(`[figma-paste] => extracted hash: "${hash.substring(0, 40)}${hash.length > 40 ? '...' : ''}"`);
2054
2055
  hashes.add(hash);
2055
2056
  } else {
2056
- console.log(`[figma-paste] => NO hash extracted from paint`);
2057
+ debugLog(`[figma-paste] => NO hash extracted from paint`);
2057
2058
  }
2058
2059
 
2059
2060
  // Collect node GUID for fallback rendering
2060
2061
  if (node.guid) {
2061
2062
  nodeIds.add(nodeId);
2062
- console.log(`[figma-paste] => node ID for rendering: ${nodeId}`);
2063
+ debugLog(`[figma-paste] => node ID for rendering: ${nodeId}`);
2063
2064
  }
2064
2065
  }
2065
2066
  }
2066
2067
  }
2067
2068
  }
2068
2069
 
2069
- console.log(`[figma-paste] collectImageInfo: ${hashes.size} hashes, ${nodeIds.size} node IDs (${instanceNodeIds.size} instances)`);
2070
+ debugLog(`[figma-paste] collectImageInfo: ${hashes.size} hashes, ${nodeIds.size} node IDs (${instanceNodeIds.size} instances)`);
2070
2071
  return {
2071
2072
  hashes: Array.from(hashes),
2072
2073
  nodeIds: Array.from(nodeIds),
@@ -2080,13 +2081,13 @@ function logObjectDeep(obj: Record<string, unknown>, prefix: string, maxDepth: n
2080
2081
  if (maxDepth <= 0) return;
2081
2082
  for (const [k, v] of Object.entries(obj)) {
2082
2083
  if (v instanceof Uint8Array) {
2083
- console.log(`[figma-paste] ${prefix}.${k} = <Uint8Array(${v.length})> hex=${bytesToHex(v).substring(0, 40)}...`);
2084
+ debugLog(`[figma-paste] ${prefix}.${k} = <Uint8Array(${v.length})> hex=${bytesToHex(v).substring(0, 40)}...`);
2084
2085
  } else if (typeof v === 'object' && v !== null) {
2085
2086
  const nested = v as Record<string, unknown>;
2086
- console.log(`[figma-paste] ${prefix}.${k} = {${Object.keys(nested).join(', ')}}`);
2087
+ debugLog(`[figma-paste] ${prefix}.${k} = {${Object.keys(nested).join(', ')}}`);
2087
2088
  logObjectDeep(nested, `${prefix}.${k}`, maxDepth - 1);
2088
2089
  } else {
2089
- console.log(`[figma-paste] ${prefix}.${k} = ${JSON.stringify(v)}`.substring(0, 120));
2090
+ debugLog(`[figma-paste] ${prefix}.${k} = ${JSON.stringify(v)}`.substring(0, 120));
2090
2091
  }
2091
2092
  }
2092
2093
  }
@@ -2126,7 +2127,7 @@ export async function fetchAndApplyFigmaImages(
2126
2127
 
2127
2128
  // Strategy 1: Try image fills API with hash matching
2128
2129
  if (hasHashes) {
2129
- console.log(`[figma-paste] Strategy 1: Fetching ${imageHashes.length} images by hash...`);
2130
+ debugLog(`[figma-paste] Strategy 1: Fetching ${imageHashes.length} images by hash...`);
2130
2131
  try {
2131
2132
  const response = await fetch('/api/figma/images', {
2132
2133
  method: 'POST',
@@ -2138,11 +2139,11 @@ export async function fetchAndApplyFigmaImages(
2138
2139
  const data = await response.json();
2139
2140
  if (data.success && data.images) {
2140
2141
  const imageMap = data.images as Record<string, string>;
2141
- console.log(`[figma-paste] Strategy 1: Got ${Object.keys(imageMap).length} image URLs`);
2142
+ debugLog(`[figma-paste] Strategy 1: Got ${Object.keys(imageMap).length} image URLs`);
2142
2143
 
2143
2144
  // Also log all available image refs for debugging
2144
2145
  if (data.allImageRefs) {
2145
- console.log(`[figma-paste] All available image refs in file:`, data.allImageRefs);
2146
+ debugLog(`[figma-paste] All available image refs in file:`, data.allImageRefs);
2146
2147
  }
2147
2148
 
2148
2149
  appliedCount += applyImagesByHash(rootElement, imageMap);
@@ -2159,7 +2160,7 @@ export async function fetchAndApplyFigmaImages(
2159
2160
  // Strategy 2: Fall back to node rendering if hash matching didn't cover all elements
2160
2161
  const remainingElements = Array.from(rootElement.querySelectorAll('[data-needs-image]'));
2161
2162
  if (remainingElements.length > 0 && hasNodeIds) {
2162
- console.log(`[figma-paste] Strategy 2: Rendering ${remainingElements.length} nodes via API...`);
2163
+ debugLog(`[figma-paste] Strategy 2: Rendering ${remainingElements.length} nodes via API...`);
2163
2164
  try {
2164
2165
  // Collect node IDs from remaining elements
2165
2166
  const nodeIdsToRender: string[] = [];
@@ -2181,7 +2182,7 @@ export async function fetchAndApplyFigmaImages(
2181
2182
  const data = await response.json();
2182
2183
  if (data.success && data.images) {
2183
2184
  const nodeImageMap = data.images as Record<string, string>;
2184
- console.log(`[figma-paste] Strategy 2: Got ${Object.keys(nodeImageMap).length} rendered images`);
2185
+ debugLog(`[figma-paste] Strategy 2: Got ${Object.keys(nodeImageMap).length} rendered images`);
2185
2186
  appliedCount += applyImagesByNodeId(rootElement, nodeImageMap);
2186
2187
  }
2187
2188
  } else {
@@ -2194,7 +2195,7 @@ export async function fetchAndApplyFigmaImages(
2194
2195
  }
2195
2196
  }
2196
2197
 
2197
- console.log(`[figma-paste] Total applied: ${appliedCount} images`);
2198
+ debugLog(`[figma-paste] Total applied: ${appliedCount} images`);
2198
2199
  return { applied: appliedCount, errors };
2199
2200
  }
2200
2201
 
@@ -2210,7 +2211,7 @@ function applyImagesByHash(rootElement: HTMLElement | Document, imageMap: Record
2210
2211
  if (hash && imageMap[hash]) {
2211
2212
  applyImageToElement(el as HTMLElement, imageMap[hash]);
2212
2213
  applied++;
2213
- console.log(`[figma-paste] Applied image by hash: ${hash.substring(0, 16)}... → ${el.getAttribute('data-name') || 'unnamed'}`);
2214
+ debugLog(`[figma-paste] Applied image by hash: ${hash.substring(0, 16)}... → ${el.getAttribute('data-name') || 'unnamed'}`);
2214
2215
  }
2215
2216
  }
2216
2217
 
@@ -2229,7 +2230,7 @@ function applyImagesByNodeId(rootElement: HTMLElement | Document, nodeImageMap:
2229
2230
  if (nodeId && nodeImageMap[nodeId]) {
2230
2231
  applyImageToElement(el as HTMLElement, nodeImageMap[nodeId]);
2231
2232
  applied++;
2232
- console.log(`[figma-paste] Applied image by node render: ${nodeId} → ${el.getAttribute('data-name') || 'unnamed'}`);
2233
+ debugLog(`[figma-paste] Applied image by node render: ${nodeId} → ${el.getAttribute('data-name') || 'unnamed'}`);
2233
2234
  }
2234
2235
  }
2235
2236
 
@@ -3,6 +3,8 @@
3
3
  * Concrete5-style element reordering in auto-layout containers
4
4
  */
5
5
 
6
+ import { debugLog } from './debug';
7
+
6
8
  /**
7
9
  * Information about a flex container
8
10
  */
@@ -479,7 +481,7 @@ export function endAutoLayoutDrag(
479
481
  const { element, ghost, originalParent, originalIndex, scale } = state;
480
482
 
481
483
  // Debug logging
482
- console.log('[endAutoLayoutDrag] Starting:', {
484
+ debugLog('[endAutoLayoutDrag] Starting:', {
483
485
  elementTag: element?.tagName,
484
486
  elementId: element?.id,
485
487
  elementClass: element?.className,
@@ -519,7 +521,7 @@ export function endAutoLayoutDrag(
519
521
  // Get the current parent of the element (should match originalParent in most cases)
520
522
  const currentParent = element.parentElement;
521
523
 
522
- console.log('[endAutoLayoutDrag] Before DOM operation:', {
524
+ debugLog('[endAutoLayoutDrag] Before DOM operation:', {
523
525
  elementInDOM: element.isConnected,
524
526
  currentParentTag: currentParent?.tagName,
525
527
  currentParentId: currentParent?.id,
@@ -545,7 +547,7 @@ export function endAutoLayoutDrag(
545
547
  dropInfo.container.id === 'artboard'
546
548
  );
547
549
 
548
- console.log('[endAutoLayoutDrag] isSameParent:', isSameParent, 'isValidDropLocation:', isValidDropLocation);
550
+ debugLog('[endAutoLayoutDrag] isSameParent:', isSameParent, 'isValidDropLocation:', isValidDropLocation);
549
551
 
550
552
  if (!isValidDropLocation && dropInfo) {
551
553
  // Drop location is outside artboard - keep element in original position
@@ -554,16 +556,16 @@ export function endAutoLayoutDrag(
554
556
  } else if (dropInfo && isSameParent && originalParent) {
555
557
  // Same parent - reorder within the same container
556
558
  const targetIndex = dropInfo.index;
557
- console.log('[endAutoLayoutDrag] Same parent reorder:', { targetIndex, originalIndex });
559
+ debugLog('[endAutoLayoutDrag] Same parent reorder:', { targetIndex, originalIndex });
558
560
  if (targetIndex !== originalIndex) {
559
561
  moved = reorderFlexChild(originalParent, element, targetIndex);
560
- console.log('[endAutoLayoutDrag] reorderFlexChild result:', moved);
562
+ debugLog('[endAutoLayoutDrag] reorderFlexChild result:', moved);
561
563
  }
562
564
  // If targetIndex === originalIndex, no move needed (element stays in place)
563
565
  } else if (dropInfo && dropInfo.container && isValidDropLocation) {
564
566
  // Different parent but valid location - move to new container
565
567
  // Safety check: don't move into self or descendants
566
- console.log('[endAutoLayoutDrag] Different parent move:', {
568
+ debugLog('[endAutoLayoutDrag] Different parent move:', {
567
569
  containerTag: dropInfo.container.tagName,
568
570
  containerId: dropInfo.container.id,
569
571
  isElementSelf: dropInfo.container === element,
@@ -571,15 +573,15 @@ export function endAutoLayoutDrag(
571
573
  });
572
574
  if (dropInfo.container !== element && !element.contains(dropInfo.container)) {
573
575
  moved = moveElementToContainer(element, dropInfo);
574
- console.log('[endAutoLayoutDrag] moveElementToContainer result:', moved);
576
+ debugLog('[endAutoLayoutDrag] moveElementToContainer result:', moved);
575
577
  } else {
576
578
  console.warn('[flex-utils] Prevented moving element into itself or descendant');
577
579
  }
578
580
  } else {
579
- console.log('[endAutoLayoutDrag] No dropInfo or container, element stays in place');
581
+ debugLog('[endAutoLayoutDrag] No dropInfo or container, element stays in place');
580
582
  }
581
583
 
582
- console.log('[endAutoLayoutDrag] After DOM operation:', {
584
+ debugLog('[endAutoLayoutDrag] After DOM operation:', {
583
585
  elementInDOM: element.isConnected,
584
586
  hasParent: !!element.parentElement,
585
587
  parentTag: element.parentElement?.tagName,
@@ -1142,7 +1144,7 @@ function moveElementToContainer(
1142
1144
  ): boolean {
1143
1145
  const { container, position, index, referenceElement } = dropInfo;
1144
1146
 
1145
- console.log('[moveElementToContainer] Called:', {
1147
+ debugLog('[moveElementToContainer] Called:', {
1146
1148
  elementTag: element.tagName,
1147
1149
  elementId: element.id,
1148
1150
  containerTag: container?.tagName,
@@ -15,6 +15,7 @@ import type {
15
15
  OverrideInstanceSwapValue,
16
16
  } from '../../types/editor-components';
17
17
  import { findElementById, findElementByPath } from './override-detection';
18
+ import { debugLog } from './debug';
18
19
 
19
20
  /**
20
21
  * Deep clone a ComponentElement tree
@@ -121,7 +122,7 @@ export function applyOverride(
121
122
 
122
123
  case 'instanceSwap':
123
124
  // Instance swap is handled at a higher level (requires loading the swapped component)
124
- console.log('[applyOverride] Instance swap override detected:', override.value);
125
+ debugLog('[applyOverride] Instance swap override detected:', override.value);
125
126
  break;
126
127
 
127
128
  default:
@@ -245,7 +246,7 @@ export function applyOverrideToDOM(
245
246
  break;
246
247
 
247
248
  case 'instanceSwap':
248
- console.log('[applyOverrideToDOM] Instance swap requires higher-level handling');
249
+ debugLog('[applyOverrideToDOM] Instance swap requires higher-level handling');
249
250
  break;
250
251
 
251
252
  default:
@@ -14,6 +14,7 @@ import type {
14
14
  ComponentInstance,
15
15
  OverridableProperties,
16
16
  } from '../../types/editor-components';
17
+ import { debugLog } from './debug';
17
18
 
18
19
  /**
19
20
  * Find an element within the ComponentElement tree by ID
@@ -148,7 +149,7 @@ function detectSinglePropertyOverride(
148
149
  switch (type) {
149
150
  case 'text': {
150
151
  // Debug: Log text comparison attempt
151
- console.log('[detectOverrides] Text comparison for element:', {
152
+ debugLog('[detectOverrides] Text comparison for element:', {
152
153
  elementId,
153
154
  masterHasInnerHTML: masterElement.innerHTML !== undefined,
154
155
  masterChildrenCount: masterElement.children.length,
@@ -160,7 +161,7 @@ function detectSinglePropertyOverride(
160
161
  // Skip text comparison for elements that use innerHTML (mixed content)
161
162
  // because the text is embedded in the HTML structure
162
163
  if (masterElement.innerHTML !== undefined) {
163
- console.log('[detectOverrides] Skipping: element uses innerHTML');
164
+ debugLog('[detectOverrides] Skipping: element uses innerHTML');
164
165
  return null;
165
166
  }
166
167
 
@@ -171,7 +172,7 @@ function detectSinglePropertyOverride(
171
172
  if (masterElement.children.length === 0) {
172
173
  const masterText = masterElement.textContent || '';
173
174
 
174
- console.log('[detectOverrides] Comparing text (leaf element):', {
175
+ debugLog('[detectOverrides] Comparing text (leaf element):', {
175
176
  elementId,
176
177
  instanceText,
177
178
  masterText,
@@ -179,7 +180,7 @@ function detectSinglePropertyOverride(
179
180
  });
180
181
 
181
182
  if (instanceText !== masterText) {
182
- console.log('[detectOverrides] Text override detected:', {
183
+ debugLog('[detectOverrides] Text override detected:', {
183
184
  elementId,
184
185
  instanceText: instanceText.substring(0, 50),
185
186
  masterText: masterText.substring(0, 50),
@@ -196,7 +197,7 @@ function detectSinglePropertyOverride(
196
197
  // Text overrides should only be created at leaf elements (no children)
197
198
  // Creating a parent-level text override would destroy the child structure
198
199
  // when the override is applied (textContent replaces all children)
199
- console.log('[detectOverrides] Skipping text override for element with children:', {
200
+ debugLog('[detectOverrides] Skipping text override for element with children:', {
200
201
  elementId,
201
202
  childrenCount: masterElement.children.length,
202
203
  reason: 'Text overrides at parent level would destroy child structure',
@@ -351,7 +352,7 @@ export function detectOverrides(
351
352
 
352
353
  // Debug: Log child matching
353
354
  if (componentElement.children.length > 0 || domChildren.length > 0) {
354
- console.log('[detectOverrides] Child matching:', {
355
+ debugLog('[detectOverrides] Child matching:', {
355
356
  parentId: componentElement.id,
356
357
  domChildrenCount: domChildren.length,
357
358
  domChildrenIds: domChildren.map(dc => dc.getAttribute('data-element-id')),
@@ -375,7 +376,7 @@ export function detectOverrides(
375
376
  if (matchingDomChild) {
376
377
  processElement(matchingDomChild, componentChild);
377
378
  } else {
378
- console.log('[detectOverrides] No matching DOM child for component child:', {
379
+ debugLog('[detectOverrides] No matching DOM child for component child:', {
379
380
  componentChildId: componentChild.id,
380
381
  componentChildTagName: componentChild.tagName,
381
382
  availableDomIds: domChildren.map(dc => dc.getAttribute('data-element-id')),
@@ -387,10 +388,10 @@ export function detectOverrides(
387
388
 
388
389
  processElement(instanceElement, masterElement);
389
390
 
390
- console.log('[detectOverrides] ========== FINAL RESULT ==========');
391
- console.log('[detectOverrides] Total overrides detected:', overrides.length);
391
+ debugLog('[detectOverrides] ========== FINAL RESULT ==========');
392
+ debugLog('[detectOverrides] Total overrides detected:', overrides.length);
392
393
  if (overrides.length > 0) {
393
- console.log('[detectOverrides] Detected overrides:', overrides.map(o => ({
394
+ debugLog('[detectOverrides] Detected overrides:', overrides.map(o => ({
394
395
  type: o.type,
395
396
  targetElementId: o.targetElementId,
396
397
  value: typeof o.value === 'string' ? o.value.substring(0, 50) : o.value,
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { twMerge } from 'tailwind-merge';
8
+ import { debugLog } from './debug';
8
9
  import {
9
10
  TAILWIND_CONFLICT_GROUPS,
10
11
  ENUM_MAPPINGS,
@@ -63,7 +64,7 @@ export function cssToTailwindClass(
63
64
  // CSS変数参照はstyle属性に直接設定
64
65
  if (value.startsWith('var(')) {
65
66
  // DEBUG: var() を検出した時のログ
66
- console.log('[DEBUG tailwind-utils] cssToTailwindClass detected var():', {
67
+ debugLog('[DEBUG tailwind-utils] cssToTailwindClass detected var():', {
67
68
  property,
68
69
  value,
69
70
  result: { className: null, styleValue: value },
@@ -421,7 +422,7 @@ export function applyTailwindStyles(
421
422
  styles: Record<string, string>
422
423
  ): void {
423
424
  // DEBUG: applyTailwindStyles 開始時のログ
424
- console.log('[DEBUG tailwind-utils] applyTailwindStyles called:', {
425
+ debugLog('[DEBUG tailwind-utils] applyTailwindStyles called:', {
425
426
  elementId: element.id,
426
427
  elementTagName: element.tagName,
427
428
  inputStyles: styles,
@@ -492,7 +493,7 @@ export function applyTailwindStyles(
492
493
  });
493
494
 
494
495
  // DEBUG: applyTailwindStyles 最終適用結果のログ
495
- console.log('[DEBUG tailwind-utils] applyTailwindStyles completed:', {
496
+ debugLog('[DEBUG tailwind-utils] applyTailwindStyles completed:', {
496
497
  elementId: element.id,
497
498
  classesToAdd,
498
499
  stylesToApply,