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 +110 -237
- package/dist/cli.js +119 -10
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -5
- package/dist/index.mjs +5 -5
- package/package.json +1 -1
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
|
|
19
|
+
## What's New in v1.12.0
|
|
20
20
|
|
|
21
|
-
- **
|
|
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
|
-
- **
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
#
|
|
131
|
-
svgfusion ./icons --output ./components --
|
|
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 <
|
|
144
|
-
-f, --framework <framework> Target framework
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
--
|
|
148
|
-
--
|
|
149
|
-
--
|
|
150
|
-
--no-
|
|
151
|
-
--
|
|
152
|
-
--
|
|
153
|
-
|
|
154
|
-
--
|
|
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
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
350
|
+
### Split Colors with Intelligent Attribute Handling
|
|
476
351
|
|
|
477
|
-
The `splitColors` option extracts individual color properties from SVG elements
|
|
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
|
|
355
|
+
// Input SVG with mixed attributes
|
|
481
356
|
const svgContent = `
|
|
482
357
|
<svg viewBox="0 0 24 24">
|
|
483
|
-
<path fill="#FF0000"
|
|
484
|
-
<
|
|
485
|
-
<
|
|
486
|
-
|
|
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
|
-
// -
|
|
501
|
-
// -
|
|
502
|
-
// -
|
|
503
|
-
|
|
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
|
-
###
|
|
428
|
+
### Batch Processing with SVGFusion Engine
|
|
561
429
|
|
|
562
430
|
```typescript
|
|
563
|
-
import {
|
|
564
|
-
|
|
565
|
-
|
|
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 =
|
|
573
|
-
const result = await
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
/**
|