@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,526 @@
1
+ /**
2
+ * Override Apply Utilities
3
+ *
4
+ * Functions to apply, reset, and manage overrides on component instances.
5
+ * This module handles the application of overrides to both ComponentElement trees
6
+ * and DOM elements.
7
+ */
8
+
9
+ import type {
10
+ ComponentElement,
11
+ ComponentOverride,
12
+ OverrideType,
13
+ ComponentInstance,
14
+ OverrideImageValue,
15
+ OverrideInstanceSwapValue,
16
+ } from '../../types/editor-components';
17
+ import { findElementById, findElementByPath } from './override-detection';
18
+
19
+ /**
20
+ * Deep clone a ComponentElement tree
21
+ */
22
+ export function cloneComponentElement(element: ComponentElement): ComponentElement {
23
+ return {
24
+ ...element,
25
+ attributes: { ...element.attributes },
26
+ styles: { ...element.styles },
27
+ overridable: { ...element.overridable },
28
+ children: element.children.map(cloneComponentElement),
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Apply a single override to a ComponentElement (mutates the element in place)
34
+ */
35
+ export function applyOverride(
36
+ element: ComponentElement,
37
+ override: ComponentOverride
38
+ ): void {
39
+ // Find the target element
40
+ let targetElement: ComponentElement | null = null;
41
+
42
+ if (override.targetElementId) {
43
+ targetElement = findElementById(element, override.targetElementId);
44
+ } else if (override.elementPath) {
45
+ targetElement = findElementByPath(element, override.elementPath);
46
+ }
47
+
48
+ if (!targetElement) {
49
+ console.warn(
50
+ `[applyOverride] Target element not found for override:`,
51
+ override.id
52
+ );
53
+ return;
54
+ }
55
+
56
+ // Apply the override based on type
57
+ switch (override.type) {
58
+ case 'text':
59
+ if (typeof override.value === 'string') {
60
+ targetElement.textContent = override.value;
61
+ }
62
+ break;
63
+
64
+ case 'fill':
65
+ if (typeof override.value === 'string') {
66
+ targetElement.styles.backgroundColor = override.value;
67
+ }
68
+ break;
69
+
70
+ case 'stroke':
71
+ if (typeof override.value === 'string') {
72
+ targetElement.styles.border = override.value;
73
+ }
74
+ break;
75
+
76
+ case 'visibility':
77
+ if (typeof override.value === 'boolean') {
78
+ targetElement.hidden = !override.value;
79
+ }
80
+ break;
81
+
82
+ case 'image':
83
+ if (isOverrideImageValue(override.value)) {
84
+ if (targetElement.tagName === 'img') {
85
+ targetElement.attributes.src = override.value.url;
86
+ if (override.value.alt) {
87
+ targetElement.attributes.alt = override.value.alt;
88
+ }
89
+ if (override.value.objectFit) {
90
+ targetElement.styles.objectFit = override.value.objectFit;
91
+ }
92
+ if (override.value.objectPosition) {
93
+ targetElement.styles.objectPosition = override.value.objectPosition;
94
+ }
95
+ } else {
96
+ // Apply as background image
97
+ targetElement.styles.backgroundImage = `url('${override.value.url}')`;
98
+ }
99
+ }
100
+ break;
101
+
102
+ case 'style':
103
+ if (typeof override.value === 'object' && !Array.isArray(override.value)) {
104
+ const styleOverrides = override.value as Record<string, string>;
105
+ Object.assign(targetElement.styles, styleOverrides);
106
+ }
107
+ break;
108
+
109
+ case 'attribute':
110
+ if (typeof override.value === 'object' && !Array.isArray(override.value)) {
111
+ const attrOverrides = override.value as Record<string, string>;
112
+ Object.assign(targetElement.attributes, attrOverrides);
113
+ }
114
+ break;
115
+
116
+ case 'children':
117
+ if (Array.isArray(override.value)) {
118
+ targetElement.children = override.value as ComponentElement[];
119
+ }
120
+ break;
121
+
122
+ case 'instanceSwap':
123
+ // Instance swap is handled at a higher level (requires loading the swapped component)
124
+ console.log('[applyOverride] Instance swap override detected:', override.value);
125
+ break;
126
+
127
+ default:
128
+ console.warn('[applyOverride] Unknown override type:', override.type);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Apply all overrides to a ComponentElement tree (returns a new tree)
134
+ */
135
+ export function applyOverrides(
136
+ element: ComponentElement,
137
+ overrides: ComponentOverride[]
138
+ ): ComponentElement {
139
+ // Create a deep clone to avoid mutating the original
140
+ const clonedElement = cloneComponentElement(element);
141
+
142
+ // Sort overrides by depth (process deeper elements first to avoid conflicts)
143
+ const sortedOverrides = [...overrides].sort((a, b) => {
144
+ const depthA = (a.elementPath?.split('.').length || 0);
145
+ const depthB = (b.elementPath?.split('.').length || 0);
146
+ return depthB - depthA;
147
+ });
148
+
149
+ // Apply each override
150
+ for (const override of sortedOverrides) {
151
+ applyOverride(clonedElement, override);
152
+ }
153
+
154
+ return clonedElement;
155
+ }
156
+
157
+ /**
158
+ * Apply a single override to a DOM element
159
+ */
160
+ export function applyOverrideToDOM(
161
+ domElement: HTMLElement,
162
+ override: ComponentOverride
163
+ ): void {
164
+ switch (override.type) {
165
+ case 'text':
166
+ if (typeof override.value === 'string') {
167
+ // Find and update only direct text nodes
168
+ const textNodes = Array.from(domElement.childNodes).filter(
169
+ (node) => node.nodeType === Node.TEXT_NODE
170
+ );
171
+
172
+ if (textNodes.length > 0) {
173
+ textNodes[0].textContent = override.value;
174
+ } else {
175
+ // If no text nodes exist, prepend one
176
+ const textNode = domElement.ownerDocument.createTextNode(override.value);
177
+ domElement.insertBefore(textNode, domElement.firstChild);
178
+ }
179
+ }
180
+ break;
181
+
182
+ case 'fill':
183
+ if (typeof override.value === 'string') {
184
+ domElement.style.backgroundColor = override.value;
185
+ }
186
+ break;
187
+
188
+ case 'stroke':
189
+ if (typeof override.value === 'string') {
190
+ if (override.value === 'none') {
191
+ domElement.style.border = 'none';
192
+ } else {
193
+ domElement.style.border = override.value;
194
+ }
195
+ }
196
+ break;
197
+
198
+ case 'visibility':
199
+ if (typeof override.value === 'boolean') {
200
+ domElement.style.display = override.value ? '' : 'none';
201
+ domElement.style.visibility = override.value ? '' : 'hidden';
202
+ }
203
+ break;
204
+
205
+ case 'image':
206
+ if (isOverrideImageValue(override.value)) {
207
+ if (domElement.tagName === 'IMG') {
208
+ (domElement as HTMLImageElement).src = override.value.url;
209
+ if (override.value.alt) {
210
+ (domElement as HTMLImageElement).alt = override.value.alt;
211
+ }
212
+ if (override.value.objectFit) {
213
+ domElement.style.objectFit = override.value.objectFit;
214
+ }
215
+ if (override.value.objectPosition) {
216
+ domElement.style.objectPosition = override.value.objectPosition;
217
+ }
218
+ } else {
219
+ domElement.style.backgroundImage = `url('${override.value.url}')`;
220
+ }
221
+ }
222
+ break;
223
+
224
+ case 'style':
225
+ if (typeof override.value === 'object' && !Array.isArray(override.value)) {
226
+ const styleOverrides = override.value as Record<string, string>;
227
+ for (const [key, value] of Object.entries(styleOverrides)) {
228
+ domElement.style.setProperty(key, value);
229
+ }
230
+ }
231
+ break;
232
+
233
+ case 'attribute':
234
+ if (typeof override.value === 'object' && !Array.isArray(override.value)) {
235
+ const attrOverrides = override.value as Record<string, string>;
236
+ for (const [key, value] of Object.entries(attrOverrides)) {
237
+ domElement.setAttribute(key, value);
238
+ }
239
+ }
240
+ break;
241
+
242
+ case 'children':
243
+ // Children replacement in DOM would require more complex handling
244
+ console.warn('[applyOverrideToDOM] Children override not supported in DOM mode');
245
+ break;
246
+
247
+ case 'instanceSwap':
248
+ console.log('[applyOverrideToDOM] Instance swap requires higher-level handling');
249
+ break;
250
+
251
+ default:
252
+ console.warn('[applyOverrideToDOM] Unknown override type:', override.type);
253
+ }
254
+
255
+ // Mark element as having an override (for visual indication)
256
+ domElement.setAttribute('data-has-override', 'true');
257
+ domElement.setAttribute(`data-override-${override.type}`, 'true');
258
+ }
259
+
260
+ /**
261
+ * Apply all overrides to DOM elements within a container
262
+ */
263
+ export function applyOverridesToDOM(
264
+ container: HTMLElement,
265
+ overrides: ComponentOverride[]
266
+ ): void {
267
+ for (const override of overrides) {
268
+ let targetElement: HTMLElement | null = null;
269
+
270
+ if (override.targetElementId) {
271
+ targetElement = container.querySelector(
272
+ `[data-element-id="${override.targetElementId}"]`
273
+ );
274
+ }
275
+
276
+ if (targetElement) {
277
+ applyOverrideToDOM(targetElement, override);
278
+ }
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Reset a specific override on an instance (returns a new instance)
284
+ */
285
+ export function resetOverride(
286
+ instance: ComponentInstance,
287
+ overrideId: string
288
+ ): ComponentInstance {
289
+ return {
290
+ ...instance,
291
+ overrides: instance.overrides.filter((o) => o.id !== overrideId),
292
+ updatedAt: new Date().toISOString(),
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Reset all overrides of a specific type on an instance
298
+ */
299
+ export function resetOverridesByType(
300
+ instance: ComponentInstance,
301
+ type: OverrideType
302
+ ): ComponentInstance {
303
+ return {
304
+ ...instance,
305
+ overrides: instance.overrides.filter((o) => o.type !== type),
306
+ updatedAt: new Date().toISOString(),
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Reset all overrides for a specific element on an instance
312
+ */
313
+ export function resetOverridesForElement(
314
+ instance: ComponentInstance,
315
+ elementId: string
316
+ ): ComponentInstance {
317
+ return {
318
+ ...instance,
319
+ overrides: instance.overrides.filter(
320
+ (o) =>
321
+ o.targetElementId !== elementId &&
322
+ !o.elementPath?.includes(elementId)
323
+ ),
324
+ updatedAt: new Date().toISOString(),
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Reset all overrides on an instance
330
+ */
331
+ export function resetAllOverrides(
332
+ instance: ComponentInstance
333
+ ): ComponentInstance {
334
+ return {
335
+ ...instance,
336
+ overrides: [],
337
+ updatedAt: new Date().toISOString(),
338
+ };
339
+ }
340
+
341
+ /**
342
+ * Create an override from a property change
343
+ */
344
+ export function createOverride(
345
+ targetElementId: string,
346
+ type: OverrideType,
347
+ value: string | boolean | OverrideImageValue | Record<string, string>
348
+ ): Omit<ComponentOverride, 'id'> {
349
+ return {
350
+ targetElementId,
351
+ type,
352
+ value,
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Create a new override with a generated ID
358
+ */
359
+ export function createOverrideWithId(
360
+ targetElementId: string,
361
+ type: OverrideType,
362
+ value: string | boolean | OverrideImageValue | Record<string, string>
363
+ ): ComponentOverride {
364
+ return {
365
+ id: `override-${targetElementId}-${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
366
+ targetElementId,
367
+ type,
368
+ value,
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Add or update an override in an instance
374
+ * If an override for the same element and type exists, it is updated
375
+ * Otherwise, a new override is added
376
+ */
377
+ export function upsertOverride(
378
+ instance: ComponentInstance,
379
+ override: Omit<ComponentOverride, 'id'>
380
+ ): ComponentInstance {
381
+ const existingIndex = instance.overrides.findIndex(
382
+ (o) =>
383
+ o.targetElementId === override.targetElementId &&
384
+ o.type === override.type
385
+ );
386
+
387
+ const newOverride: ComponentOverride = {
388
+ ...override,
389
+ id:
390
+ existingIndex >= 0
391
+ ? instance.overrides[existingIndex].id
392
+ : `override-${override.targetElementId}-${override.type}-${Date.now()}`,
393
+ };
394
+
395
+ const newOverrides =
396
+ existingIndex >= 0
397
+ ? [
398
+ ...instance.overrides.slice(0, existingIndex),
399
+ newOverride,
400
+ ...instance.overrides.slice(existingIndex + 1),
401
+ ]
402
+ : [...instance.overrides, newOverride];
403
+
404
+ return {
405
+ ...instance,
406
+ overrides: newOverrides,
407
+ updatedAt: new Date().toISOString(),
408
+ };
409
+ }
410
+
411
+ /**
412
+ * Remove override indicators from a DOM element
413
+ */
414
+ export function clearOverrideIndicators(domElement: HTMLElement): void {
415
+ domElement.removeAttribute('data-has-override');
416
+ domElement.removeAttribute('data-override-text');
417
+ domElement.removeAttribute('data-override-fill');
418
+ domElement.removeAttribute('data-override-stroke');
419
+ domElement.removeAttribute('data-override-visibility');
420
+ domElement.removeAttribute('data-override-image');
421
+ domElement.removeAttribute('data-override-style');
422
+ domElement.removeAttribute('data-override-attribute');
423
+ }
424
+
425
+ /**
426
+ * Type guard for OverrideImageValue
427
+ */
428
+ function isOverrideImageValue(value: unknown): value is OverrideImageValue {
429
+ return (
430
+ typeof value === 'object' &&
431
+ value !== null &&
432
+ 'url' in value &&
433
+ typeof (value as OverrideImageValue).url === 'string'
434
+ );
435
+ }
436
+
437
+ /**
438
+ * Type guard for OverrideInstanceSwapValue
439
+ */
440
+ function isOverrideInstanceSwapValue(value: unknown): value is OverrideInstanceSwapValue {
441
+ return (
442
+ typeof value === 'object' &&
443
+ value !== null &&
444
+ 'componentId' in value &&
445
+ typeof (value as OverrideInstanceSwapValue).componentId === 'string'
446
+ );
447
+ }
448
+
449
+ /**
450
+ * Merge overrides from multiple sources (e.g., merging variant overrides with instance overrides)
451
+ * Later overrides in the array take precedence
452
+ */
453
+ export function mergeOverrides(
454
+ ...overrideSets: ComponentOverride[][]
455
+ ): ComponentOverride[] {
456
+ const overrideMap = new Map<string, ComponentOverride>();
457
+
458
+ for (const overrides of overrideSets) {
459
+ for (const override of overrides) {
460
+ // Create a key based on target and type
461
+ const key = `${override.targetElementId || override.elementPath}-${override.type}`;
462
+ overrideMap.set(key, override);
463
+ }
464
+ }
465
+
466
+ return Array.from(overrideMap.values());
467
+ }
468
+
469
+ /**
470
+ * Check if two overrides are equivalent (same target, type, and value)
471
+ */
472
+ export function areOverridesEqual(
473
+ a: ComponentOverride,
474
+ b: ComponentOverride
475
+ ): boolean {
476
+ if (a.type !== b.type) return false;
477
+ if (a.targetElementId !== b.targetElementId) return false;
478
+ if (a.elementPath !== b.elementPath) return false;
479
+
480
+ // Compare values
481
+ if (typeof a.value !== typeof b.value) return false;
482
+
483
+ if (typeof a.value === 'object' && a.value !== null) {
484
+ return JSON.stringify(a.value) === JSON.stringify(b.value);
485
+ }
486
+
487
+ return a.value === b.value;
488
+ }
489
+
490
+ /**
491
+ * Get the display name for an override type (for UI)
492
+ */
493
+ export function getOverrideTypeDisplayName(type: OverrideType): string {
494
+ const displayNames: Record<OverrideType, string> = {
495
+ text: 'テキスト',
496
+ fill: '塗り',
497
+ stroke: '線',
498
+ visibility: '表示',
499
+ image: '画像',
500
+ style: 'スタイル',
501
+ attribute: '属性',
502
+ children: '子要素',
503
+ instanceSwap: 'インスタンス入れ替え',
504
+ };
505
+
506
+ return displayNames[type] || type;
507
+ }
508
+
509
+ /**
510
+ * Convert override type to CSS property hint
511
+ */
512
+ export function getOverrideTypeCSSProperty(type: OverrideType): string | null {
513
+ const cssProperties: Record<OverrideType, string | null> = {
514
+ text: null, // Not a CSS property
515
+ fill: 'background-color',
516
+ stroke: 'border',
517
+ visibility: 'display',
518
+ image: 'background-image',
519
+ style: null, // Multiple properties
520
+ attribute: null, // Not CSS
521
+ children: null, // Not CSS
522
+ instanceSwap: null, // Not CSS
523
+ };
524
+
525
+ return cssProperties[type];
526
+ }