@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,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant Resolver Utilities
|
|
3
|
+
*
|
|
4
|
+
* Utilities for resolving and managing component variants.
|
|
5
|
+
* Supports Figma-like variant properties with multi-dimensional combinations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
MasterComponent,
|
|
10
|
+
VariantProperty,
|
|
11
|
+
VariantDefinition,
|
|
12
|
+
} from '../../types/editor-components';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Find a variant by its property values.
|
|
16
|
+
* Matches variants where all specified property values match.
|
|
17
|
+
*
|
|
18
|
+
* @param master - The master component containing variants
|
|
19
|
+
* @param propertyValues - Object mapping property names to values
|
|
20
|
+
* @returns The matching variant definition or null if not found
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const variant = findVariant(master, { size: 'large', state: 'hover' });
|
|
24
|
+
*/
|
|
25
|
+
export function findVariant(
|
|
26
|
+
master: MasterComponent,
|
|
27
|
+
propertyValues: Record<string, string>
|
|
28
|
+
): VariantDefinition | null {
|
|
29
|
+
if (!master.variants || master.variants.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const targetKey = createVariantKey(propertyValues);
|
|
34
|
+
|
|
35
|
+
for (const variant of master.variants) {
|
|
36
|
+
if (variant.propertyValues) {
|
|
37
|
+
const variantKey = createVariantKey(variant.propertyValues);
|
|
38
|
+
if (variantKey === targetKey) {
|
|
39
|
+
return variant;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Find a variant by its unique ID.
|
|
49
|
+
*
|
|
50
|
+
* @param master - The master component containing variants
|
|
51
|
+
* @param variantId - The unique variant ID to find
|
|
52
|
+
* @returns The variant definition or null if not found
|
|
53
|
+
*/
|
|
54
|
+
export function getVariantById(
|
|
55
|
+
master: MasterComponent,
|
|
56
|
+
variantId: string
|
|
57
|
+
): VariantDefinition | null {
|
|
58
|
+
if (!master.variants) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return master.variants.find((v) => v.id === variantId) ?? null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the default variant for a master component.
|
|
67
|
+
* Falls back to first variant if no explicit default is set.
|
|
68
|
+
*
|
|
69
|
+
* @param master - The master component
|
|
70
|
+
* @returns The default variant definition or null if no variants exist
|
|
71
|
+
*/
|
|
72
|
+
export function getDefaultVariant(
|
|
73
|
+
master: MasterComponent
|
|
74
|
+
): VariantDefinition | null {
|
|
75
|
+
if (!master.variants || master.variants.length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// First, try to find by defaultVariantId
|
|
80
|
+
if (master.defaultVariantId) {
|
|
81
|
+
const defaultById = master.variants.find(
|
|
82
|
+
(v) => v.id === master.defaultVariantId
|
|
83
|
+
);
|
|
84
|
+
if (defaultById) {
|
|
85
|
+
return defaultById;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Second, try to find by isDefault flag
|
|
90
|
+
const defaultByFlag = master.variants.find((v) => v.isDefault);
|
|
91
|
+
if (defaultByFlag) {
|
|
92
|
+
return defaultByFlag;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Third, try to find by matching default property values
|
|
96
|
+
if (master.variantProperties && master.variantProperties.length > 0) {
|
|
97
|
+
const defaultPropertyValues: Record<string, string> = {};
|
|
98
|
+
for (const prop of master.variantProperties) {
|
|
99
|
+
defaultPropertyValues[prop.name] = prop.defaultValue;
|
|
100
|
+
}
|
|
101
|
+
const defaultByProps = findVariant(master, defaultPropertyValues);
|
|
102
|
+
if (defaultByProps) {
|
|
103
|
+
return defaultByProps;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fallback to first variant
|
|
108
|
+
return master.variants[0];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generate all possible variant combinations from variant properties.
|
|
113
|
+
* Creates a Cartesian product of all property values.
|
|
114
|
+
*
|
|
115
|
+
* @param variantProperties - Array of variant property definitions
|
|
116
|
+
* @returns Array of property value combinations
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const properties = [
|
|
120
|
+
* { name: 'size', values: ['sm', 'md', 'lg'] },
|
|
121
|
+
* { name: 'state', values: ['default', 'hover'] }
|
|
122
|
+
* ];
|
|
123
|
+
* const combinations = generateVariantCombinations(properties);
|
|
124
|
+
* // Returns:
|
|
125
|
+
* // [
|
|
126
|
+
* // { size: 'sm', state: 'default' },
|
|
127
|
+
* // { size: 'sm', state: 'hover' },
|
|
128
|
+
* // { size: 'md', state: 'default' },
|
|
129
|
+
* // { size: 'md', state: 'hover' },
|
|
130
|
+
* // { size: 'lg', state: 'default' },
|
|
131
|
+
* // { size: 'lg', state: 'hover' }
|
|
132
|
+
* // ]
|
|
133
|
+
*/
|
|
134
|
+
export function generateVariantCombinations(
|
|
135
|
+
variantProperties: VariantProperty[]
|
|
136
|
+
): Record<string, string>[] {
|
|
137
|
+
if (!variantProperties || variantProperties.length === 0) {
|
|
138
|
+
return [{}];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Start with an empty combination
|
|
142
|
+
let combinations: Record<string, string>[] = [{}];
|
|
143
|
+
|
|
144
|
+
// For each property, expand all existing combinations
|
|
145
|
+
for (const property of variantProperties) {
|
|
146
|
+
const newCombinations: Record<string, string>[] = [];
|
|
147
|
+
|
|
148
|
+
for (const combination of combinations) {
|
|
149
|
+
for (const value of property.values) {
|
|
150
|
+
newCombinations.push({
|
|
151
|
+
...combination,
|
|
152
|
+
[property.name]: value,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
combinations = newCombinations;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return combinations;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create a variant key from property values for indexing/lookup.
|
|
165
|
+
* Keys are deterministic (sorted alphabetically by property name).
|
|
166
|
+
*
|
|
167
|
+
* @param propertyValues - Object mapping property names to values
|
|
168
|
+
* @returns A string key representing the property values
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* createVariantKey({ size: 'lg', state: 'hover' }); // 'size:lg|state:hover'
|
|
172
|
+
*/
|
|
173
|
+
export function createVariantKey(
|
|
174
|
+
propertyValues: Record<string, string>
|
|
175
|
+
): string {
|
|
176
|
+
if (!propertyValues || Object.keys(propertyValues).length === 0) {
|
|
177
|
+
return '';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return Object.entries(propertyValues)
|
|
181
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
182
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
183
|
+
.join('|');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Parse a variant key back to property values.
|
|
188
|
+
*
|
|
189
|
+
* @param key - The variant key string to parse
|
|
190
|
+
* @returns Object mapping property names to values
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* parseVariantKey('size:lg|state:hover'); // { size: 'lg', state: 'hover' }
|
|
194
|
+
*/
|
|
195
|
+
export function parseVariantKey(key: string): Record<string, string> {
|
|
196
|
+
if (!key) {
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return Object.fromEntries(
|
|
201
|
+
key.split('|').map((pair) => {
|
|
202
|
+
const colonIndex = pair.indexOf(':');
|
|
203
|
+
if (colonIndex === -1) {
|
|
204
|
+
return [pair, ''];
|
|
205
|
+
}
|
|
206
|
+
return [pair.slice(0, colonIndex), pair.slice(colonIndex + 1)];
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Check if a variant exists for the given property values.
|
|
213
|
+
*
|
|
214
|
+
* @param master - The master component containing variants
|
|
215
|
+
* @param propertyValues - Object mapping property names to values
|
|
216
|
+
* @returns True if a variant with these property values exists
|
|
217
|
+
*/
|
|
218
|
+
export function variantExists(
|
|
219
|
+
master: MasterComponent,
|
|
220
|
+
propertyValues: Record<string, string>
|
|
221
|
+
): boolean {
|
|
222
|
+
return findVariant(master, propertyValues) !== null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create a Map of variants indexed by their variant key.
|
|
227
|
+
* Useful for fast lookups when rendering variant matrices.
|
|
228
|
+
*
|
|
229
|
+
* @param variants - Array of variant definitions
|
|
230
|
+
* @returns Map from variant key to variant definition
|
|
231
|
+
*/
|
|
232
|
+
export function createVariantMap(
|
|
233
|
+
variants: VariantDefinition[]
|
|
234
|
+
): Map<string, VariantDefinition> {
|
|
235
|
+
const map = new Map<string, VariantDefinition>();
|
|
236
|
+
|
|
237
|
+
for (const variant of variants) {
|
|
238
|
+
if (variant.propertyValues) {
|
|
239
|
+
const key = createVariantKey(variant.propertyValues);
|
|
240
|
+
map.set(key, variant);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return map;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get the count of missing variants (combinations without definitions).
|
|
249
|
+
*
|
|
250
|
+
* @param variantProperties - Array of variant property definitions
|
|
251
|
+
* @param variants - Array of existing variant definitions
|
|
252
|
+
* @returns Number of missing variant combinations
|
|
253
|
+
*/
|
|
254
|
+
export function getMissingVariantCount(
|
|
255
|
+
variantProperties: VariantProperty[],
|
|
256
|
+
variants: VariantDefinition[]
|
|
257
|
+
): number {
|
|
258
|
+
const allCombinations = generateVariantCombinations(variantProperties);
|
|
259
|
+
const variantMap = createVariantMap(variants);
|
|
260
|
+
|
|
261
|
+
let missingCount = 0;
|
|
262
|
+
for (const combination of allCombinations) {
|
|
263
|
+
const key = createVariantKey(combination);
|
|
264
|
+
if (!variantMap.has(key)) {
|
|
265
|
+
missingCount++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return missingCount;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Validate that a variant's property values match the component's variant properties.
|
|
274
|
+
*
|
|
275
|
+
* @param variantProperties - Array of variant property definitions
|
|
276
|
+
* @param propertyValues - Property values to validate
|
|
277
|
+
* @returns Object with isValid boolean and optional error message
|
|
278
|
+
*/
|
|
279
|
+
export function validateVariantPropertyValues(
|
|
280
|
+
variantProperties: VariantProperty[],
|
|
281
|
+
propertyValues: Record<string, string>
|
|
282
|
+
): { isValid: boolean; error?: string } {
|
|
283
|
+
// Check all required properties are present
|
|
284
|
+
for (const prop of variantProperties) {
|
|
285
|
+
if (!(prop.name in propertyValues)) {
|
|
286
|
+
return {
|
|
287
|
+
isValid: false,
|
|
288
|
+
error: `Missing required property: ${prop.name}`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const value = propertyValues[prop.name];
|
|
293
|
+
if (!prop.values.includes(value)) {
|
|
294
|
+
return {
|
|
295
|
+
isValid: false,
|
|
296
|
+
error: `Invalid value '${value}' for property '${prop.name}'. Valid values: ${prop.values.join(', ')}`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check for extra properties
|
|
302
|
+
const validPropertyNames = new Set(variantProperties.map((p) => p.name));
|
|
303
|
+
for (const propName of Object.keys(propertyValues)) {
|
|
304
|
+
if (!validPropertyNames.has(propName)) {
|
|
305
|
+
return {
|
|
306
|
+
isValid: false,
|
|
307
|
+
error: `Unknown property: ${propName}`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return { isValid: true };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Generate a human-readable name for a variant based on its property values.
|
|
317
|
+
*
|
|
318
|
+
* @param propertyValues - Object mapping property names to values
|
|
319
|
+
* @returns A formatted variant name
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* generateVariantName({ size: 'lg', state: 'hover' }); // 'Size=lg, State=hover'
|
|
323
|
+
*/
|
|
324
|
+
export function generateVariantName(
|
|
325
|
+
propertyValues: Record<string, string>
|
|
326
|
+
): string {
|
|
327
|
+
if (!propertyValues || Object.keys(propertyValues).length === 0) {
|
|
328
|
+
return 'Default';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return Object.entries(propertyValues)
|
|
332
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
333
|
+
.map(([key, value]) => `${capitalize(key)}=${value}`)
|
|
334
|
+
.join(', ');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Capitalize the first letter of a string.
|
|
339
|
+
*/
|
|
340
|
+
function capitalize(str: string): string {
|
|
341
|
+
if (!str) return str;
|
|
342
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Find the closest matching variant when exact match is not found.
|
|
347
|
+
* Useful for graceful degradation when a specific variant combination doesn't exist.
|
|
348
|
+
*
|
|
349
|
+
* @param master - The master component containing variants
|
|
350
|
+
* @param targetPropertyValues - The desired property values
|
|
351
|
+
* @returns The closest matching variant or default variant
|
|
352
|
+
*/
|
|
353
|
+
export function findClosestVariant(
|
|
354
|
+
master: MasterComponent,
|
|
355
|
+
targetPropertyValues: Record<string, string>
|
|
356
|
+
): VariantDefinition | null {
|
|
357
|
+
// First, try exact match
|
|
358
|
+
const exactMatch = findVariant(master, targetPropertyValues);
|
|
359
|
+
if (exactMatch) {
|
|
360
|
+
return exactMatch;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!master.variants || master.variants.length === 0) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Score each variant by how many properties match
|
|
368
|
+
let bestMatch: VariantDefinition | null = null;
|
|
369
|
+
let bestScore = -1;
|
|
370
|
+
|
|
371
|
+
for (const variant of master.variants) {
|
|
372
|
+
if (!variant.propertyValues) continue;
|
|
373
|
+
|
|
374
|
+
let score = 0;
|
|
375
|
+
for (const [key, value] of Object.entries(targetPropertyValues)) {
|
|
376
|
+
if (variant.propertyValues[key] === value) {
|
|
377
|
+
score++;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (score > bestScore) {
|
|
382
|
+
bestScore = score;
|
|
383
|
+
bestMatch = variant;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// If no matches at all, return default
|
|
388
|
+
if (bestScore === 0) {
|
|
389
|
+
return getDefaultVariant(master);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return bestMatch;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Get all unique values for a specific property across all variants.
|
|
397
|
+
*
|
|
398
|
+
* @param variants - Array of variant definitions
|
|
399
|
+
* @param propertyName - The property name to extract values for
|
|
400
|
+
* @returns Array of unique values
|
|
401
|
+
*/
|
|
402
|
+
export function getPropertyValuesFromVariants(
|
|
403
|
+
variants: VariantDefinition[],
|
|
404
|
+
propertyName: string
|
|
405
|
+
): string[] {
|
|
406
|
+
const values = new Set<string>();
|
|
407
|
+
|
|
408
|
+
for (const variant of variants) {
|
|
409
|
+
if (variant.propertyValues && variant.propertyValues[propertyName]) {
|
|
410
|
+
values.add(variant.propertyValues[propertyName]);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return Array.from(values);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Infer variant properties from existing variants.
|
|
419
|
+
* Useful when variants exist but properties aren't explicitly defined.
|
|
420
|
+
*
|
|
421
|
+
* @param variants - Array of variant definitions
|
|
422
|
+
* @returns Array of inferred variant properties
|
|
423
|
+
*/
|
|
424
|
+
export function inferVariantProperties(
|
|
425
|
+
variants: VariantDefinition[]
|
|
426
|
+
): VariantProperty[] {
|
|
427
|
+
const propertyMap = new Map<string, Set<string>>();
|
|
428
|
+
|
|
429
|
+
// Collect all property names and their values
|
|
430
|
+
for (const variant of variants) {
|
|
431
|
+
if (variant.propertyValues) {
|
|
432
|
+
for (const [key, value] of Object.entries(variant.propertyValues)) {
|
|
433
|
+
if (!propertyMap.has(key)) {
|
|
434
|
+
propertyMap.set(key, new Set());
|
|
435
|
+
}
|
|
436
|
+
propertyMap.get(key)!.add(value);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Convert to VariantProperty array
|
|
442
|
+
const properties: VariantProperty[] = [];
|
|
443
|
+
let index = 0;
|
|
444
|
+
|
|
445
|
+
for (const [name, valuesSet] of propertyMap) {
|
|
446
|
+
const values = Array.from(valuesSet).sort();
|
|
447
|
+
properties.push({
|
|
448
|
+
id: `prop-${index++}`,
|
|
449
|
+
name,
|
|
450
|
+
values,
|
|
451
|
+
defaultValue: values[0] ?? 'default',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return properties.sort((a, b) => a.name.localeCompare(b.name));
|
|
456
|
+
}
|