svger-cli 1.0.6 → 1.0.7
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/CODE_OF_CONDUCT.md +79 -0
- package/CONTRIBUTING.md +146 -0
- package/LICENSE +21 -0
- package/README.md +1862 -73
- package/TESTING.md +143 -0
- package/cli-framework.test.js +16 -0
- package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
- package/cli-test-angular/Vite.component.ts +27 -0
- package/cli-test-angular/index.ts +25 -0
- package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
- package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
- package/cli-test-output/index.ts +25 -0
- package/cli-test-react/Arrowbenddownleft.tsx +39 -0
- package/cli-test-react/Vite.tsx +39 -0
- package/cli-test-react/index.ts +25 -0
- package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
- package/cli-test-svelte/Vite.svelte +22 -0
- package/cli-test-svelte/index.ts +25 -0
- package/dist/builder.js +12 -13
- package/dist/clean.js +3 -3
- package/dist/cli.js +139 -61
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -7
- package/dist/lock.js +1 -1
- package/dist/templates/ComponentTemplate.d.ts +15 -0
- package/dist/templates/ComponentTemplate.js +15 -0
- package/dist/watch.d.ts +2 -1
- package/dist/watch.js +30 -33
- package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
- package/docs/FRAMEWORK-GUIDE.md +768 -0
- package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
- package/frameworks.test.js +170 -0
- package/package.json +7 -10
- package/src/builder.ts +12 -13
- package/src/clean.ts +3 -3
- package/src/cli.ts +148 -59
- package/src/config.ts +5 -6
- package/src/core/error-handler.ts +303 -0
- package/src/core/framework-templates.ts +428 -0
- package/src/core/logger.ts +104 -0
- package/src/core/performance-engine.ts +327 -0
- package/src/core/plugin-manager.ts +228 -0
- package/src/core/style-compiler.ts +605 -0
- package/src/core/template-manager.ts +619 -0
- package/src/index.ts +235 -0
- package/src/lock.ts +1 -1
- package/src/processors/svg-processor.ts +288 -0
- package/src/services/config.ts +241 -0
- package/src/services/file-watcher.ts +218 -0
- package/src/services/svg-service.ts +468 -0
- package/src/types/index.ts +169 -0
- package/src/utils/native.ts +352 -0
- package/src/watch.ts +36 -36
- package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
- package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
- package/test-output-mulit/TestIcon-lit.ts +35 -0
- package/test-output-mulit/TestIcon-preact.tsx +38 -0
- package/test-output-mulit/TestIcon-react.tsx +35 -0
- package/test-output-mulit/TestIcon-solid.tsx +27 -0
- package/test-output-mulit/TestIcon-svelte.svelte +22 -0
- package/test-output-mulit/TestIcon-vanilla.ts +37 -0
- package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
- package/test-output-mulit/TestIcon-vue-options.vue +31 -0
- 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();
|