1- import { NgClass , NgIf } from '@angular/common' ;
2- import { AfterViewInit , Component , ElementRef , Input , Renderer2 , ViewChild } from '@angular/core' ;
3- import { DomSanitizer , SafeHtml } from '@angular/platform-browser' ;
1+ import { NgClass } from '@angular/common' ;
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' ;
414
515import { HtmlAttributesDirective } from '../shared/html-attr.directive' ;
616import { IconSetService } from '../icon-set' ;
717import { IconSize , IIcon } from './icon.interface' ;
8- import { toCamelCase } from './icon.utils' ;
18+ import { transformName } from './icon.utils' ;
919
1020@Component ( {
21+ exportAs : 'cIconComponent' ,
22+ imports : [ NgClass , HtmlAttributesDirective ] ,
1123 selector : 'c-icon' ,
12- templateUrl : './icon.component.svg' ,
13- styleUrls : [ './icon.component.scss' ] ,
1424 standalone : true ,
15- imports : [ NgClass , NgIf , HtmlAttributesDirective ] ,
25+ styleUrls : [ './icon.component.scss' ] ,
26+ templateUrl : './icon.component.svg' ,
1627 // eslint-disable-next-line @angular-eslint/no-host-metadata-property
1728 host : { ngSkipHydration : 'true' }
1829} )
1930export class IconComponent implements IIcon , AfterViewInit {
2031
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+
2148 @Input ( ) attributes : any = { role : 'img' } ;
22- @Input ( ) content ?: string | string [ ] | any [ ] ;
49+ @Input ( ) customClasses ?: string | string [ ] | Set < string > | { [ klass : string ] : any } ;
2350 @Input ( ) size : IconSize = '' ;
2451 @Input ( ) title ?: string ;
2552 @Input ( ) use = '' ;
26- @Input ( ) customClasses ?: string | string [ ] | Set < string > | { [ klass : string ] : any } = '' ;
27- @Input ( ) width ?: string ;
2853 @Input ( ) height ?: string ;
54+ @Input ( ) width ?: string ;
55+
56+ @Input ( { transform : transformName } )
57+ set name ( value : string ) {
58+ this . #name. set ( value ) ;
59+ } ;
2960
30- @Input ( { transform : ( value : string ) => value && value . includes ( '-' ) ? toCamelCase ( value ) : value } ) name ! : string ;
61+ get name ( ) {
62+ return this . #name( ) ;
63+ }
64+
65+ readonly #name = signal ( '' ) ;
3166
3267 @Input ( )
3368 set viewBox ( viewBox : string ) {
3469 this . _viewBox = viewBox ;
3570 }
3671
3772 get viewBox ( ) : string {
38- return this . _viewBox ?? this . scale ;
73+ return this . _viewBox ?? this . scale ( ) ;
3974 }
4075
4176 private _viewBox ! : string ;
4277
4378 @ViewChild ( 'svgElement' , { read : ElementRef } ) svgElementRef ! : ElementRef ;
4479
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-
6180 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 ) ;
6483 } ) ;
65- const parentElement = this . renderer . parentNode ( this . elementRef . nativeElement ) ;
84+ const parentElement = this . # renderer. parentNode ( this . # elementRef. nativeElement ) ;
6685 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 ) ;
6988 }
7089
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+
7197 get titleCode ( ) : string {
7298 return this . title ? `<title>${ this . title } </title>` : '' ;
7399 }
74100
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( ) ;
78104 }
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( ) ) ;
81107 }
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. ` +
84110 `To use icon by 'name' prop you need to add it to IconSet service. \n` ,
85- this . name
111+ this . # name( )
86112 ) ;
87113 }
88- return undefined ;
89- }
114+ return '' ;
115+ } ) ;
90116
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+ } ) ;
94120
95121 get computedSize ( ) : Exclude < IconSize , 'custom' > | undefined {
96122 const addCustom = ! this . size && ( this . width || this . height ) ;
@@ -102,10 +128,7 @@ export class IconComponent implements IIcon, AfterViewInit {
102128 icon : true ,
103129 [ `icon-${ this . computedSize } ` ] : ! ! this . computedSize
104130 } ;
105- return ! this . customClasses ? classes : this . customClasses ;
131+ return this . customClasses ?? classes ;
106132 }
107133
108- toCamelCase ( str : string ) : string {
109- return toCamelCase ( str ) ;
110- }
111134}
0 commit comments