svger-cli 1.0.6 → 1.0.8

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.
Files changed (64) hide show
  1. package/CODE_OF_CONDUCT.md +79 -0
  2. package/CONTRIBUTING.md +146 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1862 -73
  5. package/TESTING.md +143 -0
  6. package/cli-framework.test.js +16 -0
  7. package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
  8. package/cli-test-angular/Vite.component.ts +27 -0
  9. package/cli-test-angular/index.ts +25 -0
  10. package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
  11. package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
  12. package/cli-test-output/index.ts +25 -0
  13. package/cli-test-react/Arrowbenddownleft.tsx +39 -0
  14. package/cli-test-react/Vite.tsx +39 -0
  15. package/cli-test-react/index.ts +25 -0
  16. package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
  17. package/cli-test-svelte/Vite.svelte +22 -0
  18. package/cli-test-svelte/index.ts +25 -0
  19. package/dist/builder.js +12 -13
  20. package/dist/clean.js +3 -3
  21. package/dist/cli.js +139 -61
  22. package/dist/config.d.ts +1 -1
  23. package/dist/config.js +5 -7
  24. package/dist/lock.js +1 -1
  25. package/dist/templates/ComponentTemplate.d.ts +15 -0
  26. package/dist/templates/ComponentTemplate.js +15 -0
  27. package/dist/watch.d.ts +2 -1
  28. package/dist/watch.js +30 -33
  29. package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
  30. package/docs/FRAMEWORK-GUIDE.md +768 -0
  31. package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
  32. package/frameworks.test.js +170 -0
  33. package/package.json +8 -10
  34. package/src/builder.ts +12 -13
  35. package/src/clean.ts +3 -3
  36. package/src/cli.ts +148 -59
  37. package/src/config.ts +5 -6
  38. package/src/core/error-handler.ts +303 -0
  39. package/src/core/framework-templates.ts +428 -0
  40. package/src/core/logger.ts +104 -0
  41. package/src/core/performance-engine.ts +327 -0
  42. package/src/core/plugin-manager.ts +228 -0
  43. package/src/core/style-compiler.ts +605 -0
  44. package/src/core/template-manager.ts +619 -0
  45. package/src/index.ts +235 -0
  46. package/src/lock.ts +1 -1
  47. package/src/processors/svg-processor.ts +288 -0
  48. package/src/services/config.ts +241 -0
  49. package/src/services/file-watcher.ts +218 -0
  50. package/src/services/svg-service.ts +468 -0
  51. package/src/types/index.ts +169 -0
  52. package/src/utils/native.ts +352 -0
  53. package/src/watch.ts +36 -36
  54. package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
  55. package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
  56. package/test-output-mulit/TestIcon-lit.ts +35 -0
  57. package/test-output-mulit/TestIcon-preact.tsx +38 -0
  58. package/test-output-mulit/TestIcon-react.tsx +35 -0
  59. package/test-output-mulit/TestIcon-solid.tsx +27 -0
  60. package/test-output-mulit/TestIcon-svelte.svelte +22 -0
  61. package/test-output-mulit/TestIcon-vanilla.ts +37 -0
  62. package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
  63. package/test-output-mulit/TestIcon-vue-options.vue +31 -0
  64. package/tsconfig.json +11 -9
@@ -0,0 +1,605 @@
1
+ /**
2
+ * Comprehensive styling system for SVG components
3
+ * Supports all CSS properties, responsive design, theming, and dynamic styling
4
+ */
5
+
6
+ export interface StyleTheme {
7
+ name: string;
8
+ colors: Record<string, string>;
9
+ sizes: Record<string, number | string>;
10
+ spacing: Record<string, number | string>;
11
+ breakpoints?: Record<string, string>;
12
+ animations?: Record<string, string>;
13
+ }
14
+
15
+ export interface ResponsiveValue<T> {
16
+ base: T;
17
+ sm?: T;
18
+ md?: T;
19
+ lg?: T;
20
+ xl?: T;
21
+ [key: string]: T | undefined;
22
+ }
23
+
24
+ export interface SVGStyleOptions {
25
+ // Basic styling
26
+ fill?: string | ResponsiveValue<string>;
27
+ stroke?: string | ResponsiveValue<string>;
28
+ strokeWidth?: number | string | ResponsiveValue<number | string>;
29
+
30
+ // Size and dimensions
31
+ width?: number | string | ResponsiveValue<number | string>;
32
+ height?: number | string | ResponsiveValue<number | string>;
33
+ size?: number | string | ResponsiveValue<number | string>; // shorthand for width & height
34
+
35
+ // Positioning and transform
36
+ transform?: string;
37
+ rotate?: number | string;
38
+ scale?: number | string;
39
+ translateX?: number | string;
40
+ translateY?: number | string;
41
+
42
+ // Visual effects
43
+ opacity?: number | ResponsiveValue<number>;
44
+ filter?: string;
45
+ clipPath?: string;
46
+ mask?: string;
47
+
48
+ // Animation
49
+ animation?: string;
50
+ transition?: string;
51
+
52
+ // Interaction states
53
+ hover?: Partial<SVGStyleOptions>;
54
+ focus?: Partial<SVGStyleOptions>;
55
+ active?: Partial<SVGStyleOptions>;
56
+ disabled?: Partial<SVGStyleOptions>;
57
+
58
+ // Theme integration
59
+ theme?: string | StyleTheme;
60
+
61
+ // Responsive design
62
+ responsive?: boolean;
63
+
64
+ // Custom CSS properties
65
+ css?: Record<string, string | number>;
66
+
67
+ // CSS classes
68
+ className?: string;
69
+
70
+ // Accessibility
71
+ 'aria-label'?: string;
72
+ 'aria-hidden'?: boolean;
73
+ title?: string;
74
+ role?: string;
75
+ }
76
+
77
+ export interface CompiledStyles {
78
+ inline: Record<string, string | number>;
79
+ classes: string[];
80
+ cssRules: string[];
81
+ mediaQueries: string[];
82
+ }
83
+
84
+ export class SVGStyleCompiler {
85
+ private static instance: SVGStyleCompiler;
86
+ private themes: Map<string, StyleTheme> = new Map();
87
+ private globalCSS: string[] = [];
88
+
89
+ private constructor() {
90
+ this.loadDefaultThemes();
91
+ }
92
+
93
+ public static getInstance(): SVGStyleCompiler {
94
+ if (!SVGStyleCompiler.instance) {
95
+ SVGStyleCompiler.instance = new SVGStyleCompiler();
96
+ }
97
+ return SVGStyleCompiler.instance;
98
+ }
99
+
100
+ /**
101
+ * Compile SVG styling options into CSS
102
+ */
103
+ public compileStyles(
104
+ options: SVGStyleOptions,
105
+ componentName: string
106
+ ): CompiledStyles {
107
+ const compiled: CompiledStyles = {
108
+ inline: {},
109
+ classes: [],
110
+ cssRules: [],
111
+ mediaQueries: []
112
+ };
113
+
114
+ // Get theme if specified
115
+ const theme = this.resolveTheme(options.theme);
116
+
117
+ // Compile base styles
118
+ this.compileBaseStyles(options, compiled, theme);
119
+
120
+ // Compile responsive styles
121
+ if (options.responsive) {
122
+ this.compileResponsiveStyles(options, compiled, theme, componentName);
123
+ }
124
+
125
+ // Compile interaction states
126
+ this.compileInteractionStates(options, compiled, componentName);
127
+
128
+ // Add custom CSS
129
+ if (options.css) {
130
+ Object.assign(compiled.inline, options.css);
131
+ }
132
+
133
+ // Add class names
134
+ if (options.className) {
135
+ compiled.classes.push(options.className);
136
+ }
137
+
138
+ return compiled;
139
+ }
140
+
141
+ /**
142
+ * Register a custom theme
143
+ */
144
+ public registerTheme(theme: StyleTheme): void {
145
+ this.themes.set(theme.name, theme);
146
+ }
147
+
148
+ /**
149
+ * Generate CSS for a component with all styling options
150
+ */
151
+ public generateComponentCSS(
152
+ componentName: string,
153
+ options: SVGStyleOptions
154
+ ): string {
155
+ const compiled = this.compileStyles(options, componentName);
156
+
157
+ let css = '';
158
+
159
+ // Add CSS rules
160
+ if (compiled.cssRules.length > 0) {
161
+ css += compiled.cssRules.join('\n') + '\n';
162
+ }
163
+
164
+ // Add media queries
165
+ if (compiled.mediaQueries.length > 0) {
166
+ css += compiled.mediaQueries.join('\n') + '\n';
167
+ }
168
+
169
+ return css;
170
+ }
171
+
172
+ /**
173
+ * Generate enhanced React component template with full styling support
174
+ */
175
+ public generateStyledComponent(
176
+ componentName: string,
177
+ svgContent: string,
178
+ options: SVGStyleOptions = {}
179
+ ): string {
180
+ const compiled = this.compileStyles(options, componentName);
181
+ const hasCustomStyles = compiled.cssRules.length > 0 || compiled.mediaQueries.length > 0;
182
+
183
+ const imports = hasCustomStyles
184
+ ? `import React from 'react';\nimport type { SVGProps } from "react";`
185
+ : `import type { SVGProps } from "react";`;
186
+
187
+ const styledCSS = hasCustomStyles ? `
188
+ const ${componentName}Styles = \`
189
+ ${compiled.cssRules.join('\n')}
190
+ ${compiled.mediaQueries.join('\n')}
191
+ \`;
192
+
193
+ // Inject styles
194
+ if (typeof document !== 'undefined') {
195
+ const styleId = '${componentName.toLowerCase()}-styles';
196
+ if (!document.getElementById(styleId)) {
197
+ const style = document.createElement('style');
198
+ style.id = styleId;
199
+ style.textContent = ${componentName}Styles;
200
+ document.head.appendChild(style);
201
+ }
202
+ }
203
+ ` : '';
204
+
205
+ const inlineStyleObject = Object.keys(compiled.inline).length > 0
206
+ ? `const defaultStyles = ${JSON.stringify(compiled.inline, null, 2)};`
207
+ : '';
208
+
209
+ const classNames = compiled.classes.length > 0
210
+ ? `'${compiled.classes.join(' ')} ' + (props.className || '')`
211
+ : 'props.className';
212
+
213
+ return `${imports}
214
+
215
+ /**
216
+ * ${componentName} SVG Component with Enhanced Styling
217
+ * Generated by svger-cli with comprehensive styling support
218
+ */
219
+
220
+ ${styledCSS}${inlineStyleObject}
221
+
222
+ export interface ${componentName}Props extends SVGProps<SVGSVGElement> {
223
+ // Enhanced styling props
224
+ size?: number | string;
225
+ variant?: 'primary' | 'secondary' | 'accent' | 'muted';
226
+ responsive?: boolean;
227
+
228
+ // Animation props
229
+ animate?: boolean;
230
+ animationType?: 'spin' | 'pulse' | 'bounce' | 'fade';
231
+
232
+ // Theme props
233
+ theme?: 'light' | 'dark' | 'auto';
234
+ }
235
+
236
+ const ${componentName} = React.forwardRef<SVGSVGElement, ${componentName}Props>(
237
+ ({
238
+ size,
239
+ variant = 'primary',
240
+ responsive = false,
241
+ animate = false,
242
+ animationType = 'spin',
243
+ theme = 'auto',
244
+ style,
245
+ className,
246
+ ...props
247
+ }, ref) => {
248
+
249
+ // Calculate dimensions
250
+ const dimensions = React.useMemo(() => {
251
+ if (size) {
252
+ return { width: size, height: size };
253
+ }
254
+ return {
255
+ width: props.width || ${options.width || 24},
256
+ height: props.height || ${options.height || 24}
257
+ };
258
+ }, [size, props.width, props.height]);
259
+
260
+ // Combine styles
261
+ const combinedStyles = React.useMemo(() => {
262
+ const baseStyles = ${inlineStyleObject ? 'defaultStyles' : '{}'};
263
+ const variantStyles = getVariantStyles(variant);
264
+ const animationStyles = animate ? getAnimationStyles(animationType) : {};
265
+ const themeStyles = getThemeStyles(theme);
266
+
267
+ return {
268
+ ...baseStyles,
269
+ ...variantStyles,
270
+ ...animationStyles,
271
+ ...themeStyles,
272
+ ...style
273
+ };
274
+ }, [variant, animate, animationType, theme, style]);
275
+
276
+ // Combine class names
277
+ const combinedClassName = React.useMemo(() => {
278
+ const classes = [];
279
+ ${compiled.classes.length > 0 ? `classes.push('${compiled.classes.join(' ')}');` : ''}
280
+ if (responsive) classes.push(\`\${componentName.toLowerCase()}-responsive\`);
281
+ if (animate) classes.push(\`\${componentName.toLowerCase()}-animate-\${animationType}\`);
282
+ classes.push(\`\${componentName.toLowerCase()}-variant-\${variant}\`);
283
+ classes.push(\`\${componentName.toLowerCase()}-theme-\${theme}\`);
284
+ if (className) classes.push(className);
285
+ return classes.join(' ');
286
+ }, [responsive, animate, animationType, variant, theme, className]);
287
+
288
+ return (
289
+ <svg
290
+ ref={ref}
291
+ viewBox="0 0 24 24"
292
+ xmlns="http://www.w3.org/2000/svg"
293
+ width={dimensions.width}
294
+ height={dimensions.height}
295
+ fill={props.fill || "${options.fill || 'currentColor'}"}
296
+ className={combinedClassName}
297
+ style={combinedStyles}
298
+ aria-hidden={props['aria-hidden']}
299
+ aria-label={props['aria-label']}
300
+ role={props.role || 'img'}
301
+ {...props}
302
+ >
303
+ ${options.title ? `<title>${options.title}</title>` : ''}
304
+ ${svgContent}
305
+ </svg>
306
+ );
307
+ }
308
+ );
309
+
310
+ // Utility functions for styling
311
+ function getVariantStyles(variant: string): React.CSSProperties {
312
+ const variants = {
313
+ primary: { color: 'var(--color-primary, #007bff)' },
314
+ secondary: { color: 'var(--color-secondary, #6c757d)' },
315
+ accent: { color: 'var(--color-accent, #28a745)' },
316
+ muted: { color: 'var(--color-muted, #6c757d)', opacity: 0.7 }
317
+ };
318
+ return variants[variant as keyof typeof variants] || variants.primary;
319
+ }
320
+
321
+ function getAnimationStyles(animationType: string): React.CSSProperties {
322
+ const animations = {
323
+ spin: { animation: 'svger-spin 1s linear infinite' },
324
+ pulse: { animation: 'svger-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite' },
325
+ bounce: { animation: 'svger-bounce 1s infinite' },
326
+ fade: { animation: 'svger-fade 2s ease-in-out infinite alternate' }
327
+ };
328
+ return animations[animationType as keyof typeof animations] || {};
329
+ }
330
+
331
+ function getThemeStyles(theme: string): React.CSSProperties {
332
+ if (theme === 'dark') {
333
+ return { filter: 'invert(1) hue-rotate(180deg)' };
334
+ }
335
+ if (theme === 'auto') {
336
+ return { filter: 'var(--svger-theme-filter, none)' };
337
+ }
338
+ return {};
339
+ }
340
+
341
+ ${componentName}.displayName = "${componentName}";
342
+
343
+ export default ${componentName};
344
+
345
+ // CSS Animations (injected globally)
346
+ if (typeof document !== 'undefined') {
347
+ const animationCSS = \`
348
+ @keyframes svger-spin {
349
+ from { transform: rotate(0deg); }
350
+ to { transform: rotate(360deg); }
351
+ }
352
+
353
+ @keyframes svger-pulse {
354
+ 0%, 100% { opacity: 1; }
355
+ 50% { opacity: 0.5; }
356
+ }
357
+
358
+ @keyframes svger-bounce {
359
+ 0%, 20%, 53%, 80%, 100% { transform: translate3d(0,0,0); }
360
+ 40%, 43% { transform: translate3d(0,-30px,0); }
361
+ 70% { transform: translate3d(0,-15px,0); }
362
+ 90% { transform: translate3d(0,-4px,0); }
363
+ }
364
+
365
+ @keyframes svger-fade {
366
+ from { opacity: 0.4; }
367
+ to { opacity: 1; }
368
+ }
369
+
370
+ /* CSS Custom Properties for theming */
371
+ :root {
372
+ --color-primary: #007bff;
373
+ --color-secondary: #6c757d;
374
+ --color-accent: #28a745;
375
+ --color-muted: #6c757d;
376
+ --svger-theme-filter: none;
377
+ }
378
+
379
+ @media (prefers-color-scheme: dark) {
380
+ :root {
381
+ --svger-theme-filter: invert(1) hue-rotate(180deg);
382
+ }
383
+ }
384
+ \`;
385
+
386
+ const globalStyleId = 'svger-global-animations';
387
+ if (!document.getElementById(globalStyleId)) {
388
+ const style = document.createElement('style');
389
+ style.id = globalStyleId;
390
+ style.textContent = animationCSS;
391
+ document.head.appendChild(style);
392
+ }
393
+ }
394
+ `;
395
+ }
396
+
397
+ // Private helper methods
398
+
399
+ private loadDefaultThemes(): void {
400
+ // Light theme
401
+ this.registerTheme({
402
+ name: 'light',
403
+ colors: {
404
+ primary: '#007bff',
405
+ secondary: '#6c757d',
406
+ success: '#28a745',
407
+ warning: '#ffc107',
408
+ danger: '#dc3545',
409
+ info: '#17a2b8',
410
+ light: '#f8f9fa',
411
+ dark: '#343a40'
412
+ },
413
+ sizes: {
414
+ xs: 12,
415
+ sm: 16,
416
+ md: 24,
417
+ lg: 32,
418
+ xl: 48
419
+ },
420
+ spacing: {
421
+ 1: '0.25rem',
422
+ 2: '0.5rem',
423
+ 3: '0.75rem',
424
+ 4: '1rem',
425
+ 5: '1.5rem'
426
+ },
427
+ breakpoints: {
428
+ sm: '576px',
429
+ md: '768px',
430
+ lg: '992px',
431
+ xl: '1200px'
432
+ }
433
+ });
434
+
435
+ // Dark theme
436
+ this.registerTheme({
437
+ name: 'dark',
438
+ colors: {
439
+ primary: '#0d6efd',
440
+ secondary: '#6c757d',
441
+ success: '#198754',
442
+ warning: '#ffc107',
443
+ danger: '#dc3545',
444
+ info: '#0dcaf0',
445
+ light: '#212529',
446
+ dark: '#f8f9fa'
447
+ },
448
+ sizes: {
449
+ xs: 12,
450
+ sm: 16,
451
+ md: 24,
452
+ lg: 32,
453
+ xl: 48
454
+ },
455
+ spacing: {
456
+ 1: '0.25rem',
457
+ 2: '0.5rem',
458
+ 3: '0.75rem',
459
+ 4: '1rem',
460
+ 5: '1.5rem'
461
+ },
462
+ breakpoints: {
463
+ sm: '576px',
464
+ md: '768px',
465
+ lg: '992px',
466
+ xl: '1200px'
467
+ }
468
+ });
469
+ }
470
+
471
+ private resolveTheme(theme?: string | StyleTheme): StyleTheme | null {
472
+ if (!theme) return null;
473
+
474
+ if (typeof theme === 'string') {
475
+ return this.themes.get(theme) || null;
476
+ }
477
+
478
+ return theme;
479
+ }
480
+
481
+ private compileBaseStyles(
482
+ options: SVGStyleOptions,
483
+ compiled: CompiledStyles,
484
+ theme: StyleTheme | null
485
+ ): void {
486
+ // Handle basic properties
487
+ const styleMap: Record<string, keyof SVGStyleOptions> = {
488
+ fill: 'fill',
489
+ stroke: 'stroke',
490
+ strokeWidth: 'strokeWidth',
491
+ opacity: 'opacity',
492
+ transform: 'transform',
493
+ filter: 'filter',
494
+ clipPath: 'clipPath',
495
+ mask: 'mask',
496
+ animation: 'animation',
497
+ transition: 'transition'
498
+ };
499
+
500
+ for (const [cssProp, optionKey] of Object.entries(styleMap)) {
501
+ const value = options[optionKey];
502
+ if (value !== undefined) {
503
+ if (this.isResponsiveValue(value)) {
504
+ compiled.inline[cssProp] = (value as ResponsiveValue<any>).base;
505
+ } else {
506
+ compiled.inline[cssProp] = value as string | number;
507
+ }
508
+ }
509
+ }
510
+
511
+ // Handle size shorthand
512
+ if (options.size !== undefined) {
513
+ const sizeValue = this.isResponsiveValue(options.size)
514
+ ? (options.size as ResponsiveValue<any>).base
515
+ : options.size;
516
+ compiled.inline.width = sizeValue;
517
+ compiled.inline.height = sizeValue;
518
+ }
519
+
520
+ // Handle transform shortcuts
521
+ const transforms: string[] = [];
522
+ if (options.rotate) transforms.push(`rotate(${options.rotate}deg)`);
523
+ if (options.scale) transforms.push(`scale(${options.scale})`);
524
+ if (options.translateX) transforms.push(`translateX(${options.translateX})`);
525
+ if (options.translateY) transforms.push(`translateY(${options.translateY})`);
526
+
527
+ if (transforms.length > 0) {
528
+ const existingTransform = compiled.inline.transform as string;
529
+ compiled.inline.transform = existingTransform
530
+ ? `${existingTransform} ${transforms.join(' ')}`
531
+ : transforms.join(' ');
532
+ }
533
+ }
534
+
535
+ private compileResponsiveStyles(
536
+ options: SVGStyleOptions,
537
+ compiled: CompiledStyles,
538
+ theme: StyleTheme | null,
539
+ componentName: string
540
+ ): void {
541
+ const breakpoints = theme?.breakpoints || {
542
+ sm: '576px',
543
+ md: '768px',
544
+ lg: '992px',
545
+ xl: '1200px'
546
+ };
547
+
548
+ for (const [prop, value] of Object.entries(options)) {
549
+ if (this.isResponsiveValue(value)) {
550
+ const responsiveValue = value as ResponsiveValue<any>;
551
+
552
+ for (const [breakpoint, breakpointValue] of Object.entries(responsiveValue)) {
553
+ if (breakpoint === 'base') continue;
554
+
555
+ const mediaQuery = breakpoints[breakpoint];
556
+ if (mediaQuery && breakpointValue !== undefined) {
557
+ const rule = `@media (min-width: ${mediaQuery}) {
558
+ .${componentName.toLowerCase()}-responsive {
559
+ ${this.camelToKebab(prop)}: ${breakpointValue};
560
+ }
561
+ }`;
562
+ compiled.mediaQueries.push(rule);
563
+ }
564
+ }
565
+ }
566
+ }
567
+ }
568
+
569
+ private compileInteractionStates(
570
+ options: SVGStyleOptions,
571
+ compiled: CompiledStyles,
572
+ componentName: string
573
+ ): void {
574
+ const states = ['hover', 'focus', 'active', 'disabled'] as const;
575
+
576
+ for (const state of states) {
577
+ const stateStyles = options[state];
578
+ if (stateStyles) {
579
+ const selector = state === 'disabled'
580
+ ? `.${componentName.toLowerCase()}[disabled], .${componentName.toLowerCase()}[aria-disabled="true"]`
581
+ : `.${componentName.toLowerCase()}:${state}`;
582
+
583
+ const rules: string[] = [];
584
+ for (const [prop, value] of Object.entries(stateStyles)) {
585
+ rules.push(` ${this.camelToKebab(prop)}: ${value};`);
586
+ }
587
+
588
+ if (rules.length > 0) {
589
+ compiled.cssRules.push(`${selector} {\n${rules.join('\n')}\n}`);
590
+ }
591
+ }
592
+ }
593
+ }
594
+
595
+ private isResponsiveValue(value: any): value is ResponsiveValue<any> {
596
+ return value && typeof value === 'object' && 'base' in value;
597
+ }
598
+
599
+ private camelToKebab(str: string): string {
600
+ return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
601
+ }
602
+ }
603
+
604
+ // Export singleton instance
605
+ export const styleCompiler = SVGStyleCompiler.getInstance();