11/* eslint-disable @typescript-eslint/member-ordering */
2- import { Component , OnInit , Pipe , PipeTransform , Renderer2 , forwardRef , DOCUMENT , inject } from '@angular/core' ;
3- import * as fileSaver from 'file-saver' ;
2+ import {
3+ Component ,
4+ OnInit ,
5+ Pipe ,
6+ PipeTransform ,
7+ Renderer2 ,
8+ forwardRef ,
9+ inject
10+ } from '@angular/core' ;
11+ import fileSaver from 'file-saver' ;
12+ import Fuse from 'fuse.js' ;
13+ import { Subject } from 'rxjs' ;
14+ import { debounceTime , distinctUntilChanged } from 'rxjs/operators' ;
15+ import { toSignal } from '@angular/core/rxjs-interop' ;
416
517import { IgxIconComponent , IgxIconService } from 'igniteui-angular/icon' ;
618import { ISelectionEventArgs } from 'igniteui-angular/drop-down' ;
719import { IgxSelectComponent , IgxSelectItemComponent } from 'igniteui-angular/select' ;
8- import { IgxInputDirective , IgxInputGroupComponent , IgxLabelDirective , IgxPrefixDirective , IgxSuffixDirective } from 'igniteui-angular/input-group' ;
20+ import {
21+ IgxInputDirective ,
22+ IgxInputGroupComponent ,
23+ IgxLabelDirective ,
24+ IgxPrefixDirective ,
25+ IgxSuffixDirective
26+ } from 'igniteui-angular/input-group' ;
927import { IgxButtonDirective } from 'igniteui-angular/directives' ;
1028
1129import {
@@ -23,13 +41,32 @@ interface ICategoryOption {
2341 selector : 'app-material-icons-extended' ,
2442 templateUrl : './material-icons-extended.component.html' ,
2543 styleUrls : [ './material-icons-extended.component.scss' ] ,
26- imports : [ IgxSelectComponent , IgxLabelDirective , IgxSelectItemComponent , IgxInputGroupComponent , IgxInputDirective , IgxPrefixDirective , IgxIconComponent , IgxSuffixDirective , IgxButtonDirective , forwardRef ( ( ) => CategoriesFilterPipe ) , forwardRef ( ( ) => FilterByName ) ]
44+ imports : [
45+ IgxSelectComponent ,
46+ IgxLabelDirective ,
47+ IgxSelectItemComponent ,
48+ IgxInputGroupComponent ,
49+ IgxInputDirective ,
50+ IgxPrefixDirective ,
51+ IgxIconComponent ,
52+ IgxSuffixDirective ,
53+ IgxButtonDirective ,
54+ forwardRef ( ( ) => CategoriesFilterPipe ) , forwardRef ( ( ) => FilterByName )
55+ ]
2756} )
2857export class MaterialIconsExtendedComponent implements OnInit {
2958 private iconService = inject ( IgxIconService ) ;
30- private document = inject < Document > ( DOCUMENT ) ;
3159 private renderer = inject ( Renderer2 ) ;
3260
61+ // Search with debounce using signals
62+ private searchInput$ = new Subject < string > ( ) ;
63+ public searchTerm = toSignal (
64+ this . searchInput$ . pipe (
65+ debounceTime ( 300 ) ,
66+ distinctUntilChanged ( )
67+ ) ,
68+ { initialValue : '' }
69+ ) ;
3370
3471 public categories : ICategoryOption [ ] = [
3572 {
@@ -38,6 +75,14 @@ export class MaterialIconsExtendedComponent implements OnInit {
3875 }
3976 ] ;
4077
78+ onSearchInput ( value : string ) {
79+ this . searchInput$ . next ( value ) ;
80+ }
81+
82+ clearSearch ( ) {
83+ this . searchInput$ . next ( '' ) ;
84+ }
85+
4186 public setCategories ( ) {
4287 const categories = IconCategory . values ( ) . map (
4388 ( category ) =>
@@ -65,6 +110,14 @@ export class MaterialIconsExtendedComponent implements OnInit {
65110 this . selectedCategory = 'all' ;
66111 }
67112
113+ trackByIcon ( _index : number , icon : IMXIcon ) : string {
114+ return icon . name ;
115+ }
116+
117+ trackByCategory ( _index : number , group : IIconsGroup ) : string {
118+ return group . category ;
119+ }
120+
68121 addIcons ( ) {
69122 for ( const icon of imxIcons ) {
70123 this . iconService . addSvgIconFromText (
@@ -80,36 +133,30 @@ export class MaterialIconsExtendedComponent implements OnInit {
80133 fileSaver . saveAs ( blob , icon . name ) ;
81134 }
82135
83- copyValue ( event : Event , val : string ) {
136+ async copyValue ( event : Event , val : string ) {
84137 const target = event . currentTarget as HTMLButtonElement ;
85138 const element = target . childNodes [ 0 ] as HTMLElement ;
86- const tempField = this . renderer . createElement ( 'textarea' ) ;
87-
88- this . renderer . setStyle ( tempField , 'position' , 'fixed' ) ;
89- this . renderer . setStyle ( tempField , 'opacity' , '0' ) ;
90- this . renderer . setProperty ( tempField , 'value' , val ) ;
91- this . renderer . appendChild ( this . document . body , tempField ) ;
92139
93- tempField . focus ( ) ;
94- tempField . select ( ) ;
95-
96- this . document . execCommand ( 'copy' ) ;
97- this . renderer . removeChild ( this . document . body , tempField ) ;
98-
99- if ( element . innerText !== 'done' ) {
100- this . renderer . setProperty ( element , 'innerText' , 'done' ) ;
101- this . renderer . addClass (
102- target ,
103- 'sample__grid-item-clipboard--success'
104- ) ;
140+ try {
141+ await navigator . clipboard . writeText ( val ) ;
105142
106- setTimeout ( ( ) => {
107- this . renderer . setProperty ( element , 'innerText' , 'content_copy ' ) ;
108- this . renderer . removeClass (
143+ if ( element . innerText !== 'done' ) {
144+ this . renderer . setProperty ( element , 'innerText' , 'done ' ) ;
145+ this . renderer . addClass (
109146 target ,
110147 'sample__grid-item-clipboard--success'
111148 ) ;
112- } , 1500 ) ;
149+
150+ setTimeout ( ( ) => {
151+ this . renderer . setProperty ( element , 'innerText' , 'content_copy' ) ;
152+ this . renderer . removeClass (
153+ target ,
154+ 'sample__grid-item-clipboard--success'
155+ ) ;
156+ } , 1500 ) ;
157+ }
158+ } catch ( err ) {
159+ console . error ( 'Failed to copy text: ' , err ) ;
113160 }
114161 }
115162
@@ -133,7 +180,11 @@ export class CategoriesFilterPipe implements PipeTransform {
133180 const index = acc . findIndex ( ( group ) => group . category === category ) ;
134181
135182 if ( index !== - 1 ) {
136- acc [ index ] . icons . push ( icon ) ;
183+ const exists = acc [ index ] . icons . some ( existingIcon => existingIcon . name === icon . name ) ;
184+
185+ if ( ! exists ) {
186+ acc [ index ] . icons . push ( icon ) ;
187+ }
137188 } else {
138189 acc . push ( {
139190 category,
@@ -167,17 +218,30 @@ export class CategoriesFilterPipe implements PipeTransform {
167218 name : 'filterByName'
168219} )
169220export class FilterByName implements PipeTransform {
221+ private fuse : Fuse < IMXIcon > | null = null ;
222+ private lastCollection : IMXIcon [ ] = [ ] ;
223+
170224 transform ( icons : IMXIcon [ ] , keyword : string ) : IMXIcon [ ] {
171- return icons . filter ( ( icon ) => {
172- const keywords = [ ...( icon . keywords || [ ] ) , icon . name ] ;
173- const index = keywords . indexOf ( keyword . toLowerCase ( ) ) ;
174- if ( keyword !== '' ) {
175- if ( index !== - 1 ) {
176- return keywords ;
177- }
178- } else {
179- return icons ;
180- }
181- } ) ;
225+ if ( ! keyword || keyword . trim ( ) === '' ) {
226+ return icons ;
227+ }
228+
229+ // Initialize Fuse only if collection changed
230+ if ( this . lastCollection !== icons ) {
231+ this . fuse = new Fuse ( icons , {
232+ keys : [
233+ { name : 'name' , weight : 0.7 } ,
234+ { name : 'keywords' , weight : 0.3 }
235+ ] ,
236+ threshold : 0.3 ,
237+ distance : 100 ,
238+ ignoreLocation : true ,
239+ minMatchCharLength : 1
240+ } ) ;
241+ this . lastCollection = icons ;
242+ }
243+
244+ const results = this . fuse ! . search ( keyword . toLowerCase ( ) ) ;
245+ return results . map ( result => result . item ) ;
182246 }
183247}
0 commit comments