@growgroup/visual-editor 0.1.0

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +52 -0
  3. package/README.md +143 -0
  4. package/dist/editor.css +2 -0
  5. package/package.json +94 -0
  6. package/src/components/DeckSlideRender.tsx +21 -0
  7. package/src/components/SaveNote.tsx +21 -0
  8. package/src/components/auth/AuthProvider.tsx +44 -0
  9. package/src/components/ui/accordion.tsx +57 -0
  10. package/src/components/ui/alert-dialog.tsx +141 -0
  11. package/src/components/ui/badge.tsx +36 -0
  12. package/src/components/ui/button.tsx +56 -0
  13. package/src/components/ui/card.tsx +76 -0
  14. package/src/components/ui/checkbox.tsx +30 -0
  15. package/src/components/ui/collapsible.tsx +11 -0
  16. package/src/components/ui/context-menu.tsx +200 -0
  17. package/src/components/ui/dialog.tsx +122 -0
  18. package/src/components/ui/dropdown-menu.tsx +201 -0
  19. package/src/components/ui/input.tsx +22 -0
  20. package/src/components/ui/label.tsx +26 -0
  21. package/src/components/ui/popover.tsx +33 -0
  22. package/src/components/ui/scroll-area.tsx +48 -0
  23. package/src/components/ui/scrubbable-label.tsx +95 -0
  24. package/src/components/ui/select.tsx +159 -0
  25. package/src/components/ui/separator.tsx +31 -0
  26. package/src/components/ui/slider.tsx +28 -0
  27. package/src/components/ui/switch.tsx +29 -0
  28. package/src/components/ui/tabs.tsx +55 -0
  29. package/src/components/ui/textarea.tsx +22 -0
  30. package/src/components/ui/tooltip.tsx +32 -0
  31. package/src/components/viewer/useDeck.ts +58 -0
  32. package/src/editor/EditorContext.tsx +632 -0
  33. package/src/editor/EditorToolbar.tsx +446 -0
  34. package/src/editor/FrontendVisualEditor.tsx +2595 -0
  35. package/src/editor/autosave.ts +34 -0
  36. package/src/editor/components/AiPromptPopover.tsx +378 -0
  37. package/src/editor/components/BreakpointGuides.tsx +323 -0
  38. package/src/editor/components/BreakpointSelector.tsx +180 -0
  39. package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
  40. package/src/editor/components/CssEditorDialog.tsx +168 -0
  41. package/src/editor/components/EditorCanvas.tsx +987 -0
  42. package/src/editor/components/EditorContextMenu.tsx +515 -0
  43. package/src/editor/components/EditorFooter.tsx +81 -0
  44. package/src/editor/components/EditorHeader.tsx +524 -0
  45. package/src/editor/components/EditorLayerPanel.tsx +1197 -0
  46. package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
  47. package/src/editor/components/EditorSidebar.tsx +226 -0
  48. package/src/editor/components/FigmaColorPicker.tsx +1577 -0
  49. package/src/editor/components/HtmlEditorDialog.tsx +71 -0
  50. package/src/editor/components/HtmlImportDialog.tsx +346 -0
  51. package/src/editor/components/JsEditorDialog.tsx +175 -0
  52. package/src/editor/components/LayerTreeItem.tsx +494 -0
  53. package/src/editor/components/MasterComponentEditor.tsx +846 -0
  54. package/src/editor/components/MediaLibraryDialog.tsx +447 -0
  55. package/src/editor/components/PageSettingsDialog.tsx +776 -0
  56. package/src/editor/components/VariablePicker.tsx +355 -0
  57. package/src/editor/components/VariablesDialog.tsx +564 -0
  58. package/src/editor/components/VariantEditor.tsx +687 -0
  59. package/src/editor/components/VariantMatrix.tsx +507 -0
  60. package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
  61. package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
  62. package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
  63. package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
  64. package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
  65. package/src/editor/components/component-panel/VariantList.tsx +260 -0
  66. package/src/editor/components/component-panel/index.ts +12 -0
  67. package/src/editor/components/index.ts +48 -0
  68. package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
  69. package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
  70. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
  71. package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
  72. package/src/editor/components/multi-page/PageLabel.tsx +34 -0
  73. package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
  74. package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
  75. package/src/editor/components/multi-page/index.ts +7 -0
  76. package/src/editor/components/ppt/PptChrome.tsx +2508 -0
  77. package/src/editor/components/ppt/PptComments.tsx +589 -0
  78. package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
  79. package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
  80. package/src/editor/components/ppt/PptNotes.tsx +246 -0
  81. package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
  82. package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
  83. package/src/editor/components/property-panel/BorderSection.tsx +104 -0
  84. package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
  85. package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
  86. package/src/editor/components/property-panel/EffectSection.tsx +157 -0
  87. package/src/editor/components/property-panel/FillSection.tsx +69 -0
  88. package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
  89. package/src/editor/components/property-panel/ImageSection.tsx +206 -0
  90. package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
  91. package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
  92. package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
  93. package/src/editor/components/property-panel/LinkSection.tsx +103 -0
  94. package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
  95. package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
  96. package/src/editor/components/property-panel/TypographySection.tsx +230 -0
  97. package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
  98. package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
  99. package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
  100. package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
  101. package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
  102. package/src/editor/components/property-panel/index.ts +24 -0
  103. package/src/editor/components/property-panel/unit-utils.ts +261 -0
  104. package/src/editor/components/shell/EditorTopBar.tsx +392 -0
  105. package/src/editor/components/shell/LeftPanel.tsx +47 -0
  106. package/src/editor/components/shell/PagesPanel.tsx +118 -0
  107. package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
  108. package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
  109. package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
  110. package/src/editor/components/variables-panel/index.ts +7 -0
  111. package/src/editor/constants.ts +468 -0
  112. package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
  113. package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
  114. package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
  115. package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
  116. package/src/editor/contexts/EditorRefsContext.tsx +204 -0
  117. package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
  118. package/src/editor/contexts/EditorToolContext.tsx +58 -0
  119. package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
  120. package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
  121. package/src/editor/contexts/EditorViewContext.tsx +101 -0
  122. package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
  123. package/src/editor/contexts/index.ts +17 -0
  124. package/src/editor/editor-skin.css +122 -0
  125. package/src/editor/hooks/index.ts +38 -0
  126. package/src/editor/hooks/useAiReplace.ts +397 -0
  127. package/src/editor/hooks/useAltMeasure.ts +67 -0
  128. package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
  129. package/src/editor/hooks/useCanvasControls.ts +1222 -0
  130. package/src/editor/hooks/useComponentEditMode.ts +658 -0
  131. package/src/editor/hooks/useComponentInstances.ts +515 -0
  132. package/src/editor/hooks/useContextMenuHandler.ts +157 -0
  133. package/src/editor/hooks/useCoordinateTransform.ts +207 -0
  134. package/src/editor/hooks/useDragResize.ts +1317 -0
  135. package/src/editor/hooks/useDrawingMode.ts +472 -0
  136. package/src/editor/hooks/useEditorColors.ts +144 -0
  137. package/src/editor/hooks/useEditorMessages.ts +108 -0
  138. package/src/editor/hooks/useElementActions.ts +1754 -0
  139. package/src/editor/hooks/useElementSelection.ts +1573 -0
  140. package/src/editor/hooks/useFocusManagement.ts +249 -0
  141. package/src/editor/hooks/useGoogleFonts.ts +305 -0
  142. package/src/editor/hooks/useIframeInitializer.ts +584 -0
  143. package/src/editor/hooks/useIframeSetup.ts +360 -0
  144. package/src/editor/hooks/useImageUpload.ts +270 -0
  145. package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
  146. package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
  147. package/src/editor/hooks/useMarqueeSelection.ts +388 -0
  148. package/src/editor/hooks/useMediaLibrary.ts +129 -0
  149. package/src/editor/hooks/usePageSettingsManager.ts +739 -0
  150. package/src/editor/hooks/useResizablePanel.ts +163 -0
  151. package/src/editor/hooks/useRichPaste.ts +255 -0
  152. package/src/editor/hooks/useTouchGestures.ts +197 -0
  153. package/src/editor/index.ts +27 -0
  154. package/src/editor/types/page-settings.ts +513 -0
  155. package/src/editor/types.ts +382 -0
  156. package/src/editor/utils/align-elements.ts +169 -0
  157. package/src/editor/utils/api-json.ts +21 -0
  158. package/src/editor/utils/aspect-lock.ts +50 -0
  159. package/src/editor/utils/component-renderer.ts +702 -0
  160. package/src/editor/utils/component-sync.ts +845 -0
  161. package/src/editor/utils/crop-mode.ts +265 -0
  162. package/src/editor/utils/dom-utils.ts +2238 -0
  163. package/src/editor/utils/element-effects.ts +218 -0
  164. package/src/editor/utils/eyedropper.ts +35 -0
  165. package/src/editor/utils/figma-export.ts +560 -0
  166. package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
  167. package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
  168. package/src/editor/utils/figma-paste.ts +2262 -0
  169. package/src/editor/utils/flex-reorder.ts +231 -0
  170. package/src/editor/utils/flex-utils.ts +1230 -0
  171. package/src/editor/utils/geometry.ts +232 -0
  172. package/src/editor/utils/grid-layout.ts +210 -0
  173. package/src/editor/utils/html-utils.ts +470 -0
  174. package/src/editor/utils/index.ts +11 -0
  175. package/src/editor/utils/ink-style.ts +57 -0
  176. package/src/editor/utils/inline-format.ts +236 -0
  177. package/src/editor/utils/measure-distance.ts +120 -0
  178. package/src/editor/utils/override-apply.ts +526 -0
  179. package/src/editor/utils/override-detection.ts +613 -0
  180. package/src/editor/utils/paste-processors.ts +609 -0
  181. package/src/editor/utils/paste-sanitizer.ts +244 -0
  182. package/src/editor/utils/restore-flow.ts +188 -0
  183. package/src/editor/utils/shape-library.ts +171 -0
  184. package/src/editor/utils/slide-root.ts +29 -0
  185. package/src/editor/utils/smart-guides.ts +218 -0
  186. package/src/editor/utils/style-utils.ts +685 -0
  187. package/src/editor/utils/table-edit.ts +151 -0
  188. package/src/editor/utils/tailwind-mappings.ts +778 -0
  189. package/src/editor/utils/tailwind-utils.ts +804 -0
  190. package/src/editor/utils/text-highlight.ts +216 -0
  191. package/src/editor/utils/theme-to-variables.ts +341 -0
  192. package/src/editor/utils/variant-resolver.ts +456 -0
  193. package/src/editor/utils/viewport-utils.ts +295 -0
  194. package/src/hooks/useEditorHistory.ts +195 -0
  195. package/src/hooks/useEditorShortcuts.ts +260 -0
  196. package/src/index.ts +40 -0
  197. package/src/io.ts +134 -0
  198. package/src/lib/agent/slide-agent/types.ts +547 -0
  199. package/src/lib/agent/website-agent/types.ts +805 -0
  200. package/src/lib/api/auth-fetch.ts +58 -0
  201. package/src/lib/deck.ts +69 -0
  202. package/src/lib/export.ts +47 -0
  203. package/src/lib/firebase/config.ts +29 -0
  204. package/src/lib/firebase/css-variables.ts +378 -0
  205. package/src/lib/firebase/editor-components.ts +677 -0
  206. package/src/lib/firebase/storage.ts +469 -0
  207. package/src/lib/utils.ts +6 -0
  208. package/src/styles/editor.css +52 -0
  209. package/src/styles/skin.css +122 -0
  210. package/src/types/css-variables.ts +645 -0
  211. package/src/types/editor-components.ts +944 -0
  212. package/src/types/editor.ts +420 -0
  213. package/src/types/page-master.ts +197 -0
  214. package/src/types/page.ts +419 -0
  215. package/src/types/slide.ts +402 -0
  216. package/src/types/website-theme.ts +788 -0
  217. package/src/types/website.ts +374 -0
  218. package/src/vendor/firebase-firestore.ts +13 -0
  219. package/src/vendor/firebase-functions.ts +17 -0
@@ -0,0 +1,658 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useEditorContext, useEditorComponents } from '../EditorContext';
3
+ import { htmlElementToComponentElement } from '../contexts/EditorComponentsContext';
4
+ import { renderComponentElement } from '../utils/component-renderer';
5
+ import { propagateMasterChanges } from '../utils/component-sync';
6
+ import { buildDomTree } from '../utils/dom-utils';
7
+ import { toast } from 'sonner';
8
+ import type { MasterComponent, ComponentInstance } from '../../types/editor-components';
9
+
10
+ interface UseComponentEditModeProps {
11
+ contentId?: string;
12
+ closeContextMenu: () => void;
13
+ }
14
+
15
+ interface UseComponentEditModeReturn {
16
+ // Panel state
17
+ isComponentPanelOpen: boolean;
18
+ setIsComponentPanelOpen: (open: boolean) => void;
19
+ selectedMasterComponentId: string | null;
20
+ setSelectedMasterComponentId: (id: string | null) => void;
21
+ // Master editor
22
+ editingMasterComponent: MasterComponent | null;
23
+ isMasterEditorOpen: boolean;
24
+ setIsMasterEditorOpen: (open: boolean) => void;
25
+ setEditingMasterComponent: (c: MasterComponent | null) => void;
26
+ // Component edit mode
27
+ isComponentEditMode: boolean;
28
+ editingComponentId: string | null;
29
+ // Create component dialog
30
+ createComponentDialogOpen: boolean;
31
+ setCreateComponentDialogOpen: (open: boolean) => void;
32
+ newComponentName: string;
33
+ setNewComponentName: (name: string) => void;
34
+ newComponentCategory: string;
35
+ setNewComponentCategory: (category: string) => void;
36
+ // Computed
37
+ selectedElementInstance: ComponentInstance | null;
38
+ isComponentInstance: boolean;
39
+ hasOverrides: boolean;
40
+ // Handlers
41
+ handleCreateComponent: () => void;
42
+ handleConfirmCreateComponent: () => Promise<void>;
43
+ handleEditMasterComponent: (masterId: string) => void;
44
+ handleSaveMasterComponent: (updatedMaster: MasterComponent) => Promise<void>;
45
+ handleDeleteMasterComponent: (masterId: string) => Promise<void>;
46
+ enterComponentEditMode: (masterId: string) => void;
47
+ exitComponentEditMode: (save: boolean) => Promise<void>;
48
+ handleGoToMainComponent: () => void;
49
+ handleDetachInstance: () => void;
50
+ handleResetOverrides: () => Promise<void>;
51
+ handlePushOverridesToMain: () => void;
52
+ }
53
+
54
+ /**
55
+ * コンポーネント編集モード管理フック
56
+ * マスターコンポーネントの作成・編集・削除、インスタンス操作、
57
+ * コンポーネントパネル・エディタ状態を管理
58
+ */
59
+ export function useComponentEditMode({
60
+ contentId,
61
+ closeContextMenu,
62
+ }: UseComponentEditModeProps): UseComponentEditModeReturn {
63
+ const {
64
+ iframeRef,
65
+ selectedElement,
66
+ setSelectedElement,
67
+ setSelectedElementIds,
68
+ setDomTree,
69
+ setExpandedNodes,
70
+ getIframeDoc,
71
+ notifyIframeChange,
72
+ } = useEditorContext();
73
+
74
+ const {
75
+ masterComponents,
76
+ componentInstances,
77
+ componentLibrary,
78
+ createMasterComponent,
79
+ createInstance,
80
+ updateInstance,
81
+ getInstanceByDomId,
82
+ getMasterComponent,
83
+ detachInstance,
84
+ resetAllOverrides,
85
+ updateMasterComponent,
86
+ deleteMasterComponent,
87
+ pendingNavigationTarget,
88
+ clearNavigationRequest,
89
+ } = useEditorComponents();
90
+
91
+ // コンポーネントパネル状態
92
+ const [isComponentPanelOpen, setIsComponentPanelOpen] = useState(false);
93
+ // 選択中のマスターコンポーネントID(「メインコンポーネントに移動」機能用)
94
+ const [selectedMasterComponentId, setSelectedMasterComponentId] = useState<string | null>(null);
95
+ // マスターコンポーネントエディタ(メタデータ編集用ダイアログ)状態
96
+ const [editingMasterComponent, setEditingMasterComponent] = useState<MasterComponent | null>(null);
97
+ const [isMasterEditorOpen, setIsMasterEditorOpen] = useState(false);
98
+
99
+ // コンポーネント編集モード(キャンバス内でマスターを直接編集)
100
+ const [isComponentEditMode, setIsComponentEditMode] = useState(false);
101
+ const [editingComponentId, setEditingComponentId] = useState<string | null>(null);
102
+ const originalPageHtmlRef = useRef<string | null>(null);
103
+
104
+ // コンポーネント作成ダイアログ状態
105
+ const [createComponentDialogOpen, setCreateComponentDialogOpen] = useState(false);
106
+ const [newComponentName, setNewComponentName] = useState('');
107
+ const [newComponentCategory, setNewComponentCategory] = useState('');
108
+
109
+ // Handle navigation request from property panel ("Go to Main Component")
110
+ useEffect(() => {
111
+ if (pendingNavigationTarget) {
112
+ setIsComponentPanelOpen(true);
113
+ setSelectedMasterComponentId(pendingNavigationTarget);
114
+ clearNavigationRequest();
115
+ }
116
+ }, [pendingNavigationTarget, clearNavigationRequest]);
117
+
118
+ // Check if selected element is a component instance
119
+ const selectedElementInstance = useMemo(() => {
120
+ if (!selectedElement?.id) return null;
121
+ return getInstanceByDomId(selectedElement.id);
122
+ }, [selectedElement?.id, getInstanceByDomId]);
123
+
124
+ const isComponentInstance = !!selectedElementInstance;
125
+
126
+ const hasOverrides = useMemo(() => {
127
+ if (!selectedElementInstance) return false;
128
+ return selectedElementInstance.overrides.length > 0;
129
+ }, [selectedElementInstance]);
130
+
131
+ // Create component from selected element
132
+ const handleCreateComponent = useCallback(() => {
133
+ if (!selectedElement?.id || !iframeRef.current) return;
134
+ const iframeDoc = iframeRef.current.contentDocument;
135
+ if (!iframeDoc) return;
136
+
137
+ const element = iframeDoc.querySelector(`[data-element-id="${selectedElement.id}"]`) as HTMLElement;
138
+ if (!element) return;
139
+
140
+ // Open dialog to get component name and category
141
+ setCreateComponentDialogOpen(true);
142
+ }, [selectedElement?.id, iframeRef]);
143
+
144
+ const handleConfirmCreateComponent = useCallback(async () => {
145
+ if (!selectedElement?.id || !iframeRef.current || !newComponentName.trim() || !contentId) return;
146
+ const iframeDoc = iframeRef.current.contentDocument;
147
+ if (!iframeDoc) return;
148
+
149
+ const element = iframeDoc.querySelector(`[data-element-id="${selectedElement.id}"]`) as HTMLElement;
150
+ if (!element) return;
151
+
152
+ try {
153
+ // Debug: Log the original element
154
+ console.log('[handleConfirmCreateComponent] Original element:', {
155
+ tagName: element.tagName,
156
+ className: element.className,
157
+ innerHTML: element.innerHTML.substring(0, 200),
158
+ outerHTML: element.outerHTML.substring(0, 300),
159
+ childrenCount: element.children.length,
160
+ textContent: element.textContent?.substring(0, 100),
161
+ });
162
+
163
+ // Create the master component
164
+ const component = createMasterComponent(element, newComponentName.trim(), newComponentCategory || 'content');
165
+ console.log('[handleConfirmCreateComponent] Created component:', {
166
+ id: component.id,
167
+ name: component.name,
168
+ variantsCount: component.variants.length,
169
+ defaultVariantId: component.defaultVariantId,
170
+ rootElement: component.variants[0]?.rootElement ? {
171
+ tagName: component.variants[0].rootElement.tagName,
172
+ className: component.variants[0].rootElement.className,
173
+ childrenCount: component.variants[0].rootElement.children.length,
174
+ textContent: component.variants[0].rootElement.textContent?.substring(0, 100),
175
+ } : 'NO ROOT ELEMENT',
176
+ });
177
+
178
+ // Calculate position from original element BEFORE creating instance
179
+ // Note: Can't use `parseFloat(style) || fallback` because 0 is falsy
180
+ const rect = element.getBoundingClientRect();
181
+ const iframeRect = iframeRef.current!.getBoundingClientRect();
182
+ const styleLeft = element.style.left ? parseFloat(element.style.left) : NaN;
183
+ const styleTop = element.style.top ? parseFloat(element.style.top) : NaN;
184
+ const positionX = !isNaN(styleLeft) ? styleLeft : (rect.left - iframeRect.left);
185
+ const positionY = !isNaN(styleTop) ? styleTop : (rect.top - iframeRect.top);
186
+
187
+ // Create an instance of the component to replace the original element
188
+ // Pass the component directly since state update is async
189
+ // Pass position directly to createInstance to avoid React state batching race condition
190
+ const instance = createInstance(
191
+ component.id,
192
+ undefined,
193
+ contentId,
194
+ component,
195
+ undefined, // customDomElementId
196
+ undefined, // initialOverrides
197
+ undefined, // initialPropertyValues
198
+ { x: positionX, y: positionY } // initialPosition
199
+ );
200
+ if (instance) {
201
+ console.log('[handleConfirmCreateComponent] Created instance:', {
202
+ id: instance.id,
203
+ domElementId: instance.domElementId,
204
+ variantId: instance.variantId,
205
+ position: instance.position,
206
+ });
207
+
208
+ // Render the instance to DOM (position will be applied from instance.position)
209
+ const { renderInstance } = await import('../utils/component-renderer');
210
+ const instanceElement = renderInstance(component, instance, iframeDoc);
211
+
212
+ console.log('[handleConfirmCreateComponent] Rendered instance element:', {
213
+ tagName: instanceElement.tagName,
214
+ className: instanceElement.className,
215
+ innerHTML: instanceElement.innerHTML.substring(0, 200),
216
+ outerHTML: instanceElement.outerHTML.substring(0, 300),
217
+ });
218
+
219
+ // Replace the original element with the instance
220
+ element.parentNode?.replaceChild(instanceElement, element);
221
+
222
+ // Update selection to the new instance using proper element info extraction
223
+ const { extractElementInfo } = await import('../utils/style-utils');
224
+ const elementInfo = extractElementInfo(instanceElement, iframeDoc);
225
+ if (elementInfo) {
226
+ setSelectedElement(elementInfo);
227
+ setSelectedElementIds([instance.domElementId]);
228
+ instanceElement.classList.add('selected');
229
+ }
230
+
231
+ // Update history
232
+ notifyIframeChange(true);
233
+
234
+ console.log('[handleConfirmCreateComponent] Replaced element with instance:', instance.id);
235
+ }
236
+
237
+ setCreateComponentDialogOpen(false);
238
+ setNewComponentName('');
239
+ setNewComponentCategory('');
240
+ closeContextMenu();
241
+ toast.success(`コンポーネント "${newComponentName}" を作成しました`);
242
+ } catch (error) {
243
+ console.error('Failed to create component:', error);
244
+ toast.error('コンポーネントの作成に失敗しました');
245
+ }
246
+ }, [selectedElement?.id, iframeRef, newComponentName, newComponentCategory, createMasterComponent, createInstance, contentId, notifyIframeChange, closeContextMenu, setSelectedElement, setSelectedElementIds]);
247
+
248
+ // Open master component editor
249
+ const handleEditMasterComponent = useCallback((masterId: string) => {
250
+ const master = getMasterComponent(masterId);
251
+ if (master) {
252
+ setEditingMasterComponent(master);
253
+ setIsMasterEditorOpen(true);
254
+ }
255
+ }, [getMasterComponent]);
256
+
257
+ // Save master component changes
258
+ const handleSaveMasterComponent = useCallback(async (updatedMaster: MasterComponent) => {
259
+ try {
260
+ updateMasterComponent(updatedMaster.id, updatedMaster);
261
+ setIsMasterEditorOpen(false);
262
+ setEditingMasterComponent(null);
263
+ toast.success('コンポーネントを保存しました');
264
+ } catch (error) {
265
+ console.error('Failed to save master component:', error);
266
+ toast.error('コンポーネントの保存に失敗しました');
267
+ throw error;
268
+ }
269
+ }, [updateMasterComponent]);
270
+
271
+ // Delete master component
272
+ const handleDeleteMasterComponent = useCallback(async (masterId: string) => {
273
+ try {
274
+ deleteMasterComponent(masterId);
275
+ setIsMasterEditorOpen(false);
276
+ setEditingMasterComponent(null);
277
+ toast.success('コンポーネントを削除しました');
278
+ } catch (error) {
279
+ console.error('Failed to delete master component:', error);
280
+ toast.error('コンポーネントの削除に失敗しました');
281
+ throw error;
282
+ }
283
+ }, [deleteMasterComponent]);
284
+
285
+ // Enter component edit mode - directly edit the master component in the canvas
286
+ const enterComponentEditMode = useCallback((masterId: string) => {
287
+ const master = getMasterComponent(masterId);
288
+ if (!master) {
289
+ toast.error('コンポーネントが見つかりません');
290
+ return;
291
+ }
292
+
293
+ const iframeDoc = getIframeDoc();
294
+ if (!iframeDoc) return;
295
+
296
+ // Save current page HTML
297
+ const artboard = iframeDoc.getElementById('artboard');
298
+ if (artboard) {
299
+ originalPageHtmlRef.current = artboard.innerHTML;
300
+
301
+ // DEBUG: Log what instances are being saved
302
+ console.log('[enterComponentEditMode] ========== SAVING PAGE HTML ==========');
303
+ const instancesToSave = artboard.querySelectorAll('[data-component-instance]');
304
+ console.log('[enterComponentEditMode] Saving', instancesToSave.length, 'instances');
305
+ instancesToSave.forEach((inst, idx) => {
306
+ console.log(`[enterComponentEditMode] Instance ${idx} text being saved:`, inst.textContent?.substring(0, 100));
307
+ });
308
+ }
309
+
310
+ // Get the default variant's root element
311
+ const defaultVariant = master.variants.find(v => v.id === master.defaultVariantId);
312
+ if (!defaultVariant?.rootElement) {
313
+ toast.error('コンポーネントの構造が見つかりません');
314
+ return;
315
+ }
316
+
317
+ // Render the component element to HTML
318
+ const componentElement = renderComponentElement(defaultVariant.rootElement, iframeDoc);
319
+
320
+ // Clear the artboard and add the component
321
+ if (artboard) {
322
+ artboard.innerHTML = '';
323
+ // Add padding/centering wrapper
324
+ const wrapper = iframeDoc.createElement('div');
325
+ wrapper.style.cssText = 'padding: 40px; display: flex; align-items: flex-start; justify-content: center; min-height: 100%;';
326
+ wrapper.appendChild(componentElement);
327
+ artboard.appendChild(wrapper);
328
+ }
329
+
330
+ // Add class to body to indicate component edit mode (used by selection hooks)
331
+ iframeDoc.body.classList.add('component-edit-mode');
332
+
333
+ // Set edit mode state
334
+ setIsComponentEditMode(true);
335
+ setEditingComponentId(masterId);
336
+
337
+ // Clear selection
338
+ setSelectedElement(null);
339
+ setSelectedElementIds([]);
340
+
341
+ // Rebuild DOM tree
342
+ const tree = buildDomTree(iframeDoc);
343
+ setDomTree(tree);
344
+ setExpandedNodes(new Set(tree.map((n: { id: string }) => n.id)));
345
+
346
+ toast.success(`コンポーネント「${master.name}」を編集中`);
347
+ closeContextMenu();
348
+ }, [getMasterComponent, getIframeDoc, setSelectedElement, setSelectedElementIds, setDomTree, setExpandedNodes, closeContextMenu]);
349
+
350
+ // Exit component edit mode
351
+ const exitComponentEditMode = useCallback(async (save: boolean) => {
352
+ if (!editingComponentId) return;
353
+
354
+ const iframeDoc = getIframeDoc();
355
+ if (!iframeDoc) return;
356
+
357
+ // Store the updated master component for syncing after page restore
358
+ let updatedMaster: MasterComponent | null = null;
359
+
360
+ if (save) {
361
+ // Get the master component
362
+ const master = getMasterComponent(editingComponentId);
363
+ if (!master) {
364
+ toast.error('コンポーネントが見つかりません');
365
+ return;
366
+ }
367
+
368
+ // Get the edited element from the artboard
369
+ const artboard = iframeDoc.getElementById('artboard');
370
+ const wrapper = artboard?.firstElementChild as HTMLElement;
371
+ const editedElement = wrapper?.firstElementChild as HTMLElement;
372
+
373
+ if (editedElement) {
374
+ // Convert back to ComponentElement
375
+ const newRootElement = htmlElementToComponentElement(editedElement);
376
+
377
+ // Find and update the default variant
378
+ const updatedVariants = master.variants.map(v => {
379
+ if (v.id === master.defaultVariantId) {
380
+ return { ...v, rootElement: newRootElement };
381
+ }
382
+ return v;
383
+ });
384
+
385
+ // Update the master component
386
+ try {
387
+ updateMasterComponent(master.id, {
388
+ variants: updatedVariants,
389
+ updatedAt: new Date().toISOString(),
390
+ });
391
+
392
+ // Store for syncing after page restore
393
+ updatedMaster = {
394
+ ...master,
395
+ variants: updatedVariants,
396
+ updatedAt: new Date().toISOString(),
397
+ };
398
+
399
+ toast.success('コンポーネントを保存しました');
400
+ } catch (error) {
401
+ console.error('Failed to save component:', error);
402
+ toast.error('コンポーネントの保存に失敗しました');
403
+ }
404
+ }
405
+ }
406
+
407
+ // Restore original page HTML
408
+ const artboard = iframeDoc.getElementById('artboard');
409
+
410
+ // DEBUG: Log what we're about to restore
411
+ console.log('[exitComponentEditMode] ========== HTML RESTORE DEBUG ==========');
412
+ console.log('[exitComponentEditMode] originalPageHtmlRef exists:', originalPageHtmlRef.current !== null);
413
+ if (originalPageHtmlRef.current) {
414
+ // Extract instance text from saved HTML for debugging
415
+ const tempDiv = document.createElement('div');
416
+ tempDiv.innerHTML = originalPageHtmlRef.current;
417
+ const savedInstances = tempDiv.querySelectorAll('[data-component-instance]');
418
+ console.log('[exitComponentEditMode] Saved HTML has', savedInstances.length, 'instances');
419
+ savedInstances.forEach((inst, idx) => {
420
+ console.log(`[exitComponentEditMode] Saved instance ${idx} text:`, inst.textContent?.substring(0, 100));
421
+ });
422
+ }
423
+
424
+ if (artboard && originalPageHtmlRef.current !== null) {
425
+ artboard.innerHTML = originalPageHtmlRef.current;
426
+ originalPageHtmlRef.current = null;
427
+
428
+ // DEBUG: Log what's in the DOM after restore
429
+ const restoredInstances = artboard.querySelectorAll('[data-component-instance]');
430
+ console.log('[exitComponentEditMode] After restore, DOM has', restoredInstances.length, 'instances');
431
+ restoredInstances.forEach((inst, idx) => {
432
+ console.log(`[exitComponentEditMode] Restored instance ${idx} text:`, inst.textContent?.substring(0, 100));
433
+ });
434
+ }
435
+
436
+ // Sync all instances with the updated master component
437
+ if (updatedMaster && save) {
438
+ // Get all instances as an array
439
+ const instancesArray = Array.from(componentInstances.values());
440
+
441
+ // Debug: Log the updated master and instances
442
+ console.log('[exitComponentEditMode] ========== SYNC DEBUG ==========');
443
+ console.log('[exitComponentEditMode] Updated master:', {
444
+ id: updatedMaster.id,
445
+ name: updatedMaster.name,
446
+ variantsCount: updatedMaster.variants.length,
447
+ });
448
+
449
+ // Log master's text content
450
+ const defaultVariant = updatedMaster.variants.find(v => v.id === updatedMaster.defaultVariantId);
451
+ if (defaultVariant) {
452
+ console.log('[exitComponentEditMode] Master rootElement:', {
453
+ tagName: defaultVariant.rootElement.tagName,
454
+ textContent: defaultVariant.rootElement.textContent?.substring(0, 100),
455
+ innerHTML: defaultVariant.rootElement.innerHTML?.substring(0, 100),
456
+ childrenCount: defaultVariant.rootElement.children.length,
457
+ });
458
+ }
459
+
460
+ console.log('[exitComponentEditMode] Instances to sync:', instancesArray.map(i => ({
461
+ id: i.id,
462
+ masterComponentId: i.masterComponentId,
463
+ domElementId: i.domElementId,
464
+ isDetached: i.isDetached,
465
+ existingOverrides: i.overrides.length,
466
+ })));
467
+
468
+ // Debug: Check if instances exist in restored DOM
469
+ const artboardForSync = iframeDoc.getElementById('artboard');
470
+ if (artboardForSync) {
471
+ const instanceElements = artboardForSync.querySelectorAll(`[data-component-master="${updatedMaster.id}"]`);
472
+ console.log('[exitComponentEditMode] Instance elements in DOM:', instanceElements.length);
473
+ instanceElements.forEach((el, i) => {
474
+ console.log(`[exitComponentEditMode] DOM instance ${i}:`, {
475
+ instanceId: el.getAttribute('data-component-instance'),
476
+ masterId: el.getAttribute('data-component-master'),
477
+ elementId: el.getAttribute('data-element-id'),
478
+ textContent: el.textContent?.substring(0, 100),
479
+ });
480
+ });
481
+ }
482
+
483
+ // Propagate master changes to all instances in the DOM
484
+ const syncResult = propagateMasterChanges(updatedMaster, instancesArray, iframeDoc);
485
+ console.log('[exitComponentEditMode] Synced instances:', syncResult);
486
+
487
+ // Persist updated instances with their newly detected overrides
488
+ if (syncResult.updatedInstances.length > 0) {
489
+ console.log('[exitComponentEditMode] Persisting updated instances with overrides:',
490
+ syncResult.updatedInstances.map(i => ({
491
+ id: i.id,
492
+ overridesCount: i.overrides.length,
493
+ }))
494
+ );
495
+ for (const updatedInst of syncResult.updatedInstances) {
496
+ updateInstance(updatedInst.id, {
497
+ overrides: updatedInst.overrides,
498
+ variantId: updatedInst.variantId,
499
+ });
500
+ }
501
+ }
502
+
503
+ if (syncResult.updatedCount > 0) {
504
+ toast.success(`${syncResult.updatedCount}個のインスタンスを更新しました`);
505
+ }
506
+ }
507
+
508
+ // Remove component edit mode class from body
509
+ iframeDoc.body.classList.remove('component-edit-mode');
510
+
511
+ // Clear edit mode state
512
+ setIsComponentEditMode(false);
513
+ setEditingComponentId(null);
514
+ setSelectedElement(null);
515
+ setSelectedElementIds([]);
516
+
517
+ // Rebuild DOM tree
518
+ const tree = buildDomTree(iframeDoc);
519
+ setDomTree(tree);
520
+ setExpandedNodes(new Set(tree.map((n: { id: string }) => n.id)));
521
+
522
+ // Notify change
523
+ notifyIframeChange(true);
524
+ }, [editingComponentId, getMasterComponent, getIframeDoc, updateMasterComponent, updateInstance, componentInstances, setSelectedElement, setSelectedElementIds, setDomTree, setExpandedNodes, notifyIframeChange]);
525
+
526
+ // Go to main component - enters component edit mode to directly edit the master in canvas
527
+ const handleGoToMainComponent = useCallback(() => {
528
+ if (!selectedElementInstance) return;
529
+ console.log('Go to main component:', selectedElementInstance.masterComponentId);
530
+ enterComponentEditMode(selectedElementInstance.masterComponentId);
531
+ }, [selectedElementInstance, enterComponentEditMode]);
532
+
533
+ // Detach instance
534
+ const handleDetachInstance = useCallback(() => {
535
+ if (!selectedElementInstance) return;
536
+ try {
537
+ // Get the rendered HTML element from the detach operation
538
+ const detachedElement = detachInstance(selectedElementInstance.id);
539
+
540
+ // Find and replace the instance element in the DOM
541
+ const iframeDoc = getIframeDoc();
542
+ if (iframeDoc && detachedElement) {
543
+ const existingElement = iframeDoc.querySelector(`[data-element-id="${selectedElementInstance.domElementId}"]`);
544
+ if (existingElement) {
545
+ // Remove component-related attributes from the detached element
546
+ detachedElement.removeAttribute('data-component-instance');
547
+ detachedElement.removeAttribute('data-component-master');
548
+
549
+ // Preserve position from existing element
550
+ const existingStyle = existingElement.getAttribute('style');
551
+ if (existingStyle) {
552
+ detachedElement.setAttribute('style', existingStyle);
553
+ }
554
+
555
+ // Replace with detached element
556
+ existingElement.replaceWith(detachedElement);
557
+
558
+ // Notify change
559
+ notifyIframeChange(true);
560
+ }
561
+ }
562
+
563
+ console.log('Detached instance:', selectedElementInstance.id);
564
+ closeContextMenu();
565
+ toast.success('インスタンスを解除しました');
566
+ } catch (error) {
567
+ console.error('Failed to detach instance:', error);
568
+ toast.error('インスタンスの解除に失敗しました');
569
+ }
570
+ }, [selectedElementInstance, detachInstance, getIframeDoc, notifyIframeChange, closeContextMenu]);
571
+
572
+ // Reset all overrides
573
+ const handleResetOverrides = useCallback(async () => {
574
+ if (!selectedElementInstance) return;
575
+ try {
576
+ resetAllOverrides(selectedElementInstance.id);
577
+
578
+ // Re-render the instance in the DOM
579
+ const master = getMasterComponent(selectedElementInstance.masterComponentId);
580
+ const iframeDoc = getIframeDoc();
581
+ if (master && iframeDoc) {
582
+ const existingElement = iframeDoc.querySelector(`[data-element-id="${selectedElementInstance.domElementId}"]`);
583
+ if (existingElement) {
584
+ // Get the reset instance (with no overrides)
585
+ const resetInstance = { ...selectedElementInstance, overrides: [], propertyValues: {} };
586
+ const { renderInstance } = await import('../utils/component-renderer');
587
+ const newElement = renderInstance(master, resetInstance, iframeDoc);
588
+
589
+ // Preserve position from existing element
590
+ const existingStyle = existingElement.getAttribute('style');
591
+ if (existingStyle) {
592
+ newElement.setAttribute('style', existingStyle);
593
+ }
594
+
595
+ // Replace the old element
596
+ existingElement.replaceWith(newElement);
597
+
598
+ // Notify change
599
+ notifyIframeChange(true);
600
+ }
601
+ }
602
+
603
+ console.log('Reset overrides for instance:', selectedElementInstance.id);
604
+ closeContextMenu();
605
+ toast.success('オーバーライドをリセットしました');
606
+ } catch (error) {
607
+ console.error('Failed to reset overrides:', error);
608
+ toast.error('オーバーライドのリセットに失敗しました');
609
+ }
610
+ }, [selectedElementInstance, resetAllOverrides, getMasterComponent, getIframeDoc, notifyIframeChange, closeContextMenu]);
611
+
612
+ // Push overrides to main (placeholder - requires more complex implementation)
613
+ const handlePushOverridesToMain = useCallback(() => {
614
+ if (!selectedElementInstance) return;
615
+ console.log('Push overrides to main - not yet implemented');
616
+ closeContextMenu();
617
+ toast.info('この機能は現在開発中です');
618
+ }, [selectedElementInstance, closeContextMenu]);
619
+
620
+ return {
621
+ // Panel state
622
+ isComponentPanelOpen,
623
+ setIsComponentPanelOpen,
624
+ selectedMasterComponentId,
625
+ setSelectedMasterComponentId,
626
+ // Master editor
627
+ editingMasterComponent,
628
+ isMasterEditorOpen,
629
+ setIsMasterEditorOpen,
630
+ setEditingMasterComponent,
631
+ // Component edit mode
632
+ isComponentEditMode,
633
+ editingComponentId,
634
+ // Create component dialog
635
+ createComponentDialogOpen,
636
+ setCreateComponentDialogOpen,
637
+ newComponentName,
638
+ setNewComponentName,
639
+ newComponentCategory,
640
+ setNewComponentCategory,
641
+ // Computed
642
+ selectedElementInstance,
643
+ isComponentInstance,
644
+ hasOverrides,
645
+ // Handlers
646
+ handleCreateComponent,
647
+ handleConfirmCreateComponent,
648
+ handleEditMasterComponent,
649
+ handleSaveMasterComponent,
650
+ handleDeleteMasterComponent,
651
+ enterComponentEditMode,
652
+ exitComponentEditMode,
653
+ handleGoToMainComponent,
654
+ handleDetachInstance,
655
+ handleResetOverrides,
656
+ handlePushOverridesToMain,
657
+ };
658
+ }