@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,470 @@
1
+ /**
2
+ * HTML生成・クリーンアップ関連のユーティリティ関数
3
+ */
4
+
5
+ import { SLIDE_WIDTH, SLIDE_HEIGHT, WEBPAGE_WIDTH, WEBPAGE_MIN_HEIGHT } from '../constants';
6
+ import type { EditorMode } from '../EditorContext';
7
+ import { prepareHtmlForSave } from './viewport-utils';
8
+ import { classifySaveAndRevert } from './dom-utils';
9
+
10
+ // キャンバス背景色(Figmaライクなダークグレー)
11
+ const CANVAS_BG_COLOR = '#1a1a1a';
12
+
13
+ /**
14
+ * HTMLコンテンツにTailwind CSSが読み込まれているかチェック
15
+ * @param content - HTMLコンテンツ
16
+ * @returns Tailwind CSSが読み込まれている場合はtrue
17
+ */
18
+ export function hasTailwindCss(content: string): boolean {
19
+ // Tailwind CSS CDNリンクのパターン
20
+ const tailwindPatterns = [
21
+ /cdn\.tailwindcss\.com/i,
22
+ /@tailwindcss\/browser/i,
23
+ /tailwindcss-browser/i,
24
+ /tailwind\.min\.css/i,
25
+ /tailwind\.css/i,
26
+ /<style[^>]*>[\s\S]*@tailwind\s+(base|components|utilities)/i,
27
+ ];
28
+
29
+ return tailwindPatterns.some(pattern => pattern.test(content));
30
+ }
31
+
32
+ /**
33
+ * HTMLコンテンツが外部サイトからインポートされた可能性があるかチェック
34
+ * 外部CSSリンクや大量のインラインスタイルがある場合はtrue
35
+ * @param content - HTMLコンテンツ
36
+ * @returns 外部サイトからのインポートの可能性がある場合はtrue
37
+ */
38
+ export function hasExternalStyles(content: string): boolean {
39
+ // 外部CSSリンクのパターン
40
+ const externalCssPatterns = [
41
+ /<link[^>]*rel=["']stylesheet["'][^>]*href=["']https?:\/\//i,
42
+ /<link[^>]*href=["']https?:\/\/[^>]*rel=["']stylesheet["']/i,
43
+ ];
44
+
45
+ // 外部CSSリンクがある
46
+ if (externalCssPatterns.some(pattern => pattern.test(content))) {
47
+ return true;
48
+ }
49
+
50
+ // 大量のインラインスタイル(<style>タグ内に100文字以上)
51
+ const styleTagMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
52
+ if (styleTagMatch) {
53
+ const totalStyleLength = styleTagMatch.reduce((sum, tag) => {
54
+ const innerContent = tag.replace(/<\/?style[^>]*>/gi, '');
55
+ return sum + innerContent.length;
56
+ }, 0);
57
+ // 500文字以上のスタイルがあれば外部コンテンツの可能性が高い
58
+ if (totalStyleLength > 500) {
59
+ return true;
60
+ }
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Tailwind CSS Browser スクリプトの注入用HTML
68
+ * blob URLからの読み込みに対応するため、絶対URLを使用
69
+ *
70
+ * 重要: 既存のスタイルを壊さないよう、preflightを無効化
71
+ * - ユーティリティクラスのみを有効化
72
+ * - CSSリセットは適用しない
73
+ *
74
+ * @param safeMode - trueの場合、より安全なモード(preflightを完全に無効化)
75
+ */
76
+ function getTailwindScriptHtml(safeMode: boolean = false): string {
77
+ // window.location.originを使用して絶対URLを構築
78
+ // SSR時はデフォルトのCDN URLにフォールバック
79
+ const baseUrl = typeof window !== 'undefined'
80
+ ? window.location.origin
81
+ : '';
82
+ const scriptUrl = baseUrl
83
+ ? `${baseUrl}/vendor/tailwindcss-browser.js`
84
+ : 'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4';
85
+
86
+ // セーフモード: より厳格にpreflightを無効化し、既存スタイルへの影響を最小化
87
+ if (safeMode) {
88
+ return `
89
+ <!-- Tailwind CSS Browser (safe mode - utilities only) -->
90
+ <script src="${scriptUrl}" data-tailwind-injected="true"></script>
91
+ <style type="text/tailwindcss">
92
+ /* preflightを完全に無効化 - ユーティリティのみ使用 */
93
+ @layer base {
94
+ /* 空のbaseレイヤーでpreflightを上書き */
95
+ }
96
+ @theme {
97
+ /* 最小限のテーマ設定 */
98
+ --font-sans: system-ui, sans-serif;
99
+ ${collectHostThemeTokens()}
100
+ }
101
+ </style>
102
+ <style>
103
+ /* 既存スタイルを保護: Tailwindのグローバルリセットを無効化 */
104
+ :where([data-tailwind-injected]) ~ * {
105
+ all: revert-layer;
106
+ }
107
+ </style>`;
108
+ }
109
+
110
+ // 通常モード
111
+ return `
112
+ <!-- Tailwind CSS Browser (self-hosted with CDN fallback) -->
113
+ <script src="${scriptUrl}" data-tailwind-injected="true"></script>
114
+ <style type="text/tailwindcss">
115
+ @layer base {
116
+ /* CSSリセットを最小化 */
117
+ }
118
+ @theme {
119
+ --font-sans: 'Noto Sans JP', system-ui, sans-serif;
120
+ ${collectHostThemeTokens()}
121
+ }
122
+ </style>`;
123
+ }
124
+
125
+ /**
126
+ * キャンバス+アートボード構造の編集用HTMLを生成
127
+ * @param content - HTMLコンテンツ
128
+ * @param editorMode - エディタモード('slide' または 'webpage')
129
+ *
130
+ * 構造:
131
+ * - html/body: 100%サイズ、キャンバス背景色
132
+ * - #canvas-container: ズーム/パン対象のスクロール領域
133
+ * - #artboard-wrapper: transform適用対象(ズーム/パン)
134
+ * - #artboard: 白背景、コンテンツ配置エリア
135
+ */
136
+ export function generateEditableHtml(content: string, editorMode: EditorMode = 'slide'): string {
137
+ const artboardWidth = editorMode === 'webpage' ? WEBPAGE_WIDTH : SLIDE_WIDTH;
138
+ const artboardHeight = editorMode === 'webpage' ? WEBPAGE_MIN_HEIGHT : SLIDE_HEIGHT;
139
+ const artboardHeightStyle = editorMode === 'webpage' ? 'min-height' : 'height';
140
+
141
+ // コンテンツにTailwind CSSが含まれていない場合は注入
142
+ // 外部スタイルがある場合はセーフモードを使用(既存スタイルを保護)
143
+ const needsTailwind = !hasTailwindCss(content);
144
+ const useSafeMode = needsTailwind && hasExternalStyles(content);
145
+ const tailwindScript = needsTailwind ? getTailwindScriptHtml(useSafeMode) : '';
146
+
147
+ return `
148
+ <!DOCTYPE html>
149
+ <html lang="ja">
150
+ <head>
151
+ <meta charset="UTF-8">
152
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
153
+ ${tailwindScript}
154
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
155
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
156
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
157
+ <!-- スライドの欧文(font-en)。読み込まないと游ゴシックで代替され、
158
+ 数字・フッターの幅がビューアと10px以上ずれる(検証で実測) -->
159
+ <link href="https://fonts.googleapis.com/css2?family=Jost:wght@300;400;500;600;700&display=swap" rel="stylesheet">
160
+ <style>
161
+ * { box-sizing: border-box; }
162
+ html, body {
163
+ margin: 0;
164
+ padding: 0;
165
+ width: 100%;
166
+ height: 100%;
167
+ overflow: hidden;
168
+ background-color: ${CANVAS_BG_COLOR};
169
+ /* iframe内のエディタUI(パンくず・ラベル等)用。
170
+ スライド本体は #artboard 内のルートが font-sans(游ゴシック)を持つので影響しない */
171
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Noto Sans JP', Meiryo, sans-serif;
172
+ }
173
+
174
+ /* スクロール可能なキャンバスコンテナ */
175
+ #canvas-container {
176
+ position: absolute;
177
+ inset: 0;
178
+ overflow: auto;
179
+ background-color: ${CANVAS_BG_COLOR};
180
+ }
181
+
182
+ /* パディング用ラッパー(スクロール領域確保) */
183
+ #canvas-scroll-area {
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ /* サイズはJSで動的に設定 */
188
+ }
189
+
190
+ /* ズーム/パン適用対象 */
191
+ #artboard-wrapper {
192
+ flex-shrink: 0;
193
+ transform-origin: center center;
194
+ /* transform は JS で設定 */
195
+ /* 読み込み直後は非表示。倍率適用前の等倍(巨大)な一瞬を見せない。
196
+ EditorCanvas がロード時に倍率を当ててから可視化する */
197
+ visibility: hidden;
198
+ }
199
+
200
+ /* アートボード(コンテンツ領域) */
201
+ #artboard {
202
+ width: ${artboardWidth}px;
203
+ ${artboardHeightStyle}: ${artboardHeight}px;
204
+ background-color: white;
205
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
206
+ position: relative;
207
+ overflow: ${editorMode === 'webpage' ? 'visible' : 'hidden'};
208
+ }
209
+
210
+ /* ズーム中のスムーズな遷移を無効化(操作のレスポンス向上) */
211
+ #artboard-wrapper.zooming {
212
+ transition: none !important;
213
+ }
214
+
215
+ /* パンモード時のカーソル */
216
+ body.pan-mode {
217
+ cursor: grab !important;
218
+ }
219
+ body.pan-mode:active,
220
+ body.panning {
221
+ cursor: grabbing !important;
222
+ }
223
+ body.pan-mode #artboard,
224
+ body.pan-mode [data-editable="true"] {
225
+ pointer-events: none !important;
226
+ }
227
+ body.panning #artboard,
228
+ body.panning [data-editable="true"] {
229
+ pointer-events: none !important;
230
+ }
231
+ </style>
232
+ </head>
233
+ <body tabindex="-1">
234
+ <div id="canvas-container">
235
+ <div id="canvas-scroll-area">
236
+ <div id="artboard-wrapper">
237
+ <div id="artboard">
238
+ ${content}
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ </body>
244
+ </html>`;
245
+ }
246
+
247
+ /**
248
+ * クリーンなHTMLを取得(保存用)
249
+ * エディタ用の属性やクラスを削除
250
+ * 新しいキャンバス構造から#artboard内のコンテンツを抽出
251
+ * viewport単位(vw, vh等)を復元
252
+ */
253
+ export function getCleanHtml(doc: Document): string {
254
+ // 新しい構造: #artboard からコンテンツを取得
255
+ const artboard = doc.getElementById('artboard');
256
+ const sourceElement = artboard || doc.body;
257
+
258
+ // viewport単位を復元したHTMLを取得
259
+ // prepareHtmlForSaveは内部でcloneを作成し、data-original-*から元の値を復元する
260
+ const viewportRestoredHtml = prepareHtmlForSave(sourceElement);
261
+
262
+ // 復元されたHTMLを再度パースしてクリーンアップ
263
+ const tempContainer = doc.createElement('div');
264
+ tempContainer.innerHTML = viewportRestoredHtml;
265
+
266
+ // 選択ボックスを削除
267
+ tempContainer.querySelectorAll('.selection-box').forEach(el => el.remove());
268
+
269
+ // マーキー選択ボックスを削除
270
+ tempContainer.querySelectorAll('.marquee-selection-box').forEach(el => el.remove());
271
+
272
+ // コメントの吹き出しレイヤー(表示専用)を削除
273
+ tempContainer.querySelectorAll('.gg-comment-layer').forEach(el => el.remove());
274
+ // Alt計測の赤い線(表示専用)も保存しない
275
+ tempContainer.querySelectorAll('#gg-measure-layer').forEach(el => el.remove());
276
+ // ドラッグ中の整列ガイド(表示専用)も保存しない
277
+ tempContainer.querySelectorAll('#gg-smart-guides').forEach(el => el.remove());
278
+
279
+ // トリミングモードのUI(枠・ハンドル)。確定/中断で消えるが、保険で除去
280
+ tempContainer.querySelectorAll('.gg-crop-ui').forEach(el => el.remove());
281
+
282
+ // 編集用属性を削除
283
+ tempContainer.querySelectorAll('[data-editable]').forEach(el => {
284
+ el.removeAttribute('contenteditable');
285
+ el.removeAttribute('data-editable');
286
+ el.removeAttribute('data-element-id');
287
+ el.removeAttribute('data-original-content');
288
+ el.removeAttribute('data-shape-type');
289
+ el.removeAttribute('data-inline');
290
+ el.classList.remove(
291
+ 'selected', 'dragging', 'editing', 'rotating', 'panning',
292
+ 'hover-preview', 'marquee-hover', 'marquee-active', 'text-editable-hover', 'drag-ghost',
293
+ );
294
+ if (!el.getAttribute('class')) el.removeAttribute('class');
295
+ });
296
+
297
+ // data-original-* 属性は prepareHtmlForSave で既に削除されているが、
298
+ // 念のため残っていれば削除
299
+ tempContainer.querySelectorAll('*').forEach(el => {
300
+ const attrs = Array.from(el.attributes);
301
+ attrs.forEach(attr => {
302
+ if (attr.name.startsWith('data-original-')) {
303
+ el.removeAttribute(attr.name);
304
+ }
305
+ });
306
+ });
307
+
308
+ // 触っていない要素の絶対位置変換を巻き戻し、触った要素に data-gg-dirty を付ける。
309
+ // これで保存HTMLは「原本のレイアウト + 実際の編集」だけになり、
310
+ // 原本TSXへの決定的な書き戻し(scripts/slide-writeback.mjs)が成立する
311
+ classifySaveAndRevert(tempContainer);
312
+
313
+ return tempContainer.innerHTML;
314
+ }
315
+
316
+ /**
317
+ * HTMLコンテンツから編集可能要素にIDを付与
318
+ */
319
+ export function addElementIds(html: string): string {
320
+ const parser = new DOMParser();
321
+ const doc = parser.parseFromString(html, 'text/html');
322
+
323
+ let idCounter = 0;
324
+ doc.querySelectorAll('[data-editable="true"]').forEach(el => {
325
+ if (!el.getAttribute('data-element-id')) {
326
+ el.setAttribute('data-element-id', `el-${Date.now()}-${idCounter++}`);
327
+ }
328
+ });
329
+
330
+ return doc.body.innerHTML;
331
+ }
332
+
333
+ /**
334
+ * インポートされたHTMLに編集可能属性とIDを付与
335
+ * 直接の子要素にdata-editable="true"とdata-element-idを追加
336
+ * @param container - 親コンテナ要素(通常は#artboard)
337
+ */
338
+ /**
339
+ * [移植時の追加] スライドのキャンバス本体かどうか。
340
+ * `#artboard` の直下にあり、ほぼ同じ大きさを占める器を指す。
341
+ * ここは「紙」であって編集対象の要素ではないので、選択・ドラッグさせない。
342
+ */
343
+ function isSlideCanvas(el: HTMLElement, parent: HTMLElement): boolean {
344
+ if (parent.id !== 'artboard') return false;
345
+ if (el.parentElement !== parent) return false;
346
+ return el.offsetWidth >= parent.clientWidth - 2 && el.offsetHeight >= parent.clientHeight - 2;
347
+ }
348
+
349
+ export function makeChildrenEditable(container: HTMLElement): number {
350
+ let count = 0;
351
+ const timestamp = Date.now();
352
+
353
+ // 直接の子要素をチェック
354
+ Array.from(container.children).forEach((child, index) => {
355
+ const el = child as HTMLElement;
356
+
357
+ // スキップする要素
358
+ if (
359
+ el.tagName === 'SCRIPT' ||
360
+ el.tagName === 'STYLE' ||
361
+ el.tagName === 'LINK' ||
362
+ el.classList?.contains('selection-box') ||
363
+ el.classList?.contains('marquee-selection-box') ||
364
+ // [移植時の修正] スライドのキャンバス自体(#artboard と同じ大きさの直下の器)は
365
+ // 編集対象にしない。これを選べてしまうと、掴んだ瞬間にスライドごと動いて
366
+ // 中身が版面の外(overflow:hidden)へ出るため、要素が消えたように見える
367
+ isSlideCanvas(el, container)
368
+ ) {
369
+ return;
370
+ }
371
+
372
+ // data-editableがない場合は追加
373
+ if (!el.hasAttribute('data-editable')) {
374
+ el.setAttribute('data-editable', 'true');
375
+ }
376
+
377
+ // data-element-idがない場合は追加
378
+ if (!el.hasAttribute('data-element-id')) {
379
+ el.setAttribute('data-element-id', `el-${timestamp}-${index}`);
380
+ }
381
+
382
+ count++;
383
+
384
+ // 子孫要素も再帰的に処理(ネストされた編集可能要素のため)
385
+ count += makeDescendantsEditable(el, timestamp, index * 1000);
386
+ });
387
+
388
+ return count;
389
+ }
390
+
391
+ /**
392
+ * 子孫要素に編集可能属性を付与(再帰処理)
393
+ * 特定の条件を満たす要素のみを編集可能にする
394
+ */
395
+ function makeDescendantsEditable(parent: HTMLElement, timestamp: number, baseIndex: number): number {
396
+ let count = 0;
397
+ let localIndex = 0;
398
+
399
+ // 編集可能にすべき要素のタグ名
400
+ const EDITABLE_TAGS = new Set([
401
+ 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV', 'MAIN', 'ASIDE',
402
+ 'FIGURE', 'FIGCAPTION', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
403
+ 'UL', 'OL', 'LI', 'IMG', 'BUTTON', 'A', 'FORM', 'INPUT', 'TEXTAREA',
404
+ 'TABLE', 'THEAD', 'TBODY', 'TR', 'TD', 'TH', 'SPAN', 'LABEL',
405
+ 'VIDEO', 'AUDIO', 'CANVAS', 'SVG', 'BLOCKQUOTE', 'PRE', 'CODE',
406
+ ]);
407
+
408
+ Array.from(parent.children).forEach((child) => {
409
+ const el = child as HTMLElement;
410
+
411
+ // スキップする要素
412
+ if (
413
+ el.tagName === 'SCRIPT' ||
414
+ el.tagName === 'STYLE' ||
415
+ el.tagName === 'LINK' ||
416
+ el.classList?.contains('selection-box') ||
417
+ el.classList?.contains('marquee-selection-box') ||
418
+ // [移植時の修正] スライドのキャンバス自体(#artboard と同じ大きさの直下の器)は
419
+ // 編集対象にしない。これを選べてしまうと、掴んだ瞬間にスライドごと動いて
420
+ // 中身が版面の外(overflow:hidden)へ出るため、要素が消えたように見える
421
+ isSlideCanvas(el, parent)
422
+ ) {
423
+ return;
424
+ }
425
+
426
+ // 編集可能なタグかチェック
427
+ if (EDITABLE_TAGS.has(el.tagName)) {
428
+ // 既に属性がある場合はスキップ
429
+ if (!el.hasAttribute('data-editable')) {
430
+ el.setAttribute('data-editable', 'true');
431
+ }
432
+
433
+ if (!el.hasAttribute('data-element-id')) {
434
+ el.setAttribute('data-element-id', `el-${timestamp}-${baseIndex + localIndex}`);
435
+ localIndex++;
436
+ }
437
+
438
+ count++;
439
+ }
440
+
441
+ // 再帰的に子要素を処理
442
+ count += makeDescendantsEditable(el, timestamp, baseIndex + localIndex * 100);
443
+ });
444
+
445
+ return count;
446
+ }
447
+
448
+ /**
449
+ * [移植時の追加] ホストアプリの Tailwind v4 デザイントークンを iframe へ引き継ぐ。
450
+ * :root の CSS カスタムプロパティ(--color-* / --font-*)を computedStyle から読み取り、
451
+ * iframe 内 Tailwind Browser の @theme に流し込む。これにより
452
+ * bg-gg-green のようなプロジェクト固有クラスがエディタ内でも解決される。
453
+ * (Tailwind v4 は @layer 内にトークンを出力するため styleSheets 走査では取得できない)
454
+ */
455
+ function collectHostThemeTokens(): string {
456
+ if (typeof window === 'undefined' || typeof document === 'undefined') return '';
457
+ try {
458
+ const cs = getComputedStyle(document.documentElement);
459
+ const lines: string[] = [];
460
+ for (const prop of Array.from(cs)) {
461
+ if (!prop.startsWith('--color-') && !prop.startsWith('--font-')) continue;
462
+ const value = cs.getPropertyValue(prop).trim();
463
+ if (!value) continue;
464
+ lines.push(` ${prop}: ${value};`);
465
+ }
466
+ return lines.join('\n');
467
+ } catch {
468
+ return '';
469
+ }
470
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * エディタユーティリティのエクスポート
3
+ */
4
+
5
+ export * from './dom-utils';
6
+ export * from './style-utils';
7
+ export * from './override-detection';
8
+ export * from './override-apply';
9
+ export * from './component-renderer';
10
+ export * from './component-sync';
11
+ export * from './variant-resolver';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * ペン・鉛筆・蛍光ペンの「インク」設定。
3
+ *
4
+ * PowerPointの描画タブのペンギャラリー相当で、PPT風UIとFigma風UIの
5
+ * 両方から同じ設定を共有する。描画エンジン(useDrawingMode)は
6
+ * ストローク生成時にこのモジュール変数を参照するだけなので、
7
+ * どちらのUIから切り替えても次のストロークから反映される。
8
+ */
9
+
10
+ export type InkPreset = {
11
+ id: string;
12
+ label: string;
13
+ color: string;
14
+ width: number;
15
+ opacity: number;
16
+ /** round=ペン/鉛筆, butt=蛍光ペン(マーカーの平筆感) */
17
+ cap: 'round' | 'butt';
18
+ /** エンジン側で使うツール(penはベジェ寄り、pencilはフリーハンド) */
19
+ tool: 'pen' | 'pencil';
20
+ };
21
+
22
+ export const PEN_PRESETS: InkPreset[] = [
23
+ { id: 'pen-black', label: 'ペン(黒)', color: '#1a1a1a', width: 3, opacity: 1, cap: 'round', tool: 'pen' },
24
+ { id: 'pen-red', label: 'ペン(赤)', color: '#E03131', width: 3, opacity: 1, cap: 'round', tool: 'pen' },
25
+ { id: 'pen-blue', label: 'ペン(青)', color: '#1971C2', width: 3, opacity: 1, cap: 'round', tool: 'pen' },
26
+ { id: 'pencil-gray', label: '鉛筆', color: '#495057', width: 2, opacity: 0.9, cap: 'round', tool: 'pencil' },
27
+ { id: 'hl-yellow', label: '蛍光ペン(黄)', color: '#FFD43B', width: 18, opacity: 0.45, cap: 'butt', tool: 'pencil' },
28
+ { id: 'hl-green', label: '蛍光ペン(緑)', color: '#69DB7C', width: 18, opacity: 0.45, cap: 'butt', tool: 'pencil' },
29
+ ];
30
+
31
+ export const inkStyle: {
32
+ presetId: string;
33
+ color: string;
34
+ width: number;
35
+ opacity: number;
36
+ cap: 'round' | 'butt';
37
+ } = {
38
+ presetId: 'pen-black',
39
+ color: '#1a1a1a',
40
+ width: 3,
41
+ opacity: 1,
42
+ cap: 'round',
43
+ };
44
+
45
+ export function setInkPreset(p: InkPreset): void {
46
+ inkStyle.presetId = p.id;
47
+ inkStyle.color = p.color;
48
+ inkStyle.width = p.width;
49
+ inkStyle.opacity = p.opacity;
50
+ inkStyle.cap = p.cap;
51
+ }
52
+
53
+ /** 任意色だけ差し替える(プリセットの太さは維持) */
54
+ export function setInkColor(color: string): void {
55
+ inkStyle.presetId = 'custom';
56
+ inkStyle.color = color;
57
+ }