1
1
import { NgClass } from '@angular/common' ;
2
- import { AfterViewInit , Component , ElementRef , Input , Renderer2 , ViewChild } from '@angular/core' ;
3
- import { DomSanitizer , SafeHtml } from '@angular/platform-browser' ;
2
+ import {
3
+ AfterViewInit ,
4
+ Component ,
5
+ computed ,
6
+ ElementRef ,
7
+ inject ,
8
+ Input ,
9
+ Renderer2 ,
10
+ signal ,
11
+ ViewChild
12
+ } from '@angular/core' ;
13
+ import { DomSanitizer } from '@angular/platform-browser' ;
4
14
5
15
import { HtmlAttributesDirective } from '../shared/html-attr.directive' ;
6
16
import { IconSetService } from '../icon-set' ;
7
17
import { IconSize , IIcon } from './icon.interface' ;
8
- import { toCamelCase } from './icon.utils' ;
18
+ import { transformName } from './icon.utils' ;
9
19
10
20
@Component ( {
21
+ exportAs : 'cIconComponent' ,
22
+ imports : [ NgClass , HtmlAttributesDirective ] ,
11
23
selector : 'c-icon' ,
12
- templateUrl : './icon.component.svg' ,
13
- styleUrls : [ './icon.component.scss' ] ,
14
24
standalone : true ,
15
- imports : [ NgClass , HtmlAttributesDirective ] ,
25
+ styleUrls : [ './icon.component.scss' ] ,
26
+ templateUrl : './icon.component.svg' ,
16
27
// eslint-disable-next-line @angular-eslint/no-host-metadata-property
17
28
host : { ngSkipHydration : 'true' }
18
29
} )
19
30
export class IconComponent implements IIcon , AfterViewInit {
20
31
32
+ readonly #renderer = inject ( Renderer2 ) ;
33
+ readonly #elementRef = inject ( ElementRef ) ;
34
+ readonly #sanitizer = inject ( DomSanitizer ) ;
35
+ readonly #iconSet = inject ( IconSetService ) ;
36
+
37
+ constructor ( ) {
38
+ this . #renderer. setStyle ( this . #elementRef. nativeElement , 'display' , 'none' ) ;
39
+ }
40
+
41
+ @Input ( )
42
+ set content ( value : string | string [ ] | any [ ] ) {
43
+ this . #content. set ( value ) ;
44
+ } ;
45
+
46
+ readonly #content = signal < string | string [ ] | any [ ] > ( '' ) ;
47
+
21
48
@Input ( ) attributes : any = { role : 'img' } ;
22
- @Input ( ) content ?: string | string [ ] | any [ ] ;
49
+ @Input ( ) customClasses ?: string | string [ ] | Set < string > | { [ klass : string ] : any } ;
23
50
@Input ( ) size : IconSize = '' ;
24
51
@Input ( ) title ?: string ;
25
52
@Input ( ) use = '' ;
26
- @Input ( ) customClasses ?: string | string [ ] | Set < string > | { [ klass : string ] : any } = '' ;
27
- @Input ( ) width ?: string ;
28
53
@Input ( ) height ?: string ;
54
+ @Input ( ) width ?: string ;
29
55
30
- @Input ( { transform : ( value : string ) => value && value . includes ( '-' ) ? toCamelCase ( value ) : value } ) name ! : string ;
56
+ @Input ( { transform : transformName } )
57
+ set name ( value : string ) {
58
+ this . #name. set ( value ) ;
59
+ } ;
60
+
61
+ get name ( ) {
62
+ return this . #name( ) ;
63
+ }
64
+
65
+ readonly #name = signal ( '' ) ;
31
66
32
67
@Input ( )
33
68
set viewBox ( viewBox : string ) {
34
69
this . _viewBox = viewBox ;
35
70
}
36
71
37
72
get viewBox ( ) : string {
38
- return this . _viewBox ?? this . scale ;
73
+ return this . _viewBox ?? this . scale ( ) ;
39
74
}
40
75
41
76
private _viewBox ! : string ;
42
77
43
78
@ViewChild ( 'svgElement' , { read : ElementRef } ) svgElementRef ! : ElementRef ;
44
79
45
- get innerHtml ( ) : SafeHtml {
46
- const code = Array . isArray ( this . code ) ? this . code [ 1 ] || this . code [ 0 ] : this . code ?? '' ;
47
- // todo proper sanitize
48
- // const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, code);
49
- return this . sanitizer . bypassSecurityTrustHtml ( ( this . titleCode + code ) ?? '' ) ;
50
- }
51
-
52
- constructor (
53
- private renderer : Renderer2 ,
54
- private elementRef : ElementRef ,
55
- private sanitizer : DomSanitizer ,
56
- private iconSet : IconSetService
57
- ) {
58
- this . renderer . setStyle ( this . elementRef . nativeElement , 'display' , 'none' ) ;
59
- }
60
-
61
80
ngAfterViewInit ( ) : void {
62
- this . elementRef . nativeElement . classList . forEach ( ( item : string ) => {
63
- this . renderer . addClass ( this . svgElementRef . nativeElement , item ) ;
81
+ this . # elementRef. nativeElement . classList . forEach ( ( item : string ) => {
82
+ this . # renderer. addClass ( this . svgElementRef . nativeElement , item ) ;
64
83
} ) ;
65
- const parentElement = this . renderer . parentNode ( this . elementRef . nativeElement ) ;
84
+ const parentElement = this . # renderer. parentNode ( this . # elementRef. nativeElement ) ;
66
85
const svgElement = this . svgElementRef . nativeElement ;
67
- this . renderer . insertBefore ( parentElement , svgElement , this . elementRef . nativeElement ) ;
68
- this . renderer . removeChild ( parentElement , this . elementRef . nativeElement ) ;
86
+ this . # renderer. insertBefore ( parentElement , svgElement , this . # elementRef. nativeElement ) ;
87
+ this . # renderer. removeChild ( parentElement , this . # elementRef. nativeElement ) ;
69
88
}
70
89
90
+ readonly innerHtml = computed ( ( ) => {
91
+ const code = Array . isArray ( this . code ( ) ) ? ( this . code ( ) [ 1 ] ?? this . code ( ) [ 0 ] ?? '' ) : this . code ( ) || '' ;
92
+ // todo proper sanitize
93
+ // const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, code);
94
+ return this . #sanitizer. bypassSecurityTrustHtml ( ( this . titleCode + code ) || '' ) ;
95
+ } ) ;
96
+
71
97
get titleCode ( ) : string {
72
98
return this . title ? `<title>${ this . title } </title>` : '' ;
73
99
}
74
100
75
- get code ( ) : string | string [ ] | undefined {
76
- if ( this . content ) {
77
- return this . content ;
101
+ readonly code = computed ( ( ) => {
102
+ if ( this . # content( ) ) {
103
+ return this . # content( ) ;
78
104
}
79
- if ( this . iconSet && this . name ) {
80
- return this . iconSet . getIcon ( this . name ) ;
105
+ if ( this . # iconSet && this . # name( ) ) {
106
+ return this . # iconSet. getIcon ( this . # name( ) ) ;
81
107
}
82
- if ( this . name && ! this . iconSet ?. icons [ this . name ] ) {
83
- console . warn ( `c-icon component: icon name '${ this . name } ' does not exist for IconSet service. ` +
108
+ if ( this . # name( ) && ! this . # iconSet?. icons [ this . # name( ) ] ) {
109
+ console . warn ( `c-icon component: icon name '${ this . # name( ) } ' does not exist for IconSet service. ` +
84
110
`To use icon by 'name' prop you need to add it to IconSet service. \n` ,
85
- this . name
111
+ this . # name( )
86
112
) ;
87
113
}
88
- return undefined ;
89
- }
114
+ return '' ;
115
+ } ) ;
90
116
91
- get scale ( ) : string {
92
- return Array . isArray ( this . code ) && this . code . length > 1 ? `0 0 ${ this . code [ 0 ] } ` : '0 0 64 64' ;
93
- }
117
+ readonly scale = computed ( ( ) => {
118
+ return Array . isArray ( this . code ( ) ) && this . code ( ) . length > 1 ? `0 0 ${ this . code ( ) [ 0 ] } ` : '0 0 64 64' ;
119
+ } ) ;
94
120
95
121
get computedSize ( ) : Exclude < IconSize , 'custom' > | undefined {
96
122
const addCustom = ! this . size && ( this . width || this . height ) ;
@@ -102,10 +128,7 @@ export class IconComponent implements IIcon, AfterViewInit {
102
128
icon : true ,
103
129
[ `icon-${ this . computedSize } ` ] : ! ! this . computedSize
104
130
} ;
105
- return ! this . customClasses ? classes : this . customClasses ;
131
+ return this . customClasses ?? classes ;
106
132
}
107
133
108
- toCamelCase ( str : string ) : string {
109
- return toCamelCase ( str ) ;
110
- }
111
134
}
0 commit comments