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