ng-hub-ui-utils 1.0.0 β†’ 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,24 +1,596 @@
1
- # Utils
1
+ # Hub UI - Angular Utilities Library
2
2
 
3
- This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
3
+ [![NPM Version](https://img.shields.io/npm/v/ng-hub-ui-utils.svg)](https://www.npmjs.com/package/ng-hub-ui-utils)
4
+ [![License](https://img.shields.io/npm/l/ng-hub-ui-utils.svg)](LICENSE)
5
+ [![Build Status](https://img.shields.io/github/workflow/status/carlos-morcillo/ng-hub-ui-utils/CI)](https://github.com/carlos-morcillo/ng-hub-ui-utils/actions)
4
6
 
5
- ## Code scaffolding
7
+ > Common utilities library for Angular, fundamental support for the Hub UI ecosystem.
6
8
 
7
- Run `ng generate component component-name --project utils` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project utils`.
8
- > Note: Don't forget to add `--project utils` or else it will be added to the default project in your `angular.json` file.
9
+ [EspaΓ±ol](./README.es.md) | **English**
9
10
 
10
- ## Build
11
+ ## 🏑 Part of the Hub UI Family
11
12
 
12
- Run `ng build utils` to build the project. The build artifacts will be stored in the `dist/` directory.
13
+ This library is part of the **Hub UI** ecosystem, which includes:
13
14
 
14
- ## Publishing
15
+ - 🎨 [**ng-hub-ui-accordion**](https://github.com/carlos-morcillo/ng-hub-ui-accordion) - Accordion components
16
+ - πŸ“± **ng-hub-ui-action-sheet** - Mobile action sheets
17
+ - πŸ‘€ [**ng-hub-ui-avatar**](https://github.com/carlos-morcillo/ng-hub-ui-avatar) - Avatar components
18
+ - πŸ“‹ [**ng-hub-ui-board**](https://github.com/carlos-morcillo/ng-hub-ui-board) - Kanban-style boards
19
+ - 🧭 [**ng-hub-ui-breadcrumbs**](https://github.com/carlos-morcillo/ng-hub-ui-breadcrumbs) - Navigation breadcrumbs
20
+ - πŸ“œ **ng-hub-ui-dropdown** - Dropdown components
21
+ - πŸ“ **ng-hub-ui-list** - List components
22
+ - πŸͺŸ [**ng-hub-ui-modal**](https://github.com/carlos-morcillo/ng-hub-ui-modal) - Modal components
23
+ - πŸŒ€ [**ng-hub-ui-portal**](https://github.com/carlos-morcillo/ng-hub-ui-portal) - Portal system
24
+ - πŸ”€ [**ng-hub-ui-sortable**](https://github.com/carlos-morcillo/ng-hub-ui-sortable) - Sortable components
25
+ - πŸ“Š [**ng-hub-ui-stepper**](https://github.com/carlos-morcillo/ng-hub-ui-stepper) - Step-by-step components
26
+ - πŸ› οΈ [**ng-hub-ui-utils**](https://github.com/carlos-morcillo/ng-hub-ui-utils) ← you are here - Common utilities
15
27
 
16
- After building your library with `ng build utils`, go to the dist folder `cd dist/utils` and run `npm publish`.
28
+ ## πŸ’‘ Inspiration
17
29
 
18
- ## Running unit tests
30
+ This utilities library emerged from the need to provide common, reusable, and optimized support functions for the entire Hub UI ecosystem. Inspired by best practices in Angular development and internal utilities from libraries like Angular Bootstrap and Material Design, it provides essential tools for developing modern UI components.
19
31
 
20
- Run `ng test utils` to execute the unit tests via [Karma](https://karma-runner.github.io).
32
+ ## ✨ Features
21
33
 
22
- ## Further help
34
+ ### πŸ”§ Focus Management and Accessibility
23
35
 
24
- To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
36
+ Advanced utilities for focus handling, focus trapping, and keyboard navigation.
37
+
38
+ ```typescript
39
+ import { getFocusableBoundaryElements, FOCUSABLE_ELEMENTS_SELECTOR } from 'ng-hub-ui-utils';
40
+
41
+ // Get focusable elements in a container
42
+ const [firstElement, lastElement] = getFocusableBoundaryElements(containerElement);
43
+
44
+ // Create a focus trap in a modal
45
+ const focusTrap = hubFocusTrap(ngZone, modalElement, stopFocusTrap$);
46
+ ```
47
+
48
+ ### πŸͺŸ Overlay Service
49
+
50
+ Advanced system for creating overlays and floating components with flexible positioning.
51
+
52
+ ```typescript
53
+ import { OverlayService, OverlayConfig } from 'ng-hub-ui-utils';
54
+
55
+ @Component({
56
+ selector: 'app-example'
57
+ })
58
+ export class ExampleComponent {
59
+ constructor(private overlayService: OverlayService) {}
60
+
61
+ openOverlay(elementRef: ElementRef) {
62
+ // Create overlay with configuration
63
+ const overlayRef = this.overlayService.create({
64
+ hasBackdrop: true,
65
+ backdropClass: 'custom-backdrop'
66
+ });
67
+
68
+ // Configure position strategy
69
+ const positionStrategy = this.overlayService.position()
70
+ .flexibleConnectedTo(elementRef)
71
+ .withPositions([{
72
+ originX: 'start',
73
+ originY: 'bottom',
74
+ overlayX: 'start',
75
+ overlayY: 'top'
76
+ }]);
77
+
78
+ // Attach component to overlay
79
+ const componentRef = overlayRef.attach(MyComponent);
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### 🎯 Popup Service (Base Class)
85
+
86
+ Base service for creating custom popup implementations.
87
+
88
+ ```typescript
89
+ import { PopupService } from 'ng-hub-ui-utils';
90
+
91
+ @Injectable()
92
+ export class MyPopupService extends PopupService<MyPopupComponent> {
93
+ constructor() {
94
+ super(MyPopupComponent);
95
+ }
96
+
97
+ openPopup(content?: string | TemplateRef<any>) {
98
+ const { windowRef, transition$ } = super.open(content, {}, true);
99
+ return { windowRef, transition$ };
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### πŸ“œ Scrollbar Management
105
+
106
+ Intelligent scrollbar control with layout compensation.
107
+
108
+ ```typescript
109
+ import { ScrollBar } from 'ng-hub-ui-utils';
110
+
111
+ constructor(private scrollBar: ScrollBar) {}
112
+
113
+ openModal() {
114
+ // Hide scrollbar and compensate for space
115
+ const reverter = this.scrollBar.hide();
116
+
117
+ // On modal close, restore scrollbar
118
+ modalClose.subscribe(() => reverter());
119
+ }
120
+ ```
121
+
122
+ ### ⚑ Transition System
123
+
124
+ Utilities for smooth animations and transitions with automatic detection.
125
+
126
+ ```typescript
127
+ import { hubRunTransition } from 'ng-hub-ui-utils';
128
+
129
+ // Execute transition with callback
130
+ hubRunTransition(
131
+ this.ngZone,
132
+ element,
133
+ (element, animation, context) => {
134
+ // Transition start logic
135
+ element.classList.add('transitioning');
136
+
137
+ return () => {
138
+ // Cleanup at transition end
139
+ element.classList.remove('transitioning');
140
+ };
141
+ },
142
+ {
143
+ animation: true,
144
+ runningTransition: 'continue',
145
+ context: { customData: 'value' }
146
+ }
147
+ ).subscribe(() => {
148
+ console.log('Transition completed');
149
+ });
150
+ ```
151
+
152
+ ### 🧰 Standalone Angular Pipes
153
+
154
+ Complete set of utility pipes for validation, transformation, and data manipulation.
155
+
156
+ ```typescript
157
+ import { GetPipe, IsStringPipe, IsObjectPipe, IsObservablePipe, UcfirstPipe, UnwrapAsyncPipe } from 'ng-hub-ui-utils';
158
+
159
+ @Component({
160
+ standalone: true,
161
+ imports: [GetPipe, IsStringPipe, UcfirstPipe, UnwrapAsyncPipe],
162
+ template: `
163
+ <!-- Safe nested property access -->
164
+ <p>{{ user | get : 'address.city' : 'Unknown' }}</p>
165
+
166
+ <!-- Capitalize first letter -->
167
+ <h1>{{ title | ucfirst }}</h1>
168
+
169
+ <!-- Type checking in templates -->
170
+ @if (value | isString) {
171
+ <span>It's a string: {{ value }}</span>
172
+ }
173
+
174
+ <!-- Unwrap Observable or direct value -->
175
+ <div>{{ observableOrValue | unwrapAsync }}</div>
176
+ `
177
+ })
178
+ export class ExampleComponent {
179
+ user = { address: { city: 'New York' } };
180
+ title = 'hello world';
181
+ value: any = 'test';
182
+ observableOrValue = of('Observable value');
183
+ }
184
+ ```
185
+
186
+ **Available Pipes:**
187
+
188
+ - **GetPipe** (`get`): Safe nested property access with default values
189
+ - **IsStringPipe** (`isString`): Check if value is a string
190
+ - **IsObjectPipe** (`isObject`): Check if value is an object
191
+ - **IsObservablePipe** (`isObservable`): Check if value is an Observable
192
+ - **UcfirstPipe** (`ucfirst`): Capitalize first letter of a string
193
+ - **UnwrapAsyncPipe** (`unwrapAsync`): Unwrap Observable or return direct value
194
+
195
+ ### πŸ› οΈ General Utility Functions
196
+
197
+ Complete set of helpers for validation, transformation, and data manipulation.
198
+
199
+ ```typescript
200
+ import {
201
+ toInteger,
202
+ toString,
203
+ getValueInRange,
204
+ isString,
205
+ isNumber,
206
+ isInteger,
207
+ isDefined,
208
+ isPromise,
209
+ padNumber,
210
+ regExpEscape,
211
+ closest,
212
+ reflow,
213
+ removeAccents,
214
+ getActiveElement
215
+ } from 'ng-hub-ui-utils';
216
+
217
+ // Safe conversions
218
+ const numValue = toInteger('42'); // 42
219
+ const strValue = toString(null); // ''
220
+
221
+ // Type validations
222
+ if (isString(value)) {
223
+ /* ... */
224
+ }
225
+ if (isPromise(result)) {
226
+ /* ... */
227
+ }
228
+
229
+ // DOM manipulation
230
+ const parent = closest(element, '.container');
231
+ reflow(element); // Force browser reflow
232
+
233
+ // String utilities
234
+ const clean = removeAccents('niΓ±o'); // "nino"
235
+ const escaped = regExpEscape('hello?'); // "hello\\?"
236
+
237
+ // Focus management
238
+ const activeEl = getActiveElement(); // Includes shadow DOM
239
+ ```
240
+
241
+ ### 🎯 Full TypeScript Support
242
+
243
+ Strict typing throughout the library with well-defined interfaces and types.
244
+
245
+ ```typescript
246
+ // Transition types
247
+ type TransitionStartFn<T> = (element: HTMLElement, animation: boolean, context: T) => TransitionEndFn | void;
248
+
249
+ interface TransitionOptions<T> {
250
+ animation: boolean;
251
+ runningTransition: 'continue' | 'stop';
252
+ context?: T;
253
+ }
254
+
255
+ // Scrollbar reverter type
256
+ type ScrollbarReverter = () => void;
257
+ ```
258
+
259
+ ### ⚑ Optimized Tree-shaking
260
+
261
+ Import only the utilities you need to optimize your bundle.
262
+
263
+ ```typescript
264
+ // Specific imports
265
+ import { toInteger, isString } from 'ng-hub-ui-utils';
266
+ import { ScrollBar } from 'ng-hub-ui-utils';
267
+ import { hubRunTransition } from 'ng-hub-ui-utils';
268
+ import { GetPipe, UcfirstPipe } from 'ng-hub-ui-utils';
269
+ ```
270
+
271
+ ## πŸš€ Installation
272
+
273
+ ```bash
274
+ npm install ng-hub-ui-utils
275
+ # or
276
+ yarn add ng-hub-ui-utils
277
+ ```
278
+
279
+ ## πŸ“– Quick Start
280
+
281
+ ```typescript
282
+ // Import specific utilities
283
+ import { toInteger, isString, ScrollBar, getFocusableBoundaryElements, GetPipe, UcfirstPipe } from 'ng-hub-ui-utils';
284
+
285
+ @Component({
286
+ selector: 'app-example',
287
+ standalone: true,
288
+ imports: [GetPipe, UcfirstPipe],
289
+ template: `
290
+ <div #container>
291
+ <h1>{{ title | ucfirst }}</h1>
292
+ <p>{{ user | get : 'name' : 'Anonymous' }}</p>
293
+ </div>
294
+ `
295
+ })
296
+ export class ExampleComponent {
297
+ constructor(private scrollBar: ScrollBar) {}
298
+
299
+ @ViewChild('container') containerElement!: ElementRef<HTMLElement>;
300
+
301
+ title = 'welcome';
302
+ user = { name: 'John Doe' };
303
+
304
+ ngAfterViewInit() {
305
+ // Get focusable elements
306
+ const [first, last] = getFocusableBoundaryElements(this.containerElement.nativeElement);
307
+
308
+ // Safe conversion
309
+ const value = toInteger('42');
310
+
311
+ if (isString(this.title)) {
312
+ console.log("It's a string");
313
+ }
314
+ }
315
+
316
+ openOverlay() {
317
+ // Hide scrollbar during overlay
318
+ const reverter = this.scrollBar.hide();
319
+
320
+ // Restore on close
321
+ this.overlayRef.onClose(() => reverter());
322
+ }
323
+ }
324
+ ```
325
+
326
+ ## πŸ“Š Utilities API
327
+
328
+ ### Conversion Functions
329
+
330
+ - `toInteger(value: any): number` - Safely converts to integer
331
+ - `toString(value: any): string` - Converts to string handling null/undefined
332
+ - `getValueInRange(value: number, max: number, min?: number): number` - Limits value to range
333
+ - `padNumber(value: number): string` - Adds leading zero to numbers
334
+
335
+ ### Validation Functions
336
+
337
+ - `isString(value: any): value is string` - Checks if value is a string
338
+ - `isNumber(value: any): value is number` - Checks if value is a valid number
339
+ - `isInteger(value: any): value is number` - Checks if value is an integer
340
+ - `isDefined(value: any): boolean` - Checks if not null/undefined
341
+ - `isPromise<T>(v: any): v is Promise<T>` - Checks if value is a Promise
342
+
343
+ ### String Functions
344
+
345
+ - `regExpEscape(text: string): string` - Escapes special characters for RegExp
346
+ - `removeAccents(str: string): string` - Removes accents from text
347
+
348
+ ### DOM Functions
349
+
350
+ - `closest(element: HTMLElement, selector?: string): HTMLElement | null` - Finds parent element by selector
351
+ - `reflow(element: HTMLElement): DOMRect` - Forces browser reflow
352
+ - `getActiveElement(root?: Document | ShadowRoot): Element | null` - Gets active element including Shadow DOM
353
+
354
+ ### Focus Functions
355
+
356
+ - `getFocusableBoundaryElements(element: HTMLElement): HTMLElement[]` - Gets first and last focusable elements
357
+ - `hubFocusTrap(zone, element, stopFocusTrap$, refocusOnClick?)` - Creates focus trap for modals/overlays
358
+ - `FOCUSABLE_ELEMENTS_SELECTOR: string` - CSS selector for focusable elements
359
+
360
+ ### Pipes
361
+
362
+ #### GetPipe
363
+
364
+ ```typescript
365
+ // Safe nested property access
366
+ {{ object | get:'path.to.property':'defaultValue' }}
367
+ ```
368
+
369
+ #### IsStringPipe
370
+
371
+ ```typescript
372
+ // Type checking
373
+ @if (value | isString) { <span>String value</span> }
374
+ ```
375
+
376
+ #### IsObjectPipe
377
+
378
+ ```typescript
379
+ // Object checking
380
+ @if (value | isObject) { <span>Object value</span> }
381
+ ```
382
+
383
+ #### IsObservablePipe
384
+
385
+ ```typescript
386
+ // Observable checking
387
+ @if (stream | isObservable) { <span>Observable stream</span> }
388
+ ```
389
+
390
+ #### UcfirstPipe
391
+
392
+ ```typescript
393
+ // Capitalize first letter
394
+ {{ 'hello world' | ucfirst }} <!-- Hello world -->
395
+ ```
396
+
397
+ #### UnwrapAsyncPipe
398
+
399
+ ```typescript
400
+ // Unwrap Observable or return direct value
401
+ {
402
+ {
403
+ observableOrValue | unwrapAsync;
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### Services
409
+
410
+ #### OverlayService
411
+
412
+ ```typescript
413
+ @Injectable({ providedIn: 'root' })
414
+ class OverlayService {
415
+ create(config?: OverlayConfig): OverlayRef;
416
+ position(): OverlayPosition;
417
+ }
418
+
419
+ class OverlayRef {
420
+ attach<T>(component: ComponentType<T>): ComponentRef<T>;
421
+ detach(): void;
422
+ dispose(): void;
423
+ updatePosition(): void;
424
+ }
425
+
426
+ class OverlayPosition {
427
+ flexibleConnectedTo(element: ElementRef | HTMLElement): this;
428
+ withPositions(positions: ConnectionPosition[]): this;
429
+ }
430
+ ```
431
+
432
+ #### ScrollBar Service
433
+
434
+ ```typescript
435
+ @Injectable({ providedIn: 'root' })
436
+ class ScrollBar {
437
+ hide(): ScrollbarReverter; // Hides scrollbar with compensation
438
+ }
439
+ ```
440
+
441
+ #### PopupService<T> (Base Class)
442
+
443
+ ```typescript
444
+ abstract class PopupService<T> {
445
+ // Base system for creating dynamic popups
446
+ // Extend this class to create specific popup services
447
+ open(content?, templateContext?, animation?): { windowRef: ComponentRef<T>; transition$: Observable<void> };
448
+ close(animation?): Observable<void>;
449
+ }
450
+ ```
451
+
452
+ ### Transition Utilities
453
+
454
+ - `hubRunTransition<T>(zone, element, startFn, options)` - Advanced transition system with Observable
455
+ - `hubCompleteTransition(element)` - Completes a running transition on an element
456
+ - `getTransitionDurationMs(element)` - Gets CSS transition duration in milliseconds
457
+ - `runInZone<T>(zone)` - RxJS operator to execute observables inside NgZone
458
+
459
+ ## 🎨 Support Components
460
+
461
+ This library doesn't include visual components, but support utilities used by other components in the Hub UI ecosystem:
462
+
463
+ | Utility | Description | Used by |
464
+ | --------------- | ----------------------------------- | -------------------------------------- |
465
+ | Overlay Service | Flexible overlay positioning system | ng-hub-ui-dropdown, ng-hub-ui-modal |
466
+ | Focus Trap | Focus management in modals/overlays | ng-hub-ui-modal, ng-hub-ui-dropdown |
467
+ | Scrollbar | Scrollbar compensation | ng-hub-ui-modal, ng-hub-ui-portal |
468
+ | Popup Service | Base class for popup components | ng-hub-ui-modal, ng-hub-ui-portal |
469
+ | Transitions | Smooth animations | ng-hub-ui-accordion, ng-hub-ui-modal |
470
+ | Type Guards | Type validation functions | ng-hub-ui-stepper |
471
+ | Pipes | Template utilities | All Hub UI components |
472
+
473
+ ## 🀝 Compatibility
474
+
475
+ - Angular 15+
476
+ - TypeScript 4.8+
477
+ - Node.js 16+
478
+ - Browsers: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
479
+
480
+ ## πŸ› οΈ Development
481
+
482
+ ```bash
483
+ git clone https://github.com/carlos-morcillo/ng-hub-ui-utils
484
+ cd ng-hub-ui-utils
485
+ npm install
486
+ npm run build
487
+ npm run test
488
+ ```
489
+
490
+ ### Available Scripts
491
+
492
+ ```bash
493
+ npm run build:lib # Build library
494
+ npm run test:unit # Unit tests
495
+ npm run test:e2e # End-to-end tests
496
+ npm run lint # Linting
497
+ npm run format # Format code
498
+ ```
499
+
500
+ ## πŸ§ͺ Testing
501
+
502
+ ```typescript
503
+ import { TestBed } from '@angular/core/testing';
504
+ import { ScrollBar, toInteger, isString, GetPipe } from 'ng-hub-ui-utils';
505
+
506
+ describe('ng-hub-ui-utils', () => {
507
+ it('should convert values safely', () => {
508
+ expect(toInteger('42')).toBe(42);
509
+ expect(toInteger('invalid')).toBe(NaN);
510
+ expect(isString('hello')).toBe(true);
511
+ expect(isString(42)).toBe(false);
512
+ });
513
+
514
+ it('should manage scrollbar', () => {
515
+ const scrollBar = TestBed.inject(ScrollBar);
516
+ const reverter = scrollBar.hide();
517
+
518
+ expect(typeof reverter).toBe('function');
519
+ reverter(); // Cleanup
520
+ });
521
+
522
+ it('should get nested properties safely', () => {
523
+ const pipe = new GetPipe();
524
+ const obj = { user: { name: 'John' } };
525
+
526
+ expect(pipe.transform(obj, 'user.name')).toBe('John');
527
+ expect(pipe.transform(obj, 'user.age', 0)).toBe(0);
528
+ });
529
+ });
530
+ ```
531
+
532
+ ## πŸ› Issues and Support
533
+
534
+ - [Report a bug](https://github.com/carlos-morcillo/ng-hub-ui-utils/issues)
535
+ - [Request a feature](https://github.com/carlos-morcillo/ng-hub-ui-utils/issues/new?template=feature_request.md)
536
+ - [Discussions](https://github.com/carlos-morcillo/ng-hub-ui-utils/discussions)
537
+
538
+ ## β˜• Support the Project
539
+
540
+ If Hub UI has been useful to you, consider supporting its development:
541
+
542
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow.svg?style=flat-square&logo=buy-me-a-coffee)](https://buymeacoffee.com/carlosmorcillo)
543
+ [![Sponsor](https://img.shields.io/badge/Sponsor-GitHub-red.svg?style=flat-square&logo=github)](https://github.com/sponsors/carlos-morcillo)
544
+
545
+ Your support helps to:
546
+
547
+ - πŸš€ Keep the project active
548
+ - πŸ› Fix bugs faster
549
+ - ✨ Develop new features
550
+ - πŸ“š Improve documentation
551
+
552
+ ## 🀝 Contributions
553
+
554
+ Contributions are welcome! Please:
555
+
556
+ 1. 🍴 Fork the repository
557
+ 2. 🌿 Create a branch for your feature (`git checkout -b feature/new-utility`)
558
+ 3. ✍️ Commit your changes (`git commit -am 'feat: add new utility'`)
559
+ 4. πŸ“€ Push to the branch (`git push origin feature/new-utility`)
560
+ 5. πŸ”„ Open a Pull Request
561
+
562
+ Check our [contribution guidelines](CONTRIBUTING.md) for more details.
563
+
564
+ ## πŸ“„ License
565
+
566
+ MIT Β© [Hub UI Team](https://github.com/carlos-morcillo)
567
+
568
+ ```
569
+ MIT License
570
+
571
+ Copyright (c) 2025 Hub UI Team
572
+
573
+ Permission is hereby granted, free of charge, to any person obtaining a copy
574
+ of this software and associated documentation files (the "Software"), to deal
575
+ in the Software without restriction, including without limitation the rights
576
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
577
+ copies of the Software, and to permit persons to whom the Software is
578
+ furnished to do so, subject to the following conditions:
579
+
580
+ The above copyright notice and this permission notice shall be included in all
581
+ copies or substantial portions of the Software.
582
+
583
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
584
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
585
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
586
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
587
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
588
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
589
+ SOFTWARE.
590
+ ```
591
+
592
+ ---
593
+
594
+ ⭐ **If you like this project, don't forget to give it a star on GitHub!**
595
+
596
+ [![GitHub stars](https://img.shields.io/github/stars/carlos-morcillo/ng-hub-ui-utils.svg?style=social&label=Star)](https://github.com/carlos-morcillo/ng-hub-ui-utils)