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 +585 -13
- package/fesm2022/ng-hub-ui-utils.mjs +787 -16
- package/fesm2022/ng-hub-ui-utils.mjs.map +1 -1
- package/index.d.ts +523 -3
- package/ng-hub-ui-utils-1.2.0.tgz +0 -0
- package/package.json +1 -3
- package/src/lib/styles/overlay.scss +25 -0
- package/esm2022/lib/focus-trap.mjs +0 -58
- package/esm2022/lib/popup.mjs +0 -66
- package/esm2022/lib/scrollbar.mjs +0 -46
- package/esm2022/lib/transitions/index.mjs +0 -2
- package/esm2022/lib/transitions/transition.mjs +0 -77
- package/esm2022/lib/transitions/util.mjs +0 -7
- package/esm2022/lib/util.mjs +0 -132
- package/esm2022/ng-hub-ui-utils.mjs +0 -5
- package/esm2022/public-api.mjs +0 -9
- package/lib/focus-trap.d.ts +0 -20
- package/lib/popup.d.ts +0 -25
- package/lib/scrollbar.d.ts +0 -23
- package/lib/transitions/index.d.ts +0 -1
- package/lib/transitions/transition.d.ts +0 -16
- package/lib/transitions/util.d.ts +0 -1
- package/lib/util.d.ts +0 -67
- package/ng-hub-ui-utils-1.0.0.tgz +0 -0
- package/public-api.d.ts +0 -5
package/README.md
CHANGED
|
@@ -1,24 +1,596 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hub UI - Angular Utilities Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ng-hub-ui-utils)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/carlos-morcillo/ng-hub-ui-utils/actions)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> Common utilities library for Angular, fundamental support for the Hub UI ecosystem.
|
|
6
8
|
|
|
7
|
-
|
|
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
|
-
##
|
|
11
|
+
## π‘ Part of the Hub UI Family
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
This library is part of the **Hub UI** ecosystem, which includes:
|
|
13
14
|
|
|
14
|
-
|
|
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
|
-
|
|
28
|
+
## π‘ Inspiration
|
|
17
29
|
|
|
18
|
-
|
|
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
|
-
|
|
32
|
+
## β¨ Features
|
|
21
33
|
|
|
22
|
-
|
|
34
|
+
### π§ Focus Management and Accessibility
|
|
23
35
|
|
|
24
|
-
|
|
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
|
+
[](https://buymeacoffee.com/carlosmorcillo)
|
|
543
|
+
[](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
|
+
[](https://github.com/carlos-morcillo/ng-hub-ui-utils)
|