11import { NgClass } from '@angular/common' ;
22import {
3- AfterViewInit ,
3+ afterNextRender ,
44 Component ,
55 computed ,
6+ effect ,
67 ElementRef ,
78 inject ,
8- Input ,
9+ input ,
910 Renderer2 ,
1011 signal ,
11- ViewChild
12+ viewChild
1213} from '@angular/core' ;
1314import { DomSanitizer } from '@angular/platform-browser' ;
1415
1516import { HtmlAttributesDirective } from '../shared/html-attr.directive' ;
1617import { IconSetService } from '../icon-set' ;
17- import { IconSize , IIcon } from './icon.interface' ;
18+ import { IconSize , IIcon , NgCssClass } from './icon.interface' ;
1819import { transformName } from './icon.utils' ;
1920
2021@Component ( {
@@ -24,109 +25,97 @@ import { transformName } from './icon.utils';
2425 standalone : true ,
2526 styleUrls : [ './icon.component.scss' ] ,
2627 templateUrl : './icon.component.svg' ,
27- host : { ngSkipHydration : 'true' }
28+ host : { ngSkipHydration : 'true' , style : 'display: none' }
2829} )
29- export class IconComponent implements IIcon , AfterViewInit {
30+ export class IconComponent implements IIcon {
3031 readonly #renderer = inject ( Renderer2 ) ;
3132 readonly #elementRef = inject ( ElementRef ) ;
3233 readonly #sanitizer = inject ( DomSanitizer ) ;
3334 readonly #iconSet = inject ( IconSetService ) ;
35+ readonly #hostElement = signal < ElementRef < any > | undefined > ( undefined ) ;
3436
3537 constructor ( ) {
36- this . #renderer. setStyle ( this . #elementRef. nativeElement , 'display' , 'none' ) ;
37- }
38-
39- @Input ( )
40- set content ( value : string | string [ ] | any [ ] ) {
41- this . #content. set ( value ) ;
42- }
43-
44- readonly #content = signal < string | string [ ] | any [ ] > ( '' ) ;
45-
46- @Input ( ) attributes : any = { role : 'img' } ;
47- @Input ( ) customClasses ?: string | string [ ] | Set < string > | { [ klass : string ] : any } ;
48- @Input ( ) size : IconSize = '' ;
49- @Input ( ) title ?: string ;
50- @Input ( ) use = '' ;
51- @Input ( ) height ?: string ;
52- @Input ( ) width ?: string ;
53-
54- @Input ( { transform : transformName } )
55- set name ( value : string ) {
56- this . #name. set ( value ) ;
57- }
58-
59- get name ( ) {
60- return this . #name( ) ;
61- }
62-
63- readonly #name = signal ( '' ) ;
64-
65- @Input ( )
66- set viewBox ( viewBox : string ) {
67- this . _viewBox = viewBox ;
68- }
69-
70- get viewBox ( ) : string {
71- return this . _viewBox ?? this . scale ( ) ;
38+ afterNextRender ( ( ) => {
39+ this . #hostElement. set ( this . #elementRef) ;
40+ } ) ;
7241 }
7342
74- private _viewBox ! : string ;
75-
76- @ViewChild ( 'svgElement' , { read : ElementRef } ) svgElementRef ! : ElementRef ;
43+ readonly content = input < string | string [ ] | any [ ] > ( ) ;
44+
45+ readonly attributes = input < Record < string , any > > ( { role : 'img' } ) ;
46+ readonly customClasses = input < NgCssClass > ( ) ;
47+ readonly size = input < IconSize > ( '' ) ;
48+ readonly title = input < string > ( ) ;
49+ readonly use = input < string > ( '' ) ;
50+ readonly height = input < string > ( ) ;
51+ readonly width = input < string > ( ) ;
52+ readonly name = input ( '' , { transform : transformName } ) ;
53+ readonly viewBoxInput = input < string | undefined > ( undefined , { alias : 'viewBox' } ) ;
54+
55+ readonly svgElementRef = viewChild < ElementRef > ( 'svgElement' ) ;
56+
57+ readonly svgElementEffect = effect ( ( ) => {
58+ const svgElementRef = this . svgElementRef ( ) ;
59+ const hostElement = this . #hostElement( ) ?. nativeElement ;
60+ if ( svgElementRef && hostElement ) {
61+ const svgElement = svgElementRef . nativeElement ;
62+ hostElement . classList ?. values ( ) ?. forEach ( ( item : string ) => {
63+ this . #renderer. addClass ( svgElement , item ) ;
64+ } ) ;
65+ const parentElement = this . #renderer. parentNode ( hostElement ) ;
66+ this . #renderer. insertBefore ( parentElement , svgElement , hostElement ) ;
67+ this . #renderer. removeChild ( parentElement , hostElement ) ;
68+ }
69+ } ) ;
7770
78- ngAfterViewInit ( ) : void {
79- this . #elementRef. nativeElement . classList . forEach ( ( item : string ) => {
80- this . #renderer. addClass ( this . svgElementRef . nativeElement , item ) ;
81- } ) ;
82- const parentElement = this . #renderer. parentNode ( this . #elementRef. nativeElement ) ;
83- const svgElement = this . svgElementRef . nativeElement ;
84- this . #renderer. insertBefore ( parentElement , svgElement , this . #elementRef. nativeElement ) ;
85- this . #renderer. removeChild ( parentElement , this . #elementRef. nativeElement ) ;
86- }
71+ readonly viewBox = computed ( ( ) => {
72+ return this . viewBoxInput ( ) ?? this . scale ( ) ;
73+ } ) ;
8774
8875 readonly innerHtml = computed ( ( ) => {
89- const code = Array . isArray ( this . code ( ) ) ? ( this . code ( ) [ 1 ] ?? this . code ( ) [ 0 ] ?? '' ) : this . code ( ) || '' ;
76+ const codeVal = this . code ( ) ;
77+ const code = Array . isArray ( codeVal ) ? ( codeVal ?. [ 1 ] ?? codeVal ?. [ 0 ] ?? '' ) : codeVal || '' ;
9078 // todo proper sanitize
9179 // const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, code);
92- return this . #sanitizer. bypassSecurityTrustHtml ( this . titleCode + code || '' ) ;
80+ return this . #sanitizer. bypassSecurityTrustHtml ( this . # titleCode( ) + code || '' ) ;
9381 } ) ;
9482
95- get titleCode ( ) : string {
96- return this . title ? `<title>${ this . title } </title>` : '' ;
97- }
83+ readonly # titleCode = computed ( ( ) => {
84+ return this . title ( ) ? `<title>${ this . title ( ) } </title>` : '' ;
85+ } ) ;
9886
9987 readonly code = computed ( ( ) => {
100- if ( this . #content( ) ) {
101- return this . #content( ) ;
88+ const content = this . content ( ) ;
89+ if ( content ) {
90+ return content ;
10291 }
103- if ( this . #iconSet && this . #name( ) ) {
104- return this . #iconSet. getIcon ( this . #name( ) ) ;
92+ const name = this . name ( ) ;
93+ if ( this . #iconSet && name ) {
94+ return this . #iconSet. getIcon ( name ) ;
10595 }
106- if ( this . # name( ) && ! this . #iconSet?. icons [ this . # name( ) ] ) {
96+ if ( name && ! this . #iconSet?. icons [ name ] ) {
10797 console . warn (
108- `c-icon component: icon name '${ this . #name( ) } ' does not exist for IconSet service. ` +
109- `To use icon by 'name' prop you need to add it to IconSet service. \n` ,
110- this . #name( )
98+ `c-icon component: The '${ name } ' icon not found. Add it to the IconSet service for use with the 'name' property. \n` ,
99+ name
111100 ) ;
112101 }
113102 return '' ;
114103 } ) ;
115104
116105 readonly scale = computed ( ( ) => {
117- return Array . isArray ( this . code ( ) ) && this . code ( ) . length > 1 ? `0 0 ${ this . code ( ) [ 0 ] } ` : '0 0 64 64' ;
106+ return Array . isArray ( this . code ( ) ) && ( this . code ( ) ? .length ?? 0 ) > 1 ? `0 0 ${ this . code ( ) ?. [ 0 ] } ` : '0 0 64 64' ;
118107 } ) ;
119108
120- get computedSize ( ) : Exclude < IconSize , 'custom' > | undefined {
121- const addCustom = ! this . size && ( this . width || this . height ) ;
122- return this . size === 'custom' || addCustom ? 'custom-size' : this . size ;
123- }
109+ readonly computedSize = computed ( ( ) => {
110+ const addCustom = ! this . size ( ) && ( this . width ( ) || this . height ( ) ) ;
111+ return this . size ( ) === 'custom' || addCustom ? 'custom-size' : this . size ( ) ;
112+ } ) ;
124113
125- get computedClasses ( ) {
114+ readonly computedClasses = computed ( ( ) => {
126115 const classes = {
127116 icon : true ,
128- [ `icon-${ this . computedSize } ` ] : ! ! this . computedSize
117+ [ `icon-${ this . computedSize ( ) } ` ] : ! ! this . computedSize ( )
129118 } ;
130- return this . customClasses ?? classes ;
131- }
119+ return this . customClasses ( ) ?? classes ;
120+ } ) ;
132121}
0 commit comments