@@ -27,6 +27,9 @@ const nodeNameCounters: Map<string, number> = new Map();
2727
2828const variableCache = new Map < string , string > ( ) ;
2929
30+ /**
31+ * Maps variable IDs to color names and caches the result
32+ */
3033const memoizedVariableToColorName = async (
3134 variableId : string ,
3235) : Promise < string > => {
@@ -41,6 +44,98 @@ const memoizedVariableToColorName = async (
4144 return variableCache . get ( variableId ) ! ;
4245} ;
4346
47+ /**
48+ * Maps a color hex value to its variable name using node-specific color mappings
49+ */
50+ export const getVariableNameFromColor = (
51+ hexColor : string ,
52+ colorMappings ?: Map < string , { variableId : string ; variableName : string } > ,
53+ ) : string | undefined => {
54+ if ( ! colorMappings ) return undefined ;
55+
56+ const normalizedColor = hexColor . toLowerCase ( ) ;
57+ const mapping = colorMappings . get ( normalizedColor ) ;
58+
59+ if ( mapping ) {
60+ return mapping . variableName ;
61+ }
62+
63+ return undefined ;
64+ } ;
65+
66+ /**
67+ * Collects all color variables used in a node and its descendants
68+ */
69+ const collectNodeColorVariables = async (
70+ node : any ,
71+ ) : Promise < Map < string , { variableId : string ; variableName : string } > > => {
72+ const colorMappings = new Map <
73+ string ,
74+ { variableId : string ; variableName : string }
75+ > ( ) ;
76+
77+ // Helper function to add a mapping from a paint object
78+ const addMappingFromPaint = ( paint : any ) => {
79+ // Ensure we have a solid paint, a resolved variable name, and color data
80+ if (
81+ paint . type === "SOLID" &&
82+ paint . variableColorName &&
83+ paint . color &&
84+ paint . boundVariables ?. color
85+ ) {
86+ // Prefer the actual variable name from the bound variable if available
87+ const variableName = paint . boundVariables . color . name || paint . variableColorName ;
88+
89+ if ( variableName ) {
90+ const colorInfo = {
91+ variableId : paint . boundVariables . color . id ,
92+ variableName : variableName . replace ( / [ ^ a - z A - Z 0 - 9 _ - ] / g, '-' ) , // Sanitize variable name for CSS
93+ } ;
94+
95+ // Create hex representation of the color
96+ const r = Math . round ( paint . color . r * 255 ) ;
97+ const g = Math . round ( paint . color . g * 255 ) ;
98+ const b = Math . round ( paint . color . b * 255 ) ;
99+
100+ // Standard hex format for the color mapping
101+ const hexColor = `#${ r . toString ( 16 ) . padStart ( 2 , '0' ) } ${ g . toString ( 16 ) . padStart ( 2 , '0' ) } ${ b . toString ( 16 ) . padStart ( 2 , '0' ) } ` . toLowerCase ( ) ;
102+ colorMappings . set ( hexColor , colorInfo ) ;
103+
104+ // Also add named color equivalent if it matches standard colors
105+ if ( r === 255 && g === 255 && b === 255 ) {
106+ colorMappings . set ( 'white' , colorInfo ) ; // Add "white" mapping
107+ }
108+ else if ( r === 0 && g === 0 && b === 0 ) {
109+ colorMappings . set ( 'black' , colorInfo ) ; // Add "black" mapping
110+ }
111+ }
112+ }
113+ } ;
114+
115+ // Process fills
116+ if ( node . fills && Array . isArray ( node . fills ) ) {
117+ node . fills . forEach ( addMappingFromPaint ) ;
118+ }
119+
120+ // Process strokes
121+ if ( node . strokes && Array . isArray ( node . strokes ) ) {
122+ node . strokes . forEach ( addMappingFromPaint ) ;
123+ }
124+
125+ // Process children recursively
126+ if ( node . children && Array . isArray ( node . children ) ) {
127+ for ( const child of node . children ) {
128+ const childMappings = await collectNodeColorVariables ( child ) ;
129+ // Merge child mappings with this node's mappings
130+ childMappings . forEach ( ( value , key ) => {
131+ colorMappings . set ( key , value ) ;
132+ } ) ;
133+ }
134+ }
135+
136+ return colorMappings ;
137+ } ;
138+
44139/**
45140 * Process color variables in a paint style and add pre-computed variable names
46141 * @param paint The paint style to process (fill or stroke)
@@ -376,7 +471,14 @@ const processNodePair = async (
376471
377472 // Add canBeFlattened property
378473 if ( settings . embedVectors && ! parentNode ?. canBeFlattened ) {
379- ( jsonNode as any ) . canBeFlattened = isLikelyIcon ( jsonNode as any ) ;
474+ const isIcon = isLikelyIcon ( jsonNode as any ) ;
475+ ( jsonNode as any ) . canBeFlattened = isIcon ;
476+
477+ // If this node will be flattened to SVG, collect its color variables
478+ if ( isIcon && settings . useColorVariables ) {
479+ // Schedule color mapping collection after variable processing
480+ ( jsonNode as any ) . _collectColorMappings = true ;
481+ }
380482 } else {
381483 ( jsonNode as any ) . canBeFlattened = false ;
382484 }
@@ -496,6 +598,13 @@ const processNodePair = async (
496598 adjustChildrenOrder ( jsonNode ) ;
497599 }
498600
601+ // Collect color variables for SVG nodes after all processing is done
602+ if ( ( jsonNode as any ) . _collectColorMappings ) {
603+ ( jsonNode as any ) . colorVariableMappings =
604+ await collectNodeColorVariables ( jsonNode ) ;
605+ delete ( jsonNode as any ) . _collectColorMappings ;
606+ }
607+
499608 return jsonNode ;
500609} ;
501610
0 commit comments