@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,397 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * EditorVariablesContext
5
+ *
6
+ * CSS変数(デザイントークン)の管理を行うContext
7
+ * 変更頻度: 低(変数定義時のみ)
8
+ *
9
+ * 含まれる状態:
10
+ * - variables: CSS変数定義のリスト
11
+ * - isLoading: 読み込み中フラグ
12
+ * - isSaving: 保存中フラグ
13
+ * - hasChanges: 未保存変更フラグ
14
+ *
15
+ * 含まれる操作:
16
+ * - addVariable: 変数を追加
17
+ * - updateVariable: 変数を更新
18
+ * - deleteVariable: 変数を削除
19
+ * - saveVariables: 変数を保存
20
+ * - loadVariables: 変数を読み込み
21
+ * - syncFromTheme: テーマから同期
22
+ */
23
+
24
+ import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from 'react';
25
+ import type {
26
+ CSSVariableDefinition,
27
+ CSSVariableCategory,
28
+ CSSVariableGroup,
29
+ } from '../../types/css-variables';
30
+ import {
31
+ generateVariableId,
32
+ generateCSSName,
33
+ generateResponsiveCSSFromVariables,
34
+ groupVariablesByCategory,
35
+ } from '../../types/css-variables';
36
+
37
+ // ============================================
38
+ // Context Value Interface
39
+ // ============================================
40
+
41
+ export interface EditorVariablesContextValue {
42
+ // 状態
43
+ variables: CSSVariableDefinition[];
44
+ isLoading: boolean;
45
+ isSaving: boolean;
46
+ hasChanges: boolean;
47
+ /** 変数が1つ以上定義されているかどうか */
48
+ hasVariables: boolean;
49
+
50
+ // グループ化された変数(UIで使用)
51
+ variableGroups: CSSVariableGroup[];
52
+
53
+ // CSS出力
54
+ cssString: string;
55
+
56
+ // 変数操作
57
+ addVariable: (name: string, value: string, category: CSSVariableCategory, description?: string) => CSSVariableDefinition;
58
+ updateVariable: (id: string, updates: Partial<Omit<CSSVariableDefinition, 'id'>>) => void;
59
+ deleteVariable: (id: string) => void;
60
+ reorderVariables: (categoryId: CSSVariableCategory, fromIndex: number, toIndex: number) => void;
61
+
62
+ // 変数検索
63
+ getVariableById: (id: string) => CSSVariableDefinition | undefined;
64
+ getVariableByCssName: (cssName: string) => CSSVariableDefinition | undefined;
65
+ getVariablesByCategory: (category: CSSVariableCategory) => CSSVariableDefinition[];
66
+
67
+ // 永続化
68
+ saveVariables: () => Promise<void>;
69
+ loadVariables: (websiteId: string) => Promise<void>;
70
+
71
+ // テーマ同期
72
+ syncFromTheme: (themeVariables: CSSVariableDefinition[]) => void;
73
+
74
+ // バルク操作
75
+ setVariables: (variables: CSSVariableDefinition[]) => void;
76
+ clearVariables: () => void;
77
+
78
+ // iframe注入用
79
+ injectCSSToIframe: (iframeDoc: Document) => void;
80
+ }
81
+
82
+ // ============================================
83
+ // Context Creation
84
+ // ============================================
85
+
86
+ const EditorVariablesContext = createContext<EditorVariablesContextValue | null>(null);
87
+
88
+ export function useEditorVariables(): EditorVariablesContextValue {
89
+ const context = useContext(EditorVariablesContext);
90
+ if (!context) {
91
+ throw new Error('useEditorVariables must be used within EditorVariablesProvider');
92
+ }
93
+ return context;
94
+ }
95
+
96
+ // ============================================
97
+ // Provider Props
98
+ // ============================================
99
+
100
+ interface EditorVariablesProviderProps {
101
+ children: React.ReactNode;
102
+ /** 初期変数(オプション) */
103
+ initialVariables?: CSSVariableDefinition[];
104
+ /** 変数保存コールバック */
105
+ onSave?: (variables: CSSVariableDefinition[]) => Promise<void>;
106
+ /** 変数読み込みコールバック */
107
+ onLoad?: (websiteId: string) => Promise<CSSVariableDefinition[]>;
108
+ }
109
+
110
+ // ============================================
111
+ // Provider Implementation
112
+ // ============================================
113
+
114
+ export function EditorVariablesProvider({
115
+ children,
116
+ initialVariables = [],
117
+ onSave,
118
+ onLoad,
119
+ }: EditorVariablesProviderProps) {
120
+ // 状態
121
+ const [variables, setVariablesState] = useState<CSSVariableDefinition[]>(initialVariables);
122
+ const [isLoading, setIsLoading] = useState(false);
123
+ const [isSaving, setIsSaving] = useState(false);
124
+ const [hasChanges, setHasChanges] = useState(false);
125
+
126
+ // 最後に保存した変数のスナップショット
127
+ const savedVariablesRef = useRef<CSSVariableDefinition[]>(initialVariables);
128
+
129
+ // 変数をグループ化
130
+ const variableGroups = useMemo(() => groupVariablesByCategory(variables), [variables]);
131
+
132
+ // CSS文字列生成
133
+ const cssString = useMemo(() => generateResponsiveCSSFromVariables(variables), [variables]);
134
+
135
+ // 変更フラグを更新するヘルパー
136
+ const markAsChanged = useCallback(() => {
137
+ setHasChanges(true);
138
+ }, []);
139
+
140
+ // 変数追加
141
+ const addVariable = useCallback((
142
+ name: string,
143
+ value: string,
144
+ category: CSSVariableCategory,
145
+ description?: string
146
+ ): CSSVariableDefinition => {
147
+ const now = new Date();
148
+ const newVariable: CSSVariableDefinition = {
149
+ id: generateVariableId(),
150
+ name,
151
+ cssName: generateCSSName(name, category),
152
+ value,
153
+ category,
154
+ description,
155
+ createdAt: now,
156
+ updatedAt: now,
157
+ };
158
+
159
+ setVariablesState(prev => [...prev, newVariable]);
160
+ markAsChanged();
161
+
162
+ return newVariable;
163
+ }, [markAsChanged]);
164
+
165
+ // 変数更新
166
+ const updateVariable = useCallback((
167
+ id: string,
168
+ updates: Partial<Omit<CSSVariableDefinition, 'id'>>
169
+ ) => {
170
+ setVariablesState(prev => prev.map(v => {
171
+ if (v.id !== id) return v;
172
+
173
+ const updated = { ...v, ...updates, updatedAt: new Date() };
174
+
175
+ // 名前が変更された場合、CSS名も更新
176
+ if (updates.name && updates.name !== v.name) {
177
+ updated.cssName = generateCSSName(updates.name, updates.category ?? v.category);
178
+ }
179
+
180
+ // カテゴリが変更された場合、プレフィックスを更新
181
+ if (updates.category && updates.category !== v.category && !updates.name) {
182
+ updated.cssName = generateCSSName(v.name, updates.category);
183
+ }
184
+
185
+ return updated;
186
+ }));
187
+ markAsChanged();
188
+ }, [markAsChanged]);
189
+
190
+ // 変数削除
191
+ const deleteVariable = useCallback((id: string) => {
192
+ setVariablesState(prev => prev.filter(v => v.id !== id));
193
+ markAsChanged();
194
+ }, [markAsChanged]);
195
+
196
+ // 変数並び替え
197
+ const reorderVariables = useCallback((
198
+ category: CSSVariableCategory,
199
+ fromIndex: number,
200
+ toIndex: number
201
+ ) => {
202
+ setVariablesState(prev => {
203
+ // カテゴリ内の変数のインデックスを取得
204
+ const categoryIndices: number[] = [];
205
+ prev.forEach((v, i) => {
206
+ if (v.category === category) {
207
+ categoryIndices.push(i);
208
+ }
209
+ });
210
+
211
+ // インデックスが範囲外の場合は何もしない
212
+ if (fromIndex < 0 || fromIndex >= categoryIndices.length ||
213
+ toIndex < 0 || toIndex >= categoryIndices.length) {
214
+ return prev;
215
+ }
216
+
217
+ // 新しい配列を作成
218
+ const result = [...prev];
219
+
220
+ // 移動元と移動先の実際のインデックス
221
+ const fromActualIndex = categoryIndices[fromIndex];
222
+ const toActualIndex = categoryIndices[toIndex];
223
+
224
+ // 要素を移動
225
+ const [movedVar] = result.splice(fromActualIndex, 1);
226
+
227
+ // 削除後のインデックスを再計算(削除により位置がずれる場合がある)
228
+ let adjustedToIndex = toActualIndex;
229
+ if (fromActualIndex < toActualIndex) {
230
+ adjustedToIndex = toActualIndex - 1;
231
+ }
232
+
233
+ result.splice(adjustedToIndex, 0, movedVar);
234
+
235
+ return result;
236
+ });
237
+ markAsChanged();
238
+ }, [markAsChanged]);
239
+
240
+ // ID で変数を取得
241
+ const getVariableById = useCallback((id: string): CSSVariableDefinition | undefined => {
242
+ return variables.find(v => v.id === id);
243
+ }, [variables]);
244
+
245
+ // CSS名で変数を取得
246
+ const getVariableByCssName = useCallback((cssName: string): CSSVariableDefinition | undefined => {
247
+ return variables.find(v => v.cssName === cssName);
248
+ }, [variables]);
249
+
250
+ // カテゴリで変数を取得
251
+ const getVariablesByCategory = useCallback((category: CSSVariableCategory): CSSVariableDefinition[] => {
252
+ return variables.filter(v => v.category === category);
253
+ }, [variables]);
254
+
255
+ // 変数を保存
256
+ const saveVariables = useCallback(async () => {
257
+ if (!onSave) {
258
+ console.warn('[EditorVariablesContext] onSave callback not provided');
259
+ return;
260
+ }
261
+
262
+ setIsSaving(true);
263
+ try {
264
+ await onSave(variables);
265
+ savedVariablesRef.current = [...variables];
266
+ setHasChanges(false);
267
+ console.log('[EditorVariablesContext] Variables saved successfully');
268
+ } catch (error) {
269
+ console.error('[EditorVariablesContext] Failed to save variables:', error);
270
+ throw error;
271
+ } finally {
272
+ setIsSaving(false);
273
+ }
274
+ }, [variables, onSave]);
275
+
276
+ // 変数を読み込み
277
+ const loadVariables = useCallback(async (websiteId: string) => {
278
+ if (!onLoad) {
279
+ console.warn('[EditorVariablesContext] onLoad callback not provided');
280
+ return;
281
+ }
282
+
283
+ setIsLoading(true);
284
+ try {
285
+ const loaded = await onLoad(websiteId);
286
+ setVariablesState(loaded);
287
+ savedVariablesRef.current = [...loaded];
288
+ setHasChanges(false);
289
+ console.log('[EditorVariablesContext] Variables loaded:', loaded.length);
290
+ } catch (error) {
291
+ console.error('[EditorVariablesContext] Failed to load variables:', error);
292
+ throw error;
293
+ } finally {
294
+ setIsLoading(false);
295
+ }
296
+ }, [onLoad]);
297
+
298
+ // テーマから同期
299
+ const syncFromTheme = useCallback((themeVariables: CSSVariableDefinition[]) => {
300
+ setVariablesState(themeVariables);
301
+ markAsChanged();
302
+ console.log('[EditorVariablesContext] Synced from theme:', themeVariables.length, 'variables');
303
+ }, [markAsChanged]);
304
+
305
+ // バルク設定
306
+ const setVariables = useCallback((newVariables: CSSVariableDefinition[]) => {
307
+ setVariablesState(newVariables);
308
+ markAsChanged();
309
+ }, [markAsChanged]);
310
+
311
+ // クリア
312
+ const clearVariables = useCallback(() => {
313
+ setVariablesState([]);
314
+ markAsChanged();
315
+ }, [markAsChanged]);
316
+
317
+ // iframeにCSSを注入
318
+ const injectCSSToIframe = useCallback((iframeDoc: Document) => {
319
+ const styleId = 'editor-css-variables';
320
+ let styleEl = iframeDoc.getElementById(styleId) as HTMLStyleElement | null;
321
+
322
+ if (!styleEl) {
323
+ styleEl = iframeDoc.createElement('style');
324
+ styleEl.id = styleId;
325
+ // headの最初に挿入(他のスタイルより優先)
326
+ iframeDoc.head.insertBefore(styleEl, iframeDoc.head.firstChild);
327
+ }
328
+
329
+ styleEl.textContent = cssString;
330
+ console.log('[EditorVariablesContext] Injected CSS variables to iframe');
331
+ }, [cssString]);
332
+
333
+ // Context値
334
+ const value: EditorVariablesContextValue = useMemo(() => ({
335
+ // 状態
336
+ variables,
337
+ isLoading,
338
+ isSaving,
339
+ hasChanges,
340
+ hasVariables: variables.length > 0,
341
+ variableGroups,
342
+ cssString,
343
+
344
+ // 変数操作
345
+ addVariable,
346
+ updateVariable,
347
+ deleteVariable,
348
+ reorderVariables,
349
+
350
+ // 変数検索
351
+ getVariableById,
352
+ getVariableByCssName,
353
+ getVariablesByCategory,
354
+
355
+ // 永続化
356
+ saveVariables,
357
+ loadVariables,
358
+
359
+ // テーマ同期
360
+ syncFromTheme,
361
+
362
+ // バルク操作
363
+ setVariables,
364
+ clearVariables,
365
+
366
+ // iframe注入
367
+ injectCSSToIframe,
368
+ }), [
369
+ variables,
370
+ isLoading,
371
+ isSaving,
372
+ hasChanges,
373
+ variableGroups,
374
+ cssString,
375
+ addVariable,
376
+ updateVariable,
377
+ deleteVariable,
378
+ reorderVariables,
379
+ getVariableById,
380
+ getVariableByCssName,
381
+ getVariablesByCategory,
382
+ saveVariables,
383
+ loadVariables,
384
+ syncFromTheme,
385
+ setVariables,
386
+ clearVariables,
387
+ injectCSSToIframe,
388
+ ]);
389
+
390
+ return (
391
+ <EditorVariablesContext.Provider value={value}>
392
+ {children}
393
+ </EditorVariablesContext.Provider>
394
+ );
395
+ }
396
+
397
+ export default EditorVariablesContext;
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * EditorViewContext
5
+ *
6
+ * エディタの表示/ズーム状態を管理するContext
7
+ * 変更頻度: 中(ズーム操作、ビューモード切り替え時)
8
+ *
9
+ * 含まれる状態:
10
+ * - zoom/fitZoom: シングルアートボードのズーム
11
+ * - viewportWidth: レスポンシブプレビュー幅
12
+ * - viewMode: シングル/グリッド表示
13
+ * - globalZoom/globalScrollPosition: グリッドビュー時の全体ズーム
14
+ */
15
+
16
+ import React, { createContext, useContext, useState } from 'react';
17
+ import { BREAKPOINT_PRESETS } from '../constants';
18
+
19
+ /** ビューモード(シングル、グリッド、またはキャンバス表示) */
20
+ export type ViewMode = 'single' | 'grid' | 'canvas';
21
+
22
+ export interface EditorViewContextValue {
23
+ // シングルアートボードのズーム
24
+ zoom: number;
25
+ setZoom: (zoom: number) => void;
26
+ fitZoom: number;
27
+ setFitZoom: (zoom: number) => void;
28
+
29
+ // ビューポート幅(webpageモードのレスポンシブプレビュー用)
30
+ viewportWidth: number;
31
+ setViewportWidth: (width: number) => void;
32
+
33
+ // ビューモード
34
+ viewMode: ViewMode;
35
+ setViewMode: (mode: ViewMode) => void;
36
+
37
+ // グローバルキャンバス状態(グリッド表示用)
38
+ globalZoom: number;
39
+ setGlobalZoom: (zoom: number) => void;
40
+ globalScrollPosition: { x: number; y: number };
41
+ setGlobalScrollPosition: (pos: { x: number; y: number }) => void;
42
+ }
43
+
44
+ const EditorViewContext = createContext<EditorViewContextValue | null>(null);
45
+
46
+ export function useEditorView(): EditorViewContextValue {
47
+ const context = useContext(EditorViewContext);
48
+ if (!context) {
49
+ throw new Error('useEditorView must be used within EditorViewProvider');
50
+ }
51
+ return context;
52
+ }
53
+
54
+ interface EditorViewProviderProps {
55
+ children: React.ReactNode;
56
+ /** 初期の表示幅。省略時はプリセットの先頭 */
57
+ initialViewportWidth?: number;
58
+ }
59
+
60
+ export function EditorViewProvider({
61
+ children,
62
+ initialViewportWidth,
63
+ }: EditorViewProviderProps) {
64
+ // シングルアートボードのズーム
65
+ const [zoom, setZoom] = useState(100);
66
+ const [fitZoom, setFitZoom] = useState(100);
67
+
68
+ // ビューポート幅。利用側が artboardWidth を渡せばそれが初期値になる
69
+ // (構成ラフのように「この幅で見ると決まっている」用途がある)
70
+ const [viewportWidth, setViewportWidth] = useState<number>(
71
+ initialViewportWidth ?? BREAKPOINT_PRESETS[0].width,
72
+ );
73
+
74
+ // ビューモード(デフォルトはgrid = Figma風マルチアートボード表示)
75
+ const [viewMode, setViewMode] = useState<ViewMode>('grid');
76
+
77
+ // グローバルキャンバス状態(グリッド表示用)
78
+ const [globalZoom, setGlobalZoom] = useState(100);
79
+ const [globalScrollPosition, setGlobalScrollPosition] = useState({ x: 0, y: 0 });
80
+
81
+ const value: EditorViewContextValue = {
82
+ zoom,
83
+ setZoom,
84
+ fitZoom,
85
+ setFitZoom,
86
+ viewportWidth,
87
+ setViewportWidth,
88
+ viewMode,
89
+ setViewMode,
90
+ globalZoom,
91
+ setGlobalZoom,
92
+ globalScrollPosition,
93
+ setGlobalScrollPosition,
94
+ };
95
+
96
+ return (
97
+ <EditorViewContext.Provider value={value}>
98
+ {children}
99
+ </EditorViewContext.Provider>
100
+ );
101
+ }