@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
@@ -17,6 +17,7 @@ import {
17
17
  } from '../utils/dom-utils';
18
18
  import { generateEditableHtml } from '../utils/html-utils';
19
19
  import { extractElementInfo } from '../utils/style-utils';
20
+ import { debugLog } from '../utils/debug';
20
21
 
21
22
  /**
22
23
  * iframeの初期化とイベントハンドラ設定を行うHook
@@ -357,7 +358,7 @@ export function useIframeInitializer() {
357
358
 
358
359
  element.addEventListener('mousedown', (e) => {
359
360
  const bodyClasses = iframeDoc.body.classList;
360
- if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode')) return;
361
+ if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode') || bodyClasses.contains('comment-mode')) return;
361
362
  if (element.getAttribute('contenteditable') === 'true') return;
362
363
 
363
364
  e.preventDefault();
@@ -506,7 +507,7 @@ export function useIframeInitializer() {
506
507
  // 別の幅で崩れ、デザインツールへの取り込みで意味を失う。
507
508
  if (editorMode !== 'webpage') {
508
509
  const count = convertToAbsolutePositioning(iframeDoc);
509
- console.log('[Editor] 絶対配置へ変換:', count, '要素');
510
+ debugLog('[Editor] 絶対配置へ変換:', count, '要素');
510
511
  }
511
512
 
512
513
  // 変更を通知
@@ -567,7 +568,7 @@ export function useIframeInitializer() {
567
568
  const iframeDoc = getIframeDoc();
568
569
  if (iframeDoc?.body) {
569
570
  iframeDoc.body.classList.remove(
570
- 'move-mode', 'draw-mode', 'text-mode',
571
+ 'move-mode', 'draw-mode', 'text-mode', 'comment-mode',
571
572
  'tool-rectangle', 'tool-ellipse', 'tool-line', 'tool-arrow',
572
573
  'tool-pen', 'tool-pencil', 'tool-text', 'tool-frame'
573
574
  );
@@ -578,6 +579,8 @@ export function useIframeInitializer() {
578
579
  iframeDoc.body.classList.add('draw-mode', `tool-${activeTool}`);
579
580
  } else if (activeTool === 'text') {
580
581
  iframeDoc.body.classList.add('text-mode', 'tool-text');
582
+ } else if (activeTool === 'comment') {
583
+ iframeDoc.body.classList.add('comment-mode');
581
584
  }
582
585
  }
583
586
  }, [activeTool, getIframeDoc]);
@@ -11,6 +11,7 @@ import { useEditorContext } from '../EditorContext';
11
11
  import { buildDomTree, isInlineElement } from '../utils/dom-utils';
12
12
  import { EDITOR_IFRAME_STYLES } from '../constants';
13
13
  import { isLockedInsidePart } from '../parts';
14
+ import { debugLog } from '../utils/debug';
14
15
 
15
16
  interface UseIframeSetupReturn {
16
17
  /**
@@ -145,7 +146,7 @@ export function useIframeSetup(): UseIframeSetupReturn {
145
146
  */
146
147
  const initializeIframeDocument = useCallback(
147
148
  (iframeDoc: Document) => {
148
- console.log('[IframeSetup] Initializing iframe document...');
149
+ debugLog('[IframeSetup] Initializing iframe document...');
149
150
 
150
151
  // スタイルを注入
151
152
  const style = iframeDoc.createElement('style');
@@ -158,7 +159,7 @@ export function useIframeSetup(): UseIframeSetupReturn {
158
159
 
159
160
  // 全要素にdata-editableとdata-element-idを付与
160
161
  const elementCount = initializeEditableElements(iframeDoc);
161
- console.log('[IframeSetup] Initialized', elementCount, 'editable elements');
162
+ debugLog('[IframeSetup] Initialized', elementCount, 'editable elements');
162
163
 
163
164
  // DOMツリーを構築
164
165
  const newTree = buildDomTree(iframeDoc);
@@ -278,7 +279,7 @@ export function useIframeSetup(): UseIframeSetupReturn {
278
279
  const handleMouseMove = (e: MouseEvent) => {
279
280
  // 描画モードやテキストモードでは無効
280
281
  const bodyClasses = iframeDoc.body.classList;
281
- if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode') || bodyClasses.contains('move-mode')) {
282
+ if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode') || bodyClasses.contains('move-mode') || bodyClasses.contains('comment-mode')) {
282
283
  if (currentHoveredTextElement) {
283
284
  currentHoveredTextElement.classList.remove('text-editable-hover');
284
285
  currentHoveredTextElement = null;
@@ -17,6 +17,7 @@ import {
17
17
  } from '../../lib/firebase/storage';
18
18
  import { buildDomTree } from '../utils/dom-utils';
19
19
  import { findInsertionParent, findSlideRoot } from '../utils/slide-root';
20
+ import { debugLog } from '../utils/debug';
20
21
 
21
22
  interface UploadResult {
22
23
  storageUrl: string;
@@ -177,15 +178,15 @@ export function useImageUpload(options: UseImageUploadOptions = {}) {
177
178
  clipboardData: DataTransfer,
178
179
  insertOptions?: { x?: number; y?: number }
179
180
  ): Promise<UploadResult | null> => {
180
- console.log('[useImageUpload] uploadFromClipboard called');
181
+ debugLog('[useImageUpload] uploadFromClipboard called');
181
182
  // クリップボードから画像を探す
182
183
  const items = Array.from(clipboardData.items);
183
- console.log('[useImageUpload] Clipboard items:', items.map(i => ({ type: i.type, kind: i.kind })));
184
+ debugLog('[useImageUpload] Clipboard items:', items.map(i => ({ type: i.type, kind: i.kind })));
184
185
 
185
186
  for (const item of items) {
186
187
  if (item.type.startsWith('image/')) {
187
188
  const file = item.getAsFile();
188
- console.log('[useImageUpload] Got file from clipboard item:', file);
189
+ debugLog('[useImageUpload] Got file from clipboard item:', file);
189
190
  if (file) {
190
191
  return uploadFromFile(file, insertOptions);
191
192
  }
@@ -18,6 +18,7 @@ import {
18
18
  import { extractElementInfo } from '../utils/style-utils';
19
19
  import { MARQUEE_DRAG_THRESHOLD } from '../constants';
20
20
  import type { MarqueeState } from '../types';
21
+ import { debugLog } from '../utils/debug';
21
22
 
22
23
  interface UseMarqueeSelectionOptions {
23
24
  /** マーキー選択の開始保留フラグ */
@@ -305,7 +306,7 @@ export function useMarqueeSelection(
305
306
  window.postMessage({ type: 'ELEMENT_DESELECTED' }, '*');
306
307
  }
307
308
 
308
- console.log(
309
+ debugLog(
309
310
  '[Canvas] Marquee selected',
310
311
  selectedIds.length,
311
312
  additive ? '(additive)' : '',
@@ -6,6 +6,7 @@ import type { PageSettings, ProjectPageSettings } from '../types/page-settings';
6
6
  import type { HtmlImportResult } from '../components';
7
7
  import { buildDomTree, getArtboardContent } from '../utils/dom-utils';
8
8
  import { makeChildrenEditable } from '../utils/html-utils';
9
+ import { debugLog } from '../utils/debug';
9
10
 
10
11
  interface UsePageSettingsManagerProps {
11
12
  parentId?: string;
@@ -116,12 +117,12 @@ export function usePageSettingsManager({
116
117
 
117
118
  // CSSがStorageにある場合
118
119
  if (pageData.settings.customCssUrl) {
119
- console.log('[loadPageSettings] Fetching CSS from Storage:', pageData.settings.customCssUrl);
120
+ debugLog('[loadPageSettings] Fetching CSS from Storage:', pageData.settings.customCssUrl);
120
121
  try {
121
122
  const response = await fetch(pageData.settings.customCssUrl);
122
123
  if (response.ok) {
123
124
  customCss = await response.text();
124
- console.log(`[loadPageSettings] CSS fetched: ${customCss.length} chars`);
125
+ debugLog(`[loadPageSettings] CSS fetched: ${customCss.length} chars`);
125
126
  }
126
127
  } catch (e) {
127
128
  console.error('[loadPageSettings] Failed to fetch CSS from Storage:', e);
@@ -130,12 +131,12 @@ export function usePageSettingsManager({
130
131
 
131
132
  // JSがStorageにある場合
132
133
  if (pageData.settings.customJsUrl) {
133
- console.log('[loadPageSettings] Fetching JS from Storage:', pageData.settings.customJsUrl);
134
+ debugLog('[loadPageSettings] Fetching JS from Storage:', pageData.settings.customJsUrl);
134
135
  try {
135
136
  const response = await fetch(pageData.settings.customJsUrl);
136
137
  if (response.ok) {
137
138
  customJs = await response.text();
138
- console.log(`[loadPageSettings] JS fetched: ${customJs.length} chars`);
139
+ debugLog(`[loadPageSettings] JS fetched: ${customJs.length} chars`);
139
140
  }
140
141
  } catch (e) {
141
142
  console.error('[loadPageSettings] Failed to fetch JS from Storage:', e);
@@ -177,7 +178,7 @@ export function usePageSettingsManager({
177
178
  setImportedJs(customJs);
178
179
  }
179
180
 
180
- console.log('[loadPageSettings] Loaded settings:', {
181
+ debugLog('[loadPageSettings] Loaded settings:', {
181
182
  ...loadedSettings,
182
183
  customCss: customCss.length + ' chars',
183
184
  customJs: customJs.length + ' chars',
@@ -219,7 +220,7 @@ export function usePageSettingsManager({
219
220
  }
220
221
  if (styleElement.textContent !== importedCss) {
221
222
  styleElement.textContent = importedCss;
222
- console.log('[applyStylesToIframe] Applied CSS:', importedCss.length, 'chars');
223
+ debugLog('[applyStylesToIframe] Applied CSS:', importedCss.length, 'chars');
223
224
  }
224
225
  }
225
226
 
@@ -231,7 +232,7 @@ export function usePageSettingsManager({
231
232
  const safeJs = `;try{${importedJs}}catch(e){console.error('[imported-scripts] Error:',e);}`;
232
233
  // 既にスクリプトが存在し、同じ内容であればスキップ
233
234
  if (existingScript?.textContent === safeJs) {
234
- console.log('[applyStylesToIframe] JS already applied, skipping');
235
+ debugLog('[applyStylesToIframe] JS already applied, skipping');
235
236
  return;
236
237
  }
237
238
  // 既存のスクリプトを削除
@@ -243,7 +244,7 @@ export function usePageSettingsManager({
243
244
  scriptElement.id = 'imported-scripts';
244
245
  scriptElement.textContent = safeJs;
245
246
  iframeDoc.body.appendChild(scriptElement);
246
- console.log('[applyStylesToIframe] Applied JS:', importedJs.length, 'chars');
247
+ debugLog('[applyStylesToIframe] Applied JS:', importedJs.length, 'chars');
247
248
  }
248
249
  };
249
250
 
@@ -275,11 +276,11 @@ export function usePageSettingsManager({
275
276
  iframeDoc.head.appendChild(styleElement);
276
277
  }
277
278
  styleElement.textContent = css;
278
- console.log('[handleSaveCss] Updated CSS:', css.length, 'chars');
279
+ debugLog('[handleSaveCss] Updated CSS:', css.length, 'chars');
279
280
  } else if (styleElement) {
280
281
  // CSSが空の場合はスタイル要素を削除
281
282
  styleElement.remove();
282
- console.log('[handleSaveCss] Removed empty CSS');
283
+ debugLog('[handleSaveCss] Removed empty CSS');
283
284
  }
284
285
 
285
286
  // 状態を更新
@@ -298,7 +299,7 @@ export function usePageSettingsManager({
298
299
  const styleElement = iframeDoc.getElementById('imported-styles');
299
300
  if (styleElement) {
300
301
  styleElement.remove();
301
- console.log('[handleClearCss] Cleared imported CSS');
302
+ debugLog('[handleClearCss] Cleared imported CSS');
302
303
  }
303
304
  }
304
305
  setImportedCss('');
@@ -338,11 +339,11 @@ export function usePageSettingsManager({
338
339
  const safeJs = `;try{${js}}catch(e){console.error('[imported-scripts] Error:',e);}`;
339
340
  newScriptElement.textContent = safeJs;
340
341
  iframeDoc.body.appendChild(newScriptElement);
341
- console.log('[handleSaveJs] Updated JS:', js.length, 'chars');
342
+ debugLog('[handleSaveJs] Updated JS:', js.length, 'chars');
342
343
  } else if (scriptElement) {
343
344
  // JSが空の場合はスクリプト要素を削除
344
345
  scriptElement.remove();
345
- console.log('[handleSaveJs] Removed empty JS');
346
+ debugLog('[handleSaveJs] Removed empty JS');
346
347
  }
347
348
 
348
349
  // 状態を更新
@@ -361,7 +362,7 @@ export function usePageSettingsManager({
361
362
  const scriptElement = iframeDoc.getElementById('imported-scripts');
362
363
  if (scriptElement) {
363
364
  scriptElement.remove();
364
- console.log('[handleClearJs] Cleared imported JS');
365
+ debugLog('[handleClearJs] Cleared imported JS');
365
366
  }
366
367
  }
367
368
  setImportedJs('');
@@ -461,13 +462,13 @@ export function usePageSettingsManager({
461
462
  if (settings.customJs) firestoreSettings.customJs = settings.customJs;
462
463
 
463
464
  await api.patch(apiPath, { settings: firestoreSettings });
464
- console.log('[handleSavePageSettings] Saved settings to Firestore:', firestoreSettings);
465
+ debugLog('[handleSavePageSettings] Saved settings to Firestore:', firestoreSettings);
465
466
  } catch (error) {
466
467
  console.error('[handleSavePageSettings] Failed to save settings to Firestore:', error);
467
468
  }
468
469
  }
469
470
 
470
- console.log('[handleSavePageSettings] Saved settings:', settings);
471
+ debugLog('[handleSavePageSettings] Saved settings:', settings);
471
472
  }, [getIframeDoc, notifyIframeChange, parentId, contentId, editorMode, getIdToken]);
472
473
 
473
474
  // 現在のページ設定を保存(メイン保存ボタンから呼ばれる)
@@ -492,7 +493,7 @@ export function usePageSettingsManager({
492
493
 
493
494
  // CSS/JSが空の場合は保存不要
494
495
  if (!currentCss && !currentJs && !pageSettings.title && !pageSettings.description) {
495
- console.log('[handleSaveCurrentSettings] No settings to save');
496
+ debugLog('[handleSaveCurrentSettings] No settings to save');
496
497
  return;
497
498
  }
498
499
 
@@ -525,7 +526,7 @@ export function usePageSettingsManager({
525
526
  if (currentJs) firestoreSettings.customJs = currentJs;
526
527
 
527
528
  await api.patch(apiPath, { settings: firestoreSettings });
528
- console.log('[handleSaveCurrentSettings] Saved settings to Firestore:', {
529
+ debugLog('[handleSaveCurrentSettings] Saved settings to Firestore:', {
529
530
  customCss: currentCss.length + ' chars',
530
531
  customJs: currentJs.length + ' chars',
531
532
  });
@@ -581,7 +582,7 @@ export function usePageSettingsManager({
581
582
  document.body.removeChild(a);
582
583
  URL.revokeObjectURL(url);
583
584
 
584
- console.log('[handleExport] Downloaded:', filename, 'format:', format);
585
+ debugLog('[handleExport] Downloaded:', filename, 'format:', format);
585
586
  } catch (error) {
586
587
  console.error('[handleExport] Export failed:', error);
587
588
  }
@@ -643,7 +644,7 @@ export function usePageSettingsManager({
643
644
  styleElement.id = 'imported-styles';
644
645
  styleElement.textContent = result.css;
645
646
  iframeDoc.head.appendChild(styleElement);
646
- console.log('[handleHtmlImport] Injected CSS:', result.css.length, 'chars');
647
+ debugLog('[handleHtmlImport] Injected CSS:', result.css.length, 'chars');
647
648
 
648
649
  // CSS状態を保存(編集用)
649
650
  setImportedCss(result.css);
@@ -667,7 +668,7 @@ export function usePageSettingsManager({
667
668
  const safeJs = `;try{${result.js}}catch(e){console.error('[imported-scripts] Error:',e);}`;
668
669
  scriptElement.textContent = safeJs;
669
670
  iframeDoc.body.appendChild(scriptElement);
670
- console.log('[handleHtmlImport] Injected JS:', result.js.length, 'chars');
671
+ debugLog('[handleHtmlImport] Injected JS:', result.js.length, 'chars');
671
672
 
672
673
  // JS状態を保存(編集用)- 元のJSを保存
673
674
  setImportedJs(result.js);
@@ -677,11 +678,11 @@ export function usePageSettingsManager({
677
678
 
678
679
  // インポートされた要素に data-editable と data-element-id を付与
679
680
  const count = makeChildrenEditable(artboard);
680
- console.log('[handleHtmlImport] Made', count, 'elements editable');
681
+ debugLog('[handleHtmlImport] Made', count, 'elements editable');
681
682
 
682
683
  // リソース情報をログ
683
684
  if (result.resources) {
684
- console.log('[handleHtmlImport] Resources:', {
685
+ debugLog('[handleHtmlImport] Resources:', {
685
686
  images: result.resources.images?.length || 0,
686
687
  stylesheets: result.resources.stylesheets?.length || 0,
687
688
  scripts: result.resources.scripts?.length || 0,
@@ -18,6 +18,7 @@ import {
18
18
  PasteProcessResult,
19
19
  } from '../utils/paste-processors';
20
20
  import { fetchAndApplyFigmaImages } from '../utils/figma-paste';
21
+ import { debugLog } from '../utils/debug';
21
22
 
22
23
  /**
23
24
  * Result of a rich paste operation
@@ -95,7 +96,7 @@ export function useRichPaste() {
95
96
 
96
97
  // Detect content type (or use forced type)
97
98
  const contentType = forceContentType || detectContentType(clipboardData);
98
- console.log('[useRichPaste] Detected content type:', contentType, forceContentType ? '(forced)' : '');
99
+ debugLog('[useRichPaste] Detected content type:', contentType, forceContentType ? '(forced)' : '');
99
100
 
100
101
  // Skip images (handled by useImageUpload)
101
102
  if (contentType === 'image') {
@@ -176,7 +177,7 @@ export function useRichPaste() {
176
177
  }
177
178
  }
178
179
 
179
- console.log('[useRichPaste] Successfully pasted', pastedIds.length, 'elements');
180
+ debugLog('[useRichPaste] Successfully pasted', pastedIds.length, 'elements');
180
181
 
181
182
  // For Figma content, fetch and apply images asynchronously
182
183
  let figmaImagesApplied: number | undefined;
@@ -190,7 +191,7 @@ export function useRichPaste() {
190
191
  result.figmaFileKey &&
191
192
  (hasImageHashes || hasImageNodeIds)
192
193
  ) {
193
- console.log('[useRichPaste] Fetching Figma images...',
194
+ debugLog('[useRichPaste] Fetching Figma images...',
194
195
  hasImageHashes ? `${result.figmaImageHashes!.length} hashes` : 'no hashes',
195
196
  hasImageNodeIds ? `${result.figmaImageNodeIds!.length} nodeIds` : 'no nodeIds',
196
197
  );
@@ -211,7 +212,7 @@ export function useRichPaste() {
211
212
  if (imageResult.applied > 0) {
212
213
  // Notify change again since images were applied
213
214
  notifyIframeChange();
214
- console.log(`[useRichPaste] Applied ${imageResult.applied} Figma images`);
215
+ debugLog(`[useRichPaste] Applied ${imageResult.applied} Figma images`);
215
216
  }
216
217
  }
217
218
 
@@ -228,21 +229,21 @@ export function useRichPaste() {
228
229
  * Debug helper: Log clipboard contents
229
230
  */
230
231
  const debugClipboard = useCallback((clipboardData: DataTransfer): void => {
231
- console.log('[useRichPaste] === Clipboard Debug ===');
232
- console.log('Available types:', Array.from(clipboardData.types));
232
+ debugLog('[useRichPaste] === Clipboard Debug ===');
233
+ debugLog('Available types:', Array.from(clipboardData.types));
233
234
 
234
235
  clipboardData.types.forEach((type) => {
235
236
  const data = clipboardData.getData(type);
236
- console.log(`[${type}]:`, data.substring(0, 500) + (data.length > 500 ? '...' : ''));
237
+ debugLog(`[${type}]:`, data.substring(0, 500) + (data.length > 500 ? '...' : ''));
237
238
  });
238
239
 
239
- console.log('Items:', Array.from(clipboardData.items).map(item => ({
240
+ debugLog('Items:', Array.from(clipboardData.items).map(item => ({
240
241
  kind: item.kind,
241
242
  type: item.type,
242
243
  })));
243
244
 
244
- console.log('Detected type:', detectContentType(clipboardData));
245
- console.log('[useRichPaste] === End Debug ===');
245
+ debugLog('Detected type:', detectContentType(clipboardData));
246
+ debugLog('[useRichPaste] === End Debug ===');
246
247
  }, []);
247
248
 
248
249
  return {
@@ -15,6 +15,7 @@
15
15
  * (移動・削除・並び替え)。スロットの外を直したいときは「部品から切り離す」。
16
16
  */
17
17
  import type { EditorPartDef } from '../io';
18
+ import { findFlowInsertion } from './utils/drop-target';
18
19
 
19
20
  export const PART_ATTR = 'data-part';
20
21
  export const PART_VERSION_ATTR = 'data-part-v';
@@ -281,34 +282,15 @@ export function detachPart(el: HTMLElement): void {
281
282
 
282
283
  /**
283
284
  * フロー(流し込み)への挿入位置を決める。
284
- * ドロップ位置の要素から、「セクションの器」の直下にある祖先まで上がり、その後ろに入れる。
285
+ * ドロップ位置の要素から、「セクションの器」の直下にある祖先まで上がり、その前後に入れる。
285
286
  * 器 = `[data-wf-body]` / `main` / `#artboard` / body。何も当たらなければ器の末尾。
287
+ *
288
+ * 位置の計算そのものは drop-target.ts に置いてある。ドラッグ中に出す印
289
+ * (どこに入るかの横棒)と同じ関数を使わないと、印と実際の挿入位置がずれるため。
286
290
  */
287
291
  export function insertIntoFlow(doc: Document, el: HTMLElement, x: number, y: number): void {
288
- const isContainer = (node: Element | null): boolean =>
289
- !!node &&
290
- (node === doc.body ||
291
- node.id === 'artboard' ||
292
- node.hasAttribute('data-wf-body') ||
293
- node.tagName === 'MAIN');
294
- const fallback = (): Element =>
295
- doc.querySelector('[data-wf-body]') ?? doc.querySelector('main') ?? doc.getElementById('artboard') ?? doc.body;
296
-
297
- let hit: Element | null = doc.elementFromPoint(x, y);
298
- if (!hit || hit === doc.body || hit.id === 'artboard' || hit.closest('.selection-box')) {
299
- fallback().appendChild(el);
300
- return;
301
- }
302
- // 器の直下にあたる祖先まで上がる
303
- let anchor: Element = hit;
304
- while (anchor.parentElement && !isContainer(anchor.parentElement)) {
305
- anchor = anchor.parentElement;
306
- }
307
- if (!anchor.parentElement) {
308
- fallback().appendChild(el);
309
- return;
310
- }
311
- anchor.after(el);
292
+ const { parent, before } = findFlowInsertion(doc, x, y);
293
+ parent.insertBefore(el, before);
312
294
  }
313
295
 
314
296
  /**
@@ -14,6 +14,7 @@ import type {
14
14
  OverrideImageValue,
15
15
  } from '../../types/editor-components';
16
16
  import { generateElementId } from './dom-utils';
17
+ import { debugLog } from './debug';
17
18
 
18
19
  // ============================================================
19
20
  // Constants
@@ -56,7 +57,7 @@ export function createDOMElement(
56
57
  doc: Document = document
57
58
  ): HTMLElement {
58
59
  // Debug logging
59
- console.log('[createDOMElement] Creating:', {
60
+ debugLog('[createDOMElement] Creating:', {
60
61
  tagName: element.tagName,
61
62
  className: element.className,
62
63
  hasTextContent: !!element.textContent,
@@ -110,13 +111,13 @@ export function createDOMElement(
110
111
  if (element.innerHTML !== undefined) {
111
112
  // Use innerHTML for mixed content (text + elements)
112
113
  el.innerHTML = element.innerHTML;
113
- console.log('[createDOMElement] Using innerHTML for mixed content');
114
+ debugLog('[createDOMElement] Using innerHTML for mixed content');
114
115
  } else if (element.textContent !== undefined) {
115
116
  // textContent is explicitly set (possibly via override) - use it
116
117
  // This takes precedence over children, which is important for text overrides
117
118
  el.textContent = element.textContent;
118
119
  if (element.children.length > 0) {
119
- console.log('[createDOMElement] textContent override replacing children structure');
120
+ debugLog('[createDOMElement] textContent override replacing children structure');
120
121
  }
121
122
  } else {
122
123
  // Recursively create and append children
@@ -155,7 +156,7 @@ export function createDOMElement(
155
156
  }
156
157
  }
157
158
 
158
- console.log('[createDOMElement] Created element:', el.tagName, 'with innerHTML length:', el.innerHTML.length);
159
+ debugLog('[createDOMElement] Created element:', el.tagName, 'with innerHTML length:', el.innerHTML.length);
159
160
 
160
161
  return el;
161
162
  }
@@ -417,7 +418,7 @@ export function renderInstance(
417
418
  instance: ComponentInstance,
418
419
  doc: Document = document
419
420
  ): HTMLElement {
420
- console.log('[renderInstance] Starting render:', {
421
+ debugLog('[renderInstance] Starting render:', {
421
422
  masterId: master.id,
422
423
  masterName: master.name,
423
424
  instanceId: instance.id,
@@ -428,7 +429,7 @@ export function renderInstance(
428
429
  // Log the master component's root element structure
429
430
  const defaultVariant = master.variants.find(v => v.id === instance.variantId);
430
431
  if (defaultVariant) {
431
- console.log('[renderInstance] Variant rootElement:', {
432
+ debugLog('[renderInstance] Variant rootElement:', {
432
433
  tagName: defaultVariant.rootElement.tagName,
433
434
  className: defaultVariant.rootElement.className,
434
435
  hasTextContent: !!defaultVariant.rootElement.textContent,
@@ -440,7 +441,7 @@ export function renderInstance(
440
441
 
441
442
  // Resolve the instance to a ComponentElement
442
443
  const resolved = resolveInstance(master, instance);
443
- console.log('[renderInstance] Resolved element:', {
444
+ debugLog('[renderInstance] Resolved element:', {
444
445
  tagName: resolved.tagName,
445
446
  className: resolved.className,
446
447
  hasTextContent: !!resolved.textContent,
@@ -19,6 +19,7 @@ import {
19
19
  resolveInstance,
20
20
  } from './component-renderer';
21
21
  import { detectOverrides } from './override-detection';
22
+ import { debugLog } from './debug';
22
23
 
23
24
  // ============================================================
24
25
  // Types
@@ -360,7 +361,7 @@ export function propagateMasterChanges(
360
361
  // Find all DOM elements for this master
361
362
  const domElements = findInstancesByMaster(iframeDoc, master.id);
362
363
 
363
- console.log('[propagateMasterChanges] Found DOM elements for master:', {
364
+ debugLog('[propagateMasterChanges] Found DOM elements for master:', {
364
365
  masterId: master.id,
365
366
  domElementsCount: domElements.length,
366
367
  });
@@ -369,13 +370,13 @@ export function propagateMasterChanges(
369
370
  const domElementMap = new Map<string, HTMLElement>();
370
371
  for (const el of domElements) {
371
372
  const instanceId = getInstanceIdFromElement(el);
372
- console.log('[propagateMasterChanges] DOM element instanceId:', instanceId);
373
+ debugLog('[propagateMasterChanges] DOM element instanceId:', instanceId);
373
374
  if (instanceId) {
374
375
  domElementMap.set(instanceId, el);
375
376
  }
376
377
  }
377
378
 
378
- console.log('[propagateMasterChanges] Processing instances:', {
379
+ debugLog('[propagateMasterChanges] Processing instances:', {
379
380
  totalInstances: instances.length,
380
381
  instanceIds: instances.map(i => i.id),
381
382
  domMapKeys: Array.from(domElementMap.keys()),
@@ -385,13 +386,13 @@ export function propagateMasterChanges(
385
386
  for (const instance of instances) {
386
387
  // Skip detached instances
387
388
  if (instance.isDetached) {
388
- console.log('[propagateMasterChanges] Skipping detached instance:', instance.id);
389
+ debugLog('[propagateMasterChanges] Skipping detached instance:', instance.id);
389
390
  continue;
390
391
  }
391
392
 
392
393
  // Skip instances for other masters
393
394
  if (instance.masterComponentId !== master.id) {
394
- console.log('[propagateMasterChanges] Skipping instance for other master:', instance.id);
395
+ debugLog('[propagateMasterChanges] Skipping instance for other master:', instance.id);
395
396
  continue;
396
397
  }
397
398
 
@@ -400,11 +401,11 @@ export function propagateMasterChanges(
400
401
  const domEl = domElementMap.get(instance.id);
401
402
  if (!domEl) {
402
403
  // Instance not in DOM, skip
403
- console.log('[propagateMasterChanges] DOM element not found for instance:', instance.id);
404
+ debugLog('[propagateMasterChanges] DOM element not found for instance:', instance.id);
404
405
  continue;
405
406
  }
406
407
 
407
- console.log('[propagateMasterChanges] Found DOM element for instance:', instance.id);
408
+ debugLog('[propagateMasterChanges] Found DOM element for instance:', instance.id);
408
409
 
409
410
  // Validate variant still exists
410
411
  let variantId = instance.variantId;
@@ -423,15 +424,15 @@ export function propagateMasterChanges(
423
424
  let detectedOverrides: ComponentOverride[] = [];
424
425
  if (effectiveVariant) {
425
426
  // Debug: Log what we're comparing
426
- console.log('[propagateMasterChanges] === Override Detection Debug ===');
427
- console.log('[propagateMasterChanges] DOM element:', {
427
+ debugLog('[propagateMasterChanges] === Override Detection Debug ===');
428
+ debugLog('[propagateMasterChanges] DOM element:', {
428
429
  tagName: domEl.tagName,
429
430
  elementId: domEl.getAttribute('data-element-id'),
430
431
  textContent: domEl.textContent?.substring(0, 100),
431
432
  innerHTML: domEl.innerHTML?.substring(0, 200),
432
433
  childrenCount: domEl.children.length,
433
434
  });
434
- console.log('[propagateMasterChanges] Master rootElement:', {
435
+ debugLog('[propagateMasterChanges] Master rootElement:', {
435
436
  tagName: effectiveVariant.rootElement.tagName,
436
437
  id: effectiveVariant.rootElement.id,
437
438
  textContent: effectiveVariant.rootElement.textContent?.substring(0, 100),
@@ -442,7 +443,7 @@ export function propagateMasterChanges(
442
443
 
443
444
  try {
444
445
  detectedOverrides = detectOverrides(domEl, effectiveVariant.rootElement);
445
- console.log('[propagateMasterChanges] Detected DOM overrides:', {
446
+ debugLog('[propagateMasterChanges] Detected DOM overrides:', {
446
447
  instanceId: instance.id,
447
448
  count: detectedOverrides.length,
448
449
  overrides: detectedOverrides.map(o => ({
@@ -477,7 +478,7 @@ export function propagateMasterChanges(
477
478
  }
478
479
  const mergedOverrides = Array.from(overrideMap.values());
479
480
 
480
- console.log('[propagateMasterChanges] Merged overrides:', {
481
+ debugLog('[propagateMasterChanges] Merged overrides:', {
481
482
  instanceId: instance.id,
482
483
  existing: existingOverrides.length,
483
484
  detected: detectedOverrides.length,
@@ -536,7 +537,7 @@ export function propagateMasterChanges(
536
537
  });
537
538
  };
538
539
  applyIdMappings(newEl);
539
- console.log('[propagateMasterChanges] Applied ID mappings for duplicated instance:', {
540
+ debugLog('[propagateMasterChanges] Applied ID mappings for duplicated instance:', {
540
541
  instanceId: instance.id,
541
542
  mappingsCount: idMappings.size,
542
543
  });
@@ -563,7 +564,7 @@ export function propagateMasterChanges(
563
564
  newEl.style.setProperty(prop, value);
564
565
  }
565
566
 
566
- console.log('[propagateMasterChanges] Preserved position styles:', {
567
+ debugLog('[propagateMasterChanges] Preserved position styles:', {
567
568
  instanceId: instance.id,
568
569
  preservedStyles: styleObj,
569
570
  });
@@ -585,7 +586,7 @@ export function propagateMasterChanges(
585
586
  }
586
587
  }
587
588
 
588
- console.log(
589
+ debugLog(
589
590
  `[propagateMasterChanges] Updated ${result.updatedCount} instances, failed ${result.failedCount}`
590
591
  );
591
592