@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,244 @@
1
+ /**
2
+ * paste-sanitizer.ts
3
+ *
4
+ * DOMPurify configuration for sanitizing pasted HTML content.
5
+ * Key design: Preserve inline styles (Chrome automatically converts computed styles to inline)
6
+ * while removing dangerous content like scripts and event handlers.
7
+ */
8
+
9
+ import DOMPurify, { type Config as DOMPurifyConfig } from 'dompurify';
10
+
11
+ /**
12
+ * DOMPurify configuration optimized for rich paste operations.
13
+ * - Allows broad range of HTML tags for content preservation
14
+ * - Preserves style and class attributes (critical for Chrome's inline style preservation)
15
+ * - Blocks scripts, iframes, forms, and event handlers for security
16
+ */
17
+ const PASTE_SANITIZE_CONFIG: DOMPurifyConfig = {
18
+ // Allowed tags - comprehensive list for content preservation
19
+ ALLOWED_TAGS: [
20
+ // Structure
21
+ 'div', 'span', 'section', 'article', 'header', 'footer', 'nav', 'aside', 'main',
22
+ // Text
23
+ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'br', 'hr',
24
+ // Formatting
25
+ 'strong', 'b', 'em', 'i', 'u', 's', 'strike', 'del', 'ins', 'sub', 'sup',
26
+ 'mark', 'small', 'abbr', 'cite', 'code', 'pre', 'kbd', 'samp', 'var',
27
+ // Lists
28
+ 'ul', 'ol', 'li', 'dl', 'dt', 'dd',
29
+ // Tables
30
+ 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'colgroup', 'col', 'caption',
31
+ // Media
32
+ 'img', 'figure', 'figcaption', 'picture', 'source',
33
+ // Links
34
+ 'a',
35
+ // Quotes
36
+ 'blockquote', 'q',
37
+ // SVG (for SVG paste support)
38
+ 'svg', 'path', 'circle', 'rect', 'ellipse', 'line', 'polyline', 'polygon',
39
+ 'text', 'tspan', 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask',
40
+ 'linearGradient', 'radialGradient', 'stop', 'pattern', 'image',
41
+ 'foreignObject', 'title', 'desc',
42
+ ],
43
+
44
+ // Allowed attributes - preserve styling and structure
45
+ ALLOWED_ATTR: [
46
+ // Core
47
+ 'id', 'class', 'style', 'title', 'lang', 'dir',
48
+ // Links
49
+ 'href', 'target', 'rel',
50
+ // Media
51
+ 'src', 'alt', 'width', 'height', 'loading',
52
+ // Tables
53
+ 'colspan', 'rowspan', 'scope', 'headers',
54
+ // Data attributes (for our editor)
55
+ 'data-editable', 'data-element-id',
56
+ // SVG attributes
57
+ 'viewBox', 'xmlns', 'fill', 'stroke', 'stroke-width', 'stroke-linecap',
58
+ 'stroke-linejoin', 'stroke-dasharray', 'stroke-dashoffset', 'opacity',
59
+ 'd', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'x1', 'y1', 'x2', 'y2',
60
+ 'points', 'transform', 'preserveAspectRatio', 'xlink:href',
61
+ 'font-family', 'font-size', 'font-weight', 'text-anchor', 'dominant-baseline',
62
+ 'clip-path', 'mask', 'filter', 'marker-start', 'marker-mid', 'marker-end',
63
+ 'gradientUnits', 'gradientTransform', 'spreadMethod', 'offset', 'stop-color', 'stop-opacity',
64
+ 'patternUnits', 'patternContentUnits', 'patternTransform',
65
+ ],
66
+
67
+ // Forbidden tags - security critical
68
+ FORBID_TAGS: [
69
+ 'script', 'iframe', 'object', 'embed', 'applet',
70
+ 'form', 'input', 'button', 'select', 'textarea', 'label',
71
+ 'meta', 'link', 'base', 'style', 'noscript',
72
+ ],
73
+
74
+ // Forbidden attributes - prevent XSS
75
+ FORBID_ATTR: [
76
+ // Event handlers
77
+ 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove',
78
+ 'onmouseout', 'onmouseenter', 'onmouseleave', 'onkeydown', 'onkeypress', 'onkeyup',
79
+ 'onfocus', 'onblur', 'onchange', 'oninput', 'onsubmit', 'onreset',
80
+ 'onload', 'onerror', 'onabort', 'onscroll', 'onresize',
81
+ 'oncontextmenu', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave',
82
+ 'ondragover', 'ondragstart', 'ondrop', 'oncopy', 'oncut', 'onpaste',
83
+ 'onanimationstart', 'onanimationend', 'onanimationiteration',
84
+ 'ontransitionend', 'onwheel', 'ontouchstart', 'ontouchend', 'ontouchmove',
85
+ // Form related
86
+ 'action', 'method', 'formaction', 'formmethod',
87
+ // Dangerous
88
+ 'srcdoc', 'sandbox', 'allow',
89
+ ],
90
+
91
+ // Allow data attributes
92
+ ALLOW_DATA_ATTR: true,
93
+
94
+ // Allow external protocol links (but not javascript:)
95
+ ALLOW_UNKNOWN_PROTOCOLS: false,
96
+
97
+ // Keep document structure
98
+ WHOLE_DOCUMENT: false,
99
+
100
+ // Return DOM instead of string for better manipulation
101
+ RETURN_DOM: false,
102
+ RETURN_DOM_FRAGMENT: false,
103
+
104
+ // Sanitize shadow DOM
105
+ SANITIZE_DOM: true,
106
+
107
+ // Keep aria attributes for accessibility
108
+ KEEP_CONTENT: true,
109
+
110
+ // Force all URLs to be safe
111
+ SAFE_FOR_TEMPLATES: false,
112
+ };
113
+
114
+ /**
115
+ * Sanitize pasted HTML content while preserving inline styles.
116
+ *
117
+ * @param html - Raw HTML string from clipboard
118
+ * @returns Sanitized HTML string with styles preserved
119
+ */
120
+ export function sanitizePastedHtml(html: string): string {
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ return DOMPurify.sanitize(html, PASTE_SANITIZE_CONFIG as any) as unknown as string;
123
+ }
124
+
125
+ /**
126
+ * Check if HTML contains potentially dangerous content before sanitization.
127
+ * Useful for logging/debugging suspicious paste operations.
128
+ *
129
+ * @param html - Raw HTML string to check
130
+ * @returns Object indicating what dangerous content was found
131
+ */
132
+ export function detectDangerousContent(html: string): {
133
+ hasScript: boolean;
134
+ hasEventHandlers: boolean;
135
+ hasIframe: boolean;
136
+ hasForm: boolean;
137
+ dangerousPatterns: string[];
138
+ } {
139
+ const dangerousPatterns: string[] = [];
140
+
141
+ const hasScript = /<script\b/i.test(html);
142
+ if (hasScript) dangerousPatterns.push('script tag');
143
+
144
+ const hasEventHandlers = /\bon\w+\s*=/i.test(html);
145
+ if (hasEventHandlers) dangerousPatterns.push('event handlers');
146
+
147
+ const hasIframe = /<iframe\b/i.test(html);
148
+ if (hasIframe) dangerousPatterns.push('iframe');
149
+
150
+ const hasForm = /<form\b/i.test(html);
151
+ if (hasForm) dangerousPatterns.push('form');
152
+
153
+ // Check for javascript: protocol
154
+ if (/javascript:/i.test(html)) {
155
+ dangerousPatterns.push('javascript: protocol');
156
+ }
157
+
158
+ // Check for data: URIs in src (can be dangerous)
159
+ if (/src\s*=\s*["']?\s*data:/i.test(html)) {
160
+ dangerousPatterns.push('data: URI in src');
161
+ }
162
+
163
+ return {
164
+ hasScript,
165
+ hasEventHandlers,
166
+ hasIframe,
167
+ hasForm,
168
+ dangerousPatterns,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Clean Microsoft Office specific markup while preserving visual styles.
174
+ * Office applications add proprietary tags and namespaces that should be removed
175
+ * while keeping the visual formatting.
176
+ *
177
+ * @param html - HTML string potentially containing Office markup
178
+ * @returns Cleaned HTML string
179
+ */
180
+ export function cleanOfficeMarkup(html: string): string {
181
+ let cleaned = html;
182
+
183
+ // Remove Word-specific tags
184
+ cleaned = cleaned.replace(/<o:p[^>]*>[\s\S]*?<\/o:p>/gi, '');
185
+ cleaned = cleaned.replace(/<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
186
+ cleaned = cleaned.replace(/<m:[^>]*>[\s\S]*?<\/m:[^>]*>/gi, '');
187
+
188
+ // Remove XML namespaces from tags but keep the tags
189
+ cleaned = cleaned.replace(/<(\/?)\w+:/gi, '<$1');
190
+
191
+ // Remove conditional comments (IE-specific)
192
+ cleaned = cleaned.replace(/<!--\[if[^\]]*\]>[\s\S]*?<!\[endif\]-->/gi, '');
193
+ cleaned = cleaned.replace(/<!--\[if[^\]]*\]>[\s\S]*?-->/gi, '');
194
+
195
+ // Remove XML declarations
196
+ cleaned = cleaned.replace(/<\?xml[^>]*\?>/gi, '');
197
+
198
+ // Remove meta tags that might have been included
199
+ cleaned = cleaned.replace(/<meta[^>]*>/gi, '');
200
+
201
+ // Remove style tags (we rely on inline styles)
202
+ cleaned = cleaned.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
203
+
204
+ // Clean up mso-* styles (Microsoft Office specific) that cause issues
205
+ // but preserve other inline styles
206
+ cleaned = cleaned.replace(/mso-[^;:"']+:[^;:"']+;?/gi, '');
207
+
208
+ // Remove class attributes that reference Office stylesheets
209
+ cleaned = cleaned.replace(/class="?Mso[^"'\s>]+"?/gi, '');
210
+
211
+ // Clean up empty style attributes
212
+ cleaned = cleaned.replace(/style=["']\s*["']/gi, '');
213
+
214
+ // Clean up multiple spaces
215
+ cleaned = cleaned.replace(/\s+/g, ' ');
216
+
217
+ return cleaned;
218
+ }
219
+
220
+ /**
221
+ * Clean Excel-specific markup while preserving table structure and styles.
222
+ *
223
+ * @param html - HTML string from Excel clipboard
224
+ * @returns Cleaned HTML string with table structure preserved
225
+ */
226
+ export function cleanExcelMarkup(html: string): string {
227
+ let cleaned = html;
228
+
229
+ // Apply general Office cleanup
230
+ cleaned = cleanOfficeMarkup(cleaned);
231
+
232
+ // Remove Excel-specific attributes
233
+ cleaned = cleaned.replace(/x:num="[^"]*"/gi, '');
234
+ cleaned = cleaned.replace(/x:str="[^"]*"/gi, '');
235
+
236
+ // Clean up Excel's width/height inline styles (often too specific)
237
+ // But preserve background colors, fonts, etc.
238
+ cleaned = cleaned.replace(/width:\s*[\d.]+pt;?/gi, '');
239
+ cleaned = cleaned.replace(/height:\s*[\d.]+pt;?/gi, '');
240
+
241
+ return cleaned;
242
+ }
243
+
244
+ export { PASTE_SANITIZE_CONFIG };
@@ -0,0 +1,188 @@
1
+ /**
2
+ * オートレイアウトの「明示的なON」と、そのための子のフロー化。
3
+ *
4
+ * 【なぜ明示的なONにするか】
5
+ * テンプレート由来の display:flex はスライド中に山ほどあり、それを全部
6
+ * 「並べ替えの器」として扱うと、既定のドラッグ(絶対配置化して自由移動)が
7
+ * 勝手に変わってしまう。そこで **右パネルでレイアウトをONにしたコンテナだけ** を
8
+ * 目印クラス gg-autolayout で区別し、挙動を変えるのはその中だけに閉じる。
9
+ *
10
+ * 目印を class にしているのは、書き戻しエンジンを触らずに原本TSXへ乗せるため。
11
+ * data-gg-* は保存時のサニタイズで落ちるので使えない。
12
+ *
13
+ * 【ONにした瞬間に何をするか】(Figmaの「オートレイアウトを追加」と同じ)
14
+ * エディタは開いた時点で全要素を絶対配置へ倒す(EditorCanvas の一括変換)。
15
+ * 絶対配置の子はフレックスの並びから外れるため、そのままでは gap も方向も
16
+ * 効かない(実測: gap 6→60px で子の位置は 278/285/292 のまま不動。
17
+ * position を static に戻した瞬間 278/312/347 になった)。
18
+ * そこでONの瞬間に、**今見えている位置の順**で子を並べ直し、
19
+ * position/left/top を外して流し込みへ戻す。サイズは維持する。
20
+ */
21
+
22
+ /** オートレイアウトを明示的にONにした印。class なので原本TSXまで届く */
23
+ export const AUTOLAYOUT_CLASS = 'gg-autolayout';
24
+
25
+ /** この要素は明示的にオートレイアウトONか */
26
+ export function isAutoLayoutContainer(element: HTMLElement): boolean {
27
+ return element.classList.contains(AUTOLAYOUT_CLASS);
28
+ }
29
+
30
+ /** 一括変換が書き込む幾何プロパティ。これらだけを取り除く */
31
+ const CONVERTED_PROPS = [
32
+ 'position',
33
+ 'left',
34
+ 'top',
35
+ 'right',
36
+ 'bottom',
37
+ 'width',
38
+ 'height',
39
+ 'margin',
40
+ 'flex',
41
+ ] as const;
42
+
43
+ /** 退避された変換前スタイルの中で、その要素が元々持っていた値を引く */
44
+ function prestyleValue(pres: string, prop: string): string | null {
45
+ // data-gg-prestyle は素の style 文字列。'__none__' は「元は style 属性なし」
46
+ if (!pres || pres === '__none__') return null;
47
+ const re = new RegExp(`(?:^|;)\\s*${prop.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase())}\\s*:\\s*([^;]+)`, 'i');
48
+ const m = pres.match(re);
49
+ return m ? m[1].trim() : null;
50
+ }
51
+
52
+ /**
53
+ * 要素を流し込みへ戻す。戻したら true
54
+ *
55
+ * 変換が書いた幾何プロパティを消し、元の style に同じ指定があればそれを書き戻す。
56
+ * 「元から絶対配置だった要素」(退避値に position:absolute がある)は
57
+ * ユーザーの意図なので触らない。
58
+ */
59
+ export function restoreElementToFlow(element: HTMLElement): boolean {
60
+ const pres = element.getAttribute('data-gg-prestyle');
61
+
62
+ // 退避が無い = 一括変換を経ていない。勝手に位置指定を剥がすと
63
+ // ユーザーが自分で置いた要素を動かしてしまうので何もしない
64
+ if (pres === null) return false;
65
+
66
+ const originalPosition = prestyleValue(pres, 'position');
67
+ if (originalPosition === 'absolute' || originalPosition === 'fixed') return false;
68
+
69
+ for (const prop of CONVERTED_PROPS) {
70
+ const original = prestyleValue(pres, prop);
71
+ if (original) {
72
+ element.style.setProperty(
73
+ prop.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()),
74
+ original,
75
+ );
76
+ } else {
77
+ element.style.removeProperty(prop.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()));
78
+ }
79
+ }
80
+ return true;
81
+ }
82
+
83
+ /**
84
+ * コンテナの直接の子を流し込みへ戻す。戻した数を返す
85
+ *
86
+ * 対象は**直接の子だけ**。孫まで戻すと、子の内側で絶対配置に頼っている
87
+ * 作り込み(重ね置きの図など)まで崩れる。フレックスの並びに要るのは
88
+ * 直接の子が流れに乗っていることだけ。
89
+ */
90
+ export function restoreChildrenToFlow(container: HTMLElement): number {
91
+ let n = 0;
92
+ for (const node of Array.from(container.children)) {
93
+ // [注意] `node instanceof HTMLElement` は使えない(iframeとは別realmのため常にfalse)
94
+ const el = node as HTMLElement;
95
+ if (typeof el.matches !== 'function') continue;
96
+ if (el.matches('.selection-box,.marquee-selection-box,.resize-handle,.rotation-handle,.size-label,.element-breadcrumb')) {
97
+ continue;
98
+ }
99
+ if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE') continue;
100
+ if (restoreElementToFlow(el)) n++;
101
+ }
102
+ return n;
103
+ }
104
+
105
+ /**
106
+ * コンテナ自身の「サイズの固定」を解く
107
+ *
108
+ * 一括変換は width/height も焼き込む。縦積みに変えたのに高さが固定のままだと
109
+ * 中身がはみ出す(overflow)ため、元々サイズ指定が無かったなら外す。
110
+ * 位置(left/top)は動かさない — コンテナの居場所はユーザーが決めたものなので。
111
+ */
112
+ export function relaxContainerSize(container: HTMLElement): void {
113
+ const pres = container.getAttribute('data-gg-prestyle');
114
+ if (pres === null) return;
115
+ for (const prop of ['width', 'height'] as const) {
116
+ const original = prestyleValue(pres, prop);
117
+ if (original) container.style.setProperty(prop, original);
118
+ else container.style.removeProperty(prop);
119
+ }
120
+ }
121
+
122
+ /** 並べ替え・整列の対象にしない、エディタが描いている飾り */
123
+ const OVERLAY_SELECTOR =
124
+ '.selection-box,.marquee-selection-box,.resize-handle,.rotation-handle,' +
125
+ '.size-label,.element-breadcrumb,.gg-comment-layer,.gg-crop-ui,' +
126
+ '#gg-measure-layer,#gg-smart-guides,#gg-reorder-indicator';
127
+
128
+ /** 実体のある直接の子だけを取り出す */
129
+ function layoutChildren(container: HTMLElement): HTMLElement[] {
130
+ const out: HTMLElement[] = [];
131
+ for (const node of Array.from(container.children)) {
132
+ // [注意] `node instanceof HTMLElement` は使えない(iframeとは別realmのため常にfalse)
133
+ const el = node as HTMLElement;
134
+ if (typeof el.matches !== 'function') continue;
135
+ if (el.matches(OVERLAY_SELECTOR)) continue;
136
+ if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE') continue;
137
+ out.push(el);
138
+ }
139
+ return out;
140
+ }
141
+
142
+ /**
143
+ * オートレイアウトをONにする。
144
+ *
145
+ * 1. 目印クラスを付ける
146
+ * 2. 今見えている位置の順に子を並べ替える(row=左→右 / column=上→下)
147
+ * 3. 子の絶対配置を解いて流し込みへ戻す。**サイズは維持する**
148
+ *
149
+ * 並べ替えを先に決めてから position を外すのが肝。先に外すと要素が動いてしまい、
150
+ * 「見えていた順」が分からなくなる。
151
+ */
152
+ export function enableAutoLayout(
153
+ container: HTMLElement,
154
+ direction: 'row' | 'column',
155
+ ): number {
156
+ container.classList.add(AUTOLAYOUT_CLASS);
157
+
158
+ const children = layoutChildren(container);
159
+ if (children.length === 0) return 0;
160
+
161
+ // ① 動かす前に「見えている位置」と実寸を控える
162
+ const measured = children.map((el) => {
163
+ const r = el.getBoundingClientRect();
164
+ return { el, x: r.left, y: r.top, width: el.offsetWidth, height: el.offsetHeight };
165
+ });
166
+
167
+ // ② 視覚順に並べ替える。同じ座標なら元のDOM順を保つ(安定ソート)
168
+ const ordered = [...measured].sort((a, b) => (direction === 'column' ? a.y - b.y : a.x - b.x));
169
+
170
+ // ③ 絶対配置を解く。サイズは実寸で固定して、戻した瞬間に縮まないようにする
171
+ for (const m of ordered) {
172
+ for (const prop of ['position', 'left', 'top', 'right', 'bottom'] as const) {
173
+ m.el.style.removeProperty(prop);
174
+ }
175
+ if (!m.el.style.width && m.width > 0) m.el.style.width = `${m.width}px`;
176
+ if (!m.el.style.height && m.height > 0) m.el.style.height = `${m.height}px`;
177
+ }
178
+
179
+ // ④ DOM順を視覚順に合わせる(appendChild は既存ノードの移動として働く)
180
+ for (const m of ordered) container.appendChild(m.el);
181
+
182
+ return ordered.length;
183
+ }
184
+
185
+ /** オートレイアウトをOFFにする。印を外すだけで、子の位置は今の見た目のまま残す */
186
+ export function disableAutoLayout(container: HTMLElement): void {
187
+ container.classList.remove(AUTOLAYOUT_CLASS);
188
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * 図形カタログ(PowerPointの「図形」ギャラリー相当)。
3
+ *
4
+ * 図形は div + clip-path / border-radius で表現する。SVGにしないのは、
5
+ * - 「図形の書式」の塗り・枠線・影・透明度がそのまま効く
6
+ * - ダブルクリックで中に文字を入れられる(実機と同じ)
7
+ * - 書き戻しエンジンが普通のDOMとして原本TSXへ運べる
8
+ * ため。線・矢印だけは既存の描画ツール(SVG)に任せる。
9
+ *
10
+ * ギャラリーで図形を選ぶと `pendingShape` に入り、キャンバスを
11
+ * ドラッグすればその大きさで、クリックだけなら既定サイズで作られる。
12
+ */
13
+
14
+ export type ShapeDef = {
15
+ id: string;
16
+ label: string;
17
+ /** 塗りを切り抜く形。無指定は矩形 */
18
+ clip?: string;
19
+ /** 角丸(border-radius) */
20
+ radius?: string;
21
+ /** 追加のスタイル(円環の枠など) */
22
+ extra?: Record<string, string>;
23
+ /** クリックだけで置いたときの既定サイズ */
24
+ w: number;
25
+ h: number;
26
+ };
27
+
28
+ /** 正多角形(星形)のpolygonを作る */
29
+ function starPolygon(points: number, inner = 0.42): string {
30
+ const pts: string[] = [];
31
+ const step = Math.PI / points;
32
+ for (let i = 0; i < points * 2; i++) {
33
+ const r = i % 2 === 0 ? 0.5 : inner;
34
+ const a = -Math.PI / 2 + i * step;
35
+ const x = (0.5 + r * Math.cos(a)) * 100;
36
+ const y = (0.5 + r * Math.sin(a)) * 100;
37
+ pts.push(`${Math.round(x * 10) / 10}% ${Math.round(y * 10) / 10}%`);
38
+ }
39
+ return `polygon(${pts.join(', ')})`;
40
+ }
41
+
42
+ /** 正多角形(頂点が上) */
43
+ function regularPolygon(n: number): string {
44
+ const pts: string[] = [];
45
+ for (let i = 0; i < n; i++) {
46
+ const a = -Math.PI / 2 + (i * 2 * Math.PI) / n;
47
+ const x = (0.5 + 0.5 * Math.cos(a)) * 100;
48
+ const y = (0.5 + 0.5 * Math.sin(a)) * 100;
49
+ pts.push(`${Math.round(x * 10) / 10}% ${Math.round(y * 10) / 10}%`);
50
+ }
51
+ return `polygon(${pts.join(', ')})`;
52
+ }
53
+
54
+ export const SHAPE_GROUPS: { group: string; shapes: ShapeDef[] }[] = [
55
+ {
56
+ group: '四角形',
57
+ shapes: [
58
+ { id: 'rect', label: '長方形', w: 260, h: 160 },
59
+ { id: 'square', label: '正方形', w: 200, h: 200 },
60
+ { id: 'round-rect', label: '角丸四角形', radius: '20px', w: 260, h: 160 },
61
+ { id: 'round-rect-lg', label: '角丸(大)', radius: '48px', w: 260, h: 160 },
62
+ { id: 'pill', label: '角丸(全周)', radius: '9999px', w: 280, h: 110 },
63
+ { id: 'round-top', label: '上だけ角丸', radius: '28px 28px 0 0', w: 260, h: 160 },
64
+ { id: 'round-diag', label: '対角を角丸', radius: '32px 0 32px 0', w: 260, h: 160 },
65
+ { id: 'round-one', label: '1つだけ角丸', radius: '48px 0 0 0', w: 260, h: 160 },
66
+ { id: 'cut-corner', label: '角を切り取り', clip: 'polygon(0 0, 82% 0, 100% 26%, 100% 100%, 0 100%)', w: 260, h: 170 },
67
+ { id: 'frame', label: '額縁', extra: { backgroundColor: 'transparent', border: '18px solid var(--color-gg-green)' }, w: 260, h: 180 },
68
+ ],
69
+ },
70
+ {
71
+ group: '基本図形',
72
+ shapes: [
73
+ { id: 'ellipse', label: '楕円', radius: '50%', w: 220, h: 220 },
74
+ { id: 'triangle', label: '三角形', clip: 'polygon(50% 0%, 100% 100%, 0% 100%)', w: 220, h: 190 },
75
+ { id: 'right-triangle', label: '直角三角形', clip: 'polygon(0% 0%, 0% 100%, 100% 100%)', w: 220, h: 190 },
76
+ { id: 'parallelogram', label: '平行四辺形', clip: 'polygon(18% 0%, 100% 0%, 82% 100%, 0% 100%)', w: 280, h: 160 },
77
+ { id: 'trapezoid', label: '台形', clip: 'polygon(20% 0%, 80% 0%, 100% 100%, 0% 100%)', w: 260, h: 160 },
78
+ { id: 'diamond', label: 'ひし形', clip: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)', w: 220, h: 220 },
79
+ { id: 'pentagon', label: '五角形', clip: regularPolygon(5), w: 220, h: 210 },
80
+ { id: 'hexagon', label: '六角形', clip: 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)', w: 240, h: 208 },
81
+ { id: 'heptagon', label: '七角形', clip: regularPolygon(7), w: 220, h: 220 },
82
+ { id: 'octagon', label: '八角形', clip: regularPolygon(8), w: 220, h: 220 },
83
+ { id: 'decagon', label: '十角形', clip: regularPolygon(10), w: 220, h: 220 },
84
+ { id: 'dodecagon', label: '十二角形', clip: regularPolygon(12), w: 220, h: 220 },
85
+ { id: 'cross', label: '十字', clip: 'polygon(35% 0, 65% 0, 65% 35%, 100% 35%, 100% 65%, 65% 65%, 65% 100%, 35% 100%, 35% 65%, 0 65%, 0 35%, 35% 35%)', w: 200, h: 200 },
86
+ { id: 'l-shape', label: 'L字', clip: 'polygon(0 0, 38% 0, 38% 62%, 100% 62%, 100% 100%, 0 100%)', w: 220, h: 200 },
87
+ { id: 'chord', label: '半円', clip: 'polygon(0 100%, 0 50%, 3% 34%, 12% 19%, 25% 8%, 40% 1%, 50% 0, 60% 1%, 75% 8%, 88% 19%, 97% 34%, 100% 50%, 100% 100%)', w: 240, h: 130 },
88
+ { id: 'quarter', label: '四分円', clip: 'polygon(0 0, 100% 0, 100% 100%, 92% 66%, 74% 38%, 48% 16%, 0 0)', w: 220, h: 200 },
89
+ { id: 'donut', label: '円環', radius: '50%', extra: { backgroundColor: 'transparent', border: '32px solid var(--color-gg-green)' }, w: 220, h: 220 },
90
+ { id: 'ban', label: '禁止', radius: '50%', extra: { backgroundColor: 'transparent', border: '20px solid var(--color-gg-red)' }, w: 200, h: 200 },
91
+ { id: 'heart', label: 'ハート', clip: 'polygon(50% 100%, 12% 66%, 0 38%, 6% 17%, 24% 6%, 42% 10%, 50% 22%, 58% 10%, 76% 6%, 94% 17%, 100% 38%, 88% 66%)', w: 220, h: 200 },
92
+ { id: 'bolt', label: '稲妻', clip: 'polygon(58% 0, 22% 54%, 46% 54%, 34% 100%, 82% 40%, 54% 40%, 74% 0)', w: 150, h: 220 },
93
+ { id: 'moon', label: '月', clip: 'polygon(62% 0, 40% 8%, 24% 26%, 18% 50%, 24% 74%, 40% 92%, 62% 100%, 44% 84%, 36% 60%, 38% 36%, 48% 14%)', w: 170, h: 220 },
94
+ { id: 'sun', label: '太陽', clip: starPolygon(12, 0.34), w: 220, h: 220 },
95
+ { id: 'cloud', label: '雲', clip: 'polygon(22% 100%, 8% 92%, 2% 76%, 10% 60%, 8% 44%, 22% 32%, 38% 32%, 48% 18%, 66% 14%, 80% 26%, 84% 42%, 96% 52%, 98% 72%, 88% 88%, 72% 100%)', w: 260, h: 180 },
96
+ ],
97
+ },
98
+ {
99
+ group: 'ブロック矢印',
100
+ shapes: [
101
+ { id: 'arrow-right', label: '右矢印', clip: 'polygon(0% 30%, 62% 30%, 62% 6%, 100% 50%, 62% 94%, 62% 70%, 0% 70%)', w: 280, h: 140 },
102
+ { id: 'arrow-left', label: '左矢印', clip: 'polygon(100% 30%, 38% 30%, 38% 6%, 0% 50%, 38% 94%, 38% 70%, 100% 70%)', w: 280, h: 140 },
103
+ { id: 'arrow-up', label: '上矢印', clip: 'polygon(30% 100%, 30% 38%, 6% 38%, 50% 0%, 94% 38%, 70% 38%, 70% 100%)', w: 140, h: 280 },
104
+ { id: 'arrow-down', label: '下矢印', clip: 'polygon(30% 0%, 30% 62%, 6% 62%, 50% 100%, 94% 62%, 70% 62%, 70% 0%)', w: 140, h: 280 },
105
+ { id: 'arrow-lr', label: '左右矢印', clip: 'polygon(0 50%, 20% 12%, 20% 32%, 80% 32%, 80% 12%, 100% 50%, 80% 88%, 80% 68%, 20% 68%, 20% 88%)', w: 300, h: 140 },
106
+ { id: 'arrow-ud', label: '上下矢印', clip: 'polygon(50% 0, 88% 20%, 68% 20%, 68% 80%, 88% 80%, 50% 100%, 12% 80%, 32% 80%, 32% 20%, 12% 20%)', w: 140, h: 300 },
107
+ { id: 'arrow-quad', label: '十字矢印', clip: 'polygon(50% 0, 68% 18%, 58% 18%, 58% 42%, 82% 42%, 82% 32%, 100% 50%, 82% 68%, 82% 58%, 58% 58%, 58% 82%, 68% 82%, 50% 100%, 32% 82%, 42% 82%, 42% 58%, 18% 58%, 18% 68%, 0 50%, 18% 32%, 18% 42%, 42% 42%, 42% 18%, 32% 18%)', w: 220, h: 220 },
108
+ { id: 'chevron', label: 'シェブロン', clip: 'polygon(0% 0%, 78% 0%, 100% 50%, 78% 100%, 0% 100%, 22% 50%)', w: 260, h: 120 },
109
+ { id: 'pentagon-arrow', label: 'ホームプレート', clip: 'polygon(0% 0%, 78% 0%, 100% 50%, 78% 100%, 0% 100%)', w: 260, h: 120 },
110
+ { id: 'bent-arrow', label: '曲がり矢印', clip: 'polygon(0 62%, 0 90%, 62% 90%, 62% 100%, 100% 76%, 62% 52%, 62% 62%)', w: 260, h: 180 },
111
+ ],
112
+ },
113
+ {
114
+ group: 'フローチャート',
115
+ shapes: [
116
+ { id: 'fc-process', label: '処理', w: 260, h: 130 },
117
+ { id: 'fc-alt', label: '代替処理', radius: '18px', w: 260, h: 130 },
118
+ { id: 'fc-decision', label: '判断', clip: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)', w: 240, h: 170 },
119
+ { id: 'fc-terminator', label: '端子', radius: '9999px', w: 260, h: 110 },
120
+ { id: 'fc-data', label: 'データ', clip: 'polygon(18% 0%, 100% 0%, 82% 100%, 0% 100%)', w: 280, h: 130 },
121
+ { id: 'fc-prep', label: '準備', clip: 'polygon(18% 0, 82% 0, 100% 50%, 82% 100%, 18% 100%, 0 50%)', w: 280, h: 130 },
122
+ { id: 'fc-doc', label: '書類', clip: 'polygon(0 0, 100% 0, 100% 82%, 74% 96%, 50% 84%, 24% 96%, 0 84%)', w: 260, h: 150 },
123
+ { id: 'fc-manual', label: '手操作入力', clip: 'polygon(0 22%, 100% 0, 100% 100%, 0 100%)', w: 260, h: 140 },
124
+ { id: 'fc-db', label: 'データベース', clip: 'polygon(0 12%, 12% 3%, 50% 0, 88% 3%, 100% 12%, 100% 88%, 88% 97%, 50% 100%, 12% 97%, 0 88%)', w: 220, h: 200 },
125
+ ],
126
+ },
127
+ {
128
+ group: '星とリボン',
129
+ shapes: [
130
+ { id: 'star4', label: '4芒星', clip: starPolygon(4, 0.2), w: 200, h: 200 },
131
+ { id: 'star5', label: '5芒星', clip: starPolygon(5, 0.38), w: 220, h: 210 },
132
+ { id: 'star6', label: '6芒星', clip: starPolygon(6, 0.42), w: 220, h: 220 },
133
+ { id: 'star8', label: '8芒星', clip: starPolygon(8, 0.44), w: 220, h: 220 },
134
+ { id: 'star12', label: '12芒星', clip: starPolygon(12, 0.46), w: 220, h: 220 },
135
+ { id: 'burst', label: '爆発', clip: starPolygon(16, 0.5), w: 240, h: 240 },
136
+ { id: 'ribbon', label: 'リボン', clip: 'polygon(0 0, 100% 0, 100% 74%, 88% 62%, 76% 74%, 76% 100%, 50% 84%, 24% 100%, 24% 74%, 12% 62%, 0 74%)', w: 260, h: 180 },
137
+ { id: 'banner', label: '横断幕', clip: 'polygon(0 0, 100% 0, 88% 50%, 100% 100%, 0 100%, 12% 50%)', w: 300, h: 120 },
138
+ ],
139
+ },
140
+ {
141
+ group: '吹き出し',
142
+ shapes: [
143
+ { id: 'callout-rect', label: '四角の吹き出し', clip: 'polygon(0% 0%, 100% 0%, 100% 74%, 32% 74%, 14% 100%, 17% 74%, 0% 74%)', w: 280, h: 200 },
144
+ { id: 'callout-round', label: '角丸の吹き出し', radius: '24px', clip: 'polygon(0% 0%, 100% 0%, 100% 76%, 34% 76%, 16% 100%, 19% 76%, 0% 76%)', w: 280, h: 200 },
145
+ { id: 'callout-right', label: '右向き吹き出し', clip: 'polygon(0 0, 100% 0, 100% 100%, 22% 100%, 22% 76%, 0 76%)', w: 280, h: 190 },
146
+ { id: 'callout-oval', label: '楕円の吹き出し', radius: '50% 50% 50% 50% / 60% 60% 40% 40%', w: 280, h: 190 },
147
+ ],
148
+ },
149
+ ];
150
+
151
+ export const ALL_SHAPES: ShapeDef[] = SHAPE_GROUPS.flatMap((g) => g.shapes);
152
+
153
+ /**
154
+ * ギャラリーで選ばれた図形。キャンバスのドラッグ/クリックで使われる。
155
+ * 描画エンジン(useDrawingMode)とリボンの両方から読むためモジュール変数にしている。
156
+ */
157
+ export let pendingShape: ShapeDef = ALL_SHAPES[0];
158
+
159
+ export function setPendingShape(def: ShapeDef): void {
160
+ pendingShape = def;
161
+ }
162
+
163
+ /** 図形定義からCSSを組み立てる(サイズは呼び出し側で指定) */
164
+ export function shapeStyles(def: ShapeDef): Record<string, string> {
165
+ return {
166
+ backgroundColor: 'var(--color-gg-green)',
167
+ ...(def.clip ? { clipPath: def.clip } : {}),
168
+ ...(def.radius ? { borderRadius: def.radius } : {}),
169
+ ...(def.extra ?? {}),
170
+ };
171
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * [移植時の追加] iframe内の「スライド本体」を特定するユーティリティ。
3
+ *
4
+ * このプロジェクトのスライドは 1920×1080 のルート要素で、ズーム倍率のtransformが
5
+ * かかった状態でiframe内に置かれている。要素を追加するときに、その外側
6
+ * (body や #artboard ラッパー)へ入れてしまうと座標系がスケールの分だけずれ、
7
+ * - スライドの外に表示される
8
+ * - ドラッグすると 1/zoom 倍(例: 41%なら約2.4倍)動いて飛んでいく
9
+ * という不具合になる。挿入先は必ずこのルート要素に揃える。
10
+ */
11
+
12
+ /**
13
+ * スライドの内容が入るルート要素。
14
+ * エディタは `#artboard-wrapper`(ズームのtransformがかかる) > `#artboard`(内容) の
15
+ * 入れ子でキャンバスを作るので、内容側の `#artboard` を基準にする。
16
+ * 通常のWebページ編集などで見つからない場合は null。
17
+ */
18
+ export function findSlideRoot(doc: Document): HTMLElement | null {
19
+ const artboard = doc.getElementById('artboard');
20
+ if (artboard) return artboard;
21
+ const marked = doc.querySelector('[data-slide-root]');
22
+ if (marked instanceof HTMLElement) return marked;
23
+ return null;
24
+ }
25
+
26
+ /** 新しい要素の追加先 */
27
+ export function findInsertionParent(doc: Document): HTMLElement {
28
+ return findSlideRoot(doc) ?? doc.getElementById('slide-artboard') ?? doc.body;
29
+ }