1
1
import { NgClass } from '@angular/common' ;
2
2
import {
3
- AfterViewInit ,
3
+ afterNextRender ,
4
4
Component ,
5
5
computed ,
6
+ effect ,
6
7
ElementRef ,
7
8
inject ,
8
- Input ,
9
+ input ,
9
10
Renderer2 ,
10
11
signal ,
11
- ViewChild
12
+ viewChild
12
13
} from '@angular/core' ;
13
14
import { DomSanitizer } from '@angular/platform-browser' ;
14
15
15
16
import { HtmlAttributesDirective } from '../shared/html-attr.directive' ;
16
17
import { IconSetService } from '../icon-set' ;
17
- import { IconSize , IIcon } from './icon.interface' ;
18
+ import { IconSize , IIcon , NgCssClass } from './icon.interface' ;
18
19
import { transformName } from './icon.utils' ;
19
20
20
21
@Component ( {
@@ -24,109 +25,97 @@ import { transformName } from './icon.utils';
24
25
standalone : true ,
25
26
styleUrls : [ './icon.component.scss' ] ,
26
27
templateUrl : './icon.component.svg' ,
27
- host : { ngSkipHydration : 'true' }
28
+ host : { ngSkipHydration : 'true' , style : 'display: none' }
28
29
} )
29
- export class IconComponent implements IIcon , AfterViewInit {
30
+ export class IconComponent implements IIcon {
30
31
readonly #renderer = inject ( Renderer2 ) ;
31
32
readonly #elementRef = inject ( ElementRef ) ;
32
33
readonly #sanitizer = inject ( DomSanitizer ) ;
33
34
readonly #iconSet = inject ( IconSetService ) ;
35
+ readonly #hostElement = signal < ElementRef < any > | undefined > ( undefined ) ;
34
36
35
37
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
+ } ) ;
72
41
}
73
42
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
+ } ) ;
77
70
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
+ } ) ;
87
74
88
75
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 || '' ;
90
78
// todo proper sanitize
91
79
// const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, code);
92
- return this . #sanitizer. bypassSecurityTrustHtml ( this . titleCode + code || '' ) ;
80
+ return this . #sanitizer. bypassSecurityTrustHtml ( this . # titleCode( ) + code || '' ) ;
93
81
} ) ;
94
82
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
+ } ) ;
98
86
99
87
readonly code = computed ( ( ) => {
100
- if ( this . #content( ) ) {
101
- return this . #content( ) ;
88
+ const content = this . content ( ) ;
89
+ if ( content ) {
90
+ return content ;
102
91
}
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 ) ;
105
95
}
106
- if ( this . # name( ) && ! this . #iconSet?. icons [ this . # name( ) ] ) {
96
+ if ( name && ! this . #iconSet?. icons [ name ] ) {
107
97
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
111
100
) ;
112
101
}
113
102
return '' ;
114
103
} ) ;
115
104
116
105
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' ;
118
107
} ) ;
119
108
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
+ } ) ;
124
113
125
- get computedClasses ( ) {
114
+ readonly computedClasses = computed ( ( ) => {
126
115
const classes = {
127
116
icon : true ,
128
- [ `icon-${ this . computedSize } ` ] : ! ! this . computedSize
117
+ [ `icon-${ this . computedSize ( ) } ` ] : ! ! this . computedSize ( )
129
118
} ;
130
- return this . customClasses ?? classes ;
131
- }
119
+ return this . customClasses ( ) ?? classes ;
120
+ } ) ;
132
121
}
0 commit comments