svgfusion 1.12.0 → 1.14.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/README.md CHANGED
@@ -16,37 +16,33 @@ A powerful Node.js CLI tool and library that converts SVG files into optimized R
16
16
 
17
17
  </div>
18
18
 
19
- ## What's New in v2.0
19
+ ## What's New in v1.12.0
20
20
 
21
- - **Custom SVGFusion Engine**: Complete rewrite with custom SVG parser replacing SVGR/SVGO
21
+ - **Enhanced Color Splitting**: Smart color extraction with intelligent fill/stroke="none" handling
22
+ - **Improved CLI**: Streamlined command structure with better option handling
22
23
  - **Native SVG Props**: Full React.SVGProps and Vue SVGAttributes support
23
24
  - **Enhanced Type Safety**: Better TypeScript integration and type inference
24
- - **Improved Performance**: Faster processing with streamlined architecture
25
+ - **SVGFusion Engine**: Built-in SVG parser with reliable transformations
25
26
  - **Better Control**: Fine-grained control over SVG transformations and output
26
27
 
27
28
  ## Features
28
29
 
29
- - **Native SVG Props**: Generated components extend React.SVGProps<SVGSVGElement> and Vue SVGAttributes
30
- - **Dual Framework Support**: Generate both React and Vue 3 components from the same SVG
31
- - **Custom Engine**: Built-in SVGFusion engine for reliable SVG parsing and transformation
32
- - **Complex SVG Support**: Handles gradients, masks, filters, patterns, and Figma exports
33
- - **ID Collision Prevention**: Automatic unique ID generation for complex SVGs
34
- - **Optimized Output**: Smart optimization with customizable settings
35
- - **Icon Builder Ready**: Perfect for design systems and icon libraries
36
- - **TypeScript Ready**: Full TypeScript support with proper type definitions
37
- - **Flexible API**: Both CLI and programmatic usage
38
- - **Batch Processing**: Convert entire directories of SVG files
39
- - **Production Ready**: Robust output with proper error handling
40
- - **Zero Configuration**: Works out of the box with sensible defaults
41
- - **Simple CLI**: Direct, intuitive command structure without subcommands
42
-
43
- ### Advanced Features
44
-
45
- - **Split Colors Mode**: Extract individual color props for maximum Tailwind CSS compatibility
46
- - **Fixed Stroke Width**: Add `vector-effect="non-scaling-stroke"` support for consistent strokes
47
- - **Duplicate Detection**: Automatic validation prevents component name conflicts
48
- - **Gradient Support**: Full support for linearGradient and radialGradient color extraction
49
- - **Smart Naming**: Intelligent component naming with conflict resolution suggestions
30
+ **Native SVG Props**: Generated components extend React.SVGProps<SVGSVGElement> and Vue SVGAttributes
31
+ **React & Vue Support**: Generate both React and Vue 3 components from the same SVG
32
+ **Complex SVG Support**: Handles gradients, masks, filters, patterns, and Figma exports
33
+ **TypeScript Ready**: Full TypeScript support with proper type definitions
34
+ **Batch Processing**: Convert entire directories of SVG files
35
+ **Production Ready**: Optimized output, error handling, and accessibility
36
+ **Simple CLI**: Direct, intuitive command structure
37
+
38
+ ### Color Splitting (splitColors)
39
+
40
+ Extracts all unique fill, stroke, and gradient colors from your SVG and generates props for each color and color class. Automatically adds `fill="none"` or `stroke="none"` to prevent unwanted browser defaults:
41
+
42
+ - Path with only fill gets `stroke="none"`
43
+ - Path with only stroke → gets `fill="none"`
44
+ - Path with both → keeps both as props
45
+ - Path with neither → stays unchanged
50
46
 
51
47
  ## Quick Start
52
48
 
@@ -127,34 +123,53 @@ svgfusion ./icons --output ./components --recursive
127
123
  # Generate index file for tree-shaking
128
124
  svgfusion ./icons --output ./components --index
129
125
 
130
- # Skip optimization
131
- svgfusion ./icons --output ./components --no-optimize
126
+ # Advanced: Split colors for maximum customization
127
+ svgfusion ./icons --output ./components --split-colors
128
+
129
+ # Advanced: Fixed stroke width with split colors
130
+ svgfusion ./icons --output ./components --split-colors --fixed-stroke-width
132
131
 
133
132
  # Using npx (no global install needed)
134
133
  npx svgfusion ./icons --output ./components --framework react
135
134
  ```
136
135
 
137
- ### CLI Options
136
+ ### Available CLI Options
138
137
 
139
138
  ```bash
140
139
  svgfusion <input> [options]
141
140
 
141
+ Arguments:
142
+ <input> SVG file or directory to convert
143
+
142
144
  Options:
143
- -o, --output <output> Output directory (default: "./components")
144
- -f, --framework <framework> Target framework (react|vue) (default: "react")
145
- -t, --typescript Generate TypeScript files
146
- -r, --recursive Recursively scan input directory for SVG files
147
- --index Generate index file for tree-shaking
148
- --index-format <format> Index file format (ts|js) (default: "ts")
149
- --export-type <type> Export type (named|default) (default: "named")
150
- --no-optimize Skip SVG optimization
151
- --prefix <prefix> Add prefix to component name (sanitized)
152
- --suffix <suffix> Add suffix to component name (sanitized)
153
- --split-colors Extract individual color props for each SVG color
154
- --fixed-stroke-width Add support for non-scaling stroke width
145
+ -o, --output <dir> Output directory for generated components (default: "./components")
146
+ -f, --framework <framework> Target framework: react or vue (default: "react")
147
+ --typescript Generate TypeScript components (default: true)
148
+ --javascript Generate JavaScript components
149
+ --split-colors Enable color splitting feature
150
+ --fixed-stroke-width Enable fixed stroke width feature
151
+ --memo Wrap component with React.memo (default: true)
152
+ --no-memo Disable React.memo wrapping
153
+ --forward-ref Enable forwardRef support (default: true)
154
+ --no-forward-ref Disable forwardRef support
155
+ -n, --name <name> Custom component name
156
+ --optimize Enable SVG optimization (default: true)
157
+ --no-optimize Disable SVG optimization
158
+ --recursive Process directories recursively
159
+ --prefix <prefix> Add prefix to component names
160
+ --suffix <suffix> Add suffix to component names
161
+ --index Generate index.ts file for directory processing
155
162
  -h, --help Show help
156
163
  ```
157
164
 
165
+ --prefix <prefix> Add prefix to component name (sanitized)
166
+ --suffix <suffix> Add suffix to component name (sanitized)
167
+ --split-colors Extract individual color props for each SVG color
168
+ --fixed-stroke-width Add support for non-scaling stroke width
169
+ -h, --help Show help
170
+
171
+ ````
172
+
158
173
  ### Using with npx (No Installation Required)
159
174
 
160
175
  Perfect for trying out SVGFusion or one-time conversions:
@@ -180,170 +195,30 @@ npx svgfusion ./assets/icons --output ./src/components --split-colors --prefix I
180
195
 
181
196
  # Advanced: Fixed stroke width with split colors
182
197
  npx svgfusion ./assets/icons --output ./src/components --split-colors --fixed-stroke-width
183
- ```
198
+ ````
184
199
 
185
200
  ### Programmatic Usage
186
201
 
187
202
  #### Single File Conversion
188
203
 
189
204
  ```typescript
190
- import { SVGFusion, readFileSync } from 'svgfusion';
205
+ import { SVGFusion } from 'svgfusion';
191
206
 
192
- // Create SVGFusion engine instance
193
207
  const engine = new SVGFusion();
208
+ const svgContent = `<svg viewBox="0 0 24 24"><path fill="#FF0000" stroke="#00FF00" d="..."/></svg>`;
194
209
 
195
- // Read SVG file
196
- const svgContent = readFileSync('./icons/star.svg', 'utf8');
197
-
198
- // React conversion with native SVG props
199
- const reactResult = engine.convert(svgContent, {
200
- framework: 'react',
201
- componentName: 'StarIcon',
202
- typescript: true,
203
- features: {
204
- colorSplitting: true,
205
- strokeFixing: true,
206
- accessibility: true,
207
- },
208
- });
209
-
210
- // Vue conversion with native SVG attributes
211
- const vueResult = engine.convert(svgContent, {
212
- framework: 'vue',
213
- componentName: 'StarIcon',
214
- typescript: true,
215
- features: {
216
- colorSplitting: true,
217
- strokeFixing: true,
218
- accessibility: true,
219
- },
210
+ const result = await engine.convert(svgContent, {
211
+ framework: 'react', // or 'vue'
212
+ transformation: { splitColors: true },
213
+ generator: { componentName: 'MyIcon', typescript: true },
220
214
  });
221
215
 
222
- console.log(reactResult.code); // Generated React component with React.SVGProps
223
- console.log(vueResult.code); // Generated Vue component with SVGAttributes
224
- ```
225
-
226
- #### Batch Processing with CLI
227
-
228
- ```typescript
229
- import { processInput } from 'svgfusion/cli';
230
-
231
- // Convert entire directory
232
- processInput('./icons', {
233
- output: './components',
234
- framework: 'react',
235
- recursive: true,
236
- index: true,
237
- typescript: true,
238
- prefix: 'Icon',
239
- suffix: 'Component',
240
- splitColors: true,
241
- fixedStrokeWidth: true,
242
- });
216
+ console.log(result.code); // The generated component code
243
217
  ```
244
218
 
245
219
  ## API Reference
246
220
 
247
- ### `SVGFusion`
248
-
249
- The main engine class for converting SVG content to framework components.
250
-
251
- ```typescript
252
- import { SVGFusion } from 'svgfusion';
253
-
254
- const engine = new SVGFusion();
255
- ```
256
-
257
- #### `convert(svgContent: string, options: SVGFusionOptions): ConversionResult`
258
-
259
- Convert SVG content to React or Vue component.
260
-
261
- **Options:**
262
-
263
- - `framework: 'react' | 'vue'` - Target framework
264
- - `componentName?: string` - Component name (auto-generated from filename if not provided)
265
- - `typescript?: boolean` - Generate TypeScript component (default: `true`)
266
- - `features?: FeatureConfig` - Enable/disable transformation features
267
- - `colorSplitting?: boolean` - Extract individual color props (default: `false`)
268
- - `strokeFixing?: boolean` - Add fixed stroke width support (default: `false`)
269
- - `accessibility?: boolean` - Add accessibility enhancements (default: `true`)
270
-
271
- **Returns:** `ConversionResult`
272
-
273
- - `code: string` - Generated component code
274
- - `framework: string` - Target framework
275
- - `componentName: string` - Final component name
276
- - `warnings: string[]` - Any conversion warnings
277
-
278
- ### React Components
279
-
280
- Generated React components automatically extend `React.SVGProps<SVGSVGElement>`, providing:
281
-
282
- - All native SVG element props (onClick, onMouseOver, className, style, etc.)
283
- - Full TypeScript support with proper type inference
284
- - Native event handling and accessibility features
285
-
286
- ```tsx
287
- // Generated component signature
288
- interface StarIconProps extends React.SVGProps<SVGSVGElement> {
289
- // Custom color props if colorSplitting is enabled
290
- color1?: string;
291
- color2?: string;
292
- }
293
-
294
- const StarIcon: React.FC<StarIconProps> = props => {
295
- // Component implementation with native SVG props support
296
- };
297
- ```
298
-
299
- ### Vue Components
300
-
301
- Generated Vue components extend `SVGAttributes` with `v-bind="$attrs"`, providing:
302
-
303
- - All native SVG element attributes
304
- - Event handlers (onClick, onMouseover, etc.)
305
- - Full TypeScript support with proper attribute typing
306
-
307
- ```vue
308
- <script setup lang="ts">
309
- import type { SVGAttributes } from 'vue';
310
-
311
- interface StarIconProps extends SVGAttributes {
312
- // Custom color props if colorSplitting is enabled
313
- color1?: string;
314
- color2?: string;
315
- }
316
-
317
- defineOptions({ inheritAttrs: false });
318
- </script>
319
-
320
- <template>
321
- <svg v-bind="$attrs" viewBox="0 0 24 24">
322
- <!-- SVG content -->
323
- </svg>
324
- </template>
325
- ```
326
-
327
- ### CLI Integration
328
-
329
- ```typescript
330
- import { processInput } from 'svgfusion/cli';
331
-
332
- // Process single file or directory
333
- processInput(inputPath: string, options: CliOptions): void
334
- ```
335
-
336
- **CLI Options:**
337
-
338
- - `output?: string` - Output directory (default: './components')
339
- - `framework?: 'react' | 'vue'` - Target framework (default: 'react')
340
- - `typescript?: boolean` - Generate TypeScript files (default: true)
341
- - `recursive?: boolean` - Process directories recursively (default: false)
342
- - `index?: boolean` - Generate index file (default: false)
343
- - `prefix?: string` - Add prefix to component names
344
- - `suffix?: string` - Add suffix to component names
345
- - `splitColors?: boolean` - Enable color splitting feature
346
- - `fixedStrokeWidth?: boolean` - Enable stroke fixing feature
221
+ For the complete API documentation, visit [svgfusion.netlify.app](https://svgfusion.netlify.app/docs/api-reference).
347
222
 
348
223
  ## Examples
349
224
 
@@ -472,20 +347,18 @@ defineOptions({ inheritAttrs: false });
472
347
 
473
348
  ## Advanced Configuration
474
349
 
475
- ### Split Colors for Tailwind CSS
350
+ ### Split Colors with Intelligent Attribute Handling
476
351
 
477
- The `splitColors` option extracts individual color properties from SVG elements, making them perfect for Tailwind CSS integration:
352
+ The `splitColors` option extracts individual color properties from SVG elements and intelligently manages fill/stroke attributes:
478
353
 
479
354
  ```typescript
480
- // Input SVG with colors
355
+ // Input SVG with mixed attributes
481
356
  const svgContent = `
482
357
  <svg viewBox="0 0 24 24">
483
- <path fill="#FF0000" stroke="#00FF00" />
484
- <circle fill="#0000FF" />
485
- <linearGradient>
486
- <stop stop-color="#FFFF00" />
487
- <stop stop-color="#FF00FF" />
488
- </linearGradient>
358
+ <path fill="#FF0000" d="..." /> <!-- Has fill only -->
359
+ <path stroke="#00FF00" d="..." /> <!-- Has stroke only -->
360
+ <path fill="#0000FF" stroke="#FFFF00" /> <!-- Has both -->
361
+ <path d="..." /> <!-- Has neither -->
489
362
  </svg>
490
363
  `;
491
364
 
@@ -496,11 +369,31 @@ const result = await convertToReact(svgContent, {
496
369
  typescript: true,
497
370
  });
498
371
 
372
+ // Generated component behavior:
373
+ // - Path with fill only: gets stroke="none" (prevents unwanted stroke)
374
+ // - Path with stroke only: gets fill="none" (prevents black fill default)
375
+ // - Path with both: keeps both as dynamic props
376
+ // - Path with neither: remains unchanged (no unnecessary attributes)
377
+
499
378
  // Generated component props:
500
- // - fillColor1, fillColor2 (for fill colors)
501
- // - strokeColor1 (for stroke colors)
502
- // - gradientColor1, gradientColor2 (for gradient colors)
503
- // - fillColor1Class, strokeColor1Class, etc. (for CSS classes)
379
+ // - color, color2, color3 (for extracted colors in order)
380
+ // - colorClass, color2Class, color3Class (for CSS classes)
381
+ // - Colors are automatically converted to hex format
382
+ ```
383
+
384
+ **Color Extraction Rules:**
385
+ - Colors are extracted from `fill`, `stroke`, and `stop-color` attributes
386
+ - All colors are converted to lowercase hex format (e.g., `#ff0000`)
387
+ - Empty or invalid color values are ignored
388
+ - Duplicate colors are automatically deduplicated
389
+ - Colors are assigned as `color`, `color2`, `color3`, etc. in order
390
+
391
+ **Intelligent Attribute Handling:**
392
+ - Elements with `fill` only → adds `stroke="none"`
393
+ - Elements with `stroke` only → adds `fill="none"`
394
+ - Elements with both `fill` and `stroke` → keeps both as props
395
+ - Elements with neither → no attributes added
396
+ - Empty attribute values (`fill=""`) are treated as non-existent
504
397
  ````
505
398
 
506
399
  ### Fixed Stroke Width Support
@@ -518,31 +411,6 @@ const result = await convertToReact(svgContent, {
518
411
  // - vector-effect="non-scaling-stroke" when prop is true
519
412
  ```
520
413
 
521
- ### Duplicate Name Detection
522
-
523
- The batch converter automatically detects and prevents duplicate component names:
524
-
525
- ```typescript
526
- // These files would generate the same component name "Icon"
527
- const files = ['./icons/icon.svg', './assets/icon.svg', './ui/icon.svg'];
528
-
529
- // Batch processing will throw an error with conflict details
530
- try {
531
- await batchConverter.convertBatch({
532
- inputDir: './icons',
533
- outputDir: './components',
534
- // ... other options
535
- });
536
- } catch (error) {
537
- console.error(error.message);
538
- // Error: Duplicate component names detected:
539
- // Component name "Icon" conflicts:
540
- // - ./icons/icon.svg
541
- // - ./assets/icon.svg
542
- // - ./ui/icon.svg
543
- }
544
- ```
545
-
546
414
  ### Custom SVGO Configuration
547
415
 
548
416
  ```typescript
@@ -557,25 +425,30 @@ const customConfig = createSvgoConfig({
557
425
  const optimizedSvg = optimizeSvg(svgContent, customConfig);
558
426
  ```
559
427
 
560
- ### Manual Batch Processing
428
+ ### Batch Processing with SVGFusion Engine
561
429
 
562
430
  ```typescript
563
- import {
564
- readSvgDirectory,
565
- convertToReact,
566
- writeComponentFile,
567
- } from 'svgfusion';
431
+ import { SVGFusion, readSvgDirectory } from 'svgfusion';
432
+ import { writeFileSync } from 'fs';
433
+ import path from 'path';
568
434
 
435
+ const engine = new SVGFusion();
569
436
  const svgFiles = await readSvgDirectory('./icons', true); // recursive
570
437
 
571
438
  for (const svgFile of svgFiles) {
572
- const svgContent = await readSvgFile(svgFile);
573
- const result = await convertToReact(svgContent, {
574
- name: path.basename(svgFile, '.svg'),
575
- prefix: 'Icon',
439
+ const svgContent = readFileSync(svgFile, 'utf-8');
440
+ const result = await engine.convert(svgContent, {
441
+ framework: 'react',
442
+ generator: {
443
+ componentName: path.basename(svgFile, '.svg'),
444
+ typescript: true,
445
+ },
446
+ transformation: {
447
+ splitColors: true,
448
+ },
576
449
  });
577
450
 
578
- await writeComponentFile(`./components/${result.filename}`, result.code);
451
+ writeFileSync(`./components/${result.filename}`, result.code);
579
452
  }
580
453
  ```
581
454
 
package/dist/cli.js CHANGED
@@ -80774,6 +80774,40 @@ var ColorSplittingFeature = class {
80774
80774
  newAttributes["stop-color"]
80775
80775
  )}}`;
80776
80776
  }
80777
+ if (newAttributes.style) {
80778
+ let updatedStyle = newAttributes.style;
80779
+ const fillMatch = updatedStyle.match(/fill:\s*([^;]+)/);
80780
+ if (fillMatch) {
80781
+ const fillColor = fillMatch[1].trim();
80782
+ if (colorMap.has(fillColor)) {
80783
+ updatedStyle = updatedStyle.replace(
80784
+ /fill:\s*[^;]+/,
80785
+ `fill: {${colorMap.get(fillColor)}}`
80786
+ );
80787
+ }
80788
+ }
80789
+ const strokeMatch = updatedStyle.match(/stroke:\s*([^;]+)/);
80790
+ if (strokeMatch) {
80791
+ const strokeColor = strokeMatch[1].trim();
80792
+ if (colorMap.has(strokeColor)) {
80793
+ updatedStyle = updatedStyle.replace(
80794
+ /stroke:\s*[^;]+/,
80795
+ `stroke: {${colorMap.get(strokeColor)}}`
80796
+ );
80797
+ }
80798
+ }
80799
+ const stopColorMatch = updatedStyle.match(/stop-color:\s*([^;]+)/);
80800
+ if (stopColorMatch) {
80801
+ const stopColor = stopColorMatch[1].trim();
80802
+ if (colorMap.has(stopColor)) {
80803
+ updatedStyle = updatedStyle.replace(
80804
+ /stop-color:\s*[^;]+/,
80805
+ `stop-color: {${colorMap.get(stopColor)}}`
80806
+ );
80807
+ }
80808
+ }
80809
+ newAttributes.style = updatedStyle;
80810
+ }
80777
80811
  if (this.isDrawableElement(el2.tag)) {
80778
80812
  const originalHadFill = el2.attributes.fill !== void 0 && el2.attributes.fill !== "";
80779
80813
  const originalHadStroke = el2.attributes.stroke !== void 0 && el2.attributes.stroke !== "";
@@ -80815,6 +80849,17 @@ var ColorSplittingFeature = class {
80815
80849
  attribute: "stop-color"
80816
80850
  });
80817
80851
  }
80852
+ if (element.attributes.style) {
80853
+ const styleColors = this.extractColorsFromStyle(element.attributes.style);
80854
+ styleColors.forEach((color) => {
80855
+ colors.push({
80856
+ value: color.value,
80857
+ type: color.type,
80858
+ element,
80859
+ attribute: "style"
80860
+ });
80861
+ });
80862
+ }
80818
80863
  element.children.forEach((child) => this.traverseElement(child, colors));
80819
80864
  }
80820
80865
  /**
@@ -80883,6 +80928,34 @@ var ColorSplittingFeature = class {
80883
80928
  ];
80884
80929
  return namedColors.includes(color.toLowerCase());
80885
80930
  }
80931
+ /**
80932
+ * Extract colors from style attribute
80933
+ */
80934
+ extractColorsFromStyle(style) {
80935
+ const colors = [];
80936
+ const fillMatch = style.match(/fill:\s*([^;]+)/);
80937
+ if (fillMatch) {
80938
+ const fillColor = fillMatch[1].trim();
80939
+ if (this.isValidColor(fillColor)) {
80940
+ colors.push({ value: fillColor, type: "fill" });
80941
+ }
80942
+ }
80943
+ const strokeMatch = style.match(/stroke:\s*([^;]+)/);
80944
+ if (strokeMatch) {
80945
+ const strokeColor = strokeMatch[1].trim();
80946
+ if (this.isValidColor(strokeColor)) {
80947
+ colors.push({ value: strokeColor, type: "stroke" });
80948
+ }
80949
+ }
80950
+ const stopColorMatch = style.match(/stop-color:\s*([^;]+)/);
80951
+ if (stopColorMatch) {
80952
+ const stopColor = stopColorMatch[1].trim();
80953
+ if (this.isValidColor(stopColor)) {
80954
+ colors.push({ value: stopColor, type: "stop-color" });
80955
+ }
80956
+ }
80957
+ return colors;
80958
+ }
80886
80959
  };
80887
80960
 
80888
80961
  // src/features/stroke-fixing.ts
@@ -80925,12 +80998,6 @@ var StrokeFixingFeature = class {
80925
80998
  if (this.options.preserveExisting && element.attributes["vector-effect"]) {
80926
80999
  return false;
80927
81000
  }
80928
- if (this.options.onlyIfStrokePresent) {
80929
- const hasStroke = element.attributes.stroke && element.attributes.stroke !== "none" && element.attributes.stroke !== "transparent";
80930
- if (!hasStroke) {
80931
- return false;
80932
- }
80933
- }
80934
81001
  const strokeElements = [
80935
81002
  "path",
80936
81003
  "line",
@@ -81113,7 +81180,8 @@ var SVGTransformer = class {
81113
81180
  */
81114
81181
  applyFixedStrokeWidth(ast) {
81115
81182
  const strokeFixingFeature = new StrokeFixingFeature({
81116
- onlyIfStrokePresent: true,
81183
+ onlyIfStrokePresent: false,
81184
+ // Apply to all stroke-capable elements for consistent scaling
81117
81185
  preserveExisting: true
81118
81186
  });
81119
81187
  const result = strokeFixingFeature.apply(ast.root);
@@ -101496,14 +101564,26 @@ ${childrenJsx}
101496
101564
  const hasOriginalClass = "class" in attributes || "className" in attributes;
101497
101565
  Object.entries(attributes).forEach(([key2, value]) => {
101498
101566
  const jsxKey = toReactProp(key2);
101499
- if ((key2 === "fill" || key2 === "stroke") && value.startsWith("{") && value.endsWith("}")) {
101567
+ if (key2 === "vector-effect" && value === "non-scaling-stroke") {
101568
+ jsxAttributes.push(
101569
+ `vectorEffect={isFixedStrokeWidth ? 'non-scaling-stroke' : undefined}`
101570
+ );
101571
+ } else if ((key2 === "fill" || key2 === "stroke") && value.startsWith("{") && value.endsWith("}")) {
101500
101572
  const colorVar = value.slice(1, -1);
101501
101573
  jsxAttributes.push(`${jsxKey}=${value}`);
101502
101574
  const classVar = `${colorVar}Class`;
101503
101575
  classNames.push(classVar);
101504
101576
  } else if (key2 === "style") {
101505
101577
  const styleObj = this.parseStyleString(value);
101506
- jsxAttributes.push(`style={${JSON.stringify(styleObj)}}`);
101578
+ const styleEntries = Object.entries(styleObj).map(([prop, val]) => {
101579
+ if (typeof val === "string" && val.startsWith("{") && val.endsWith("}")) {
101580
+ const varName = val.slice(1, -1);
101581
+ return `${prop}: ${varName}`;
101582
+ } else {
101583
+ return `${prop}: '${val}'`;
101584
+ }
101585
+ });
101586
+ jsxAttributes.push(`style={{ ${styleEntries.join(", ")} }}`);
101507
101587
  } else {
101508
101588
  if (value.startsWith("{") && value.endsWith("}")) {
101509
101589
  jsxAttributes.push(`${jsxKey}=${value}`);
@@ -101668,7 +101748,14 @@ ${children}
101668
101748
  const classVars = [];
101669
101749
  const hasOriginalClass = "class" in attributes;
101670
101750
  Object.entries(attributes).forEach(([key2, value]) => {
101671
- if (value.startsWith("{") && value.endsWith("}")) {
101751
+ if (key2 === "vector-effect" && value === "non-scaling-stroke") {
101752
+ vueAttributes.push(
101753
+ `:vector-effect="props.isFixedStrokeWidth ? 'non-scaling-stroke' : undefined"`
101754
+ );
101755
+ } else if (key2 === "style") {
101756
+ const parsedStyle = this.parseStyleStringForVue(value);
101757
+ vueAttributes.push(`:style="${parsedStyle}"`);
101758
+ } else if (value.startsWith("{") && value.endsWith("}")) {
101672
101759
  const variableName = value.slice(1, -1);
101673
101760
  vueAttributes.push(`:${key2}="props.${variableName}"`);
101674
101761
  if (key2 === "fill" || key2 === "stroke") {
@@ -101879,6 +101966,28 @@ ${children}
101879
101966
  ${className}?: string;`;
101880
101967
  }).join("\n");
101881
101968
  }
101969
+ /**
101970
+ * Parse style string for Vue template syntax
101971
+ */
101972
+ parseStyleStringForVue(styleStr) {
101973
+ const styleEntries = [];
101974
+ styleStr.split(";").forEach((declaration) => {
101975
+ const colonIndex = declaration.indexOf(":");
101976
+ if (colonIndex > 0) {
101977
+ const property = declaration.slice(0, colonIndex).trim();
101978
+ const value = declaration.slice(colonIndex + 1).trim();
101979
+ if (property && value) {
101980
+ if (value.startsWith("{") && value.endsWith("}")) {
101981
+ const variableName = value.slice(1, -1);
101982
+ styleEntries.push(`'${property}': props.${variableName}`);
101983
+ } else {
101984
+ styleEntries.push(`'${property}': '${value}'`);
101985
+ }
101986
+ }
101987
+ }
101988
+ });
101989
+ return `{ ${styleEntries.join(", ")} }`;
101990
+ }
101882
101991
  /**
101883
101992
  * Generate SVG root attributes for Vue template
101884
101993
  */
package/dist/index.d.mts CHANGED
@@ -348,6 +348,10 @@ declare class VueGenerator extends ComponentGenerator {
348
348
  * Generate TypeScript interface for color props
349
349
  */
350
350
  private generateColorPropsInterface;
351
+ /**
352
+ * Parse style string for Vue template syntax
353
+ */
354
+ private parseStyleStringForVue;
351
355
  /**
352
356
  * Generate SVG root attributes for Vue template
353
357
  */
@@ -458,6 +462,10 @@ declare class ColorSplittingFeature {
458
462
  * Check if a color value is valid and should be replaced
459
463
  */
460
464
  private isValidColor;
465
+ /**
466
+ * Extract colors from style attribute
467
+ */
468
+ private extractColorsFromStyle;
461
469
  }
462
470
 
463
471
  /**
package/dist/index.d.ts CHANGED
@@ -348,6 +348,10 @@ declare class VueGenerator extends ComponentGenerator {
348
348
  * Generate TypeScript interface for color props
349
349
  */
350
350
  private generateColorPropsInterface;
351
+ /**
352
+ * Parse style string for Vue template syntax
353
+ */
354
+ private parseStyleStringForVue;
351
355
  /**
352
356
  * Generate SVG root attributes for Vue template
353
357
  */
@@ -458,6 +462,10 @@ declare class ColorSplittingFeature {
458
462
  * Check if a color value is valid and should be replaced
459
463
  */
460
464
  private isValidColor;
465
+ /**
466
+ * Extract colors from style attribute
467
+ */
468
+ private extractColorsFromStyle;
461
469
  }
462
470
 
463
471
  /**