@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.
- package/LICENSE +21 -0
- package/NOTICE +52 -0
- package/README.md +143 -0
- package/dist/editor.css +2 -0
- package/package.json +94 -0
- package/src/components/DeckSlideRender.tsx +21 -0
- package/src/components/SaveNote.tsx +21 -0
- package/src/components/auth/AuthProvider.tsx +44 -0
- package/src/components/ui/accordion.tsx +57 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +11 -0
- package/src/components/ui/context-menu.tsx +200 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/scrubbable-label.tsx +95 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/viewer/useDeck.ts +58 -0
- package/src/editor/EditorContext.tsx +632 -0
- package/src/editor/EditorToolbar.tsx +446 -0
- package/src/editor/FrontendVisualEditor.tsx +2595 -0
- package/src/editor/autosave.ts +34 -0
- package/src/editor/components/AiPromptPopover.tsx +378 -0
- package/src/editor/components/BreakpointGuides.tsx +323 -0
- package/src/editor/components/BreakpointSelector.tsx +180 -0
- package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
- package/src/editor/components/CssEditorDialog.tsx +168 -0
- package/src/editor/components/EditorCanvas.tsx +987 -0
- package/src/editor/components/EditorContextMenu.tsx +515 -0
- package/src/editor/components/EditorFooter.tsx +81 -0
- package/src/editor/components/EditorHeader.tsx +524 -0
- package/src/editor/components/EditorLayerPanel.tsx +1197 -0
- package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
- package/src/editor/components/EditorSidebar.tsx +226 -0
- package/src/editor/components/FigmaColorPicker.tsx +1577 -0
- package/src/editor/components/HtmlEditorDialog.tsx +71 -0
- package/src/editor/components/HtmlImportDialog.tsx +346 -0
- package/src/editor/components/JsEditorDialog.tsx +175 -0
- package/src/editor/components/LayerTreeItem.tsx +494 -0
- package/src/editor/components/MasterComponentEditor.tsx +846 -0
- package/src/editor/components/MediaLibraryDialog.tsx +447 -0
- package/src/editor/components/PageSettingsDialog.tsx +776 -0
- package/src/editor/components/VariablePicker.tsx +355 -0
- package/src/editor/components/VariablesDialog.tsx +564 -0
- package/src/editor/components/VariantEditor.tsx +687 -0
- package/src/editor/components/VariantMatrix.tsx +507 -0
- package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
- package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
- package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
- package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
- package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
- package/src/editor/components/component-panel/VariantList.tsx +260 -0
- package/src/editor/components/component-panel/index.ts +12 -0
- package/src/editor/components/index.ts +48 -0
- package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
- package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
- package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
- package/src/editor/components/multi-page/PageLabel.tsx +34 -0
- package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
- package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
- package/src/editor/components/multi-page/index.ts +7 -0
- package/src/editor/components/ppt/PptChrome.tsx +2508 -0
- package/src/editor/components/ppt/PptComments.tsx +589 -0
- package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
- package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
- package/src/editor/components/ppt/PptNotes.tsx +246 -0
- package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
- package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
- package/src/editor/components/property-panel/BorderSection.tsx +104 -0
- package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
- package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
- package/src/editor/components/property-panel/EffectSection.tsx +157 -0
- package/src/editor/components/property-panel/FillSection.tsx +69 -0
- package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
- package/src/editor/components/property-panel/ImageSection.tsx +206 -0
- package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
- package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
- package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
- package/src/editor/components/property-panel/LinkSection.tsx +103 -0
- package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
- package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
- package/src/editor/components/property-panel/TypographySection.tsx +230 -0
- package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
- package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
- package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
- package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
- package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
- package/src/editor/components/property-panel/index.ts +24 -0
- package/src/editor/components/property-panel/unit-utils.ts +261 -0
- package/src/editor/components/shell/EditorTopBar.tsx +392 -0
- package/src/editor/components/shell/LeftPanel.tsx +47 -0
- package/src/editor/components/shell/PagesPanel.tsx +118 -0
- package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
- package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
- package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
- package/src/editor/components/variables-panel/index.ts +7 -0
- package/src/editor/constants.ts +468 -0
- package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
- package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
- package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
- package/src/editor/contexts/EditorRefsContext.tsx +204 -0
- package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
- package/src/editor/contexts/EditorToolContext.tsx +58 -0
- package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
- package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
- package/src/editor/contexts/EditorViewContext.tsx +101 -0
- package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
- package/src/editor/contexts/index.ts +17 -0
- package/src/editor/editor-skin.css +122 -0
- package/src/editor/hooks/index.ts +38 -0
- package/src/editor/hooks/useAiReplace.ts +397 -0
- package/src/editor/hooks/useAltMeasure.ts +67 -0
- package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
- package/src/editor/hooks/useCanvasControls.ts +1222 -0
- package/src/editor/hooks/useComponentEditMode.ts +658 -0
- package/src/editor/hooks/useComponentInstances.ts +515 -0
- package/src/editor/hooks/useContextMenuHandler.ts +157 -0
- package/src/editor/hooks/useCoordinateTransform.ts +207 -0
- package/src/editor/hooks/useDragResize.ts +1317 -0
- package/src/editor/hooks/useDrawingMode.ts +472 -0
- package/src/editor/hooks/useEditorColors.ts +144 -0
- package/src/editor/hooks/useEditorMessages.ts +108 -0
- package/src/editor/hooks/useElementActions.ts +1754 -0
- package/src/editor/hooks/useElementSelection.ts +1573 -0
- package/src/editor/hooks/useFocusManagement.ts +249 -0
- package/src/editor/hooks/useGoogleFonts.ts +305 -0
- package/src/editor/hooks/useIframeInitializer.ts +584 -0
- package/src/editor/hooks/useIframeSetup.ts +360 -0
- package/src/editor/hooks/useImageUpload.ts +270 -0
- package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
- package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
- package/src/editor/hooks/useMarqueeSelection.ts +388 -0
- package/src/editor/hooks/useMediaLibrary.ts +129 -0
- package/src/editor/hooks/usePageSettingsManager.ts +739 -0
- package/src/editor/hooks/useResizablePanel.ts +163 -0
- package/src/editor/hooks/useRichPaste.ts +255 -0
- package/src/editor/hooks/useTouchGestures.ts +197 -0
- package/src/editor/index.ts +27 -0
- package/src/editor/types/page-settings.ts +513 -0
- package/src/editor/types.ts +382 -0
- package/src/editor/utils/align-elements.ts +169 -0
- package/src/editor/utils/api-json.ts +21 -0
- package/src/editor/utils/aspect-lock.ts +50 -0
- package/src/editor/utils/component-renderer.ts +702 -0
- package/src/editor/utils/component-sync.ts +845 -0
- package/src/editor/utils/crop-mode.ts +265 -0
- package/src/editor/utils/dom-utils.ts +2238 -0
- package/src/editor/utils/element-effects.ts +218 -0
- package/src/editor/utils/eyedropper.ts +35 -0
- package/src/editor/utils/figma-export.ts +560 -0
- package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
- package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
- package/src/editor/utils/figma-paste.ts +2262 -0
- package/src/editor/utils/flex-reorder.ts +231 -0
- package/src/editor/utils/flex-utils.ts +1230 -0
- package/src/editor/utils/geometry.ts +232 -0
- package/src/editor/utils/grid-layout.ts +210 -0
- package/src/editor/utils/html-utils.ts +470 -0
- package/src/editor/utils/index.ts +11 -0
- package/src/editor/utils/ink-style.ts +57 -0
- package/src/editor/utils/inline-format.ts +236 -0
- package/src/editor/utils/measure-distance.ts +120 -0
- package/src/editor/utils/override-apply.ts +526 -0
- package/src/editor/utils/override-detection.ts +613 -0
- package/src/editor/utils/paste-processors.ts +609 -0
- package/src/editor/utils/paste-sanitizer.ts +244 -0
- package/src/editor/utils/restore-flow.ts +188 -0
- package/src/editor/utils/shape-library.ts +171 -0
- package/src/editor/utils/slide-root.ts +29 -0
- package/src/editor/utils/smart-guides.ts +218 -0
- package/src/editor/utils/style-utils.ts +685 -0
- package/src/editor/utils/table-edit.ts +151 -0
- package/src/editor/utils/tailwind-mappings.ts +778 -0
- package/src/editor/utils/tailwind-utils.ts +804 -0
- package/src/editor/utils/text-highlight.ts +216 -0
- package/src/editor/utils/theme-to-variables.ts +341 -0
- package/src/editor/utils/variant-resolver.ts +456 -0
- package/src/editor/utils/viewport-utils.ts +295 -0
- package/src/hooks/useEditorHistory.ts +195 -0
- package/src/hooks/useEditorShortcuts.ts +260 -0
- package/src/index.ts +40 -0
- package/src/io.ts +134 -0
- package/src/lib/agent/slide-agent/types.ts +547 -0
- package/src/lib/agent/website-agent/types.ts +805 -0
- package/src/lib/api/auth-fetch.ts +58 -0
- package/src/lib/deck.ts +69 -0
- package/src/lib/export.ts +47 -0
- package/src/lib/firebase/config.ts +29 -0
- package/src/lib/firebase/css-variables.ts +378 -0
- package/src/lib/firebase/editor-components.ts +677 -0
- package/src/lib/firebase/storage.ts +469 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/editor.css +52 -0
- package/src/styles/skin.css +122 -0
- package/src/types/css-variables.ts +645 -0
- package/src/types/editor-components.ts +944 -0
- package/src/types/editor.ts +420 -0
- package/src/types/page-master.ts +197 -0
- package/src/types/page.ts +419 -0
- package/src/types/slide.ts +402 -0
- package/src/types/website-theme.ts +788 -0
- package/src/types/website.ts +374 -0
- package/src/vendor/firebase-firestore.ts +13 -0
- package/src/vendor/firebase-functions.ts +17 -0
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Sync Utilities
|
|
3
|
+
*
|
|
4
|
+
* Utilities for synchronizing master component changes to instances.
|
|
5
|
+
* Handles propagation of master updates, override validation, and instance reconciliation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ComponentElement,
|
|
10
|
+
ComponentInstance,
|
|
11
|
+
ComponentOverride,
|
|
12
|
+
MasterComponent,
|
|
13
|
+
OverrideType,
|
|
14
|
+
} from '../../types/editor-components';
|
|
15
|
+
import {
|
|
16
|
+
renderInstance,
|
|
17
|
+
findInstancesByMaster,
|
|
18
|
+
getInstanceIdFromElement,
|
|
19
|
+
resolveInstance,
|
|
20
|
+
} from './component-renderer';
|
|
21
|
+
import { detectOverrides } from './override-detection';
|
|
22
|
+
|
|
23
|
+
// ============================================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result of an override validation check.
|
|
29
|
+
*/
|
|
30
|
+
export interface OverrideValidationResult {
|
|
31
|
+
/** Whether the override is valid */
|
|
32
|
+
isValid: boolean;
|
|
33
|
+
/** Reason for invalidity if not valid */
|
|
34
|
+
reason?: string;
|
|
35
|
+
/** Suggested action to fix the issue */
|
|
36
|
+
suggestion?: 'remove' | 'update' | 'keep';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Result of a sync operation.
|
|
41
|
+
*/
|
|
42
|
+
export interface SyncResult {
|
|
43
|
+
/** Number of instances successfully updated */
|
|
44
|
+
updatedCount: number;
|
|
45
|
+
/** Number of instances that failed to update */
|
|
46
|
+
failedCount: number;
|
|
47
|
+
/** Instance IDs that failed to update */
|
|
48
|
+
failedInstanceIds: string[];
|
|
49
|
+
/** Errors encountered during sync */
|
|
50
|
+
errors: Array<{ instanceId: string; error: Error }>;
|
|
51
|
+
/** Instances updated with newly detected overrides (for persistence) */
|
|
52
|
+
updatedInstances: ComponentInstance[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================
|
|
56
|
+
// Override Validation
|
|
57
|
+
// ============================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find an element in a ComponentElement tree by ID.
|
|
61
|
+
*/
|
|
62
|
+
function findElementById(
|
|
63
|
+
root: ComponentElement,
|
|
64
|
+
elementId: string
|
|
65
|
+
): ComponentElement | null {
|
|
66
|
+
if (root.id === elementId) {
|
|
67
|
+
return root;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const child of root.children) {
|
|
71
|
+
const found = findElementById(child, elementId);
|
|
72
|
+
if (found) return found;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find an element in a ComponentElement tree by path.
|
|
80
|
+
*/
|
|
81
|
+
function findElementByPath(
|
|
82
|
+
root: ComponentElement,
|
|
83
|
+
path: string
|
|
84
|
+
): ComponentElement | null {
|
|
85
|
+
if (path === 'root') {
|
|
86
|
+
return root;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const segments = path.split('.');
|
|
90
|
+
let current: ComponentElement | undefined = root;
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < segments.length; i += 2) {
|
|
93
|
+
if (segments[i] !== 'children' || !current) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const index = parseInt(segments[i + 1], 10);
|
|
97
|
+
if (isNaN(index) || index < 0 || index >= current.children.length) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
current = current.children[index];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return current || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if an override type is allowed for an element based on its overridable properties.
|
|
108
|
+
*/
|
|
109
|
+
function isOverrideTypeAllowed(
|
|
110
|
+
element: ComponentElement,
|
|
111
|
+
overrideType: OverrideType
|
|
112
|
+
): boolean {
|
|
113
|
+
const { overridable } = element;
|
|
114
|
+
|
|
115
|
+
switch (overrideType) {
|
|
116
|
+
case 'text':
|
|
117
|
+
return overridable.text;
|
|
118
|
+
case 'fill':
|
|
119
|
+
return overridable.fill;
|
|
120
|
+
case 'stroke':
|
|
121
|
+
return overridable.stroke;
|
|
122
|
+
case 'visibility':
|
|
123
|
+
return overridable.visibility;
|
|
124
|
+
case 'image':
|
|
125
|
+
return overridable.image;
|
|
126
|
+
case 'style':
|
|
127
|
+
case 'attribute':
|
|
128
|
+
case 'children':
|
|
129
|
+
case 'instanceSwap':
|
|
130
|
+
// These override types are generally allowed
|
|
131
|
+
return true;
|
|
132
|
+
default:
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a single override is still valid after a master component update.
|
|
139
|
+
*
|
|
140
|
+
* @param override - The override to validate
|
|
141
|
+
* @param master - The updated master component
|
|
142
|
+
* @param variantId - The variant ID the instance is using
|
|
143
|
+
* @returns Validation result with details
|
|
144
|
+
*/
|
|
145
|
+
export function isOverrideValid(
|
|
146
|
+
override: ComponentOverride,
|
|
147
|
+
master: MasterComponent,
|
|
148
|
+
variantId: string = master.defaultVariantId
|
|
149
|
+
): OverrideValidationResult {
|
|
150
|
+
// Find the variant
|
|
151
|
+
const variant = master.variants.find((v) => v.id === variantId);
|
|
152
|
+
if (!variant) {
|
|
153
|
+
return {
|
|
154
|
+
isValid: false,
|
|
155
|
+
reason: `Variant "${variantId}" no longer exists in master`,
|
|
156
|
+
suggestion: 'remove',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Find the target element
|
|
161
|
+
let targetElement: ComponentElement | null = null;
|
|
162
|
+
|
|
163
|
+
if (override.targetElementId) {
|
|
164
|
+
targetElement = findElementById(variant.rootElement, override.targetElementId);
|
|
165
|
+
if (!targetElement) {
|
|
166
|
+
return {
|
|
167
|
+
isValid: false,
|
|
168
|
+
reason: `Target element "${override.targetElementId}" no longer exists in master`,
|
|
169
|
+
suggestion: 'remove',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
} else if (override.elementPath) {
|
|
173
|
+
targetElement = findElementByPath(variant.rootElement, override.elementPath);
|
|
174
|
+
if (!targetElement) {
|
|
175
|
+
return {
|
|
176
|
+
isValid: false,
|
|
177
|
+
reason: `Target path "${override.elementPath}" no longer valid in master`,
|
|
178
|
+
suggestion: 'remove',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
return {
|
|
183
|
+
isValid: false,
|
|
184
|
+
reason: 'Override has no target (neither elementId nor path)',
|
|
185
|
+
suggestion: 'remove',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check if the override type is allowed
|
|
190
|
+
if (!isOverrideTypeAllowed(targetElement, override.type)) {
|
|
191
|
+
return {
|
|
192
|
+
isValid: false,
|
|
193
|
+
reason: `Override type "${override.type}" is no longer allowed on target element`,
|
|
194
|
+
suggestion: 'remove',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Validate override value based on type
|
|
199
|
+
switch (override.type) {
|
|
200
|
+
case 'text':
|
|
201
|
+
if (typeof override.value !== 'string') {
|
|
202
|
+
return {
|
|
203
|
+
isValid: false,
|
|
204
|
+
reason: 'Text override value must be a string',
|
|
205
|
+
suggestion: 'update',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
case 'fill':
|
|
211
|
+
case 'stroke':
|
|
212
|
+
if (typeof override.value !== 'string') {
|
|
213
|
+
return {
|
|
214
|
+
isValid: false,
|
|
215
|
+
reason: 'Color override value must be a string',
|
|
216
|
+
suggestion: 'update',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case 'visibility':
|
|
222
|
+
if (typeof override.value !== 'boolean') {
|
|
223
|
+
return {
|
|
224
|
+
isValid: false,
|
|
225
|
+
reason: 'Visibility override value must be a boolean',
|
|
226
|
+
suggestion: 'update',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'image':
|
|
232
|
+
if (
|
|
233
|
+
typeof override.value !== 'object' ||
|
|
234
|
+
!override.value ||
|
|
235
|
+
!('url' in override.value)
|
|
236
|
+
) {
|
|
237
|
+
return {
|
|
238
|
+
isValid: false,
|
|
239
|
+
reason: 'Image override value must be an object with url property',
|
|
240
|
+
suggestion: 'update',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
|
|
245
|
+
case 'style':
|
|
246
|
+
case 'attribute':
|
|
247
|
+
if (
|
|
248
|
+
typeof override.value !== 'object' ||
|
|
249
|
+
Array.isArray(override.value) ||
|
|
250
|
+
override.value === null
|
|
251
|
+
) {
|
|
252
|
+
return {
|
|
253
|
+
isValid: false,
|
|
254
|
+
reason: `${override.type} override value must be a key-value object`,
|
|
255
|
+
suggestion: 'update',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
|
|
260
|
+
case 'children':
|
|
261
|
+
if (!Array.isArray(override.value)) {
|
|
262
|
+
return {
|
|
263
|
+
isValid: false,
|
|
264
|
+
reason: 'Children override value must be an array',
|
|
265
|
+
suggestion: 'update',
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
case 'instanceSwap':
|
|
271
|
+
if (
|
|
272
|
+
typeof override.value !== 'object' ||
|
|
273
|
+
!override.value ||
|
|
274
|
+
!('componentId' in override.value)
|
|
275
|
+
) {
|
|
276
|
+
return {
|
|
277
|
+
isValid: false,
|
|
278
|
+
reason: 'Instance swap override value must have componentId',
|
|
279
|
+
suggestion: 'update',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { isValid: true };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Filter overrides to only those compatible with the new master component.
|
|
290
|
+
*
|
|
291
|
+
* @param overrides - The overrides to filter
|
|
292
|
+
* @param master - The updated master component
|
|
293
|
+
* @param variantId - The variant ID to validate against
|
|
294
|
+
* @returns Array of valid overrides
|
|
295
|
+
*/
|
|
296
|
+
export function filterCompatibleOverrides(
|
|
297
|
+
overrides: ComponentOverride[],
|
|
298
|
+
master: MasterComponent,
|
|
299
|
+
variantId: string = master.defaultVariantId
|
|
300
|
+
): ComponentOverride[] {
|
|
301
|
+
return overrides.filter((override) => {
|
|
302
|
+
const result = isOverrideValid(override, master, variantId);
|
|
303
|
+
if (!result.isValid) {
|
|
304
|
+
console.warn(
|
|
305
|
+
`[filterCompatibleOverrides] Removing invalid override: ${result.reason}`,
|
|
306
|
+
override
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return result.isValid;
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Validate all overrides on an instance and return detailed results.
|
|
315
|
+
*
|
|
316
|
+
* @param instance - The component instance
|
|
317
|
+
* @param master - The master component
|
|
318
|
+
* @returns Array of validation results for each override
|
|
319
|
+
*/
|
|
320
|
+
export function validateInstanceOverrides(
|
|
321
|
+
instance: ComponentInstance,
|
|
322
|
+
master: MasterComponent
|
|
323
|
+
): Array<{ override: ComponentOverride; result: OverrideValidationResult }> {
|
|
324
|
+
return instance.overrides.map((override) => ({
|
|
325
|
+
override,
|
|
326
|
+
result: isOverrideValid(override, master, instance.variantId),
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ============================================================
|
|
331
|
+
// Master Change Propagation
|
|
332
|
+
// ============================================================
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Propagate changes from a master component to all linked instances in a document.
|
|
336
|
+
*
|
|
337
|
+
* This function:
|
|
338
|
+
* 1. Finds all DOM elements that are instances of the master
|
|
339
|
+
* 2. Re-renders each instance with the updated master
|
|
340
|
+
* 3. Preserves valid overrides while removing incompatible ones
|
|
341
|
+
*
|
|
342
|
+
* @param master - The updated master component
|
|
343
|
+
* @param instances - Array of component instances to update
|
|
344
|
+
* @param iframeDoc - The iframe document containing the instances
|
|
345
|
+
* @returns Sync result with statistics
|
|
346
|
+
*/
|
|
347
|
+
export function propagateMasterChanges(
|
|
348
|
+
master: MasterComponent,
|
|
349
|
+
instances: ComponentInstance[],
|
|
350
|
+
iframeDoc: Document
|
|
351
|
+
): SyncResult {
|
|
352
|
+
const result: SyncResult = {
|
|
353
|
+
updatedCount: 0,
|
|
354
|
+
failedCount: 0,
|
|
355
|
+
failedInstanceIds: [],
|
|
356
|
+
errors: [],
|
|
357
|
+
updatedInstances: [],
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Find all DOM elements for this master
|
|
361
|
+
const domElements = findInstancesByMaster(iframeDoc, master.id);
|
|
362
|
+
|
|
363
|
+
console.log('[propagateMasterChanges] Found DOM elements for master:', {
|
|
364
|
+
masterId: master.id,
|
|
365
|
+
domElementsCount: domElements.length,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Create a map for quick lookup
|
|
369
|
+
const domElementMap = new Map<string, HTMLElement>();
|
|
370
|
+
for (const el of domElements) {
|
|
371
|
+
const instanceId = getInstanceIdFromElement(el);
|
|
372
|
+
console.log('[propagateMasterChanges] DOM element instanceId:', instanceId);
|
|
373
|
+
if (instanceId) {
|
|
374
|
+
domElementMap.set(instanceId, el);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.log('[propagateMasterChanges] Processing instances:', {
|
|
379
|
+
totalInstances: instances.length,
|
|
380
|
+
instanceIds: instances.map(i => i.id),
|
|
381
|
+
domMapKeys: Array.from(domElementMap.keys()),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Process each instance
|
|
385
|
+
for (const instance of instances) {
|
|
386
|
+
// Skip detached instances
|
|
387
|
+
if (instance.isDetached) {
|
|
388
|
+
console.log('[propagateMasterChanges] Skipping detached instance:', instance.id);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Skip instances for other masters
|
|
393
|
+
if (instance.masterComponentId !== master.id) {
|
|
394
|
+
console.log('[propagateMasterChanges] Skipping instance for other master:', instance.id);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
// Find the DOM element
|
|
400
|
+
const domEl = domElementMap.get(instance.id);
|
|
401
|
+
if (!domEl) {
|
|
402
|
+
// Instance not in DOM, skip
|
|
403
|
+
console.log('[propagateMasterChanges] DOM element not found for instance:', instance.id);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
console.log('[propagateMasterChanges] Found DOM element for instance:', instance.id);
|
|
408
|
+
|
|
409
|
+
// Validate variant still exists
|
|
410
|
+
let variantId = instance.variantId;
|
|
411
|
+
const variant = master.variants.find((v) => v.id === variantId);
|
|
412
|
+
if (!variant) {
|
|
413
|
+
// Variant was removed, fall back to default
|
|
414
|
+
console.warn(
|
|
415
|
+
`[propagateMasterChanges] Variant "${instance.variantId}" removed, using default`
|
|
416
|
+
);
|
|
417
|
+
variantId = master.defaultVariantId;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// CRITICAL: Detect DOM-level overrides BEFORE re-rendering
|
|
421
|
+
// This captures any user edits that haven't been saved as overrides yet
|
|
422
|
+
const effectiveVariant = master.variants.find((v) => v.id === variantId);
|
|
423
|
+
let detectedOverrides: ComponentOverride[] = [];
|
|
424
|
+
if (effectiveVariant) {
|
|
425
|
+
// Debug: Log what we're comparing
|
|
426
|
+
console.log('[propagateMasterChanges] === Override Detection Debug ===');
|
|
427
|
+
console.log('[propagateMasterChanges] DOM element:', {
|
|
428
|
+
tagName: domEl.tagName,
|
|
429
|
+
elementId: domEl.getAttribute('data-element-id'),
|
|
430
|
+
textContent: domEl.textContent?.substring(0, 100),
|
|
431
|
+
innerHTML: domEl.innerHTML?.substring(0, 200),
|
|
432
|
+
childrenCount: domEl.children.length,
|
|
433
|
+
});
|
|
434
|
+
console.log('[propagateMasterChanges] Master rootElement:', {
|
|
435
|
+
tagName: effectiveVariant.rootElement.tagName,
|
|
436
|
+
id: effectiveVariant.rootElement.id,
|
|
437
|
+
textContent: effectiveVariant.rootElement.textContent?.substring(0, 100),
|
|
438
|
+
innerHTML: effectiveVariant.rootElement.innerHTML?.substring(0, 200),
|
|
439
|
+
childrenCount: effectiveVariant.rootElement.children.length,
|
|
440
|
+
hasChildren: effectiveVariant.rootElement.children.length > 0,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
detectedOverrides = detectOverrides(domEl, effectiveVariant.rootElement);
|
|
445
|
+
console.log('[propagateMasterChanges] Detected DOM overrides:', {
|
|
446
|
+
instanceId: instance.id,
|
|
447
|
+
count: detectedOverrides.length,
|
|
448
|
+
overrides: detectedOverrides.map(o => ({
|
|
449
|
+
type: o.type,
|
|
450
|
+
targetElementId: o.targetElementId,
|
|
451
|
+
value: typeof o.value === 'string' ? o.value.substring(0, 50) : o.value,
|
|
452
|
+
})),
|
|
453
|
+
});
|
|
454
|
+
} catch (e) {
|
|
455
|
+
console.warn('[propagateMasterChanges] Failed to detect DOM overrides:', e);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Merge: existing stored overrides + newly detected DOM overrides
|
|
460
|
+
// Detected overrides take precedence (they are the current state)
|
|
461
|
+
const existingOverrides = filterCompatibleOverrides(
|
|
462
|
+
instance.overrides,
|
|
463
|
+
master,
|
|
464
|
+
variantId
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// Create a map of existing overrides by target+type for merging
|
|
468
|
+
const overrideMap = new Map<string, ComponentOverride>();
|
|
469
|
+
for (const override of existingOverrides) {
|
|
470
|
+
const key = `${override.targetElementId || override.elementPath}-${override.type}`;
|
|
471
|
+
overrideMap.set(key, override);
|
|
472
|
+
}
|
|
473
|
+
// Detected overrides take precedence
|
|
474
|
+
for (const override of detectedOverrides) {
|
|
475
|
+
const key = `${override.targetElementId || override.elementPath}-${override.type}`;
|
|
476
|
+
overrideMap.set(key, override);
|
|
477
|
+
}
|
|
478
|
+
const mergedOverrides = Array.from(overrideMap.values());
|
|
479
|
+
|
|
480
|
+
console.log('[propagateMasterChanges] Merged overrides:', {
|
|
481
|
+
instanceId: instance.id,
|
|
482
|
+
existing: existingOverrides.length,
|
|
483
|
+
detected: detectedOverrides.length,
|
|
484
|
+
merged: mergedOverrides.length,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Create a temporary instance with merged overrides for rendering
|
|
488
|
+
const updatedInstance: ComponentInstance = {
|
|
489
|
+
...instance,
|
|
490
|
+
variantId,
|
|
491
|
+
overrides: mergedOverrides,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// Collect unique ID mappings from old element before replacing
|
|
495
|
+
// (for duplicated instances that have data-master-element-id attributes)
|
|
496
|
+
const idMappings = new Map<string, string>();
|
|
497
|
+
const collectIdMappings = (el: HTMLElement) => {
|
|
498
|
+
const masterRefId = el.getAttribute('data-master-element-id');
|
|
499
|
+
const uniqueId = el.getAttribute('data-element-id');
|
|
500
|
+
if (masterRefId && uniqueId) {
|
|
501
|
+
idMappings.set(masterRefId, uniqueId);
|
|
502
|
+
}
|
|
503
|
+
// Recursively collect from children
|
|
504
|
+
el.querySelectorAll('[data-master-element-id]').forEach((child) => {
|
|
505
|
+
const childMasterRefId = child.getAttribute('data-master-element-id');
|
|
506
|
+
const childUniqueId = child.getAttribute('data-element-id');
|
|
507
|
+
if (childMasterRefId && childUniqueId) {
|
|
508
|
+
idMappings.set(childMasterRefId, childUniqueId);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
};
|
|
512
|
+
collectIdMappings(domEl);
|
|
513
|
+
|
|
514
|
+
// Re-render the instance
|
|
515
|
+
const newEl = renderInstance(master, updatedInstance, iframeDoc);
|
|
516
|
+
|
|
517
|
+
// Apply unique ID mappings to the new element
|
|
518
|
+
// This preserves unique IDs for duplicated instances
|
|
519
|
+
if (idMappings.size > 0) {
|
|
520
|
+
const applyIdMappings = (el: HTMLElement) => {
|
|
521
|
+
const currentId = el.getAttribute('data-element-id');
|
|
522
|
+
if (currentId && idMappings.has(currentId)) {
|
|
523
|
+
const uniqueId = idMappings.get(currentId)!;
|
|
524
|
+
el.setAttribute('data-master-element-id', currentId);
|
|
525
|
+
el.setAttribute('data-element-id', uniqueId);
|
|
526
|
+
}
|
|
527
|
+
// Recursively apply to children
|
|
528
|
+
el.querySelectorAll('[data-element-id]').forEach((child) => {
|
|
529
|
+
const childEl = child as HTMLElement;
|
|
530
|
+
const childCurrentId = childEl.getAttribute('data-element-id');
|
|
531
|
+
if (childCurrentId && idMappings.has(childCurrentId)) {
|
|
532
|
+
const childUniqueId = idMappings.get(childCurrentId)!;
|
|
533
|
+
childEl.setAttribute('data-master-element-id', childCurrentId);
|
|
534
|
+
childEl.setAttribute('data-element-id', childUniqueId);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
};
|
|
538
|
+
applyIdMappings(newEl);
|
|
539
|
+
console.log('[propagateMasterChanges] Applied ID mappings for duplicated instance:', {
|
|
540
|
+
instanceId: instance.id,
|
|
541
|
+
mappingsCount: idMappings.size,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Preserve position from existing DOM element
|
|
546
|
+
// The DOM element may have position styles that aren't in the instance data
|
|
547
|
+
// (e.g., user moved the element but didn't save)
|
|
548
|
+
const existingStyle = domEl.getAttribute('style');
|
|
549
|
+
if (existingStyle) {
|
|
550
|
+
// Extract position-related styles from existing element
|
|
551
|
+
const positionProps = ['position', 'left', 'top', 'right', 'bottom', 'width', 'height'];
|
|
552
|
+
const styleObj: Record<string, string> = {};
|
|
553
|
+
|
|
554
|
+
existingStyle.split(';').forEach(pair => {
|
|
555
|
+
const [prop, value] = pair.split(':').map(s => s.trim());
|
|
556
|
+
if (prop && value && positionProps.includes(prop)) {
|
|
557
|
+
styleObj[prop] = value;
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Apply preserved position styles to new element
|
|
562
|
+
for (const [prop, value] of Object.entries(styleObj)) {
|
|
563
|
+
newEl.style.setProperty(prop, value);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
console.log('[propagateMasterChanges] Preserved position styles:', {
|
|
567
|
+
instanceId: instance.id,
|
|
568
|
+
preservedStyles: styleObj,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Replace the old element with the new one
|
|
573
|
+
domEl.parentNode?.replaceChild(newEl, domEl);
|
|
574
|
+
|
|
575
|
+
// Store the updated instance for persistence (with merged overrides)
|
|
576
|
+
result.updatedInstances.push(updatedInstance);
|
|
577
|
+
result.updatedCount++;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
result.failedCount++;
|
|
580
|
+
result.failedInstanceIds.push(instance.id);
|
|
581
|
+
result.errors.push({
|
|
582
|
+
instanceId: instance.id,
|
|
583
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
console.log(
|
|
589
|
+
`[propagateMasterChanges] Updated ${result.updatedCount} instances, failed ${result.failedCount}`
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Reconcile instances after a master component update.
|
|
597
|
+
* Returns updated instances with filtered overrides.
|
|
598
|
+
*
|
|
599
|
+
* @param master - The updated master component
|
|
600
|
+
* @param instances - Array of instances to reconcile
|
|
601
|
+
* @returns Array of reconciled instances with valid overrides
|
|
602
|
+
*/
|
|
603
|
+
export function reconcileInstances(
|
|
604
|
+
master: MasterComponent,
|
|
605
|
+
instances: ComponentInstance[]
|
|
606
|
+
): ComponentInstance[] {
|
|
607
|
+
return instances.map((instance) => {
|
|
608
|
+
// Skip detached instances
|
|
609
|
+
if (instance.isDetached) {
|
|
610
|
+
return instance;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Skip instances for other masters
|
|
614
|
+
if (instance.masterComponentId !== master.id) {
|
|
615
|
+
return instance;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Check if variant still exists
|
|
619
|
+
let variantId = instance.variantId;
|
|
620
|
+
const variant = master.variants.find((v) => v.id === variantId);
|
|
621
|
+
if (!variant) {
|
|
622
|
+
// Fall back to default variant
|
|
623
|
+
variantId = master.defaultVariantId;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Filter overrides
|
|
627
|
+
const validOverrides = filterCompatibleOverrides(
|
|
628
|
+
instance.overrides,
|
|
629
|
+
master,
|
|
630
|
+
variantId
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
// Return updated instance if changes were made
|
|
634
|
+
if (
|
|
635
|
+
variantId !== instance.variantId ||
|
|
636
|
+
validOverrides.length !== instance.overrides.length
|
|
637
|
+
) {
|
|
638
|
+
return {
|
|
639
|
+
...instance,
|
|
640
|
+
variantId,
|
|
641
|
+
overrides: validOverrides,
|
|
642
|
+
updatedAt: new Date().toISOString(),
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return instance;
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ============================================================
|
|
651
|
+
// Instance Comparison
|
|
652
|
+
// ============================================================
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Check if an instance has diverged from its master (has overrides).
|
|
656
|
+
*
|
|
657
|
+
* @param instance - The instance to check
|
|
658
|
+
* @returns True if the instance has any overrides
|
|
659
|
+
*/
|
|
660
|
+
export function hasOverrides(instance: ComponentInstance): boolean {
|
|
661
|
+
return instance.overrides.length > 0;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Get the effective property values for an instance,
|
|
666
|
+
* combining master defaults with instance overrides.
|
|
667
|
+
*
|
|
668
|
+
* @param instance - The component instance
|
|
669
|
+
* @param master - The master component
|
|
670
|
+
* @returns Combined property values
|
|
671
|
+
*/
|
|
672
|
+
export function getEffectivePropertyValues(
|
|
673
|
+
instance: ComponentInstance,
|
|
674
|
+
master: MasterComponent
|
|
675
|
+
): Record<string, string | number | boolean> {
|
|
676
|
+
// Start with master defaults
|
|
677
|
+
const defaults: Record<string, string | number | boolean> = {};
|
|
678
|
+
for (const prop of master.exposedProperties) {
|
|
679
|
+
defaults[prop.id] = prop.defaultValue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Merge with instance values
|
|
683
|
+
return {
|
|
684
|
+
...defaults,
|
|
685
|
+
...instance.propertyValues,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// ============================================================
|
|
690
|
+
// Batch Operations
|
|
691
|
+
// ============================================================
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Update multiple instances in a document at once.
|
|
695
|
+
* More efficient than updating one at a time.
|
|
696
|
+
*
|
|
697
|
+
* @param master - The master component
|
|
698
|
+
* @param instances - Array of instances to update
|
|
699
|
+
* @param iframeDoc - The iframe document
|
|
700
|
+
* @param updateFn - Function to apply updates to each instance
|
|
701
|
+
* @returns Sync result
|
|
702
|
+
*/
|
|
703
|
+
export function batchUpdateInstances(
|
|
704
|
+
master: MasterComponent,
|
|
705
|
+
instances: ComponentInstance[],
|
|
706
|
+
iframeDoc: Document,
|
|
707
|
+
updateFn: (instance: ComponentInstance) => ComponentInstance
|
|
708
|
+
): SyncResult {
|
|
709
|
+
// Apply updates to all instances first
|
|
710
|
+
const updatedInstances = instances.map(updateFn);
|
|
711
|
+
|
|
712
|
+
// Then propagate to DOM
|
|
713
|
+
return propagateMasterChanges(master, updatedInstances, iframeDoc);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Find all instances that would be affected by a master change.
|
|
718
|
+
*
|
|
719
|
+
* @param masterId - The master component ID
|
|
720
|
+
* @param instances - All instances to search
|
|
721
|
+
* @returns Array of affected instances
|
|
722
|
+
*/
|
|
723
|
+
export function findAffectedInstances(
|
|
724
|
+
masterId: string,
|
|
725
|
+
instances: ComponentInstance[]
|
|
726
|
+
): ComponentInstance[] {
|
|
727
|
+
return instances.filter(
|
|
728
|
+
(instance) =>
|
|
729
|
+
instance.masterComponentId === masterId && !instance.isDetached
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ============================================================
|
|
734
|
+
// Override Creation Helpers
|
|
735
|
+
// ============================================================
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Create a text override for an element.
|
|
739
|
+
*
|
|
740
|
+
* @param targetElementId - The target element ID
|
|
741
|
+
* @param text - The new text content
|
|
742
|
+
* @returns A ComponentOverride object (without id)
|
|
743
|
+
*/
|
|
744
|
+
export function createTextOverride(
|
|
745
|
+
targetElementId: string,
|
|
746
|
+
text: string
|
|
747
|
+
): Omit<ComponentOverride, 'id'> {
|
|
748
|
+
return {
|
|
749
|
+
targetElementId,
|
|
750
|
+
type: 'text',
|
|
751
|
+
value: text,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Create a fill (background) override for an element.
|
|
757
|
+
*
|
|
758
|
+
* @param targetElementId - The target element ID
|
|
759
|
+
* @param color - The new background color
|
|
760
|
+
* @returns A ComponentOverride object (without id)
|
|
761
|
+
*/
|
|
762
|
+
export function createFillOverride(
|
|
763
|
+
targetElementId: string,
|
|
764
|
+
color: string
|
|
765
|
+
): Omit<ComponentOverride, 'id'> {
|
|
766
|
+
return {
|
|
767
|
+
targetElementId,
|
|
768
|
+
type: 'fill',
|
|
769
|
+
value: color,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Create a stroke (border) override for an element.
|
|
775
|
+
*
|
|
776
|
+
* @param targetElementId - The target element ID
|
|
777
|
+
* @param color - The new border color
|
|
778
|
+
* @returns A ComponentOverride object (without id)
|
|
779
|
+
*/
|
|
780
|
+
export function createStrokeOverride(
|
|
781
|
+
targetElementId: string,
|
|
782
|
+
color: string
|
|
783
|
+
): Omit<ComponentOverride, 'id'> {
|
|
784
|
+
return {
|
|
785
|
+
targetElementId,
|
|
786
|
+
type: 'stroke',
|
|
787
|
+
value: color,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Create a visibility override for an element.
|
|
793
|
+
*
|
|
794
|
+
* @param targetElementId - The target element ID
|
|
795
|
+
* @param visible - Whether the element should be visible
|
|
796
|
+
* @returns A ComponentOverride object (without id)
|
|
797
|
+
*/
|
|
798
|
+
export function createVisibilityOverride(
|
|
799
|
+
targetElementId: string,
|
|
800
|
+
visible: boolean
|
|
801
|
+
): Omit<ComponentOverride, 'id'> {
|
|
802
|
+
return {
|
|
803
|
+
targetElementId,
|
|
804
|
+
type: 'visibility',
|
|
805
|
+
value: visible,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Create an image override for an element.
|
|
811
|
+
*
|
|
812
|
+
* @param targetElementId - The target element ID
|
|
813
|
+
* @param url - The new image URL
|
|
814
|
+
* @param alt - Optional alt text
|
|
815
|
+
* @returns A ComponentOverride object (without id)
|
|
816
|
+
*/
|
|
817
|
+
export function createImageOverride(
|
|
818
|
+
targetElementId: string,
|
|
819
|
+
url: string,
|
|
820
|
+
alt?: string
|
|
821
|
+
): Omit<ComponentOverride, 'id'> {
|
|
822
|
+
return {
|
|
823
|
+
targetElementId,
|
|
824
|
+
type: 'image',
|
|
825
|
+
value: { url, alt },
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Create a style override for an element.
|
|
831
|
+
*
|
|
832
|
+
* @param targetElementId - The target element ID
|
|
833
|
+
* @param styles - The style properties to override
|
|
834
|
+
* @returns A ComponentOverride object (without id)
|
|
835
|
+
*/
|
|
836
|
+
export function createStyleOverride(
|
|
837
|
+
targetElementId: string,
|
|
838
|
+
styles: Record<string, string>
|
|
839
|
+
): Omit<ComponentOverride, 'id'> {
|
|
840
|
+
return {
|
|
841
|
+
targetElementId,
|
|
842
|
+
type: 'style',
|
|
843
|
+
value: styles,
|
|
844
|
+
};
|
|
845
|
+
}
|